Files
DCU_APP/GD32E508_DCU_APP/sys/task_runner.cpp
T
cbb b78e36ff37 feat(canfd): 添加通道使能控制和SPI握手信号支持
- 添加通道使能开关配置,通过sys/config.h中的CONFIG_CAN_CHx_ENABLE控制各通道
- 实现isChannelEnabled()方法用于查询通道使能状态
- 修正CAN1引脚映射配置,将RX/TX引脚对调并禁用重映射
- 修正错误计数器读取逻辑,REC字段从bit24开始提取而非bit8
- 修正busOff状态判断,使用CAN_ERR_BOERR标志位
- 调整滤波器配置时机,移到所有hardReset完成后防止共享寄存器被清零
- 在发送时增加总线健康检查,避免高错误计数时继续发送导致bus off
- 添加SPI Ready握手信号(PB6)控制,改善主从机通信同步

fix(spi): 实现SPI命令处理中的READY信号握手协议

- 添加SPI Ready信号控制,PB6输出指示从机就绪状态
- CS下降沿立即拉低READY信号防主机抢跑
- 命令处理完成且无待处理命令时拉高READY信号
- 忽略无效事务后恢复READY信号避免超时
- 在繁忙期间保持READY信号低电平通知主机等待
2026-07-06 19:13:56 +08:00

407 lines
15 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.
/**
* @file task_runner.cpp
* @brief 任务执行器实现。
*/
#include "main.h"
#include "device/adc.h"
#include "device/watchdog.h"
#include "device/spi/app_spi_command.h"
#include "device/canfd.h"
#include "device/temp_sensor.h"
#include "sys/config.h"
#include "sys/ota_boot_request.h"
#include "sys/task_manager.h"
#include "sys/task_runner.h"
#include "comm/ethercat_slave.h"
namespace {
constexpr uint32_t kAdcFilterWindow = CONFIG_ADC_FILTER_SIZE + 1U;
/**
* @brief Push one sample into a fixed-size sliding window buffer.
* @param[in,out] buffer Sample buffer with `kAdcFilterWindow` elements.
* @param[in] value New sample value.
*/
static void pushSample(float *buffer, float value)
{
for(uint32_t i = 0U; i < CONFIG_ADC_FILTER_SIZE; ++i) {
buffer[i] = buffer[i + 1U];
}
buffer[CONFIG_ADC_FILTER_SIZE] = value;
}
/**
* @brief Apply median filtering to a fixed-size sample window.
* @param[in] buffer Sample buffer with `kAdcFilterWindow` elements.
* @return Median value in the current window.
*/
static float medianFilter(const float *buffer)
{
float sorted[kAdcFilterWindow];
for(uint32_t i = 0U; i < kAdcFilterWindow; ++i) {
sorted[i] = buffer[i];
}
for(uint32_t i = 1U; i < kAdcFilterWindow; ++i) {
const float key = sorted[i];
uint32_t j = i;
while((j > 0U) && (sorted[j - 1U] > key)) {
sorted[j] = sorted[j - 1U];
--j;
}
sorted[j] = key;
}
return sorted[kAdcFilterWindow / 2U];
}
} // namespace
struct TaskRunner::Runtime {
temp_sensor_data_t sample{};
float temperature_samples[kAdcFilterWindow] = {0.0f};
float vdda_samples[kAdcFilterWindow] = {0.0f};
float vin_adc_samples[kAdcFilterWindow] = {0.0f};
uint16_t last_vin_raw = 0U;
uint16_t last_vref_raw = 0U;
uint16_t last_temp_raw = 0U;
uint8_t last_rx_channel = 0U;
uint32_t last_rx_id = 0U;
uint8_t last_rx_len = 0U;
uint8_t last_rx_data[64] = {0};
};
TaskRunner::Runtime TaskRunner::s_runtime;
/**
* @brief 采样 VIN 与 VREF 原始 ADC 数值。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 从 ADC DMA 缓冲中读取外部 VIN_ADC 与内部 VREFINT 通道最新值。
*/
void TaskRunner::taskAdcVrefSample()
{
s_runtime.last_vin_raw = Adc::readVinRaw();
s_runtime.last_vref_raw = Adc::readVrefRaw();
}
/**
* @brief 采样内部温度通道原始 ADC 数值。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 读取 ADC DMA 缓冲中的内部温度通道最新值。
*/
void TaskRunner::taskAdcTempSample()
{
s_runtime.last_temp_raw = Adc::readTempRaw();
}
/**
* @brief 根据原始采样值计算工程量结果。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 输出 `vin_adc_v`、`vdda` 和 `temperature` 等可直接使用的工程值。
*/
void TaskRunner::taskAdcProcess()
{
auto &s = s_runtime.sample;
const float vdda = Adc::calculateVdda(s_runtime.last_vref_raw);
const float vin_adc_v = Adc::calculateVinAdcVoltage(s_runtime.last_vin_raw, vdda);
const float temperature = Adc::calculateTemperature(s_runtime.last_temp_raw, vdda);
const bool sampleValid = (s_runtime.last_vref_raw > 0U) &&
(s_runtime.last_vref_raw < 4095U) &&
(s_runtime.last_temp_raw > 0U) &&
(s_runtime.last_temp_raw < 4095U) &&
(vdda >= 2.7f) && (vdda <= 3.6f) &&
(temperature >= -40.0f) && (temperature <= 125.0f);
if(sampleValid) {
pushSample(s_runtime.vdda_samples, vdda);
pushSample(s_runtime.vin_adc_samples, vin_adc_v);
pushSample(s_runtime.temperature_samples, temperature);
}
s.vin_raw = s_runtime.last_vin_raw;
s.vref_raw = s_runtime.last_vref_raw;
s.temp_raw = s_runtime.last_temp_raw;
s.vdda = medianFilter(s_runtime.vdda_samples);
s.vin_adc_v = medianFilter(s_runtime.vin_adc_samples);
s.temperature = medianFilter(s_runtime.temperature_samples);
s.valid = sampleValid ? 1U : 0U;
}
/**
* @brief 执行兼容保留的 CAN 接收轮询任务。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 当前三路 CANFD 接收已由 RX0 中断驱动,该任务仅保留接口兼容性。
*/
void TaskRunner::taskCanRxPoll() { CanFd::poll(); }
/**
* @brief 执行预留的 CAN 发送执行任务。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 当前阶段不做周期性发送调度;若后续需要显式验证某一路发送能力,可由调用方先 prepare,再直接调用 `CanFd::sendPrepared()`。
*/
void TaskRunner::taskCanTxSend()
{
/* 启动后每 100ms 轮流发送一路 CAN 通道的 CANFD+BRS 64 字节测试帧。
* 三路通道时分复用:CH0→CH1→CH2→CH0→...,每 100ms 只发一路,
* 避免多路同时发送导致总线仲裁冲突(当多路并接到同一总线时)。
* 帧格式:扩展帧 29-bit ID,CANFD+BRS,DLC=64。
* 载荷布局:
* [0] 通道号 0/1/2
* [1] 保留
* [2..5] 发送序号(小端 uint32,每通道独立递增)
* [6..9] 系统运行秒数 g_app_uptime_sec(小端 uint32)
* [10..63] 递增填充,按通道偏移便于抓包区分 */
static uint32_t seq[3] = {0U, 0U, 0U};
static uint8_t round = 0U; /* 0=CH0, 1=CH1, 2=CH2, 轮流 */
const uint8_t ch = round;
round = (round + 1U) % 3U;
/* 跳过已禁用的通道 */
if (!CanFd::isChannelEnabled(ch)) {
return;
}
uint8_t data[64];
data[0] = ch;
data[1] = 0U;
const uint32_t s = seq[ch];
data[2] = static_cast<uint8_t>(s & 0xFFU);
data[3] = static_cast<uint8_t>((s >> 8) & 0xFFU);
data[4] = static_cast<uint8_t>((s >> 16) & 0xFFU);
data[5] = static_cast<uint8_t>((s >> 24) & 0xFFU);
data[6] = static_cast<uint8_t>(g_app_uptime_sec & 0xFFU);
data[7] = static_cast<uint8_t>((g_app_uptime_sec >> 8) & 0xFFU);
data[8] = static_cast<uint8_t>((g_app_uptime_sec >> 16) & 0xFFU);
data[9] = static_cast<uint8_t>((g_app_uptime_sec >> 24) & 0xFFU);
for (uint8_t i = 10U; i < 64U; ++i) {
data[i] = static_cast<uint8_t>(i + ch * 0x40U);
}
const uint32_t extId = 0x1FFFFF00U | static_cast<uint32_t>(ch);
CanFd::transmitFrame(ch, extId, data, 64U);
seq[ch]++;
}
/**
* @brief 批量解析并消费待处理的 CAN 接收报文。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 一次任务按预算尽量清空软件接收队列;若仍有积压,则重新置位解析任务。
*/
void TaskRunner::taskCanRxParse()
{
uint32_t consumed = 0U;
while(consumed < CONFIG_CAN_RX_PARSE_BUDGET) {
if(!CanFd::consumeRxFrame(&s_runtime.last_rx_id, s_runtime.last_rx_data, &s_runtime.last_rx_len, &s_runtime.last_rx_channel)) {
break;
}
if(OtaBootRequest::handleCanFrame(s_runtime.last_rx_channel,
s_runtime.last_rx_id,
s_runtime.last_rx_data,
s_runtime.last_rx_len)) {
OtaBootRequest::process();
return;
}
consumed++;
}
if(CanFd::hasRxFrame()) {
TaskManager::set(1UL << TASK_CAN_RX_PARSE);
}
}
/**
* @brief 执行预留的 CAN 发送准备任务。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 当前阶段三路 CANFD 仅要求底层收发能力可用,不做周期性业务状态广播。
*/
void TaskRunner::taskCanTxPrepare()
{
/* 当前阶段不做周期性业务状态广播。 */
}
/**
* @brief 执行状态灯维护任务。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 当前仅保留任务入口。
*/
void TaskRunner::taskStatusLed() { /* 预留 */ }
/**
* @brief 执行系统心跳维护任务。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 当前仅保留任务入口。
*/
void TaskRunner::taskHeartbeat() { OtaBootRequest::process(); CanFd::refreshFeedbackSnapshot(); }
/**
* @brief 输出当前温度、电压与 CAN 诊断信息。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 由 1 秒节拍触发,通过 UART 打印当前温度、电压;采用定点整数格式,避免依赖浮点 `printf` 支持。
*/
void TaskRunner::taskDiagnosticPrint()
{
const auto *diag = CanFd::diag();
const auto &s = s_runtime.sample;
const int32_t temp_x100 = static_cast<int32_t>(s.temperature * 100.0f);
const uint32_t vdda_mV = static_cast<uint32_t>(s.vdda * 1000.0f);
const uint32_t vin_adc_mV = static_cast<uint32_t>(s.vin_adc_v * 1000.0f);
#if (CONFIG_UART_DIAG_LEVEL == 0)
printf("[APP] uptime=%lus valid=%u, temp_raw=%u, vref_raw=%u, temp=%ld.%02ld C, vdda=%lu.%03lu V, vin_adc=%lu.%03lu V\r\n",
(unsigned long)g_app_uptime_sec,
static_cast<unsigned int>(s.valid),
static_cast<unsigned int>(s.temp_raw),
static_cast<unsigned int>(s.vref_raw),
static_cast<long>(temp_x100 / 100),
static_cast<long>((temp_x100 >= 0 ? temp_x100 : -temp_x100) % 100),
static_cast<unsigned long>(vdda_mV / 1000U),
static_cast<unsigned long>(vdda_mV % 1000U),
static_cast<unsigned long>(vin_adc_mV / 1000U),
static_cast<unsigned long>(vin_adc_mV % 1000U));
#else
printf("[APP] uptime=%lus valid=%u, temp=%ld.%02ld C, vdda=%lu.%03lu V, vin_adc=%lu.%03lu V, rx_ch=%u, rx_id=0x%08lX, rx_len=%u, tx_ch=%u, can_tx=%lu, can_rx=%lu, sw_drop=%lu, hw_full=%lu, hw_over=%lu, tx_drop=%lu, tx_abort=%lu, can_err=%lu\r\n",
(unsigned long)g_app_uptime_sec,
static_cast<unsigned int>(s.valid),
static_cast<long>(temp_x100 / 100),
static_cast<long>((temp_x100 >= 0 ? temp_x100 : -temp_x100) % 100),
static_cast<unsigned long>(vdda_mV / 1000U),
static_cast<unsigned long>(vdda_mV % 1000U),
static_cast<unsigned long>(vin_adc_mV / 1000U),
static_cast<unsigned long>(vin_adc_mV % 1000U),
static_cast<unsigned int>(s_runtime.last_rx_channel),
static_cast<unsigned long>(s_runtime.last_rx_id),
static_cast<unsigned int>(s_runtime.last_rx_len),
static_cast<unsigned int>(diag->last_tx_channel),
static_cast<unsigned long>(diag->tx_count),
static_cast<unsigned long>(diag->rx_count),
static_cast<unsigned long>(diag->rx_drop_count),
static_cast<unsigned long>(diag->rx_fifo_full_count),
static_cast<unsigned long>(diag->rx_fifo_overfull_count),
static_cast<unsigned long>(diag->tx_drop_count),
static_cast<unsigned long>(diag->tx_abort_count),
static_cast<unsigned long>(diag->error_count));
#endif
{
if (EthercatSlave::isEscReady()) {
uint32_t maxCyc = 0U, avgCyc = 0U, cnt = 0U;
EthercatSlave::getIsrStats(&maxCyc, &avgCyc, &cnt);
/* 180MHz: cycles / 180 = µs;用 *1000/180 获得毫微秒精度避免浮点 */
const uint32_t maxUs_x1000 = maxCyc * 1000U / 180U;
const uint32_t avgUs_x1000 = avgCyc * 1000U / 180U;
printf("[ECAT] periodicIsr cnt=%lu max=%lu.%03luus avg=%lu.%03luus\r\n",
(unsigned long)cnt,
(unsigned long)(maxUs_x1000 / 1000U), (unsigned long)(maxUs_x1000 % 1000U),
(unsigned long)(avgUs_x1000 / 1000U), (unsigned long)(avgUs_x1000 % 1000U));
} else {
printf("[ECAT] ESC not detected (chip id=0x%08lX), EtherCAT disabled\r\n",
(unsigned long)EthercatSlave::getChipId());
}
}
}
/**
* @brief 执行 SPI CS release 后推迟的命令解析(从 ISR 移出)。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note ISR 瘦身:onCsRelease() 仅置位 TASK_SPI_CMD_PROCESS,实际解析在此执行。
*/
void TaskRunner::taskSpiCmdProcess()
{
AppSpiCommandBus.processPendingCommand();
}
/**
* @brief 执行 EtherCAT 周期/事件处理(非实时应用层 PDO 映射)。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 由周期 ISR(EXTI0 SYNC0)或 ESC 事件 ISR(EXTI1 IRQ)置位 TASK_ETHERCAT_PROCESS。
*/
void TaskRunner::taskEthercatProcess()
{
EthercatSlave::process();
}
/**
* @brief 初始化任务执行器运行时上下文。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 执行一次完整 ADC 采样,并同步填充运行时缓存的原始值起点。
*/
void TaskRunner::init()
{
Adc::collectSample(&s_runtime.sample);
s_runtime.last_vin_raw = s_runtime.sample.vin_raw;
s_runtime.last_vref_raw = s_runtime.sample.vref_raw;
s_runtime.last_temp_raw = s_runtime.sample.temp_raw;
for(uint32_t i = 0U; i < kAdcFilterWindow; ++i) {
s_runtime.temperature_samples[i] = s_runtime.sample.temperature;
s_runtime.vdda_samples[i] = s_runtime.sample.vdda;
s_runtime.vin_adc_samples[i] = s_runtime.sample.vin_adc_v;
}
}
/**
* @brief 执行当前所有已置位的待处理任务。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 先原子取出任务位图;若当前没有待处理任务则直接返回。
*/
void TaskRunner::runPending()
{
Watchdog::feed();
/* 周期路径( SYNC0 ISR preempt=0 )用 s_periodicSeq 序列号通知主循环,
* 不再走 task_flags,避免 BASEPRI 临界区与 preempt=0 的 RMW 竞态。
* 优先于 takePending() 处理,确保周期数据及时消费。
* ESC 未焊接时 selfTest 失败,isEscReady()=false,中断未使能,
* s_periodicSeq 不会递增,此分支自然不触发——双重守卫仅为防御性编程。 */
if (EthercatSlave::isEscReady() && EthercatSlave::hasPendingPeriodic()) {
EthercatSlave::markPeriodicProcessed();
taskEthercatProcess();
}
const uint32_t pending = TaskManager::takePending();
if(pending == 0U) return;
if(pending & (1UL << TASK_ADC_VREF_SAMPLE)) taskAdcVrefSample();
if(pending & (1UL << TASK_ADC_TEMP_SAMPLE)) taskAdcTempSample();
if(pending & (1UL << TASK_ADC_PROCESS)) taskAdcProcess();
if(pending & (1UL << TASK_CAN_RX_POLL)) taskCanRxPoll();
if(pending & (1UL << TASK_CAN_RX_PARSE)) taskCanRxParse();
if(pending & (1UL << TASK_CAN_TX_SEND)) taskCanTxSend();
if(pending & (1UL << TASK_STATUS_LED)) taskStatusLed();
if(pending & (1UL << TASK_HEARTBEAT)) taskHeartbeat();
if(pending & (1UL << TASK_DIAGNOSTIC_PRINT)) taskDiagnosticPrint();
if(pending & (1UL << TASK_SPI_LOG)) AppSpiCommandBus.poll();
if(pending & (1UL << TASK_SPI_CMD_PROCESS)) taskSpiCmdProcess();
if(pending & (1UL << TASK_ETHERCAT_PROCESS)) taskEthercatProcess();
}