diff --git a/Robot_V2/device/ADC.cpp b/Robot_V2/device/ADC.cpp index 3432ce7..ff6f38c 100644 --- a/Robot_V2/device/ADC.cpp +++ b/Robot_V2/device/ADC.cpp @@ -13,6 +13,7 @@ #include "math.h" #include "systick.h" #include +#include #include // ADC 换算常量均为编译期常量,非可变全局状态。 @@ -31,6 +32,15 @@ constexpr float VCC_FACTOR = 11.0f; * 修改 config.h 的 DRV8323_CSA_GAIN_VV 后此系数自动跟随。 */ constexpr float CURRENT_FACTOR = 1000.0f / (DRV8323_CSA_GAIN_VV * DRV8323_SHUNT_OHM); +#elif CURRENT_SENSE_INA240 +/** @brief CURRENT_FACTOR 常量定义(低边采样板:下桥臂 10mΩ + INA240A2) + * 电流链路单位为安培:get_current() 返回 A,cal_Iq_Id/Can 上报处 ×1000 转 mA。 + * OUT = 1.65V ± I×R×G(REF1=AGND、REF2=A3V3,芯片内部分压取中点 1.65V), + * G=50V/V、R=10mΩ → 0.5V/A → 2.0 A/V,ADC 3.3V 满量程对应约 ±3.3A。 + * 与老板外置放大链(50倍×10mΩ)标定数值恰好一致;换增益/阻值时此系数 + * 随 config.h 的 INA240_CSA_GAIN_VV / INA240_SHUNT_OHM 自动跟随。 */ +constexpr float CURRENT_FACTOR = + 1.0f / (INA240_CSA_GAIN_VV * INA240_SHUNT_OHM); #else /** @brief CURRENT_FACTOR 常量定义(老板:外置放大链,保持原标定) */ constexpr float CURRENT_FACTOR = @@ -52,13 +62,23 @@ volatile u16 adc_mcu_temp_raw = 0; // ADC0 injected rank3: MCU温度原始值 static volatile u16 adc_current_value[3] = {0}; // injected 同步采样: U / V / W static volatile u8 adc_current_frame_ready = 0; +// ---- 三相电流零点配平结果(上电一次,hardware_init 在 gate_driver_bringup +// 之后调用 zero_calibrate 采集)。含义:各通道实测零点相对 CURRENT_OFFSET +// 基准的偏差(V),采样换算时逐通道扣除,吸收 REF 中点偏差、AGND/PGND 地差 +// 与 INA 失调。初始 0 = 未配平(按标称零点换算)。 +static float adc_zero_offset_v[3] = {0.0f, 0.0f, 0.0f}; + /** - * @brief 将电流 ADC 原始值换算为毫安值 - * @param[in] raw_adc 参数 + * @brief 将电流 ADC 原始值换算为电流值 + * @param[in] raw_adc 注入组转换原始值 + * @param[in] ch 通道号 0/1/2 = U/V/W(逐通道扣除上电配平零点偏差) * @return float 返回值 + * @note 函数名沿用历史(_to_ma),实际返回单位是安培:MIN/MAX_CURRENT(±40) + * 为安培阈值,cal_Iq_Id / CAN 上报处再 ×1000 转 mA。 */ -static inline float current_adc_to_ma(u16 raw_adc) { - return (raw_adc * ADC_SCALE + CURRENT_OFFSET) * CURRENT_FACTOR; +static inline float current_adc_to_ma(u16 raw_adc, u8 ch) { + return (raw_adc * ADC_SCALE + CURRENT_OFFSET - adc_zero_offset_v[ch]) * + CURRENT_FACTOR; } // ADC1 连续转换运行标志:adc1_config() 启动后置位, @@ -187,6 +207,15 @@ u16 adc1_read_raw(u8 channel) { 采样电容残余污染注入组首通道。移除 regular 后注入组再无打断源。 */ void adc_config(void) { + /* 电流采样引脚配模拟输入:U/V/W = PA3/PA4/PA5(ADC0_IN3/4/5)。 + * 手册要求 ADC 引脚必须配 AIN——复位默认浮空输入态下数字输入缓冲器 + * 挂在引脚上,静态 1.65V 中点正处于线性区,开关噪声环境下会向引脚 + * 注入电荷、污染 13.5 周期短采样窗口(v1 外置放大链下凑合未暴露)。 + * 顺手关掉该三脚的数字输入缓冲(低功耗&抗扰)。 */ + rcu_periph_clock_enable(RCU_GPIOA); + gpio_init(GPIOA, GPIO_MODE_AIN, GPIO_OSPEED_2MHZ, + GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5); + /* enable ADC clock */ rcu_periph_clock_enable(RCU_ADC0); /* config ADC clock(ADC0/ADC1 共用分频:120MHz/4 = 30MHz) */ @@ -204,17 +233,21 @@ void adc_config(void) { adc_tempsensor_vrefint_enable(); /* injected 组:rank0=U, rank1=V, rank2=W, rank3=MCU温度,TIMER1 TRGO 触发。 - * 温度传感器(ch16)为高内阻源,数据手册要求采样时间≥17.1μs,保持最大档 - * 239.5 周期,且挂在序列尾部:整段转换不会被打断,每 PWM 周期同步采一次, - * 40kHz 过采样 + 中值滤波后温度比旧架构更稳。 - * 原先的 rank0 dummy 已删除:dummy 用于吸收"注入打断 regular"产生的采样 - * 电容残余电荷,现 regular 已移除,序列空闲期电容挂在温度通道(~1.3V), - * U 相(ch3 运放低阻输出) 的 55.5 周期采样足以完成重建。 - * 序列总时长 68×3+252 = 456 周期 = 15.2μs < 25μs 触发周期。 */ + * 电流通道用最短 13.5 周期采样(INA240 低阻输出足以驱动采样电容): + * 每通道 26 周期,U/V/W 的采样保持窗末端仅滞后触发点 0.45/1.32/2.18µs + * (54/158/262 个计数),三相样本整体压进下管导通窗头部,最大占空比 + * ~90% 时仍全部有效;温度通道 ch16 高内阻按手册仍需 239.5 周期,挂尾部。 + * 整段 26×3+252 = 330 周期 = 11µs。 + * 触发点紧贴计数峰顶(见 config.h):中心对齐下 CH3 每周期匹配两次 + * (上升沿 2989 / 下降沿 3011,仅差 22 计数),无论第二次触发是被 ADC + * 忽略(注入忙)还是重启序列,帧的等效采样位置都贴着峰顶——三相下管 + * 同时导通窗的正中,帧率 20kHz 与控制环同频。 + * 序列空闲期采样电容挂在温度通道(~1.3V),U 相(ch3 运放低阻输出)的 + * 13.5 周期保持窗足以完成重建。 */ adc_channel_length_config(ADC0, ADC_INSERTED_CHANNEL, 4); - adc_inserted_channel_config(ADC0, 0, ADC_CHANNEL_3, ADC_SAMPLETIME_55POINT5); - adc_inserted_channel_config(ADC0, 1, ADC_CHANNEL_4, ADC_SAMPLETIME_55POINT5); - adc_inserted_channel_config(ADC0, 2, ADC_CHANNEL_5, ADC_SAMPLETIME_55POINT5); + adc_inserted_channel_config(ADC0, 0, ADC_CHANNEL_3, ADC_SAMPLETIME_13POINT5); + adc_inserted_channel_config(ADC0, 1, ADC_CHANNEL_4, ADC_SAMPLETIME_13POINT5); + adc_inserted_channel_config(ADC0, 2, ADC_CHANNEL_5, ADC_SAMPLETIME_13POINT5); adc_inserted_channel_config(ADC0, 3, ADC_CHANNEL_16, ADC_SAMPLETIME_239POINT5); adc_external_trigger_source_config(ADC0, ADC_INSERTED_CHANNEL, @@ -239,7 +272,7 @@ u8 sampler::init() { delay_1ms(1); const float current_init = current_adc_to_ma( - static_cast((-CURRENT_OFFSET / ADC_SCALE) + 0.5f)); + static_cast((-CURRENT_OFFSET / ADC_SCALE) + 0.5f), 0); // 配平前按标称零点 // 初始采样 for (int i = 0; i < ADC_FILTER_SIZE; i++) { mcu_temp_samples[i] = @@ -259,6 +292,66 @@ u8 sampler::init() { } return 0; } +// ---- 三相电流零点配平(上电一次,hardware_init 在 gate_driver_bringup 之后调用) ---- +// 必须在门驱动唤醒后进行:DRV8323 板唤醒后 INLx=DRVSLP 常高,0% 占空比下 +// 三相下管导通、采样电阻经下管接地,INA240 输入有确定共模(0V),输出即 +// 真实零点;若在芯片睡眠 Hi-Z 时采样,下管源极悬空,INA240 IN+ 浮空会把 +// 输出拉到轨,配平值无效(老板下管常通,两种时序均有效,但统一放后面)。 +// 采集 N 帧均值作为每通道零点偏差(V,相对 CURRENT_OFFSET 基准),折算 +// 电流超过 CURRENT_ZERO_CAL_LIMIT 视为电流检测初始化错误。 + +/** + * @brief 三相电流零点配平并校验 + * @return u8 0=成功;1=无采样帧(ADC/触发链路异常); + * 2=配平值超过 CURRENT_ZERO_CAL_LIMIT(电流检测初始化错误) + * @note 阻塞约 15ms(跳过 5ms 上电瞬态 + 采集 200 帧 @20kHz),仅限 + * hardware_init 上下文调用(看门狗在其后才初始化,无喂狗需求)。 + * main 循环尚未启动,期间 EOIC 中断只刷新 adc_current_value, + * 不会与 update_current 并发写环形缓冲。 + */ +u8 sampler::zero_calibrate(void) { + delay_1ms(5); // 跳过上电瞬态(INA 建立、驱动使能稳定),≈100 帧 + + u32 zero_acc[3] = {0, 0, 0}; + const u16 ZERO_CAL_FRAMES = 200; + for (u16 i = 0; i < ZERO_CAL_FRAMES; i++) { + u32 timeout = 0; + while (adc_current_frame_ready == 0U) { + /* 帧周期 50µs,超时覆盖 ~30ms 无帧:ADC/触发链路异常 */ + if (++timeout > 600000U) { + printf("[ADC] zero cal fail: no sample frame\r\n"); + return 1; + } + } + zero_acc[0] += adc_current_value[0]; + zero_acc[1] += adc_current_value[1]; + zero_acc[2] += adc_current_value[2]; + adc_current_frame_ready = 0U; + } + + u8 result = 0; + printf("[ADC] zero cal:"); + for (u8 ch = 0; ch < 3U; ch++) { + /* 实测零点偏差(V)= 样本均值电压 + CURRENT_OFFSET 基准 */ + adc_zero_offset_v[ch] = + ((float)zero_acc[ch] / (float)ZERO_CAL_FRAMES) * ADC_SCALE + + CURRENT_OFFSET; + /* 配平值折算电流(A)超限 → 电流检测初始化错误 */ + float offset_a = adc_zero_offset_v[ch] * CURRENT_FACTOR; + printf(" %c=%dmA", "UVW"[ch], (int)(offset_a * 1000.0f)); + if (offset_a > CURRENT_ZERO_CAL_LIMIT || + offset_a < -CURRENT_ZERO_CAL_LIMIT) { + result = 2; + } + } + if (result == 2) { + printf(" -> over limit, current sense init error\r\n"); + } else { + printf(" ok\r\n"); + } + return result; +} + /** * @brief 获取MCU温度 * @return float @@ -377,9 +470,9 @@ void sampler::update_current() { } const u8 idx = current_sample_head; - currU_samples[idx] = current_adc_to_ma(adc_current_value[0]); - currV_samples[idx] = current_adc_to_ma(adc_current_value[1]); - currW_samples[idx] = current_adc_to_ma(adc_current_value[2]); + currU_samples[idx] = current_adc_to_ma(adc_current_value[0], 0); + currV_samples[idx] = current_adc_to_ma(adc_current_value[1], 1); + currW_samples[idx] = current_adc_to_ma(adc_current_value[2], 2); current_sample_head++; if (current_sample_head >= ADC_FILTER_SIZE) { diff --git a/Robot_V2/device/Can.cpp b/Robot_V2/device/Can.cpp index 1292b30..f518a72 100644 --- a/Robot_V2/device/Can.cpp +++ b/Robot_V2/device/Can.cpp @@ -783,8 +783,10 @@ u8 CAN_transmiter::CANFD_all_Send() { u32 success_flag = 0; - // 打包 status 数据 - 只清零实际使用的 32 字节 - memset(transmit_data, 0, 32); + // 打包 status 数据 - 只清零实际使用的 48 字节 + // v0.74:字节 32~35 新增 InitStatus(初始化错误码位图,0=全部正常, + // 定义见 config.h INIT_ERR_*,含电流检测配平超限/无采样帧),36~47 预留 + memset(transmit_data, 0, 48); pack_u16_to_bytes(transmit_data, 0, mcu_temp); pack_u16_to_bytes(transmit_data, 2, motor_temp); pack_current_s16_to_bytes(transmit_data, 4, u_current_union.f); @@ -802,9 +804,10 @@ u8 CAN_transmiter::CANFD_all_Send() { transmit_data[29] = test_u16 & 0xFF; transmit_data[30] = (test_u16 >> 8) & 0xFF; transmit_data[31] = cpu_usage; + pack_u32_to_bytes(transmit_data, 32, (u32)init_error_flags); // 使用节点 ID 构建发送 ID (高 16 位为节点 ID) success_flag += - CANFD_AsyncSend(build_can_id(CANFD_STATUS_ID), 32, transmit_data); + CANFD_AsyncSend(build_can_id(CANFD_STATUS_ID), 48, transmit_data); // 打包 config 数据 - 只清零实际使用的 24 字节 memset(transmit_data, 0, 24); @@ -972,6 +975,16 @@ void CAN_transmiter::handle_node_message(can_receive_message_struct ¤t_mes void CAN_transmiter::handle_control_command(can_receive_message_struct ¤t_message) { // 控制帧布局:模式、电压限制、目标电流/速度/角度、三组 P 参数。 + /* 收到非零控制模式即视为驱动意图:v2 板 EN 经 R17 10k 下拉、DRV8313 + * 上电默认睡眠(上电配平后也保持睡眠态),控制路径里没有其他唤醒点, + * 不在此唤醒则所有闭环控制都落在 Hi-Z 桥臂上永远无效。 + * 模式 0(drv_hold 主动锁止,A 相恒 HOLD_PWM_DUTY)不唤醒:上位机 + * 上电先发模式 0 当"静止待机"时不至于意外通电锁止发热;驱动已在 + * 唤醒态时模式 0 仍是锁止。幂等;关机态(过流/过压/过热)不唤醒, + * 恢复仍由 power_manage / resume_from_thermal 统一管理。 */ + if (current_message.rx_data[0] != 0U && !FOC.is_shut_down) { + motor_resume(); + } 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; diff --git a/Robot_V2/device/Can.h b/Robot_V2/device/Can.h index 733694b..d53606d 100644 --- a/Robot_V2/device/Can.h +++ b/Robot_V2/device/Can.h @@ -38,6 +38,18 @@ static inline void pack_u16_to_bytes(u8 *buffer, u16 offset, u16 value) { buffer[offset + 1] = (u8)((value >> 8) & 0xFF); } +/** + * @brief pack_u32_to_bytes 函数(小端) + * @param[in] buffer 参数 + * @param[in] offset 参数 + * @param[in] value 参数 + * @return void + */ +static inline void pack_u32_to_bytes(u8 *buffer, u16 offset, u32 value) { + pack_u16_to_bytes(buffer, offset, (u16)(value & 0xFFFF)); + pack_u16_to_bytes(buffer, offset + 2, (u16)((value >> 16) & 0xFFFF)); +} + // 将 mA 电流值饱和转换为 int16 后按小端打包。 // DBC 中 U/V/W current 定义为 signed int16([-32768,32767] mA); // 相电流有正有负,此前用 u16() 打包负值被削成 0,上位机波形下半周丢失。 diff --git a/Robot_V2/device/Timer.cpp b/Robot_V2/device/Timer.cpp index e867f06..aaf6497 100644 --- a/Robot_V2/device/Timer.cpp +++ b/Robot_V2/device/Timer.cpp @@ -266,7 +266,8 @@ void timer1_pwm_config(void) { TIMER1_DEFAULT_PULSE); timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_2, TIMER1_DEFAULT_PULSE); - // 设置 CH3 为固定 ADC 采样点,避免与三相 PWM duty 耦合 + // 设置 CH3 为固定 ADC 采样点(低边采样:必须落在三相下管同时导通窗, + // 即中心对齐计数的峰顶附近,见 config.h TIMER1_CH3_COMPARE_VALUE 说明) timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_3, TIMER1_CH3_COMPARE_VALUE); diff --git a/Robot_V2/device/adc.h b/Robot_V2/device/adc.h index a9a5bc8..ce52d09 100644 --- a/Robot_V2/device/adc.h +++ b/Robot_V2/device/adc.h @@ -40,6 +40,12 @@ class sampler { * @return u8 返回值 */ u8 init(void); + /** + * @brief 三相电流零点配平并校验(hardware_init 在 FOC.init 之后调用) + * @return u8 0=成功;1=无采样帧(ADC/触发链路异常); + * 2=配平值超过 CURRENT_ZERO_CAL_LIMIT(电流检测初始化错误) + */ + u8 zero_calibrate(void); /** * @brief 更新 mcu_temp 相关状态 * @return void diff --git a/Robot_V2/main.cpp b/Robot_V2/main.cpp index 4346926..537efc3 100644 --- a/Robot_V2/main.cpp +++ b/Robot_V2/main.cpp @@ -41,6 +41,9 @@ TaskScheduler scheduler = {0}; u32 motor_runtime_50us = 0; // 负载检测/通信心跳计数:主循环递增,CAN 周期发送任务清零。 u32 cnt_test = 0; +// 初始化错误位图(INIT_ERR_*,config.h):hardware_init 按模块置位, +// CAN STATUS 帧 InitStatus 反馈给上位机,0 表示全部初始化正常 +volatile u32 init_error_flags = 0; /** * @brief 应用程序入口函数 @@ -50,7 +53,11 @@ int main(void) { u8 init_status = hardware_init(); while (init_status) { // 初始化检测 - MAIN_DEBUG_PRINT("初始化失败\r\n"); + // 初始化失败:持续通过 CAN STATUS 帧上报 InitStatus 错误码 + // (含电流检测配平超限/无采样帧),上位机无需调试串口即可识别故障模块 + printf("初始化失败 InitStatus=0x%08X\r\n", init_error_flags); + CAN0_transmiter.CANFD_all_Send(); // STATUS 帧字节 32~35 携带错误位图 + delay_1ms(100); } OTA_initial(); // OTA 初始化,A/B 分区处理 @@ -86,24 +93,61 @@ u8 hardware_init(void) { USART0_init(); // 调试串口初始化 User_timer_init(); // 任务 Timer 初始化 - u8 init_status = 0; - init_status += KTH7823.init(); // 角度传感器初始化 - init_status += sampler.init(); // 采样器初始化 + // 初始化错误码按模块置位(INIT_ERR_*,位或组合;0=全部正常), + // 经 CAN STATUS 帧 InitStatus 反馈 + init_error_flags = 0; + if (KTH7823.init()) { + init_error_flags |= INIT_ERR_ANGLE_SENSOR; // 角度传感器初始化 + } + if (sampler.init()) { + init_error_flags |= INIT_ERR_ADC; // 采样器/ADC 初始化 + } // EEPROM 初始化 (必须在 CAN 初始化之前,因为 CAN 会从 Flash 读取节点 ID) flashEEP.init(); // EEPROM 初始化 // CAN 初始化 (会先设置默认节点 ID,然后从 Flash 加载已保存的节点 ID) - init_status += CAN0_transmiter.init(); // CAN 初始化 + if (CAN0_transmiter.init()) { + init_error_flags |= INIT_ERR_CAN; // CAN 初始化 + } - init_status += Angle_kalman.init(KTH7823.getAngle()); // 角度滤波初始化 - init_status += Vel_kalman.init(0.0f); // 速度滤波初始化 - init_status += FOC.init(); // FOC 初始化 + if (Angle_kalman.init(KTH7823.getAngle())) { + init_error_flags |= INIT_ERR_ANG_KALMAN; // 角度滤波器初始化 + } + if (Vel_kalman.init(0.0f)) { + init_error_flags |= INIT_ERR_VEL_KALMAN; // 速度滤波器初始化 + } + if (FOC.init()) { + init_error_flags |= INIT_ERR_FOC; // FOC 初始化 + } - // 门驱动上电配置放在最后:此时 TIMER1/PWM 已启动且占空比为 0, + // 门驱动上电配置放在 FOC 校验之后:此时 TIMER1/PWM 已启动且占空比为 0, // 新板(DRV8323)在此拉高 DRVSLP 唤醒芯片并经 SPI 配置 3x PWM/增益/保护, // 随后即使任务立即输出 0 占空比,桥臂也处于"下管常开"的静止安全态 - init_status += gate_driver_bringup(); - return init_status; + if (gate_driver_bringup()) { + init_error_flags |= INIT_ERR_GATE_DRIVER; // 门驱动 bringup + } + + // 三相电流零点配平:采样前唤醒门驱动,让 0% 占空比下三相下管导通, + // PGNDx 与功率地等电位、共模与运行时一致,配平结果才可用。 + // 老板 bringup 是空操作、上电默认睡眠:此处临时唤醒(此时占空比 0= + // 三相下管导通,静止安全态),配平完恢复睡眠,保持"上电未使能"的 + // 原始安全态(模式 0 的 drv_hold 恒占空比不会真实输出);新板 + // bringup 已唤醒并保持常醒,无需额外操作。配平值超限/无采样帧 + // 分别置独立错误位。 +#if !GATE_DRIVER_DRV8323 + motor_resume(); +#endif + u8 current_cal = sampler.zero_calibrate(); +#if !GATE_DRIVER_DRV8323 + motor_sleep(); +#endif + if (current_cal == 1) { + init_error_flags |= INIT_ERR_CURRENT_NOFRAME; // 无采样帧 + } else if (current_cal == 2) { + init_error_flags |= INIT_ERR_CURRENT_BAL; // 配平值超限 + } + + return (init_error_flags != 0U) ? 1U : 0U; } diff --git a/Robot_V2/sys/config.h b/Robot_V2/sys/config.h index 18a0815..1b82363 100644 --- a/Robot_V2/sys/config.h +++ b/Robot_V2/sys/config.h @@ -102,6 +102,23 @@ constexpr u16 BOOT_VERSION_BUILD = 0; constexpr float DRV8323_CSA_GAIN_VV = 10.0f; constexpr float DRV8323_SHUNT_OHM = 0.010f; #endif + +// ===================== 相电流采样前端选择(独立于门驱动芯片) ===================== +// 1 = 低边采样板(DRV8313 + 下桥臂 10mΩ + INA240A2):采样电阻串在各相下管 +// 源极(PGNDx)与功率地之间,INA240A2(50V/V)放大后接 ADC0 注入组。 +// OUT 静态中点 = (V_REF1+V_REF2)/2 = 1.65V(REF1 接 AGND、REF2 接 A3V3, +// 由芯片内部分压取中,数据手册支持的接法)。 +// 换算 0.5V/A → 2.0 A/V,与老板外置放大链(50倍×10mΩ)数值一致, +// 电流链路单位为安培(get_current 返回 A,上报处 ×1000 转 mA)。 +// 注意:下桥臂采样电阻只有对应下管导通(INx=0)时才有相电流流过, +// ADC 采样点必须在三相下管同时导通的窗口内(见 TIMER1_CH3_COMPARE_VALUE)。 +// 0 = 老板(DRV8313 + 外置放大链):保持原标定系数与采样点。 +#define CURRENT_SENSE_INA240 1 + +#if CURRENT_SENSE_INA240 +constexpr float INA240_CSA_GAIN_VV = 50.0f; // INA240A2 = 50V/V(A1=20/A3=100/A4=200) +constexpr float INA240_SHUNT_OHM = 0.010f; // 低边采样电阻 10mΩ +#endif // 编译时间(AC6 编译器自动生成) #define APP_BUILD_DATE __DATE__ #define APP_BUILD_TIME __TIME__ @@ -167,7 +184,25 @@ constexpr u32 OTA_AVAL = 0x00000000; // 全零表示可用 constexpr u8 PP = 11; constexpr float DIR = 1.0f; // 默认方向:-1.0f 反转,1.0f 正转 constexpr u16 REDUCTION_RATIO = 25; -constexpr float CURRENT_OFFSET = -3.31f / 2.0f; // 当前电流传感器的零点偏移 +constexpr float CURRENT_OFFSET = -3.31f / 2.0f; // 电流通道零点偏移(V), +// 与 INA240 中点 1.65V / DRV8323 SO 中点 VREF/2 一致 +// 三相电流零点配平:配平值(各通道实测零偏折算电流)超过该限值视为 +// 电流检测初始化错误,随 init_status 阻止启动 +constexpr float CURRENT_ZERO_CAL_LIMIT = 0.1f; // 100mA + +// ===================== 初始化错误码(CAN STATUS 帧 InitStatus 反馈) ===================== +// hardware_init 按模块置位(可位或组合),InitStatus=0 表示全部初始化正常; +// 信号定义见 DBC v0.74 的 STATUS 帧字节 32~35 +constexpr u32 INIT_ERR_ANGLE_SENSOR = 1u << 0; // 角度传感器 KTH7823 初始化错误 +constexpr u32 INIT_ERR_ADC = 1u << 1; // 采样器/ADC 初始化错误 +constexpr u32 INIT_ERR_CAN = 1u << 2; // CAN 初始化错误 +constexpr u32 INIT_ERR_ANG_KALMAN = 1u << 3; // 角度滤波器初始化错误 +constexpr u32 INIT_ERR_VEL_KALMAN = 1u << 4; // 速度滤波器初始化错误 +constexpr u32 INIT_ERR_FOC = 1u << 5; // FOC 初始化错误 +constexpr u32 INIT_ERR_CURRENT_BAL = 1u << 6; // 电流检测:零点配平值超限(>100mA) +constexpr u32 INIT_ERR_CURRENT_NOFRAME = 1u << 7; // 电流检测:无采样帧(触发链路异常) +constexpr u32 INIT_ERR_GATE_DRIVER = 1u << 8; // 门驱动 bringup 错误 +extern volatile u32 init_error_flags; // 初始化错误位图,定义在 main.cpp constexpr float HOLD_PWM_DUTY = 0.1f; constexpr u16 ANGLESENS_OFFSET = 0; constexpr float ANGLESENS_CALIBRATION_ANGLE = 83.0f; @@ -336,8 +371,18 @@ constexpr float MOTOR_FREQUENCY = 1 / TIME_SLICE / MOTOR_PERIOD; // 频率 constexpr int TIMER1_PRESCALER = 0; // 不分频,计数器时钟为 120MHz constexpr int TIMER1_PERIOD = 2999; // 120MHz / (0+1) / (2999+1) = 40kHz计数, 中心对齐实际PWM 20kHz constexpr int TIMER1_DEFAULT_PULSE = 0; -constexpr int TIMER1_CH3_COMPARE_VALUE = - (TIMER1_PERIOD + 1) * 0.41 - 1; // PWM 周期 41% 位置触发ADC采样 +// CH3 为固定 ADC 采样触发点(TRGO=O3CPRE)。低边采样要求触发落在"三相下管 +// 同时导通"的窗口内:中心对齐 PWM0 下 INx 高(上管导通)区间以计数谷底 +// CNT=0 为中心,INx 低(下管导通)区间以计数峰顶 CNT=PERIOD 为中心,故采样 +// 点必须贴近峰顶(在线采样时代取 41% 是任意有效点,直接沿用会采到上管 +// 导通期、三相电流恒读零)。 +// 取 PERIOD-10:紧贴峰顶。中心对齐下每周期匹配两次(上升沿 2989 / 下降沿 +// 3011,仅差 22 计数),无论第二次触发被 ADC 忽略(注入转换忙)还是重启 +// 序列,帧的等效采样位置都在峰顶附近——行为确定,不依赖芯片的触发丢弃 +// 策略;配合 ADC.cpp 电流通道 13.5 周期短采样,最大占空比 ~90% 时三相样本 +// 仍全部落在下管导通窗内。占空比更高时序列末位的 W 相先落出窗口(读数 +// 趋零、U+V+W≠0),软件侧需对占空比限幅。 +constexpr int TIMER1_CH3_COMPARE_VALUE = TIMER1_PERIOD - 10; // 齿槽转矩补偿 constexpr u16 COGGING_TABLE_SIZE = 128; // 每电周期采样点数