本文隶属于AVR单片机教程系列。

上次我们用 delay 函数与 while 循环实现了一个LED的闪烁。这一次我们把所有LED加入进来,让它们依次闪烁,形成流水灯的效果。

开发板上有4个LED,我们可以用不多的语句把循环体直接描述出来(看看就行,不用敲):

  1. led_set(LED_RED , LED_ON);
  2. delay();
  3. led_set(LED_RED , LED_OFF);
  4. led_set(LED_YELLOW, LED_ON);
  5. delay();
  6. led_set(LED_YELLOW, LED_OFF);
  7. led_set(LED_GREEN , LED_ON);
  8. delay();
  9. led_set(LED_GREEN , LED_OFF);
  10. led_set(LED_BLUE , LED_ON);
  11. delay();
  12. led_set(LED_BLUE , LED_OFF);

但是如果LED多了怎么办?这样写一点都不优雅。一个更合理的方法是,用数组把LED存储起来,在无限循环中套循环,对LED进行遍历。代码如下:

  1. #include <ee1/led.h>
  2. #include <ee1/delay.h>
  3.  
  4. int main()
  5. {
  6. led_init();
  7. const uint8_t leds[LED_COUNT] = {LED_RED, LED_YELLOW, LED_GREEN, LED_BLUE};
  8. while ()
  9. {
  10. for (uint8_t i = ; i < LED_COUNT; i++)
  11. {
  12. led_set(leds[i], LED_ON);
  13. delay();
  14. led_set(leds[i], LED_OFF);
  15. }
  16. }
  17. }

在代码中,uint8_t 是一种无符号8位整数类型,定义在 <stdint.h> 中。相应地有 int8_t 表示带符号8位整数,以及 int16_t 、uint16_t 等,一直到64位。avr-gcc还提供了24位整型作为编译器扩展:__int24、 __uint24 ,但它们毕竟是编译器扩展,尽量别用;同时如果你的单片机程序中有16位整数搞不定的东西,那就应该考虑简化一下了。

我们用的单片机是8位机,指令只能处理8位整数,如果是16位,则需要多条指令进行组合。而C语言内置类型 int 在这个环境中是16位的。因此,为了节省空间、提升性能,当一个数可以用8位表示时,应该使用 int8_t 或 uint8_t 代替 int 。

讲了这么多,为什么LED可以用 uint8_t 表示呢?把光标移动到任一表示LED的宏上,右键,点击Goto Implementation,或按下快捷键Alt+G,你就能看到头文件中对这些宏的定义:

  1. #define LED_RED (uint8_t)(0)
  2. #define LED_YELLOW (uint8_t)(1)
  3. #define LED_GREEN (uint8_t)(2)
  4. #define LED_BLUE (uint8_t)(3)

同时,led_set 等函数都接受 uint8_t 类型的参数表示LED:

  1. void led_set(uint8_t _which, bool _on);
  2. void led_flip(uint8_t _which);

这些都能说明为什么用 uint8_t 就能保存一个表示LED的宏。至于我为什么选择用 uint8_t ,当然是因为刚才说过的性能因素。

顺便,我们可以发现表示LED状态的宏本质上就是 true 和 false ,表示LED的宏就是简单的0、1、2、3,因此程序中可以不用数组来存储LED,也不再需要任何宏,并且 true 和 false 也很容易对应到灯的亮和暗上:

  1. #include <ee1/ee.h>
  2.  
  3. int main(void)
  4. {
  5. led_init();
  6. uint8_t i = ;
  7. while ()
  8. {
  9. led_set(i % , true);
  10. delay();
  11. led_set(i % , false);
  12. i++;
  13. }
  14. }

第一行包含了头文件 ee.h ,相当于包含了库中所有的头文件。

这样的程序是不是更好看一些?实际上 LED_RED 等宏的作用都只有让程序变得直观,如果直接用数字表示更方便,也完全没有问题。

这里还用到一个小技巧来消除内层循环:在循环外设一个变量,每次循环让它加1,传入的参数由它本身改为它对4的模。

你可能会想如果 i 过大了怎么办?当 i 等于255时, i++ 会让 i 变为0,而此时传入函数的参数还是从3回到0,符合控制逻辑。

为什么程序没有 #include <stdbool.h> 就能直接使用 true 和 false ?因为你包含的头文件中用到了它们,这个头文件必定或直接或间接地包含了标准库头文件,因此你就可以直接用了。

还有一个你可能疑惑的地方是,流水灯的逻辑本应是关闭上一个灯,打开下一个灯,然后延时一段时间,为什么在程序中是打开、延时,再关闭?想一想你会发现没什么不对,不过是把循环体的界限移动了一步。

实际上,如果按前一种逻辑来写程序的话,循环体得写成这样:

  1. led_set((i + ) % , false);
  2. led_set(i % , true);
  3. delay();

这样虽然逻辑也正确,但反而更难理解。

你也许还有疑问:把上一个灯关闭了以后,程序还要运行一会才能打开下一个灯,会不会看起来有一段时间是没有灯亮的?观察一下你的开发板,你就知道不会,原因也很简单,关灯和开灯之间的时间是微秒级的,人眼根本无法察觉。

除了上述实现,流水灯还有一种效果,就是灯依次亮起,再依次关闭。留作今天的作业吧。

上一篇:闪烁LED

下一篇:烧写hex文件

AVR单片机教程——流水灯的更多相关文章

  1. AVR单片机教程——随机点亮LED

    之前我们做的闪烁LED和流水灯,灯效都是循环的.这次我们来尝试一些不一样的——每一次随机选择一个LED并点亮. 要实现随机的效果,我们要用C语言标准库中的相关设施: #define RAND_MAX ...

  2. AVR单片机教程——闪烁LED

    上次我们把LED点亮了.你可能已经试过把 LED_RED 换成其他灯,也可能已经用 led_on() 把所有LED一起点亮了.但是LED点亮以后,程序就退出了,之后LED一直没有暗,直到没有供电.这一 ...

  3. AVR单片机教程——数字输出

    从上一篇教程中我们了解到,按键与开关的输入本质上就是数字信号的读取.这一篇教程要讲的是,控制LED的原理是数字信号的输出.数字IO是单片机编程之有别于桌面编程的各项内容中最简单.最基础的. 在讲数字信 ...

  4. AVR单片机教程——数字输入

    我们已经学习了如何使用按键和拨动开关,不知你有没有好奇 button_down 和 switch_status 等函数是如何实现的.本篇教程带你一探究竟,让我们从按键的原理开始. 在原理图中,按键的符 ...

  5. AVR单片机教程——ADC

    ADC 计算机的世界是0和1的.单片机可以通过读取0和1来确定按键状态,也可以输出0和1来控制LED.即使是看起来不太0和1的PWM,好像可以输出0到5V之间的电压一样,达到0和1之间的效果,但本质上 ...

  6. AVR单片机教程——定时器中断

    本文隶属于AVR单片机教程系列.   中断,是单片机的精华. 中断基础 当一个事件发生时,CPU会停止当前执行的代码,转而处理这个事件,这就是一个中断.触发中断的事件成为中断源,处理事件的函数称为中断 ...

  7. AVR单片机教程——串口接收

    本文隶属于AVR单片机教程系列.   上一讲中,我们实现了单片机开发板向电脑传输数据.在这一讲中,我们将通过电脑向单片机发送指令,让单片机根据指令控制LED.这一次,两端的TX与RX需要交叉连接,单片 ...

  8. AVR单片机教程——PWM调光

    本文隶属于AVR单片机教程系列.   PWM 两位数码管的驱动方式是动态扫描,每一位都只有50%的时间是亮的,我们称这个数值为其占空比.让引脚输出高电平点亮LED,占空比就是100%. 在驱动数码管时 ...

  9. AVR单片机教程——DAC

    本文隶属于AVR单片机教程系列.   单片机的应用场景时常涉及到模拟信号.我们已经会使用ADC把模拟信号转换成数字信号,本讲中我们要学习使用DAC把数字信号转换成模拟信号.我们还将搭建一个简单的功率放 ...

随机推荐

  1. 2018-2019 20165226 Exp 8 Web基础

    2018-2019 20165226 Exp 8 Web基础 目录 一.实验内容说明及基础问题回答 二.实验过程 1.Web前端:HTML 2.Web前端j:avascipt 3.Web后端:MySQ ...

  2. iOS开发之如何在用户删除应用后保持一些数据

    在开发过程中我们有时候在用户删除时候保存一些信息在用户下次安装应用时候使用,这个时候我们可以使用剪切版UIPasteboard的FindUIPasteboard和钥匙串keychain的使用 剪切版剪 ...

  3. DELPHI给整个项目加编译开关

    DELPHI给整个项目加编译开关 project--options

  4. 从Windows命令行启动MySQL

    SERVER: 从Windows命令行启动MySQL 可以从命令行手动启动MySQL服务器.可以在任何版本的Windows中实现. 要想从命令行启动mysqld服务器,你应当启动控制台窗口(或“DOS ...

  5. sqlserver2016 kb补丁

    1. win2012r2 安装时 总是提示: 然后费了半天劲 下载下来又提示 找了一下 需要先安装这么一个补丁才可以 KB2919442 然后才能安装上 KB2919355 然后就可以正常安装了:

  6. Diamond types are not supported at language level '5‘

    当时,我问了下大神,他们问我是不是jdk问题.因为jdk8才支持这样的棱形写法.当时自己的jdk版本是jdk8,然后就奇怪了,最后我发现原来在Language level中调成了5.0 5.0不支持6 ...

  7. layui select 下拉框 级联 动态赋值 与获取选中值

    //下拉框必须在 class="layui-form" 里 不然监听事件没有作用 <div class="layui-form" > <div ...

  8. Qt编写气体安全管理系统20-控制器管理

    一.前言 控制器管理,主要就是对控制器进行添加删除和修改,其中包括编号.端口名称.控制器名称.控制器地址.控制器型号.探测器数量这几个字段,端口名称表示当前控制器所属哪个端口,一个系统中可以有好多个端 ...

  9. Asynchronous Streaming Request Processing in Spring MVC 4.2 + Spring Boot(SpringBoot中处理异步流请求 SpringMvc4.2以上)

    With the release of Spring 4.2 version, Three new classes have been introduced to handle Requests As ...

  10. 转Python 爬虫入门实战

    转自:https://www.cnblogs.com/babycomeon/p/11651888.html