#include "platform.h" #include "subghz_phy_app.h" #include "radio.h" #include "usart_if.h" #include "usart.h" #include "main.h" #include #include #include #include #include #include #define RX_TIMEOUT_VALUE_MS 0U #define TX_TIMEOUT_VALUE_MS 3000U #define RX_CONTINUOUS_ON 1U #define RADIO_SYNCWORD_LEN 3U #define RADIO_WHITENING_SEED 0x01FFU #define RADIO_CRC_POLY 0x8005U #define RADIO_CRC_SEED 0xFFFFU #define UART_DATA_BUFFER_SIZE 220U #define RADIO_MAX_PAYLOAD_SIZE 220U #define TX_QUEUE_DEPTH 4U #define CONFIG_LINE_SIZE 96U #define CONFIG_ESCAPE_GUARD_MS 800U #define DEFAULT_UART_PACKET_TIMEOUT_MS 20U typedef enum { APP_MODE_DATA = 0, APP_MODE_CONFIG } AppMode_t; typedef struct { uint32_t rf_frequency; int8_t tx_power; uint32_t fsk_bitrate; uint32_t fsk_bandwidth; uint32_t fsk_fdev; uint16_t fsk_preamble_len; uint8_t syncword[RADIO_SYNCWORD_LEN]; uint16_t uart_packet_timeout_ms; uint32_t uart_baudrate; } BridgeConfig_t; typedef struct { uint8_t data[RADIO_MAX_PAYLOAD_SIZE]; uint8_t len; } TxPacket_t; typedef struct { uint8_t active; uint8_t count; uint8_t bytes[3]; uint32_t start_tick; uint32_t last_tick; } EscapeDetector_t; static RadioEvents_t RadioEvents; static BridgeConfig_t g_cfg = { .rf_frequency = RF_FREQUENCY_DEFAULT, .tx_power = TX_OUTPUT_POWER_DEFAULT, .fsk_bitrate = FSK_DATARATE_DEFAULT, .fsk_bandwidth = FSK_BANDWIDTH_DEFAULT, .fsk_fdev = FSK_FDEV_DEFAULT, .fsk_preamble_len = FSK_PREAMBLE_LENGTH_DEFAULT, .syncword = {0xC1, 0x94, 0xC1}, .uart_packet_timeout_ms = DEFAULT_UART_PACKET_TIMEOUT_MS, .uart_baudrate = 115200UL, }; static volatile uint8_t g_radio_tx_done = 0; static volatile uint8_t g_radio_tx_timeout = 0; static volatile uint8_t g_radio_rx_done = 0; static volatile uint8_t g_radio_rx_timeout = 0; static volatile uint8_t g_radio_rx_error = 0; static volatile int16_t g_last_rx_rssi = 0; static volatile int8_t g_last_rx_cfo = 0; static volatile uint8_t g_radio_busy = 0; static volatile uint8_t g_radio_needs_rx_restart = 0; static uint8_t g_rx_payload[RADIO_MAX_PAYLOAD_SIZE]; static uint16_t g_rx_payload_len = 0; static TxPacket_t g_tx_queue[TX_QUEUE_DEPTH]; static uint8_t g_tx_q_head = 0; static uint8_t g_tx_q_tail = 0; static uint8_t g_tx_q_count = 0; static uint8_t g_uart_build_buf[UART_DATA_BUFFER_SIZE]; static uint16_t g_uart_build_len = 0; static uint32_t g_uart_last_data_tick = 0; static EscapeDetector_t g_escape = {0}; static AppMode_t g_mode = APP_MODE_DATA; static char g_cfg_line[CONFIG_LINE_SIZE]; static uint16_t g_cfg_line_len = 0; static uint32_t g_stat_uart_packets_tx = 0; static uint32_t g_stat_uart_bytes_tx = 0; static uint32_t g_stat_radio_packets_rx = 0; static uint32_t g_stat_radio_bytes_rx = 0; static uint32_t g_stat_queue_overflow = 0; static void OnTxDone(void); static void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t cfo); static void OnTxTimeout(void); static void OnRxTimeout(void); static void OnRxError(void); static void UartRxByteCallback(uint8_t *rxChar, uint16_t size, uint8_t error); static void App_ProcessRadioEvents(void); static void App_ProcessUartPacketizer(void); static void App_ProcessEscape(void); static void App_StartNextTxIfPossible(void); static void App_RadioEnterRx(void); static void App_RadioApplyConfig(void); static void App_RadioConfigureRx(void); static void App_RadioConfigureTx(void); static void App_EnterConfigMode(void); static void App_ExitConfigMode(void); static void App_ResetDataPath(void); static void App_DataModeFeedByte(uint8_t ch, uint32_t now); static void App_DataModeFlushBuilder(void); static uint8_t App_QueuePush(const uint8_t *data, uint16_t len); static void App_QueuePop(void); static void App_ConfigFeedByte(uint8_t ch); static void App_ConfigExecuteLine(char *line); static void App_PrintConfigPrompt(void); static void App_PrintHelp(void); static void App_PrintStatus(void); static void App_Printf(const char *fmt, ...); static void App_Write(const uint8_t *data, uint16_t len); static void App_ReconfigureUart(uint32_t baudrate); static uint8_t App_ParseHexSyncWord(const char *text, uint8_t out[3]); static char *App_SkipSpaces(char *s); void SubghzApp_Init(void) { RadioEvents.TxDone = OnTxDone; RadioEvents.RxDone = OnRxDone; RadioEvents.TxTimeout = OnTxTimeout; RadioEvents.RxTimeout = OnRxTimeout; RadioEvents.RxError = OnRxError; Radio.Init(&RadioEvents); App_RadioApplyConfig(); App_RadioEnterRx(); g_uart_last_data_tick = HAL_GetTick(); (void)vcom_ReceiveInit(UartRxByteCallback); App_Printf("\r\nSTM32WL UART<->SUBGHZ bridge started\r\n"); App_Printf("DATA mode, escape sequence: silence 800 ms + +++ + silence 800 ms\r\n"); } void SubghzApp_Process(void) { App_ProcessEscape(); App_ProcessUartPacketizer(); App_ProcessRadioEvents(); App_StartNextTxIfPossible(); } static void App_ProcessRadioEvents(void) { if (g_radio_tx_done != 0U) { g_radio_tx_done = 0U; g_radio_busy = 0U; g_stat_uart_packets_tx++; App_QueuePop(); g_radio_needs_rx_restart = 1U; } if (g_radio_tx_timeout != 0U) { g_radio_tx_timeout = 0U; g_radio_busy = 0U; App_QueuePop(); App_Printf("\r\n[WARN] radio tx timeout\r\n"); g_radio_needs_rx_restart = 1U; } if (g_radio_rx_done != 0U) { g_radio_rx_done = 0U; g_stat_radio_packets_rx++; g_stat_radio_bytes_rx += g_rx_payload_len; if (g_mode == APP_MODE_DATA) { App_Write(g_rx_payload, g_rx_payload_len); } g_radio_needs_rx_restart = 1U; } if ((g_radio_rx_timeout != 0U) || (g_radio_rx_error != 0U)) { g_radio_rx_timeout = 0U; g_radio_rx_error = 0U; g_radio_needs_rx_restart = 1U; } if ((g_radio_needs_rx_restart != 0U) && (g_radio_busy == 0U) && (g_tx_q_count == 0U)) { g_radio_needs_rx_restart = 0U; App_RadioEnterRx(); } } static void App_ProcessUartPacketizer(void) { uint32_t now = HAL_GetTick(); if (g_mode != APP_MODE_DATA) { return; } if ((g_uart_build_len > 0U) && ((now - g_uart_last_data_tick) >= g_cfg.uart_packet_timeout_ms) && (g_escape.active == 0U)) { App_DataModeFlushBuilder(); } } static void App_ProcessEscape(void) { uint32_t now = HAL_GetTick(); uint8_t i; if ((g_mode != APP_MODE_DATA) || (g_escape.active == 0U)) { return; } if ((g_escape.count == 3U) && ((now - g_escape.last_tick) >= CONFIG_ESCAPE_GUARD_MS)) { g_escape.active = 0U; g_escape.count = 0U; App_EnterConfigMode(); return; } if ((g_escape.count < 3U) && ((now - g_escape.last_tick) >= CONFIG_ESCAPE_GUARD_MS)) { for (i = 0U; i < g_escape.count; i++) { App_DataModeFeedByte(g_escape.bytes[i], now); } g_escape.active = 0U; g_escape.count = 0U; } } static void App_StartNextTxIfPossible(void) { if ((g_mode != APP_MODE_DATA) || (g_radio_busy != 0U) || (g_tx_q_count == 0U)) { return; } App_RadioConfigureTx(); g_radio_busy = 1U; (void)Radio.Send(g_tx_queue[g_tx_q_head].data, g_tx_queue[g_tx_q_head].len); } static void App_RadioApplyConfig(void) { Radio.SetChannel(g_cfg.rf_frequency); g_radio_needs_rx_restart = 1U; } static void App_RadioConfigureRx(void) { RxConfigGeneric_t rx = {0}; Radio.SetChannel(g_cfg.rf_frequency); rx.fsk.ModulationShaping = RADIO_FSK_MOD_SHAPING_G_BT_05; rx.fsk.Bandwidth = g_cfg.fsk_bandwidth; rx.fsk.BitRate = g_cfg.fsk_bitrate; rx.fsk.PreambleLen = g_cfg.fsk_preamble_len; rx.fsk.SyncWordLength = RADIO_SYNCWORD_LEN; rx.fsk.PreambleMinDetect = RADIO_FSK_PREAMBLE_DETECTOR_08_BITS; rx.fsk.SyncWord = g_cfg.syncword; rx.fsk.whiteSeed = RADIO_WHITENING_SEED; rx.fsk.LengthMode = RADIO_FSK_PACKET_VARIABLE_LENGTH; rx.fsk.CrcLength = RADIO_FSK_CRC_2_BYTES_IBM; rx.fsk.CrcPolynomial = RADIO_CRC_POLY; rx.fsk.CrcSeed = RADIO_CRC_SEED; rx.fsk.Whitening = RADIO_FSK_DC_FREEWHITENING; rx.fsk.MaxPayloadLength = RADIO_MAX_PAYLOAD_SIZE; rx.fsk.StopTimerOnPreambleDetect = 0; rx.fsk.AddrComp = RADIO_FSK_ADDRESSCOMP_FILT_OFF; Radio.Standby(); if (0UL != Radio.RadioSetRxGenericConfig(GENERIC_FSK, &rx, RX_CONTINUOUS_ON, 0U)) { Error_Handler(); } } static void App_RadioConfigureTx(void) { TxConfigGeneric_t tx = {0}; Radio.SetChannel(g_cfg.rf_frequency); tx.fsk.ModulationShaping = RADIO_FSK_MOD_SHAPING_G_BT_05; tx.fsk.FrequencyDeviation = g_cfg.fsk_fdev; tx.fsk.BitRate = g_cfg.fsk_bitrate; tx.fsk.PreambleLen = g_cfg.fsk_preamble_len; tx.fsk.SyncWordLength = RADIO_SYNCWORD_LEN; tx.fsk.SyncWord = g_cfg.syncword; tx.fsk.whiteSeed = RADIO_WHITENING_SEED; tx.fsk.HeaderType = RADIO_FSK_PACKET_VARIABLE_LENGTH; tx.fsk.CrcLength = RADIO_FSK_CRC_2_BYTES_IBM; tx.fsk.CrcPolynomial = RADIO_CRC_POLY; tx.fsk.CrcSeed = RADIO_CRC_SEED; tx.fsk.Whitening = RADIO_FSK_DC_FREEWHITENING; Radio.Standby(); if (0UL != Radio.RadioSetTxGenericConfig(GENERIC_FSK, &tx, g_cfg.tx_power, TX_TIMEOUT_VALUE_MS)) { Error_Handler(); } } static void App_RadioEnterRx(void) { App_RadioConfigureRx(); Radio.Rx(RX_TIMEOUT_VALUE_MS); } static void App_EnterConfigMode(void) { App_ResetDataPath(); g_mode = APP_MODE_CONFIG; App_Printf("\r\n\r\n[CONFIG MODE]\r\n"); App_Printf("type 'help' for commands\r\n"); App_PrintConfigPrompt(); } static void App_ExitConfigMode(void) { g_cfg_line_len = 0U; App_ResetDataPath(); g_mode = APP_MODE_DATA; App_Printf("\r\n[DATA MODE]\r\n"); g_radio_needs_rx_restart = 1U; } static void App_ResetDataPath(void) { g_uart_build_len = 0U; g_escape.active = 0U; g_escape.count = 0U; g_tx_q_head = 0U; g_tx_q_tail = 0U; g_tx_q_count = 0U; } static void App_DataModeFeedByte(uint8_t ch, uint32_t now) { if (g_uart_build_len < UART_DATA_BUFFER_SIZE) { g_uart_build_buf[g_uart_build_len++] = ch; g_uart_last_data_tick = now; g_stat_uart_bytes_tx++; } else { App_DataModeFlushBuilder(); if (g_uart_build_len < UART_DATA_BUFFER_SIZE) { g_uart_build_buf[g_uart_build_len++] = ch; g_uart_last_data_tick = now; g_stat_uart_bytes_tx++; } } } static void App_DataModeFlushBuilder(void) { if (g_uart_build_len == 0U) { return; } if (App_QueuePush(g_uart_build_buf, g_uart_build_len) == 0U) { g_stat_queue_overflow++; } g_uart_build_len = 0U; } static uint8_t App_QueuePush(const uint8_t *data, uint16_t len) { if ((len == 0U) || (len > RADIO_MAX_PAYLOAD_SIZE) || (g_tx_q_count >= TX_QUEUE_DEPTH)) { return 0U; } memcpy(g_tx_queue[g_tx_q_tail].data, data, len); g_tx_queue[g_tx_q_tail].len = (uint8_t)len; g_tx_q_tail = (uint8_t)((g_tx_q_tail + 1U) % TX_QUEUE_DEPTH); g_tx_q_count++; return 1U; } static void App_QueuePop(void) { if (g_tx_q_count == 0U) { return; } g_tx_q_head = (uint8_t)((g_tx_q_head + 1U) % TX_QUEUE_DEPTH); g_tx_q_count--; } static void UartRxByteCallback(uint8_t *rxChar, uint16_t size, uint8_t error) { uint8_t ch; uint32_t now; uint8_t i; if ((error != 0U) || (size == 0U) || (rxChar == NULL)) { return; } ch = rxChar[0]; now = HAL_GetTick(); if (g_mode == APP_MODE_CONFIG) { App_ConfigFeedByte(ch); return; } if (g_escape.active == 0U) { if (((now - g_uart_last_data_tick) >= CONFIG_ESCAPE_GUARD_MS) && (ch == '+')) { g_escape.active = 1U; g_escape.count = 1U; g_escape.bytes[0] = ch; g_escape.start_tick = now; g_escape.last_tick = now; return; } App_DataModeFeedByte(ch, now); return; } if ((ch == '+') && (g_escape.count < 3U)) { g_escape.bytes[g_escape.count++] = ch; g_escape.last_tick = now; return; } for (i = 0U; i < g_escape.count; i++) { App_DataModeFeedByte(g_escape.bytes[i], now); } g_escape.active = 0U; g_escape.count = 0U; if (((now - g_uart_last_data_tick) >= CONFIG_ESCAPE_GUARD_MS) && (ch == '+')) { g_escape.active = 1U; g_escape.count = 1U; g_escape.bytes[0] = ch; g_escape.start_tick = now; g_escape.last_tick = now; return; } App_DataModeFeedByte(ch, now); } static void App_ConfigFeedByte(uint8_t ch) { if ((ch == '\r') || (ch == '\n')) { if (g_cfg_line_len > 0U) { g_cfg_line[g_cfg_line_len] = '\0'; App_Printf("\r\n"); App_ConfigExecuteLine(g_cfg_line); g_cfg_line_len = 0U; } App_PrintConfigPrompt(); return; } if ((ch == 0x08U) || (ch == 0x7FU)) { if (g_cfg_line_len > 0U) { g_cfg_line_len--; App_Write((const uint8_t *)"\b \b", 3U); } return; } if ((isprint(ch) != 0) && (g_cfg_line_len < (CONFIG_LINE_SIZE - 1U))) { g_cfg_line[g_cfg_line_len++] = (char)ch; App_Write(&ch, 1U); } } static void App_ConfigExecuteLine(char *line) { char *arg; uint32_t u32; uint8_t sync[3]; line = App_SkipSpaces(line); if (*line == '\0') { return; } if ((strcmp(line, "help") == 0) || (strcmp(line, "?") == 0)) { App_PrintHelp(); return; } if ((strcmp(line, "show") == 0) || (strcmp(line, "status") == 0)) { App_PrintStatus(); return; } if (strcmp(line, "exit") == 0) { App_ExitConfigMode(); return; } if (strcmp(line, "defaults") == 0) { g_cfg.rf_frequency = RF_FREQUENCY_DEFAULT; g_cfg.tx_power = TX_OUTPUT_POWER_DEFAULT; g_cfg.fsk_bitrate = FSK_DATARATE_DEFAULT; g_cfg.fsk_bandwidth = FSK_BANDWIDTH_DEFAULT; g_cfg.fsk_fdev = FSK_FDEV_DEFAULT; g_cfg.fsk_preamble_len = FSK_PREAMBLE_LENGTH_DEFAULT; g_cfg.syncword[0] = 0xC1U; g_cfg.syncword[1] = 0x94U; g_cfg.syncword[2] = 0xC1U; g_cfg.uart_packet_timeout_ms = DEFAULT_UART_PACKET_TIMEOUT_MS; App_RadioApplyConfig(); App_Printf("defaults restored\r\n"); return; } if (strncmp(line, "freq ", 5) == 0) { u32 = strtoul(&line[5], NULL, 10); if (u32 < 150000000UL || u32 > 960000000UL) { App_Printf("bad frequency\r\n"); return; } g_cfg.rf_frequency = u32; App_RadioApplyConfig(); App_Printf("freq=%lu\r\n", (unsigned long)g_cfg.rf_frequency); return; } if (strncmp(line, "power ", 6) == 0) { long pwr = strtol(&line[6], NULL, 10); if ((pwr < -9L) || (pwr > 22L)) { App_Printf("bad power\r\n"); return; } g_cfg.tx_power = (int8_t)pwr; App_RadioApplyConfig(); App_Printf("power=%d\r\n", g_cfg.tx_power); return; } if (strncmp(line, "bitrate ", 8) == 0) { u32 = strtoul(&line[8], NULL, 10); if ((u32 < 600UL) || (u32 > 300000UL)) { App_Printf("bad bitrate\r\n"); return; } g_cfg.fsk_bitrate = u32; App_RadioApplyConfig(); App_Printf("bitrate=%lu\r\n", (unsigned long)g_cfg.fsk_bitrate); return; } if (strncmp(line, "bandwidth ", 10) == 0) { u32 = strtoul(&line[10], NULL, 10); if ((u32 < 2600UL) || (u32 > 250000UL)) { App_Printf("bad bandwidth\r\n"); return; } g_cfg.fsk_bandwidth = u32; App_RadioApplyConfig(); App_Printf("bandwidth=%lu\r\n", (unsigned long)g_cfg.fsk_bandwidth); return; } if (strncmp(line, "fdev ", 5) == 0) { u32 = strtoul(&line[5], NULL, 10); if (u32 > 200000UL) { App_Printf("bad fdev\r\n"); return; } g_cfg.fsk_fdev = u32; App_RadioApplyConfig(); App_Printf("fdev=%lu\r\n", (unsigned long)g_cfg.fsk_fdev); return; } if (strncmp(line, "preamble ", 9) == 0) { u32 = strtoul(&line[9], NULL, 10); if ((u32 < 2UL) || (u32 > 65535UL)) { App_Printf("bad preamble\r\n"); return; } g_cfg.fsk_preamble_len = (uint16_t)u32; App_RadioApplyConfig(); App_Printf("preamble=%u\r\n", g_cfg.fsk_preamble_len); return; } if (strncmp(line, "timeout ", 8) == 0) { u32 = strtoul(&line[8], NULL, 10); if ((u32 < 1UL) || (u32 > 1000UL)) { App_Printf("bad timeout\r\n"); return; } g_cfg.uart_packet_timeout_ms = (uint16_t)u32; App_Printf("timeout=%u\r\n", g_cfg.uart_packet_timeout_ms); return; } if (strncmp(line, "uart ", 5) == 0) { u32 = strtoul(&line[5], NULL, 10); if ((u32 < 1200UL) || (u32 > 921600UL)) { App_Printf("bad uart baudrate\r\n"); return; } g_cfg.uart_baudrate = u32; App_Printf("switching uart to %lu baud\r\n", (unsigned long)g_cfg.uart_baudrate); App_ReconfigureUart(g_cfg.uart_baudrate); return; } if (strncmp(line, "sync ", 5) == 0) { arg = App_SkipSpaces(&line[5]); if (App_ParseHexSyncWord(arg, sync) == 0U) { App_Printf("bad sync, use 6 hex chars, e.g. C194C1\r\n"); return; } memcpy(g_cfg.syncword, sync, sizeof(sync)); App_RadioApplyConfig(); App_Printf("sync=%02X%02X%02X\r\n", g_cfg.syncword[0], g_cfg.syncword[1], g_cfg.syncword[2]); return; } App_Printf("unknown command: %s\r\n", line); } static void App_PrintConfigPrompt(void) { if (g_mode == APP_MODE_CONFIG) { App_Printf("cfg> "); } } static void App_PrintHelp(void) { App_Printf("commands:\r\n"); App_Printf(" help - this help\r\n"); App_Printf(" show - current config and counters\r\n"); App_Printf(" freq - rf frequency\r\n"); App_Printf(" power - tx power (-9..22)\r\n"); App_Printf(" bitrate - fsk bitrate\r\n"); App_Printf(" bandwidth - fsk rx bandwidth\r\n"); App_Printf(" fdev - fsk frequency deviation\r\n"); App_Printf(" preamble - fsk preamble length\r\n"); App_Printf(" sync - 3-byte syncword, example C194C1\r\n"); App_Printf(" timeout - uart silence before rf packet send\r\n"); App_Printf(" uart - change uart baudrate immediately\r\n"); App_Printf(" defaults - restore default config\r\n"); App_Printf(" exit - return to transparent bridge mode\r\n"); } static void App_PrintStatus(void) { App_Printf("mode=%s\r\n", (g_mode == APP_MODE_CONFIG) ? "config" : "data"); App_Printf("freq=%lu Hz\r\n", (unsigned long)g_cfg.rf_frequency); App_Printf("power=%d dBm\r\n", g_cfg.tx_power); App_Printf("bitrate=%lu bps\r\n", (unsigned long)g_cfg.fsk_bitrate); App_Printf("bandwidth=%lu Hz\r\n", (unsigned long)g_cfg.fsk_bandwidth); App_Printf("fdev=%lu Hz\r\n", (unsigned long)g_cfg.fsk_fdev); App_Printf("preamble=%u bytes\r\n", g_cfg.fsk_preamble_len); App_Printf("sync=%02X%02X%02X\r\n", g_cfg.syncword[0], g_cfg.syncword[1], g_cfg.syncword[2]); App_Printf("uart_baud=%lu\r\n", (unsigned long)g_cfg.uart_baudrate); App_Printf("uart_pkt_timeout=%u ms\r\n", g_cfg.uart_packet_timeout_ms); App_Printf("tx_queue=%u/%u\r\n", g_tx_q_count, TX_QUEUE_DEPTH); App_Printf("last_rx_rssi=%d dBm\r\n", (int)g_last_rx_rssi); App_Printf("last_rx_cfo=%d\r\n", (int)g_last_rx_cfo); App_Printf("stat_uart_packets_tx=%lu\r\n", (unsigned long)g_stat_uart_packets_tx); App_Printf("stat_uart_bytes_tx=%lu\r\n", (unsigned long)g_stat_uart_bytes_tx); App_Printf("stat_radio_packets_rx=%lu\r\n", (unsigned long)g_stat_radio_packets_rx); App_Printf("stat_radio_bytes_rx=%lu\r\n", (unsigned long)g_stat_radio_bytes_rx); App_Printf("stat_queue_overflow=%lu\r\n", (unsigned long)g_stat_queue_overflow); } static void App_Printf(const char *fmt, ...) { char buffer[192]; va_list ap; int len; va_start(ap, fmt); len = vsnprintf(buffer, sizeof(buffer), fmt, ap); va_end(ap); if (len <= 0) { return; } if ((size_t)len >= sizeof(buffer)) { len = (int)(sizeof(buffer) - 1U); } App_Write((const uint8_t *)buffer, (uint16_t)len); } static void App_Write(const uint8_t *data, uint16_t len) { if ((data == NULL) || (len == 0U)) { return; } (void)HAL_UART_Transmit(&huart2, (uint8_t *)data, len, 1000U); } static void App_ReconfigureUart(uint32_t baudrate) { huart2.Init.BaudRate = baudrate; (void)HAL_UART_AbortReceive(&huart2); if (HAL_UART_Init(&huart2) != HAL_OK) { Error_Handler(); } if (HAL_UARTEx_SetTxFifoThreshold(&huart2, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK) { Error_Handler(); } if (HAL_UARTEx_SetRxFifoThreshold(&huart2, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK) { Error_Handler(); } if (HAL_UARTEx_EnableFifoMode(&huart2) != HAL_OK) { Error_Handler(); } (void)vcom_ReceiveInit(UartRxByteCallback); } static uint8_t App_ParseHexSyncWord(const char *text, uint8_t out[3]) { char buf[7]; char *endptr; unsigned long value; size_t i; size_t n = 0U; if ((text == NULL) || (out == NULL)) { return 0U; } while ((*text != '\0') && (n < 6U)) { if (isxdigit((unsigned char)*text) != 0) { buf[n++] = *text; } text++; } if (n != 6U) { return 0U; } for (i = 0U; i < n; i++) { if (isxdigit((unsigned char)buf[i]) == 0) { return 0U; } } buf[6] = '\0'; value = strtoul(buf, &endptr, 16); if ((endptr == NULL) || (*endptr != '\0')) { return 0U; } out[0] = (uint8_t)((value >> 16) & 0xFFU); out[1] = (uint8_t)((value >> 8) & 0xFFU); out[2] = (uint8_t)(value & 0xFFU); return 1U; } static char *App_SkipSpaces(char *s) { while ((s != NULL) && (*s != '\0') && isspace((unsigned char)*s)) { s++; } return s; } static void OnTxDone(void) { g_radio_tx_done = 1U; } static void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t cfo) { g_last_rx_rssi = rssi; g_last_rx_cfo = cfo; if (size > RADIO_MAX_PAYLOAD_SIZE) { size = RADIO_MAX_PAYLOAD_SIZE; } memcpy(g_rx_payload, payload, size); g_rx_payload_len = size; g_radio_rx_done = 1U; } static void OnTxTimeout(void) { g_radio_tx_timeout = 1U; } static void OnRxTimeout(void) { g_radio_rx_timeout = 1U; } static void OnRxError(void) { g_radio_rx_error = 1U; }