- 添加通道使能开关配置,通过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信号低电平通知主机等待
407 lines
15 KiB
C++
407 lines
15 KiB
C++
/**
|
||
* @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();
|
||
}
|