Networking and Communications
the assignment for this week was to design, build, and connect wired or wireless node(s) with network or bus addresses and local input &/or output device(s)
The ch572
I have fallen in love with WCH's CH572. The CH572 Features a up to 100mhz RISC-V processor, 256kb of flash, 12kb of ram, and a built in BLE radio. It also has a built in USB interface, making it easy to program, and most importantly for myself, costs only 30 cents for 20 from LCSC, and a bit more from aliexpress. It also is extremely low power, and The cheapest 2.4ghz/BLE chip availible
Main thought
A CH572D board of my own creation, built to act as a usb dongle, uses the radio to communicate with the evaluation board of the ch572, which connects to an oled screen, and displays messages. For a simple example I'll just use a "heartbeat" (display a message while the dongle is powered), but I'll include an example on further steps/customization.
Hardware
Evaluation Board
The Evaluation board can be found here. This board, and WCH in general, is great as it comes in a kit that features both it and 5 chips.
Custom Board
The custom board was designed by me, and is a simple board that has a CH572D, A PCB antenna, and 4 GPIOs, 3.3v power, and ground. It is also strong in that it features a built in usb connector, meaning that the only hard thing to solder was the chip and its (sadly) required external 32mhz oscillator. This week doesn't focus on hardware design, but If your very interested in the CH572 and example hardware, I have A lot of different example boards of it on my github.
Software
Ch32fun
Ch32fun is a library for the CH572(and other WCH chips) that fully replaces the bulky factory provided coding solution (Mounriver Studio), and allows for easy coding to the chip. It is essentially C++ with a few extra functions to allow for easy coding to the chip. It is also open source, and can be found here.
Network Layout
This network will be made through 2.4ghz. The CH572 supports BLE, and I plan on using it to communicate with the evaluation board. However, BLE itself is really hard to implement, and the ch32fun library currently doesn't support it. It does support sending raw packets, that can be made with custom formats. This will essentially be a single packet, probably around 6 bytes long, that has a source identifier, something like a set 5 byte identifier, then a actualy 1 byte packet. Since no negotiation needs to happen, the source identifier will be hard coded into the output, and the input will send a packet, on button press, with the last byte simply being a 1 to confirm that the button was pressed.
Output: Screen code
#include "ch32fun.h"
#include "iSLER.h"
#include "oled_i2c.h"
int device_state = -1;
// Helper: Print integer to OLED since I forgot to add that to the main oled_i2c
// library
void ssd1306_print_int(int val) {
char buf[16];
if (val == 0) {
buf[0] = '0';
buf[1] = 0;
} else {
int i = 0;
int k = val;
while (k > 0) {
k /= 10;
i++;
}
buf[i] = 0;
while (val > 0) {
buf[--i] = (val % 10) + '0';
val /= 10;
}
}
ssd1306_print(buf);
}
// Update time display on OLED
void update_time_display(int ms) {
ssd1306_set_cursor(1, 0);
ssd1306_print("Time: ");
ssd1306_print_int(ms);
ssd1306_print(" ms ");
}
// On/off managers
void set_device_on() {
if (device_state == 1)
return;
device_state = 1;
ssd1306_set_cursor(0, 0);
ssd1306_print("Device: ON ");
}
void set_device_off() {
if (device_state == 0)
return;
device_state = 0;
ssd1306_set_cursor(0, 0);
ssd1306_print("Device: OFF ");
}
int main() {
SystemInit();
DCDCEnable();
// Init OLED
i2c_init();
ssd1306_init();
ssd1306_clear();
// Initial state
set_device_off();
update_time_display(0);
// Init RF
RFCoreInit(LL_TX_POWER_0_DBM); // not gonna be transmitting anything
int heartbeat_timer_ticks = 100; // ~525ms real-time
int ticks_since = 0;
while (1) {
// Listen on ch37
Frame_RX(NULL, 37, PHY_1M);
// Wait for packet
while (!rx_ready) {
Delay_Ms(1);
ticks_since++;
// Timeout logic
if (heartbeat_timer_ticks > 0) {
heartbeat_timer_ticks--;
} else {
set_device_off();
}
// Update every ~50ms real-time (correction applied)
if (ticks_since % 33 == 0) {
update_time_display((ticks_since * 3) / 2);
}
}
// Packet received
if (rx_ready) {
uint8_t *pBuf = (uint8_t *)LLE_BUF;
// Check magic bytes
if (pBuf[2] == 0xCA && pBuf[3] == 0xFE && pBuf[4] == 0xBA &&
pBuf[5] == 0xBE) {
heartbeat_timer_ticks = 100;
ticks_since = 0;
update_time_display(0);
set_device_on();
}
}
}
}
Input: Button code
#include "ch32fun.h"
#include "iSLER.h"
#include <stdint.h>
#include <stdio.h>
int main() {
SystemInit();
DCDCEnable();
// turn on rf
RFCoreInit(LL_TX_POWER_0_DBM);
uint8_t magic_packet[] = {0xCA, 0xFE, 0xBA,
0xBE}; // 4 randomly selected, low chance of copied
while (1) {
// Transmit heartbeat on channel 37 only to decrease latency
Frame_TX(magic_packet, sizeof(magic_packet), 37, PHY_1M);
// ~150ms real-time delay (chip timing is geeked)
Delay_Ms(75);
}
}
Working Example
In the video, you can see me turning on the dongle (getting it at the right angle so that it actually has power), and then when it is on, it starts to send out "life" packets every 150ms. These are received, and the screen updates with how long the device has been off, or when the device is on.
Network Capabilities
This system allows for really tiny packets to be sent, and received, with a very low latency. It also would allow for small amounts of data to be sent and received. The biggest advantage of this is allowing a custom data packet. I did the minium viable packet (I guess you could make the identifier even smaller), but if you just made the magic_packet a 5 byte long one, then you could do whatever you want with that 5th byte, and to read it all that would need to be added is pBuf[6].
Advanced Example
Here is a pseudo-code example of how you might pack highly customized data into a very small, efficient packet. An example of a 7 byte packet, 4 for ID and 3 for actual data, is below
Transmitter
// first 4 bytes identifies that this is the wanted packet
uint8_t packet[7] = {0xCA, 0xFE, 0xBA, 0xBE, 0, 0, 0};
// Manual packing to define which each byte is
packet[4] = (uint8_t)digitalRead(Pin11); // Byte 4: pin11 on/off
packet[5] = (uint8_t)(time >> 8); // Byte 5: High byte of 'time'
packet[6] = (uint8_t)(time & 0xFF); // Byte 6: Low byte of 'time'
// times packed like that in order to be 16 bit instead of just 8
Receiver
// Extract the data from the exact indices we packed them into
uint8_t pin11_state = pBuf[4];
// Recombine the two separate bytes back into the single 'time' integer
uint16_t received_time = (pBuf[5] << 8) | pBuf[6];
Group Work
As part of the group work, me and Cooper connected our 2 devices by connecting to the same network and using MDNS (very cool wifi trick for the ESPs, read into it). A link to our work can be found here
Files/Further Reading/Links
The Code for this week can be found here The Kicad files for my custom board can be found here The Evaluation board can be found here The software stack (ch32fun) can be found here The Chip's datasheet (CH572) can be found here
If you do want to make that custom board by the way, order it with a 2mm PCB thickness (more expensive), or just 1.6mm and be wobbly.
Reflection
Individual
Working with the CH572 is a very uphill battle. I try to very aware of the costs of a board, and with recent events, my old favorites the espressif chips have gotten way too expensive for simple projects. The CH572, while not supporting wifi, still supports BLE (with really hard code from the manufactorer), or custom 2.4ghz like what I did here. I think what I used here will be the standard for any CH572 wireless chips I make, as this is basically the exact setup that a wireless keyboard/mouse with a USB dongle does, and WCH also makes chips very similar to the CH572 for exactly that purpose. I'll eventually order the dongle in 2.0mm thickness and just keep it plugged into my computer, but first I'll have to find some way to send it into bootloader mode from either USB or wireless, I know both of which possible but the USB stack is, compared to an esp, way way harder to work with.
Group
If I teach someone reading this anything, its the blessing of MDNS. It basically allows for the router to act as a DNS server, and instead of referencing the esp32 as 192.168.1.230 or whatever, it can be esp.local. This doesnt just serve the use of making it easier to type in, it also essentially is the same as setting a static IP for the esp. with it active, any device can ping/query/whatever esp.local, and arduino has the command "MDNS.resolvehostname" that converts the esp.local into the current address of the esp. This is big for making projects both look professional and just work right, and made connecting our two projects way easier.