In a previous post, you had the opportunity to discover a new Shield for Arduino, based on Semtech’s SX1278 chip that, in view of a very limited price, allows the implementation of a radio communication for a wide range of applications: from the transmission of digital signals in OOK or FSK mode, to the long distance communication in LoRa mode. In fact, the chip has been conceived in a way that follows a real approach to the SDR philosophy. We will remember that the SDR philosophy consists in making the transmission device completely configurable and adaptable, by making extensive use of digital techniques (as a substitution for the analog ones); this makes the SX1278 very flexible and programmable. Its programmability is based on about a hundred configuration registries; in order to go beyond the complexity thus generated, we developed a library that allows for a quick configuration of the main parameters.
Receiving in OOK
We will remember that the SX1278 Semtech chip also allows for two transmission modes, in addition to the standard LoRa (long range) one: they are described as follows.
- A Basic mode in which the SX1278 operates as a simple radio module, and the data is serialized/deserialized by Arduino, by means of Semtech integrated circuit’s I/O DIO2 pin, that is connected to Arduino’s D3 pin.
- A Packet mode, in which the data is input/gathered – byte by byte – to and from a broadcasting/receiving queue, and in which the SX1278 may operate with a considerable autonomy as for the management of the radio connection.
The packet mode considers a series of data added by the SX1278 in order to synchronize the receiving, and a network address that may cause the packet to be discarded if it does not correspond to the one set on the receiving board. For these reasons, we cannot use the packet mode when trying to receive and decode the remote controls of remote controlled socket systems. When transmitting, the removal of the preamble, of the (Sync) address and of the CRC does not have any effect, but when receiving such elements are needed for the synchronization and the validation and they cannot be removed.
Because of this reason, when we wanted to create a scanner function in order to detect the remote control’s encoding as for the Velleman and Avidsen systems, we had to use the basic mode. This time, therefore, Arduino receives the pulses on the D3 pin and tries to decode them.
The pulse reception is a task for the radio module, therefore it is common to both the packet and the “continuous” modes. In the case of the OOK modulation, it is based on three possible alternatives:
- Adaptive management of the pulse detection threshold gestione; this is the default mode and consists in adapting the indicator’s threshold to the radio signal (RSSI) value (that is continuously sampled), decreased of 6 dBms;
- A manually fixed threshold, by means of the registry 0x15;
- A continuously updated threshold, as an average of RSSIs (this is the most complex of the modes).
In the case of the adaptive threshold it is possible to regulate the readjustment time of the same after the end of the pulse, in order to optimize the behaviour for peculiar cases.
Actually, the RSSI value’s sampling frequency may also be changed by means of the 0x0E registry, even if the default value is usually adequate.
In the case of the FSK modulation, a Gaussian filter with three different values may be applied, or the pulse’s rise or fall speed may be adjusted.
Scanner for the remote controls’ decoding
Additional functions have been added in the REMOTEC class, so to create this simplified receiving; you may find them in Table.
The sketch to be used in order to create such a function, and that you find proposed in this installment is a very short one – as you may see from Listing 1 – and enables the scanning of remote controls.
Listing1
/**************************************************************************/ /* * Sketch to analyze the transmission of the radio controls or Avidsen Velleman * Determines the length basic dell'impuso and try to decode the command. * */ #include <SPI.h> #include “REMOTEC.h” REMOTEC RC; void setup() { Serial.begin(9600); RC.begin(); if (RC.setScannerMode()) Serial.println(“Receiving...”); else Serial.println(“Problem! No ready to receive”); } void loop() { RC.scanImpulses(); }
Transmission
and packets’ reception
The transmission and the reception of normal packets require some configurations to be set; but while as for the transmission the steps are only a few ones, as regards the reception things are a bit more complicated.
As you could see in the previous installment, the SX1278 deals with adding the preamble, the network address and a CRC to the data (payload), by following the indications given by the configuration registries. It is however opportune to point out that in non-LoRa modes the FIFO queue is limited to 64 bytes, therefore if you want to send longer packets, the queue must be continuously fed. The same goes, conversely, as regards the reception.
For this reason, the interrupts (and the flags) connected to the FIFO queue have been prepared: for example an empty queue, a full queue, or a queue that is full beyond a certain level (registry 0x35).
As for the reception, the SX1278 integrated circuit has to be placed in the FSRX state (state 4) and, after a few hundreds of microseconds, in RX mode (state 5). At this stage it is needed to verify the arrival of the data, by means of interrupts or flags. For example, it is possible to verify the “PreambleDetect” flag or, even better, the “SyncAddressMatch” flag that informs that the packet is really addressed to the receiver.
It is then possible to download the packet in a single time – if it is under 64 bytes – by verifying the “PayloadReady” flag; or it is possible to download the FIFO queue until the previous flag does not inform about the completion.
At this stage, the SX1278 may be placed in standby mode, or it is possible to restart the procedure while waiting for other packets.
Actually, the reception may be automatized via the usage of a registry of “AutoRestartRxMode” conditions.
Finally, it is also possible to enable a “Sequencer”, that is to say a veritable self-management program for the transmission-reception. This sophisticated “finite-state machine” enables the automatization of the whole communication process. We will leave to you the reading of SX1278’s data-sheet, so that you may deepen your knowledge on the subject of this complex mode.
LoRa mode
With the packet mode, it is possible to make the boards communicate with the SX1278 in a sophisticated, reliable way, and requiring only a small intervention from Arduino, be it with the OOK modulation or – even better – with the FSK modulation. However, if a good speed is not required (that is to say high bps), it is much more convenient to switch to the SX1278’s main mode – that is to say the LoRa mode – that increases the range and the reliability very much.
As mentioned in the first installment, the LoRa mode uses a proprietary modulation protocol that is based on a dispersal function of each bit on more modulation elements and on a wide spectrum (centered on the carrier frequency). In practice, each byte (or information symbol) is represented by more modulation elements (chip): we are then talking about the “Spreading Factor”, that is the number of chips used for each symbol. The greater the “Spreading Factor”, the better the receiving; that is to say, the signal/noise ratio (SNR) gets better. The “Spreading Factor” may vary from 64 to 4,096. The default value is 128 and it may be modified by using the registry 0x1E (we will remember that the registries have different copies
for the LoRa/OOK-FSK mode, even if they share the same address).
The “Spreading Factor” is coded in the registry 0x1E, along with the numbers from 6 to 12, as it can be seen from Table. The default value is 128.
Moreover, in order to increase the protocol’s stability, a “Cyclic Error Connection” is added, that is to say, some additional bits for the correction of possible errors; this increases further the length of the information sent. The number of these redundant bits may be modified in the registry 0x1D, that by default is regulated on 5 bits out of 4 (code 1); for the purpose please refer to Table (the default value is 4/5).
At this stage, it is also possible to modify the emission spectrum, that by default setting is 125kHz. This piece of data is also defined in the registry 0x1D, and may have values from 7.8kHz to 500kHz (codes from 0 to 9). By increasing the spectrum the emission rate increases as well, while the signal’s intensity decreases; but above all we have to take into account the law regulations concerning the radio emissions.
In any case, with the predefined values as for the “Spreading Factor”, the “Cycling Coding Rate” and the emission band, there is approximately a rate of nominal 8 kbps, while with a spread of 4,096 (code 12) and a redundancy of 4/8 (code 4) there are about 150 bps. As for the calculation, it is sufficient to consider a modulation chip for each band Hz. As for the “Symbol Rate”, it is enough to divide by 8: in practice we go from a thousand bytes per second to some tens of bytes per second, not wanting to count some extreme cases.
LoRa’s Packet mode
Even LoRa’s modulation communicates in the packet mode. The standard packet is also called as one “with explicit header” and has a variable length. It is composed of the following parts.
- A synchronization preamble with a modifiable length and a format having a predefined and proprietary symbol (byte); the predefined length is 12 symbols.
- A header with its CRC; the header contains the information concerning the payload, such as its length, the “code rate” adopted by the payload and the presence (or not) of the CRC at the end of the payload. The same header, however, is always transmitted with the maximum redundancy (code-rate=4/8) and has its CRC.
- A variable length Payload (for a maximum of 255 bytes).
- A possible CRC, referred to the payload (and enabled by means of the bit 2 of the registry 0x1E).
There is even an “implicit” mode, in which the header is removed, so to reduce the length. In this case the payload’s length must be fixed along with its other features, and must correspond between the transmitter and the receiver. This mode is defined by the bit 0 of the registry 0x10.
In the case of long and slow transmissions, it is possible to enable a flag that forces the SX1278 to a greater frequency stability. This “LowDataRateOptimize” flag corresponds to bit 3 of the registry 0x25 but, being in the case, it must be configured for both the transmit and the receive functions.
As you may see, this time there is no network address that is automatically controlled by the SX1278; consequently, a possible addressing may only be a payload’s duty, verified by Arduino.
Frequency hopping option
In the case you do not want to bind a channel of the available carrier frequencies’ band for a long time, it is possible to resort to the “Frequency Hopping Spread Spectrum” (FHSS). This mode could be needed in the case of slow and long packets, in areas in which the laws regarding the matter are more binding ones (for example, in the U.S.A., with the band of 902÷928 MHz), therefore in our case it is only optional.
When choosing this mode (via the configuration of the registry 0x24 with a value that is greater than zero), a greater intervention from Arduino is required; this involves establishing a table of the frequencies that are shared by the transmitter and by the receiver. At this stage, by starting from the index 0 channel, and after that the time established in the registry 0x24 has passed, an interrupt is generated and Arduino has the duty to change the frequency on channel 1… and so on. The library does not take this mode – that is to say, the frequency hopping – into account.
The FIFO queue in LoRa mode
This time the FIFO queue is 256 byte but, above all, it has a completely adaptable management, in the sense that it is composed by two separated but variable sectors: one is used for the packets to be transmitted, the other one for the ones to be received. Therefore, it is possible to use it for the transmission and the reception at the same time, on the contrary of what happened for the FIFO queue in OOK/FSK mode.
Moreover, the size of the two sectors may be configured, and it is possible to increase the area destined to the receiving, to the disadvantage of the one for the transmission, or vice versa. In fact, this time the queue is completely managed by means of the pointers’ technique. A pointer (registry 0x0F) points to the received data sector’s base and the other one (registry 0x0E) to the base of the data that is ready to be sent. An access pointer (registry 0x0D) is needed in order to address the data to be written or to be read, that is anyway accessed by means of the FIFO port (registry 0x00).
As a default configuration, the FIFO is equally divided between the RX and the TX areas, therefore the RX area starts from the address 0x0, while the TX one starts from the address 0x80. clarifies the FIFO’s composition. The data to be sent (the payload) is inserted by writing on the FIFO port (registry 0x00), after having loaded the TX area’s starting address value on the pointer’s registry, and by inserting the total of the registry 0x13 at the end.
The received data, on the other hand, is collected by loading the value contained in the starting registry of the last packet received, on the pointer’s registry. Since the packet has a variable length, a further registry (0x13) contains the number of bytes that have arrived and thus that are to be downloaded.
Interrupts and timeouts
The interrupts/flags are found in a different registry (0x12) from the one that is used in the OOK/FSK mode, and they correspond to the signals summarized in Table.
To each one of these flags corresponds an interrupt that may be detected on a specific DIO pin, accordingly to a configuration that may be set by means of the “mapping” registries 0x40 and 0x41, as shown in the first part of the article.
Moreover, the interrupts may be disabled by using the “mask” registry 0x11. The flags/interrupts may be manually “reset”, but for some of them it happens in an automatic way.
There is a timeout for the reception waiting time that may be set by means of the registry 0x1F and of the first two bits of the registry 0x1E, for a maximum value of 1023. This timeout, however, is not expressed in time units but in symbols units (bytes), thus it corresponds to the time needed in order to transmit n symbols, which makes the timeout a variable that may change as a function of the communication’s features. In practice, in terms of time units, the formula in figure is valid.
Operativity
The activity of the SX1278 is spelled out by the updates of the Operating Mode registry (registry 0x01). Neither less nor more than in OOK/FSK mode. There is, however, some change concerning the receiving and it has been added a further state. For your convenience, we summarize all the operating states in Table.
Transmission
After having defined the features of the packets and of the communication (a one-time procedure), the necessary steps in order to transmit a packet – starting from the the standby state – are:
- to initialize the FIFO pointer (0x0D) at the base of the TX area (the default value is 0x80);
- to insert the payload’s bytes by means of the FIFO port (registry 0x00); the pointer will increase by itself;
- in the end, please insert the number of bytes to be sent (payload’s length) in the registry 0x22;
- to pass to the FSTX state and wait for about a hundred microseconds;
- to start the transmission (TX state);
- after the last byte has been transmitted, the SX1278 will automatically be brought to the standby state;
It is possible to verify if the transmission actually happened, by means of the TxDone flag/interrupt. Please notice that the passage to the Sleep state deletes the content of the FIFO memory, while when remaining in Standby mode, the FIFO memory may be used again in order to send the same message: it is sufficient to not update the pointer.
Reception
The reception may occur for a single packet or in a continuous way – packet after packet – in the sense that it is possible to start the continuous reception by verifying that the packet has arrived and by downloading it without the SX1278 changing the operating state. As regards the single reception, it is carried out via this sequence:
- to initialize the FIFO pointer (0x0D) at the base of the TX area (the default value is è 0x00);
- to pass to the FSRX state and to wait for about a hundred microseconds;
- to start the receiving (RXsingle state); the reception automatically ends by bringing back the SX1278 to the Standby state, when the configured Timeout has been passed (Timeout flag/interrupt) or when a packet has arrived (RxDone flag/interrupt);
- in the case a packet arrived, it will be extracted by the FIFO, by reading the port 0x00 for as many times as indicated by the value of the NumeroByte Ricevuti registry (0x13).
As for the continuous reception mode, the execution sequence is:
- to initialize the FIFO pointer (0x0D) at the base of the RX area (the default value is 0x00);
- to pass to the FSRX state and to wait for about a hundred microseconds;
- to start the continuous reception mode (Rxcontinuous state); the activity may be blocked only manually, by changing the state;
- if a packet arrived, the RxDone flag is updated and the receiving continues;
- if the RxDone flag is activated, the registry pointer is loaded with the value of the Start Pack registry (RxCurrentAddress 0x10) that points to the beginning of the last complete packet arrived;
- the FIFO queue is read for a number of times that corresponds to the length of the last packet arrived (registry 0x13);
- the RxDone flag is zeroed, so to signal a new packet;
- if you want to interrupt the continuous reception mode, it is sufficient to bring the SX1278 to the Standby state, otherwise it is left active and the receiving continues.
Channel Activity Detection (CAD)
Since in LoRa mode the signal gets confused with the noise, if you wanted to monitor the presence of a communication, it would not be acceptable to use the RSSI value; in order to solve this, a mode has been prepared: by means of it, the SX1278 tunes in for the preamble only. In this way, it is possible to verify the channel’s occupation or possibly to pass to the receiving state. In this state, the preamble’s reception is highlighted by the CadDetected flag/interrupt and the SX1278 returns to the Standby state. The preamble’s detection has been made very quickly (some bytes) by means of correlation algorithms and therefore uses little energy.
The library in LoRa mode
The library for the LoRa mode is defined by the LORA class, that is composed of a few major instructions, that we list in Table. A few other functions are then available, in order to modify the transmission parameters: spreading factor, band width, etc.
The basic configuration has a carrier frequency of 434 MHz, a code 9 spreading factor, a code 6 band width and a code 4 redundancy. In order to change other features, it is always possible to use even the functions of the SX class, that allow to operate on all the registries. The SX class is automatically imported from the LORA class. Let’s see now an example of LoRa communication, that is composed of two sketches: one that transmits a short sentence, and one that receives and transmits it again, as an echo. These sketches have been tested in the city and have shown an operating range of about 200 m, in presence of obstacles (such as walls and buildings), while in the open field they allowed for a range of about 1 km.
Transmitter’s Sketch
The sketch (Listing 2) verifies and initializes the board, then it starts to wait for a button closing the pin 8 to ground. When the button is pressed the predetermined message is sent. Therefore, the sketch tunes in for the reception, for a certain time, waiting for the echo. When the latter arrives, it compares it to the starting message and if they correspond, it warns of the correct reception and prints an evaluation of the values for RSSI and SNR to console.
Listing2
#include <LORA.h> #include <SPI.h> #define psound 9 // pin for buzzer #define pingo 8 // pin forpush-button #define pinf 7 // pin for the signal LED #define MESS “Simple echo test” // message to send and receive as eco #define RXTIMEOUT 500 //tenths of milliseconds (500 = 500 * 10 = 5 seconds) LORA LR; // LORA instance of the class #define inplen 64 // length of the shipping buffer #define reclen 64 // length of the receive buffer char inpbuff[inplen]; // buffer of the shipping char recbuff[reclen]; // bufferof the receive char data[64]; //buffer for the RSSI and SNR values #define format “|Rssi: %d RssiPk: %d SnrPk %d| “ int SF=9; //Spreading factor code (if you want to change for test) int BW=6; //Bandwidth code (if you want to change for test) int PWR=2; //Transmitting power (code) boolean SHIELD=true; void setup() { pinMode(pingo,INPUT_PULLUP); // Pull-up fir push-button digitalWrite(pinf,0); // initializes led pinMode(pinf,OUTPUT); pinMode(psound,OUTPUT); //initializes buzzer Serial.begin(9600); if (!LR.begin()) //if the card is not out {Serial.println(“No LoRa shield detected! Stop!”);SHIELD=false;return;} Serial.println(“LoRa echo transmitter.”); SX.setPower(PWR); // LR.setConfig(SF,BW,4); // if you want to change to testing (of: 9,6,4) showConfig(); //Printing configuration strlcpy(inpbuff,MESS,inplen); //loads the message into the buffer Serial.print(“Close pin “); Serial.print(pingo);Serial.println(“ to ground to send message”); } void loop() { delay(200); if (!SHIELD) return; if (getInput()) {sendBuff();getReplay();} //If push-button pressed sends and receives echo } boolean getInput() { if (digitalRead(pingo)>0) return false; //test push-button return true; } void sendBuff() { sound(300,1); // warns that is shipping blinkpinf(100,10); digitalWrite(pinf,LOW); Serial.print(“> “);Serial.println(inpbuff); int f=LR.sendMess(inpbuff); //sends the message if (f<0) Serial.println(“Error in transmission!”); SX.setState(STDBY); } void getReplay() { LR.receiveMessMode(); //It puts in reception boolean OK=false; int i; for (i=0;i<RXTIMEOUT;i++) // occurs if a response comes within RX TIMEOUT {if (LR.dataRead(recbuff,reclen)>0) {OK=true;break;} delay(10);} if (!OK) {Serial.println(“No replay!”);blinkpinf(50,20);sound(300,3);return;} //if you print RSSI values and checking the correctness snprintf(data,63,format,SX.getLoraRssi(),SX.lastLoraPacketRssi(), SX.lastLoraPacketSnr()); Serial.println(data); Serial.print(“< “);Serial.println(recbuff); int inc=strlen(inpbuff); if (strncmp(recbuff,inpbuff,inc)==0) {digitalWrite(pinf,HIGH);sound(1000,1);} else {blinkpinf(500,4);sound(500,2);} } void blinkpinf(int time,int n) { int i; byte p=1; for (i=0;i<n;i++) {digitalWrite(pinf,p);delay(time); p=p^1;} digitalWrite(pinf,0); } void sound(int time,int n) { // return; //uncomment if you like sound int i; for (i=0;i<n;i++) {digitalWrite(psound,1);delay(time);digitalWrite(psound,0);delay(100);} digitalWrite(psound,0); } void showConfig() { Serial.print(“Replay timeout (millisec.): “);Serial.println(RXTIMEOUT*10); Serial.print(“Frequence: “);Serial.println(SX.readFreq()); Serial.print(“Transmit power (mW): “);Serial.println(SX.getPower(3)); Serial.print(“Preamble bytes: “); Serial.print(SX.getLoraPreambleLen());Serial.println(“+4”); snprintf(data,63,”SpFactor: %d BandW: %d Cr: %d”, SX.getLoraSprFactor(),SX.getLoraBw(),SX.getLoraCr()); Serial.println(data); Serial.print(“Rate (byte/sec): “);Serial.println(SX.getSRate()); }
The signals are made on the console, but also with a LED on pin 7 and with a buzzer on pin 9.
Reception and echo sketches
After the initialization phase, the sketch goes in reception mode and waits for the messages; once one is received, it exits the reception mode, and views the RSSI and SNR evaluation data. It then sends the message again, and then returns to the reception mode (Listing 3).
Listing3
#include <LORA.h> #include <SPI.h> LORA LR; //LORA instance of the class #define reclen 64 // length of the buffer char recbuff[reclen]; // Shipping buffer char sendbuff[reclen]; // receive buffer char data[64]; // buffer for the RSSI and SNR values #define format “|Rssi: %d RssiPk: %d SnrPk %d| “ int SF=9; //Spreading factor code (if you want to change for test) int BW=6; //Bandwidth code (if you want to change for test) int PWR=2; //Transmitting power (code) boolean SHIELD=true; void setup() { Serial.begin(9600); if (!LR.begin()) {Serial.println(“No LoRa shield detected! It can’t perform echo!”);SHIELD=false;return;} Serial.println(“LoRa echo receiver.”); SX.setPower(PWR); // LR.setConfig(SF,BW,4); //if you want to change to testing (of: 9,6,4) showConfig(); Serial.println(“Waiting for message...”); LR.receiveMessMode(); //It puts in reception mode } void loop() { if (!SHIELD) {delay(200);return;} if (getMess()) {sendEcho();LR.receiveMessMode();} //if there's mess. Ships eco // And call into reception } boolean getMess() { if (LR.dataRead(recbuff,reclen)<=0) {delay(10);return false;} Serial.print(“< “);Serial.println(recbuff); snprintf(data,63,format,SX.getLoraRssi(),SX.lastLoraPacketRssi(), SX.lastLoraPacketSnr()); Serial.println(data); return true; } void sendEcho() { SX.setState(STDBY); //exits the receive mode delay(300); strncpy(sendbuff,recbuff,reclen); //copy what he has received in the shipping buffer int bufflen=strlen(sendbuff); Serial.print(“> “);Serial.println(sendbuff); //spedisce if (LR.sendMess(sendbuff,bufflen)<0) Serial.println(“Sending error!”); } void showConfig() { Serial.print(“Frequence: “);Serial.println(SX.readFreq()); Serial.print(“Transmit power (mW): “);Serial.println(SX.getPower(2)); Serial.print(“Preamble bytes: );Serial.print(SX.getLoraPreambleLen());Serial.println(“+4”); snprintf(data,63,”SpFactor: %d BandW: %d Cr: %d”,SX.getLoraSprFactor(), SX.getLoraBw(),SX.getLoraCr()); Serial.println(data); Serial.print(“Rate (byte/sec): “);Serial.println(SX.getSRate()); }