feat(can): 优化CAN反馈任务错误日志记录机制
- 添加s_lastLoggedError数组跟踪各通道最后记录的错误计数 - 实现错误计数变化时才触发告警,避免重复打印 - 扩展错误日志信息,包含TEC、REC和总线状态详细信息 - 修复P0/P1页面携带相同统计数据时的重复打印问题 perf(ota): 移除冗余的OTA协调器状态检查方法 - 删除isGd32BusyState、isEsp32BusyState等未使用的状态判断方法 - 移除esp32FinalizeErrCategory错误分类函数 - 清理相关的头文件声明,减少代码冗余 refactor(relay): 移除未使用的中继状态获取方法 - 删除getRelaySeq、getRelayTotalPackets状态查询接口 - 移除getGd32Version版本获取方法 - 清理StateLockGuard中的isLocked方法
This commit is contained in:
@@ -4,11 +4,11 @@
|
||||
// 固件版本
|
||||
#define FW_VERSION_MAJOR 0
|
||||
#define FW_VERSION_MINOR 3
|
||||
#define FW_VERSION_PATCH 39
|
||||
#define FW_VERSION_STRING "0.3.39"
|
||||
#define FW_VERSION_PATCH 40
|
||||
#define FW_VERSION_STRING "0.3.40"
|
||||
|
||||
// 编译时间
|
||||
#define BUILD_TIME "2026-07-09 02:43:44"
|
||||
#define BUILD_TIME "2026-07-11 17:47:55"
|
||||
|
||||
// Git 信息
|
||||
#define GIT_HASH "661ae54"
|
||||
#define GIT_HASH "fedb08e"
|
||||
|
||||
@@ -39,6 +39,7 @@ static volatile bool s_paused = false;
|
||||
/* 累积工作缓冲:跨 page 0/1 累积每通道的完整反馈 */
|
||||
static can_feedback_result_t s_work [CAN_CHANNEL_MAX];
|
||||
static uint8_t s_workIdx[CAN_CHANNEL_MAX];
|
||||
static uint32_t s_lastLoggedError[CAN_CHANNEL_MAX];
|
||||
|
||||
static void publish(uint8_t ch, const can_feedback_result_t &fb)
|
||||
{
|
||||
@@ -86,19 +87,22 @@ static void taskLoop(void *)
|
||||
}
|
||||
|
||||
/* 解析本页并累积到工作缓冲,page=0 会自动重置 */
|
||||
const uint8_t page = ring_payload[1];
|
||||
if (canFdParseFeedbackPage(ring_payload, ring_len,
|
||||
&s_work[ch], &s_workIdx[ch]) == 0) {
|
||||
publish(ch, s_work[ch]);
|
||||
|
||||
/* 仅在出现错误时打印告警,正常工作静默 */
|
||||
if (s_work[ch].stats.error_count != 0U) {
|
||||
/* 累计错误发生变化时才告警一次;P0/P1 携带相同统计,不能重复打印。 */
|
||||
if (s_work[ch].stats.error_count != 0U
|
||||
&& s_work[ch].stats.error_count != s_lastLoggedError[ch]) {
|
||||
DEBUG_WARN(kTag,
|
||||
"CH%u P%u seq=%lu err=%lu",
|
||||
"CH%u seq=%lu err=%lu TEC=%u REC=%u state=0x%02X",
|
||||
static_cast<unsigned>(ch),
|
||||
static_cast<unsigned>(page),
|
||||
static_cast<unsigned long>(s_work[ch].stats.snapshot_seq),
|
||||
static_cast<unsigned long>(s_work[ch].stats.error_count));
|
||||
static_cast<unsigned long>(s_work[ch].stats.error_count),
|
||||
static_cast<unsigned>(s_work[ch].stats.tec),
|
||||
static_cast<unsigned>(s_work[ch].stats.rec),
|
||||
static_cast<unsigned>(s_work[ch].stats.bus_state));
|
||||
s_lastLoggedError[ch] = s_work[ch].stats.error_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +127,7 @@ bool startCanFeedbackTask()
|
||||
}
|
||||
}
|
||||
memset(s_latest, 0, sizeof(s_latest));
|
||||
memset(s_lastLoggedError, 0, sizeof(s_lastLoggedError));
|
||||
memset(s_valid, 0, sizeof(s_valid));
|
||||
memset(s_work, 0, sizeof(s_work));
|
||||
memset(s_workIdx, 0, sizeof(s_workIdx));
|
||||
@@ -153,21 +158,6 @@ void pauseCanFeedbackTask(bool pause)
|
||||
}
|
||||
}
|
||||
|
||||
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 表示拿到一份有效数据。
|
||||
|
||||
@@ -7,13 +7,6 @@
|
||||
#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);
|
||||
|
||||
@@ -35,14 +28,6 @@ bool startCanFeedbackTask();
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -555,18 +555,6 @@ void OtaCoordinator::restartEsp32()
|
||||
// 不会返回
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将 ESP32 OTA finalize 错误码分类。
|
||||
* @param err finalize 返回的 esp_err_t。
|
||||
* @return 0=成功,1=SHA不匹配,2=其他失败。
|
||||
*/
|
||||
int OtaCoordinator::esp32FinalizeErrCategory(esp_err_t err)
|
||||
{
|
||||
if (err == ESP_OK) return 0; // 成功
|
||||
if (err == ESP_ERR_INVALID_VERSION) return 1; // SHA 不匹配
|
||||
return 2; // 其他失败
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 中止
|
||||
// ============================================================
|
||||
@@ -655,39 +643,12 @@ bool OtaCoordinator::isIdleState(ota_system_state_t s) const
|
||||
s == OTA_SYS_GD32_COMPLETE);
|
||||
}
|
||||
|
||||
/** @brief 判断给定状态值是否属于 GD32 忙态(不加锁,纯判断)。 */
|
||||
bool OtaCoordinator::isGd32BusyState(ota_system_state_t s) const
|
||||
{
|
||||
return (s == OTA_SYS_GD32_STORING ||
|
||||
s == OTA_SYS_GD32_RELAYING);
|
||||
}
|
||||
|
||||
/** @brief 判断给定状态值是否属于 ESP32 忙态(不加锁,纯判断)。 */
|
||||
bool OtaCoordinator::isEsp32BusyState(ota_system_state_t s) const
|
||||
{
|
||||
return (s == OTA_SYS_ESP32_RECEIVING ||
|
||||
s == OTA_SYS_ESP32_FINALIZING ||
|
||||
s == OTA_SYS_ESP32_REBOOTING);
|
||||
}
|
||||
|
||||
/** @brief 判断系统是否处于空闲状态(IDLE/STORED/COMPLETE)。 */
|
||||
bool OtaCoordinator::isIdle() const
|
||||
{
|
||||
return isIdleState(getSystemState());
|
||||
}
|
||||
|
||||
/** @brief 判断 GD32 通道是否正忙(STORING 或 RELAYING)。 */
|
||||
bool OtaCoordinator::isGd32Busy() const
|
||||
{
|
||||
return isGd32BusyState(getSystemState());
|
||||
}
|
||||
|
||||
/** @brief 判断 ESP32 通道是否正忙(RECEIVING/FINALIZING/REBOOTING)。 */
|
||||
bool OtaCoordinator::isEsp32Busy() const
|
||||
{
|
||||
return isEsp32BusyState(getSystemState());
|
||||
}
|
||||
|
||||
/** @brief 把状态枚举转成可读字符串(不加锁,纯函数)。
|
||||
* 供持锁代码路径和已取一次状态值的调用方复用,避免二次加锁。 */
|
||||
const char* OtaCoordinator::stateToString(ota_system_state_t s)
|
||||
@@ -706,29 +667,12 @@ const char* OtaCoordinator::stateToString(ota_system_state_t s)
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief 获取系统状态的字符串名称(用于日志和前端显示)。
|
||||
* 委托 stateToString(getSystemState()),保持向后兼容。 */
|
||||
const char* OtaCoordinator::getSystemStateString() const
|
||||
{
|
||||
return stateToString(getSystemState());
|
||||
}
|
||||
|
||||
/** @brief 获取 GD32 APP 存活状态(委托 OtaRelay)。 */
|
||||
bool OtaCoordinator::getGd32AppAlive() const
|
||||
{
|
||||
return OtaRelay::getGd32AppAlive();
|
||||
}
|
||||
|
||||
/** @brief 获取 GD32 固件是否已存储完成(线程安全)。 */
|
||||
bool OtaCoordinator::isGd32StorageDone() const
|
||||
{
|
||||
// ★ M1/L3: m_mutex 为 mutable + lock()/unlock() 为 const,无需 const_cast
|
||||
bool h = lock();
|
||||
bool done = m_gd32StorageDone;
|
||||
unlock(h);
|
||||
return done;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// OTA 状态快照(PR1: 收口状态读取)
|
||||
// ============================================================
|
||||
@@ -738,7 +682,7 @@ OtaSnapshot OtaCoordinator::getSnapshot() const
|
||||
|
||||
// ── coordinator 自身状态(一次 mutex,避免二次加锁导致快照不一致)──
|
||||
// 持锁时一次性取 m_sysState,并调不加锁的 stateToString()
|
||||
// 就地转字符串。旧实现释放锁后再调 getSystemStateString() 二次加锁,
|
||||
// 就地转字符串。旧实现释放锁后再调 stateToString(getSystemState()) 二次加锁,
|
||||
// 两次取值之间状态可能变化,导致 snap.sys_state 与 snap.sys_state_str
|
||||
// 描述不同时刻状态。
|
||||
bool h = lock();
|
||||
|
||||
@@ -395,20 +395,6 @@ public:
|
||||
*/
|
||||
bool isIdleState(ota_system_state_t s) const;
|
||||
|
||||
/**
|
||||
* @brief 检查给定状态值是否属于 GD32 忙态(不加锁,纯判断)。
|
||||
* @param s 状态枚举值。
|
||||
* @return true 表示 STORING 或 RELAYING。
|
||||
*/
|
||||
bool isGd32BusyState(ota_system_state_t s) const;
|
||||
|
||||
/**
|
||||
* @brief 检查给定状态值是否属于 ESP32 忙态(不加锁,纯判断)。
|
||||
* @param s 状态枚举值。
|
||||
* @return true 表示 RECEIVING/FINALIZING/REBOOTING。
|
||||
*/
|
||||
bool isEsp32BusyState(ota_system_state_t s) const;
|
||||
|
||||
/**
|
||||
* @brief 检查系统是否空闲(可接受新的 OTA 请求)。
|
||||
* 内部仍加锁取一次状态后调 isIdleState()。
|
||||
@@ -416,27 +402,6 @@ public:
|
||||
*/
|
||||
bool isIdle() const;
|
||||
|
||||
/**
|
||||
* @brief 检查是否有 GD32 相关操作正在进行。
|
||||
* 内部仍加锁取一次状态后调 isGd32BusyState()。
|
||||
* @return true 表示 STORING 或 RELAYING。
|
||||
*/
|
||||
bool isGd32Busy() const;
|
||||
|
||||
/**
|
||||
* @brief 检查是否有 ESP32 OTA 正在进行。
|
||||
* 内部仍加锁取一次状态后调 isEsp32BusyState()。
|
||||
* @return true 表示 RECEIVING/FINALIZING/REBOOTING。
|
||||
*/
|
||||
bool isEsp32Busy() const;
|
||||
|
||||
/**
|
||||
* @brief 获取系统状态的可读字符串描述。
|
||||
* 内部委托 stateToString(getSystemState()),保持向后兼容。
|
||||
* @return 状态名称字符串(如 "IDLE"、"GD32_STORING" 等)。
|
||||
*/
|
||||
const char* getSystemStateString() const;
|
||||
|
||||
// ── GD32 状态(供 status API 使用,委托给 OtaRelay 查询)──
|
||||
/**
|
||||
* @brief 获取 GD32 APP 存活状态。
|
||||
@@ -444,27 +409,12 @@ public:
|
||||
*/
|
||||
bool getGd32AppAlive() const;
|
||||
|
||||
/**
|
||||
* @brief GD32 固件存储完成标志(供状态查询)。
|
||||
* @return true 表示固件已完整存储。
|
||||
*/
|
||||
bool isGd32StorageDone() const;
|
||||
|
||||
// ── 完全复位 ──
|
||||
/**
|
||||
* @brief 完全复位协调器和所有子系统状态。
|
||||
*/
|
||||
void reset();
|
||||
|
||||
// ── ESP32 OTA finalize 错误分类(BLE/WiFi 共用)──
|
||||
/**
|
||||
* @brief 把 finalize 返回的 esp_err_t 映射为前端可读的事件码。
|
||||
* 统一 BLE/WiFi 两端语义,便于日志/状态一致。
|
||||
* @param err finalize 返回的 esp_err_t。
|
||||
* @return 0=成功;1=SHA 不匹配;2=其他失败。
|
||||
*/
|
||||
static int esp32FinalizeErrCategory(esp_err_t err);
|
||||
|
||||
// ── IRelayListener 接口实现(H4: OtaRelay 通过接口回调通知协调器)──
|
||||
/**
|
||||
* @brief IRelayListener 回调:中继已开始。
|
||||
|
||||
@@ -123,31 +123,12 @@ relay_state_t OtaRelay::getState()
|
||||
return relayState;
|
||||
}
|
||||
|
||||
uint32_t OtaRelay::getRelaySeq()
|
||||
{
|
||||
StateLockGuard lock;
|
||||
return relaySeq;
|
||||
}
|
||||
|
||||
uint32_t OtaRelay::getRelayTotalPackets()
|
||||
{
|
||||
StateLockGuard lock;
|
||||
return relayTotalPackets;
|
||||
}
|
||||
|
||||
bool OtaRelay::getGd32AppAlive()
|
||||
{
|
||||
StateLockGuard lock;
|
||||
return gd32AppAlive;
|
||||
}
|
||||
|
||||
void OtaRelay::getGd32Version(gd32_version_t *out)
|
||||
{
|
||||
if (!out) return;
|
||||
StateLockGuard lock;
|
||||
*out = gd32Version;
|
||||
}
|
||||
|
||||
RelaySnapshot OtaRelay::getSnapshot()
|
||||
{
|
||||
StateLockGuard lock;
|
||||
|
||||
@@ -134,26 +134,11 @@ public:
|
||||
* @return 当前 relay_state_t 状态值。
|
||||
*/
|
||||
static relay_state_t getState();
|
||||
/**
|
||||
* @brief 获取中继当前序号.
|
||||
* @return 当前中继序号。
|
||||
*/
|
||||
static uint32_t getRelaySeq();
|
||||
/**
|
||||
* @brief 获取中继总包数.
|
||||
* @return 本次中继的总包数。
|
||||
*/
|
||||
static uint32_t getRelayTotalPackets();
|
||||
/**
|
||||
* @brief 获取GD32 APP存活标志位.
|
||||
* @return true 表示 GD32 APP 存活。
|
||||
*/
|
||||
static bool getGd32AppAlive();
|
||||
/**
|
||||
* @brief 获取GD32固件版本.
|
||||
* @param out 输出版本信息结构体指针。
|
||||
*/
|
||||
static void getGd32Version(gd32_version_t *out);
|
||||
/**
|
||||
* @brief 一次性读取所有中继状态字段(单次锁保护)。
|
||||
* 消除逐 getter 调用之间 relay task 更新状态导致的不一致。
|
||||
@@ -238,7 +223,6 @@ public:
|
||||
~StateLockGuard() {
|
||||
if (m_locked && s_stateMutex) { xSemaphoreGive(s_stateMutex); }
|
||||
}
|
||||
bool isLocked() const { return m_locked; }
|
||||
StateLockGuard(const StateLockGuard&) = delete;
|
||||
StateLockGuard& operator=(const StateLockGuard&) = delete;
|
||||
private:
|
||||
|
||||
@@ -199,109 +199,6 @@ static int recv_json_header(httpd_req_t *req, char *buf, int buf_size)
|
||||
return total;
|
||||
}
|
||||
|
||||
esp_err_t ota_prepare_handler(httpd_req_t *req)
|
||||
{
|
||||
http_set_cors_headers(req);
|
||||
DEBUG_INFO("WIFI", "=== HTTP: POST /ota/prepare ===");
|
||||
|
||||
char buf[256] = {0};
|
||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||
if (ret <= 0) {
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "recv failed");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
buf[ret] = '\0';
|
||||
|
||||
cJSON *root = cJSON_Parse(buf);
|
||||
if (!root) {
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "JSON parse failed");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
cJSON *type_item = cJSON_GetObjectItem(root, "type");
|
||||
cJSON *size_item = cJSON_GetObjectItem(root, "total_size");
|
||||
if (!type_item || !cJSON_IsString(type_item) ||
|
||||
!size_item || !cJSON_IsNumber(size_item)) {
|
||||
cJSON_Delete(root);
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "missing type or total_size");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
const char *type = cJSON_GetStringValue(type_item);
|
||||
uint32_t total_size = (uint32_t)cJSON_GetNumberValue(size_item);
|
||||
cJSON_Delete(root);
|
||||
|
||||
if (type && strcmp(type, "esp32") == 0) {
|
||||
clear_esp32_prepare();
|
||||
|
||||
esp_err_t err = OtaCoordinator::instance().beginEsp32OtaWithRetry(total_size, OTA_CHANNEL_WIFI);
|
||||
if (err != ESP_OK) {
|
||||
if (err == ESP_ERR_INVALID_STATE) {
|
||||
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN,
|
||||
"ESP32 OTA occupied by another channel");
|
||||
} else {
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "OTA begin failed");
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
s_esp32_prepared = true;
|
||||
s_esp32_prepare_size = total_size;
|
||||
s_esp32_prepare_epoch = OtaCoordinator::instance().getOtaEpoch();
|
||||
const char resp[] = "{\"status\": \"ok\", \"target\": \"esp32\"}";
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, resp, strlen(resp));
|
||||
DEBUG_INFO("WIFI", "ESP32 OTA 准备完成: %u 字节", total_size);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (type && strcmp(type, "gd32") == 0) {
|
||||
clear_gd32_prepare();
|
||||
|
||||
if (OtaRelay::getState() != RELAY_IDLE) {
|
||||
DEBUG_WARN("WIFI", "GD32 prepare 前复位中继状态: %d", OtaRelay::getState());
|
||||
OtaRelay::instance().reset();
|
||||
}
|
||||
|
||||
// ★ 统一调用 prepareGd32Storage 完成:validate + begin + erase + clearMeta + saveMeta
|
||||
// 与 BLE 路径 / ota_upload_handler 共享同一序列,消除手写重复
|
||||
esp_err_t prep_err = OtaCoordinator::instance().prepareGd32Storage(total_size, OTA_CHANNEL_WIFI);
|
||||
if (prep_err == ESP_ERR_INVALID_SIZE) {
|
||||
DEBUG_ERR("WIFI", "prepare: firmware size invalid: %u", total_size);
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "firmware size invalid");
|
||||
return ESP_FAIL;
|
||||
} else if (prep_err == ESP_ERR_INVALID_STATE) {
|
||||
DEBUG_ERR("WIFI", "GD32 prepare 协调器拒绝(系统忙)");
|
||||
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN,
|
||||
"system busy with another OTA operation");
|
||||
return ESP_FAIL;
|
||||
} else if (prep_err != ESP_OK) {
|
||||
DEBUG_ERR("WIFI", "GD32 prepare 失败: %s", esp_err_to_name(prep_err));
|
||||
char err_msg[96];
|
||||
snprintf(err_msg, sizeof(err_msg), "prepare gd32_ota failed: %s",
|
||||
esp_err_to_name(prep_err));
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, err_msg);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
WifiOta::instance().stateLock();
|
||||
WifiOta::instance().setGd32StorageDone(false);
|
||||
WifiOta::instance().stateUnlock();
|
||||
|
||||
s_gd32_prepared = true;
|
||||
s_gd32_prepare_size = total_size;
|
||||
s_gd32_prepare_epoch = OtaCoordinator::instance().getOtaEpoch();
|
||||
const char resp[] = "{\"status\": \"ok\", \"target\": \"gd32\"}";
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, resp, strlen(resp));
|
||||
DEBUG_INFO("WIFI", "GD32 OTA 准备完成: %u 字节", total_size);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "unknown type");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t ota_upload_handler(httpd_req_t *req)
|
||||
{
|
||||
http_set_cors_headers(req);
|
||||
|
||||
@@ -69,8 +69,6 @@ public:
|
||||
bool getStaRetryExhausted() const { return m_staRetryExhausted.load(); }
|
||||
/** @brief 设置 STA 重试耗尽标志。 */
|
||||
void setStaRetryExhausted(bool v) { m_staRetryExhausted.store(v); }
|
||||
/** @brief 获取 GD32 固件存储完成标志。 */
|
||||
bool getGd32StorageDone() const { return m_gd32StorageDone.load(); }
|
||||
/** @brief 设置 GD32 固件存储完成标志。 */
|
||||
void setGd32StorageDone(bool v) { m_gd32StorageDone.store(v); }
|
||||
// ── 状态互斥锁(保护复合读-改-写操作)──
|
||||
|
||||
@@ -58,7 +58,6 @@ static int sendCanFrameOverSpi(can_channel_t channel, uint32_t ext_id,
|
||||
|
||||
constexpr uint8_t kSpiCanFeedbackType = 0xC1U;
|
||||
constexpr uint8_t kSpiCanSetModeType = 0xC2U;
|
||||
constexpr uint8_t kSpiIdSnapshotType = 0xC3U;
|
||||
constexpr size_t kFeedbackPayloadLen = 123U; // 与 GD32 协议表对齐 (kTxBufSize=128 - overhead=5)
|
||||
|
||||
static inline uint32_t readU32Le(const uint8_t *p) {
|
||||
@@ -86,21 +85,6 @@ int canFdSend(can_channel_t channel, uint32_t ext_id,
|
||||
return sendCanFrameOverSpi(channel, ext_id, data, dlc, recv_buf, recv_max, timeout_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发送一帧无需上层消费响应的 CANFD 数据。
|
||||
* GD32 APP 仍会返回 ACK/ERROR,这里主动读走响应,避免残留到下一笔 SPI 事务。
|
||||
*/
|
||||
int canFdSendNoReply(can_channel_t channel, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc)
|
||||
{
|
||||
if (channel >= CAN_CHANNEL_MAX || dlc > CAN_MAX_DLC || !data) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (sendCanFrameOverSpi(channel, ext_id, data, dlc, nullptr, 0U, 1000U) >= 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief 解析 GD32 主动推送的一页 CAN 反馈 payload,累积到结果结构中。
|
||||
*
|
||||
@@ -176,76 +160,6 @@ int canFdParseFeedbackPage(const uint8_t *payload, size_t len,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 拉取指定通道的不重复 ID 快照表(GD32 SPI 帧类型 0xC3)。
|
||||
* 分页拉取,每页 8 条,最多拉 2 页(16 条)。
|
||||
*/
|
||||
int canFdPullIdSnapshot(can_channel_t channel,
|
||||
id_snapshot_entry_t *out,
|
||||
uint8_t *out_count,
|
||||
uint32_t timeout_ms)
|
||||
{
|
||||
if (channel >= CAN_CHANNEL_MAX || out == nullptr || out_count == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*out_count = 0U;
|
||||
uint8_t total = 0U;
|
||||
|
||||
for (uint8_t page = 0U; page < 2U; ++page) {
|
||||
uint8_t req[2] = { static_cast<uint8_t>(channel), page };
|
||||
uint8_t resp_buf[kFeedbackPayloadLen] = {};
|
||||
uint8_t resp_type = 0U;
|
||||
size_t resp_len = 0U;
|
||||
|
||||
esp_err_t ret = SpiMaster::instance().otaSendAndPoll(
|
||||
kSpiIdSnapshotType,
|
||||
req, 2U,
|
||||
&resp_type, resp_buf, sizeof(resp_buf), &resp_len, timeout_ms);
|
||||
if (ret != ESP_OK || resp_type != SPI_OTA_RSP_ACK || resp_len < 4U) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* resp: [ch][page][total_count][entry_count][entries...] */
|
||||
if (page == 0U) {
|
||||
total = resp_buf[2];
|
||||
}
|
||||
const uint8_t entryCount = resp_buf[3];
|
||||
if (entryCount == 0U) break;
|
||||
|
||||
uint16_t offset = 4U;
|
||||
for (uint8_t i = 0U; i < entryCount && *out_count < CANFD_ID_SNAPSHOT_MAX; ++i) {
|
||||
if (offset + 16U > resp_len) break;
|
||||
|
||||
id_snapshot_entry_t &e = out[*out_count];
|
||||
e.id = readU32Le(&resp_buf[offset]);
|
||||
e.seq = readU32Le(&resp_buf[offset + 4U]);
|
||||
e.rx_count = readU32Le(&resp_buf[offset + 8U]);
|
||||
e.dlc = resp_buf[offset + 12U];
|
||||
const uint8_t flags = resp_buf[offset + 13U];
|
||||
e.fd_flag = (flags & 0x01U) ? 1U : 0U;
|
||||
e.brs = (flags & 0x02U) ? 1U : 0U;
|
||||
e.esi = (flags & 0x04U) ? 1U : 0U;
|
||||
|
||||
const uint8_t dataLen = (e.dlc < CANFD_ID_SNAPSHOT_DATA_MAX)
|
||||
? e.dlc : static_cast<uint8_t>(CANFD_ID_SNAPSHOT_DATA_MAX);
|
||||
const uint16_t entrySize = 16U + dataLen;
|
||||
if (offset + entrySize > resp_len) break;
|
||||
|
||||
memcpy(e.data, &resp_buf[offset + 16U], dataLen);
|
||||
/* 清零剩余字节 */
|
||||
for (uint8_t d = dataLen; d < CANFD_ID_SNAPSHOT_DATA_MAX; ++d) {
|
||||
e.data[d] = 0U;
|
||||
}
|
||||
|
||||
offset += entrySize;
|
||||
(*out_count)++;
|
||||
}
|
||||
|
||||
if (*out_count >= total) break; /* 已拉完 */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int canFdSetRxMode(can_rx_mode_t mode,
|
||||
can_rx_mode_t *actual,
|
||||
uint32_t timeout_ms)
|
||||
|
||||
@@ -50,18 +50,6 @@ int canFdSend(can_channel_t channel, uint32_t ext_id,
|
||||
uint8_t *recv_buf, uint8_t recv_max,
|
||||
uint32_t timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief 发送一帧无需响应的 CANFD 数据。
|
||||
* @param channel CAN 通道编号(0~2)。
|
||||
* @param ext_id 扩展帧 ID(29 位)。
|
||||
* @param data CAN 数据负载。
|
||||
* @param dlc 数据长度。
|
||||
* @return 成功返回 0,失败返回 -1。
|
||||
*/
|
||||
int canFdSendNoReply(can_channel_t channel, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 不重复 ID 快照表(SPI 0xC3 协议配套)
|
||||
// ============================================================
|
||||
@@ -147,16 +135,4 @@ int canFdSetRxMode(can_rx_mode_t mode,
|
||||
can_rx_mode_t *actual,
|
||||
uint32_t timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief 拉取指定通道的不重复 ID 快照表(SPI 帧 0xC3,分页)。
|
||||
* @param channel 目标通道 0/1/2。
|
||||
* @param out 输出条目数组(至少 CANFD_ID_SNAPSHOT_MAX 个元素)。
|
||||
* @param out_count 输出实际条目数。
|
||||
* @param timeout_ms SPI 超时。
|
||||
* @return 0 成功,<0 失败。
|
||||
*/
|
||||
int canFdPullIdSnapshot(can_channel_t channel,
|
||||
id_snapshot_entry_t *out,
|
||||
uint8_t *out_count,
|
||||
uint32_t timeout_ms);
|
||||
#endif // CANFD_BRIDGE_H
|
||||
|
||||
Reference in New Issue
Block a user