任务要求:
1、按键 1、2、3、4 按下,使 8 个 LED 实现下面对应的模式 1、 2、 3、4,上电默认每种模式流水灯的流转时间间隔为 500ms。
1)模式1:按照L1、L2……L8的顺序,从左到右循环点亮。
2)模式2:按照L8、L7……L1的顺序,从右刀座循环点亮。
3)模式3:从两边向中间点亮( (L1,L8)->(L2,L7)->(L3,L6)->(L4,L5) )
4)模式4:从中间向两边点亮( (L4,L5)->(L3,L6)->(L2,L7)->(L1,L8) )
2、按键5按下流水灯的流转时间间隔增加100ms,超过1200ms从400ms开始,用定时器控制时间
3、代码简洁,注释简单易懂。
实现思路:
程序大体框架如下图:
还没学过Visio画图,第一次尝试,轻喷~
按键部分
我们先把按键放在定时器里刷新,识别到几号按键按下,就对应LED灯按照第几个模式点亮,按键1按下就是模式1,按键2按下就是模式2……模式1234转换可以用一个全局变量实现,我代码中本变量名称为Light_Mode
主函数要循环判断Light_Mode变量的数值,所以写一个无参数无返回值的LED处理的函数,先根据按键返回值,给Light_Mode变量赋相应的状态值,再根据这个状态值,实现四种不同模式的点灯。
定时器+中断
定时时间10ms,因为51单片机定时时间上限大约是70ms,按键消抖的部分大概是10ms,所以按键在定时器中以10ms时间扫描一次它的状态,每10ms扫描一次它的状态,就会滤除硬件抖动的部分,LED要求的500ms定时,可以在这个10ms基础上累加计数,就是每10ms计数变量自加1,计数变量==50的时候,就是500ms了
要求2 要改变LED闪烁时间间隔 ,那就再定义一个全局变量 unsigned int SpaceT = 500;
名称SpaceT,初值500ms
按键5 按一下SpaceT变量自加100,超过1200,给SpaceT赋值400
定时器0初始化代码
1. void Timer0_Init(void) //1毫秒@11.0592MHz 2. { 3. TMOD &= 0xF0; //设置定时器模式 4. TMOD |= 0x01; //设置定时器模式 5. TL0 = 0x66; //设置定时初值 6. TH0 = 0xFC; //设置定时初值 7. TF0 = 0; //清除TF0标志 8. TR0 = 1; //定时器0开始计时 9. ET0=1; 10. EA=1; 11. PT0=0; 12. }
的中断定时器0
1. void Timer0_Routine() interrupt 1 //定时器0的中断函数 2. { 3. static unsigned char Button; //按键扫描时间变量 10ms 4. static unsigned int T0Count; 5. 6. TL0 = 0x66; //设置定时初值 7. TH0 = 0xFC; //设置定时初值 8. Button++; 9. T0Count++; 10. 11. if(T0Count >= SpaceT){ 12. i++; 13. T0Count = 0; //软件复位 14. } 15. 16. if(Button >= 10){ 17. MatrixKey_Loop(); 18. Button = 0; 19. } 20. 21. }
LED处理
上图是普中科技的51开发板 原理图上说明P2端口对应了8个发光二极管,从P20~P27
如果想点亮LED1(图示D1),就写P2_0 = 0;
或者对P2整个端口赋值,P2 = 0xFE; 十六进制的0xFE转化二进制后为1111 1110,只有第一个LED点亮。
如果想先点亮第二个LED,那就P2 = 0xFD; 0xFD代表1111 1101,只有第二个灯点亮
如果想实现流水灯的效果,那就给P2端口依次赋值如下八个数
1111 1110
1111 1101
1111 1011
1111 0111
1110 1111
1101 1111
1011 1111
0111 1111
不难发现,只有0向前移位,一次移动移位,由此可以想到位运算中的移位运算符
参考书 C primer plus
书上指出,左移运算符,高位舍弃,低位补0(补0的那些位LED就会亮起),所以不符合我们的要求
由于高位舍弃,低位补0,如果我们找到和上面八个数相反的数,再给它取反,就得到了想要的效果
留点空余时间思考一下
……
我们可以对0x01 (0000 0001)移位,左移
左移1次 0000 0010 (高位舍弃,低位补0), 取反 1111 1101
左移2次 0000 0100 取反 1111 1011
……
变量 i 也要定义为全局变量,因为 i 要在中断函数里改变
1. for(i=0;i<8;) //i变量在定时器里加加,控制闪烁时间间隔 2. { 3. P2 = ~(0x01<<i); 4. }
模式2流水
1. for(i=0;i<8;) //i变量在定时器里加加,控制闪烁时间间隔 2. { 3. P2 = ~(0x80>>i); 4. 5. }
模式3
1. for(i=0;i<4;) 2. { 3. P2 = ~((0x01<<i) | (0x80>>i));//两边向中间 亮灯 4. }
模式4
1. for(i=0;i<4;) 2. { 3. P2 = ~((0x10<<i) | (0x08>>i)); 4. }
程序源码
本题要求简单,所以我就建了两个模块化的代码
Timer0.c
1. #include <REGX52.H> 2. #include "Timer0.h" 3. 4. 5. //定时器0初始化模板 1毫秒@11.0592MHz 6. void Timer0_Init(void) //1毫秒@11.0592MHz 7. { 8. TMOD &= 0xF0; //设置定时器模式 9. TMOD |= 0x01; //设置定时器模式 10. TL0 = 0x66; //设置定时初值 11. TH0 = 0xFC; //设置定时初值 12. TF0 = 0; //清除TF0标志 13. TR0 = 1; //定时器0开始计时 14. ET0=1; 15. EA=1; 16. PT0=0; 17. }
Timer0.h
1. #ifndef __TIMER0_H__ 2. #define __TIMER0_H__ 3. 4. void Timer0_Init(void); //1毫秒@11.0592MHz 5. 6. #endif
MatrixKeyT.c
矩阵按键模块化的代码在之前的文章有发过,需要的童鞋们可以去翻阅一下
1. #include <REGX52.H> 2. #include "MatrixKeyT.h" 3. 4. unsigned char Key_KeyNumber; 5. 6. 7. /** 8. *@brief 名称:矩阵键盘扫描,获取按键键码 9. *@param 参数:无 (放在主函数while里 赋值给一个 代表键码的变量) 10. *@retval返回值:KeyNumber 0代表无按键按下,1~16代表键码 11. */ 12. unsigned char Key(void) 13. { 14. unsigned char Temp = 0; 15. Temp = Key_KeyNumber; //赋值给暂存变量 16. Key_KeyNumber = 0; //清零 17. return Temp; //返回暂存变量 18. } 19. 20. 21. /** 22. *@brief 名称:矩阵键盘读取按键键码, 内部函数(不需要外部调用的) 23. *@param 参数:无 24. *@retval返回值:KeyNumber 0代表无按键按下,1~16代表键码 25. */ 26. unsigned char MatrixKey_GetState() 27. { 28. unsigned char KeyNumber=0; 29. 30. P1=0xFF; 31. P1_3=0; 32. if(P1_7==0 && P1_3==0){KeyNumber=1;} 33. if(P1_6==0 && P1_3==0){KeyNumber=5;} 34. if(P1_5==0 && P1_3==0){KeyNumber=9;} 35. if(P1_4==0 && P1_3==0){KeyNumber=13;} 36. 37. P1=0xFF; 38. P1_2=0; 39. if(P1_7==0 && P1_2==0){KeyNumber=2;} 40. if(P1_6==0 && P1_2==0){KeyNumber=6;} 41. if(P1_5==0 && P1_2==0){KeyNumber=10;} 42. if(P1_4==0 && P1_2==0){KeyNumber=14;} 43. 44. P1=0xFF; 45. P1_1=0; 46. if(P1_7==0 && P1_1==0){KeyNumber=3;} 47. if(P1_6==0 && P1_1==0){KeyNumber=7;} 48. if(P1_5==0 && P1_1==0){KeyNumber=11;} 49. if(P1_4==0 && P1_1==0){KeyNumber=15;} 50. 51. P1=0xFF; 52. P1_0=0; 53. if(P1_7==0 && P1_0==0){KeyNumber=4;} 54. if(P1_6==0 && P1_0==0){KeyNumber=8;} 55. if(P1_5==0 && P1_0==0){KeyNumber=12;} 56. if(P1_4==0 && P1_0==0){KeyNumber=16;} 57. 58. return KeyNumber; 59. } 60. 61. 62. void MatrixKey_Loop(void) 63. { 64. static unsigned char NowState,LastState; 65. LastState = NowState; //按键状态更新 66. NowState = MatrixKey_GetState(); //获取当前按键状态 67. if(LastState == 1 && NowState == 0) {Key_KeyNumber=1;} 68. if(LastState == 2 && NowState == 0) {Key_KeyNumber=2;} 69. if(LastState == 3 && NowState == 0) {Key_KeyNumber=3;} 70. if(LastState == 4 && NowState == 0) {Key_KeyNumber=4;} 71. if(LastState == 5 && NowState == 0) {Key_KeyNumber=5;} 72. if(LastState == 6 && NowState == 0) {Key_KeyNumber=6;} 73. if(LastState == 7 && NowState == 0) {Key_KeyNumber=7;} 74. if(LastState == 8 && NowState == 0) {Key_KeyNumber=8;} 75. if(LastState == 9 && NowState == 0) {Key_KeyNumber=9;} 76. if(LastState ==10 && NowState == 0) {Key_KeyNumber=10;} 77. if(LastState ==11 && NowState == 0) {Key_KeyNumber=11;} 78. if(LastState ==12 && NowState == 0) {Key_KeyNumber=12;} 79. if(LastState ==13 && NowState == 0) {Key_KeyNumber=13;} 80. if(LastState ==14 && NowState == 0) {Key_KeyNumber=14;} 81. if(LastState ==15 && NowState == 0) {Key_KeyNumber=15;} 82. if(LastState ==16 && NowState == 0) {Key_KeyNumber=16;} 83. } 84.
MatrixKeyT.h
1. #ifndef __MATRIXKEYT_H__ 2. #define __MATRIXKEYT_H__ 3. 4. unsigned char Key(void); 5. void MatrixKey_Loop(void); 6. 7. #endif
main.c
1. #include <REGX52.H> 2. #include "Timer0.h" 3. #include "MatrixKeyT.h" 4. 5. unsigned char i;//循环变量 6. unsigned char KeyNumer; 7. unsigned int SpaceT = 500;//初值400ms 8. unsigned char Light_Mode; 9. 10. void LED_Func(); 11. 12. void main() 13. { 14. Timer0_Init(); 15. 16. while(1) 17. { 18. KeyNumer = Key(); 19. LED_Func(); 20. 21. } 22. } 23. 24. void LED_Func() 25. { 26. if (KeyNumer == 1) Light_Mode=1; 27. else if(KeyNumer == 2) Light_Mode=2; 28. else if(KeyNumer == 3) Light_Mode=3; 29. else if(KeyNumer == 4) Light_Mode=4; 30. else if(KeyNumer == 5) 31. { 32. SpaceT += 100; 33. } 34. if(SpaceT>1200) 35. SpaceT = 400; 36. 37. if(Light_Mode == 1) 38. { 39. for(i=0;i<8;) //i变量在定时器里加加,控制闪烁时间间隔 40. { 41. P2 = ~(0x01<<i); 42. // if(Light_Mode != 1) //for循环,按键键码数值改变,但是模式没及时改变,此处有个bug 43. // break; //要小写 44. } 45. } 46. 47. else if(Light_Mode == 2) 48. { 49. for(i=0;i<8;) //i变量在定时器里加加,控制闪烁时间间隔 50. { 51. P2 = ~(0x80>>i); 52. // if(Light_Mode != 2) 53. // break; 54. } 55. } 56. 57. else if(Light_Mode == 3) 58. { 59. for(i=0;i<4;) 60. { 61. P2 = ~((0x01<<i) | (0x80>>i));//两边向中间 亮灯 62. } 63. } 64. 65. else if(Light_Mode == 4) 66. { 67. for(i=0;i<4;) 68. { 69. P2 = ~((0x10<<i) | (0x08>>i)); 70. } 71. } 72. } 73. 74. void Timer0_Routine() interrupt 1 //定时器0的中断函数 75. { 76. static unsigned char Button; //按键扫描时间变量 10ms 77. static unsigned int T0Count; 78. 79. TL0 = 0x66; //设置定时初值 80. TH0 = 0xFC; //设置定时初值 81. Button++; 82. T0Count++; 83. 84. if(T0Count >= SpaceT){ 85. i++; 86. T0Count = 0; //软件复位 87. } 88. 89. if(Button >= 10){ 90. MatrixKey_Loop(); 91. Button = 0; 92. } 93. 94. }
LED流水实现部分有个BUG:因为我写的是for循环,虽然这时候按下按键,变量Light_Mode已经被重新赋值了,所以不能立刻跳出循环,执行下一个流水灯效果。之前有测试过跳出那个for循环,但是没有实现过,有兴趣的小伙伴可以尝试修改一下bug。
不过有一个解决方案,把每一个流水灯状态否存放在数组里,依次实现,这样就避免使用for循环~
补充一个模式1流水灯的方法
1. /* void forward(void)函数说明 2. 第一次给P1端口赋值 0xfe = 1111 1110 3. 延时200ms 4. 1111 1110<<1 1111 1100 最后一个跟着亮了,给最后一位清零,即灭,“或”0x01,变成1111 1101 ,下次赋值本数 5. 第二次赋值后,延时200ms 6. 1111 1101<<1 1111 1010 也要把最后一位清零,|0x01,变成1111 1011…… 7. */ 8. 9. void forward(void) 10. { 11. unsigned char TempOut = 0xfe,j; 12. for (j=0;j<8;j++) 13. { 14. P1 = TempOut; 15. Delay_ms(200); 16. TempOut = (TempOut << 1) | 0x01; 17. 18. } 19. }
小生文采拙劣,多多包涵!
欢迎大家讨论!