Files
MySensors/drivers/RFM95/RFM95.cpp
T

469 lines
16 KiB
C++

/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2016 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* Based on Mike McCauley's RFM95 library, Copyright (C) 2014 Mike McCauley <mikem@airspayce.com>
* Radiohead http://www.airspayce.com/mikem/arduino/RadioHead/index.html
* RFM95 driver refactored and optimized for MySensors, Copyright (C) 2016 Olivier Mauti <olivier@mysensors.org>
*
*/
#include "RFM95.h"
LOCAL void RFM95_csn(const bool level) {
hwDigitalWrite(MY_RFM95_SPI_CS, level);
}
LOCAL uint8_t RFM95_spiMultiByteTransfer(const uint8_t cmd, uint8_t* buf, uint8_t len, const bool aReadMode) {
uint8_t status;
uint8_t* current = buf;
noInterrupts();
#if !defined(MY_SOFTSPI)
_SPI.beginTransaction(SPISettings(MY_RFM95_SPI_MAX_SPEED, MY_RFM95_SPI_DATA_ORDER, MY_RFM95_SPI_DATA_MODE));
#endif
RFM95_csn(LOW);
status = _SPI.transfer(cmd);
while (len--) {
if (aReadMode) {
status = _SPI.transfer((uint8_t)0x00);
if (buf != NULL) {
*current++ = status;
}
}
else status = _SPI.transfer(*current++);
}
RFM95_csn(HIGH);
#if !defined(MY_SOFTSPI)
_SPI.endTransaction();
#endif
interrupts();
return status;
}
// low level register access
LOCAL uint8_t RFM95_RAW_readByteRegister(const uint8_t address) {
return RFM95_spiMultiByteTransfer(address, NULL, 1, true);
}
// low level register access
LOCAL uint8_t RFM95_RAW_writeByteRegister(const uint8_t address, uint8_t value) {
return RFM95_spiMultiByteTransfer(address, &value, 1, false);
}
// macros, saves space
#define RFM95_readReg(__reg) RFM95_RAW_readByteRegister(__reg & RFM95_READ_REGISTER)
#define RFM95_writeReg(__reg, __value) RFM95_RAW_writeByteRegister((__reg | RFM95_WRITE_REGISTER), __value )
#define RFM95_burstReadReg(__reg, __buf, __len) RFM95_spiMultiByteTransfer( __reg & RFM95_READ_REGISTER, (uint8_t*)__buf, __len, true )
#define RFM95_burstWriteReg(__reg, __buf, __len) RFM95_spiMultiByteTransfer( __reg | RFM95_WRITE_REGISTER, (uint8_t*)__buf, __len, false )
// check RegPktRssiValue (0x1A) vs. RegRssiValue (0x1B)
LOCAL bool RFM95_initialise(const float frequency) {
RFM95_DEBUG(PSTR("RFM95:INIT\n"));
// reset radio module if rst pin defined
#if defined(MY_RFM95_RST_PIN)
hwPinMode(MY_RFM95_RST_PIN, OUTPUT);
hwDigitalWrite(MY_RFM95_RST_PIN, HIGH);
// 100uS
delayMicroseconds(100);
hwDigitalWrite(MY_RFM95_RST_PIN, LOW);
// wait until chip ready
delay(5);
#endif
// set variables
RFM95.address = RFM95_BROADCAST_ADDRESS;
RFM95.rxBufferValid = false;
RFM95.txSequenceNumber = 0; // initialise TX sequence counter
RFM95.powerLevel = 0;
RFM95.ATCenabled = false;
RFM95.ATCtargetRSSI = RFM95_TARGET_RSSI + RFM95_RSSI_OFFSET;
// SPI init
hwDigitalWrite(MY_RFM95_SPI_CS, HIGH);
hwPinMode(MY_RFM95_SPI_CS, OUTPUT);
_SPI.begin();
// Set LoRa mode (during sleep mode)
RFM95_writeReg(RFM95_REG_01_OP_MODE, RFM95_MODE_SLEEP | RFM95_LONG_RANGE_MODE);
delay(10); // Wait for sleep mode to take over from say, CAD
// Check we are in sleep mode, with LORA set
if (RFM95_readReg(RFM95_REG_01_OP_MODE) != (RFM95_MODE_SLEEP | RFM95_LONG_RANGE_MODE)) {
return false; // No device present?
}
// Set up FIFO, 256 bytes: LoRa max message 64 bytes, set half RX half TX (default)
RFM95_writeReg(RFM95_REG_0F_FIFO_RX_BASE_ADDR, RFM95_RX_FIFO_ADDR);
RFM95_writeReg(RFM95_REG_0E_FIFO_TX_BASE_ADDR, RFM95_TX_FIFO_ADDR);
RFM95_writeReg(RFM95_REG_23_MAX_PAYLOAD_LENGTH, RFM95_MAX_PACKET_LEN);
(void)RFM95_setRadioMode(RFM95_RADIO_MODE_STDBY);
const modemConfig_t configuration = { MY_RFM95_MODEM_CONFIGRUATION };
RFM95_setModemRegisters(configuration);
RFM95_setPreambleLength(8); // default
RFM95_setFrequency(frequency);
// set power level
RFM95_setTxPower(MY_RFM95_TX_POWER);
// IRQ
hwPinMode(MY_RFM95_IRQ_PIN, INPUT);
#if defined (SPI_HAS_TRANSACTION) && !defined (ESP8266)
_SPI.usingInterrupt(digitalPinToInterrupt(MY_RFM95_IRQ_PIN));
#endif
attachInterrupt(digitalPinToInterrupt(MY_RFM95_IRQ_PIN), RFM95_interruptHandler, RISING);
return true;
}
// RxDone, TxDone, CADDone is mapped to DI0
LOCAL void RFM95_interruptHandler(void) {
// Read the interrupt register
const uint8_t irq_flags = RFM95_readReg(RFM95_REG_12_IRQ_FLAGS);
if (RFM95.radioMode == RFM95_RADIO_MODE_RX && (irq_flags & (RFM95_RX_TIMEOUT | RFM95_PAYLOAD_CRC_ERROR)) ) {
// CRC error or timeout
// RXcontinuous mode: radio stays in RX mode, clearing IRQ needed
}
else if (RFM95.radioMode == RFM95_RADIO_MODE_RX && (irq_flags & RFM95_RX_DONE)) {
// set radio to STDBY (we are in RXcontinuous mode)
(void)RFM95_setRadioMode(RFM95_RADIO_MODE_STDBY);
// Have received a packet
//In order to retrieve received data from FIFO the user must ensure that ValidHeader, PayloadCrcError, RxDone and RxTimeout interrupts in the status register RegIrqFlags are not asserted to ensure that packet reception has terminated successfully(i.e.no flags should be set).
const uint8_t bufLen = min(RFM95_readReg(RFM95_REG_13_RX_NB_BYTES), RFM95_MAX_PACKET_LEN);
if (bufLen >= RFM95_HEADER_LEN) {
// Reset the fifo read ptr to the beginning of the packet
RFM95_writeReg(RFM95_REG_0D_FIFO_ADDR_PTR, RFM95_readReg(RFM95_REG_10_FIFO_RX_CURRENT_ADDR));
RFM95_burstReadReg(RFM95_REG_00_FIFO, RFM95.currentPacket.data, bufLen);
RFM95.currentPacket.RSSI = RFM95_readReg(RFM95_REG_1A_PKT_RSSI_VALUE);
RFM95.currentPacket.SNR = static_cast<rfm95_SNR_t>(RFM95_readReg(RFM95_REG_19_PKT_SNR_VALUE));
RFM95.currentPacket.payloadLen = bufLen - RFM95_HEADER_LEN;
// Message for us
if ((RFM95.currentPacket.header.version >= RFM95_MIN_PACKET_HEADER_VERSION) &&
(RFM95_PROMISCUOUS || RFM95.currentPacket.header.recipient == RFM95.address || RFM95.currentPacket.header.recipient == RFM95_BROADCAST_ADDRESS)) {
RFM95.rxBufferValid = true;
}
}
}
else if (RFM95.radioMode == RFM95_RADIO_MODE_TX && (irq_flags & RFM95_TX_DONE) ) {
(void)RFM95_setRadioMode(RFM95_RADIO_MODE_STDBY);
}
else if (RFM95.radioMode == RFM95_RADIO_MODE_CAD && (irq_flags & RFM95_CAD_DONE) ) {
RFM95.cad = irq_flags & RFM95_CAD_DETECTED;
(void)RFM95_setRadioMode(RFM95_RADIO_MODE_STDBY);
}
// Clear all IRQ flags
RFM95_writeReg(RFM95_REG_12_IRQ_FLAGS, 0xff);
}
LOCAL bool RFM95_available(void) {
if (RFM95.radioMode == RFM95_RADIO_MODE_TX) {
return false;
}
(void)RFM95_setRadioMode(RFM95_RADIO_MODE_RX);
return RFM95.rxBufferValid;
}
LOCAL void RFM95_clearRxBuffer(void) {
noInterrupts();
RFM95.rxBufferValid = false;
interrupts();
}
LOCAL uint8_t RFM95_recv(uint8_t* buf) {
if (!RFM95_available()) {
return false;
}
// atomic
noInterrupts();
const uint8_t payloadLen = RFM95.currentPacket.payloadLen;
const uint8_t sender = RFM95.currentPacket.header.sender;
const rfm95_sequenceNumber_t sequenceNumber = RFM95.currentPacket.header.sequenceNumber;
const uint8_t controlFlags = RFM95.currentPacket.header.controlFlags;
const rfm95_RSSI_t RSSI = RFM95.currentPacket.RSSI; // of incoming packet
const rfm95_SNR_t SNR = RFM95.currentPacket.SNR;
if (buf != NULL) {
memcpy((void*)buf, (void*)RFM95.currentPacket.payload, payloadLen);
RFM95.rxBufferValid = false;
}
interrupts();
// ACK handling
if (RFM95_getACKRequested(controlFlags)) {
RFM95_DEBUG(PSTR("RFM95:RCV:SEND ACK\n"));
RFM95_sendACK(sender, sequenceNumber, RSSI, SNR);
}
return payloadLen;
}
LOCAL bool RFM95_send(rfm95_packet_t &packet) {
const uint8_t finalLen = packet.payloadLen + RFM95_HEADER_LEN;
// Make sure we dont interrupt an outgoing message
RFM95_waitPacketSent();
(void)RFM95_setRadioMode(RFM95_RADIO_MODE_STDBY);
// Check channel activity
if (!RFM95_waitCAD()) {
return false;
}
packet.header.sequenceNumber = RFM95.txSequenceNumber++;
// Position at the beginning of the TX FIFO
RFM95_writeReg(RFM95_REG_0D_FIFO_ADDR_PTR, RFM95_TX_FIFO_ADDR);
// write packet
RFM95_burstWriteReg(RFM95_REG_00_FIFO, packet.data, finalLen);
// total payload length
RFM95_writeReg(RFM95_REG_22_PAYLOAD_LENGTH, finalLen);
// send message, if sent, irq fires and radio returns to standby
(void)RFM95_setRadioMode(RFM95_RADIO_MODE_TX);
return true;
}
LOCAL bool RFM95_sendFrame(const uint8_t recipient, uint8_t* data, const uint8_t len, const rfm95_flag_t flags) {
rfm95_packet_t packet;
packet.header.version = RFM95_PACKET_HEADER_VERSION;
packet.header.sender = RFM95.address;
packet.header.recipient = recipient;
packet.header.controlFlags = 0x00;
packet.payloadLen = min(len, RFM95_MAX_PAYLOAD_LEN);
packet.header.controlFlags = flags;
memcpy(&packet.payload, data, packet.payloadLen);
return RFM95_send(packet);
}
LOCAL void RFM95_setFrequency(const float centre) {
const uint32_t freq = (centre * 1000000.0) / RFM95_FSTEP;
RFM95_writeReg(RFM95_REG_06_FRF_MSB, (freq >> 16) & 0xff);
RFM95_writeReg(RFM95_REG_07_FRF_MID, (freq >> 8) & 0xff);
RFM95_writeReg(RFM95_REG_08_FRF_LSB, freq & 0xff);
}
LOCAL bool RFM95_setTxPower(uint8_t powerLevel) {
// RFM95/96/97/98 does not have RFO pins connected to anything. Only PA_BOOST
powerLevel = max(RFM95_MIN_POWER_LEVEL_DBM, powerLevel);
powerLevel = min(RFM95_MAX_POWER_LEVEL_DBM, powerLevel);
if (powerLevel != RFM95.powerLevel) {
RFM95.powerLevel = powerLevel;
uint8_t val;
if (powerLevel > 20) {
// enable DAC, adds 3dBm
// The documentation is pretty confusing on this topic: PaSelect says the max power is 20dBm,
// but OutputPower claims it would be 17dBm.
// My measurements show 20dBm is correct
RFM95_writeReg(RFM95_REG_4D_PA_DAC, RFM95_PA_DAC_ENABLE);
val = powerLevel - 8;
}
else {
RFM95_writeReg(RFM95_REG_4D_PA_DAC, RFM95_PA_DAC_DISABLE);
val = powerLevel - 5;
}
RFM95_writeReg(RFM95_REG_09_PA_CONFIG, RFM95_PA_SELECT | val);
RFM95_DEBUG(PSTR("RFM95:PTX:LEVEL=%d\n"), powerLevel);
return true;
}
return false;
}
// Sets registers from a canned modem configuration structure
LOCAL void RFM95_setModemRegisters(const modemConfig_t config) {
RFM95_writeReg(RFM95_REG_1D_MODEM_CONFIG1, config.reg_1d);
RFM95_writeReg(RFM95_REG_1E_MODEM_CONFIG2, config.reg_1e);
RFM95_writeReg(RFM95_REG_26_MODEM_CONFIG3, config.reg_26);
}
LOCAL void RFM95_setPreambleLength(const uint16_t preambleLength) {
RFM95_writeReg(RFM95_REG_20_PREAMBLE_MSB, preambleLength >> 8);
RFM95_writeReg(RFM95_REG_21_PREAMBLE_LSB, preambleLength & 0xff);
}
LOCAL void RFM95_setAddress(const uint8_t addr) {
RFM95.address = addr;
}
LOCAL uint8_t RFM95_getAddress(void) {
return RFM95.address;
}
LOCAL bool RFM95_isChannelActive(void) {
(void)RFM95_setRadioMode(RFM95_RADIO_MODE_CAD);
while (RFM95.radioMode == RFM95_RADIO_MODE_CAD) {
yield();
}
return RFM95.cad;
}
LOCAL bool RFM95_setRadioMode(const rfm95_radio_mode_t newRadioMode) {
if (RFM95.radioMode == newRadioMode) {
return false;
}
uint8_t regMode;
if (newRadioMode == RFM95_RADIO_MODE_STDBY) {
regMode = RFM95_MODE_STDBY;
}
else if (newRadioMode == RFM95_RADIO_MODE_SLEEP) {
regMode = RFM95_MODE_SLEEP;
}
else if (newRadioMode == RFM95_RADIO_MODE_CAD) {
regMode = RFM95_MODE_CAD;
RFM95_writeReg(RFM95_REG_40_DIO_MAPPING1, 0x80); // Interrupt on CadDone, DIO0
}
else if (newRadioMode == RFM95_RADIO_MODE_RX) {
regMode = RFM95_MODE_RXCONTINUOUS;
RFM95_writeReg(RFM95_REG_40_DIO_MAPPING1, 0x00); // Interrupt on RxDone, DIO0
}
else if (newRadioMode == RFM95_RADIO_MODE_TX) {
regMode = RFM95_MODE_TX;
RFM95_writeReg(RFM95_REG_40_DIO_MAPPING1, 0x40); // Interrupt on TxDone, DIO0
}
else return false;
RFM95_writeReg(RFM95_REG_01_OP_MODE, regMode);
RFM95.radioMode = newRadioMode;
return true;
}
LOCAL bool RFM95_sleep(void) {
return RFM95_setRadioMode(RFM95_RADIO_MODE_SLEEP);
}
// should be called immediately after reception in case sender wants ACK
LOCAL void RFM95_sendACK(const uint8_t recipient, const rfm95_sequenceNumber_t sequenceNumber, const rfm95_RSSI_t RSSI, const rfm95_RSSI_t SNR) {
RFM95_DEBUG(PSTR("RFM95:SAC:SEND ACK to=%d,RSSI=%d\n"),recipient,RSSI);
rfm95_ack_t ACK;
ACK.sequenceNumber = sequenceNumber;
ACK.RSSI = RSSI;
ACK.SNR = SNR;
rfm95_flag_t flags = 0x00;
RFM95_setACKReceived(flags, true);
RFM95_setACKRSSIReport(flags, true);
(void)RFM95_sendFrame(recipient, (uint8_t*)&ACK, sizeof(rfm95_ack_t), flags);
}
LOCAL bool RFM95_executeATC(const rfm95_RSSI_t currentRSSI, const rfm95_RSSI_t targetRSSI) {
// allow +-5%
if (currentRSSI < (targetRSSI*1.05) && RFM95.powerLevel < RFM95_MAX_POWER_LEVEL_DBM) {
// increase transmitter power
RFM95.powerLevel++;
}
else if (currentRSSI > (targetRSSI*0.95) && RFM95.powerLevel > RFM95_MIN_POWER_LEVEL_DBM) {
// decrease transmitter power
RFM95.powerLevel--;
}
else {
// nothing to adjust
return false;
}
RFM95_DEBUG(PSTR("RFM95:ATC:ADJ TXL,cR=%d,tR=%d,TXL=%d\n"), currentRSSI - RFM95_RSSI_OFFSET, targetRSSI - RFM95_RSSI_OFFSET, RFM95.powerLevel);
return RFM95_setTxPower(RFM95.powerLevel);;
}
LOCAL bool RFM95_sendWithRetry(const uint8_t recipient, const void* buffer, const uint8_t bufferSize, const uint8_t retries, const uint32_t retryWaitTime) {
for (uint8_t retry = 0; retry < retries; retry++) {
RFM95_DEBUG(PSTR("RFM95:SWR:SEND TO=%d,RETRY=%d\n"), recipient, retry);
rfm95_flag_t flags = 0x00;
RFM95_setACKRequested(flags, (recipient != RFM95_BROADCAST_ADDRESS));
(void)RFM95_sendFrame(recipient, (uint8_t*)buffer, bufferSize, flags);
(void)RFM95_waitPacketSent();
(void)RFM95_setRadioMode(RFM95_RADIO_MODE_RX);
if (recipient == RFM95_BROADCAST_ADDRESS) {
return true;
}
const uint32_t enterMS = hwMillis();
while (hwMillis() - enterMS < 500) {
if (RFM95.rxBufferValid) {
const uint8_t sender = RFM95.currentPacket.header.sender;
const rfm95_sequenceNumber_t ACKsequenceNumber = RFM95.currentPacket.ACK.sequenceNumber;
const rfm95_flag_t flag = RFM95.currentPacket.header.controlFlags;
if (sender == recipient && RFM95_getACKReceived(flag) && (ACKsequenceNumber == RFM95.txSequenceNumber - 1)) {
RFM95_DEBUG(PSTR("RFM95:SWR:ACK FROM=%d,SEQ=%d,RSSI=%d,SNR=%d\n"),sender,ACKsequenceNumber, RFM95.currentPacket.ACK.RSSI-RFM95_RSSI_OFFSET, (int8_t)RFM95.currentPacket.ACK.SNR / 4);
RFM95_clearRxBuffer();
// ATC
if (RFM95.ATCenabled && RFM95_getACKRSSIReport(flag)) {
(void)RFM95_executeATC(RFM95.currentPacket.ACK.RSSI, RFM95.ATCtargetRSSI);
} // ATC
return true;
} // seq check
}
yield();
}
RFM95_DEBUG(PSTR("!RFM95:SWR:NACK\n"));
if (RFM95.ATCenabled) {
// No ACK received, maybe out of reach: increase power level
RFM95_setTxPower(RFM95.powerLevel++);
}
}
return false;
}
// Wait until no channel activity detected or timeout
LOCAL bool RFM95_waitCAD(void) {
const uint32_t enterMS = hwMillis();
while (RFM95_isChannelActive()) {
if (hwMillis() - enterMS > RFM95_CAD_TIMEOUT_MS) {
return false;
}
yield();
}
return true;
}
// Wait for any previous transmission to finish
LOCAL bool RFM95_waitPacketSent(void) {
while (RFM95.radioMode == RFM95_RADIO_MODE_TX)
yield();
return true;
}
LOCAL int16_t RFM95_getRSSI(void) {
return (int16_t)(RFM95.currentPacket.RSSI - RFM95_RSSI_OFFSET);
}
LOCAL int16_t RFM95_getSNR(void) {
return (int16_t)(RFM95.currentPacket.SNR / 4);
}
LOCAL int8_t RFM95_getTxPower(void) {
return RFM95.powerLevel;
}
LOCAL void RFM95_ATCmode(const bool OnOff, const int16_t targetRSSI) {
RFM95.ATCenabled = OnOff;
RFM95.ATCtargetRSSI = (uint8_t)(targetRSSI + RFM95_RSSI_OFFSET);
}
LOCAL bool RFM95_sanityCheck(void) {
return true;
}