EasyLoRaWAN/EasyLoRaWANGateway/_loraModem.ino

1105 lines
37 KiB
C++

// 1-channel LoRa Gateway for ESP8266 and ESP32
// Copyright (c) 2016-2020 Maarten Westenberg version for ESP8266
//
// based on work done by Thomas Telkamp for Raspberry PI 1ch gateway
// and many others.
//
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the MIT License
// which accompanies this distribution, and is available at
// https://opensource.org/licenses/mit-license.php
//
// NO WARRANTY OF ANY KIND IS PROVIDED
//
// Author: Maarten Westenberg (mw12554@hotmail.com)
//
// This file contains the LoRa modem specific code enabling to receive
// and transmit packages/messages.
// The functions implemented work in user-space so not with interrupt.
// ========================================================================================
//
//
// ----------------------------------------------------------------------------------------
// Variable definitions
//
//
// ----------------------------------------------------------------------------------------
//
// ========================================================================================
// SPI AND INTERRUPTS
// The RFM96/SX1276 communicates with the ESP8266 by means of interrupts
// and SPI interface. The SPI interface is bidirectional and allows both
// parties to simultaneous write and read to registers.
// Major drawback is that access is not protected for interrupt and non-
// interrupt access. This means that when a program in loop() and a program
// in interrupt do access the readRegister and writeRegister() function
// at the same time that probably an error will occur.
// Therefore it is best to either not use interrupts AT all (like LMIC)
// or only use these functions in interrupts and to further processing
// in the main loop() program.
//
// ========================================================================================
// ----------------------------------------------------------------------------------------
// Mutex definitions
//
// ----------------------------------------------------------------------------------------
#if _MUTEX==1
void CreateMutux(int *mutex) {
*mutex=1;
}
#define LIB__MUTEX 1
#if LIB__MUTEX==1
bool GetMutex(int *mutex) {
//noInterrupts();
if (*mutex==1) {
*mutex=0;
//interrupts();
return(true);
}
//interrupts();
return(false);
}
#else
bool GetMutex(int *mutex) {
int iOld = 1, iNew = 0;
asm volatile (
"rsil a15, 1\n" // read and set interrupt level to 1
"l32i %0, %1, 0\n" // load value of mutex
"bne %0, %2, 1f\n" // compare with iOld, branch if not equal
"s32i %3, %1, 0\n" // store iNew in mutex
"1:\n" // branch target
"wsr.ps a15\n" // restore program state
"rsync\n"
: "=&r" (iOld)
: "r" (mutex), "r" (iOld), "r" (iNew)
: "a15", "memory"
);
return (bool)iOld;
}
#endif
void ReleaseMutex(int *mutex) {
*mutex=1;
}
#endif //_MUTEX==1
// ----------------------------------------------------------------------------------------
// Read one byte value, par addr is address
// Returns the value of register(addr)
//
// The SS (Chip select) pin is used to make sure the RFM95 is selected
// The variable is for obvious reasons valid for read and write traffic at the
// same time. Since both read and write mean that we write to the SPI interface.
// Parameters:
// Address: SPI address to read from. Type uint8_t
// Return:
// Value read from address
// ----------------------------------------------------------------------------------------
// define the SPI settings for reading messages
SPISettings readSettings(SPISPEED, MSBFIRST, SPI_MODE0);
uint8_t readRegister(uint8_t addr)
{
SPI.beginTransaction(readSettings);
digitalWrite(pins.ss, LOW); // Select Receiver
SPI.transfer(addr & 0x7F);
uint8_t res = (uint8_t) SPI.transfer(0x00);
digitalWrite(pins.ss, HIGH); // Unselect Receiver
SPI.endTransaction();
return((uint8_t) res);
}
// ----------------------------------------------------------------------------------------
// Write value to a register with address addr.
// Function writes one byte at a time.
// Parameters:
// addr: SPI address to write to
// value: The value to write to address
// Returns:
// <void>
// ----------------------------------------------------------------------------------------
// define the settings for SPI writing
SPISettings writeSettings(SPISPEED, MSBFIRST, SPI_MODE0);
void writeRegister(uint8_t addr, uint8_t value)
{
SPI.beginTransaction(writeSettings);
digitalWrite(pins.ss, LOW); // Select Receiver
SPI.transfer((addr | 0x80) & 0xFF);
SPI.transfer(value & 0xFF);
digitalWrite(pins.ss, HIGH); // Unselect Receiver
SPI.endTransaction();
}
// ----------------------------------------------------------------------------------------
// Write a buffer to a register with address addr.
// Function writes one byte at a time.
// Parameters:
// addr: SPI address to write to
// value: The value to write to address
// Returns:
// <void>
// ----------------------------------------------------------------------------------------
void writeBuffer(uint8_t addr, uint8_t *buf, uint8_t len)
{
//noInterrupts(); // XXX
SPI.beginTransaction(writeSettings);
digitalWrite(pins.ss, LOW); // Select Receiver
SPI.transfer((addr | 0x80) & 0xFF); // write buffer address
for (uint8_t i=0; i<len; i++) {
SPI.transfer(buf[i] & 0xFF);
}
digitalWrite(pins.ss, HIGH); // Unselect Receiver
SPI.endTransaction();
}
// ----------------------------------------------------------------------------------------
// setRate is setting rate and spreading factor and CRC etc. for transmission
// for example
// Modem Config 1 (MC1) == 0x72 for sx1276
// Modem Config 2 (MC2) == (CRC_ON) | (sf<<4)
// Modem Config 3 (MC3) == 0x04 | (optional SF11/12 LOW DATA OPTIMIZE 0x08)
// sf == SF7 default 0x07, (SF7<<4) == SX72_MC2_SF7
// bw == 125 == 0x70
// cr == CR4/5 == 0x02
// CRC_ON == 0x04
//
// sf is SF7 to SF12
// CRC is 0x00 (off) or
// ----------------------------------------------------------------------------------------
void setRate(uint8_t sf, uint8_t crc)
{
uint8_t mc1=0, mc2=0, mc3=0;
if ((sf<SF7) || (sf>SF12)) {
# if _MONITOR>=2
if (( debug>=1 ) && ( pdebug & P_RADIO )) {
mPrint("setRate:: SF=" + String(sf));
}
# endif //_MONITOR
sf=8;
}
// Set rate based on Spreading Factor etc
if (sx1272) {
mc1= 0x0A; // SX1276_MC1_BW_250 0x80 | SX1276_MC1_CR_4_5 0x02
mc2= ((sf<<4) | crc) % 0xFF;
// SX1276_MC1_BW_250 0x80 | SX1276_MC1_CR_4_5 0x02 | SX1276_MC1_IMPLICIT_HEADER_MODE_ON 0x01
if (sf == SF11 || sf == SF12) {
mc1= 0x0B;
}
}
// For sx1276 chips is the CRC ON is
else {
if (sf==SF8) {
mc1= 0x78; // SX1276_MC1_BW_125==0x70 | SX1276_MC1_CR_4_8==0x08
}
else {
mc1= 0x72; // SX1276_MC1_BW_125==0x70 | SX1276_MC1_CR_4_5==0x02
}
mc2= ((sf<<4) | crc) & 0xFF; // crc is 0x00 or 0x04==SX1276_MC2_RX_PAYLOAD_CRCON
mc3= 0x04; // 0x04; SX1276_MC3_AGCAUTO
if (sf == SF11 || sf == SF12) { mc3|= 0x08; } // 0x08 | 0x04
}
// Implicit Header (IH), for CLASS B beacons (&& SF6)
//if (getIh(LMIC.rps)) {
// mc1 |= SX1276_MC1_IMPLICIT_HEADER_MODE_ON;
// writeRegister(REG_PAYLOAD_LENGTH, getIh(LMIC.rps)); // required length
//}
writeRegister(REG_MODEM_CONFIG1, (uint8_t) mc1);
writeRegister(REG_MODEM_CONFIG2, (uint8_t) mc2);
writeRegister(REG_MODEM_CONFIG3, (uint8_t) mc3);
// Symbol timeout settings
if (sf == SF10 || sf == SF11 || sf == SF12) {
writeRegister(REG_SYMB_TIMEOUT_LSB, (uint8_t) 0x05);
} else {
writeRegister(REG_SYMB_TIMEOUT_LSB, (uint8_t) 0x08);
}
return;
}
// ----------------------------------------------------------------------------------------
// Set the frequency for our gateway
// The function has no parameter other than the freq setting used in init.
// Since we are using a 1ch gateway this value is set fixed.
// ----------------------------------------------------------------------------------------
void setFreq(uint32_t freq)
{
// set frequency
uint64_t frf = ((uint64_t)freq << 19) / 32000000;
writeRegister(REG_FRF_MSB, (uint8_t)(frf>>16) );
writeRegister(REG_FRF_MID, (uint8_t)(frf>> 8) );
writeRegister(REG_FRF_LSB, (uint8_t)(frf>> 0) );
return;
}
// ----------------------------------------------------------------------------------------
// Set Power for our gateway
// ----------------------------------------------------------------------------------------
void setPow(uint8_t powe)
{
if (powe >= 16) powe = 15;
//if (powe >= 15) powe = 14;
else if (powe < 2) powe =2;
ASSERT((powe>=2)&&(powe<=15));
uint8_t pac = (0x80 | (powe & 0xF)) & 0xFF;
writeRegister(REG_PAC, (uint8_t)pac); // set 0x09 to pac
// Note: Power settings for CFG_sx1272 are different
return;
}
// ----------------------------------------------------------------------------------------
// Set the opmode to a value as defined on top
// Values are 0x00 to 0x07
// The value is set for the lowest 3 bits, the other bits are as before.
// ----------------------------------------------------------------------------------------
void opmode(uint8_t mode)
{
if (mode == OPMODE_LORA)
writeRegister(REG_OPMODE, (uint8_t) mode);
else
writeRegister(REG_OPMODE, (uint8_t)((readRegister(REG_OPMODE) & ~OPMODE_MASK) | mode));
}
// ----------------------------------------------------------------------------------------
// Hop to next frequency as defined by NUM_HOPS
// This function should only be used for receiver operation. The current
// receiver frequency is determined by gwayConfig.ch index like so: freqs[gwayConfig.ch]
// ----------------------------------------------------------------------------------------
void hop()
{
// 1. Set radio to standby
opmode(OPMODE_STANDBY);
// 3. Set frequency based on value in freq
gwayConfig.ch = (gwayConfig.ch + 1) % NUM_HOPS ; // Increment the freq round robin
setFreq(freqs[gwayConfig.ch].upFreq);
// 4. Set spreading Factor
sf = SF7; // Starting the new frequency
setRate(sf, 0x40); // set the sf to SF7
// Low Noise Amplifier used in receiver
writeRegister(REG_LNA, (uint8_t) LNA_MAX_GAIN); // 0x0C, 0x23
// 7. set sync word
writeRegister(REG_SYNC_WORD, (uint8_t) 0x34); // set 0x39 to 0x34 LORA_MAC_PREAMBLE
// prevent node to node communication
writeRegister(REG_INVERTIQ,0x27); // 0x33, 0x27; to reset from TX
// Max Payload length is dependent on 256 byte buffer. At startup TX starts at
// 0x80 and RX at 0x00. RX therefore maximized at 128 Bytes
writeRegister(REG_MAX_PAYLOAD_LENGTH,MAX_PAYLOAD_LENGTH); // set 0x23 to 0x80==128 bytes
writeRegister(REG_PAYLOAD_LENGTH,PAYLOAD_LENGTH); // 0x22, 0x40==64Byte long
writeRegister(REG_FIFO_ADDR_PTR, (uint8_t) readRegister(REG_FIFO_RX_BASE_AD)); // set reg 0x0D to 0x0F
writeRegister(REG_HOP_PERIOD,0x00); // reg 0x24, set to 0x00
// 5. Config PA Ramp up time // set reg 0x0A
writeRegister(REG_PARAMP, (readRegister(REG_PARAMP) & 0xF0) | 0x08); // set PA ramp-up time 50 uSec
// Set 0x4D PADAC for SX1276 ; XXX register is 0x5a for sx1272
writeRegister(REG_PADAC_SX1276, 0x84); // set 0x4D (PADAC) to 0x84
// 8. Reset interrupt Mask, enable all interrupts
writeRegister(REG_IRQ_FLAGS_MASK, 0x00);
// 9. clear all radio IRQ flags
writeRegister(REG_IRQ_FLAGS, 0xFF);
// Be aware that micros() has increased significantly from calling
// the hop function until printed below
//
# if _MONITOR>=1
if (( debug>=2 ) && ( pdebug & P_RADIO )){
String response = "hop:: hopTime:: " + String(micros() - hopTime);
mStat(0, response);
mPrint(response);
}
# endif //_MONITOR
// Remember the last time we hop
hopTime = micros(); // At what time did we hop
}
// ------------------------------------- UP -----------------------------------------------
//
// This LoRa function reads a message from the LoRa transceiver
// on Success: returns message length read when message correctly received.
// on Failure: it returns a negative value on error (CRC error for example).
// This is the "lowlevel" receive function called by stateMachine()
// dealing with the radio specific LoRa functions.
//
// | | CR = 4/8 | CR= Coding Rate | |
// |Preamble |Header| CRC| Payload | Payload CRC |
// |---------|------|----|----------------------|-------------|
//
// The function deals with Explicit Header Mode. Implicit header mode is only
// valid for SF6 and only for Uplink so it does not work for LoraWAN.
//
// Parameters:
// Payload: uint8_t[] message. when message is read it is returned in payload.
// Returns:
// Length of payload received
//
// 9 bytes header (Explicit Header mode only)
// Code Rate is 4/8 for Header, might be different for Payload content
//
// 1: Payload Length (in bytes)
// 2: Forward Error Correction Rate
// 3: Optional 16 Bit (2 Byte) PHDR_CRC for the PHDR (Physical Header)
//
// followed by data N bytes Payload (Max 255)
//
// 4 bytes MIC end
//
// ----------------------------------------------------------------------------------------
uint8_t receivePkt(uint8_t *payload)
{
statc.msg_ttl++; // Receive statistics counter
uint8_t irqflags = readRegister(REG_IRQ_FLAGS); // 0x12; read back flags
uint8_t crcUsed = readRegister(REG_HOP_CHANNEL); // Is CRC used? (Register 0x1C)
if (crcUsed & 0x40) {
# if _DUSB>=1
if (( debug>=2) && (pdebug & P_RX )) {
mPrint("R rxPkt:: CRC used");
}
# endif //_DUSB
}
// Check for payload IRQ_LORA_CRCERR_MASK=0x20 set
if (irqflags & IRQ_LORA_CRCERR_MASK) // Is CRC error?
{
# if _MONITOR>=1
if (( debug>=0) && ( pdebug & P_RADIO )) {
String response=("rxPkt:: Err CRC, t=");
stringTime(now(), response);
mPrint(response);
}
# endif //_MONITOR
return 0;
}
// Is header OK?
// Please note that if we reset the HEADER interrupt in RX,
// that we would here conclude that there is no HEADER
else if ((irqflags & IRQ_LORA_HEADER_MASK) == false) // Header not ok?
{
# if _MONITOR>=1
if (( debug>=0) && ( pdebug & P_RADIO )) {
mPrint("rxPkt:: Err HEADER");
}
# endif //_MONITOR
// Reset VALID-HEADER flag 0x10
writeRegister(REG_IRQ_FLAGS, (uint8_t)(IRQ_LORA_HEADER_MASK | IRQ_LORA_RXDONE_MASK)); // 0x12; clear HEADER (== 0x10) flag
return 0;
}
// If there are no error messages, read the buffer from the FIFO
// This means "Set FifoAddrPtr to FifoRxBaseAddr"
else {
statc.msg_ok++; // Receive OK statistics counter
switch(statr[0].ch) {
case 0: statc.msg_ok_0++; break;
case 1: statc.msg_ok_1++; break;
case 2: statc.msg_ok_2++; break;
}
if (readRegister(REG_FIFO_RX_CURRENT_ADDR) != readRegister(REG_FIFO_RX_BASE_AD)) {
#if _MONITOR>=1
if (( debug>=1 ) && ( pdebug & P_RADIO )) {
mPrint("RX BASE <" + String(readRegister(REG_FIFO_RX_BASE_AD)) + "> != RX CURRENT <" + String(readRegister(REG_FIFO_RX_CURRENT_ADDR)) + ">" );
}
# endif //_MONITOR
}
//uint8_t currentAddr = readRegister(REG_FIFO_RX_CURRENT_ADDR); // 0x10
uint8_t currentAddr = readRegister(REG_FIFO_RX_BASE_AD); // 0x0F
uint8_t receivedCount = readRegister(REG_RX_NB_BYTES); // 0x13; How many bytes were read
# if _MONITOR>=1
if ((debug>=1) && (currentAddr > 64)) { // More than 64 read?
mPrint("rxPkt:: ERROR Rx addr>64"+String(currentAddr));
}
# endif //_MONITOR
writeRegister(REG_FIFO_ADDR_PTR, (uint8_t) currentAddr); // 0x0D
if (receivedCount > PAYLOAD_LENGTH) {
# if _MONITOR>=1
if (( debug>=0 ) & ( pdebug & P_RADIO )) {
mPrint("rxPkt:: ERROR Payliad receivedCount="+String(receivedCount));
}
# endif //_MONITOR
receivedCount=PAYLOAD_LENGTH;
}
for(int i=0; i < receivedCount; i++)
{
payload[i] = readRegister(REG_FIFO); // 0x00, FIFO will auto shift register
}
writeRegister(REG_IRQ_FLAGS, (uint8_t) 0xFF); // Reset ALL interrupts
// As long as _MONITOR is enabled, and P_RX debug messages are selected,
// the received packet is displayed on the output.
# if _MONITOR>=1
if ((debug>=1) && (pdebug & P_RX)){
String response = "UP receivePkt:: rxPkt: t=";
stringTime(now(), response);
response += ", f=" + String(gwayConfig.ch) + ", sf=" + String(sf);
response += ", a=";
uint8_t DevAddr [4];
DevAddr[0] = payload[4];
DevAddr[1] = payload[3];
DevAddr[2] = payload[2];
DevAddr[3] = payload[1];
printHex((IPAddress)DevAddr, ':', response);
response += ", flags=" + String(irqflags,HEX);
response += ", addr=" + String(currentAddr);
response += ", len=" + String(receivedCount);
// If debug level 1 is specified, we display the content of the message as well
// We need to decode the message as well will it make any sense
# if _TRUSTED_DECODE>=2
if (debug>=1) { // Must be 1 for operational use
int index; // The index of the codex struct to decode
//String response="";
uint8_t data[receivedCount];
if ((index = inDecodes((char *)(payload+1))) >=0 ) {
mPrint(", Ind="+String(index));
}
else if (debug>=1) {
mPrint(", ERRRO No Index");
return(receivedCount);
}
// ------------------------------
Serial.print(F(", data="));
for (int i=0; i<receivedCount; i++) { // Copy array
data[i] = payload[i];
}
uint16_t frameCount=payload[7]*256 + payload[6];
// The message received has a length, but data starts at byte 9, and stops 4 bytes
// before the end since those are MIC bytes
uint8_t CodeLength = encodePacket((uint8_t *)(data + 9), receivedCount-9-4, (uint16_t)frameCount, DevAddr, decodes[index].appKey, 0);
Serial.print(F("- NEW fc="));
Serial.print(frameCount);
Serial.print(F(", addr="));
for (int i=0; i<4; i++) {
if (DevAddr[i]<=0xF) {
Serial.print('0');
}
Serial.print(DevAddr[i], HEX);
Serial.print(' ');
}
Serial.print(F(", len="));
Serial.print(CodeLength);
Serial.print(F(", data="));
for (int i=0; i<receivedCount; i++) {
if (data[i]<=0xF) Serial.print('0');
Serial.print(data[i], HEX);
Serial.print(' ');
}
}
# endif // _TRUSTED_DECODE
mPrint(response); // Print response for Serial or mPrint
}
# endif //MONITOR
return(receivedCount);
}
writeRegister(REG_IRQ_FLAGS, (uint8_t) (
IRQ_LORA_RXDONE_MASK |
IRQ_LORA_RXTOUT_MASK |
IRQ_LORA_HEADER_MASK |
IRQ_LORA_CRCERR_MASK)); // 0x12; Clear RxDone IRQ_LORA_RXDONE_MASK
return 0;
} //receivePkt UP
// ------------------------------ DOWN ----------------------------------------------------
//
// This DOWN function sends a payload to the LoRa node over the air
// Radio must go back in standby mode as soon as the transmission is finished
//
// NOTE:: writeRegister functions should not be used outside interrupts
// ----------------------------------------------------------------------------------------
bool sendPkt(uint8_t *payLoad, uint8_t payLength)
{
# if _MONITOR>=2
if (payLength>=128) {
if (debug>=1) {
mPrint("sendPkt Dwn:: len="+String(payLength));
}
return false;
}
# endif //_MONITOR
writeRegister(REG_FIFO_ADDR_PTR, (uint8_t) readRegister(REG_FIFO_TX_BASE_AD)); // 0x0D, 0x0E
writeRegister(REG_PAYLOAD_LENGTH, (uint8_t) payLength); // 0x22
payLoad[payLength] = 0x00; // terminate buffer
writeBuffer(REG_FIFO, (uint8_t *) payLoad, payLength);
return true;
} // sendPkt()
// ------------------------------------------ DOWN ----------------------------------------
// loraWait()
// This function implements the wait protocol needed for downstream transmissions.
// Note: Timing of downstream and JoinAccept messages is VERY critical.
//
// As the ESP8266 watchdog will not like us to wait more than a few hundred
// milliseconds (or it will kick in) we have to implement a simple way to wait
// time in case we have to wait seconds before sending messages (e.g. for OTAA 5 or 6 seconds)
// Without it, the system is known to crash in half of the cases it has to wait for
// JOIN-ACCEPT messages to send.
//
// This function uses a combination of delay() statements and delayMicroseconds().
// As we use delay() only when there is still enough time to wait and we use micros()
// to make sure that delay() did not take too much time this works.
//
// Parameter: uint32-t tmst gives the micros() value when transmission should start. (!!!)
// so it contains the local Gateway time as a reference when to start Downlink.
// Note: We assume LoraDown->sfTx contains the SF we will use for downstream message.
// gwayConfig.txDelay is the delay as specified in the GUI
// ----------------------------------------------------------------------------------------
void loraWait(struct LoraDown *LoraDown)
{
int32_t delayTmst = (int32_t)(LoraDown->tmst - micros()) + gwayConfig.txDelay;
// delayTmst based on txDelay and spreading factor
if ((delayTmst > 8000000) || (delayTmst < 0)) { // Delay is > 8 secs
# if _MONITOR>=1
String response= "Dwn loraWait:: ERROR: ";
printDwn(LoraDown,response);
mPrint(response);
# endif // _MONITOR
gwayConfig.waitErr++;
return;
}
// For larger delay times we use delay() since that is for > 15ms
// This is the most efficient way.
// MMM Check for huge wait times
while (delayTmst > 15000) {
delay(15); // ms delay including yield, slightly shorter
// delayMicroseconds(15000); // ms delay including yield, slightly shorter
delayTmst -= 15000;
}
// The remaining wait time is less than 15000 uSecs
// but more than 1000 mSecs (see above)
// therefore we use delayMicroseconds() to wait
delayMicroseconds(delayTmst);
gwayConfig.waitOk++;
return;
}
// -------------------------------------- DOWN --------------------------------------------
// txLoraModem
// Init the transmitter and transmit the buffer
// After successful transmission (dio0==1) TxDone re-init the receiver
//
// crc is set to 0x00 for TX
// iiq is set to 0x27 (or 0x40 based on ipol value in txpkt)
//
// 1. opmode Lora (only in Sleep mode)
// 2. opmode StandBY
// 3. Configure Modem
// 4. Configure Channel
// 5. write PA Ramp
// 6. config Power
// 7. RegLoRaSyncWord LORA_MAC_PREAMBLE
// 8. write REG dio mapping (dio0)
// 9. write REG IRQ flags
// 10. write REG IRQ mask
// 11. write REG LoRa Fifo Base Address
// 12. write REG LoRa Fifo Addr Ptr
// 13. write REG LoRa Payload Length
// 14. Write buffer (byte by byte)
// 15. Wait until the right time to transmit has arrived
// 16. opmode TX
//
// Transmission to the device not is not done often, but is time critical.
// ----------------------------------------------------------------------------------------
void txLoraModem(struct LoraDown *LoraDown)
{
_state = S_TX;
// 1. Select LoRa modem from sleep mode
//opmode(OPMODE_LORA); // set register 0x01 to 0x80
// Assert the value of the current mode
ASSERT((readRegister(REG_OPMODE) & OPMODE_LORA) != 0);
// 2. enter standby mode (required for FIFO loading))
opmode(OPMODE_STANDBY); // set 0x01 to 0x01
// 3. Init spreading factor and other Modem setting
setRate(LoraDown->sfTx, LoraDown->crc);
// Frequency hopping
//writeRegister(REG_HOP_PERIOD, (uint8_t) 0x00); // set 0x24 to 0x00 only for receivers
// 4. Init Frequency, config channel
setFreq(LoraDown->freq);
// 6. Set power level, REG_PAC
setPow(LoraDown->powe);
// 7. prevent node to node communication
writeRegister(REG_INVERTIQ, (uint8_t) (LoraDown->iiq)); // 0x33, (0x27 or 0x40)
// 8. set the IRQ mapping DIO0=TxDone DIO1=NOP DIO2=NOP (or less for 1ch gateway)
writeRegister(REG_DIO_MAPPING_1, (uint8_t)(
MAP_DIO0_LORA_TXDONE |
MAP_DIO1_LORA_NOP |
MAP_DIO2_LORA_NOP |
MAP_DIO3_LORA_CRC));
// 9. clear all radio IRQ flags
writeRegister(REG_IRQ_FLAGS, (uint8_t) 0xFF);
// 10. mask all IRQs but TxDone
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) ~IRQ_LORA_TXDONE_MASK);
// txLora
opmode(OPMODE_FSTX); // set 0x01 to 0x02 (actual value becomes 0x82)
// 11, 12, 13, 14. write the buffer to the FiFo
sendPkt(LoraDown->payLoad, LoraDown->payLength);
//Set the base addres of the transmit buffer in FIFO
writeRegister(REG_FIFO_ADDR_PTR, (uint8_t) readRegister(REG_FIFO_TX_BASE_AD)); // set 0x0D to 0x0F (contains 0x80);
//For TX we have to set the PAYLOAD_LENGTH
writeRegister(REG_PAYLOAD_LENGTH, (uint8_t) LoraDown->payLength); // set 0x22, max 0x40==64Byte long
//For TX we have to set the MAX_PAYLOAD_LENGTH
writeRegister(REG_MAX_PAYLOAD_LENGTH, (uint8_t) MAX_PAYLOAD_LENGTH); // set 0x22, max 0x40==64Byte long
// Reset the IRQ register
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) 0x00); // Clear the mask
// writeRegister(REG_IRQ_FLAGS, (uint8_t) IRQ_LORA_TXDONE_MASK);// set 0x12 to 0x08, clear TXDONE
writeRegister(REG_IRQ_FLAGS, (uint8_t) 0xFF);// set 0x12 to 0xFF, clear TXDONE and others
// 16. Initiate actual transmission of FiFo
opmode(OPMODE_TX); // set 0x01 to 0x03 (actual value becomes 0x83)
# if _MONITOR>=1
if (( debug>=1 ) && ( pdebug & P_TX )) {
String response = "Dwn txLoraModem:: end: ";
printDwn(LoraDown, response);
mPrint(response);
}
# endif //_MONITOR
}// txLoraModem
// ----------------------------------------------------------------------------------------
// Setup the LoRa receiver on the connected transceiver.
// - Determine the correct transceiver type (sx1272/RFM92 or sx1276/RFM95)
// - Set the frequency to listen to (1-channel remember)
// - Set Spreading Factor (standard SF7)
// The reset RST pin might not be necessary for at least the RFM95 transceiver
//
// 1. Put the radio in LoRa mode
// 2. Put modem in sleep or in standby
// 3. Set Frequency
// 4. Spreading Factor
// 5. Set interrupt mask
// 6.
// 7. Set opmode to OPMODE_RX
// 8. Set _state to S_RX
// 9. Reset all interrupts
// ----------------------------------------------------------------------------------------
void rxLoraModem()
{
// 1. Put system in LoRa mode
//opmode(OPMODE_LORA); // Is already so
// 2. Put the radio in sleep mode
opmode(OPMODE_STANDBY); // CAD set 0x01 to 0x00
// 3. Set frequency based on value in freq
setFreq(freqs[gwayConfig.ch].upFreq); // set to the right frequency
// 4. Set spreading Factor and CRC
setRate(sf, 0x04);
// prevent node to node communication
writeRegister(REG_INVERTIQ, (uint8_t) 0x27); // 0x33, 0x27; to reset from TX
// Max Payload length is dependent on 256 byte buffer.
// At startup TX starts at 0x80 and RX at 0x00. RX therefore maximized at 128 Bytes
//For TX we have to set the PAYLOAD_LENGTH
//writeRegister(REG_PAYLOAD_LENGTH, (uint8_t) PAYLOAD_LENGTH); // set 0x22, 0x40==64Byte long
// Set CRC Protection for MAX payload protection
//writeRegister(REG_MAX_PAYLOAD_LENGTH, (uint8_t) MAX_PAYLOAD_LENGTH); // set 0x23 to 0x80==128
//Set the start address for the FiFO (Which should be 0)
writeRegister(REG_FIFO_ADDR_PTR, (uint8_t) readRegister(REG_FIFO_RX_BASE_AD)); // set 0x0D to 0x0F (contains 0x00);
// Low Noise Amplifier used in receiver
writeRegister(REG_LNA, (uint8_t) LNA_MAX_GAIN); // 0x0C, 0x23
// 5. Accept no interrupts except RXDONE, RXTOUT en RXCRC
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) ~(
IRQ_LORA_RXDONE_MASK |
IRQ_LORA_RXTOUT_MASK |
IRQ_LORA_HEADER_MASK |
IRQ_LORA_CRCERR_MASK));
// set frequency hopping
if (gwayConfig.hop) {
//writeRegister(REG_HOP_PERIOD, 0x01); // 0x24, 0x01 was 0xFF
writeRegister(REG_HOP_PERIOD,0x00); // 0x24, 0x00 was 0xFF
}
else {
writeRegister(REG_HOP_PERIOD,0x00); // 0x24, 0x00 was 0xFF
}
// Set RXDONE interrupt to dio0
writeRegister(REG_DIO_MAPPING_1, (uint8_t)(
MAP_DIO0_LORA_RXDONE |
MAP_DIO1_LORA_RXTOUT |
MAP_DIO2_LORA_NOP |
MAP_DIO3_LORA_CRC));
// 7+8. Set the opmode to either single or continuous receive. The first is used when
// every message can come on a different SF, the second when we have fixed SF
if (gwayConfig.cad) {
// cad Scanner setup, set _state to S_RX
// Set Single Receive Mode, and go in STANDBY mode after receipt
_state= S_RX; // 8.
opmode(OPMODE_RX_SINGLE); // 7. 0x80 | 0x06 (listen one message)
}
else {
// Set Continous Receive Mode, useful if we stay on one SF
_state= S_RX; // 8.
# if _DUSB>=1
if (gwayConfig.hop) {
Serial.println(F("rxLoraModem:: ERROR continuous receive in hop mode"));
}
# endif
opmode(OPMODE_RX); // 7. 0x80 | 0x05 (listen)
}
// 9. clear all radio IRQ flags
writeRegister(REG_IRQ_FLAGS, 0xFF);
return;
}// rxLoraModem
// ----------------------------------------------------------------------------------------
// function cadScanner()
//
// CAD Scanner will scan on the given channel for a valid Symbol/Preamble signal.
// So instead of receiving continuous on a given channel/sf combination
// we will wait on the given channel and scan for a preamble. Once received
// we will set the radio to the SF with best rssi (indicating reception on that sf).
// The function sets the _state to S_SCAN
// NOTE: DO not set the frequency here but use the frequency hopper
// ----------------------------------------------------------------------------------------
void cadScanner()
{
// 1. Put system in LoRa mode (which destroys all other modes)
//opmode(OPMODE_LORA);
// 2. Put the radio in sleep mode
opmode(OPMODE_STANDBY); // Was old value
// 3. Set frequency based on value in gwayConfig.ch // might be needed when receiving down
setFreq(freqs[gwayConfig.ch].upFreq); // set to the right frequency
// For every time we start the scanner, we set the SF to the begin value
//sf = SF7; // XXX 180501 Not by default
// 4. Set spreading Factor and CRC
setRate(sf, 0x04);
// listen to LORA_MAC_PREAMBLE
writeRegister(REG_SYNC_WORD, (uint8_t) 0x34); // set reg 0x39 to 0x34
// Set the interrupts we want to listen to
writeRegister(REG_DIO_MAPPING_1, (uint8_t)(
MAP_DIO0_LORA_CADDONE |
MAP_DIO1_LORA_CADDETECT |
MAP_DIO2_LORA_NOP |
MAP_DIO3_LORA_CRC ));
// Set the mask for interrupts (we do not want to listen to) except for
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) ~(
IRQ_LORA_CDDONE_MASK |
IRQ_LORA_CDDETD_MASK |
IRQ_LORA_CRCERR_MASK |
IRQ_LORA_HEADER_MASK));
// Set the opMode to CAD
opmode(OPMODE_CAD);
// Clear all relevant interrupts
//writeRegister(REG_IRQ_FLAGS, (uint8_t) 0xFF ); // May work better, clear ALL interrupts
// If we are here. we either might have set the SF or we have a timeout in which
// case the receive is started just as normal.
return;
}// cadScanner
// ----------------------------------------------------------------------------------------
// First time initialisation of the LoRa modem
// Subsequent changes to the modem state etc. done by txLoraModem or rxLoraModem
// After initialisation the modem is put in rx mode (listen)
// 1. Set Radio to sleep
// 2. Set opmode to LoRa
// 3. Set Frequency
// 4. Set rate and Spreading Factor
// 5. Set chip version
// 6. Set SYNC word
// 7. Set ranp-up time
// 8. Set interrupt masks
// 9. Clear INT flags
// ----------------------------------------------------------------------------------------
void initLoraModem()
{
_state = S_INIT;
#if defined(ESP32_ARCH)
digitalWrite(pins.rst, LOW);
delayMicroseconds(10000);
digitalWrite(pins.rst, HIGH);
delayMicroseconds(10000);
digitalWrite(pins.ss, HIGH);
#else
// Reset the transceiver chip with a pulse of 10 mSec
digitalWrite(pins.rst, HIGH);
delayMicroseconds(10000);
digitalWrite(pins.rst, LOW);
delayMicroseconds(10000);
#endif
// 1. Set radio to sleep
opmode(OPMODE_SLEEP); // set register 0x01 to 0x00
// 2 Set LoRa Mode
opmode(OPMODE_LORA); // set register 0x01 to 0x80
// 3. Set frequency based on value in gwayConfig.ch
setFreq(freqs[gwayConfig.ch].upFreq); // set to 868.1MHz or the last saved frequency
// 4. Set spreading Factor
setRate(sf, 0x04);
// Low Noise Amplifier used in receiver
writeRegister(REG_LNA, (uint8_t) LNA_MAX_GAIN); // 0x0C, 0x23
# if _PIN_OUT==4
delay(1);
# endif
// 5. Set chip type/version
uint8_t version = readRegister(REG_VERSION); // Read the LoRa chip version id
if (version == 0x22) {
// sx1272
# if _DUSB>=2
Serial.println(F("WARNING:: SX1272 detected"));
# endif
sx1272 = true;
}
else if (version == 0x12) {
// sx1276?
# if _DUSB>=2
if (debug >=1)
Serial.println(F("SX1276 starting"));
# endif
sx1272 = false;
}
else {
// Normally this means that we connected the wrong type of board and
// therefore specified the wrong type of wiring/pins to the software
// Maybe this issue can be resolved of we try one of the other defined
// boards. (Comresult or Hallard or ...)
# if _DUSB>=1
Serial.print(F("Unknown transceiver="));
Serial.print(version,HEX);
Serial.print(F(", pins.rst =")); Serial.print(pins.rst);
Serial.print(F(", pins.ss =")); Serial.print(pins.ss);
Serial.print(F(", pins.dio0 =")); Serial.print(pins.dio0);
Serial.print(F(", pins.dio1 =")); Serial.print(pins.dio1);
Serial.print(F(", pins.dio2 =")); Serial.print(pins.dio2);
Serial.println();
Serial.flush();
# endif
die(""); // Maybe first try another kind of receiver
}
// If we are here, the chip is recognized successfully
// 6. set sync word
writeRegister(REG_SYNC_WORD, (uint8_t) 0x34); // set 0x39 to 0x34 LORA_MAC_PREAMBLE
// prevent node to node communication
writeRegister(REG_INVERTIQ,0x27); // 0x33, 0x27; to reset from TX
// Max Payload length is dependent on 256 byte buffer. At startup TX starts at
// 0x80 and RX at 0x00. RX therefore maximized at 128 Bytes
writeRegister(REG_MAX_PAYLOAD_LENGTH,MAX_PAYLOAD_LENGTH); // set 0x23 to 0x80==128 bytes
writeRegister(REG_PAYLOAD_LENGTH,PAYLOAD_LENGTH); // 0x22, 0x40==64Byte long
writeRegister(REG_FIFO_ADDR_PTR, (uint8_t) readRegister(REG_FIFO_RX_BASE_AD)); // set reg 0x0D to 0x0F
writeRegister(REG_HOP_PERIOD,0x00); // reg 0x24, set to 0x00
// 7. Config PA Ramp up time // set reg 0x0A
writeRegister(REG_PARAMP, (readRegister(REG_PARAMP) & 0xF0) | 0x08); // set PA ramp-up time 50 uSec
// Set 0x4D PADAC for SX1276 ; XXX register is 0x5a for sx1272
writeRegister(REG_PADAC_SX1276, 0x84); // set 0x4D (PADAC) to 0x84
//writeRegister(REG_PADAC, readRegister(REG_PADAC) | 0x4);
// 8. Reset interrupt Mask, enable all interrupts
writeRegister(REG_IRQ_FLAGS_MASK, 0x00);
// 9. clear all radio IRQ flags
writeRegister(REG_IRQ_FLAGS, 0xFF);
}// initLoraModem
// ----------------------------------------------------------------------------------------
// Void function startReceiver.
// This function starts the receiver loop of the LoRa service.
// It starts the LoRa modem with initLoraModem(), and then starts
// the receiver either in single message (CAD) of in continuous
// reception (STD).
// ----------------------------------------------------------------------------------------
void startReceiver()
{
initLoraModem(); // XXX 180326, after adapting this function
if (gwayConfig.cad) {
# if _DUSB>=1
if (( debug>=1 ) && ( pdebug & P_SCAN )) {
Serial.println(F("S PULL:: _state set to S_SCAN"));
if (debug>=2) Serial.flush();
}
# endif
_state = S_SCAN;
sf = SF7;
cadScanner();
}
else {
_state = S_RX;
rxLoraModem();
}
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) 0x00);
writeRegister(REG_IRQ_FLAGS, 0xFF); // Reset all interrupt flags
}
// ----------------------------------------------------------------------------------------
// Interrupt_0 Handler.
// Both interrupts DIO0 and DIO1 are mapped on GPIO15. Se we have to look at
// the interrupt flags to see which interrupt(s) are called.
//
// NOTE:: This method may work not as good as just using more GPIO pins on
// the ESP8266 mcu. But in practice it works good enough
// ----------------------------------------------------------------------------------------
void ICACHE_RAM_ATTR Interrupt_0()
{
_event=1;
}
// ----------------------------------------------------------------------------------------
// Interrupt handler for DIO1 having High Value
// As DIO0 and DIO1 may be multiplexed on one GPIO interrupt handler
// (as we do) we have to be careful only to call the right Interrupt_x
// handler and clear the corresponding interrupts for that dio.
// NOTE: Make sure all Serial communication is only for debug level 3 and up.
// Handler for:
// - CDDETD
// - RXTIMEOUT
// - (RXDONE error only)
// ----------------------------------------------------------------------------------------
void ICACHE_RAM_ATTR Interrupt_1()
{
_event=1;
}
// ----------------------------------------------------------------------------------------
// Frequency Hopping Channel (FHSS) dio2
// ----------------------------------------------------------------------------------------
void ICACHE_RAM_ATTR Interrupt_2()
{
_event=1;
}