SX1276 LoRa Module on Raspberry Pi

2020-09-15 hardware lora wireless

I recently purchased two E32 868T20D LoRa wireless modules from Amazon. These E32 modules run off the SX1276 LoRa Chip. In this post I’ll provide come C code that creates a command line tool for the SX1276. Then we’ll look at ways to send and receive data over wireless using two Raspberry Pi’s and LoRa.

The source code can be found in github. To quickly get started refer to this blog post and the Github project.

What is LoRa?

LoRa (Lo ng Ra nge) is a low-power wide-area network protocol developed by Semtech. It’s based on spread spectrum technology and uses chirp modulation. Effectively, it’s a wireless technology designed for:

  • Long Range
  • Low Power
  • Low Throughput (not necessarily, but true with the SX1276)

LoRa claims to have range up to 10km. Wait … what? 10km!? That would be truly amazing! I was hoping to get the real range in this post wasn’t able to. Although, getting even 1km line-of-sight with this current setup would be challenging.

Background

The entire motivation here is to send and receive over LoRa between two Raspberry Pi devices. Since, this is done without Internet Connectivity an SSD1306 was used to display the results on the receiver. The SSD1306 is the little black LCD.

Raspberry Pi Receiver

Below is a picture of the Receiver using the Raspberry Pi. It has a little SSD1306 Display module displaying the quick brown fox on the OLED. These are stacks with a Raspberry Pi B+ in a case, a battery, a bread board and the components wired together using the breadboard.

Raspberry Pi Lora Receiver with E32

Raspberry Pi E32 Lora Receive

Side view of the receiver module.

Raspberry Pi E32 Lora Receive Side

Raspberry Pi Lora Transmitter with E32

The transmitter is simply a battery and the E32 wired directly to the RPi.

Raspberry Pi E32 Lora Transmit

Side view of the transmitter.

Raspberry Pi E32 Lora Transmit Side

Wireless LoRa Configuration

I used an E32 module which has an MCU wrapper around the SX1267 LoRa Module and an Omni Antenna.

LoRa Module

E32 868T20D

Antenna

5dBi Omni, 195mm, 50Ω

Theory of Operation

As as user you want to send and receive data from the module. Many code examples will accomplish this by requiring users to write code directly in the example project. This is too frail and in-flexible. This tool will run a daemon and you can interface to this daemon via Unix Domain Sockets. Though there is some theory to learn here this allows anyone to write a client in any language to interface through a socket.

To run the E32 we have two modes of operation:

  • Configuration - A one-time command to write configuration, after this we can send and receive at will
  • Run - After we have configured the E32 we can now send and receive data

Wiring

Here is the wiring between the E32 and the Raspberry Pi. Please make sure the UART TX of the RPI is wired to the RX of the E32 and vice-versa.

RPI Pin RPI Desc E32 Pin
+5V +5V Power VCC
GND Ground GND
GPIO23 General Purpose I/O 23 M0
GPIO24 General Purpose I/O 24 M1
GPIO18 General Purpose I/O 18 AUX
GPIO14 UART TX /dev/ttyAMA0 TX RX
GPIO15 UART RX /dev/ttyAMA0 RX TX

Raspberry Pi to E32 Wiring

Configuring Raspberry Pi Serial Port

Using the sudo raspi-config command we need to configure the serial port to be enabled but NOT be accessible for a login shell. If we enable the login shell it will break because interfacing with the e32 requires a binary interface without the buffering and control required to interface over terminals.

raspi-config for serial connection raspi-config disable serial login raspi-config disable serial enable

Additional Required Serial Port Settings and Verification

Once the serial port is enabled on the Raspberry Pi we need our user that will run the program be in the correct Unix groups and to modify the /dev/ttyAMA0 file to have group read permissions.

$ whoami
pi
$ groups # require dialout, tty, and gpio. Log out for it to take effect
pi adm tty dialout cdrom sudo audio video plugdev games users input netdev gpio i2c spi
$ ls -l /dev/ttyAMA0 # see that the dialout group can read
crw-rw---- 1 root dialout 204, 64 Apr  6 11:53 /dev/ttyAMA0
$ stty -F /dev/ttyAMA0 -a
speed 9600 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q;
stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 0; time = 0;
-parenb -parodd -cmspar cs8 hupcl -cstopb cread clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany -imaxbel -iutf8
-opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
-isig -icanon -iexten -echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc
$

Note, in the stty command we are looking primarily for rows 0; columns 0; line = 0;. The command line tool e32 will modify the terminal for it’s needs. If you don’t have the baud rate at 9600 or all the other options don’t worry the e32 command line tool will configure the terminal correctly and in raw mode.

Usage

The C project will build a command line tool. Below are the following options. Note, there are many ways to send and receive data.

$ e32 -h
Usage: e32 [OPTIONS]
Version 1.5

A command line tool to transmit and receive data from the EByte e32 LORA Module. If this tool is run without options the e32 will transmit what is sent from the keyboard - stdin and will output what is received to stdout. Hit return to send the message. To test a connection between two e32 boards run a e32 -s on both to ensure status information is correct and matching. Once the status is deemed compatible on both e32 modules then run e32 without options on both. On the first type something and hit enter, which will transmit from one e32 to the other and you should see this message show up on second e32.

OPTIONS:
-h --help                  Print help
-r --reset                 SW Reset
-t --test                  Perform a test
-v --verbose               Verbose Output
-s --status                Get status model, frequency, address, channel, data rate, baud, parity and transmit power.
-y --tty                   The UART to use. Defaults to /dev/serial0 the soft link
-m --mode MODE             Set mode to normal, wake-up, power-save or sleep.
   --m0                    GPIO M0 Pin for output [23]
   --m1                    GPIO M1 Pin for output [24]
   --aux                   GPIO Aux Pin for input interrupt [18]
   --in-file  FILENAME     Read intput from a File
   --out-file FILENAME     Write output to a File
-x --socket-unix FILENAME  Send and Receive data from a Unix Domain Socket
-d --daemon                Run as a Daemon

Getting Status

Here is an example to get status. This should be the first verification step. If it doesn’t work it’s doubtful anything else will.

$ e32 --status
Version Raw Value:        0xc3450d14
Frequency:                868 MHz
Version:                  13
Features:                 0x14
Settings Raw Value:       0xc000001a0644
Power Down Save:          Save parameters on power down
Address:                  0x0000
Parity:                   8N1
UART Baud Rate:           9600 bps
Air Data Rate:            2400 bps
Channel:                  410 MHz
Transmission Mode:        Fixed
IO Drive:                 TXD and AUX push-pull output, RXD pull-up input
Wireless Wakeup Time:     250 ms
Forward Error Correction: on
TX Power:                 30 dBm

Sending from Standard Input

We can type directly into the e32 and send what we type by hitting enter.

./e32
waiting for input from terminal
I am typing, when I hit enter this line will be sent
^C

Sending a file

We can send a file to the ether. Here we are not running as a daemon.

$ cat f
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyy
$ e32 --in-file f
waiting for input from terminal
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyy
$

Installing

These instructions are to be run on your Raspberry Pi. Download e32. See instructions below.

wget http://lloydrochester.com/code/e32-1.5.tar.gz
tar zxf e32-1.5.tar.gz
cd e32-1.5
./configure
make
sudo make install
e32 --help # this should show the help of the tool that was installed

Advanced Features

We can run the e32 in the background. To do this we will run the program as a daemon using the -d flag. When it’s running from the background we can have it write what it receives to a file. Or we can use a socket to transmit and receive. There is a Python example below to interface with the socket.

Configuring the Service

The autotools package will install the service files on a sudo make install, there is no need to do anything. The service that is created is called e32. Here are the contents for reference. The service runs the e32 in daemon mode.

[Unit]
Description=ebyte e32 systemd service

[Service]
Type=forking
ExecStartPre=/usr/local/bin/e32 --reset
ExecStart=/usr/local/bin/e32 -v --daemon --socket-unix /run/e32.socket
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

When we do a sudo make install the services are already installed for us.

$ sudo make install
...
 /bin/mkdir -p '/lib/systemd/system'
 /usr/bin/install -c -m 644 e32.service e32tx.timer e32tx.service '/lib/systemd/system'
...
$

Transferring Files Between two Raspberry Pi Boards

The e32 service should be running as a daemon on both Raspberry Pi. This is done simply by doing:

$ systemctl daemon-reload
$ syttemctl start e32

Now that both of them are running we need clients to connect to the sockets on each. These sockets are Unix Domain Sockets.

Python Client to send data over the Socket

Here is a simple Python client that will connect. It’s installed in /usr/local/bin/:

$ ls -l /usr/local/bin/e32tx
-rwxr-xr-x 1 root root 785 Jul  8 09:32 /usr/local/bin/e32tx

Here are the contents:

#!/usr/bin/python3

import socket
import sys
import os, os.path
import time

e32_sock = "/run/e32.socket"
csock_file = "/tmp/client"

if os.path.exists(csock_file):
  os.remove(csock_file)

csock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
csock.bind(csock_file)

print("registering socket", e32_sock)
csock.sendto(b'', e32_sock)
(msg, address) = csock.recvfrom(10)

if msg[0] != 0:
    print("bad return code exiting")
    sys.exit(1)

print("sending", sys.argv[1])
csock.sendto(str.encode(sys.argv[1]), e32_sock)
(bytes, address) = csock.recvfrom(10)
print("return code", bytes[0])

csock.close()

if os.path.exists(csock_file):
 os.remove(csock_file)

We can simply now transmit data by doing the following:

# /usr/local/bin/e32tx "the quick brown hare"
registering socket /run/e32.socket
sending the quick brown hare
return code 0
#

Service to Transmit through the Socket out the E32

If you want to get more complex here are some services to automatically send data through the Unix Domain Socket to the E32 via simple service and a timer.

Simple Service to Transmit

Here is a service, it just runs one time:

[Unit]
Description=Transmit on the ebyte e32

[Service]
Type=simple
ExecStart=/usr/local/bin/e32tx "the quick brown hare"
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
Wants=e32.service

We’ll use a timer to run this service every 10 seconds:

[Unit]
Description=Timer to transmit on the ebyte e32

[Timer]
OnCalendar=*:*:0/10
AccuracySec=1us

[Install]
WantedBy=e32.service

These services are found in the tarball for E32 and can be installed manually.

Service to Receive Data from the Socket

Now on the other end we need a Raspberry Pi running E32 listening on the other end of the socket. This will be for reference since these examples take from the service and display it on an SSD1306.

A service rxdisp.service.

[Unit]
Description=Read from e32 and display on ssd1306

[Service]
Type=simple
ExecStart=/usr/local/bin/rxdisp
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
Wants=e32.service ssd1306.service

Another service ssd1306.service:

[Unit]
Description=ssd1306 service

[Service]
Type=simple
ExecStart=/usr/local/bin/ssd1306 --socket-unix /run/ssd1306.socket
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Python to read from the Socket for Receive

Here is some python that will pull from the socket recvfrom the socket and send it to the SSD1306 for display:

#!/usr/bin/python3
import socket
import sys
import os, os.path
import signal
import time

def handler(signum, frame):
    if signum == signal.SIGINT:
        close_sock()

signal.signal(signal.SIGALRM, handler)

def close_sock():
    global client_sock

    print("closing client socket")

    client_sock.close()

    if os.path.exists(client_sock):
      os.remove(client_sock)

client_sock = "/home/pi/client"
e32_sock = "/run/e32.socket"
ssd1306_sock = "/run/ssd1306.socket"

if os.path.exists(client_sock):
  os.remove(client_sock)

csock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
csock.bind(client_sock)

print("registering socket", e32_sock)
csock.sendto(b'', e32_sock)
(msg, address) = csock.recvfrom(10)

if msg[0] != 0:
    print("bad return ode exiting")
    sys.exit(1)

row = 0

msg = b"listening"
command = bytearray([2, row, 3, len(msg)])
command = command + bytearray(msg)
print("sending", len(command), bytes(command))
csock.sendto(bytes(command), ssd1306_sock)
(msg, address) = csock.recvfrom(2)
if msg[1] != 0:
    print("bad value", msg[1], "at", msg[0])

while True:
    # receive from the e32
    (msg, address) = csock.recvfrom(59)
    print("received", len(msg), msg)

    # create a command to write what we received
    command = bytearray([2, row, 3, len(msg)])
    command = command + bytearray(msg)
    print("sending", len(command), bytes(command))
    csock.sendto(bytes(command), ssd1306_sock)
    (msg, address) = csock.recvfrom(2)
    if msg[1] != 0:
        print("bad value", msg[1], "at", msg[0])

    row = row + 1
    # clear the next row
    command = bytearray([2, row, 3, 20])
    command = command + bytearray(b"                    ")
    csock.sendto(bytes(command), ssd1306_sock)
    (msg, address) = csock.recvfrom(2)
    if msg[1] != 0:
        print("bad value", msg[1], "at", msg[0])

    row = row % 8c

Conclusion

This post was a whirlwind of information. I presented a command line tool to read from the E32 that runs as a daemon, amongst other ways. Clients can connect to this daemon over a Unix Domain Socket and transmit and receive from it. I provided an example to transmit and receive from this socket on the E32.

I originally wanted to post on the actual range from Field Tests of the E32. I wasn’t able to do it as there seems to be some issue electronically with one or both of my Raspberry Pi Boards. I’m able to send and receive but something isn’t reliable. This has seemed to degrade over time. The farthest I was able to reliably transmit was about 0.4 km.

comments powered by Disqus