本节内容我们设计一个电子秒表,通过本节的学习我们将制作出一款由Arduino控制的电子秒表。在设计制作之前,我们还是需要考虑一下我们设计的这款电子秒表需要有些什么功能?在这里,每人的需求肯定是不一样的,大家可以充分发挥想象,考虑一下你的电子秒表需要设计成一个什么样,需要有哪些功能?

1.需求分析

为了方便后续的讲解,在此设计的电子秒表将具有如下功能:
(1)至少需要由4位数码管显示秒表的计时信息,可以采用其他显示方式;
(2)秒表具有两个按键,一个作为开始/停止键,另一个作为清零键;
(3)能完成60分钟内的计时,可以考虑加入声光提示;
(4)*可以考虑加入定时功能。

2.实现方案设计

根据需求分析时的要求,现在开始进行电子秒表的方案设计。显示部分,在第三章中介绍过的TM1637数码管显示模块刚好能满足显示要求,所以我将采用此模块来实现电子秒表的显示功能。根据需求,需要两个按键模块来实现对电子秒表的控制操作。满足基本的需求其实只需要以上提到的硬件模块,但是还可以加入蜂鸣器或者LED灯模块作为声光提醒使用。

3.电路搭建

在电路搭建环节,通过方案设计中使用到的电子模块,再根据第二章中介绍的硬件连接方式,完成硬件电路的搭建。在此我们还是需要用到Arduino主板和Arduino扩展板,如果采用Arduino UNO+扩展板的方案搭建起来电路太厚的话,可以考虑采用Arduino nano主板+扩展板的方式来完成电路的搭建。为了方便后续编程演示,在此我们给出一个各个电子模块的引脚连接布局表仅供参考。具体如表4.2.1所示。
表4.2.1 电子秒表各电路模块引脚分布表

各电子模块 引脚 Arduino引脚 备注
按键开关1
(开始/暂停)
VCC VCC 供电引脚
GND GND 供电引脚
S/OUT D2 Arduino数字引脚
按键开关2
(清零)
VCC VCC 供电引脚
GND GND 供电引脚
S/OUT D3 Arduino数字引脚
TM1637显示模块 VCC VCC 供电引脚
GND GND 供电引脚
DIO D4 Arduino数字引脚
CLK D5 Arduino数字引脚

4.程序设计

根据需求和电路设计,接下来我们需要完成程序的设计。通过第四章第一节的学习我们不难发现,在程序设计时,不要把一个完整的程序写好了再去做测试的,而是在编写程序开始时,就应该思考完整的程序是由哪几部分组成,从最简单的开始,完成一部分就验证测试这一部分的程序,测试通过后再叠加下一部分程序上去,通过这样一部分一部分的叠加,最终完成整个程序的编写。通过这样分步的程序设计方式,大大降低了程序设计及调试的难度,可以快速的完成程序的设计及调试。下面我们就采用分步的方式完成程序的设计。

4.1 TM1637数码管显示程序设计

根据电子秒表的需求,我们需要创建几个变量来存储计时的时间数据,根据需求,需要能记录60分钟以内的计时,第一个变量肯定存储分钟的数据,第二变量用来存储秒的数据。在我们常见的秒表中,记录时间都是精确到秒后面两位,也就是百分之一秒位(10ms),所以还需要一个变量来存储百分之一秒位。
我们采用的TM1637数码管显示模块,只能显示四位数据,通过前面的分析,我们的3个变量一共有6位数据,没法完成显示。在此,我们可以通过程序设计做个处理,具体如下描述。在当分钟数据还在是零的时候(开始计时60秒之内),此时显示分钟数据是无意义的,所以,此时显示的数据可以是秒数据的2位加上百分之一秒数据的两位;当分钟数据不为零时(开始计时60秒后),此时百分之秒位数据在整体的计时数据中的作用就不是那么重要了,所以,此时我们可以显示分钟数据的两位加秒数据的两位。具体的程序设计如图4.2.1所示。
image.png
图 4.2.1 TM1637数码管显示程序

4.2 计时程序设计

在前面一步中我们实现了数据的显示,但是数据是不会变的,实际的秒表在开始计时后,是需要实时的显示计时时间的。接下来我们就来实现实时计时时间的显示。在第四章第一节中我们已经介绍过了Arduino 的定时器功能,Arduino的定时器属于芯片内部的一个硬件功能,我们通过程序方式去设置并启动后,定时器将会按照我们设置的方式去工作。在需要实现定时功能的使用场景或者是需要通过时间片段控制来协调处理的多任务系统中,采用定时器来实现是比较方便的一种解决办法,在以后的项目中可以考虑使用。
因为我们秒表的计时精度设计的是百分之一秒(10ms),所以我们设置定时器每10ms执行一次,每次定时器执行对百分之一秒变量进行加已处理,还需要进行分、秒、百分之一秒三个变量的关系进行计算并赋值,在此需要注意的是秒和毫秒之间的进制是1000(1秒=1000毫秒)。具体的程序设计如图4.2.2所示。
image.png
图 4.2.2 定时器实现定时程序
把以上程序成功上传到Arduino主板后,会发现计时时间在60秒以内时,百分之一的两位数字显示不正常,不会实现快速的跳动变化。这不是程序逻辑的问题,出现这个显示不正常的问题在于采用图形化方式编程,TM1637驱动显示的刷新速度不够造成。想要得到理想的显示效果可以采用Arduino IDE代码编程的方式,以下给出Arduino IDE编程方式的完整代码以便大家参考。

  1. #include <TimerOne.h>
  2. #include "TM1637.h"
  3. #define ON 1
  4. #define OFF 0
  5. int8_t TimeDisp[] = {0x00,0x00,0x00,0x00};
  6. unsigned char ClockPoint = 1;
  7. unsigned char Update;
  8. unsigned char halfsecond = 0;
  9. unsigned char second;
  10. unsigned char minute = 0;
  11. unsigned char hour = 12;
  12. #define CLK 5 //TM1637显示模块连接的引脚
  13. #define DIO 4
  14. TM1637 tm1637(CLK,DIO); //创建一个对象名字叫TM1637
  15. void setup()
  16. {
  17. tm1637.set(); //设置tm1637模块,默认就好
  18. tm1637.init(); //初始化tm1637模块,默认就好
  19. Timer1.initialize(10000); //定时器实现10ms的定时(10000us)
  20. Timer1.attachInterrupt(TimingISR); //定时器每10ms去执行一次TimingISR函数
  21. }
  22. void loop()
  23. {
  24. TimeUpdate(); //调用更新实现函数
  25. tm1637.display(TimeDisp); //调用显示数码管
  26. }
  27. /*定时器每10ms执行一次的函数体
  28. * 实现对时间变量minute(分)、hour(时)的计算
  29. * 计算完成的值通过数码管显示出来
  30. */
  31. void TimingISR()
  32. {
  33. halfsecond ++;
  34. if(halfsecond == 100){
  35. ClockPoint = (~ClockPoint) & 0x01;
  36. second ++;
  37. if(second == 60)
  38. {
  39. minute ++;
  40. if(minute == 60)
  41. {
  42. hour ++;
  43. if(hour == 24)hour = 0;
  44. minute = 0;
  45. }
  46. second = 0;
  47. }
  48. halfsecond = 0;
  49. }
  50. }
  51. //更新时间函数体
  52. void TimeUpdate(void)
  53. {
  54. if(ClockPoint)tm1637.point(POINT_ON);//判断是否显示时钟点,实现500ms闪烁一次
  55. else tm1637.point(POINT_OFF);
  56. if(minute>0){
  57. TimeDisp[0] = minute / 10; //把hour的十位和各位分离开来
  58. TimeDisp[1] = minute % 10;
  59. TimeDisp[2] = second / 10;//把minute的十位和各位分离开来
  60. TimeDisp[3] = second % 10;
  61. }
  62. else{
  63. TimeDisp[0] = second / 10; //把hour的十位和各位分离开来
  64. TimeDisp[1] = second % 10;
  65. TimeDisp[2] = halfsecond / 10;//把minute的十位和各位分离开来
  66. TimeDisp[3] = halfsecond % 10;
  67. }
  68. }

以上程序在Arduino IDE中编译时,可能会提示缺少TimerOne.h和TM1637.h库文件,此时需要在IDE的工具菜单中的“管理库”工具中添加这两个库文件。

4.3 按键功能程序设计

通过前面两步的程序设计,我们的秒表的基本功能是实现了,但是现在还有一个问题,那就是我们的秒表的计时时间是不受我们使用者控制的,只要给秒表供电就开始走时,而且还没法停止计时。在方案设计的时候我们就考虑过了这个问题,我们的设计是通过两个按键来实现对秒表的控制。接下来我们就需要设计按键的程序,让这两个按键完成对秒表计时开始/停止的控制,完成计时后还需要清除记录时间,这也是通过按键来实现的。
在程序设计时,有一个按键需要有两个功能(开始计时、停止计时),这样的按键叫做复用功能按键。在对复用功能按键进行程序设计时,通常情况下我们可以通过记录按键按下的次数来完成功能复用的程序设计。因为图形化显示方式没办法实现我们想要的显示效果,所以在此不再提供图形化编程的程序,请参考Arduino IDE的代码编程。具体参考程序如下所示。

  1. #include <TimerOne.h>
  2. #include "TM1637.h"
  3. #define ON 1
  4. #define OFF 0
  5. int8_t TimeDisp[] = {0x00,0x00,0x00,0x00};
  6. unsigned char ClockPoint = 1;
  7. unsigned char Update;
  8. unsigned char halfsecond = 0;
  9. unsigned char second;
  10. unsigned char minute = 0;
  11. unsigned char hour = 12;
  12. #define CLK 5 //TM1637显示模块连接的引脚
  13. #define DIO 4
  14. #define KEY_start 2 //开始暂停键 按键按下输出1
  15. #define KEY_clear 3 //清除键 按键按下输出1
  16. bool flag_KEY=0; //开始键按下次标志位
  17. TM1637 tm1637(CLK,DIO); //创建一个对象名字叫TM1637
  18. void setup()
  19. {
  20. tm1637.set(); //设置tm1637模块,默认就好
  21. tm1637.init(); //初始化tm1637模块,默认就好
  22. Timer1.initialize(10000); //定时器实现10ms的定时(10000us)
  23. Timer1.attachInterrupt(TimingISR); //定时器每500ms去执行一次TimingISR函数
  24. }
  25. void loop()
  26. {
  27. TimeUpdate(); //调用更新实现函数
  28. tm1637.display(TimeDisp); //调用显示数码管
  29. if(digitalRead(KEY_start)){ //检测按键是否按下 开始/暂停
  30. delay(20);
  31. if(digitalRead(KEY_start)){
  32. while(digitalRead(KEY_start)); //等待按键释放
  33. flag_KEY=~flag_KEY;
  34. }
  35. }
  36. if(digitalRead(KEY_clear)){ //检测按键是否按下 清除
  37. delay(20);
  38. if(digitalRead(KEY_start)){
  39. while(digitalRead(KEY_start)); //等待按键释放
  40. halfsecond=0; //清除计时
  41. second =0;
  42. minute = 0;
  43. }
  44. }
  45. }
  46. /*定时器每10ms执行一次的函数体
  47. * 实现对时间变量的计算
  48. * 计算完成的值通过数码管显示出来
  49. */
  50. void TimingISR()
  51. {
  52. if(flag_KEY){
  53. halfsecond ++;
  54. if(halfsecond == 100){
  55. ClockPoint = (~ClockPoint) & 0x01;
  56. second ++;
  57. if(second == 60)
  58. {
  59. minute ++;
  60. if(minute == 60)
  61. {
  62. hour ++;
  63. if(hour == 24)hour = 0;
  64. minute = 0;
  65. }
  66. second = 0;
  67. }
  68. halfsecond = 0;
  69. }
  70. }
  71. }
  72. //更新时间函数体
  73. void TimeUpdate(void)
  74. {
  75. if(ClockPoint)tm1637.point(POINT_ON);//判断是否显示时钟点,实现500ms闪烁一次
  76. else tm1637.point(POINT_OFF);
  77. if(minute>0){
  78. TimeDisp[0] = minute / 10; //把hour的十位和各位分离开来
  79. TimeDisp[1] = minute % 10;
  80. TimeDisp[2] = second / 10;//把minute的十位和各位分离开来
  81. TimeDisp[3] = second % 10;
  82. }
  83. else{
  84. TimeDisp[0] = second / 10; //把hour的十位和各位分离开来
  85. TimeDisp[1] = second % 10;
  86. TimeDisp[2] = halfsecond / 10;//把minute的十位和各位分离开来
  87. TimeDisp[3] = halfsecond % 10;
  88. }
  89. }

5.整体测试

根据设计方案,结合搭建好的硬件电路和和程序设计时分布编写的程序,进行一步一步测试。其实在程序设计时的分步设计过程就是一步一步的测试过程。测试到达最后一步就是完整的一个作品完成的时候。
测试完成以后,根据需求分析设计的一个秒表就完成了。到此如果觉得我们的作品不像一个秒表,那是因为目前我们只是实现了秒表的基本功能,并没有考虑到秒表的外观设计。在有条件的情况下,我们可以设计一个外壳,把组装好的硬件电路放进去,完成这一步后你将会看到一个不一样的效果。

6.完善改进

在需求分析中我们已经提到过,完成秒表设计后,我们还可以加入定时功能等等。具体的完善功能可以根据自己的创意想法进一步的完善。