2025-08-12 19:56:56 +08:00

346 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "command.h"
#include "uart_ring_buffer.h"
#include "led.h"
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
/*
协议说明:
Host -> Device 帧格式:
[0] HEADER = 0xD5
[1] BOARD_TYPE = 0x03
[2] LEN = 数据区字节数
[3..(3+LEN-1)] 数据
[last] CRC = 从下标 1 到 (last-1) 的累加和低 8 位
最小协议包长度为 6 字节
数据示例(两字节命令):"M1" / "M2" / "M3"
Device -> Host 应答:复用 0xB5 开头:
[0] 0xB5, [1] TYPE(例如 0xF0=OK, 0xF1..=错误类), [2] LEN, [3..] payload, [last] CRC同上规则
*/
// 旧工程中的外部状态与复位函数(本工程暂不直接使用,按要求保留为注释):
// void fwdgt_reset_mcu(void); // 看门狗复位
#define PROTOCOL_PACKAGE_HEADER 0xD5
#define PROTOCOL_BOARD_TYPE 0x03
#define PROTOCOL_PACKAGE_LENGTH 0x02
#define COMMAND_MIN_LEN 2 // 最小命令长度如M1 M2命令
// 最小/最大整帧长度header(1)+type(1)+len(1)+payload(len>=2)+crc(1) = 3 + LEN + 1
#define PROTOCOL_MIN_FRAME_LEN (3 + COMMAND_MIN_LEN + 1)
#define PROTOCOL_MAX_FRAME_LEN 32
/* 可选的应答类型定义(与示例保持一致,可按需扩展) */
#define RESP_HEADER 0xB5
#define RESP_TYPE_OK 0xF0
#define RESP_TYPE_CRC_ERR 0xF1
#define RESP_TYPE_HEADER_ERR 0xF2
#define RESP_TYPE_TYPE_ERR 0xF3
#define RESP_TYPE_LEN_ERR 0xF4
static volatile bool s_sensor_report_enabled = false;
static volatile uint32_t s_cmd_param = 0; // 保存形如 M123S<param> 的附加参数(十进制),完整保留为 uint32_t
// 统一的应答负载常量
static const uint8_t s_report_status_ok[] = { 'o', 'k' };
static const uint8_t s_report_status_err[] = { 'e','r','r' };
/**
* @brief 查询是否启用周期性传感器上报。
* @return true 表示启用false 表示禁用。
* @ingroup Command
*/
bool get_sensor_report_enabled(void)
{
return s_sensor_report_enabled;
}
/**
* @brief 设置是否启用周期性传感器上报标志。
* @details 本模块内部保存的布尔状态,供其他逻辑决定是否进行周期性数据上报;
* 推荐通过本函数修改而非直接访问全局/静态变量,以便后续扩展(如加锁/回调)。
* @param enabled true 启用周期上报false 禁用。
* @ingroup Command
*/
void set_sensor_report_enabled(bool enabled)
{
s_sensor_report_enabled = enabled;
}
/**
* @brief 计算协议包的 8 位累加校验值Checksum
* @details 对输入缓冲区逐字节累加并取低 8 位,累加范围为 data[1] 至 data[len-2]
* 即不包含包头 HEADER索引 0与尾部 CRC 字节(索引 len-1
* 当 len 小于最小协议帧长度PACKAGE_MIN_LENGTH时返回 0。
* @param data 指向待校验的完整协议包缓冲区。
* @param len 缓冲区总长度(字节),应满足 header + type + len + payload + crc 的最小格式。
* @return uint8_t 计算得到的 8 位校验值。
* @note 本函数实现为简单求和校验Checksum非多项式 CRC与本协议“从索引 1 累加到 len-2”的规则一致。
* @ingroup Command
*/
static uint8_t command_sum_crc_calc(const uint8_t *data, uint8_t len)
{
uint16_t crc = 0;
// 仅在满足协议最小帧长时计算header + type + len + payload + crc
if (len < PROTOCOL_MIN_FRAME_LEN) return 0;
// 累加从索引 1 到 len-2 的字节(不含 header 和 crc 字节)
for (uint8_t i = 1; i < (len - 1); i++)
{
crc += data[i];
}
return (uint8_t)(crc & 0xFF);
}
/* 发送应答header(0xB5), type, len, payload[len], crc */
static void send_response(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;
for (uint8_t i = 0; i < len; i++) buf[3 + i] = payload ? payload[i] : 0;
buf[buf_len - 1] = command_sum_crc_calc(buf, buf_len);
for (uint8_t i = 0; i < buf_len; i++) {
printf("%c", buf[i]);
}
}
/* 简单数字工具 */
/**
* @brief 判断字符是否为十进制数字字符。
* @param c 输入字符ASCII范围通常为 0..255)。
* @return true 当且仅当 c 在 '0'..'9' 区间内;否则返回 false。
* @ingroup Command
*/
static inline bool is_dec_digit(uint8_t c) { return (c >= '0' && c <= '9'); }
/**
* @brief 从给定缓冲区前缀解析十进制无符号整数。
* @details 自 s[0] 起连续读取十进制数字字符 '0'..'9',累加成无符号 32 位整数,
* 最多读取 n 个字节;一旦遇到非数字或用尽 n 则停止。
* @param s 输入字符缓冲区起始(不保证以 '\0' 结束)。
* @param n 可供解析的最大字节数(从 s[0] 起)。
* @param out 若非 NULL输出解析出的数值当返回 0 时 out 不被更新)。
* @return uint8_t 实际消耗的数字字符个数;若首字符不是数字则返回 0。
* @note 本函数不处理空白、正负号及前缀;不做溢出检测,超过 uint32_t 的情形按无符号溢出语义累加。
* @ingroup Command
*/
static uint8_t parse_uint_dec(const uint8_t *s, uint8_t n, uint32_t *out)
{
uint8_t i = 0;
uint32_t v = 0;
while (i < n && is_dec_digit(s[i]))
{
v = v * 10u + (uint32_t)(s[i] - '0');
i++;
}
if (i == 0) return 0; // 未读到数字
if (out) *out = v; //
return i;
}
/**
* @brief 解析并处理一条完整的命令帧。
* @details 帧格式D5 03 LEN [cmd] CRCLEN 为命令负载字节数,[cmd] 以 'M' 开头:
* - 无参M<base>(例如 M1、M10、M201、M100
* - 带参M<base>S<param>param 为十进制uint32_t例如 M1S123、M22S456
* 本函数首先校验长度一致性3+LEN+1==len与最小帧长然后按上述规则解析 [cmd]
* 调用具体动作(如 led_on/off、状态开关并通过 send_response 回应。
* @param frame 指向完整帧的缓冲区(从 HEADER=0xD5 起始)。
* @param len 完整帧总长度(字节)。
* @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];
if (len < PROTOCOL_MIN_FRAME_LEN || (uint8_t)(3 + cmd_len + 1) != len) return; // 长度不匹配或者小于最小限制
const uint8_t *cmd = &frame[3]; // 提取命令部分
// 命令必须以 'M' 开头
if (cmd[0] != 'M'){
send_response(RESP_TYPE_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err));
return;
}
// 从 'M' 后开始解析
uint8_t cmd_index = 1;
// 解析M后的十进制数即命令本体
uint32_t base_cmd = 0;
uint8_t used_base_cmd = parse_uint_dec(&cmd[cmd_index], (cmd_len - cmd_index), &base_cmd);
if (used_base_cmd == 0)
{
// 'M' 后没有数字,格式错误
send_response(RESP_TYPE_LEN_ERR, s_report_status_err, sizeof(s_report_status_err));
return;
}
cmd_index = (uint8_t)(cmd_index + used_base_cmd); // 更新索引到命令后
// 情况A无附加参数的基础命令
if (cmd_index == cmd_len) {
// 仅基础命令,如 M1, M2, M3
switch (base_cmd) {
case 1u: // M1命令
led_on();
set_sensor_report_enabled(true);
led_on();
send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok));
return;
case 2u: // M2命令
led_off();
set_sensor_report_enabled(false);
led_off();
send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok));
return;
// 示例M3、M10、M201、M100 等(按需添加)
// case 3u: // M3命令
// send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok));
// return;
// case 10u: // M10命令
// send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok));
// 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));
return;
}
switch (base_cmd)
{
// case 100u:
// // set_pwm(param_value);
// printf("Set PWM to %u\n", param_value);
// return;
default:
break;
}
send_response(RESP_TYPE_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err));
}
}
void command_process(void) {
static uint8_t cmd_buf[PROTOCOL_MAX_FRAME_LEN];
static uint8_t cmd_len = 0;
static uint8_t expected_total = 0; // 0 表示尚未确定总长度
while (uart_ring_buffer_available() > 0) {
int byte = uart_ring_buffer_get();
if (byte < 0) break;
if (cmd_len == 0) {
if ((uint8_t)byte == PROTOCOL_PACKAGE_HEADER) {
cmd_buf[cmd_len++] = (uint8_t)byte;
expected_total = 0; // 等待进一步字段以确定长度
} else {
// 丢弃非起始字节
}
continue;
}
// 累积后续字节
cmd_buf[cmd_len++] = (uint8_t)byte;
// 当到达长度字段(索引 2确定总长度3 + LEN + 1
if (cmd_len == 3) {
uint8_t payload_len = cmd_buf[2];
expected_total = (uint8_t)(3 + payload_len + 1);
if (expected_total > PROTOCOL_MAX_FRAME_LEN) {
// 异常:长度超界,复位状态机
cmd_len = 0;
expected_total = 0;
}
continue;
}
if (expected_total > 0 && cmd_len == expected_total) {
// 到帧尾,进行各项校验
bool ok = true;
if (cmd_buf[0] != PROTOCOL_PACKAGE_HEADER) {
send_response(RESP_TYPE_HEADER_ERR, s_report_status_err, sizeof(s_report_status_err));
ok = false;
}
if (ok && cmd_buf[1] != PROTOCOL_BOARD_TYPE) {
send_response(RESP_TYPE_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err));
ok = false;
}
if (ok) {
uint8_t crc_calc = command_sum_crc_calc(cmd_buf, expected_total);
uint8_t crc_recv = cmd_buf[expected_total - 1];
if (crc_calc != crc_recv) {
send_response(RESP_TYPE_CRC_ERR, s_report_status_err, sizeof(s_report_status_err));
ok = false;
}
}
if (ok) {
handle_command(cmd_buf, expected_total);
}
// 复位,等待下一帧
cmd_len = 0;
expected_total = 0;
}
if (cmd_len >= PROTOCOL_MAX_FRAME_LEN) {
// 防御:缓冲溢出,复位状态机
cmd_len = 0;
expected_total = 0;
}
}
}
// -----------------------------------------------------------------------------
// 下列为与传感器相关的报告函数占位,按要求“保留但注释掉”。
// 原工程中依赖 ldc1612/tmp112a 读数并通过 0xB5 帧打印上报。
// 若后续集成这些传感器驱动,可解注释并补齐实现。
//
// static uint8_t package_header[3] = {0xB5, 0xF0, 0x04};
// static uint8_t package_data[4] = {0};
//
// void eddy_current_value_report(void) {
// uint32_t eddy_current_value_uint32 = ldc1612_get_raw_channel_result(CHANNEL_0);
// (void)eddy_current_value_uint32;
// // 按原协议组帧并 printf 发送B5 F0 04 [data3..data0] CRC
// }
//
// void tempture_value_report(void) {
// uint32_t temperature_uint32 = tmp112a_get_raw_channel_result();
// (void)temperature_uint32;
// // 按原协议组帧并 printf 发送B5 F0 04 [data3..data0] CRC
// }