For robots and stuff

Dependents:   Base Station

CC1101/CC1101.cpp

Committer:
jjones646
Date:
2014-12-31
Revision:
2:c42a035d71ed
Parent:
1:05a48c038381

File content as of revision 2:c42a035d71ed:

#include "CC1101.h"

#define DEBUG_MODE 0
#define ACK_SHOW 1

// 0db power
#define RF_DB 10

CC1101::CC1101() :
    Radio()
{  };

CC1101::CC1101(PinName mosi, PinName miso, PinName clk, PinName csn, PinName tx_led, PinName rx_led, PinName interpt, unsigned int xosc) :
    Radio(tx_led, rx_led),
    _crystal_freq(xosc)
{
    setup_spi(mosi, miso, clk);
    setup_pins(csn, interpt);
    setup_chip();
}

// Deconstructor
CC1101::~CC1101()
{
    if ( _spi ) {
        delete _spi;
    }
    if ( _rx_int ) {
        delete _rx_int;
    }
    if ( _csn ) {
        delete _csn;
    }
}

// Configuration method that is called from the constructors
void CC1101::setup_spi(PinName mosi, PinName miso, PinName clk)
{
    if (mosi != NC & miso != NC & clk != NC) {
        // Setup the spi for 8 bit data, high steady state clock, second edge capture
        _so = miso;
        _si = mosi;
        _sck = clk;
        _spi = new SPI(mosi, miso, clk);
        _spi->format(8,0);
        _spi->frequency(5000000);
    }
}


// Configuration method that is called from the constructors
void CC1101::setup_pins(PinName csn, PinName int_pin)
{
    if (csn != NC) {
        _csn = new DigitalOut(csn);
    }
    if (int_pin != NC) {
        InterruptIn *_rx_int = new InterruptIn(int_pin);
        _rx_int->mode(PullDown);
        _rx_int->rise(this, &CC1101::isr_receive);   // attach member function for interrupt trigger
    }
}

/*
// Configuration method that is called from the constructors
void CC1101::setup_lights(void)
{
    for (int i=0; i<10; i++) {
        _tx_led_thread.signal_set(SET_LED_TICK);
        _rx_led_thread.signal_set(SET_LED_TICK);
    }
}
*/

void CC1101::isr_receive(void)
{
    // do nothing
}


// Configuration method that is called from the constructors
void CC1101::setup_chip()
{
    // define an initial state of an unselected chip
    *_csn = 1;

    // frequency that radio links with another CC1101 over the air
    _carrier_freq = _902MHZ_;

    // turn off address packet filtering and assign 0 (broadcast address) to the address value
    _pck_control.addr_check = ADDR_OFF;

    // these values determine how the CC1101 will handel a packet
    _pck_control.whitening_en = 0;

    // enable CRC calculation in TX and CRC checking in RX
    _pck_control.crc_en = 1;

    // enable automatically flushing the RX buffer on a bad CRC (only works if 1 packet is in the RX buffer)
    _pck_control.autoflush_en = 1;

    // enable appending 2 status bytes to the end of every packet that includes the CRC and
    _pck_control.status_field_en = 1;

    // normal packet mode uses RX and TX buffers
    _pck_control.format_type = FORMAT_DEFAULT;

    // setup how the payload of the packet is transmitted - default to a fixed length of 61 bytes
    _pck_control.length_type = PACKET_VARIABLE;

    //_pck_control.length_type = PACKET_FIXED;
    //_pck_control.size = 61;

    // this is a preamble threshold for determining when a packet should be accepted
    _pck_control.preamble_thresh = 2;

    // these values determine how the frequency bands and channels are distributed as well as defining the modulation type
    _modem.dc_filter_off_en = 0;
    _modem.manchester_encode_en = 0;
    _modem.fec_en = 0;

    // bandwidth configurations
    _modem.channel_bw = 2;
    _modem.channel_bw_exp = 0;
    _modem.channel_space_exp = 2;
    _modem.data_rate_exp = 13;

    _modem.mod_type = MOD_GFSK;
    _modem.sync_mode = SYNC_HIGH_ALLOW_TWO;
    _modem.preamble_bytes = PREAM_FOUR;

    //_send_count = 1;
    //_receive_count = 0;

    // the values assigned here are used for the frequency synthesizer control
    assign_if_freq(_316KHZ_);
    assign_freq_offset(0);

    // set all the configuration values into the rfSettings struct
    set_rf_settings();

    // get the chip's partnumber
    _partnum = status(CCxxx0_PARTNUM);

    // get the chip's version number and fail if different from what was expected
    _chip_version = status(CCxxx0_VERSION);
    uint8_t expected_version = 0x04;

    if (_chip_version != expected_version) {

        // write results to the log file before killing mbed from going any further
#if RJ_BOOT_LOG
        FILE *fp = fopen("/local/log.txt", "a");  // Open text file for tracking boot sequence results
        if (fp == NULL) {
            error("Could not get file pointer to log file\r\n");
        }
        fprintf(fp, "FATAL ERROR: CC1101 Version Error\n");
        fclose(fp);
#endif

        // flip the error led ON
        _err_led = !_err_led;
        
        _has_error = true;

        // send message over serial port
        std::printf(
            "[FATAL ERROR]\r\n"
            "  Wrong version number returned from chip's 'VERSION' register (Addr: 0x%02X)\r\n"
            "\r\n"
            "  Expected: 0x%02X\r\n"
            "  Found:    0x%02X\r\n"
            "\r\n"
            "  Troubleshooting Tips:\r\n"
            "    - Check that the chip is fully connected with no soldering errors\r\n"
            "    - Determine if chip is newer version & update firmware\r\n"
            , CCxxx0_VERSION, expected_version, _chip_version);
    }
}


void CC1101::powerUp(void)
{
#if DEBUG_MODE > 0
    std::printf("[CC1101 RADIO TRANSCEIVER INITILIZATION]\r\n");
#endif
    // execute a power-on-reset call to the CC1101 before writing all configuration values
    power_on_reset();

    // now send the assigned rfSettings struct to the CC1101 for full inililization
    init();

    scan();

    //  start the regular class operations for the thread. This should always be the last call in the constructor!
    _transmit_thread.signal_set(START_THREAD);
    _receive_thread.signal_set(START_THREAD);


#if DEBUG_MODE > 1
    uint8_t start_address = 0x00;
    uint16_t reg_count = 46;
    uint8_t buf[reg_count];

    read_reg(start_address ,buf, reg_count);
    std::printf("\r\nDumping CC1101 register contents\r\n  ___________________\r\n | Address |  Value  |\r\n |===================|\r\n");
    for (int i = 0; i<reg_count+1; i++) {
        std::printf(" |   0x%02X  |   0x%02X  |\r\n", start_address + i, buf[start_address + i]);
    }

    std::printf(" |_________|_________|\r\n\r\nDumping CC1101 status register conetnts\r\n  ___________________\r\n | Address |  Value  |\r\n |===================|\r\n");
    for (int i = 0; i<14; i++) {
        std::printf(" |   0x%02X  |   0x%02X  |\r\n", CCxxx0_PARTNUM + i, status(CCxxx0_PARTNUM + i));
    }
    std::printf(" |_________|_________|\r\n\r\n");
#endif

#if DEBUG_MODE > 0
    compute_freq();
    std::printf("    CC1101 READY!\r\n      Chip Version: %-02u\r\n      Channel:      %-02u\r\n      Frequency:    %-3.2f MHz\r\n      Baud Rate:    %3u kHz\r\n", _chip_version, Radio::channel(), static_cast<float>(Radio::freq()/1000000), _baud_rate/1000 );
#endif

}

// ===============
void CC1101::set_rf_settings()
{
    // set rfSettings carrier frequency fields
    freq(_carrier_freq);

    // set the fields for packet controls
    assign_packet_params();

    // set the fields for the frequency limits of the modem
    assign_modem_params();

    // assign an address to the CC1101. This can be used to filter packets
    rfSettings.ADDR = _addr;

    // there can be 16 different channel numbers. The bandwidth and spacing are defined in other registers
    rfSettings.CHANNR = _channel;

    // compute the final adjusted frequency so that it will be correct after the constructor
    compute_freq();

    assign_baud_rate(250000);  // 250 kBaud

    assign_channel_spacing(200000);    // 200kHz

    // disable GDO0
    rfSettings.IOCFG0 = 0x2E;

    // setup for serial synchronous data output
    rfSettings.IOCFG1 = 0x0C;

    // setup for going HIGH when packet received and CRC is ok
    rfSettings.IOCFG2 = 0x07;

    rfSettings.DEVIATN = 0x62;

    rfSettings.FREND1 = 0xB6;
    rfSettings.FREND0 = 0x10;

    bool RX_TIME_RSSI = 0;
    bool RX_TIME_QUAL = 0;
    uint8_t RX_TIME = 0x07;    // no timeout

    rfSettings.MCSM2 = (RX_TIME_RSSI<<4) | (RX_TIME_QUAL<<3) | (RX_TIME & 0x07);

    uint8_t CCA_MODE = 0x00;
    uint8_t RXOFF_MODE = 0x00;  // go directly to IDLE when existing RX
    //uint8_t RXOFF_MODE = 0x03;  // stay in RX when existing RX
    uint8_t TXOFF_MODE = 0x03;  // go directly to RX when existing TX
    // uint8_t TXOFF_MODE = 0x00;  // go directly to IDLE when existing TX

    rfSettings.MCSM1 = ((CCA_MODE & 0x03)<<4) | ((RXOFF_MODE & 0x03)<<2) | (TXOFF_MODE & 0x03);

    uint8_t FS_AUTOCAL = 0x01;  // calibrate when going from IDLE to RX or TX
    uint8_t PO_TIMEOUT = 0x02;
    bool PIN_CTRL_EN = 0;
    bool XOSC_FORCE_ON = 0;

    rfSettings.MCSM0 = ((FS_AUTOCAL & 0x03)<<4) | ((PO_TIMEOUT & 0x03)<<2) | (PIN_CTRL_EN<<1) | (XOSC_FORCE_ON);

    bool FOC_BS_CS_GATE = 0;
    uint8_t FOC_PRE_K = 0x03;
    bool FOC_POST_K = 1;
    uint8_t FOC_LIMIT = 0x01;

    rfSettings.FOCCFG = 0x40 | (FOC_BS_CS_GATE<<5) | ((FOC_PRE_K & 0x03)<<3) | (FOC_POST_K<<2) | (FOC_LIMIT & 0x03);

    rfSettings.BSCFG = 0x1C;

    rfSettings.AGCCTRL2 = 0xC7;
    rfSettings.AGCCTRL1 = 0x00;
    rfSettings.AGCCTRL0 = 0xB0;

    // rfSettings.FIFOTHR = 0x0F;   // RXFIFO and TXFIFO thresholds.

    // 33 byte TX FIFO & 32 byte RX FIFO
    rfSettings.FIFOTHR = 0x07;   // RXFIFO and TXFIFO thresholds.
}

#if RF_DB == 0
// PATABLE (0 dBm output power)
char paTable[] = {0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
#elif RF_DB == 10
// PATABLE (10 dBm output power)
char paTable[] = {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
#endif


// ===============
void CC1101::assign_baud_rate(uint32_t rate)
{
    // update the baud rate class member
    _baud_rate = rate;

    // have to be careful with bit shifting here since it requires a large amount of shifts
    uint32_t shift_val = 28 - (_modem.data_rate_exp & 0x0F);

    // compute the register value and assign it
    rfSettings.MDMCFG3 = ((_baud_rate)/(_crystal_freq>>shift_val)) - 256;
}


// ===============
void CC1101::assign_channel_spacing(uint32_t freq)
{
    // update the channel spacing frequency's class member
    _channel_spacing = freq;

    // have to be careful with bit shifting here since it requires a large amount of shifts
    uint32_t shift_val = 18 - (_modem.channel_space_exp & 0x03);

    // compute the register value and assign it
    rfSettings.MDMCFG0 = (_channel_spacing/(_crystal_freq>>shift_val)) - 256;
}


// ===============
void CC1101::assign_modem_params()
{
    rfSettings.MDMCFG4 = (_modem.channel_bw_exp & 0x03)<<6 | (_modem.channel_bw & 0x03)<<4 | (_modem.data_rate_exp & 0x0F);
    rfSettings.MDMCFG2 = _modem.dc_filter_off_en<<7 | (_modem.mod_type & 0x07)<<4 | _modem.manchester_encode_en<<3 | (_modem.sync_mode & 0x07);
    rfSettings.MDMCFG1 = _modem.fec_en<<7 | (_modem.preamble_bytes & 0x07)<<4 | (_modem.channel_space_exp & 0x03);
}

// ===============
void CC1101::assign_packet_params()
{
    rfSettings.PCKCTRL0 = _pck_control.whitening_en<<6 | (_pck_control.format_type & 0x3)<<4 | _pck_control.crc_en<<2 | (_pck_control.length_type & 0x3);
    rfSettings.PCKCTRL1 = (_pck_control.preamble_thresh & 0x07)<<5 | _pck_control.autoflush_en<<3 | _pck_control.status_field_en<<2 | (_pck_control.addr_check & 0x03);
    rfSettings.PCKLEN = _pck_control.size;
}


void CC1101::assign_if_freq(uint32_t freq)
{
    // The desired IF frequency for RX. Subtracted from FS base frequency in RX.
    // bits 7..5 are always 0
    _if_freq = freq;
    rfSettings.FSCTRL1 = (_if_freq/(_crystal_freq>>10)) & 0x1F;
    rfSettings.FSCTRL0 = 0x00;  // set the initial freq calibration to 0
}


// computes the final adjusted operating frequency
void CC1101::compute_freq()
{
    // there's no need to make this heavy computation numerous times when it rarely ever changes - that's why it has its own class method
    uint32_t freq = (rfSettings.FREQ2<<16) | (rfSettings.FREQ1<<8) | (rfSettings.FREQ0);
    uint32_t offset = (_channel & 0xFF)*((256 + rfSettings.MDMCFG0)<<(_modem.channel_space_exp & 0x03));
    _freq = (_crystal_freq>>16)*(freq + offset);    // set the frequency from the base class
}


// SET FREQUENCY
void CC1101::freq(uint32_t freq)
{
    /* calculate the value that is written to the register for settings the base frequency
     * that the CC1101 should use for sending/receiving over the air. Default value is equivalent
     * to 901.83 MHz.
     */

    // update the class's frequency value
    _carrier_freq = freq;

    // this is split into 3 bytes that are written to 3 different registers on the CC1101
    uint32_t reg_freq = _carrier_freq / (_crystal_freq>>16);

    rfSettings.FREQ2 = (reg_freq>>16) & 0xFF;   // high byte, bits 7..6 are always 0 for this register
    rfSettings.FREQ1 = (reg_freq>>8) & 0xFF;    // middle byte
    rfSettings.FREQ0 = reg_freq & 0xFF;         // low byte
}


// set the device's hardware address (8 bits)
void CC1101::address(uint8_t addr)
{
    _addr = addr;
}


// returns the CC1101's VERSION register that specifices what exact chip version is being used
uint8_t CC1101::version(void)
{
    return _chip_version;
}


// return's the part number for the CC1101 chip being used
uint8_t CC1101::partnum(void)
{
    return _partnum;
}


// returns the current mode that the CC1101 is operating in
uint8_t CC1101::mode(void)
{
    return status(CCxxx0_MARCSTATE);
}


// returns the LQI value from the most recently received packet
uint8_t CC1101::lqi(void)
{
    return 0x3F - (_lqi & 0x3F);
}


// update the channel within the class and also update the CC1101's channel register
void CC1101::channel(uint8_t chan)
{
    // only update channel numbers that are valid (8 bits)
    if ( chan != _channel ) {
        // channels start at 0 for the CC1101. this subtracts 1 before saving the channel number for the reason defined in the _channel member's getter method
        _channel = chan;

        // update the value with the CC1101's channel number register
        write_reg(CCxxx0_CHANNR, _channel);

        // recalculate the adjusted frequency value since the channel number has changed
        compute_freq();

#if DEBUG_MODE > 0
        std::printf("\r\n[CHANNEL UPDATED]\r\n  Channel: %02u\r\n  Freq:    %3.2f MHz\r\n", _channel, static_cast<float>(_final_freq)/1000000);
#endif
    }
}


// returns the RSSI value from the most recently received packet
uint8_t CC1101::rssi(void)
{
    return _rssi;
}

void CC1101::rssi(uint8_t rssi_reg)
{
    int8_t temp;

    if (rssi_reg & 0x80) {
        temp = (rssi_reg - 256)>>1;
    } else {
        temp = rssi_reg>>1; // divide by 2
    }
    _rssi = temp - 74;
}


// Assign the offset frequency to the class definition
void CC1101::assign_freq_offset(uint8_t freq)
{
    _offset_freq = freq;
}


// Macro to calibrate the frequency synthesizer
void CC1101::calibrate()
{
    // Send the calibration strobe
    strobe(CCxxx0_SCAL);

    // Wait for the radio to leave the calibration step
    while( (mode() == 0x04) | (mode() == 0x05) );

    // The radio is now is IDLE mode, so go to RX mode
    rx_mode();

    // Wait for the radio to enter back into RX mode
    while( mode() != 0x0D );
}


// Macro to reset the CCxxx0 and wait for it to be ready
void CC1101::reset(void)
{
    strobe(CCxxx0_SRES);
}


void CC1101::power_on_reset(void)
{
#if DEBUG_MODE > 0
    std::printf("    Starting Power-on-Reset procedure...");
#endif
    delete _spi;

    // make sure chip is not selected
    *_csn = 1;

    DigitalOut *SI = new DigitalOut(_si);
    DigitalOut *SCK = new DigitalOut(_sck);
    DigitalIn *SO = new DigitalIn(_so);

    // bring SPI lines to a defined state. Reasons are outlined in CC1101 datasheet - section 11.3
    *SI = 0;
    *SCK = 1;

    // toggle chip select and remain in high state afterwards
    *_csn = 0;
    *_csn = 1;

    // wait at least 40us
    wait_us(45);

    // pull CSn low & wait for the serial out line to go low
    *_csn = 0;

    while(*SO);

    // cleanup everything before the mbed's SPI library calls take back over
    delete SI;
    delete SO;
    delete SCK;

    // reestablish the SPI bus and call the reset strobe
    setup_spi(_si, _so, _sck);
    reset();

    delete _spi;
    // wait for the SO line to go low again. Once low, reset is complete and CC1101 is in IDLE state
    DigitalIn *SO2 = new DigitalIn(_so);
    while(*SO2);

    // make sure chip is deselected before returning
    *_csn = 1;

    // reestablish the SPI bus for the final time after removing the DigitalIn object
    delete SO2;
    setup_spi(_si, _so, _sck);

#if DEBUG_MODE > 0
    std::printf("done\r\n");
#endif
}

///////////////////////////////////////////////////////////////////////////////////////
uint8_t CC1101::status(void)
{
    return strobe(CCxxx0_SNOP);
}

///////////////////////////////////////////////////////////////////////////////////////
//  uint8_t status(uint8_t addr)
//
//  DESCRIPTION:
//      This function reads a CCxxx0 status register.
//
//  ARGUMENTS:
//      uint8_t addr
//          Address of the CCxxx0 status register to be accessed.
//
//  RETURN VALUE:
//      uint8_t
//          Value of the accessed CCxxx0 status register.
///////////////////////////////////////////////////////////////////////////////////////
uint8_t CC1101::status(uint8_t addr)
{
    *_csn = 0;
    _spi->write(addr | READ_BURST);
    tiny_delay();
    uint8_t x = _spi->write(0);
    *_csn = 1;
    return x;
}// status


///////////////////////////////////////////////////////////////////////////////////////
//  uint8_t strobe(uint8_t strobe)
//
//  DESCRIPTION:
//      Function for writing a strobe command to the CCxxx0
//
//  ARGUMENTS:
//      uint8_t strobe
//          strobe command
///////////////////////////////////////////////////////////////////////////////////////
uint8_t CC1101::strobe(uint8_t strobe)
{
    *_csn = 0;
    uint8_t x = _spi->write(strobe);
    *_csn = 1;
    return x;
}// strobe


///////////////////////////////////////////////////////////////////////////////////////
//  void put_rf_settings(rf_settings_t *pRfSettings)
//
//  DESCRIPTION:
//      This function is used to configure the CCxxx0 based on a given rf setting
//
//  ARGUMENTS:
//      rf_settings_t *pRfSettings
//          Pointer to a struct containing rf register settings
///////////////////////////////////////////////////////////////////////////////////////
void CC1101::put_rf_settings()
{
    write_reg(CCxxx0_IOCFG2,   rfSettings.IOCFG2);
    write_reg(CCxxx0_IOCFG1,   rfSettings.IOCFG1);
    write_reg(CCxxx0_IOCFG0,   rfSettings.IOCFG0);
    write_reg(CCxxx0_FIFOTHR,  rfSettings.FIFOTHR);
    // SYNC1
    // SYNC0
    write_reg(CCxxx0_PCKLEN,   rfSettings.PCKLEN);
    write_reg(CCxxx0_PCKCTRL1, rfSettings.PCKCTRL1);
    write_reg(CCxxx0_PCKCTRL0, rfSettings.PCKCTRL0);
    write_reg(CCxxx0_ADDR,     rfSettings.ADDR);
    write_reg(CCxxx0_CHANNR,   rfSettings.CHANNR);
    write_reg(CCxxx0_FSCTRL1,  rfSettings.FSCTRL1);
    write_reg(CCxxx0_FSCTRL0,  rfSettings.FSCTRL0);
    write_reg(CCxxx0_FREQ2,    rfSettings.FREQ2);
    write_reg(CCxxx0_FREQ1,    rfSettings.FREQ1);
    write_reg(CCxxx0_FREQ0,    rfSettings.FREQ0);
    write_reg(CCxxx0_MDMCFG4,  rfSettings.MDMCFG4);
    write_reg(CCxxx0_MDMCFG3,  rfSettings.MDMCFG3);
    write_reg(CCxxx0_MDMCFG2,  rfSettings.MDMCFG2);
    write_reg(CCxxx0_MDMCFG1,  rfSettings.MDMCFG1);
    write_reg(CCxxx0_MDMCFG0,  rfSettings.MDMCFG0);
    write_reg(CCxxx0_DEVIATN,  rfSettings.DEVIATN);
    write_reg(CCxxx0_MCSM2 ,   rfSettings.MCSM2);
    write_reg(CCxxx0_MCSM1 ,   rfSettings.MCSM1);
    write_reg(CCxxx0_MCSM0 ,   rfSettings.MCSM0 );
    write_reg(CCxxx0_FOCCFG,   rfSettings.FOCCFG);
    write_reg(CCxxx0_BSCFG,    rfSettings.BSCFG);
    write_reg(CCxxx0_AGCCTRL2, rfSettings.AGCCTRL2);
    write_reg(CCxxx0_AGCCTRL1, rfSettings.AGCCTRL1);
    write_reg(CCxxx0_AGCCTRL0, rfSettings.AGCCTRL0);
    // WOREVT1
    // WOREVT0
    // WORCTRL
    write_reg(CCxxx0_FREND1,   rfSettings.FREND1);
    write_reg(CCxxx0_FREND0,   rfSettings.FREND0);
    // FSCAL3
    // FSCAL2
    // FSCAL1
    //write_reg(CCxxx0_FSCAL0,   rfSettings.FSCAL0);
    // PCCTRL1
    // PCCTRL0
    // FSTEST
    // PTEST
    // AGCTEST
    // TEST2
    // TEST1
    // TEST0
}   // put_rf_settings


// main ititilization
void CC1101::init(void)
{
    // strobe(CCxxx0_SIDLE);
    // send all configuration values to the CC1101 registers
#if DEBUG_MODE > 0
    std::printf("    Writing configuration registers...");
#endif
    put_rf_settings();
    write_reg(CCxxx0_PATABLE, paTable[0]);
#if DEBUG_MODE > 0
    std::printf("done\r\n");
#endif


#if DEBUG_MODE > 0
    std::printf("    Calibrating...");
#endif
    calibrate();
//    while( (mode() == 0x04) | (mode() == 0x05) );   // wait until it leaves calibration
    write_reg(CCxxx0_FSCTRL0,  status(CCxxx0_FREQEST));
#if DEBUG_MODE > 0
    std::printf("done\r\n");
#endif


    // flush TX and RX buffers before beginning
#if DEBUG_MODE > 0
    std::printf("    Clearing TX and RX buffers...");
#endif
    flush_rx();
    flush_tx();
#if DEBUG_MODE > 0
    std::printf("done\r\n");
#endif


    // Enter RX mode
#if DEBUG_MODE > 0
    std::printf("    Entering RX mode...");
#endif
    rx_mode();
    while( mode() != 0x0D );    // wait until it enters RX state
#if DEBUG_MODE > 0
    std::printf("done\r\n");
#endif
}


///////////////////////////////////////////////////////////////////////////////////////
//  uint8_t read_reg(uint8_t addr)
//
//  DESCRIPTION:
//      This function gets the value of a single specified CCxxx0 register.
//
//  ARGUMENTS:
//      uint8_t addr
//          Address of the CCxxx0 register to be accessed.
//
//  RETURN VALUE:
//      uint8_t
//          Value of the accessed CCxxx0 register.
///////////////////////////////////////////////////////////////////////////////////////
uint8_t CC1101::read_reg(uint8_t addr)
{
    *_csn = 0;
    _spi->write(addr | READ_SINGLE);
    uint8_t x = _spi->write(0);
    *_csn = 1;

#if DEBUG_MODE > 1
    std::printf("\r\n== Single Register Read ==\r\n    Address: 0x%02X\r\n    Value:   0x%02X\r\n", addr, x);
#endif
    return x;
}   // read


///////////////////////////////////////////////////////////////////////////////////////
//  void read_reg(uint8_t addr, uint8_t *buffer, uint8_t count)
//
//  DESCRIPTION:
//      This function reads multiple CCxxx0 register, using SPI burst access.
//
//  ARGUMENTS:
//      uint8_t addr
//          Address of the first CCxxx0 register to be accessed.
//      uint8_t *buffer
//          Pointer to a byte array which stores the values read from a
//          corresponding range of CCxxx0 registers.
//      uint8_t count
//          Number of bytes to be read from the subsequent CCxxx0 registers.
///////////////////////////////////////////////////////////////////////////////////////
void CC1101::read_reg(uint8_t addr, uint8_t *buffer, uint8_t count)
{
    *_csn = 0;
    _spi->write(addr | READ_BURST);
    tiny_delay();
    for (uint8_t i = 0; i < count; i++) {
        buffer[i] = _spi->write(0);
        tiny_delay();
    }
    *_csn = 1;

#if DEBUG_MODE > 1
    std::printf("\r\n== Burst Register Read ==\r\n    Address: 0x%02X\r\n    Bytes:   %u\r\n", addr, count);
#endif
}   // read


///////////////////////////////////////////////////////////////////////////////////////
//  void write_reg(uint8_t addr, uint8_t value)
//
//  DESCRIPTION:
//      Function for writing to a single CCxxx0 register
//
//  ARGUMENTS:
//      uint8_t addr
//          Address of a specific CCxxx0 register to accessed.
//      uint8_t value
//          Value to be written to the specified CCxxx0 register.
///////////////////////////////////////////////////////////////////////////////////////
void CC1101::write_reg(uint8_t addr, uint8_t value)
{
    *_csn = 0;
    _spi->write(addr);
    _spi->write(value);
    *_csn = 1;

#if DEBUG_MODE > 1
    std::printf("\r\n== Single Register Write ==\r\n    Address: 0x%02X\r\n    Value:   0x%02X\r\n", _addr, value);
#endif
}   // write


///////////////////////////////////////////////////////////////////////////////////////
//  void write_reg(uint8_t addr, uint8_t *buffer, uint8_t count)
//
//  DESCRIPTION:
//      This function writes to multiple CCxxx0 register, using SPI burst access.
//
//  ARGUMENTS:
//      uint8_t addr
//          Address of the first CCxxx0 register to be accessed.
//      uint8_t *buffer
//          Array of bytes to be written into a corresponding range of
//          CCxx00 registers, starting by the address specified in _addr_.
//      uint8_t count
//          Number of bytes to be written to the subsequent CCxxx0 registers.
///////////////////////////////////////////////////////////////////////////////////////
void CC1101::write_reg(uint8_t addr, uint8_t *buffer, uint8_t count)
{
    *_csn = 0;
    _spi->write(addr | WRITE_BURST);
    tiny_delay();
    for (uint8_t i = 0; i < count; i++) {
        _spi->write(buffer[i]);
        tiny_delay();
    }
    *_csn = 1;

#if DEBUG_MODE > 1
    std::printf("\r\n== Burst Register Write ==\r\n    Address: 0x%02X\r\n    Bytes:   %u\r\n", addr, count);
#endif
}   // write


void CC1101::tiny_delay(void)
{
    int i = 0;
    while(i < 5) {
        i++;
    }
}



///////////////////////////////////////////////////////////////////////////////////////
//  void put_pck(uint8_t *txBuffer, uint8_t size)
//
//  DESCRIPTION:
//      This function can be used to transmit a packet with packet length up to 63 bytes.
//
//  ARGUMENTS:
//      uint8_t *txBuffer
//          Pointer to a buffer containing the data that are going to be transmitted
//
//      uint8_t size
//          The size of the txBuffer
//
void CC1101::put_pck(uint8_t *txBuffer, uint8_t size)
{
    // Blink the TX LED
    _tx_led_thread.signal_set(SET_LED_TICK);

    // move all values down by 1 to make room for the packet size value
    for (uint8_t i = size; i > 0; i--)
        txBuffer[i] = txBuffer[i-1];

    // place the packet's size as the array's first value
    txBuffer[0] = size++;

#if DEBUG_MODE > 0
    std::printf("\r\n[PACKET TRANSMITTED]\r\n  Bytes: %u\r\n  ACK:   %sRequested\r\n", size, (ack==1 ? "":"Not ") );
#endif

    // send the data to the CC1101
    write_reg(CCxxx0_TXFIFO, txBuffer, size);

#if DEBUG_MODE > 1
    Timer t1;
#endif
    // enter the TX state
    strobe(CCxxx0_STX);

    /*  For the debug mode, this will determine how long the CC1101 takes to transmit everything in the TX buffer
        and enter or return to the RX state.
    */
#if DEBUG_MODE > 1
    t1.start();
    float ti = t1.read();
#endif


    // wait until radio enters back to the RX state. takes very few cycles, so might as well wait before returning to elimate querky errors
    while(mode() != 0x0D);

#if DEBUG_MODE > 1
    t1.stop();
    std::printf("  Time:  %02.4f ms\r\n", (t1.read() - ti)*1000);
#endif

}   // put_pck


///////////////////////////////////////////////////////////////////////////////////////
//  bool get_pck(uint8_t *rxBuffer, uint8_t *length)
//
//  DESCRIPTION:
//      This function can be used to receive a packet of variable packet length (first byte in the packet
//      must be the length byte). The packet length should not exceed the RX FIFO size.
//
//  ARGUMENTS:
//      uint8_t *rxBuffer
//          Pointer to the buffer where the incoming data should be stored
//      uint8_t *length
//          Pointer to a variable containing the size of the buffer where the incoming data should be
//          stored. After this function returns, that variable holds the packet length.
//
//  RETURN VALUE:
//      BOOL
//          1:  CRC OK
//          0:  CRC NOT OK (or no packet was put in the RX FIFO due to filtering)
//
bool CC1101::get_pck(uint8_t *rxBuffer, uint8_t *length)
{
    // Blink the RX LED
    _rx_led_thread.signal_set(SET_LED_TICK);

    // Update the frequency offset estimate
    write_reg(CCxxx0_FSCTRL0, status(CCxxx0_FREQEST));

    // Get the packet's size
    uint8_t rx_size;
    read_reg(CCxxx0_RXFIFO, &rx_size, 1);

    if (rx_size & BYTES_IN_RXFIFO) {

        // Read data from RX FIFO and store in rxBuffer
        if (rx_size <= *length) {

            *length = rx_size;
            read_reg(CCxxx0_RXFIFO, rxBuffer, *length);

            // Read the 2 appended status bytes (status[0] = RSSI, status[1] = LQI)
            uint8_t status_bytes[2];
            read_reg(CCxxx0_RXFIFO, status_bytes, 2);

            // update the RSSI reading
            rssi(status_bytes[RSSI]);
            _lqi = status_bytes[LQI] & 0x7F; // MSB of LQI is the CRC_OK bit

#if DEBUG_MODE > 0
            std::printf("\r\n[PACKET RECEIVED]\r\n  Bytes: %u\r\n", *length);
            std::printf("  RSSI: %ddBm\r\n", _rssi);
            std::printf("  LQI:  %u\r\n", _lqi);
#endif

            if (_p_count++%5 == 0) {
                //calibrate the frequency synthesizer
                calibrate();
            }

            // Go back to the receiving state since CC1101 is configured for transitioning to IDLE on receiving a packet
            rx_mode();
            return 1;

        } else {
            *length = rx_size;
        }
    }

    flush_rx();
    return 0;

}   // get_pck


void CC1101::flush_rx(void)
{
    // Make sure that the radio is in IDLE state before flushing the FIFO
    idle();

    // Flush RX FIFO
    strobe(CCxxx0_SFRX);

    // Enter back into a RX state
    rx_mode();
}


void CC1101::flush_tx(void)
{
    // Make sure that the radio is in IDLE state before flushing the FIFO
    idle();

    // Flush TX FIFO
    strobe(CCxxx0_SFTX);

    // Enter back into a RX state
    rx_mode();
}


void CC1101::rx_mode(void)
{
    //strobe(CCxxx0_SIDLE);
    strobe(CCxxx0_SRX);
    //while(mode() != 0x0D);
}


void CC1101::tx_mode(void)
{
    //strobe(CCxxx0_SIDLE);
    strobe(CCxxx0_STX);
    // while(mode() != 0x13);
}

void CC1101::idle(void)
{
    // Send the IDLE strobe
    strobe(CCxxx0_SIDLE);
    // Wait before returning
    // === THIS LIKELY ISN'T NEEDED ===
    while( mode() != 0x01);
}