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.
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
Side view of the receiver module.
Raspberry Pi Lora Transmitter with E32
Transmission is simply just a straight wiring to the E32 module.
Side view of the transmitter.
Wireless LoRa Configuration
I used an E32 module which has an MCU wrapper around the SX1267 LoRa Module and an Omni Antenna.
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
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|
|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|
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
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
raspi-config and ensure that the UART is enabled. The user is running the commands needs to be part of
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
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
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
$ 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: print("bad return code exiting") sys.exit(1) print("sending", sys.argv) csock.sendto(str.encode(sys.argv), e32_sock) (bytes, address) = csock.recvfrom(10) print("return code", bytes) 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.
[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
[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: 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 != 0: print("bad value", msg, "at", msg) 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 != 0: print("bad value", msg, "at", msg) 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 != 0: print("bad value", msg, "at", msg) row = row % 8c
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.