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.
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
The transmitter is simply a battery and the E32 wired directly to the RPi.
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|
Configuring Raspberry Pi Serial Port
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.
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
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  --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 -d --daemon Run as a Daemon
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 $
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
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
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
$ 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=*:*: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.
[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.comments powered by Disqus