diff --git a/Robot_V2/Algorithm/FOC.cpp b/Robot_V2/Algorithm/FOC.cpp index 1a39da6..52817f4 100644 --- a/Robot_V2/Algorithm/FOC.cpp +++ b/Robot_V2/Algorithm/FOC.cpp @@ -20,6 +20,9 @@ #include #include +// 全局 FOC 控制器单例:系统只有一个电机控制对象。 +// 初始化顺序由 main::hardware_init() 明确调用 FOC.init() 完成; +// 运行期主要由主循环控制任务访问,CAN 命令只更新目标/参数。 FOC_Controller FOC; // FOC控制初始化 extern sampler sampler; extern angle_sensor KTH7823; @@ -62,6 +65,14 @@ inline float FOC_Controller::get_cogging_compensation(float angle_el) { // 启用方法:config.h 中 FOC_DEBUG 改为 1,通过 CAN 命令触发(RESET_ID bit4) // 校准完成后把 C Array 硬编码到 init() 中,再改回 FOC_DEBUG 0 #if FOC_DEBUG +// 齿槽转矩校准参数 +constexpr u32 COG_CALIB_SETTLE_TICKS = 16000; // 800ms / 50us +constexpr u32 COG_CALIB_STOP_TICKS = 10000; // 500ms / 50us +constexpr u32 COG_CALIB_TARGET_SAMPLES = 3 * COGGING_TABLE_SIZE * 8; +constexpr float COG_CALIB_SLOW_VEL = 1.0f; // 开环速度 rad/s +constexpr u32 COG_CALIB_SAMPLE_DIV = 20; // 1ms / 50us +constexpr u8 COG_CALIB_TOTAL_RUNS = 6; // 3正+3反 + // 齿槽转矩校准静态数据 float FOC_Controller::cog_calib_run_data[6][COGGING_TABLE_SIZE] = {}; float FOC_Controller::cog_calib_accum[COGGING_TABLE_SIZE] = {}; @@ -70,172 +81,181 @@ u32 FOC_Controller::cog_calib_count[COGGING_TABLE_SIZE] = {}; /** * @brief 齿槽转矩校准 - 非阻塞状态机 * 正转3次 + 反转3次,分开存储补偿表。 - * 只打印最终平均数据和 C Array。 */ void FOC_Controller::calibrate_cogging_tick(void) { - /** @brief SETTLE_TICKS 常量定义 */ - constexpr u32 SETTLE_TICKS = 16000; // 800ms / 50us - /** @brief STOP_TICKS 常量定义 */ - constexpr u32 STOP_TICKS = 10000; // 500ms / 50us - /** @brief TARGET_SAMPLES 常量定义 */ - constexpr u32 TARGET_SAMPLES = 3 * COGGING_TABLE_SIZE * 8; - /** @brief SLOW_VEL 常量定义 */ - constexpr float SLOW_VEL = 1.0f; // 开环速度 rad/s - /** @brief SAMPLE_DIV 常量定义 */ - constexpr u32 SAMPLE_DIV = 20; // 1ms / 50us - /** @brief TOTAL_RUNS 常量定义 */ - constexpr u8 TOTAL_RUNS = 6; // 3正+3反 - + // 入口只做状态派发,避免单函数堆叠所有校准分支。 switch (cog_calib_state) { case CogCalibState::IDLE: - if (!cogging_calib_request) return; - cogging_calib_request = false; - - cog_calib_run = 0; - cog_calib_saved_mode = control_mode; - cog_calib_saved_vel = target_velocity; - cogging_comp_enabled = false; - memset(cog_calib_run_data, 0, sizeof(cog_calib_run_data)); - - printf("\r\n[COG] === Cogging Calibration: 3 fwd + 3 rev ===\r\n"); - - control_mode = 6; - target_velocity = SLOW_VEL; - cog_calib_settle_tick = 0; - cog_calib_state = CogCalibState::SETTLING; + start_cogging_calibration(); return; - case CogCalibState::SETTLING: - cog_calib_settle_tick++; - if (cog_calib_settle_tick >= SETTLE_TICKS) { - cog_calib_state = CogCalibState::SAMPLING; - cog_calib_sample_count = 0; - memset(cog_calib_accum, 0, sizeof(cog_calib_accum)); - memset(cog_calib_count, 0, sizeof(cog_calib_count)); - } + cogging_settling_tick(); return; - case CogCalibState::SAMPLING: - // 采样分频 - static u32 sample_div = 0; - sample_div++; - if (sample_div < SAMPLE_DIV) return; - sample_div = 0; - - { - float real_angle = KTH7823.getcurrent_sensorangle(); - float angle_el = normalizeAngle360(real_angle * PP + KTH7823.calibration_angle); - - float iq = rt_current; - - if (fabsf(iq) < 5000.0f) { - u16 idx = (u16)(angle_el / 360.0f * (float)COGGING_TABLE_SIZE); - if (idx >= COGGING_TABLE_SIZE) idx = COGGING_TABLE_SIZE - 1; - cog_calib_accum[idx] += iq; - cog_calib_count[idx]++; - } - cog_calib_sample_count++; - } - if (cog_calib_sample_count >= TARGET_SAMPLES) { - target_velocity = 0; - cog_calib_state = CogCalibState::STOPPING; - cog_calib_stop_tick = 0; - } + cogging_sampling_tick(); return; - case CogCalibState::STOPPING: - cog_calib_stop_tick++; - if (cog_calib_stop_tick >= STOP_TICKS) { - // 处理本轮数据:去直流 - float dc = 0; - for (int i = 0; i < COGGING_TABLE_SIZE; i++) { - cog_calib_run_data[cog_calib_run][i] = - (cog_calib_count[i] > 0) ? cog_calib_accum[i] / (float)cog_calib_count[i] : 0.0f; - dc += cog_calib_run_data[cog_calib_run][i]; - } - dc /= (float)COGGING_TABLE_SIZE; - for (int i = 0; i < COGGING_TABLE_SIZE; i++) { - cog_calib_run_data[cog_calib_run][i] -= dc; - } - - printf("[COG] Run %d/%d done. DC=%.2f mA\r\n", - cog_calib_run + 1, TOTAL_RUNS, dc); - - cog_calib_run++; - if (cog_calib_run < TOTAL_RUNS) { - // run 0~2 正转,run 3~5 反转 - control_mode = 6; - target_velocity = (cog_calib_run < 3) ? SLOW_VEL : -SLOW_VEL; - cog_calib_state = CogCalibState::SETTLING; - cog_calib_settle_tick = 0; - } else { - cog_calib_state = CogCalibState::DONE; - } - } + cogging_stopping_tick(); return; - case CogCalibState::DONE: - // 正转平均 (run 0~2) - for (int i = 0; i < COGGING_TABLE_SIZE; i++) { - cogging_table[i] = (cog_calib_run_data[0][i] + - cog_calib_run_data[1][i] + - cog_calib_run_data[2][i]) / 3.0f; - } - // 反转平均 (run 3~5) - for (int i = 0; i < COGGING_TABLE_SIZE; i++) { - cogging_table_rev[i] = (cog_calib_run_data[3][i] + - cog_calib_run_data[4][i] + - cog_calib_run_data[5][i]) / 3.0f; - } - cogging_comp_enabled = true; - control_mode = cog_calib_saved_mode; - target_velocity = cog_calib_saved_vel; - - // 打印正转平均 - printf("\r\n[COG] === FWD Average ===\r\n"); - for (int i = 0; i < COGGING_TABLE_SIZE; i++) { - float angle_deg = (float)i / (float)COGGING_TABLE_SIZE * 360.0f; - printf("[COG] %.2f, %.2f\r\n", angle_deg, cogging_table[i]); - fwdgt_counter_reload(); - } - - // 打印反转平均 - printf("\r\n[COG] === REV Average ===\r\n"); - for (int i = 0; i < COGGING_TABLE_SIZE; i++) { - float angle_deg = (float)i / (float)COGGING_TABLE_SIZE * 360.0f; - printf("[COG] %.2f, %.2f\r\n", angle_deg, cogging_table_rev[i]); - fwdgt_counter_reload(); - } - - // 打印正转 C Array - printf("\r\n[COG] --- FWD C Array ---\r\n"); - printf("static const float COGGING_TABLE_FWD[COGGING_TABLE_SIZE] = {\r\n"); - for (int i = 0; i < COGGING_TABLE_SIZE; i++) { - if (i % 8 == 0) printf(" "); - printf("%.2ff", cogging_table[i]); - if (i < COGGING_TABLE_SIZE - 1) printf(", "); - if (i % 8 == 7 || i == COGGING_TABLE_SIZE - 1) printf("\r\n"); - fwdgt_counter_reload(); - } - printf("};\r\n"); - - // 打印反转 C Array - printf("\r\n[COG] --- REV C Array ---\r\n"); - printf("static const float COGGING_TABLE_REV[COGGING_TABLE_SIZE] = {\r\n"); - for (int i = 0; i < COGGING_TABLE_SIZE; i++) { - if (i % 8 == 0) printf(" "); - printf("%.2ff", cogging_table_rev[i]); - if (i < COGGING_TABLE_SIZE - 1) printf(", "); - if (i % 8 == 7 || i == COGGING_TABLE_SIZE - 1) printf("\r\n"); - fwdgt_counter_reload(); - } - printf("};\r\n"); - - printf("\r\n[COG] === Done. ===\r\n"); + cogging_done_tick(); + return; + default: + // 防御性处理:枚举值异常时回到空闲态,避免状态机卡死。 cog_calib_state = CogCalibState::IDLE; return; } } + +void FOC_Controller::start_cogging_calibration(void) { + // IDLE 状态下没有请求则保持空闲,不扰动正常控制路径。 + if (!cogging_calib_request) return; + cogging_calib_request = false; + + cog_calib_run = 0; + // 保存原控制状态,校准完成后恢复现场。 + cog_calib_saved_mode = control_mode; + cog_calib_saved_vel = target_velocity; + cogging_comp_enabled = false; + memset(cog_calib_run_data, 0, sizeof(cog_calib_run_data)); + + printf("\r\n[COG] === Cogging Calibration: 3 fwd + 3 rev ===\r\n"); + + control_mode = 6; + target_velocity = COG_CALIB_SLOW_VEL; + cog_calib_settle_tick = 0; + cog_calib_state = CogCalibState::SETTLING; +} + +void FOC_Controller::cogging_settling_tick(void) { + // 给低速开环运行留出稳定时间,再开始采样。 + cog_calib_settle_tick++; + if (cog_calib_settle_tick < COG_CALIB_SETTLE_TICKS) return; + + cog_calib_state = CogCalibState::SAMPLING; + cog_calib_sample_count = 0; + memset(cog_calib_accum, 0, sizeof(cog_calib_accum)); + memset(cog_calib_count, 0, sizeof(cog_calib_count)); +} + +void FOC_Controller::cogging_sampling_tick(void) { + static u32 sample_div = 0; + // 每 1ms 采一次,降低校准数据相关处理对实时控制的影响。 + sample_div++; + if (sample_div < COG_CALIB_SAMPLE_DIV) return; + sample_div = 0; + + float real_angle = KTH7823.getcurrent_sensorangle(); + float angle_el = normalizeAngle360(real_angle * PP + KTH7823.calibration_angle); + float iq = rt_current; + + if (fabsf(iq) < 5000.0f) { + u16 idx = (u16)(angle_el / 360.0f * (float)COGGING_TABLE_SIZE); + if (idx >= COGGING_TABLE_SIZE) idx = COGGING_TABLE_SIZE - 1; + cog_calib_accum[idx] += iq; + cog_calib_count[idx]++; + } + cog_calib_sample_count++; + + if (cog_calib_sample_count >= COG_CALIB_TARGET_SAMPLES) { + target_velocity = 0; + cog_calib_state = CogCalibState::STOPPING; + cog_calib_stop_tick = 0; + } +} + +void FOC_Controller::cogging_stopping_tick(void) { + // 每轮采样结束后先停稳,再计算本轮去直流数据。 + cog_calib_stop_tick++; + if (cog_calib_stop_tick < COG_CALIB_STOP_TICKS) return; + + float dc = process_cogging_run_data(); + printf("[COG] Run %d/%d done. DC=%.2f mA\r\n", + cog_calib_run + 1, COG_CALIB_TOTAL_RUNS, dc); + + cog_calib_run++; + if (cog_calib_run < COG_CALIB_TOTAL_RUNS) { + start_next_cogging_run(); + } else { + cog_calib_state = CogCalibState::DONE; + } +} + +float FOC_Controller::process_cogging_run_data(void) { + // 先求每个角度桶的平均电流,再统一去除直流偏置。 + float dc = 0; + for (int i = 0; i < COGGING_TABLE_SIZE; i++) { + cog_calib_run_data[cog_calib_run][i] = + (cog_calib_count[i] > 0) ? cog_calib_accum[i] / (float)cog_calib_count[i] : 0.0f; + dc += cog_calib_run_data[cog_calib_run][i]; + } + dc /= (float)COGGING_TABLE_SIZE; + for (int i = 0; i < COGGING_TABLE_SIZE; i++) { + cog_calib_run_data[cog_calib_run][i] -= dc; + } + return dc; +} + +void FOC_Controller::start_next_cogging_run(void) { + control_mode = 6; + target_velocity = (cog_calib_run < 3) ? COG_CALIB_SLOW_VEL : -COG_CALIB_SLOW_VEL; + cog_calib_state = CogCalibState::SETTLING; + cog_calib_settle_tick = 0; +} + +void FOC_Controller::cogging_done_tick(void) { + // 生成最终表后恢复用户进入校准前的控制模式和目标速度。 + build_cogging_tables(); + cogging_comp_enabled = true; + control_mode = cog_calib_saved_mode; + target_velocity = cog_calib_saved_vel; + + print_cogging_table("\r\n[COG] === FWD Average ===\r\n", cogging_table); + print_cogging_table("\r\n[COG] === REV Average ===\r\n", cogging_table_rev); + print_cogging_c_array("\r\n[COG] --- FWD C Array ---\r\n", + "COGGING_TABLE_FWD", cogging_table); + print_cogging_c_array("\r\n[COG] --- REV C Array ---\r\n", + "COGGING_TABLE_REV", cogging_table_rev); + + printf("\r\n[COG] === Done. ===\r\n"); + cog_calib_state = CogCalibState::IDLE; +} + +void FOC_Controller::build_cogging_tables(void) { + // 前 3 轮为正转,后 3 轮为反转,分别求平均表。 + for (int i = 0; i < COGGING_TABLE_SIZE; i++) { + cogging_table[i] = (cog_calib_run_data[0][i] + + cog_calib_run_data[1][i] + + cog_calib_run_data[2][i]) / 3.0f; + cogging_table_rev[i] = (cog_calib_run_data[3][i] + + cog_calib_run_data[4][i] + + cog_calib_run_data[5][i]) / 3.0f; + } +} + +void FOC_Controller::print_cogging_table(const char *title, float *table) { + printf("%s", title); + for (int i = 0; i < COGGING_TABLE_SIZE; i++) { + float angle_deg = (float)i / (float)COGGING_TABLE_SIZE * 360.0f; + printf("[COG] %.2f, %.2f\r\n", angle_deg, table[i]); + fwdgt_counter_reload(); + } +} + +void FOC_Controller::print_cogging_c_array(const char *title, + const char *arrayName, + float *table) { + printf("%s", title); + printf("static const float %s[COGGING_TABLE_SIZE] = {\r\n", arrayName); + for (int i = 0; i < COGGING_TABLE_SIZE; i++) { + if (i % 8 == 0) printf(" "); + printf("%.2ff", table[i]); + if (i < COGGING_TABLE_SIZE - 1) printf(", "); + if (i % 8 == 7 || i == COGGING_TABLE_SIZE - 1) printf("\r\n"); + fwdgt_counter_reload(); + } + printf("};\r\n"); +} #endif // FOC_DEBUG // 初始变量及函数定义 diff --git a/Robot_V2/Algorithm/FOC.h b/Robot_V2/Algorithm/FOC.h index c8eeb6b..80857d6 100644 --- a/Robot_V2/Algorithm/FOC.h +++ b/Robot_V2/Algorithm/FOC.h @@ -167,10 +167,66 @@ class FOC_Controller { #if FOC_DEBUG volatile bool cogging_calib_request = false; /** - * @brief calibrate_cogging_tick 函数 + * @brief 齿槽校准状态机入口,每个周期按当前状态派发到对应处理函数 * @return void */ void calibrate_cogging_tick(void); + /** + * @brief 处理齿槽校准空闲态,收到请求后保存现场并进入稳定等待态 + * @return void + */ + void start_cogging_calibration(void); + /** + * @brief 处理齿槽校准稳定等待态,等待电机进入稳定低速运行 + * @return void + */ + void cogging_settling_tick(void); + /** + * @brief 处理齿槽校准采样态,按分频周期累计每个电角度桶的电流 + * @return void + */ + void cogging_sampling_tick(void); + /** + * @brief 处理齿槽校准停止态,等待停稳后完成本轮数据归一化 + * @return void + */ + void cogging_stopping_tick(void); + /** + * @brief 处理齿槽校准完成态,生成补偿表并打印校准结果 + * @return void + */ + void cogging_done_tick(void); + /** + * @brief 计算当前校准轮次的平均电流并去除直流偏置 + * @return float 当前轮次直流偏置,单位 mA + */ + float process_cogging_run_data(void); + /** + * @brief 切换到下一轮齿槽校准运行方向并重新进入稳定等待态 + * @return void + */ + void start_next_cogging_run(void); + /** + * @brief 根据 3 次正转和 3 次反转采样结果生成最终补偿表 + * @return void + */ + void build_cogging_tables(void); + /** + * @brief 打印齿槽补偿表的人类可读角度/电流列表 + * @param[in] title 表格标题字符串 + * @param[in] table 待打印的补偿表 + * @return void + */ + void print_cogging_table(const char *title, float *table); + /** + * @brief 打印可直接复制回源码的齿槽补偿 C 数组 + * @param[in] title 输出标题字符串 + * @param[in] arrayName C 数组名 + * @param[in] table 待打印的补偿表 + * @return void + */ + void print_cogging_c_array(const char *title, const char *arrayName, + float *table); /** * @brief CogCalibState 枚举定义 */ diff --git a/Robot_V2/Robot_V2.cproject.yml b/Robot_V2/Robot_V2.cproject.yml index 60c191f..b5ae6b8 100644 --- a/Robot_V2/Robot_V2.cproject.yml +++ b/Robot_V2/Robot_V2.cproject.yml @@ -4,6 +4,8 @@ project: files: - file: main.h - file: main.cpp + - file: tasks.h + - file: tasks.cpp - group: sys files: - file: sys/systick.cpp diff --git a/Robot_V2/device/ADC.cpp b/Robot_V2/device/ADC.cpp index 74044af..7ec12e7 100644 --- a/Robot_V2/device/ADC.cpp +++ b/Robot_V2/device/ADC.cpp @@ -16,6 +16,8 @@ #include #include +// ADC 换算常量均为编译期常量,非可变全局状态。 +// 用于 ADC 原始值到电压、温度、电流的固定比例换算。 /** @brief ADC_SCALE 常量定义 */ constexpr float ADC_SCALE = 3.3f / 4096.0f; /** @brief TEMP_FACTOR 常量定义 */ @@ -27,6 +29,9 @@ constexpr float CURRENT_FACTOR = 1.0f / 50.0f * 100.0f; // 50倍放大,100倍电流采样电阻, 单位mA extern CAN_transmiter CAN0_transmiter; extern FOC_Controller FOC; + +// 全局采样器单例:系统只有一组 ADC 采样缓存,由 hardware_init() 调用 sampler.init() 初始化。 +// regular DMA 写 adc_value[];injected ADC 中断写 adc_current_value[] 并置位 adc_current_frame_ready。 sampler sampler; // 采样器初始化 u16 adc_value[3] = {0}; // regular DMA: MCU温度 / DRV温度 / VBUS static volatile u16 adc_current_value[3] = {0}; // injected 同步采样: U / V / W diff --git a/Robot_V2/device/Can.cpp b/Robot_V2/device/Can.cpp index fdd41de..2b75bb6 100644 --- a/Robot_V2/device/Can.cpp +++ b/Robot_V2/device/Can.cpp @@ -33,6 +33,7 @@ #define CAN_DEBUG_PRINT(fmt, ...) ((void)0) #endif +// CAN0 固定外设号,编译期常量,非可变全局状态。 /** @brief CANX 常量定义 */ constexpr u16 CANX = CAN0; extern FOC_Controller FOC; @@ -44,7 +45,9 @@ extern FlashEEPROM flashEEP; extern VersionManager Version; // 声明外部版本管理器实例 extern u32 motor_runtime_50us; // 电机驱动计算轮次(用于静默模式计时) -// 添加 DMA 相关变量 +// CAN RX DMA 与收发队列模块状态: +// - DMA 元数据/busy 标志由 CAN RX 中断与 DMA 完成中断协同维护; +// - rx/tx 队列 head/tail/count 同时被主循环和中断访问,队列操作通过 CAN_EnterCritical()/CAN_ExitCritical() 保护。 dma_parameter_struct dma_rx_parameter; uint8_t can_rx_buffer[64]; // CAN 接收缓冲区(支持CAN FD最大64字节) static uint32_t can_rx_dma_words[16]; // CAN FD接收DMA按32位搬运,最多64字节 @@ -81,6 +84,8 @@ static volatile u8 tx_queue_count = 0; // CAN FD DLC 9~15对应的非线性payload长度表 static constexpr u8 CANFD_DLC_LENGTH_TABLE[7] = {12, 16, 20, 24, 32, 48, 64}; +// 全局 CAN 发送器单例:系统只有一个 CAN0 通信对象。 +// 初始化顺序由 main::hardware_init() 调用 CAN0_transmiter.init() 保证。 CAN_transmiter CAN0_transmiter; // CAN 初始化 // ============================================================================ @@ -701,8 +706,10 @@ u8 CAN_transmiter::init() { } /** - * @brief CAN_EnterCritical 函数 - * @return uint32_t 返回值 + * @brief 进入 CAN 临界区并保存原 PRIMASK 状态 + * @return uint32_t 进入临界区前的 PRIMASK,用于退出时恢复原中断状态 + * @note 退出时使用 CAN_ExitCritical() 调用 __set_PRIMASK() 恢复, + * 因此这里不会直接与 __enable_irq() 成对出现。 */ static uint32_t CAN_EnterCritical(void) { uint32_t primask = __get_PRIMASK(); @@ -711,9 +718,10 @@ static uint32_t CAN_EnterCritical(void) { } /** - * @brief CAN_ExitCritical 函数 - * @param[in] primask 参数 + * @brief 退出 CAN 临界区并恢复进入前的中断屏蔽状态 + * @param[in] primask CAN_EnterCritical() 返回的原 PRIMASK 状态 * @return void + * @note 不直接调用 __enable_irq(),避免误开启外层已经关闭的中断。 */ static void CAN_ExitCritical(uint32_t primask) { __set_PRIMASK(primask); } @@ -1074,228 +1082,217 @@ u8 CAN_transmiter::CANFD_all_Send() { * 1: 有新消息 */ void CAN_transmiter::communication_check() { - // CAN0 发送数据标志 - if (SET == can0_send_flag) { - // 检查是否在静默模式中 - if (!is_in_silent_mode()) { - update(); - CANFD_all_Send(); - } else { - check_silent_mode(); // 检查静默是否结束 - } - can0_send_flag = RESET; - } + // 先处理周期发送,再抽空接收队列,保持原来的调度顺序。 + handle_periodic_send_tick(); - // 处理所有接收到的 CANFD 消息(从底层中断队列中取出) can_rx_queue_item_t rx_item; while (CAN_RX_QueuePop(&rx_item)) { - can_receive_message_struct current_message; - can_struct_para_init(CAN_RX_MESSAGE_STRUCT, ¤t_message); - current_message.rx_efid = rx_item.id; - current_message.rx_dlen = rx_item.length; - memcpy(current_message.rx_data, rx_item.data, rx_item.length); + handle_received_queue_item(rx_item.id, rx_item.length, rx_item.data); + } +} - // 提取基础 ID (低 16 位,去除高 16 位节点 ID 部分) - u32 base_id = current_message.rx_efid & 0xFFFF; +void CAN_transmiter::handle_periodic_send_tick() { + // 无发送请求时直接返回,避免每周期进入发送逻辑。 + if (SET != can0_send_flag) return; - // 提取发送方的节点 ID - u16 sender_node_id = (u16)((current_message.rx_efid >> 16) & 0xFFFF); + if (!is_in_silent_mode()) { + update(); + CANFD_all_Send(); + } else { + check_silent_mode(); + } + can0_send_flag = RESET; +} - // 特殊处理 base_id=0x0000(BOOT 命令):即使是其他节点的广播消息也要处理 - if (base_id == CANFD_BOOT_ID) { - // 判断是广播消息还是其他节点的命令 - if (sender_node_id == BROADCAST_NODE_ID || sender_node_id != node_id) { - // 广播消息或其他节点的命令 → 进入静默模式(停发 10 秒) - CAN_DEBUG_PRINT( - "Received base_id=0x0000 from node %d, entering silent mode for 10s\r\n", - sender_node_id); - enter_silent_mode(); - } else { - // 自己节点的命令 → 执行 BOOT 功能 - CAN_DEBUG_PRINT( - "Received base_id=0x0000 from self, jumping to BOOT\r\n"); - motor_sleep(); - flashEEP.save_system_config(false); - fmc_unlock(); - OTA_SET(n_APP_LATEST_EN_ADDR, OTA_NOT_AVAL); // 设置 APP 为不激活 - fmc_lock(); - jumpToBOOT(); - } - continue; // BOOT 命令处理完跳过后续逻辑 - } +void CAN_transmiter::handle_received_queue_item(u32 id, u8 length, const u8 *data) { + // 将轻量队列项还原为原 CAN 消息结构,后续沿用原解析逻辑。 + can_receive_message_struct current_message; + can_struct_para_init(CAN_RX_MESSAGE_STRUCT, ¤t_message); + current_message.rx_efid = id; + current_message.rx_dlen = length; + memcpy(current_message.rx_data, data, length); - // 设置节点 ID 命令 (基础 ID: 0x0400) - // 注意:此命令在 is_message_for_me 检查之前处理,所有节点都会响应 - // 这样即使节点ID已经改变,仍然可以用旧节点ID或广播ID来修改 - else if (base_id == CANFD_SET_NODE_ID) { - // 打印接收到的原始数据 - CAN_DEBUG_PRINT( - "SET_NODE_ID: rx_dlen=%d, data[0]=%02X data[1]=%02X\r\n", - current_message.rx_dlen, current_message.rx_data[0], - current_message.rx_data[1]); - // 解析新节点 ID - 支持 1 字节或 2 字节格式 - u16 new_node_id; - if (current_message.rx_dlen >= 2) { - new_node_id = unpack_bytes_to_u16(current_message.rx_data, 0); - } else { - new_node_id = current_message.rx_data[0]; // 只发送 1 字节时直接使用 - } - u16 old_node_id = node_id; // 保存旧节点 ID 用于构建响应 ID - CAN_DEBUG_PRINT( - "SET_NODE_ID: recv new_id=%d (0x%04X), old_id=%d\r\n", + u32 base_id = current_message.rx_efid & 0xFFFF; + u16 sender_node_id = (u16)((current_message.rx_efid >> 16) & 0xFFFF); + + // BOOT 和 SET_NODE_ID 需要在节点过滤前处理。 + if (handle_special_message(current_message, base_id, sender_node_id)) return; + if (!is_message_for_me(current_message.rx_efid)) return; + + handle_node_message(current_message, base_id); +} + +bool CAN_transmiter::handle_special_message(can_receive_message_struct ¤t_message, + u32 base_id, u16 sender_node_id) { + if (base_id == CANFD_BOOT_ID) { + handle_boot_message(sender_node_id); + return true; + } + if (base_id == CANFD_SET_NODE_ID) { + handle_set_node_id_message(current_message); + return true; + } + return false; +} + +void CAN_transmiter::handle_boot_message(u16 sender_node_id) { + // 其他节点或广播 BOOT 命令只让本节点静默,避免抢占总线。 + if (sender_node_id == BROADCAST_NODE_ID || sender_node_id != node_id) { + CAN_DEBUG_PRINT( + "Received base_id=0x0000 from node %d, entering silent mode for 10s\r\n", + sender_node_id); + enter_silent_mode(); + return; + } + + CAN_DEBUG_PRINT("Received base_id=0x0000 from self, jumping to BOOT\r\n"); + motor_sleep(); + flashEEP.save_system_config(false); + fmc_unlock(); + OTA_SET(n_APP_LATEST_EN_ADDR, OTA_NOT_AVAL); + fmc_lock(); + jumpToBOOT(); +} + +void CAN_transmiter::handle_set_node_id_message(can_receive_message_struct ¤t_message) { + // 支持 1 字节和 2 字节节点 ID 命令,兼容旧上位机。 + CAN_DEBUG_PRINT( + "SET_NODE_ID: rx_dlen=%d, data[0]=%02X data[1]=%02X\r\n", + current_message.rx_dlen, current_message.rx_data[0], + current_message.rx_data[1]); + + u16 new_node_id = (current_message.rx_dlen >= 2) ? + unpack_bytes_to_u16(current_message.rx_data, 0) : current_message.rx_data[0]; + u16 old_node_id = node_id; + + CAN_DEBUG_PRINT("SET_NODE_ID: recv new_id=%d (0x%04X), old_id=%d\r\n", new_node_id, new_node_id, old_node_id); - if (new_node_id <= MAX_NODE_ID && new_node_id != BROADCAST_NODE_ID) { - // 先发送响应(使用旧节点ID构建CAN ID) - // 响应数据格式:[status:1byte][new_node_id:2byte] = 3 字节 - transmit_data[0] = 0x01; // 成功 - transmit_data[1] = (u8)(new_node_id & 0xFF); // 新 ID 低字节 - transmit_data[2] = - (u8)((new_node_id >> 8) & 0xFF); // 新 ID 高字节 + transmit_data[1] = (u8)(new_node_id & 0xFF); + transmit_data[2] = (u8)((new_node_id >> 8) & 0xFF); + // 响应帧使用旧节点 ID 构造,确保上位机能收到确认。 + node_id = old_node_id; - // 使用旧节点 ID 发送响应 - node_id = old_node_id; - u8 send_result = CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3, - transmit_data); + if (new_node_id > MAX_NODE_ID || new_node_id == BROADCAST_NODE_ID) { + CAN_DEBUG_PRINT("SET_NODE_ID: invalid new_id=%d (max=%d)\r\n", + new_node_id, MAX_NODE_ID); + transmit_data[0] = 0x00; + CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3, transmit_data); + return; + } - // 然后保存新节点 ID 到 Flash - u8 save_status = save_node_id_to_flash(new_node_id); + transmit_data[0] = 0x01; + u8 send_result = CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3, + transmit_data); + u8 save_status = save_node_id_to_flash(new_node_id); - CAN_DEBUG_PRINT( - "SET_NODE_ID: save_status=%d, send_result=%d\r\n", - save_status, send_result); + CAN_DEBUG_PRINT("SET_NODE_ID: save_status=%d, send_result=%d\r\n", + save_status, send_result); - if (save_status == FLASH_EEPROM_OK) { - CAN_DEBUG_PRINT( - "SET_NODE_ID: SUCCESS, node_id updated from %d to %d\r\n", - old_node_id, new_node_id); - } else { - CAN_DEBUG_PRINT( - "SET_NODE_ID: Flash write FAILED, status=%d\r\n", - save_status); - // 发送错误响应 - transmit_data[0] = 0x00; // 失败 - node_id = old_node_id; - CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3, - transmit_data); - } - } else { - // 发送错误响应 - 使用旧节点 ID 构建响应 ID - CAN_DEBUG_PRINT("SET_NODE_ID: invalid new_id=%d (max=%d)\r\n", - new_node_id, MAX_NODE_ID); - transmit_data[0] = 0x00; // 失败 - transmit_data[1] = (u8)(new_node_id & 0xFF); - transmit_data[2] = (u8)((new_node_id >> 8) & 0xFF); - node_id = old_node_id; // 临时恢复旧 ID 用于构建响应 - CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3, - transmit_data); - // 保持原 ID 不变 - } - continue; // SET_NODE_ID 命令处理完跳过后续逻辑 - } + if (save_status == FLASH_EEPROM_OK) { + CAN_DEBUG_PRINT("SET_NODE_ID: SUCCESS, node_id updated from %d to %d\r\n", + old_node_id, new_node_id); + return; + } - // 其他 base_id 的消息,检查是否是发给本节点的 - if (!is_message_for_me(current_message.rx_efid)) { - continue; // 不是发给本节点的消息,跳过 - } + CAN_DEBUG_PRINT("SET_NODE_ID: Flash write FAILED, status=%d\r\n", save_status); + transmit_data[0] = 0x00; + node_id = old_node_id; + CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3, transmit_data); +} - if (base_id == CANFD_CMD_ID) { - // 控制模式 - FOC.control_mode = current_message.rx_data[0]; - // 目标电压限制 - FOC.limit_voltage = - float(unpack_bytes_to_u16(current_message.rx_data, 1)) / 1000; - // 目标电流 - target_current_union.f = - unpack_bytes_to_float(current_message.rx_data, 3).f; - FOC.current_loop.set_limit(target_current_union.f); - FOC.vel_inMITloop.set_limit(target_current_union.f); - // 目标速度 - target_velocity_union.f = - unpack_bytes_to_float(current_message.rx_data, 7).f; - FOC.ang_inVelAngloop.set_limit(target_velocity_union.f); - FOC.ang_inMITloop.set_limit(target_velocity_union.f); - // 目标角度 - target_angle_union.f = - unpack_bytes_to_float(current_message.rx_data, 11).f; - // 赋值 - FOC.target_angle = target_angle_union.f; - FOC.target_velocity = target_velocity_union.f; - FOC.target_current = target_current_union.f; +void CAN_transmiter::handle_node_message(can_receive_message_struct ¤t_message, + u32 base_id) { + // 这里只处理已经确认发给本节点的普通协议消息。 + if (base_id == CANFD_CMD_ID) { + handle_control_command(current_message); + } else if (base_id == CANFD_RESET_ID) { + handle_reset_command(current_message); + } else if (base_id == CANFD_VERSION_ID) { + Version.sendVersionInfoViaCan(); + } else if (base_id == CANFD_OTA_ACK_ID) { + handle_ota_ack_message(); + } else if (base_id == CANFD_GET_NODE_ID) { + pack_u16_to_bytes(transmit_data, 0, get_node_id()); + CANFD_AsyncSend(build_can_id(CANFD_GET_NODE_ID), 2, transmit_data); + } else { + CAN_DEBUG_PRINT("Unknown CAN ID: %X (node_id=%d)\n", + current_message.rx_efid, node_id); + } +} - // 解析 PID 参数 - float voltage = FOC.limit_voltage; - // PID 参数 - u16 P_config = unpack_bytes_to_u16(current_message.rx_data, 15); - FOC.cur_loop_p = DEFAULT_current_loop_p * P_config / voltage; - FOC.cur_MIT_p = DEFAULT_current_MIT_p * P_config / voltage; +void CAN_transmiter::handle_control_command(can_receive_message_struct ¤t_message) { + // 控制帧布局:模式、电压限制、目标电流/速度/角度、三组 P 参数。 + FOC.control_mode = current_message.rx_data[0]; + FOC.limit_voltage = float(unpack_bytes_to_u16(current_message.rx_data, 1)) / 1000; + target_current_union.f = unpack_bytes_to_float(current_message.rx_data, 3).f; + FOC.current_loop.set_limit(target_current_union.f); + FOC.vel_inMITloop.set_limit(target_current_union.f); + target_velocity_union.f = unpack_bytes_to_float(current_message.rx_data, 7).f; + FOC.ang_inVelAngloop.set_limit(target_velocity_union.f); + FOC.ang_inMITloop.set_limit(target_velocity_union.f); + target_angle_union.f = unpack_bytes_to_float(current_message.rx_data, 11).f; + FOC.target_angle = target_angle_union.f; + FOC.target_velocity = target_velocity_union.f; + FOC.target_current = target_current_union.f; - P_config = unpack_bytes_to_u16(current_message.rx_data, 17); - FOC.vel_loop_p = DEFAULT_vel_loop_p * P_config / voltage; - FOC.vel_angloop_p = DEFAULT_vel_angloop_p * P_config; - FOC.vel_MIT_p = DEFAULT_vel_MIT_p * P_config; + float voltage = FOC.limit_voltage; + u16 P_config = unpack_bytes_to_u16(current_message.rx_data, 15); + FOC.cur_loop_p = DEFAULT_current_loop_p * P_config / voltage; + FOC.cur_MIT_p = DEFAULT_current_MIT_p * P_config / voltage; + + P_config = unpack_bytes_to_u16(current_message.rx_data, 17); + FOC.vel_loop_p = DEFAULT_vel_loop_p * P_config / voltage; + FOC.vel_angloop_p = DEFAULT_vel_angloop_p * P_config; + FOC.vel_MIT_p = DEFAULT_vel_MIT_p * P_config; - P_config = unpack_bytes_to_u16(current_message.rx_data, 19); - FOC.ang_loop_p = DEFAULT_angle_loop_p * P_config / voltage; - FOC.ang_angloop_p = DEFAULT_angle_angloop_p * P_config / voltage; - FOC.ang_MIT_p = DEFAULT_angle_MIT_p * P_config / voltage; - } else if (base_id == CANFD_RESET_ID) { - u8 first_byte = current_message.rx_data[0]; - u8 eigth_byte = current_message.rx_data[7]; - // 电机重置 - if (first_byte & 0x01) { - motor_reset(); - testfloat_union.f = KTH7823.getcurrent_sensorangle(); - } - // EEPROM 重置 - if (first_byte >> 1 & 0x01) { - // flashEEP.format(); - } - // 系统参数保存 - if (first_byte >> 2 & 0x01) { - flashEEP.save_system_config(false); - } - // 角度传感器重置 - if (first_byte >> 3 & 0x01) { - KTH7823.angle_set(0); - Angle_kalman.init(0); // 角度滤波 - Vel_kalman.init(0); // 角度滤波 - } - // 齿槽转矩校准 (first_byte bit4) — DEBUG,平时不启用 - #if FOC_DEBUG - if (first_byte >> 4 & 0x01) { - FOC.cogging_calib_request = true; - } - #endif - // 休眠 - if (eigth_byte & 0x80 >> 7) { - app_prepare_deep_sleep(true); - // CAN_Sleep(); - } - } else if (base_id == CANFD_VERSION_ID) { - // 处理版本号请求,发送当前系统版本信息 - Version.sendVersionInfoViaCan(); - } else if (base_id == CANFD_OTA_ACK_ID) { - volatile u32 *flash_ptr = (volatile u32 *)n_APP_LATEST_EN_ADDR; - u32 app_enable = *flash_ptr; - flash_ptr = (volatile u32 *)n_APP_LATEST_CHECK_ADDR; - u32 app_check = *flash_ptr; - transmit_data[0] = 0x0A; - transmit_data[1] = 0xAC; - delay_1ms(1); - if (app_enable == OTA_AVAL && app_check == OTA_AVAL) { - CANFD_AsyncSend(build_can_id(CANFD_OTA_ACK_ID), 2, - transmit_data); - } - } - // 查询节点 ID 命令 (基础 ID: 0x0402) - else if (base_id == CANFD_GET_NODE_ID) { - pack_u16_to_bytes(transmit_data, 0, get_node_id()); - CANFD_AsyncSend(build_can_id(CANFD_GET_NODE_ID), 2, transmit_data); - } else { - CAN_DEBUG_PRINT("Unknown CAN ID: %X (node_id=%d)\n", - current_message.rx_efid, node_id); - } + P_config = unpack_bytes_to_u16(current_message.rx_data, 19); + FOC.ang_loop_p = DEFAULT_angle_loop_p * P_config / voltage; + FOC.ang_angloop_p = DEFAULT_angle_angloop_p * P_config / voltage; + FOC.ang_MIT_p = DEFAULT_angle_MIT_p * P_config / voltage; +} + +void CAN_transmiter::handle_reset_command(can_receive_message_struct ¤t_message) { + // RESET 帧按 bit 位触发独立动作,保持与原协议兼容。 + u8 first_byte = current_message.rx_data[0]; + u8 eigth_byte = current_message.rx_data[7]; + + if (first_byte & 0x01) { + motor_reset(); + testfloat_union.f = KTH7823.getcurrent_sensorangle(); + } + if (first_byte >> 1 & 0x01) { + // flashEEP.format(); + } + if (first_byte >> 2 & 0x01) { + flashEEP.save_system_config(false); + } + if (first_byte >> 3 & 0x01) { + KTH7823.angle_set(0); + Angle_kalman.init(0); + Vel_kalman.init(0); + } + #if FOC_DEBUG + if (first_byte >> 4 & 0x01) { + FOC.cogging_calib_request = true; + } + #endif + if (eigth_byte & 0x80 >> 7) { + app_prepare_deep_sleep(true); + } +} + +void CAN_transmiter::handle_ota_ack_message() { + // 只有当前 APP 标记为可用且校验通过时才回 ACK。 + volatile u32 *flash_ptr = (volatile u32 *)n_APP_LATEST_EN_ADDR; + u32 app_enable = *flash_ptr; + flash_ptr = (volatile u32 *)n_APP_LATEST_CHECK_ADDR; + u32 app_check = *flash_ptr; + transmit_data[0] = 0x0A; + transmit_data[1] = 0xAC; + delay_1ms(1); + if (app_enable == OTA_AVAL && app_check == OTA_AVAL) { + CANFD_AsyncSend(build_can_id(CANFD_OTA_ACK_ID), 2, transmit_data); } } diff --git a/Robot_V2/device/Can.h b/Robot_V2/device/Can.h index 47c0268..649948d 100644 --- a/Robot_V2/device/Can.h +++ b/Robot_V2/device/Can.h @@ -235,6 +235,65 @@ class CAN_transmiter { * @return void */ void check_silent_mode(); // 检查静默模式是否结束 + /** + * @brief 处理周期性发送请求,静默模式下只检查是否退出静默 + * @return void + */ + void handle_periodic_send_tick(); + /** + * @brief 将底层接收队列项转换为 CAN 消息并按协议分发 + * @param[in] id 接收到的扩展 CAN ID + * @param[in] length 接收数据长度 + * @param[in] data 接收数据指针 + * @return void + */ + void handle_received_queue_item(u32 id, u8 length, const u8 *data); + /** + * @brief 处理不受本节点过滤限制的特殊消息 + * @param[in,out] current_message 当前 CAN 消息 + * @param[in] base_id 低 16 位基础 ID + * @param[in] sender_node_id 发送方节点 ID + * @return bool true 表示消息已处理,false 表示继续按普通节点消息处理 + */ + bool handle_special_message(can_receive_message_struct ¤t_message, + u32 base_id, u16 sender_node_id); + /** + * @brief 处理 BOOT 命令或其他节点 BOOT 广播导致的静默逻辑 + * @param[in] sender_node_id 发送方节点 ID + * @return void + */ + void handle_boot_message(u16 sender_node_id); + /** + * @brief 处理节点 ID 设置命令,发送应答并保存新 ID 到 Flash + * @param[in,out] current_message 当前 CAN 消息 + * @return void + */ + void handle_set_node_id_message(can_receive_message_struct ¤t_message); + /** + * @brief 处理已经确认发给本节点的普通协议消息 + * @param[in,out] current_message 当前 CAN 消息 + * @param[in] base_id 低 16 位基础 ID + * @return void + */ + void handle_node_message(can_receive_message_struct ¤t_message, + u32 base_id); + /** + * @brief 解析控制命令并更新 FOC 目标值和 PID 参数 + * @param[in,out] current_message 当前 CAN 消息 + * @return void + */ + void handle_control_command(can_receive_message_struct ¤t_message); + /** + * @brief 解析复位命令各 bit 位并执行对应动作 + * @param[in,out] current_message 当前 CAN 消息 + * @return void + */ + void handle_reset_command(can_receive_message_struct ¤t_message); + /** + * @brief 处理 OTA ACK 查询,APP 有效时回发确认帧 + * @return void + */ + void handle_ota_ack_message(); public: // 节点 ID 相关辅助函数 (使用高 16 位存储节点 ID) diff --git a/Robot_V2/device/Timer.cpp b/Robot_V2/device/Timer.cpp index ae7ec4c..74719f0 100644 --- a/Robot_V2/device/Timer.cpp +++ b/Robot_V2/device/Timer.cpp @@ -15,7 +15,7 @@ #include "gd32c10x_timer.h" #include "initial.h" -volatile u32 total_us100_counter = 0; // 总计数器,每 100 微秒增加 1 +volatile u32 total_us100_counter = 0; // TIMER2 ISR 内部 100us 调度索引 extern u16 adc_value[3]; extern FOC_Controller FOC; extern TaskScheduler scheduler; diff --git a/Robot_V2/device/ang_sensor.cpp b/Robot_V2/device/ang_sensor.cpp index b37917f..d7d8b15 100644 --- a/Robot_V2/device/ang_sensor.cpp +++ b/Robot_V2/device/ang_sensor.cpp @@ -34,10 +34,12 @@ /** @brief DRV_SPI_CS_DISABLE 宏定义 */ #define DRV_SPI_CS_DISABLE() gpio_bit_set(GPIOB, GPIO_PIN_14) -// DMA 传输完成标志 +// SPI DMA 完成标志:由 DMA ISR 置位,spi_rw_half_word_dma() 发起传输前清零并等待。 volatile bool spi_dma_tx_complete = false; volatile bool spi_dma_rx_complete = false; +// 全局角度传感器单例:系统只有一个 KTH7823 角度传感器。 +// 初始化顺序由 main::hardware_init() 调用 KTH7823.init() 保证。 angle_sensor KTH7823; // 角度传感器初始化 extern FlashEEPROM flashEEP; extern EncoderAngleKalman Angle_kalman; diff --git a/Robot_V2/device/motor.cpp b/Robot_V2/device/motor.cpp index eaf5cd1..db76814 100644 --- a/Robot_V2/device/motor.cpp +++ b/Robot_V2/device/motor.cpp @@ -35,7 +35,9 @@ void motor_init(void) { */ void motor_reset(void) { motor_sleep(); +#if FOC_DEBUG printf("motor reseting...\r\n"); +#endif motor_resume(); } diff --git a/Robot_V2/main.cpp b/Robot_V2/main.cpp index d352c55..77c8622 100644 --- a/Robot_V2/main.cpp +++ b/Robot_V2/main.cpp @@ -5,6 +5,7 @@ */ #include "main.h" +#include "tasks.h" #include "Can.h" #include "FOC.h" #include "Filter.h" @@ -30,29 +31,16 @@ extern sampler sampler; // 采样器 extern angle_sensor KTH7823; // 角度传感器 extern EncoderVelocityKalman Vel_kalman; // 速度滤波 extern EncoderAngleKalman Angle_kalman; // 角度滤波 -extern ThermalController thermalController; // 热量控制器 extern FOC_Controller FOC; // FOC 控制 extern FlashEEPROM flashEEP; // EEPROM 类 -extern VersionManager Version; -// 全局版本管理器实例 +// 主任务调度共享位图:TIMER2 ISR 置位任务,main 循环短关中断后取快照并清位。 TaskScheduler scheduler = {0}; -u32 motor_runtime_50us = 0; // 电机驱动计算轮次 -u32 cnt_test = 0; // 负载检测 +// 电机 50us 控制周期计数:主循环电机任务递增,观测器/计时逻辑读取。 +u32 motor_runtime_50us = 0; +// 负载检测/通信心跳计数:主循环递增,CAN 周期发送任务清零。 +u32 cnt_test = 0; -// 函数指针数组,包含各种控制模式对应的函数 -static FOCFunctionPtr control_functions[] = { - &FOC_Controller::drv_hold, // 0: 占空比置 - &FOC_Controller::angle_control, // 1: 位置闭环控制 - &FOC_Controller::velocity_control, // 2: 速度闭环控制 - &FOC_Controller:: - angle_control_with_velocity_limit, // 3: - // 角度闭环控制,带速度限制 - &FOC_Controller::MIT_Mode, // 4: MIT 模式 - &FOC_Controller::current_control, // 5: 电流闭环控制 - &FOC_Controller::velocity_open_loop, // 6: 速度开环控制 - &FOC_Controller::PWM_out, // 0: 占空比置 -}; /** * @brief 应用程序入口函数 * @return int 返回值 @@ -112,94 +100,3 @@ u8 hardware_init(void) { return init_status; } -/** - * @brief 根据任务标志调度周期性业务任务 - * @param[in] currentTasks 参数 - * @return void - */ -void do_task(u32 currentTasks) { - busy_task_enter(); // DWT: 记录忙碌起始周期 - cnt_test++; - - // 按照优先级和重要性处理任务 - if (currentTasks & (1 << TASK_CURRENT_UPDATE)) { // 更新电流采样值 - sampler.update_current(); - } - if (currentTasks & (1 << TASK_ANGLE_UPDATE)) { // 更新角度传感器值 - KTH7823.update(); - FOC.rt_angle = Angle_kalman.update(KTH7823.getAngle()); - } - if (currentTasks & (1 << TASK_VELOCITY_UPDATE)) { // 更新速度观测值 - FOC.rt_velocity = Angle_kalman.getVelocity(); - } - if (currentTasks & (1 << TASK_MOTOR_CONTROL)) { // 电机控制任务 (最高优先级) - motor_runtime_50us++; - - // 快速电压检测(每100us执行一次,响应时间 < 200us) - static u8 vcheck_div = 0; - if (++vcheck_div >= 2u) { - vcheck_div = 0; - FOC.fast_voltage_check(); - } - - // 静态变量用于跟踪上一次的控制模式 - static int last_control_mode = -1; - - // 边界检查,防止数组越界 - if (FOC.control_mode >= 0 && - FOC.control_mode < 8) { // 更新为 8 个控制模式 - - // 检查控制模式是否发生变化 - if (FOC.control_mode != last_control_mode) { - // 重置所有 PID 控制器的状态,避免积分项累积 - FOC.vel_loop.reset(); - FOC.angle_loop.reset(); - FOC.current_loop.reset(); - FOC.vel_inVelAngloop.reset(); - FOC.ang_inVelAngloop.reset(); - FOC.ang_inMITloop.reset(); - FOC.vel_inMITloop.reset(); - FOC.current_inMITloop.reset(); - - // 更新上次控制模式 - last_control_mode = FOC.control_mode; - } - if (!FOC.is_shut_down) { - // 调用对应的控制函数 - (FOC.*control_functions[FOC.control_mode])(); - } else { - // 调用对应的控制函数 - FOC.drv_stop(); - } - } - } - - // 齿槽转矩校准 tick(DEBUG,平时不启用) - #if FOC_DEBUG - FOC.calibrate_cogging_tick(); - #endif - - if (currentTasks & (1 << TASK_MCU_TEMP_UPDATE)) { // 更新 mcu 温度传感器值 - sampler.update_mcu_temp(); - } - if (currentTasks & (1 << TASK_DRV_TEMP_UPDATE)) { // 更新电机温度传感器值 - sampler.update_drv_temp(); - } - if (currentTasks & (1 << TASK_THERMAL_CONTROL)) { // 热量控制 - thermalController.update(); - } - if (currentTasks & (1 << TASK_MAGNETIC_CHECK)) { // 磁感应强度检测 - angsens_MG_check(); - } - if (currentTasks & (1 << TASK_VBUS_UPDATE)) { // 更新 vbus 电压值 - power_manage(); - } - if (currentTasks & (1 << TASK_CAN_TRANSMIT)) { // CAN 通信 - CAN0_transmiter.test_u16 = CAN0_transmiter.get_node_id(); // 负载检测 - cnt_test = 0; - CAN0_transmiter.can0_send_flag = SET; - CAN0_transmiter.communication_check(); - fwdgt_counter_reload(); // 看门狗复位 - } - busy_task_exit(); // DWT: 累加忙碌周期 -} \ No newline at end of file diff --git a/Robot_V2/main.h b/Robot_V2/main.h index 3992cf0..104885b 100644 --- a/Robot_V2/main.h +++ b/Robot_V2/main.h @@ -36,13 +36,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define MAIN_H #include - -/** - * @brief 根据任务标志调度周期性业务任务 - * @param[in] currentTasks 参数 - * @return void - */ -void do_task(u32 currentTasks); /** * @brief 初始化应用层使用的硬件与软件模块 * @return u8 返回值 diff --git a/Robot_V2/sys/config.h b/Robot_V2/sys/config.h index 57d8294..ddcb335 100644 --- a/Robot_V2/sys/config.h +++ b/Robot_V2/sys/config.h @@ -12,71 +12,41 @@ // 如果使用 CMSIS DSP 库,需要定义 ARM_MATH_CM4 #define ARM_MATH_CM4 -/** @brief EEP_DEBUG 宏定义 */ #define EEP_DEBUG 0 -/** @brief OTA_DEBUG 宏定义 */ #define OTA_DEBUG 0 -/** @brief SLEEP_DEBUG 宏定义 */ #define SLEEP_DEBUG 0 -/** @brief ANG_SENS_DEBUG 宏定义 */ #define ANG_SENS_DEBUG 0 -/** @brief CAN_DEBUG 宏定义 */ #define CAN_DEBUG 0 -/** @brief MAIN_DEBUG 宏定义 */ #define MAIN_DEBUG 0 -/** @brief FOC_DEBUG 宏定义 */ #define FOC_DEBUG 0 // 版本号定义 - 可根据实际项目进行调整 -/** @brief SOFTWARE_VERSION_MAJOR 常量定义 */ constexpr u16 SOFTWARE_VERSION_MAJOR = 26; -/** @brief SOFTWARE_VERSION_MINOR 常量定义 */ constexpr u16 SOFTWARE_VERSION_MINOR = 5; -/** @brief SOFTWARE_VERSION_PATCH 常量定义 */ -constexpr u16 SOFTWARE_VERSION_PATCH = 19; -/** @brief SOFTWARE_VERSION_BUILD 常量定义 */ +constexpr u16 SOFTWARE_VERSION_PATCH = 24; constexpr u16 SOFTWARE_VERSION_BUILD = 2; -/** @brief HARDWARE_VERSION_MAJOR 常量定义 */ constexpr u16 HARDWARE_VERSION_MAJOR = 25; -/** @brief HARDWARE_VERSION_MINOR 常量定义 */ constexpr u16 HARDWARE_VERSION_MINOR = 12; -/** @brief HARDWARE_VERSION_PATCH 常量定义 */ constexpr u16 HARDWARE_VERSION_PATCH = 01; -/** @brief HARDWARE_VERSION_BUILD 常量定义 */ constexpr u16 HARDWARE_VERSION_BUILD = 0; -/** @brief BOOT_VERSION_MAJOR 常量定义 */ constexpr u16 BOOT_VERSION_MAJOR = 26; -/** @brief BOOT_VERSION_MINOR 常量定义 */ constexpr u16 BOOT_VERSION_MINOR = 1; -/** @brief BOOT_VERSION_PATCH 常量定义 */ constexpr u16 BOOT_VERSION_PATCH = 12; -/** @brief BOOT_VERSION_BUILD 常量定义 */ constexpr u16 BOOT_VERSION_BUILD = 0; // Flash 分区配置 -/** @brief BOOTLOADER_SIZE 常量定义 */ constexpr u32 BOOTLOADER_SIZE = 0x6000; // 24KB -/** @brief BOOTLOADER_START_ADDR 常量定义 */ constexpr u32 BOOTLOADER_START_ADDR = 0x08000000; // BOOT 起始地址 -/** @brief APPLATEST_START_ADDR 常量定义 */ constexpr u32 APPLATEST_START_ADDR = 0x08012000; // APP 起始地址 -/** @brief APPBACK_START_ADDR 常量定义 */ constexpr u32 APPBACK_START_ADDR = 0x08006000; // APPBACK 起始地址 -/** @brief APP_MAX_SIZE 常量定义 */ constexpr u32 APP_MAX_SIZE = 0xC000; // 48KB -/** @brief FMC_PAGE_SIZE 常量定义 */ constexpr u32 FMC_PAGE_SIZE = 0x400; // 1KB -/** @brief SLEEP_CONFIG_START 常量定义 */ constexpr u32 SLEEP_CONFIG_START = 0x0801E400; // 睡眠配置起始地址 -/** @brief SLEEP_CONFIG_END 常量定义 */ constexpr u32 SLEEP_CONFIG_END = 0x0801E800; // 睡眠配置结束地址 -/** @brief FLASH_EMPTY 常量定义 */ constexpr u32 FLASH_EMPTY = 0xFFFFFFFF; // 空数据 -/** @brief SLEEP_ENABLE 常量定义 */ constexpr u32 SLEEP_ENABLE = 0x11111111; // 睡眠使能 -/** @brief SLEEP_DISABLE 常量定义 */ constexpr u32 SLEEP_DISABLE = 0x00000000; // 睡眠禁止 // 节点 ID 配置 (用于多电机 CAN 总线隔离) @@ -88,19 +58,12 @@ constexpr u32 SLEEP_DISABLE = 0x00000000; // 睡眠禁止 // - 采用双重备份机制 (主备份 + 备用备份) // - 使用魔数 (NODE_ID_MAGIC) 验证数据有效性,防止位翻转导致错读 // - 使用写入计数器区分最新数据 -/** @brief NODE_ID_PAGE_ADDR 常量定义 */ constexpr u32 NODE_ID_PAGE_ADDR = 0x0801E800; // 节点 ID 存储页 (1KB,独立区域) -/** @brief NODE_ID_PRIMARY_ADDR 常量定义 */ constexpr u32 NODE_ID_PRIMARY_ADDR = 0x0801E800; // 主存储地址 -/** @brief NODE_ID_BACKUP_ADDR 常量定义 */ constexpr u32 NODE_ID_BACKUP_ADDR = 0x0801E840; // 备份存储地址 (偏移64字节) -/** @brief NODE_ID_MAGIC 常量定义 */ constexpr u32 NODE_ID_MAGIC = 0x4E4F4445; // 魔数 'NODE' 用于验证数据有效性 -/** @brief DEFAULT_NODE_ID 常量定义 */ constexpr u16 DEFAULT_NODE_ID = 1; // 默认节点 ID -/** @brief BROADCAST_NODE_ID 常量定义 */ constexpr u16 BROADCAST_NODE_ID = 0x1FFF; // 广播 ID (所有节点响应) -/** @brief MAX_NODE_ID 常量定义 */ constexpr u16 MAX_NODE_ID = 0x1FFE; // 最大有效节点 ID (0~0x1FFE) // 节点 ID 存储结构 (存储在Flash中的格式) @@ -109,296 +72,174 @@ constexpr u16 MAX_NODE_ID = 0x1FFE; // 最大有效节点 ID (0~0x1FFE) // 地址偏移 0x06: 保留 (u16,填充为0xFFFF) // 地址偏移 0x08: 写入计数器 (u32,用于区分最新数据) // 地址偏移 0x0C: CRC32 校验 (u32) -/** @brief NODE_ID_STRUCT_SIZE 常量定义 */ constexpr u8 NODE_ID_STRUCT_SIZE = 0x10; // 结构体大小 16 字节 -/** @brief NODE_ID_MAGIC_OFFSET 常量定义 */ constexpr u8 NODE_ID_MAGIC_OFFSET = 0x00; // 魔数偏移 -/** @brief NODE_ID_VALUE_OFFSET 常量定义 */ constexpr u8 NODE_ID_VALUE_OFFSET = 0x04; // 节点ID值偏移 -/** @brief NODE_ID_COUNTER_OFFSET 常量定义 */ constexpr u8 NODE_ID_COUNTER_OFFSET = 0x08; // 写入计数器偏移 -/** @brief NODE_ID_CRC_OFFSET 常量定义 */ constexpr u8 NODE_ID_CRC_OFFSET = 0x0C; // CRC偏移 // BOOT 相关 -/** @brief OTA_CONFIG_NUM 常量定义 */ constexpr u32 OTA_CONFIG_NUM = 6; -/** @brief n_APP_LATEST_AVAL_ADDR 常量定义 */ constexpr u32 n_APP_LATEST_AVAL_ADDR = 0x0801E000; // 最新 APP 可用地址 -/** @brief n_APP_BACKUP_AVAL_ADDR 常量定义 */ constexpr u32 n_APP_BACKUP_AVAL_ADDR = 0x0801E004; // 备份 APP 可用地址 -/** @brief n_APP_LATEST_EN_ADDR 常量定义 */ constexpr u32 n_APP_LATEST_EN_ADDR = 0x0801E008; // 最新 APP 激活 -/** @brief n_APP_BACKUP_EN_ADDR 常量定义 */ constexpr u32 n_APP_BACKUP_EN_ADDR = 0x0801E00C; // 备份 APP 激活 -/** @brief n_APP_LATEST_CHECK_ADDR 常量定义 */ constexpr u32 n_APP_LATEST_CHECK_ADDR = 0x0801E010; // 最新 APP 校验 -/** @brief n_APP_BACKUP_CHECK_ADDR 常量定义 */ constexpr u32 n_APP_BACKUP_CHECK_ADDR = 0x0801E014; // 备份 APP 校验 -/** @brief OTA_NOT_AVAL 常量定义 */ constexpr u32 OTA_NOT_AVAL = 0xFFFFFFFF; -/** @brief OTA_AVAL 常量定义 */ constexpr u32 OTA_AVAL = 0x00000000; // 全零表示可用 // 参数配置 -/** @brief PP 常量定义 */ constexpr u8 PP = 11; -/** @brief DIR 常量定义 */ constexpr float DIR = 1.0f; // 默认方向:-1.0f 反转,1.0f 正转 -/** @brief REDUCTION_RATIO 常量定义 */ constexpr u16 REDUCTION_RATIO = 25; -/** @brief CURRENT_OFFSET 常量定义 */ constexpr float CURRENT_OFFSET = -3.31f / 2.0f; // 当前电流传感器的零点偏移 -/** @brief HOLD_PWM_DUTY 常量定义 */ constexpr float HOLD_PWM_DUTY = 0.1f; -/** @brief ANGLESENS_OFFSET 常量定义 */ constexpr u16 ANGLESENS_OFFSET = 0; -/** @brief ANGLESENS_CALIBRATION_ANGLE 常量定义 */ constexpr float ANGLESENS_CALIBRATION_ANGLE = 83.0f; -/** @brief KE_FACTOR 常量定义 */ constexpr float KE_FACTOR = 0.0f; // 角度/速度 α-β 观测器参数 -/** @brief ANGLE_KALMAN_INITIAL_STATE 常量定义 */ constexpr float ANGLE_KALMAN_INITIAL_STATE = 0.0f; -/** @brief ANGLE_KALMAN_INITIAL_COVARIANCE 常量定义 */ constexpr float ANGLE_KALMAN_INITIAL_COVARIANCE = 0.0f; -/** @brief ANGLE_KALMAN_PROCESS_NOISE_Q 常量定义 */ constexpr float ANGLE_KALMAN_PROCESS_NOISE_Q = 0.0f; -/** @brief ANGLE_KALMAN_MEASUREMENT_NOISE_R 常量定义 */ constexpr float ANGLE_KALMAN_MEASUREMENT_NOISE_R = 0.0f; -/** @brief ANGLE_OBSERVER_ALPHA 常量定义 */ constexpr float ANGLE_OBSERVER_ALPHA = 0.75f; // 角度校正增益,越大越跟测量 -/** @brief ANGLE_OBSERVER_BETA 常量定义 */ constexpr float ANGLE_OBSERVER_BETA = 0.002f; // 速度校正增益,原0.001微调至0.002 // 旧速度 Kalman 参数保留用于接口兼容;主速度输出现在直接来自 α-β 观测器 -/** @brief VELOCITY_KALMAN_INITIAL_STATE 常量定义 */ constexpr float VELOCITY_KALMAN_INITIAL_STATE = 0.0f; -/** @brief VELOCITY_KALMAN_INITIAL_COVARIANCE 常量定义 */ constexpr float VELOCITY_KALMAN_INITIAL_COVARIANCE = 1e-3f; -/** @brief VELOCITY_KALMAN_PROCESS_NOISE_Q 常量定义 */ constexpr float VELOCITY_KALMAN_PROCESS_NOISE_Q = 5e-4f; // 略增大,减少拖尾 -/** @brief VELOCITY_KALMAN_MEASUREMENT_NOISE_R 常量定义 */ constexpr float VELOCITY_KALMAN_MEASUREMENT_NOISE_R = 0.05f; // 明显小于旧值,避免压得太死 // 电流卡尔曼滤波器参数 -/** @brief CURRENT_KALMAN_INITIAL_STATE 常量定义 */ constexpr float CURRENT_KALMAN_INITIAL_STATE = 0.0f; -/** @brief CURRENT_KALMAN_INITIAL_COVARIANCE 常量定义 */ constexpr float CURRENT_KALMAN_INITIAL_COVARIANCE = 100.0f; // 增大初始协方差 -/** @brief CURRENT_KALMAN_PROCESS_NOISE_Q 常量定义 */ constexpr float CURRENT_KALMAN_PROCESS_NOISE_Q = 10.0f; // 降低过程噪声 -/** @brief CURRENT_KALMAN_MEASUREMENT_NOISE_R 常量定义 */ constexpr float CURRENT_KALMAN_MEASUREMENT_NOISE_R = 5e-5f; // 增大测量噪声 // PID 配置 -/** @brief DEFAULT_current_loop_p 常量定义 */ constexpr float DEFAULT_current_loop_p = 0.1f / 100 * 12; // 电流 P -/** @brief current_loop_Ki 常量定义 */ constexpr float current_loop_Ki = 0.55f; // 电流 P -/** @brief current_loop_Kd 常量定义 */ constexpr float current_loop_Kd = 3e-4f; // 电流 P -/** @brief DEFAULT_vel_loop_p 常量定义 */ constexpr float DEFAULT_vel_loop_p = 0.018f / 100 * 12; // 速度 P -/** @brief vel_loop_Ku 常量定义 */ constexpr float vel_loop_Ku = 3.600000e-03 * 0.7 * 100 / 12; -/** @brief vel_loop_Tu 常量定义 */ constexpr float vel_loop_Tu = 17.0f * 1e-3f; -/** @brief vel_loop_Ki 常量定义 */ constexpr float vel_loop_Ki = 1.2 * vel_loop_Ku / vel_loop_Tu; -/** @brief vel_loop_Kd 常量定义 */ constexpr float vel_loop_Kd = 0.075 * vel_loop_Ku * vel_loop_Tu * 2; -/** @brief DEFAULT_vel_angloop_p 常量定义 */ constexpr float DEFAULT_vel_angloop_p = 0.4f / 100; // 速度 速度 - 角度 P -/** @brief vel_angloop_Ki 常量定义 */ constexpr float vel_angloop_Ki = 5.0f; // 速度 速度 - 角度 I -/** @brief vel_angloop_Kd 常量定义 */ constexpr float vel_angloop_Kd = 5e-05f; // 速度 速度 - 角度 D -/** @brief DEFAULT_angle_angloop_p 常量定义 */ constexpr float DEFAULT_angle_angloop_p = 150.0f / 100 * 12; // 角度 速度 - 角度 P -/** @brief ang_angloop_Ki 常量定义 */ constexpr float ang_angloop_Ki = 0.00f; // 角度 速度 - 角度 I -/** @brief ang_angloop_Kd 常量定义 */ constexpr float ang_angloop_Kd = 8e-04f; // 角度 速度 - 角度 D -/** @brief DEFAULT_angle_loop_p 常量定义 */ constexpr float DEFAULT_angle_loop_p = 60.0f / 100 * 12; // 角度 P -/** @brief DEFAULT_angle_MIT_p 常量定义 */ constexpr float DEFAULT_angle_MIT_p = 100.0f / 100 * 12; // 角度 MIT P -/** @brief angle_MIT_Ki 常量定义 */ constexpr float angle_MIT_Ki = 0.0f; -/** @brief angle_MIT_Kd 常量定义 */ constexpr float angle_MIT_Kd = 8e-04f; -/** @brief DEFAULT_vel_MIT_p 常量定义 */ constexpr float DEFAULT_vel_MIT_p = 5.0f / 100; // 速度 MIT P -/** @brief vel_MIT_Ki 常量定义 */ constexpr float vel_MIT_Ki = 15.0f; -/** @brief vel_MIT_Kd 常量定义 */ constexpr float vel_MIT_Kd = 1e-04f; -/** @brief DEFAULT_current_MIT_p 常量定义 */ constexpr float DEFAULT_current_MIT_p = 0.2f / 100 * 12; // 电流 MIT P -/** @brief current_MIT_Ki 常量定义 */ constexpr float current_MIT_Ki = 0.1f; -/** @brief current_MIT_Kd 常量定义 */ constexpr float current_MIT_Kd = 3e-4f; // 常量定义 -/** @brief SIZE_FLOAT 常量定义 */ constexpr u8 SIZE_FLOAT = sizeof(float); -/** @brief SIZE_U32 常量定义 */ constexpr u8 SIZE_U32 = sizeof(uint32_t); -/** @brief ADC_MAX 常量定义 */ constexpr float ADC_MAX = 65536.0f; -/** @brief PI 常量定义 */ constexpr float PI = 3.14159265358979323846f; -/** @brief INV_PI_3 常量定义 */ constexpr float INV_PI_3 = 3.0f / PI; -/** @brief PI_3 常量定义 */ constexpr float PI_3 = PI / 3.0f; -/** @brief _1_SQRT3 常量定义 */ constexpr float _1_SQRT3 = 0.57735026919f; // 1/sqrt(3) -/** @brief _3PI_2 常量定义 */ constexpr float _3PI_2 = 4.71238898038f; -/** @brief SQRT3 常量定义 */ constexpr float SQRT3 = 1.7320508075688772f; -/** @brief SQRT3DV2 常量定义 */ constexpr float SQRT3DV2 = 0.8660254037844386f; // sqrt(3)/2 -/** @brief COS_PI_3 常量定义 */ constexpr float COS_PI_3 = 0.5f; -/** @brief ANGLE_FACTOR 常量定义 */ constexpr float ANGLE_FACTOR = 25.0f / 180.0f * PI; -/** @brief DEG2RAD 常量定义 */ constexpr float DEG2RAD = 1.0f / 180.0f * PI; -/** @brief RAD2DEG 常量定义 */ constexpr float RAD2DEG = 180.0f / PI; // 修正 RAD2DEG 定义,应该是 180/π -/** @brief DEFAULT_VALUE 常量定义 */ constexpr u8 DEFAULT_VALUE = 0; -/** @brief CONFIG_SAVED_NUM 常量定义 */ constexpr u8 CONFIG_SAVED_NUM = 6; // 三角函数快速计算 LUT -/** @brief LUT_SIZE 常量定义 */ constexpr u16 LUT_SIZE = 1024; -/** @brief LUT_MASK 常量定义 */ constexpr u16 LUT_MASK = (LUT_SIZE - 1); -/** @brief LUT_SCALE 常量定义 */ constexpr u16 LUT_SCALE = (LUT_SIZE / (2.0f * PI)); // CAN 相关 // 注意:以下为 CAN 基础 ID(只保留低 16 位),实际发送/接收时会自动叠加节点 ID // 到高 16 位 格式:[节点 ID(16 位)][基础 ID(16 位)] 例如:节点 ID=1,发送命令 // ID = (1 << 16) | CANFD_CMD_ID = 0x00010200 -/** @brief DEV_CAN0_ID 常量定义 */ constexpr u16 DEV_CAN0_ID = 0x0000; -/** @brief DEV_CAN0_MASK 常量定义 */ constexpr u16 DEV_CAN0_MASK = 0x0000; -/** @brief DEV_CAN_LOOPBACK_ID 常量定义 */ constexpr u16 DEV_CAN_LOOPBACK_ID = 0x0000; // 反馈基础 ID -/** @brief CANFD_STATUS_ID 常量定义 */ constexpr u16 CANFD_STATUS_ID = 0x0100; // 状态反馈基础 ID -/** @brief CANFD_CONFIG_ID 常量定义 */ constexpr u16 CANFD_CONFIG_ID = 0x0101; // 配置信息基础 ID -/** @brief CANFD_VERSION_ID 常量定义 */ constexpr u16 CANFD_VERSION_ID = 0x00FF; // 版本信息基础 ID // 命令基础 ID -/** @brief CANFD_CMD_ID 常量定义 */ constexpr u16 CANFD_CMD_ID = 0x0200; // 控制命令基础 ID -/** @brief CANFD_RESET_ID 常量定义 */ constexpr u16 CANFD_RESET_ID = 0x0301; // 重置命令基础 ID // 节点 ID 管理基础 ID -/** @brief CANFD_SET_NODE_ID 常量定义 */ constexpr u16 CANFD_SET_NODE_ID = 0x0400; // 设置节点 ID 基础 ID -/** @brief CANFD_GET_NODE_ID 常量定义 */ constexpr u16 CANFD_GET_NODE_ID = 0x0401; // 查询节点 ID 基础 ID // OTA 相关基础 ID 不受到node ID影响 -/** @brief CANFD_BOOT_ID 常量定义 */ constexpr u16 CANFD_BOOT_ID = 0x0000; // BOOT 模式基础 ID (进入 BOOT 使用此 ID) -/** @brief CANFD_OTA_REQ_ID 常量定义 */ constexpr u16 CANFD_OTA_REQ_ID = 0x0100; // OTA 请求基础 ID -/** @brief CANFD_OTA_DATA_ID 常量定义 */ constexpr u16 CANFD_OTA_DATA_ID = 0x0101; // OTA 数据基础 ID -/** @brief CANFD_OTA_ACK_ID 常量定义 */ constexpr u16 CANFD_OTA_ACK_ID = 0x0102; // OTA 确认基础 ID -/** @brief CANFD_OTA_WAIT_ID 常量定义 */ constexpr u16 CANFD_OTA_WAIT_ID = 0x0103; // OTA 等待基础 ID // ADC -/** @brief ADC_FILTER_SIZE 常量定义 */ constexpr u8 ADC_FILTER_SIZE = 6; -/** @brief BLOCK_SIZE 常量定义 */ constexpr u8 BLOCK_SIZE = ADC_FILTER_SIZE * SIZE_FLOAT; -/** @brief ERROR_ADC_VAL 常量定义 */ constexpr float ERROR_ADC_VAL = 255.0f; -/** @brief MAX_TEMP 常量定义 */ constexpr float MAX_TEMP = 150.0f; -/** @brief MIN_TEMP 常量定义 */ constexpr float MIN_TEMP = -40.0f; -/** @brief MAX_CURRENT 常量定义 */ constexpr float MAX_CURRENT = 40.0f; -/** @brief MIN_CURRENT 常量定义 */ constexpr float MIN_CURRENT = -40.0f; -/** @brief MAX_VCC 常量定义 */ constexpr float MAX_VCC = 65.0f; -/** @brief MIN_VCC 常量定义 */ constexpr float MIN_VCC = -0.1f; // ang_sensor -/** @brief VEL_FILTER_SIZE 常量定义 */ constexpr u16 VEL_FILTER_SIZE = 4; -/** @brief DEFAULT_COMMAND 常量定义 */ constexpr u16 DEFAULT_COMMAND = 0x0000; -/** @brief FAULT_RECEIVE 常量定义 */ constexpr u16 FAULT_RECEIVE = 0xFFFF; -/** @brief ANGLE_WRAP_THRESHOLD 常量定义 */ constexpr float ANGLE_WRAP_THRESHOLD = 300.0f; -/** @brief ANGLE_WRAP_VALUE 常量定义 */ constexpr float ANGLE_WRAP_VALUE = 360.0f; -/** @brief RAD_FACTOR 常量定义 */ constexpr float RAD_FACTOR = 1.0f / ADC_MAX * 2.0f * PI; -/** @brief ANGLESENS_FACTOR 常量定义 */ constexpr float ANGLESENS_FACTOR = 1.0f / ADC_MAX * 360.0f; // TIMER2-任务调度 -/** @brief TIMER2_PRESCALER 常量定义 */ constexpr int TIMER2_PRESCALER = 119; // 120MHz / 120 = 1MHz -/** @brief TIMER2_PERIOD 常量定义 */ constexpr int TIMER2_PERIOD = 49; // 1 / 1MHz * 50 = 50us -/** @brief MOTOR_PERIOD 常量定义 */ constexpr int MOTOR_PERIOD = 1; // 控制每50us执行一次,无分频 -/** @brief TIME_SLICE 常量定义 */ constexpr float TIME_SLICE = (TIMER2_PERIOD + 1) * (TIMER2_PRESCALER + 1) / 120000000.0f; // 周期 -/** @brief TIMER2_FREQUENCY 常量定义 */ constexpr float TIMER2_FREQUENCY = 1 / TIME_SLICE; // 频率 -/** @brief MOTOR_FREQUENCY 常量定义 */ constexpr float MOTOR_FREQUENCY = 1 / TIME_SLICE / MOTOR_PERIOD; // 频率 // TIMER1-PWM -/** @brief TIMER1_PRESCALER 常量定义 */ constexpr int TIMER1_PRESCALER = 0; // 不分频,计数器时钟为 120MHz -/** @brief TIMER1_PERIOD 常量定义 */ constexpr int TIMER1_PERIOD = 2999; // 120MHz / (0+1) / (2999+1) = 40kHz计数, 中心对齐实际PWM 20kHz -/** @brief TIMER1_DEFAULT_PULSE 常量定义 */ constexpr int TIMER1_DEFAULT_PULSE = 0; -/** @brief TIMER1_CH3_COMPARE_VALUE 常量定义 */ constexpr int TIMER1_CH3_COMPARE_VALUE = (TIMER1_PERIOD + 1) * 0.41 - 1; // PWM 周期 41% 位置触发ADC采样 // 齿槽转矩补偿 -/** @brief COGGING_TABLE_SIZE 常量定义 */ constexpr u16 COGGING_TABLE_SIZE = 128; // 每电周期采样点数 -/** @brief COGGING_COMP_SCALE 常量定义 */ constexpr float COGGING_COMP_SCALE = 0.005f; // 补偿换算系数 V/mA,40mA × 0.005 = 0.2V // 电压检测参数(基于跌幅检测,不依赖固定额定电压) @@ -409,13 +250,9 @@ constexpr float COGGING_COMP_SCALE = 0.005f; // 补偿换算系数 V/mA,40mA // 慢速管理层(power_manage,~10ms周期): // - 执行Flash保存 // - 状态恢复判断(电压恢复到基准电压的90%以上) -/** @brief VBUS_DROP_THRESHOLD 常量定义 */ constexpr float VBUS_DROP_THRESHOLD = 5.0f; // 电压跌幅阈值 (V) -/** @brief VBUS_ABSOLUTE_MIN 常量定义 */ constexpr float VBUS_ABSOLUTE_MIN = 12.0f; // 绝对最低电压 (V) -/** @brief VBUS_DEBOUNCE_COUNT 常量定义 */ constexpr u32 VBUS_DEBOUNCE_COUNT = 5; // 快速检测:防抖计数 (5次×100us = 500us) -/** @brief VBUS_RECOVERY_RATIO 常量定义 */ constexpr float VBUS_RECOVERY_RATIO = 0.9f; // 恢复比例(基准电压的90%) diff --git a/Robot_V2/sys/health.cpp b/Robot_V2/sys/health.cpp index cfb93cc..75d50cd 100644 --- a/Robot_V2/sys/health.cpp +++ b/Robot_V2/sys/health.cpp @@ -16,6 +16,7 @@ extern sampler sampler; extern CAN_transmiter CAN0_transmiter; extern FOC_Controller FOC; extern FlashEEPROM flashEEP; +// 全局热管理控制器单例:由 main 任务周期调用 update(),用于维护过热保护状态。 ThermalController thermalController; // 热量控制器初始化 /** @brief max 宏定义 */ @@ -24,7 +25,7 @@ ThermalController thermalController; // 热量控制器初始化 /** @brief PROTECT_TIME 常量定义 */ constexpr u16 PROTECT_TIME = 6000; // 过热保护时间,单位*10ms -// 电压管理状态机 +// 电压管理状态机:仅 power_manage() 慢速任务访问,文件内封装。 static VoltageState voltage_state = VoltageState::NORMAL; static float last_vbus_slow = 0.0f; // 上次电压值(慢速任务用) /** diff --git a/Robot_V2/sys/initial.cpp b/Robot_V2/sys/initial.cpp index 7237407..0f43f67 100644 --- a/Robot_V2/sys/initial.cpp +++ b/Robot_V2/sys/initial.cpp @@ -36,6 +36,7 @@ #endif extern CAN_transmiter CAN0_transmiter; // CAN 初始化 +// 全局版本管理器单例:构造函数只复制 constexpr 版本号常量,不依赖外设初始化。 VersionManager Version; VersionManager::VersionManager() { @@ -120,14 +121,48 @@ void VersionManager::setBootVersion(u16 major, u16 minor, u16 patch, bootVersion.build = build; } +static void appendVersionChar(char *&out, size_t &remaining, char ch) { + if (remaining > 1U) { + *out++ = ch; + --remaining; + *out = '\0'; + } +} + +static void appendVersionU16(char *&out, size_t &remaining, u16 value) { + char digits[5]; // u16 最大值 65535,最多 5 位十进制数字 + u8 count = 0; + + do { + digits[count++] = static_cast('0' + (value % 10U)); + value = static_cast(value / 10U); + } while (value > 0U && count < sizeof(digits)); + + while (count > 0U) { + appendVersionChar(out, remaining, digits[--count]); + } +} + char *VersionManager::versionToString(const VersionInfo &version, char *buffer, size_t bufSize) const { if (buffer == nullptr) { return nullptr; } + if (bufSize == 0U) { + return buffer; + } - snprintf(buffer, bufSize, "%d.%d.%d.%d", version.major, version.minor, - version.patch, version.build); + char *out = buffer; + size_t remaining = bufSize; + *out = '\0'; + + appendVersionU16(out, remaining, version.major); + appendVersionChar(out, remaining, '.'); + appendVersionU16(out, remaining, version.minor); + appendVersionChar(out, remaining, '.'); + appendVersionU16(out, remaining, version.patch); + appendVersionChar(out, remaining, '.'); + appendVersionU16(out, remaining, version.build); return buffer; } diff --git a/Robot_V2/sys/systick.cpp b/Robot_V2/sys/systick.cpp index 386f41b..a02c987 100644 --- a/Robot_V2/sys/systick.cpp +++ b/Robot_V2/sys/systick.cpp @@ -50,6 +50,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define SLEEP_DEBUG_PRINT(fmt, ...) ((void)0) #endif +// SysTick 模块内部状态,仅在本文件访问: +// - delay / systick_ms 由 SysTick 中断维护,用 volatile 保证主循环可见性; +// - DWT 统计变量用于 CPU 占用率采样; +// - static 限制文件作用域,外部只能通过 get_systick_ms()/get_cpu_usage() 读取。 volatile static uint32_t delay; volatile static uint32_t systick_ms = 0; // 系统运行时间(毫秒) @@ -253,6 +257,7 @@ void app_prepare_deep_sleep(bool save_data) { fwdgt_config(0xFFF, FWDGT_PSC_DIV256); // 设置最大超时时间 fwdgt_write_disable(); + uint32_t primask = __get_PRIMASK(); __disable_irq(); // 遍历最后一个数字,后一个地址写 +1 for (u32 address = SLEEP_CONFIG_END - SIZE_U32; @@ -271,6 +276,7 @@ void app_prepare_deep_sleep(bool save_data) { if (fmc_status != FMC_READY) { // 擦除失败报错 fmc_lock(); flashEEP.EEPROMStatus = FLASH_EEPROM_ERASE_ERROR; + __set_PRIMASK(primask); // 失败返回前恢复进入前的中断状态 return; } address = SLEEP_CONFIG_START - SIZE_U32; // 重新开始写入 @@ -289,6 +295,7 @@ void app_prepare_deep_sleep(bool save_data) { flashEEP.EEPROMStatus = FLASH_EEPROM_WRITE_ERROR; SLEEP_DEBUG_PRINT("app wrote %08x at %08x ERROR\r\n", SLEEP_ENABLE, address); + __set_PRIMASK(primask); // 失败返回前恢复进入前的中断状态 return; } break; diff --git a/Robot_V2/tasks.cpp b/Robot_V2/tasks.cpp new file mode 100644 index 0000000..fe3bf54 --- /dev/null +++ b/Robot_V2/tasks.cpp @@ -0,0 +1,172 @@ +/** + * @file tasks.cpp + * @brief 周期任务调度与任务执行实现 + */ + +#include "tasks.h" +#include "Can.h" +#include "FOC.h" +#include "Filter.h" +#include "adc.h" +#include "ang_sensor.h" +#include "health.h" +#include "systick.h" + +extern CAN_transmiter CAN0_transmiter; +extern sampler sampler; +extern angle_sensor KTH7823; +extern EncoderAngleKalman Angle_kalman; +extern ThermalController thermalController; +extern FOC_Controller FOC; +extern u32 motor_runtime_50us; +extern u32 cnt_test; + +// 函数指针数组,包含各种控制模式对应的函数 +static FOCFunctionPtr control_functions[] = { + &FOC_Controller::drv_hold, // 0: 占空比置 + &FOC_Controller::angle_control, // 1: 位置闭环控制 + &FOC_Controller::velocity_control, // 2: 速度闭环控制 + &FOC_Controller:: + angle_control_with_velocity_limit, // 3: + // 角度闭环控制,带速度限制 + &FOC_Controller::MIT_Mode, // 4: MIT 模式 + &FOC_Controller::current_control, // 5: 电流闭环控制 + &FOC_Controller::velocity_open_loop, // 6: 速度开环控制 + &FOC_Controller::PWM_out, // 7: 占空比输出 +}; + +/** + * @brief 判断任务标志中是否包含指定任务 + * @param[in] currentTasks 当前任务位图快照 + * @param[in] task 待检查任务枚举 + * @return bool true 表示该任务需要执行 + */ +static bool has_task(u32 currentTasks, SystemTask task) { + return (currentTasks & (1u << task)) != 0U; +} + +/** + * @brief 控制模式变化时复位所有控制环,避免积分项跨模式继承 + * @return void + */ +static void reset_control_loops_on_mode_change(void) { + static int last_control_mode = -1; + if (FOC.control_mode == last_control_mode) return; + + FOC.vel_loop.reset(); + FOC.angle_loop.reset(); + FOC.current_loop.reset(); + FOC.vel_inVelAngloop.reset(); + FOC.ang_inVelAngloop.reset(); + FOC.ang_inMITloop.reset(); + FOC.vel_inMITloop.reset(); + FOC.current_inMITloop.reset(); + last_control_mode = FOC.control_mode; +} + +/** + * @brief 执行 50us 电机控制任务,包含快速电压检测和控制函数派发 + * @return void + */ +static void run_motor_control_task(void) { + motor_runtime_50us++; + + static u8 vcheck_div = 0; + // 快速电压检测每 100us 执行一次,避免每 50us 都读取处理。 + if (++vcheck_div >= 2u) { + vcheck_div = 0; + FOC.fast_voltage_check(); + } + + if (FOC.control_mode >= 8) return; + + // 模式合法时再派发控制函数,防止数组越界。 + reset_control_loops_on_mode_change(); + if (!FOC.is_shut_down) { + (FOC.*control_functions[FOC.control_mode])(); + } else { + FOC.drv_stop(); + } +} + +/** + * @brief 执行高频采样和电机控制相关任务 + * @param[in] currentTasks 当前任务位图快照 + * @return void + */ +static void run_fast_sample_tasks(u32 currentTasks) { + if (has_task(currentTasks, TASK_CURRENT_UPDATE)) { + sampler.update_current(); + } + if (has_task(currentTasks, TASK_ANGLE_UPDATE)) { + KTH7823.update(); + FOC.rt_angle = Angle_kalman.update(KTH7823.getAngle()); + } + if (has_task(currentTasks, TASK_VELOCITY_UPDATE)) { + FOC.rt_velocity = Angle_kalman.getVelocity(); + } + if (has_task(currentTasks, TASK_MOTOR_CONTROL)) { + run_motor_control_task(); + } +} + +/** + * @brief 执行温度、热管理、磁检测和母线电压等低频维护任务 + * @param[in] currentTasks 当前任务位图快照 + * @return void + */ +static void run_housekeeping_tasks(u32 currentTasks) { + if (has_task(currentTasks, TASK_MCU_TEMP_UPDATE)) { + sampler.update_mcu_temp(); + } + if (has_task(currentTasks, TASK_DRV_TEMP_UPDATE)) { + sampler.update_drv_temp(); + } + if (has_task(currentTasks, TASK_THERMAL_CONTROL)) { + thermalController.update(); + } + if (has_task(currentTasks, TASK_MAGNETIC_CHECK)) { + angsens_MG_check(); + } + if (has_task(currentTasks, TASK_VBUS_UPDATE)) { + power_manage(); + } +} + +/** + * @brief 执行 CAN 周期发送任务并喂狗 + * @return void + */ +static void run_can_task(void) { + CAN0_transmiter.test_u16 = CAN0_transmiter.get_node_id(); + cnt_test = 0; + CAN0_transmiter.can0_send_flag = SET; + CAN0_transmiter.communication_check(); + fwdgt_counter_reload(); +} + +/** + * @brief 根据任务标志调度周期性业务任务 + * @param[in] currentTasks 当前任务位图快照 + * @return void + */ +void do_task(u32 currentTasks) { + busy_task_enter(); // DWT: 记录忙碌起始周期 + cnt_test++; + + // 高频任务先执行,保持电机控制优先级。 + run_fast_sample_tasks(currentTasks); + + // 齿槽转矩校准 tick(DEBUG,平时不启用) + #if FOC_DEBUG + FOC.calibrate_cogging_tick(); + #endif + + run_housekeeping_tasks(currentTasks); + + // CAN 发送放在最后,避免通信处理影响前面的实时控制任务。 + if (has_task(currentTasks, TASK_CAN_TRANSMIT)) { + run_can_task(); + } + busy_task_exit(); // DWT: 累加忙碌周期 +} diff --git a/Robot_V2/tasks.h b/Robot_V2/tasks.h new file mode 100644 index 0000000..21fd04f --- /dev/null +++ b/Robot_V2/tasks.h @@ -0,0 +1,21 @@ +#ifndef TASKS_H +#define TASKS_H + +#include "initial.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief 根据任务标志调度周期性业务任务 + * @param[in] currentTasks 当前任务位图快照 + * @return void + */ +void do_task(u32 currentTasks); + +#ifdef __cplusplus +} +#endif + +#endif /* TASKS_H */ diff --git a/STATIC_ANALYSIS_NOTES.md b/STATIC_ANALYSIS_NOTES.md new file mode 100644 index 0000000..a5870ef --- /dev/null +++ b/STATIC_ANALYSIS_NOTES.md @@ -0,0 +1,162 @@ +# 静态检查告警审查说明 + +本文件用于记录人工确认后的静态检查告警处理结论。原则: + +- 不为消除低风险 warning 引入会改变实时行为的代码。 +- 裸机系统级单例、编译期常量、ISR/主循环共享状态分别分类说明。 +- 对涉及实时路径的并发项,优先记录访问边界和偏离理由;只有经过实机验证后再做逻辑修复。 + +## SUP-GLB-001:文件作用域对象 + +### 编译期常量 + +以下 `constexpr` 对象为编译期常量,非可变全局状态,无运行期写入、并发访问或动态初始化顺序风险: + +- `device/ADC.cpp`: `ADC_SCALE`, `TEMP_FACTOR`, `VCC_FACTOR`, `CURRENT_FACTOR` +- `device/Can.cpp`: `CANX` +- `sys/health.cpp`: `PROTECT_TIME` +- `sys/config.h`: 版本号、Flash 地址、协议 ID、滤波/PID 参数、数学常量、Timer 参数等 `constexpr` 配置项 + +处理结论:按静态检查误报/偏离处理。 + +### 系统级硬件/模块单例 + +以下对象对应裸机固件中的唯一硬件资源或唯一管理器: + +- `Algorithm/FOC.cpp`: `FOC_Controller FOC` +- `device/ADC.cpp`: `sampler sampler` +- `device/Can.cpp`: `CAN_transmiter CAN0_transmiter` +- `device/ang_sensor.cpp`: `angle_sensor KTH7823` +- `sys/health.cpp`: `ThermalController thermalController` +- `sys/initial.cpp`: `VersionManager Version` + +访问边界: + +- 初始化顺序由 `main::hardware_init()` 显式控制,例如 `KTH7823.init()`、`sampler.init()`、`CAN0_transmiter.init()`、`FOC.init()`。 +- `VersionManager Version` 构造仅复制 `constexpr` 版本号常量,不依赖外设初始化。 +- 运行期主要由主循环任务访问;CAN 命令路径会更新 FOC 目标值和参数。 + +处理结论:接受系统级单例设计,保留文件作用域对象,并通过注释说明初始化顺序和访问边界。 + +### ISR/主循环共享状态 + +#### `main.cpp`: `TaskScheduler scheduler` + +- `TIMER2_IRQHandler()` 置位任务标志。 +- main 循环短关中断后读取快照并清除已处理任务位。 + +处理结论:已有短临界区保护,接受。 + +#### `device/Can.cpp`: CAN RX/TX 队列索引 + +涉及: + +- `rx_queue_head`, `rx_queue_tail`, `rx_queue_count` +- `tx_queue_head`, `tx_queue_tail`, `tx_queue_count` + +访问边界: + +- RX/TX 队列被主循环、CAN 中断、DMA 完成中断共同访问。 +- 队列 push/pop/peek/disable 通过 `CAN_EnterCritical()` / `CAN_ExitCritical()` 保护。 +- `CAN_ExitCritical()` 使用 `__set_PRIMASK(primask)` 恢复进入前中断状态,而不是直接 `__enable_irq()`,避免误打开外层已关闭的中断。 + +处理结论:已有临界区保护,接受。 + +#### `device/ADC.cpp`: injected 电流采样缓存 + +涉及: + +- `adc_current_value[3]` +- `adc_current_frame_ready` + +访问边界: + +- `ADC0_1_IRQHandler()` 写入 U/V/W injected 同步采样值,并置位 `adc_current_frame_ready`。 +- `sampler::update_current()` 在主循环任务中看到 ready 后读取三相采样并更新滤波缓存,随后清 ready。 +- 该路径属于高频电机采样/控制链路。此前尝试在读取侧加入 PRIMASK 快照后,实机出现上电连续重启;回滚后恢复正常。 + +处理结论:当前不再为消除 warning 增加临界区逻辑,避免改变高频实时路径时序。保留现有 `volatile` 共享标志设计,作为实机已验证的稳定实现;如后续需要修正,应单独建立实验分支并做示波/日志验证。 + +#### `sys/systick.cpp`: DWT CPU 占用率统计变量 + +涉及: + +- `task_enter_cyccnt` +- `busy_cycles_accum` +- `sample_seconds` +- `cpu_usage` +- `dwt_initialized` + +访问边界: + +- `busy_task_enter()` / `busy_task_exit()` 在主循环任务侧记录和累加忙碌周期。 +- `SysTick_Handler()` 每 1 秒读取并清零统计值,更新 `cpu_usage`。 +- 此路径仅用于诊断 CPU 使用率,不参与控制闭环。 +- 此前尝试在 `busy_task_exit()` 加入 PRIMASK 临界区后,与 ADC 临界区改动同轮导致实机上电连续重启;已回滚并恢复正常。 + +处理结论:当前不再为该诊断统计项增加临界区,避免影响高频任务时序。允许 1 秒窗口内存在极小统计误差,作为诊断值可接受。 + +#### `device/ang_sensor.cpp`: SPI DMA 完成标志 + +涉及: + +- `spi_dma_tx_complete` +- `spi_dma_rx_complete` + +访问边界: + +- SPI 传输前清零标志。 +- DMA ISR 完成后置位。 +- 轮询函数等待标志或超时。 + +处理结论:典型 ISR/轮询共享 volatile 标志,接受。 + +#### `device/Timer.cpp`: `total_us100_counter` + +访问边界: + +- 仅在 `TIMER2_IRQHandler()` 内作为 100us 调度索引写入和使用。 + +处理结论:虽然为文件作用域变量,但无跨上下文并发写风险,接受。后续可考虑改为 ISR 内 `static`,但当前不为 warning 改运行逻辑。 + +#### `sys/health.cpp`: 电压管理状态 + +涉及: + +- `voltage_state` +- `last_vbus_slow` + +访问边界: + +- 仅 `power_manage()` 慢速任务访问。 +- 文件内 `static` 封装。 + +处理结论:模块私有状态机变量,接受。 + +## SUP-CMP-001:浮点比较 + +### `normalizeAngle360()` 边界判断 + +代码形态: + +```cpp +while (angle >= 360.0f) angle -= 360.0f; +while (angle < 0.0f) angle += 360.0f; +``` + +结论:该比较用于角度归一化边界判断,目标区间严格定义为 `[0, 360)`,不是浮点相等性判断,也不是传感器阈值判定。不需要容差。 + +## printf / snprintf 类检查 + +- FOC 齿槽校准打印位于 `#if FOC_DEBUG`,正式配置 `FOC_DEBUG=0` 时不参与编译。 +- `VersionManager::versionToString()` 已从 `snprintf()` 改为固定格式的轻量手写拼接,避免格式化库引入。 + +## AUTO-MET-001:圈复杂度 + +已拆分以下函数,降低单函数圈复杂度: + +- `FOC_Controller::calibrate_cogging_tick()`:拆分为状态派发和各状态处理函数。 +- `CAN_transmiter::communication_check()`:拆分为周期发送、接收分发、特殊消息、节点消息等处理函数。 +- `do_task()`:拆分为高频采样/电机控制、低频维护、CAN 任务。 + +处理结论:拆分后原目标函数复杂度已显著下降;实编仍需在 Keil/CMSIS/cbuild 环境验证。 diff --git a/backup_pre_tasks_split_20260524_011314/Robot_V2.csolution.yml b/backup_pre_tasks_split_20260524_011314/Robot_V2.csolution.yml new file mode 100644 index 0000000..54608ea --- /dev/null +++ b/backup_pre_tasks_split_20260524_011314/Robot_V2.csolution.yml @@ -0,0 +1,52 @@ +# A solution is a collection of related projects that share same base configuration. +solution: + created-for: CMSIS-Toolbox@2.12.0 + cdefault: # use CMSIS-Toolbox default Compiler options + + # Compilers that can be selected + select-compiler: + - compiler: AC6 + - compiler: GCC + - compiler: IAR + - compiler: CLANG + + # List of miscellaneous tool-specific controls + misc: + - for-compiler: AC6 # GDB requires DWARF 5, remove when using uVision Debugger + C-CPP: + - -gdwarf-5 + ASM: + - -gdwarf-5 + + # List the packs that define the device and/or board. + packs: + - pack: GigaDevice::GD32C10x_DFP + - pack: ARM::CMSIS + + # List different hardware targets that are used to deploy the solution. + target-types: + - type: GD32C103CB + device: GD32C103CB + target-set: + - set: + images: + - project-context: Robot_V2.Release + debugger: + name: ST-Link@pyOCD + clock: 10000000 + protocol: swd + + # List of different build configurations. + build-types: + - type: Debug + debug: on + optimize: none + + - type: Release + debug: off + optimize: balanced + + # List related projects. + projects: + - project: Robot_V2/Robot_V2.cproject.yml + compiler: AC6 diff --git a/backup_pre_tasks_split_20260524_011314/Robot_V2/initial.h b/backup_pre_tasks_split_20260524_011314/Robot_V2/initial.h new file mode 100644 index 0000000..bd597e0 --- /dev/null +++ b/backup_pre_tasks_split_20260524_011314/Robot_V2/initial.h @@ -0,0 +1,182 @@ +/** + * @file sys/initial.h + * @brief 系统服务与平台支持源文件 + * @note 本文件仅补充 Doxygen 文档注释,不改变代码逻辑。 + */ + +#ifndef _INITIAL_H +#define _INITIAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "config.h" +#include "gd32c10x_eval.h" +#include +#include + +// 任务标志定义 +/** + * @brief 匿名枚举 枚举定义 + */ +typedef enum { + TASK_MOTOR_CONTROL = 0, + TASK_CURRENT_UPDATE, + TASK_VELOCITY_UPDATE, + TASK_ANGLE_UPDATE, + TASK_MCU_TEMP_UPDATE, + TASK_DRV_TEMP_UPDATE, + TASK_VBUS_UPDATE, + TASK_THERMAL_CONTROL, + TASK_CAN_TRANSMIT, + TASK_MAGNETIC_CHECK, + TASK_COUNT +} SystemTask; + +/** + * @brief rcu_config 函数 + * @return void + */ +void rcu_config(void); +static volatile uint32_t timer_delay_count = 0; // volatile 很重要! +/** + * @brief fputc 函数 + * @param[in] ch 参数 + * @param[in] f 参数 + * @return int 返回值 + */ +int fputc(int ch, FILE *f); // 声明fputc函数以重载标准库实现 + +/** + * @brief 初始化 USART0 调试串口 + * @return u8 返回值 + */ +u8 USART0_init(void); +/** + * @brief 初始化独立看门狗 + * @param[in] time 参数 + * @return u8 返回值 + */ +u8 watchdog_init(u32 time); +/** + * @brief 获取复位原因 + * @return u16 返回值 + */ +u16 get_reset_cause(void); +/** + * @brief 初始化未使用引脚状态 + * @return void + */ +void unused_pin_init(); +/** + * @brief 初始化 OTA 与 A/B 分区状态 + * @return void + */ +void OTA_initial(void); + +// 任务管理结构 +typedef struct { + volatile u32 taskFlags; // 任务标志位(每个位代表一个任务) +} TaskScheduler; + +// 版本管理类 +/** + * @brief VersionManager 类或结构体 + */ +class VersionManager { + public: + // 版本信息结构体 + typedef struct { + u16 major; + u16 minor; + u16 patch; + u16 build; + } VersionInfo; + + // 构造函数 + VersionManager(); + + // 获取软件版本 + /** + * @brief 获取 getSoftwareVersion 对应的数据或状态 + * @return VersionInfo 返回值 + */ + VersionInfo getSoftwareVersion() const; + + // 获取硬件版本 + /** + * @brief 获取 getHardwareVersion 对应的数据或状态 + * @return VersionInfo 返回值 + */ + VersionInfo getHardwareVersion() const; + + // 获取BOOT版本 + /** + * @brief 获取 getBootVersion 对应的数据或状态 + * @return VersionInfo 返回值 + */ + VersionInfo getBootVersion() const; + + // 设置软件版本 + /** + * @brief 设置 setSoftwareVersion 对应的参数或状态 + * @param[in] major 参数 + * @param[in] minor 参数 + * @param[in] patch 参数 + * @param[in] build 参数 + * @return void + */ + void setSoftwareVersion(u16 major, u16 minor, u16 patch, u16 build); + + // 设置硬件版本 + /** + * @brief 设置 setHardwareVersion 对应的参数或状态 + * @param[in] major 参数 + * @param[in] minor 参数 + * @param[in] patch 参数 + * @param[in] build 参数 + * @return void + */ + void setHardwareVersion(u16 major, u16 minor, u16 patch, u16 build); + + // 设置BOOT版本 + /** + * @brief 设置 setBootVersion 对应的参数或状态 + * @param[in] major 参数 + * @param[in] minor 参数 + * @param[in] patch 参数 + * @param[in] build 参数 + * @return void + */ + void setBootVersion(u16 major, u16 minor, u16 patch, u16 build); + + // 将版本信息转换为字符串 + char *versionToString(const VersionInfo &version, char *buffer, + size_t bufSize) const; + + // 打印所有版本信息 + /** + * @brief printVersions 函数 + * @return void + */ + void printVersions() const; + + // 通过CAN发送版本信息 + /** + * @brief sendVersionInfoViaCan 函数 + * @return void + */ + void sendVersionInfoViaCan() const; + + private: + VersionInfo softwareVersion; + VersionInfo hardwareVersion; + VersionInfo bootVersion; +}; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/backup_pre_tasks_split_20260524_011314/Robot_V2/main.cpp b/backup_pre_tasks_split_20260524_011314/Robot_V2/main.cpp new file mode 100644 index 0000000..e364fe9 --- /dev/null +++ b/backup_pre_tasks_split_20260524_011314/Robot_V2/main.cpp @@ -0,0 +1,252 @@ +/** + * @file main.cpp + * @brief 应用程序主流程源文件 + * @note 本文件仅补充 Doxygen 文档注释,不改变代码逻辑。 + */ + +#include "main.h" +#include "Can.h" +#include "FOC.h" +#include "Filter.h" +#include "Timer.h" +#include "adc.h" +#include "ang_sensor.h" +#include "flash.h" +#include "health.h" +#include "initial.h" +#include "systick.h" + +// MAIN 调试宏定义 +#if MAIN_DEBUG +/** @brief MAIN_DEBUG_PRINT 宏定义 */ +#define MAIN_DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__) +#else +/** @brief MAIN_DEBUG_PRINT 宏定义 */ +#define MAIN_DEBUG_PRINT(fmt, ...) ((void)0) +#endif + +extern CAN_transmiter CAN0_transmiter; // CAN 初始化 +extern sampler sampler; // 采样器 +extern angle_sensor KTH7823; // 角度传感器 +extern EncoderVelocityKalman Vel_kalman; // 速度滤波 +extern EncoderAngleKalman Angle_kalman; // 角度滤波 +extern ThermalController thermalController; // 热量控制器 +extern FOC_Controller FOC; // FOC 控制 +extern FlashEEPROM flashEEP; // EEPROM 类 +extern VersionManager Version; + +// 主任务调度共享位图:TIMER2 ISR 置位任务,main 循环短关中断后取快照并清位。 +TaskScheduler scheduler = {0}; +// 电机 50us 控制周期计数:主循环电机任务递增,观测器/计时逻辑读取。 +u32 motor_runtime_50us = 0; +// 负载检测/通信心跳计数:主循环递增,CAN 周期发送任务清零。 +u32 cnt_test = 0; + +// 函数指针数组,包含各种控制模式对应的函数 +static FOCFunctionPtr control_functions[] = { + &FOC_Controller::drv_hold, // 0: 占空比置 + &FOC_Controller::angle_control, // 1: 位置闭环控制 + &FOC_Controller::velocity_control, // 2: 速度闭环控制 + &FOC_Controller:: + angle_control_with_velocity_limit, // 3: + // 角度闭环控制,带速度限制 + &FOC_Controller::MIT_Mode, // 4: MIT 模式 + &FOC_Controller::current_control, // 5: 电流闭环控制 + &FOC_Controller::velocity_open_loop, // 6: 速度开环控制 + &FOC_Controller::PWM_out, // 0: 占空比置 +}; + +/** + * @brief 判断任务标志中是否包含指定任务 + * @param[in] currentTasks 当前任务位图快照 + * @param[in] task 待检查任务枚举 + * @return bool true 表示该任务需要执行 + */ +static bool has_task(u32 currentTasks, SystemTask task) { + return (currentTasks & (1u << task)) != 0U; +} + +/** + * @brief 控制模式变化时复位所有控制环,避免积分项跨模式继承 + * @return void + */ +static void reset_control_loops_on_mode_change(void) { + static int last_control_mode = -1; + if (FOC.control_mode == last_control_mode) return; + + FOC.vel_loop.reset(); + FOC.angle_loop.reset(); + FOC.current_loop.reset(); + FOC.vel_inVelAngloop.reset(); + FOC.ang_inVelAngloop.reset(); + FOC.ang_inMITloop.reset(); + FOC.vel_inMITloop.reset(); + FOC.current_inMITloop.reset(); + last_control_mode = FOC.control_mode; +} + +/** + * @brief 执行 50us 电机控制任务,包含快速电压检测和控制函数派发 + * @return void + */ +static void run_motor_control_task(void) { + motor_runtime_50us++; + + static u8 vcheck_div = 0; + // 快速电压检测每 100us 执行一次,避免每 50us 都读取处理。 + if (++vcheck_div >= 2u) { + vcheck_div = 0; + FOC.fast_voltage_check(); + } + + if (FOC.control_mode >= 8) return; + + // 模式合法时再派发控制函数,防止数组越界。 + reset_control_loops_on_mode_change(); + if (!FOC.is_shut_down) { + (FOC.*control_functions[FOC.control_mode])(); + } else { + FOC.drv_stop(); + } +} + +/** + * @brief 执行高频采样和电机控制相关任务 + * @param[in] currentTasks 当前任务位图快照 + * @return void + */ +static void run_fast_sample_tasks(u32 currentTasks) { + if (has_task(currentTasks, TASK_CURRENT_UPDATE)) { + sampler.update_current(); + } + if (has_task(currentTasks, TASK_ANGLE_UPDATE)) { + KTH7823.update(); + FOC.rt_angle = Angle_kalman.update(KTH7823.getAngle()); + } + if (has_task(currentTasks, TASK_VELOCITY_UPDATE)) { + FOC.rt_velocity = Angle_kalman.getVelocity(); + } + if (has_task(currentTasks, TASK_MOTOR_CONTROL)) { + run_motor_control_task(); + } +} + +/** + * @brief 执行温度、热管理、磁检测和母线电压等低频维护任务 + * @param[in] currentTasks 当前任务位图快照 + * @return void + */ +static void run_housekeeping_tasks(u32 currentTasks) { + if (has_task(currentTasks, TASK_MCU_TEMP_UPDATE)) { + sampler.update_mcu_temp(); + } + if (has_task(currentTasks, TASK_DRV_TEMP_UPDATE)) { + sampler.update_drv_temp(); + } + if (has_task(currentTasks, TASK_THERMAL_CONTROL)) { + thermalController.update(); + } + if (has_task(currentTasks, TASK_MAGNETIC_CHECK)) { + angsens_MG_check(); + } + if (has_task(currentTasks, TASK_VBUS_UPDATE)) { + power_manage(); + } +} + +/** + * @brief 执行 CAN 周期发送任务并喂狗 + * @return void + */ +static void run_can_task(void) { + CAN0_transmiter.test_u16 = CAN0_transmiter.get_node_id(); + cnt_test = 0; + CAN0_transmiter.can0_send_flag = SET; + CAN0_transmiter.communication_check(); + fwdgt_counter_reload(); +} + +/** + * @brief 应用程序入口函数 + * @return int 返回值 + */ +int main(void) { + u8 init_status = hardware_init(); + + while (init_status) { // 初始化检测 + MAIN_DEBUG_PRINT("初始化失败\r\n"); + } + OTA_initial(); // OTA 初始化,A/B 分区处理 + + angsens_MG_check(); // 角度传感器磁感应强度测试 + watchdog_init(50); // 看门狗初始化 + + while (1) { + __disable_irq(); + u32 currentTasks = + scheduler.taskFlags; // 保存当前任务标志快照以提高效率并确保一致性 + scheduler.taskFlags &= ~currentTasks; // 一次性清除所有已处理的任务标志 + __enable_irq(); + + if (currentTasks == 0U) { + idle_task_count(); + continue; + } + + do_task(currentTasks); + } + return 0; +} + +/** + * @brief 初始化应用层使用的硬件与软件模块 + * @return u8 返回值 + */ +u8 hardware_init(void) { + __enable_irq(); + systick_config(); // 系统时钟初始化 + unused_pin_init(); // 未使用引脚初始化 + USART0_init(); // 调试串口初始化 + User_timer_init(); // 任务 Timer 初始化 + + u8 init_status = 0; + init_status += KTH7823.init(); // 角度传感器初始化 + init_status += sampler.init(); // 采样器初始化 + + // EEPROM 初始化 (必须在 CAN 初始化之前,因为 CAN 会从 Flash 读取节点 ID) + flashEEP.init(); // EEPROM 初始化 + + // CAN 初始化 (会先设置默认节点 ID,然后从 Flash 加载已保存的节点 ID) + init_status += CAN0_transmiter.init(); // CAN 初始化 + + init_status += Angle_kalman.init(KTH7823.getAngle()); // 角度滤波初始化 + init_status += Vel_kalman.init(0.0f); // 速度滤波初始化 + init_status += FOC.init(); // FOC 初始化 + return init_status; +} + +/** + * @brief 根据任务标志调度周期性业务任务 + * @param[in] currentTasks 参数 + * @return void + */ +void do_task(u32 currentTasks) { + busy_task_enter(); // DWT: 记录忙碌起始周期 + cnt_test++; + + // 高频任务先执行,保持电机控制优先级。 + run_fast_sample_tasks(currentTasks); + + // 齿槽转矩校准 tick(DEBUG,平时不启用) + #if FOC_DEBUG + FOC.calibrate_cogging_tick(); + #endif + + run_housekeeping_tasks(currentTasks); + + // CAN 发送放在最后,避免通信处理影响前面的实时控制任务。 + if (has_task(currentTasks, TASK_CAN_TRANSMIT)) { + run_can_task(); + } + busy_task_exit(); // DWT: 累加忙碌周期 +} \ No newline at end of file diff --git a/backup_pre_tasks_split_20260524_011314/Robot_V2/main.h b/backup_pre_tasks_split_20260524_011314/Robot_V2/main.h new file mode 100644 index 0000000..3992cf0 --- /dev/null +++ b/backup_pre_tasks_split_20260524_011314/Robot_V2/main.h @@ -0,0 +1,52 @@ +/*! + \file main.h + \brief the header file of main + + \version 2025-06-20, V1.5.1, firmware for GD32C10x +*/ + +/* + Copyright (c) 2024, GigaDevice Semiconductor Inc. + + Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from this +software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef MAIN_H +#define MAIN_H +#include + + +/** + * @brief 根据任务标志调度周期性业务任务 + * @param[in] currentTasks 参数 + * @return void + */ +void do_task(u32 currentTasks); +/** + * @brief 初始化应用层使用的硬件与软件模块 + * @return u8 返回值 + */ +u8 hardware_init(void); + +#endif /* MAIN_H */ diff --git a/static_test_automotive_business_20260523/robot_v2_app_static_report_check_progress.json b/static_test_automotive_business_20260523/robot_v2_app_static_report_check_progress.json index 28f03db..84409c7 100644 --- a/static_test_automotive_business_20260523/robot_v2_app_static_report_check_progress.json +++ b/static_test_automotive_business_20260523/robot_v2_app_static_report_check_progress.json @@ -2,9 +2,261 @@ "version": 1, "storageKey": "robot_v2_app_static_report_checklist_v1", "report": "ROBOT_V2_APP_static_test_automotive_business_20260523", - "updatedAt": "2026-05-23T14:41:51.724Z", + "updatedAt": "2026-05-23T16:25:00.825Z", "checked": { "main-3117cb79205a": true, - "main-527272d22f5f": true + "main-527272d22f5f": true, + "main-5ceb0ceaf414": true, + "main-0d853312a4fb": true, + "main-d5c0067cb8c9": true, + "main-b3497022fd57": true, + "main-fb0f6eef33ee": true, + "main-e8d34916ef43": true, + "main-a0e7f44822d5": true, + "main-e8581b775b86": true, + "main-a61db2bcd87e": true, + "main-1bfba6db3552": true, + "main-29a13abcf59d": true, + "main-cc75af885325": true, + "main-2665cbd3f00e": true, + "main-843a028f8696": true, + "main-4117283cf3e7": true, + "main-344a33bfcf2b": true, + "main-efc8f847b950": true, + "main-92473d3ec470": true, + "main-dca22002f30a": true, + "main-668e5afb0750": true, + "main-778a368d79d3": true, + "main-c8ae670ed482": true, + "main-44e5b708ced1": true, + "main-eecf1f323336": true, + "main-cd72d3b37996": true, + "supp-091ea0cb4563": true, + "supp-b251edcd19a4": true, + "supp-72780618de89": true, + "supp-428c4d2d4361": true, + "supp-5bc6c7028f31": true, + "supp-5f30c71b560c": true, + "supp-ec236e967cf5": true, + "supp-6c83765299a8": true, + "supp-a3f3ec52d5c9": true, + "supp-dba1f5ee2bac": true, + "supp-a00f50fd7e91": true, + "supp-cdd0a4a9b8e7": true, + "supp-fc1eb40a78e5": true, + "supp-7b0c20306d7f": true, + "supp-955e70ca702a": true, + "supp-26a75340d328": true, + "supp-b684bf6daa82": true, + "supp-cfb32941b86c": true, + "supp-4db7e8721b38": true, + "supp-18273d7ff405": true, + "supp-a0b262ee0d7f": true, + "supp-c8fcf2049028": true, + "supp-e82e3e95d9c3": true, + "supp-9631705a92be": true, + "supp-0ba6f0a3cbb6": true, + "supp-bfd02b0e6428": true, + "supp-d6a6beb85f8e": true, + "supp-54f7152d525f": true, + "supp-57ed4b5aeb01": true, + "supp-97688039a586": true, + "supp-5f838841063f": true, + "supp-a2c7b377ff4e": true, + "supp-296377880c63": true, + "supp-b038358270db": true, + "supp-6c02c2686174": true, + "supp-038975fb0589": true, + "supp-ddcf3c5981d6": true, + "supp-1f9a0ad0330e": true, + "supp-eb8c172d8fa7": true, + "supp-415c4b510e1b": true, + "supp-5f5afb443061": true, + "supp-52e7dfa0bd8a": true, + "supp-7eca8869fe8d": true, + "supp-cd1b03277370": true, + "supp-9e0e93117b39": true, + "supp-476cb8529804": true, + "supp-9d2d2a633571": true, + "supp-c843a8b60fa0": true, + "supp-a372ad7cc25f": true, + "supp-d47f02ccdc6f": true, + "supp-02752fc39463": true, + "supp-fba82de9f335": true, + "supp-b464d624b89c": true, + "supp-177d79286976": true, + "supp-4b1489b395b5": true, + "supp-fd0c805a01cb": true, + "supp-4f9e1ff5f977": true, + "supp-5bf3d6123aea": true, + "supp-378c2eadddd6": true, + "supp-20a5d232f926": true, + "supp-f3b55e35f9d0": true, + "supp-1acaf41cfdae": true, + "supp-fbc927cfd61d": true, + "supp-b187de9b2c71": true, + "supp-d7851021e615": true, + "supp-7265bf9ffeb6": true, + "supp-262745c8fde4": true, + "supp-fb700e5b9217": true, + "supp-09cf228fa618": true, + "supp-8f1a0be480db": true, + "supp-2ec9227ef7dd": true, + "supp-674252936cac": true, + "supp-7c9b992c78be": true, + "supp-f938632efd00": true, + "supp-ec2f21ab7179": true, + "supp-b59a8a08dd46": true, + "supp-a954c6db3be8": true, + "supp-191496d69aae": true, + "supp-334250f82da0": true, + "supp-cb34e84dcbdf": true, + "supp-624129e18b9e": true, + "supp-58df8dc7015c": true, + "supp-442b031030c0": true, + "supp-4182de41f8da": true, + "supp-4becb5ead2c4": true, + "supp-2eaa8c7f0941": true, + "supp-ff8f09c6bfa5": true, + "supp-63c6dddf8bf5": true, + "supp-9d4b1e19e4f1": true, + "supp-b163701fc702": true, + "supp-6495be6cf728": true, + "supp-007928464d9b": true, + "supp-df5a0f8d5a72": true, + "supp-3dbd2f0de758": true, + "supp-9946aa57c8a9": true, + "supp-07a2866f170a": true, + "supp-2c87813f8aa3": true, + "supp-aa5f59116193": true, + "supp-1fbd51d49dad": true, + "supp-e0380903f6c3": true, + "supp-479efab46368": true, + "supp-09cce3719ad2": true, + "supp-d88f31fa5c0e": true, + "supp-4cf2fa02952e": true, + "supp-a3df150b3937": true, + "supp-fd66a295e97d": true, + "supp-473f9bd57824": true, + "supp-4a07cfb831d2": true, + "supp-85f55cfb9bd2": true, + "supp-816cb5806281": true, + "supp-e6f6ac38039c": true, + "supp-41033b3cde59": true, + "supp-a6c89579f6e4": true, + "supp-9dd3d0dbc734": true, + "supp-1f027dfb746a": true, + "supp-da022ec3d833": true, + "supp-6f6ffee00f58": true, + "supp-89f2f99e8e8d": true, + "supp-b07105c03caa": true, + "supp-a374b2a62cc3": true, + "supp-2687440608b0": true, + "supp-19455bafa3ac": true, + "supp-6e12b0ca84d2": true, + "supp-05a59d0def43": true, + "supp-e7dccd9a4003": true, + "supp-5df96dbf6738": true, + "supp-8148e3027849": true, + "supp-c5e6fb0e92b3": true, + "supp-f21a6ce0ca0e": true, + "supp-d3c74b81ee6f": true, + "supp-206d431ac433": true, + "supp-8f19a6ce3b2b": true, + "supp-dc7c842eaf8a": true, + "supp-b646b277d882": true, + "supp-8035701aea54": true, + "supp-8c8575a287e0": true, + "supp-9345c6e7b49b": true, + "supp-2871d296449d": true, + "supp-d86607ccbc20": true, + "supp-eb4e342e74d7": true, + "supp-23f2e81c8f43": true, + "supp-1a4ed985430d": true, + "supp-cef1f0c10332": true, + "supp-cfa7b96290df": true, + "supp-8066dac3a4e4": true, + "supp-045b8947a919": true, + "supp-39a61666efaf": true, + "supp-da4349769920": true, + "supp-df53690c09aa": true, + "supp-a977c6fa3fc0": true, + "supp-90bbab9c52ba": true, + "supp-7e9875638f06": true, + "supp-2cb25842f03e": true, + "supp-5f1962ce4046": true, + "main-3d52cdf723db": true, + "main-a22197731662": true, + "main-0c3a88910d56": true, + "main-687056efb188": true, + "main-2787e912f9ed": true, + "main-f53db085a93d": true, + "main-552e5d7b3055": true, + "main-4e82e66f670a": true, + "main-ce883b19cd52": true, + "main-5240bf640bb6": true, + "main-61c463500c61": true, + "main-977f3df23a03": true, + "main-a82cf38aaae0": true, + "metric-48d7e9621754": true, + "metric-65df715e5faa": true, + "metric-e7f74eabf84b": true, + "metric-f6c2bfb36b40": true, + "metric-0dfc09c00e94": true, + "metric-98b568f0e6b4": true, + "metric-ac925d0fa661": true, + "supp-be0750f37b75": true, + "supp-babc355f3ba4": true, + "supp-07a2fcbd63fb": true, + "supp-c48ffa36def4": true, + "supp-7464d86d386a": true, + "supp-c2c374b8a410": true, + "supp-f303809c0871": true, + "supp-897f14107d25": true, + "supp-bc539f456a7e": true, + "supp-6466d05c08ad": true, + "supp-e14bc6bcacd6": true, + "supp-73804b17c385": true, + "supp-52ca6be1c9f5": true, + "supp-b0ab1b3cde0e": true, + "supp-e73532e2d89c": true, + "supp-220ae6c1673f": true, + "supp-c0e88a884841": true, + "supp-191ba764ae89": true, + "supp-74fddf697b8f": true, + "supp-c3fac9483ce8": true, + "supp-e92b16152a23": true, + "supp-c08e7fa1d6c4": true, + "supp-5c8269165c25": true, + "supp-212345faf28b": true, + "supp-9838229d4cbb": true, + "supp-3038da272c7c": true, + "supp-0d2a8817da44": true, + "supp-89c98258c38a": true, + "supp-528a6cec1e84": true, + "supp-e242b707d647": true, + "supp-9c6e067632b0": true, + "supp-570cf82a0ea5": true, + "supp-b848b2393298": true, + "supp-2dd905d3d33a": true, + "supp-151515384d9d": true, + "supp-834473d55cbd": true, + "supp-0ed73a1e9822": true, + "supp-1f41a4e9399a": true, + "supp-0d1b7279df03": true, + "supp-3b9068c82c27": true, + "supp-7a89b7e99a15": true, + "supp-0512b0719836": true, + "supp-2bdf5602b64c": true, + "supp-014e603e9a05": true, + "supp-192ee126bacd": true, + "supp-5e8bbfb22dca": true, + "supp-cfd2894ba6c6": true, + "supp-80fbfa1e7f2a": true, + "supp-0a7fb6af2bfa": true, + "supp-79489853ba70": true, + "supp-8f5907226506": true, + "supp-da2839ee2717": true, + "supp-c7cb3cd0801f": true } } \ No newline at end of file