文章目录
一、51单片机基础知识
1、单片机简介
单片机全称叫单片微型计算机
单片机的八大功能部件:
(1)微处理器8位CPU)
(2)程序存储器(ROM)
(3)数据存储器(RAM)
(4)四个8位并行可编程I/O端口(P0、P1、P2、P3)
(5)一个串行口(UART)
(6)两个16位定时器/计数器(T0/T1)
(7)中断系统(含5~8个中断源)
(8)特殊功能寄存器(SFR)
2、单片机最小系统
要使单片机工作起来,最基本的电路的构成包括
1、电源电路:向单片机供电。
2、时钟电路:单片机工作的时间基准,决定单片机工作速度。
3、复位电路:确定单片机工作的起始状态,成单片机的启动过程。
3、c51编程基本模板
#include<reg52.h>
#define uint16_t unsigned int
#define uint8_t unsigned char
void delay_ms(uint16_t ms);
int main(void){
while(1){
}
}
void delay_ms(uint16_t ms)
{
uint16_t i;
uint8_t j;
for(i=0;i<ms;i++)
{
for(j=0;j<110;j++);
}
}
-
一个小细节
-
unsigned char 和 unsigned char code? unsigned char 定义的变量存储在RAM里面 unsigned char code 定义的变量存储在ROM里面 ROM 空间比较大、且在程序运行中不可修改
#define uint16_t unsigned int
why?代码的规范的重要性
二、显示
1、LED灯
-
每个灯的赋值是按照07-00的顺序
-
1,led灯接的是单片机的P0口,意味着我们只能用P0口来编程。
2,led灯是共阳极,当给低电平时led灯才会亮,(单片机灌电流能力较强,输出电流较弱,所以一般用共阳极)
3,led所在的P0口是读io口,故使用时用一个变量来做中介
(1)首先选择端口:
两种方法:
-
使用sbit位声明方式
sbit LED0=P0^0; LED0=0;
-
直接给整个端口赋值方式
P0=0xFE;
(2)初始化+点亮
如果你想点亮一盏LED就对把单片机相对应的IO赋为低电平。
LED = 0XFF; //LED初始化
//以不同频率点亮可通过delay_ms()实现
delay_ms(200);
LED = 0XFE;
delay_ms(500);
void LED_twinkle(uint16_t time, uint16_t frequency) {
uint8_t i; //控制闪烁次数
for(i = 0; i < time; i ++) {
LED = 0X00; //亮
delay_ms(frequency);
LED = 0XFF; //灭
delay_ms(frequency);
}
}
//流水灯
#define LED P0
void LED_waterfall(uint16_t frequency)
{
uint8_t i=0;
LED =0XFE; //1111 1110
delay_ms(frequency);
for(i=0;i<7;i++)
{
LED<<=1;
// LED=LED+1; //1111 1101
LED=LED | 0X01;
delay_ms(frequency);
}
}
//利用函数 _crol_ :左移 _cror_ :右移
#include <instrins.h>
#define LED P0
int main(){
LED=0xFE;
while(1)
{
LED=_crol_(LED,1);
delay_ms(100);
}
}
个人推荐函数用法,对函数库运用有待加强
2、数码管
(1)段码和位码
char code dofly_DuanMa[]={
//0 1 2 3 4 5 6 7 8 9
0XC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90
};//段码
char code positionCode[]={
0X7F,0XBF,0XDF,0XEF,0XF7,0XFB,0XFD,0XFE
};//位选通码
LED数码管的a~g七个发光二极管。加正电压的发光,加零电压的不能发光,不同亮暗的组合就能形成不同的字型,这种组合称为字型码。
(2)静态显示
优点:占用CPU时间少,便于检测和控制
缺点:硬件电路复杂,成本高
#include <reg52.h>
char code segmentCode[]={
//0 1 2 3 4 5 6 7 8 9
0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90
};
void delay(unsigned int x);
void main()
{
unsigned char i;
P1=0x00;
while(1)
{
for(i=0;i<10;i++)
{
P0=segmentCode[i];
delay(1000);
}
}
}
void delay(unsigned int x)
{
unsigned int i,j;
for(i=x;i>0;i--)
for(j=110;j>0;j--);
}
(3)动态显示
特点:将数码管段选线并联,由位选线控制显示
优点:简化电路,占用IO口资源少
缺点:显示亮度稍差,容易出现重影
... ...
char TempData[4];
void display(unsigned char FirstBit,unsigned char Num);
void delay_ms(unsigned int x);
int main(void)
{
unsigned int num,j;
while(1)
{
j++;
if(j==30)
{
j=0;
num++;
if(num==10000)
num=0;
}
TempData[0]=dofly_DuanMa[num/1000];
TempData[1]=dofly_DuanMa[num%1000/100];
TempData[2]=dofly_DuanMa[num%100/10];
TempData[3]=dofly_DuanMa[num%10];
display(0,4);
}
}
void display(unsigned char FirstBit,unsigned char Num)
{
unsigned char i;
for(i=0;i<Num;i++)
{
P1 =0xff;//消影
P0 =TempData[i];//段码
P1 =positionCode[i+FirstBit];//位码
delay_ms(4);//扫描间隙
}
}
void delay_ms(unsigned int x)
{unsigned int i,j;
for(i=x;i>0;i--)
for(j=110;j>0;j--);
}
-
需要注意人眼的视觉暂留一般是40ms;
-
由于LED需要的驱动电流较大,对于普通的I/O口,需要增加驱动器来驱动LED. 我们手上的单片机开发板上是用三极管来驱动LED。
3、LCD1602
(1)1602LCD的基本特性:
-
+5V电压,对比度可调
-
内含复位电路
-
提供各种控制命令如:清屏、字符闪烁、光标闪烁、显示移位等多种功能
-
有80字节显示数据存储器DDRAM
-
内建有60个5X7点阵的字型的字符发生器CGROM
-
8个可由用户自定义的5X7的字符发生器
(2)静态和滚动显示
熟悉多个标注函数,以后就可直接模板套。
#include <reg52.h>
sbit LCD_RS = P1^0;
sbit LCD_RW = P1^1;
sbit LCD_EN = P1^2;
unsigned char line1[] = "I love You";
unsigned char line2[] = "So you always make me happy";
void delay_ms(unsigned int x)
{
unsigned int i, j;
for (i = x; i > 0; i--)
for(j = 110; j > 0; j--);
}
bit lcd_busy()
{
bit result;
LCD_RS = 0;
LCD_RW = 1;
LCD_EN = 1;
delay_ms(1);
result = (bit)(P0&0x80);
LCD_EN = 0;
return result;
}
void lcd_wcmd(unsigned char cmd)
{
while (lcd_busy());
LCD_RS = 0;
LCD_RW = 0;
LCD_EN = 0;
P0 = cmd;
delay_ms(1);
LCD_EN = 1;
delay_ms(1);
LCD_EN = 0;
}
void lcd_wdat(unsigned char dat)
{
while (lcd_busy());
LCD_RS = 1;
LCD_RW = 0;
LCD_EN = 0;
P0 = dat;
delay_ms(1);
LCD_EN = 1;
delay_ms(1);
LCD_EN = 0;
}
void lcd_init()
{
lcd_wcmd(0x38);
lcd_wcmd(0x0c);
lcd_wcmd(0x06);
lcd_wcmd(0x01);
delay_ms(5);
}
void lcd_pos(unsigned char pos)
{
lcd_wcmd(pos | 0x80);
}
void main()
{
unsigned char m, n = 0;
P0 =0xff;
lcd_init();
while (1)
{
lcd_wcmd(0x01);
lcd_pos(0);
m = 0;
while (line1[m] != '\0')
{
lcd_wdat(line1[m]);
m++;
}
m = 0;
lcd_pos(40);
while (line2[m + n] != '\0')
{
lcd_wdat(line2[m + n]);
m++;
}
if (n++ == 27)
n = 0;
delay_ms(1000);
}
}
(3)显示秒表
#include <reg52.h>
sbit LCD_RS = P1^0;
sbit LCD_RW = P1^1;
sbit LCD_EN = P1^2;
sbit K1 = P2^3;
unsigned char line1[] = "The Second is :";
unsigned char line2[16] = " 0";
unsigned char code a[] = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39};
unsigned int second = 0;
unsigned char begin = 0;
void delay_ms(unsigned int x)
{
unsigned int i, j;
for (i = x; i > 0; i--)
for(j = 110; j > 0; j--);
}
void timer_init()
{
TMOD = 0x01;
TH0 = 15536 / 256;
TL0 = 15536 % 256;
EA = 1;
ET0 = 1;
TR0 = 1;
}
bit lcd_busy()
{
bit result;
LCD_RS = 0;
LCD_RW = 1;
LCD_EN = 1;
delay_ms(1);
result = (bit)(P0&0x80);
LCD_EN = 0;
return result;
}
//
void lcd_wcmd(unsigned char cmd)
{
while (lcd_busy());
LCD_RS = 0;
LCD_RW = 0;
LCD_EN = 0;
P0 = cmd;
delay_ms(1);
LCD_EN = 1;
delay_ms(1);
LCD_EN = 0;
}
//
void lcd_wdat(unsigned char dat)
{
while (lcd_busy());
LCD_RS = 1;
LCD_RW = 0;
LCD_EN = 0;
P0 = dat;
delay_ms(1);
LCD_EN = 1;
delay_ms(1);
LCD_EN = 0;
}
//
void lcd_init()
{
lcd_wcmd(0x38);
lcd_wcmd(0x0c);
lcd_wcmd(0x06);
lcd_wcmd(0x01);
delay_ms(5);
}
//
void lcd_pos(unsigned char pos)
{
lcd_wcmd(pos | 0x80);
}
void writetime()
{
unsigned int i, tem;
tem = second;
for (i = 15; i >= 0; i--)
{
line2[i] = a[tem % 10];
tem /= 10;
if (!tem)
break;
}
}
void key_down()
{
static unsigned char k = 0;
K1 = 1;
if (!K1)
{
delay_ms(4);
if (!K1)
{
while (!K1);
k++;
switch (k)
{
case 1 : begin = 1; break;
case 2 : ET0 = 0; EA = 0; break;
case 3 : second = 0; begin = 0; break;
}
}
}
}
void display()
{
unsigned char i;
lcd_pos(0);
i = 0;
while (line1[i] != '\0')
{
lcd_wdat(line1[i]);
i++;
}
i = 0;
lcd_pos(40);
while (line2[i] != '\0')
{
lcd_wdat(line2[i]);
i++;
}
}
void time() interrupt 1
{
static unsigned char count = 0;
TH0 = 15536 / 256;
TL0 = 15536 % 256;
count++;
if (count == 20)
{
count = 0;
second++;
}
}
void main()
{
P0 =0xff;
lcd_init();
while (1)
{
display();
key_down();
if (begin)
{
timer_init();
while (1)
{
key_down();
writetime();
display();
if (!begin)
{
unsigned char i;
for (i = 0; i < 15; i++)
{
line2[i] = ' ';
}
line2[15] = '0';
}
}
}
}
}
三、键盘
1、按键确认
检测行线电平 高电平:断开;低电平:闭合
常用软件延时来消除按键抖动
基本思想:检测到有键按下,键对应的行线为低,软件延时4ms后,行线如仍为低,则确认该行有键按下。 当键松开时,行线变高,软件延时4ms后, 行线仍为高,说明按键已松开。
采取以上措施,躲开了两个抖动期t1和t3的 影响。
- 按键按下——对应的IO接口为低电平 反之为高电平。
- 独立按键理解为是一种开关,通过按下不同的键来控制其他模块的运作与否。
- 按键按下时会有抖动所以这里通过延时来消除影响。
2、键扫描
键扫描就是要判断有无键按下
矩阵式键盘描通常有两种实现方法:逐行扫描法和 线反转法。
**(1)逐行扫描法。**依次从第一至最末行线上 发出低电平信号, 如果该行线所连接的键没有按 下的话, 则列线所接的端口得到的是全“1”信号, 如果有键按下的话, 则得到非全“1”信号。
P2=0xef;
temp=P2;
temp=temp&0x0f;
while(temp!=0x0f) {
delay(5);
temp=P2;
temp=temp&0x0f; //读取列的值 while(temp!=0x0f) {
temp=P2;
switch(temp){
case 0xee :num=1;break;
case 0xed :num=2;break;
case 0xeb :num=3;break;
case 0xe7 :num=4;break;}
while(temp!=0x0f) //松手操作检测 {temp=P2; //若键盘依然被按下
temp=temp&0x0f; }}}
**(2)线反转法 。**线反转法也是识别闭合键的一种常用方法, 该法比行扫描速度快, 但在硬件上要求行线与列 线外接上拉电阻。
先将行线作为输出线, 列线作为输入线, 行线 输出全“0”信号, 读入列线的值, 那么在闭合键所 在的列线上的值必为0;然后从列线输出全“0” 信号,再读取行线的输入值,闭合键所在的行线 值必为 0。这样,当一个键被按下时, 必定可读到 一对唯一的行列值。再由这一对行列值可以求出 闭合键所在的位置。
- 简单来说,一个为依次顺序扫描,另一个为选确定行再确定列
//测试列
GPIO_KEY=0X0F;
switch(GPIO_KEY) {
case(0X07): KeyValue=1;break;
case(0X0b): KeyValue=2;break;
case(0X0d): KeyValue=3;break;
case(0X0e): KeyValue=4;break; }
//测试行
GPIO_KEY=0XF0; switch(GPIO_KEY) {
case(0X70): KeyValue=KeyValue;break;
case(0Xb0): KeyValue=KeyValue+4;break;
case(0Xd0): KeyValue=KeyValue+8;break;
case(0Xe0): KeyValue=KeyValue+12;break;
}
- 推荐第二种方法
3、键盘控制
//左右移动
#include <reg52.h>
#include <intrins.h>
sbit K1=P2^3;
sbit K2=P2^2;
#define SEG_SLC P0
#define POS_SLC P1
#define uint8_t unsigned char
#define uint16_t unsigned int
#define MAX_LENGTH 8
uint8_t TempData[MAX_LENGTH] = {0X00};
void delay_ms(uint16_t ms);
void main(void) {
SEG_SLC=0XFE;//初始化
while(1) {
if(K1==0)
{
delay_ms(4);
if(K1==0)
{
while(K1==0);
SEG_SLC=_cror_(SEG_SLC,1);
}
}
if(K2==0)
{
delay_ms(4);
if(K2==0)
{
while(K2==0);
SEG_SLC=_crol_(SEG_SLC,1);
}
}
}
}
void delay_ms(uint16_t ms) {
uint16_t i;
uint8_t j;
for(i = 0; i < ms; i ++) {
for(j = 0; j < 110; j ++);
}
}
4、应用
//抢答器
//设想,针对每一个按键,设置对应功能,
//可以用循环吗?
#include <reg52.h>
#define uint8_t unsigned char
#define uint16_t unsigned int
uint8_t code segmentCode[] = {
0XC0, 0XF9, 0XA4, 0XB0, 0X99, 0X92, 0X82, 0XF8, 0X80, 0X90
};
uint8_t code positionCode[] = {
0X7F, 0XBF, 0XDF, 0XEF, 0XF7, 0XFB, 0XFD, 0XFE
};
uint8_t TempData[5];
sbit K1=P2^3;
sbit K2=P2^2;
sbit K3=P2^1;
sbit K4=P2^0;
void delay_ms(uint16_t ms);
void present(uint16_t t);
int main(void) {
uint16_t t=0,a=0;
while(1) {
P1=0XFF;
if(a==0) {
if(K1==0) {
delay_ms(4);
if(K1==0) {
while(K1 == 0);
t=1;
a++;
}
}
if(K2==0) {
delay_ms(4);
if(K2==0) {
while(K2 == 0);
t=2;
a++;
}
}
if(K3==0) {
delay_ms(4);
if(K3==0) {
while(K3 == 0);
t=3;
a++;
}
}
if(K4==0) {
delay_ms(4);
if(K4==0) {
while(K4 == 0);
t=4;
a++;
}
}
}
//表示数字
else present(t);
}
}
void delay_ms(uint16_t ms) {
uint16_t i;
uint8_t j;
for(i = 0; i < ms; i ++) {
for(j = 0; j < 110; j ++);
}
}
void present(uint16_t t) {
P1 = positionCode[t-1];
P0 = segmentCode[t];
}
四、中断
1、中断寄存器
-
1.中断允许控制寄存器IE
位7 位6 位5 位4 位3 位2 位1 位0 EA ES ET1 EX1 ET0 EX0 - **EX0(EX1)😗*外部中断允许控制位
EX0=1 外部中断0开关闭合 //开外部0中断
EX0=0 外部中断0开关断开
- **ET0(ET1)😗*定时中断允许控制位
ET0=1 定时器中断0开关闭合 //开内部中断0
ET0=0 定时器中断0开关断开
- ES: 串口中断允许控制位
ES=1 串口中断开关闭合 //开串口中断
ES=0 串口中断开关断开
-
2.定时器控制寄存器TCON
•IT0(TCON.0),外部中断0触发方式控制位。
• 当IT0=0时,为电平触发方式。
• 当IT0=1时,为边沿触发方式(下降沿有效).
•IE0(TCON.1),外部中断0中断请求标志位。
•IT1(TCON.2),外部中断1触发方式控制位。
•IE1(TCON.3),外部中断1中断请求标志位。
•TF0(TCON.5),定时/计数器T0溢出中断请求标志位。 •TF1(TCON.7),定时/计数器T1溢出中断请求标志位。
-
3.定时器方式寄存器TMOD
位7 位6 位5 位4 位3 位2 位1 位0 GATE C/T M1 MO GATE C/T M1 M0 T1 T2 工作方式选择表
M1 M0 方式 说明
0 0 0 13位定时器/计数器,TL存放低5位,TH存放高八位
0 1 1 16位定时器/计数器
1 0 2 初值自动装载的8位定时器/计数器
1 1 3 (不重要)
GATE:门控制位,相当于总开关
C/T:0定时器 1计数器
-
4.串口控制寄存器SCON
位7 位6 位5 位4 位3 位2 位1 位0 SM0 SM1 SM2 REN TB8 RB8 Tl Rl SM0 SM 1 组合选择位
串行口方式选择
SM0 SM1 方式 说明 波特率
0 0 0 8位全部数据发送 fosc/12
0 1 1 10位数据发送,包括起始位,停止位 可变
1 0 2 11位数据发送,包括起始位,停止位 ,校验位 fosc/64
1 1 3 同方式2
-
SM2——多机通信控制位
-
REN——允许串行接收位
REN:串口数据接收允许位 1允许,0禁止。该位有软件置位或清0
TB8:在方式2和方式3中,这位发送的是第9位,就是校验位。
RB8:在方式2和方式3中,这位发送的是第9位,就是校验位。
-
TI:发送中断标志位 ,用完时要用软件清0
-
Rl:接受中断标志位,用完时要用软件清0
-
-
5.中断优先控制寄存器IP(不常用)
PS=1/0:串行口中断定义为高级/低级优先级中断
PT0=1/0:定时器/计数器0定义为高级/低级优先级中断 PT1=1/0:定时器/计数器1定义为高级/低级优先级中断 PT2=1/0:定时器/计数器2定义为高级/低级优先级中断
PX0=1/0: 外部中断0定义为高级/低级优先级中断
PX1=1/0: 外部中断1定义为高级/低级优先级中断
2、简要运用
1)、用到外部中断时:
EX0 = 1;//中断允许开关
IT0 = 0;//下降沿触发方式
EA = 1;//总开关
2)、用到定时/计数器中断时
ET0 = 1;//启动计数器中断开关
EA = 1;//总开关
/*
定时器的核心在这
*/
TMOD = 0x09;
TH0 = 0x0D8;
TL0 = 0x0F0;
TR0 = 1;//启动定时器
3)、用到串口中断时
EX1 = 1;//外部中断1分开关
IT1 = 1;//触发方式:下降沿
PX1 = 1;//设置为高优先级
//步骤一:波特率配置,由定时器1的益处率决定
TMOD = 0x20;//0010 0000 = 0x20,定时器1设置为工作方式2,8位自动装载的定时器
TH1 = 0xF4;//初值
TL1 = 0xF4;//波特率4800
ET1 = 1;//定时器1允许分开关
TR1 = 1;//启动定时器
//设置串口工作方式
SCON = 0x50;//等同于TMOD,方式一,允许接收 0101 0000
//PCON = 0x00;
//TI = 0;发送中断标志位
//RI = 0;接收中断标志位
ES = 1;//IE寄存器第四位,串口中断允许位
EA = 1;//外部中断总开关
//记录一个博主
未完待续…