Jamie Smith / CC1200

Dependents:   CC1200-MorseEncoder CC1200-Examples

CC1200.cpp

Committer:
Jamie Smith
Date:
2020-08-10
Revision:
3:f464b14ce62f
Parent:
2:2a447e8e50b8
Child:
4:c609cc7c9ea7

File content as of revision 3:f464b14ce62f:

#pragma clang diagnostic push
#pragma ide diagnostic ignored "readability-magic-numbers"
//
// Created by jamie on 3/27/2020.
//

#include "CC1200.h"
#include "CC1200Bits.h"

#include <cinttypes>
#include <cmath>
#include <array>

// change to 1 to print debug info
#define CC1200_DEBUG 0

// change to 1 to print register read/write level debug info
#define CC1200_REGISTER_LEVEL_DEBUG 0

// miscellaneous constants
#define CC1200_READ (1 << 7) // SPI initial byte flag indicating read
#define CC1200_WRITE 0 // SPI initial byte flag indicating write
#define CC1200_BURST (1 << 6) // SPI initial byte flag indicating burst access

// SPI commands to access data buffers.  Can be used with CC1200_BURST.
#define CC1200_ENQUEUE_TX_FIFO 0x3F
#define CC1200_DEQUEUE_RX_FIFO 0xBF

// SPI command to access FIFO memory (or several other areas depending on mode)
#define CC1200_MEM_ACCESS 0x3E

#define CC1200_RX_FIFO (1 << 7) // address flag to access RX FIFO
#define CC1200_TX_FIFO 0 // address flag to access TX FIFO


#define CC1200_PART_NUMBER ((uint8_t)0x20) // part number we expect the chip to read
#define CC1201_PART_NUMBER ((uint8_t)0x21)
#define CC1200_EXT_ADDR 0x2F // SPI initial byte address indicating extended register space

#define SPI_MODE 0
#define SPI_FREQ 5000000 // hz
// NOTE: the chip supports a higher frequency for most operations but reads to extended registers require a lower frequency

// frequency of the chip's crystal oscillator
#define CC1200_OSC_FREQ 40000000 // hz
#define CC1200_OSC_FREQ_LOG2 25.253496f // log2 of above number

// length of the TX and RX FIFOS
#define CC1200_FIFO_SIZE 128

// maximum length of the packets we can send, including the length byte which we add.
// Since the TX and RX FIFOs are 128 bytes, supporting packet lengths longer than 128 bytes
// requires streaming bytes in during the transmission, which would make things complicated.
#define MAX_PACKET_LENGTH 128

// utility function: compile-time power calculator.
// Works on all signed and unsigned integer types for T.
// from: http://prosepoetrycode.potterpcs.net/2015/07/a-simple-constexpr-power-function-c/
template <typename T>
constexpr T constexpr_pow(T num, unsigned int pow)
{
	return pow == 0 ? 1 : num * constexpr_pow(num, pow-1);
}

// power of two constants
const float twoToThe16 = constexpr_pow(2.0f, 16);
const float twoToThe20 = constexpr_pow(2.0f, 20);
const float twoToThe21 = constexpr_pow(2.0f, 21);
const float twoToThe22 = constexpr_pow(2.0f, 22);
const float twoToThe38 = constexpr_pow(2.0f, 38);
const float twoToThe39 = constexpr_pow(2.0f, 39);

// binary value size constants
const size_t maxValue3Bits = constexpr_pow(2, 3) - 1;
const size_t maxValue4Bits = constexpr_pow(2, 4) - 1;
const size_t maxValue8Bits = constexpr_pow(2, 8) - 1;
const size_t maxValue20Bits = constexpr_pow(2, 20) - 1;
const size_t maxValue24Bits = constexpr_pow(2, 24) - 1;

CC1200::CC1200(PinName mosiPin, PinName misoPin, PinName sclkPin, PinName csPin, PinName rstPin, Stream * _debugStream, bool _isCC1201):
spi(mosiPin, misoPin, sclkPin, csPin, use_gpio_ssel),
rst(rstPin, 1),
debugStream(_debugStream),
isCC1201(_isCC1201)
{
	spi.format(8, SPI_MODE);
	spi.frequency(SPI_FREQ);
}

bool CC1200::begin()
{
	chipReady = false;

	// reset
	rst.write(0);
	wait_us(100);
	rst.write(1);

	const auto resetTimeout = 10ms;
	Timer timeoutTimer;
	timeoutTimer.start();

	while(!chipReady)
	{
		// datasheet specifies 240us reset time
		wait_us(250);
		updateState();

		if(timeoutTimer.elapsed_time() > resetTimeout)
		{
			debugStream->printf("Timeout waiting for ready response from CC1200\n");
			return false;
		}
	}

	// read ID register
	uint8_t partNumber = readRegister(ExtRegister::PARTNUMBER);
	uint8_t partVersion = readRegister(ExtRegister::PARTVERSION);

	uint8_t expectedPartNumber = isCC1201 ? CC1201_PART_NUMBER : CC1200_PART_NUMBER;
	if(partNumber != expectedPartNumber)
	{
		debugStream->printf("Read incorrect part number 0x%" PRIx8 " from CC1200, expected 0x%" PRIx8 "\n", partNumber, expectedPartNumber);
		return false;
	}

#if CC1200_DEBUG
	debugStream->printf("Detected CC1200, Part Number 0x%" PRIx8 ", Hardware Version %" PRIx8 "\n", partNumber, partVersion);
#endif


	// Set packet format settings for this driver
	// ------------------------------------------------------------------------

	// enable CRC but disable status bytes
	writeRegister(Register::PKT_CFG1, (0b01 << PKT_CFG1_CRC_CFG));

	// configure packet length to adjustable
	writeRegister(Register::PKT_CFG0, (0b01 << PKT_CFG0_LENGTH_CONFIG));

	// set max packet length
	writeRegister(Register::PKT_LEN, MAX_PACKET_LENGTH);


	return true;
}

size_t CC1200::getTXFIFOLen()
{
	return readRegister(ExtRegister::NUM_TXBYTES);
}

size_t CC1200::getRXFIFOLen()
{
	return readRegister(ExtRegister::NUM_RXBYTES);
}

bool CC1200::enqueuePacket(char const * data, size_t len)
{
	uint8_t totalLength = len + 1; // add one byte for length byte

	if(totalLength > MAX_PACKET_LENGTH)
	{
		// packet too big
		return false;
	}

	uint8_t txFreeBytes = CC1200_FIFO_SIZE - getTXFIFOLen();
	if(totalLength > txFreeBytes)
	{
		// packet doesn't fit in TX FIFO
		return false;
	}

	// burst write to TX FIFO
	spi.select();
	loadStatusByte(spi.write(CC1200_ENQUEUE_TX_FIFO | CC1200_BURST));
	spi.write(len);
	for(size_t byteIndex = 0; byteIndex < len; ++byteIndex)
	{
		spi.write(data[byteIndex]);
	}
	spi.deselect();

#if CC1200_REGISTER_LEVEL_DEBUG
	debugStream->printf("Wrote packet of data length %zu: %" PRIx8, len, static_cast<uint8_t>(len));
	for(size_t byteIndex = 0; byteIndex < len; ++byteIndex)
	{
		debugStream->printf(" %" PRIx8, data[byteIndex]);
	}
	debugStream->printf("\n");
#endif
	return true;
}

bool CC1200::hasReceivedPacket()
{
	size_t bytesReceived = getRXFIFOLen();

	if(bytesReceived < 1)
	{
		// no bytes at all, can't check the length
		return false;
	}

	// get value of first byte of the packet, which is the length.

	// The datasheet is wrong about this!  It says that the first byte in the RX FIFO can
	// be found by accessing address RXFIRST via direct fifo access.
	// However, in my own testing, RXFIRST points to the second entry in the FIFO, and
	// the first entry must be accessed through RXFIFO_PRE_BUF.
	uint8_t packetLen = readRegister(ExtRegister::RXFIFO_PRE_BUF);

	// if we have received a full packet's worth of bytes, then we have received a full packet.
	return bytesReceived >= static_cast<size_t>(packetLen + 1); // Add one because length field does not include itself
}

size_t CC1200::receivePacket(char *buffer, size_t bufferLen)
{
	// burst read from RX FIFO
	spi.select();
	loadStatusByte(spi.write(CC1200_DEQUEUE_RX_FIFO | CC1200_BURST));

	// first read length byte
	uint8_t dataLen = spi.write(0);

	for(size_t byteIndex = 0; byteIndex < dataLen; ++byteIndex)
	{
		uint8_t currByte = spi.write(0);
		if(byteIndex < bufferLen)
		{
			buffer[byteIndex] = currByte;
		}
	}
	spi.deselect();

#if CC1200_REGISTER_LEVEL_DEBUG
	debugStream->printf("Read packet of data length %" PRIu8 ": %" PRIx8, dataLen, static_cast<uint8_t>(dataLen));
	for(size_t byteIndex = 0; byteIndex < dataLen; ++byteIndex)
	{
		debugStream->printf(" %" PRIx8, buffer[byteIndex]);
	}
	debugStream->printf("\n");
#endif

	return dataLen;
}

// helper function: convert a state to the bits for RXOFF_MODE and TXOFF_MODE
inline uint8_t getOffModeBits(CC1200::State state)
{
	uint8_t offBits = 0b0;
	if(state == CC1200::State::IDLE)
	{
		offBits = 0b0;
	}
	else if(state == CC1200::State::FAST_ON)
	{
		offBits = 0b1;
	}
	else if(state == CC1200::State::TX)
	{
		offBits = 0b10;
	}
	else if(state == CC1200::State::RX)
	{
		offBits = 0b11;
	}
	return offBits;
}

void CC1200::setOnReceiveState(CC1200::State goodPacket, CC1200::State badPacket)
{
	// configure good packet action via RXOFF_MODE
	uint8_t rfendCfg1 = readRegister(Register::RFEND_CFG1);
	rfendCfg1 &= ~(0b11 << RFEND_CFG1_RXOFF_MODE);
	rfendCfg1 |= getOffModeBits(goodPacket) << RFEND_CFG1_RXOFF_MODE;
	writeRegister(Register::RFEND_CFG1, rfendCfg1);

	// configure bad packet action via TERM_ON_BAD_PACKET_EN
	uint8_t rfendCfg0 = readRegister(Register::RFEND_CFG0);
	if(badPacket == State::RX)
	{
		rfendCfg0 &= ~(1 << RFEND_CFG0_TERM_ON_BAD_PACKET_EN);
	}
	else
	{
		rfendCfg0 |= 1 << RFEND_CFG1_RXOFF_MODE;
	}
	writeRegister(Register::RFEND_CFG0, rfendCfg0);
}

void CC1200::setOnTransmitState(CC1200::State txState)
{
	uint8_t rfendCfg0 = readRegister(Register::RFEND_CFG0);
	rfendCfg0 &= ~(0b11 << RFEND_CFG0_TXOFF_MODE);
	rfendCfg0 |= getOffModeBits(txState) << RFEND_CFG0_TXOFF_MODE;
	writeRegister(Register::RFEND_CFG0, rfendCfg0);
}

void CC1200::setFSCalMode(FSCalMode mode)
{
	uint8_t settlingCfg = readRegister(Register::SETTLING_CFG);
	settlingCfg &= ~(0b11 << SETTLING_CFG_FS_AUTOCAL);
	settlingCfg |= static_cast<uint8_t>(mode) << SETTLING_CFG_FS_AUTOCAL;
	writeRegister(Register::SETTLING_CFG, settlingCfg);
}

void CC1200::configureGPIO(uint8_t gpioNumber, CC1200::GPIOMode mode, bool outputInvert)
{
	// gpio 3 is the first register, then it goes down to 0
	Register gpioReg = static_cast<Register>(static_cast<uint8_t>(Register::IOCFG3) + (3 - gpioNumber));

	uint8_t gpioCfgVal = static_cast<uint8_t>(mode);
	if(outputInvert)
	{
		gpioCfgVal |= (1 << GPIO_INV);
	}
	writeRegister(gpioReg, gpioCfgVal);
}

void CC1200::configureFIFOMode()
{
	// configure packet format
	uint8_t pktCfg2 = readRegister(Register::PKT_CFG2);
	pktCfg2 &= ~(0b11 << PKT_CFG2_PKT_FORMAT);
	writeRegister(Register::PKT_CFG2, pktCfg2);

	// enable fifo
	uint8_t mdmCfg1 = readRegister(Register::MDMCFG1);
	mdmCfg1 |= 1 << MDMCFG1_FIFO_EN;
	writeRegister(Register::MDMCFG1, mdmCfg1);

	// make sure transparent mode is disabled
	uint8_t mdmCfg0 = readRegister(Register::MDMCFG0);
	mdmCfg0 &= ~(1 << MDMCFG0_TRANSPARENT_MODE_EN);
	writeRegister(Register::MDMCFG0, mdmCfg0);
}

void CC1200::setModulationFormat(CC1200::ModFormat format)
{
	uint8_t modcfgDevE = readRegister(Register::MODCFG_DEV_E);
	modcfgDevE &= ~(0b111 << MODCFG_DEV_E_MOD_FORMAT);
	modcfgDevE |= static_cast<uint8_t>(format) << MODCFG_DEV_E_DEV_E;
	writeRegister(Register::MODCFG_DEV_E, modcfgDevE);
}

void CC1200::setFSKDeviation(float deviation)
{
	// Deviation is set as two values, an exponent register from 0-3 and a mantissa register from 0-256.
	// See user guide page 81 for the original equation, this function was worked out from that

	// First assume mantissa is zero and calculate the needed exponent
	float exactExponent = std::log2(deviation) - CC1200_OSC_FREQ_LOG2 + 14;
	uint8_t actualExponent = static_cast<uint8_t>(std::min(static_cast<float>(maxValue3Bits), exactExponent));
	// note: 14 comes from log2(2^22) - log2(256)

	float exactMantissa;
	if(actualExponent >= 1)
	{
		exactMantissa = (std::pow(2.0f, static_cast<float>(22 - actualExponent)) * deviation)
						/ CC1200_OSC_FREQ - 256;
	}
	else
	{
		// use alternate high-resolution formula for case where exponent = 0
		exactMantissa = deviation * twoToThe21 / CC1200_OSC_FREQ;
	}

	// now calculate closest mantissa
	uint8_t actualMantissa = static_cast<uint8_t>(std::min(static_cast<float>(maxValue8Bits), exactMantissa));

	// set exponent and mantissa
	writeRegister(Register::DEVIATION_M, actualMantissa);

	uint8_t modcfgDevE = readRegister(Register::MODCFG_DEV_E);
	modcfgDevE &= ~(0b111 << MODCFG_DEV_E_DEV_E);
	modcfgDevE |= actualExponent << MODCFG_DEV_E_DEV_E;
	writeRegister(Register::MODCFG_DEV_E, modcfgDevE);

#if CC1200_DEBUG
	debugStream->printf("Setting FSK deviation, requested +-%.00f Hz, setting DEV_E = 0x%" PRIx8 " DEV_M = 0x%" PRIx8 "\n",
			deviation, actualExponent, actualMantissa);

	float actualDeviation;
	if(actualExponent == 0)
	{
		actualDeviation = CC1200_OSC_FREQ * actualMantissa / twoToThe21;
	}
	else
	{
		actualDeviation = (CC1200_OSC_FREQ / twoToThe22) * (256.0f + static_cast<float>(actualMantissa)) * pow(2.0f, static_cast<float>(actualExponent));
	}
	// sanity check: calculate actual deviation
	debugStream->printf("This yields an actual deviation of +-%.00f Hz\n", actualDeviation);
#endif
}

void CC1200::setSymbolRate(float symbolRateHz)
{
	// Datasheet says that the cc1200 works in ksps, but testing with SmartRF studio
	// shows that it actually is sps.

	symbolRateSps = symbolRateHz;

	// Note: these equations are given on page 29 of the user guide

	float exactExponent = std::log2(symbolRateSps) - CC1200_OSC_FREQ_LOG2 + 19;
	// note: 19 comes from log2(2^39) - 20
	uint8_t actualExponent = static_cast<uint8_t>(std::min(static_cast<float>(maxValue4Bits), exactExponent));
	// note: 15 comes from the largest number storable in 4 bits

	float exactMantissa;
	if(actualExponent >= 1)
	{
		exactMantissa = (std::pow(2.0f, static_cast<float>(39 - actualExponent)) * symbolRateSps)
		  / CC1200_OSC_FREQ - twoToThe20;
	}
	else
	{
		// use alternate high-resolution formula for case where exponent = 0
		exactMantissa = symbolRateSps * twoToThe38 / CC1200_OSC_FREQ;
	}

	// mantissa is a 20 bit number, so restrict it to that domain
	uint32_t actualMantissa = static_cast<uint32_t>(std::min(static_cast<float>(maxValue20Bits), exactMantissa));

	// program symbol rate registers
	std::array<uint8_t, 3> symbolRateRegisters ={
			static_cast<uint8_t>((actualExponent << SYMBOL_RATE2_SRATE_E) | (static_cast<uint8_t>((actualMantissa >> 16) & 0xFF) << SYMBOL_RATE2_SRATE_M_19_16)),
			static_cast<uint8_t>((actualMantissa >> 8) & 0xFF),
			static_cast<uint8_t>((actualMantissa & 0xFF))
	};
	writeRegisters(Register::SYMBOL_RATE2, symbolRateRegisters);

	// Calculate upsampler value according to the forumula in its register description
	float upsamplingFactor = CC1200_OSC_FREQ / (64 * symbolRateSps);
	uint8_t upsamplerPVal = std::floor(std::log2(upsamplingFactor));

	uint8_t mdmcfg2 = readRegister(ExtRegister::MDMCFG2);
	mdmcfg2 &= ~(0b111 << MDMCFG2_UPSAMPLER_P);
	mdmcfg2 |= (upsamplerPVal << MDMCFG2_UPSAMPLER_P);

	writeRegister(ExtRegister::MDMCFG2, mdmcfg2);

#if CC1200_DEBUG
	debugStream->printf("Setting symbol rate, requested %.00f Hz, setting SRATE_E = 0x%" PRIx8 " SRATE_M = 0x%" PRIx32 "\n",
						symbolRateHz, actualExponent, actualMantissa);

	// sanity check: calculate actual symbol rate
	float actualSymbolRateKsps;
	if(actualExponent == 0)
	{
		actualSymbolRateKsps = (static_cast<float>(actualMantissa) * CC1200_OSC_FREQ) / twoToThe38;
	}
	else
	{

		actualSymbolRateKsps = ((static_cast<float>(actualMantissa) + twoToThe20) *
				pow(2.0f,static_cast<float>(actualExponent)) * CC1200_OSC_FREQ)
				/ twoToThe39;
	}

	debugStream->printf("This yields an actual symbol rate of %.00f Hz\n", actualSymbolRateKsps * 1000.0f);

	uint8_t actualUpsampling = static_cast<uint8_t>(pow(2.0f, static_cast<float>(upsamplerPVal)));
	debugStream->printf("Also setting upsampling factor to %" PRIu8 " via UPSAMPLER_P = %" PRIx8 "\n", actualUpsampling, upsamplerPVal);
#endif
}

void CC1200::setOutputPower(float outPower)
{
	const float minOutputPower = -16.0f;

	// note: datasheet says this is 14.5, but that must be a mistake: 14.5 would produce a number
	// too large to fit into 6 bits.
	const float maxOutputPower = 14.0f;

	// clamp output power into correct range
	outPower = std::min(outPower, maxOutputPower);
	outPower = std::max(outPower, minOutputPower);

	// this equation derived from user guide section 7.1
	float exactPowerRamp = (2 * outPower) + 35;
	uint8_t actualPowerRamp = static_cast<uint8_t>(exactPowerRamp); // round to nearest

	uint8_t paCfg1 = readRegister(Register::PA_CFG1);
	paCfg1 &= ~(0b111111 << PA_CFG1_PA_POWER_RAMP);
	paCfg1 |= actualPowerRamp << PA_CFG1_PA_POWER_RAMP;
	writeRegister(Register::PA_CFG1, paCfg1);
}

void CC1200::setRadioFrequency(CC1200::Band band, float frequencyHz)
{
	// Frequency synthesizer configuration.  This is completely opaque and it is unknown what these bits do --
	// they are only generated by SmartRF studio.
	// I manually deduplicated them since only a few change based on frequency.
	writeRegister(ExtRegister::IF_ADC1,0xEE);
	writeRegister(ExtRegister::IF_ADC0,0x10);
	writeRegister(ExtRegister::FS_DIG1,0x04);
	writeRegister(ExtRegister::FS_CAL1,0x40);
	writeRegister(ExtRegister::FS_CAL0,0x0E);
	writeRegister(ExtRegister::FS_DIVTWO,0x03);
	writeRegister(ExtRegister::FS_DSM0,0x33);
	writeRegister(ExtRegister::FS_DVC1,0xF7);
	writeRegister(ExtRegister::FS_PFD,0x00);
	writeRegister(ExtRegister::FS_PRE,0x6E);
	writeRegister(ExtRegister::FS_REG_DIV_CML,0x1C);
	writeRegister(ExtRegister::FS_SPARE,0xAC);
	writeRegister(ExtRegister::FS_VCO0,0xB5);
	writeRegister(ExtRegister::XOSC5,0x0E);
	writeRegister(ExtRegister::XOSC1,0x03);
	if(band == Band::BAND_820_960MHz)
	{
		writeRegister(ExtRegister::FS_DIG0,0x55);
		writeRegister(ExtRegister::FS_DVC0,0x17);
		writeRegister(ExtRegister::IFAMP,0x09);
	}
	else if(band == Band::BAND_410_480MHz || band == Band::BAND_164_192MHz)
	{
		writeRegister(ExtRegister::FS_DIG0,0x50);
		writeRegister(ExtRegister::FS_DVC0,0x0F);
		writeRegister(ExtRegister::IFAMP,0x0D);
	}
	else
	{
		// TI doesn't make settings public for the other radio bands.
		// Let's take a guess and use the 480-164MHz values.
		writeRegister(ExtRegister::FS_DIG0,0x50);
		writeRegister(ExtRegister::FS_DVC0,0x0F);
		writeRegister(ExtRegister::IFAMP,0x0D);
	}

	// convert band to LO Divider value.
	// Most of the bands just multiply the register value by 2, but nooo, not BAND_136_160MHz.
	uint8_t loDividerValue;
	if(band == Band::BAND_136_160MHz)
	{
		loDividerValue = 24;
	}
	else
	{
		loDividerValue = static_cast<uint8_t>(band) * 2;
	}

	// program band (also enable FS out of lock detector, which is useful for testing)
	writeRegister(Register::FS_CFG, (1 << FS_CFG_FS_LOCK_EN) | (static_cast<uint8_t>(band) << FS_CFG_FSD_BANDSELECT));

	// equation derived from user guide section 9.12
	float exactFreqValue = (twoToThe16 * frequencyHz * static_cast<float>(loDividerValue)) / CC1200_OSC_FREQ;
	uint32_t actualFreqValue = static_cast<uint32_t>(std::min(static_cast<float>(maxValue24Bits), exactFreqValue));

	// program frequency registers
	std::array<uint8_t, 3> freqRegisters ={
			static_cast<uint8_t>((actualFreqValue >> 16) & 0xFF),
			static_cast<uint8_t>((actualFreqValue >> 8) & 0xFF),
			static_cast<uint8_t>((actualFreqValue & 0xFF))
	};
	writeRegisters(ExtRegister::FREQ2, freqRegisters);

#if CC1200_DEBUG
	debugStream->printf("Setting radio frequency, requested %.00f Hz, setting FREQ = 0x%" PRIx32 "\n",
						frequencyHz, actualFreqValue);

	// sanity check: calculate actual frequency
	float actualFrequency = (static_cast<float>(actualFreqValue) * CC1200_OSC_FREQ) / (twoToThe16 * static_cast<float>(loDividerValue));

	debugStream->printf("This yields an actual frequency of %.00f Hz\n", actualFrequency);
#endif
}

// helper function for setRXFilterBandwidth:
// calculate actual receive bandwidth from the given decimations.
float calcReceiveBandwidth(uint8_t adcDecimation, uint8_t cicDecimation)
{
	return CC1200_OSC_FREQ / (static_cast<float>(adcDecimation) * static_cast<float>(cicDecimation) * 2);
}

void CC1200::setRXFilterBandwidth(float bandwidthHz)
{
	// settings that the chip supports
	const uint8_t possibleADCDecimations[] = {12, 24, 48}; // indexes in this array represent the register value
	const size_t numADCDecimations = sizeof(possibleADCDecimations) / sizeof(uint8_t);
	const uint8_t minBBDecimation = 1;

	// maximum supported BB decimation based on ADC decimation varies based on chip
	const uint8_t maxBBDecimations1201[] = {33, 16, 8};
	const uint8_t maxBBDecimations1200[] = {44, 44, 44};
	uint8_t const * maxBBDecimations = isCC1201 ? maxBBDecimations1201 : maxBBDecimations1200;

	// the datasheet suggests to use the highest possible ADC decimation factor that will work for the requested frequency.
	// So, we compute the closest we can get with each ADC decimation, and if there's a tie, we choose the one with the higher ADC bandwidth

	uint8_t actualBBDecimations[numADCDecimations];

	for(size_t adcDecimationIndex = 0; adcDecimationIndex < numADCDecimations; ++adcDecimationIndex)
	{
		uint8_t adcDecimation = possibleADCDecimations[adcDecimationIndex];

		// calculate BB decimation closest to the requested frequency
		// derived from formula in section 6.1
		float exactBBDecimation = CC1200_OSC_FREQ / (2 * bandwidthHz * static_cast<float>(adcDecimation));
		exactBBDecimation = std::max(exactBBDecimation, static_cast<float>(minBBDecimation));
		exactBBDecimation = std::min(exactBBDecimation, static_cast<float>(maxBBDecimations[adcDecimationIndex]));

		actualBBDecimations[adcDecimationIndex] = static_cast<uint8_t>(exactBBDecimation);
	}

	// now, choose the best of the ones we calculated
	uint8_t bestDecimationIndex = 0;
	float bestDecimationError = std::abs(bandwidthHz - calcReceiveBandwidth(possibleADCDecimations[0], actualBBDecimations[0]));

	for(size_t adcDecimationIndex = 1; adcDecimationIndex < numADCDecimations; ++adcDecimationIndex)
	{
		float thisDecimationError = std::abs(bandwidthHz -
				calcReceiveBandwidth(possibleADCDecimations[adcDecimationIndex], actualBBDecimations[adcDecimationIndex]));
		if(thisDecimationError <= bestDecimationError)
		{
			bestDecimationError = thisDecimationError;
			bestDecimationIndex = adcDecimationIndex;
		}
	}

	// now use the best value!
	uint8_t chanBwValue = (bestDecimationIndex << CHAN_BW_ADC_CIC_DECFACT) | (actualBBDecimations[bestDecimationIndex] << CHAN_BW_BB_CIC_DECFACT);
	writeRegister(Register::CHAN_BW, chanBwValue);

	// also set DVGA_GAIN, which depends on bandwidth
	uint8_t mdmCfg1Value = readRegister(Register::MDMCFG1);

	mdmCfg1Value &= ~(0b11 << MDMCFG1_DVGA_GAIN);
	if(bandwidthHz >= 100000)
	{
		mdmCfg1Value |= 1 << MDMCFG1_DVGA_GAIN;
	}

	writeRegister(Register::MDMCFG1, mdmCfg1Value);

	// also set MDMCFG0.DATA_FILTER_EN, which should be 0b11 iff bandwidth / symbol rate > 10
	uint8_t mdmCfg0Val = readRegister(Register::MDMCFG0);

	if(bandwidthHz / symbolRateSps > 10.0f)
	{
		mdmCfg0Val |= 0b11 << MDMCFG0_DATA_FILTER_EN;
	}
	else
	{
		mdmCfg0Val &= ~(0b11 << MDMCFG0_DATA_FILTER_EN);
	}

	writeRegister(Register::MDMCFG0, mdmCfg0Val);

	// finally, we need to set RX_CONFIG_LIMITATION.  It's not exactly clear what this does, but its setting changes
	// based on the filter BW.
	uint8_t syncCfg0Value = readRegister(Register::SYNC_CFG0);

	if(symbolRateSps < bandwidthHz / 2 || bandwidthHz > 1500000)
	{
		// clear RX_CONFIG_LIMITATION
		syncCfg0Value &= ~(1 << SYNC_CFG0_RX_CONFIG_LIMITATION);
	}
	else
	{
		// set RX_CONFIG_LIMITATION
		syncCfg0Value |= (1 << SYNC_CFG0_RX_CONFIG_LIMITATION);
	}

	writeRegister(Register::SYNC_CFG0, syncCfg0Value);

	adcCicDecimation = possibleADCDecimations[bestDecimationIndex];

#if CC1200_DEBUG
	debugStream->printf("Setting BB decimation to %" PRIu8 " and ADC decimation to %" PRIu8 "\n",
			actualBBDecimations[bestDecimationIndex], possibleADCDecimations[bestDecimationIndex]);
	debugStream->printf("This yields an actual RX filter BW of %.00f\n",
			calcReceiveBandwidth(possibleADCDecimations[bestDecimationIndex], actualBBDecimations[bestDecimationIndex]));
#endif
}

void CC1200::configureDCFilter(bool enableAutoFilter, uint8_t settlingCfg, uint8_t cutoffCfg)
{
	uint8_t dcfiltCfg = 0;

	if(!enableAutoFilter)
	{
		// set "freeze coeff" bit
		dcfiltCfg |= (1 << DCFILT_CFG_DCFILT_FREEZE_COEFF);
	}

	dcfiltCfg |= settlingCfg << DCFILT_CFG_DCFILT_BW_SETTLE;
	dcfiltCfg |= cutoffCfg << DCFILT_CFG_DCFILT_BW;

	writeRegister(Register::DCFILT_CFG, dcfiltCfg);
}

void CC1200::setIQMismatchCompensationEnabled(bool enabled)
{
	uint8_t iqicValue = readRegister(Register::IQIC);

	if(enabled)
	{
		iqicValue |= (1 << IQIC_IQIC_EN);
	}
	else
	{
		iqicValue &= ~(1 << IQIC_IQIC_EN);
	}

	writeRegister(Register::IQIC, iqicValue);
}

void CC1200::configureSyncWord(uint32_t syncWord, CC1200::SyncMode mode, uint8_t syncThreshold)
{
	// program sync word registers
	std::array<uint8_t, 4> syncWordRegisters ={
			static_cast<uint8_t>((syncWord >> 24) & 0xFF),
			static_cast<uint8_t>((syncWord >> 16) & 0xFF),
			static_cast<uint8_t>((syncWord >> 8) & 0xFF),
			static_cast<uint8_t>(syncWord & 0xFF)
	};
	writeRegisters(Register::SYNC3, syncWordRegisters);

	// program sync word cfg
	writeRegister(Register::SYNC_CFG1, (static_cast<uint8_t>(mode) << SYNC_CFG1_SYNC_MODE) | (syncThreshold << SYNC_CFG1_SYNC_THR));
}

bool CC1200::isFSLocked()
{
	return readRegister(ExtRegister::FSCAL_CTRL) & (1 << FSCAL_CTRL_LOCK);
}

void CC1200::configurePreamble(uint8_t preambleLengthCfg, uint8_t preambleFormatCfg)
{
	uint8_t preambleCfg1 = 0;
	preambleCfg1 |= preambleLengthCfg << PREAMBLE_CFG1_NUM_PREAMBLE;
	preambleCfg1 |= preambleFormatCfg << PREAMBLE_CFG1_PREAMBLE_WORD;
	writeRegister(Register::PREAMBLE_CFG1, preambleCfg1);
}

void CC1200::setPARampRate(uint8_t firstRampLevel, uint8_t secondRampLevel, CC1200::RampTime rampTime)
{
	uint8_t paCfg0Val = 0;
	paCfg0Val |= (firstRampLevel << PA_CFG0_FIRST_IPL);
	paCfg0Val |= (secondRampLevel << PA_CFG0_SECOND_IPL);
	paCfg0Val |= (static_cast<uint8_t>(rampTime) << PA_CFG0_RAMP_SHAPE);

	writeRegister(Register::PA_CFG0, paCfg0Val);
}

void CC1200::setAGCReferenceLevel(uint8_t level)
{
	writeRegister(Register::AGC_REF, level);
}

void CC1200::setAGCSyncBehavior(CC1200::SyncBehavior behavior)
{
	uint8_t agcCfg3Val = readRegister(Register::AGC_CFG3);
	agcCfg3Val &= ~(0b111 << AGC_CFG3_AGC_SYNC_BEHAVIOUR);
	agcCfg3Val |= static_cast<uint8_t>(behavior) << AGC_CFG3_AGC_SYNC_BEHAVIOUR;
	writeRegister(Register::AGC_CFG3, agcCfg3Val);
}

void CC1200::setAGCGainTable(CC1200::GainTable table, uint8_t minGainIndex, uint8_t maxGainIndex)
{
	uint8_t agcCfg3Val = readRegister(Register::AGC_CFG3);
	uint8_t agcCfg2Val = readRegister(Register::AGC_CFG2);

	agcCfg3Val &= ~(0b11111 << AGC_CFG3_AGC_MIN_GAIN);
	agcCfg2Val &= ~(0b11 << AGC_CFG2_FE_PERFORMANCE_MODE);
	agcCfg2Val &= ~(0b11111 << AGC_CFG2_AGC_MAX_GAIN);

	agcCfg3Val |= maxGainIndex << AGC_CFG3_AGC_MIN_GAIN;
	agcCfg2Val |= static_cast<uint8_t>(table) << AGC_CFG2_FE_PERFORMANCE_MODE;
	agcCfg2Val |= maxGainIndex << AGC_CFG2_AGC_MAX_GAIN;

	writeRegister(Register::AGC_CFG3, agcCfg3Val);
	writeRegister(Register::AGC_CFG2, agcCfg2Val);
}

void CC1200::setAGCHysteresis(uint8_t hysteresisCfg)
{
	uint8_t agcCfg0Val = readRegister(Register::AGC_CFG0);
	agcCfg0Val &= ~(0b11 << AGC_CFG0_AGC_HYST_LEVEL);
	agcCfg0Val |= hysteresisCfg << AGC_CFG0_AGC_HYST_LEVEL;
	writeRegister(Register::AGC_CFG0, agcCfg0Val);
}

void CC1200::setAGCSlewRate(uint8_t slewrateCfg)
{
	uint8_t agcCfg0Val = readRegister(Register::AGC_CFG0);
	agcCfg0Val &= ~(0b11 << AGC_CFG0_AGC_SLEWRATE_LIMIT);
	agcCfg0Val |= slewrateCfg << AGC_CFG0_AGC_SLEWRATE_LIMIT;
	writeRegister(Register::AGC_CFG0, agcCfg0Val);
}

void CC1200::setIFMixCFG(uint8_t value)
{
	uint8_t ifMixCfg = readRegister(ExtRegister::IF_MIX_CFG);
	ifMixCfg &= ~(0b111 << IF_MIX_CFG_CMIX_CFG);
	ifMixCfg |= (value << IF_MIX_CFG_CMIX_CFG);
	writeRegister(ExtRegister::IF_MIX_CFG, ifMixCfg);
}

uint8_t CC1200::readRegister(CC1200::Register reg)
{
	spi.select();
	loadStatusByte(spi.write(CC1200_READ | static_cast<uint8_t>(reg)));
	uint8_t regValue = spi.write(0);
	spi.deselect();

#if CC1200_REGISTER_LEVEL_DEBUG
	debugStream->printf("Read register 0x%" PRIx8 " -> 0x%" PRIx8 "\n", static_cast<uint8_t>(reg), regValue);
#endif

	return regValue;
}

void CC1200::writeRegister(Register reg, uint8_t value)
{
	spi.select();
	loadStatusByte(spi.write(CC1200_WRITE | static_cast<uint8_t>(reg)));
	spi.write(value);
	spi.deselect();

#if CC1200_REGISTER_LEVEL_DEBUG
	debugStream->printf("Wrote register 0x%" PRIx8 " <- 0x%" PRIx8 "\n", static_cast<uint8_t>(reg), value);
#endif
}

void CC1200::writeRegisters(CC1200::Register startReg, uint8_t const *values, size_t numRegisters)
{
	spi.select();
	loadStatusByte(spi.write(CC1200_WRITE | CC1200_BURST | static_cast<uint8_t>(startReg)));

	for(size_t byteIndex = 0; byteIndex < numRegisters; ++byteIndex)
	{
		spi.write(values[byteIndex]);
	}
	spi.deselect();

#if CC1200_REGISTER_LEVEL_DEBUG
	for(size_t byteIndex = 0; byteIndex < numRegisters; ++byteIndex)
	{
		debugStream->printf("Wrote register 0x%" PRIx8 " <- 0x%" PRIx8 "\n",
				static_cast<uint8_t>(static_cast<uint8_t>(startReg) + byteIndex),
				values[byteIndex]);
	}
#endif
}

void CC1200::loadStatusByte(uint8_t status)
{
	chipReady = !(status >> 7);
	state = static_cast<State>((status >> 4) & 0x7);

#if CC1200_REGISTER_LEVEL_DEBUG
	debugStream->printf("Updated status, state = 0x%" PRIx8 " ready = %s\n", static_cast<uint8_t>(state), chipReady ? "true" : "false");
#endif
}

uint8_t CC1200::readRegister(CC1200::ExtRegister reg)
{
	spi.select();
	loadStatusByte(spi.write(CC1200_READ | CC1200_EXT_ADDR));
	spi.write(static_cast<uint8_t>(reg));
	uint8_t regValue = spi.write(0);
	spi.deselect();

#if CC1200_REGISTER_LEVEL_DEBUG
	debugStream->printf("Read ext register 0x%" PRIx8 " -> 0x%" PRIx8 "\n", static_cast<uint8_t>(reg), regValue);
#endif

	return regValue;
}

void CC1200::writeRegister(CC1200::ExtRegister reg, uint8_t value)
{
	spi.select();
	loadStatusByte(spi.write(CC1200_WRITE | CC1200_EXT_ADDR));
	spi.write(static_cast<uint8_t>(reg));
	spi.write(value);
	spi.deselect();

#if CC1200_REGISTER_LEVEL_DEBUG
	debugStream->printf("Wrote ext register 0x%" PRIx8 " <- 0x%" PRIx8 "\n", static_cast<uint8_t>(reg), value);
#endif
}

void CC1200::writeRegisters(CC1200::ExtRegister startReg, uint8_t const *values, size_t numRegisters)
{
	spi.select();
	loadStatusByte(spi.write(CC1200_WRITE | CC1200_BURST | CC1200_EXT_ADDR));
	spi.write(static_cast<uint8_t>(startReg));

	for(size_t byteIndex = 0; byteIndex < numRegisters; ++byteIndex)
	{
		spi.write(values[byteIndex]);
	}
	spi.deselect();

#if CC1200_REGISTER_LEVEL_DEBUG
	for(size_t byteIndex = 0; byteIndex < numRegisters; ++byteIndex)
	{
		debugStream->printf("Wrote extended register 0x%" PRIx8 " <- 0x%" PRIx8 "\n",
							static_cast<uint8_t>(static_cast<uint8_t>(startReg) + byteIndex),
							values[byteIndex]);
	}
#endif
}

void CC1200::sendCommand(CC1200::Command command)
{
	spi.select();
	loadStatusByte(spi.write(static_cast<uint8_t>(command)));
	spi.deselect();

#if CC1200_REGISTER_LEVEL_DEBUG
	debugStream->printf("Sent SPI command 0x%" PRIx8 "\n", static_cast<uint8_t>(command));
#endif
}

uint8_t CC1200::readRXFIFOByte(uint8_t address)
{
	spi.select();
	loadStatusByte(spi.write(CC1200_READ | CC1200_MEM_ACCESS));
	spi.write(CC1200_RX_FIFO | address);
	int value = spi.write(0);
	spi.deselect();

#if CC1200_REGISTER_LEVEL_DEBUG
	debugStream->printf("Read RX FIFO[0x%" PRIx8 "]: 0x%x 0x%x -> 0x%" PRIx8 "\n", static_cast<uint8_t>(address), CC1200_READ | CC1200_MEM_ACCESS, CC1200_RX_FIFO | address, value);
#endif

	return value;
}


#pragma clang diagnostic pop