feat(vd960DBN): 实现 DLD960Loop 串口通信协议 (0x7F)

新增:
- docs/DLD960Loop_串口通信协议.md — 协议文档 V1.02
- loop_uart_proto.h/c — 协议实现: checksum/组包/解析/帧状态机/命令状态机

修改:
- usart_biz.c: 使用 lup_feed_byte() 帧解析器替代 timeout heuristic; 波特率修正为 115200
- tcp_json_srv.c/h: loop_param_set/query 真实实现(0x63/0x64), 0xC0 传感器推流, 延迟响应机制
- peripheral_main.c: 添加 tcp_json_push_sensor() 调用, 帧解析器超时保护

校验验证: 5个协议例程 XOR+SUM 全部通过
This commit is contained in:
wangfq
2026-07-02 09:26:34 +08:00
parent 4e75312a0f
commit 4fbda96078
7 changed files with 1522 additions and 64 deletions

View File

@@ -0,0 +1,679 @@
/**
******************************************************************************
* @file loop_uart_proto.c
* @author wangfq
* @version V1.0
* @date 2026-07-02
* @brief DLD960Loop 串口通信协议实现
* - 校验计算 (XOR + SUM)
* - 命令组包 / 响应解析
* - 帧接收状态机 (替代 timeout heuristic)
* - 命令发送 + 超时跟踪
******************************************************************************
*/
#include "CONFIG.h"
#include "loop_uart_proto.h"
#include "cmcng.h"
#include <string.h>
/*===========================================================================
* Global State
*===========================================================================*/
LUP_CmdTracker g_lup_cmd = {0, 0, 0, LUP_STATE_IDLE, {0}, 0};
/* --- Frame Parser --- */
LUP_FrameParser g_lup_parser = {
.state = LUP_FRAME_STATE_IDLE,
.idx = 0,
.value_len = 0,
.value_idx = 0
};
/*===========================================================================
* Checksum
*===========================================================================*/
/*
* XOR 校验: 从 Addr 字节开始,到命令数据的最后一个字节结束
*/
uint8_t lup_calc_xor(const uint8_t *data, uint16_t len)
{
uint8_t x = 0;
uint16_t i;
for (i = 0; i < len; i++) {
x ^= data[i];
}
return x;
}
/*
* SUM 校验: 从 Addr 字节开始,到命令数据的最后一个字节结束
*/
uint8_t lup_calc_sum(const uint8_t *data, uint16_t len)
{
uint8_t s = 0;
uint16_t i;
for (i = 0; i < len; i++) {
s += data[i];
}
return s;
}
/*
* 校验整包: pkg = [7F] [Addr] [LEN] [CMD] [Data...] [XOR] [SUM]
* Checksum covers: Addr + LEN_field + Value (=CMD + Data) = 2 + LEN bytes
* starting at pkg[1]
*/
int lup_verify_checksum(const uint8_t *pkg, uint16_t len)
{
if (len < 6) return -1; // 最小: 7F + 3 header + 2 checksum = 6
uint8_t pkg_len = pkg[2]; // LEN field
uint16_t check_len = 2 + pkg_len; // Addr(1)+LEN_field(1)+Value(LEN)
if (len < check_len + 3) return -4; // Not enough data: Magic(1) + covered + Check(2)
uint8_t expect_xor = lup_calc_xor(pkg + 1, check_len);
uint8_t expect_sum = lup_calc_sum(pkg + 1, check_len);
uint8_t got_xor = pkg[1 + check_len];
uint8_t got_sum = pkg[2 + check_len];
if (expect_xor != got_xor) return -2;
if (expect_sum != got_sum) return -3;
return 0;
}
/*
* 为已填充好 Data 的 buf 追加 checksum。
* buf 格式: [7F] [Addr] [LEN] [CMD] [Data...]
* Checksum 覆盖: Addr(1) + LEN(1) + Value(LEN) = 2 + LEN bytes
* Output: [7F][Addr][LEN][CMD][Data...][XOR][SUM]
*/
void lup_append_checksum(uint8_t *pkg)
{
// pkg[0]=7F, pkg[1]=Addr, pkg[2]=LEN, pkg[3]=CMD, ...
uint8_t total_len = 1 + 3 + pkg[2] + 2; // Magic(1) + Header(3) + Value(LEN) + Check(2)
uint16_t check_len = 2 + pkg[2]; // Addr(1) + LEN_field(1) + Value(LEN)
pkg[total_len - 2] = lup_calc_xor(pkg + 1, check_len);
pkg[total_len - 1] = lup_calc_sum(pkg + 1, check_len);
}
/*===========================================================================
* Packet Builders
*===========================================================================*/
/*
* 0x4A — 获取设备版本号
* Req: 7F 00 01 4A 4B 4B
*/
uint16_t lup_build_get_version(uint8_t *buf)
{
buf[0] = LUP_MAGIC; // 7F
buf[1] = LUP_ADDR_DEFAULT; // 0x00
buf[2] = 0x01; // LEN = 1 (CMD byte)
buf[3] = LUP_CMD_GET_VERSION; // 0x4A
lup_append_checksum(buf);
return 6; // Magic(1) + Header(3) + Check(2)
}
/*
* 0x6D — 设备复位 (无回复)
* Req: 7F 00 01 6D 6C 6E
*/
uint16_t lup_build_reset(uint8_t *buf)
{
buf[0] = LUP_MAGIC;
buf[1] = LUP_ADDR_DEFAULT;
buf[2] = 0x01;
buf[3] = LUP_CMD_RESET;
lup_append_checksum(buf);
return 6;
}
/*
* 0x92 — 出厂初始化
* Req: 7F 00 01 92 93 93
*/
uint16_t lup_build_factory_init(uint8_t *buf)
{
buf[0] = LUP_MAGIC;
buf[1] = LUP_ADDR_DEFAULT;
buf[2] = 0x01;
buf[3] = LUP_CMD_FACTORY_INIT;
lup_append_checksum(buf);
return 6;
}
/*
* 0x8A — 读灵敏度列表
* Req: 7F 00 04 8A 00 00 ...
*/
uint16_t lup_build_sensitivity_read(uint8_t *buf)
{
buf[0] = LUP_MAGIC;
buf[1] = LUP_ADDR_DEFAULT;
buf[2] = 0x04; // LEN = 4 (CMD + R/W + Amount + reserved?)
buf[3] = LUP_CMD_SENSITIVITY; // 0x8A
// Value: R/W + Amount (2 bytes, all zeros for read)
// 协议说 R/W: 1 byte, Amount: 1 byte, 后面是 Amount * (SensityIn + SensityOut)
// 对于 Read: R/W=0, Amount 忽略
buf[4] = LUP_SENS_RW_READ;
buf[5] = 0x00; // Amount (ignored for read)
buf[6] = 0x00; // padding?
buf[7] = 0x00; // padding?
lup_append_checksum(buf);
return 10; // Magic(1)+Header(3)+Value(4)+Check(2)
}
/*
* 0x8A — 写灵敏度列表
*/
uint16_t lup_build_sensitivity_write(uint8_t *buf, const LUP_Sensitivity *sens)
{
uint8_t i;
uint16_t idx = 0;
buf[idx++] = LUP_MAGIC;
buf[idx++] = LUP_ADDR_DEFAULT;
// LEN field: 2 + 2*sens->amount
uint8_t val_len = 2 + 2 * sens->amount;
buf[idx++] = val_len;
buf[idx++] = LUP_CMD_SENSITIVITY;
buf[idx++] = sens->rw; // 0x01 (Write)
buf[idx++] = sens->amount;
for (i = 0; i < sens->amount; i++) {
buf[idx++] = (uint8_t)(sens->sens_in[i] & 0xFF);
buf[idx++] = (uint8_t)((sens->sens_in[i] >> 8) & 0xFF);
buf[idx++] = (uint8_t)(sens->sens_out[i] & 0xFF);
buf[idx++] = (uint8_t)((sens->sens_out[i] >> 8) & 0xFF);
}
lup_append_checksum(buf);
return idx + 2; // +2 for checksum bytes
}
/*
* 0x63 — 设置多路参数
* Value: AutoMode(1) + Amount(1) + Amount*(Param[5])
*/
uint16_t lup_build_set_param(uint8_t *buf, const LUP_ParamSet *ps)
{
uint8_t i;
uint16_t idx = 0;
buf[idx++] = LUP_MAGIC;
buf[idx++] = LUP_ADDR_DEFAULT;
// LEN = 2 + 5 * Amount
uint8_t val_len = 2 + 5 * ps->amount;
buf[idx++] = val_len;
buf[idx++] = LUP_CMD_SET_PARAM;
buf[idx++] = ps->auto_mode;
buf[idx++] = ps->amount;
for (i = 0; i < ps->amount; i++) {
buf[idx++] = ps->params[i].sensitivity;
buf[idx++] = ps->params[i].loop_delay;
buf[idx++] = ps->params[i].output_mode;
buf[idx++] = ps->params[i].exist_mode;
buf[idx++] = ps->params[i].direction_mode;
}
lup_append_checksum(buf);
return idx + 2;
}
/*
* 0x64 — 读取多路参数
* Req: 7F 00 01 64 ...
*/
uint16_t lup_build_get_param(uint8_t *buf)
{
buf[0] = LUP_MAGIC;
buf[1] = LUP_ADDR_DEFAULT;
buf[2] = 0x01; // LEN = 1
buf[3] = LUP_CMD_GET_PARAM; // 0x64
lup_append_checksum(buf);
return 6;
}
/*
* 0xC0 mid-packet ACK
* Req: 7F | 00 | 07 | C0 | SensType | Seq | SubAmount | XOR | SUM
*/
uint16_t lup_build_sensor_ack(uint8_t *buf, uint8_t sens_type,
uint8_t seq, uint8_t sub_amount)
{
buf[0] = LUP_MAGIC;
buf[1] = LUP_ADDR_DEFAULT;
buf[2] = 0x07; // LEN = 7 (CMD + SensType + Seq + SubAmount)
buf[3] = LUP_CMD_SENSOR_REPORT;
buf[4] = sens_type;
buf[5] = seq;
buf[6] = sub_amount;
buf[7] = 0x00; // padding
buf[8] = 0x00;
buf[9] = 0x00;
buf[10] = 0x00;
lup_append_checksum(buf);
return 13; // Magic(1)+Header(3)+Value(7)+Check(2)
}
/*===========================================================================
* Packet Parsers
*===========================================================================*/
/*
* Parse 0x4A Response:
* Header: 00 08 4A (Addr, LEN=8, CMD)
* Value: Status + Hard_Main + Hard_Sub + Hard_SSub + Soft_Main + Soft_Sub + Soft_SSub
*/
int lup_parse_version(const uint8_t *pkg, uint16_t len, LUP_VersionInfo *info)
{
if (len < 10) return -1; // 1+3+7+2 = 13 min
// pkg layout: [7F][Addr=0][LEN=8][CMD=0x4A][Status...7bytes][XOR][SUM]
if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_GET_VERSION) return -2;
if (pkg[2] < 7) return -3; // LEN should be >= 7
const uint8_t *val = pkg + 4; // skip magic + header(3)
info->status = val[0];
info->hard_main = val[1];
info->hard_sub = val[2];
info->hard_ssub = val[3];
info->soft_main = val[4];
info->soft_sub = val[5];
info->soft_ssub = val[6];
return 0;
}
/*
* Parse 0x92 Response:
* Value: 00/01 (00=success)
*/
int lup_parse_factory_init_resp(const uint8_t *pkg, uint16_t len, uint8_t *success)
{
if (len < 7) return -1;
if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_FACTORY_INIT) return -2;
*success = (pkg[4] == 0x00) ? 1 : 0;
return 0;
}
/*
* Parse 0x8A Response:
* Value: Amount (1byte) + Amount * SensityValue (2bytes each)
*/
int lup_parse_sensitivity_resp(const uint8_t *pkg, uint16_t len, LUP_Sensitivity *sens)
{
uint8_t i;
if (len < 6) return -1;
if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_SENSITIVITY) return -2;
const uint8_t *val = pkg + 4;
uint8_t amount = val[0];
if (amount > LUP_COIL_COUNT) amount = LUP_COIL_COUNT;
sens->amount = amount;
sens->rw = 0; // response, not request
for (i = 0; i < amount; i++) {
uint16_t sv = val[1 + i * 2] | ((uint16_t)val[2 + i * 2] << 8);
// Response returns unified SensityValue (not split in/out)
sens->sens_in[i] = sv;
sens->sens_out[i] = sv;
}
return 0;
}
/*
* Parse 0x64 Response:
* Value: AutoMode(1) + Amount(1) + Amount * [Sensitivity, Loop_Delay, Output_Mode,
* Exist_Mode, Direction_Mode, freq1(2), freq2(2), freq3(2)]
* Each coil = 8 bytes
*/
int lup_parse_param_get(const uint8_t *pkg, uint16_t len, LUP_ParamGet *pg)
{
uint8_t i;
if (len < 6) return -1;
if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_GET_PARAM) return -2;
const uint8_t *val = pkg + 4;
pg->auto_mode = val[0];
pg->amount = val[1];
if (pg->amount > LUP_COIL_COUNT) pg->amount = LUP_COIL_COUNT;
for (i = 0; i < pg->amount; i++) {
const uint8_t *c = val + 2 + i * 8;
pg->params[i].sensitivity = c[0];
pg->params[i].loop_delay = c[1];
pg->params[i].output_mode = c[2];
pg->params[i].exist_mode = c[3];
pg->params[i].direction_mode = c[4];
pg->freq[i][0] = c[5] | ((uint32_t)c[6] << 8);
pg->freq[i][1] = c[7] | ((uint32_t)c[8] << 8);
pg->freq[i][2] = c[9] | ((uint32_t)c[10] << 8);
}
return 0;
}
/*
* Parse 0x63 Response (Set Param):
* Value: Status(1 byte, 0x00=success)
*/
int lup_parse_set_param_resp(const uint8_t *pkg, uint16_t len, uint8_t *success)
{
if (len < 7) return -1;
if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_SET_PARAM) return -2;
*success = (pkg[4] == 0x00) ? 1 : 0;
return 0;
}
/*
* Parse 0xC0/0x0C — 多路线圈传感信息
*
* 每路线圈数据单元 = 11 bytes:
* [0] 配置1: freq_level(2) | Direction(1) | freq_type(1) | sens(4)
* [1] 评估条件: condition(4) | loop_state(1) | car_state(1) | misc_type(2)
* [2-4] 频率 (3 bytes, LE)
* [5-6] 变化量 (2 bytes, LE)
* [7-10] 杂项 (4 bytes)
*/
int lup_parse_sensor_report(const uint8_t *pkg, uint16_t len, LUP_SensorReport *report)
{
uint8_t i;
if (len < 9) return -1; // 7F + 3 header + 2 sensType/SubPkgFlag + 2 checksum min
if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_SENSOR_REPORT) return -2;
uint8_t sens_type = pkg[4];
if (sens_type != LUP_SENS_TYPE_MULTI_COIL) return -3;
uint8_t sub_pkg = pkg[5];
report->sens_type = sens_type;
report->sub_amount = (sub_pkg >> 4) & 0x0F;
report->sub_sequence = sub_pkg & 0x0F;
// 计算线圈数: Value = sensType(1) + SubPkgFlag(1) + coil_count * 11
uint16_t val_len = pkg[2]; // LEN field
uint16_t data_offset = 6; // 跳过 7F + Addr + LEN + CMD + SensType + SubPkgFlag
uint16_t data_len = val_len - 2; // 减去 SensType + SubPkgFlag
uint8_t coil_count = data_len / 11;
if (coil_count > LUP_COIL_COUNT) coil_count = LUP_COIL_COUNT;
report->coil_count = coil_count;
for (i = 0; i < coil_count; i++) {
const uint8_t *c = pkg + data_offset + i * 11;
LUP_CoilSensor *cs = &report->coils[i];
cs->freq_level = (c[0] >> 6) & 0x03;
cs->direction = (c[0] >> 5) & 0x01;
cs->freq_type = (c[0] >> 4) & 0x01;
cs->sensitivity = c[0] & 0x0F;
cs->condition = (c[1] >> 4) & 0x0F;
cs->loop_state = (c[1] >> 3) & 0x01;
cs->car_state = (c[1] >> 2) & 0x01;
cs->misc_type = c[1] & 0x03;
cs->freq = c[2] | ((uint32_t)c[3] << 8) | ((uint32_t)c[4] << 16);
cs->variation = c[5] | ((uint16_t)c[6] << 8);
cs->misc.passtime_ms5 = c[7] | ((uint32_t)c[8] << 8)
| ((uint32_t)c[9] << 16) | ((uint32_t)c[10] << 24);
}
return 0;
}
/*===========================================================================
* Command State Machine
*===========================================================================*/
void lup_cmd_begin(uint8_t cmd, uint32_t timeout_ms)
{
g_lup_cmd.pending_cmd = cmd;
g_lup_cmd.send_tick = mstick();
g_lup_cmd.timeout_ms = timeout_ms;
g_lup_cmd.state = LUP_STATE_WAIT_RESPONSE;
g_lup_cmd.resp_len = 0;
memset(g_lup_cmd.resp_buf, 0, sizeof(g_lup_cmd.resp_buf));
}
void lup_cmd_done(void)
{
g_lup_cmd.pending_cmd = 0;
g_lup_cmd.state = LUP_STATE_IDLE;
g_lup_cmd.resp_len = 0;
}
int lup_cmd_check_timeout(void)
{
if (g_lup_cmd.state != LUP_STATE_WAIT_RESPONSE) return 0;
if (mstick() - g_lup_cmd.send_tick > g_lup_cmd.timeout_ms) {
g_lup_cmd.state = LUP_STATE_TIMEOUT;
PRINT("LUP: cmd 0x%02X timeout\n", g_lup_cmd.pending_cmd);
return 1;
}
return 0;
}
/*
* 收到响应时调用 — 如果当前有挂起命令且 CMD 匹配,则保存响应
*/
void lup_cmd_on_response(const uint8_t *pkg, uint16_t len)
{
if (g_lup_cmd.state != LUP_STATE_WAIT_RESPONSE) return;
if (len < 4) return;
uint8_t resp_cmd = pkg[3];
if (resp_cmd == g_lup_cmd.pending_cmd) {
// 保存响应数据
uint16_t copy_len = (len < sizeof(g_lup_cmd.resp_buf)) ? len : sizeof(g_lup_cmd.resp_buf);
memcpy(g_lup_cmd.resp_buf, pkg, copy_len);
g_lup_cmd.resp_len = copy_len;
g_lup_cmd.state = LUP_STATE_RESPONSE_READY;
}
}
/*===========================================================================
* High-level Send Commands
*===========================================================================*/
void lup_send_get_version(void)
{
uint8_t buf[LUP_MAX_PKG_LEN];
uint16_t len = lup_build_get_version(buf);
UART2_SendString(buf, len);
lup_cmd_begin(LUP_CMD_GET_VERSION, 200); // 200ms timeout
}
void lup_send_reset(void)
{
uint8_t buf[LUP_MAX_PKG_LEN];
uint16_t len = lup_build_reset(buf);
UART2_SendString(buf, len);
// No response expected
}
void lup_send_factory_init(void)
{
uint8_t buf[LUP_MAX_PKG_LEN];
uint16_t len = lup_build_factory_init(buf);
UART2_SendString(buf, len);
lup_cmd_begin(LUP_CMD_FACTORY_INIT, 500);
}
void lup_send_sensitivity_read(void)
{
uint8_t buf[LUP_MAX_PKG_LEN];
uint16_t len = lup_build_sensitivity_read(buf);
UART2_SendString(buf, len);
lup_cmd_begin(LUP_CMD_SENSITIVITY, 200);
}
void lup_send_set_param(const LUP_ParamSet *ps)
{
uint8_t buf[LUP_MAX_PKG_LEN];
uint16_t len = lup_build_set_param(buf, ps);
UART2_SendString(buf, len);
lup_cmd_begin(LUP_CMD_SET_PARAM, 500);
}
void lup_send_get_param(void)
{
uint8_t buf[LUP_MAX_PKG_LEN];
uint16_t len = lup_build_get_param(buf);
UART2_SendString(buf, len);
lup_cmd_begin(LUP_CMD_GET_PARAM, 500);
}
/*===========================================================================
* Frame Parser (called per-byte from USART2 ISR / timer context)
*
* 帧格式: [7F] [Addr] [LEN] [CMD] [Value: LEN bytes] [XOR] [SUM]
* LEN = Value 字段的长度,包括 CMD byte
* 所以: Value data bytes = LEN - 1 (减掉 CMD)
*
* 状态机:
* IDLE → 找 0x7F → HEADER
* HEADER → 收完 3 bytes Header → VALUE (若 LEN>1)
* VALUE → 收完 data_bytes → CHECK
* CHECK → 收完 2 bytes → COMPLETE
*===========================================================================*/
void lup_frame_reset(void)
{
g_lup_parser.state = LUP_FRAME_STATE_IDLE;
g_lup_parser.idx = 0;
g_lup_parser.value_len = 0;
g_lup_parser.value_idx = 0;
memset(g_lup_parser.buf, 0, sizeof(g_lup_parser.buf));
}
const uint8_t *lup_frame_data(void)
{
return g_lup_parser.buf;
}
uint16_t lup_frame_len(void)
{
return g_lup_parser.idx;
}
/*
* 喂一个字节。返回 1 = 帧接收完成(在 buf 中)
*/
int lup_feed_byte(uint8_t byte)
{
LUP_FrameParser *p = &g_lup_parser;
switch (p->state) {
case LUP_FRAME_STATE_IDLE:
if (byte == LUP_MAGIC) {
p->buf[0] = byte;
p->idx = 1;
p->state = LUP_FRAME_STATE_HEADER;
}
break;
case LUP_FRAME_STATE_HEADER:
p->buf[p->idx++] = byte;
if (p->idx == 4) {
// Header 收完: [7F][Addr][LEN][CMD]
// Value data bytes = LEN - 1 (CMD byte 已计入 LEN)
uint8_t len_field = p->buf[2];
uint16_t data_bytes; // Value 中除 CMD 之外的 data 字节数
if (len_field < 1) {
data_bytes = 0;
} else {
data_bytes = len_field - 1;
}
if (data_bytes > LUP_MAX_VALUE_LEN) {
// 非法长度,丢弃
lup_frame_reset();
break;
}
if (data_bytes > 0) {
p->value_len = data_bytes;
p->value_idx = 0;
p->state = LUP_FRAME_STATE_VALUE;
} else {
// 无 Data直接等 Checksum
p->value_len = 0;
p->value_idx = 0;
p->state = LUP_FRAME_STATE_CHECK;
}
}
break;
case LUP_FRAME_STATE_VALUE:
p->buf[p->idx++] = byte;
p->value_idx++;
if (p->value_idx >= p->value_len) {
p->state = LUP_FRAME_STATE_CHECK;
}
break;
case LUP_FRAME_STATE_CHECK:
p->buf[p->idx++] = byte;
if (p->idx >= p->value_len + 1 + 3 + 2) { // Magic(1) + Header(3) + Value data + Check(2)
p->state = LUP_FRAME_STATE_COMPLETE;
return 1;
}
break;
case LUP_FRAME_STATE_COMPLETE:
// 不应该到达这里,调用方应在 COMPLETE 后 reset
lup_frame_reset();
break;
}
return 0;
}
/*
* 收到完整帧后的处理:
* 1. 校验 checksum
* 2. 如果是当前命令的响应 → 通知状态机
* 3. 如果是主动上报 (0xC0) → 处理传感器数据
*/
void lup_process_frame(const uint8_t *pkg, uint16_t len)
{
int csr;
uint8_t i;
if (len < 6) return;
// Debug: print raw
PRINT("LUP Rx:");
for (i = 0; i < len; i++) {
PRINT(" %02X", pkg[i]);
}
PRINT("\n");
// --- Checksum ---
csr = lup_verify_checksum(pkg, len);
if (csr != 0) {
PRINT("LUP: checksum fail (%d)\n", csr);
return;
}
// --- Dispatch by CMD ---
uint8_t cmd = pkg[3];
// Active reports (0xC0) are NOT responses to commands
if (cmd == LUP_CMD_SENSOR_REPORT) {
// Sensor report — will be handled in upper layer (uart_srv + TCP JSON)
// Don't consume as command response
return;
}
// Try matching with pending command
lup_cmd_on_response(pkg, len);
}