STM32平衡车代码从标准库迁移HAL库全记录:踩坑与实现细节

发布时间:2026/11/4 15:30:47
STM32平衡车代码从标准库迁移HAL库全记录:踩坑与实现细节 经常玩STM32的朋友应该都刷到过B站铁头山羊那个标准库版本的平衡车教程代码写得规整、注释也到位算是很多人入门自平衡小车的启蒙项目。不过我实际开发中一直用的HAL库毕竟配合CubeMX做初始化确实省事后来就专门抽了几天时间把这套代码整体从标准库迁到了HAL库。过程不算复杂但里面坑不少涉及到GPIO、PWM、I2C、编码器接口、定时器中断优先级这些细节每一步都得对照着改。这篇文章就把我移植过程中踩过的坑、对应代码怎么写、调参顺序怎么做完整聊一遍。适合手里已经有平衡车硬件、想从标准库平滑切到HAL库的朋友也适合准备做平衡车毕业设计或者电赛练手的人参考。1. 项目整体设计与移植方案选择在做移植之前我先理清楚为什么非要选铁头山羊的标准库版本作为底子而不是直接从零写一套。因为这套代码经过很多人验证控制逻辑上没有什么隐藏的大坑我只需要解决库层面的差异问题就行这样能把工作重心放到理解HAL库的API映射上。1.1 为什么选择铁头山羊标准库版本作为底子铁头山羊那套平衡车的代码核心思路很清晰使用MPU6050读取角度和角速度通过串级PID控制两个带编码器的减速电机主控是STM32F103C8T6。它代码里把直立环、速度环、转向环分开处理控制周期用定时器中断固定成2毫秒整个逻辑脉络非常干净。我选择它还有一个重要原因就是B站视频里已经把硬件连接和PID参数讲得很细。哪怕我后面用HAL库重写硬件接线方式、传感器供电、电机驱动芯片的PWM输入引脚这些完全不用动。说白了我只需要解决标准库到HAL库的代码翻译问题不需要重新设计整个控制方案这大大降低了风险。1.2 标准库与HAL库的核心差异标准库和HAL库在处理外设寄存器时的思路完全不同。标准库是直接把寄存器操作封装成函数比如GPIO_SetBits、TIM_SetCompare1这些你用的时候会很清楚当前在操作哪个寄存器。HAL库则是在外设之上再加了一层用一个句柄结构体来管理外设初始化时还要绑定底层的MSP回调函数。以GPIO为例标准库配置一个引脚的模式是这样的GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure);而HAL库里面长这样GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_8; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct);表面看差别不大但HAL库在调用HAL_GPIO_Init之前实际上还要设置这个引脚对应的AFIO复用映射这在标准库里面是用GPIO_PinRemapConfig或者直接查手册配置的。如果你从标准库项目搬代码最容易漏的就是AFIO这一层因为初始化结构体里根本没体现出来。这里我给出的经验是既然用了HAL库就老老实实用CubeMX生成初始化代码不要手动改寄存器去硬凑否则后面查问题会非常痛苦。CubeMX帮你把所有引脚的复用关系都映射好了这正是HAL库的最大优势。1.3 硬件方案与引脚分配说明我的硬件配置沿用了原版方案主控为STM32F103C8T6传感器是MPU6050电机驱动为TB6612两个带编码器的N20电机。引脚分配上我在CubeMX里这样规划MPU6050接I2C1SCL为PB6SDA为PB7电机1的PWM用TIM2的CH1PA0方向控制用PA2、PA3电机2的PWM用TIM2的CH2PA1方向控制用PA4、PA5左编码器接TIM3的CH1、CH2PA6、PA7右编码器接TIM4的CH1、CH2PB6、PB7等等这里有个冲突TIM4的编码器通道是PB6、PB7但I2C1也用这两个引脚放在同一块最小系统板上肯定打架。所以我在实际接线时把MPU6050改到了I2C2也就是PB10、PB11这样才把引脚冲突避开。这也顺带提醒大家做平衡车项目的引脚规划一定要把编码器接口放第一位因为它必须是硬件定时器的输入捕获通道不能随便挪。随后再分配PWM通道最后才是I2C这种可以用软件模拟的外设。2. HAL库重写平衡车核心模块这一章是整篇博文的重头戏。平衡车代码拆开来看其实就那么几块时钟配置、电机PWM输出、编码器读取、MPU6050传感器数据读取、姿态解算、串级PID控制。HAL库重写过程中最折腾人的就是外设初始化方式和中断回调机制这些细节。2.1 时钟系统配置与SysTick优先级处理HAL库有个特点它的HAL_Init函数会默认把SysTick中断配置成最高优先级这个优先级是TICK_INT_PRIORITY在stm32f1xx_hal_conf.h这个文件里用宏定义控制。平衡车这种对实时性要求很高的项目控制中断最好放在定时器里面而且优先级要高于SysTick。但问题在于如果SysTick优先级最高你的定时器中断再怎么设计都会被打断延时函数反而占了上风。我在项目里直接改了两个地方把TICK_INT_PRIORITY调低到3控制定时器中断优先级设为1。这样SysTick的HAL_Delay照常工作也不会干扰2毫秒控制周期的稳定性。CubeMX生成的时钟配置长这样void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct {0}; RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL RCC_PLL_MUL9; if (HAL_RCC_ClockConfig(RCC_ClkInitStruct, FLASH_LATENCY_2) ! HAL_OK) { Error_Handler(); } }这里要注意几个点STM32F103C8T6的Flash容量是64KB延时周期必须设成FLASH_LATENCY_2也就是两个等待周期因为在72MHz主频下Flash访问速度跟不上等待周期设少了程序会随机跑飞。另外HSE_PREDIV_DIV1表示不分频PLL倍频倍到98MHz的外部晶振进来经过PLL变成72MHz这也是F103最经典的超频配置。如果你板子上用的是HSE 8MHz这组配置直接抄就行但如果是HSI内部时钟或者晶振频率不同一定要回头改。我有块板子用的12MHz晶振当时忘改PLL倍频结果主频跑到108MHz串口打印全乱码差点怀疑人生。2.2 GPIO初始化与方向控制引脚移植GPIO这块看起来简单但引脚多、方向控制逻辑复杂容易写乱。平衡车里GPIO除了控制PWM方向还要驱动蜂鸣器、LED指示灯、按键等。我把所有方向引脚统一放在一个文件里管理用HAL库的HAL_GPIO_TogglePin配合HAL_GPIO_WritePin控制。比如电机前进后退标准库通常直接写GPIO_SetBits(GPIOA, GPIO_Pin_2)HAL库对应的是HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET)。我发现铁头山羊源代码里有一个Motor_SetSpeed函数里面会根据目标速度的正负值去判断方向这个逻辑移植过来完全不用改只需要把底层寄存器操作换成HAL库API就行。贴一段我当时重写的方向控制代码void Motor_SetSpeed(int16_t speed_right, int16_t speed_left) { if (speed_right 0) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET); speed_right -speed_right; } else { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET); } if (speed_left 0) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); speed_left -speed_left; } else { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); } __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, speed_right); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_2, speed_left); }这段代码逻辑不复杂核心就是先把方向置好然后把速度值当作PWM比较值写入定时器通道。有一点值得注意HAL库提供了__HAL_TIM_SET_COMPARE这个宏它比调用HAL_TIM_PWM_Start之后再频繁设置比较值要高效得多因为它直接操作寄存器不经过函数调用层级。2.3 定时器PWM配置与输出限幅电机驱动的PWM频率我用了16kHz这个频率对大多数减速电机来说是合适的听不到人耳可感的啸叫驱动芯片也不会过热。CubeMX里配置TIM2时PWM频率的计算公式是PWM_Freq TIM_CLK / (PSC 1) / (ARR 1)我的TIM2挂载在APB1上APB1时钟是36MHz定时器时钟经过倍频后是72MHz。我配置了Prescaler为0Period为4499这样算出来就是72000000 / 1 / 4500 16000 Hz也就是16kHz。PWM的占空比通过__HAL_TIM_SET_COMPARE设置比较值范围就是0到4499。铁头山羊原版代码里PID输出直接是0到1000的归一化值标准库做法是用TIM_SetCompare直接把0到1000塞进比较寄存器。但HAL库里ARR配置成了4499如果直接把PID输出塞进去占空比会小很多车的动力会严重不足听起来电机转得很温柔但推不动车。所以我加了一层缩放uint32_t pwm_compare (uint32_t)(pid_output * 4.5f);还要在输出前做限幅防止PID震荡的时候把比较值写超出ARR范围导致波形异常if (pwm_compare 4499) pwm_compare 4499;这个环节是很多新手容易忽略的PID调参调半天车就是不走最后发现是占空比映射错了。我用过一段时间后还是觉得在代码里统一用0到1000作为PID输出标度最后做一次比例映射这样清晰很多。2.4 编码器接口从标准库到HAL库的迁移编码器是平衡车反馈环节里最关键的部分测速不准的话速度环完全没法正常工作。F103的定时器有编码器模式可以直接读取正交编码信号不需要外部中断去数脉冲效率高得多。标准库的编码器初始化一般在TIM_EncoderInterfaceConfig这个函数里完成代码看起来不多但HAL库这边初始化路径完全不同。如果你用CubeMX可以直接把TIM3和TIM4的Combined Channels设置成Encoder Mode然后处理完Channel Polarity这些参数。我贴一段用HAL库手动配置编码器的代码void Encoder_Init(void) { TIM_Encoder_InitTypeDef sEncoderConfig {0}; htim3.Instance TIM3; htim3.Init.Prescaler 0; htim3.Init.CounterMode TIM_COUNTERMODE_UP; htim3.Init.Period 65535; htim3.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim3.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_DISABLE; HAL_TIM_Encoder_Init(htim3, sEncoderConfig); }使用HAL库的编码器驱动有一个我从标准库迁移过程中踩到的坑就是计数方向的问题。F103的编码器模式计数方向对正反转是自动处理的溢出行为和普通计数器不同在正转溢出和反转溢出时计数器会从0跳到65535或者反过来。我一开始直接读取TIM3的CNT寄存器想办法把它转成int16结果经常出现速度突跳。正确做法是这样的int16_t encoder_value (int16_t)(htim3.Instance-CNT);先把CNT强转成int16_t让编译器帮忙处理溢出回绕然后再把CNT清零继续计数。我在2毫秒控制中断里读取编码器每次都执行CNT清零这样读出来的值就是周期内的增量脉冲数直接代表电机转速。2.5 MPU6050数据读取与HAL库I2C驱动这估计是整个项目里最让人头疼的地方。F103的硬件I2C本身没有网上传的那么不堪但HAL库的I2C驱动默认带超时机制如果不注意配置很容易卡死在HAL_I2C_Mem_Read上车体一翻就死机。我最终选择的方案是MPU6050接在I2C2上CubeMX里把I2C2配成标准模式100kHz。读取加速度和角速度数据不需要全部寄存器的数据都读只需要读陀螺仪和加速度计各自的高字节和低字节共6个寄存器。核心读取代码uint8_t Read_Data[6]; HAL_StatusTypeDef status HAL_I2C_Mem_Read( hi2c2, MPU6050_ADDR 1, MPU6050_ACCEL_XOUT_H, I2C_MEMADD_SIZE_8BIT, Read_Data, 6, 100);这里地址必须左移一位因为HAL库使用的是8位地址格式而MPU6050的原始地址是7位地址0x68左移后变成0xD0。如果直接传0x68通信大概率失败。后来我为了让程序更健壮还加了重试机制当读取失败时重新初始化I2C并等待一段时间再读。实测下来用了重试机制之后就再也没发生过长时间卡死的问题。2.6 姿态解算代码的保留与迁移姿态解算部分和库的关联度其实不大因为互补滤波、四元数解算这些都是纯数学运算不依赖任何外设API。我直接把铁头山羊代码里的姿态解算函数搬过来用只需要调整一下数据类型。这里我需要多提一句HAL库环境下HAL_GPIO_EXTI_Callback这类中断回调函数名是固定的不能在中断服务函数里直接写自定义名称。MPU6050的DRDY引脚如果配置成外部中断那么中断处理逻辑只能写在HAL_GPIO_EXTI_Callback里面或者自己处理EXTI中断向量否则IRQHandler会重复定义导致编译错误。我实际没有用MPU6050的外部中断因为数据更新频率和控制频率都在500Hz以内直接在定时器中断里读取传感器数据就够了完全不需要那么复杂。3. 完整实操流程与关键代码实现有了上面的模块拆分接下来就要讲具体怎么拼起来。我建议直接用CubeMX初始化工程再逐步把功能模块代码加进去。这个顺序很重要不要一上来就贴代码否则出了问题很难排查。3.1 基于CubeMX配置工程第一步是建立CubeMX工程芯片选择STM32F103C8Tx然后按下面几个步骤配置时钟配置是在RCC里把HSE设为Crystal/Ceramic Resonator然后Clock Configuration里把系统时钟调到72MHz。我之前的配置方式是直接把HCLK编辑成72CubeMX会自动把PLL参数算好。GPIO配置上PA0、PA1、PA6、PA7、PB6、PB7这些引脚的分配参照前文表格其中PA0和PA1是PWM输出剩余的是编码器输入。L298N或者TB6612的方向控制引脚就配成普通的GPIO_Output。定时器配置是Tim2设置成PWM Generation CH1和PWM Generation CH2Tim3和Tim4设置成Encoder Mode。如果CubeMX版本里没有直接显示Encoder Mode就把Combined Channels设置成Encoder Mode。I2C配置是把I2C2的Speed Mode设为Standard Mode100kHz即可。生成工程时选择ToolchainMDK-ARM然后在Project Settings里勾选Generated files里的Generate peripheral initialization as a pair of .c/.h files。这个选项很关键它会为每个外设单独生成一对.c和.h文件代码结构清晰很多。3.2 电机接口与PWM输出代码生成好工程后我先把电机控制封装成独立的Motor.c文件代码前文已经贴过。这里要补充一点用HAL库操作PWM比较值时我习惯直接操作寄存器而不经过HAL函数因为控制周期只有2毫秒中断里的代码越精简越好。__HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, motor_speed_right); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_2, motor_speed_left);而启动PWM输出则放在初始化时做一次就够了HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_2);PWM参数我直接用前文计算出来的16kHz。如果你的电机或驱动模块对频率有特殊要求可以自己按公式改但主频72MHz这个前提别动。3.3 平衡车核心控制代码解析我把控制主逻辑整理如下这一段是系统的心脏。控制周期由定时器6中断固定为2毫秒中断里依次做读取传感器、更新姿态角、执行PID计算、设置电机PWM。void TIM6_DAC_IRQHandler(void) { if (__HAL_TIM_GET_FLAG(htim6, TIM_FLAG_UPDATE) ! RESET) { __HAL_TIM_CLEAR_FLAG(htim6, TIM_FLAG_UPDATE); MPU6050_Read(); Get_Attitude(); Balance_PID(); Velocity_PID(); Turn_PID(); Motor_SetSpeed(motor_output_right, motor_output_left); } }这里有个细节HAL库里定时器中断服务函数是固定的函数名比如TIM6的DAC_IRQHandler名字是TIM6_DAC_IRQHandler你在里面判断更新标志位清除后执行控制逻辑。执行时间必须严格控制实测整个中断处理流程大约需要50微秒远小于2毫秒所以系统实时性没有问题。另外PID参数我参考了铁头山羊原版的初始值直立环Kp大概是28左右Kd大概0.6速度环Kp大概0.5Ki大概0.05。这些参数在HAL库移植后不需要改因为控制频率和控制算法没变。但是实际还得根据你的车体重心适当调整。直立环计算公式balance_output Kp_balance * angle Kd_balance * gyro_y;速度环计算公式velocity_output Kp_velocity * velocity_error Ki_velocity * integral_velocity; velocity_error target_speed - current_speed;转向环简单一点直接根据遥控器的转向值叠加一个PWM偏置就行。不过铁头山羊原版用了编码器差速形成闭环我也保留了这个逻辑左右轮速度方向相反时车身转动。3.4 参数调节与实测效果调参顺序是这种实时控制系统的关键。我先调直立环再调速度环最后调转向环。直立环的参数如果没调好后面都是白费。实际调的时候我把车体拿在手上轻轻前后倾斜观察电机响应方向。如果车往前倒电机应该往前转让轮子去追重心这个方向一定要对否则就是正反馈车会加速倒下。直立环的Kp先从小到大加加到车体能够自己在原地轻微振荡就差不多了然后加Kd降低振荡。铁头山羊原版的Kp是28Kd是0.6我在自己车上微调成了Kp30Kd0.65因为我的车体重心略高需要稍大一点的回正力度。速度环调试的时候我先把直立环Kp和Kd固定好然后把车放地上。速度环Kp是从0.3开始加加到手推它自己能恢复到匀速状态为止。Ki主要用来消除静态误差但我发现幅度太大容易震荡所以Ki只给了0.03比原版小。实测下来平衡车在水平地面上能稳定站立抗干扰能力不错用手推一下能在1秒内恢复到直立状态。整体控制周期2毫秒没出现明显的滞后感。4. 移植过程典型问题排查这次移植过程中我遇到了好几个问题有些是HAL库特有的有些是标准库迁移时容易忽略的。我把这些问题整理成一个速查表方便大家调试的时候对照排查。问题现象可能原因解决方案编译报错IRQHandler重复定义中断回调函数命名错误检查HAL库版本对应的IRQHandler函数名I2C卡死程序跑飞硬件I2C忙超时配置I2C超时重试机制必要时软件I2C兜底电机不转或动力弱PWM比较值没有按ARR缩放将PID输出映射到0到4499范围编码器计数突变溢出方向处理不当读取时强制int16转换并每次清零车体高频振荡直立环Kd过小或控制周期不稳定调整Kd检查是否有长耗时函数进入中断上电后随机死机Flash等待周期配置错误确认FLASH_LATENCY宏为FLASH_LATENCY_2陀螺仪零漂严重MPU6050上电后未稳定或未校准初始化后延时1秒再读取数据做零偏补偿4.1 HAL库中断优先级与SysTick冲突这个问题我前文提过值得再单独强调一次。HAL库的HAL_Delay依赖SysTick而SysTick中断的优先级默认最高。如果你的控制定时器中断优先级设置得比SysTick低那么控制中断会不断被SysTick打断轻则控制周期抖动重则平衡效果大打折扣。我在stm32f1xx_hal_conf.h里把TICK_INT_PRIORITY改成了3在main.c里把定时器6中断优先级设为1。这个设置组合稳定运行很久了控制中断基本不受干扰。排查优先级问题有个土办法在控制中断里用IO翻转示波器看频率如果波形抖动明显或者偶尔出现双脉冲说明有中断干扰。4.2 HAL库I2C死锁的终极对策HAL库的I2C如果发生通信错误状态位会卡住。我加了重试函数但即便如此MPU6050偶尔还是会出现读取失败的情况特别是在电机启动瞬间因为电流冲击会造成电源毛刺I2C时序被破坏。我给I2C总线加了定时器中断里多次重试的机制如果连续失败3次就重新执行I2C设备初始化。反正MPU6050初始化就几行代码不影响大局。如果真的是硬件I2C不给力直接换成软件模拟I2C也是一种思路。标准库版本的铁头山羊代码本来就是软件I2C移植到HAL库里可以完全保留。不过我在实际项目里硬件I2C配合重试已经够用了就没有再切回软件方式。4.3 编码器方向与几何安装问题编码器读数方向反了是最容易被忽略但危害很大的问题。我装完轮子后直接把编码器值接入速度环发现车体转了一圈但电机来回抖动说明速度反馈方向不对PID正反馈了。解决方式是先使能编码器手动转动左电机的前进方向打印出来的编码器值应该为正数如果为负就把定时器的Channel Polarity翻转一下sEncoderConfig.IC1Polarity TIM_ICPOLARITY_RISING;实际调试时不要只靠改代码猜方向我会写个极简的测试程序把编码器原始值直接打印到串口转电机看数值正负和大小这样最快定位。4.4 电源问题导致的复位与看门狗处理平衡车经常遇到的现象是电机急加速时系统突然复位串口打印不断输出初始化信息。这种情况通常是电源跌落导致芯片掉电复位。我在电源输入端并联了两颗大容量电容一颗470uF电解电容一颗100nF陶瓷电容给电机驱动部分单独用了一组电容。同时把HAL库里的HAL_Delay延时代码检查了一遍避免电机高负载时主控芯片还执行耗时过长的操作从根源减少功耗波动。如果你用了窗口看门狗也要注意一个细节在电机启动瞬间主循环里如果有耗时操作可能会导致喂狗不及时复位。这个问题的排查就要靠逻辑分析仪看复位引脚电平了如果确实频繁复位考虑把看门狗超时时间适当放宽。4.5 陀螺仪数据抖动与滤波处理MPU6050刚上电时数据会有明显的零漂表现为角度缓慢漂移甚至不倾斜车体角度已经变了。我做了两件事第一是在初始化后延时一秒钟再开始读数据等传感器稳定第二是在直立环读取角度时对陀螺仪的零偏值做动态校准。实际运行中互补滤波的效果已经很好了不需要上卡尔曼滤波。铁头山羊原版用的互补滤波系数好像是0.98也就是加速度角度和陀螺仪积分的加权比重这个参数我保留了因为从标准库换到HAL库不影响控制算法。5. 移植后的调参心得与技巧整个项目做完后我自己对标准库和HAL库的迁移有了更深的理解。很多人觉得HAL库性能不如标准库但实际只要注意几个性能敏感点HAL库完全可以用于实时控制项目。第一在控制中断里尽量用寄存器宏操作不要用HAL_TIM_PWM_Start这种函数频繁调用。我在HAL库里保留了__HAL_TIM_SET_COMPARE来写PWM速度上和寄存器操作完全一样。第二能用CubeMX配置的功能就不要手写代码这样代码可读性高很多。特别是GPIO复用、定时器模式、I2C时序这些容易出错的地方可视化配置比手动写寄存器靠谱得多。第三调参前一定要先确认底层链路是否正常。我花了两个晚上才意识到车立不起来不是PID参数问题而是编码器方向反了轮子速度反馈全乱了。建议每次调参前先做几个基础测试编码器是否正常计数、PWM输出是否线性、MPU6050角度是否平滑。最后再说一个小技巧HAL库移植时保留原有的printf重定向到串口打印非常有用。不管是在标准库还是在HAL库把fputc函数重定向后就能用串口助手实时查看角度、速度、PID输出这些关键变量。我用了一个简单的上位机波形软件直接看到角度跟随效果整个调试效率提升了好几倍。后续想扩展的话这个HAL库版本的代码还可以很自然地接进蓝牙模块做遥控控制或者加OLED显示实时数据又或者升级成卡尔曼滤波获得更好的动态响应。从标准库迁移出来的这套底子已经够稳后面基本上就是加功能的事情了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询