在上一期的“教你花式点灯”中,我们已经教会了大家如何将一颗灯点到极致。而点灯到这里还没有完哦,这次给大家来点进阶的小课程,来教教大家如何花式点亮一排灯。快快上车,老司机准备起飞了!
最简单的一个 LED 的实验之后,自然是增加几个 LED,咱排成一排来玩吧。最后,再把一排的 LED 排成一个 8 字来玩——七段数码管。 流水灯一 —— 跑马灯 第一个出场的肯定是经典的流水灯,也叫跑马灯
流水灯二 —— 端口整体操作 再试流水灯,这里换一个端口整体操作,感受到更简洁没?
流水灯三 —— 74HC959 扩展 IO 口 又是流水灯!这次使用 74HC595 来扩展 IO 口。对于 arduino 来说,IO 口资源是很紧张的,迟早会遇到不够用的时候,这时候就需要扩展。使用 74HC595 是一种比较常见的方案。 雨滴拖尾效果流水灯 不好意思,还是流水灯。这次模拟得更真实一点,加上水滴的拖尾效果。
首先是简单的方法,使用 uno 的六个 pwm 口来实现。
1. // ---------------------------------------------------------------------------- 2. // raindropLEDS.ino 3. // 4. // Created 2015-06-04 5. // By seesea <seesea2517#gmail#com> 6. // 7. // 雨滴流动效果 8. // 雨滴流动效果与流水灯(跑马灯)的区别在于雨滴流水效果有拖尾效果,即亮过的灯是慢慢熄灭的 9. // 10. // 使用 UNO 的六个 PWM 引脚实现雨滴流动的效果 11. // UNO 的六个 PWM 引脚为 pin 3 5 6 9 10 11,这几个脚分别接 6 个 LED 正极,LED 负极接 GND 12. // 13. // ---------------------------------------------------------------------------- 14. 15. const unsigned char pins[] = { 3, 5, 6, 9, 10, 11 }; // 六个 pwm 引脚 16. const unsigned char initPwm = 240; // 最亮的灯的 pwm 值,即移动的时候在最头一个灯的亮度 17. const unsigned char deltaPwm = 10; // 灯慢慢熄灭的 pwm 值,后一个灯比前一个灯暗多少。这相当于是一个等差队列。等差的亮度感觉不大好,所以引入下一个等比的因素 18. const unsigned char deltaPercent = 30; // 后一个灯比前一个灯暗,其亮度是前一个灯的百分之几。相对于前面的递减,这个相当于是等比级数 19. const unsigned long delayMs = 100; // 移动延迟,单位 ms 20. const unsigned char pinNum = sizeof(pins) / sizeof(pins[0]); // 引脚数量,即 LED 个数 21. 22. unsigned char ledPwm[pinNum]; // 存放运行时每一个 LED 的亮度 PWM 值 23. 24. void setup() 25. { 26. for (char i = 0; i < pinNum; ++i) 27. { 28. pinMode(pins, OUTPUT); 29. ledPwm = 0; 30. } 31. } 32. 33. void loop() 34. { 35. static unsigned char head = 0; 36. 37. // 每一次进入 loop() 函数都对所有的灯亮度进行处理 38. for (unsigned char i = 0; i < pinNum; ++i) 39. { 40. ledPwm = ledPwm * deltaPercent / 100; 41. if (ledPwm <= deltaPwm) 42. ledPwm = 0; 43. else 44. ledPwm -= deltaPwm; 45. 46. if (i == head) 47. ledPwm = initPwm; 48. 49. analogWrite(pins, ledPwm); 50. } 51. 52. // 移动水滴头 53. head = (head + 1) % pinNum; 54. 55. // 延时 56. delay(delayMs); 57. }
雨滴拖尾效果流水灯二——数字 IO 口模拟 前面只有 6 个 PWM 口实在是不过瘾啊,来,我们把所有的 IO 口都用上,包括模拟口,它也是可以作为数字 IO 口使用的。 1. // ---------------------------------------------------------------------------- 2. // digitalRaindropLEDS.ino 3. // 4. // Created 2015-06-04 5. // By seesea <seesea2517#gmail#com> 6. // 7. // 数字引脚实现的雨滴流动效果 8. // 雨滴流动效果与流水灯(跑马灯)的区别在于雨滴流水效果有拖尾效果,即亮过的灯是慢慢熄灭的 9. // 10. // 使用 UNO 的所有引脚用模拟 PWM 实现雨滴流动的效果,包括模拟输入口也可以用做数字输出 11. // 各引脚接 LED 正极,LED 负极接 GND 12. // ---------------------------------------------------------------------------- 13. 14. const unsigned char leds[] = { A5, A4, A3, A2, A1, A0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 }; // 所有的引脚按 LED 接线顺序排列 15. const unsigned int maxPwm = 100; // 手工模拟 PWM,可以自己定义最大的 PWM 值是多少,所以定义一个整百整千的数比较方便计算 16. const unsigned int initPwm = 100; // 最亮的灯的 pwm 值,即移动的时候在最头一个灯的亮度 17. const unsigned int deltaPwm = 1; // 灯慢慢熄灭的 pwm 值,后一个灯比前一个灯暗多少。这相当于是一个等差队列。等差的亮度感觉不大好,所以引入下一个等比的因素 18. const unsigned int deltaPercent = 70; // 后一个灯比前一个灯暗,其亮度是前一个灯的百分之几。相对于前面的递减,这个相当于是等比级数 19. const unsigned long delayMs = 70; // 移动延迟,单位 ms 20. const unsigned char ledNum = sizeof(leds) / sizeof(leds[0]); // 引脚数量,即 LED 个数 21. 22. unsigned int ledPwm[ledNum]; // 存放运行时每一个 LED 的亮度 PWM 值 23. 24. void setup() 25. { 26. for (char i = 0; i < ledNum; ++i) 27. { 28. pinMode(leds, OUTPUT); 29. ledPwm = 0; 30. } 31. } 32. 33. extern volatile unsigned long timer0_millis; // 声明外部变量 timer0_millis 以便在程序中使用,其实就是 millis() 的返回值——程序运行的毫秒数 34. void loop() 35. { 36. static unsigned char head = 0; 37. static unsigned long lastTick = timer0_millis; 38. unsigned int i, j; 39. 40. // 先亮灯,等占空比到切换点的时候灭灯 41. for (i = 0; i < ledNum; ++i) 42. { 43. if (ledPwm == 0) 44. continue; 45. 46. digitalWrite(leds, HIGH); 47. } 48. 49. // 水滴头是最亮的 50. ledPwm[head] = initPwm; 51. 52. // 这里就是数字口模拟的 PWM 程序了 53. for (i = 0; i < maxPwm; ++i) 54. { 55. for (j = 0; j < ledNum; ++j) 56. { 57. if (i == ledPwm[j]) 58. digitalWrite(leds[j], LOW); 59. } 60. 61. delayMicroseconds(1); 62. } 63. 64. // 如果延时时间还没到,先跳出,不进行水滴的移动 65. // 由于是用数字口模拟的 PWM,程序要不停的跑,不能使用 delay() 来延时,会卡住的 66. if (timer0_millis - lastTick < delayMs) 67. return; 68. 69. lastTick = timer0_millis; 70. 71. // 处理每一个灯的亮度 72. for (i = 0; i < ledNum; ++i) 73. { 74. ledPwm = ledPwm * deltaPercent / 100; 75. if (ledPwm <= deltaPwm) 76. ledPwm = 0; 77. else 78. ledPwm -= deltaPwm; 79. 80. if (i == head) 81. ledPwm = initPwm; 82. } 83. 84. // 移动水滴头 85. head = (head + 1) % ledNum; 86. } POV摇摇棒是时候舞起来了,摇起来吧。 1. // ---------------------------------------------------------------------------- 2. // povLEDS.ino 3. // 4. // Created 2015-05-31 5. // By seesea <seesea2517#gmail#com> 6. // 7. // 摇摇棒 8. // 9. // pin 0 - 7 接 LED 正极,LED 负极接 GND 10. // pin 8 接水银开关一脚,水银开关另一脚接 GND 11. // 水银开关水平于摇动方向固定放置。如果实际效果与预想效果水平相反,只要把水银开关两脚的接线互换就可以了 12. // 注意:由于 pin 0 1 通过 LED 接地了,下载的时候需要断开才能下载成功 13. // ---------------------------------------------------------------------------- 14. 15. const unsigned char key = 8; // 水银开关 16. const unsigned char delayTimeMs = 2; // 扫描时LED的点亮延时 17. 18. // 取模软件使用纵向8点上高位的方式取模 19. unsigned char col[] = 20. { 21. // 心 22. // 0x30,0x48,0x44,0x22,0x44,0x48,0x30,0x00 23. 24. // I 心 U 25. 0x82,0xFE,0xFE,0x82,0x00,0x30,0x78,0x7C, 26. 0x3E,0x7C,0x78,0x30,0x00,0xFC,0xFE,0x02, 27. 0x02,0xFE,0xFC,0x00,0x00,0x00 28. 29. /* 1 2 3 30. 0x00,0x42,0xFE,0x02,0x00,0x00,0x00,0x00, 31. 0x46,0x8A,0x92,0x92,0x62,0x00,0x00,0x00, 32. 0x84,0x82,0x92,0xB2,0xCC,0x00 33. */ 34. }; 35. 36. unsigned char cols = (sizeof(col) / sizeof(col[0])); 37. 38. void setup() 39. { 40. DDRD = 0xFF; 41. pinMode(key, INPUT_PULLUP); 42. } 43. 44. void loop() 45. { 46. char i = 0; 47. char keyValue = digitalRead(key); 48. char delta = 1; 49. 50. if (keyValue == 1) // 摇动方向由右向左的时候 51. { 52. // 这里可以尝试两种方案 53. // 一种是向左扫的时候熄灭所有灯 54. // 另一种是反向点亮各列,从而由于方向也是反的,显示就变回正的 55. // 根据需要,把 if 条件改成 0 或 1 可以选择不同的方案 56. // 第一种方案的优点是不容易有重影,但缺点是亮度低 57. // 第二种方案的优缺正好相反 58. if (0) 59. { 60. // 向左摇熄灭所有灯 61. i = 0; 62. PORTD = 0x00; 63. 64. // 如果选择这种方案就不需要继续下面的扫描了,return掉即可 65. return; 66. } 67. else 68. { 69. delta = -delta; 70. } 71. } 72. 73. if (delta == 1) 74. { 75. // 向右摇的时候 76. for (i = 0; i < cols; ++i) 77. { 78. PORTD = col; 79. delay(delayTimeMs); 80. } 81. } 82. else 83. { 84. // 向左摇的时候 85. for(i = cols - 1; i >= 0; --i) 86. { 87. PORTD = col; 88. delay(delayTimeMs); 89. } 90. } 91. } 接线图我觉得得上一下,效果图嘛,从几百张照片里挑个好看的来: 静态数码管显示 总站成一排多无聊,是时候改改造行了。现在排成 8 字吧。先点亮一个 8 字,用静态显示方法。 1. // ---------------------------------------------------------------------------- 2. // static7segLEDS.ino 3. // 4. // Created 2015-06-06 5. // By seesea <seesea2517#gmail#com> 6. // 7. // 静态七段数码管 8. // 使用 PORTD 静态显示七段数码管,循环显示 0 - F 9. // 10. // 这里使用的是共阳七段数码管,如果使用共阴的,只要把段码表取反即可 11. // 数码管段的顺序按如下标示排列 12. // 13. // A 14. // --- 15. // F| G |B 16. // --- 17. // E| D |C 18. // --- . DP 19. // 20. // 然后根据需要显示的数字生该段点亮输出 1,可以制作段码表。这种方式做出来的是共阴的,所以使用的时候取反即为共阳的段码 21. // +---+--+--+--+--+--+--+--+--+-----+ 22. // | | A| B| C| D| E| F| G|DP| 段码| 23. // +---+--+--+--+--+--+--+--+--+-----+ 24. // | 0 | 1| 1| 1| 1| 1| 1| 0| 0| 0xFC| 25. // +---+--+--+--+--+--+--+--+--+-----+ 26. // | 1 | 0| 1| 1| 0| 0| 0| 0| 0| 0x60| 27. // +---+--+--+--+--+--+--+--+--+-----+ 28. // | 2 | 1| 1| 0| 1| 1| 0| 1| 0| 0xDA| 29. // +---+--+--+--+--+--+--+--+--+-----+ 30. // | 3 | 1| 1| 1| 1| 0| 0| 1| 0| 0xF2| 31. // +---+--+--+--+--+--+--+--+--+-----+ 32. // | 4 | 0| 1| 1| 0| 0| 1| 1| 0| 0x66| 33. // +---+--+--+--+--+--+--+--+--+-----+ 34. // | 5 | 1| 0| 1| 1| 0| 1| 1| 0| 0xB6| 35. // +---+--+--+--+--+--+--+--+--+-----+ 36. // | 6 | 1| 0| 1| 1| 1| 1| 1| 0| 0xBE| 37. // +---+--+--+--+--+--+--+--+--+-----+ 38. // | 7 | 1| 1| 1| 0| 0| 0| 0| 0| 0xE0| 39. // +---+--+--+--+--+--+--+--+--+-----+ 40. // | 8 | 1| 1| 1| 1| 1| 1| 1| 0| 0xFE| 41. // +---+--+--+--+--+--+--+--+--+-----+ 42. // | 9 | 1| 1| 1| 1| 0| 1| 1| 0| 0xF6| 43. // +---+--+--+--+--+--+--+--+--+-----+ 44. // | A | 1| 1| 1| 0| 1| 1| 1| 0| 0xEE| 45. // +---+--+--+--+--+--+--+--+--+-----+ 46. // | B | 0| 0| 1| 1| 1| 1| 1| 0| 0x3E| 47. // +---+--+--+--+--+--+--+--+--+-----+ 48. // | C | 0| 0| 0| 1| 1| 0| 1| 0| 0x1A| 49. // +---+--+--+--+--+--+--+--+--+-----+ 50. // | D | 0| 1| 1| 1| 1| 0| 1| 0| 0x7A| 51. // +---+--+--+--+--+--+--+--+--+-----+ 52. // | E | 1| 0| 0| 1| 1| 1| 1| 0| 0x9E| 53. // +---+--+--+--+--+--+--+--+--+-----+ 54. // | F | 1| 0| 0| 0| 1| 1| 1| 0| 0x8E| 55. // +---+--+--+--+--+--+--+--+--+-----+ 56. // ---------------------------------------------------------------------------- 57. 58. // 共阳段码表 0 - F 59. const unsigned char segTable[] = { 60. ~0xFC, ~0x60, ~0xDA, ~0xF2, ~0x66, ~0xB6, 61. ~0xBE, ~0xE0, ~0xFE, ~0xF6, ~0xEE, ~0x3E, 62. ~0x1A, ~0x7A, ~0x9E, ~0x8E 63. }; 64. 65. void setup() 66. { 67. DDRD = 0xFF; // PORTD 设置为输出 68. } 69. 70. void loop() 71. { 72. for (char i = 0; i < sizeof(segTable) / sizeof(segTable[0]); ++i) 73. { 74. PORTD = segTable; 75. delay(500); 76. } 77. } 动态数码管显示 上面是一位数码管的显示,可以使用静态,如果要显示多位,那可得动起来了。 1. // ---------------------------------------------------------------------------- 2. // dynamic7segLEDS.ino 3. // 4. // Created 2015-06-06 5. // By seesea <seesea2517#gmail#com> 6. // 7. // 动态七段数码管 8. // 动态显示七段数码管,从 0000 开始计数,9999 后回 0 9. // 10. // 使用 PORTD 进行段选,使用 pin 8 9 10 11 进行位选 11. // ---------------------------------------------------------------------------- 12. 13. #define COM_ON HIGH // 共阳数码管启用的公共端电平 14. #define COM_OFF LOW // 共阳数码管禁用的公共端电平 15. 16. // 共阳段码表 0 - F 17. const unsigned char segTable[] = { 18. ~0xFC, ~0x60, ~0xDA, ~0xF2, ~0x66, ~0xB6, 19. ~0xBE, ~0xE0, ~0xFE, ~0xF6, ~0xEE, ~0x3E, 20. ~0x1A, ~0x7A, ~0x9E, ~0x8E 21. }; 22. 23. const unsigned char pinPos[] = { 8, 9, 10, 11 }; // 位选管脚,按从低位到高位的排列 24. const unsigned int initNum = 0; // 计数初始值 25. const unsigned int maxNum = 9999; // 最大计数 26. const unsigned char com_num = sizeof(pinPos) / sizeof(pinPos[0]); // 公共端的数量,用于确定有几位 27. const unsigned long delayMs = 50; // 动态扫描延迟时间 28. 29. void setup() 30. { 31. DDRD = 0xFF; // PORTD 设置为输出 32. for (char i = 0; i < sizeof(pinPos) / sizeof(pinPos[0]); ++i) 33. { 34. pinMode(pinPos, OUTPUT); 35. } 36. } 37. 38. // 于七段数码管上显示数字 num 39. // 将 num 数字的个十百千等各位的数字一位一位分离出来显示到每一位数码管上 40. void display7segLED(unsigned int num) 41. { 42. unsigned int digital; 43. for (unsigned char i = 0; i < com_num; ++i) 44. { 45. digital = num % 10; 46. num /= 10; 47. 48. PORTD = segTable[digital]; 49. digitalWrite(pinPos, COM_ON); 50. 51. delayMicroseconds(10); 52. digitalWrite(pinPos, COM_OFF); // 传说中的消隐,避免发生重影 53. } 54. } 55. 56. void loop() 57. { 58. static unsigned long lastTick = millis(); 59. static unsigned int num = initNum; 60. 61. display7segLED(num); 62. 63. // 动态扫描,不能用 delay 阻塞式的延迟哦,前面试验过的这种用法现在派上用场了 64. if (millis() - lastTick < delayMs) 65. return; 66. 67. lastTick = millis(); 68. ++num; 69. if (num >= maxNum) 70. num = initNum; 71. }
|