Port of the nRF24l01+ library of these dudes. Not GPLed, so yeah, you can use it. Copyright (c) 2007 Stefan Engelke <mbox@stefanengelke.de> Some parts copyright (c) 2012 Eric Brundick <spirilis [at] linux dot com>
Diff: Enrf24.cpp
- Revision:
- 0:670ecbc1478a
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Enrf24.cpp Tue May 28 03:48:04 2013 +0000 @@ -0,0 +1,556 @@ +/* nRF24L01+ I/O for mbed + * Ported by Jas Strong <jasmine@heroicrobotics.com> + * + * Copyright (c) 2013 Eric Brundick <spirilis [at] linux dot com> + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "mbed.h" +#include <stdint.h> +#include "Enrf24.h" + +/* Constructor */ +Enrf24::Enrf24(PinName cePin, PinName csnPin, PinName irqPin, PinName miso, PinName mosi, PinName sck) : + _cePin(cePin), + _csnPin(csnPin), + _irqPin(irqPin), + _miso(miso), + _mosi(mosi), + _sck(sck), + _spi(mosi, miso, sck) +{ + + rf_status = 0; + rf_addr_width = 5; + txbuf_len = 0; + readpending = 0; +} + +/* Initialization */ +void Enrf24::begin(uint32_t datarate, uint8_t channel) +{ + _cePin = 0; + _csnPin = 1; + + _spi.format(8,0); + _spi.frequency(2000000); + + _spi.write(0); // Strawman transfer, fixes USCI issue on G2553 + + // Is the transceiver present/alive? + if (!_isAlive()) + return; // Nothing more to do here... + + // Init certain registers + _writeReg(RF24_CONFIG, 0x00); // Deep power-down, everything disabled + _writeReg(RF24_EN_AA, 0x03); + _writeReg(RF24_EN_RXADDR, 0x03); + _writeReg(RF24_RF_SETUP, 0x00); + _writeReg(RF24_STATUS, ENRF24_IRQ_MASK); // Clear all IRQs + _writeReg(RF24_DYNPD, 0x03); + _writeReg(RF24_FEATURE, RF24_EN_DPL); // Dynamic payloads enabled by default + + // Set all parameters + if (channel > 125) + channel = 125; + deepsleep(); + _issueCmd(RF24_FLUSH_TX); + _issueCmd(RF24_FLUSH_RX); + readpending = 0; + _irq_clear(ENRF24_IRQ_MASK); + setChannel(channel); + setSpeed(datarate); + setTXpower(); + setAutoAckParams(); + setAddressLength(rf_addr_width); + setCRC(true); // Default = CRC on, 8-bit +} + +/* Formal shut-down/clearing of library state */ +void Enrf24::end() +{ + txbuf_len = 0; + rf_status = 0; + rf_addr_width = 5; + + if (!_isAlive()) + return; + deepsleep(); + _issueCmd(RF24_FLUSH_TX); + _issueCmd(RF24_FLUSH_RX); + readpending = 0; + _irq_clear(ENRF24_IRQ_MASK); + _cePin = 0; + _csnPin = 1; +} + +/* Basic SPI I/O */ +uint8_t Enrf24::_readReg(uint8_t addr) +{ + uint8_t result; + + _csnPin = 0; + rf_status = _spi.write(RF24_R_REGISTER | addr); + result = _spi.write(RF24_NOP); + _csnPin = 1; + return result; +} + +void Enrf24::_readRegMultiLSB(uint8_t addr, uint8_t *buf, size_t len) +{ + uint8_t i; + _csnPin = 0; + rf_status = _spi.write(RF24_R_REGISTER | addr); + for (i=0; i<len; i++) { + buf[len-i-1] = _spi.write(RF24_NOP); + } + _csnPin = 1; +} + +void Enrf24::_writeReg(uint8_t addr, uint8_t val) +{ + _csnPin=0; + rf_status = _spi.write(RF24_W_REGISTER | addr); + _spi.write(val); + _csnPin=1; +} + +void Enrf24::_writeRegMultiLSB(uint8_t addr, uint8_t *buf, size_t len) +{ + size_t i; + + _csnPin = 0; + rf_status = _spi.write(RF24_W_REGISTER | addr); + for (i=0; i<len; i++) { + _spi.write(buf[len-i-1]); + } + _csnPin = 1; +} + +void Enrf24::_issueCmd(uint8_t cmd) +{ + _csnPin=0; + rf_status = _spi.write(cmd); + _csnPin=1; +} + +void Enrf24::_issueCmdPayload(uint8_t cmd, uint8_t *buf, size_t len) +{ + size_t i; + + _csnPin = 0; + rf_status = _spi.write(cmd); + for (i=0; i<len; i++) { + _spi.write(buf[i]); + } + _csnPin = 1; +} + +void Enrf24::_readCmdPayload(uint8_t cmd, uint8_t *buf, size_t len, size_t maxlen) +{ + size_t i; + + _csnPin = 0; + rf_status = _spi.write(cmd); + for (i=0; i<len; i++) { + if (i < maxlen) { + buf[i] = _spi.write(RF24_NOP); + } else { + _spi.write(RF24_NOP); // Beyond maxlen bytes, just discard the remaining data. + } + } + _csnPin = 1; +} + +boolean Enrf24::_isAlive() +{ + uint8_t aw; + + aw = _readReg(RF24_SETUP_AW); + return ((aw & 0xFC) == 0x00 && (aw & 0x03) != 0x00); +} + +uint8_t Enrf24::_irq_getreason() +{ + lastirq = _readReg(RF24_STATUS) & ENRF24_IRQ_MASK; + return lastirq; +} + +// Get IRQ from last known rf_status update without querying module over SPI. +uint8_t Enrf24::_irq_derivereason() +{ + lastirq = rf_status & ENRF24_IRQ_MASK; + return lastirq; +} + +void Enrf24::_irq_clear(uint8_t irq) +{ + _writeReg(RF24_STATUS, irq & ENRF24_IRQ_MASK); +} + +#define ENRF24_CFGMASK_CRC(a) (a & (RF24_EN_CRC | RF24_CRCO)) + +void Enrf24::_readTXaddr(uint8_t *buf) +{ + _readRegMultiLSB(RF24_TX_ADDR, buf, rf_addr_width); +} + +void Enrf24::_writeRXaddrP0(uint8_t *buf) +{ + _writeRegMultiLSB(RF24_RX_ADDR_P0, buf, rf_addr_width); +} + + +/* nRF24 I/O maintenance--called as a "hook" inside other I/O functions to give + * the library a chance to take care of its buffers et al + */ +void Enrf24::_maintenanceHook() +{ + uint8_t i; + + _irq_getreason(); + + if (lastirq & ENRF24_IRQ_TXFAILED) { + lastTXfailed = true; + _issueCmd(RF24_FLUSH_TX); + _irq_clear(ENRF24_IRQ_TXFAILED); + } + + if (lastirq & ENRF24_IRQ_TX) { + lastTXfailed = false; + _irq_clear(ENRF24_IRQ_TX); + } + + if (lastirq & ENRF24_IRQ_RX) { + if ( !(_readReg(RF24_FIFO_STATUS) & RF24_RX_FULL) ) { /* Don't feel it's necessary + * to be notified of new + * incoming packets if the RX + * queue is full. + */ + _irq_clear(ENRF24_IRQ_RX); + } + + /* Check if RX payload is 0-byte or >32byte (erroneous conditions) + * Also check if data was received on pipe#0, which we are ignoring. + * The reason for this is pipe#0 is needed for receiving AutoACK acknowledgements, + * its address gets reset to the module's default and we do not care about data + * coming in to that address... + */ + _readCmdPayload(RF24_R_RX_PL_WID, &i, 1, 1); + if (i == 0 || i > 32 || ((rf_status & 0x0E) >> 1) == 0) { + /* Zero-width RX payload is an error that happens a lot + * with non-AutoAck, and must be cleared with FLUSH_RX. + * Erroneous >32byte packets are a similar phenomenon. + */ + _issueCmd(RF24_FLUSH_RX); + _irq_clear(ENRF24_IRQ_RX); + readpending = 0; + } else { + readpending = 1; + } + // Actual scavenging of RX queues is performed by user-directed use of read(). + } +} + + + +/* Public functions */ +boolean Enrf24::available(boolean checkIrq) +{ + if (checkIrq && _irqPin && readpending == 0) + return false; + _maintenanceHook(); + if ( !(_readReg(RF24_FIFO_STATUS) & RF24_RX_EMPTY) ) { + return true; + } + if (readpending) { + return true; + } + return false; +} + +size_t Enrf24::read(void *inbuf, uint8_t maxlen) +{ + uint8_t *buf = (uint8_t *)inbuf; + uint8_t plwidth; + + _maintenanceHook(); + readpending = 0; + if ((_readReg(RF24_FIFO_STATUS) & RF24_RX_EMPTY) || maxlen < 1) { + return 0; + } + _readCmdPayload(RF24_R_RX_PL_WID, &plwidth, 1, 1); + _readCmdPayload(RF24_R_RX_PAYLOAD, buf, plwidth, maxlen); + buf[plwidth] = '\0'; // Zero-terminate in case this is a string. + if (_irq_derivereason() & ENRF24_IRQ_RX) { + _irq_clear(ENRF24_IRQ_RX); + } + + return (size_t) plwidth; +} + +// Perform TX of current ring-buffer contents +void Enrf24::flush() +{ + uint8_t reg, addrbuf[5]; + boolean enaa=false, origrx=false; + + if (!txbuf_len) + return; // Zero-length buffer? Nothing to send! + + reg = _readReg(RF24_FIFO_STATUS); + if (reg & BIT5) { // RF24_TX_FULL #define is BIT0, which is not the correct bit for FIFO_STATUS. + return; // Should never happen, but nonetheless a precaution to take. + } + + _maintenanceHook(); + + if (reg & RF24_TX_REUSE) { + // If somehow TX_REUSE is enabled, we need to flush the TX queue before loading our new payload. + _issueCmd(RF24_FLUSH_TX); + } + + if (_readReg(RF24_EN_AA) & 0x01 && (_readReg(RF24_RF_SETUP) & 0x28) != 0x20) { + /* AutoACK enabled, must write TX addr to RX pipe#0 + * Note that 250Kbps doesn't support auto-ack, so we check RF24_RF_SETUP to verify that. + */ + enaa = true; + _readTXaddr(addrbuf); + _writeRXaddrP0(addrbuf); + } + + reg = _readReg(RF24_CONFIG); + if ( !(reg & RF24_PWR_UP) ) { + _writeReg(RF24_CONFIG, ENRF24_CFGMASK_IRQ | ENRF24_CFGMASK_CRC(reg) | RF24_PWR_UP); + wait_us(5000); // 5ms delay required for nRF24 oscillator start-up + } + if (reg & RF24_PRIM_RX) { + origrx=true; + _cePin = 0; + _writeReg(RF24_CONFIG, ENRF24_CFGMASK_IRQ | ENRF24_CFGMASK_CRC(reg) | RF24_PWR_UP); + } + + _issueCmdPayload(RF24_W_TX_PAYLOAD, txbuf, txbuf_len); + _cePin = 1; + wait_us(30); + _cePin = 0; + + txbuf_len = 0; // Reset TX ring buffer + + while (_irqPin) // Wait until IRQ fires + ; + // IRQ fired + _maintenanceHook(); // Handle/clear IRQ + + // Purge Pipe#0 address (set to module's power-up default) + if (enaa) { + addrbuf[0] = 0xE7; addrbuf[1] = 0xE7; addrbuf[2] = 0xE7; addrbuf[3] = 0xE7; addrbuf[4] = 0xE7; + _writeRXaddrP0(addrbuf); + } + + // If we were in RX mode before writing, return back to RX mode. + if (origrx) { + enableRX(); + } +} + +void Enrf24::purge() +{ + txbuf_len = 0; +} + +size_t Enrf24::write(uint8_t c) +{ + if (txbuf_len == 32) { // If we're trying to stuff an already-full buffer... + flush(); // Blocking OTA TX + } + + txbuf[txbuf_len] = c; + txbuf_len++; + + return 1; +} + +uint8_t Enrf24::radioState() +{ + uint8_t reg; + + if (!_isAlive()) + return ENRF24_STATE_NOTPRESENT; + + reg = _readReg(RF24_CONFIG); + if ( !(reg & RF24_PWR_UP) ) + return ENRF24_STATE_DEEPSLEEP; + + // At this point it's either Standby-I, II or PRX. + if (reg & RF24_PRIM_RX) { + if (_cePin) + return ENRF24_STATE_PRX; + // PRIM_RX=1 but CE=0 is a form of idle state. + return ENRF24_STATE_IDLE; + } + // Check if TX queue is empty, if so it's idle, if not it's PTX. + if (_readReg(RF24_FIFO_STATUS) & RF24_TX_EMPTY) + return ENRF24_STATE_IDLE; + return ENRF24_STATE_PTX; +} + +void Enrf24::deepsleep() +{ + uint8_t reg; + + reg = _readReg(RF24_CONFIG); + if (reg & (RF24_PWR_UP | RF24_PRIM_RX)) { + _writeReg(RF24_CONFIG, ENRF24_CFGMASK_IRQ | ENRF24_CFGMASK_CRC(reg)); + } + _cePin = 0; +} + +void Enrf24::enableRX() +{ + uint8_t reg; + + reg = _readReg(RF24_CONFIG); + _writeReg(RF24_CONFIG, ENRF24_CFGMASK_IRQ | ENRF24_CFGMASK_CRC(reg) | RF24_PWR_UP | RF24_PRIM_RX); + _cePin = 1; + + if ( !(reg & RF24_PWR_UP) ) { // Powering up from deep-sleep requires 5ms oscillator start delay + wait(5 / 1000); + } +} + +void Enrf24::disableRX() +{ + uint8_t reg; + + _cePin = 0; + + reg = _readReg(RF24_CONFIG); + if (reg & RF24_PWR_UP) { /* Keep us in standby-I if we're coming from RX mode, otherwise stay + * in deep-sleep if we call this while already in PWR_UP=0 mode. + */ + _writeReg(RF24_CONFIG, ENRF24_CFGMASK_IRQ | ENRF24_CFGMASK_CRC(reg) | RF24_PWR_UP); + } else { + _writeReg(RF24_CONFIG, ENRF24_CFGMASK_IRQ | ENRF24_CFGMASK_CRC(reg)); + } +} + +void Enrf24::autoAck(boolean onoff) +{ + uint8_t reg; + + reg = _readReg(RF24_EN_AA); + if (onoff) { + if ( !(reg & 0x01) || !(reg & 0x02) ) { + _writeReg(RF24_EN_AA, 0x03); + } + } else { + if (reg & 0x03) { + _writeReg(RF24_EN_AA, 0x00); + } + } +} + +void Enrf24::setChannel(uint8_t channel) +{ + if (channel > 125) + channel = 125; + _writeReg(RF24_RF_CH, channel); +} + +void Enrf24::setTXpower(int8_t dBm) +{ + uint8_t reg, pwr; + + reg = _readReg(RF24_RF_SETUP) & 0xF8; // preserve RF speed settings + pwr = 0x03; + if (dBm < 0) + pwr = 0x02; + if (dBm < -6) + pwr = 0x01; + if (dBm < -12) + pwr = 0x00; + _writeReg(RF24_RF_SETUP, reg | (pwr << 1)); +} + +void Enrf24::setSpeed(uint32_t rfspeed) +{ + uint8_t reg, spd; + + reg = _readReg(RF24_RF_SETUP) & 0xD7; // preserve RF power settings + spd = 0x01; + if (rfspeed < 2000000) + spd = 0x00; + if (rfspeed < 1000000) + spd = 0x04; + _writeReg(RF24_RF_SETUP, reg | (spd << 3)); +} + +void Enrf24::setCRC(boolean onoff, boolean crc16bit) +{ + uint8_t reg, crcbits=0; + + reg = _readReg(RF24_CONFIG) & 0xF3; // preserve IRQ mask, PWR_UP/PRIM_RX settings + if (onoff) + crcbits |= RF24_EN_CRC; + if (crc16bit) + crcbits |= RF24_CRCO; + _writeReg(RF24_CONFIG, reg | crcbits); +} + +void Enrf24::setAutoAckParams(uint8_t autoretry_count, uint16_t autoretry_timeout) +{ + uint8_t setup_retr=0; + + setup_retr = autoretry_count & 0x0F; + autoretry_timeout -= 250; + setup_retr |= ((autoretry_timeout / 250) & 0x0F) << 4; + _writeReg(RF24_SETUP_RETR, setup_retr); +} + +void Enrf24::setAddressLength(size_t len) +{ + if (len < 3) + len = 3; + if (len > 5) + len = 5; + + _writeReg(RF24_SETUP_AW, len-2); + rf_addr_width = len; +} + +void Enrf24::setRXaddress(void *rxaddr) +{ + _writeRegMultiLSB(RF24_RX_ADDR_P1, (uint8_t*)rxaddr, rf_addr_width); +} + +void Enrf24::setTXaddress(void *rxaddr) +{ + _writeRegMultiLSB(RF24_TX_ADDR, (uint8_t*)rxaddr, rf_addr_width); +} + +boolean Enrf24::rfSignalDetected() +{ + uint8_t rpd; + + rpd = _readReg(RF24_RPD); + return (boolean)rpd; +}