首页 > 其他分享 >51单片机

51单片机

时间:2022-09-28 05:44:05浏览次数:52  
标签:定时器 中断 51 unsigned char 单片机 key 按键

1 定时器0延时模块

Timer0Delay.c 代码

#include <REGX52.H>

/**
  * @brief  定时器0初始化,1ms中断一次
  * @param  无
  * @retval 无
  */
void Timer0Init()
{                       //此代码及定时器初值可以用STC-ISP软件定时器计算器生成
	TMOD &= 0xF0;		//定时器0模式,工作 方式1,仅用TR0打开启动
	TMOD |= 0x01;		//设置定时器模式
	TH0=0XFC;	//给定时器赋初值,1毫秒@11.0592MHz,误差0.04%
	TL0=0X66;	
	ET0=1;//打开定时器0中断允许
	EA=1;//打开总中断
	TR0=1;//打开定时器			
}
/* 定时器0中断程序模板,copy到主程序中
void Timer0() interrupt 1
{
	static u16 i;
	TH0=0XFC;	//给定时器赋初值,定时1ms
	TL0=0X66;
	i++;
	if(i==1000)  //延时1000ms
	{
		i=0;
		//此处放入定时1s到后执行代码	
	}	
} */

2 延时模块

Delay.c 代码

#include<intrins.h>	

/**
  * @brief  延时程序N毫秒,阻塞
  * @param  xms 延时毫秒数
  * @retval 无
  */
void Delay(unsigned int xms)		//@11.059MHz,延时1ms
{			//用stc-isp程序软件延时计算器先生成延时1ms的程序,再修改加入参数			
	unsigned char i, j;
	while(xms--)
	{
		_nop_();
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
	}
}

3 独立按键判断程序

//独立按键,不用延时去抖动,用定时器20ms调用一次

#include <REGX52.H>

unsigned char TimeKey_get_key()  //返回按键值1-4,无按键返回0
{
	unsigned char key=0;
	if(P3_1==0){key=1;} 
	if(P3_0==0){key=2;}
	if(P3_2==0){key=3;}
	if(P3_3==0){key=4;}
	return key;
}
/*
void Timer0Init(void)		//1毫秒@11.0592MHz
{
	TMOD &= 0xF0;		//设置定时器16位模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x66;		//设置定时初始值
	TH0 = 0xFC;		//设置定时初始值
	TF0 = 0;		//清除TF0标志
	ET0=1;
	EA=1;
	PT0=0;
	TR0 = 1;		//定时器0开始计时
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	static unsigned char last_key,now_key;
	TL0 = 0x66;		//设置定时初始值
	TH0 = 0xFC;		//设置定时初始值
	T0Count++;
	if(T0Count>=20)
	{
		T0Count=0;
		last_key=now_key;
		now_key=TimeKey_get_key();
		if(last_key==0 && now_key!=0)
			//执行代码,只有上次无按键,这次有按键才动作
	}
}
*/
#include <REGX52.H>  
#include "Delay.h"

sbit key=P3^1;
sbit led=P2^0;
void main()
{
	while(1)
	{
		if(key==0)			//如果K1按键按下
		{
			//1.有按键阻塞,松开按键才动作
            Delay(20);		//延时消抖
			while(key==0);	//松手检测
			Delay(20);		//延时消抖
			led=~led;		//LED1取反,松开按键才动作
            
            //2.有按键阻塞,按键后就动作,等待释放
            Delay(20);		//延时消抖
			if(key==0)
			{
				led=~led;  //按键按下就动作
				while(key==0);	//松手检测,不松开就阻塞
			}
            
            //3.有按键阻塞,长按超时500ms退出函数(继续按压会检测到按键重复输入)。按键后就动作
            unsigned char i=0;
            Delay(20);		//延时消抖
			if(key==0)
			{
				led=~led;  //按键按下就动作
				while((key==0)&&(i<50)) //松手检测,不松开超时500ms后继续执行
                {
                	Delay(10);
                	i++;
                }
			}
        }
    }
}

4 矩阵键盘获取按键模块

定时器20ms扫描矩阵键盘,不用延时去抖动

#include <REGX52.H>

#define GPIO_KEY P1 //P17-P14为行(P17在上),P13-P10为列(P13在左)

/**
  * @brief  获取矩阵按键的值
  * @param  GPIO_KEY的四位用于键盘的行,另4位用于键盘的列
  * @retval 按键的值(0-15),无按键返回255
  */
unsigned char TimeScanKey_getKey()
{
	unsigned char key=255; //无按键返回255
	GPIO_KEY = 0xf0; //四列置低
	if(GPIO_KEY!=0xf0) //有按键,高四位就不全为高电平
	{
		GPIO_KEY = 0xf0;      //先置列全为0
		switch(GPIO_KEY)
		{
			case(0x70):key=0;break; //第一行有按键
			case(0xb0):key=4;break;
			case(0xd0):key=8;break;
			case(0xe0):key=12;break;
		}
		GPIO_KEY = 0x0f;      //再置行全为0
		switch(GPIO_KEY)
		{
			case(0x07):key=key;break; //第一列有按键
			case(0x0b):key=key+1;break;
			case(0x0d):key=key+2;break;
			case(0x0e):key=key+3;break;
		}
	}
	return key;
}

/*
void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	static unsigned char last_key,now_key;
	TL0 = 0x66;		//设置定时初始值
	TH0 = 0xFC;		//设置定时初始值
	T0Count++;
	if(T0Count>=20)
	{
		T0Count=0;
		last_key=now_key;
		now_key=TimeScanKey_getKey(); 
		if(last_key==255 && now_key!=255)
			//有键执行代码,只有上次无按键,这次有按键才动作
	}
}
*/

MatrixKey.c 代码(延时程序去抖动)

#include <REGX52.H>
#include "Delay.h"

#define GPIO_KEY P1 //P17-P14为行(P17在上),P13-P10为列(P13在左)

/**
  * @brief  获取矩阵按键的值
  * @param  GPIO_KEY的四位用于键盘的行,另4位用于键盘的列
  * @retval 按键的值(0-15),无按键返回255
  */
unsigned char getKey()
{
	unsigned char key=255,a=0; //无按键返回255,a按键时间计时
	GPIO_KEY = 0xf0; //四列置低
	if(GPIO_KEY!=0xf0) //有按键,高四位就不全为高电平
	{
		Delay(10);
		if(GPIO_KEY!=0xf0) //延时后,无按键返回
		{                      //开始按键扫描
			GPIO_KEY = 0xf0;      //先置列全为0
			switch(GPIO_KEY)
			{
				case(0x70):key=0;break; //第一行有按键
				case(0xb0):key=4;break;
				case(0xd0):key=8;break;
				case(0xe0):key=12;break;
			}
			GPIO_KEY = 0x0f;      //再置行全为0
			switch(GPIO_KEY)
			{
				case(0x07):key=key;break; //第一列有按键
				case(0x0b):key=key+1;break;
				case(0x0d):key=key+2;break;
				case(0x0e):key=key+3;break;
			}
			while(GPIO_KEY!=0x0f && a < 50) //等待按键释放或者超时500ms退出函数
			{
				Delay(10);
				a++;
			}
		}
		
	}
	return key;
}

5 动态数码管显示模块

Nixie.c (用定时器1ms中断一次,每1-2ms显示一位。

//定时器1ms中断一次,中断中调用一次,显示一位。
#include <REGX52.H>

//数码管段码表
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7c,0x39,0x5e,0x79,0x71,
							0xbf,0x86,0xdB,0xcF,0xe6,0xeD,0xfD,0x87,0xfF,0xeF,0xf7,0xfc,0xb9,0xde,0xf9,0xf1,
							0x00}; //共阴数码管d0-d7对应abcdefg和dp,第一排是0-9a-f,第二排是0.-9.a.-f.,最后0x00是熄灭
//数码管显示缓存区
unsigned char Nixie_Buf[8]={0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20}; //从左到右的8位led数码管的显示数据区,开始都存放熄灭段码,可以把数组放到头文件或者用extern共享

#define SegmentGPIO P0    //段码:P0口经过74hc245缓冲,1点亮
#define PosGPIO P2 //位选P24、P23、P22经过74HC138译码器,最左边数码管y7(对应3个全一)
void Nixie_scan(unsigned char location)  //location 显示位置(从最左到最右依次是1到8)
{
	SegmentGPIO=0x00;  //消影
	PosGPIO=(PosGPIO & 0xe3) | ((8-location)<<2); //送位选,位置1,位选应该是全一
	SegmentGPIO=NixieTable[Nixie_Buf[location-1]];
}

void Nixie_loop(void)
{
	static unsigned char i=0;
	Nixie_scan(i+1);
	i=++i & 0x07;
}
/*
void Timer0_Routine() interrupt 1
{
	Nixie_loop();//1ms中断一次,调用显示1位
}
*/

LedNumberShow.c 代码(显示一位后,延时,直到8位都显示一遍)

#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,
							0xbf,0x86,0xdB,0xcF,0xe6,0xeD,0xfD,0x87,0xfF,0xeF,0xf7,0xfc,0xb9,0xde,0xf9,0xf1,
							0x00}; //共阴数码管d0-d7对应abcdefg和dp,第一排是0-9a-f,第二排是0.-9.a.-f.,最后一排0x00是熄灭

#define SegmentGPIO P0
#define PosGPIO P2 //P2口的4、3、2位

/**
  * @brief  八个数码管动态显示一遍,段码:P0口经过74hc245缓冲,位选P24、P23、P22经过74HC138译码器,最左边数码管y7(对应3个全一)
  * @param  存放8个无符号字符的数组,
  * @retval 无
  */
void LedNumShow(unsigned char table[8])
{
	unsigned int i;
	for(i=0;i<8;i++)
	{
		SegmentGPIO=NixieTable[table[7-i]]; //送段码,先送最右边
		PosGPIO=(PosGPIO & 0xe3) | (i<<2); //送位选,先送最右边
		Delay(1);    //为避免按键消抖动延时影响动态刷新显示,可以用定时器中断,每10ms调用本函数一次
		SegmentGPIO=0x00;  //消影
	}
}

5-2 LCD1602调试工具

lcd1602模块的DB0--DB7脚接P00-P07脚,E脚接P27,RW接P25,RS脚接P26

把LCD1602.c和LCD1602.h文件复制到main.c文件同目录下

#include <REGX52.H>
#include "LCD1602.h"	//包含LCD1602头文件
#include "Delay.h"		//包含Delay头文件

int Result=0;

void main()
{
	LCD_Init();
	while(1)
	{
		Result++;					//Result自增
		Delay(1000);				//延时1秒
		LCD_ShowNum(1,1,Result,3);	//在LCD的1行1列显示Result,长度为3位
	}
}

函数 作用
LCD_Init(); 初始化
LCD_ShowChar(1,1,'A'); 显示一个字符。第一个参数:行位置12,第二个参数:列位置116
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); 显示二进制数字

6 串口模块,初始化,发给上位机调试信息

UART.c 代码

#include <REGX52.H>

/**
  * @brief  串口初始化,4800bps@11.0592MHz
  * @param  无
  * @retval 无
  */
void UART_Init()
{
	SCON=0x50;  //SM0 SM1为方式1,8位UART。REN为1,允许接收。TI RI为0
	PCON &= 0x80;  //SMOD保持不变为0,波特率不加倍。
	TMOD &= 0x0F;		//设置定时器1模式,不影响定时器0
	TMOD |= 0x20;		//设置定时器1,8位重装
	TL1 = 0xFA;		//设定定时初值 4800波特率
	TH1 = 0xFA;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
	EA=1;   //打开总中断
	ES=1;   //打开串口中断
}

/**
  * @brief  串口发送一个字节数据
  * @param  Byte 要发送的一个字节数据
  * @retval 无
  */
void UART_SendByte(unsigned char Byte)
{
	SBUF=Byte;
	while(TI==0);
	TI=0;
}

/*     用puts printf 输出到上位机串口调试信息
#include <REGX52.H>
#include <stdio.H>

void main()
{
	unsigned int i=6235;
	unsigned char j=200;
	float k=2.569;
	UART_Init();
	
	TI=1;           //puts printf执行之前先置TI为1
	puts("sun wen ping."); //puts 输出串口字符串,并且换行
	while(!TI);
	TI=0;
	
	TI=1;
	printf("%f--%d--%c",k,i,j); // 2.569000--6235--c8 不换行
	while(!TI);
	TI=0;
	
	while(1)
	{
	}	
}
*/

/*串口中断函数模板
void UART_Routine() interrupt 4 // 函数名任意,interrupt 4 标明是串口中断处理程序
{
	if(RI==1)
	{
		RI=0;
	}
}
*/

9-2 LED点阵屏显示动画

8*8 led点阵,8列共阴极从左到右,接P07--P00. 8行共阳极从上到下分别接74hc595的D7--D0。

74hc595(74hc164类似):串行输入并行输出的移位寄存器。三脚控制,SER脚数据在SERCLK脚的上升沿移入移位寄存器,移位寄存器的数据在RCLK脚的上升沿写入输出寄存器。oe为高,输出高阻。MR为低,清空移位寄存器。

MatrixLED.c 代码

#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  74HC595写入一个字节
  * @param  Byte 要写入的字节
  * @retval 无
  */
void _74HC595_WriteByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		SER=Byte&(0x80>>i); //高位先进,位赋值规则,非零即1
		SCK=1;  //上升沿移位
		SCK=0;
	}
	RCK=1;  //上升沿移位到输出
	RCK=0;
}

/**
  * @brief  点阵屏初始化
  * @param  无
  * @retval 无
  */
void MatrixLED_Init()
{
	SCK=0;   //两位 置低,为上升沿做准备
	RCK=0;
}

/**
  * @brief  LED点阵屏显示一列数据
  * @param  Column 要选择的列,范围:0~7,0在最左边,低电平点亮列
  * @param  Data 选择列显示的数据,高位在上,1为亮,0为灭
  * @retval 无
  */
void MatrixLED_ShowColumn(unsigned char Column,Data)
{
	_74HC595_WriteByte(Data); //输出显示数据数据
	MATRIX_LED_PORT=~(0x80>>Column); //输出列选
	Delay(1);           
	MATRIX_LED_PORT=0xFF;   //消除数据 列选的重叠
}

main.c 代码

#include <REGX52.H>
#include "Delay.h"
#include "MatrixLED.h"

//动画数据,用“字模提取”软件生成
unsigned char code Animation[]={  //关键字code,会把数组放到程序代码中,减少RAM占用
	0x3C,0x42,0xA9,0x85,0x85,0xA9,0x42,0x3C,
	0x3C,0x42,0xA1,0x85,0x85,0xA1,0x42,0x3C,
	0x3C,0x42,0xA5,0x89,0x89,0xA5,0x42,0x3C,
};

void main()
{
	unsigned char i,Offset=0,Count=0;
	MatrixLED_Init();
	while(1)
	{
		for(i=0;i<8;i++)	//循环8次,显示8列数据
		{
			MatrixLED_ShowColumn(i,Animation[i+Offset]);
		}
		Count++;			//计次延时,画面重复送led显示16次。
		if(Count>15)
		{
			Count=0;
			Offset+=8;		//偏移+8,切换下一帧画面
			if(Offset>16)   //最后一个画面已经显示?
			{
				Offset=0;    //切换到第一个画面
			}
		}
	} //代码是动态显示3个完整画面,如果是动态左移显示,改变代码offset+=8 为 offset++
}     //offset>16,根据数组点阵数据修改

10 c51变量定义

c51变量定义的四要素:

1【存储种类】(标准c) 2 数据类型 (标准c+c51)3【存储类型】(c51特有) 4变量名(标准c)

举例:extern(缺省为auto,static) unsigned char(sfr) data(code) temp;

  • 存储种类:

    • auto(自动型):缺省默认,变量的作用范围在定义它的函数体或语句块内,执行结束后,内存即被释放。
    • extern(外部型):在一个源文件中定义的变量,在其它源文件中需要通过extern说明方可使用。
    • static(静态型):变量定义所在的函数或语句块执行结束后,其分配的内存单元继续保留。
  • 数据类型:

    • 标准c数据类型:char、int、long 及三者的unsigned类型,float、double,p*
    • c51增加的类型:bit(位变量)、sfr和sfr16(特殊功能寄存器)、sbit(sfr位地址变量)
  • 存储类型:

    • data:片内低128B存储区。
    • bdata:片内可位寻址存储区(低128B中20H--2FH共16字节,位地址00H--7FH)
    • idata:片内高128B存储区(地址80H-FFH,只有52系列才有)只能通过@R0或@R1间接寻址
    • pdata:片外页RAM(一页256B,高8位地址一样)
    • xdata:片外64KB RAM(stc89c52系列大容量单片机集成了1024B的扩展RAM,汇编中寻址用dptr、Ri间接寻址,c语言中声明为xdata)
    • code:程序ROM(数据不可以更改)
    • 三种编译模式对应三种缺省存储类型:small(小编译模式)对应data,Compact(紧凑模式)对应pdata,Large(大编译模式)对应xdata。
  • 变量名:变量名可以由字母、数字和下划线三种字符组成,且第一个字符必须为字母或

11 stc89c52 定时计数器

定时器1.2相关寄存器

定时计数器模式1

定时计数器01相关寄存器

TF1(TF0):定时器/计数器T1(T0)溢出标志。响应中断后,由硬件清零(也可由程序查询清零)。

TR1(TR0): 定时器T1(T0)的运行控制位。由软件置位和清零。置位后计数条件:GATE=0或者GATE=1,INT1脚为高电平。

IT1(IT0): 外部中断1触发方式控制位。IT1=0,外部中断INT1引脚为低电平触发。IT1=1,下降沿触发。

IE1(IE0): 外部中断1请求源标志。当引脚INT1低电平或者下降沿,置位IE1. 响应中断后,由硬件清零。

GATE:为0不影响。置1时,只有INT引脚为高时,TR才能有效。(可以测量INT脚高电平时间)

C/T: 清零为定时器(内部时钟输入),置1为计数器(从T引脚输入)。

M1、M0: 模式选择。00(13位)、01(16位)、10(8位自动重装载)、11(定时器0作为双8位)

#include <REGX52.H>

typedef unsigned char BYTE;
typedef unsigned int WORD;

#define FOSC 11059200L  //晶振频率
#define TIMS (65536-FOSC/12*0.001) // 16位定时器,12T模式,1毫秒(0.001秒)中断一次

WORD count; //计数变量,对1ms计数1000次,即1s执行一次代码

void tm0_isr() interrupt 1
{
	TL0=TIMS;  //16位赋值给8位,自动取低8位
	TH0=TIMS>>8;
	if(count--==0)
	{
		count=1000;
		//执行代码
	}
}

void main()
{
	TMOD &= 0xF0;		//setp1:设置定时器模式1,16位模式
	TMOD |= 0x01;		//
	TF0 = 0;		//清除TF0标志
	TL0=TIMS;       //setp2:设置定时器初始值
	TH0=TIMS>>8;
	ET0=1;      //setp3:打开中断
	EA=1;
	count=0;
	TR0=1;  //setp4:最后才启动定时器,以免启动早,进入中断
	while(1);
}

定时器2相关寄存器表:

定时计数器2相关寄存器

TF2:定时器2溢出标志,必须由软件清除。定时器2用作串口时(RCLK或TCLK=1),TF2将不会置位。

TR2: 定时器2启动/停止控制位,置1时启动定时器。

C/T2: 0=内部定时器(12T或者6T),1=外部事件计数器(T2引脚下降沿触发)

EXEN2:定时器2外部使能标志。置位时且定时器2未作为串行口时钟时,允许T2EX的负跳变产生捕获或重装。

EXF2:定时器2外部标志。当EXEN2=1且T2EX的负跳变产生捕获或者重装时,EXF2置位。必须软件清零。递增递减模式(DCEN=1)中,EXF2不会引起中断。

RCLK:接收时钟标志。置位后,定时器2作为串行口模式1和3的接收时钟。RCLK=0,串行口用定时器1。

TCLK:发送时钟标志。置位后,定时器2作为串行口模式1和3的发送时钟。TCLK=0,串行口用定时器1。

CP/RL2: 捕获/重装标志。置位且EXEN2=1时,T2EX的负跳变产生捕获。清零且EXEN2=0时,定时器溢出或者T2EX的负跳变都可使定时器自动重装。定时器2用作串行口时,此位无效,溢出自动重装。

T2OE:定时器2输出使能位

DECN:向下计数使能位。为0时,默认向上计数。为1时,T2EX引脚确定递增或者递减计数。

定时计数器2工作方式

定时计数器2设置

定时器2的捕获模式:

定时计数器2

定时器2作为16位定时器或者计数器,溢出置位TF2。该模式中,无重新装载值。

当EXEN2=1时,T2EX的负跳变,将TL2、TH2的值捕获到RCAP2L、RCAP2H中,并且置位EXF2。

TF2、EXF2中断地址相同,中断程序中查询。

定时器2的自动重装模式(递增、递减计数器)

定时计数器21

定时计数器22

DCEN=0,自动重装模式:16位向上计数,溢出或者负跳变(EXEN2使能)时16位重新装载。

DCEN=1,递增递减模式:T2EX=1时,递增计数,溢出置位TF2,且把RCAP2值重新装载。

​ T2EX=0时,递减计数,当计数到等于RCAP时,中断,重装值为0XFFFF。

12 中断系统

中断查询顺序

中断寄存器

中断允许位

EA: 总中断允许控制位。

ET2、ET1、ET0: 定时/计数器T2、T1、T0中断允许位。

ES: 串行口1中断允许位。

EX0、EX1、EX2、EX3 : 外部中断0、1、2、3中断允许位。

中断请求标志位

TF0、TF1、TF2、EXF2 :定时/计数器T0、T1、T2溢出标志位,定时器2外部标志位。

IE0、IE1、IE2、IE3:外部中断中断请求标志位。

RI、TI 串行口中断标志位。

外部中断源类型选择位

IT0、IT1、IT2、IT3 :为0,引脚低电平触发外部中断,为1,下降沿触发。

中断优先级(只设置IP,2级)

PX3H,PX3、PX2H,PX2、PX1H,PX1、PX0H,PX0: 外部中断。00-01-10-11对应优先级0-1-2-3

PT2H,PT2、PT1H,PT1、PT0H,PT0 :定时器。

PSH,PS : 串口1.

13 串行口通信

窜口相关寄存器

SM0 SM1(SMOD0=0时): 串行口工作方式,0 0 方式0,移位寄存器。0 1 方式1,8位UART,波特率可变。1 0 方式2,9位UART。1 1 方式3,9位,波特率可变. (SMOD0=1时,SM0用于帧错误检测,无效停止位时置位)

SM2:多机通信允许控制位。方式2、3时,如果SM2=1,REN=1,只有接收到RB8为1(地址帧)时,RI才为一。

REN: 允许、禁止串行接收控制位。为1,允许接收。为0,禁止接收。

TB8、RB8:方式2、3中是发送或接收到的第9位。

TI、RI:发送(接收)中断请求标志位。必须用软件复位。中断入口是 一个,中断处理程序中判断。

SMOD:置位时,方式1、2、3波特率加倍。

SMOD0:帧错误检测有效控制位。置位时,SM0用于帧错误检测,无效停止位时置位。

SADEN SADDR: 从机地址掩膜寄存器,从机地址寄存器。

标签:定时器,中断,51,unsigned,char,单片机,key,按键
From: https://www.cnblogs.com/sunwenping/p/16736637.html

相关文章