目的:让单片机输出一段可调频率的脉冲,用于驱动步进电机。
方案:1、使用定时器溢出中断,定时中断一次,在中断通过判断来翻转IO口。
优点:实现比较简单,对硬件要求不高。
缺点:不适合高速脉冲输出,而且脉冲分辨率也很低。
2、使用PWM模式,通过改变ARR的值来改变脉冲周期,从而控制IO口反转。
优点:可以输出高速的脉冲。
缺点:一个定时器只能输出一路脉冲,脉冲数量不可设置。
3、使用定时器的输出比较模式,设置输出比较匹配时翻转IO口,并开启输出比较中断,
在中断中装载下一次比较值。
优点:可以输出高速脉冲,并且脉冲数量控制。
缺点:进入中断频繁,增加CPU负担。
4、使用定时器的输出比较模式,设置输出比较匹配时翻转IO口,不开启输出比较中断,
开启DMA模式。
优点:可以输出高速脉冲,并且脉冲数量控制。
缺点:需要预装载脉冲频率的值,占用空间多。
结论:由于本次需要多个步进电机同时工作,并且步进电机需要加减速启动,电机转动的圈数不多。决定使用方案4。
*我搞这个模式搞了三天,今天终于把这坑踩平了,把这点经验分享给大家,避免重复踩坑。
*网上百度找了好久,稀稀疏疏就一两篇文章有用到这个模式的,而且还不是HAL库的,这个模式对于高速脉冲的输出真的是好用。
好了,直接上配置和代码,以及需要避坑的地方。
1、CubeMX配置
1.1 基本设置
1、先开始进行时钟配置,如上图,一般选用外部晶振
2、配置下载方式,Serial Wire就是SWD下载
3、配置定时器,我现在只需要通道2的比较输出模式,如图配置,其他模式看简介。
1.2时钟配置
我是用的是STM32F103的芯片,系统时钟最高72MHZ,所以如图配置。
1.3 具体配置
1、用到定时器肯定要配置定时器,这里的配置是:72分频,向上计数,计数最大值65536,通道2的输出比较模式要反转IO口。
2、DMA配置如图所示,这里不用循环模式,用半字传输,因为定时器是十六位的,CR2寄存器也是十六位的,存储器的地址需要递增,而外设地址不需要地址。
3、最后配置NVIC,这个版本的CubeMX是强制配置DMA中断的,因为用DMA传输数据,所以定时器输出比较中断不需要配置。
2 代码
2.1使能
首先需要一个数组,然后往数组里面放数据,这个就是输出比较模式里CR2设置的值。第一个数为200,
第二个为400,第三个为800,第四个为1400,看出规律没有。
**第一个坑来了
程序运行完这些就使能定时器和DMA吧,刚开始我是这样写的
其实是可以的,后来点进去第一个函数HAL_TIM_OC_Start_DMA看看,发现里面有这么一段。
就知道第二个函数在第一个函数里面已经调用了,重复调用浪费时间。
2.2调试
既然开启了自动装载,那就仿真看看CR2的值是不是真的在改变吧。(当时我们公司没有示波器和逻辑分析仪这些东西)
**第二个坑报道
然后发现,一开始全速运行,CR2的值就马上变成这样子。。。
我在想,应该能看到变化才对,我想,是不是频率太快了,然后就把数组增大,使用6400个数据,大致算了下也要几秒钟的时间,后来仿真一看还是直接变成最后那个值。
后来各种搞,各种不行。今天上班来问隔壁公司借个示波器测测,一看,波形很对。上波形。
第二张波形图可以看到,后面还有无数个波形,而且波形比较大,这个是因为定时器一直没有关闭,所以这个脉冲周期就是定时器溢出周期的两倍(不知道为什么是两倍的同学看看比较输出模式的IO口翻转功能)。
2.3中断回调函数
第三个坑,神坑
因为DMA中断已经开启了,在是stm32f1xx_it.c函数里面有这些中断函数,找到这个定时器CH2的DMA中断函数。
然后我小心翼翼地点了进去,然后找回调函数
当时我惊呆了,hdma->XferCpltCallback(hdma);这个是啥玩意,点进去看居然是这样的。
这个写的是DMA传输完成中断回调函数,但是这玩意咋用,学了C语言这么多年,没见过能在结构体里面定义函数的。。。
不知道就百度吧,百度出一筐,看得云里雾里,最后一个热心的群友解答了我的问题,说这是一个指针,不是函数
实体,我个人就认为这只是用来指向某个具体函数的(大佬来解答一下,这个东西真的是第一次见到)。
看完还是不会用,然后试一下直接使用,下载,仿真,DMA进入了中断,但是没有进入这个回调函数,放弃(我是说再百度试试,毕竟搞不出来老板会扣我工资的)。
又百度到说DMA中断调用的其实是和普通中断调用一样的函数,怎么理解,例如不用DMA而用中断,触发中断调用的是A函数,那么用DMA,DMA完成中断也是调用A函数,哇,ST设计的HAL真厉害(这对于小白很好用)。然后我马上在
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim);这个函数失能定时器和DMA。发觉不好使,调试也进不去。
搞了一个多小时,想了想可以一步一步地仿真,看看程序到底进入了那个回调函数,发现TMD进入的是这个函数。如图。
好吧,那就用这个函数失能定时器和DMA,注意一下,这里的__weak是弱函数,也就是说你可以重新定义一个同名的函数,而不需要把他删除,程序就会执行你定义的函数。如图。
最终完成了,波形图。
最后说明一下,这个方式可以一个定时器输出4个频率可变的波形,而且数量可以控制,也不需要单片机频繁进入中断。但是会很耗内存,在波形不会因为其他因素而改变的情况下可以放在单片机的ROM,或者可以试试使用8位传输。