refactor: 完成R8裸机lwIP移植并更新文档
This commit is contained in:
+231
-573
@@ -1,113 +1,60 @@
|
||||
/**
|
||||
* @file config.c
|
||||
* @brief AT command configuration module implementation
|
||||
* @brief Bare-metal 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 <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Private Definitions
|
||||
*---------------------------------------------------------------------------*/
|
||||
#define CONFIG_RX_BUFFER_SIZE 128u
|
||||
#define CONFIG_TX_BUFFER_SIZE 256u
|
||||
#define CONFIG_CMD_MAX_LEN 128u
|
||||
|
||||
#define CONFIG_RX_BUFFER_SIZE 128
|
||||
#define CONFIG_TX_BUFFER_SIZE 256
|
||||
#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 uint8_t g_rx_buffer[CONFIG_RX_BUFFER_SIZE];
|
||||
static volatile uint16_t g_rx_index;
|
||||
static volatile bool g_rx_complete;
|
||||
static volatile bool g_reset_requested;
|
||||
|
||||
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++;
|
||||
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'))
|
||||
{
|
||||
int len = (int)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')
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (c1 != c2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -115,348 +62,93 @@ static bool equals_ignore_case(const char *a, const char *b)
|
||||
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];
|
||||
|
||||
char ip_str[16];
|
||||
char mask_str[16];
|
||||
char gw_str[16];
|
||||
char rip_str[16];
|
||||
char 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
|
||||
);
|
||||
|
||||
"MAC: %s\r\n"
|
||||
"DHCP: %u\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",
|
||||
mac_str,
|
||||
g_config.dhcp_enable,
|
||||
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 */
|
||||
flash_param_init();
|
||||
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 ||
|
||||
if (flash_param_read(&g_config, sizeof(g_config)) != 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 */
|
||||
g_config.crc != config_calc_crc(&g_config)) {
|
||||
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));
|
||||
return flash_param_write(&g_config, sizeof(g_config));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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));
|
||||
|
||||
const uint8_t default_ip[] = DEFAULT_IP;
|
||||
const uint8_t default_mask[] = DEFAULT_MASK;
|
||||
const uint8_t default_gw[] = DEFAULT_GW;
|
||||
const uint8_t default_mac[] = DEFAULT_MAC;
|
||||
const uint8_t default_rip[] = DEFAULT_REMOTE_IP;
|
||||
|
||||
memset(&g_config, 0, sizeof(g_config));
|
||||
g_config.magic = CONFIG_MAGIC;
|
||||
g_config.version = CONFIG_VERSION;
|
||||
|
||||
memcpy(g_config.mac, default_mac, 6);
|
||||
memcpy(g_config.mac, default_mac, sizeof(g_config.mac));
|
||||
memcpy(g_config.ip, default_ip, sizeof(g_config.ip));
|
||||
memcpy(g_config.mask, default_mask, sizeof(g_config.mask));
|
||||
memcpy(g_config.gw, default_gw, sizeof(g_config.gw));
|
||||
memcpy(g_config.remote_ip, default_rip, sizeof(g_config.remote_ip));
|
||||
|
||||
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;
|
||||
@@ -468,305 +160,271 @@ void config_set_defaults(void)
|
||||
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;
|
||||
const char *p;
|
||||
|
||||
if (cmd == NULL || response == NULL || max_len == 0)
|
||||
{
|
||||
if (cmd == NULL || response == NULL || max_len == 0u) {
|
||||
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';
|
||||
|
||||
strncpy(cmd_copy, cmd, CONFIG_CMD_MAX_LEN - 1u);
|
||||
cmd_copy[CONFIG_CMD_MAX_LEN - 1u] = '\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;
|
||||
}
|
||||
p = skip_whitespace(cmd_copy);
|
||||
|
||||
if ((p[0] != 'A' && p[0] != 'a') || (p[1] != 'T' && p[1] != 't')) {
|
||||
snprintf(response, max_len, "ERROR: Unknown command\r\n");
|
||||
return AT_UNKNOWN_CMD;
|
||||
}
|
||||
|
||||
/* Move past AT+ */
|
||||
|
||||
if (p[2] == '\0') {
|
||||
snprintf(response, max_len, "OK\r\n");
|
||||
return AT_OK;
|
||||
}
|
||||
|
||||
if (p[2] != '+') {
|
||||
snprintf(response, max_len, "ERROR: Unknown command\r\n");
|
||||
return AT_UNKNOWN_CMD;
|
||||
}
|
||||
|
||||
p += 3;
|
||||
|
||||
/* Find '=' separator if present */
|
||||
value = strchr((char *)p, '=');
|
||||
if (value != NULL)
|
||||
{
|
||||
*value = '\0'; /* Terminate command part */
|
||||
value++; /* Point to value */
|
||||
if (value != NULL) {
|
||||
*value = '\0';
|
||||
++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);
|
||||
|
||||
if ((equals_ignore_case(cmd_name, "?") || equals_ignore_case(cmd_name, "QUERY")) && value == NULL) {
|
||||
return handle_query(response, max_len);
|
||||
}
|
||||
else if (equals_ignore_case(cmd_name, "MASK") && value != NULL)
|
||||
{
|
||||
result = handle_mask(value, response, max_len);
|
||||
if (equals_ignore_case(cmd_name, "SAVE") && value == NULL) {
|
||||
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;
|
||||
}
|
||||
else if (equals_ignore_case(cmd_name, "GW") && value != NULL)
|
||||
{
|
||||
result = handle_gw(value, response, max_len);
|
||||
if (equals_ignore_case(cmd_name, "RESET") && value == NULL) {
|
||||
g_reset_requested = true;
|
||||
snprintf(response, max_len, "OK: Resetting...\r\n");
|
||||
return AT_OK;
|
||||
}
|
||||
else if (equals_ignore_case(cmd_name, "PORT") && value != NULL)
|
||||
{
|
||||
result = handle_port(value, response, max_len);
|
||||
if (equals_ignore_case(cmd_name, "DEFAULT") && value == NULL) {
|
||||
config_set_defaults();
|
||||
snprintf(response, max_len, "OK: Defaults restored\r\n");
|
||||
return AT_OK;
|
||||
}
|
||||
else if (equals_ignore_case(cmd_name, "RIP") && value != NULL)
|
||||
{
|
||||
result = handle_rip(value, response, max_len);
|
||||
if (equals_ignore_case(cmd_name, "IP") && value != NULL) {
|
||||
if (config_str_to_ip(value, g_config.ip) != 0) {
|
||||
snprintf(response, max_len, "ERROR: Invalid IP format\r\n");
|
||||
return AT_INVALID_PARAM;
|
||||
}
|
||||
snprintf(response, max_len, "OK\r\n");
|
||||
return AT_NEED_REBOOT;
|
||||
}
|
||||
else if (equals_ignore_case(cmd_name, "RPORT") && value != NULL)
|
||||
{
|
||||
result = handle_rport(value, response, max_len);
|
||||
if (equals_ignore_case(cmd_name, "MASK") && value != NULL) {
|
||||
if (config_str_to_ip(value, g_config.mask) != 0) {
|
||||
snprintf(response, max_len, "ERROR: Invalid mask format\r\n");
|
||||
return AT_INVALID_PARAM;
|
||||
}
|
||||
snprintf(response, max_len, "OK\r\n");
|
||||
return AT_NEED_REBOOT;
|
||||
}
|
||||
else if (equals_ignore_case(cmd_name, "BAUD1") && value != NULL)
|
||||
{
|
||||
result = handle_baud1(value, response, max_len);
|
||||
if (equals_ignore_case(cmd_name, "GW") && value != NULL) {
|
||||
if (config_str_to_ip(value, g_config.gw) != 0) {
|
||||
snprintf(response, max_len, "ERROR: Invalid gateway format\r\n");
|
||||
return AT_INVALID_PARAM;
|
||||
}
|
||||
snprintf(response, max_len, "OK\r\n");
|
||||
return AT_NEED_REBOOT;
|
||||
}
|
||||
else if (equals_ignore_case(cmd_name, "BAUD2") && value != NULL)
|
||||
{
|
||||
result = handle_baud2(value, response, max_len);
|
||||
if (equals_ignore_case(cmd_name, "RIP") && value != NULL) {
|
||||
if (config_str_to_ip(value, g_config.remote_ip) != 0) {
|
||||
snprintf(response, max_len, "ERROR: Invalid remote IP format\r\n");
|
||||
return AT_INVALID_PARAM;
|
||||
}
|
||||
snprintf(response, max_len, "OK\r\n");
|
||||
return AT_NEED_REBOOT;
|
||||
}
|
||||
else if (equals_ignore_case(cmd_name, "MAC") && value != NULL)
|
||||
{
|
||||
result = handle_mac(value, response, max_len);
|
||||
if (equals_ignore_case(cmd_name, "MAC") && value != NULL) {
|
||||
if (config_str_to_mac(value, g_config.mac) != 0) {
|
||||
snprintf(response, max_len, "ERROR: Invalid MAC format\r\n");
|
||||
return AT_INVALID_PARAM;
|
||||
}
|
||||
snprintf(response, max_len, "OK\r\n");
|
||||
return AT_NEED_REBOOT;
|
||||
}
|
||||
else if (equals_ignore_case(cmd_name, "DHCP") && value != NULL)
|
||||
{
|
||||
result = handle_dhcp(value, response, max_len);
|
||||
if (equals_ignore_case(cmd_name, "PORT") && value != NULL) {
|
||||
int port = atoi(value);
|
||||
if (port < 1 || port > 65535) {
|
||||
snprintf(response, max_len, "ERROR: Invalid port\r\n");
|
||||
return AT_INVALID_PARAM;
|
||||
}
|
||||
g_config.server_port = (uint16_t)port;
|
||||
snprintf(response, max_len, "OK\r\n");
|
||||
return AT_NEED_REBOOT;
|
||||
}
|
||||
else if (equals_ignore_case(cmd_name, "SAVE") && value == NULL)
|
||||
{
|
||||
result = handle_save(response, max_len);
|
||||
if (equals_ignore_case(cmd_name, "RPORT") && value != NULL) {
|
||||
int port = atoi(value);
|
||||
if (port < 1 || port > 65535) {
|
||||
snprintf(response, max_len, "ERROR: Invalid port\r\n");
|
||||
return AT_INVALID_PARAM;
|
||||
}
|
||||
g_config.remote_port = (uint16_t)port;
|
||||
snprintf(response, max_len, "OK\r\n");
|
||||
return AT_NEED_REBOOT;
|
||||
}
|
||||
else if (equals_ignore_case(cmd_name, "RESET") && value == NULL)
|
||||
{
|
||||
result = handle_reset(response, max_len);
|
||||
if (equals_ignore_case(cmd_name, "BAUD1") && value != NULL) {
|
||||
g_config.uart2_baudrate = (uint32_t)atoi(value);
|
||||
snprintf(response, max_len, "OK\r\n");
|
||||
return AT_NEED_REBOOT;
|
||||
}
|
||||
else if (equals_ignore_case(cmd_name, "DEFAULT") && value == NULL)
|
||||
{
|
||||
result = handle_default(response, max_len);
|
||||
if (equals_ignore_case(cmd_name, "BAUD2") && value != NULL) {
|
||||
g_config.uart3_baudrate = (uint32_t)atoi(value);
|
||||
snprintf(response, max_len, "OK\r\n");
|
||||
return AT_NEED_REBOOT;
|
||||
}
|
||||
else if ((equals_ignore_case(cmd_name, "?") || equals_ignore_case(cmd_name, "QUERY")) && value == NULL)
|
||||
{
|
||||
result = handle_query(response, max_len);
|
||||
if (equals_ignore_case(cmd_name, "DHCP") && value != NULL) {
|
||||
int dhcp = atoi(value);
|
||||
if (dhcp != 0 && dhcp != 1) {
|
||||
snprintf(response, max_len, "ERROR: Invalid value\r\n");
|
||||
return AT_INVALID_PARAM;
|
||||
}
|
||||
if (dhcp != 0) {
|
||||
snprintf(response, max_len, "ERROR: DHCP disabled in this build\r\n");
|
||||
return AT_INVALID_PARAM;
|
||||
}
|
||||
g_config.dhcp_enable = (uint8_t)dhcp;
|
||||
snprintf(response, max_len, "OK\r\n");
|
||||
return AT_NEED_REBOOT;
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(response, max_len, "ERROR: Unknown command\r\n");
|
||||
result = AT_UNKNOWN_CMD;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
snprintf(response, max_len, "ERROR: Unknown command\r\n");
|
||||
return AT_UNKNOWN_CMD;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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]);
|
||||
sprintf(str, "%u.%u.%u.%u", 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)
|
||||
{
|
||||
|
||||
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)
|
||||
{
|
||||
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]);
|
||||
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;
|
||||
}
|
||||
|
||||
if (sscanf(str, "%x:%x:%x:%x:%x:%x", &a[0], &a[1], &a[2], &a[3], &a[4], &a[5]) != 6 &&
|
||||
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)
|
||||
{
|
||||
|
||||
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)
|
||||
{
|
||||
uint16_t len = (uint16_t)(CONFIG_RX_BUFFER_SIZE - dma_counter);
|
||||
|
||||
if (len > 0u) {
|
||||
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)
|
||||
void config_poll(void)
|
||||
{
|
||||
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);
|
||||
uint16_t len;
|
||||
|
||||
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));
|
||||
if (!g_rx_complete) {
|
||||
return;
|
||||
}
|
||||
|
||||
len = g_rx_index;
|
||||
if (len >= CONFIG_CMD_MAX_LEN) {
|
||||
len = CONFIG_CMD_MAX_LEN - 1u;
|
||||
}
|
||||
|
||||
memcpy(cmd_buffer, g_rx_buffer, len);
|
||||
cmd_buffer[len] = '\0';
|
||||
g_rx_complete = false;
|
||||
g_rx_index = 0u;
|
||||
|
||||
result = config_process_at_cmd(cmd_buffer, response, sizeof(response));
|
||||
HAL_UART_Transmit(&huart1, (uint8_t *)response, (uint16_t)strlen(response), 1000u);
|
||||
|
||||
if (result == AT_NEED_REBOOT) {
|
||||
static const char hint[] = "Note: Use AT+SAVE then AT+RESET to apply changes\r\n";
|
||||
HAL_UART_Transmit(&huart1, (uint8_t *)hint, sizeof(hint) - 1u, 1000u);
|
||||
}
|
||||
}
|
||||
|
||||
bool config_is_reset_requested(void)
|
||||
{
|
||||
return g_reset_requested;
|
||||
}
|
||||
|
||||
void config_clear_reset_requested(void)
|
||||
{
|
||||
g_reset_requested = false;
|
||||
}
|
||||
|
||||
+15
-7
@@ -144,13 +144,6 @@ device_config_t *config_get_mutable(void);
|
||||
*/
|
||||
at_result_t config_process_at_cmd(const char *cmd, char *response, uint16_t max_len);
|
||||
|
||||
/**
|
||||
* @brief Configuration task (for FreeRTOS)
|
||||
* Handles UART1 reception and AT command processing
|
||||
* @param argument Task argument (unused)
|
||||
*/
|
||||
void config_task(void *argument);
|
||||
|
||||
/**
|
||||
* @brief UART1 IDLE interrupt handler for config module
|
||||
*/
|
||||
@@ -161,6 +154,21 @@ void config_uart_idle_handler(void);
|
||||
*/
|
||||
void config_start_reception(void);
|
||||
|
||||
/**
|
||||
* @brief Poll configuration UART and process pending AT commands
|
||||
*/
|
||||
void config_poll(void);
|
||||
|
||||
/**
|
||||
* @brief Check whether AT+RESET requested a system reset
|
||||
*/
|
||||
bool config_is_reset_requested(void);
|
||||
|
||||
/**
|
||||
* @brief Clear the pending reset request flag
|
||||
*/
|
||||
void config_clear_reset_requested(void);
|
||||
|
||||
/**
|
||||
* @brief Format IP address to string
|
||||
* @param ip IP address bytes
|
||||
|
||||
+190
-345
@@ -1,431 +1,276 @@
|
||||
/**
|
||||
* @file tcp_client.c
|
||||
* @brief TCP Client module implementation for transparent transmission with UART3
|
||||
* @brief lwIP RAW TCP client for the UART3 bridge.
|
||||
*/
|
||||
|
||||
#include "tcp_client.h"
|
||||
#include "config.h"
|
||||
#include "lwip/opt.h"
|
||||
#include "lwip/tcp.h"
|
||||
#include "lwip/sys.h"
|
||||
#include "lwip/sockets.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "stream_buffer.h"
|
||||
#include "main.h"
|
||||
|
||||
#include "lwip/ip_addr.h"
|
||||
#include "lwip/pbuf.h"
|
||||
#include "lwip/tcp.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Private Variables
|
||||
*---------------------------------------------------------------------------*/
|
||||
typedef struct {
|
||||
struct tcp_pcb *pcb;
|
||||
uint8_t rx_ring[TCP_CLIENT_RX_BUFFER_SIZE];
|
||||
uint16_t rx_head;
|
||||
uint16_t rx_tail;
|
||||
uint32_t next_retry_ms;
|
||||
tcp_client_config_t config;
|
||||
tcp_client_status_t status;
|
||||
} tcp_client_ctx_t;
|
||||
|
||||
/* Client configuration */
|
||||
static tcp_client_config_t client_config = {
|
||||
.server_ip = {192, 168, 1, 100},
|
||||
.server_port = TCP_CLIENT_DEFAULT_PORT,
|
||||
.auto_reconnect = true,
|
||||
.reconnect_interval_ms = TCP_CLIENT_RECONNECT_DELAY_MS
|
||||
};
|
||||
static tcp_client_ctx_t g_client;
|
||||
|
||||
/* Client status */
|
||||
static tcp_client_status_t client_status = {
|
||||
.state = TCP_CLIENT_STATE_IDLE,
|
||||
.rx_bytes = 0,
|
||||
.tx_bytes = 0,
|
||||
.reconnect_count = 0,
|
||||
.errors = 0
|
||||
};
|
||||
|
||||
/* Socket descriptor */
|
||||
static int client_socket = -1;
|
||||
|
||||
/* Stream buffers for UART integration */
|
||||
static StreamBufferHandle_t rx_stream = NULL; /* TCP RX -> UART TX */
|
||||
static StreamBufferHandle_t tx_stream = NULL; /* UART RX -> TCP TX */
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Private Functions
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
static int tcp_client_send_all(int sock, const uint8_t *data, uint16_t len)
|
||||
static uint16_t ring_free(uint16_t head, uint16_t tail, uint16_t size)
|
||||
{
|
||||
uint16_t total = 0;
|
||||
return (head >= tail) ? (uint16_t)(size - head + tail - 1u) : (uint16_t)(tail - head - 1u);
|
||||
}
|
||||
|
||||
while (total < len)
|
||||
{
|
||||
int sent = send(sock, data + total, len - total, 0);
|
||||
if (sent > 0)
|
||||
{
|
||||
total += (uint16_t)sent;
|
||||
static err_t tcp_client_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
|
||||
{
|
||||
tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg;
|
||||
struct pbuf *q;
|
||||
|
||||
if (err != ERR_OK) {
|
||||
if (p != NULL) {
|
||||
pbuf_free(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
return err;
|
||||
}
|
||||
|
||||
if (p == NULL) {
|
||||
ctx->pcb = NULL;
|
||||
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
for (q = p; q != NULL; q = q->next) {
|
||||
const uint8_t *src = (const uint8_t *)q->payload;
|
||||
for (uint16_t i = 0; i < q->len; ++i) {
|
||||
if (ring_free(ctx->rx_head, ctx->rx_tail, TCP_CLIENT_RX_BUFFER_SIZE) == 0u) {
|
||||
ctx->status.errors++;
|
||||
break;
|
||||
}
|
||||
ctx->rx_ring[ctx->rx_head] = src[i];
|
||||
ctx->rx_head = (uint16_t)((ctx->rx_head + 1u) % TCP_CLIENT_RX_BUFFER_SIZE);
|
||||
ctx->status.rx_bytes++;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)total;
|
||||
tcp_recved(pcb, p->tot_len);
|
||||
pbuf_free(p);
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal connect function
|
||||
*/
|
||||
static int tcp_client_do_connect(void)
|
||||
static err_t tcp_client_on_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
|
||||
{
|
||||
struct sockaddr_in server_addr;
|
||||
int ret;
|
||||
|
||||
if (client_socket >= 0)
|
||||
{
|
||||
/* Already connected */
|
||||
return 0;
|
||||
}
|
||||
|
||||
client_status.state = TCP_CLIENT_STATE_CONNECTING;
|
||||
|
||||
/* Create socket */
|
||||
client_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (client_socket < 0)
|
||||
{
|
||||
client_status.state = TCP_CLIENT_STATE_ERROR;
|
||||
client_status.errors++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Prepare server address */
|
||||
memset(&server_addr, 0, sizeof(server_addr));
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_port = htons(client_config.server_port);
|
||||
server_addr.sin_addr.s_addr = ((uint32_t)client_config.server_ip[0]) |
|
||||
((uint32_t)client_config.server_ip[1] << 8) |
|
||||
((uint32_t)client_config.server_ip[2] << 16) |
|
||||
((uint32_t)client_config.server_ip[3] << 24);
|
||||
|
||||
/* Connect to server */
|
||||
ret = connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
|
||||
if (ret < 0)
|
||||
{
|
||||
close(client_socket);
|
||||
client_socket = -1;
|
||||
client_status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||
client_status.errors++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
client_status.state = TCP_CLIENT_STATE_CONNECTED;
|
||||
|
||||
return 0;
|
||||
tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg;
|
||||
(void)pcb;
|
||||
ctx->status.tx_bytes += len;
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Public Functions
|
||||
*---------------------------------------------------------------------------*/
|
||||
static void tcp_client_on_err(void *arg, err_t err)
|
||||
{
|
||||
tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg;
|
||||
(void)err;
|
||||
ctx->pcb = NULL;
|
||||
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||
ctx->status.errors++;
|
||||
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
||||
}
|
||||
|
||||
static err_t tcp_client_on_connected(void *arg, struct tcp_pcb *pcb, err_t err)
|
||||
{
|
||||
tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg;
|
||||
|
||||
if (err != ERR_OK) {
|
||||
ctx->pcb = NULL;
|
||||
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||
ctx->status.errors++;
|
||||
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
||||
return err;
|
||||
}
|
||||
|
||||
ctx->pcb = pcb;
|
||||
ctx->status.state = TCP_CLIENT_STATE_CONNECTED;
|
||||
tcp_arg(pcb, ctx);
|
||||
tcp_recv(pcb, tcp_client_on_recv);
|
||||
tcp_sent(pcb, tcp_client_on_sent);
|
||||
tcp_err(pcb, tcp_client_on_err);
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize TCP Client module
|
||||
*/
|
||||
int tcp_client_init(const tcp_client_config_t *config)
|
||||
{
|
||||
if (config != NULL)
|
||||
{
|
||||
memcpy(&client_config, config, sizeof(tcp_client_config_t));
|
||||
memset(&g_client, 0, sizeof(g_client));
|
||||
g_client.config.server_ip[0] = 192u;
|
||||
g_client.config.server_ip[1] = 168u;
|
||||
g_client.config.server_ip[2] = 1u;
|
||||
g_client.config.server_ip[3] = 100u;
|
||||
g_client.config.server_port = TCP_CLIENT_DEFAULT_PORT;
|
||||
g_client.config.auto_reconnect = true;
|
||||
g_client.config.reconnect_interval_ms = TCP_CLIENT_RECONNECT_DELAY_MS;
|
||||
g_client.status.state = TCP_CLIENT_STATE_IDLE;
|
||||
|
||||
if (config != NULL) {
|
||||
g_client.config = *config;
|
||||
}
|
||||
|
||||
/* Create stream buffers */
|
||||
if (rx_stream == NULL)
|
||||
{
|
||||
rx_stream = xStreamBufferCreate(TCP_CLIENT_RX_BUFFER_SIZE, 1);
|
||||
if (rx_stream == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (tx_stream == NULL)
|
||||
{
|
||||
tx_stream = xStreamBufferCreate(TCP_CLIENT_TX_BUFFER_SIZE, 1);
|
||||
if (tx_stream == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
client_status.state = TCP_CLIENT_STATE_IDLE;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connect to remote server
|
||||
*/
|
||||
int tcp_client_connect(void)
|
||||
{
|
||||
return tcp_client_do_connect();
|
||||
struct tcp_pcb *pcb;
|
||||
ip_addr_t remote_addr;
|
||||
err_t err;
|
||||
|
||||
if (g_client.pcb != NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pcb = tcp_new_ip_type(IPADDR_TYPE_V4);
|
||||
if (pcb == NULL) {
|
||||
g_client.status.errors++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
IP_ADDR4(&remote_addr,
|
||||
g_client.config.server_ip[0],
|
||||
g_client.config.server_ip[1],
|
||||
g_client.config.server_ip[2],
|
||||
g_client.config.server_ip[3]);
|
||||
|
||||
g_client.status.state = TCP_CLIENT_STATE_CONNECTING;
|
||||
tcp_arg(pcb, &g_client);
|
||||
err = tcp_connect(pcb, &remote_addr, g_client.config.server_port, tcp_client_on_connected);
|
||||
if (err != ERR_OK) {
|
||||
tcp_abort(pcb);
|
||||
g_client.status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||
g_client.status.errors++;
|
||||
g_client.next_retry_ms = HAL_GetTick() + g_client.config.reconnect_interval_ms;
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_client.pcb = pcb;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnect from server
|
||||
*/
|
||||
int tcp_client_disconnect(void)
|
||||
{
|
||||
if (client_socket >= 0)
|
||||
{
|
||||
close(client_socket);
|
||||
client_socket = -1;
|
||||
if (g_client.pcb != NULL) {
|
||||
tcp_arg(g_client.pcb, NULL);
|
||||
tcp_recv(g_client.pcb, NULL);
|
||||
tcp_sent(g_client.pcb, NULL);
|
||||
tcp_err(g_client.pcb, NULL);
|
||||
tcp_abort(g_client.pcb);
|
||||
g_client.pcb = NULL;
|
||||
}
|
||||
|
||||
client_status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||
|
||||
|
||||
g_client.status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send data to server
|
||||
*/
|
||||
int tcp_client_send(const uint8_t *data, uint16_t len)
|
||||
{
|
||||
int sent;
|
||||
|
||||
if (client_socket < 0)
|
||||
{
|
||||
err_t err;
|
||||
|
||||
if (g_client.pcb == NULL || data == NULL || len == 0u) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sent = tcp_client_send_all(client_socket, data, len);
|
||||
if (sent > 0)
|
||||
{
|
||||
client_status.tx_bytes += sent;
|
||||
|
||||
if (tcp_sndbuf(g_client.pcb) < len) {
|
||||
return 0;
|
||||
}
|
||||
else if (sent < 0)
|
||||
{
|
||||
/* Connection error */
|
||||
close(client_socket);
|
||||
client_socket = -1;
|
||||
client_status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||
client_status.errors++;
|
||||
|
||||
err = tcp_write(g_client.pcb, data, len, TCP_WRITE_FLAG_COPY);
|
||||
if (err != ERR_OK) {
|
||||
g_client.status.errors++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return sent;
|
||||
|
||||
err = tcp_output(g_client.pcb);
|
||||
if (err != ERR_OK) {
|
||||
g_client.status.errors++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (int)len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Receive data from server
|
||||
*/
|
||||
int tcp_client_recv(uint8_t *data, uint16_t max_len, uint32_t timeout_ms)
|
||||
{
|
||||
int received;
|
||||
struct timeval tv;
|
||||
|
||||
if (client_socket < 0)
|
||||
{
|
||||
uint16_t copied = 0u;
|
||||
(void)timeout_ms;
|
||||
|
||||
if (data == NULL || max_len == 0u) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set receive timeout */
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
received = recv(client_socket, data, max_len, 0);
|
||||
if (received > 0)
|
||||
{
|
||||
client_status.rx_bytes += received;
|
||||
|
||||
while (copied < max_len && g_client.rx_tail != g_client.rx_head) {
|
||||
data[copied++] = g_client.rx_ring[g_client.rx_tail];
|
||||
g_client.rx_tail = (uint16_t)((g_client.rx_tail + 1u) % TCP_CLIENT_RX_BUFFER_SIZE);
|
||||
}
|
||||
else if (received == 0)
|
||||
{
|
||||
/* Connection closed by server */
|
||||
close(client_socket);
|
||||
client_socket = -1;
|
||||
client_status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
return received;
|
||||
|
||||
return (int)copied;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if connected to server
|
||||
*/
|
||||
bool tcp_client_is_connected(void)
|
||||
{
|
||||
return (client_socket >= 0);
|
||||
return (g_client.pcb != NULL) && (g_client.status.state == TCP_CLIENT_STATE_CONNECTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update server configuration
|
||||
*/
|
||||
int tcp_client_set_server(const uint8_t *ip, uint16_t port)
|
||||
{
|
||||
if (ip == NULL)
|
||||
{
|
||||
if (ip == NULL || port == 0u) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Disconnect if connected */
|
||||
if (client_socket >= 0)
|
||||
{
|
||||
tcp_client_disconnect();
|
||||
}
|
||||
|
||||
memcpy(client_config.server_ip, ip, 4);
|
||||
client_config.server_port = port;
|
||||
|
||||
|
||||
memcpy(g_client.config.server_ip, ip, 4u);
|
||||
g_client.config.server_port = port;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get TCP Client status
|
||||
*/
|
||||
void tcp_client_get_status(tcp_client_status_t *status)
|
||||
{
|
||||
if (status != NULL)
|
||||
{
|
||||
memcpy(status, &client_status, sizeof(tcp_client_status_t));
|
||||
if (status != NULL) {
|
||||
*status = g_client.status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get RX StreamBuffer handle
|
||||
*/
|
||||
void *tcp_client_get_rx_stream(void)
|
||||
{
|
||||
return rx_stream;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get TX StreamBuffer handle
|
||||
*/
|
||||
void *tcp_client_get_tx_stream(void)
|
||||
{
|
||||
return tx_stream;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief TCP Client task
|
||||
*/
|
||||
void tcp_client_task(void *argument)
|
||||
{
|
||||
const device_config_t *cfg;
|
||||
tcp_client_config_t task_cfg;
|
||||
uint8_t rx_buffer[256];
|
||||
uint8_t tx_buffer[256];
|
||||
int received;
|
||||
size_t tx_len;
|
||||
fd_set read_fds;
|
||||
struct timeval tv;
|
||||
uint32_t reconnect_timer = 0;
|
||||
|
||||
(void)argument;
|
||||
|
||||
/* Initialize client */
|
||||
task_cfg.server_ip[0] = 192;
|
||||
task_cfg.server_ip[1] = 168;
|
||||
task_cfg.server_ip[2] = 1;
|
||||
task_cfg.server_ip[3] = 100;
|
||||
task_cfg.server_port = TCP_CLIENT_DEFAULT_PORT;
|
||||
task_cfg.auto_reconnect = true;
|
||||
task_cfg.reconnect_interval_ms = TCP_CLIENT_RECONNECT_DELAY_MS;
|
||||
}
|
||||
|
||||
cfg = config_get();
|
||||
if (cfg != NULL)
|
||||
{
|
||||
memcpy(task_cfg.server_ip, cfg->remote_ip, sizeof(task_cfg.server_ip));
|
||||
if (cfg->remote_port > 0)
|
||||
{
|
||||
task_cfg.server_port = cfg->remote_port;
|
||||
}
|
||||
if (cfg->reconnect_interval > 0)
|
||||
{
|
||||
task_cfg.reconnect_interval_ms = cfg->reconnect_interval;
|
||||
}
|
||||
void tcp_client_poll(void)
|
||||
{
|
||||
uint32_t now;
|
||||
|
||||
if (!g_client.config.auto_reconnect || g_client.pcb != NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
tcp_client_init(&task_cfg);
|
||||
|
||||
while (1)
|
||||
{
|
||||
/* Handle connection state */
|
||||
if (client_socket < 0)
|
||||
{
|
||||
/* Not connected - try to reconnect */
|
||||
if (client_config.auto_reconnect)
|
||||
{
|
||||
if (xTaskGetTickCount() - reconnect_timer >= pdMS_TO_TICKS(client_config.reconnect_interval_ms))
|
||||
{
|
||||
if (tcp_client_do_connect() == 0)
|
||||
{
|
||||
/* Connected successfully */
|
||||
client_status.reconnect_count++;
|
||||
}
|
||||
reconnect_timer = xTaskGetTickCount();
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait before retry */
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Handle data transfer if connected */
|
||||
|
||||
/* Check for data from TCP server */
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(client_socket, &read_fds);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 10000; /* 10ms timeout */
|
||||
|
||||
if (select(client_socket + 1, &read_fds, NULL, NULL, &tv) > 0)
|
||||
{
|
||||
if (FD_ISSET(client_socket, &read_fds))
|
||||
{
|
||||
received = recv(client_socket, rx_buffer, sizeof(rx_buffer), 0);
|
||||
if (received > 0)
|
||||
{
|
||||
/* Forward to UART via stream buffer */
|
||||
xStreamBufferSend(rx_stream, rx_buffer, received, pdMS_TO_TICKS(10));
|
||||
client_status.rx_bytes += received;
|
||||
}
|
||||
else if (received == 0)
|
||||
{
|
||||
/* Connection closed */
|
||||
close(client_socket);
|
||||
client_socket = -1;
|
||||
client_status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||
reconnect_timer = xTaskGetTickCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Error */
|
||||
close(client_socket);
|
||||
client_socket = -1;
|
||||
client_status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||
client_status.errors++;
|
||||
reconnect_timer = xTaskGetTickCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for data from UART to send to TCP */
|
||||
tx_len = xStreamBufferReceive(tx_stream, tx_buffer, sizeof(tx_buffer), 0);
|
||||
if (tx_len > 0)
|
||||
{
|
||||
int sent = tcp_client_send_all(client_socket, tx_buffer, (uint16_t)tx_len);
|
||||
if (sent > 0)
|
||||
{
|
||||
client_status.tx_bytes += sent;
|
||||
}
|
||||
else if (sent < 0)
|
||||
{
|
||||
/* Send error */
|
||||
close(client_socket);
|
||||
client_socket = -1;
|
||||
client_status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||
client_status.errors++;
|
||||
reconnect_timer = xTaskGetTickCount();
|
||||
}
|
||||
}
|
||||
|
||||
/* Small delay to prevent tight loop */
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
now = HAL_GetTick();
|
||||
if (now >= g_client.next_retry_ms) {
|
||||
g_client.status.reconnect_count++;
|
||||
g_client.next_retry_ms = now + g_client.config.reconnect_interval_ms;
|
||||
(void)tcp_client_connect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,8 @@ void *tcp_client_get_tx_stream(void);
|
||||
*/
|
||||
void tcp_client_task(void *argument);
|
||||
|
||||
void tcp_client_poll(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+165
-324
@@ -1,405 +1,246 @@
|
||||
/**
|
||||
* @file tcp_server.c
|
||||
* @brief TCP Server module implementation for transparent transmission with UART2
|
||||
* @brief lwIP RAW TCP server for the UART2 bridge.
|
||||
*/
|
||||
|
||||
#include "tcp_server.h"
|
||||
#include "config.h"
|
||||
#include "lwip/opt.h"
|
||||
#include "lwip/tcp.h"
|
||||
#include "lwip/sys.h"
|
||||
#include "lwip/sockets.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "stream_buffer.h"
|
||||
#include "lwip/pbuf.h"
|
||||
#include "lwip/tcp.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Private Variables
|
||||
*---------------------------------------------------------------------------*/
|
||||
typedef struct {
|
||||
struct tcp_pcb *listen_pcb;
|
||||
struct tcp_pcb *client_pcb;
|
||||
uint8_t rx_ring[TCP_SERVER_RX_BUFFER_SIZE];
|
||||
uint16_t rx_head;
|
||||
uint16_t rx_tail;
|
||||
tcp_server_config_t config;
|
||||
tcp_server_status_t status;
|
||||
} tcp_server_ctx_t;
|
||||
|
||||
/* Server configuration */
|
||||
static tcp_server_config_t server_config = {
|
||||
.port = TCP_SERVER_DEFAULT_PORT,
|
||||
.auto_reconnect = true
|
||||
};
|
||||
static tcp_server_ctx_t g_server;
|
||||
|
||||
/* Server status */
|
||||
static tcp_server_status_t server_status = {
|
||||
.state = TCP_SERVER_STATE_IDLE,
|
||||
.rx_bytes = 0,
|
||||
.tx_bytes = 0,
|
||||
.connections = 0,
|
||||
.errors = 0
|
||||
};
|
||||
|
||||
/* Socket descriptors */
|
||||
static int listen_socket = -1;
|
||||
static int client_socket = -1;
|
||||
|
||||
/* Stream buffers for UART integration */
|
||||
static StreamBufferHandle_t rx_stream = NULL; /* TCP RX -> UART TX */
|
||||
static StreamBufferHandle_t tx_stream = NULL; /* UART RX -> TCP TX */
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Public Functions
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
static int tcp_server_send_all(int sock, const uint8_t *data, uint16_t len)
|
||||
static uint16_t ring_free(uint16_t head, uint16_t tail, uint16_t size)
|
||||
{
|
||||
uint16_t total = 0;
|
||||
return (head >= tail) ? (uint16_t)(size - head + tail - 1u) : (uint16_t)(tail - head - 1u);
|
||||
}
|
||||
|
||||
while (total < len)
|
||||
{
|
||||
int sent = send(sock, data + total, len - total, 0);
|
||||
if (sent > 0)
|
||||
{
|
||||
total += (uint16_t)sent;
|
||||
static err_t tcp_server_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
|
||||
{
|
||||
tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg;
|
||||
struct pbuf *q;
|
||||
|
||||
if (err != ERR_OK) {
|
||||
if (p != NULL) {
|
||||
pbuf_free(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
return err;
|
||||
}
|
||||
|
||||
if (p == NULL) {
|
||||
ctx->client_pcb = NULL;
|
||||
ctx->status.state = TCP_SERVER_STATE_LISTENING;
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
for (q = p; q != NULL; q = q->next) {
|
||||
const uint8_t *src = (const uint8_t *)q->payload;
|
||||
for (uint16_t i = 0; i < q->len; ++i) {
|
||||
if (ring_free(ctx->rx_head, ctx->rx_tail, TCP_SERVER_RX_BUFFER_SIZE) == 0u) {
|
||||
ctx->status.errors++;
|
||||
break;
|
||||
}
|
||||
ctx->rx_ring[ctx->rx_head] = src[i];
|
||||
ctx->rx_head = (uint16_t)((ctx->rx_head + 1u) % TCP_SERVER_RX_BUFFER_SIZE);
|
||||
ctx->status.rx_bytes++;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)total;
|
||||
tcp_recved(pcb, p->tot_len);
|
||||
pbuf_free(p);
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
static err_t tcp_server_on_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
|
||||
{
|
||||
tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg;
|
||||
(void)pcb;
|
||||
ctx->status.tx_bytes += len;
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
static void tcp_server_on_err(void *arg, err_t err)
|
||||
{
|
||||
tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg;
|
||||
(void)err;
|
||||
ctx->client_pcb = NULL;
|
||||
ctx->status.state = TCP_SERVER_STATE_LISTENING;
|
||||
ctx->status.errors++;
|
||||
}
|
||||
|
||||
static err_t tcp_server_on_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
|
||||
{
|
||||
tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg;
|
||||
|
||||
if (err != ERR_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ctx->client_pcb != NULL) {
|
||||
tcp_abort(newpcb);
|
||||
return ERR_ABRT;
|
||||
}
|
||||
|
||||
ctx->client_pcb = newpcb;
|
||||
ctx->status.state = TCP_SERVER_STATE_CONNECTED;
|
||||
ctx->status.connections++;
|
||||
|
||||
tcp_arg(newpcb, ctx);
|
||||
tcp_recv(newpcb, tcp_server_on_recv);
|
||||
tcp_sent(newpcb, tcp_server_on_sent);
|
||||
tcp_err(newpcb, tcp_server_on_err);
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize TCP Server module
|
||||
*/
|
||||
int tcp_server_init(const tcp_server_config_t *config)
|
||||
{
|
||||
if (config != NULL)
|
||||
{
|
||||
memcpy(&server_config, config, sizeof(tcp_server_config_t));
|
||||
memset(&g_server, 0, sizeof(g_server));
|
||||
g_server.config.port = TCP_SERVER_DEFAULT_PORT;
|
||||
g_server.config.auto_reconnect = true;
|
||||
g_server.status.state = TCP_SERVER_STATE_IDLE;
|
||||
|
||||
if (config != NULL) {
|
||||
g_server.config = *config;
|
||||
}
|
||||
|
||||
/* Create stream buffers */
|
||||
if (rx_stream == NULL)
|
||||
{
|
||||
rx_stream = xStreamBufferCreate(TCP_SERVER_RX_BUFFER_SIZE, 1);
|
||||
if (rx_stream == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (tx_stream == NULL)
|
||||
{
|
||||
tx_stream = xStreamBufferCreate(TCP_SERVER_TX_BUFFER_SIZE, 1);
|
||||
if (tx_stream == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
server_status.state = TCP_SERVER_STATE_IDLE;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start TCP Server
|
||||
*/
|
||||
int tcp_server_start(void)
|
||||
{
|
||||
struct sockaddr_in server_addr;
|
||||
int opt = 1;
|
||||
|
||||
if (listen_socket >= 0)
|
||||
{
|
||||
/* Already started */
|
||||
struct tcp_pcb *pcb;
|
||||
err_t err;
|
||||
|
||||
if (g_server.listen_pcb != NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Create socket */
|
||||
listen_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_socket < 0)
|
||||
{
|
||||
server_status.state = TCP_SERVER_STATE_ERROR;
|
||||
server_status.errors++;
|
||||
|
||||
pcb = tcp_new_ip_type(IPADDR_TYPE_V4);
|
||||
if (pcb == NULL) {
|
||||
g_server.status.errors++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set socket options */
|
||||
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
|
||||
/* Bind to port */
|
||||
memset(&server_addr, 0, sizeof(server_addr));
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
server_addr.sin_port = htons(server_config.port);
|
||||
|
||||
if (bind(listen_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
|
||||
{
|
||||
close(listen_socket);
|
||||
listen_socket = -1;
|
||||
server_status.state = TCP_SERVER_STATE_ERROR;
|
||||
server_status.errors++;
|
||||
|
||||
err = tcp_bind(pcb, IP_ANY_TYPE, g_server.config.port);
|
||||
if (err != ERR_OK) {
|
||||
tcp_abort(pcb);
|
||||
g_server.status.errors++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Start listening */
|
||||
if (listen(listen_socket, TCP_SERVER_MAX_CONNECTIONS) < 0)
|
||||
{
|
||||
close(listen_socket);
|
||||
listen_socket = -1;
|
||||
server_status.state = TCP_SERVER_STATE_ERROR;
|
||||
server_status.errors++;
|
||||
|
||||
g_server.listen_pcb = tcp_listen_with_backlog(pcb, 1);
|
||||
if (g_server.listen_pcb == NULL) {
|
||||
g_server.status.errors++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
server_status.state = TCP_SERVER_STATE_LISTENING;
|
||||
|
||||
|
||||
tcp_arg(g_server.listen_pcb, &g_server);
|
||||
tcp_accept(g_server.listen_pcb, tcp_server_on_accept);
|
||||
g_server.status.state = TCP_SERVER_STATE_LISTENING;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stop TCP Server
|
||||
*/
|
||||
int tcp_server_stop(void)
|
||||
{
|
||||
if (client_socket >= 0)
|
||||
{
|
||||
close(client_socket);
|
||||
client_socket = -1;
|
||||
if (g_server.client_pcb != NULL) {
|
||||
tcp_arg(g_server.client_pcb, NULL);
|
||||
tcp_recv(g_server.client_pcb, NULL);
|
||||
tcp_sent(g_server.client_pcb, NULL);
|
||||
tcp_err(g_server.client_pcb, NULL);
|
||||
tcp_abort(g_server.client_pcb);
|
||||
g_server.client_pcb = NULL;
|
||||
}
|
||||
|
||||
if (listen_socket >= 0)
|
||||
{
|
||||
close(listen_socket);
|
||||
listen_socket = -1;
|
||||
|
||||
if (g_server.listen_pcb != NULL) {
|
||||
tcp_arg(g_server.listen_pcb, NULL);
|
||||
tcp_accept(g_server.listen_pcb, NULL);
|
||||
tcp_close(g_server.listen_pcb);
|
||||
g_server.listen_pcb = NULL;
|
||||
}
|
||||
|
||||
server_status.state = TCP_SERVER_STATE_IDLE;
|
||||
|
||||
|
||||
g_server.status.state = TCP_SERVER_STATE_IDLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send data to connected client
|
||||
*/
|
||||
int tcp_server_send(const uint8_t *data, uint16_t len)
|
||||
{
|
||||
int sent;
|
||||
|
||||
if (client_socket < 0)
|
||||
{
|
||||
err_t err;
|
||||
|
||||
if (g_server.client_pcb == NULL || data == NULL || len == 0u) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sent = tcp_server_send_all(client_socket, data, len);
|
||||
if (sent > 0)
|
||||
{
|
||||
server_status.tx_bytes += sent;
|
||||
|
||||
if (tcp_sndbuf(g_server.client_pcb) < len) {
|
||||
return 0;
|
||||
}
|
||||
else if (sent < 0)
|
||||
{
|
||||
/* Connection error */
|
||||
close(client_socket);
|
||||
client_socket = -1;
|
||||
server_status.state = TCP_SERVER_STATE_LISTENING;
|
||||
server_status.errors++;
|
||||
|
||||
err = tcp_write(g_server.client_pcb, data, len, TCP_WRITE_FLAG_COPY);
|
||||
if (err != ERR_OK) {
|
||||
g_server.status.errors++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return sent;
|
||||
|
||||
err = tcp_output(g_server.client_pcb);
|
||||
if (err != ERR_OK) {
|
||||
g_server.status.errors++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (int)len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Receive data from connected client
|
||||
*/
|
||||
int tcp_server_recv(uint8_t *data, uint16_t max_len, uint32_t timeout_ms)
|
||||
{
|
||||
int received;
|
||||
struct timeval tv;
|
||||
|
||||
if (client_socket < 0)
|
||||
{
|
||||
uint16_t copied = 0u;
|
||||
(void)timeout_ms;
|
||||
|
||||
if (data == NULL || max_len == 0u) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set receive timeout */
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
received = recv(client_socket, data, max_len, 0);
|
||||
if (received > 0)
|
||||
{
|
||||
server_status.rx_bytes += received;
|
||||
|
||||
while (copied < max_len && g_server.rx_tail != g_server.rx_head) {
|
||||
data[copied++] = g_server.rx_ring[g_server.rx_tail];
|
||||
g_server.rx_tail = (uint16_t)((g_server.rx_tail + 1u) % TCP_SERVER_RX_BUFFER_SIZE);
|
||||
}
|
||||
else if (received == 0)
|
||||
{
|
||||
/* Connection closed by client */
|
||||
close(client_socket);
|
||||
client_socket = -1;
|
||||
server_status.state = TCP_SERVER_STATE_LISTENING;
|
||||
}
|
||||
else if (received < 0)
|
||||
{
|
||||
/* Timeout or error - check errno */
|
||||
/* For timeout, just return 0 */
|
||||
}
|
||||
|
||||
return received;
|
||||
|
||||
return (int)copied;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if client is connected
|
||||
*/
|
||||
bool tcp_server_is_connected(void)
|
||||
{
|
||||
return (client_socket >= 0);
|
||||
return g_server.client_pcb != NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get TCP Server status
|
||||
*/
|
||||
void tcp_server_get_status(tcp_server_status_t *status)
|
||||
{
|
||||
if (status != NULL)
|
||||
{
|
||||
memcpy(status, &server_status, sizeof(tcp_server_status_t));
|
||||
if (status != NULL) {
|
||||
*status = g_server.status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get RX StreamBuffer handle
|
||||
*/
|
||||
void *tcp_server_get_rx_stream(void)
|
||||
{
|
||||
return rx_stream;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get TX StreamBuffer handle
|
||||
*/
|
||||
void *tcp_server_get_tx_stream(void)
|
||||
{
|
||||
return tx_stream;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief TCP Server task
|
||||
*/
|
||||
void tcp_server_task(void *argument)
|
||||
{
|
||||
const device_config_t *cfg;
|
||||
tcp_server_config_t task_cfg;
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t addr_len;
|
||||
uint8_t rx_buffer[256];
|
||||
uint8_t tx_buffer[256];
|
||||
int received;
|
||||
size_t tx_len;
|
||||
fd_set read_fds;
|
||||
struct timeval tv;
|
||||
int max_fd;
|
||||
|
||||
(void)argument;
|
||||
|
||||
/* Initialize server */
|
||||
task_cfg.port = TCP_SERVER_DEFAULT_PORT;
|
||||
task_cfg.auto_reconnect = true;
|
||||
cfg = config_get();
|
||||
if (cfg != NULL && cfg->server_port > 0)
|
||||
{
|
||||
task_cfg.port = cfg->server_port;
|
||||
}
|
||||
tcp_server_init(&task_cfg);
|
||||
|
||||
/* Start server */
|
||||
while (tcp_server_start() != 0)
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
||||
while (1)
|
||||
{
|
||||
/* Check if we need to accept a new connection */
|
||||
if (client_socket < 0 && listen_socket >= 0)
|
||||
{
|
||||
/* Use select with timeout to check for incoming connections */
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(listen_socket, &read_fds);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 100000; /* 100ms timeout */
|
||||
|
||||
if (select(listen_socket + 1, &read_fds, NULL, NULL, &tv) > 0)
|
||||
{
|
||||
addr_len = sizeof(client_addr);
|
||||
client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, &addr_len);
|
||||
if (client_socket >= 0)
|
||||
{
|
||||
server_status.state = TCP_SERVER_STATE_CONNECTED;
|
||||
server_status.connections++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle data transfer if connected */
|
||||
if (client_socket >= 0)
|
||||
{
|
||||
/* Check for data from TCP client */
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(client_socket, &read_fds);
|
||||
max_fd = client_socket;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 10000; /* 10ms timeout */
|
||||
|
||||
if (select(max_fd + 1, &read_fds, NULL, NULL, &tv) > 0)
|
||||
{
|
||||
if (FD_ISSET(client_socket, &read_fds))
|
||||
{
|
||||
received = recv(client_socket, rx_buffer, sizeof(rx_buffer), 0);
|
||||
if (received > 0)
|
||||
{
|
||||
/* Forward to UART via stream buffer */
|
||||
xStreamBufferSend(rx_stream, rx_buffer, received, pdMS_TO_TICKS(10));
|
||||
server_status.rx_bytes += received;
|
||||
}
|
||||
else if (received == 0)
|
||||
{
|
||||
/* Connection closed */
|
||||
close(client_socket);
|
||||
client_socket = -1;
|
||||
server_status.state = TCP_SERVER_STATE_LISTENING;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Error */
|
||||
close(client_socket);
|
||||
client_socket = -1;
|
||||
server_status.state = TCP_SERVER_STATE_LISTENING;
|
||||
server_status.errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for data from UART to send to TCP */
|
||||
tx_len = xStreamBufferReceive(tx_stream, tx_buffer, sizeof(tx_buffer), 0);
|
||||
if (tx_len > 0)
|
||||
{
|
||||
int sent = tcp_server_send_all(client_socket, tx_buffer, (uint16_t)tx_len);
|
||||
if (sent > 0)
|
||||
{
|
||||
server_status.tx_bytes += sent;
|
||||
}
|
||||
else if (sent < 0)
|
||||
{
|
||||
/* Send error */
|
||||
close(client_socket);
|
||||
client_socket = -1;
|
||||
server_status.state = TCP_SERVER_STATE_LISTENING;
|
||||
server_status.errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Small delay to prevent tight loop */
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
}
|
||||
|
||||
+253
-450
@@ -1,528 +1,331 @@
|
||||
/**
|
||||
* @file uart_trans.c
|
||||
* @brief UART transparent transmission module implementation
|
||||
*
|
||||
* Uses DMA + IDLE interrupt for efficient variable-length data reception.
|
||||
* Integrates with TCP modules via FreeRTOS StreamBuffers.
|
||||
* @brief Bare-metal UART DMA/IDLE transport layer.
|
||||
*/
|
||||
|
||||
#include "uart_trans.h"
|
||||
#include "usart.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "stream_buffer.h"
|
||||
#include "usart.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Private Definitions
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
/* Channel context structure */
|
||||
typedef struct {
|
||||
UART_HandleTypeDef *huart; /* HAL UART handle */
|
||||
DMA_HandleTypeDef *hdma_rx; /* DMA RX handle */
|
||||
|
||||
uint8_t rx_dma_buffer[UART_RX_DMA_BUFFER_SIZE]; /* DMA RX buffer */
|
||||
uint8_t tx_dma_buffer[UART_TX_DMA_BUFFER_SIZE]; /* DMA TX buffer */
|
||||
|
||||
volatile uint16_t rx_read_index; /* Last read position */
|
||||
volatile bool tx_busy; /* TX in progress flag */
|
||||
|
||||
StreamBufferHandle_t rx_stream; /* From TCP (for UART TX) */
|
||||
StreamBufferHandle_t tx_stream; /* To TCP (from UART RX) */
|
||||
|
||||
uart_config_t config; /* UART configuration */
|
||||
uart_stats_t stats; /* Statistics */
|
||||
|
||||
UART_HandleTypeDef *huart;
|
||||
uint8_t rx_dma_buffer[UART_RX_DMA_BUFFER_SIZE];
|
||||
uint8_t tx_dma_buffer[UART_TX_DMA_BUFFER_SIZE];
|
||||
uint8_t rx_ring[UART_RX_RING_BUFFER_SIZE];
|
||||
uint8_t tx_ring[UART_TX_RING_BUFFER_SIZE];
|
||||
volatile uint16_t rx_dma_read_index;
|
||||
volatile uint16_t rx_head;
|
||||
volatile uint16_t rx_tail;
|
||||
volatile uint16_t tx_head;
|
||||
volatile uint16_t tx_tail;
|
||||
volatile uint16_t tx_dma_len;
|
||||
volatile bool tx_busy;
|
||||
uart_config_t config;
|
||||
uart_stats_t stats;
|
||||
bool initialized;
|
||||
bool running;
|
||||
} uart_channel_ctx_t;
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Private Variables
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
static uart_channel_ctx_t g_channels[UART_CHANNEL_MAX];
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Private Functions
|
||||
*---------------------------------------------------------------------------*/
|
||||
static uint16_t ring_used(uint16_t head, uint16_t tail, uint16_t size)
|
||||
{
|
||||
return (head >= tail) ? (head - tail) : (size - tail + head);
|
||||
}
|
||||
|
||||
static uint16_t ring_free(uint16_t head, uint16_t tail, uint16_t size)
|
||||
{
|
||||
return (uint16_t)(size - ring_used(head, tail, size) - 1u);
|
||||
}
|
||||
|
||||
static void apply_default_config(uart_channel_ctx_t *ctx)
|
||||
{
|
||||
ctx->config.baudrate = UART_DEFAULT_BAUDRATE;
|
||||
ctx->config.data_bits = UART_DEFAULT_DATA_BITS;
|
||||
ctx->config.stop_bits = UART_DEFAULT_STOP_BITS;
|
||||
ctx->config.parity = UART_DEFAULT_PARITY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply UART configuration
|
||||
*/
|
||||
static int apply_uart_config(uart_channel_t channel)
|
||||
{
|
||||
uart_channel_ctx_t *ctx = &g_channels[channel];
|
||||
UART_HandleTypeDef *huart = ctx->huart;
|
||||
|
||||
if (huart == NULL)
|
||||
{
|
||||
|
||||
if (huart == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Stop UART if running */
|
||||
if (ctx->running)
|
||||
{
|
||||
|
||||
if (ctx->running) {
|
||||
HAL_UART_DMAStop(huart);
|
||||
ctx->running = false;
|
||||
}
|
||||
|
||||
/* Update UART parameters */
|
||||
|
||||
huart->Init.BaudRate = ctx->config.baudrate;
|
||||
|
||||
/* Data bits */
|
||||
if (ctx->config.data_bits == 9)
|
||||
{
|
||||
huart->Init.WordLength = UART_WORDLENGTH_9B;
|
||||
huart->Init.WordLength = (ctx->config.data_bits == 9u) ? UART_WORDLENGTH_9B : UART_WORDLENGTH_8B;
|
||||
huart->Init.StopBits = (ctx->config.stop_bits == 2u) ? UART_STOPBITS_2 : UART_STOPBITS_1;
|
||||
|
||||
switch (ctx->config.parity) {
|
||||
case 1:
|
||||
huart->Init.Parity = UART_PARITY_ODD;
|
||||
break;
|
||||
case 2:
|
||||
huart->Init.Parity = UART_PARITY_EVEN;
|
||||
break;
|
||||
default:
|
||||
huart->Init.Parity = UART_PARITY_NONE;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
huart->Init.WordLength = UART_WORDLENGTH_8B;
|
||||
}
|
||||
|
||||
/* Stop bits */
|
||||
if (ctx->config.stop_bits == 2)
|
||||
{
|
||||
huart->Init.StopBits = UART_STOPBITS_2;
|
||||
}
|
||||
else
|
||||
{
|
||||
huart->Init.StopBits = UART_STOPBITS_1;
|
||||
}
|
||||
|
||||
/* Parity */
|
||||
switch (ctx->config.parity)
|
||||
{
|
||||
case 1:
|
||||
huart->Init.Parity = UART_PARITY_ODD;
|
||||
break;
|
||||
case 2:
|
||||
huart->Init.Parity = UART_PARITY_EVEN;
|
||||
break;
|
||||
default:
|
||||
huart->Init.Parity = UART_PARITY_NONE;
|
||||
|
||||
return (HAL_UART_Init(huart) == HAL_OK) ? 0 : -1;
|
||||
}
|
||||
|
||||
static void process_rx_snapshot(uart_channel_t channel, uint16_t dma_write_index)
|
||||
{
|
||||
uart_channel_ctx_t *ctx = &g_channels[channel];
|
||||
|
||||
while (ctx->rx_dma_read_index != dma_write_index) {
|
||||
uint16_t next_head;
|
||||
|
||||
next_head = (uint16_t)((ctx->rx_head + 1u) % UART_RX_RING_BUFFER_SIZE);
|
||||
if (next_head == ctx->rx_tail) {
|
||||
ctx->stats.errors++;
|
||||
break;
|
||||
}
|
||||
|
||||
ctx->rx_ring[ctx->rx_head] = ctx->rx_dma_buffer[ctx->rx_dma_read_index];
|
||||
ctx->rx_head = next_head;
|
||||
ctx->rx_dma_read_index = (uint16_t)((ctx->rx_dma_read_index + 1u) % UART_RX_DMA_BUFFER_SIZE);
|
||||
ctx->stats.rx_bytes++;
|
||||
}
|
||||
|
||||
/* Reinitialize UART */
|
||||
if (HAL_UART_Init(huart) != HAL_OK)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void kick_tx(uart_channel_t channel)
|
||||
{
|
||||
uart_channel_ctx_t *ctx = &g_channels[channel];
|
||||
uint16_t available;
|
||||
uint16_t chunk;
|
||||
|
||||
if (!ctx->running || ctx->tx_busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
available = ring_used(ctx->tx_head, ctx->tx_tail, UART_TX_RING_BUFFER_SIZE);
|
||||
if (available == 0u) {
|
||||
return;
|
||||
}
|
||||
|
||||
chunk = available;
|
||||
if (chunk > UART_TX_DMA_BUFFER_SIZE) {
|
||||
chunk = UART_TX_DMA_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < chunk; ++i) {
|
||||
ctx->tx_dma_buffer[i] = ctx->tx_ring[ctx->tx_tail];
|
||||
ctx->tx_tail = (uint16_t)((ctx->tx_tail + 1u) % UART_TX_RING_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
ctx->tx_dma_len = chunk;
|
||||
ctx->tx_busy = true;
|
||||
ctx->stats.tx_packets++;
|
||||
|
||||
if (HAL_UART_Transmit_DMA(ctx->huart, ctx->tx_dma_buffer, chunk) != HAL_OK) {
|
||||
ctx->tx_busy = false;
|
||||
ctx->stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
int uart_trans_init(void)
|
||||
{
|
||||
memset(g_channels, 0, sizeof(g_channels));
|
||||
|
||||
g_channels[UART_CHANNEL_SERVER].huart = &huart2;
|
||||
g_channels[UART_CHANNEL_CLIENT].huart = &huart3;
|
||||
|
||||
apply_default_config(&g_channels[UART_CHANNEL_SERVER]);
|
||||
apply_default_config(&g_channels[UART_CHANNEL_CLIENT]);
|
||||
|
||||
g_channels[UART_CHANNEL_SERVER].initialized = true;
|
||||
g_channels[UART_CHANNEL_CLIENT].initialized = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void process_rx_data_from_isr(uart_channel_t channel, uint16_t end_index)
|
||||
int uart_trans_config(uart_channel_t channel, const uart_config_t *config)
|
||||
{
|
||||
uart_channel_ctx_t *ctx = &g_channels[channel];
|
||||
uint16_t start = ctx->rx_read_index;
|
||||
uint16_t end = end_index;
|
||||
uint16_t len;
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
|
||||
if (start >= UART_RX_DMA_BUFFER_SIZE)
|
||||
{
|
||||
start = 0;
|
||||
if (channel >= UART_CHANNEL_MAX || config == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (end > UART_RX_DMA_BUFFER_SIZE)
|
||||
{
|
||||
end = UART_RX_DMA_BUFFER_SIZE;
|
||||
g_channels[channel].config = *config;
|
||||
return apply_uart_config(channel);
|
||||
}
|
||||
|
||||
int uart_trans_start(uart_channel_t channel)
|
||||
{
|
||||
uart_channel_ctx_t *ctx;
|
||||
|
||||
if (channel >= UART_CHANNEL_MAX) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (end >= start)
|
||||
{
|
||||
len = end - start;
|
||||
if (len > 0 && ctx->tx_stream != NULL)
|
||||
{
|
||||
xStreamBufferSendFromISR(ctx->tx_stream,
|
||||
&ctx->rx_dma_buffer[start],
|
||||
len,
|
||||
&xHigherPriorityTaskWoken);
|
||||
ctx->stats.rx_bytes += len;
|
||||
ctx->stats.rx_packets++;
|
||||
}
|
||||
ctx = &g_channels[channel];
|
||||
if (!ctx->initialized || ctx->huart == NULL) {
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
len = UART_RX_DMA_BUFFER_SIZE - start;
|
||||
if (len > 0 && ctx->tx_stream != NULL)
|
||||
{
|
||||
xStreamBufferSendFromISR(ctx->tx_stream,
|
||||
&ctx->rx_dma_buffer[start],
|
||||
len,
|
||||
&xHigherPriorityTaskWoken);
|
||||
ctx->stats.rx_bytes += len;
|
||||
}
|
||||
|
||||
if (end > 0 && ctx->tx_stream != NULL)
|
||||
{
|
||||
xStreamBufferSendFromISR(ctx->tx_stream,
|
||||
ctx->rx_dma_buffer,
|
||||
end,
|
||||
&xHigherPriorityTaskWoken);
|
||||
ctx->stats.rx_bytes += end;
|
||||
}
|
||||
ctx->rx_dma_read_index = 0u;
|
||||
ctx->rx_head = 0u;
|
||||
ctx->rx_tail = 0u;
|
||||
ctx->tx_head = 0u;
|
||||
ctx->tx_tail = 0u;
|
||||
ctx->tx_dma_len = 0u;
|
||||
ctx->tx_busy = false;
|
||||
|
||||
__HAL_UART_ENABLE_IT(ctx->huart, UART_IT_IDLE);
|
||||
if (HAL_UART_Receive_DMA(ctx->huart, ctx->rx_dma_buffer, UART_RX_DMA_BUFFER_SIZE) != HAL_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ctx->running = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uart_trans_stop(uart_channel_t channel)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
HAL_UART_DMAStop(g_channels[channel].huart);
|
||||
g_channels[channel].running = false;
|
||||
g_channels[channel].tx_busy = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void uart_trans_get_stats(uart_channel_t channel, uart_stats_t *stats)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX || stats == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
*stats = g_channels[channel].stats;
|
||||
}
|
||||
|
||||
void uart_trans_reset_stats(uart_channel_t channel)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&g_channels[channel].stats, 0, sizeof(g_channels[channel].stats));
|
||||
}
|
||||
|
||||
uint16_t uart_trans_rx_available(uart_channel_t channel)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
return ring_used(g_channels[channel].rx_head, g_channels[channel].rx_tail, UART_RX_RING_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
uint16_t uart_trans_read(uart_channel_t channel, uint8_t *data, uint16_t max_len)
|
||||
{
|
||||
uart_channel_ctx_t *ctx;
|
||||
uint16_t copied = 0u;
|
||||
|
||||
if (channel >= UART_CHANNEL_MAX || data == NULL || max_len == 0u) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
ctx = &g_channels[channel];
|
||||
while (copied < max_len && ctx->rx_tail != ctx->rx_head) {
|
||||
data[copied++] = ctx->rx_ring[ctx->rx_tail];
|
||||
ctx->rx_tail = (uint16_t)((ctx->rx_tail + 1u) % UART_RX_RING_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (copied > 0u) {
|
||||
ctx->stats.rx_packets++;
|
||||
}
|
||||
|
||||
ctx->rx_read_index = (end == UART_RX_DMA_BUFFER_SIZE) ? 0 : end;
|
||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
return copied;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Public Functions
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief Initialize UART transparent transmission module
|
||||
*/
|
||||
int uart_trans_init(void)
|
||||
uint16_t uart_trans_write(uart_channel_t channel, const uint8_t *data, uint16_t len)
|
||||
{
|
||||
/* Initialize Server channel (UART2) */
|
||||
memset(&g_channels[UART_CHANNEL_SERVER], 0, sizeof(uart_channel_ctx_t));
|
||||
g_channels[UART_CHANNEL_SERVER].huart = &huart2;
|
||||
g_channels[UART_CHANNEL_SERVER].config.baudrate = UART_DEFAULT_BAUDRATE;
|
||||
g_channels[UART_CHANNEL_SERVER].config.data_bits = UART_DEFAULT_DATA_BITS;
|
||||
g_channels[UART_CHANNEL_SERVER].config.stop_bits = UART_DEFAULT_STOP_BITS;
|
||||
g_channels[UART_CHANNEL_SERVER].config.parity = UART_DEFAULT_PARITY;
|
||||
g_channels[UART_CHANNEL_SERVER].initialized = true;
|
||||
|
||||
/* Initialize Client channel (UART3) */
|
||||
memset(&g_channels[UART_CHANNEL_CLIENT], 0, sizeof(uart_channel_ctx_t));
|
||||
g_channels[UART_CHANNEL_CLIENT].huart = &huart3;
|
||||
g_channels[UART_CHANNEL_CLIENT].config.baudrate = UART_DEFAULT_BAUDRATE;
|
||||
g_channels[UART_CHANNEL_CLIENT].config.data_bits = UART_DEFAULT_DATA_BITS;
|
||||
g_channels[UART_CHANNEL_CLIENT].config.stop_bits = UART_DEFAULT_STOP_BITS;
|
||||
g_channels[UART_CHANNEL_CLIENT].config.parity = UART_DEFAULT_PARITY;
|
||||
g_channels[UART_CHANNEL_CLIENT].initialized = true;
|
||||
|
||||
return 0;
|
||||
uart_channel_ctx_t *ctx;
|
||||
uint16_t written = 0u;
|
||||
|
||||
if (channel >= UART_CHANNEL_MAX || data == NULL || len == 0u) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
ctx = &g_channels[channel];
|
||||
while (written < len && ring_free(ctx->tx_head, ctx->tx_tail, UART_TX_RING_BUFFER_SIZE) > 0u) {
|
||||
ctx->tx_ring[ctx->tx_head] = data[written++];
|
||||
ctx->tx_head = (uint16_t)((ctx->tx_head + 1u) % UART_TX_RING_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (written < len) {
|
||||
ctx->stats.errors++;
|
||||
}
|
||||
|
||||
kick_tx(channel);
|
||||
return written;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure UART channel parameters
|
||||
*/
|
||||
int uart_trans_config(uart_channel_t channel, const uart_config_t *config)
|
||||
void uart_trans_poll(void)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX || config == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
uart_channel_ctx_t *ctx = &g_channels[channel];
|
||||
|
||||
memcpy(&ctx->config, config, sizeof(uart_config_t));
|
||||
|
||||
/* Apply configuration if already initialized */
|
||||
if (ctx->initialized)
|
||||
{
|
||||
return apply_uart_config(channel);
|
||||
}
|
||||
|
||||
return 0;
|
||||
kick_tx(UART_CHANNEL_SERVER);
|
||||
kick_tx(UART_CHANNEL_CLIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start UART reception
|
||||
*/
|
||||
int uart_trans_start(uart_channel_t channel)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
uart_channel_ctx_t *ctx = &g_channels[channel];
|
||||
|
||||
if (!ctx->initialized || ctx->huart == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Reset read index */
|
||||
ctx->rx_read_index = 0;
|
||||
ctx->tx_busy = false;
|
||||
|
||||
/* Enable IDLE interrupt */
|
||||
__HAL_UART_ENABLE_IT(ctx->huart, UART_IT_IDLE);
|
||||
|
||||
/* Start DMA reception (circular mode) */
|
||||
HAL_UART_Receive_DMA(ctx->huart, ctx->rx_dma_buffer, UART_RX_DMA_BUFFER_SIZE);
|
||||
|
||||
ctx->running = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stop UART reception
|
||||
*/
|
||||
int uart_trans_stop(uart_channel_t channel)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
uart_channel_ctx_t *ctx = &g_channels[channel];
|
||||
|
||||
if (ctx->huart == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Disable IDLE interrupt */
|
||||
__HAL_UART_DISABLE_IT(ctx->huart, UART_IT_IDLE);
|
||||
|
||||
/* Stop DMA */
|
||||
HAL_UART_DMAStop(ctx->huart);
|
||||
|
||||
ctx->running = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set StreamBuffer handles
|
||||
*/
|
||||
void uart_trans_set_streams(uart_channel_t channel,
|
||||
void *rx_stream,
|
||||
void *tx_stream)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
g_channels[channel].rx_stream = (StreamBufferHandle_t)rx_stream;
|
||||
g_channels[channel].tx_stream = (StreamBufferHandle_t)tx_stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get UART statistics
|
||||
*/
|
||||
void uart_trans_get_stats(uart_channel_t channel, uart_stats_t *stats)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX || stats == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(stats, &g_channels[channel].stats, sizeof(uart_stats_t));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset UART statistics
|
||||
*/
|
||||
void uart_trans_reset_stats(uart_channel_t channel)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&g_channels[channel].stats, 0, sizeof(uart_stats_t));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief UART IDLE interrupt handler
|
||||
*/
|
||||
void uart_trans_idle_handler(uart_channel_t channel)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX)
|
||||
{
|
||||
UART_HandleTypeDef *huart;
|
||||
uint16_t dma_write_index;
|
||||
|
||||
if (channel >= UART_CHANNEL_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
uart_channel_ctx_t *ctx = &g_channels[channel];
|
||||
|
||||
if (!ctx->running || ctx->huart == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get current DMA position */
|
||||
uint16_t dma_counter = __HAL_DMA_GET_COUNTER(ctx->huart->hdmarx);
|
||||
uint16_t current_pos = UART_RX_DMA_BUFFER_SIZE - dma_counter;
|
||||
|
||||
/* Process received data */
|
||||
if (current_pos != ctx->rx_read_index)
|
||||
{
|
||||
process_rx_data_from_isr(channel, current_pos);
|
||||
|
||||
huart = g_channels[channel].huart;
|
||||
dma_write_index = (uint16_t)(UART_RX_DMA_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart->hdmarx));
|
||||
if (dma_write_index >= UART_RX_DMA_BUFFER_SIZE) {
|
||||
dma_write_index = 0u;
|
||||
}
|
||||
|
||||
process_rx_snapshot(channel, dma_write_index);
|
||||
}
|
||||
|
||||
void uart_trans_rx_half_cplt_handler(uart_channel_t channel)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
uart_channel_ctx_t *ctx = &g_channels[channel];
|
||||
if (!ctx->running || ctx->huart == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t dma_counter = __HAL_DMA_GET_COUNTER(ctx->huart->hdmarx);
|
||||
uint16_t current_pos = UART_RX_DMA_BUFFER_SIZE - dma_counter;
|
||||
|
||||
if (current_pos != ctx->rx_read_index)
|
||||
{
|
||||
process_rx_data_from_isr(channel, current_pos);
|
||||
}
|
||||
process_rx_snapshot(channel, UART_RX_DMA_BUFFER_SIZE / 2u);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief UART DMA RX complete callback (buffer half/full)
|
||||
*/
|
||||
void uart_trans_rx_cplt_handler(uart_channel_t channel)
|
||||
{
|
||||
/* In circular mode, this is called when buffer is full */
|
||||
/* The IDLE handler already processes data continuously */
|
||||
/* This is a safety handler for high-speed continuous data */
|
||||
|
||||
if (channel >= UART_CHANNEL_MAX)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
uart_channel_ctx_t *ctx = &g_channels[channel];
|
||||
|
||||
if (!ctx->running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t dma_counter = __HAL_DMA_GET_COUNTER(ctx->huart->hdmarx);
|
||||
uint16_t current_pos = UART_RX_DMA_BUFFER_SIZE - dma_counter;
|
||||
|
||||
if (current_pos != ctx->rx_read_index)
|
||||
{
|
||||
process_rx_data_from_isr(channel, current_pos);
|
||||
}
|
||||
process_rx_snapshot(channel, 0u);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief UART DMA TX complete callback
|
||||
*/
|
||||
void uart_trans_tx_cplt_handler(uart_channel_t channel)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX)
|
||||
{
|
||||
if (channel >= UART_CHANNEL_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
g_channels[channel].tx_busy = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Server transparent transmission task (UART2 <-> TCP Server)
|
||||
*/
|
||||
void uart_server_trans_task(void *argument)
|
||||
{
|
||||
uart_channel_ctx_t *ctx = &g_channels[UART_CHANNEL_SERVER];
|
||||
uint8_t tx_buffer[128];
|
||||
size_t len;
|
||||
|
||||
(void)argument;
|
||||
|
||||
/* Wait for streams to be set */
|
||||
while (ctx->rx_stream == NULL || ctx->tx_stream == NULL)
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
/* Start UART reception */
|
||||
uart_trans_start(UART_CHANNEL_SERVER);
|
||||
|
||||
while (1)
|
||||
{
|
||||
/* Check for data from TCP to send to UART */
|
||||
if (!ctx->tx_busy && ctx->rx_stream != NULL)
|
||||
{
|
||||
len = xStreamBufferReceive(ctx->rx_stream, tx_buffer,
|
||||
sizeof(tx_buffer), pdMS_TO_TICKS(10));
|
||||
if (len > 0)
|
||||
{
|
||||
/* Copy to DMA buffer and send */
|
||||
memcpy(ctx->tx_dma_buffer, tx_buffer, len);
|
||||
ctx->tx_busy = true;
|
||||
|
||||
if (HAL_UART_Transmit_DMA(ctx->huart, ctx->tx_dma_buffer, len) != HAL_OK)
|
||||
{
|
||||
ctx->tx_busy = false;
|
||||
ctx->stats.errors++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx->stats.tx_bytes += len;
|
||||
ctx->stats.tx_packets++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TX busy or no stream, wait a bit */
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Client transparent transmission task (UART3 <-> TCP Client)
|
||||
*/
|
||||
void uart_client_trans_task(void *argument)
|
||||
{
|
||||
uart_channel_ctx_t *ctx = &g_channels[UART_CHANNEL_CLIENT];
|
||||
uint8_t tx_buffer[128];
|
||||
size_t len;
|
||||
|
||||
(void)argument;
|
||||
|
||||
/* Wait for streams to be set */
|
||||
while (ctx->rx_stream == NULL || ctx->tx_stream == NULL)
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
/* Start UART reception */
|
||||
uart_trans_start(UART_CHANNEL_CLIENT);
|
||||
|
||||
while (1)
|
||||
{
|
||||
/* Check for data from TCP to send to UART */
|
||||
if (!ctx->tx_busy && ctx->rx_stream != NULL)
|
||||
{
|
||||
len = xStreamBufferReceive(ctx->rx_stream, tx_buffer,
|
||||
sizeof(tx_buffer), pdMS_TO_TICKS(10));
|
||||
if (len > 0)
|
||||
{
|
||||
/* Copy to DMA buffer and send */
|
||||
memcpy(ctx->tx_dma_buffer, tx_buffer, len);
|
||||
ctx->tx_busy = true;
|
||||
|
||||
if (HAL_UART_Transmit_DMA(ctx->huart, ctx->tx_dma_buffer, len) != HAL_OK)
|
||||
{
|
||||
ctx->tx_busy = false;
|
||||
ctx->stats.errors++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx->stats.tx_bytes += len;
|
||||
ctx->stats.tx_packets++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TX busy or no stream, wait a bit */
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
}
|
||||
g_channels[channel].stats.tx_bytes += g_channels[channel].tx_dma_len;
|
||||
g_channels[channel].tx_dma_len = 0u;
|
||||
kick_tx(channel);
|
||||
}
|
||||
|
||||
+20
-105
@@ -1,48 +1,41 @@
|
||||
/**
|
||||
* @file uart_trans.h
|
||||
* @brief UART transparent transmission module for TCP2UART
|
||||
*
|
||||
* - UART2 <-> TCP Server (via StreamBuffer)
|
||||
* - UART3 <-> TCP Client (via StreamBuffer)
|
||||
* - DMA + IDLE interrupt for efficient reception
|
||||
* @brief Bare-metal UART DMA/IDLE transport layer.
|
||||
*/
|
||||
|
||||
#ifndef __UART_TRANS_H__
|
||||
#define __UART_TRANS_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* UART channel definitions */
|
||||
typedef enum {
|
||||
UART_CHANNEL_SERVER = 0, /* UART2 - TCP Server channel */
|
||||
UART_CHANNEL_CLIENT = 1, /* UART3 - TCP Client channel */
|
||||
UART_CHANNEL_SERVER = 0,
|
||||
UART_CHANNEL_CLIENT = 1,
|
||||
UART_CHANNEL_MAX
|
||||
} uart_channel_t;
|
||||
|
||||
/* DMA buffer sizes */
|
||||
#define UART_RX_DMA_BUFFER_SIZE 128
|
||||
#define UART_TX_DMA_BUFFER_SIZE 128
|
||||
#define UART_RX_DMA_BUFFER_SIZE 128u
|
||||
#define UART_TX_DMA_BUFFER_SIZE 128u
|
||||
#define UART_RX_RING_BUFFER_SIZE 512u
|
||||
#define UART_TX_RING_BUFFER_SIZE 512u
|
||||
|
||||
/* UART configuration */
|
||||
typedef struct {
|
||||
uint32_t baudrate;
|
||||
uint8_t data_bits; /* 8 or 9 */
|
||||
uint8_t stop_bits; /* 1 or 2 */
|
||||
uint8_t parity; /* 0=None, 1=Odd, 2=Even */
|
||||
uint8_t data_bits;
|
||||
uint8_t stop_bits;
|
||||
uint8_t parity;
|
||||
} uart_config_t;
|
||||
|
||||
/* Default configurations */
|
||||
#define UART_DEFAULT_BAUDRATE 115200
|
||||
#define UART_DEFAULT_DATA_BITS 8
|
||||
#define UART_DEFAULT_STOP_BITS 1
|
||||
#define UART_DEFAULT_PARITY 0
|
||||
#define UART_DEFAULT_BAUDRATE 115200u
|
||||
#define UART_DEFAULT_DATA_BITS 8u
|
||||
#define UART_DEFAULT_STOP_BITS 1u
|
||||
#define UART_DEFAULT_PARITY 0u
|
||||
|
||||
/* UART statistics */
|
||||
typedef struct {
|
||||
uint32_t rx_bytes;
|
||||
uint32_t tx_bytes;
|
||||
@@ -51,101 +44,23 @@ typedef struct {
|
||||
uint32_t errors;
|
||||
} uart_stats_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize UART transparent transmission module
|
||||
* @return 0 on success, negative on error
|
||||
*/
|
||||
int uart_trans_init(void);
|
||||
|
||||
/**
|
||||
* @brief Configure UART channel parameters
|
||||
* @param channel UART channel (SERVER or CLIENT)
|
||||
* @param config UART configuration
|
||||
* @return 0 on success, negative on error
|
||||
*/
|
||||
int uart_trans_config(uart_channel_t channel, const uart_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Start UART reception (enable DMA + IDLE interrupt)
|
||||
* @param channel UART channel
|
||||
* @return 0 on success, negative on error
|
||||
*/
|
||||
int uart_trans_start(uart_channel_t channel);
|
||||
|
||||
/**
|
||||
* @brief Stop UART reception
|
||||
* @param channel UART channel
|
||||
* @return 0 on success, negative on error
|
||||
*/
|
||||
int uart_trans_stop(uart_channel_t channel);
|
||||
|
||||
/**
|
||||
* @brief Set StreamBuffer handles for TCP integration
|
||||
* @param channel UART channel
|
||||
* @param rx_stream StreamBuffer to receive data from TCP (for UART TX)
|
||||
* @param tx_stream StreamBuffer to send data to TCP (from UART RX)
|
||||
*/
|
||||
void uart_trans_set_streams(uart_channel_t channel,
|
||||
void *rx_stream,
|
||||
void *tx_stream);
|
||||
|
||||
/**
|
||||
* @brief Get UART statistics
|
||||
* @param channel UART channel
|
||||
* @param stats Pointer to statistics structure
|
||||
*/
|
||||
void uart_trans_poll(void);
|
||||
uint16_t uart_trans_rx_available(uart_channel_t channel);
|
||||
uint16_t uart_trans_read(uart_channel_t channel, uint8_t *data, uint16_t max_len);
|
||||
uint16_t uart_trans_write(uart_channel_t channel, const uint8_t *data, uint16_t len);
|
||||
void uart_trans_get_stats(uart_channel_t channel, uart_stats_t *stats);
|
||||
|
||||
/**
|
||||
* @brief Reset UART statistics
|
||||
* @param channel UART channel
|
||||
*/
|
||||
void uart_trans_reset_stats(uart_channel_t channel);
|
||||
|
||||
/**
|
||||
* @brief UART IDLE interrupt handler - call from stm32f1xx_it.c
|
||||
* @param channel UART channel
|
||||
*
|
||||
* Usage in stm32f1xx_it.c USART2_IRQHandler:
|
||||
* if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) {
|
||||
* __HAL_UART_CLEAR_IDLEFLAG(&huart2);
|
||||
* uart_trans_idle_handler(UART_CHANNEL_SERVER);
|
||||
* }
|
||||
*/
|
||||
void uart_trans_idle_handler(uart_channel_t channel);
|
||||
|
||||
/**
|
||||
* @brief UART DMA RX half complete callback - call from HAL callback
|
||||
* @param channel UART channel
|
||||
*/
|
||||
void uart_trans_rx_half_cplt_handler(uart_channel_t channel);
|
||||
|
||||
/**
|
||||
* @brief UART DMA RX complete callback - call from HAL callback
|
||||
* @param channel UART channel
|
||||
*/
|
||||
void uart_trans_rx_cplt_handler(uart_channel_t channel);
|
||||
|
||||
/**
|
||||
* @brief UART DMA TX complete callback - call from HAL callback
|
||||
* @param channel UART channel
|
||||
*/
|
||||
void uart_trans_tx_cplt_handler(uart_channel_t channel);
|
||||
|
||||
/**
|
||||
* @brief Server transparent transmission task (UART2 <-> TCP Server)
|
||||
* @param argument Task argument (unused)
|
||||
*/
|
||||
void uart_server_trans_task(void *argument);
|
||||
|
||||
/**
|
||||
* @brief Client transparent transmission task (UART3 <-> TCP Client)
|
||||
* @param argument Task argument (unused)
|
||||
*/
|
||||
void uart_client_trans_task(void *argument);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __UART_TRANS_H__ */
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user