博客专栏

EEPW首页>博客> 见鬼!PWM 没有输出和串口有啥关系?

见鬼!PWM 没有输出和串口有啥关系?

发布人:鱼鹰谈单片机 时间:2021-04-16 来源:工程师 发布文章

鱼鹰在做一个项目时,曾经遇到一个问题,8 路 PWM 输出,有一个高级定时器死活无法输出PWM,另一个高级定时器却可以顺利输出,初始化配置完全是一样的。

根据鱼鹰的经验,定时器没有输出有几个方面:

1、如果通过中断翻转电平输出PWM,那么需要检查是否进入中断(检查中断控制器是否开启中断,外设相应中断是否开启)。

2、如果使用 PWM模式,一般问题出在 IO复用功能和重映射上。如果没有使用 IO的复用功能,那么它是不可能被定时器外设所驱动的。而如果你的 IO不是该定时器默认的输出 IO,那么就需要进行重映射。

而STM32F1 和 STM32F4 的重映射机制是不一样的。

4.png

可以看到 F4的重映射比较简单,直接和 GPIO绑定,不需要另外开启时钟,并且我们可以从该函数的参数表中直接找到我们想要的对应 IO复用功能(上面的代码代表 GPIOD_12的 TIM4 复用功能)。

5.png

但是 F1 分为部分映射,全映射,还可能有部分映射2……

6.png

没有参考手册,你根本不知道这些映射对应的到底是哪个引脚。

此时,我们打开相应参考手册,找到 GPIO那一章节,AFIO 小节,找到定时器部分,你可以看到所有定时器的IO映射关系。

7.png

从这张图,我们可以得到以下几点信息:

1)如果不开启重映射(没有重映射),定时器2默认输出 IO为:PA0、PA1、PA2、PA3。 2)部分重映射1 :PA15、PB3、PA2、PA3。 3)部分重映射2:PA0、PA1、PB10、PB11。 4)完全重映射:PA15、PB3、PB10、PB11。

从中我们也可以看到另一个坑,那就是可能端口换了,而你相应的时钟并没有打开,导致初始化配置失败,最终导致无法输出。而使用 PB3 时必须禁用部分 JTAG引脚才行。

而在重映射上,鱼鹰也确实踩了一个坑,到现在我也没明白为什么。

当时在程序开始关闭了部分 JTAG引脚功能,然后再初始化定时器,并且开启相应重映射,最终还是没有任何输出,鱼鹰甚至直接查看最终的 AFIO寄存器的值,但情况就是配置的值一样,但还是无法输出,所幸的是,当我把复用功能在禁用部分 JTAG引脚后再开启所有定时器的重映射功能,发现能用了……

3、GPIO 的时钟没有打开,或者打开错误。

RCC_APB1PeriphClockCmd(RCC_APB2Periph_GPIOA ENABLE);

比如像这样,用 APB1的函数打开 APB2外设的时钟,当然会出现问题,当然这种坑还是蛮容易检查出来的

4、高级定时器的坑:

要想高级定时器输出脉冲,必须在初始化后增加一条语句:

TIM_CtrlPWMOutputs(TIM8, ENABLE);

没有这个函数,定时器是不可能输出脉冲的。

毕竟是高级定时器,很傲娇,也很任性。

而因为它特殊性,鱼鹰也是在这里载了一个大跟头,这也是鱼鹰为什么要写这篇笔记的原因了。

定时器1 和定时器 8 同属于高级定时器,但定时器 8 可以正常输出,而定时器 1 却没法输出,这是怎么回事?

为了解决这个问题,鱼鹰专门把定时器初始化部分封装成了一个函数,只留一个定时器作为参数部分:

8.png

但最终的结果还是没法输出,这不应该的啊,都是一样的函数,一样的配置,怎么一个成功,一个失败。

认真检查了初始化函数的每一条语句,参数并没有任何问题。

那到底是什么问题导致的?

最终鱼鹰只能上网查找相关问题了,有可能就有前辈踩过这种坑呢。

经过多方查找,鱼鹰尝试了各种办法也没有效果,最终死马当活马医的试了一个完全没有道理的可能:把串口初始化函数屏蔽了。

9.png

试了之后,发现定时器真的有输出了。

那么这是怎么一回事?为什么串口还能干扰到定时器的输出。

这个时候就要说一说我们的栈了(关于栈,鱼鹰写过一篇笔记《今天,你的栈溢出了吗?》)。

很多关于栈的话题,基本都是栈溢出,但事实上,还有一个容易忽略的话题是,栈的值不确定。

我们都知道,函数进入时会进行压栈操作(用于保持寄存器的值),同时如果函数有局部变量,也可能会从栈中申请空间。

10.png

因为局部变量用完即毁,不会占用我们宝贵的 RAM资源,所以很多时候我们会选择使用局部变量。

而大部分网上参考例程在使用局部变量时并不规范,导致问题的发生。

现在鱼鹰解释一下为什么串口函数会影响高级定时器的输出,而其他普通定时器并没有受影响。

当串口函数执行时,使用的栈比较大,而在定时器函数执行时,刚好使用了这部分已被修改的栈空间,并且使用时没有初始化它,导致出现了问题。

11.png

那为什么屏蔽了串口就没有问题呢?

那是因为单片机开始运行时,__main 函数会将栈空间全部清零(在运行到main前完成该工作),如果不运行串口函数,那么栈中的脏数据就不会很多,那么定时器函数的局部变量即使不初始化,也可认为就是 0,而这正是定时器需要的默认值。

12.png

那么为什么普通定时器不受影响呢?拿 TIM_TimeBaseInitTypeDef 结构体来举例。

13.png

基本定时器一般会初始化这几个变量,但是你查看该结构体的定义时你会发现:

14.png

该结构体还有一个变量是专为高级定时器准备的,你并没有对它进行初始化,此时它可能是任何值,并会直接赋值给高级定时器的寄存器中。

而关于输出的结构体 TIM_OCInitTypeDef 更是如此,很多变量在基本定时器中不会使用,却会在高级定时器中影响它的输出功能。

所以为了解决这个问题,有两个办法:

1、使用库函数提前初始化局部变量:

15.png

这样后续就不用关心不需要的变量了。

2、直接在初始化时,将所有的结构体成员变量都初始化一遍,确定没有任何一个变量遗漏:

16.png

鱼鹰推荐第二种办法,这样高级定时器和普通定时器的代码可以统一,也能更直观的看出函数提供的功能。

当然,两种方法同时使用也是没有任何问题的。

以上就是鱼鹰踩的坑,希望对各位道友有所帮助。咱们下期再见!

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

接地电阻相关文章:接地电阻测试方法




关键词:PWM

相关推荐

技术专区

关闭