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

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

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

 led_set(LED_RED   , LED_ON);
delay();
led_set(LED_RED , LED_OFF);
led_set(LED_YELLOW, LED_ON);
delay();
led_set(LED_YELLOW, LED_OFF);
led_set(LED_GREEN , LED_ON);
delay();
led_set(LED_GREEN , LED_OFF);
led_set(LED_BLUE , LED_ON);
delay();
led_set(LED_BLUE , LED_OFF);

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

 #include <ee1/led.h>
#include <ee1/delay.h> int main()
{
led_init();
const uint8_t leds[LED_COUNT] = {LED_RED, LED_YELLOW, LED_GREEN, LED_BLUE};
while ()
{
for (uint8_t i = ; i < LED_COUNT; i++)
{
led_set(leds[i], LED_ON);
delay();
led_set(leds[i], LED_OFF);
}
}
}

在代码中,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,你就能看到头文件中对这些宏的定义:

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

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

 void led_set(uint8_t _which, bool _on);
void led_flip(uint8_t _which);

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

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

 #include <ee1/ee.h>

 int main(void)
{
led_init();
uint8_t i = ;
while ()
{
led_set(i % , true);
delay();
led_set(i % , false);
i++;
}
}

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

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

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

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

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

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

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

 led_set((i + ) % , false);
led_set(i % , true);
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. SOA面向服务体系架构

    SOA概念 1.什么是SOA 面向服务的体系结构(Service-Oriented Architecture,SOA)是一个组件模型. 它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的 ...

  2. DNGuard HVM Unpacker(3.71 trial support and x64 fixed)

    DNGuard HVM Unpacker(3.71 trial support and x64 fixed) Gr8 news. Finally got the x64 crash fixed. DN ...

  3. GIS地理处理工具案例教程-成本距离

    GIS地理处理工具案例教程-成本距离 关键词:最短路径,成本路径,最佳路径,最优路径,路径分析,选线分析 商务合作,科技咨询,版权转让:向日葵,135-4855__4328,xiexiaokui#qq ...

  4. 让remix使用本地文件系统

    让remix使用本地文件系统   转:https://blog.csdn.net/platocnet/article/details/83376792 1. 测试发现使用npm命令安装相关环境不成功, ...

  5. ES6深入浅出-4 迭代器与生成器-1.字面量增强

    今天的内容 字面量literal 写出来就是它的值 例如字符串hello.这就是自变量. 一个空对象,也是自变量 写出来就是代表它写出来的那个意思就是自变量. 与其相反的就是构造出来的.例如下面的ne ...

  6. 软件定义网络基础---SDN的发展

    一:发展初期阶段--提出 架构.设计思想和实现技术的提出 二:发展中期阶段--企业加入,推动发展 三:SDN的发展趋势 (一)SD-DC SDN被大规模应用数据中心的服务器和设备部署运维,产生了软件定 ...

  7. Hashset(不能添加相同的字符进入数组)

    参考: https://ke.qq.com/webcourse/index.html#cid=434021&term_id=100518216&taid=377652179413386 ...

  8. 123457123457#0#----com.MC.3or1KongLongPT867----前拼后广--3or1恐龙PtGame-mc

    com.MC.3or1KongLongPT867----前拼后广--3or1恐龙PtGame-mc

  9. Python核心编程(第二版)PDF

    Python核心编程(第二版) 目录 第1部分 Python核心第1章 欢迎来到Python世界1.1 什么是Python1.2 起源1.3 特点1.3.1 高级1.3.2 面向对象1.3.3 可升级 ...

  10. Python机器学习实践指南pdf (中文版带书签)、原书代码、数据集

    Python机器学习实践指南 目 录 第1章Python机器学习的生态系统 1 1.1 数据科学/机器学习的工作 流程 2 1.1.1 获取 2 1.1.2 检查和探索 2 1.1.3 清理和准备 3 ...