本节主要目的是:
1. 通过项目学编程:通过实现一个小小的项目功能,梳理编码思路;
2. 积累代码库:实现的这个小功能,后续可以不断使用并且在多处移植;
1.项目需求:
实现一个LED控制函数,可以控制LED点亮x毫秒,熄灭y毫秒,并重复z次;为了避免阻塞,不能采用任何delay。
2.实现方法1: 采用全局变量来实现
由于不能使用delay函数,我们只能按照上一节提到的方法,将LED分为两种状态:熄灭状态和点亮状态。
在熄灭状态时,设定倒计时时间y,在定时器中自减;自减为0后,点亮LED,并设定倒计时时间x,同样再定时器自减。如此反复z次,即可实现led按照指定占空比和指定次数来闪烁。
2.1 初始化全局变量on_time和off_time
这里必须用全局变量,因为这里的on_time和off_time会分别在led.c it.c和main.c中调用。
- 凡是必须在多个文件中使用的变量,必须采用全局变量。
- 全局变量建议在.c文件中定义,在.h文件中声明。这样需要用到这个全局变量的.c文件,直接通过包含头文件即可使用对应的全局变量。
下面介绍下具体步骤:
第一步:我们首先在led.c中,定义两个全局变量on_time和off_time;注意,这里类型是uint16_t,也就是两个字节长度的变量。另外还需要一个静态全局变量。
- 静态全局变量是指这个全局变量只能在文件内部使用,在文件外部无法使用。
- 不需要外部使用的全局变量,尽量采用静态方式,避免在其他地方被误修改,便于维护;
第二步:写一个函数,配置上面设置的静态全局变量 。
同样为了便于维护,对于静态全局变量,我们一般需要用set或者get函数,设置对应值,或者读取对应数值。
这里led_cnt是静态全局变量,我们必须用set_led_config方式修改。而led_on_time和led_off_time为全局变量,我们可以直接赋初值方式进行修改。
所有的数字都采用宏定义方式,这样便于后续统一修改on_time和off_time
需要被其他文件调用的全局变量,放在头文件中进行声明,这样通过查看头文件,就会了解有哪些全局变量需要查看和修改。
写完这个函数后,不要忘记在头文件中,更新对应的
- 宏定义;
- 外部变量;
- 外部函数;
第三步:在中断处理函数中,对刚才的on_time和off_time进行自减操作;
按照前面的配置,我们每隔1ms进入一次中断,也就是on_time和off_time每隔1ms,会自动减1,一直减到0为止。
第四步:在main.c中,增加set_led_config函数的调用,增加led_check函数的调用。
这样,就实现了开机时,设置led闪烁3次,每次闪烁开100ms,熄灭900ms的效果。
我们下载下去,发现启动后,LED确实亮了3次,但是还有一个问题:第三次LED点亮后,居然没有熄灭。
注意:我们的教程中,常常会记录这些BUG解决过程,因为在真实的开发过程中,调试和解决BUG往往是开发的必备技能,甚至说是核心技能。
代码中,在检查led_cnt的地方出了问题:
1.当led_cnt=1时,这时执行到了led_cnt--的地方,led_cnt会变为0;
2.下一次检查的时候,发现不符合条件(led_cnt>0),就会直接退出函数,而没有能够再次进入函数,执行led_off()语句。
如何解决这个问题呢?
很简单:在if(led_cnt>0)判断结束后,增加else if(led_cnt==0),这样就可以在最后,把led熄灭了。
如上面代码,增加一条判断调节,即可解决问题。
代码执行结果如上图,每次启动时候,就会LED点亮三次,点亮时间为100ms,熄灭时间为900ms;
如果现在需求变了,需要点亮200ms,熄灭800ms,怎么办呢?
由于我们坚持采用一切数字都用宏定义的方式,所以直接修改前面定义的宏,就可以完成这个功能变更。
纸上得来终觉浅,绝知此事要躬行!
马上下载测试代码,看看效果。
https://download.youkuaiyun.com/download/book_drabit/85926549
3.实现方法改进: 采用结构体变量实现
在GPIO初始化的第二节,我们讲到,可以跟着ST学编程,沿用ST常用的方式,进行初始化设置。具体可以看下面链接:
每节课都是一个项目 手把手用STM32打造联网气象站-3-STM32基础三件套-掌握GPIO初始化_可志嵌入式的博客-优快云博客
ST常用的初始化方式,我们再复习一下:
1. 创建结构体类型,把对应参数放入结构体类型中;
2. 用这个类型,创建结构体变量;
3. 初始化这个变量,完成参数设置。
这一套方法,在GPIO初始化,TIM初始化,NVIC初始化中,都有反复使用。
这套方法的好处是:通过定义结构体,将GPIO相关参数汇聚在一起,方便根据结构体类型,进行修改,提高代码的内聚性,避免代码松散。
接下来,我们就用类似方法,把LED初始化为结构体,并进行相应参数设置。
先复习一下ST教我们的写法,然后照葫芦画瓢写代码
第一步:在头文件中,定义Led_TypeDef;包含了全部LED的全部三个控制参数;
第二步:在.c文件中,用前面定义的结构体类型,定义led_ctl这个参数; c文件中完成定义后,需要在.h文件中进行声明,方便其他文件调用;
第三步:it.c中,对结构体变量进行自减;
第四步:更新led.c中函数,采用结构体变量来进行计数;
完成上面步骤之后,采用结构体类型定义的led_ctl就完成了。在其他代码中,如果需要重复使用这个led_ctl,只需要简单包含对应头文件即可。
纸上得来终觉浅,绝知此事要躬行!
马上下载测试代码,看看效果。
https://download.youkuaiyun.com/download/book_drabit/85928189
4.采用一个定时器,进行分钟定时
需求描述:在前面一期项目基础上,需要增加功能。
- 每分钟LED进行闪烁,闪烁占空比为:亮50ms,熄灭200ms;闪烁次数为开机之后度过的分钟数。比如5分钟就会闪烁5次,10分钟就会闪烁10次;
- 为了避免闪烁次数过多,超出10分钟后,改为按照小时闪烁,闪烁次数为开机后的小时数;
- 超出10小时候,就不再闪烁了;
根据上面需求,完成代码开发。
4.1需求分析
根据上面需求,我们首先需要实现按照分钟计数,然后还需要实现按照小时计数。其中计数的分钟值和小时值作为参数,传递到set_led_config中。
在主循环中,每到1分钟,我们设定点亮led,完成led闪烁;
当分钟数超出10之后,改为每小时点亮led,完成led闪烁;
完成上面需求分析后,我们还需要掌握一个重要的方法:求余数法。在C语言的宇宙中,常常会通过余数为零,来找到整数倍。例如:一小时为60分钟,我们将分钟数除以60,当余数为0时,就恰好为一小时时间。
4.2分步实现代码
需求梳理完毕,接着分步开始整代码。
第一步:在tim.h中,增加SysTimer这个结构体类型定义;同时,定义多个时间标志位;
第二步:在it.c中,SysCounter自增,当增加到60000毫秒时,设置1分钟标志位;达到60分钟时,设置1小时标志位;
第三步:在main.c中,检查SysTickFlags时间标志,根据分钟标志,实现分钟显示;
第四步:在main.c中,根据分钟显示,计算小时,并且将小时参数放入led_set_config中,实现小时显示。
根据上面步骤,我们就完美实现了项目需求。
4.3 重用代码
上面代码中,我们有大量可以重复使用的内容,在这里归纳一下:
1.用SysCounter和SysFlag来设置不同计时间隔时间;
2. 用%n==0方式,来取得不同倍数关系;
3. 用&SysTickFlag方式,获取整点标志位,获取之后,需要用SytemTimer.SysFlag&=(~)方式,将标志位清零;
这里需要补充一下C中的知识点:
C中,给x数值的第nbit设置为1,常用方式是:x|=(0x01)<<n;
同样,如果需要把x数值的第nbit设置为0,常用方式是x&=~(0x01)<<n;
上面这两种操作是需要熟练掌握的。
https://download.youkuaiyun.com/download/book_drabit/85932726
纸上得来终觉浅,绝知此事要躬行!
马上下载测试代码,看看效果。