学习51单片机开发板的代码样例备份一下。
LED相关控制
点亮一个LED
先来看一下LED部分和51单片机的电路连接,这个影响到我们代码应该怎么写,比如LED是共阴极,那你LED控制亮的话,要输出高电平,如果你LED共阳极,则需要输出低电平,

1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include<8052.h> // 0.01 * 100 = 1ms * 500 void delay_500ms(){ int us = 50000; while(us--); } void main() { // 1111 1110 P2 = 0xFE; delay_500ms(); P2 = 0xFF; delay_500ms();
}
|
单片机LED轮流闪烁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include<8052.h> // 0.01 * 100 = 1ms * 500 void delay_500ms(){ int us = 50000; while(us--); } void main() { P2 = 0xFE;// 1111 1110 delay_500ms(); P2 = 0xFD;// 1111 1101 delay_500ms(); P2 = 0xFB;// 1111 1011 delay_500ms(); P2 = 0xF7;// 1111 0111 delay_500ms(); P2 = 0xEF;// 1110 1111 delay_500ms(); P2 = 0xDF;// 1101 1111 delay_500ms(); P2 = 0xBF;// 1011 1111 delay_500ms(); P2 = 0x7F;// 0111 1111 delay_500ms(); }
|
按键控制LED
单个独立按键控制LED,按下点亮,松开熄灭
这里主要是检测IO口的电平高低,上电后寄存器可以操作IO引脚电平高低,比如P1_0 = 1 输出高电平,那么同理,反过来 如果外围电路按键,参考电路图,
这里的电路图,K1按键按下,P3_1 和 GND导通,那么P3_1 自然就是低电平,此时如果去检测P3_1 IO口的电平,那他就是低电平,IO口除了可以用来输出高低电平,还可以用作检测输入,那么这里,是把按键绑定到了LED亮灭来指示效果。
1 2 3 4 5 6 7 8 9 10
| #include<8052.h> void main() { while(1) { if(P3_1 == 0) { P2_0 = 0; } else { P2_0 = 1; } } }
|
两个按键一起绑定,控制LED亮灭,K1 & K2 一起按下LED点亮,松开其中一个熄灭
同样,中间的表达式改成 || 的话,就两个按键 都可以控制LED亮灭。再同样,左移,右移操作,可以玩LED灯的按位与,按位或,可以实现一个逻辑门电路的结果展示,以及LED左右移,来实现流水灯。
1 2 3 4 5 6 7 8 9 10
| #include<8052.h> void main() { while(1) { if(P3_1 == 0 && P3_0 == 0) { P2_0 = 0; } else { P2_0 = 1; } } }
|
按键控制LED长亮,或长灭
这里包含了按键消抖,按键因为是机械部件,设计不太会很精密,按下和松开的时候,不会直接就变高电平或低电平,他可能会有一部分抖动的时间,大概20ms左右才变稳定,所以要加按键消抖。
而代码上,P2_0 的取反,江科大他写的是 P2_0 = ~P2_0; 但是在我们这里不起作用,问了AI,建议是用 逻辑取反,因为AI说 具体运行的时候,P2_0会被当作int来处理,int是16位,也不知真假,估计是编译器的问题,可能用Keil就正常了,这个差异,AI说是我们编译器的问题,我们用的是 sdcc,这个本身没有什么问题,能够学到就ok了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include<8052.h> // 0.01 * 100 = 1ms * 500 void delay(int xms){ int us = xms * 100; while(us--); }
void main() { while(1) { if(P3_1 == 0) { // 按键消抖 delay(20); while(P3_1 ==0); delay(20); P2_0 = !P2_0; } } }
|
LED展示二进制加法操作
P2++ 这个是核心的操作,最开始是1111 1111 8个全亮,++ 第一次全部熄灭,然后按一次D1亮,按第二次,D1灭,D2亮,第三次D1再亮,D2也亮,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include<8052.h> // 0.01 * 100 = 1ms * 500 void delay(int xms){ int us = xms * 100; while(us--); }
void main() { while(1) { if(P3_1 == 0) { // 按键消抖 delay(20); while(P3_1 ==0); delay(20); P2++; } } }
|
LED显示二进制加法操作的另一种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include<8052.h>
// 0.01 * 100 = 1ms * 500 void delay(int xms){ int us = xms * 100; while(us--); }
void main() { unsigned char LED = 0; while(1) { if(P3_1 == 0) { // 按键消抖 delay(20); while(P3_1 ==0); delay(20); LED++; P2 = ~LED; } } }
|
使用位移来实现LED流水灯
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include<8052.h> // 0.01 * 100 = 1ms * 500 void delay(int xms){ int us = xms * 100; while(us--); }
void main() { unsigned char LED = 0; P2 = ~0x01; while(1) { if(P3_1 == 0) { // 按键消抖 delay(20); while(P3_1 ==0); delay(20); LED++; LED = LED%8; P2 = ~(0x01<<LED);// 这里LED会是0-7,0x01是 0000 0001 然后取反, 只有一个灯高电平,但是这个LED输出低电平才会亮,所以
} } }
|
一个按键控制LED往左移,一个按键控制LED往右移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include<8052.h> // 0.01 * 100 = 1ms * 500 void delay(int xms){ int us = xms * 100; while(us--); }
void main() { unsigned char LED = 0; P2 = ~0x01; while(1) { if(P3_1 == 0) { // 按键消抖 delay(20); while(P3_1 ==0); delay(20); LED++; LED = LED%8; P2 = ~(0x01<<LED); } if(P3_0 == 0) { // 按键消抖 delay(20); while(P3_0 ==0); delay(20); LED--; LED = LED%8; P2 = ~(0x01<<LED); } } }
|
单片机控制数码管的操作
背景资料
我们使用的数码管,有单个位的,只能显示一个数字,一般是有共阴极数码管和共阳极数码管,每个段其实可以看成是一个LED,那么数码管的每一位其实都是很多LED组成,单个段的话,ABCDEFG DP 从数码管顶那个横杠开始,,这两个编程方式不一样,
示意图如图所示,abcdefgDP 这几个 是开发的时候每个数码管的段选顺序,按照这个特定的顺序,给ABCDEFG DP几个引脚 给对应的亮灭,就可以让这个数码管出现对应的数字,而共阴极和共阳极的数码管,体现在段选的差异上,段选就是说要哪个数码管显示数字, 
比如上面的图,上面的是共阴极,那如果想要第一位 数码管显示,就要12引脚给低电平,其他9 8 6三个引脚给高电平,而ABCDEFG DP 这几个位,可以看到这几个是统一接到了 1 2 3 4 5 7 10 11 这几个引脚上,可以提高单片机IO口的复用率,用一套8个引脚,控制所有数码管的位选,而用另外4个引脚,控制4个段选,而我们这个单片机上一共有2组4位一体数码管,所以位选是统一绑定到P00-P07,也就是P0口上,而段选,可以看到旁边有个 74LS138芯片,他可以通过3个引脚,控制8个引脚输出高低电平,因为2^3=8,而每个引脚可以有2个状态,高低电平,通过这个,可以进一步减少IO口的占用,提高IO口的使用效率。可以看下接线:
,可以看到,P22,P23,P24这三个引脚通过38译码器,控制LED1-LED8,而LED1-LED8正好是8个LED的段选引脚,再然后,可以看一下 又有一个 74HC245,他是一个单向的输出功能,可以理解成是驱动电路,数码管需要VCC来驱动电路,而单片机的IO口引脚输出的电平太低,需要有一个类似三极管的芯片,这里可以不用管这个 74HC245,对编程无感。
第一个例程,数码管的静态显示
主要用到段选,和位选,位选负责控制数码管显示什么数字,段选控制哪个数码管亮,P22-P24 3个引脚,负责控制8个数码管。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include<8052.h> // 0.01 * 100 = 1ms * 500 void delay(int xms){ int us = xms * 100; while(us--); } void main() { // 计算第几位,是反过来的 比如 P22-P24 000 得出来0,Y0,对应LED1,那就是第8个数码管,101 得出来5,那就是Y5,得出来第3个数码管 // 如果想要第四个,那就是 101 100 P2_4 = 1; P2_3 = 0; P2_2 = 1; P0 = 0x7D; while(1){} }
|
通过内置的数组和 switch case 来实现数码管单个的延时显示
主要内容是循环每个数码管的位,在每个数码管位上轮流显示0-F,显示间隔为500ms
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #include<8052.h> // 0.01 * 100 = 1ms * 500 void delay(int xms){ int us = xms * 100; while(us--); }
unsigned char digital_led_numbers[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x00}; void digital_led_segment(int seg, int number) { switch (seg) { case 1: P2_4 = 1; P2_3 = 1; P2_2 = 1; break; case 2: P2_4 = 1; P2_3 = 1; P2_2 = 0; break; case 3: P2_4 = 1; P2_3 = 0; P2_2 = 1; break; case 4: P2_4 = 1; P2_3 = 0; P2_2 = 0; break; case 5: P2_4 = 0; P2_3 = 1; P2_2 = 1; break; case 6: P2_4 = 0; P2_3 = 1; P2_2 = 0; break; case 7: P2_4 = 0; P2_3 = 0; P2_2 = 1; break; case 8: P2_4 = 0; P2_3 = 0; P2_2 = 0; break; } P0 = digital_led_numbers[number]; }
void main() { while(1){ for(int i=1; i<9; i++) { for(int j=0; j< 16; j++) { digital_led_segment(i, j); delay(500); } } } }
|
数码管的动态显示与消影
上面的基础上,要在第一位显示1,第二位显示2,第三位显示3,需要注意一下digital_led_segment 这个函数的最后两行,delay(1);P0 = 0x00,这个就是去消影,因为数码管的段选和位选是同时操作的,所以会有概率段选和位选串位,导致下一个段显示了上一个段的内容,所以说 在输出之后立马把P0口置0,就不会受到上一位的影响。这个是因为我们这个74LS138这个芯片,被设置为单向输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #include<8052.h> // 0.01 * 100 = 1ms * 500 void delay(int xms){ int us = xms * 100; while(us--); }
unsigned char digital_led_numbers[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x00};
void digital_led_segment(int seg, int number) { switch (seg) { case 1: P2_4 = 1; P2_3 = 1; P2_2 = 1; break; case 2: P2_4 = 1; P2_3 = 1; P2_2 = 0; break; case 3: P2_4 = 1; P2_3 = 0; P2_2 = 1; break; case 4: P2_4 = 1; P2_3 = 0; P2_2 = 0; break; case 5: P2_4 = 0; P2_3 = 1; P2_2 = 1; break; case 6: P2_4 = 0; P2_3 = 1; P2_2 = 0; break; case 7: P2_4 = 0; P2_3 = 0; P2_2 = 1; break; case 8: P2_4 = 0; P2_3 = 0; P2_2 = 0; break; } P0 = digital_led_numbers[number]; delay(1); P0 = 0x00; }
void main() { while(1){ digital_led_segment(1,1); // delay(20); digital_led_segment(2,2); // delay(20); digital_led_segment(3,3); // delay(20); } }
|
模块化编程
模块化编程,其实就是 把原本写到一个 main.c文件里的内容,分散到多个 .h 和 .c 组成的 文件中,可以代码结构比较合理,同C语言的分散编程。
然后他教程里,直接出了个 LCD1602的debug演示,所以我们就从 LCD1602这个地方开始学,学完之后再去debug LCD1602之后的课程继续学。
LCD1602的控制与输出
LCD1602的概念,也就是一个 16列,2行的古早显示器,每行每列都是一个5*7的点阵组成的字符显示输出设备,LCD1602的原理就和现代LCD屏幕比较像了,所以学一下,后面可以控制更现代化的LCD屏幕。
先看一下LCD1602的电路图,相关的代码,得结合LCD 1602的数据手册来分析,主要是RS,RW,和E这几个引脚,以及1是控制写命令,还是写数据,写数据得往P0 这个组合引脚写,因为我们这几个LCD的数据引脚,在D0-D7,A/K引脚,VCC,VSS,GND这几个也可以不管,模块也都解决了。
那么主要就是看LCD1602的数据手册了,数据手册上有给一些demo样例参考,可以看一下。
代码部分,由于用了分布模块的方式,每个文件都在自己的那一小块里面,所以一起贴上来吧。
main.c,主函数,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include<8052.h> #include "delay.h" #include "lcd_1602.h" void main() { // 注意一下,这里的每一行注释 就是一个例程,需要搭配 LCD_Init 函数一起用,最后那个 while循环里写 0x18是控制LCD屏幕滑动,实现类似字幕循环滚动的效果 LCD_Init(); // LCD_ShowChar(1,1, '\\'); // LCD_ShowString(1,2, "abcdefg"); // LCD_ShowNum(1,1, 88, 4); // LCD_ShowSignedNum(1,1, -32768, 5); // LCD_ShowHexNum(1,1, 0xFF, 2); // LCD_ShowBinNum(1,1, 0xA5, 8);
LCD_ShowString(1,1, "Welcome to CHINA!");
while(1){ LCD_WriteCommand(0x18); delay(500); } }
|
delay.h,这个就不贴了,是个延时函数,可以参考上面的 delay函数抄一个
lcd_1602.h,这个里面主要是 LCD1602的控制和显示部分了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
| #include<8052.h> #include "delay.h"
#ifndef __LCD_1602_H__ #define __LCD_1602_H__
#define LCD_RS P2_6 #define LCD_RW P2_5 #define LCD_E P2_7 #define LCD_DataPort P0
void LCD_WriteCommand(unsigned char command) { // 参考数据手册 LCD_RS = 0; LCD_RW = 0; LCD_DataPort = command; LCD_E = 1; delay(1); LCD_E = 0; delay(1); }
void LCD_WriteData(unsigned char data) { // 参考数据手册 LCD_RS = 1; LCD_RW = 0; LCD_DataPort = data; LCD_E = 1; delay(1); LCD_E = 0; delay(1);
}
void LCD_Init() { LCD_WriteCommand(0x38); LCD_WriteCommand(0x0C); LCD_WriteCommand(0x06); LCD_WriteCommand(0x01); }
void lcd_setCursor(unsigned char row, unsigned char col) { if(row == 1){ LCD_WriteCommand(0x80|(col-1)); } else { LCD_WriteCommand(0x80|(col-1)+0x40); }
}
void LCD_ShowChar(unsigned char row, unsigned char col, unsigned char data) { lcd_setCursor(row, col); LCD_WriteData(data); }
void LCD_ShowString(unsigned char row, unsigned char col, unsigned char *strs) { lcd_setCursor(row, col); for(int i=0;strs[i]!= '\0'; i++) { LCD_WriteData(strs[i]); } }
int lcd_pow(int x, int y) { int result = 1; for(int i=0; i<y; i++) { result =result * x; } return result; }
void LCD_ShowNum(unsigned char row, unsigned char col, int num, int length) { lcd_setCursor(row, col); for(int i=length; i>0; i--) { LCD_WriteData(num/ lcd_pow(10, i-1)%10 + '0'); } }
void LCD_ShowSignedNum(unsigned char row, unsigned char col, int num, int length) { lcd_setCursor(row, col); unsigned int showNum = 0; if(num >=0) { LCD_WriteData('+'); showNum = num; } else { LCD_WriteData('-'); showNum = -num; } for(int i=length; i>0; i--) { LCD_WriteData(showNum/ lcd_pow(10, i-1)%10 + '0'); } }
void LCD_ShowHexNum(unsigned char row, unsigned char col, unsigned int num, int length) { lcd_setCursor(row, col); unsigned char bitNum = 0; for(int i=length; i>0; i--) { bitNum = num/ lcd_pow(16, i-1)%16; if(bitNum < 10) { LCD_WriteData(bitNum + '0'); } else { LCD_WriteData(bitNum + 'A' - 10); } } }
void LCD_ShowBinNum(unsigned char row, unsigned char col, unsigned int num, int length) { lcd_setCursor(row, col); for(int i=length; i>0; i--) { LCD_WriteData(num/ lcd_pow(2, i-1)%2 + '0'); } }
#endif
|
这个代码上,还是有很多小巧思,易读,模块化,以及抽出冗余的地方,这些还是真的挺牛逼的。
矩阵键盘
矩阵键盘输入检测以及结合LCD1602做显示
电路图,
同理,delay.h,lcd1602.h,这几个都不继续贴了,主要贴一下 核心的 metrixkeys.h,还有另外的几个方法,叫做线反转法,或者是行检测,列检测等多种方法,等后面有空了再给独立补全一下这些方法。实现的效果,就是按 S1-S16,LCD上显示对应的标号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include<8052.h> #include "delay.h" #ifndef __MARRTXKEYS_H__ #define __MARRTXKEYS_H__
unsigned char MetrixKeys() { unsigned char KeyNum = 0;
P1 = 0xFF; P1_3 = 0; if(P1_7 ==0) { delay(20); while(P1_7 == 0); delay(20); KeyNum = 1; } if(P1_6 ==0) { delay(20); while(P1_6 == 0); delay(20); KeyNum = 5; } if(P1_5 ==0) { delay(20); while(P1_5 == 0); delay(20); KeyNum = 9; } if(P1_4 ==0) { delay(20); while(P1_4 == 0); delay(20); KeyNum = 13; }
P1 = 0xFF; P1_2 = 0; if(P1_7 ==0) { delay(20); while(P1_7 == 0); delay(20); KeyNum = 2; } if(P1_6 ==0) { delay(20); while(P1_6 == 0); delay(20); KeyNum = 6; } if(P1_5 ==0) { delay(20); while(P1_5 == 0); delay(20); KeyNum = 10; } if(P1_4 ==0) { delay(20); while(P1_4 == 0); delay(20); KeyNum = 14; }
P1 = 0xFF; P1_1 = 0; if(P1_7 ==0) { delay(20); while(P1_7 == 0); delay(20); KeyNum = 3; } if(P1_6 ==0) { delay(20); while(P1_6 == 0); delay(20); KeyNum = 7; } if(P1_5 ==0) { delay(20); while(P1_5 == 0); delay(20); KeyNum = 11; } if(P1_4 ==0) { delay(20); while(P1_4 == 0); delay(20); KeyNum = 15; }
P1 = 0xFF; P1_0 = 0; if(P1_7 ==0) { delay(20); while(P1_7 == 0); delay(20); KeyNum = 4; } if(P1_6 ==0) { delay(20); while(P1_6 == 0); delay(20); KeyNum = 8; } if(P1_5 ==0) { delay(20); while(P1_5 == 0); delay(20); KeyNum = 12; } if(P1_4 ==0) { delay(20); while(P1_4 == 0); delay(20); KeyNum = 16; } return KeyNum; } #endif
|
还有一个main.c里面的调用的地方:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include<8052.h> #include "delay.h" #include "lcd_1602.h" #include "matrixkeys.h"
unsigned char keyNum = 0;
void main() { LCD_Init(); // LCD_ShowChar(1,1, '\\'); // LCD_ShowString(1,2, "abcdefg"); // LCD_ShowNum(1,1, 88, 4); // LCD_ShowSignedNum(1,1, -32768, 5); // LCD_ShowHexNum(1,1, 0xFF, 2); // LCD_ShowBinNum(1,1, 0xA5, 8);
LCD_ShowString(1,1, "Welcome to CHINA!");
while(1){ // LCD_WriteCommand(0x18); // delay(500); keyNum = MetrixKeys(); if(keyNum != 0) { LCD_ShowNum(2,1, keyNum, 2); } } }
|
矩阵键盘密码锁
在矩阵键盘的基础上,衍生出来的密码锁应用,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include<8052.h> #include "delay.h" #include "lcd_1602.h" #include "matrixkeys.h"
unsigned char keyNum = 0; int pass = 0; int count = 0;
void main() { LCD_Init(); LCD_ShowString(1,1, "Password:"); while(1){ keyNum = MetrixKeys(); if(keyNum != 0) { if(keyNum <=10) { if(count < 4) { pass = pass*10; pass += keyNum % 10; } count++; } LCD_ShowNum(2,1, pass, 4); if(keyNum == 11) { if(pass == 1234) { LCD_ShowString(1, 14, "OK "); pass = 0; count = 0; LCD_ShowNum(2,1, pass, 4); } else { LCD_ShowString(1, 14, "ERR"); pass = 0; count = 0; LCD_ShowNum(2,1, pass, 4); } } if(keyNum == 12) { pass = 0; count = 0; LCD_ShowNum(2,1, pass, 4); } } } }
|
定时器
定时器的基础知识
STC89C52RC里面有三个定时器,T0/T1/T2,一般不用T2,只用T0,T1。详细的内容要参考数据手册来写,不过有个问题是 定时器和晶振的频率还有区别。
我的开发板 晶振是11.0592Mhz,所以在AI的辅助下,我和江科大的定时器参数有些许的不一样,如果你的晶振也是12Mhz,可以按照他的来。
他的思路
他的思路是:他的晶振是12Mhz,用16位计数器,计数器每累加一次 代表一个时钟周期,单片机内部采用12T分频,也就是1us,得到 12Mhz / 12 = 1us,就是1微秒,刚好能被整除,然后16位计数器累加最大的就是 2^16=65536,我们用无符号整数就可以刚好拿到 0-65535这个值,那么这个累加器每次超过65535以后,这个值就要溢出归零,溢出归零以后,我们通过寄存器开一个中断,溢出以后定时器就会调用一次中断函数,来达到我们的目的。
那么可以给TH0和 TL0这两个寄存器,这两个就是存那个16位累加器的数字的,给他俩一个初始值,这样子,限定到65535 只有 1000的位置,假如说是64535,那么初始化上去,他第一次就从64535开始累加,累加1000次,也就是1000us=1ms,自动触发中断函数,中断函数里再次把这个寄存器给到这个初始,然后就一直能够得到稳定的1ms,这样子我们累计1000次,得到1000ms=1s,这样子1s 以后 就可以控制 P2_0口反转,获得稳定的时间。
我们的思路
我们的晶振是11.0592Mhz,被1ms除不尽,所以永远会有误差,我们如果想要模拟1ms的话, T = 12 / 11.0592 = 1.08507us,定时1ms = 1000us / 1.08507us = 921.6次累加,那所以是 近似922次,65536-922=64614,所以我们的TH0和 TL0初始值是 64614,但是我试了,LED闪烁可能刚开始还好,后来就慢慢不准。但,按照我们这个晶振频率,给出来50ms是准确的,能够被算法除尽,所以我们就改成了50ms中断一次。
main.c 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include<8052.h> #include "timer0.h" void Timer0_Routine() __interrupt(1) { // 这里是ai给的一个补偿的代码,好像是回读高位补偿,也不太懂 推荐了就给写上去,能够得到和现代时间比较接近的时间了 unsigned int n = ((unsigned int)TH0 << 8) | TL0; n += 19456; TH0 = n >> 8; TL0 = n; T0TimerCount++; // 50ms 中断一次 20*50 = 1000ms = 1s if(T0TimerCount >= 20) { T0TimerCount = 0; // TODO 这里是你想实现的控制位 P2_0 = !P2_0; P2_1 = !P2_1; } }
void main() { Timer0_Init(); while(1) { } }
|
timer0.h代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include<8052.h>
static unsigned int T0TimerCount = 0; void Timer0_Init() { // 工作模式寄存器,0x01 代表:M0=1 M1=0 代表定时器工作在16位累加器,TH0 和 TL0 两个8位寄存器 TMOD = 0x01; TF0 = 0; // 中断标志位 TR0 = 1; // TH0 = 19456/256; // 这里是 因为我的单片机是 11.0592Mhz的晶振,改为按50ms 触发一次中断 TL0 = 19456%256; ET0 = 1; EA = 1; PT0 = 0; }
/* 定时器中断函数 void Timer0_Routine() __interrupt(1) { unsigned int n = ((unsigned int)TH0 << 8) | TL0; n += 19456; TH0 = n >> 8; TL0 = n; T0TimerCount++; // 50ms 中断一次 20*50 = 1000ms = 1s if(T0TimerCount >= 20) { T0TimerCount = 0; // TODO 这里是你想实现的控制位 } } */
|
结合定时器+独立按键+LED流水灯的左移,右移
独立按键,实现控制LED左移右移的变量;另外是这个方法里,多了很多其他的东西,有循环移位,之类的操作比较高级了;
库函数:intrins.h,因为这个是keil的库函数,所以我们自己开发的话,AI给我们提供了一个自己实现的库函数,放到 include目录里 引入就好。
在这里,我们用的是 crol 循环左移 和 cror 循环右移这两个函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #ifndef __INTRINS_H__ #define __INTRINS_H__
// SDCC 兼容版 intrins.h,模拟 Keil C51 内建函数
#define _nop_() __asm__("nop")
static unsigned char _crol_(unsigned char v, unsigned char n) { n %= 8; if (n == 0) return v; return (unsigned char)((v << n) | (v >> (8 - n))); }
static unsigned char _cror_(unsigned char v, unsigned char n) { n %= 8; if (n == 0) return v; return (unsigned char)((v >> n) | (v << (8 - n))); }
static unsigned char _irol_(unsigned int v, unsigned char n) { n %= 16; if (n == 0) return v; return (unsigned int)((v << n) | (v >> (16 - n))); }
static unsigned char _iror_(unsigned int v, unsigned char n) { n %= 16; if (n == 0) return v; return (unsigned int)((v >> n) | (v << (16 - n))); }
static unsigned char _testbit_(volatile unsigned char *b, unsigned char bit) { if (*b & (1 << bit)) { *b &= (unsigned char)~(1 << bit); return 1; } return 0; } #endif
|
timer0.h 的内容,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include<8052.h>
static unsigned int T0TimerCount = 0; void Timer0_Init() { // 工作模式寄存器,0x01 代表:M0=1 M1=0 代表定时器工作在16位累加器,TH0 和 TL0 两个8位寄存器 TMOD = 0x01; TF0 = 0; // 中断标志位 TR0 = 1; // TH0 = 19456/256; // 这里是 因为我的单片机是 11.0592Mhz的晶振,改为按50ms 触发一次中断 TL0 = 19456%256; ET0 = 1; EA = 1; PT0 = 0; }
/* 定时器中断函数 void Timer0_Routine() __interrupt(1) { unsigned int n = ((unsigned int)TH0 << 8) | TL0; n += 19456; TH0 = n >> 8; TL0 = n; T0TimerCount++; // 50ms 中断一次 20*50 = 1000ms = 1s if(T0TimerCount >= 20) { T0TimerCount = 0; // TODO 这里是你想实现的控制位 } } */
|
keys.h 的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include<8052.h> #include "delay.h" #ifndef __KEYS_H__ #define __KEYS_H__ unsigned char Key() { unsigned char KeyNum = 0; if(P3_1 ==0) { delay(20); while(P3_1 == 0); delay(20); KeyNum = 1; } if(P3_0 ==0) { delay(20); while(P3_0 == 0); delay(20); KeyNum = 2; } if(P3_2 ==0) { delay(20); while(P3_2 == 0); delay(20); KeyNum = 3; } if(P3_3 ==0) { delay(20); while(P3_3 == 0); delay(20); KeyNum = 4; } return KeyNum; } #endif
|
主函数 main.c的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include<8052.h> #include "delay.h" #include "keys.h" #include "intrins.h" #include "timer0.h"
// #include "lcd_1602.h"
unsigned char keyNum = 0; unsigned char ledMode = 0;
void Timer0_Routine() __interrupt(1) { unsigned int n = ((unsigned int)TH0 << 8) | TL0; n += 19456; TH0 = n >> 8; TL0 = n; T0TimerCount++; // 50ms 中断一次 10*50 = 500ms if(T0TimerCount >= 10) { T0TimerCount = 0; // TODO 这里是你想实现的控制 if(ledMode ==0 ) { P2 = _crol_(P2, 1); } else { P2 = _cror_(P2, 1); }
} }
void main() { P2 = 0xFE; Timer0_Init(); // LCD_Init(); while(1) { unsigned char keyNum = Key(); if(keyNum != 0) { if(keyNum == 1) { ledMode++; if (ledMode >=2) { ledMode = 0; } } } } }
|
结合LCD1602,和定时器,做一个简易的24小时时钟
先来main.c的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include<8052.h> #include "delay.h" #include "intrins.h" #include "timer0.h" #include "lcd_1602.h"
unsigned char sec, min, hour = 0;
void Timer0_Routine() __interrupt(1) { unsigned int n = ((unsigned int)TH0 << 8) | TL0; n += 19456; TH0 = n >> 8; TL0 = n; T0TimerCount++; // 50ms 中断一次 20*50 = 1000ms = 1s if(T0TimerCount >= 20) { T0TimerCount = 0; // TODO 这里是你想实现的控制 sec++; if(sec >=60) { min++; sec = 0; } if(min>=60){ hour++; min = 0; sec = 0; } if(hour >=24) { hour = 0; min = 0; sec = 0; } } }
void main() { LCD_Init(); LCD_ShowString(1,1, "Now Colck:"); LCD_ShowString(2,1, " : :"); Timer0_Init(); while(1) { LCD_ShowNum(2,1, hour, 2); LCD_ShowNum(2,4, min, 2); LCD_ShowNum(2,7, sec, 2); } }
|
其他的参考一下之前的就可以,可以在LCD1602上显示一个 时分秒的时钟,到了24小时还会归零那种,学到这里 成就感满满,可以睡了。