Several years ago Solder Party released the Keyboard Featherwing, a PCB that combines a Blackberry keyboard, a 320x240 TFT resistive touchscreen display, a 5-input DPAD, 4 tactile buttons, a microSD card reader, and more, all driven by an Adafruit Feather board of your choice. Eventually these were discontinued, I'm assuming because of the difficulty of sourcing the increasingly rare Blackberry keyboards. I didn't know what I wanted to do with them at the time, but I knew I was gonna want to do something with them at some point, so I ordered several of them before the stock was depleted.
One popular use for the Keyboard Featherwing has been to pair them with LoRa radio transceivers, to create a set of "Doomsday Messengers". These are devices that are able to send short text messages to each other using radio signals, sort of like walkie-talkies, but for text messaging. This makes sense: with the tactile keyboard, huge display, and instant compatibility with the Feather ecosystem (including the ability to use rechargable LiPo batteries), the Keyboard Featherwing practically seems designed for the use case!
I hacked together a quick demo a few years ago allowing for very basic communication between the devices, and then promptly lost interest. I wanted to write firmware that allowed for robust, reliable communication between the devices, but I also wanted something offering some of the affordances of a modern smartphone UI. If you've ever worked on UI for microcontrollers, you probably realize there are a lot of challenges. One of those challenges can be figuring out how to write a custom, complex UI with just the basic drawing functions provided by the commonly available drawing libraries. While possible, it can be really cumbersome once you start to want more modern UI features, such as widgets, scrolling, animation, multi-screen interfaces, and so on. Another challenge is implementing all of that in a performant way, given the limited speed and memory of most microcontrollers. At the time, I wasn't sure how I was to accomplish this without pulling my out my hair, so the project was put on the back burner.
Recently I decided to dig these up and give it another shot. I still wasn't sure exactly how I was going to do it, but I did have a concrete feature set in mind:
- The ability to send encrypted, reliable messages between two paired devices using LoRa technology
- The ability to pair each device with any other device using the same hardware and firmware, via a settings screen (i.e., no re-compilation needed to pair devices)
- The ability to modify and persist device settings and a small message history across power cycles
- Granular battery monitoring; specifically the ability to see the percentage of battery life remaining at any given time
- Heavy focus on physical controls, using the touchscreen to supplement the UI only where practical and/or necessary (if you aren't familiar with any of my previous projects, I'm a huge fan of physical controls)
The first choice I needed to make was which Feather board to use. A popular choice for this type of project has been the Feather M0 Packet Radio. The M0 is a nice little microcontroller, and with the built-in radio on this board it might seem like the obvious choice at first glance, but its 48MHz clock speed and scant 32k of RAM doesn't lend itself well toward driving a 320x240 16-bit display. For this task I needed something with a little more juice.Ā
Fortunately, Adafruit has a Featherwing form factor for their radio boards, allowing us to pretty much use any Feather we want for packet radio communication. I decided that I would use an ESP32-S2 Feather coupled with the LoRa Featherwing. The ESP32-S2 has a nice speedy 240 MHz processor, 320KB of internal RAM, and a whopping 2MB of external PSRAM; perfect for driving the large display. Throw in a microSD card, a LiPo battery, and an antenna, and that takes care of all the hardware required to achieve the desired feature set. Additionally the Feather ESP32-S2 + LoRa Featherwing combo totals out to only about $3 more than the M0 radio Feather, so it's practically the same price for a massive upgrade. The ESP32-S2 Feather even has a built-in LiPo battery monitor!
There's one other feature of the ESP32-S2 that makes it a neat choice for this project: it has a built-in 2.4GHz WiFi-capable radio featuring direct device-to-device communication, via ESP-NOW. I decided it would be cool if the Messenger firmware could also take advantage of this radio. This afforded me two practical options for messenger hardware configurations:
- Support ESP-NOW only, providing only short-range capability, but reducing the cost of the build as the additional LoRa board and antenna aren't necessary
- Support ESP-NOW and LoRa, providing the ability to switch between short-range and long-range capability at runtime, providing compatibility with ESP-NOW-only messengers.
Required:
- Keyboard Featherwing Rev 2 (discontinued)
- Feather ESP32-S2
- 2000mAh LiPo battery
- MicroSD card (doesn't need to be large, only a couple of small files are saved)
- 3D printed case (see 3D printing section below)
- 4 M2.5 heat set inserts
- 4 M2.5 x 12mm countersunk screws
Optional (for long-range capability):
- LoRa Featherwing, either 900 MHz or 433 MHz, depending on country.
- Antenna, either 915 MHz orĀ 433 MHz, depending on your LoRa radio hardware and country
- uFL SMT Antenna Connector
- Short headers kit for Feather
Solder the headers onto the Feather. Note the orientation of the headers in the photos above. The are inserted into the holes on the underside of the Feather with the long side of the pins sticking out the top. The solder is applied to the pins on the top-side of the board.
Solder wires on the LoRa Featherwing as shown above. A different configuration can be used but it requires an update to the LoRa hardware pin assignments in the source code.
Solder the short female headers as shown above, with the headers inserted into the holes from the underside of the board, and solder applied to the pins sticking out the top.
Solder the uFL connector onto the antenna mount on the LoRa Featherwing (this learn guide has some good tips for soldering on the connector).
The LoRa Featherwing is mounted to the top of the Feather, as shown in the photo above, and the Feather assembly plugs into the headers on the Keyboard Featherwing.
The original source for the 3D case design I used can be found at https://www.printables.com/model/66291-lora-msg. However, my current build only uses the face plate from this author. The middle piece is a remix by a different author, and I remixed 2 versions of the body piece: one with a USB hole to line up with the Feather/Featherwing sandwich shown above, and another to line up with the standalone Feather using the standard headers. I also designed a simple upright stand for the messengers for when they are not in use.
The case requires 4 M2.5 heat set inserts and 4 M2.5x12mm countersunk screws.
The face plate uses the file lora_msg_front_chief.stl from https://www.printables.com/model/66291-lora-msg. Printed face-down at 15% infill with no supports.
The middle piece uses the file Communicator_Mid_Mod.stl from https://www.thingiverse.com/thing:5212843. Printed as shown in the image on the site, using 15% infill and no supports.
The two versions of the remixed body I used can be found on my Printables page atĀ https://www.printables.com/model/1031410-lora-doomsday-messenger-body-remix
- lora-body-modified-lower-usb.stl - This version is for the "sandwiched" Feather + LoRa Featherwing boards in the LoRa-enabled hardware configuration.
- lora-body-standard-headers-usb.stl - This version is for the ESP-NOW-only configuration using the Feather and standard-height headers.
This piece is printed with the open part facing down with supports on build plate enabled, using 15% infill.
The 3D file for the stand can be found at https://www.printables.com/model/1031443-display-stand-for-keyboard-featherwing-messengers. The stand was printed with no supports at 15% infill. I also attached someĀ little rubber bumper feet to the bottom for non-slip operation.
That takes care of the hardware, but what about the UI? Knowing that I didn't want to build the entire UI using basic drawing functions, I began looking around to see what kind of Arduino-compatible UI libraries existed. It didn't take me long to find the excellent LVGL library. LVGL was *exactly* what I was looking for. It's specifically designed for microcontrollers, has a rich feature set with dozens of widgets, tons of customization options, and is written in efficient and highly-portable C code. Given my experience with toolkits such as UIKit, and given LVGL's mature and capable APIs, it did not take me long to figure out how to piece together a functional version of the UI I envisioned.
For the theme, I wanted to present something with modern widgets, but with a somewhat "old-school" aesthetic. I chose to use an amber and phosphor green color scheme, reminiscent of old CRT computer monitors.
The messages screen is the first screen that appears in the firmware. All messages sent to and received from the paired device will be displayed here. The Messages screen will show the last 10 messages sent/received. If the messages exceed the height of the screen, the message list can be scrolled using the touch screen or the UP/DOWN direction buttons on the 5-way DPAD. The history can be cleared by pressing the Clear button on the right side of the title bar. The Compose and Settings buttons navigate to the Compose and Settings screens, respectively.
Note the small dot indicators underneath the Settings and Compose navigation buttons. This is to indicate that these on-screen buttons are paired to the physical buttons on the Keyboard Featherwing directly underneath the indicators. In this case, pressing theĀ first physical button is identical to tapping the Settings button on the touch screen, as is the fourth physical button with the Compose button.
From the Messages screen, pressing the Compose button in the bottom right will navigate to the Compose screen. Here is where the user types out their message to send to the paired device. The right side of the title bar displays the current character count of the message being entered. To send the message, the user presses the Send button.
If a message is received while the user is composing a message, a New! indicator will appear in the status bar, the NeoPixel will light up green, and theĀ Messages button will change to display the textĀ New Messages. If the user returns to the Messages screen without sending, any text they have entered will still be there when navigating back to the Compose screen.
Pressing the Settings button in the bottom-left corner of the screen from the Messages screen will navigate to the Settings screen. Depending on the hardware configuration, the Settings screen will display three or four tabs (three if the device is ESP-NOW-only, four for LoRa-enabled devices).
In the General tab, the user can adjust the display and keyboard brightness. The device can also be rebooted by pressing the Reboot button.Ā Pressing the Save & Exit button will save any settings modifications and return to the Messages screen. TheĀ Save & Exit button is disabled if any settings are invalid (for example, one of the encryption keys is not 16 characters long).Ā Tabs may be selected by tapping the tabs on the touch screen, or can be cycled by pressing theĀ Next Tab on-screen or physical button.
If a message is received while in the Settings screen, a New! indicator will appear in the status bar, and the NeoPixel will light up green.
Encryption settings can be modified in the Encryption tab. Here the user can enter a 16-byte string for both the Primary Key and the Local Key. The Primary Key is used by both ESP-NOW and LoRa modes, while the Local Key is used in ESP-NOW mode only. Paired devices must use the same keys in order to communicate with each other. In ESP-NOW mode, messages using non-matching keys will be ignored. In LoRa mode, messages received from a paired device using a different Primary Key will be garbled.
The ESP-NOW tab is where the user configures the MAC address for a paired device in ESP-NOW mode. The MAC address of user's device is displayed in green at the top by the My MAC text. The user can enter the Other MAC address of a companion device to "pair" them (both devices must contain the companion device's address here).
For LoRa-enabled devices, a Switch To ESP-NOW button is enabled if the device is current in LoRa mode. When pressed, the device will reboot into ESP-NOW mode.
For LoRa-enabled devices, the LoRa tab is where the user can set the address of their own device and a companion device to "pair" them (both devices must contain the companion device's address here). The address is a single byte, with valid values ranging from 0-254 (255 is reserved).Ā
If the device is currently in ESP-NOW mode, a Switch to LoRa button will be present. Pressing this button reboots the device into LoRa mode.
I used the VSCode IDE with the PlatformIO plugin to build the firmware for the Doomsday Messenger. Source code can be found atĀ https://github.com/apendley/doomsday-messenger-lvgl. Clone or download and unzip the repo, and open the folder in VSCode to view the project.
Main configuration
The main config file can be found at include/config.h. This file defines some hardware pin assignments for the Keyboard Featherwing. This file shouldn't need to change if the Messenger device uses the Keyboard Featherwing.Ā
Device configurations
In order to build the project, first a device configuration needs to be selected, and any necessary adjustments need to be made to the appropriate configuration header file. The project includes 4 device-specific configuration headers, found in theĀ include/devices directory. Each one defines hardware configuration and default settings specific to each individual device. In my case, I built a pair of LoRa-enabled devices, and a pair of ESP-NOW-only devices, each with their own configuration, totally 4 configuration headers. I named them lora_1, lora_2, espnow_1, and espnow_2.
TheĀ lora_1/lora_2 and espnow_1/espnow_2Ā configurations are set up to be paired with each other by default, using the same encryption keys and complimentary addresses (these can always be changed in the Settings screen on-device at runtime). This project takes advantage the enviroment feature in PlatformIO/VSCode in order to easily select the device configuration.
Let's useĀ lora_1 as an example configuration for a LoRa-enabled messenger device. First, we make any necessary changes in theĀ lora_1.h file to match our hardware configuration and encryption key preferences.
Next, we need to let PlatformIO know which device to build for. At the bottom of the IDE window, look for the environment selector (highlighted below in red
Next, select the env:lora_1 environment from the environment drop-down, highlighted below in red.
Wait a few second for the IDE to re-configure the environment. When the overlay highlighted in red below disappears, we are ready to build the firmware.
Before building, we may also want to set the ports for the devices we're uploading the firmware to. This can be done in theĀ platformio.ini file, by uncommenting the upload_port setting and entering the specific port the device is plugged into (see below). This makes it easier to have multiple devices plugged in to your computer at the same time.
Finally, in the top-right corner of the IDE, click on the build button, and select UploadĀ
If there are no issues, the firmware should compile and upload to the target device. For the paired device, the process should be repeated with theĀ lora_2 configuration (alternatively, use the espnow_1 and espnow_2 configurations if devices will not be LoRa-enabled).
Of course, one obstacle to re-creating this project and taking advantage of the firmware is the discontinued Keyboard Featherwing. If you are interested in a project like this, and are willing to tinker with the firmware, I've identified an attractive alternative. The LILYGO T-Deck has a built-in ESP32-S3 microcontroller and an SX1262 LoRa transceiver, as well as a MicroSD card reader, touchscreen, and a LiPo charging circuit, consolidating much of the base hardware necessary for this project. From what I can tell, the following modifications to the firmware would be required:
- The ESP32-S2 and ESP32-S3 are similar enough that minimal changes should be required to support the S3.
- SX1262 LoRa transceiver is supported by RadioHead, only the specific device driver would need to be swapped out
- ST7789 Display is supported by Adafruit. Should only be a matter of swapping out the specific driver instance, as the API should be identical
- Hardware pin assignments would need to be updated for the T-Deck
- Keyboard library would need to be swapped out for the one used by the T-Deck
- Code using the 5-way dpad and 4 tactile buttons would need to be removed
- The T-Deck uses a different touchscreen, so the driver would need to be replaced
- Code to support the T-Deck trackball would need to be added
- Code driving the Keyboard Featherwing's NeoPixel would need to be removed/omitted
- Device only has a voltage divider for measuring the battery, so battery monitor component would need to be modified/disabled
The battery connector on the T-Deck is different than the one used by the Feather, so a different battery or an adapter of some sort may be necessary to connect the battery to the T-Deck. Make sure to check the polarity!
A different 3D printed case would also be required. A cursory search on Printables yields a nice selection of case options. LILYGO also sells the T-Deck Plus, a version with a case, GPS, and battery for a bit more if you'd rather use that.
If you'd like this on a T-Deck but don't want to modify the code yourself, I could possibly be persuaded to do it for a generous donation of 2 T-Decks for testing.
This page (Keyboard Featherwing Messenger powered by LVGL) was last updated on January 21, 2025.
Text editor powered by tinymce.