feat(main): 添加CAN 1Hz反馈轮询任务和HTTP接口
- 新增monitor/can_feedback_task.cpp组件,实现CAN 1Hz反馈轮询功能 - 在main.cpp中注册CAN反馈轮询任务,依赖SPI初始化完成后启动 - 在OTA协调器和中继模块中集成CAN反馈任务暂停/恢复机制 - 扩展CANFD桥接层,添加0xC1反馈查询、0xC2模式切换、0xC3ID快照协议支持 - 新增HTTP接口/can/feedback提供三路CAN反馈快照数据 - 新增HTTP接口/can/mode支持查询和切换CAN接收过滤模式 - 更新版本号从0.3.34到0.35 - 扩大SPI缓冲区以支持更大帧传输需求 BREAKING CHANGE: 新增CAN反馈监控功能需要GD32固件支持0xC1/0xC2/0xC3协议扩展
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* can_feedback_task.cpp - 周期性轮询 GD32 三路 CAN 1Hz 反馈快照
|
||||
*
|
||||
* 通过 SPI 协议帧 0xC1 拉取 GD32 端的通道统计与最近一帧信息,
|
||||
* 用调试日志输出;其它消费者(Web/WebSocket)可直接读取
|
||||
* CanFeedbackTask::snapshot(channel) 拷贝最新一份数据。
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#include "transport/canfd_bridge.h"
|
||||
#include "monitor/debug_log.h"
|
||||
#include "monitor/can_feedback_task.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t kPollIntervalMs = 1000U; ///< 1Hz 拉取节拍
|
||||
constexpr uint32_t kPollTimeoutMs = 200U; ///< 单次 SPI 事务超时
|
||||
constexpr const char *kTag = "CAN_FB";
|
||||
|
||||
static can_feedback_result_t s_latest[CAN_CHANNEL_MAX];
|
||||
static bool s_valid [CAN_CHANNEL_MAX];
|
||||
static SemaphoreHandle_t s_mutex = nullptr;
|
||||
static volatile bool s_paused = false;
|
||||
static can_channel_feedback_t s_prev_stats[CAN_CHANNEL_MAX];
|
||||
static bool s_prev_valid[CAN_CHANNEL_MAX];
|
||||
static bool s_prev_poll_ok[CAN_CHANNEL_MAX];
|
||||
|
||||
static void publish(uint8_t ch, const can_feedback_result_t &fb)
|
||||
{
|
||||
if (s_mutex == nullptr) return;
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(50)) == pdTRUE) {
|
||||
s_latest[ch] = fb;
|
||||
s_valid[ch] = true;
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static void taskLoop(void *)
|
||||
{
|
||||
DEBUG_INFO(kTag, "CAN 反馈轮询任务启动 (1Hz, 0xC1)");
|
||||
|
||||
can_feedback_result_t fb{};
|
||||
for (;;) {
|
||||
/* OTA 进行中时暂停 CAN 轮询,避免抢占 SPI 导致 OTA 握手失败 */
|
||||
if (s_paused) {
|
||||
vTaskDelay(pdMS_TO_TICKS(kPollIntervalMs));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint8_t ch = 0U; ch < CAN_CHANNEL_MAX; ++ch) {
|
||||
const int rc = canFdPollFeedback(static_cast<can_channel_t>(ch),
|
||||
&fb, kPollTimeoutMs);
|
||||
if (rc == 0) {
|
||||
publish(ch, fb);
|
||||
|
||||
const bool prevOk = s_prev_poll_ok[ch];
|
||||
const bool changed = (!s_prev_valid[ch]) ||
|
||||
(fb.stats.rx_count != s_prev_stats[ch].rx_count) ||
|
||||
(fb.stats.tx_count != s_prev_stats[ch].tx_count) ||
|
||||
(fb.stats.rx_drop_count != s_prev_stats[ch].rx_drop_count) ||
|
||||
(fb.stats.error_count != s_prev_stats[ch].error_count) ||
|
||||
(fb.stats.mode != s_prev_stats[ch].mode) ||
|
||||
(fb.stats.snapshot_seq != s_prev_stats[ch].snapshot_seq);
|
||||
const bool abnormal = (fb.stats.rx_drop_count != 0U) ||
|
||||
(fb.stats.error_count != 0U) ||
|
||||
(fb.stats.mode != 0U);
|
||||
|
||||
if (!prevOk || changed || abnormal) {
|
||||
DEBUG_INFO(kTag,
|
||||
"CH%u seq=%lu mode=%u rx=%lu tx=%lu drop=%lu err=%lu ids=%u",
|
||||
static_cast<unsigned>(ch),
|
||||
static_cast<unsigned long>(fb.stats.snapshot_seq),
|
||||
static_cast<unsigned>(fb.stats.mode),
|
||||
static_cast<unsigned long>(fb.stats.rx_count),
|
||||
static_cast<unsigned long>(fb.stats.tx_count),
|
||||
static_cast<unsigned long>(fb.stats.rx_drop_count),
|
||||
static_cast<unsigned long>(fb.stats.error_count),
|
||||
static_cast<unsigned>(fb.valid));
|
||||
}
|
||||
|
||||
s_prev_stats[ch] = fb.stats;
|
||||
s_prev_valid[ch] = true;
|
||||
s_prev_poll_ok[ch] = true;
|
||||
} else {
|
||||
if (s_prev_poll_ok[ch]) {
|
||||
DEBUG_WARN(kTag, "CH%u 轮询失败", static_cast<unsigned>(ch));
|
||||
}
|
||||
s_prev_stats[ch] = {};
|
||||
s_prev_valid[ch] = false;
|
||||
s_prev_poll_ok[ch] = false;
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(kPollIntervalMs));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief 启动 1Hz CAN 反馈轮询任务(幂等)。
|
||||
* @return true 表示任务已存在或创建成功。
|
||||
*/
|
||||
bool startCanFeedbackTask()
|
||||
{
|
||||
static bool s_started = false;
|
||||
if (s_started) return true;
|
||||
|
||||
if (s_mutex == nullptr) {
|
||||
s_mutex = xSemaphoreCreateMutex();
|
||||
if (s_mutex == nullptr) {
|
||||
DEBUG_ERR(kTag, "互斥量创建失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
memset(s_latest, 0, sizeof(s_latest));
|
||||
memset(s_valid, 0, sizeof(s_valid));
|
||||
|
||||
BaseType_t xRet = xTaskCreatePinnedToCore(
|
||||
taskLoop,
|
||||
"can_fb",
|
||||
4096,
|
||||
nullptr,
|
||||
4,
|
||||
nullptr,
|
||||
0);
|
||||
if (xRet != pdPASS) {
|
||||
DEBUG_ERR(kTag, "任务创建失败");
|
||||
return false;
|
||||
}
|
||||
s_started = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void pauseCanFeedbackTask(bool pause)
|
||||
{
|
||||
s_paused = pause;
|
||||
if (pause) {
|
||||
DEBUG_INFO(kTag, "CAN 轮询已暂停(OTA 进行中)");
|
||||
} else {
|
||||
DEBUG_INFO(kTag, "CAN 轮询已恢复");
|
||||
}
|
||||
}
|
||||
|
||||
bool getLatestIdSnapshot(uint8_t channel, id_snapshot_cache_t *out)
|
||||
{
|
||||
if (channel >= CAN_CHANNEL_MAX || out == nullptr || s_mutex == nullptr) return false;
|
||||
bool ok = false;
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
if (s_latest[channel].valid) {
|
||||
out->count = s_latest[channel].valid;
|
||||
memcpy(out->entries, s_latest[channel].entries, sizeof(out->entries));
|
||||
ok = true;
|
||||
}
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 拷贝某通道的最新快照(线程安全)。
|
||||
* @return true 表示拿到一份有效数据。
|
||||
*/
|
||||
bool getLatestCanFeedback(uint8_t channel, can_feedback_result_t *out)
|
||||
{
|
||||
if (channel >= CAN_CHANNEL_MAX || out == nullptr || s_mutex == nullptr) {
|
||||
return false;
|
||||
}
|
||||
bool ok = false;
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(50)) == pdTRUE) {
|
||||
if (s_valid[channel]) {
|
||||
*out = s_latest[channel];
|
||||
ok = true;
|
||||
}
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* can_feedback_task.h - 周期性轮询 GD32 CAN 反馈快照的任务接口
|
||||
*/
|
||||
#ifndef CAN_FEEDBACK_TASK_H
|
||||
#define CAN_FEEDBACK_TASK_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "transport/canfd_bridge.h"
|
||||
|
||||
/** @brief ID 快照缓存结构。 */
|
||||
typedef struct {
|
||||
id_snapshot_entry_t entries[CANFD_ID_SNAPSHOT_MAX];
|
||||
uint8_t count;
|
||||
bool valid;
|
||||
} id_snapshot_cache_t;
|
||||
|
||||
/** @brief 暂停/恢复 CAN 反馈轮询(OTA 期间调用 pause=true)。 */
|
||||
void pauseCanFeedbackTask(bool pause);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 启动 1Hz CAN 反馈轮询任务(幂等)。
|
||||
* @return true 表示任务已存在或创建成功。
|
||||
*/
|
||||
bool startCanFeedbackTask();
|
||||
|
||||
/**
|
||||
* @brief 拷贝某通道的最新反馈快照(线程安全)。
|
||||
* @param channel 0/1/2。
|
||||
* @param out 输出缓冲。
|
||||
* @return true 表示拿到一份有效数据,false 表示通道无效或尚未拿到第一份。
|
||||
*/
|
||||
bool getLatestCanFeedback(uint8_t channel, can_feedback_result_t *out);
|
||||
|
||||
/**
|
||||
* @brief 获取指定通道的最新 ID 快照(线程安全)。
|
||||
* @param channel 0/1/2。
|
||||
* @param out 输出缓冲。
|
||||
* @return true 有有效数据。
|
||||
*/
|
||||
bool getLatestIdSnapshot(uint8_t channel, id_snapshot_cache_t *out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // CAN_FEEDBACK_TASK_H
|
||||
Reference in New Issue
Block a user