For a long time, I have wanted to experiment with LoRa (short for long-range), which is a spread spectrum modulation technique derived from chirp spread spectrum (CSS) technology. Since I am fond of vintage walkie-talkies utilizing radio waves, I decided to create a walkie-talkie (two-way radio) with a LoRa module from scratch to transmit and receive short text messages.
After perusing different LoRa module datasheets, I decided to employ the RYLR998 LoRa (transceiver) module since I wanted to make this project as compact as possible. The RYLR998 LoRa module provides ultra-long range spread spectrum communication with high sensitivity and high interference immunity while minimizing current consumption. Also, this module has a built-in antenna and supports AT commands via serial communication. You can inspect the product datasheet from here.
After connecting the RYLR998 module to the Arduino Nano, I utilized a Nokia 5110 screen and designed a custom 4x4 keypad with pushbuttons to obviate the need for sending and displaying text messages via the serial monitor. Finally, I added a 5mm common anode RGB LED to indicate different menu options and message notifications.
After completing wiring on a breadboard and testing the code for two LoRa-based walkie-talkies (two-way radios), I designed an Iron Man-inspired PCB for this project. I thought Iron Man was germane to my vintage walkie-talkie theme due to a scene from Iron Man 3 near the end of the movie, in which all remaining armors communicate with each other via radio waves covertly. Even though radio is seldom utilized by Iron Man in comics pages, I was inspired by the movie to design Iron Man-themed walkie-talkies (two-way radios) after recently rewatching it :)
Huge thanks to PCBWay for sponsoring this project.
Also, huge thanks to REYAX for sending me the RYLR998 LoRa modules.
Before prototyping my Iron Man-themed PCB design, I tested all connections and wiring with the Arduino Nano and the RYLR998 LoRa module for two walkie-talkies (two-way radios).
Then, I designed the Iron Man Walkie-Talkie (Two-Way Radio) PCB by utilizing KiCad - inspired by the invincible and legendary Iron Man :) I attached the Gerber file of the PCB below. Therefore, if you want, you can order this PCB from PCBWay to create your LoRa-based walkie-talkies (two-way radios) so as to transmit and receive text messages.
Click here to inspect and order this PCB directly on PCBWay.
First of all, by utilizing a soldering iron, I attached headers (female), resistors (1K, 4.7K, 10K), pushbuttons (6x6), a 5mm common anode RGB LED, and a power jack.
Component list on the PCB:
A1 (Headers for Arduino Nano)
LoRa_1 (Headers for RYLR998 LoRa Module)
S1 (Headers for Nokia 5110 Screen)
D1 (5mm Common Anode RGB LED)
K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, K12, K13, K14, K15, K16 (6x6 Pushbutton)
R1, R2, R3, R4, R5 (1K Resistor)
R6 (10K Resistor)
R7 (4.7K Resistor)
J1 (Power Jack)
Since I needed two Iron Man-themed walkie-talkies (two-way radios) so as to manage to transmit and receive text messages simultaneously, I soldered components to two PCBs.
Since the RYLR998 LoRa (transceiver) module requires to be adjusted to transmit and receive text messages simultaneously, I needed to configure each RYLR998 LoRa module with specified settings and addresses. Fortunately, I was able to configure RYLR998 modules effortlessly with the supported AT command list via serial communication.
To configure the RYLR998 modules and send text messages, I utilized the AT commands as follows. You can inspect all available AT commands from here.
#️⃣ Use the AT+ADDRESS command to specify the RYLR998 LoRa module address while transmitting and receiving text messages.
AT+ADDRESS=<Address>
<Address>=0~65535 (default 0)
#️⃣ Use the AT+NETWORKID command to set the LoRa network ID of the RYLR998 module. Setting the correct LoRa network ID is crucial since only the RYLR998 modules with the same LoRa network ID can communicate with each other.
AT+NETWORKID=<Network ID>
<Network ID>=3~15, 18 (default 18)
#️⃣ Use the AT+BAND command to set the LoRa radio frequency (Hz) of the RYLR998 module. Two modules must have the same radio frequency to transmit and receive messages.
AT+BAND=<parameter>
<parameter>=
470000000: 470000000Hz
915000000: 915000000Hz (default)
#️⃣ Use the AT+PARAMETER command to set the LoRa communication parameters of the RYLR998 module. Two modules must have the same parameters to communicate with each other successfully:
#️⃣ The spreading factor (SF) implies the chirp rate and thus controls the speed of data transmission. Lower spreading factors mean faster chirps and higher data transmission rates. Conversely, higher spreading factors imply fewer chirps and high sensitivity with longer transmission time.
#️⃣ The bandwidth means channels: 125 kHz, 250 kHz, or 500 kHz. The sensitivity and the data transmission time are inversely proportional to the bandwidth.
#️⃣ The coding rate refers to the proportion of transmitted bits that actually carry information - 6/8, 4/8, etc.
#️⃣ The preamble is used to keep the receiver synchronized with the incoming data stream. The default is 12 symbol lengths.
AT+PARAMETER=<Spreading Factor>, <Bandwidth>, <Coding Rate>, <Preamble>
<Spreading Factor>=7~11 (default 9)
<Bandwidth>=7~9
7: 125 kHz (default)
8: 250 kHz
9: 500 kHz
<Coding Rate>=1~4 (default 1)
<Preamble> (default 12)
When the LoRa network ID equals 18, the preamble value can be configured to 4~24. Otherwise, it can only be configured to 12.
#️⃣ Use the AT+SEND command to transmit data (text messages) to the given RYLR998 LoRa module address. To evaluate the transmission time (airtime), employ LoRa calculator tools.
AT+SEND=<Address>, <Payload Length>, <Data>
<Address>=0~65535
When the receiver address is set as 0, the module transmits data to all available addresses.
<Payload Length> Maximum 240 bytes
<Data> ASCII Format
#️⃣ To send AT commands successfully to the RYLR998 module via serial communication, add \r\n to the end of all commands.
#️⃣ To obtain the current setting values of AT commands from the RYLR998 module via serial communication, add the question mark (?) to the end of them:
AT+ADDRESS?
AT+SEND?
To transmit and receive data (text messages) with my two RYLR998 LoRa modules successfully, I applied these settings:
📌 RYLR998 LoRa Module (1)
AT+ADDRESS=108
AT+NETWORKID=10
AT+BAND=470000000
AT+PARAMETER=10, 8, 1, 12
AT+SEND=107, <Message Length>, <Message>
📌 RYLR998 LoRa Module (2)
AT+ADDRESS=107
AT+NETWORKID=10
AT+BAND=470000000
AT+PARAMETER=10, 8, 1, 12
AT+SEND=108, <Message Length>, <Message>
#️⃣ Check the error response codes if the module cannot be configured with AT commands successfully.
Download the required library to control the Nokia 5110 screen:
LCD5110_Graph | Download
Download the required library to control the custom 4x4 keypad:
Keypad | Download
⭐ Include the required libraries and define the RYLR998 LoRa module.
#include <SoftwareSerial.h>
#include <Keypad.h>
#include <LCD5110_Graph.h>
SoftwareSerial LoRa(2, 3); // RX, TX
⭐ Define the Nokia 5110 screen settings and the graphics (monochrome images).
⭐ To create different graphics (monochrome images), go to Monochrome Image Converter.
LCD5110 myGLCD(4,7,8,9,13);
extern uint8_t SmallFont[];
extern uint8_t MediumNumbers[];
// Define the graphics:
extern uint8_t iron_man[];
⭐ Define the symbols on the custom 4x4 keypad buttons as letters (alphabet) and numbers. Then, initialize instances of different keypad classes (k_letters_1, k_letters_2, and k_numbers).
const byte ROWS = 4; // four rows
const byte COLS = 4; // four columns
// Define the symbols on the keypad buttons as letters (alphabet) and numbers:
char letters_1[ROWS][COLS] = {
{'A','B','C','D'},
{'E','F','G','H'},
{'I','J','K','L'},
{'#','-','*','+'}
};
char letters_2[ROWS][COLS] = {
{'M','N','O','P'},
{'Q','R','S','T'},
{'U','V','W','X'},
{'#','-','*','+'}
};
char numbers[ROWS][COLS] = {
{'Y','Z','1','2'},
{'3','4','5','6'},
{'7','8','9','0'},
{'#','-','*','+'}
};
// Connect to the row pinouts of the keypad:
byte rowPins[ROWS] = {11,12,A0,A1};
// Connect to the column pinouts of the keypad:
byte colPins[COLS] = {A2,A3,A4,A5};
// Initialize instances of different keypad classes:
Keypad k_letters_1 = Keypad( makeKeymap(letters_1), rowPins, colPins, ROWS, COLS);
Keypad k_letters_2 = Keypad( makeKeymap(letters_2), rowPins, colPins, ROWS, COLS);
Keypad k_numbers = Keypad( makeKeymap(numbers), rowPins, colPins, ROWS, COLS);
⭐ Initiate the serial communication between the Arduino Nano and the RYLR998 LoRa module.
⭐ Initiate the Nokia 5110 screen.
LoRa.begin(115200);
// Initiate the screen.
myGLCD.InitLCD();
myGLCD.setFont(SmallFont);
⭐ In the read_keypad function, obtain the recently pressed key on the 4x4 keypad and change keypad classes (k_letters_1, k_letters_2, and k_numbers) depending on the keypad number.
void read_keypad(){
// Change keypad classes depending on the keypad number.
if(keypad_number == 1) key = k_letters_1.getKey();
if(keypad_number == 2) key = k_letters_2.getKey();
if(keypad_number == 3) key = k_numbers.getKey();
}
⭐ In the change_menu_options function:
⭐ Increase or decrease the option number using the keypad buttons (+, -).
⭐ Depending on the selected option number, change the boolean status.
void change_menu_options(){
// Increase or decrease the option number using the keypad buttons (+, -).
if(key){
if(key == '-') selected--;
if(key == '+') selected++;
}
if(selected < 0) selected = 4;
if(selected > 4) selected = 1;
delay(100);
// Depending on the selected option number, change the boolean status.
switch(selected){
case 1:
msg_send = true; module_settings = false; last_msg = false; _sleep = false;
break;
case 2:
msg_send = false; module_settings = true; last_msg = false; _sleep = false;
break;
case 3:
msg_send = false; module_settings = false; last_msg = true; _sleep = false;
break;
case 4:
msg_send = false; module_settings = false; last_msg = false; _sleep = true;
break;
}
}
⭐ In the interface function, display the menu options.
void interface(){
// Define options.
myGLCD.print("LoRa:", 0, 0);
myGLCD.print("A.Send Message", 0, 16);
myGLCD.print("B.Settings", 0, 24);
myGLCD.print("C.Last Message", 0, 32);
myGLCD.print("D.Sleep", 0, 40);
myGLCD.update();
}
⭐ In the get_serial_data function, send commands to the RYLR998 LoRa module and get responses from the module via serial communication.
void get_serial_data(){
while(LoRa.available()){
msg += (char)LoRa.read();
}
while(Serial.available()){
commands += (char)Serial.read();
}
}
⭐ In the set_AT_commands function, configure the RYLR998 LoRa module with the given settings via AT commands.
void set_AT_commands(String address, String network_ID){
commands = "";
LoRa.print("AT\r\n");
delay(t);
LoRa.print("AT+ADDRESS=" + address + "\r\n");
delay(t);
LoRa.print("AT+NETWORKID="+network_ID+"\r\n");
delay(t);
LoRa.print("AT+BAND=470000000\r\n");
delay(t);
LoRa.print("AT+PARAMETER=10,8,1,12\r\n");
delay(t);
}
⭐ In the check_AT_settings function, obtain the current setting values of AT commands from the RYLR998 module.
void check_AT_settings(){
commands = "";
LoRa.print("AT\r\n");
delay(t);
LoRa.print("AT+ADDRESS?\r\n");
delay(t);
LoRa.print("AT+NETWORKID?\r\n");
delay(t);
LoRa.print("AT+BAND?\r\n");
delay(t);
LoRa.print("AT+PARAMETER?\r\n");
delay(t);
}
⭐ In the scrolling_text function, scroll the given text by using the '#' keypad button.
void scrolling_text(String text, int y){
int len = text.length();
// Scroll the given text using the keypad button (#).
if(key == '#') x--;
if(x<=-(len*6)) x = -(len*6);
// Print.
myGLCD.print(text, x, y);
delay(25);
}
⭐ If there are incoming bytes from the transmitter RYLR998 LoRa module, elicit and format the received message by utilizing the given delimiter - the percent sign (%).
+RCV=108, 7, %HELLO%, -7, 11
⭐ Then, blink the RGB LED as green and print the recently received message on the screen.
⭐ Scroll the received message by pressing the '#' keypad button.
⭐ If the '-' keypad button is pressed, return to the interface.
if(msg != ""){
Serial.print(msg);
if(msg.indexOf("RCV") > 0){
myGLCD.clrScr();
myGLCD.update();
activated = true;
// Elicit and format the received message:
int delimiter_1, delimiter_2;
delimiter_1 = msg.indexOf("%");
delimiter_2 = msg.indexOf("%", delimiter_1 + 1);
// Glean information as substrings.
received_msg = msg.substring(delimiter_1 + 1, delimiter_2);
adjustColor(0,255,0); delay(1000); adjustColor(0,0,0); delay(1000); adjustColor(0,255,0);
while(activated == true){
read_keypad();
myGLCD.print("Received:", 0, 0);
scrolling_text(received_msg, 16);
myGLCD.update();
// Exit.
if(key && key == '-'){ activated = false; msg = ""; received_msg = ""; x = 0; myGLCD.clrScr(); myGLCD.update(); }
}
}
msg = "";
}
⭐ After being selected, if the Send Message menu option is activated by pressing the '*' keypad button:
⭐ Change the keypad classes by pressing the '+' keypad button:
- k_letters_1
- k_letters_2
- k_numbers
⭐ Enter the text message.
⭐ Scroll the entered message by pressing the '#' keypad button.
⭐ Press the '*' keypad button so as to transmit the recently entered text message to the receiver RYLR998 LoRa module.
⭐ If the given text message is transmitted successfully, blink the RGB LED as yellow.
⭐ If the '-' keypad button is pressed, return to the interface.
if(msg_send){
do{
myGLCD.invertText(true);
myGLCD.print("A.Send Message", 0, 16);
myGLCD.invertText(false);
myGLCD.update();
adjustColor(255, 0, 255);
delay(100);
if(key && key == '*'){
myGLCD.clrScr();
myGLCD.update();
activated = true;
while(activated == true){
read_keypad();
get_serial_data();
myGLCD.print("Enter Message:", 0, 0);
if(key){
if(key == '*' && MESSAGE != ""){
// Send the recently entered message to the given RYLR998 transceiver module.
LoRa.print("AT+SEND=107,"+String(MESSAGE.length()+2)+",%"+MESSAGE+"%\r\n");
delay(t);
adjustColor(255, 255, 0); delay(1000); adjustColor(255, 0, 255);
}else if(key == '+'){
// Change keypad classes:
keypad_number++;
if(keypad_number > 3) keypad_number = 1;
}else{
if(key != '#') MESSAGE += key;
scrolling_text(MESSAGE, 16);
}
}
// Print the response from the RYLR998 module.
if(msg != ""){ Serial.println(msg); msg = ""; }
myGLCD.update();
// Exit.
if(key && key == '-'){ activated = false; MESSAGE = ""; x = 0; myGLCD.clrScr(); myGLCD.update(); }
}
}
}while(!msg_send);
}
⭐ After being selected, if the Settings menu option is activated by pressing the '*' keypad button:
⭐ Utilizing the serial monitor, check and modify the RYLR998 LoRa module settings with AT commands.
⭐ If the '-' keypad button is pressed, return to the interface.
if(module_settings){
do{
myGLCD.invertText(true);
myGLCD.print("B.Settings", 0, 24);
myGLCD.invertText(false);
myGLCD.update();
adjustColor(0, 0, 255);
delay(100);
if(key && key == '*'){
myGLCD.clrScr();
myGLCD.update();
activated = true;
while(activated == true){
read_keypad();
get_serial_data();
myGLCD.print("Use the serial", 0, 0);
myGLCD.print("monitor to", 0, 8);
myGLCD.print("check and set", 0, 16);
myGLCD.print("module", 0, 24);
myGLCD.print("settings!", 0, 32);
// Utilizing the serial monitor, check and set the RYLR998 module settings with AT commands.
if(commands == "check") check_AT_settings();
if(commands == "set") set_AT_commands("108", "10");
// Print the response from the RYLR998 module.
if(msg != ""){ Serial.println(msg); msg = ""; }
myGLCD.update();
// Exit.
if(key && key == '-'){ activated = false; commands = ""; msg = ""; myGLCD.clrScr(); myGLCD.update(); }
}
}
}while(!module_settings);
}
⭐ After being selected, if the Last Message menu option is activated by pressing the '*' keypad button:
⭐ If the '*' keypad button is pressed, obtain and display the latest transmitted message by utilizing the given delimiter - the percent sign (%).
⭐ If there is no previously transmitted message saved in the module's memory, then print Nothing Transmitted Yet! as the latest message.
⭐ Scroll the latest transmitted message by pressing the '#' keypad button.
⭐ If the '-' keypad button is pressed, return to the interface.
if(last_msg){
do{
myGLCD.invertText(true);
myGLCD.print("C.Last Message", 0, 32);
myGLCD.invertText(false);
myGLCD.update();
adjustColor(0, 255, 255);
delay(100);
if(key && key == '*'){
myGLCD.clrScr();
myGLCD.update();
activated = true;
while(activated == true){
read_keypad();
get_serial_data();
myGLCD.print("Last Message:", 0, 0);
// Obtain and display the latest transmitted message.
if(key && key == '*') LoRa.print("AT+SEND?\r\n");
if(msg != ""){
// Print the response from the RYLR998 module.
Serial.println(msg);
latest_message = "";
x = 0;
if(msg.indexOf("%") > 0){
int delimiter_1, delimiter_2;
delimiter_1 = msg.indexOf("%");
delimiter_2 = msg.indexOf("%", delimiter_1 + 1);
// Glean information as substrings.
latest_message = msg.substring(delimiter_1 + 1, delimiter_2);
}else{
latest_message = "Nothing Transmitted Yet!";
}
msg = "";
}
scrolling_text(latest_message, 16);
myGLCD.update();
// Exit.
if(key && key == '-'){ activated = false; latest_message = ""; x = 0; myGLCD.clrScr(); myGLCD.update(); }
}
}
}while(!last_msg);
}
⭐ After being selected, if the Sleep menu option is activated by pressing the '*' keypad button:
⭐ Display the monochrome image (iron_man) as the screensaver while sleeping.
⭐ If the '-' keypad button is pressed, return to the interface.
if(_sleep){
do{
myGLCD.invertText(true);
myGLCD.print("D.Sleep", 0, 40);
myGLCD.invertText(false);
myGLCD.update();
adjustColor(255, 0, 0);
delay(100);
if(key && key == '*'){
myGLCD.clrScr();
myGLCD.update();
activated = true;
while(activated == true){
read_keypad();
// Define and print monochrome images on the screen:
myGLCD.drawBitmap(25,0,iron_man,36,50);
myGLCD.update();
// Exit.
if(key && key == '-'){ activated = false; myGLCD.clrScr(); myGLCD.update(); }
}
}
}while(!_sleep);
}
// Connections
// Arduino Nano :
// Nokia 5110 Screen
// D4 --------------------------- SCK (Clk)
// D7 --------------------------- MOSI (Din)
// D8 --------------------------- DC
// D9 --------------------------- RST
// D13 --------------------------- CS (CE)
// RYLR998 LoRa Module
// D2 --------------------------- TX
// D3 --------------------------- RX
// RGB LEB (RAGB)
// D5 --------------------------- R
// D6 --------------------------- G
// D10 --------------------------- B
// 4x4 Keypad
// D11 --------------------------- R1
// D12 --------------------------- R2
// A0 --------------------------- R3
// A1 --------------------------- R4
// A2 --------------------------- C1
// A3 --------------------------- C2
// A4 --------------------------- C3
// A5 --------------------------- C4
After completing soldering and uploading the code, I attached all remaining components to the Iron Man-themed PCBs via headers - Arduino Nano boards, Nokia 5110 screens, and RYLR998 LoRa modules.
📻📞 The device shows four different modes (menu options) on the interface:
- A. Send Message
- B. Settings
- C. Last Message
- D. Sleep
📻📞 The device allows the user to select a mode (option) on the interface via the '-', '+', and '*' keypad buttons:
- '-' ➡ Go Up
- '+' ➡ Go Down
- '*' ➡ Activate
📻📞 While selecting among modes on the interface, the device turns the RGB LED to different colors for each mode:
- Send Message ➡ Purple
- Settings ➡ Blue
- Last Message ➡ Cyan
- Sleep ➡ Red
📻📞 After activating any modes, the device lets the user return to the interface by pressing the '-' keypad button.
📌 Modes (Menu Options):
📌 A. Send Message
📻📞 The device changes the 4x4 keypad pattern (symbols) with three different keymaps if the '+' keypad button is pressed:
🖥️ letters_1
'A', 'B', 'C', 'D'
'E', 'F', 'G', 'H'
'I', 'J', 'K', 'L'
'#', '-', '*', '+'
🖥️ letters_2
'M', 'N', 'O', 'P'
'Q', 'R', 'S', 'T'
'U', 'V', 'W', 'X'
'#', '-', '*', '+'
🖥️ numbers
'Y', 'Z', '1', '2'
'3', '4', '5', '6'
'7', '8', '9', '0'
'#', '-', '*', '+'
📻📞 After entering the message, the device lets the user scroll the entered message by pressing the '#' keypad button.
📻📞 The device transmits the recently entered message to the receiver Iron Man-themed walkie-talkie (two-way radio) if the '*' keypad button is pressed.
📻📞 If the given message is transmitted successfully to the receiver module, then the device makes the RGB LED blink yellow.
📌 B. Settings
📻📞 The device allows the user to check and modify the RYLR998 LoRa module settings with AT commands on the serial monitor.
📌 C. Last Message
📻📞 The device displays the latest message transmitted to the receiver module if the '*' keypad button is pressed.
📻📞 Also, the device lets the user scroll the latest transmitted message by pressing the '#' keypad button.
📻📞 If there is no previously transmitted message saved in the LoRa module's memory, then the device shows Nothing Transmitted Yet! as the latest message.
📌 D. Sleep
📻📞 The device shows the Iron Man logo (monochrome image) as the screensaver while sleeping.
📌 Message Received
📻📞 The device shows the recently received message from the transmitter module. Then, it makes the RGB LED blink green.
📻📞 Also, the device lets the user scroll the received message by pressing the '#' keypad button.
📻📞 Until the '-' keypad button is pressed, the device shows the received message and keeps the RGB LED green.
After completing all steps above, I experimented with my Iron Man-themed walkie-talkies (two-way radios) to transmit and receive text messages. As far as my experiments go, I did not encounter any problems :)
Comments