feat: 添加状态报告和反馈功能,更新协议文档
This commit is contained in:
@@ -14,6 +14,8 @@
|
|||||||
#include "bsp_uart.h"
|
#include "bsp_uart.h"
|
||||||
#include "bsp_rc522.h"
|
#include "bsp_rc522.h"
|
||||||
#include "bsp_beep.h"
|
#include "bsp_beep.h"
|
||||||
|
#include "bsp_sr04.h"
|
||||||
|
#include "bsp_track_ir.h"
|
||||||
#include "checksum.h"
|
#include "checksum.h"
|
||||||
#include "cmsis_os.h"
|
#include "cmsis_os.h"
|
||||||
#include "elog.h"
|
#include "elog.h"
|
||||||
@@ -235,6 +237,7 @@ static void CarCtrl_HandleCommand(const char *cmd_payload)
|
|||||||
CarCtrl_StopAll();
|
CarCtrl_StopAll();
|
||||||
elog_w(Protocol_TAG, "已到达站点,请先重新设置目标站点 (GS:xxx) 之后再启动");
|
elog_w(Protocol_TAG, "已到达站点,请先重新设置目标站点 (GS:xxx) 之后再启动");
|
||||||
CarCtrl_BeepTimes(2U);
|
CarCtrl_BeepTimes(2U);
|
||||||
|
Protocol_SendFeedback("ST", 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +246,7 @@ static void CarCtrl_HandleCommand(const char *cmd_payload)
|
|||||||
CarCtrl_StopAll();
|
CarCtrl_StopAll();
|
||||||
elog_w(Protocol_TAG, "未设置有效站点,拒绝启动。请先下发 GS:001 或 GS:002");
|
elog_w(Protocol_TAG, "未设置有效站点,拒绝启动。请先下发 GS:001 或 GS:002");
|
||||||
CarCtrl_BeepTimes(2U);
|
CarCtrl_BeepTimes(2U);
|
||||||
|
Protocol_SendFeedback("ST", 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,13 +254,16 @@ static void CarCtrl_HandleCommand(const char *cmd_payload)
|
|||||||
car_target_reached = 0;
|
car_target_reached = 0;
|
||||||
elog_i(Protocol_TAG, "小车自动循迹启动, speed=%u%%, station=%u", car_speed_percent,
|
elog_i(Protocol_TAG, "小车自动循迹启动, speed=%u%%, station=%u", car_speed_percent,
|
||||||
car_target_station);
|
car_target_station);
|
||||||
|
Protocol_SendFeedback("ST", 1);
|
||||||
} else if (strcmp(arg + 1, "STOP") == 0) {
|
} else if (strcmp(arg + 1, "STOP") == 0) {
|
||||||
car_running = 0;
|
car_running = 0;
|
||||||
car_target_reached = 0;
|
car_target_reached = 0;
|
||||||
CarCtrl_StopAll();
|
CarCtrl_StopAll();
|
||||||
elog_i(Protocol_TAG, "小车自动循迹暂停");
|
elog_i(Protocol_TAG, "小车自动循迹暂停");
|
||||||
|
Protocol_SendFeedback("ST", 1);
|
||||||
} else {
|
} else {
|
||||||
elog_w(Protocol_TAG, "未知启动/停止命令: %s", cmd_payload);
|
elog_w(Protocol_TAG, "未知启动/停止命令: %s", cmd_payload);
|
||||||
|
Protocol_SendFeedback("ST", 0);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -278,8 +285,10 @@ static void CarCtrl_HandleCommand(const char *cmd_payload)
|
|||||||
if (car_running != 0U) {
|
if (car_running != 0U) {
|
||||||
CarCtrl_ApplySpeed();
|
CarCtrl_ApplySpeed();
|
||||||
}
|
}
|
||||||
|
Protocol_SendFeedback("SP", 1);
|
||||||
} else {
|
} else {
|
||||||
elog_w(Protocol_TAG, "速度参数解析失败: %s", cmd_payload);
|
elog_w(Protocol_TAG, "速度参数解析失败: %s", cmd_payload);
|
||||||
|
Protocol_SendFeedback("SP", 0);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -301,8 +310,10 @@ static void CarCtrl_HandleCommand(const char *cmd_payload)
|
|||||||
car_target_reached = 0;
|
car_target_reached = 0;
|
||||||
elog_i(Protocol_TAG, "设置目标站点: %03u", car_target_station);
|
elog_i(Protocol_TAG, "设置目标站点: %03u", car_target_station);
|
||||||
CarCtrl_BeepTimes(1U);
|
CarCtrl_BeepTimes(1U);
|
||||||
|
Protocol_SendFeedback("GS", 1);
|
||||||
} else {
|
} else {
|
||||||
elog_w(Protocol_TAG, "站点参数解析失败: %s", cmd_payload);
|
elog_w(Protocol_TAG, "站点参数解析失败: %s", cmd_payload);
|
||||||
|
Protocol_SendFeedback("GS", 0);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -311,6 +322,65 @@ static void CarCtrl_HandleCommand(const char *cmd_payload)
|
|||||||
elog_w(Protocol_TAG, "未支持的控制命令: %s", cmd_payload);
|
elog_w(Protocol_TAG, "未支持的控制命令: %s", cmd_payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 打包并发送协议帧到上位机 (TCP)
|
||||||
|
* @param payload 有效载荷文本 (不含帧头 LOGI:, 分隔符 :, 校验位 CS 和 帧尾 #)
|
||||||
|
*/
|
||||||
|
static void Protocol_SendPacket(const char *payload) {
|
||||||
|
char packet[64] = {0};
|
||||||
|
uint8_t cs = 0;
|
||||||
|
|
||||||
|
// 1. 组装基础段: LOGI:PAYLOAD
|
||||||
|
snprintf(packet, sizeof(packet), "LOGI:%s", payload);
|
||||||
|
|
||||||
|
// 2. 计算校验和 (从 LOGI 开始到 payload 结束的累加和)
|
||||||
|
cs = Calculate_CheckSum((uint8_t *)packet, 0, (uint16_t)strlen(packet));
|
||||||
|
|
||||||
|
// 3. 拼接校验位和帧尾
|
||||||
|
size_t current_len = strlen(packet);
|
||||||
|
snprintf(packet + current_len, sizeof(packet) - current_len, ":%02X#", cs);
|
||||||
|
|
||||||
|
// 4. 发送
|
||||||
|
ESP12F_TCP_SendMessage(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发送状态报告
|
||||||
|
* 格式示例: STAT:SP:080,STA:001,RUN:1,DIS:12.5,TRK:0010,RPM:25:25:25:25
|
||||||
|
*/
|
||||||
|
void Protocol_SendStatusReport(void) {
|
||||||
|
char payload[128] = {0};
|
||||||
|
float dist = sr04_get_distance();
|
||||||
|
uint8_t trk_mask = track_ir_get_line_mask();
|
||||||
|
|
||||||
|
// STAT:SP:xxx,STA:xxx,RUN:x,DIS:xxx.x,TRK:xxxx,RPM:m1:m2:m3:m4
|
||||||
|
// TRK 此时上报 4 位二进制掩码 (H4 H3 H2 H1)
|
||||||
|
// RPM 为各个电机的实际 RPM,保留整数
|
||||||
|
snprintf(payload, sizeof(payload),
|
||||||
|
"STAT:SP:%03u,STA:%03u,RUN:%u,DIS:%.1f,TRK:%u%u%u%u,RPM:%d:%d:%d:%d",
|
||||||
|
car_speed_percent, car_target_station, car_running, dist,
|
||||||
|
(trk_mask & TRACK_IR_H4_BIT) ? 1 : 0,
|
||||||
|
(trk_mask & TRACK_IR_H3_BIT) ? 1 : 0,
|
||||||
|
(trk_mask & TRACK_IR_H2_BIT) ? 1 : 0,
|
||||||
|
(trk_mask & TRACK_IR_H1_BIT) ? 1 : 0,
|
||||||
|
(int)hall_get_speed(MOTOR_1),
|
||||||
|
(int)hall_get_speed(MOTOR_2),
|
||||||
|
(int)hall_get_speed(MOTOR_3),
|
||||||
|
(int)hall_get_speed(MOTOR_4));
|
||||||
|
|
||||||
|
Protocol_SendPacket(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发送反馈
|
||||||
|
* 格式示例: FB:ST:1 (ST成功), FB:GS:0 (GS失败)
|
||||||
|
*/
|
||||||
|
void Protocol_SendFeedback(const char *cmd_type, uint8_t status) {
|
||||||
|
char payload[16] = {0};
|
||||||
|
snprintf(payload, sizeof(payload), "FB:%s:%u", cmd_type, status);
|
||||||
|
Protocol_SendPacket(payload);
|
||||||
|
}
|
||||||
|
|
||||||
/* 引用在 freertos.c 中定义的消息队列句柄 */
|
/* 引用在 freertos.c 中定义的消息队列句柄 */
|
||||||
extern osMessageQueueId_t CmdQueueHandle;
|
extern osMessageQueueId_t CmdQueueHandle;
|
||||||
/**
|
/**
|
||||||
@@ -403,12 +473,15 @@ void Protocol_HandleMessage(uint8_t *data, uint16_t len) {
|
|||||||
void CarCtrl_Task(void *argument) {
|
void CarCtrl_Task(void *argument) {
|
||||||
/* USER CODE BEGIN CarCtrl_Task */
|
/* USER CODE BEGIN CarCtrl_Task */
|
||||||
char cmd_payload[16] = {0};
|
char cmd_payload[16] = {0};
|
||||||
|
uint32_t last_report_tick = 0;
|
||||||
|
|
||||||
/* 初始化闭环 PID 控制器 */
|
/* 初始化闭环 PID 控制器 */
|
||||||
CarCtrl_InitClosedLoop();
|
CarCtrl_InitClosedLoop();
|
||||||
|
|
||||||
/* Infinite loop */
|
/* Infinite loop */
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
uint32_t now = HAL_GetTick();
|
||||||
|
|
||||||
/* 1. 处理控制指令 (非阻塞获取,如果没有指令则继续执行闭环) */
|
/* 1. 处理控制指令 (非阻塞获取,如果没有指令则继续执行闭环) */
|
||||||
if (osMessageQueueGet(CmdQueueHandle, cmd_payload, NULL, 0) == osOK) {
|
if (osMessageQueueGet(CmdQueueHandle, cmd_payload, NULL, 0) == osOK) {
|
||||||
elog_d(CarCtrlTask_TAG, "CarCtrl: Command %s", cmd_payload);
|
elog_d(CarCtrlTask_TAG, "CarCtrl: Command %s", cmd_payload);
|
||||||
@@ -421,6 +494,12 @@ void CarCtrl_Task(void *argument) {
|
|||||||
/* 2. 执行 PID 闭环控制更新 */
|
/* 2. 执行 PID 闭环控制更新 */
|
||||||
CarCtrl_UpdateClosedLoop();
|
CarCtrl_UpdateClosedLoop();
|
||||||
|
|
||||||
|
/* 3. 定期向应用层/上位机发送状态报告 (每 500ms) */
|
||||||
|
if (now - last_report_tick >= 500) {
|
||||||
|
Protocol_SendStatusReport();
|
||||||
|
last_report_tick = now;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 与 hall_update_speed() 的 100ms 采样周期对齐,避免 PID 使用过期速度反复修正。
|
* 与 hall_update_speed() 的 100ms 采样周期对齐,避免 PID 使用过期速度反复修正。
|
||||||
* 如果后续把测速周期改成 20ms,这里也要同步改回 20ms。
|
* 如果后续把测速周期改成 20ms,这里也要同步改回 20ms。
|
||||||
|
|||||||
@@ -10,6 +10,18 @@
|
|||||||
*/
|
*/
|
||||||
void Protocol_HandleMessage(uint8_t *data, uint16_t len);
|
void Protocol_HandleMessage(uint8_t *data, uint16_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发送状态报告到上位机 (TCP)
|
||||||
|
* @details 包含当前速度、目标站点、循迹状态、超声波距离等
|
||||||
|
*/
|
||||||
|
void Protocol_SendStatusReport(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发送命令执行结果反馈
|
||||||
|
* @param cmd_type 命令类型 (如 "ST", "SP", "GS")
|
||||||
|
* @param status 状态 (0: 失败, 1: 成功)
|
||||||
|
*/
|
||||||
|
void Protocol_SendFeedback(const char *cmd_type, uint8_t status);
|
||||||
|
|
||||||
void CarCtrl_Task(void *argument);
|
void CarCtrl_Task(void *argument);
|
||||||
|
|
||||||
|
|||||||
75
Core/Bsp/up_readme.md
Normal file
75
Core/Bsp/up_readme.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
好的!为了方便你开发上位机,我为你整理了一份详细的**物流小车 TCP 通信协议文档**。这份文档完全基于我们目前代码中的逻辑实现。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 🚚 物流小车 TCP 通信协议文档 (v1.2)
|
||||||
|
|
||||||
|
## 1. 协议基础格式
|
||||||
|
所有数据包(上行和下行)均采用以下固定包装结构:
|
||||||
|
`LOGI:<PAYLOAD>:<CS>#`
|
||||||
|
|
||||||
|
* **`LOGI:`**: 固定帧头。
|
||||||
|
* **`<PAYLOAD>`**: 有效载荷文本。
|
||||||
|
* **`:`**: 有效载荷与校验位之间的分隔符。
|
||||||
|
* **`<CS>`**: 2字节 ASCII 十六进制校验和(CheckSum)。
|
||||||
|
* *计算规则*:从 `L` 开始(下标0)累加到最后一个冒号 `:` 之前的所有字节。
|
||||||
|
* **`#`**: 固定帧尾。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 下行指令 (上位机 -> 小车)
|
||||||
|
|
||||||
|
| 指令前缀 | 示例 | 说明 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **ST:RUN** | `LOGI:ST:RUN:3B#` | **启动**: 开始自动运输。必须先发 `GS` 设置目标站。 |
|
||||||
|
| **ST:STOP**| `LOGI:ST:STOP:8C#`| **停止**: 立即停止所有电机,清除到站锁存。 |
|
||||||
|
| **SP:xxx** | `LOGI:SP:050:D7#` | **速度**: 设置速度百分比 (000~100)。 |
|
||||||
|
| **GS:xxx** | `LOGI:GS:001:CA#` | **站点**: 设置目标站点 (目前支持 001/002)。会清除到站锁存并响铃。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 上行遥测 (小车 -> 上位机)
|
||||||
|
|
||||||
|
### 3.1 周期状态报告 (下位机每 500ms 自动推送)
|
||||||
|
**格式:** `LOGI:STAT:SP:速度,STA:站点,RUN:运行,DIS:距离,TRK:循迹:CS#`
|
||||||
|
**示例:** `LOGI:STAT:SP:050,STA:001,RUN:1,DIS:52.4,TRK:00100:B2#`
|
||||||
|
|
||||||
|
| 字段 | 示例值 | 说明 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **SP** | `050` | 当前速度百分比 (0~100)。 |
|
||||||
|
| **STA**| `001` | 当前设置的目标站点编号。 |
|
||||||
|
| **RUN**| `1` | 运行状态 (1:运动中, 0:停止/待机)。 |
|
||||||
|
| **DIS**| `12.5`| 超声波测距 (单位: cm,保留1位小数)。 |
|
||||||
|
| **TRK**| `00100`| 5位循迹灯状态 (0: 未触发感应, 1: 感应到黑线)。从左到右对应 IR1-IR5。 |
|
||||||
|
|
||||||
|
### 3.2 指令执行反馈 (收到指令后立即回复)
|
||||||
|
**格式:** `LOGI:FB:指令类型:状态值:CS#`
|
||||||
|
**示例:** `LOGI:FB:GS:1:A5#` (代表站点设置成功)
|
||||||
|
|
||||||
|
| 字段 | 值意义 | 说明 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **指令类型** | `ST` / `SP` / `GS` | 对应上位机发来的指令头。 |
|
||||||
|
| **状态值** | `1` / `0` | 1: 执行成功;0: 失败(如未设站点就点运行等)。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 特殊业务逻辑说明 (供上位机逻辑参考)
|
||||||
|
|
||||||
|
1. **到站锁死机制**:
|
||||||
|
* 小车到达 `STA` 对应的 RFID 站点后,会自动进入 `STOP` 状态并推送 `RUN:0`。
|
||||||
|
* **此时直接发 `ST:RUN` 会被拒绝**(收到 `FB:ST:0` 反馈),蜂鸣器响两声。
|
||||||
|
* **解锁方式**:必须重新发送 `GS:xxx` 指令覆盖目标站点(即使设成同一个号也可以),然后再发 `ST:RUN`。
|
||||||
|
|
||||||
|
2. **安全保护**:
|
||||||
|
* 若上位机未发送过任何 `GS` 指令,尝试发 `ST:RUN` 会触发失败反馈。
|
||||||
|
|
||||||
|
3. **校验和计算 Python 参考代码**:
|
||||||
|
```python
|
||||||
|
def calculate_cs(data_str):
|
||||||
|
# 如 data_str 为 "LOGI:ST:RUN"
|
||||||
|
return sum(data_str.encode('ascii')) & 0xFF
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
你可以直接复制到你的上位机开发笔记里。如需增加更多字段(如电池电压、电机电流等),我可以随时在底层代码里帮你补齐。
|
||||||
44
README.md
44
README.md
@@ -178,6 +178,50 @@ const char *message = "Hello, ESP12F! This is a test message.";
|
|||||||
HAL_StatusTypeDef status = ESP12F_TCP_SendMessage(message);
|
HAL_StatusTypeDef status = ESP12F_TCP_SendMessage(message);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 物流小车 TCP 通信协议 (v1.2)
|
||||||
|
|
||||||
|
本项目采用自定义 ASCII 协议进行上位机控制与状态监控。
|
||||||
|
|
||||||
|
### 1. 指令帧结构
|
||||||
|
`LOGI:<PAYLOAD>:<CS>#`
|
||||||
|
- **LOGI**: 固定帧头
|
||||||
|
- **PAYLOAD**: 有效载荷 (详见下表)
|
||||||
|
- **CS**: 2字节十六进制校验和 (从 'L' 累加到 ':' 之前)
|
||||||
|
- **#**: 固定帧尾
|
||||||
|
|
||||||
|
### 2. 控制指令 (上位机 -> 小车)
|
||||||
|
|
||||||
|
| 功能 | 指令格式 | 示例 | 说明 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| **设置站点** | `GS:NNN` | `LOGI:GS:001:CA#` | NNN为3位站点号 (支持 001/002) |
|
||||||
|
| **启动运行** | `ST:RUN` | `LOGI:ST:RUN:3B#` | 开始任务 (必须先设有效站点) |
|
||||||
|
| **停止运行** | `ST:STOP` | `LOGI:ST:STOP:8C#` | 立即停止 |
|
||||||
|
| **设置速度** | `SP:VVV` | `LOGI:SP:050:D7#` | VVV为000-100 (百分比) |
|
||||||
|
|
||||||
|
### 3. 上行遥测 (小车 -> 上位机)
|
||||||
|
|
||||||
|
#### 3.1 状态推送 (每 500ms 推送一次)
|
||||||
|
**格式**: `LOGI:STAT:SP:速度,STA:站点,RUN:运行,DIS:距离,TRK:循迹状态,RPM:M1:M2:M3:M4:CS#`
|
||||||
|
**字段**:
|
||||||
|
- `SP`: 当前速度 %
|
||||||
|
- `STA`: 目标站点号
|
||||||
|
- `RUN`: 运行状态 (1:运行, 0:停止)
|
||||||
|
- `DIS`: 避障距离 (cm)
|
||||||
|
- `TRK`: 4位红外状态 (0/1组合, 顺序为 H4 H3 H2 H1)
|
||||||
|
- `RPM`: 四路电机实际转速,以 `:` 分隔 (顺序为 LR:LF:RF:RR)
|
||||||
|
|
||||||
|
#### 3.2 指令反馈 (即时回复)
|
||||||
|
**格式**: `LOGI:FB:指令类型:状态值:CS#`
|
||||||
|
**示例**: `LOGI:FB:GS:1:A5#` (1代表成功, 0代表失败)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 业务逻辑约束
|
||||||
|
1. **到站锁存**: 到达站点后小车自动停下,`RUN` 变为 0。
|
||||||
|
2. **解锁流程**: 车辆停稳后,必须重新发送 `GS` 指令设置新站点(或覆盖旧站点),方可再次发送 `ST:RUN` 启动。否则,小车将报警并拒绝运行。
|
||||||
|
|
||||||
|
|
||||||
这些是上位机发送的指令:
|
这些是上位机发送的指令:
|
||||||
按照这些来写代码
|
按照这些来写代码
|
||||||
|
|||||||
Reference in New Issue
Block a user