update template

This commit is contained in:
2026-02-18 17:44:38 +08:00
parent d324d5f92a
commit 7419dec1b5
14 changed files with 1076 additions and 281 deletions

View File

@@ -15,8 +15,11 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "board_config.h"
#include "gd32e23x_usart.h"
#include "i2c.h"
#include "core_cm23.h"
/* ============================================================================
* 协议格式说明
@@ -28,7 +31,7 @@
* @details
* Host -> Device 命令帧格式:
* [0] HEADER = 0xD5 // 包头标识
* [1] BOARD_TYPE = 0x03 // 板卡类型标识
* [1] BOARD_TYPE = 0x01 // 板卡类型标识
* [2] LEN = 数据区字节数 // 有效载荷长度
* [3..(3+LEN-1)] 数据 // 命令数据,如 "M1", "M2S123"
* [last] CRC = 校验码 // 从索引1到(last-1)的累加和低8位
@@ -52,24 +55,26 @@
/** @name 协议帧标识符
* @{ */
#define PROTOCOL_PACKAGE_HEADER 0xD5 /**< 命令帧包头标识 */
#define PROTOCOL_BOARD_TYPE 0x03 /**< 板卡类型标识 */
/** @} */
/** @name 命令长度限制
* @{ */
#define COMMAND_MIN_LEN 2 /**< 最小命令长度,如"M1" */
#define PROTOCOL_MIN_FRAME_LEN (3 + COMMAND_MIN_LEN + 1) /**< 最小完整帧长度header+type+len+payload+crc = 6 */
#define PROTOCOL_MAX_FRAME_LEN 16 /**< 最大完整帧长度 */
#define PROTOCOL_MAX_FRAME_LEN 32 /**< 最大完整帧长度 */
/** @} */
/** @name 响应帧标识符
* @{ */
#define RESP_HEADER 0xB5 /**< 响应帧包头标识 */
#define RESP_TYPE_OK 0xF0 /**< 成功响应类型 */
#define RESP_TYPE_CRC_ERR 0xF1 /**< CRC校验错误 */
#define RESP_TYPE_HEADER_ERR 0xF2 /**< 包头错误 */
#define RESP_TYPE_TYPE_ERR 0xF3 /**< 类型错误 */
#define RESP_TYPE_LEN_ERR 0xF4 /**< 长度错误 */
#define RESP_HEADER 0xB5 /**< 响应帧包头标识 >**/
#define RESP_TYPE_OK 0xF0 /**< 成功响应类型 >**/
#define RESP_TYPE_CRC_ERR 0xF1 /**< CRC校验错误 >**/
#define RESP_TYPE_HEADER_ERR 0xF2 /**< 包头错误 >**/
#define RESP_TYPE_TYPE_ERR 0xF3 /**< 类型错误 >**/
#define RESP_TYPE_LEN_ERR 0xF4 /**< 长度错误 >**/
#define RESP_TYPE_PARAM_ERR 0xFD /**< 参数错误 >**/
#define RESP_TYPE_CMD_ERR 0xFE /**< 命令错误 >**/
#define RESP_TYPE_ERR 0xFF /**< 通用错误 >**/
/** @} */
/* ============================================================================
@@ -78,10 +83,29 @@
/** @name 预设响应数据
* @{ */
static const uint8_t s_report_status_ok[] = { 'o', 'k' }; /**< 成功响应数据 */
static const uint8_t s_report_status_ok[] __attribute__((unused)) = { 'o', 'k' }; /**< 成功响应数据 */
static const uint8_t s_report_status_err[] = { 'e','r','r' }; /**< 错误响应数据 */
/** @} */
void system_software_reset(void)
{
// 确保所有待处理的内存访问完成
__DSB();
// 执行系统复位
NVIC_SystemReset();
// 以下代码不会执行
while(1);
}
/* Debug output control */
#ifdef COM_DEBUG
#include <stdio.h>
#define COMMAND_DEBUG(fmt, ...) printf("[COMMAND] " fmt "\n", ##__VA_ARGS__)
#else
#define COMMAND_DEBUG(fmt, ...)
#endif
/* ============================================================================
* 公共接口函数
* ============================================================================ */
@@ -144,12 +168,12 @@ static void send_response(uint8_t type, const uint8_t *payload, uint8_t len)
// 使用GD32E230标准库函数逐字节发送标准库实现
for (uint8_t i = 0; i < buf_len; i++) {
// 等待发送缓冲区空
while (usart_flag_get(RS485_PHY, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(RS485_PHY, buf[i]);
while (usart_flag_get(UART_PHY, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(UART_PHY, buf[i]);
}
// 等待发送完成
while (usart_flag_get(RS485_PHY, USART_FLAG_TC) == RESET) {}
while (usart_flag_get(UART_PHY, USART_FLAG_TC) == RESET) {}
// // 使用printf发送通过重定向到串口
// for (uint8_t i = 0; i < buf_len; i++) {
@@ -160,6 +184,54 @@ static void send_response(uint8_t type, const uint8_t *payload, uint8_t len)
// fflush(stdout);
}
/**
* @brief 发送协议响应帧调试用发送到DEBUG_UART
* @details 构造并发送格式为 B5 TYPE LEN [payload] CRC 的响应帧,
* 自动计算CRC校验值并通过DEBUG_UART输出。
* @param type 响应类型码(如 RESP_TYPE_OK, RESP_TYPE_CRC_ERR 等)。
* @param payload 指向响应数据的缓冲区当len为0时可为NULL。
* @param len 响应数据长度字节为0时不复制payload数据。
* @note 内部使用固定大小缓冲区,超长响应将被丢弃。
* @warning 使用GD32E230标准库函数发送确保DEBUG_UART已正确初始化。
* @ingroup Command
*/
static void send_response_debug(uint8_t type, const uint8_t *payload, uint8_t len) __attribute__((unused));
static void send_response_debug(uint8_t type, const uint8_t *payload, uint8_t len)
{
uint8_t buf_len = (uint8_t)(3 + len + 1);
uint8_t buf[16]; // 简单场景足够,必要时可增大
if (buf_len > sizeof(buf)) return; // 防御
buf[0] = RESP_HEADER;
buf[1] = type;
buf[2] = len;
// 简化逻辑只有当len > 0且payload非空时才复制数据
if (len > 0 && payload != NULL) {
for (uint8_t i = 0; i < len; i++) {
buf[3 + i] = payload[i];
}
}
buf[buf_len - 1] = command_sum_crc_calc(buf, buf_len);
// 使用GD32E230标准库函数逐字节发送标准库实现
for (uint8_t i = 0; i < buf_len; i++) {
// 等待发送缓冲区空
while (usart_flag_get(DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(DEBUG_UART, buf[i]);
}
// 发送换行符 \r\n
// while (usart_flag_get(DEBUG_UART, USART_FLAG_TBE) == RESET) {}
// usart_data_transmit(DEBUG_UART, '\r');
// while (usart_flag_get(DEBUG_UART, USART_FLAG_TBE) == RESET) {}
// usart_data_transmit(DEBUG_UART, '\n');
// 等待发送完成
while (usart_flag_get(DEBUG_UART, USART_FLAG_TC) == RESET) {}
}
/**
* @brief 判断字符是否为十进制数字字符。
* @param c 待检查的字符ASCII码值
@@ -170,6 +242,57 @@ static void send_response(uint8_t type, const uint8_t *payload, uint8_t len)
*/
static inline bool is_dec_digit(uint8_t c) { return (c >= '0' && c <= '9'); }
/**
* @brief 将一个无符号整数转换为字符串并追加到缓冲区。
* @param value 要转换的数字。
* @param buffer 指向目标缓冲区的指针,转换后的字符串将写入此处。
* @return uint8_t 写入的字符数。
*/
static uint8_t uint_to_str(uint32_t value, char *buffer) {
char temp[10]; // 32位无符号整数最多10位
int i = 0;
if (value == 0) {
buffer[0] = '0';
return 1;
}
// 将数字逆序转换为字符存入临时数组
while (value > 0) {
temp[i++] = (char)((value % 10) + '0');
value /= 10;
}
// 将逆序的字符串反转并存入目标缓冲区
uint8_t len = (uint8_t)i;
for (int j = 0; j < len; j++) {
buffer[j] = temp[--i];
}
return len;
}
/**
* @brief 将有符号整数转换为字符串
* @param value 要转换的数字
* @param buffer 目标缓冲区
* @return uint8_t 写入的字符数
*/
static uint8_t __attribute__((unused)) int_to_str(int32_t value, char *buffer) {
uint8_t len = 0;
if (value < 0) {
buffer[0] = '-';
len++;
// 处理最小负数溢出问题 (虽然int16不会溢出int32但为了健壮性)
// 这里直接取反转为正数处理
value = -value;
}
len += uint_to_str((uint32_t)value, &buffer[len]);
return len;
}
/**
* @brief 从缓冲区解析十进制无符号整数。
* @details 从指定位置开始连续读取十进制数字字符累加构成32位无符号整数。
@@ -194,10 +317,78 @@ static uint8_t parse_uint_dec(const uint8_t *s, uint8_t n, uint32_t *out)
i++;
}
if (i == 0) return 0; // 未读到数字
if (out) *out = v; //
if (out) *out = v; //
return i;
}
/**
* @brief 通用T参数解析函数
* @details 解析命令中的T参数定时参数格式为T<数字>
* @param cmd 指向命令缓冲区
* @param cmd_index 当前解析位置的指针(会被更新)
* @param cmd_len 命令总长度
* @param timer_value 输出参数,存储解析到的定时值
* @return bool 解析结果
* @retval true 成功解析到T参数
* @retval false 没有T参数或解析失败
* @note 如果解析成功cmd_index会被更新到T参数后的位置
* @ingroup Command
*/
static bool __attribute__((unused)) parse_timer_parameter(const uint8_t *cmd, uint8_t *cmd_index, uint8_t cmd_len, uint32_t *timer_value)
{
if (*cmd_index >= cmd_len || cmd[*cmd_index] != 'T') {
return false; // 没有T参数
}
uint8_t temp_index = *cmd_index + 1; // T后的位置
const uint8_t used_timer_cmd = parse_uint_dec(&cmd[temp_index], (uint8_t)(cmd_len - temp_index), timer_value);
if (used_timer_cmd == 0) {
return false; // T后面没有有效数字
}
*cmd_index = (uint8_t)(temp_index + used_timer_cmd); // 更新索引
return true;
}
/**
* @brief 检查命令是否完全解析完毕
* @details 验证命令中是否还有未解析的字符,用于格式验证
* @param cmd_index 当前解析位置
* @param cmd_len 命令总长度
* @return bool 检查结果
* @retval true 命令完全解析完毕
* @retval false 还有未解析的字符
* @ingroup Command
*/
static bool __attribute__((unused)) is_command_fully_parsed(uint8_t cmd_index, uint8_t cmd_len)
{
return (cmd_index == cmd_len);
}
/**
* @brief 在命令字符串中查找指定参数的值
* @param cmd 指向命令起始位置Mxxx 之后)
* @param cmd_len 命令剩余长度
* @param key 要查找的参数字符(如 'P', 'S'
* @param value 输出参数,存储解析到的值
* @return bool 是否找到该参数
*/
static bool __attribute__((unused)) find_parameter_value(const uint8_t *cmd, uint8_t cmd_len, char key, uint32_t *value)
{
for (uint8_t i = 0; i < cmd_len; i++) {
if (cmd[i] == (uint8_t)key) {
uint8_t param_idx = i + 1;
if (param_idx >= cmd_len) return false;
if (parse_uint_dec(&cmd[param_idx], cmd_len - param_idx, value) > 0) {
return true;
}
return false;
}
}
return false;
}
/* ============================================================================
* 命令处理函数
* ============================================================================ */
@@ -219,6 +410,7 @@ static uint8_t parse_uint_dec(const uint8_t *s, uint8_t n, uint32_t *out)
* @warning 假设输入帧已通过基本协议校验包头、类型、CRC等
* @ingroup Command
*/
void handle_command(const uint8_t *frame, uint8_t len) {
// 帧格式D5 03 LEN [cmd] CRC; cmd 支持变长,如 "M1"、"M10"、"M201"、"M123S400",有最小长度限制和命令长度校验
uint8_t cmd_len = frame[2];
@@ -246,60 +438,60 @@ void handle_command(const uint8_t *frame, uint8_t len) {
cmd_index = (uint8_t)(cmd_index + used_base_cmd); // 更新索引到命令后
// 情况A无附加参数的基础命令
if (cmd_index == cmd_len) {
// 仅基础命令,如 M1, M2, M3
switch (base_cmd) {
switch (base_cmd) {
case 1u: // M1
send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok));
return;
// case 2u: // M2
// return;
// case 3u:
// return;
// case 4u:
// return;
// case 201u: // M201命令
// send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok));
// return;
default:
// 其它无参数命令在此扩展示例M100处理逻辑该如何待定
// send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok));
// return;
break;
}
// 未在处理列表的无参数基础命令,回复错误
send_response(RESP_TYPE_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err));
return;
}
// 情况B有附加参数的命令
if (cmd[cmd_index] == 'S') {
cmd_index++;
uint32_t param_value = 0;
const uint8_t used_param_cmd = parse_uint_dec(&cmd[cmd_index], (uint8_t)(cmd_len - cmd_index), &param_value);
if (used_param_cmd == 0) {
// 'S' 后没有数字,格式错误
send_response(RESP_TYPE_LEN_ERR, s_report_status_err, sizeof(s_report_status_err));
/* ==========================================
* M888 软件重启命令
* ========================================== */
case 888u:
// 先发送确认响应
send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok));
// 短暂延时确保响应发送完成
delay_ms(100);
// 执行软件重启
system_software_reset();
return;
}
switch (base_cmd)
{
// case 100u:
// // set_pwm(param_value);
// printf("Set PWM to %u\n", param_value);
// return;
/* ==========================================
* M999 输出固件版本号命令
* ========================================== */
case 999u: //M999: 输出固件版本号
{
char version_str[16];
char *p = version_str;
*p++ = 'v';
p += uint_to_str(BOARD_TYPE_CODE, p);
*p++ = '.';
p += uint_to_str(FW_VERSION_MAJOR, p);
*p++ = '.';
p += uint_to_str(FW_VERSION_MINOR, p);
*p++ = '.';
p += uint_to_str(FW_VERSION_PATCH, p);
*p = '\0'; // null-terminate for printf safety
uint8_t n = (uint8_t)(p - version_str);
send_response(RESP_TYPE_OK, (uint8_t *)version_str, n);
COMMAND_DEBUG("Firmware Version: %s", version_str);
}
return;
/* ==========================================
* M9999 进入OTA模式
* ========================================== */
case 9999u: //M9999: 进入OTA模式
__disable_irq(); // 关中断,防止竞态条件
NVIC_SystemReset(); // 触发系统复位进入Bootloader
return;
default:
send_response(RESP_TYPE_CMD_ERR, s_report_status_err, sizeof(s_report_status_err));
break;
}
send_response(RESP_TYPE_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err));
}
}
@@ -307,7 +499,7 @@ void handle_command(const uint8_t *frame, uint8_t len) {
* @brief 处理串口环形缓冲区中的命令数据,解析完整的协议帧。
* @details 本函数实现一个基于状态机的协议解析器,用于处理格式为 D5 03 LEN [cmd] CRC 的命令帧:
* - 状态1等待包头字节 PROTOCOL_PACKAGE_HEADER (0xD5)
* - 状态2接收板卡类型字节 PROTOCOL_BOARD_TYPE (0x03)
* - 状态2接收板卡类型字节 PROTOCOL_BOARD_TYPE
* - 状态3接收长度字段并计算期望的完整帧长度
* - 状态4继续接收剩余数据直到完整帧
* - 状态5对完整帧进行校验包头、板卡类型、CRC并处理
@@ -350,6 +542,7 @@ void command_process(void) {
// 防御:缓冲溢出,复位状态机
cmd_len = 0;
expected_cmd_len = 0;
continue;
}
// 缓存后续字节
@@ -393,6 +586,9 @@ void command_process(void) {
if (verification_status) {
handle_command(cmd_buf, expected_cmd_len);
} else {
// 验证失败时清空缓冲区,避免后续帧受影响
uart_ring_buffer_clear();
}
// 复位,等待下一帧
@@ -401,3 +597,45 @@ void command_process(void) {
}
}
}
/**
* @brief 执行命令(简化版)
* @details 根据命令字符串直接构造命令帧并执行,无需手动构造协议帧
* @param cmd_str 命令字符串(如"M730S0T1000"、"M731S100"等)
* @note 简化的测试函数自动处理协议帧构造、CRC计算和命令执行
* @ingroup Command
*/
void command_execute(const char *cmd_str)
{
if (cmd_str == NULL) return;
uint8_t cmd_len = (uint8_t)strlen(cmd_str);
uint8_t frame_len = 3 + cmd_len + 1; // header + type + len + cmd + crc
uint8_t frame_buf[32]; // 简单固定缓冲区
if (frame_len > sizeof(frame_buf)) return;
// 构造命令帧
frame_buf[0] = PROTOCOL_PACKAGE_HEADER; // 0xD5
frame_buf[1] = PROTOCOL_BOARD_TYPE; // Board Type
frame_buf[2] = cmd_len; // 命令长度
// 复制命令数据
for (uint8_t i = 0; i < cmd_len; i++) {
frame_buf[3 + i] = (uint8_t)cmd_str[i];
}
// 计算CRC
uint16_t crc = 0;
for (uint8_t i = 1; i < (frame_len - 1); i++) {
crc += frame_buf[i];
}
frame_buf[frame_len - 1] = (uint8_t)(crc & 0xFF);
// 清空缓冲区并执行命令
uart_ring_buffer_clear();
for (uint8_t i = 0; i < frame_len; i++) {
uart_ring_buffer_put(frame_buf[i]);
}
command_process();
}