Driver for TI's CC1200 radio ICs. Forget hardcoded register settings -- this driver calculates everything from scratch!

Dependents:   CC1200-MorseEncoder CC1200-Examples

CC1200 Driver

by Jamie Smith / USC Rocket Propulsion Lab

After months of work, we are proud to present our driver for Texas Instruments' CC1200 digital radio IC! This driver has been written from scratch to be an easy and flexible way of using this radio transceiver. For our application, we needed to be able to tune each and every setting of the radio to try and eke that last bit of performance of our system - so using premade configurations alone wasn't going to cut it! Instead, this driver calculates each parameter of the radio using the equations and instructions given in the datasheet. So, you can tweak parameters to your heart's content, and you shouldn't have to do any math yourself!

Features

  • Automatic calculation of correct register values for:
    • RF frequency
    • FSK deviation
    • Symbol rate
    • Output power
    • RX filter bandwidth (this one's harder than it looks!)
  • Easy handling of data packets
  • GPIO configuration
  • Preamble and sync word configuration
  • RTOS compatible (always locks SPI bus during transactions)
  • Two debug levels available
  • RSSI and LQI support

Not Supported

  • Transparent mode
  • FM mode
  • ASK parameter configuration
  • Frequency offsets

Examples

  • See the example project here for an example of how to use the driver.
  • Another example (using a more exotic configuration) is the CC1200-MorseEncoder.

Changelog

Version 1.2 May 3 2021

  • Added unfinished infinite length packet support via the readStream() and writeStream() functions. The API is complete and basic usage works but there's still a bug I haven't been able to track down yet where incorrect data is transmitted at the end of a stream. Use with caution!
  • Added preferHigherCICDec parameter to setRXFilterBandwidth
  • Removed setIFMixCFG() (which takes a byte parameter) and replaced it with setIFCfg(), which takes documented enum class values.
  • Added setAGCSettleWait(), which per my testing is needed for correct 430MHz operation.
  • Added support for reading RSSI and LQI values, both from packet appended status bytes and from the registers.
  • Update 430MHz black box registers based on SmartRF values
  • Removed setIQMismatchCompensationEnabled(). This call has been replaced by the new 2nd parameter to setIFCfg().

Version 1.1 Aug 28 2020

  • Add fixed length packet support and other features needed for Morse support.
  • Fix bug causing weird behavior with low sample rates (<1ksps).

NOTE: you must now call setPacketMode() when configuring the radio.

Version 1.0 Aug 10 2020

Initial Release

Revision:
4:c609cc7c9ea7
Parent:
3:f464b14ce62f
Child:
5:d22a8885800b
--- a/CC1200.cpp	Mon Aug 10 01:47:24 2020 -0700
+++ b/CC1200.cpp	Fri Aug 28 15:39:31 2020 -0700
@@ -135,13 +135,6 @@
 	// 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;
 }
 
@@ -175,7 +168,10 @@
 	// burst write to TX FIFO
 	spi.select();
 	loadStatusByte(spi.write(CC1200_ENQUEUE_TX_FIFO | CC1200_BURST));
-	spi.write(len);
+	if(_packetMode == PacketMode::VARIABLE_LENGTH)
+	{
+		spi.write(len);
+	}
 	for(size_t byteIndex = 0; byteIndex < len; ++byteIndex)
 	{
 		spi.write(data[byteIndex]);
@@ -183,10 +179,14 @@
 	spi.deselect();
 
 #if CC1200_REGISTER_LEVEL_DEBUG
-	debugStream->printf("Wrote packet of data length %zu: %" PRIx8, len, static_cast<uint8_t>(len));
+	debugStream->printf("Wrote packet of data length %zu:", len);
+	if(_packetMode == PacketMode::VARIABLE_LENGTH)
+	{
+		debugStream->printf(" %02" PRIx8, static_cast<uint8_t>(len));
+	}
 	for(size_t byteIndex = 0; byteIndex < len; ++byteIndex)
 	{
-		debugStream->printf(" %" PRIx8, data[byteIndex]);
+		debugStream->printf(" %02" PRIx8, data[byteIndex]);
 	}
 	debugStream->printf("\n");
 #endif
@@ -203,16 +203,23 @@
 		return false;
 	}
 
-	// get value of first byte of the packet, which is the length.
+	if(_packetMode == PacketMode::FIXED_LENGTH)
+	{
+		return bytesReceived >= _packetTotalLength;
+	}
+	else // _packetMode == PacketMode::VARIABLE_LENGTH
+	{
+		// 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);
+		// 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
+		// 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)
@@ -221,8 +228,16 @@
 	spi.select();
 	loadStatusByte(spi.write(CC1200_DEQUEUE_RX_FIFO | CC1200_BURST));
 
-	// first read length byte
-	uint8_t dataLen = spi.write(0);
+	uint8_t dataLen;
+	if(_packetMode == PacketMode::VARIABLE_LENGTH)
+	{
+		// first read length byte
+		dataLen = spi.write(0);
+	}
+	else // _packetMode == PacketMode::FIXED_LENGTH)
+	{
+		dataLen = _packetTotalLength;
+	}
 
 	for(size_t byteIndex = 0; byteIndex < dataLen; ++byteIndex)
 	{
@@ -337,11 +352,65 @@
 	writeRegister(Register::MDMCFG0, mdmCfg0);
 }
 
+void CC1200::setPacketMode(PacketMode mode)
+{
+	_packetMode = mode;
+
+	uint8_t pktCfg0 = readRegister(Register::PKT_CFG0);
+	// set length config field
+	pktCfg0 &= ~(0b11 << PKT_CFG0_LENGTH_CONFIG);
+	pktCfg0 |= static_cast<uint8_t>(mode) << PKT_CFG0_LENGTH_CONFIG;
+	writeRegister(Register::PKT_CFG0, pktCfg0);
+
+	if(mode == PacketMode::VARIABLE_LENGTH)
+	{
+		// disable packet length limit
+		writeRegister(Register::PKT_LEN, MAX_PACKET_LENGTH);
+	}
+	else
+	{
+		// reset to selected fixed lengths
+		setPacketLength(_packetByteLength, _packetBitLength);
+	}
+}
+
+void CC1200::setPacketLength(uint8_t length, uint8_t bitLength)
+{
+	_packetByteLength = length;
+	_packetBitLength = bitLength;
+	_packetTotalLength = _packetByteLength;
+
+	if(bitLength > 0)
+	{
+		// tell the driver to read the extra bits into another byte
+		_packetTotalLength++;
+	}
+
+	writeRegister(Register::PKT_LEN, _packetByteLength);
+
+	uint8_t pktCfg0 = readRegister(Register::PKT_CFG0);
+	pktCfg0 &= ~(0b111 << PKT_CFG0_PKT_BIT_LEN);
+	pktCfg0 |= _packetBitLength << PKT_CFG0_PKT_BIT_LEN;
+	writeRegister(Register::PKT_CFG0, pktCfg0);
+
+#if CC1200_DEBUG
+	debugStream->printf("Set total length to %zu, byte length = %zu, bit length = %" PRIu8 "\n", _packetTotalLength, _packetByteLength, _packetBitLength);
+#endif
+}
+
+void CC1200::setCRCEnabled(bool enabled)
+{
+	uint8_t pktCfg1 = readRegister(Register::PKT_CFG1);
+	pktCfg1 &= ~(0b11 << PKT_CFG1_CRC_CFG);
+	pktCfg1 |= (enabled ? 0b01 : 0b00) << PKT_CFG1_CRC_CFG;
+	writeRegister(Register::PKT_CFG1, pktCfg1);
+}
+
 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;
+	modcfgDevE |= static_cast<uint8_t>(format) << MODCFG_DEV_E_MOD_FORMAT;
 	writeRegister(Register::MODCFG_DEV_E, modcfgDevE);
 }
 
@@ -408,7 +477,6 @@
 	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)
@@ -433,10 +501,13 @@
 	};
 	writeRegisters(Register::SYMBOL_RATE2, symbolRateRegisters);
 
-	// Calculate upsampler value according to the forumula in its register description
-	float upsamplingFactor = CC1200_OSC_FREQ / (64 * symbolRateSps);
+	// Calculate upsampler value according to the formula in its register description
+	float upsamplingFactor = CC1200_OSC_FREQ / (64 * symbolRateSps); // 2^upsamplerPVal needs to be less than this value
 	uint8_t upsamplerPVal = std::floor(std::log2(upsamplingFactor));
 
+	// prevent low sampling rates from choosing a nonexistent upsampling
+	upsamplerPVal = std::min<uint8_t>(upsamplerPVal, 0b110);
+
 	uint8_t mdmcfg2 = readRegister(ExtRegister::MDMCFG2);
 	mdmcfg2 &= ~(0b111 << MDMCFG2_UPSAMPLER_P);
 	mdmcfg2 |= (upsamplerPVal << MDMCFG2_UPSAMPLER_P);
@@ -444,31 +515,32 @@
 	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",
+	debugStream->printf("Setting symbol rate, requested %.03f Hz, setting SRATE_E = 0x%" PRIx8 " SRATE_M = 0x%" PRIx32 "\n",
 						symbolRateHz, actualExponent, actualMantissa);
 
 	// sanity check: calculate actual symbol rate
-	float actualSymbolRateKsps;
+	float actualSymbolRateSps;
 	if(actualExponent == 0)
 	{
-		actualSymbolRateKsps = (static_cast<float>(actualMantissa) * CC1200_OSC_FREQ) / twoToThe38;
+		actualSymbolRateSps = (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;
+		actualSymbolRateSps = ((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);
+	debugStream->printf("This yields an actual symbol rate of %.02f Hz\n", actualSymbolRateSps);
 
 	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)
+// helper function for power setting
+inline uint8_t dBPowerToRegValue(float powerDB)
 {
 	const float minOutputPower = -16.0f;
 
@@ -477,12 +549,17 @@
 	const float maxOutputPower = 14.0f;
 
 	// clamp output power into correct range
-	outPower = std::min(outPower, maxOutputPower);
-	outPower = std::max(outPower, minOutputPower);
+	powerDB = std::min(powerDB, maxOutputPower);
+	powerDB = std::max(powerDB, 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
+	float exactPowerRamp = (2 * powerDB) + 35;
+	return static_cast<uint8_t>(exactPowerRamp); // round to nearest
+}
+
+void CC1200::setOutputPower(float outPower)
+{
+	uint8_t actualPowerRamp = dBPowerToRegValue(outPower);
 
 	uint8_t paCfg1 = readRegister(Register::PA_CFG1);
 	paCfg1 &= ~(0b111111 << PA_CFG1_PA_POWER_RAMP);
@@ -490,6 +567,30 @@
 	writeRegister(Register::PA_CFG1, paCfg1);
 }
 
+const float CC1200::ASK_MIN_POWER_OFF = -17.5f;
+
+void CC1200::setASKPowers(float maxPower, float minPower)
+{
+	uint8_t maxPowerValue = dBPowerToRegValue(maxPower);
+
+	minPower = std::min(minPower, maxPower);
+	minPower = std::max(minPower, -17.5f);
+
+	// calculate min power using formula derived from manual
+	uint8_t minPowerValue = static_cast<uint8_t>((maxPower - minPower) * 2);
+
+	// write registers
+	uint8_t paCfg1 = readRegister(Register::PA_CFG1);
+	paCfg1 &= ~(0b111111 << PA_CFG1_PA_POWER_RAMP);
+	paCfg1 |= maxPowerValue << PA_CFG1_PA_POWER_RAMP;
+	writeRegister(Register::PA_CFG1, paCfg1);
+
+	uint8_t askCfg = readRegister(Register::ASK_CFG);
+	askCfg &= ~(0b111111 << ASK_CFG_ASK_DEPTH);
+	askCfg |= minPowerValue << ASK_CFG_ASK_DEPTH;
+	writeRegister(Register::ASK_CFG, askCfg);
+}
+
 void CC1200::setRadioFrequency(CC1200::Band band, float frequencyHz)
 {
 	// Frequency synthesizer configuration.  This is completely opaque and it is unknown what these bits do --
@@ -739,6 +840,12 @@
 
 void CC1200::setPARampRate(uint8_t firstRampLevel, uint8_t secondRampLevel, CC1200::RampTime rampTime)
 {
+	// enable PA ramping
+	uint8_t paCfg1Val = readRegister(Register::PA_CFG1);
+	paCfg1Val |= 1 << PA_CFG1_PA_RAMP_SHAPE_EN;
+	writeRegister(Register::PA_CFG1, paCfg1Val);
+
+	// configure properties
 	uint8_t paCfg0Val = 0;
 	paCfg0Val |= (firstRampLevel << PA_CFG0_FIRST_IPL);
 	paCfg0Val |= (secondRampLevel << PA_CFG0_SECOND_IPL);
@@ -747,6 +854,13 @@
 	writeRegister(Register::PA_CFG0, paCfg0Val);
 }
 
+void CC1200::disablePARamping()
+{
+	uint8_t paCfg1Val = readRegister(Register::PA_CFG1);
+	paCfg1Val &= ~(1 << PA_CFG1_PA_RAMP_SHAPE_EN);
+	writeRegister(Register::PA_CFG1, paCfg1Val);
+}
+
 void CC1200::setAGCReferenceLevel(uint8_t level)
 {
 	writeRegister(Register::AGC_REF, level);
@@ -935,4 +1049,5 @@
 }
 
 
-#pragma clang diagnostic pop
\ No newline at end of file
+
+