// RH_MRF89.cpp // // Copyright (C) 2015 Mike McCauley // $Id: RH_MRF89.cpp,v 1.7 2015/12/31 04:23:12 mikem Exp $ #include #define BAND_915 #define DATA_RATE_200 #define LNA_GAIN LNA_GAIN_0_DB #define TX_POWER TX_POWER_13_DB // Interrupt vectors for the 3 Arduino interrupt pins // Each interrupt can be handled by a different instance of RH_MRF89, allowing you to have // 2 or more LORAs per Arduino RH_MRF89* RH_MRF89::_deviceForInterrupt[RH_MRF89_NUM_INTERRUPTS] = {0, 0, 0}; uint8_t RH_MRF89::_interruptCount = 0; // Index into _deviceForInterrupt for next device // These are indexed by the values of ModemConfigChoice // Values based on sample modulation values from MRF89XA.h // TXIPOLFV set to be more than Fd PROGMEM static const RH_MRF89::ModemConfig MODEM_CONFIG_TABLE[] = { // MODSEL, FDVAL, BRVAL, FILCREG=(PASFILV|BUTFILV), TXIPOLFV // FSK, No Manchester, Whitening { RH_MRF89_MODSEL_FSK, 0x0B, 0x63, 0x40 | 0x01, 0x20 }, // FSK_Rb2Fd33 { RH_MRF89_MODSEL_FSK, 0x0B, 0x27, 0x40 | 0x01, 0x20 }, // FSK_Rb5Fd33 { RH_MRF89_MODSEL_FSK, 0x0B, 0x13, 0x40 | 0x01, 0x20 }, // FSK_Rb10Fd33 { RH_MRF89_MODSEL_FSK, 0x09, 0x09, 0x70 | 0x02, 0x20 }, // FSK_Rb20Fd40 { RH_MRF89_MODSEL_FSK, 0x04, 0x04, 0xB0 | 0x05, 0x40 }, // FSK_Rb40Fd80 { RH_MRF89_MODSEL_FSK, 0x03, 0x03, 0xD0 | 0x06, 0x40 }, // FSK_Rb50Fd100 { RH_MRF89_MODSEL_FSK, 0x02, 0x02, 0xE0 | 0x09, 0x60 }, // FSK_Rb66Fd133 { RH_MRF89_MODSEL_FSK, 0x01, 0x01, 0xF0 | 0x0F, 0x80 }, // FSK_Rb100Fd200 { RH_MRF89_MODSEL_FSK, 0x01, 0x00, 0xF0 | 0x0F, 0x80 } // FSK_Rb200Fd200 }; RH_MRF89::RH_MRF89(uint8_t csconPin, uint8_t csdatPin, uint8_t interruptPin, RHGenericSPI& spi) : RHNRFSPIDriver(csconPin, spi), _csconPin(csconPin), _csdatPin(csdatPin), _interruptPin(interruptPin) { _myInterruptIndex = 0xff; // Not allocated yet } bool RH_MRF89::init() { // MRF89 data cant handle SPI greater than 1MHz. // Sigh on teensy at 1MHz, need special delay after writes, see RHNRFSPIDriver::spiWrite _spi.setFrequency(RHGenericSPI::Frequency1MHz); if (!RHNRFSPIDriver::init()) return false; // Initialise the chip select pins pinMode(_csconPin, OUTPUT); digitalWrite(_csconPin, HIGH); pinMode(_csdatPin, OUTPUT); digitalWrite(_csdatPin, HIGH); // Determine the interrupt number that corresponds to the interruptPin int interruptNumber = digitalPinToInterrupt(_interruptPin); if (interruptNumber == NOT_AN_INTERRUPT) return false; #ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER interruptNumber = _interruptPin; #endif // Make sure we are not in some unexpected mode from a previous run setOpMode(RH_MRF89_CMOD_STANDBY); // No way to check the device type but lets trivially check there is something there // by trying to change a register: spiWriteRegister(RH_MRF89_REG_02_FDEVREG, 0xaa); if (spiReadRegister(RH_MRF89_REG_02_FDEVREG) != 0xaa) return false; spiWriteRegister(RH_MRF89_REG_02_FDEVREG, 0x3); // Back to the default for FDEV if (spiReadRegister(RH_MRF89_REG_02_FDEVREG) != 0x3) return false; // Add by Adrien van den Bossche for Teensy // ARM M4 requires the below. else pin interrupt doesn't work properly. // On all other platforms, its innocuous, belt and braces pinMode(_interruptPin, INPUT); // Set up interrupt handler // Since there are a limited number of interrupt glue functions isr*() available, // we can only support a limited number of devices simultaneously // On some devices, notably most Arduinos, the interrupt pin passed in is actually the // interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping // yourself based on knowledge of what Arduino board you are running on. if (_myInterruptIndex == 0xff) { // First run, no interrupt allocated yet if (_interruptCount <= RH_MRF89_NUM_INTERRUPTS) _myInterruptIndex = _interruptCount++; else return false; // Too many devices, not enough interrupt vectors } _deviceForInterrupt[_myInterruptIndex] = this; if (_myInterruptIndex == 0) attachInterrupt(interruptNumber, isr0, RISING); else if (_myInterruptIndex == 1) attachInterrupt(interruptNumber, isr1, RISING); else if (_myInterruptIndex == 2) attachInterrupt(interruptNumber, isr2, RISING); else return false; // Too many devices, not enough interrupt vectors // When used with the MRF89XAM9A module, per 75017B.pdf section 1.3, need: // crystal freq = 12.8MHz // clock output disabled // frequency bands 902-915 or 915-928 // VCOT 60mV // OOK max 28kbps // Based on 70622C.pdf, section 3.12: spiWriteRegister(RH_MRF89_REG_00_GCONREG, RH_MRF89_CMOD_STANDBY | RH_MRF89_FBS_950_960 | RH_MRF89_VCOT_60MV); spiWriteRegister(RH_MRF89_REG_01_DMODREG, RH_MRF89_MODSEL_FSK | RH_MRF89_OPMODE_PACKET); // FSK, Packet mode, LNA 0dB spiWriteRegister(RH_MRF89_REG_02_FDEVREG, 0); // Set by setModemConfig spiWriteRegister(RH_MRF89_REG_03_BRSREG, 0); // Set by setModemConfig spiWriteRegister(RH_MRF89_REG_04_FLTHREG, 0); // Set by setModemConfig (OOK only) spiWriteRegister(RH_MRF89_REG_05_FIFOCREG, RH_MRF89_FSIZE_64); spiWriteRegister(RH_MRF89_REG_06_R1CREG, 0); // Set by setFrequency spiWriteRegister(RH_MRF89_REG_07_P1CREG, 0); // Set by setFrequency spiWriteRegister(RH_MRF89_REG_08_S1CREG, 0); // Set by setFrequency spiWriteRegister(RH_MRF89_REG_09_R2CREG, 0); // Frequency set 2 not used spiWriteRegister(RH_MRF89_REG_0A_P2CREG, 0); // Frequency set 2 not used spiWriteRegister(RH_MRF89_REG_0B_S2CREG, 0); // Frequency set 2 not used spiWriteRegister(RH_MRF89_REG_0C_PACREG, RH_MRF89_PARC_23); // IRQ0 rx mode: SYNC (not used) // IRQ1 rx mode: CRCOK // IRQ1 tx mode: TXDONE spiWriteRegister(RH_MRF89_REG_0D_FTXRXIREG, RH_MRF89_IRQ0RXS_PACKET_SYNC | RH_MRF89_IRQ1RXS_PACKET_CRCOK | RH_MRF89_IRQ1TX); spiWriteRegister(RH_MRF89_REG_0E_FTPRIREG, RH_MRF89_LENPLL); spiWriteRegister(RH_MRF89_REG_0F_RSTHIREG, 0x00); // default not used if no RSSI interrupts spiWriteRegister(RH_MRF89_REG_10_FILCREG, 0); // Set by setModemConfig spiWriteRegister(RH_MRF89_REG_11_PFCREG, 0x38);// 100kHz, recommended, but not used, see RH_MRF89_REG_12_SYNCREG OOK only? spiWriteRegister(RH_MRF89_REG_12_SYNCREG, RH_MRF89_SYNCREN | RH_MRF89_SYNCWSZ_32); // No polyphase, no bsync, sync, 0 errors spiWriteRegister(RH_MRF89_REG_13_RSVREG, 0x07);//default // spiWriteRegister(RH_MRF89_REG_14_RSTSREG, 0x00); // NO, read only spiWriteRegister(RH_MRF89_REG_15_OOKCREG, 0x00); // Set by setModemConfig OOK only spiWriteRegister(RH_MRF89_REG_16_SYNCV31REG, 0x69); // Set by setSyncWords spiWriteRegister(RH_MRF89_REG_17_SYNCV23REG, 0x81); // Set by setSyncWords spiWriteRegister(RH_MRF89_REG_18_SYNCV15REG, 0x7E); // Set by setSyncWords spiWriteRegister(RH_MRF89_REG_19_SYNCV07REG, 0x96); // Set by setSyncWords // TXIPOLFV set by setModemConfig. power set by setTxPower spiWriteRegister(RH_MRF89_REG_1A_TXCONREG, 0xf0 | RH_MRF89_TXOPVAL_13DBM); // TX cutoff freq=375kHz, spiWriteRegister(RH_MRF89_REG_1B_CLKOREG, 0x00); // Disable clock output to save power spiWriteRegister(RH_MRF89_REG_1C_PLOADREG, 0x40); // payload=64bytes (no RX-filtering on packet length) spiWriteRegister(RH_MRF89_REG_1D_NADDSREG, 0x00); // Node Address (0=default) Not used spiWriteRegister(RH_MRF89_REG_1E_PKTCREG, RH_MRF89_PKTLENF | RH_MRF89_PRESIZE_4 | RH_MRF89_WHITEON | RH_MRF89_CHKCRCEN | RH_MRF89_ADDFIL_OFF); spiWriteRegister(RH_MRF89_REG_1F_FCRCREG, 0x00); // default (FIFO access in standby=write, clear FIFO on CRC mismatch) // Looking OK now // Set some suitable defaults: setPreambleLength(3); // The default uint8_t syncwords[] = { 0x69, 0x81, 0x7e, 0x96 }; // Same as RH_MRF89XA setSyncWords(syncwords, sizeof(syncwords)); setTxPower(RH_MRF89_TXOPVAL_1DBM); if (!setFrequency(915.4)) return false; // Some slow, reliable default speed and modulation if (!setModemConfig(FSK_Rb20Fd40)) return false; return true; } bool RH_MRF89::printRegisters() { #ifdef RH_HAVE_SERIAL uint8_t i; for (i = 0; i <= 0x1f; i++) { Serial.print(i, HEX); Serial.print(": "); Serial.println(spiReadRegister(i), HEX); } #endif return true; } // C++ level interrupt handler for this instance // MRF89XA is unusual in that it has 2 interrupt lines, and not a single, combined one. // Only one of the several interrupt lines (IRQ1) from the RFM95 needs to be // connnected to the processor. // We use this to get CRCOK and TXDONE interrupts void RH_MRF89::handleInterrupt() { // Serial.println("I"); if (_mode == RHModeTx) { // Serial.println("T"); // TXDONE // Transmit is complete _txGood++; setModeIdle(); } else if (_mode == RHModeRx) { // Serial.println("R"); // CRCOK // We have received a packet. // First byte in FIFO is packet length // REVISIT: Capture last rssi from RSTSREG // based roughly on Figure 3-9 _lastRssi = (spiReadRegister(RH_MRF89_REG_14_RSTSREG) >> 1) - 120; _bufLen = spiReadData(); if (_bufLen < 4) { // Drain the FIFO uint8_t i; for (i = 0; spiReadRegister(RH_MRF89_REG_0D_FTXRXIREG) & RH_MRF89_FIFOEMPTY; i++) spiReadData(); clearRxBuf(); return; } // Now drain all the data from the FIFO into _buf uint8_t i; for (i = 0; spiReadRegister(RH_MRF89_REG_0D_FTXRXIREG) & RH_MRF89_FIFOEMPTY; i++) _buf[i] = spiReadData(); // All good. See if its for us validateRxBuf(); if (_rxBufValid) setModeIdle(); // Got one } } // These are low level functions that call the interrupt handler for the correct // instance of RH_MRF89. // 3 interrupts allows us to have 3 different devices void RH_MRF89::isr0() { if (_deviceForInterrupt[0]) _deviceForInterrupt[0]->handleInterrupt(); } void RH_MRF89::isr1() { if (_deviceForInterrupt[1]) _deviceForInterrupt[1]->handleInterrupt(); } void RH_MRF89::isr2() { if (_deviceForInterrupt[2]) _deviceForInterrupt[2]->handleInterrupt(); } uint8_t RH_MRF89::spiReadRegister(uint8_t reg) { // Tell the chip we want to talk to the configuration registers setSlaveSelectPin(_csconPin); digitalWrite(_csdatPin, HIGH); return spiRead(((reg & 0x1f) << 1) | RH_MRF89_SPI_READ_MASK); } uint8_t RH_MRF89::spiWriteRegister(uint8_t reg, uint8_t val) { // Tell the chip we want to talk to the configuration registers setSlaveSelectPin(_csconPin); digitalWrite(_csdatPin, HIGH); // Hmmm, on teensy 3.1, needed some special behaviour in RHNRFSPIDriver::spiWrite // because otherwise, CSCON returns high before the final clock goes low, // which prevents the MRF89XA spi write succeeding. Clock must be low when CSCON goes high. return spiWrite(((reg & 0x1f) << 1), val); } uint8_t RH_MRF89::spiWriteData(uint8_t data) { spiWriteRegister(RH_MRF89_REG_1F_FCRCREG, RH_MRF89_ACFCRC); // Write to FIFO setSlaveSelectPin(_csdatPin); digitalWrite(_csconPin, HIGH); return spiCommand(data); } uint8_t RH_MRF89::spiWriteData(const uint8_t* data, uint8_t len) { spiWriteRegister(RH_MRF89_REG_1F_FCRCREG, RH_MRF89_ACFCRC); // Write to FIFO setSlaveSelectPin(_csdatPin); digitalWrite(_csconPin, HIGH); uint8_t status = 0; ATOMIC_BLOCK_START; _spi.beginTransaction(); digitalWrite(_slaveSelectPin, LOW); while (len--) _spi.transfer(*data++); digitalWrite(_slaveSelectPin, HIGH); _spi.endTransaction(); ATOMIC_BLOCK_END; return status; } uint8_t RH_MRF89::spiReadData() { spiWriteRegister(RH_MRF89_REG_1F_FCRCREG, RH_MRF89_ACFCRC | RH_MRF89_FRWAXS); // Read from FIFO setSlaveSelectPin(_csdatPin); digitalWrite(_csconPin, HIGH); return spiCommand(0); } void RH_MRF89::setOpMode(uint8_t mode) { // REVISIT: do we need to have time delays when switching between modes? uint8_t val = spiReadRegister(RH_MRF89_REG_00_GCONREG); val = (val & ~RH_MRF89_CMOD) | (mode & RH_MRF89_CMOD); spiWriteRegister(RH_MRF89_REG_00_GCONREG, val); } void RH_MRF89::setModeIdle() { if (_mode != RHModeIdle) { setOpMode(RH_MRF89_CMOD_STANDBY); _mode = RHModeIdle; } } bool RH_MRF89::sleep() { if (_mode != RHModeSleep) { setOpMode(RH_MRF89_CMOD_SLEEP); _mode = RHModeSleep; } return true; } void RH_MRF89::setModeRx() { if (_mode != RHModeRx) { setOpMode(RH_MRF89_CMOD_RECEIVE); _mode = RHModeRx; } } void RH_MRF89::setModeTx() { if (_mode != RHModeTx) { setOpMode(RH_MRF89_CMOD_TRANSMIT); _mode = RHModeTx; } } void RH_MRF89::setTxPower(uint8_t power) { uint8_t txconreg = spiReadRegister(RH_MRF89_REG_1A_TXCONREG); txconreg |= (power & RH_MRF89_TXOPVAL); spiWriteRegister(RH_MRF89_REG_1A_TXCONREG, txconreg); } bool RH_MRF89::available() { if (_mode == RHModeTx) return false; setModeRx(); return _rxBufValid; // Will be set by the interrupt handler when a good message is received } bool RH_MRF89::recv(uint8_t* buf, uint8_t* len) { if (!available()) return false; if (buf && len) { ATOMIC_BLOCK_START; // Skip the 4 headers that are at the beginning of the rxBuf if (*len > _bufLen - RH_MRF89_HEADER_LEN) *len = _bufLen - RH_MRF89_HEADER_LEN; memcpy(buf, _buf + RH_MRF89_HEADER_LEN, *len); ATOMIC_BLOCK_END; } clearRxBuf(); // This message accepted and cleared return true; } bool RH_MRF89::send(const uint8_t* data, uint8_t len) { if (len > RH_MRF89_MAX_MESSAGE_LEN) return false; waitPacketSent(); // Make sure we dont interrupt an outgoing message setModeIdle(); // First octet is the length of the chip payload // 0 length messages are transmitted but never trigger a receive! spiWriteData(len + RH_MRF89_HEADER_LEN); spiWriteData(_txHeaderTo); spiWriteData(_txHeaderFrom); spiWriteData(_txHeaderId); spiWriteData(_txHeaderFlags); spiWriteData(data, len); setModeTx(); // Start transmitting return true; } uint8_t RH_MRF89::maxMessageLength() { return RH_MRF89_MAX_MESSAGE_LEN; } // Check whether the latest received message is complete and uncorrupted void RH_MRF89::validateRxBuf() { if (_bufLen < 4) return; // Too short to be a real message // Extract the 4 headers _rxHeaderTo = _buf[0]; _rxHeaderFrom = _buf[1]; _rxHeaderId = _buf[2]; _rxHeaderFlags = _buf[3]; if (_promiscuous || _rxHeaderTo == _thisAddress || _rxHeaderTo == RH_BROADCAST_ADDRESS) { _rxGood++; _rxBufValid = true; } } void RH_MRF89::clearRxBuf() { ATOMIC_BLOCK_START; _rxBufValid = false; _bufLen = 0; ATOMIC_BLOCK_END; } bool RH_MRF89::verifyPLLLock() { // Verify PLL-lock per instructions in Note 1 section 3.12 // Need to do this after changing frequency. uint8_t ftpriVal = spiReadRegister(RH_MRF89_REG_0E_FTPRIREG); spiWriteRegister(RH_MRF89_REG_0E_FTPRIREG, ftpriVal | RH_MRF89_LSTSPLL); // Clear PLL lock bit setOpMode(RH_MRF89_CMOD_FS); unsigned long ulStartTime = millis(); while ((millis() - ulStartTime < 1000)) { ftpriVal = spiReadRegister(RH_MRF89_REG_0E_FTPRIREG); if ((ftpriVal & RH_MRF89_LSTSPLL) != 0) break; } setOpMode(RH_MRF89_CMOD_STANDBY); return ((ftpriVal & RH_MRF89_LSTSPLL) != 0); } bool RH_MRF89::setFrequency(float centre) { // REVISIT: FSK only: its different for OOK :-( uint8_t FBS; if (centre >= 902.0 && centre < 915.0) { FBS = RH_MRF89_FBS_902_915; } else if (centre >= 915.0 && centre <= 928.0) { FBS = RH_MRF89_FBS_915_928; } else if (centre >= 950.0 && centre <= 960.0) { // Not all modules support this frequency band: // The MRF98XAM9A does not FBS = RH_MRF89_FBS_950_960; } // else if (centre >= 863.0 && centre <= 870.0) // { // // Not all modules support this frequency band: // // The MRF98XAM9A does not // FBS = RH_MRF89_FBS_950_960; // Yes same as above // } else { // Cant do this freq return false; } // Based on frequency calcs done in MRF89XA.h // uint8_t R = 100; // Recommended uint8_t R = 119; // Also recommended :-( uint32_t centre_kHz = centre * 1000; uint32_t xtal_kHz = (RH_MRF89_XTAL_FREQ * 1000); uint32_t compare = (centre_kHz * 8 * (R + 1)) / (9 * xtal_kHz); uint8_t P = ((compare - 75) / 76) + 1; uint8_t S = compare - (75 * (P + 1)); // Now set the new register values: uint8_t val = spiReadRegister(RH_MRF89_REG_00_GCONREG); val = (val & ~RH_MRF89_FBS) | (FBS & RH_MRF89_FBS); spiWriteRegister(RH_MRF89_REG_00_GCONREG, val); spiWriteRegister(RH_MRF89_REG_06_R1CREG, R); spiWriteRegister(RH_MRF89_REG_07_P1CREG, P); spiWriteRegister(RH_MRF89_REG_08_S1CREG, S); return verifyPLLLock(); } // Set one of the canned FSK Modem configs // Returns true if its a valid choice bool RH_MRF89::setModemConfig(ModemConfigChoice index) { if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE) / sizeof(ModemConfig))) return false; RH_MRF89::ModemConfig cfg; memcpy_P(&cfg, &MODEM_CONFIG_TABLE[index], sizeof(cfg)); // Now update the registers uint8_t val = spiReadRegister(RH_MRF89_REG_01_DMODREG); val = (val & ~RH_MRF89_MODSEL) | cfg.MODSEL; spiWriteRegister(RH_MRF89_REG_01_DMODREG, val); spiWriteRegister(RH_MRF89_REG_02_FDEVREG, cfg.FDVAL); spiWriteRegister(RH_MRF89_REG_03_BRSREG, cfg.BRVAL); spiWriteRegister(RH_MRF89_REG_10_FILCREG, cfg.FILCREG); // The sample configs in MRF89XA.h all use TXIPOLFV = 0xf0 => 375kHz, which is too wide for most modulations val = spiReadRegister(RH_MRF89_REG_1A_TXCONREG); val = (val & ~RH_MRF89_TXIPOLFV) | (cfg.TXIPOLFV & RH_MRF89_TXIPOLFV); spiWriteRegister(RH_MRF89_REG_1A_TXCONREG, val); return true; } void RH_MRF89::setPreambleLength(uint8_t bytes) { if (bytes >= 1 && bytes <= 4) { bytes--; uint8_t pktcreg = spiReadRegister(RH_MRF89_REG_1E_PKTCREG); pktcreg = (pktcreg & ~RH_MRF89_PRESIZE) | ((bytes << 5) & RH_MRF89_PRESIZE); spiWriteRegister(RH_MRF89_REG_1E_PKTCREG, pktcreg); } } void RH_MRF89::setSyncWords(const uint8_t* syncWords, uint8_t len) { if (syncWords && (len > 0 and len <= 4)) { uint8_t syncreg = spiReadRegister(RH_MRF89_REG_12_SYNCREG); syncreg = (syncreg & ~RH_MRF89_SYNCWSZ) | (((len - 1) << 3) & RH_MRF89_SYNCWSZ); spiWriteRegister(RH_MRF89_REG_12_SYNCREG, syncreg); uint8_t i; for (i = 0; i < 4; i++) { if (len > i) spiWriteRegister(RH_MRF89_REG_16_SYNCV31REG + i, syncWords[i]); } } }