Lora module
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.
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; }