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

Files at this revision

API Documentation at this revision

Comitter:
Jamie Smith
Date:
Mon May 03 02:41:34 2021 -0700
Parent:
4:c609cc7c9ea7
Commit message:
Update to v1.2

Changed in this revision

CC1200.cpp Show annotated file Show diff for this revision Revisions of this file
CC1200.h Show annotated file Show diff for this revision Revisions of this file
CC1200Bits.h Show annotated file Show diff for this revision Revisions of this file
diff -r c609cc7c9ea7 -r d22a8885800b CC1200.cpp
--- a/CC1200.cpp	Fri Aug 28 15:39:31 2020 -0700
+++ b/CC1200.cpp	Mon May 03 02:41:34 2021 -0700
@@ -12,7 +12,7 @@
 #include <array>
 
 // change to 1 to print debug info
-#define CC1200_DEBUG 0
+#define CC1200_DEBUG 1
 
 // change to 1 to print register read/write level debug info
 #define CC1200_REGISTER_LEVEL_DEBUG 0
@@ -53,6 +53,9 @@
 // requires streaming bytes in during the transmission, which would make things complicated.
 #define MAX_PACKET_LENGTH 128
 
+// Length of the status bytes that can be appended to packets
+#define PACKET_STATUS_LEN 2U
+
 // 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/
@@ -109,7 +112,7 @@
 		if(timeoutTimer.elapsed_time() > resetTimeout)
 		{
 			debugStream->printf("Timeout waiting for ready response from CC1200\n");
-			return false;
+			break;
 		}
 	}
 
@@ -205,7 +208,12 @@
 
 	if(_packetMode == PacketMode::FIXED_LENGTH)
 	{
-		return bytesReceived >= _packetTotalLength;
+		return bytesReceived >= _packetTotalLength + (appendStatusEnabled ? PACKET_STATUS_LEN : 0);
+	}
+	else if(_packetMode == PacketMode::INFINITE_LENGTH)
+	{
+		// Any amount of bytes constitutes a packet.
+		return bytesReceived > 0;
 	}
 	else // _packetMode == PacketMode::VARIABLE_LENGTH
 	{
@@ -218,7 +226,7 @@
 		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
+		return bytesReceived >= static_cast<size_t>(packetLen + 1 /* Add one because length field does not include itself */) + (appendStatusEnabled ? PACKET_STATUS_LEN : 0);
 	}
 }
 
@@ -247,6 +255,19 @@
 			buffer[byteIndex] = currByte;
 		}
 	}
+
+	if(appendStatusEnabled)
+	{
+		uint8_t statusBytes[PACKET_STATUS_LEN];
+		for(size_t byteIndex = 0; byteIndex < PACKET_STATUS_LEN; ++byteIndex)
+		{
+			statusBytes[byteIndex] = spi.write(0);
+		}
+
+		lastRSSI = statusBytes[0];
+		lastLQI = statusBytes[1] & 0b01111111;
+	}
+
 	spi.deselect();
 
 #if CC1200_REGISTER_LEVEL_DEBUG
@@ -261,6 +282,115 @@
 	return dataLen;
 }
 
+size_t CC1200::writeStream(const char *buffer, size_t count)
+{
+	size_t freeBytes = CC1200_FIFO_SIZE - getTXFIFOLen();
+
+	/*if(state == State::TX)
+	{
+		if(freeBytes > 0)
+		{
+			freeBytes--;
+		}
+	}*/
+
+	size_t bytesToWrite = std::min(freeBytes, count);
+
+	if(bytesToWrite == 0)
+	{
+		return 0;
+	}
+
+	spi.select();
+	loadStatusByte(spi.write(CC1200_ENQUEUE_TX_FIFO | CC1200_BURST));
+	for(size_t byteIndex = 0; byteIndex < bytesToWrite; ++byteIndex)
+	{
+		spi.write(buffer[byteIndex]);
+	}
+	spi.deselect();
+
+#if CC1200_REGISTER_LEVEL_DEBUG
+	debugStream->printf("%zu bytes were free, wrote stream of data length %zu:", freeBytes, bytesToWrite);
+	for(size_t byteIndex = 0; byteIndex < bytesToWrite; ++byteIndex)
+	{
+		debugStream->printf(" %02" PRIx8, buffer[byteIndex]);
+	}
+	debugStream->printf("\n");
+#endif
+
+	return bytesToWrite;
+}
+
+bool CC1200::writeStreamBlocking(const char *buffer, size_t count)
+{
+	//size_t origCount = count;
+	size_t bufferOffset = 0;
+	while(state == State::TX && count > 0)
+	{
+		size_t bytesWritten = writeStream(buffer + bufferOffset, count);
+		count -= bytesWritten;
+		bufferOffset += bytesWritten;
+	}
+
+	//debugStream->printf("Read stream of data length %zu\n:", origCount);
+
+	return count == 0;
+}
+
+size_t CC1200::readStream(char *buffer, size_t maxLen)
+{
+	size_t bytesToRead = std::min(maxLen, getRXFIFOLen());
+	if(bytesToRead == 0)
+	{
+		return 0;
+	}
+
+	// burst read from RX FIFO
+	spi.select();
+	loadStatusByte(spi.write(CC1200_DEQUEUE_RX_FIFO | CC1200_BURST));
+	for(size_t byteIndex = 0; byteIndex < bytesToRead; ++byteIndex)
+	{
+		buffer[byteIndex] = spi.write(0);
+	}
+	spi.deselect();
+
+#if CC1200_REGISTER_LEVEL_DEBUG
+	debugStream->printf("Read stream of data length %zu:", bytesToRead);
+	for(size_t byteIndex = 0; byteIndex < bytesToRead; ++byteIndex)
+	{
+		debugStream->printf(" %" PRIx8, buffer[byteIndex]);
+	}
+	debugStream->printf("\n");
+#endif
+
+	return bytesToRead;
+}
+
+bool CC1200::readStreamBlocking(char *buffer, size_t count, std::chrono::microseconds timeout)
+{
+	//size_t origCount = count;
+	Timer timeoutTimer;
+
+	if(timeout > 0us)
+	{
+		timeoutTimer.start();
+	}
+
+	size_t bufferOffset = 0;
+	while((timeoutTimer.elapsed_time() < timeout || timeout == 0us) && state == State::RX && count > 0)
+	{
+		size_t bytesRead = readStream(buffer + bufferOffset, count);
+		count -= bytesRead;
+		bufferOffset += bytesRead;
+	}
+
+	//debugStream->printf("Read stream of data length %zu, first %" PRIx8 " last %" PRIx8 "\n:", origCount,
+	//		buffer[0], buffer[origCount - 1]);
+
+	return count == 0;
+}
+
+
 // helper function: convert a state to the bits for RXOFF_MODE and TXOFF_MODE
 inline uint8_t getOffModeBits(CC1200::State state)
 {
@@ -352,7 +482,7 @@
 	writeRegister(Register::MDMCFG0, mdmCfg0);
 }
 
-void CC1200::setPacketMode(PacketMode mode)
+void CC1200::setPacketMode(PacketMode mode, bool appendStatus)
 {
 	_packetMode = mode;
 
@@ -367,14 +497,31 @@
 		// disable packet length limit
 		writeRegister(Register::PKT_LEN, MAX_PACKET_LENGTH);
 	}
-	else
+	else if(mode == PacketMode::FIXED_LENGTH)
 	{
 		// reset to selected fixed lengths
 		setPacketLength(_packetByteLength, _packetBitLength);
 	}
+	else
+	{
+		// Infinite length packets, PKT_LEN register is a don't care.
+	}
+
+	// set append status
+	appendStatusEnabled = appendStatus;
+	uint8_t pktCfg1 = readRegister(Register::PKT_CFG1);
+	if(appendStatus)
+	{
+		pktCfg1 |= 1 << PKT_CFG1_APPEND_STATUS;
+	}
+	else
+	{
+		pktCfg1 &= ~(1 << PKT_CFG1_APPEND_STATUS);
+	}
+	writeRegister(Register::PKT_CFG1, pktCfg1);
 }
 
-void CC1200::setPacketLength(uint8_t length, uint8_t bitLength)
+void CC1200::setPacketLength(uint16_t length, uint8_t bitLength)
 {
 	_packetByteLength = length;
 	_packetBitLength = bitLength;
@@ -386,7 +533,15 @@
 		_packetTotalLength++;
 	}
 
-	writeRegister(Register::PKT_LEN, _packetByteLength);
+	if(_packetTotalLength == 256)
+	{
+		// Length byte of 0 indicates 256 bytes
+		writeRegister(Register::PKT_LEN, 0);
+	}
+	else
+	{
+		writeRegister(Register::PKT_LEN, _packetByteLength);
+	}
 
 	uint8_t pktCfg0 = readRegister(Register::PKT_CFG0);
 	pktCfg0 &= ~(0b111 << PKT_CFG0_PKT_BIT_LEN);
@@ -565,6 +720,10 @@
 	paCfg1 &= ~(0b111111 << PA_CFG1_PA_POWER_RAMP);
 	paCfg1 |= actualPowerRamp << PA_CFG1_PA_POWER_RAMP;
 	writeRegister(Register::PA_CFG1, paCfg1);
+
+#if CC1200_DEBUG
+	debugStream->printf("Output power set to %.01f dBm\n", outPower);
+#endif
 }
 
 const float CC1200::ASK_MIN_POWER_OFF = -17.5f;
@@ -617,7 +776,13 @@
 		writeRegister(ExtRegister::FS_DVC0,0x17);
 		writeRegister(ExtRegister::IFAMP,0x09);
 	}
-	else if(band == Band::BAND_410_480MHz || band == Band::BAND_164_192MHz)
+	else if(band == Band::BAND_410_480MHz)
+	{
+		writeRegister(ExtRegister::FS_DIG0,0xA3);
+		writeRegister(ExtRegister::FS_DVC0,0x0F);
+		writeRegister(ExtRegister::IFAMP,0x0D);
+	}
+	else if(band == Band::BAND_164_192MHz)
 	{
 		writeRegister(ExtRegister::FS_DIG0,0x50);
 		writeRegister(ExtRegister::FS_DVC0,0x0F);
@@ -626,7 +791,7 @@
 	else
 	{
 		// TI doesn't make settings public for the other radio bands.
-		// Let's take a guess and use the 480-164MHz values.
+		// Let's take a guess and use the 164-192MHz values.
 		writeRegister(ExtRegister::FS_DIG0,0x50);
 		writeRegister(ExtRegister::FS_DVC0,0x0F);
 		writeRegister(ExtRegister::IFAMP,0x0D);
@@ -648,25 +813,24 @@
 	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));
+	float exactFreqRegValue = (twoToThe16 * frequencyHz * static_cast<float>(loDividerValue)) / CC1200_OSC_FREQ;
+	uint32_t actualFreqRegValue = static_cast<uint32_t>(std::min(static_cast<float>(maxValue24Bits), exactFreqRegValue));
 
 	// 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))
+			static_cast<uint8_t>((actualFreqRegValue >> 16) & 0xFF),
+			static_cast<uint8_t>((actualFreqRegValue >> 8) & 0xFF),
+			static_cast<uint8_t>((actualFreqRegValue & 0xFF))
 	};
 	writeRegisters(ExtRegister::FREQ2, freqRegisters);
 
+	// sanity check: calculate actual frequency
+	radioFreqHz = (static_cast<float>(actualFreqRegValue) * CC1200_OSC_FREQ) / (twoToThe16 * static_cast<float>(loDividerValue));
+
 #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);
+						frequencyHz, actualFreqRegValue);
+	debugStream->printf("This yields an actual frequency of %.00f Hz\n", radioFreqHz);
 #endif
 }
 
@@ -677,7 +841,7 @@
 	return CC1200_OSC_FREQ / (static_cast<float>(adcDecimation) * static_cast<float>(cicDecimation) * 2);
 }
 
-void CC1200::setRXFilterBandwidth(float bandwidthHz)
+void CC1200::setRXFilterBandwidth(float bandwidthHz, bool preferHigherCICDec)
 {
 	// settings that the chip supports
 	const uint8_t possibleADCDecimations[] = {12, 24, 48}; // indexes in this array represent the register value
@@ -715,7 +879,7 @@
 	{
 		float thisDecimationError = std::abs(bandwidthHz -
 				calcReceiveBandwidth(possibleADCDecimations[adcDecimationIndex], actualBBDecimations[adcDecimationIndex]));
-		if(thisDecimationError <= bestDecimationError)
+		if((preferHigherCICDec && thisDecimationError <= bestDecimationError) || (!preferHigherCICDec && thisDecimationError < bestDecimationError))
 		{
 			bestDecimationError = thisDecimationError;
 			bestDecimationIndex = adcDecimationIndex;
@@ -769,12 +933,12 @@
 	writeRegister(Register::SYNC_CFG0, syncCfg0Value);
 
 	adcCicDecimation = possibleADCDecimations[bestDecimationIndex];
+	currentRXFilterBW = calcReceiveBandwidth(possibleADCDecimations[bestDecimationIndex], actualBBDecimations[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]));
+	debugStream->printf("This yields an actual RX filter BW of %.00f\n", currentRXFilterBW);
 #endif
 }
 
@@ -794,22 +958,6 @@
 	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
@@ -836,6 +984,10 @@
 	preambleCfg1 |= preambleLengthCfg << PREAMBLE_CFG1_NUM_PREAMBLE;
 	preambleCfg1 |= preambleFormatCfg << PREAMBLE_CFG1_PREAMBLE_WORD;
 	writeRegister(Register::PREAMBLE_CFG1, preambleCfg1);
+
+#if CC1200_DEBUG
+	debugStream->printf("Preamble length CFG set to 0x%" PRIx8 "\n", preambleLengthCfg);
+#endif
 }
 
 void CC1200::setPARampRate(uint8_t firstRampLevel, uint8_t secondRampLevel, CC1200::RampTime rampTime)
@@ -883,7 +1035,7 @@
 	agcCfg2Val &= ~(0b11 << AGC_CFG2_FE_PERFORMANCE_MODE);
 	agcCfg2Val &= ~(0b11111 << AGC_CFG2_AGC_MAX_GAIN);
 
-	agcCfg3Val |= maxGainIndex << AGC_CFG3_AGC_MIN_GAIN;
+	agcCfg3Val |= minGainIndex << AGC_CFG3_AGC_MIN_GAIN;
 	agcCfg2Val |= static_cast<uint8_t>(table) << AGC_CFG2_FE_PERFORMANCE_MODE;
 	agcCfg2Val |= maxGainIndex << AGC_CFG2_AGC_MAX_GAIN;
 
@@ -907,12 +1059,103 @@
 	writeRegister(Register::AGC_CFG0, agcCfg0Val);
 }
 
-void CC1200::setIFMixCFG(uint8_t value)
+void CC1200::setAGCSettleWait(uint8_t settleWaitCfg)
+{
+	uint8_t agcCfg1Val = readRegister(Register::AGC_CFG1);
+	agcCfg1Val &= ~(0b111 << AGC_CFG1_AGC_SETTLE_WAIT);
+	agcCfg1Val |= (settleWaitCfg << AGC_CFG1_AGC_SETTLE_WAIT);
+	writeRegister(Register::AGC_CFG1, agcCfg1Val);
+}
+
+float CC1200::getRSSIRegister()
 {
+	uint8_t rssi1Val = readRegister(ExtRegister::RSSI1);
+	uint8_t rssi0Val = readRegister(ExtRegister::RSSI0);
+
+	if(!(rssi0Val & (1 << RSSI0_RSSI_VALID)))
+	{
+		// no valid measurement
+		return NAN;
+	}
+
+	// first convert to two's compliment number
+	int16_t rssiInt = 0;
+	rssiInt |= (rssi0Val >> RSSI0_RSSI_3_0) & 0b1111;
+	rssiInt |= rssi1Val << 4;
+
+	if(rssi1Val & 0x80)
+	{
+		// negative number, sign extend from 12 to 16 bits
+		rssiInt |= (0b1111 << 12);
+	}
+
+	//debugStream->printf("Approx RSSI: %" PRIi8 ", exact RSSI: %f\n", static_cast<int8_t>(rssi1Val), static_cast<float>(rssiInt) * 0.0625f);
+
+	return static_cast<float>(rssiInt) * 0.0625f; // conversion factor given in datasheet
+}
+
+void CC1200::setRSSIOffset(int8_t adjust)
+{
+	writeRegister(Register::AGC_GAIN_ADJUST, static_cast<uint8_t>(adjust));
+}
+
+uint8_t CC1200::getLQIRegister()
+{
+	return readRegister(ExtRegister::LQI_VAL) & 0b1111111;
+}
+
+void CC1200::setIFCfg(IFCfg value, bool enableIQIC)
+{
+	// make sure prerequisites have been run
+	MBED_ASSERT(radioFreqHz > 0);
+	MBED_ASSERT(adcCicDecimation > 0 && currentRXFilterBW > 0);
+
 	uint8_t ifMixCfg = readRegister(ExtRegister::IF_MIX_CFG);
 	ifMixCfg &= ~(0b111 << IF_MIX_CFG_CMIX_CFG);
-	ifMixCfg |= (value << IF_MIX_CFG_CMIX_CFG);
+	ifMixCfg |= (static_cast<uint8_t>(value) << IF_MIX_CFG_CMIX_CFG);
 	writeRegister(ExtRegister::IF_MIX_CFG, ifMixCfg);
+
+	float effectiveIF;
+
+	// calculate effective IF value
+	if(value == IFCfg::ZERO)
+	{
+		effectiveIF = radioFreqHz;
+	}
+	else
+	{
+		int32_t dividerValue = 0;
+		switch(value)
+		{
+			case IFCfg::NEGATIVE_DIV_4: dividerValue = -4; break;
+			case IFCfg::NEGATIVE_DIV_6: dividerValue = -6; break;
+			case IFCfg::NEGATIVE_DIV_8: dividerValue = -8; break;
+			case IFCfg::POSITIVE_DIV_4: dividerValue = 4; break;
+			case IFCfg::POSITIVE_DIV_6: dividerValue = 6; break;
+			case IFCfg::POSITIVE_DIV_8: dividerValue = 8; break;
+			default: break;
+		}
+
+		// formula from IF_MIX_CFG register description
+		effectiveIF = (CC1200_OSC_FREQ / static_cast<float>(adcCicDecimation * dividerValue)) * 1000;
+	}
+
+	uint8_t iqicValue = readRegister(Register::IQIC);
+	if(enableIQIC && effectiveIF > currentRXFilterBW)
+	{
+		iqicValue |= (1 << IQIC_IQIC_EN);
+	}
+	else
+	{
+		iqicValue &= ~(1 << IQIC_IQIC_EN);
+	}
+	writeRegister(Register::IQIC, iqicValue);
+
+#if CC1200_DEBUG
+	debugStream->printf("Setting IF Mix Cfg to 0x%" PRIx8 " and IQIC_EN to %d\n", static_cast<uint8_t>(value),
+					 !!(iqicValue & (1 << IQIC_IQIC_EN))); // note: double ! used to convert boolean to either 1 or 0.
+	debugStream->printf("This yields an actual IF of %.00f\n", effectiveIF);
+#endif
 }
 
 uint8_t CC1200::readRegister(CC1200::Register reg)
@@ -1047,7 +1290,3 @@
 
 	return value;
 }
-
-
-
-
diff -r c609cc7c9ea7 -r d22a8885800b CC1200.h
--- a/CC1200.h	Fri Aug 28 15:39:31 2020 -0700
+++ b/CC1200.h	Mon May 03 02:41:34 2021 -0700
@@ -125,6 +125,11 @@
 		XOSC1 = 0x36,
 		XOSC0 = 0x37,
 		//...
+		RSSI1 = 0x71,
+		RSSI0 = 0x72,
+		MARCSTATE = 0x73,
+		LQI_VAL = 0x74,
+		//...
 		FREQOFF_EST1 = 0x77,
 		FREQOFF_EST2 = 0x78,
 		//...
@@ -185,8 +190,13 @@
 	// current symbol rate of the radio
 	float symbolRateSps = 0;
 
-	// current ADC CIC decimation of the radio
+	// current RX filter params
 	uint8_t adcCicDecimation = 0;
+	float currentRXFilterBW = 0;
+
+	// current RF params
+	float radioFreqHz;
+
 
 public:
 
@@ -241,6 +251,8 @@
 	 *
 	 * In fixed length mode, the length should be the fixed packet length.
 	 *
+	 * The function is not for use with infinite-length mode, use writeStream() instead.
+	 *
 	 * Also reads the radio's state.
 	 *
 	 * @param data
@@ -253,6 +265,7 @@
 
 	/**
 	 * Check whether there is at least one complete packet in the RX FIFO.
+	 * In infinite length mode, this returns true if any data is present at all.
 	 * NOTE: An alternate way to do this using hardware is to configure one
 	 * of the CC1200's GPIOs as PKT_SYNC_RXTX, then set a falling edge interrupt to receive a packet.
 	 * @return
@@ -268,6 +281,8 @@
 	 * in the FIFO, *undefined behavior* can occur.  An arbitrary amount of data will be read from
 	 * the FIFO and garbage may be returned.
 	 *
+	 * The function is not for use with infinite-length mode, use readStream() instead.
+	 *
 	 * NOTE: A null terminator is NOT added unless it was present in the transmitted data.
 	 * Be careful when treating the returned data as a string!
 	 *
@@ -278,6 +293,78 @@
 	 */
 	size_t receivePacket(char * buffer, size_t bufferLen);
 
+	// Infinite length tx & rx functions
+	// ------------------------------------------------------------------------------
+
+	/*
+	 * How to use infinite length from the TX side:
+	 * 1. Place up to 128 bytes of data in the TX FIFO for transmission using writeStream().
+	 * 2. Enable TX mode using startTX()
+	 * 3. Keep streaming in data using writeStream() writeStreamBlocking() or at least as fast as it is transmitted.
+	 * 4. Disable TX mode when done using idle().
+	 *
+	 * Take care that the TX fifo never runs completely out of data, or the chip will go into TX FIFO ERROR state.
+	 */
+
+	/*
+	 * How to use infinite length from the RX side:
+	 * 1. Enable RX mode using startRX().  I'm pretty sure that if sync words are enabled, you must start RX before the transmitter starts transmitting.
+	 * 2. Wait for data to arrive using hasReceivedPacket().
+	 * 3. Start streaming out data using readStream() or readStreamBlocking().
+	 * 4. Disable RX mode when done using idle().
+	 *
+	 * Take care that the RX fifo doesn't become full, or the chip will go into RX FIFO ERROR state.
+	 */
+
+	/**
+	 * Write a stream of data to the chip.  This function is only for use in infinite-length mode.
+	 * As many bytes from the given buffer will be written as can fit in the chip's FIFO.
+	 * If the FIFO is full, this function does nothing.
+	 *
+	 * @param buffer
+	 * @param count
+	 * @return Number of bytes written.
+	 */
+	size_t writeStream(const char* buffer, size_t count);
+
+	/**
+	 * Write a stream of data to the chip.  This function is only for use in infinite-length mode.
+	 * Will block until all bytes in the given buffer have been written to the TX FIFO, or an error has been
+	 * detected (the radio switches to any state other than TX).
+	 *
+	 * @param buffer
+	 * @param count
+	 * @return True if successful, false if there was an error.
+	 */
+	bool writeStreamBlocking(const char* buffer, size_t count);
+
+	/**
+	 * Read up to maxLen bytes into buffer.
+	 * @param buffer
+	 * @param maxLen
+	 * @return How many bytes were actually read.  0 if the RX FIFO was empty.
+	 */
+	size_t readStream(char* buffer, size_t maxLen);
+
+	/**
+	 * Read a stream of data from the chip.  This function is only for use in infinite-length mode.
+	 * Will block until the buffer was filled, an error has been
+	 * detected (the radio switches to any state other than RX), or the timeout expires.
+	 *
+	 * If false is returned, some data may have been written to the buffer, and the rest will not have been modified.
+	 *
+	 * Note: if using a zero timeout, this function could cause a hang if the transmitter stops transmitting.
+	 *
+	 * @param buffer
+	 * @param count
+	 * @param timeout Timeout, or 0us to disable timeout.
+	 * @return True iff the buffer was completely filled.
+	 */
+	bool readStreamBlocking(char* buffer, size_t count, std::chrono::microseconds timeout=0us);
+
+	// State transition configuration
+	// ------------------------------------------------------------------------------
+
 	/**
 	 * Set what state the radio will enter when a packet is received.
 	 * @param goodPacket State when a good (CRC pass) packet is received.
@@ -349,6 +436,8 @@
 
 	enum class PacketMode : uint8_t
 	{
+		/// Use infinite length transmissions (streaming mode).
+		INFINITE_LENGTH = 0b10,
 		/// Use fixed length packets.
 		VARIABLE_LENGTH = 0b1,
 		/// Use variable length packets, the length is encoded in the first byte of the packet.
@@ -356,10 +445,12 @@
 	};
 
 	/**
-	 * Set the packet mode that the system will use
+	 * Set the packet mode that the system will use.
 	 * @param mode
+	 * @param appendStatus Have the radio append a status byte to each packet received.  This takes up
+	 * 2 bytes per packet in the RX FIFO, but provides status information about each packet.
 	 */
-	void setPacketMode(PacketMode mode);
+	void setPacketMode(PacketMode mode, bool appendStatus = false);
 
 	/**
 	 * Set the packet length when in fixed length packet mode.
@@ -367,19 +458,24 @@
 	 * For example, if your packets are 20 bits long, you should set length to 2 bytes and bitLength to
 	 * 4 bytes, so that 2 complete bytes + 4 extra bits are transmitted.  Buffers used for sending and receiving packets
 	 * should then be 3 bytes long.
+	 *
+	 * When appendStatus is disabled, the max length is 256 bytes.  When it is enabled, the max length is 254 bytes.
 	 * @param length
 	 */
-	void setPacketLength(uint8_t length, uint8_t bitLength = 0);
+	void setPacketLength(uint16_t length, uint8_t bitLength = 0);
 
 private:
 	// current packet mode
 	PacketMode _packetMode;
 
 	// current packet length when in fixed length packet mode
-	uint8_t _packetTotalLength = 0; // length in bytes including final partial byte
-	uint8_t _packetByteLength = 0; // length in whole bytes
+	uint16_t _packetTotalLength = 0; // length in bytes including final partial byte
+	uint16_t _packetByteLength = 0; // length in whole bytes
 	uint8_t _packetBitLength = 0; // extra bit length at end
 
+	// Whether the two status bytes are included in each received packet
+	bool appendStatusEnabled = true;
+
 public:
 
 	/**
@@ -480,8 +576,10 @@
 	 * - SYNC_CFG0.RX_CONFIG_LIMITATION
 	 *
 	 * @param bandwidthHz the bandwidth in Hz
+	 * @param preferHigherCICDec If there are multiple register value choices, prefer the one with higher CIC decimation
+	 * and lower BB decimation.  This is the recommendation of the datasheet but it actually causes transmission to fail in some cases.
 	 */
-	void setRXFilterBandwidth(float bandwidthHz);
+	void setRXFilterBandwidth(float bandwidthHz, bool preferHigherCICDec = true);
 
 	/**
 	 * Get the ADC CIC decimation that was calculated by the most recent setRXFilterBandwidth() call.
@@ -491,7 +589,7 @@
 	uint8_t getADCCICDecimation() { return adcCicDecimation; }
 
 	/**
-	 * Configure the radio's automatic DC offset removal algorithm is enabled.
+	 * Configure the radio's automatic DC offset removal algorithm as enabled.
 	 * DC offset correction must be enabled when using zero IF mode, and in my testing
 	 * it seems to be important when staying in TX mode for a long time at
 	 * higher sample rates.
@@ -506,17 +604,32 @@
 	void configureDCFilter(bool enableAutoFilter, uint8_t settlingCfg, uint8_t cutoffCfg);
 
 	/**
-	 * Set the IF mixing configuration.
+	 * Possible intermediate frequency values.
+	 * For right now it seems like you have to get these from SmartRF.
 	 * See the user guide section on IF_MIX_CFG.CMIX_CFG for details.
 	 */
-	void setIFMixCFG(uint8_t value);
+	enum class IFCfg : uint8_t
+	{
+		ZERO = 0, // Zero IF.  From what I can find, this means samples are taken at the radio frequency.
+		NEGATIVE_DIV_4 = 0b001,
+		NEGATIVE_DIV_6 = 0b010,
+		NEGATIVE_DIV_8 = 0b011,
+		POSITIVE_DIV_4 = 0b101,
+		POSITIVE_DIV_6 = 0b110,
+		POSITIVE_DIV_8 = 0b111
+	};
 
 	/**
-	 * Set whether the ImageExtinct IQ mismatch compensation logic is enabled.
-	 * This should be disabled if IF < RX filter bandwidth
-	 * @param enabled
+	 * Set the receiver IF mixing configuration.
+	 * See the user guide section on IF_MIX_CFG.CMIX_CFG for details.
+	 *
+	 * You must call *both* setRXFilterBandwidth() and setRadioFrequency() before calling this function.
+	 *
+	 * @param value Divider value to use, or zero-IF
+	 * @param enableIQIC Whether to enable the ImageExtinct IQ mismatch compensation when supported
+	 *   (when if IF > RX filter bandwidth).
 	 */
-	void setIQMismatchCompensationEnabled(bool enabled);
+	void setIFCfg(IFCfg value, bool enableIQIC);
 
 	/**
 	 * Mode describing the size and setup of the sync word.
@@ -654,6 +767,62 @@
 	 */
 	void setAGCSlewRate(uint8_t slewrateCfg);
 
+	/**
+	 * Configure the time that the AGC takes to settle.
+	 * See the register description for AGC_CFG1.AGC_SETTLE_WAIT
+	 * @param settleWaitCfg bytes to write to AGC_CFG1.AGC_SETTLE_WAIT
+	 */
+	 void setAGCSettleWait(uint8_t settleWaitCfg);
+
+	// Received Signal Strength Indicator (RSSI) and Link Quality Indicator (LQI) functions
+	// ------------------------------------------------------------------------------
+
+private:
+	// data from packet status bytes
+	int8_t lastRSSI;
+	uint8_t lastLQI;
+
+public:
+
+	/**
+	 * Get the RSSI as of the last packet received.
+	 * Only provides valid data if appendStatus is enabled.
+	 * @return RSSI in dBm, or -128 if no valid RSSI measurement exists.
+	 */
+	int8_t getLastRSSI() {return lastRSSI;}
+
+	/**
+	 * Get the current RSSI from the RSSI register.  Note: I think
+	 * this might only work while the radio is actively receiving.
+	 *
+	 * @return RSSI in dBm, or NaN if no valid RSSI measurement exists.
+	 */
+	float getRSSIRegister();
+
+	/**
+	 * Set the RSSI gain adjustment.  This value is added to the reported RSSI, and also used
+	 * in the calculation of the Carrier Sense (CS) line.
+	 * You have to calibrate this in a lab by feeding in a known amplitude signal,
+	 * see the user manual section 6.9 for details.
+	 */
+	void setRSSIOffset(int8_t adjust);
+
+	/**
+	 * Get the LQI from the LQI_VAL register.
+	 * This is a qualitative estimate from 1-128 of how easily a packet can be demodulated.
+	 * Lower is better, but 0 indicates invalid.
+	 * @return
+	 */
+	uint8_t getLQIRegister();
+
+	/**
+	 * Get the LQI as of the last packet received.
+	 * Only provides valid data if appendStatus is enabled.
+	 * This is a qualitative estimate from 1-128 of how easily a packet can be demodulated.
+	 * Lower is better, but 0 indicates invalid.
+	 */
+	uint8_t getLastLQI() {return lastLQI;}
+
 	// Register level functions
 	// ------------------------------------------------------------------------------
 
diff -r c609cc7c9ea7 -r d22a8885800b CC1200Bits.h
--- a/CC1200Bits.h	Fri Aug 28 15:39:31 2020 -0700
+++ b/CC1200Bits.h	Mon May 03 02:41:34 2021 -0700
@@ -54,7 +54,7 @@
 #define PKT_CFG1_PN9_SWAP_EN 5
 #define PKT_CFG1_ADDR_CHECK_CFG 3
 #define PKT_CFG1_CRC_CFG 1
-#define PKT_CFG1_APPEND_STATUS
+#define PKT_CFG1_APPEND_STATUS 0
 
 #define PKT_CFG0_LENGTH_CONFIG 5
 #define PKT_CFG0_PKT_BIT_LEN 2
@@ -127,4 +127,13 @@
 #define ASK_CFG_AGC_ASK_BW 6
 #define ASK_CFG_ASK_DEPTH 0
 
+#define RSSI0_RSSI_3_0 3
+#define RSSI0_CARRIER_SENSE 2
+#define RSSI0_CARRIER_SENSE_VALID 1
+#define RSSI0_RSSI_VALID 0
+
+#define AGC_CFG1_RSSI_STEP_THR 6
+#define AGC_CFG1_AGC_WIN_SIZE 3
+#define AGC_CFG1_AGC_SETTLE_WAIT 0
+
 #endif //LIGHTSPEEDRANGEFINDER_CC1200BITS_H