51单片机系列
此文章记录我学单片机进程,以及所写的代码集合,当作笔记和备份吧,以后可以温习用。
目前正在学的是51单片机,普中A2版本。
先上张原理图

再来张十六进制换算表

LED模块
最简单的点灯代码
1 2 3 4 5 6 7 8 9
| #include <REGX52.H>
void main() { P2 = 0xFE; while(1) {} }
|
控制点灯时间代码
通过软件内置的时间延迟计算生成函数,来控制时间。

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
| #include <REGX52.H> #include <INTRINS.H> void Delay500ms() //@12.000MHz { unsigned char i, j, k;
_nop_(); i = 4; j = 205; k = 187; do { do { while (--k); } while (--j); } while (--i); }
void main() { while(1) { P2=0xFE; Delay500ms(); P2=0xFF; Delay500ms(); } }
|
流水灯代码
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
| #include <REGX52.H> void Delay1ms(unsigned int xms) //@12MHz { unsigned char i, j; while(xms){ i = 2; j = 239; do { while (--j); } while (--i); xms--; } }
void main() { while(1) { P2=0xFE; Delay1ms(120); P2=0xFD; Delay1ms(500); P2=0xFB; Delay1ms(100); P2=0xF7; Delay1ms(200); P2=0xEF; Delay1ms(500); P2=0xDF; Delay1ms(400); P2=0xBF; Delay1ms(300); P2=0x7F; Delay1ms(100); } }
|
通过调整函数变量,可以自由决定每一个灯亮起的时间跟间隔。
按钮控制点灯v1
1 2 3 4 5 6 7 8 9 10 11 12
| #include <REGX52.H> void main() { while(1) { if(P3_1==0) { P2_0=0; } else P2_0=1; } }
|
可以通过按钮的判断来控制小灯的亮灭了,但是这些代码只能需要按钮一直按下才可以把灯点亮,如果松开按钮灯就灭了,而且没有消抖。
按钮控制点灯v2
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
| #include <REGX52.H>
void Delay1ms(unsigned int xms) //@12MHz { unsigned char i, j; while(xms>0){ i = 2; j = 239; do { while (--j); } while (--i); xms--; } } void main() {P2_0=0; while(1) { if(P3_1==0) { Delay1ms(20); while(P3_1==0); Delay1ms(20); P2_0=~P2_0; } } }
|
这个就相对完善了很多了,已经可以实现按钮按一次就会一直亮,再按就灭的功能,而且也加入了延时来消除抖动。也引入了位运算,可以通过取反来更加便捷地控制小灯亮灭。
按钮控制点灯v3
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 <REGX52.H> char LEDNum=0; void delay(int xms) //@12MHz {while(xms) { unsigned char i, j;
i = 2; j = 239; do { while (--j); } while (--i); xms--; } } void main() { P2=~0x01; while(1) { if(P3_1==0) //实现左移 { delay(30); while(P3_1==0) delay(30); LEDNum++; if(LEDNum==8) LEDNum=0; } P2 =~(0x01<<LEDNum); if(P3_0==0) //实现右移 { delay(30); while(P3_0==0) delay(30); if(LEDNum==0) LEDNum=7; else LEDNum--; P2 =~(0x01<<LEDNum); } } }
|
通过if语句和一个中间变量再加上移位运算和取反运算,最终实现了用两个按钮来控制小灯的亮起的左移和右移,并且对小灯在边缘亮起的情况做了处理,让其可以正常运行。
静态数码管显示
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 <REGX52.H> unsigned char nixietable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00}; void nixie(unsigned char location,number) { switch(location) { 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=nixietable[number]; } void main() { nixie(2,3); while(1) { } }
|
定义了一个nixie的函数,用来更为方便的控制静态数码管,数码管的控制主要有两个来决定,一个是位,一个是数,在51单片机中,代码中的switch判断所控制的就是数码管的位置,采用的是38译码器,通过三个数的0和1来表示8个位置的数码管,大限度的减短控制所需要的接口,非常的巧妙,而代码中的nixietable数组则是控制显示的数字,通过十六进制的数字来控制所显示的东西,一切都刚刚好,具体可以去看原理图,如果使用的话,调用现成的库就足够了。
但是,这套控制系统虽然简单,只用了很少的接口,但是也有局限性,就是一次只能表示一个位置的一个数字,所以就有了动态数码管。
动态数码管
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
| #include <REGX52.H> unsigned char nixietable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00}; void delay(int xms) //@12MHz { unsigned char i, j; while(xms){ i = 2; j = 239; do { while (--j); } while (--i);xms--; } } void nixie(unsigned char location,number) { switch(location) { 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=nixietable[number]; delay(1); P0=0x00; } void main() { while(1) { nixie(1,1);delay(5); nixie(2,2);delay(5); nixie(3,3);delay(5); } }
|
这个就已经比较完善了,通过delay延时,利用人眼的延时,只要变化的够快,人眼就感知不出来(doge)。
同时呢,也加入了消隐,可以避免数码管显示出错,当然,也可以让延时改为1毫秒,也可以避免显示出错。

模块化编程
这是一种方法,我们可以将一些函数打包成一个.c文件和一个.h文件,只要做好声明并调用,就可以非常方便的使用这些函数了,并且也容易分享和修改。
下面是一个.h文件的示例
1 2 3 4 5
| #ifndef __DELAY_H__ #define __DELAY_H__ void delay(int xms); #endif
|
.c文件就是类似于自定义函数的写法,放在同一目录下就可以正常使用了。
矩阵键盘
为了简化io口,矩阵键盘也应用了类似于数码管的模式,仅使用了8个io口,便可以控制了16个矩阵按键,相应的,获取矩阵按键的状态,也是需要不断扫描,以时间赢得空间。
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
| #include <REGX52.H> #include "delay.h" #include "lcd1602.h" /** * @brief 矩阵键盘读取按健键码 * @param 无 * @retval 按下按键的键码值 */ unsigned char Martrixkey() { unsigned char KeyNumber = 0; P1=0xFF; P1_3=0; if(P1_7==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=1;} if(P1_6==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=5;} if(P1_5==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=9;} if(P1_4==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=13;} P1=0xFF; P1_2=0; if(P1_7==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=2;} if(P1_6==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=6;} if(P1_5==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=10;} if(P1_4==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=14;} P1=0xFF; P1_1=0; if(P1_7==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=3;} if(P1_6==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=7;} if(P1_5==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=11;} if(P1_4==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=15;} P1=0xFF; P1_0=0; if(P1_7==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=4;} if(P1_6==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=8;} if(P1_5==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=12;} if(P1_4==0) {delay(20);while(P1_7==0);delay(20);KeyNumber=16;} return KeyNumber; }
|
这是矩阵键盘模块的内部代码,以按列扫描的模式,不断读取每个按键的状态,并返回相对应的结果。
下面是一个简单的矩阵键盘使用案例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <REGX52.H> #include "delay.h" #include "lcd1602.h" #include "Martrixkey.h" unsigned char KeyNum; void main() { LCD_Init(); LCD_ShowString(1,2,"Hello world"); while(1) { KeyNum=MartrixKey(); if(KeyNum) { LCD_ShowNum(2,1,KeyNum,2); } } }
|
这些代码通过调用矩阵键盘的模块可以实现读取键盘的数值,并让其实时打印在LCD屏幕上。
定时器系列
delay函数之所以可以延时,是因为它通过让cpu执行一系列空的事情,来拖延出时间,所以当我们在运行delay函数时,cpu是占满的,我们这个时候再想运行其他函数,cpu是不会响应的,有弊端在,所以,引入了一个重要的模块——定时器。
定时器,是芯片内部的资源,通过晶振来推动,51单片机定时器0内部有两个寄存器TH0和TL0,都是一字节的,理解位定时器0高位寄存器(TH0),定时器0低位寄存器(TL0), 我们知道2字节最大能存0到65535一共65536个数字。每过一个指令周期(1us),寄存器的值+1,当加到溢出后发出一个溢出中断,我们程序可以捕获到这个中断,并执行中断函数内容,就可以知道此时经历了(65535+1)us。如果我们要定时1ms,设置寄存器的初值为64536,这样到溢出值65536就正好1ms。如果想要定时1s的话,设置一个计数器,让定时器循环1000次即可。
定时器由于是芯片内部的资源,所以不需要特别占用cpu,比delay好很多。
下面就是一个简单的由定时器驱动的程序。这是一个按键控制流水灯的程序,按下按键,流水灯就会换一个方向亮起,引入了第三方库INTRINS.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 34
| #include <REGX52.H> #include "Timer0.h" #include "Key.h" #include <INTRINS.H> unsigned char KeyNum,LEDmode; void main() { P2=0xFE; Timer0_Init(); while(1) { KeyNum=Key(); if(KeyNum) { if(KeyNum==1) { LEDmode++; if(LEDmode>=2)LEDmode=0; } } } } void Timer0_Routine() interrupt 1 //中断程序内容 { static signed int T0Count; TL0 = 0x66; TH0 = 0xFC; T0Count++; if(T0Count>=1000) //计次1000次以达到1s钟 { T0Count=0; if(LEDmode==0) P2=_crol_(P2,1); if(LEDmode==1) P2=_cror_(P2,1); } }
|
像这种程序如果不用定时器而用delay是实现不了的,或者说效果很差,按键根本反应不了。
1 2 3 4 5 6 7 8 9 10 11 12
| void Timer0_Init() { TMOD &= 0xF0; TMOD |= 0x01; TL0 = 0x66; TH0 = 0xFC; TF0 = 0; TR0 = 1; ET0=1; EA=1; PT0=0; }
|
这是定时器的内部寄存器配置。
PWM
PWM是一种控制输出电功率的方法,就是控制高低电平的比例,从而控制电力的输出,常用的领域有呼吸灯的制作,还有电机的调速。
呼吸灯
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
| #include <REGX52.H> sbit LED=P2^0; void delay(unsigned int t) //简易延时函数 { while(t--); } void main() { signed char time,t; while(1) { for(time=0;time<100;time++) { for(t=0;t<20;t++) //灯泡亮暗的速度太快了,加入这个循环,降低其速度 { LED=0; delay(time); LED=1; delay(100-time); } } for(time=100;time>0;time--) { for(t=0;t<20;t++) { LED=0; delay(time); LED=1; delay(100-time); } } }
}
|
这个有用了delay的方法,有规律的控制小灯暗亮。
电机调速
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
| #include <REGX52.H> #include "delay.h" #include "Key.h" #include "LCD1602.h" #include "Martrixkey.h" #include "nixie.h" unsigned char counter,compare; unsigned char keynum,speed; sbit Motor = P1^0; void main() { Timer0_Init(); while(1) { keynum=Key(); if(keynum==1) { speed++; speed%=4; if(speed==0)compare=0; if(speed==1)compare=50; if(speed==2)compare=75; if(speed==3)compare=100; } nixie(1,speed); } } void Timer0_Routine() interrupt 1 { TL0 = 0xA4; TH0 = 0xFF; counter++; if(counter>100) {counter=0; //counter%=100; } if(counter<compare) Motor=1; else Motor=0;
}
|
一共有三档调速度,通过比较counter和compare两个变量的大小,来控制电机的运转与否。
8*8点阵屏
8*8点阵屏是一个由64个小灯组成的小屏幕,类似于矩阵键盘,也是通过时间换io口,总共只需要控制11个io口,就可以在点阵屏上显示内容,,采用的是按列扫描的模式,而控制每一列的内容,则是通过3个io口进行处理,需要74HC595模块,就想填子弹一样,一个一个把数据填进寄存器,再统一发送到点阵屏上。
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
| #include <REGX52.H> #include "delay.h"
sbit RCK=P3^5; //RCLK sbit SCK=P3^6; //SRCLK sbit SER=P3^4; //SER
#define MATRIX_LED_PORT P0
/** * @brief 控制每一列的输入 * @param * @retval */
void _74HC595_WriteByte(unsigned char Byte) { unsigned char i; for(i=0;i<8;i++) { SER=Byte&(0x80>>i); SCK=1; SCK=0; } RCK=1;RCK=0;
} /** * @brief 点阵屏显示一列数据 * @param column 要选择的列,范围0-7,date 要选择的列的数据,高位在上,1为亮 * @retval */ void MatrixLED_ShowColumn(unsigned char column,date) { _74HC595_WriteByte(date); MATRIX_LED_PORT=~(0x80>>column); delay(1); MATRIX_LED_PORT=0xFF;
} void main() { SCK=0; RCK=0; /// while(1) { MatrixLED_ShowColumn(0,0x3c); MatrixLED_ShowColumn(1,0x42); MatrixLED_ShowColumn(2,0xA9); MatrixLED_ShowColumn(3,0x85); MatrixLED_ShowColumn(4,0x85); MatrixLED_ShowColumn(5,0xA9); MatrixLED_ShowColumn(6,0x42); MatrixLED_ShowColumn(7,0x3c); } }
|
这是一个简单的在点阵屏显示笑脸的代码,没有进行模块化处理,想要模块化处理就定义一个初始化函数跟一个显示函数就可以了,当然,这个点阵屏也是实时扫描的,所以想进行同时进行其他任务比较困难,当然也可以带上定时器试试。
效果图如下

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
| #include <REGX52.H> #include "delay.h" #include "MatrixLED.h"
unsigned char code Animation[]= {
}; //这个数组存放所有的列的信息 void main() { unsigned char i,offset,count=0; void Martrix_LED_Init(); while(1) { for(i=0;i<8;i++) { MatrixLED_ShowColumn(0,Animation[i+offset]); } count++; if(count>10) //调整速度用的 { count=0; offset++; //如果是显示一帧一帧的动画,而不是流水的图案,则需要改成offset+=8; offset%=24; //这个要随时调 } }
}
|
这些代码可以实现在8*8点阵屏上显示动画,将所有动画的每一行的内容,写进数组Animation中,然后再按照规律进行遍历,就可以显示动画了。