江科大51单片机学习笔记
单片机 单片机不是完成某一个逻辑功能的芯片,而是把一个计算机系统集成到一个芯片上。相当于一个微型的计算机。
命名规则:
51单片机 51单片机
是对所有兼容Intel8031指令系统
的单片机的统称,这一系列的单片机的始祖是Intel的8031单片机
,后来随着flash ROM技术的发展,8031单片机取得了长足的进展成为了应用最广泛的8bit单片机之一,他的代表型号就是ATMEL公司的AT89系列。
芯片 8051是一种8位元的单芯片微控制器,属于MCS-51单芯片的一种。8051单芯片是同步式的顺序逻辑系统
,整个系统的工作完全是依赖系统内部的时脉信号,用以来产生各种动作周期及同步信号。
看门狗:在单片机运行中,可能会遇到电磁场等恶劣环境干扰失控,造成程序进入死循环,程序跑飞(死机)等意外故障,程序的正常运行被打断,系统无法继续运行,陷入停滞状态,发生不可预料的后果。此时可通过按下复位按钮,强制系统复位。但更理想的实现是通过一套监控系统,实时监视MCU运行状态,在运行异常时,使系统摆脱故障状态。看门狗(Watchdog)技术能够解决这一问题。
单片机管脚图
引脚说明:
单片机最小系统 单片机最小系统也称为单片机最小应用系统,是指使用最少的原件组成单片机可以工作的系统
单片机最小系统的三要素为:电源电路
、晶振电路
和复位电路
。
小圆坑或者小标记所对应的引脚就是这个芯片的第 1脚,然后逆时针方向数下去,即 1 到最后一个引脚。
51单片机与stm32单片机的区别
51是8bit单片机,stm32是32bit单片机
51单片机采用冯·诺依曼结构(von Neumann architecture)
,stm32单片机采用普林斯顿体系结构(Princetion architecture)称作冯诺依曼体系
……
冯·诺依曼体系和哈佛总线体系二者的区别就是程序空间和数据空间是否是一体的。
冯·诺依曼结构的处理器使用同一个存储器,经由同一个总线传输。
哈佛结构是一种将程序指令存储 和数据存储 分开的存储器结构,可以使指令和数据有不同的数据宽度 。
哈佛结构设计复杂,但效率高。冯诺依曼结构则比较简单,但也比较慢。
详细可查看此文章:哈佛结构和冯诺依曼结构 - 知乎 (zhihu.com)
GPIO(general purpose intput output) 是通用输入输出端口的简称,可以通过软件来控制其输入和输出。51 单片机芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。
可位寻址/不可位寻址
:说白了就是可位寻址允许对寄存器某位操作,不可位寻址只能对某个寄存器进行整体赋值操作
对不可位寻址的寄存器
,若要只操作其中一位而不影响其它位时,可用“&=”、“|=”、“^=”的方法进行位操作
&=
,101 & 011 = 001,可用于置0,也可用于修改特定位
|=
,101 | 011 = 111,可用于置1
^=
,101 ^ 011 = 110,取反
LED 发光二极管(Light Emitting Diode),它具有单向导电性.
在单片机中,1代表高电平,0代表低电平。由图可知, LED 采用共阳接法,即所有LED 阳极管脚接电源 VCC,当p2口为低电平时,产生电势差,LED发光。
图中电阻显示为102,实际值为1000。
电阻值为除最后一位+最后一位值*0.
由于单片机需要不停运行,查看汇编可知,在汇编代码结束前会返回到main函数开始处,因此单片机不会停止。
我们需要在main
最后加个无限循环以保证单片机的正常运行。
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 #include "reg52.h" #include "intrins.h" #define LED P2 sbit LED1=P2^0 ; void Delay500ms (void ) { unsigned char data i, j, k; _nop_(); i = 4 ; j = 205 ; k = 187 ; do { do { while (--k); } while (--j); } while (--i); } void main () { int n=2 ; int i=0 ; while (n--) { LED=0xFF ; Delay500ms(); LED=0x00 ; Delay500ms(); } n=2 ; while (n--) { for (i=0 ;i<8 ;i++) { LED=~(0x01 <<i); Delay500ms(); } } LED=~0x01 ; Delay500ms(); n=2 ; while (n--) { for (i=0 ;i<7 ;i++) { LED=_crol_(LED,1 ); Delay500ms(); } for (i=0 ;i<7 ;i++) { LED=_cror_(LED,1 ); Delay500ms(); } } while (1 ) LED=0x00 ; }
独立按键控制 按键是一种电子开关,使用时轻轻按开关按钮就可使开关接通,当松开手时, 开关断开。实现原理是通过轻触按键内部的金属弹片受力弹动来实现接通和断开。
可以看到我们需要修改P3。
- 对于机械开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动。
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 #include <REGX52.H> #include "intrins.h" #define LED P2 void Delay10ms (unsigned int n) { unsigned char data i, j;while (n--){ i = 117 ; j = 184 ; do { while (--j); } while (--i); } } void main () { int n=3 ; int i=0 ; unsigned char LEDNum=0 ; while (1 ) { if (P3_1==0 ) { LED=~0x01 ; Delay10ms(n*5 ); while (n--) { for (i=0 ;i<7 ;i++) { LED=_crol_(LED,1 ); Delay10ms(n*5 ); } for (i=0 ;i<7 ;i++) { LED=_cror_(LED,1 ); Delay10ms(n*5 ); } } LED=0xFF ; } if (P3_0==0 ) { Delay10ms(2 ); while (P3_0==0 ); Delay10ms(2 ); LED=~LED; } if (P3_2==0 ) { Delay10ms(2 ); while (P3_2==0 ); Delay10ms(2 ); LED=~(LEDNum++); } if (P3_3==0 ) { Delay10ms(2 ); while (P3_2==0 ); Delay10ms(2 ); LED=0xFE ; while (1 ) { if (P3_0==0 ) { Delay10ms(2 ); while (P3_0==0 ); Delay10ms(2 ); LED=_cror_(LED,1 ); } if (P3_2==0 ) { Delay10ms(2 ); while (P3_2==0 ); Delay10ms(2 ); LED=_crol_(LED,1 ); } if (P3_1==0 ) { Delay10ms(2 ); while (P3_1==0 ); Delay10ms(2 ); break ; } } LED=0xFF ; } } }
数码管
LED数码管:数码管是一种简单、廉价的显示器,是由多个发光二极管封装在一起组成“8”字型的器件
通过控制LED管来控制数码管的显示。
多个数码管共用引脚。
74HC245
是一种三态输出、八路信号收发器,主要应用于大屏显示,以及其它的消费类电子产品中增加驱动。
74HC138
是常用的数字芯片,是一种3入8出译码器,三位表示八位。
这也是为什么LED采用共阳极接法,因为P2口输入的电流不够。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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #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 (unsigned int xms) { unsigned char data i, j; while (xms--){ i = 2 ; j = 239 ; do { while (--j); } while (--i); } } 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 ); Nixie(2 ,1 ); Nixie(3 ,4 ); Nixie(4 ,5 ); Nixie(5 ,1 ); Nixie(6 ,4 ); } }
单片机直接扫描:硬件设备简单,但会耗费大量的单片机CPU时间
专用驱动芯片:内部自带显存、扫描电路,单片机只需告诉它显示什么即可
模块化编程
.c文件:函数、变量的定义
.h文件:可被外部调用的函数、变量的声明
任何自定义的变量、函数在调用前必须有定义或声明(同一个.c)
使用到的自定义函数的.c文件必须添加到工程参与编译
使用到的.h文件必须要放在编译器可寻找到的地方(工程文件夹根目录、安装目录、自定义)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #ifndef __DELAY_H__ #define __DELAY_H__ void Delay (unsigned int xms) ;#endif void Delay (unsigned int xms) { unsigned char data i, j; while (xms--){ i = 2 ; j = 239 ; do { while (--j); } while (--i); } }
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 #ifndef __Nixie_H__ #define __Nixie_H__ void Nixie (unsigned char Location,Number) ;#endif #include <REGX52.H> #include "Delay.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]; Delay(1 ); P0=0x00 ;; }
LCD1602 液晶屏
函数
作用
LCD_Init();
初始化
LCD_ShowChar(1,1,’A’);
显示一个字符
LCD_ShowString(1,3,”Hello”);
显示字符串
LCD_ShowNum(1,9,123,3);
显示十进制数字
LCD_ShowSignedNum(1,13,-66,2);
显示有符号十进制数字
LCD_ShowHexNum(2,1,0xA8,2);
显示十六进制数字
LCD_ShowBinNum(2,4,0xAA,8);
显示二进制数字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <REGX52.H> #include "Delay.h" #include "lcd1602.h" void main () { int result; LCD_Init(); LCD_ShowChar(1 ,1 ,'a' ); LCD_ShowString(1 ,3 ,"hello" ); LCD_ShowSignedNum(1 ,13 ,-66 ,2 ); LCD_ShowHexNum(2 ,1 ,0xA8 ,2 ); LCD_ShowBinNum(2 ,4 ,0xAA ,8 ); while (1 ) { result++; Delay(1000 ); LCD_ShowNum(1 ,9 ,result,3 ); } }
矩阵键盘
扫描的概念
采用逐行扫描,这里就直接copy了:
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 <REGX52.H> #include "Delay.h" unsigned char MatrixKey () { 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_6==0 );Delay(20 );KeyNumber=5 ;} if (P1_5==0 ){Delay(20 );while (P1_5==0 );Delay(20 );KeyNumber=9 ;} if (P1_4==0 ){Delay(20 );while (P1_4==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_6==0 );Delay(20 );KeyNumber=6 ;} if (P1_5==0 ){Delay(20 );while (P1_5==0 );Delay(20 );KeyNumber=10 ;} if (P1_4==0 ){Delay(20 );while (P1_4==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_6==0 );Delay(20 );KeyNumber=7 ;} if (P1_5==0 ){Delay(20 );while (P1_5==0 );Delay(20 );KeyNumber=11 ;} if (P1_4==0 ){Delay(20 );while (P1_4==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_6==0 );Delay(20 );KeyNumber=8 ;} if (P1_5==0 ){Delay(20 );while (P1_5==0 );Delay(20 );KeyNumber=12 ;} if (P1_4==0 ){Delay(20 );while (P1_4==0 );Delay(20 );KeyNumber=16 ;} return KeyNumber; } #ifndef __MATRIXKEY_H__ #define __MATRIXKEY_H__ unsigned char MatrixKey () ;#endif
矩阵键盘密码锁(8位) 由于我们最大定义int整型,因此要采用两个password实现。然后每四位显示相隔,可以采用LCD_ShowChar()解决
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 #include <REGX52.H> #include "Delay.h" #include "LCD1602.h" #include "MatrixKey.h" void main () { unsigned char KeyNum; unsigned int Password1,Password2,Count; LCD_Init(); LCD_ShowString(1 ,1 ,"PASSWORD:" ); while (1 ) { KeyNum=MatrixKey(); if (KeyNum) { if (KeyNum<=10 ) { if (Count<4 ) { Password1*=10 ; Password1+=KeyNum%10 ; Count++; } LCD_ShowNum(2 ,1 ,Password1,4 ); if (Count>4 &&Count<=8 ) { Password2*=10 ; Password2+=KeyNum%10 ; Count++; } LCD_ShowNum(2 ,5 ,Password2,4 ); if (Count==4 ) Count++; } if (KeyNum==11 ) {if (Password1==1234 &&Password2==5678 ) { LCD_ShowString(1 ,14 ,"OK " ); Password1=0 ; Password2=0 ; Count=0 ; LCD_ShowNum(2 ,1 ,Password1,4 ); LCD_ShowNum(2 ,5 ,Password2,4 ); } else { LCD_ShowString(1 ,14 ,"ERR" ); Password1=0 ; Password2=0 ; Count=0 ; LCD_ShowNum(2 ,1 ,Password1,4 ); LCD_ShowNum(2 ,5 ,Password2,4 ); } } if (KeyNum==12 ) { Password1=0 ; Password2=0 ; Count=0 ; LCD_ShowNum(2 ,1 ,Password1,4 ); LCD_ShowNum(2 ,5 ,Password2,4 ); } } } }
定时器
定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。
定时器个数2个(T0、T1、T2),不同单片机个数不同。
SYSclk:系统时钟,即晶振周期,本开发板上的晶振为12MHz
CPU 时序的有关知识
在计算机体系结构和微处理器设计中,有几个与时间周期相关的概念,它们是:
振荡周期 (Oscillation Cycle):
振荡周期也称为时钟周期,是指时钟振荡器产生的一个完整的周期的时间。它是处理器内部操作的基本时间单位。
状态周期 (State Cycle):
机器周期 (Machine Cycle):
指令周期 (Instruction Cycle):
指令周期是处理器从取指令到执行完该指令所需的全部时间。
例如:外接晶振为 12MHz 时,51 单片机相关周期的具体值为: 振荡周期=1/12us; 状态周期=1/6us; 机器周期=1us; 指令周期=1~4us;
中断系统
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 void Timer0Init (void ) { TMOD|=0X01 ; TH0=0XFC ; TL0=0X18 ; ET0=1 ; EA=1 ; TR0=1 ; } #include <REGX52.H> #include "Delay.h" #include "LCD1602.h" #include "Timer0.h" unsigned char Sec=14 ,Min=45 ,Hour=11 ;void main () { LCD_Init(); Timer0Init(); LCD_ShowString(1 ,1 ,"Clock:" ); LCD_ShowString(1 ,8 ," : :" ); while (1 ) { LCD_ShowNum(1 ,8 ,Hour,2 ); LCD_ShowNum(1 ,11 ,Min,2 ); LCD_ShowNum(1 ,14 ,Sec,2 ); } } void Timer0_Routine () interrupt 1{ static unsigned int T0Count; TL0 = 0x18 ; TH0 = 0xFC ; T0Count++; if (T0Count>=1000 ) { T0Count=0 ; Sec++; if (Sec>=60 ) { Sec=0 ; Min++; if (Min>=60 ) { Min=0 ; Hour++; if (Hour>=24 ) { Hour=0 ; } } } } }
串口
串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信。
串行通信是指使用一条数据线,将数据一位一位地依次传输,每一位数据占据一个固定的时间长度。
电平标准
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
TTL电平:+5V表示1,0V表示0
RS232电平:-3-15V表示1,+3~+15V表示0
RS485电平:两线压差+2+6V表示1,-2-6V表示0(差分信号)
通信术语
常见通信接口
名称
引脚定义
通信方式
特点
UART
TXD、RXD
全双工、异步
点对点通信
I²C
SCL、SDA
半双工、同步
可挂载多个设备
SPI
SCLK、MOSI、MISO、CS
全双工、同步
可挂载多个设备
1-Wire
DQ
半双工、异步
可挂载多个设备
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 #include <REGX52.H> #include "Delay.h" #include "UART.h" void main () { UART_Init(); while (1 ) { } } void UART_Routine () interrupt 4{ if (RI == 1 ) { P2=SBUF; UART_SendByte(SBUF); RI=0 ; } } #include <REGX52.H> void UART_Init (void ) { PCON |= 0x80 ; SCON = 0x50 ; TMOD &= 0x0F ; TMOD |= 0x20 ; TL1 = 0xF4 ; TH1 = 0xF4 ; ET1 = 0 ; TR1 = 1 ; EA = 1 ; ES = 1 ; } void UART_SendByte (unsigned char Byte) { SBUF = Byte; while (TI == 0 ); TI = 0 ; }