Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: CC1200-MorseEncoder CC1200-Examples
CC1200.h
- Committer:
- Jamie Smith
- Date:
- 2020-08-28
- Revision:
- 4:c609cc7c9ea7
- Parent:
- 2:2a447e8e50b8
- Child:
- 5:d22a8885800b
File content as of revision 4:c609cc7c9ea7:
// // Created by jamie on 3/27/2020. // #ifndef LIGHTSPEEDRANGEFINDER_CC1200_H #define LIGHTSPEEDRANGEFINDER_CC1200_H #include <mbed.h> #include <Stream.h> #include <cstdint> /** * Base driver for the CC1200 radio communications IC. * This class provides basic functions and register level IO with the chip. */ class CC1200 { // connections to chip SPI spi; DigitalOut rst; // Output to print debug messages to Stream * debugStream; public: // register definitions enum class Register : uint8_t { IOCFG3 = 0x00, IOCFG2 = 0x01, IOCFG1 = 0x02, IOCFG0 = 0x03, SYNC3 = 0x4, SYNC2 = 0x5, SYNC1 = 0x6, SYNC0 = 0x7, SYNC_CFG1 = 0x8, SYNC_CFG0 = 0x9, DEVIATION_M = 0xA, MODCFG_DEV_E = 0xB, DCFILT_CFG = 0xC, PREAMBLE_CFG1 = 0xD, PREAMBLE_CFG0 = 0xE, IQIC = 0xF, CHAN_BW = 0x10, MDMCFG1 = 0x11, MDMCFG0 = 0x12, SYMBOL_RATE2 = 0x13, SYMBOL_RATE1 = 0x14, SYMBOL_RATE0 = 0x15, AGC_REF = 0x16, AGC_CS_THR = 0x17, AGC_GAIN_ADJUST = 0x18, AGC_CFG3 = 0x19, AGC_CFG2 = 0x1A, AGC_CFG1 = 0x1B, AGC_CFG0 = 0x1C, FIFO_CFG = 0x1D, DEV_ADDR = 0x1E, SETTLING_CFG = 0x1F, FS_CFG = 0x20, WOR_CFG1 = 0x21, WOR_CFG0 = 0x22, WOR_EVENT0_MSB = 0x23, WOR_EVENT0_LSB = 0x24, RXDCM_TIME = 0x25, PKT_CFG2 = 0x26, PKT_CFG1 = 0x27, PKT_CFG0 = 0x28, RFEND_CFG1 = 0x29, RFEND_CFG0 = 0x2A, PA_CFG1 = 0x2B, PA_CFG0 = 0x2C, ASK_CFG = 0x2D, PKT_LEN = 0x2E }; // extended register definitions enum class ExtRegister : uint8_t { IF_MIX_CFG = 0x0, FREQOFF_CFG = 0x1, TOC_CFG = 0x2, //... MDMCFG2 = 0x5, //... FREQOFF1 = 0xA, FREQOFF2 = 0xB, FREQ2 = 0xC, FREQ1 = 0xD, FREQ0 = 0xE, IF_ADC2 = 0xF, IF_ADC1 = 0x10, IF_ADC0 = 0x11, FS_DIG1 = 0x12, FS_DIG0 = 0x13, //... FS_CAL1 = 0x16, FS_CAL0 = 0x17, FS_CHP = 0x18, FS_DIVTWO = 0x19, FS_DSM1 = 0x1A, FS_DSM0 = 0x1B, FS_DVC1 = 0x1C, FS_DVC0 = 0x1D, FS_LBI = 0x1E, FS_PFD = 0x1F, FS_PRE = 0x20, FS_REG_DIV_CML = 0x21, FS_SPARE = 0x22, FS_VCO4 = 0x23, FS_VCO3 = 0x24, FS_VCO2 = 0x25, FS_VCO1 = 0x26, FS_VCO0 = 0x27, //... IFAMP = 0x2F, //.. XOSC5 = 0x32, XOSC4 = 0x33, XOSC3 = 0x34, XOSC2 = 0x35, XOSC1 = 0x36, XOSC0 = 0x37, //... FREQOFF_EST1 = 0x77, FREQOFF_EST2 = 0x78, //... FSCAL_CTRL = 0x8D, PARTNUMBER = 0x8F, PARTVERSION = 0x90, //... RXFIRST = 0xD2, TXFIRST = 0xD3, RXLAST = 0xD4, TXLAST = 0xD5, NUM_TXBYTES = 0xD6, NUM_RXBYTES = 0xD7, //... RXFIFO_PRE_BUF = 0xDA }; // Command strobe definitions. See user guide section 3.2.2 enum class Command : uint8_t { SOFT_RESET = 0x30, FAST_TX_ON = 0x31, OSC_OFF = 0x32, CAL_FREQ_SYNTH = 0x33, RX = 0x34, TX = 0x35, IDLE = 0x36, AUTO_FREQ_COMP = 0x37, WAKE_ON_RADIO = 0x38, SLEEP = 0x39, FLUSH_RX = 0x3A, FLUSH_TX = 0x3B, WOR_RESET = 0x3C, NOP = 0x3D }; // State of the radio chip. See user guide Figure 2. enum class State : uint8_t { IDLE = 0x0, RX = 0x1, TX = 0x2, FAST_ON = 0x3, CALIBRATE = 0x4, SETTLING = 0x5, RX_FIFO_ERROR = 0x6, TX_FIFO_ERROR = 0x7 }; private: // chip data variables bool chipReady = false; State state = State::IDLE; bool isCC1201; // current state variables // current symbol rate of the radio float symbolRateSps = 0; // current ADC CIC decimation of the radio uint8_t adcCicDecimation = 0; public: /** * Construct a CC1200 radio driver from the given set of pins. * * @param misoPin * @param mosiPin * @param sclkPin * @param csPin * @param rstPin * @param _debugStream Stream to print error/debug information on. * @param isCC1201 True if the chip is a CC1201, false if it is a CC1200. The CC1201 is a cheaper option that lacks low bandwidth settings but is otherwise identical. */ CC1200(PinName mosiPin, PinName misoPin, PinName sclkPin, PinName csPin, PinName rstPin, Stream * _debugStream, bool _isCC1201 = false); /** * Reset the chip and attempt to connect to it. * Returns whether the chip could be contacted. * @return */ bool begin(); /** * Get the radio's most recently known state. * State is updated whenever registers are read or commands are sent, or when you call updateState. * @return */ State getState() { return state; } // Data tx & rx functions // ------------------------------------------------------------------------------ /** * Get the number of bytes currently in the TX FIFO * @return */ size_t getTXFIFOLen(); /** * Get the number of bytes currently in the RX FIFO * @return */ size_t getRXFIFOLen(); /** * Enqueue a packet to be sent over the radio. It will be sent the next time the radio is in * transmit state. * * In variable length mode, the length of a packet is variable, from 1 byte to 127 bytes. * The length will be transmitted along with the packet data. * * In fixed length mode, the length should be the fixed packet length. * * Also reads the radio's state. * * @param data * @param len * * @return Whether the packet was enqueued. Could return false if there was not enough FIFO * space to enqueue the packet, or if the packet is too long. */ bool enqueuePacket(char const * data, size_t len); /** * Check whether there is at least one complete packet in the RX FIFO. * 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 */ bool hasReceivedPacket(); /** * Receive a packet from the radio. Only packets that pass CRC check are received into the FIFO; * those which do not pass checksum will be discarded. * * This function assumes that there is a packet in the buffer. You should only call it after * hasReceivedPacket() is true or a PKT_SYNC_RXTX pulse is received. If there is not a packet * in the FIFO, *undefined behavior* can occur. An arbitrary amount of data will be read from * the FIFO and garbage may be returned. * * 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! * * @param buffer Buffer to store received bytes in. * @param bufferLen Length of the buffer supplied. If the packet is longer than this buffer, then * the full packet will be read from the FIFO but only a buffer's worth will be stored. * @return Number of bytes actually received. */ size_t receivePacket(char * buffer, size_t bufferLen); /** * Set what state the radio will enter when a packet is received. * @param goodPacket State when a good (CRC pass) packet is received. * Accepts State::TX, State::IDLE, State::FAST_TX_ON, and State::RX. * @param badPacket State when a bad (CRC fail) packet is received. * Accepts State::RX and State::IDLE */ void setOnReceiveState(State goodPacket, State badPacket); /** * Set what state the radio will enter when a packet is sent. * @param txState State when a packet is transmitted. * Accepts State::TX, State::IDLE, State::FAST_TX_ON, and State::RX. */ void setOnTransmitState(State txState); enum class FSCalMode : uint8_t { NONE = 0b00, // never calibrate the FS automatically FROM_IDLE = 0b01, // calibrate the FS when going from idle to TX, RX, or fast TX on TO_IDLE = 0b10, // calibrate the FS when going from TX, RX, or fast TX on to idle TO_IDLE_1_4 = 0b11 // calibrate the FS 1/4 of the time when going from TX, RX, or fast TX on to idle }; /** * Set when the radio calibrates its frequency synthesizer. * * Per https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz/f/156/t/375189 * it looks like the FS can drift with changes in supply voltage and/or temperature, * so it is good to continually calibrate it in case these change. */ void setFSCalMode(FSCalMode mode); // GPIO configuration // ------------------------------------------------------------------------------ /** * Enum for all possible GPIO modes. * Note: Some modes do different things depending on which GPIOs they're assigned to. * Duplicate enum values have been intentionally defined for this. */ enum class GPIOMode : uint8_t { RXFIFO_THR_PKT = 1, PKT_SYNC_RXTX = 6, RSSI_UPDATE = 14, // GPIO3 and GPIO2 AGC_HOLD = 14, // GPIO1 AGC_UPDATE = 14, // GPIO0 SYNC_EVENT = 41, // GPIO2 HIGHZ = 48, HW0 = 51 }; /** * Configure a CC1200 GPIO pin. * @param gpioNumber Pin number, from 0-3. * @param mode Mode to set the pin to. * @param outputInvert Whether to invert the output of the pin. */ void configureGPIO(uint8_t gpioNumber, GPIOMode mode, bool outputInvert = false); // RF configuration // ------------------------------------------------------------------------------ /** * Set up the radio for FIFO mode. */ void configureFIFOMode(); enum class PacketMode : uint8_t { /// Use fixed length packets. VARIABLE_LENGTH = 0b1, /// Use variable length packets, the length is encoded in the first byte of the packet. FIXED_LENGTH = 0b0 }; /** * Set the packet mode that the system will use * @param mode */ void setPacketMode(PacketMode mode); /** * Set the packet length when in fixed length packet mode. * The bit length parameter can be used to send only the x most significant bits of the final byte. * 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. * @param length */ void setPacketLength(uint8_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 uint8_t _packetBitLength = 0; // extra bit length at end public: /** * Set whether the CRC check is enabled. This driver enables it by default. * Enabling CRC will cause a 16 bit checksum to be transmitted along with the packet. * It will be automatically checked by the receiving CC1200, and the packet will be discarded if the CRC * doesn't match. * * NOTE: it is not recommended to disable the CRC when using variable length mode. * If the length byte is corrupted and the CRC doesn't check this, then the driver could read * too little or too much from the chip's FIFO and cause the chip to enter the FIFO underflow state. * @param enabled */ void setCRCEnabled(bool enabled); enum class ModFormat : uint8_t { FSK_2 = 0x0, GFSK_2 = 0x1, ASK = 0x3, FSK_4 = 0x4, GFSK_4 = 0x5 }; /** * Set the modulation format of the radio. * @param format */ void setModulationFormat(ModFormat format); /** * Set the frequency deviation from the center frequency in Hz. * See user guide section 5.2.1 for details, and cc1200 datasheet section 4.10.2 for example values. */ void setFSKDeviation(float deviation); /** * Set the RF symbol rate in Hz. If this radio is to be used in receive mode you must call * setRXFilterBandwidth() after calling this function. * @param symbolRateHz */ void setSymbolRate(float symbolRateHz); /** * Set the approximate output power in dBm. * Must be between -16dBm and +14dBm. * @param outPower */ void setOutputPower(float outPower); // min power to use to turn the radio completely off const static float ASK_MIN_POWER_OFF; /** * Set the high and low output powers when transmitting in ASK mode. * Overrides the setOutputPower() power setting. * @param maxPower High output power. Must be between -16dBm and +14dBm. * @param minPower Low output power. Must be between maxPower and -17.5dBm. -17.5dBm gives completely off, * so OOK modulation instead of ASK. */ void setASKPowers(float maxPower, float minPower); // Radio band for the chip to operate on. // See user guide description for FS_CFG register. enum class Band : uint8_t { BAND_820_960MHz = 0x2, BAND_410_480MHz = 0x4, BAND_273_320MHz = 0x6, BAND_205_240MHz = 0x8, BAND_164_192MHz = 0xA, BAND_136_160MHz = 0xB }; /** * Set the radio band and specific frequency. See user guide section 9.12 for details. * Note: Frequency offsets are not currently implemented, so the frequency can't be * set at the finest resolution. However, the resolution should be fine for most applications. * (at 900MHz this function has a resolution of 152.5Hz) * @param band * @param frequencyHz */ void setRadioFrequency(Band band, float frequencyHz); /** * Set the the RX filter bandwidth. You must call this AFTER setting the symbol rate. * See user guide section 6.1 for details. * * NOTE: The symbol rate and the RX filter bandwidth must be compatible with each other. * See the user guide for details. * * A number of different registers must be configured in order to properly configure the radio for a given bandwidth. * This call currently sets the following register fields: * - CHAN_BW.ADC_CIC_DECFACT * - CHAN_BW.BB_CIC_DECFACT * - MDMCFG1.DVGA_GAIN * - MDMCFG0.DATA_FILTER_EN * - SYNC_CFG0.RX_CONFIG_LIMITATION * * @param bandwidthHz the bandwidth in Hz */ void setRXFilterBandwidth(float bandwidthHz); /** * Get the ADC CIC decimation that was calculated by the most recent setRXFilterBandwidth() call. * This is used for certain other calculations such as the DC offset. * @return */ uint8_t getADCCICDecimation() { return adcCicDecimation; } /** * Configure the radio's automatic DC offset removal algorithm is 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. * * See the datasheet register description for DCFILT_CFG for explanations of what these values do. * Maybe you'll actually be able to make some sense out of what it says... I sure couldn't. * * @param enableAutoFilter Whether automatic filtering is enabled. * @param settlingCfg Settling time configuration bits. * @param cutoffCfg Cutoff frequency configuration bits. */ void configureDCFilter(bool enableAutoFilter, uint8_t settlingCfg, uint8_t cutoffCfg); /** * Set the IF mixing configuration. * See the user guide section on IF_MIX_CFG.CMIX_CFG for details. */ void setIFMixCFG(uint8_t value); /** * Set whether the ImageExtinct IQ mismatch compensation logic is enabled. * This should be disabled if IF < RX filter bandwidth * @param enabled */ void setIQMismatchCompensationEnabled(bool enabled); /** * Mode describing the size and setup of the sync word. * See user guide register description for SYNC_CFG1 */ enum class SyncMode : uint8_t { SYNC_NONE = 0, SYNC_11_BITS = 0b1, SYNC_16_BITS = 0b10, SYNC_18_BITS = 0b11, SYNC_24_BITS = 0b100, SYNC_32_BITS = 0b101, SYNC_16_BITS_HIGH_BYTE = 0b110, SYNC_16_BITS_DUAL = 0b111 }; /** * Configure the sync word settings of the radio. The sync word is the bit string sent before each packet -- the * radio knows to switch into receive mode when it detects it. Specific values with low autocorrelation should * be used for the sync word. * * @param syncWord Sync word value. * @param mode Sync word mode. Configures how many bits of the value are significant. * @param syncThreshold Correspondance threshold before the radio switches into receive mode. */ void configureSyncWord(uint32_t syncWord, SyncMode mode, uint8_t syncThreshold); /** * Check whether the frequency synthesizer is locked on to the correct frequency. * If not, then the correct RF frequency is not being used. * If the FS is not locking then check that the correct black box FS registers are applied * and that the FS has been calibrated. * @return */ bool isFSLocked(); /** * Configure the preamble that the radio is configured to send/receive. The main purpose of the preamble is to * provide receiving radios with a chance to calibrate their RX gain. However, you can also require that receiving * radios see a valid preamble before they can detect the sync word (this is not on by default). * * @param preambleLengthCfg Bits that determine the length of the preamble. See the PREAMBLE_CFG1 register description for details. Set to 0 disable transmitting a preamble. * @param preambleFormatCfg Bits that determine the format of the preamble. See the PREAMBLE_CFG1 register description for details. */ void configurePreamble(uint8_t preambleLengthCfg, uint8_t preambleFormatCfg); /** * Enum for different PA ramp times. */ enum class RampTime : uint8_t { RAMP_3_8_SYMBOL = 0b0, RAMP_3_2_SYMBOL = 0b1, RAMP_3_SYMBOL = 0b10, RAMP_6_SYMBOL = 0b11 }; /** * Enable the the power amplifier ramp-up curve and set its shape and time. * See section 7.1 for details. * This is also used to set the ASK ramping between different power levels. * * The PA will gradually ramp from off to full amplitude in rampTime relative to the * symbol rate. At 1/3 of rampTime it will have ramped to (firstRampLevel / 16) * full amplitude, * and at 2/3 of rampTime it will have ramped to ((secondRampLevel + 7) / 16) * full amplitude. */ void setPARampRate(uint8_t firstRampLevel, uint8_t secondRampLevel, RampTime rampTime); /** * Disable the power amplifier ramp-up curve. */ void disablePARamping(); // Automatic Gain Control (AGC) Config // ------------------------------------------------------------------------------ /** * Set the AGC reference level which is the internal target power level that * the AGC tries to adjust to. * * The user manual section 6.4 gives a rough formula to calculate this, but I've just used the SmartRF values. * * @param level Internal power level in dB. */ void setAGCReferenceLevel(uint8_t level); /** * Enum for possible AGC actions after is a sync word detection. * See AGC_CFG3 register description for more info. */ enum class SyncBehavior : uint8_t { FREEZE_NONE = 0b000, FREEZE_GAIN = 0b001, AGC_SLOWMODE = 0b010, FREEZE_BOTH = 0b011 }; /** * Set the AGC behavior after a sync word is detected. * @param behavior */ void setAGCSyncBehavior(SyncBehavior behavior); /** * Enum for possible gain tables to use. * See AGC_CFG2 register description for more info. */ enum class GainTable : uint8_t { OPTIMIZED_LINEARITY = 0b00, NORMAL = 0b01, LOW_POWER = 0b10, ZERO_IF = 0b11 }; /** * Set the gain table and min and max values within that table to use. * Min and max values are indexes into the current selected table. */ void setAGCGainTable(GainTable table, uint8_t minGainIndex, uint8_t maxGainIndex); /** * Configure the change in input signal power that must be sensed before the AGC starts to adjust itself. * See the register description for AGC_CFG0.AGC_HYST_LEVEL * @param hysteresisCfg */ void setAGCHysteresis(uint8_t hysteresisCfg); /** * Configure the rate that the AGC changes the receive gain. * See the register description for AGC_CFG0.AGC_SLEWRATE_LIMIT * @param slewrateCfg */ void setAGCSlewRate(uint8_t slewrateCfg); // Register level functions // ------------------------------------------------------------------------------ /** * Read a register and return the byte value. Also reads the radio's state. */ uint8_t readRegister(Register reg); /** * Write a register with a byte value. Also reads the radio's state. */ void writeRegister(Register reg, uint8_t value); /** * Write a series of consecutive registers with byte values. Also reads the radio's state. */ void writeRegisters(CC1200::Register startReg, uint8_t const *values, size_t numRegisters); /** * Write a series of consecutive registers with byte values. Also reads the radio's state. * Template version that takes an std::array. */ template<size_t numRegisters> void writeRegisters(CC1200::Register startReg, std::array<uint8_t, numRegisters> const & values) { writeRegisters(startReg, values.data(), values.size()); } /** * Read an extended register and return the byte value. Also reads the radio's state. */ uint8_t readRegister(ExtRegister reg); /** * Write an extended register with a byte value. Also reads the radio's state. */ void writeRegister(ExtRegister reg, uint8_t value); /** * Write a series of consecutive extended registers with byte values. Also reads the radio's state. */ void writeRegisters(CC1200::ExtRegister startReg, uint8_t const *values, size_t numRegisters); /** * Write a series of consecutive registers with byte values. Also reads the radio's state. * Template version that takes an std::array. */ template<size_t numRegisters> void writeRegisters(CC1200::ExtRegister startReg, std::array<uint8_t, numRegisters> const & values) { writeRegisters(startReg, values.data(), values.size()); } /** * Send a command. Also reads the radio's state. * @param command */ void sendCommand(Command command); /** * Update the current known state of the radio. */ void updateState() { sendCommand(Command::NOP); } /** * Get a byte from the RX FIFO. * @param address The byte address, from 0-127. */ uint8_t readRXFIFOByte(uint8_t address); // State change functions // ------------------------------------------------------------------------------ /** * Send the STX strobe to change the radio into TX state. * Valid when the radio is in IDLE, FAST_TX_ON, and RX. * A calibration will be performed if needed. * * The radio will stay in TX state until it is commanded to a different state, or a packet is * transmitted and it is configured to change states when this happens, or a FIFO error occurs (which * shouldn't be possible with the current configuration). */ void startTX() { sendCommand(Command::TX); } /** * Send the SRX strobe to change the radio into TX state. * Valid when the radio is in IDLE, FAST_TX_ON, and TX. * A calibration will be performed if needed. * * The radio will stay in RX state until it is commanded to a different state, or a packet is * received and it configured to change states when this happens, or a FIFO overflow occurs * (because the host is not reading data out fast enough). */ void startRX() { sendCommand(Command::RX); } /** * Send the radio into idle mode. Stops a currently running tx or rx. */ void idle() { sendCommand(Command::IDLE); } private: /** * Called whenever we get a status byte from another operation. Saves the info from it to member variables. * @param status */ void loadStatusByte(uint8_t status); }; #endif //LIGHTSPEEDRANGEFINDER_CC1200_H