51单片机笔记

本文详细介绍了51单片机的基础知识,包括单片机介绍、最小系统搭建、C51编程模板,以及LED灯、数码管、LCD1602显示控制、键盘操作(包括按键确认、扫描和控制)和中断系统使用。深入浅出地展示了如何通过实例掌握这些关键技术。
摘要由CSDN通过智能技术生成


请添加图片描述

一、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 charunsigned 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
    EAESET1EX1ET0EX0
    • **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
    GATEC/TM1MOGATEC/TM1M0
    T1T2

    工作方式选择表

    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
    SM0SM1SM2RENTB8RB8TlRl

    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;//外部中断总开关

//记录一个博主

51单片机教程

未完待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不是很彳亍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值