
简介基于STM32的电动汽车交流充电桩代码是一份面向嵌入式开发者和新能源领域学习者的完整源码工程适用于交流充电桩控制系统的设计、调试、二次开发及课程教学。资源包共包含129个文件其中49个h头文件与46个c源文件占据主体另有hex固件、汇编启动文件、Keil工程配置文件等文件类型覆盖STM32定时器、ADC、CAN、USART、FSMC等常用外设驱动能够支撑充电电流与电压的控制逻辑以及充电桩和电动汽车之间的通信处理。整个压缩包约478KB结构紧凑便于快速下载与工程移植。目前已有57人学习下载。代码通过清晰的目录和模块划分将硬件驱动、通信协议与上层控制逻辑有机结合LCD显示、Flash存储等外设亦被整合进来能够帮助读者理解交流充电桩从板级初始化到功能实现的完整链路。通过学习该工程可以快速掌握STM32外设的配置方法、嵌入式C代码的编写规范以及充电桩控制系统的整体设计思路适合作为相关课程设计、毕业设计或项目初版参考。1. 基于STM32的交流充电桩代码先分清桩侧与车侧的职责交流充电桩和直流桩的最大区别是桩不做整流更不管理电池的充电策略。真正决定充电电流的是车载充电机桩侧要做的只是把电网侧的能力通过一根 CP 信号线告诉车然后等车确认状态、闭合继电器送电。所以基于 STM32 的交流充电桩代码核心不在“充电”两个字上而在连接确认、状态机迁移和保护逻辑上。这也是这类项目作为基于 STM32 的毕业设计课题时初学者最常跑偏的地方。下面按实际开发顺序把协议、代码框架、功能模块和调试手段一次讲透。2. 交流充电桩CP信号协议状态判定与PWM占空比换算2.1 桩侧与车侧的职责边界充电桩与电动汽车之间靠 CPControl Pilot和 CCConnection Confirm两根信号线建立联系。CC 线用电阻编码告诉桩当前插的是 16A 还是 32A 的枪头CP 线则承载了连接状态和 PWM 占空比指令。桩侧代码要做的两件事是输出 1kHz 的 CP PWM 信号以及采集 CP 线上的直流电压来识别车辆状态。这两个任务一个输出一个输入配合状态机就能覆盖从“插枪”到“充满断电”的全流程。状态切换的顺序是固定的A 状态代表未连接插枪后因为车侧电阻网络的下拉桩侧检测到约 9V进入 B 状态车身同意充电后 S2 开关闭合电压继续降到约 6V进入 C 状态。代码上不需要考虑车为什么这么做只需要按标准给出正确的电压门限和迁移条件。2.2 占空比与充电电流换算表CP 信号的 PWM 频率固定为 1kHz频率偏差要在 ±0.5% 以内否则部分车载充电机会拒绝充电。占空比在 10% 到 85% 之间时充电电流按公式计算电流值 占空比百分比 × 0.6A占空比桩侧告知的最大电流适用场景10%6A慢速充电、临时补电16%9.6A家用 220V/10A 插座26%15.6A常见 16A 交流桩50%30A商用 32A 充电桩85%51A三相交流桩大功率代码实现时建议把“占空比”按千分比传递因为 50% 和 50.5% 这种差异在整数百分比下表达不了。我一般用一个uint16_t duty_x10表示占空比乘以 10例如 500 表示 50.0%换算成定时器比较寄存器时再乘上自动重装载值避免中途使用浮点数。2.3 CP电压状态判定与阈值设计CP 线上的直流电平对应不同的车辆状态代码需要周期采样并映射到状态机。临界点不能只取一个固定值要留滞回区间否则插枪瞬间的接触抖动会造成状态来回跳。状态标准电压判定区间(毫伏)含义A12V10500 ~ 12500未连接B9V8500 ~ 9600已连接未充电C6V5500 ~ 6600车辆已就绪可充电D3V2500 ~ 3500需要通风或电加热F0V小于 1000故障例如从 B 状态进入 C 状态判定阈值取 7600mV但反过来从 C 回到 B阈值取 7000mV让两个方向使用不同门限天然消除边沿抖动。这个滞回用一组宏定义放在头文件里就行调试时改起来最快。2.4 用STM32定时器输出1kHz CP PWM以常见的 84MHz 系统时钟为例用定时器 TIM1 的通道 1 输出 PWM分频后得到一个 1MHz 的计数时钟自动重装载值设为 999输出频率就是 1kHz。换用不同主频时只要把分频系数重算一次。void cp_pwm_init(void) { htim1.Instance TIM1; htim1.Init.Prescaler 83; // 84MHz / 84 1MHz htim1.Init.Period 999; // 1MHz / 1000 1kHz htim1.Init.CounterMode TIM_COUNTERMODE_UP; htim1.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(htim1); TIM_OC_InitTypeDef oc {0}; oc.OCMode TIM_OCMODE_PWM1; oc.Pulse 500; // 先给 50% 占空比 oc.OCPolarity TIM_OCPOLARITY_HIGH; HAL_TIM_PWM_ConfigChannel(htim1, oc, TIM_CHANNEL_1); } void cp_pwm_set_duty(uint16_t duty_x10) { uint32_t pulse (uint32_t)htim1.Init.Period * duty_x10 / 1000; __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, (uint16_t)pulse); } void cp_pwm_start(void) { HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); }cp_pwm_set_duty(500)对应 50.0% 占空比写入比较寄存器的值是Period × 500 / 1000 499正脉宽约 500μs。注意 STM32 引脚输出的 3.3V PWM 不能直接驱动 CP 线外部必须加电平转换电路把 PWM 幅度抬到 12V 和 -12V 之间切换引脚只负责输出逻辑电平。2.5 ADC采样与软件滤波CP 线上叠加了 PWM 分量直接用一次 ADC 读数来判断状态不靠谱。常见做法是加一级 RC 低通滤波同时代码里做滑动平均。#define ADC_SAMPLE_NUM 8 uint16_t cp_voltage_mv(void) { static uint32_t buf[ADC_SAMPLE_NUM]; static uint8_t idx 0; static uint32_t sum 0; sum - buf[idx]; buf[idx] adc_read_mv(CP_ADC_CH); sum buf[idx]; idx (idx 1) % ADC_SAMPLE_NUM; return (uint16_t)(sum / ADC_SAMPLE_NUM); }adc_read_mv返回的是换算成毫伏的采样值用 ADC 通道对应的满量程电压除以 4096 得到每 LSB 的毫伏数。滑动窗口长度为 8 时CP 电压的抖动会被明显压低而状态切换的响应延迟仍然控制在几毫秒内不会导致继电器动作变慢。3. STM32充电桩代码框架裸机任务调度与状态机实现3.1 前后台还是RTOS交流桩的逻辑并不复杂CP 检测、继电器控制、计量芯片读取、按键和屏幕显示。这些任务对实时性的要求都只在几十毫秒级用裸机前后台结构完全够不必要引入 RTOS。引入 RTOS 会带来任务栈分配、优先级反转等问题对简单的状态机反而是负担。我习惯的做法是一个 1ms 的 SysTick 中断维护系统时基主循环轮询各个任务的到期时间到点就执行对应函数。每个功能模块都是独立的.c文件模块之间通过函数接口通信不共用全局变量这样单独测试每个模块也很方便。3.2 代码文件怎么划分工程目录按功能拆分而不是按 STM32 外设拆分。cp.c负责 CP/CC 状态判定relay.c负责继电器和漏保反馈meter.c封装电表 Modbus 协议ui.c处理按键和屏幕。每个文件只对外暴露两三个函数接口。app/ main.c // 主循环和任务调度 cp.c / cp.h // 状态识别与 PWM 占空比控制 relay.c / relay.h // 继电器动作与反馈检测 meter.c / meter.h // 电表数据读取 ui.c / ui.h // 按键扫描与屏幕刷新 board_config.h // 引脚映射与参数配置board_config.h集中放 GPIO 引脚和延时参数比如 CP 检测通道、继电器引脚、默认占空比、状态切换延时等。换一块板子时只需要改这个文件不用在每个模块里找宏定义。3.3 主循环任务调度任务调度用“时间片轮询”实现主循环里每个任务都记录上一次执行的时间当前时基减去上一次记录值超过任务周期就执行一次。uint32_t tick; void main_loop(void) { static uint32_t t_cp 0, t_relay 0, t_ui 0; while (1) { tick get_tick_1ms(); if (tick - t_cp 10) { // 10msCP 状态检测 cp_poll(); t_cp tick; } if (tick - t_relay 100) { // 100ms继电器反馈防抖 relay_poll(); t_relay tick; } if (tick - t_ui 200) { // 200ms屏幕数据刷新 ui_update(); t_ui tick; } } }为什么是 10ms 和 100ms 两个周期CP 状态直接决定继电器是否闭合响应时间必须短但继电器辅助触点存在机械抖动采样太快反而容易读到不确定电平所以反馈检测放在 100ms 周期里做软件防抖。屏幕内容变化不需要高刷新率200ms 足够而且能降低 MCU 负载和屏幕功耗。3.4 状态机实现充电流程充电状态机是整份代码最主要的部分用枚举类型定义状态用 switch-case 实现状态迁移不推荐用 if-else 写大段嵌套逻辑很难查。typedef enum { CHARGE_IDLE, // A 状态等待插枪 CHARGE_CONNECTED, // B 状态已插枪PWM 已输出 CHARGE_CHARGING, // C 状态继电器闭合正在充电 CHARGE_FAULT // 故障 } charge_state_t; void cp_poll(void) { static charge_state_t state CHARGE_IDLE; uint8_t cp_state cp_state_get(); switch (state) { case CHARGE_IDLE: if (cp_state CP_STATE_B) { cp_pwm_set_duty(charge_current_duty()); // 按设定电流换算 cp_pwm_start(); state CHARGE_CONNECTED; } break; case CHARGE_CONNECTED: if (cp_state CP_STATE_C) { relay_close(); state CHARGE_CHARGING; } if (cp_state CP_STATE_A) { // 拔枪后回到空闲 cp_pwm_stop(); state CHARGE_IDLE; } break; case CHARGE_CHARGING: if (cp_state ! CP_STATE_C) { // S2 断开或插头松动 relay_open(); cp_pwm_stop(); state (cp_state CP_STATE_B) ? CHARGE_CONNECTED : CHARGE_IDLE; } break; default: relay_open(); state CHARGE_IDLE; break; } }CHARGE_CONNECTED状态下如果直接拔枪CP 会瞬间从 9V 跳到 12V这个状态机在 10ms 内就能做出 PWM 停止的反应。实际充电过程中按急停按钮、检测到漏电等事件也会要求断开继电器这些事件不走 CP 状态枚举而是通过故障标志位让relay_open在任意状态下都能被调用。4. 充电桩代码进阶功能继电器控制、Modbus计量与人机交互4.1 继电器闭合时序与反馈检测继电器不能检测到 C 状态就立刻闭合那样在车辆继电器和桩侧继电器同时动作时触点容易发生抖动拉弧。常见做法是延迟 1 秒再闭合给车载充电机留出软启动时间。void relay_close(void) { if (relay_closed()) return; // 已闭合则跳过 HAL_GPIO_WritePin(RELAY_OUT_PORT, RELAY_OUT_PIN, GPIO_PIN_SET); relay_close_tick get_tick_1ms(); }闭合之后读取辅助触点反馈引脚电平变化后要连读三次保持一致才算有效否则判定继电器操作失败上报故障。如果辅助触点反馈的是常开信号则闭合后引脚必须变为高电平高电平没出现可能是继电器线圈断线也可能是驱动三极管烧了排查方向完全不同所以反馈检测不能省。4.2 漏电保护识别与故障闭锁漏电保护器动作后不会自动恢复需要人工排除故障后复位。代码上要区分“当前有漏电”和“曾经发生过漏电且未复位”两种状态后者要一直锁存故障码直到操作人员按键确认。static uint8_t fault_latch 0; void fault_clear(void) { if (fault_latch relay_open_ok()) { fault_latch 0; // 手动解除闭锁 } } void relay_poll(void) { if (HAL_GPIO_ReadPin(LEAK_PORT, LEAK_PIN) GPIO_PIN_RESET) { fault_latch 1; relay_open(); // 无论当前在哪个状态 ui_show_fault(FAULT_LEAK); } }漏保反馈引脚通常接到漏保的辅助触点上动作时电平翻转。有些方案在漏电瞬间通过硬件直接切断继电器MCU 只负责记录故障原因这种方式更安全代码上也更可控。软件闭锁的思路是上电后如果检测到漏保已断开禁止进入充电流程除非故障清除条件满足。4.3 Modbus RTU读取电表数据交流桩通常外接电能表计量充电电量电表一般支持 Modbus RTU 协议通过 RS485 与 STM32 通信。RS485 是半双工总线发送时要把 DE 引脚拉高发送完成后延时等待一帧结束再拉低。uint16_t crc16_modbus(uint8_t *data, uint8_t len) { uint16_t crc 0xFFFF; for (uint8_t i 0; i len; i) { crc ^ data[i]; for (uint8_t j 0; j 8; j) { crc (crc 1) ? (crc 1) ^ 0xA001 : (crc 1); } } return crc; } void modbus_read_reg(uint8_t addr, uint16_t reg, uint8_t count, uint8_t *rx) { uint8_t frame[8]; frame[0] addr; // 从机地址 frame[1] 0x03; // 读保持寄存器 frame[2] reg 8; frame[3] reg 0xFF; frame[4] 0; frame[5] count; uint16_t crc crc16_modbus(frame, 6); frame[6] crc 0xFF; // CRC 低字节在前 frame[7] crc 8; HAL_GPIO_WritePin(DE_PORT, DE_PIN, GPIO_PIN_SET); HAL_UART_Transmit(huart2, frame, 8, 100); HAL_GPIO_WritePin(DE_PORT, DE_PIN, GPIO_PIN_RESET); HAL_UART_Receive(huart2, rx, 3 2 * count 2, 500); }CRC 放在帧末尾且低字节在前这是 Modbus RTU 固定格式写错顺序从机会一直返回异常码。读取电压时常用两个连续寄存器返回的数据是 32 位无符号整数实际值是原始值除以 10 或 100具体取决于电表厂商的精度定义这个要在代码里用条件编译处理不能统一写死。4.4 按键消抖与屏幕数据刷新充电桩的面板一般只有启动、停止和故障复位三个按键。按键扫描放在 20ms 周期里连续两次读到同一种电平才算有效从根源上消除机械抖动。void ui_key_scan(void) { static uint8_t cnt 0; static uint8_t last KEY_RELEASED; uint8_t now HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN); cnt (now last) ? cnt 1 : 0; if (cnt 3) { // 60ms 稳定 if (now KEY_PRESSED last KEY_PRESSED) { charge_start(); // 短按开始充电 } last now; } }屏幕刷新采用“缓存区 差值刷新”方式不需要每帧全量重绘。充电中显示的数据包括实时电压、电流、当前已充电量和状态栏文字只有数值变化时才更新对应区域避免屏幕闪烁。5. 调试交流充电桩代码CP波形验证与典型故障定位5.1 搭建最小调试台调试交流桩代码并不需要一辆真车。用几个电阻和一个开关就能模拟车侧的 CP 负载原理是 CP 线对 PE 之间的等效电阻网络。B 状态用 2.74kΩ 电阻模拟C 状态再并一个 1.3kΩ 电阻和开关。把逻辑分析仪的 CH1 接到 CP 线上CH2 接到继电器驱动引脚。插上调试工装后CP 波形应迅速从 12V 直流变成 1kHz 的 PWM 方波占空比和设定的充电电流一致。闭合模拟 S2 的开关CH2 应该在 1 秒后出现高电平表示继电器闭合。5.2 CP波形判断要点观察逻辑分析仪截图时先看频率再看作空比。PWM 频率用光标量两个上升沿间距标准值是 1ms 即 1kHz。占空比误差超过 2% 就可能导致车载充电机拒绝充电所以定时器分频值一定要按实际时钟频率计算不能照抄别的板子配置。再注意 CP 电平切换是否干净。正常 PWM 应该从 12V 切到 -12V边沿时间应小于几微秒。如果看到明显圆角或幅值偏低说明外部电平转换电路驱动能力不足大概率需要换 MOS 管或增大栅极驱动电流。5.3 典型故障排查方向故障现象优先检查项排查方法插枪无反应CP 供电、CC 检测电阻万用表量 CP 静态电压是否 12VPWM 无输出定时器主输出使能引脚直接量波形排除外部电路状态跳变不停ADC 阈值滞回区间看电压是否落在两个状态阈值附近继电器不闭合检测到 C 后 1 秒延时确认 CP 电压稳定在 5.5~6.6V计量数据不对Modbus 寄存器地址用调试工具发 03H 指令对比返回帧拔枪后仍充电S2 状态未反向检测确认状态机在 C 状态时也持续读 CP5.4 样机安全验证清单代码能跑通状态机之后要做几项不依赖整车就能完成的验证用可调电阻模拟 CP 电压在状态下边界和上边界时的跳变情况确认不会误动作继电器闭合后人为断开 CP 线确认 10ms 内继电器能断开漏保测试按钮按下后确认故障码锁存复位按键才能解除。最后用示波器记录一次完整的“插枪-充电-拔枪”过程波形确认 CP PWM 在继电器的动作时序前后没有毛刺。本文还有配套的精品资源点击获取