/** * bldc_bemf.c — 反电动势过零检测实现 * * 端电压比较法: * 1. 三相端电压经 ADC 采样 * 2. 软件计算虚拟中点 V_mid = (V_U + V_V + V_W) / 3 * 3. 悬空相电压与中点比较, 符号变化即为过零 * 4. 3点滑动平均滤波去噪 * * ADC 采样时序: * TIM1 中心对齐模式, 在 PWM 周期中点 (CNT=ARR/2) 触发 ADC 注入组 * 此时下管导通, 开关噪声最小, 采样值稳定 */ #include "bldc_bemf.h" #include "bldc_comm.h" /* 内联 abs 避免 stdlib 依赖 */ #define ABS(x) ((x) < 0 ? -(x) : (x)) /* ==================== 全局变量 (在 bldc_hw.h 中 extern 声明) ==================== */ volatile uint16_t g_adc_raw[ADC_INJ_CH_COUNT] = {0}; volatile uint16_t g_adc_filtered[3] = {0}; volatile uint16_t g_bemf_samples[3][BEMF_FILTER_TAPS] = {{0}}; volatile uint8_t g_bemf_idx[3] = {0}; /* 过零检测内部状态 */ static int16_t s_last_bemf_diff[3] = {0, 0, 0}; /* 上次悬空相与中点差值 */ static uint8_t s_valid_zc_count = 0; /* 连续有效过零计数 */ static uint8_t s_last_zc_phase = 0xFF; /* 上次过零相位, 防止重复计数 */ /* ==================== 初始化 ==================== */ void BEMF_Init(void) { for (int i = 0; i < 3; i++) { g_bemf_idx[i] = 0; s_last_bemf_diff[i] = 0; for (int j = 0; j < BEMF_FILTER_TAPS; j++) { g_bemf_samples[i][j] = 0; g_adc_filtered[i] = 0; } } s_valid_zc_count = 0; s_last_zc_phase = 0xFF; } /* ==================== 滑动平均滤波 ==================== */ /* 对三相各自维护 BEMF_FILTER_TAPS=3 的滑动窗口 */ void BEMF_UpdateFilter(void) { for (int phase = 0; phase < 3; phase++) { uint8_t idx = g_bemf_idx[phase]; g_bemf_samples[phase][idx] = g_adc_raw[phase]; uint32_t sum = 0; for (int j = 0; j < BEMF_FILTER_TAPS; j++) { sum += g_bemf_samples[phase][j]; } g_adc_filtered[phase] = (uint16_t)(sum / BEMF_FILTER_TAPS); if (++idx >= BEMF_FILTER_TAPS) idx = 0; g_bemf_idx[phase] = idx; } } /* ==================== 过零检测 ==================== */ /* * 检测原理: * 悬空相的端电压 V_bemf 在 PWM-ON 调制下呈现中性点电压 ± BEMF 的形状 * 过零点 = V_bemf 穿越 V_mid (虚拟中点) 的时刻 * * 判断: diff = V_bemf - V_mid * 正半周: diff > 0 * 负半周: diff < 0 * 过零: diff 符号发生变化 * * 为避免噪声误触发, 加死区阈值 (ADC 码值, ~20mV): * |diff| < BEMF_DEADBAND 认为未穿过 */ #define BEMF_DEADBAND 8 /* ADC 码值死区, 对应 ~6.4mV (3300/4096 ≈ 0.8mV/LSB, 8 × 0.8 = 6.4mV) */ /* 过零后延迟计数 — 用于 30° 延迟换相 */ /* 在 TIM1 中断中以 get_zc_delay_count() 获取延迟 PWM 周期数 */ static uint16_t s_zc_delay_pwm = 0; /* 需延迟的 PWM 周期计数 */ static uint16_t s_zc_delay_cnt = 0; /* 当前延迟计数器 */ /* 设置延迟时间 (基于上次换相间隔) */ /* delay_count = T_comm / 2 / T_pwm = comm_interval_pwm / 2 */ __STATIC_INLINE void BEMF_SetDelayCount(uint16_t comm_interval_pwm) { s_zc_delay_pwm = comm_interval_pwm >> 1; /* 30° = 半个换相周期 */ if (s_zc_delay_pwm < 2) s_zc_delay_pwm = 2; /* 最小延迟 2 个 PWM 周期 */ s_zc_delay_cnt = 0; } /* 延迟计数器递增, 返回 1 = 延迟时间到, 可以换相 */ __STATIC_INLINE uint8_t BEMF_DelayTick(void) { if (s_zc_delay_cnt < s_zc_delay_pwm) { s_zc_delay_cnt++; return 0; } return 1; } /* ==================== 过零检测核心逻辑 ==================== */ /* * 在 TIM1_UP_IRQHandler 中调用, 每 PWM 周期执行一次 * 返回 1: 检测到过零且通过了有效性检查 */ uint8_t BEMF_DetectZeroCross(uint32_t current_step) { /* 1. 确定悬空相 */ uint8_t float_phase = COMM_GetFloatingPhase(current_step); /* 2. 获取悬空相的滤波后 ADC 值 */ uint16_t bemf_val = g_adc_filtered[float_phase]; /* 3. 计算虚拟中点 */ uint16_t v_mid = BEMF_CalcMidpoint(); /* 4. 计算差值 (有符号) */ int16_t diff = (int16_t)bemf_val - (int16_t)v_mid; /* 5. 判断符号变化 (过零) */ int16_t prev_diff = s_last_bemf_diff[float_phase]; uint8_t zc = 0; if ((prev_diff > 0 && diff < 0) || (prev_diff < 0 && diff > 0)) { /* 符号变化, 但需确认幅度超过死区 */ if (ABS(diff) > BEMF_DEADBAND) { zc = 1; } } /* 更新上次差值 */ s_last_bemf_diff[float_phase] = diff; /* 6. 过零有效性检查 */ if (zc) { /* 防止同一电周期内重复计: 连续两次过零不能在同一相 */ if (float_phase != s_last_zc_phase) { s_valid_zc_count++; s_last_zc_phase = float_phase; /* 根据换相间隔设置 30° 延迟时间 */ /* comm_interval_pwm 在换相时更新, 此处直接用全局值 */ BEMF_SetDelayCount(g_comm_interval_pwm); return 1; /* 有效过零 */ } } return 0; /* 无过零 */ } /* ==================== 连续过零计数管理 ==================== */ void BEMF_ClearZcCount(void) { s_valid_zc_count = 0; s_last_zc_phase = 0xFF; } uint8_t BEMF_GetValidZcCount(void) { return s_valid_zc_count; } /* ==================== 30° 延迟查询接口 (换相时调用) ==================== */ /* 主中断中每 PWM 周期调用延迟 tick, 然后查询是否可换相 */ uint8_t BEMF_IsDelayDone(void) { return BEMF_DelayTick(); }