【嵌入式开发问题汇总】程序篇

此文是我很久之前的一个计划,目的是让大家可以在别人的错误中有所收获。(最新更新日期:2020/07/17)

写下我和实验室小伙伴以及一些网友遇到的问题和分析。大家可以遇到问题也可以在博文下留言。当然,本人能力有限,错漏之处请直接提出。希望这篇博文能为所有喜欢嵌入式的朋友答疑解惑!

本文中,如没有特殊说明,描述的都是STM32系列单片机相关问题。

相关博文:【嵌入式开发问题汇总】硬件篇 (持续更新中...)


目录

问题1:定时器PWM——多个定时器的PWM频率不一致

问题2:定时器PWM——高级定时器(TIM1/TIM8)不输出PWM

问题3:定时器PWM——频率和计算值不一致

问题4:定时器中断——中断无法产生

问题5:定时器中断——中断代码执行异常/开启中断后主程序异常/程序死机

问题6:定时器中断——中断代码执行一次之后失效

问题7:正交编码器不工作/数据波动

问题8:代码下载后不运行/重新上电后才执行

问题9:串口连续发送错误

问题10:串口不能发送

问题11:串口发送第一字节接收两次


问题1:定时器PWM——多个定时器的PWM频率不一致

描述:同样的定时器预分频系数(PSC)和重装载值(ARR)配置下的PWM频率不一致(差了一倍)

分析:出现这种问题,一般是没有意识到高级定时器与一般定时器的差别,高级定时器所使用的的时钟和一般定时器不同。在STM32F10xx系列单片机中,TIM1、TIM8是高级定时器(小容量芯片没有TIM8,比如STM32F103Cxxx)。在STM32F4xx系列单片机中,多了TIM9/TIM10/TIM11定时器(这些不是高级定时器但是也使用APB2时钟)。定时器使用外设时钟APB,但是高级定时器(以及F4xx系列中的TIM9/TIM10/TIM11通用定时器)使用APB2时钟,其他的定时器使用APB1时钟。一般来说,APB2时钟频率是APB1的两倍。所以输出PWM时,要根据定时器所使用的时钟的频率计算分频数PSC与重装载值ARR。

STM32F1xx中定时器所使用的的外设时钟APB
STM32F4xx中定时器所使用的的外设时钟APB

问题2:定时器PWM——高级定时器(TIM1/TIM8)不输出PWM

描述:按照正常的PWM配置与GPIO配置后,TIM1/TIM8没有输出PWM波形。

分析:高级定时器之所以高级,他有互补输出PWM的功能。这个功能有一个寄存器叫BDTR,高级定时器通过次寄存器完成互补PWM的死区控制。我们在使用普通的PWM输出时,需要关闭死区控制,单打开PWM输出的使能(BDTR寄存器的最高位)即可。所以需要在初始化完成之后加上以下代码(以TIM1为例):

寄存器版本:

TIM1->BDTR |= 0x8000;

库函数版本:

TIM_CtrlPWMOutputs(TIM1, ENABLE);

问题3:定时器PWM——频率和计算值不一致

描述:已知定时器所用的外设时钟APB频率并设置好PWM的预分频PSC和重装载值ARR之后,发现输出的PWM频率与计算所得不一致。

分析:新手长犯的错误,PWM频率越高问题越多。这个就是寄存器的配置问题,我举个例子大家就懂了:时钟频率72MHz,要到的1kHz的PWM,可以这样设置(PSC=72-1;ARR=1000-1),给这俩寄存器的数值是计算值减一,这在手册里说的很清楚。定时器PWM的频率计算公式为:

f_{PWM}=\frac{f_{APB}}{\left ( PSC-1 \right )\left ( ARR-1 \right )}

问题4:定时器中断——中断无法产生

描述:配置好定时器中断之后,发现程序运行时无法进入中断

分析:只要定时器中断初始化的配置正确,就能进入中断。这里列出几个定时器中断配置时容易忽略的问题:

(1)中断使能未开启,一般情况下,我们使用的中断时间为计数器溢出时间IT,检查初始化中是否有以下代码(库函数):

TIM_ITConfig(TIM7,TIM_IT_Update,ENABLE);//允许更新中断

(2)中断入口函数设置不正确,我曾经犯过这个问题,问题非常隐秘,害我一顿好找:

大小写错误(“H”小写了)
拼写错误(少了"l")
正确的中断入口函数

以上的错误其实会看汇编指令的话很容易发现问题,在触发中断时,代码死在在汇编指令"B   ."上,B 是无条件跳转指令,一个点“.”表示没有找到跳转的位置(正常情况下应该跳转到正确的中断入口地址).

问题5:定时器中断——中断代码执行异常/开启中断后主程序异常/程序死机

描述:定时器中断可以运行,但是一旦打开定时器中断,会发现中断代码为未执行完/主程序出现卡顿或异常/程序运行一段时间后死机。

分析:这种情况有几个可能的原因

(1)在对单片机性能要求不严格的情况下,我也会偏好使用基于Systick时钟的延时函数delay_ms()delay_us(),但往往就是这个函数引发了众多BUG。为什么会这样呢?主要是Systick时钟只有一个,当然同时也只能执行一个延时函数(如果对其做了基于操作系统的调度等等不在讨论范围内)。如果主函数中和定时器中断同时使用了基于Systick的延时函数,那么第一个触发的延时函数就会出现不可预料的错误,导致中断代码执行异常或主程序异常。最好的建议就是,不要在中断程序中使用基于Systick时钟的延时函数delay_ms()和delay_us(),中断是无法预料的代码段,在里面放置延时函数就是为自己埋雷,如果一定要在中断中使用延时函数,建议使用_NOP空指令或计数器延时。

(2)出现了“套娃错误”:触发中断之后,中断程序还没有执行完,新的中断又来了,如果发生在单个中断函数,只要将中断函数设置成阻塞式即可(在中断程序执行完成之后在开启中断)。如果发生在多个中断之间,则需要合理调度中断,设置合理的中断优先级。

问题6:定时器中断——中断代码执行一次之后失效

描述:中断执行了一次之后就不在被触发,即使已经满足触发条件。

分析:就我目前的经历看,只有一种可能,第一次退出中断之后没有清除中断标志位,所以再也进不去。这种情况在定时器,串口,SPI,DMA的配置中都会发生。大家写BUG的时候一定小心。

比如这个是清除定时器7溢出中断的标志位的函数

问题7:正交编码器不工作/数据波动

描述:使用正交编码器时,编码器不能正常工作,或者编码器在单向旋转时数据在一个固定数值波动

分析:首先要说的是,支持正交编码模式的定时器要看好了,不是所有的定时器都可以用作正交编码器计数的,在STM32F4xx中,只有TIM1、TIM2、TIM3、TIM4、TIM5、TIM8有正交编码器计数功能,也就是那几个具有硬件PWM功能的定时器,且一定要注意,编码器的AB两相,一定要接到定时器的第1,2通道,3,4通道不行哦!

如果是数据波动,先看数据趋势是否正确,如果只是计数出现毛刺,可以增加滤波(库函数中的初始化结构体):TIM_ICInitStructure.TIM_ICFilter = 5;

如果数据就是在几个值波动,建议检查编码器的电源是否正常供电。有些编码器的霍尔元件需要5-12V的电压在能操作工作。

问题8:代码下载后不运行/重新上电后才执行

描述:修改代码之后下载,一定要手动复位或者重新上电之后才会运行修改过后的代码。

分析:调试器一般都有下载完之后自动复位的选择,这里以ST-Link调试器为例:

点击魔术棒
进入调试器的设置(这里为ST-link)
在Flash下载中勾选复位运行

问题9:串口连续发送错误

描述:串口数据在单次发送是正常,连续发送时出现数据帧不完整或发送一部分之后停止发送

分析:这种问题一般出现在串口发送指令放在定时中断中或者循环体中,一般是两个数据帧之间没有足够的时间导致的。比如115200的波特率下,起始位,八位数据加一位停止位,一秒钟最多发送11520个字节,若使放在100Hz的中断中,一个数据帧最多只能发115字节。解决这个问题主要有两种办法,一种是修改发送频率,留够发送时间。另一种是建立消息栈,阻塞式发送,这种方案比较消耗内存,发的太快容易挤爆堆栈。

问题10:串口不能发送

描述:串口TX始终处于高电平

分析:正常的串口在空闲时刻是高电平(如图,发送数据为:0x5A,0xA5,0x00,0x00,0x00,0x00,6字节@115200bps,8IN1),

出现高电平,至少确定了初始化基本无问题。在STM32F1系列单片机上,需要确认所使用的的管脚是否需要重映射,具体方法参照我之前的博文,我遇到的一个问题是发送数据有非常小的波形,比3.3V略高一些,看起来像是纹波一样,但是直觉告诉我这个纹波是数字波形而且与我发送的数据相关,仔细排查后,发现我只发送数据,却常常竟然进入接受中断,后发现原来在别的代码中开启了空闲中断,串口一旦接受到空闲,则立即进入中断,导致频繁打断正常的发送时序。关闭空闲中断即可解决问题。

问题11:串口发送第一字节接收两次

描述:串口接收的数据中第一个字节被接受了两次。

分析:本问题在笔者使用串口空闲中断IDLE触发DMA接收时出现,使用示波器观察串口数据发送正确。最后检查发现是DMA搬运的数据长度和发送的数据长度不一致导致的,匹配后问题解决。


如果以上的问题与解决方案对你有帮助,请为我在右上角(PC端)/右下角(移动端App)点一下赞,本文持续更新中,可以收藏本文哦,谢谢!

如有问题,可以私信交流或直接在评论区留言!

猜你喜欢

转载自blog.csdn.net/ReadAir/article/details/105701930