Add ESP-IDF and STM32 C examples; document platform examples

Both compile the same portable core (src/meshcore_companion.c) and differ only
in the UART transport:
- examples-esp-idf/tty_bridge: full ESP-IDF project (UART driver), core
  compiled directly via the component CMakeLists (no copy)
- examples-stm32/uart_bridge: HAL drop-in (meshcore_setup/meshcore_poll) for a
  CubeMX/CubeIDE project, with integration README

README updated: new examples in layout + an 'Other platform examples' section.
Verified host build/test still pass and both new examples pass -Wall -Wextra
syntax checks against stubbed platform headers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scott Penrose
2026-06-08 02:21:22 +10:00
parent cdfceba34d
commit b54e1c22e7
7 changed files with 376 additions and 5 deletions
+44
View File
@@ -0,0 +1,44 @@
# STM32 example: uart_bridge
Integration example showing how to drive a MeshCore Companion Radio from an STM32
using the CubeMX-generated HAL. The protocol logic is the repo's portable C core
(`src/meshcore_companion.c`); this file supplies only the UART transport and two
entry points you call from your generated `main()`.
Because an STM32 firmware build is tied to your specific MCU, clocks, pins and
linker script (all produced by STM32CubeIDE / CubeMX), this is **not** a
standalone buildable project — it is a drop-in.
## Steps (STM32CubeIDE / CubeMX)
1. Generate a project with one USART enabled at **115200 8N1** (e.g. `USART1`).
Wire it to the companion radio: host TX → radio RX, host RX ← radio TX, GND↔GND.
The radio must run the serial companion firmware (`companion_radio_usb`).
2. Add `src/meshcore_companion.c` and `src/meshcore_companion.h` from this repo to
your project (or add this repo's `src/` to the include paths).
3. Add `meshcore_stm32.c` to your project. If your USART handle isn't `huart1`,
change the `extern UART_HandleTypeDef huart1;` line near the top.
4. In the generated `main.c`:
```c
/* USER CODE BEGIN 2 */
meshcore_setup();
/* USER CODE END 2 */
while (1) {
/* USER CODE BEGIN 3 */
meshcore_poll();
}
```
## Notes
- `meshcore_poll()` polls the UART one byte at a time, which is fine for the
companion's low data rate. For high throughput, switch the RX side to
interrupt/DMA into a ring buffer and feed that to `mc_rx_feed()` — the core
code is unchanged.
- Logging uses `printf()`. Retarget it to a **separate** debug UART or SWO/ITM
(commonly USART2 = the ST-Link VCP) by implementing `_write()`; do not point it
at the companion UART.
- The same two-function transport pattern (`send bytes` / `read available bytes`)
ports directly to bare-metal STM32, nRF52 (nRF5 SDK or Zephyr), or any other
MCU — only the HAL calls change.
+123
View File
@@ -0,0 +1,123 @@
/*
* meshcore_stm32.c -- STM32 HAL integration example.
*
* Unlike the Linux and ESP-IDF examples, STM32 builds are board- and
* toolchain-specific (the CubeMX-generated HAL, startup code and linker script
* belong to your project), so this is an *integration example* rather than a
* standalone build. It shows the only two things the portable core needs from a
* platform: write some bytes, and read whatever bytes have arrived.
*
* How to use (STM32CubeIDE / CubeMX):
* 1. Generate a project with one USART enabled at 115200 8N1 (e.g. USART1).
* Wire it to the companion radio (host TX -> radio RX, host RX <- radio TX).
* 2. Add src/meshcore_companion.c and src/meshcore_companion.h to the project
* (Core/Src and Core/Inc, or add this repo's src/ to the include paths).
* 3. Add this file to the project.
* 4. In the generated main(): call meshcore_setup() once after MX_USARTx_UART_Init(),
* then call meshcore_poll() every iteration of the main while(1) loop.
*
* Byte-at-a-time polling is fine for the companion's low data rate; for high
* throughput switch the transport to interrupt/DMA RX into a ring buffer and
* feed that buffer to mc_rx_feed() — the core code does not change.
*
* Logging here uses printf(); retarget it to a *separate* debug UART or SWO/ITM
* (do not point it at the companion UART). On many CubeIDE projects that means
* implementing _write() to HAL_UART_Transmit on USART2 (the ST-Link VCP).
*
* SPDX-License-Identifier: MIT
* Author: Scott Penrose / Digital Dimensions.
*/
#include "main.h" /* CubeMX-generated: pulls in stm32xxxx_hal.h + handles */
#include <stdio.h>
#include <stdlib.h>
#include "meshcore_companion.h"
/* The USART you enabled in CubeMX and wired to the companion radio. */
extern UART_HandleTypeDef huart1;
#define MC_UART (&huart1)
static mc_rx_t s_rx;
static void send_payload(const uint8_t *payload, size_t len)
{
uint8_t frame[MC_RX_BUFSZ];
size_t flen = mc_frame_encode(payload, len, frame, sizeof frame);
if (flen) HAL_UART_Transmit(MC_UART, frame, (uint16_t)flen, HAL_MAX_DELAY);
}
static void on_event(const mc_event_t *ev)
{
switch (ev->code) {
case MC_RESP_DEVICE_INFO:
printf("radio model=%s fw=%d channels=%u build=%s\r\n",
ev->u.device_info.model, ev->u.device_info.fw_ver,
(unsigned)ev->u.device_info.max_channels,
ev->u.device_info.build_date);
break;
case MC_RESP_CHANNEL_MSG_RECV: /* body is "SenderName: message" */
printf("[ch %d] %s\r\n", ev->u.channel_msg.channel_idx,
ev->u.channel_msg.text);
break;
case MC_RESP_CHANNEL_DATA_RECV: {
int centi = ev->u.channel_data.snr_q4 * 25; /* q4 -> /4 then *100 */
const char *sign = centi < 0 ? "-" : "";
printf("[ch %d] %u bytes type=0x%04X snr=%s%d.%02d dB %s\r\n",
ev->u.channel_data.channel_idx,
(unsigned)ev->u.channel_data.data_len,
(unsigned)ev->u.channel_data.data_type,
sign, abs(centi) / 100, abs(centi) % 100,
ev->u.channel_data.path_len == MC_PATH_DIRECT ? "direct" : "flood");
break;
}
case MC_RESP_CURR_TIME:
printf("device time = %u (epoch secs)\r\n", (unsigned)ev->u.curr_time);
break;
case MC_RESP_ERR:
printf("radio error response (code=%d)\r\n", ev->u.err_code);
break;
default:
break;
}
}
/* Call once after MX_USARTx_UART_Init(). */
void meshcore_setup(void)
{
mc_rx_init(&s_rx);
uint8_t cmd[MC_MAX_PAYLOAD];
size_t n;
n = mc_cmd_app_start(cmd, sizeof cmd, "stm32"); if (n) send_payload(cmd, n);
n = mc_cmd_device_query(cmd, sizeof cmd, 1); if (n) send_payload(cmd, n);
n = mc_cmd_get_device_time(cmd, sizeof cmd); if (n) send_payload(cmd, n);
}
/* Call from your main while(1) loop. Non-blocking. */
void meshcore_poll(void)
{
uint8_t cmd[MC_MAX_PAYLOAD];
size_t n;
uint8_t b;
/* Drain every byte currently available (timeout 0 = return immediately). */
while (HAL_UART_Receive(MC_UART, &b, 1, 0) == HAL_OK) {
mc_rx_feed(&s_rx, &b, 1);
uint8_t payload[MC_MAX_PAYLOAD];
size_t plen;
while (mc_rx_poll(&s_rx, payload, sizeof payload, &plen)) {
mc_event_t ev;
if (!mc_parse(payload, plen, &ev)) continue;
on_event(&ev);
if (ev.code == MC_PUSH_MSG_WAITING ||
ev.code == MC_RESP_CHANNEL_MSG_RECV ||
ev.code == MC_RESP_CHANNEL_DATA_RECV ||
ev.code == MC_RESP_CONTACT_MSG_RECV) {
n = mc_cmd_sync_next_message(cmd, sizeof cmd);
if (n) send_payload(cmd, n);
}
}
}
}