简介:这份资源面向伺服控制、电机驱动及自动化方向的工程师与爱好者,提供EP100伺服系统的完整工程资料,帮助读者从电路原理到嵌入式代码全面理解伺服系统的设计与实现。压缩包共352个文件,约41.9MB,涵盖原理图与PCB设计文档、C语言源码与头文件、Keil工程配置、编译中间文件及说明书PDF等,其中原理图展示驱动板、控制板与显示板的电源管理、信号处理、电机驱动和反馈电路布局,PCB工程文件可用于实际制板,C代码则包含PID控制器、电机控制算法、通信协议与故障保护机制等核心逻辑。已有1275人学习下载,适合希望掌握硬件设计、软件编程与系统集成全流程的读者,也可作为伺服控制、机器人技术等领域的实践参考。
1. EP100 伺服系统全套工程包:从原理图到 C 代码的完整闭环
如果你正在找一套能直接上手拆解的伺服驱动方案,EP100 这套资源大概率能满足你。它不是零散的 demo,而是把原理图、PCB 和源代码打包在一起的完整工程。我拿到手第一反应是:终于不用在某个论坛翻十几页找配套文件了。伺服驱动本身涉及功率变换、电流采样、位置环控制、通信协议,单看某一层都容易卡住,而这套东西把硬件和软件放在同一个目录里,你可以对着原理图去源码里找对应的外设初始化,也能反过来从代码里的 PWM 配置去反推功率级的驱动逻辑。适合谁?做电机控制的嵌入式工程师、想从方波驱动过渡到 FOC 的开发者,以及需要一套参考设计来改自己板子的硬件同学。下面按「能干什么 → 怎么跑 → 怎么改」的顺序拆。
2. 硬件与软件架构:EP100 的模块划分和信号链路
2.1 从原理图看功率级与控制级的边界
打开原理图第一件事不是看芯片型号,而是找隔离带。EP100 的硬件设计里,功率级和控制级分得很清楚:强电侧是整流、母线电容、IPM 或分立 MOS 构成的逆变桥,弱电侧是主控 MCU、电流采样运放、编码器接口和通信收发器。常见做法是用光耦或数字隔离器把 PWM 信号和故障反馈隔开,EP100 的原理图里也能看到类似的隔离器件。你拿到图之后,先沿着「MCU PWM 输出 → 隔离 → 栅极驱动 → 逆变桥 → 电机」这条链路走一遍,再沿着「电流采样 → 运放调理 → ADC 引脚」走一遍,基本就能把功率板和控制板的接口对上。
这里有个容易忽略的点:母线电压采样和相电流采样的分压比、放大倍数,直接决定了源代码里 ADC 换算的系数。如果你只改硬件不改代码,或者只改代码不看硬件,电流环大概率会震荡或者直接报过流。我一般会先把原理图里的采样电阻值、运放增益记下来,再去源码里找对应的宏定义,两边对不上就先停下来查。
2.2 源代码目录结构与主控外设映射
EP100 的源代码通常是按功能分目录的,常见结构如下:
EP100_Servo/ ├── Drivers/ │ ├── MCU/ # 主控外设驱动:PWM、ADC、TIM、UART │ └── Board/ # 板级支持:GPIO 映射、LED、继电器 ├── Middlewares/ │ ├── FOC/ # 磁场定向控制:Clarke、Park、SVPWM │ └── Encoder/ # 编码器解码:增量式或绝对值 ├── App/ │ ├── Control/ # 电流环、速度环、位置环 │ ├── Comm/ # 通信协议:Modbus、CANopen 或自定义 │ └── Protect/ # 过流、过压、欠压、过热保护 └── Main/ └── main.c # 初始化与主循环这个结构不是固定的,但 EP100 的代码基本能按这个思路去拆。你拿到源码后,先看Drivers/MCU里的 PWM 初始化,确认载波频率和死区时间;再看Middlewares/FOC里的 SVPWM 实现,确认扇区判断和占空比计算;最后看App/Control里的 PID 参数,确认电流环和速度环的带宽。这三步走完,整个系统的控制链路就清楚了。
2.3 关键参数表:硬件与代码的对应关系
| 硬件参数 | 原理图位置 | 代码对应 | 常见值 |
|---|---|---|---|
| PWM 载波频率 | 主控 TIM 配置 | PWM_FREQ宏 | 10kHz~20kHz |
| 死区时间 | 栅极驱动电路 | DEAD_TIME宏 | 0.5us~2us |
| 电流采样电阻 | 逆变桥下桥臂 | SHUNT_RES宏 | 5mΩ~50mΩ |
| 运放增益 | 电流调理电路 | AMP_GAIN宏 | 5~20 倍 |
| 编码器线数 | 编码器接口 | ENCODER_LINES宏 | 2500 线 |
| 母线电压分压比 | 母线采样电路 | VDC_DIV宏 | 100:1~200:1 |
这张表建议你拿到资源后自己填一遍,填不出来的地方就是你需要重点看的地方。很多调试翻车不是因为算法难,而是因为采样系数写错了,电流环一跑就飞。
3. 编译与烧录:把 EP100 源码跑起来的完整步骤
3.1 开发环境搭建与工程导入
EP100 的源码大概率是基于 Keil MDK 或者 IAR 的工程,也有可能是 STM32CubeIDE 或 Makefile 工程。先看根目录有没有.uvprojx、.eww或者Makefile。如果是 Keil 工程,直接双击打开,检查一下器件型号和下载器配置。常见坑是工程里选的芯片型号和你手上的板子不一致,比如工程选的是 STM32F103VET6,你板子上是 STM32F103C8T6,编译能过但烧录后外设映射全错。
我一般会先做三件事:第一,确认工程里的器件型号和实际板子一致;第二,确认下载器配置(ST-Link 还是 J-Link)和复位方式;第三,确认编译输出的 hex 或 bin 文件大小没有超出 Flash 容量。这三步做完再点编译,能省掉很多「编译过了但跑不起来」的玄学问题。
# 如果工程是 Makefile 结构,先清理再编译 make clean make -j4 # 查看生成的固件大小 arm-none-eabi-size build/EP100_Servo.elf上面这段命令适用于 Makefile 工程。make clean清掉旧的目标文件,make -j4用四核并行编译加快速度,最后用arm-none-eabi-size看 text、data、bss 段的大小。如果 text 段接近 Flash 容量上限,就要考虑裁剪功能或者换更大 Flash 的芯片。
3.2 烧录与上电前的硬件检查
烧录之前,先别急着插电机。我习惯先用万用表量一下母线电容两端的电阻,确认没有短路;再量一下 IPM 或 MOS 的栅极和源极之间有没有击穿;最后确认编码器接口的电源和地没有接反。这些检查花不了两分钟,但能避免上电瞬间炸管。
烧录时如果用的是 ST-Link,注意接线:SWDIO、SWCLK、GND、3.3V 四根线就够了,但有些板子需要把 BOOT0 拉低才能从 Flash 启动。烧录完成后,先不接电机,上电看主控能不能正常跑起来——LED 闪烁、串口有输出、示波器能看到 PWM 波形,这三样至少要有一样。
// main.c 里常见的初始化顺序 int main(void) { HAL_Init(); // HAL 库初始化 SystemClock_Config(); // 时钟树配置,确认主频 MX_GPIO_Init(); // GPIO 初始化 MX_ADC_Init(); // ADC 初始化,校准 MX_TIM_PWM_Init(); // PWM 初始化,设置载波和死区 MX_ENCODER_Init(); // 编码器接口初始化 MX_UART_Init(); // 通信初始化 FOC_Init(); // FOC 参数初始化 while (1) { FOC_Loop(); // 主控制循环 Comm_Handler(); // 通信处理 Protect_Check(); // 保护检测 } }这段初始化顺序是有讲究的:时钟配置必须在最前面,否则后面外设的时钟频率全错;ADC 校准要在 PWM 启动之前做,否则开关噪声会干扰校准结果;FOC 初始化要在所有外设就绪之后,因为它依赖 ADC 和编码器的数据。如果你把FOC_Init()放到MX_ADC_Init()前面,大概率会读到全零的电流值。
3.3 首次上电与电机参数识别
第一次接电机,建议先用手转一下电机轴,看编码器读数有没有变化。如果编码器读数不变,先查编码器接线和供电;如果读数跳变,查一下编码器线数和代码里的ENCODER_LINES是否一致。确认编码器正常后,再给一个很小的电流指令,看电机能不能轻微转动。这一步是为了验证电流环的方向和增益,如果电机抖动或者飞车,立刻断电检查相序和电流采样极性。
常见做法是先做电阻和电感辨识,再做编码器零位校准。EP100 的代码里如果有Motor_Identify()之类的函数,直接调用;如果没有,就手动给 d 轴电流、q 轴置零,让电机锁定在一个位置,然后读编码器值作为零位。这个过程可能需要反复几次,直到零位稳定。
4. 核心代码拆解:FOC 电流环与 SVPWM 实现
4.1 Clarke 与 Park 变换的代码落地
FOC 的核心是把三相电流变成两个直流量:d 轴和 q 轴。EP100 的代码里通常会有Clarke()和Park()两个函数。Clarke 变换把 ia、ib、ic 变成 iα、iβ,Park 变换再根据电角度把 iα、iβ 变成 id、iq。
// Clarke 变换:三相转两相 void Clarke(float ia, float ib, float ic, float *ialpha, float *ibeta) { // 假设 ia + ib + ic = 0,可以简化计算 *ialpha = ia; *ibeta = (ia + 2.0f * ib) * ONE_BY_SQRT3; // 1/√3 } // Park 变换:静止坐标系转旋转坐标系 void Park(float ialpha, float ibeta, float theta, float *id, float *iq) { float sin_t = sinf(theta); float cos_t = cosf(theta); *id = ialpha * cos_t + ibeta * sin_t; *iq = -ialpha * sin_t + ibeta * cos_t; }ONE_BY_SQRT3是 1/√3 的宏定义,约等于 0.57735。Clarke 变换里用到了三相电流和为零的假设,如果采样有偏差,这个假设不成立,iα 和 iβ 会有误差。Park 变换里的theta是电角度,不是机械角度,电角度 = 机械角度 × 极对数。如果你把机械角度直接传进去,电机转起来会一顿一顿的。
4.2 SVPWM 扇区判断与占空比计算
SVPWM 的目标是根据 ud、uq 算出三个半桥的占空比。EP100 的代码里一般会先算合成电压矢量的幅值和角度,再判断扇区,最后算作用时间。
// SVPWM 扇区判断与占空比计算 void SVPWM(float ud, float uq, float theta, float *duty_a, float *duty_b, float *duty_c) { // 反 Park 变换得到 alpha-beta 电压 float ualpha = ud * cosf(theta) - uq * sinf(theta); float ubeta = ud * sinf(theta) + uq * cosf(theta); // 扇区判断 int sector = 0; if (ubeta > 0) sector |= 0x01; if ((ubeta * ONE_BY_SQRT3 + ualpha * 0.5f) > 0) sector |= 0x02; if ((-ubeta * ONE_BY_SQRT3 + ualpha * 0.5f) > 0) sector |= 0x04; // 根据扇区计算基本矢量作用时间 float t1, t2; switch (sector) { case 1: t1 = ubeta; t2 = ualpha * 0.5f - ubeta * ONE_BY_SQRT3; break; case 2: t1 = ualpha * 0.5f + ubeta * ONE_BY_SQRT3; t2 = -ubeta; break; // ... 其他扇区 } // 计算占空比,注意死区补偿 *duty_a = (t1 + t2) * 0.5f; *duty_b = (*duty_a) - t1; *duty_c = (*duty_b) - t2; }扇区判断用的是三个条件,每个条件对应一个二进制位,组合起来就是 1 到 6 六个扇区。t1和t2是两个相邻基本矢量的作用时间,算完之后再分配给三个半桥。这里有个细节:占空比计算完之后要做限幅,防止超过 PWM 周期。另外死区补偿也很关键,死区时间设得太大,低速时电流波形会畸变;设得太小,上下桥臂可能直通。
4.3 电流环 PID 的参数整定与限幅
电流环是 FOC 里响应最快的环,通常放在 PWM 中断里执行,频率和载波频率一致。EP100 的代码里电流环 PID 一般长这样:
// 电流环 PID 控制器 typedef struct { float kp; float ki; float integral; float out_max; float out_min; } PID_Handle; float PID_Calc(PID_Handle *pid, float error) { // 比例项 float p_term = pid->kp * error; // 积分项,带限幅 pid->integral += pid->ki * error; if (pid->integral > pid->out_max) pid->integral = pid->out_max; if (pid->integral < pid->out_min) pid->integral = pid->out_min; // 输出限幅 float output = p_term + pid->integral; if (output > pid->out_max) output = pid->out_max; if (output < pid->out_min) output = pid->out_min; return output; }kp决定响应速度,ki决定消除静差的速度。整定时先把ki置零,慢慢加kp直到电流波形开始震荡,然后退回一点;再加ki,直到静差消除但超调不大。out_max和out_min是输出限幅,一般对应最大电压矢量幅值。积分限幅和输出限幅都要做,否则积分饱和后恢复很慢,电机响应会拖泥带水。
5. 避坑与排查:EP100 调试中最容易翻车的五个点
5.1 上电就过流保护
现象:一上电,还没给指令,过流保护就触发,或者母线电流直接冲上去。
原因:最常见的是电流采样极性反了,或者 ADC 偏移没校准。也有可能是 PWM 死区太小,上下桥臂直通。
解决:先不接电机,用示波器看三相下桥臂的电流采样波形,确认静态时是零附近。如果偏了,重新校准 ADC 偏移。然后量一下 PWM 死区时间,确保大于栅极驱动的最小死区要求。最后检查相序,如果电机线接反了,电流环会正反馈。
5.2 电机抖动不转或者飞车
现象:给指令后电机抖动,或者直接飞车不受控。
原因:编码器方向反了,或者电角度零位不对。也有可能是极对数设错。
解决:先用手转电机,看编码器读数是增加还是减少,和代码里的方向定义对比。如果反了,在代码里取反或者调换编码器 A/B 相。然后做零位校准,给 d 轴电流锁定电机,读编码器值作为零位。极对数在电机铭牌上有,没有的话用示波器看反电动势波形,数一个电周期里的机械周期数。
5.3 电流波形畸变严重
现象:示波器看相电流,波形不是正弦,有明显的平顶或者毛刺。
原因:死区时间太大,或者电流采样时刻不对。也有可能是 SVPWM 的扇区判断有误。
解决:先减小死区时间,看波形有没有改善。然后检查 ADC 采样时刻,常见做法是在 PWM 中心对齐模式下,在计数器为零的时候触发 ADC 采样,这时候开关噪声最小。如果扇区判断错了,电流波形会周期性畸变,检查扇区判断的三个条件有没有写反。
5.4 通信丢包或者响应慢
现象:上位机发指令,驱动器偶尔不响应,或者响应延迟很大。
原因:通信中断优先级太低,被 PWM 中断或者电流环中断打断。也有可能是串口波特率不匹配。
解决:把通信中断优先级设到比 PWM 中断低,但比主循环高。如果用的是 DMA 收发,检查 DMA 配置有没有冲突。波特率不匹配的话,用示波器量一下串口波形,算一下实际波特率。
5.5 编译通过但烧录后没反应
现象:编译零错误零警告,烧录也成功,但板子没反应。
原因:启动文件选错了,或者时钟配置不对。也有可能是 Flash 等待周期没设对。
解决:先确认启动文件是startup_stm32f103xe.s还是对应你芯片型号的。然后检查SystemClock_Config()里的晶振频率和实际板子上的晶振一致。如果主频跑得高,Flash 等待周期要相应增加,否则取指会出错。
6. 二次开发:从 EP100 改出你自己的伺服方案
6.1 硬件改造:替换功率级和采样电路
EP100 的功率级如果是 IPM,你想换成分立 MOS,原理图上的栅极驱动电路要重画。常见做法是用半桥驱动芯片,比如 IR2104 或者 DRV8301,注意自举电容和死区电阻的取值。电流采样如果从单电阻改成三电阻,代码里的采样时刻和重构算法都要改。三电阻采样可以在 PWM 的任意时刻采,但单电阻只能在特定时刻采,重构算法复杂得多。
我一般会先把采样电路改好,用示波器确认三路电流波形干净,再去改代码。硬件没调好就改代码,等于在沙子上盖楼。
6.2 软件改造:增加新的通信协议
EP100 如果用的是自定义串口协议,你想改成 Modbus RTU 或者 CANopen,主要改App/Comm目录。Modbus RTU 的帧格式是地址 + 功能码 + 数据 + CRC,CANopen 是 COB-ID + 数据。改的时候注意中断优先级和缓冲区大小,通信数据量大的话,DMA 加环形缓冲区是标配。
// Modbus RTU 解析示例 typedef struct { uint8_t addr; uint8_t func; uint16_t reg; uint16_t len; uint8_t data[64]; } Modbus_Frame; void Modbus_Parse(uint8_t *buf, uint16_t len) { Modbus_Frame frame; frame.addr = buf[0]; frame.func = buf[1]; frame.reg = (buf[2] << 8) | buf[3]; frame.len = (buf[4] << 8) | buf[5]; // CRC 校验 uint16_t crc = Modbus_CRC16(buf, len - 2); if (crc != ((buf[len-2] << 8) | buf[len-1])) return; // 根据功能码处理 switch (frame.func) { case 0x03: Modbus_ReadReg(frame.reg, frame.len); break; case 0x06: Modbus_WriteReg(frame.reg, frame.data); break; default: break; } }Modbus_CRC16是 CRC 校验函数,Modbus RTU 用的是 CRC-16/IBM,多项式是 0xA001。功能码 0x03 是读保持寄存器,0x06 是写单个寄存器。解析的时候先校验地址和 CRC,再处理功能码。如果通信数据量大,建议用 DMA 接收,然后在空闲中断里解析,避免在串口中断里做耗时操作。
6.3 控制算法升级:从 PI 到自抗扰或者模型预测
EP100 的电流环和速度环大概率是 PI 控制。如果你想升级到自抗扰控制(ADRC)或者模型预测控制(MPC),主要改App/Control里的控制器实现。ADRC 的核心是扩张状态观测器,把内部扰动和外部扰动一起估计出来补偿掉。MPC 则是用电机模型预测下一时刻的电流,选一个最优电压矢量。
我试过在 EP100 的框架上把速度环换成 ADRC,低速时的抗扰动能力确实比 PI 好,但参数整定比 PI 麻烦得多。如果你只是想快速出活,PI 加前馈够用了;如果想发论文或者做高端应用,再考虑 ADRC 或 MPC。
6.4 验证方法:用示波器和上位机看波形
改完之后怎么验证?我习惯用三样东西:示波器看相电流和 PWM 波形,上位机看转速和电流的阶跃响应,温度枪看功率级的温升。相电流波形要正弦、平滑、无毛刺;阶跃响应要超调小、恢复快;温升在额定负载下不超过器件规格的 80%。
从那以后我每次改完控制参数,都强制走一遍「空载 → 轻载 → 额定负载」的阶梯测试,确认每个负载点都稳定再往下走。希望帮到你。
本文还有配套的精品资源,点击获取