概述
最简单的一个 LED 的实验之后,自然是增加几个 LED,咱排成一排来玩吧。最后,再把一排的 LED 排成一个 8 字来玩——七段数码管。
示例程序
流水灯
第一个出场的肯定是经典的流水灯,也叫跑马灯
// ----------------------------------------------------------------------------
// flowingLEDS.ino
//
// Created 2015-05-31
// By seesea <seesea2517#gmail#com>
//
// 流水灯
//
// pin 5 - 12 接到 8 个 LED 正级,LED 负极接 GND
// ---------------------------------------------------------------------------- // 配置流水灯引脚
const unsigned char pins[] = { , , , , , , , }; void setup()
{
for (char i = ; i < sizeof(pins) / sizeof(char); ++i)
{
pinMode(pins[i], OUTPUT);
}
} void loop()
{
static unsigned char i = ; digitalWrite(pins[i], LOW); i = (i + ) % ;
digitalWrite(pins[i], HIGH); delay();
}
流水灯二——端口整体操作
再试流水灯,这里换一个端口整体操作,感受到更简洁没?
// ----------------------------------------------------------------------------
// portDFlowingLEDS.ino
//
// Created 2015-05-31
// By seesea <seesea2517#gmail#com>
//
// 使用 PORTD 的操作来实现的流水灯
// 体验端口的整体操作
//
// 从官网找到原理图或管脚对应说明,或者直接找到 arduino IDE 目录下的头文件可以看到,数字口 pin 0 - 7 就是 PORTD 对应的低到高的 0 - 7 位
// pin 0 - 7 接到 8 个 LED 正极,LED 负极接 GND
// 由于 pin 0 1 通过 LED 接到地了,下载程序的时候需要将 pin 0 1 两个引脚的接线拔掉,否则下载不了程序
// ---------------------------------------------------------------------------- void setup()
{
DDRD = 0xFF; // DDRD 用于设置引脚功能是输入还是输出,置 1 为输出。对应 arduino 就是 pinMode 置为 OUTPUT
} void loop()
{
static unsigned char i = ; i = (i + ) % ;
PORTD = << i; // 左移位操作。PORTD 用于对引脚输出的设置,对应 arduino 就是 digitalWrite delay();
}
流水灯三——74HC959 扩展 IO 口
又是流水灯!这次使用 74HC595 来扩展 IO 口。对于 arduino 来说,IO 口资源是很紧张的,迟早会遇到不够用的时候,这时候就需要扩展。使用 74HC595 是一种比较常见的方案。
// ----------------------------------------------------------------------------
// 959FlowingLEDS.ino
//
// Created 2015-06-07
// By seesea <seesea2517#gmail#com>
//
// 使用 74HC959 扩展 IO 口实现的流水灯
// 只要用三个 IO 口就可以扩展出 8 个 IO 口了,很节省 arudino 的紧张 IO 资源(理论上三个 IO 口可以无限扩展)
//
// 959 的 Q0-Q7 接 8 个 LED 正极,LED 负极接 GND
// 959 的其它引脚按相关说明接即可。VCC 通过 200 ohm 电阻接电源正,偷懒的限流办法
// ---------------------------------------------------------------------------- const unsigned char latchPin = ; // 595 的 ST_CP
const unsigned char clockPin = ; // 595 的 SH_CP
const unsigned char dataPin = ; // 595 的 DS void setup ()
{
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
} void loop()
{
static unsigned char i = ; digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, << i);
digitalWrite(latchPin, HIGH); delay(); ++i;
if (i > )
i = ;
}
雨滴拖尾效果流水灯
不好意思,还是流水灯。这次模拟得更真实一点,加上水滴的拖尾效果。
首先是简单的方法,使用 uno 的六个 pwm 口来实现。
// ----------------------------------------------------------------------------
// raindropLEDS.ino
//
// Created 2015-06-04
// By seesea <seesea2517#gmail#com>
//
// 雨滴流动效果
// 雨滴流动效果与流水灯(跑马灯)的区别在于雨滴流水效果有拖尾效果,即亮过的灯是慢慢熄灭的
//
// 使用 UNO 的六个 PWM 引脚实现雨滴流动的效果
// UNO 的六个 PWM 引脚为 pin 3 5 6 9 10 11,这几个脚分别接 6 个 LED 正极,LED 负极接 GND
//
// ---------------------------------------------------------------------------- const unsigned char pins[] = { , , , , , }; // 六个 pwm 引脚
const unsigned char initPwm = ; // 最亮的灯的 pwm 值,即移动的时候在最头一个灯的亮度
const unsigned char deltaPwm = ; // 灯慢慢熄灭的 pwm 值,后一个灯比前一个灯暗多少。这相当于是一个等差队列。等差的亮度感觉不大好,所以引入下一个等比的因素
const unsigned char deltaPercent = ; // 后一个灯比前一个灯暗,其亮度是前一个灯的百分之几。相对于前面的递减,这个相当于是等比级数
const unsigned long delayMs = ; // 移动延迟,单位 ms
const unsigned char pinNum = sizeof(pins) / sizeof(pins[]); // 引脚数量,即 LED 个数 unsigned char ledPwm[pinNum]; // 存放运行时每一个 LED 的亮度 PWM 值 void setup()
{
for (char i = ; i < pinNum; ++i)
{
pinMode(pins[i], OUTPUT);
ledPwm[i] = ;
}
} void loop()
{
static unsigned char head = ; // 每一次进入 loop() 函数都对所有的灯亮度进行处理
for (unsigned char i = ; i < pinNum; ++i)
{
ledPwm[i] = ledPwm[i] * deltaPercent / ;
if (ledPwm[i] <= deltaPwm)
ledPwm[i] = ;
else
ledPwm[i] -= deltaPwm; if (i == head)
ledPwm[i] = initPwm; analogWrite(pins[i], ledPwm[i]);
} // 移动水滴头
head = (head + ) % pinNum; // 延时
delay(delayMs);
}
雨滴拖尾效果流水灯二——数字 IO 口模拟
前面只有 6 个 PWM 口实在是不过瘾啊,来,我们把所有的 IO 口都用上,包括模拟口,它也是可以作为数字 IO 口使用的。
// ----------------------------------------------------------------------------
// digitalRaindropLEDS.ino
//
// Created 2015-06-04
// By seesea <seesea2517#gmail#com>
//
// 数字引脚实现的雨滴流动效果
// 雨滴流动效果与流水灯(跑马灯)的区别在于雨滴流水效果有拖尾效果,即亮过的灯是慢慢熄灭的
//
// 使用 UNO 的所有引脚用模拟 PWM 实现雨滴流动的效果,包括模拟输入口也可以用做数字输出
// 各引脚接 LED 正极,LED 负极接 GND
// ---------------------------------------------------------------------------- const unsigned char leds[] = { A5, A4, A3, A2, A1, A0, , , , , , , , , , , , , , }; // 所有的引脚按 LED 接线顺序排列
const unsigned int maxPwm = ; // 手工模拟 PWM,可以自己定义最大的 PWM 值是多少,所以定义一个整百整千的数比较方便计算
const unsigned int initPwm = ; // 最亮的灯的 pwm 值,即移动的时候在最头一个灯的亮度
const unsigned int deltaPwm = ; // 灯慢慢熄灭的 pwm 值,后一个灯比前一个灯暗多少。这相当于是一个等差队列。等差的亮度感觉不大好,所以引入下一个等比的因素
const unsigned int deltaPercent = ; // 后一个灯比前一个灯暗,其亮度是前一个灯的百分之几。相对于前面的递减,这个相当于是等比级数
const unsigned long delayMs = ; // 移动延迟,单位 ms
const unsigned char ledNum = sizeof(leds) / sizeof(leds[]); // 引脚数量,即 LED 个数 unsigned int ledPwm[ledNum]; // 存放运行时每一个 LED 的亮度 PWM 值 void setup()
{
for (char i = ; i < ledNum; ++i)
{
pinMode(leds[i], OUTPUT);
ledPwm[i] = ;
}
} extern volatile unsigned long timer0_millis; // 声明外部变量 timer0_millis 以便在程序中使用,其实就是 millis() 的返回值——程序运行的毫秒数
void loop()
{
static unsigned char head = ;
static unsigned long lastTick = timer0_millis;
unsigned int i, j; // 先亮灯,等占空比到切换点的时候灭灯
for (i = ; i < ledNum; ++i)
{
if (ledPwm[i] == )
continue; digitalWrite(leds[i], HIGH);
} // 水滴头是最亮的
ledPwm[head] = initPwm; // 这里就是数字口模拟的 PWM 程序了
for (i = ; i < maxPwm; ++i)
{
for (j = ; j < ledNum; ++j)
{
if (i == ledPwm[j])
digitalWrite(leds[j], LOW);
} delayMicroseconds();
} // 如果延时时间还没到,先跳出,不进行水滴的移动
// 由于是用数字口模拟的 PWM,程序要不停的跑,不能使用 delay() 来延时,会卡住的
if (timer0_millis - lastTick < delayMs)
return; lastTick = timer0_millis; // 处理每一个灯的亮度
for (i = ; i < ledNum; ++i)
{
ledPwm[i] = ledPwm[i] * deltaPercent / ;
if (ledPwm[i] <= deltaPwm)
ledPwm[i] = ;
else
ledPwm[i] -= deltaPwm; if (i == head)
ledPwm[i] = initPwm;
} // 移动水滴头
head = (head + ) % ledNum;
}
POV摇摇棒
是时候舞起来了,摇起来吧。
// ----------------------------------------------------------------------------
// povLEDS.ino
//
// Created 2015-05-31
// By seesea <seesea2517#gmail#com>
//
// 摇摇棒
//
// pin 0 - 7 接 LED 正极,LED 负极接 GND
// pin 8 接水银开关一脚,水银开关另一脚接 GND
// 水银开关水平于摇动方向固定放置。如果实际效果与预想效果水平相反,只要把水银开关两脚的接线互换就可以了
// 注意:由于 pin 0 1 通过 LED 接地了,下载的时候需要断开才能下载成功
// ---------------------------------------------------------------------------- const unsigned char key = ; // 水银开关
const unsigned char delayTimeMs = ; // 扫描时LED的点亮延时 // 取模软件使用纵向8点上高位的方式取模
unsigned char col[] =
{
// 心
// 0x30,0x48,0x44,0x22,0x44,0x48,0x30,0x00 // I 心 U
0x82,0xFE,0xFE,0x82,0x00,0x30,0x78,0x7C,
0x3E,0x7C,0x78,0x30,0x00,0xFC,0xFE,0x02,
0x02,0xFE,0xFC,0x00,0x00,0x00 /* 1 2 3
0x00,0x42,0xFE,0x02,0x00,0x00,0x00,0x00,
0x46,0x8A,0x92,0x92,0x62,0x00,0x00,0x00,
0x84,0x82,0x92,0xB2,0xCC,0x00
*/
}; unsigned char cols = (sizeof(col) / sizeof(col[])); void setup()
{
DDRD = 0xFF;
pinMode(key, INPUT_PULLUP);
} void loop()
{
char i = ;
char keyValue = digitalRead(key);
char delta = ; if (keyValue == ) // 摇动方向由右向左的时候
{
// 这里可以尝试两种方案
// 一种是向左扫的时候熄灭所有灯
// 另一种是反向点亮各列,从而由于方向也是反的,显示就变回正的
// 根据需要,把 if 条件改成 0 或 1 可以选择不同的方案
// 第一种方案的优点是不容易有重影,但缺点是亮度低
// 第二种方案的优缺正好相反
if ()
{
// 向左摇熄灭所有灯
i = ;
PORTD = 0x00; // 如果选择这种方案就不需要继续下面的扫描了,return掉即可
return;
}
else
{
delta = -delta;
}
} if (delta == )
{
// 向右摇的时候
for (i = ; i < cols; ++i)
{
PORTD = col[i];
delay(delayTimeMs);
}
}
else
{
// 向左摇的时候
for(i = cols - ; i >= ; --i)
{
PORTD = col[i];
delay(delayTimeMs);
}
}
}
接线图我觉得得上一下,效果图嘛,从几百张照片里挑个好看的来:
静态数码管显示
总站成一排多无聊,是时候改改造行了。现在排成 8 字吧。先点亮一个 8 字,用静态显示方法。
// ----------------------------------------------------------------------------
// static7segLEDS.ino
//
// Created 2015-06-06
// By seesea <seesea2517#gmail#com>
//
// 静态七段数码管
// 使用 PORTD 静态显示七段数码管,循环显示 0 - F
//
// 这里使用的是共阳七段数码管,如果使用共阴的,只要把段码表取反即可
// 数码管段的顺序按如下标示排列
//
// A
// ---
// F| G |B
// ---
// E| D |C
// --- . DP
//
// 然后根据需要显示的数字生该段点亮输出 1,可以制作段码表。这种方式做出来的是共阴的,所以使用的时候取反即为共阳的段码
// +---+--+--+--+--+--+--+--+--+-----+
// | | A| B| C| D| E| F| G|DP| 段码|
// +---+--+--+--+--+--+--+--+--+-----+
// | 0 | 1| 1| 1| 1| 1| 1| 0| 0| 0xFC|
// +---+--+--+--+--+--+--+--+--+-----+
// | 1 | 0| 1| 1| 0| 0| 0| 0| 0| 0x60|
// +---+--+--+--+--+--+--+--+--+-----+
// | 2 | 1| 1| 0| 1| 1| 0| 1| 0| 0xDA|
// +---+--+--+--+--+--+--+--+--+-----+
// | 3 | 1| 1| 1| 1| 0| 0| 1| 0| 0xF2|
// +---+--+--+--+--+--+--+--+--+-----+
// | 4 | 0| 1| 1| 0| 0| 1| 1| 0| 0x66|
// +---+--+--+--+--+--+--+--+--+-----+
// | 5 | 1| 0| 1| 1| 0| 1| 1| 0| 0xB6|
// +---+--+--+--+--+--+--+--+--+-----+
// | 6 | 1| 0| 1| 1| 1| 1| 1| 0| 0xBE|
// +---+--+--+--+--+--+--+--+--+-----+
// | 7 | 1| 1| 1| 0| 0| 0| 0| 0| 0xE0|
// +---+--+--+--+--+--+--+--+--+-----+
// | 8 | 1| 1| 1| 1| 1| 1| 1| 0| 0xFE|
// +---+--+--+--+--+--+--+--+--+-----+
// | 9 | 1| 1| 1| 1| 0| 1| 1| 0| 0xF6|
// +---+--+--+--+--+--+--+--+--+-----+
// | A | 1| 1| 1| 0| 1| 1| 1| 0| 0xEE|
// +---+--+--+--+--+--+--+--+--+-----+
// | B | 0| 0| 1| 1| 1| 1| 1| 0| 0x3E|
// +---+--+--+--+--+--+--+--+--+-----+
// | C | 0| 0| 0| 1| 1| 0| 1| 0| 0x1A|
// +---+--+--+--+--+--+--+--+--+-----+
// | D | 0| 1| 1| 1| 1| 0| 1| 0| 0x7A|
// +---+--+--+--+--+--+--+--+--+-----+
// | E | 1| 0| 0| 1| 1| 1| 1| 0| 0x9E|
// +---+--+--+--+--+--+--+--+--+-----+
// | F | 1| 0| 0| 0| 1| 1| 1| 0| 0x8E|
// +---+--+--+--+--+--+--+--+--+-----+
// ---------------------------------------------------------------------------- // 共阳段码表 0 - F
const unsigned char segTable[] = {
~0xFC, ~0x60, ~0xDA, ~0xF2, ~0x66, ~0xB6,
~0xBE, ~0xE0, ~0xFE, ~0xF6, ~0xEE, ~0x3E,
~0x1A, ~0x7A, ~0x9E, ~0x8E
}; void setup()
{
DDRD = 0xFF; // PORTD 设置为输出
} void loop()
{
for (char i = ; i < sizeof(segTable) / sizeof(segTable[]); ++i)
{
PORTD = segTable[i];
delay();
}
}
动态数码管显示
上面是一位数码管的显示,可以使用静态,如果要显示多位,那可得动起来了。
// ----------------------------------------------------------------------------
// dynamic7segLEDS.ino
//
// Created 2015-06-06
// By seesea <seesea2517#gmail#com>
//
// 动态七段数码管
// 动态显示七段数码管,从 0000 开始计数,9999 后回 0
//
// 使用 PORTD 进行段选,使用 pin 8 9 10 11 进行位选
//
// 这里使用的是共阳七段数码管,如果使用共阴的,只要把段码表取反即可
// 数码管段的顺序按如下标示排列
//
// A
// ---
// F| G |B
// ---
// E| D |C
// --- . DP
//
// 然后根据需要显示的数字生该段点亮输出 1,可以制作段码表。这种方式做出来的是共阴的,所以使用的时候取反即为共阳的段码
// +---+--+--+--+--+--+--+--+--+-----+
// | | A| B| C| D| E| F| G|DP| 段码|
// +---+--+--+--+--+--+--+--+--+-----+
// | 0 | 1| 1| 1| 1| 1| 1| 0| 0| 0xFC|
// +---+--+--+--+--+--+--+--+--+-----+
// | 1 | 0| 1| 1| 0| 0| 0| 0| 0| 0x60|
// +---+--+--+--+--+--+--+--+--+-----+
// | 2 | 1| 1| 0| 1| 1| 0| 1| 0| 0xDA|
// +---+--+--+--+--+--+--+--+--+-----+
// | 3 | 1| 1| 1| 1| 0| 0| 1| 0| 0xF2|
// +---+--+--+--+--+--+--+--+--+-----+
// | 4 | 0| 1| 1| 0| 0| 1| 1| 0| 0x66|
// +---+--+--+--+--+--+--+--+--+-----+
// | 5 | 1| 0| 1| 1| 0| 1| 1| 0| 0xB6|
// +---+--+--+--+--+--+--+--+--+-----+
// | 6 | 1| 0| 1| 1| 1| 1| 1| 0| 0xBE|
// +---+--+--+--+--+--+--+--+--+-----+
// | 7 | 1| 1| 1| 0| 0| 0| 0| 0| 0xE0|
// +---+--+--+--+--+--+--+--+--+-----+
// | 8 | 1| 1| 1| 1| 1| 1| 1| 0| 0xFE|
// +---+--+--+--+--+--+--+--+--+-----+
// | 9 | 1| 1| 1| 1| 0| 1| 1| 0| 0xF6|
// +---+--+--+--+--+--+--+--+--+-----+
// | A | 1| 1| 1| 0| 1| 1| 1| 0| 0xEE|
// +---+--+--+--+--+--+--+--+--+-----+
// | B | 0| 0| 1| 1| 1| 1| 1| 0| 0x3E|
// +---+--+--+--+--+--+--+--+--+-----+
// | C | 0| 0| 0| 1| 1| 0| 1| 0| 0x1A|
// +---+--+--+--+--+--+--+--+--+-----+
// | D | 0| 1| 1| 1| 1| 0| 1| 0| 0x7A|
// +---+--+--+--+--+--+--+--+--+-----+
// | E | 1| 0| 0| 1| 1| 1| 1| 0| 0x9E|
// +---+--+--+--+--+--+--+--+--+-----+
// | F | 1| 0| 0| 0| 1| 1| 1| 0| 0x8E|
// +---+--+--+--+--+--+--+--+--+-----+
// ---------------------------------------------------------------------------- #define COM_ON HIGH // 共阳数码管启用的公共端电平
#define COM_OFF LOW // 共阳数码管禁用的公共端电平 // 共阳段码表 0 - F
const unsigned char segTable[] = {
~0xFC, ~0x60, ~0xDA, ~0xF2, ~0x66, ~0xB6,
~0xBE, ~0xE0, ~0xFE, ~0xF6, ~0xEE, ~0x3E,
~0x1A, ~0x7A, ~0x9E, ~0x8E
}; const unsigned char pinPos[] = { , , , }; // 位选管脚,按从低位到高位的排列
const unsigned int initNum = ; // 计数初始值
const unsigned int maxNum = ; // 最大计数
const unsigned char com_num = sizeof(pinPos) / sizeof(pinPos[]); // 公共端的数量,用于确定有几位
const unsigned long delayMs = ; // 动态扫描延迟时间 void setup()
{
DDRD = 0xFF; // PORTD 设置为输出
for (char i = ; i < sizeof(pinPos) / sizeof(pinPos[]); ++i)
{
pinMode(pinPos[i], OUTPUT);
}
} // 于七段数码管上显示数字 num
// 将 num 数字的个十百千等各位的数字一位一位分离出来显示到每一位数码管上
void display7segLED(unsigned int num)
{
unsigned int digital;
for (unsigned char i = ; i < com_num; ++i)
{
digital = num % ;
num /= ; PORTD = segTable[digital];
digitalWrite(pinPos[i], COM_ON); delayMicroseconds();
digitalWrite(pinPos[i], COM_OFF); // 传说中的消隐,避免发生重影
}
} void loop()
{
static unsigned long lastTick = millis();
static unsigned int num = initNum; display7segLED(num); // 动态扫描,不能用 delay 阻塞式的延迟哦,前面试验过的这种用法现在派上用场了
if (millis() - lastTick < delayMs)
return; lastTick = millis();
++num;
if (num >= maxNum)
num = initNum;
}