尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
STM32F103移植DengFOC:MT6701磁编码器与PID整定
简介这是一份基于STM32与HAL库的DengFOC闭环位置控制移植工程面向对FOC矢量控制有一定基础、需要在STM32平台上快速落地的开发者可直接解决电机位置伺服调试中代码移植与配置适配问题。压缩包共183个文件约1.4MB以HAL库驱动源文件c/h和Keil工程文件uvprojx/uvoptx为核心包含定时器、I2C、UART、DMA等外设配置另含编译生成的hex、axf、map及调试日志文件便于直接烧录和对照分析。已有578人学习下载内容覆盖FOC闭环位置控制的完整工程结构从外设初始化到控制算法均有清晰代码组织适合结合官方DengFOC资料进行二次开发与调参实践。对于希望理解STM32 HAL库下FOC位置环实现细节的学习者这份工程提供了直观可运行的参考模板。 移植DengFOC不是第一次了但每次在HAL库环境下重来最先卡住我的永远不是FOC算法本身而是那个看起来简单到不行的磁编码器。角度读不准、方向反了、零点漂移随便一个都能让电机在原地抽风半小时。这篇就把我在STM32F103C8T6上移植DengFOC做闭环位置控制的完整过程写出来重点放在编码器处理、HAL接口适配和PID整定这三个最容易翻车的环节给后面想入坑的朋友一条能走通的路。1. 为什么是DengFOC一套给穷玩党的FOC方案1.1 它到底解决了我什么痛点玩无刷电机FOC控制传统路线是看TI的应用笔记、啃AN1078手写Clark变换、Park变换、SVPWM那一整套数学。不是不行但对只是想做个小机械臂、小云台的人来说成本太高。DengFOC这个开源项目的价值在于它把FOC最核心的控制循环封装成了几个直接调用的函数你给它当前角度和目标值它在内部完成电流环运算、坐标变换和SVPWM输出剩下的就是配置好几个定时器的PWM通道再往串口丢目标角度电机就能转。移植它的意义不是省掉学习过程而是让你先跑起来、再深入进去。我个人的建议路线是先用这套框架把位置闭环跑通确认硬件接线和编码器没问题再回头去逐行读FOC_loop里到底做了什么。反过来如果一上来就自己从零写FOC编码器数据又是错的你根本分不清是算法问题还是传感器问题。1.2 我的硬件选型清单这次移植用的都是最容易买到、也最便宜的方案全套硬件不到五十块钱硬件型号/规格说明主控STM32F103C8T6蓝板最小系统板性价比之王驱动板集成三路半桥的FOC驱动小板常见国产板INA240电流采样电机云台无刷电机7对极2204或2804型号都行编码器MT6701磁编码器模块14bit分辨率IIC输出电源12V 2A直流电源电机额定电压12V调试器ST-Link V2下载程序 虚拟串口这套组合里最需要注意的就是MT6701编码器。DengFOC原版生态里常用AS5600但MT6701贵在自带IIC接口、分辨率更高14bit vs 12bit而且抗磁偏能力强很多。缺点是比较冷门网上资料少HAL库下能直接抄的代码更少。后面第2章我详细展开这个部分。2. 移植路上第一个坎MT6701磁编码器的读取与校准2.1 模拟IIC为什么比硬件IIC稳看热词里也有“用stm32f103c8t6 hal库模拟iic读取mt6701磁编码器的滤波与校准实战”说明大家都卡在这。MT6701的IIC接口最高支持1MHz时钟但STM32F103的硬件IIC在高速下经常出现锁死问题——这是老生常谈了硬件IIC一旦遇到从设备不响应就卡在BUSY状态等不到释放必须复位外设才能恢复。我的做法是直接用GPIO模拟IIC把两个引脚改成开漏输出加上拉电阻。虽然代码多写几行但胜在稳定和可控时钟频率可以自己把握出错能立刻释放总线对现场调试友好得多。初始化部分// 模拟IIC引脚初始化PB6SCLPB7SDA void MT6701_IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD; // 开漏输出 GPIO_InitStruct.Pull GPIO_PULLUP; // 外部/内部上拉 GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); IIC_SCL_HIGH(); IIC_SDA_HIGH(); }注意这里一定是开漏模式不要用推挽。开漏配合上拉电阻才能实现IIC总线的线与逻辑不然多个设备挂总线上会打架。2.2 14位角度读取的完整实现MT6701的IIC设备地址是6位的0x03左移一位后写地址是0x06、读地址是0x07。角度数据存放在0x00寄存器连续读两个字节取低14位就是完整的机械角度值。uint16_t MT6701_ReadRawAngle(void) { uint8_t buf[2] {0}; uint16_t angle_raw 0; IIC_Start(); IIC_SendByte(0x06); // 写地址 IIC_WaitAck(); IIC_SendByte(0x00); // 角度寄存器地址 IIC_WaitAck(); IIC_Start(); // 重复起始信号 IIC_SendByte(0x07); // 读地址 IIC_WaitAck(); buf[0] IIC_ReadByte(1); // ACK buf[1] IIC_ReadByte(0); // NACK最后一字节回NACK IIC_Stop(); angle_raw ((uint16_t)buf[0] 8 | buf[1]) 2; return angle_raw; }为什么要右移两位而不是取高14位因为MT6701的14位数据是左对齐存放在16位寄存器中的低两位永远为0所以右移两位才能得到真实的角度码值。角度码值范围是0到16383对应0到360度机械角度。读取频率这里有个易踩的坑MT6701的转换时间大概是1ms级别如果你读得太频繁会读到重复数据。实测下来200us到500us读一次没问题刚好能覆盖10kHz的FOC控制频率。2.3 滤波、零位与方向开环测试前必须先做好滤波不能省但也不能乱来。我用的是8次滑动平均不是那种一阶低通。原因很简单滑动平均对角度这种周期性信号的相位延迟小不会在高频段引入明显的滞后感。一阶低通滤波响应慢位置闭环时会感觉电机反应钝。#define FILTER_LEN 8 uint16_t angle_buf[FILTER_LEN]; uint8_t angle_index 0; uint32_t angle_sum 0; uint16_t MT6701_Filter(uint16_t new_angle) { angle_sum - angle_buf[angle_index]; angle_buf[angle_index] new_angle; angle_sum new_angle; angle_index (angle_index 1) % FILTER_LEN; return (uint16_t)(angle_sum / FILTER_LEN); }零位校准的意思是在电机上电时记录当前角度作为机械零点后面所有位置控制都相对这个零点计算。这个做法比每次手动找零位靠谱得多因为安装编码器时你不可能保证转子在绝对的0度位置。float zero_offset 0.0f; void MT6701_CalibrateZero(void) { uint16_t raw MT6701_ReadRawAngle(); for (int i 0; i 32; i) { raw MT6701_ReadRawAngle(); HAL_Delay(1); } raw MT6701_Filter(MT6701_ReadRawAngle()); zero_offset (float)raw * 360.0f / 16384.0f; } float MT6701_GetAngle(void) { float angle (float)MT6701_Filter(MT6701_ReadRawAngle()) * 360.0f / 16384.0f; angle - zero_offset; if (angle 0) angle 360.0f; return angle; }方向问题很多人到最后都没注意到。MT6701角度顺时针增加还是减少取决于磁铁充磁方向和你怎么贴的磁铁。最稳妥的判断方法用手缓慢顺时针转动电机轴看串口打印的角度值是增加还是减小。如果减小直接把角度取个补数就行// 若方向反了在GetAngle返回值处处理不要改后面的电角度计算 float angle MT6701_GetAngle(); if (direction_reverse) angle 360.0f - angle;3. CubeMX工程搭建PWM、ADC、调度定时器一个都不能少3.1 定时器通道分配思路FOC驱动板需要六路PWM对应三相上下桥臂。STM32F103C8T6只有TIM1和TIM8是高级定时器能输出带死区插入的互补PWM但TIM8在C8T6这个封装里并没有引出全部通道。所以实际工程里最省事的方案是TIM1输出三路互补PWM_CH1、CH1N、CH2、CH2N、CH3、CH3N正好对应驱动板的AH、AL、BH、BL、CH、CL。CubeMX里的配置要点是TIM1的Clock Source选Internal ClockChannel1到Channel3都选PWM Generation CHx and CHxNPrescaler设为72-1Counter Period设为1000-1这样PWM频率就是72MHz/72/10001kHz不对这个算错了。我要算的是20kHz左右的PWM频率因为FOC控制惯用电机的开关频率是20kHz~40kHz。所以Prescaler设为72-1Counter Period设为50-172MHz/72/5020kHz。Pulse初始设为0后面在代码里动态改占空比。死区时间的设置这里留个心眼。驱动板自带死区插入时你可以设小一点但如果驱动板的栅极驱动芯片本身有延迟死区设太小会导致上下桥直通烧MOS。我一般设1us左右也就是TIM1的DeadTime寄存器里写72因为时钟是72MHz1us就是72个时钟周期。这个值根据驱动板的实际情况调。3.2 电流采样两路ADCDMADengFOC默认只采样两相电流第三相通过基尔霍夫定律算出来。STM32F103的ADC1有多个通道我配置ADC1的IN1和IN2为两个电流采样通道用DMA循环模式把结果自动搬运到内存数组。ADC_HandleTypeDef hadc1; DMA_HandleTypeDef hdma_adc1; uint16_t adc_buf[2] {0}; float current_a, current_b; // 主循环前启动DMA采样之后不用管 HAL_ADC_Start_DMA(hadc1, (uint32_t*)adc_buf, 2);电流计算的核心是把ADC原始值转换为实际电流值这个需要参考驱动板的采样电阻阻值和运放放大倍数。常见国产FOC驱动板用的INA240放大倍数是20倍采样电阻0.005欧5毫欧配合3.3V参考电压转换关系大概是float v_ia (float)adc_buf[0] * 3.3f / 4096.0f; float current_a (v_ia - 1.65f) / 0.1f; // 20倍放大 * 5mR 0.1V/A1.65V是运放的偏置电压也就是零电流时的输出电压。这个中点电压你可以用万用表实测不同板子会有差异精确校准能明显减少电流环的噪声。如果中点偏移没校准电机会在零电流附近轻微抖动听起来像有规律的嘶嘶声。3.3 delay与串口的HAL适配DengFOC是从Arduino生态出来的代码里有大量delay()、delayMicroseconds()和Serial.println()这样的调用。搬到HAL下需要先做一层适配。我在工程里新建了一个platform.c把这些东西统一封装uint32_t micros_counter 0; void delay_ms(uint32_t ms) { HAL_Delay(ms); } void delay_us(uint32_t us) { // 用DWT实现精确微秒延时比循环精确 CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; uint32_t start DWT-CYCCNT; uint32_t ticks us * 72; // 72MHz主频1us 72周期 while (DWT-CYCCNT - start ticks); }串口输出我用重映射printf的方式实现方便FOC调试时把角度、目标值、PID输出打印出来看曲线。注意DengFOC原版在非调试模式下会关掉串口打印因为串口阻断会影响控制周期调试完记得关。4. 把DengFOC控制循环接进HAL工程核心接口改造4.1 哪些文件搬过来就能用从DengFOC仓库下载源码后核心文件就两个dengfoc.c和dengfoc.h。这两个文件直接复制进工程即可不需要改动算法逻辑本身。它的对外接口也很干净FOC_init()初始化内部结构体必须在所有控制前调用一次setPidPosition(p, i, d)设置位置环PID参数setPidSpeed(p, i, d)设置速度环PID参数setVoltageLimit(v)限制母线电压比例防止输出过压setTorque(iq)力矩模式直接给电流目标值FOC_loop(angle, target)核心控制循环内部完成电流采样、坐标变换和PID这几个函数在dengfoc.h中都有声明移植时不需要改动它们的接口。4.2 必须适配的接口清单以下这些接口是DengFOC运行时强依赖的必须在你的HAL工程里实现了才能正常运转接口/资源DengFOC默认实现我的HAL实现delay函数Arduino delay() / delayMicroseconds()前述delay_ms / delay_us角度获取默认从ADC读取电位器或编码器我改为MT6701_GetAngle()电流采样默认读取ADC通道值我改为HAL_ADC_DMA读取后的换算结果串口打印Serial.printlnprintf重映射到HAL_UART_Transmit定时器PWM直接寄存器操作使用HAL_TIM_PWM_Start和__HAL_TIM_SET_COMPARE特别注意电流采样这部分。DengFOC内部电流采样函数默认从模拟引脚读取0到3.3V电压它在FOC_loop内部会做一次从ADC值到电流值的换算。如果你像我一样已经换算成实际电流值就要在FOC_loop里找到那行电流读取代码替换成你的变量。不同版本的DengFOC源码这里可能有差异打开dengfoc.c搜索“current_a ”或“_read_ADC”就能定位。4.3 控制循环放哪定时器中断最稳妥FOC控制对实时性要求很高位置环一般跑1kHz到2kHz电流环也就是FOC_loop内部那部分需要跑到10kHz到20kHz。把它放主循环里不可靠因为串口打印、按键扫描都会阻塞它。我的做法是用TIM2做10kHz定时中断每次中断调用一次FOC_loopvoid HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim htim2) { float current_angle MT6701_GetAngle(); FOC_loop(current_angle, target_position); } }CubeMX里TIM2的配置Prescaler72-1Counter Period100-1这样72MHz/72/10010kHz。开启TIM2中断。有一个非常重要的细节FOC控制循环必须传入电角度而不是机械角度。我的电机是7对极所以电角度机械角度×7。DengFOC内部默认极对数是7吗不一定。你需要在dengfoc.h里找到极对数宏定义改成自己电机的极对数或者在传给FOC_loop之前就把电角度算好。我习惯在前者改这样位置环输出的目标角度也能自动换算。5. 闭环位置控制调试PID参数从零到稳5.1 开环先转起来接线验证与方向确认闭环调试最忌讳的就是直接上闭环。我的顺序永远是先开环验证三件事第一电机能不能正常换向。用setTorque给一个固定电流目标比如1A看电机是否转动。如果电机只震动不转大概率是两相线序接反了任意交换两根电机线再试。第二角度反馈是否连续平滑。用手慢慢旋转电机轴串口打印角度值必须是0到360平滑变化不能有跳变、突刺。跳变说明IIC读取偶发错误检查上拉电阻和线长突刺说明滤波不够或者线接触不良。第三方向关系是否一致。给力矩让电机正向转同时观察角度值变化。DengFOC的电机正向应该对应角度增大如果是反的就需要按第2章的方法把角度方向取反。这步不做对后面位置环一定会发散。5.2 位置环PID的整定顺序位置环有两个嵌套的PID环在起作用外位置环的输出作为内速度环的输入内速度环的输出再作为电流环的输入。如果你直接用位置环PID一顿乱调会发现无论怎么加P电机就是不正因为内环没调好。我的整定步骤分三步每步验证通过再进下一步第一步只调速度环。把位置环的P和I调成0直接给一个固定的目标速度值观察电机是否匀速转动。速度环的P从小往大加加到电机能平滑启动且不抖动I的作用是消除静态误差但加太大会引起低频震荡。速度环调好后电机给任意目标速度都能顺滑运行。第二步位置环只加P。把位置环的目标位置设为一个固定角度比如45度P从0.5开始加。观察现象P太小电机转不动P适中电机快速转到位但有轻微回冲P过大电机会在目标点来回震荡。第三步位置环加I和D。位置环的I没必要加太大因为位置控制本身没有明显的静差问题加一点I是为了消除长时间停机时的微小漂移。D主要是抑制过冲但如果编码器滤波不好D会放大噪声导致电机高频振动所以D要非常小。5.3 我记录的一组可行参数与回冲处理技巧以我的12V云台电机、14bit编码器为例下面是实际能用的一组参数注意不能直接照抄你的电机电阻电感不同参数会差很大参数位置环速度环P3.00.15I0.020.05D0.050.00输出限幅12V不设调完之后的静态精度实测目标位置和实际位置误差在±0.2度以内动态过程从0到45度大概需要200ms无超调无回冲。这个指标拿来做个简单的云台或机械臂关节够用了。关于回冲的一个实用技巧位置环输出直接接速度环目标值时如果速度环I太大位置到位瞬间速度方向会反复横跳看起来就是回冲。解决办法是把速度环的I稍微减小让速度响应没那么“粘”还有就是把位置环的误差做死区处理比如误差小于0.3度时直接清零防止在目标点附近来回修正。6. 编译烧录与调试阶段的典型坑和排查路径6.1 no stm32 target found芯片连不上这个报错导致程序根本下载不进去。先别慌按这个顺序排查第一步检查ST-Link和板子的接线SWDIO、SWCLK、GND三条线必须确认接通很多问题就是杜邦线虚接。第二步确认给板子供电了有些最小系统板的3.3V稳压是USB口给的ST-Link的3.3V不够用。第三步按住板子上的复位键不放点下载按钮进度条出现后立刻松开复位键这是最常用的“抢时间”下载法。如果还是不行换STM32CubeProgrammer在连接设置里勾选Connect under reset模式这能解决90%的连不上问题。如果以上全试过还是不行最终大招是把BOOT0引脚接3.3V让芯片进入ISP模式用串口连接后再全片擦除。擦干净之后把BOOT0跳回GND重新用ST-Link就能连上了。导致这个问题的常见原因是你程序里把SWD引脚复用成了普通GPIO下载一次后就把自己锁死了。6.2 虚拟串口感叹号驱动不匹配ST-Link板载虚拟串口在Windows下偶尔显示为带感叹号的未知设备。这不是硬件坏了是Windows自动更新装了个不匹配的驱动。解决方法是到ST官网下载STSW-LINK009驱动包解压后在设备管理器里右键感叹号设备选择“更新驱动”手动指向那个驱动目录强制替换成官方驱动。装完后重新插拔ST-Link串口就正常枚举出来了。顺带提醒FOC调试串口协议建议用二进制或简单文本协议不要用JSON之类的大包串口发送本身会阻塞主循环数据量太大会直接影响控制周期。6.3 HAL串口中断接收只收一次的诡异表现调试位置控制时需要用串口发送目标位置指令结果发现第一次能收到指令电机也动了后面再发就没反应。这是HAL库的典型特性HAL_UART_Receive_IT只启动一次接收收到一个字节后中断自动关闭等你下一次手动调用才会继续接收。解决办法是在中断回调里重新启动接收uint8_t rx_byte 0; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart huart1) { // 处理rx_byte HAL_UART_Receive_IT(huart1, rx_byte, 1); // 重新启动接收 } }不要把这个回调写成一个死循环做接收等待不然接收期间主循环和FOC控制循环全部被卡住。如果用了DMA接收还会遇到空闲中断和接收完成的配合问题这里不多展开建议调试阶段先用这个简单的单字节接收中断方式。最后分享一个我在调试中反复确认过的感受移植DengFOC这种成熟开源框架最大的坑其实不在技术实现而在于你急于求成跳过了前置验证。我花了一整天排查电机抖动最后发现只是编码器零点偏移没校准两根线之间差了3.5度。如果当时老老实实先打印角度看曲线十分钟就能定位。所以位置闭环前花半小时把编码器数据当成最优先的事情来验证后面PID整定你会感谢自己的耐心。本文还有配套的精品资源点击获取
RELATED

相关推荐

Tabby IntelliJ 插件版本演进全解析:从 CHANGELOG 看 AI 编程助手的迭代路线

Tabby IntelliJ 插件版本演进全解析:从 CHANGELOG 看 AI 编程助手的迭代路线

Tabby IntelliJ 插件版本演进全解析:从 CHANGELOG 看 AI 编程助手的迭代路线 【免费下载链接】tabby Self-hosted AI coding assistant 项目地址: https://gitcode.com/GitHub_Trending/tab/tabby 导读:本文以仓库中 clients/intellij/CHANGELOG.m…

📅 2026/9/10 2:23:58
基于LabVIEW的分布式四驱扭矩主动分配测控系统实战解析

基于LabVIEW的分布式四驱扭矩主动分配测控系统实战解析

简介:基于LabVIEW的汽车扭矩主动分配测控系统是一份源自全国虚拟仪器大赛的完整工程方案,面向测控、汽车电子方向的工程师与学习者,解决驱动轮扭矩实时分配与车辆稳定性控制问题。压缩包内共5个文件,包含Simulink模型(…

📅 2026/9/10 2:23:58
VOC手机检测数据集:5000张双格式标注工业级样本

VOC手机检测数据集:5000张双格式标注工业级样本

简介:本资源为面向计算机视觉初学者与算法工程师的手机目标检测专用数据集,适用于YOLO系列、Faster R-CNN等主流检测模型的训练与验证。数据集覆盖多样真实场景,含5000张高质量JPG图像,全部由LabelImg人工精标,统一标注…

📅 2026/9/10 2:23:58
MORE NEWS

更多资讯

📰

curl 限速指南:使用 `--limit-rate` 精确控制上传与下载带宽

curl 限速指南:使用 --limit-rate 精确控制上传与下载带宽 【免费下载链接】curl A command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQ…

📰

telegram-bot核心功能揭秘:多媒体处理与社交平台集成实战

telegram-bot核心功能揭秘:多媒体处理与社交平台集成实战 telegram-bot是一款基于插件架构的Telegram机器人,提供强大的多媒体处理能力和丰富的社交平台集成功能。本文将深入剖析其核心功能实现,帮助用户快速掌握机器人的实用技巧&#xff0…

📰

SpringBoot+Vue兼职平台开发实战:从设计到部署的完整避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

agents24 incident-response 插件解析:incident-response-code-reviewer 代码审查子代理的职责、能力与调用链路

agents24 incident-response 插件解析:incident-response-code-reviewer 代码审查子代理的职责、能力与调用链路 【免费下载链接】agents Multi-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigra…

📰

C语言常量与变量深度解析:内存布局、const用法与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

索纳拉完成收购,宣称让大脑活动测量如常规生理信号般简单助力可穿戴设备

索纳拉收购背后的可穿戴设备野心此次收购于今年早些时候完成。索纳拉(Sonera)在 2023 年宣称,要让“大脑活动的测量像心率、体温及其他生理信号一样简单”。这一表态显示出索纳拉在可穿戴设备领域的宏大目标。可穿戴设备发展下索纳拉的战略布…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬