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.

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

Transmission is simply just a straight wiring to the E32 module.

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

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]

A command line tool to interact 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.
-m --mode MODE                Set mode to normal, wake-up, power-save or sleep.
   --m0                       GPIO M0 Pin for output
   --m1                       GPIO M1 Pin for output
   --aux                      GPIO Aux Pin for input interrupt
   --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
-b --binary                   Used with the -f and -u options for binary output
-d --daemon                   Run as a Daemon

Getting Status

Here is an example to get status:

$ 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 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
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyy
$

Pre-Setup before we can Interface with the E32

Run a raspi-config and ensure that the UART is enabled. The user is running the commands needs to be part of dailout and gpio groups. I do this with a usermod -a -G dailout pi for example. You have to log out for the change to be reflected.

$ groups # need dailout and gpio
pi adm tty dialout cdrom sudo audio video plugdev games users input netdev gpio i2c spi

Check the permissions of the UART0 device. See the rw- on the dailout. This allows your user to read and write out the UART.

$ ls -l /dev/ttyAMA0
crw-rw---- 1 root dialout 204, 64 Oct 3 09:13 /dev/ttyAMA0

Installing

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

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

Verifying it works

The software uses the UART and GPIO.

Before doing anything please run a e32 -s. If this doesn’t work, nothing will. The status command will read from the E32 it’s successful output means the wiring is correct.

After you’ve run it the following should be GPIO exported.

$ cd /sys/class/gpio
$ ls
export  gpio18  gpio23  gpio24  gpiochip0  unexport
$ cat gpio23/direction
out

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.

[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.

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=*-*-* *:*:00
OnCalendar=*:*:0/10
#OnUnitActiveSec=3s
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.