Lora module

From Alnwlsn - Projects Repository
Jump to navigation Jump to search

When I was working on my robot wheelchair base, and changing the radio to a LoRa radio, I soon ran into some problems, mainly, that I was out of IO pins. The setup of these radios is also not that ideal; they use interrupts when I am already using interrupts for another realtime application, and I have to constantly check the radios for any new messages, slowing down my loop time. Also, the modules require some specialized setup in software, so if I ever wanted to change out the radios to a different type, I would have to redo my entire software.

All of these problems could be solved quite simply by having a separate microcontroller to manage the radios, so that's what I did. The first version used a client module and server module like the examples in the Radiohead library, but I revised this so that there is just one module, and it is up to the things they are connected to to figure it out.

However, because the modules have their own processor, you don't have to do any error checking on the host, the modules will just tell you when a good message comes in.

For simplicity, all the radio settings: frequency, channel, radio power, etc. are all handled by the module programming.

This is that generic LoRa module

Hardware/Interfacing I made the modules out of a 3.3V Arduino Pro Mini, a RFM95 LoRa module, and a logic level converter (so that I can use the modules with 5V devices).

The interface to the module is just three wires: two for I2C, and one that signals when a new valid message has been received.

Operation The operation is about as simple as it can get. The modules appear to the host at I2C address 8 (you can change this to whatever you want).

The size of all messages is 32 bytes, which is the size of the Arduino Wire library's buffer.

To send a message, write out 32 bytes to the module over the I2C bus. The message will be transmitted as soon as the 32 bytes are written.

When not transmitting, the module is listening. When a message comes in, it does a CRC16 on it to make sure it was received correctly. If true, it will save the message and assert the RXREAY pin high. On the host, you should monitor this pin to know when a valid message comes in. Then, you can request a Read of 32 bytes over i2c, and expect to get 32 bytes in, which is the received message. As soon as the Read is done, the RXREADY pin goes low again.

Sketch - Aug 2019 v1

Arduino Radiohead library is required. One item the next time I have my wheelchair controllers open: lockout the reception of new messages until we Read out the valid message we already have.

//gLora module - easily send data using i2c over radio - Alan Wilson 2019
#define DIO0 3
#define DIO1 3
#define RESET 5
#define NSS 6
#include <util/crc16.h>
#define RXREADY 0

#include <Wire.h>

#include <SPI.h>
#include <RH_RF95.h>
RH_RF95 rf95(NSS, DIO0); //an arduino pro mini with bare RFM95
uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; // Should be a message for us now 
uint8_t len = sizeof(buf);

#define packetsize 32 //this is Wire max size
uint8_t rxd[packetsize]; //data packets recived 
uint8_t txd[packetsize]; //data packets to send
bool rxdvalid=0;

bool txdFlag=0; //when 1, do a request to the serer 

void setup(){
  pinMode(DIO1, INPUT_PULLUP); //not using this pin right now
  
  digitalWrite(RESET, LOW); //reset radio
  delay(200);
  pinMode(RESET, INPUT_PULLUP);
  
  rf95.init();

  pinMode(RXREADY, OUTPUT);digitalWrite(RXREADY, LOW); //pin zero will be used as the "new data" signal

  rf95.setModemConfig(1); //Bw500Cr45Sf128
  rf95.setFrequency(915.0);
  rf95.setTxPower(23,false); //set RFM95 between 5 and 23

  Wire.setClock(400000);
  Wire.begin(8);
  Wire.onRequest(requestEvent);
  Wire.onReceive(receiveEvent);
}

void requestEvent(){
  //copy rxd data; send it out
  for(byte i=0; i<packetsize; i++){
    buf[i]=rxd[i]; 
  }
  Wire.write(buf,packetsize);
  rxdvalid=0; //now that data has been read out, assume invalid
  digitalWrite(RXREADY, LOW);
}
void receiveEvent(int howMany){
  //copy txd data in
  byte iindex=0;
  while(Wire.available()){
    txd[iindex]=Wire.read();
    iindex++;
    if(iindex>=packetsize-1){iindex=packetsize-1;}
  }
  txdFlag=1; //set the flag for the radio to transmit
}

void loop(){
  if(txdFlag==1){
    txdFlag=0;
    //TRANSMIT***************************************
    uint16_t txcrc=CRC16(txd, packetsize);
    buf[0]=txcrc>>8;
    buf[1]=txcrc;
    for(byte i=0;i<packetsize;i++){
      buf[i+2]=txd[i];
    }
    rf95.send(buf, packetsize+2);
    rf95.waitPacketSent();
  }

  if(rf95.available()){
    if(rf95.recv(buf, &len)){
      uint16_t rxcrc=buf[0]<<8; rxcrc+=buf[1]; //get the crc sent along with the data
      for(int i=0; i<packetsize; i++){//shift all the buf data up 2 places for crc calc
          buf[i]=buf[i+2];
      }
      if(CRC16(buf, packetsize)==rxcrc){ //calculate crc of rxd data, if match, then data ok
        for(int i=0; i<packetsize; i++){
          rxd[i]=buf[i];
        }
        digitalWrite(RXREADY, HIGH);
      }else{/* bad data */}
    }
  }
} 

uint16_t CRC16(uint8_t *data, uint8_t lent){ //crc16 based on AVR _crc16_update
  uint16_t crc=0;
  for (int i=0;i<lent;i++){
    crc= _crc16_update (crc, data[i]); // update the crc value
  }
  return crc;
}