feat: 完成TCP2UART透传核心集成

集成CH390驱动、LwIP协议栈和FreeRTOS多任务透传框架,确保TCP Server/Client与UART链路按配置稳定联动。
This commit is contained in:
2026-03-30 11:39:40 +08:00
parent d5803ca7dd
commit 4996b451d9
235 changed files with 80607 additions and 27 deletions
+772
View File
@@ -0,0 +1,772 @@
/**
* @file config.c
* @brief AT command configuration module implementation
*/
#include "config.h"
#include "flash_param.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
/*---------------------------------------------------------------------------
* Private Definitions
*---------------------------------------------------------------------------*/
#define CONFIG_RX_BUFFER_SIZE 256
#define CONFIG_TX_BUFFER_SIZE 512
#define CONFIG_CMD_MAX_LEN 128
/* AT command prefixes */
#define AT_PREFIX "AT+"
#define AT_QUERY "AT+?"
/*---------------------------------------------------------------------------
* Private Variables
*---------------------------------------------------------------------------*/
/* Current device configuration */
static device_config_t g_config;
static uint32_t config_calc_crc(const device_config_t *cfg)
{
return flash_param_crc32(cfg, offsetof(device_config_t, crc));
}
/* UART1 reception buffer */
static uint8_t g_rx_buffer[CONFIG_RX_BUFFER_SIZE];
static volatile uint16_t g_rx_index = 0;
static volatile bool g_rx_complete = false;
static volatile bool g_reset_requested = false;
/*---------------------------------------------------------------------------
* Private Functions - String Utilities
*---------------------------------------------------------------------------*/
/**
* @brief Skip whitespace in string
*/
static const char *skip_whitespace(const char *str)
{
while (*str == ' ' || *str == '\t')
{
str++;
}
return str;
}
/**
* @brief Trim trailing whitespace and newlines
*/
static void trim_trailing(char *str)
{
int len = strlen(str);
while (len > 0 && (str[len-1] == ' ' || str[len-1] == '\t' ||
str[len-1] == '\r' || str[len-1] == '\n'))
{
str[--len] = '\0';
}
}
/**
* @brief Compare string prefix (case-insensitive)
*/
static bool starts_with(const char *str, const char *prefix)
{
while (*prefix)
{
char c1 = *str++;
char c2 = *prefix++;
/* Convert to uppercase for comparison */
if (c1 >= 'a' && c1 <= 'z') c1 -= 32;
if (c2 >= 'a' && c2 <= 'z') c2 -= 32;
if (c1 != c2)
{
return false;
}
}
return true;
}
static bool equals_ignore_case(const char *a, const char *b)
{
while (*a != '\0' && *b != '\0')
{
char c1 = *a++;
char c2 = *b++;
if (c1 >= 'a' && c1 <= 'z') c1 -= 32;
if (c2 >= 'a' && c2 <= 'z') c2 -= 32;
if (c1 != c2)
{
return false;
}
}
return (*a == '\0' && *b == '\0');
}
/*---------------------------------------------------------------------------
* Private Functions - AT Command Handlers
*---------------------------------------------------------------------------*/
/**
* @brief Handle AT+IP command
*/
static at_result_t handle_ip(const char *value, char *response, uint16_t max_len)
{
uint8_t ip[4];
if (config_str_to_ip(value, ip) != 0)
{
snprintf(response, max_len, "ERROR: Invalid IP format\r\n");
return AT_INVALID_PARAM;
}
memcpy(g_config.ip, ip, 4);
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
/**
* @brief Handle AT+MASK command
*/
static at_result_t handle_mask(const char *value, char *response, uint16_t max_len)
{
uint8_t mask[4];
if (config_str_to_ip(value, mask) != 0)
{
snprintf(response, max_len, "ERROR: Invalid mask format\r\n");
return AT_INVALID_PARAM;
}
memcpy(g_config.mask, mask, 4);
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
/**
* @brief Handle AT+GW command
*/
static at_result_t handle_gw(const char *value, char *response, uint16_t max_len)
{
uint8_t gw[4];
if (config_str_to_ip(value, gw) != 0)
{
snprintf(response, max_len, "ERROR: Invalid gateway format\r\n");
return AT_INVALID_PARAM;
}
memcpy(g_config.gw, gw, 4);
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
/**
* @brief Handle AT+PORT command
*/
static at_result_t handle_port(const char *value, char *response, uint16_t max_len)
{
int port = atoi(value);
if (port < 1 || port > 65535)
{
snprintf(response, max_len, "ERROR: Invalid port (1-65535)\r\n");
return AT_INVALID_PARAM;
}
g_config.server_port = (uint16_t)port;
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
/**
* @brief Handle AT+RIP command
*/
static at_result_t handle_rip(const char *value, char *response, uint16_t max_len)
{
uint8_t ip[4];
if (config_str_to_ip(value, ip) != 0)
{
snprintf(response, max_len, "ERROR: Invalid remote IP format\r\n");
return AT_INVALID_PARAM;
}
memcpy(g_config.remote_ip, ip, 4);
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
/**
* @brief Handle AT+RPORT command
*/
static at_result_t handle_rport(const char *value, char *response, uint16_t max_len)
{
int port = atoi(value);
if (port < 1 || port > 65535)
{
snprintf(response, max_len, "ERROR: Invalid port (1-65535)\r\n");
return AT_INVALID_PARAM;
}
g_config.remote_port = (uint16_t)port;
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
/**
* @brief Handle AT+BAUD1 command (UART2)
*/
static at_result_t handle_baud1(const char *value, char *response, uint16_t max_len)
{
int baud = atoi(value);
/* Validate common baud rates */
if (baud != 9600 && baud != 19200 && baud != 38400 &&
baud != 57600 && baud != 115200 && baud != 230400 &&
baud != 460800 && baud != 921600)
{
snprintf(response, max_len, "ERROR: Invalid baud rate\r\n");
return AT_INVALID_PARAM;
}
g_config.uart2_baudrate = (uint32_t)baud;
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
/**
* @brief Handle AT+BAUD2 command (UART3)
*/
static at_result_t handle_baud2(const char *value, char *response, uint16_t max_len)
{
int baud = atoi(value);
/* Validate common baud rates */
if (baud != 9600 && baud != 19200 && baud != 38400 &&
baud != 57600 && baud != 115200 && baud != 230400 &&
baud != 460800 && baud != 921600)
{
snprintf(response, max_len, "ERROR: Invalid baud rate\r\n");
return AT_INVALID_PARAM;
}
g_config.uart3_baudrate = (uint32_t)baud;
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
/**
* @brief Handle AT+MAC command
*/
static at_result_t handle_mac(const char *value, char *response, uint16_t max_len)
{
uint8_t mac[6];
if (config_str_to_mac(value, mac) != 0)
{
snprintf(response, max_len, "ERROR: Invalid MAC format\r\n");
return AT_INVALID_PARAM;
}
memcpy(g_config.mac, mac, 6);
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
/**
* @brief Handle AT+DHCP command
*/
static at_result_t handle_dhcp(const char *value, char *response, uint16_t max_len)
{
int dhcp = atoi(value);
if (dhcp != 0 && dhcp != 1)
{
snprintf(response, max_len, "ERROR: Invalid value (0 or 1)\r\n");
return AT_INVALID_PARAM;
}
g_config.dhcp_enable = (uint8_t)dhcp;
snprintf(response, max_len, "OK\r\n");
return AT_NEED_REBOOT;
}
/**
* @brief Handle AT+SAVE command
*/
static at_result_t handle_save(char *response, uint16_t max_len)
{
if (config_save() != 0)
{
snprintf(response, max_len, "ERROR: Save failed\r\n");
return AT_SAVE_FAILED;
}
snprintf(response, max_len, "OK: Configuration saved\r\n");
return AT_OK;
}
/**
* @brief Handle AT+RESET command
*/
static at_result_t handle_reset(char *response, uint16_t max_len)
{
snprintf(response, max_len, "OK: Resetting...\r\n");
g_reset_requested = true;
return AT_OK;
}
/**
* @brief Handle AT+DEFAULT command
*/
static at_result_t handle_default(char *response, uint16_t max_len)
{
config_set_defaults();
snprintf(response, max_len, "OK: Defaults restored. Use AT+SAVE to save.\r\n");
return AT_OK;
}
/**
* @brief Handle AT+? query command
*/
static at_result_t handle_query(char *response, uint16_t max_len)
{
char ip_str[16], mask_str[16], gw_str[16], rip_str[16], mac_str[18];
config_ip_to_str(g_config.ip, ip_str);
config_ip_to_str(g_config.mask, mask_str);
config_ip_to_str(g_config.gw, gw_str);
config_ip_to_str(g_config.remote_ip, rip_str);
config_mac_to_str(g_config.mac, mac_str);
snprintf(response, max_len,
"=== TCP2UART Configuration ===\r\n"
"MAC: %s\r\n"
"DHCP: %s\r\n"
"IP: %s\r\n"
"MASK: %s\r\n"
"GW: %s\r\n"
"PORT: %u\r\n"
"RIP: %s\r\n"
"RPORT: %u\r\n"
"BAUD1: %lu\r\n"
"BAUD2: %lu\r\n"
"==============================\r\n",
mac_str,
g_config.dhcp_enable ? "Enabled" : "Disabled",
ip_str,
mask_str,
gw_str,
g_config.server_port,
rip_str,
g_config.remote_port,
g_config.uart2_baudrate,
g_config.uart3_baudrate
);
return AT_OK;
}
/*---------------------------------------------------------------------------
* Public Functions
*---------------------------------------------------------------------------*/
/**
* @brief Initialize configuration module
*/
int config_init(void)
{
/* Load configuration from Flash */
return config_load();
}
/**
* @brief Load configuration from Flash
*/
int config_load(void)
{
int ret;
ret = flash_param_read(&g_config, sizeof(device_config_t));
if (ret != 0 ||
g_config.magic != CONFIG_MAGIC ||
g_config.version != CONFIG_VERSION ||
g_config.crc != config_calc_crc(&g_config))
{
/* Invalid or corrupted configuration, use defaults */
config_set_defaults();
return -1;
}
return 0;
}
/**
* @brief Save configuration to Flash
*/
int config_save(void)
{
/* Update magic and CRC before saving */
g_config.magic = CONFIG_MAGIC;
g_config.version = CONFIG_VERSION;
g_config.crc = config_calc_crc(&g_config);
return flash_param_write(&g_config, sizeof(device_config_t));
}
/**
* @brief Reset configuration to factory defaults
*/
void config_set_defaults(void)
{
uint8_t default_ip[] = DEFAULT_IP;
uint8_t default_mask[] = DEFAULT_MASK;
uint8_t default_gw[] = DEFAULT_GW;
uint8_t default_mac[] = DEFAULT_MAC;
uint8_t default_rip[] = DEFAULT_REMOTE_IP;
memset(&g_config, 0, sizeof(device_config_t));
g_config.magic = CONFIG_MAGIC;
g_config.version = CONFIG_VERSION;
memcpy(g_config.mac, default_mac, 6);
g_config.dhcp_enable = DEFAULT_DHCP_ENABLE;
memcpy(g_config.ip, default_ip, 4);
memcpy(g_config.mask, default_mask, 4);
memcpy(g_config.gw, default_gw, 4);
g_config.server_port = DEFAULT_SERVER_PORT;
memcpy(g_config.remote_ip, default_rip, 4);
g_config.remote_port = DEFAULT_REMOTE_PORT;
g_config.reconnect_interval = DEFAULT_RECONNECT_MS;
g_config.uart2_baudrate = DEFAULT_UART_BAUDRATE;
g_config.uart3_baudrate = DEFAULT_UART_BAUDRATE;
g_config.uart2_databits = DEFAULT_UART_DATABITS;
g_config.uart2_stopbits = DEFAULT_UART_STOPBITS;
g_config.uart2_parity = DEFAULT_UART_PARITY;
g_config.uart3_databits = DEFAULT_UART_DATABITS;
g_config.uart3_stopbits = DEFAULT_UART_STOPBITS;
g_config.uart3_parity = DEFAULT_UART_PARITY;
g_config.crc = config_calc_crc(&g_config);
}
/**
* @brief Get current configuration (read-only)
*/
const device_config_t *config_get(void)
{
return &g_config;
}
/**
* @brief Get mutable configuration
*/
device_config_t *config_get_mutable(void)
{
return &g_config;
}
/**
* @brief Process AT command
*/
at_result_t config_process_at_cmd(const char *cmd, char *response, uint16_t max_len)
{
char cmd_copy[CONFIG_CMD_MAX_LEN];
char *cmd_name;
char *value;
at_result_t result = AT_UNKNOWN_CMD;
if (cmd == NULL || response == NULL || max_len == 0)
{
return AT_ERROR;
}
/* Make a copy for modification */
strncpy(cmd_copy, cmd, CONFIG_CMD_MAX_LEN - 1);
cmd_copy[CONFIG_CMD_MAX_LEN - 1] = '\0';
trim_trailing(cmd_copy);
/* Skip leading whitespace */
const char *p = skip_whitespace(cmd_copy);
/* Check for AT+ prefix */
if (!starts_with(p, "AT+"))
{
if (equals_ignore_case(p, "AT"))
{
snprintf(response, max_len, "OK\r\n");
return AT_OK;
}
snprintf(response, max_len, "ERROR: Unknown command\r\n");
return AT_UNKNOWN_CMD;
}
/* Move past AT+ */
p += 3;
/* Find '=' separator if present */
value = strchr((char *)p, '=');
if (value != NULL)
{
*value = '\0'; /* Terminate command part */
value++; /* Point to value */
value = (char *)skip_whitespace(value);
}
cmd_name = (char *)p;
trim_trailing(cmd_name);
/* Process commands */
if (equals_ignore_case(cmd_name, "IP") && value != NULL)
{
result = handle_ip(value, response, max_len);
}
else if (equals_ignore_case(cmd_name, "MASK") && value != NULL)
{
result = handle_mask(value, response, max_len);
}
else if (equals_ignore_case(cmd_name, "GW") && value != NULL)
{
result = handle_gw(value, response, max_len);
}
else if (equals_ignore_case(cmd_name, "PORT") && value != NULL)
{
result = handle_port(value, response, max_len);
}
else if (equals_ignore_case(cmd_name, "RIP") && value != NULL)
{
result = handle_rip(value, response, max_len);
}
else if (equals_ignore_case(cmd_name, "RPORT") && value != NULL)
{
result = handle_rport(value, response, max_len);
}
else if (equals_ignore_case(cmd_name, "BAUD1") && value != NULL)
{
result = handle_baud1(value, response, max_len);
}
else if (equals_ignore_case(cmd_name, "BAUD2") && value != NULL)
{
result = handle_baud2(value, response, max_len);
}
else if (equals_ignore_case(cmd_name, "MAC") && value != NULL)
{
result = handle_mac(value, response, max_len);
}
else if (equals_ignore_case(cmd_name, "DHCP") && value != NULL)
{
result = handle_dhcp(value, response, max_len);
}
else if (equals_ignore_case(cmd_name, "SAVE") && value == NULL)
{
result = handle_save(response, max_len);
}
else if (equals_ignore_case(cmd_name, "RESET") && value == NULL)
{
result = handle_reset(response, max_len);
}
else if (equals_ignore_case(cmd_name, "DEFAULT") && value == NULL)
{
result = handle_default(response, max_len);
}
else if ((equals_ignore_case(cmd_name, "?") || equals_ignore_case(cmd_name, "QUERY")) && value == NULL)
{
result = handle_query(response, max_len);
}
else
{
snprintf(response, max_len, "ERROR: Unknown command\r\n");
result = AT_UNKNOWN_CMD;
}
return result;
}
/**
* @brief Format IP address to string
*/
void config_ip_to_str(const uint8_t *ip, char *str)
{
sprintf(str, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
}
/**
* @brief Parse IP address from string
*/
int config_str_to_ip(const char *str, uint8_t *ip)
{
int a, b, c, d;
if (sscanf(str, "%d.%d.%d.%d", &a, &b, &c, &d) != 4)
{
return -1;
}
if (a < 0 || a > 255 || b < 0 || b > 255 ||
c < 0 || c > 255 || d < 0 || d > 255)
{
return -1;
}
ip[0] = (uint8_t)a;
ip[1] = (uint8_t)b;
ip[2] = (uint8_t)c;
ip[3] = (uint8_t)d;
return 0;
}
/**
* @brief Format MAC address to string
*/
void config_mac_to_str(const uint8_t *mac, char *str)
{
sprintf(str, "%02X:%02X:%02X:%02X:%02X:%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
/**
* @brief Parse MAC address from string
*/
int config_str_to_mac(const char *str, uint8_t *mac)
{
int a[6];
if (sscanf(str, "%x:%x:%x:%x:%x:%x",
&a[0], &a[1], &a[2], &a[3], &a[4], &a[5]) != 6)
{
/* Try alternate format with dashes */
if (sscanf(str, "%x-%x-%x-%x-%x-%x",
&a[0], &a[1], &a[2], &a[3], &a[4], &a[5]) != 6)
{
return -1;
}
}
for (int i = 0; i < 6; i++)
{
if (a[i] < 0 || a[i] > 255)
{
return -1;
}
mac[i] = (uint8_t)a[i];
}
return 0;
}
/**
* @brief UART1 IDLE interrupt handler
*/
void config_uart_idle_handler(void)
{
uint16_t dma_counter = __HAL_DMA_GET_COUNTER(huart1.hdmarx);
uint16_t len = CONFIG_RX_BUFFER_SIZE - dma_counter;
if (len > 0)
{
g_rx_index = len;
g_rx_complete = true;
}
/* Stop DMA and restart */
HAL_UART_DMAStop(&huart1);
HAL_UART_Receive_DMA(&huart1, g_rx_buffer, CONFIG_RX_BUFFER_SIZE);
}
/**
* @brief Start UART1 reception
*/
void config_start_reception(void)
{
/* Enable IDLE interrupt */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
/* Start DMA reception */
HAL_UART_Receive_DMA(&huart1, g_rx_buffer, CONFIG_RX_BUFFER_SIZE);
}
/**
* @brief Configuration task
*/
void config_task(void *argument)
{
char response[CONFIG_TX_BUFFER_SIZE];
char cmd_buffer[CONFIG_CMD_MAX_LEN];
at_result_t result;
(void)argument;
/* Initialize configuration */
config_init();
/* Start UART1 reception */
config_start_reception();
/* Send startup message */
snprintf(response, sizeof(response),
"\r\n=== TCP2UART v1.0 ===\r\n"
"Type AT+? for configuration\r\n\r\n");
HAL_UART_Transmit(&huart1, (uint8_t *)response, strlen(response), 1000);
while (1)
{
/* Wait for command */
if (g_rx_complete)
{
/* Copy command and null-terminate */
uint16_t len = g_rx_index;
if (len >= CONFIG_CMD_MAX_LEN)
{
len = CONFIG_CMD_MAX_LEN - 1;
}
memcpy(cmd_buffer, g_rx_buffer, len);
cmd_buffer[len] = '\0';
/* Reset reception state */
g_rx_complete = false;
g_rx_index = 0;
/* Process command */
result = config_process_at_cmd(cmd_buffer, response, sizeof(response));
/* Send response */
HAL_UART_Transmit(&huart1, (uint8_t *)response, strlen(response), 1000);
if (g_reset_requested)
{
g_reset_requested = false;
vTaskDelay(pdMS_TO_TICKS(100));
NVIC_SystemReset();
}
/* Handle reboot needed */
if (result == AT_NEED_REBOOT)
{
HAL_UART_Transmit(&huart1,
(uint8_t *)"Note: Use AT+SAVE then AT+RESET to apply changes\r\n",
51, 1000);
}
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}