2026-07-02 02:02:41 +08:00
|
|
|
|
/**
|
2026-05-10 17:28:14 +08:00
|
|
|
|
* @file task_runner.cpp
|
|
|
|
|
|
* @brief 任务执行器实现。
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include "main.h"
|
|
|
|
|
|
#include "device/adc.h"
|
2026-05-30 00:24:31 +08:00
|
|
|
|
#include "device/watchdog.h"
|
2026-06-26 00:57:59 +08:00
|
|
|
|
#include "device/spi/app_spi_command.h"
|
2026-05-10 17:28:14 +08:00
|
|
|
|
#include "device/canfd.h"
|
2026-07-26 20:39:59 +08:00
|
|
|
|
#include "device/motor_state.h"
|
|
|
|
|
|
#include "device/motor_version.h"
|
2026-05-10 21:27:51 +08:00
|
|
|
|
#include "sys/config.h"
|
2026-05-30 00:24:31 +08:00
|
|
|
|
#include "sys/ota_boot_request.h"
|
2026-07-26 20:39:59 +08:00
|
|
|
|
#include "sys/motor_ota_relay.h"
|
2026-05-10 17:28:14 +08:00
|
|
|
|
#include "sys/task_manager.h"
|
2026-09-05 20:07:22 +08:00
|
|
|
|
#include "sys/cpu_load.h"
|
2026-05-10 17:28:14 +08:00
|
|
|
|
#include "sys/task_runner.h"
|
2026-06-26 00:57:59 +08:00
|
|
|
|
#include "comm/ethercat_slave.h"
|
2026-09-01 03:07:48 +08:00
|
|
|
|
#include "comm/ethercat_sync.h"
|
2026-09-05 16:54:38 +08:00
|
|
|
|
#include "comm/ethercat_can_bridge.h"
|
2026-08-09 03:27:21 +08:00
|
|
|
|
#include "comm/esc_exmc_bus.h"
|
|
|
|
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
|
|
void MainLoop(void);
|
|
|
|
|
|
}
|
2026-05-10 17:28:14 +08:00
|
|
|
|
|
2026-05-30 00:24:31 +08:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-05-10 17:28:14 +08:00
|
|
|
|
struct TaskRunner::Runtime {
|
2026-07-15 02:49:37 +08:00
|
|
|
|
Adc::Sample sample{};
|
2026-05-30 00:24:31 +08:00
|
|
|
|
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;
|
2026-05-10 21:27:51 +08:00
|
|
|
|
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};
|
2026-05-10 17:28:14 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
TaskRunner::Runtime TaskRunner::s_runtime;
|
|
|
|
|
|
|
2026-05-10 21:27:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 采样 VIN 与 VREF 原始 ADC 数值。
|
|
|
|
|
|
* @param[in] 无。
|
|
|
|
|
|
* @param[out] 无。
|
|
|
|
|
|
* @retval 无返回值。
|
|
|
|
|
|
* @note 从 ADC DMA 缓冲中读取外部 VIN_ADC 与内部 VREFINT 通道最新值。
|
|
|
|
|
|
*/
|
2026-05-10 17:28:14 +08:00
|
|
|
|
void TaskRunner::taskAdcVrefSample()
|
|
|
|
|
|
{
|
2026-05-30 00:24:31 +08:00
|
|
|
|
s_runtime.last_vin_raw = Adc::readVinRaw();
|
|
|
|
|
|
s_runtime.last_vref_raw = Adc::readVrefRaw();
|
2026-05-10 17:28:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 21:27:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 采样内部温度通道原始 ADC 数值。
|
|
|
|
|
|
* @param[in] 无。
|
|
|
|
|
|
* @param[out] 无。
|
|
|
|
|
|
* @retval 无返回值。
|
|
|
|
|
|
* @note 读取 ADC DMA 缓冲中的内部温度通道最新值。
|
|
|
|
|
|
*/
|
2026-05-10 17:28:14 +08:00
|
|
|
|
void TaskRunner::taskAdcTempSample()
|
|
|
|
|
|
{
|
2026-05-30 00:24:31 +08:00
|
|
|
|
s_runtime.last_temp_raw = Adc::readTempRaw();
|
2026-05-10 17:28:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 21:27:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 根据原始采样值计算工程量结果。
|
|
|
|
|
|
* @param[in] 无。
|
|
|
|
|
|
* @param[out] 无。
|
|
|
|
|
|
* @retval 无返回值。
|
|
|
|
|
|
* @note 输出 `vin_adc_v`、`vdda` 和 `temperature` 等可直接使用的工程值。
|
|
|
|
|
|
*/
|
2026-05-10 17:28:14 +08:00
|
|
|
|
void TaskRunner::taskAdcProcess()
|
|
|
|
|
|
{
|
2026-05-30 00:24:31 +08:00
|
|
|
|
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;
|
2026-05-10 17:28:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 21:27:51 +08:00
|
|
|
|
/**
|
2026-07-26 20:39:59 +08:00
|
|
|
|
* @brief 执行 CAN 周期发送任务:每 10ms 在每个使能通道上发送一帧 CANFD 测试帧。
|
2026-05-10 21:27:51 +08:00
|
|
|
|
* @param[in] 无。
|
|
|
|
|
|
* @param[out] 无。
|
|
|
|
|
|
* @retval 无返回值。
|
2026-07-11 17:52:05 +08:00
|
|
|
|
* @note 帧格式:扩展帧 29-bit ID,CAN-FD,DLC=64,BRS 受 CONFIG_CAN_FD_BRS_ENABLE 控制。
|
|
|
|
|
|
* 载荷布局:
|
|
|
|
|
|
* [0] 固定标识 0xC0
|
|
|
|
|
|
* [1] 通道号
|
|
|
|
|
|
* [2..5] 发送序号(小端 uint32)
|
|
|
|
|
|
* [6..7] 系统运行秒数低 16 位
|
|
|
|
|
|
* [8..63] 填充 0xA0 + (index & 0x0F)
|
|
|
|
|
|
* periodic=true:仅允许一个邮箱保持自动重发,不主动 abort 旧帧,
|
|
|
|
|
|
* 跳过 TEC 保护以匹配诊断行为。
|
2026-05-10 21:27:51 +08:00
|
|
|
|
*/
|
|
|
|
|
|
void TaskRunner::taskCanTxSend()
|
|
|
|
|
|
{
|
2026-07-26 20:39:59 +08:00
|
|
|
|
#if (CONFIG_CAN_PERIODIC_TEST_TX_ENABLE != 0)
|
2026-07-11 17:52:05 +08:00
|
|
|
|
static uint32_t seq = 0U;
|
2026-07-02 02:02:41 +08:00
|
|
|
|
|
2026-07-11 17:52:05 +08:00
|
|
|
|
for (uint8_t ch = 0U; ch < 3U; ++ch) {
|
|
|
|
|
|
if (!CanFd::isChannelEnabled(ch)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2026-07-02 02:02:41 +08:00
|
|
|
|
|
2026-07-11 17:52:05 +08:00
|
|
|
|
uint8_t data[64];
|
|
|
|
|
|
data[0] = 0xC0U;
|
|
|
|
|
|
data[1] = ch;
|
|
|
|
|
|
const uint32_t s = seq;
|
|
|
|
|
|
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);
|
|
|
|
|
|
for (uint8_t i = 8U; i < 64U; ++i) {
|
|
|
|
|
|
data[i] = static_cast<uint8_t>(0xA0U + (i & 0x0FU));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const uint32_t extId = 0x18FF5000U | static_cast<uint32_t>(ch);
|
2026-07-15 00:34:56 +08:00
|
|
|
|
if (CanFd::transmitFrame(ch, extId, data, sizeof(data), true) == CanFd::CanTxResult::kOk) {
|
2026-07-11 17:52:05 +08:00
|
|
|
|
seq++;
|
|
|
|
|
|
}
|
2026-07-02 02:02:41 +08:00
|
|
|
|
}
|
2026-07-26 20:39:59 +08:00
|
|
|
|
#else
|
|
|
|
|
|
/* CONFIG_CAN_PERIODIC_TEST_TX_ENABLE=0:测试帧无业务接收节点 ACK,
|
|
|
|
|
|
* 单发模式下会持续产生 MTEn 错误帧,整任务空跑。 */
|
|
|
|
|
|
(void)0;
|
|
|
|
|
|
#endif
|
2026-05-10 21:27:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-05-30 00:24:31 +08:00
|
|
|
|
* @brief 批量解析并消费待处理的 CAN 接收报文。
|
2026-05-10 21:27:51 +08:00
|
|
|
|
* @param[in] 无。
|
|
|
|
|
|
* @param[out] 无。
|
|
|
|
|
|
* @retval 无返回值。
|
2026-05-30 00:24:31 +08:00
|
|
|
|
* @note 一次任务按预算尽量清空软件接收队列;若仍有积压,则重新置位解析任务。
|
2026-05-10 21:27:51 +08:00
|
|
|
|
*/
|
2026-05-10 17:28:14 +08:00
|
|
|
|
void TaskRunner::taskCanRxParse()
|
|
|
|
|
|
{
|
2026-05-30 00:24:31 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-07-26 20:39:59 +08:00
|
|
|
|
if(MotorOtaRelay::onCanFrame(s_runtime.last_rx_channel,
|
|
|
|
|
|
s_runtime.last_rx_id,
|
|
|
|
|
|
s_runtime.last_rx_data,
|
|
|
|
|
|
s_runtime.last_rx_len)) {
|
|
|
|
|
|
consumed++;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
#if (CONFIG_MOTOR_STATE_ENABLE != 0)
|
|
|
|
|
|
MotorState::onCanFrame(s_runtime.last_rx_channel,
|
|
|
|
|
|
s_runtime.last_rx_id,
|
|
|
|
|
|
s_runtime.last_rx_data,
|
|
|
|
|
|
s_runtime.last_rx_len);
|
|
|
|
|
|
#endif
|
|
|
|
|
|
#if (CONFIG_MOTOR_VERSION_ENABLE != 0)
|
|
|
|
|
|
MotorVersion::onCanFrame(s_runtime.last_rx_channel,
|
|
|
|
|
|
s_runtime.last_rx_id,
|
|
|
|
|
|
s_runtime.last_rx_data,
|
|
|
|
|
|
s_runtime.last_rx_len);
|
|
|
|
|
|
#endif
|
2026-09-05 16:54:38 +08:00
|
|
|
|
/* 1.5.53:旁路投递给 EtherCAT PDO 桥。放在分发链末尾且**不消费**帧
|
|
|
|
|
|
* (不 return、不 continue),因此 OTA relay / 电机状态的取帧不受影响——
|
|
|
|
|
|
* 本队列只有 taskCanRxParse 一个消费者,新增第二个消费者会互相抢帧。 */
|
|
|
|
|
|
EthercatCanBridge::onCanFrame(s_runtime.last_rx_channel,
|
|
|
|
|
|
s_runtime.last_rx_id,
|
|
|
|
|
|
s_runtime.last_rx_data,
|
|
|
|
|
|
s_runtime.last_rx_len);
|
2026-05-30 00:24:31 +08:00
|
|
|
|
consumed++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(CanFd::hasRxFrame()) {
|
|
|
|
|
|
TaskManager::set(1UL << TASK_CAN_RX_PARSE);
|
|
|
|
|
|
}
|
2026-05-10 17:28:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 21:27:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 执行系统心跳维护任务。
|
|
|
|
|
|
* @param[in] 无。
|
|
|
|
|
|
* @param[out] 无。
|
|
|
|
|
|
* @retval 无返回值。
|
2026-08-09 03:27:21 +08:00
|
|
|
|
* @note 推进 OTA Boot 请求、刷新 CAN 反馈快照、启动 ADC 1Hz 推送轮次。
|
2026-05-10 21:27:51 +08:00
|
|
|
|
*/
|
2026-07-20 02:36:56 +08:00
|
|
|
|
void TaskRunner::taskHeartbeat()
|
|
|
|
|
|
{
|
|
|
|
|
|
OtaBootRequest::process();
|
|
|
|
|
|
CanFd::refreshFeedbackSnapshot();
|
2026-07-26 20:39:59 +08:00
|
|
|
|
#if (CONFIG_MOTOR_STATE_ENABLE != 0)
|
|
|
|
|
|
MotorState::checkOffline();
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-20 02:36:56 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 拷贝当前 ADC 工程量快照(供 SPI 0xC4 等只读路径)。
|
|
|
|
|
|
* @param[out] out 非空时写入最新 Sample
|
|
|
|
|
|
* @return true 已写入(不保证 sample.valid==1)
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool TaskRunner::copyAdcSample(Adc::Sample *out)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (out == nullptr) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
*out = s_runtime.sample;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-05-10 17:28:14 +08:00
|
|
|
|
|
2026-09-01 03:07:48 +08:00
|
|
|
|
/* SSC 侧 AL 诊断快照(dc_app.c): [19:16]=AL 状态 [31:24]=AL 错误码 [15:0]=AL 事件寄存器 */
|
|
|
|
|
|
extern "C" uint32_t EcatDbg_ReadAlDiag(void);
|
2026-09-05 16:54:38 +08:00
|
|
|
|
/* SSC 侧 AL Event Mask(0x204) 守护(dc_app.c, 1.5.48):缺位自愈+WARN,返回实际值/0xFFFF=不适用 */
|
|
|
|
|
|
extern "C" uint32_t EcatDbg_GuardAleventMask(void);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 1.5.48/1.5.49 AL Event Mask(0x204) 守护 + SM2 事件楔死自愈 + 1.5.52 邮箱
|
|
|
|
|
|
* SM 地址/长度失配自愈:250ms 周期。
|
|
|
|
|
|
* @note 实测"停止回 INIT 后重进 OP"概率性挂起(SAFEOP+0x001B, WKC=3/3 但
|
|
|
|
|
|
* bEcatFirstOutputsReceived 不置位,0x220 bit10 挂起 10s 无人处理)——
|
|
|
|
|
|
* PDI_Isr 通路楔死。守护在 SAFEOP 后 1s 内替 PDI_Isr 完成工作(置位
|
|
|
|
|
|
* 标志+应答事件),主站 12s OP 等待窗口内可自愈;WARN 携带全部内部旗标,
|
|
|
|
|
|
* 下次复现一行日志定位楔死路径(详见 dc_app.c 注释)。
|
|
|
|
|
|
* 1.5.52:周期 1s→250ms——新加的邮箱地址自愈(1.5.52 根治 SSC 侧
|
|
|
|
|
|
* u16EscAddrSendMbx 错位楔死,详见 dc_app.c)必须快过主站 SDO 超时
|
|
|
|
|
|
* (~700ms),否则自愈永远落在主站放弃之后,形同虚设。
|
2026-09-05 17:38:35 +08:00
|
|
|
|
* 1.5.59:邮箱楔死已根治(mailbox.c 几何常量化,CSR 错值无进入缓存路径),
|
|
|
|
|
|
* 本守护降级为运行期内存踩踏兜底;且修复了 1.5.52~1.5.58 的 SWAPWORD
|
|
|
|
|
|
* 字节序 bug——那段时期本守护的失配判定恒短路,实际从未生效过。
|
2026-09-05 16:54:38 +08:00
|
|
|
|
*/
|
|
|
|
|
|
static void ethercatAleMaskGuard()
|
|
|
|
|
|
{
|
|
|
|
|
|
static uint32_t s_lastGuardMs = 0U;
|
|
|
|
|
|
if (!EthercatSync::isActive()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const uint32_t nowMs = g_scheduler.tick_1ms;
|
|
|
|
|
|
if ((uint32_t)(nowMs - s_lastGuardMs) >= 250U) {
|
|
|
|
|
|
s_lastGuardMs = nowMs;
|
|
|
|
|
|
EcatDbg_GuardAleventMask();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-09-01 03:07:48 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 1.5.32 ECAT 心跳:主循环存活证明 + ESC AL 状态/事件寄存器快照。
|
|
|
|
|
|
* @note 仅在同步 ISR 激活(SSC 在跑)时打印。心跳停 = 主循环卡死;
|
|
|
|
|
|
* al=0x1 且 evt bit0 长期置位 = SSC 没处理 AL 请求;
|
|
|
|
|
|
* al 与主站读到的不一致 = ESC 写回路径问题。
|
|
|
|
|
|
*/
|
|
|
|
|
|
static void ethercatAlHeartbeat()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!EthercatSync::isActive()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const uint32_t alDiag = EcatDbg_ReadAlDiag();
|
|
|
|
|
|
printf("[ECAT][HB] al=0x%lX code=0x%02lX evt=0x%04lX (uptime=%lus)\r\n",
|
|
|
|
|
|
(unsigned long)((alDiag >> 16) & 0x0FU),
|
|
|
|
|
|
(unsigned long)((alDiag >> 24) & 0xFFU),
|
|
|
|
|
|
(unsigned long)(alDiag & 0xFFFFU),
|
|
|
|
|
|
(unsigned long)g_app_uptime_sec);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 打印 EtherCAT 同步中断计数与速率(DC 通路验证)。
|
|
|
|
|
|
* @note 主站启用 DC(SYNC0 周期 T)后,sync0 速率应稳定 ≈ 1e6/T_us 次/秒;
|
|
|
|
|
|
* free-run 模式下 SYNC0 引脚无脉冲,三个计数器恒为 0(本函数不打印)。
|
|
|
|
|
|
* 仅在有增量时输出,避免未接主站时刷屏。
|
|
|
|
|
|
*/
|
|
|
|
|
|
static void ethercatSyncCounterPrint()
|
|
|
|
|
|
{
|
|
|
|
|
|
static uint32_t s_prevSync0 = 0U;
|
|
|
|
|
|
static uint32_t s_prevSync1 = 0U;
|
|
|
|
|
|
static uint32_t s_prevEscIrq = 0U;
|
|
|
|
|
|
static uint32_t s_prevSec = 0U;
|
|
|
|
|
|
|
|
|
|
|
|
const uint32_t sync0 = EthercatSync::getSync0Count();
|
|
|
|
|
|
const uint32_t sync1 = EthercatSync::getSync1Count();
|
|
|
|
|
|
const uint32_t escIrq = EthercatSync::getEscIrqCount();
|
|
|
|
|
|
|
|
|
|
|
|
const uint32_t d0 = sync0 - s_prevSync0; /* 无符号回绕自然成立 */
|
|
|
|
|
|
const uint32_t d1 = sync1 - s_prevSync1;
|
|
|
|
|
|
const uint32_t di = escIrq - s_prevEscIrq;
|
|
|
|
|
|
|
|
|
|
|
|
const uint32_t now = g_app_uptime_sec;
|
|
|
|
|
|
const uint32_t elapsed = (now > s_prevSec) ? (now - s_prevSec) : 1U;
|
|
|
|
|
|
|
|
|
|
|
|
s_prevSync0 = sync0;
|
|
|
|
|
|
s_prevSync1 = sync1;
|
|
|
|
|
|
s_prevEscIrq = escIrq;
|
|
|
|
|
|
s_prevSec = now;
|
|
|
|
|
|
|
|
|
|
|
|
if (d0 == 0U && d1 == 0U && di == 0U) {
|
|
|
|
|
|
return; /* 未接主站或 free-run:无脉冲,静默 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
printf("[ECAT][SYNC] sync0=%lu(+%lu, %lu/s) sync1=%lu(+%lu) escirq=%lu(+%lu) dt=%lus\r\n",
|
|
|
|
|
|
(unsigned long)sync0, (unsigned long)d0, (unsigned long)(d0 / elapsed),
|
|
|
|
|
|
(unsigned long)sync1, (unsigned long)d1,
|
|
|
|
|
|
(unsigned long)escIrq, (unsigned long)di,
|
|
|
|
|
|
(unsigned long)elapsed);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 21:27:51 +08:00
|
|
|
|
/**
|
2026-07-26 20:39:59 +08:00
|
|
|
|
* @brief 5s 诊断任务:默认仅状态变化/错误增量;可选全量 UART 摘要。
|
|
|
|
|
|
* @note CONFIG_UART_TEMP_PERIODIC_PRINT=1 时打印 ADC/CAN/ECAT 全量行(调试用)。
|
|
|
|
|
|
* 默认=0:相对上次快照有 CAN 错误计数增量、ADC valid 跳变、ECAT ready 变化或 ISR 超阈时才打印。
|
|
|
|
|
|
* 采用定点整数格式,避免依赖浮点 printf。
|
2026-05-10 21:27:51 +08:00
|
|
|
|
*/
|
2026-05-10 17:28:14 +08:00
|
|
|
|
void TaskRunner::taskDiagnosticPrint()
|
|
|
|
|
|
{
|
|
|
|
|
|
const auto *diag = CanFd::diag();
|
|
|
|
|
|
const auto &s = s_runtime.sample;
|
2026-07-26 20:39:59 +08:00
|
|
|
|
|
|
|
|
|
|
#if (CONFIG_UART_TEMP_PERIODIC_PRINT != 0)
|
|
|
|
|
|
/* ---- Debug 全量摘要:周期刷 ADC / CAN 吞吐 / ECAT ISR 统计 ---- */
|
|
|
|
|
|
{
|
|
|
|
|
|
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);
|
2026-05-10 17:28:14 +08:00
|
|
|
|
|
2026-05-10 21:27:51 +08:00
|
|
|
|
#if (CONFIG_UART_DIAG_LEVEL == 0)
|
2026-07-26 20:39:59 +08:00
|
|
|
|
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));
|
2026-05-10 21:27:51 +08:00
|
|
|
|
#else
|
2026-07-26 20:39:59 +08:00
|
|
|
|
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_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_abort_count),
|
|
|
|
|
|
static_cast<unsigned long>(diag->error_count));
|
2026-05-10 21:27:51 +08:00
|
|
|
|
#endif
|
2026-06-29 01:51:02 +08:00
|
|
|
|
|
|
|
|
|
|
if (EthercatSlave::isEscReady()) {
|
2026-09-01 03:07:48 +08:00
|
|
|
|
/* 1.5.27:periodicIsr 已删除(死代码,无调用点),PDO 交换由 SSC
|
|
|
|
|
|
* Sync0_Isr → ECAT_Application → dc_app.c APPL_* 完成。 */
|
|
|
|
|
|
printf("[ECAT] ESC ready (chip id=0x%08lX), PDO via SSC Sync0_Isr\r\n",
|
|
|
|
|
|
(unsigned long)EthercatSlave::getChipId());
|
2026-06-29 01:51:02 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
printf("[ECAT] ESC not detected (chip id=0x%08lX), EtherCAT disabled\r\n",
|
|
|
|
|
|
(unsigned long)EthercatSlave::getChipId());
|
|
|
|
|
|
}
|
2026-09-01 03:07:48 +08:00
|
|
|
|
ethercatSyncCounterPrint();
|
|
|
|
|
|
ethercatAlHeartbeat();
|
2026-06-29 01:51:02 +08:00
|
|
|
|
}
|
2026-07-26 20:39:59 +08:00
|
|
|
|
#else
|
|
|
|
|
|
/* ---- 默认静默路径:仅错误增量 / 状态变化,无 uptime 心跳 ---- */
|
|
|
|
|
|
{
|
|
|
|
|
|
static uint32_t s_prev_rx_drop = 0U;
|
|
|
|
|
|
static uint32_t s_prev_rx_fifo_full = 0U;
|
|
|
|
|
|
static uint32_t s_prev_rx_fifo_overfull = 0U;
|
|
|
|
|
|
static uint32_t s_prev_tx_abort = 0U;
|
|
|
|
|
|
static uint32_t s_prev_error = 0U;
|
|
|
|
|
|
static uint8_t s_prev_adc_valid = 0xFFU; /* 0xFF=尚未采样 */
|
|
|
|
|
|
static bool s_prev_esc_ready = false;
|
|
|
|
|
|
static bool s_esc_ready_inited = false;
|
|
|
|
|
|
|
|
|
|
|
|
const uint32_t rx_drop = diag->rx_drop_count;
|
|
|
|
|
|
const uint32_t rx_full = diag->rx_fifo_full_count;
|
|
|
|
|
|
const uint32_t rx_over = diag->rx_fifo_overfull_count;
|
|
|
|
|
|
const uint32_t tx_abort = diag->tx_abort_count;
|
|
|
|
|
|
const uint32_t can_err = diag->error_count;
|
|
|
|
|
|
|
|
|
|
|
|
if (rx_drop > s_prev_rx_drop || rx_full > s_prev_rx_fifo_full
|
|
|
|
|
|
|| rx_over > s_prev_rx_fifo_overfull || tx_abort > s_prev_tx_abort
|
|
|
|
|
|
|| can_err > s_prev_error) {
|
|
|
|
|
|
printf("[CAN-DIAG] uptime=%lus err=%lu(+%lu) drop=%lu(+%lu) full=%lu(+%lu) over=%lu(+%lu) abort=%lu(+%lu)\r\n",
|
|
|
|
|
|
(unsigned long)g_app_uptime_sec,
|
|
|
|
|
|
(unsigned long)can_err,
|
|
|
|
|
|
(unsigned long)(can_err - s_prev_error),
|
|
|
|
|
|
(unsigned long)rx_drop,
|
|
|
|
|
|
(unsigned long)(rx_drop - s_prev_rx_drop),
|
|
|
|
|
|
(unsigned long)rx_full,
|
|
|
|
|
|
(unsigned long)(rx_full - s_prev_rx_fifo_full),
|
|
|
|
|
|
(unsigned long)rx_over,
|
|
|
|
|
|
(unsigned long)(rx_over - s_prev_rx_fifo_overfull),
|
|
|
|
|
|
(unsigned long)tx_abort,
|
|
|
|
|
|
(unsigned long)(tx_abort - s_prev_tx_abort));
|
|
|
|
|
|
s_prev_rx_drop = rx_drop;
|
|
|
|
|
|
s_prev_rx_fifo_full = rx_full;
|
|
|
|
|
|
s_prev_rx_fifo_overfull = rx_over;
|
|
|
|
|
|
s_prev_tx_abort = tx_abort;
|
|
|
|
|
|
s_prev_error = can_err;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (s_prev_adc_valid == 0xFFU) {
|
|
|
|
|
|
s_prev_adc_valid = s.valid;
|
|
|
|
|
|
} else if (s.valid != s_prev_adc_valid) {
|
|
|
|
|
|
printf("[APP] adc valid %u -> %u (uptime=%lus)\r\n",
|
|
|
|
|
|
static_cast<unsigned int>(s_prev_adc_valid),
|
|
|
|
|
|
static_cast<unsigned int>(s.valid),
|
|
|
|
|
|
(unsigned long)g_app_uptime_sec);
|
|
|
|
|
|
s_prev_adc_valid = s.valid;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const bool esc_ready = EthercatSlave::isEscReady();
|
|
|
|
|
|
if (!s_esc_ready_inited) {
|
|
|
|
|
|
s_prev_esc_ready = esc_ready;
|
|
|
|
|
|
s_esc_ready_inited = true;
|
|
|
|
|
|
} else if (esc_ready != s_prev_esc_ready) {
|
|
|
|
|
|
printf("[ECAT] ESC ready %u -> %u chip_id=0x%08lX (uptime=%lus)\r\n",
|
|
|
|
|
|
static_cast<unsigned int>(s_prev_esc_ready ? 1U : 0U),
|
|
|
|
|
|
static_cast<unsigned int>(esc_ready ? 1U : 0U),
|
|
|
|
|
|
(unsigned long)EthercatSlave::getChipId(),
|
|
|
|
|
|
(unsigned long)g_app_uptime_sec);
|
|
|
|
|
|
s_prev_esc_ready = esc_ready;
|
|
|
|
|
|
}
|
2026-09-01 03:07:48 +08:00
|
|
|
|
/* 1.5.27:periodicIsr ISR 统计随死代码一并删除 */
|
|
|
|
|
|
/* DC SYNC0 通路验证:仅在有脉冲增量时打印(未接主站时静默)。 */
|
|
|
|
|
|
ethercatSyncCounterPrint();
|
|
|
|
|
|
ethercatAlHeartbeat();
|
2026-07-26 20:39:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2026-05-10 17:28:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 21:27:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 初始化任务执行器运行时上下文。
|
|
|
|
|
|
* @param[in] 无。
|
|
|
|
|
|
* @param[out] 无。
|
|
|
|
|
|
* @retval 无返回值。
|
|
|
|
|
|
* @note 执行一次完整 ADC 采样,并同步填充运行时缓存的原始值起点。
|
|
|
|
|
|
*/
|
2026-05-10 17:28:14 +08:00
|
|
|
|
void TaskRunner::init()
|
|
|
|
|
|
{
|
|
|
|
|
|
Adc::collectSample(&s_runtime.sample);
|
2026-05-30 00:24:31 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-05-10 17:28:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 21:27:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 执行当前所有已置位的待处理任务。
|
|
|
|
|
|
* @param[in] 无。
|
|
|
|
|
|
* @param[out] 无。
|
|
|
|
|
|
* @retval 无返回值。
|
|
|
|
|
|
* @note 先原子取出任务位图;若当前没有待处理任务则直接返回。
|
|
|
|
|
|
*/
|
2026-05-10 17:28:14 +08:00
|
|
|
|
void TaskRunner::runPending()
|
|
|
|
|
|
{
|
2026-05-30 00:24:31 +08:00
|
|
|
|
Watchdog::feed();
|
|
|
|
|
|
|
2026-09-01 03:07:48 +08:00
|
|
|
|
/* === HBI 周期诊断:每 20 秒重新跑一次 EXMC/HBI 探活,方便热插拔/焊接后观察 ===
|
|
|
|
|
|
* 1.5.27:EtherCAT 同步 ISR 激活后停止(diagDump 会把 EXMC 引脚切成 GPIO,
|
2026-09-02 02:38:35 +08:00
|
|
|
|
* 拆毁运行中的总线;诊断的 bit-bang 也与 HBI 事务互斥冲突)。
|
|
|
|
|
|
* 1.5.33:整块由 CONFIG_ESC_EXMC_DIAG 门控,默认关闭——硬件已确认全通,
|
|
|
|
|
|
* 不再每 20s 拆总线做探针;换板/补焊排查浮空时置 1 恢复。 */
|
|
|
|
|
|
#if CONFIG_ESC_EXMC_DIAG
|
2026-08-09 03:27:21 +08:00
|
|
|
|
static uint32_t s_lastDiagSec = 0xFFFFFFFFU;
|
2026-09-01 03:07:48 +08:00
|
|
|
|
if (!EthercatSync::isActive()
|
|
|
|
|
|
&& (g_app_uptime_sec >= s_lastDiagSec + 20U || s_lastDiagSec == 0xFFFFFFFFU)) {
|
2026-08-09 03:27:21 +08:00
|
|
|
|
s_lastDiagSec = g_app_uptime_sec;
|
|
|
|
|
|
printf("[ECAT][DIAG] === 20s periodic HBI probe (uptime=%lus) ===\r\n",
|
|
|
|
|
|
(unsigned long)g_app_uptime_sec);
|
|
|
|
|
|
EscExmcBus::diagDump();
|
|
|
|
|
|
}
|
2026-09-02 02:38:35 +08:00
|
|
|
|
#endif
|
2026-08-09 03:27:21 +08:00
|
|
|
|
|
2026-09-01 03:07:48 +08:00
|
|
|
|
/* 延迟 EEPROM 烧录已删除(1.5.29):ESI 镜像改由 EtherCAT 主站侧刷写
|
|
|
|
|
|
* (tools/ecat/ecat_flash_eeprom.py 经 ESC 0x0500 EEPROM 接口),
|
|
|
|
|
|
* 固件不再承担烧录/校验职责。 */
|
2026-08-09 03:27:21 +08:00
|
|
|
|
|
2026-07-09 02:47:22 +08:00
|
|
|
|
/* poll() 含按铃超时看门狗(cancelRing),必须每轮无条件执行。
|
2026-08-09 03:27:21 +08:00
|
|
|
|
* 主动推送已随 2026-08 请求驱动改造移除,此处仅需消费请求响应的按铃。 */
|
2026-07-09 02:47:22 +08:00
|
|
|
|
AppSpiCommandBus.poll();
|
|
|
|
|
|
|
2026-07-13 03:10:37 +08:00
|
|
|
|
/* 0xB0 进 BOOT:必须每圈检查,不能只靠 1Hz TASK_HEARTBEAT。
|
|
|
|
|
|
* 否则 Flash 写/复位被拖到下一秒,ESP32 HELLO 一直 role=app 超时。 */
|
|
|
|
|
|
if (OtaBootRequest::isRebootPending()) {
|
2026-09-05 20:07:22 +08:00
|
|
|
|
CpuLoad::beginWork();
|
2026-07-13 03:10:37 +08:00
|
|
|
|
OtaBootRequest::process();
|
2026-09-05 20:07:22 +08:00
|
|
|
|
CpuLoad::endWork();
|
2026-07-13 03:10:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-26 20:39:59 +08:00
|
|
|
|
/* 电机 OTA relay:活跃时每圈推进状态机(等 ACK / 超时 / 发下一包)。
|
|
|
|
|
|
* 非活跃时立即返回,开销可忽略。 */
|
2026-09-05 20:07:22 +08:00
|
|
|
|
/* 负载率挂钩(1.5.63):relay 活跃时才计 busy——isActive() 是廉价枚举判断,
|
|
|
|
|
|
* 空闲路径零挂钩开销;计数段覆盖等 ACK/超时/发包的真实工作。 */
|
|
|
|
|
|
const bool motorOtaActive = MotorOtaRelay::isActive();
|
|
|
|
|
|
if (motorOtaActive) {
|
|
|
|
|
|
CpuLoad::beginWork();
|
|
|
|
|
|
}
|
2026-07-26 20:39:59 +08:00
|
|
|
|
MotorOtaRelay::process();
|
|
|
|
|
|
/* 数据发送推进:主循环每圈调用,按 SysTick 钟表控制每包间隔
|
|
|
|
|
|
* (替代原靠 tick100us 计数 + TASK_MOTOR_OTA_TX 的方式,消除计数器竞争)。 */
|
|
|
|
|
|
MotorOtaRelay::onTxTick();
|
2026-09-05 20:07:22 +08:00
|
|
|
|
if (motorOtaActive) {
|
|
|
|
|
|
CpuLoad::endWork();
|
|
|
|
|
|
}
|
2026-07-26 20:39:59 +08:00
|
|
|
|
|
2026-08-09 03:27:21 +08:00
|
|
|
|
/* 2026-08 请求驱动改造:0xC1 CAN 反馈 / 0xC5 电机状态 / 0xC4 ADC 已改为
|
|
|
|
|
|
* ESP32 周期查询(主动按铃推送全部移除,避免 OTA 期间悬空按铃刷屏)。
|
|
|
|
|
|
* 仅保留事件驱动、低频的 0xC6 电机版本推送。 */
|
2026-07-26 20:39:59 +08:00
|
|
|
|
motorVersionPushStep();
|
2026-07-08 02:51:24 +08:00
|
|
|
|
|
2026-08-09 03:27:21 +08:00
|
|
|
|
/* SSC 协议栈主循环:AL 状态机 + 邮箱处理 + FreeRun PDO。
|
|
|
|
|
|
* 每轮无条件调用,SSC 内部自行判断是否有待处理事件。
|
|
|
|
|
|
* DC 模式下 PDO 交换由 SYNC0 ISR (Sync0_Isr) 完成,此处仅处理低优先级逻辑。 */
|
|
|
|
|
|
MainLoop();
|
2026-06-29 01:51:02 +08:00
|
|
|
|
|
2026-09-05 16:54:38 +08:00
|
|
|
|
/* 1.5.48:AL Event Mask(0x204) 守护——每圈入口执行,内部 1s 节流。
|
|
|
|
|
|
* 放 MainLoop 旁而非诊断任务:守护必须独立于诊断打印频率,
|
|
|
|
|
|
* OP 等待期(12s)内从站进 SAFEOP 后 1s 内即介入自愈。 */
|
|
|
|
|
|
ethercatAleMaskGuard();
|
|
|
|
|
|
|
2026-05-10 17:28:14 +08:00
|
|
|
|
const uint32_t pending = TaskManager::takePending();
|
|
|
|
|
|
if(pending == 0U) return;
|
|
|
|
|
|
|
2026-09-05 20:07:22 +08:00
|
|
|
|
/* 负载率挂钩(1.5.63):派发的周期任务属"非延迟工作",整段计入 busy;
|
|
|
|
|
|
* 前面的常开轮询(Watchdog/poll/MainLoop/guard)不计入,见 sys/cpu_load.h。 */
|
|
|
|
|
|
CpuLoad::beginWork();
|
2026-05-10 17:28:14 +08:00
|
|
|
|
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_PARSE)) taskCanRxParse();
|
2026-07-02 02:02:41 +08:00
|
|
|
|
if(pending & (1UL << TASK_CAN_TX_SEND)) taskCanTxSend();
|
2026-05-10 17:28:14 +08:00
|
|
|
|
if(pending & (1UL << TASK_HEARTBEAT)) taskHeartbeat();
|
|
|
|
|
|
if(pending & (1UL << TASK_DIAGNOSTIC_PRINT)) taskDiagnosticPrint();
|
2026-09-05 20:07:22 +08:00
|
|
|
|
CpuLoad::endWork();
|
2026-05-30 00:24:31 +08:00
|
|
|
|
}
|