保姆级教程:用STM32F103实现国标交流充电桩的CP信号检测(附完整代码)
保姆级教程:用STM32F103实现国标交流充电桩的CP信号检测(附完整代码)
在电动汽车普及的浪潮中,交流充电桩因其成本优势和电池友好性成为主流选择。作为嵌入式开发者,理解并实现充电控制导引(CP)信号的精准检测,是构建可靠充电系统的关键一步。本文将手把手带你用STM32F103完成从硬件设计到软件实现的完整闭环,特别适合正在开发充电桩控制模块或想深入理解电动汽车通信协议的工程师。
1. 硬件设计:搭建CP信号检测电路
CP信号的电压检测需要解决两个核心问题:宽电压范围(6V-12V)的精确采样和1kHz PWM波的稳定捕获。我们采用电阻分压+RC滤波的方案适配STM32的ADC输入范围:
[电路示意图] Vin(CP) ──┬── 10kΩ ────┬── Vout(ADC1_IN1) │ │ 4.7kΩ 100nF │ │ GND GND关键参数计算:
- 分压比:4.7k/(10k+4.7k) ≈ 0.32
- 最大输入电压:12V×0.32=3.84V < 3.3V(STM32极限)
- RC滤波截止频率:1/(2π×10k×100n)≈159Hz(有效滤除高频噪声)
注意:实际PCB布局时,ADC输入走线应远离PWM信号线,避免交叉干扰。建议使用屏蔽线连接CP信号源。
硬件材料清单:
| 元件 | 规格 | 数量 | 备注 |
|---|---|---|---|
| 电阻 | 10kΩ 1%精度 | 2 | 分压网络 |
| 电阻 | 4.7kΩ 1%精度 | 2 | 分压网络 |
| 电容 | 100nF X7R材质 | 2 | 滤波 |
| STM32F103C8 | 核心板 | 1 | 带USB转串口 |
| 示波器 | 带宽≥50MHz | 1 | 调试必备 |
2. 软件架构:状态机驱动设计
CP信号检测本质是状态识别问题,我们采用分层状态机实现:
[状态转换图] IDLE ──→ PLUGGED ──→ READY ──→ CHARGING ↑ │ │ │ └───────────┴───────────┴───────────┘对应状态定义如下:
typedef enum { CP_STATE_IDLE, // 12V 无PWM CP_STATE_PLUGGED, // 9V 有PWM CP_STATE_READY, // 6V 有PWM CP_STATE_CHARGING, // 充电中 CP_STATE_ERROR // 异常状态 } CP_StateTypeDef;状态迁移条件通过定时器中断实现50ms的检测周期:
void TIM3_IRQHandler(void) { if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) { static uint32_t stable_count = 0; float voltage = Get_CP_Voltage(); PWM_Status pwm = Check_PWM_Exist(); // 状态判断逻辑 if(voltage > 10.5f && !pwm.exist) { current_state = CP_STATE_IDLE; } else if(voltage > 7.5f && pwm.exist) { current_state = CP_STATE_PLUGGED; } else if(voltage > 4.5f && pwm.exist) { current_state = CP_STATE_READY; } TIM_ClearITPendingBit(TIM3, TIM_IT_Update); } }3. 核心算法实现
3.1 ADC采样与软件滤波
采用STM32内置的ADC1通道1进行采样,结合移动平均滤波提升稳定性:
#define SAMPLE_SIZE 16 float Get_CP_Voltage(void) { static uint16_t raw_buf[SAMPLE_SIZE]; static uint8_t index = 0; uint32_t sum = 0; // 触发单次转换 ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 更新采样缓冲区 raw_buf[index++] = ADC_GetConversionValue(ADC1); if(index >= SAMPLE_SIZE) index = 0; // 计算移动平均值 for(int i=0; i<SAMPLE_SIZE; i++) { sum += raw_buf[i]; } // 转换为实际电压 (3.3V参考电压, 12位ADC) float adc_voltage = (sum * 3.3f) / (SAMPLE_SIZE * 4095.0f); return adc_voltage / 0.32f; // 分压比补偿 }3.2 PWM捕获与占空比计算
使用TIM2的输入捕获功能检测1kHz PWM:
typedef struct { uint8_t exist; // PWM是否存在 float duty_cycle; // 占空比百分比 uint16_t freq; // 频率(Hz) } PWM_Status; PWM_Status Check_PWM_Exist(void) { PWM_Status result = {0}; if(TIM_GetCapture2(TIM2) > 0) { result.exist = 1; uint32_t ic1 = TIM_GetCapture1(TIM2); uint32_t ic2 = TIM_GetCapture2(TIM2); result.duty_cycle = (ic2 * 100.0f) / ic1; result.freq = SystemCoreClock / (ic1 * (TIM2->PSC + 1)); } return result; }定时器初始化关键配置:
void TIM2_IC_Init(void) { TIM_ICInitTypeDef TIM_ICInitStructure; // 时钟配置略... TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x04; // 适当滤波 TIM_ICInit(TIM2, &TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI; TIM_ICInit(TIM2, &TIM_ICInitStructure); TIM_SelectInputTrigger(TIM2, TIM_TS_TI1FP1); TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset); TIM_SelectMasterSlaveMode(TIM2, TIM_MasterSlaveMode_Enable); TIM_Cmd(TIM2, ENABLE); }4. 系统联调与故障处理
4.1 典型调试流程
静态电压测试:
- 断开PWM信号源,用可调电源模拟12V/9V/6V输入
- 通过串口打印
Get_CP_Voltage()返回值 - 误差应控制在±0.3V以内
动态PWM测试:
- 使用信号发生器输出1kHz PWM(占空比10%-90%)
- 验证
Check_PWM_Exist()返回的duty_cycle精度 - 要求频率误差<±5Hz,占空比误差<±2%
状态切换测试:
- 按顺序模拟:IDLE→PLUGGED→READY→CHARGING
- 用逻辑分析仪抓取K1/K2控制信号时序
- 各状态停留时间应≥2秒
4.2 常见问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| ADC读数波动大 | 分压电阻精度不足 | 更换1%精度电阻 |
| 接地不良 | 检查PCB地线布局 | |
| PWM检测不稳定 | 输入信号毛刺多 | 增加RC滤波(如1kΩ+1μF) |
| 定时器配置错误 | 检查TIM2的PSC和ARR值 | |
| 状态切换响应慢 | 滤波参数过大 | 减少移动平均的SAMPLE_SIZE |
| 中断优先级冲突 | 调整NVIC优先级分组 |
在真实项目中遇到最棘手的问题是PWM捕获时偶尔丢失上升沿,后来发现是TIM2的输入滤波值设置不当。将TIM_ICFilter从0x0F调整为0x04后,既保持了抗干扰能力,又避免了信号延迟。
5. 完整代码实现
核心模块的完整实现如下(基于STM32标准外设库):
/* cp_detector.h */ #ifndef __CP_DETECTOR_H #define __CP_DETECTOR_H #include "stm32f10x.h" typedef struct { uint8_t exist; float duty_cycle; uint16_t freq; } PWM_Status; typedef enum { CP_STATE_IDLE, CP_STATE_PLUGGED, CP_STATE_READY, CP_STATE_CHARGING, CP_STATE_ERROR } CP_StateTypeDef; void CP_Detector_Init(void); float Get_CP_Voltage(void); PWM_Status Check_PWM_Exist(void); CP_StateTypeDef Get_CP_State(void); #endif/* cp_detector.c */ #include "cp_detector.h" #include <math.h> static CP_StateTypeDef current_state = CP_STATE_IDLE; void ADC1_Init(void) { ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); } void TIM2_IC_Init(void) { // 初始化代码见前文... } void TIM3_Init(uint16_t period_ms) { // 定时器初始化代码... } void CP_Detector_Init(void) { ADC1_Init(); TIM2_IC_Init(); TIM3_Init(50); // 50ms检测周期 } // 其他函数实现见前文...实际部署时发现,在强电磁干扰环境下,单纯依靠软件滤波可能不够。后来我们增加了硬件比较器电路作为第二级保护,当检测到电压异常时立即切断接触器控制信号。
