STM32F103ZE与AS5600开环FOC实战:无电流环的电机控制入门
简介这份资源面向嵌入式电机控制初学者与工程师提供一套基于STM32F103ZE、MS8313陀螺仪与AS5600磁性编码器的FOC角度控制实现方案且不引入电流环专注角度闭环控制适合希望快速上手无刷电机磁场定向控制、简化控制逻辑的开发者参考。压缩包共1131个文件约21.13MB以C源码与头文件为主体567个.c、250个.h并包含IAR工程配置、启动文件、链接脚本、数学库及少量说明文档覆盖从底层驱动到算法实现的完整工程结构。资源已有504人学习下载内容涉及串口设定角度、传感器角度解算与FOC坐标变换等关键环节读者可据此搭建可运行工程理解无电流环条件下角度控制的实现思路与调试方法并借助现成库文件与工程模板缩短开发周期。1. 手头只有一块 F103ZE 和一颗 AS5600怎么把电机转起来很多人第一次碰 FOC脑子里全是坐标变换、SVPWM、电流采样三环串级结果板子焊好了电机就是不转或者一转就抖。我这次拿到的硬件很朴素STM32F103ZE 做主控MS8313 做三相栅极驱动AS5600 做磁编码器没有电流采样电路。也就是说这是一个开环电压 位置反馈的方案没有电流环。听起来像残废版 FOC但它恰好是新手理解 FOC 骨架最合适的起点——你不需要跟运放失调、采样时序、ADC 注入通道打架只需要把角度、扇区、占空比这三件事理清楚。这套东西适合谁适合手上有 F103 板子、想先让电机闭环转起来、再逐步加电流环的人。它不适合直接拿去跑云台或高动态伺服但作为 FOC 入门的第一块跳板够用。2. 无电流环的 FOC 到底在控什么从 AS5600 角度到 SVPWM 占空比2.1 为什么没有电流环也能叫 FOCFOC 的核心不是“必须有电流环”而是“在旋转坐标系下控制定子磁场”。标准三环结构是电流环、速度环、位置环但电流环的作用是把定子电流分解成励磁分量 Id 和转矩分量 Iq然后分别控制。没有电流采样你就不知道实际 Id/Iq也就没法做电流闭环。但你可以做另一件事给定一个旋转电压矢量让它的角度跟随转子角度 90 度或某个超前角这样定子磁场始终超前转子磁场电机就能产生转矩。这就是开环电压 FOC也叫 V/F 或 I/F 的变种。MS8313 在这里的角色是三相半桥驱动它接收 MCU 的六路 PWM输出到电机三相。AS5600 提供转子绝对角度用来计算电压矢量的方向。整个链路里没有电流传感器所以电流环被跳过速度环和位置环仍然可以跑。常见做法是用 AS5600 读机械角度乘以极对数得到电角度然后电角度加上一个固定超前角比如 90 度作为 SVPWM 的参考矢量角度。电压幅值由速度环输出或直接给定。这样电机就能转起来而且比六步换相平滑得多。2.2 AS5600 角度读取I2C 时序与电角度换算AS5600 是 12 位磁编码器输出 0~4095 对应 0~360 机械角度。它支持 I2C 和模拟输出这里用 I2C。F103ZE 的 I2C1 挂在 PB6/PB7注意 AS5600 的地址是 0x367 位写地址 0x6C读地址 0x6D。原始角度寄存器是 0x0C 和 0x0D高字节在前但 0x0C 的高 4 位是无效的实际角度是 12 位。// AS5600 读取原始角度返回 0~4095 uint16_t AS5600_ReadRawAngle(void) { uint8_t buf[2]; // 寄存器 0x0C 是角度高字节0x0D 是低字节 I2C_ReadBytes(AS5600_ADDR, 0x0C, buf, 2); // 高字节低 4 位有效拼成 12 位 uint16_t raw ((uint16_t)(buf[0] 0x0F) 8) | buf[1]; return raw 0x0FFF; }逻辑说明先读 0x0C再读 0x0D拼成 12 位。注意 AS5600 的 I2C 读操作需要先写寄存器地址再 restart 读数据。很多人的翻车点在这里用硬件 I2C 时忘了发 restart或者用软件 I2C 时延时不够读出来全是 0 或 4095。参数上I2C 速率建议 400kHz 以下AS5600 支持快速模式但 F103 的 I2C 在 400kHz 时容易出玄学问题我一般降到 100kHz 先跑通。电角度换算假设电机极对数是 7机械角度 0~4095 对应 0~2π电角度 机械角度 × 7。但注意 AS5600 装到电机轴上时零点和电机 A 相轴线不一定对齐需要做一次偏移校准。简单做法是给 A 相通一个固定电压让转子稳定到 A 相轴线然后读 AS5600 值作为零点偏移。// 机械角度转电角度带零点偏移 float GetElecAngle(void) { uint16_t raw AS5600_ReadRawAngle(); // 减去零点偏移注意处理负数 int32_t mech (int32_t)raw - (int32_t)zero_offset; if (mech 0) mech 4096; // 机械角度 0~4095 映射到 0~2π float mech_rad (float)mech * (2.0f * 3.1415926f / 4096.0f); // 乘以极对数得到电角度 float elec_rad mech_rad * POLE_PAIRS; // 归一化到 0~2π while (elec_rad 2.0f * 3.1415926f) elec_rad - 2.0f * 3.1415926f; return elec_rad; }参数说明POLE_PAIRS 是极对数不是极数。比如 7 对极的电机极数是 14。zero_offset 需要实测不同安装方式不一样。如果电机反转电角度方向也要取反。2.3 SVPWM 扇区判断与占空比计算SVPWM 的目标是给定一个电压矢量幅值 U、角度 θ计算三个半桥的占空比。F103 没有硬件 SVPWM需要用定时器发六路 PWM然后软件计算比较值。常见做法是用中心对齐模式定时器计数到 ARR 时翻转比较值决定占空比。扇区判断把 θ 归一化到 0~2π然后除以 π/3得到 0~5 的扇区号。每个扇区内两个相邻基本矢量作用时间 T1、T2 由正弦定理算出。零矢量作用时间 T0 T - T1 - T2。然后根据扇区把 T1、T2 分配给三个半桥。// 输入Ualpha, Ubeta静止坐标系电压Udc 母线电压 // 输出三路占空比 Ta, Tb, Tc0~1 void SVPWM_Calc(float Ualpha, float Ubeta, float Udc, float *Ta, float *Tb, float *Tc) { // 扇区判断用三个辅助变量 float U1 Ubeta; float U2 0.8660254f * Ualpha - 0.5f * Ubeta; // sin60*Ualpha - cos60*Ubeta float U3 -0.8660254f * Ualpha - 0.5f * Ubeta; int sector 0; if (U1 0) sector | 0x01; if (U2 0) sector | 0x02; if (U3 0) sector | 0x04; // 扇区映射常见表1-3, 2-1, 3-5, 4-4, 5-6, 6-2 // 这里直接算作用时间避免查表 float T 1.0f; // 一个 PWM 周期归一化 float T1, T2; float Uref sqrtf(Ualpha*Ualpha Ubeta*Ubeta); float angle atan2f(Ubeta, Ualpha); if (angle 0) angle 2.0f * 3.1415926f; int sec (int)(angle / (3.1415926f / 3.0f)); // 根据扇区计算相邻矢量作用时间 // 具体公式略可参考标准 SVPWM 推导 // 这里给出一个简化实现用七段式先算 T1 T2 // ... // 最终占空比 (T - T1 - T2)/2 T1 T2 的分配 // 实际代码需要根据扇区把 T1 T2 映射到 Ta Tb Tc }上面代码只给了框架因为完整 SVPWM 公式较长。关键参数Udc 是母线电压如果电池供电Udc 会变需要实时采样或估算。Ualpha、Ubeta 来自电角度和电压幅值Ualpha U * cos(θ)Ubeta U * sin(θ)。θ 电角度 超前角。超前角一般取 90 度但实际要根据负载调整轻载时 90 度可能不够重载时要减小。注意F103 的定时器 1 是高级定时器支持互补输出和死区。MS8313 是三相栅极驱动输入是六路 PWM所以需要配置 TIM1 的 CH1/CH1N、CH2/CH2N、CH3/CH3N。死区时间根据 MS8313 的开关特性设置一般 1~2us。如果死区太小上下桥直通炸管太大输出波形畸变。3. 把代码烧进 F103ZE定时器、I2C 与中断的配置细节3.1 TIM1 中心对齐 PWM 配置F103ZE 的 TIM1 挂在 APB2时钟 72MHz。要生成 20kHz 的 PWM中心对齐模式下ARR 72M / (2 * 20k) 1800。预分频器 PSC 0。死区时间寄存器 BDTR 的 DTG 位假设死区 1us则 DTG 72M * 1us 72但 DTG 不是线性需要查表。简单取 DTG 0x48 左右。void TIM1_PWM_Init(void) { // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); // 时基中心对齐ARR1800PSC0 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_CenterAligned1; TIM_TimeBaseStructure.TIM_Period 1800 - 1; TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_RepetitionCounter 0; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure); // 输出比较PWM 模式 1 TIM_OCInitTypeDef TIM_OCInitStructure; TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OutputNState TIM_OutputNState_Enable; TIM_OCInitStructure.TIM_Pulse 0; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OCNPolarity TIM_OCNPolarity_High; TIM_OCInitStructure.TIM_OCIdleState TIM_OCIdleState_Reset; TIM_OCInitStructure.TIM_OCNIdleState TIM_OCNIdleState_Reset; TIM_OC1Init(TIM1, TIM_OCInitStructure); TIM_OC2Init(TIM1, TIM_OCInitStructure); TIM_OC3Init(TIM1, TIM_OCInitStructure); // 死区 TIM_BDTRInitTypeDef TIM_BDTRInitStructure; TIM_BDTRInitStructure.TIM_OSSRState TIM_OSSRState_Enable; TIM_BDTRInitStructure.TIM_OSSIState TIM_OSSIState_Enable; TIM_BDTRInitStructure.TIM_LOCKLevel TIM_LOCKLevel_1; TIM_BDTRInitStructure.TIM_DeadTime 0x48; // 约 1us TIM_BDTRInitStructure.TIM_Break TIM_Break_Disable; TIM_BDTRInitStructure.TIM_BreakPolarity TIM_BreakPolarity_High; TIM_BDTRInitStructure.TIM_AutomaticOutput TIM_AutomaticOutput_Enable; TIM_BDTRConfig(TIM1, TIM_BDTRInitStructure); // 使能 TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); }逻辑说明中心对齐模式让 PWM 对称减少谐波。ARR 决定频率PWM 频率 72M / (2 * ARR)。这里 ARR1799实际频率约 20kHz。死区时间 0x48 对应约 1us具体看参考手册。注意 TIM_CtrlPWMOutputs 必须调用否则没有输出。参数怎么改如果电机噪音大提高 PWM 频率到 30kHzARR 改成 1200。但频率越高开关损耗越大MS8313 可能发热。如果死区导致低速抖动适当减小死区但别低于 0.5us。3.2 I2C 读取 AS5600 的两种实现F103 的硬件 I2C 是出了名的难用常见做法是用软件 I2C。软件 I2C 只需要两个 GPIO延时用 __NOP() 或定时器。下面给一个软件 I2C 读 AS5600 的片段。// 软件 I2C 读 AS5600 角度 #define SCL_H() GPIO_SetBits(GPIOB, GPIO_Pin_6) #define SCL_L() GPIO_ResetBits(GPIOB, GPIO_Pin_6) #define SDA_H() GPIO_SetBits(GPIOB, GPIO_Pin_7) #define SDA_L() GPIO_ResetBits(GPIOB, GPIO_Pin_7) #define SDA_READ() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) void I2C_Start(void) { SDA_H(); SCL_H(); delay_us(2); SDA_L(); delay_us(2); SCL_L(); delay_us(2); } void I2C_Stop(void) { SDA_L(); SCL_H(); delay_us(2); SDA_H(); delay_us(2); } uint8_t I2C_ReadByte(void) { uint8_t data 0; for (int i 0; i 8; i) { SCL_H(); delay_us(2); data 1; if (SDA_READ()) data | 1; SCL_L(); delay_us(2); } return data; } uint16_t AS5600_ReadAngleSW(void) { uint16_t raw; I2C_Start(); I2C_WriteByte(0x6C); // 写地址 I2C_WriteByte(0x0C); // 寄存器 I2C_Start(); // restart I2C_WriteByte(0x6D); // 读地址 uint8_t hi I2C_ReadByte(); I2C_Ack(0); // 非应答 uint8_t lo I2C_ReadByte(); I2C_Ack(0); I2C_Stop(); raw ((uint16_t)(hi 0x0F) 8) | lo; return raw; }逻辑说明软件 I2C 的关键是延时和 restart。AS5600 读角度需要先写寄存器地址再 restart 读数据。如果不用 restart直接 stop 再 start有些批次会读失败。参数上delay_us(2) 对应约 250kHz实际用示波器看波形上升沿太慢就减小上拉电阻。3.3 速度环与位置环的定时中断没有电流环速度环和位置环可以放在同一个定时中断里比如 1kHz。速度计算用 AS5600 角度差分注意处理过零。位置环输出作为速度环给定速度环输出作为电压幅值 U。U 再和电角度一起送 SVPWM。// 1kHz 中断服务函数 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 读角度 uint16_t raw AS5600_ReadAngleSW(); // 速度计算角度差分注意过零 int32_t diff (int32_t)raw - last_raw; if (diff 2048) diff - 4096; if (diff -2048) diff 4096; last_raw raw; float speed (float)diff * SPEED_SCALE; // 转成 rad/s 或 rpm // 速度环 PI float speed_err target_speed - speed; integral speed_err * KI; if (integral INTEGRAL_MAX) integral INTEGRAL_MAX; if (integral -INTEGRAL_MAX) integral -INTEGRAL_MAX; float U speed_err * KP integral; if (U U_MAX) U U_MAX; if (U 0) U 0; // 电角度 float elec GetElecAngle(); // 超前角 float theta elec ADVANCE_ANGLE; // 计算 Ualpha Ubeta float Ualpha U * cosf(theta); float Ubeta U * sinf(theta); // SVPWM float Ta, Tb, Tc; SVPWM_Calc(Ualpha, Ubeta, UDC, Ta, Tb, Tc); // 写比较值 TIM_SetCompare1(TIM1, (uint16_t)(Ta * 1800)); TIM_SetCompare2(TIM1, (uint16_t)(Tb * 1800)); TIM_SetCompare3(TIM1, (uint16_t)(Tc * 1800)); } }参数说明SPEED_SCALE 根据采样周期和编码器分辨率算。1kHz 下diff 是每毫秒的角度变化乘以 1000 得到每秒角度变化再转 rad/s。KP、KI 需要整定先给 KP0.5KI0.01 试。U_MAX 限制最大电压防止过流。ADVANCE_ANGLE 一般 90 度但可以微调。注意中断里不要做浮点运算太频繁F103 没有 FPU浮点靠软件模拟1kHz 下计算量要评估。如果跑不动把速度环降到 500Hz或者用定点数。4. 调试时最容易翻车的五个点从电机不转到编码器跳变4.1 电机完全不转但 PWM 有输出现象示波器看 TIM1 的 CH1/CH1N 有互补 PWM但电机不转或者只抖一下。 原因电角度方向反了或者超前角不对。如果电角度方向反定子磁场和转子磁场夹角不是 90 度而是 0 度或 180 度转矩为零或负。另外AS5600 的零点偏移没校准电角度整体偏了。 解决先开环给一个固定角度比如 θ0看电机是否锁定到某个位置。然后手动转电机看 AS5600 读数是否变化。如果方向反把电角度取反。零点校准给 A 相通固定电压读 AS5600 值作为 zero_offset。4.2 电机转起来但噪音大、发热现象电机能转但声音刺耳MS8313 发热严重。 原因PWM 频率太低或者死区时间不对。20kHz 以下人耳可闻噪音大。死区太小导致直通电流尖峰大发热。另外SVPWM 的零矢量分配不对七段式变成五段式谐波大。 解决提高 PWM 频率到 20kHz 以上死区调到 1~2us。检查 SVPWM 实现确保每个周期都有零矢量。用示波器看相电流波形如果有尖刺加大死区。4.3 AS5600 读数跳变或不变现象角度读数偶尔跳变或者一直不变。 原因I2C 时序问题或者磁铁距离太远。AS5600 要求磁铁在芯片上方 0.5~3mm太远信号弱。另外I2C 上拉电阻太大上升沿太慢导致数据错误。 解决调整磁铁位置用示波器看 I2C 波形。上拉电阻一般 4.7k如果线长降到 2.2k。软件 I2C 的延时加大或者改用硬件 I2C 加 DMA。4.4 速度环振荡现象电机速度忽快忽慢或者有低频抖动。 原因速度环 PI 参数太激进或者速度计算噪声大。AS5600 分辨率 12 位低速时差分量化误差大。 解决降低 KP增大 KI 的积分时间。速度计算加低通滤波比如一阶滤波。或者提高速度环频率但注意计算量。4.5 上电瞬间炸管现象一上电MS8313 冒烟。 原因死区时间不够上下桥直通。或者 TIM1 的 MOE 位没使能但互补输出已经有效。另外电机相线接错导致反电动势反馈。 解决先不接电机用示波器看六路 PWM确认死区。上电顺序先给 MCU 电再给驱动电。MS8313 的使能脚要加下拉防止浮空。5. 从开环到闭环的进阶用 AS5600 做位置环和参数整定5.1 位置环的 P 控制与轨迹规划有了速度环位置环就是外环。位置环输出作为速度环给定。最简单的 P 控制speed_target Kp_pos * (pos_target - pos_actual)。但直接给阶跃电机会猛冲需要做轨迹规划比如梯形加减速。// 位置环1kHz 调用 float pos_target 0; // 目标位置单位 rad float pos_actual GetMechAngle(); // 机械角度 rad float pos_err pos_target - pos_actual; // 梯形规划限制最大速度和加速度 float speed_cmd Kp_pos * pos_err; if (speed_cmd MAX_SPEED) speed_cmd MAX_SPEED; if (speed_cmd -MAX_SPEED) speed_cmd -MAX_SPEED; // 送给速度环 target_speed speed_cmd;参数说明Kp_pos 一般取 5~20太大振荡太小响应慢。MAX_SPEED 根据电机和负载定。如果要做更平滑用 S 曲线但 F103 资源有限梯形够用。5.2 超前角与电压幅值的整定表没有电流环电压幅值 U 和超前角是两个关键参数。U 太小带不动负载U 太大过流。超前角太小转矩不够太大效率低。下面是一个参考整定表针对 24V 母线、7 对极电机。转速 (rpm)电压幅值 U (占 Udc 比例)超前角 (度)现象0~5000.1~0.290启动平稳500~15000.2~0.480~90转矩足够1500~30000.4~0.670~80高速弱磁30000.6~0.860~70注意过流注意这个表是经验值不同电机差异大。整定时先固定超前角 90 度慢慢加 U直到电机能转。然后加负载如果失步减小超前角或加大 U。5.3 验证 FOC 是否真的在工作怎么判断你的 FOC 是真的在跑而不是碰巧转起来看相电压波形。用示波器差分探头看电机任意两相之间的电压应该是正弦波而且频率和电角度一致。如果波形是六步梯形说明 SVPWM 没生效。另外用手轻捏电机轴如果阻力均匀说明磁场旋转平滑。如果有一顿一顿的感觉扇区切换有问题。我一般会做这个测试给定一个很低的速度比如 10rpm看 AS5600 的角度是否线性增加。如果角度跳变说明 I2C 读取有问题。然后给定位置环让电机在 0 度和 180 度之间来回看是否过冲。过冲大就减小 Kp_pos。从那以后我每次调 FOC都先把电流环断开用开环电压把电机转起来确认角度和 SVPWM 没问题再逐步加环。这个习惯帮我省了很多炸管和烧电机的后悔药。希望帮到你。本文还有配套的精品资源点击获取