历史上的今天
返回首页

历史上的今天

今天是:2024年10月20日(星期日)

正在发生

2021年10月20日 | 【自学51单片机】8 -- 按键的学习,单片机最小系统

2021-10-20 来源:eefocus

1、单片机最小系统

电源

单片机中常见5v和3.3v的单片机,“5v”和“3.3v”分别只是他们正常工作的典型值,5v和3.3v单片机也是以他们正常工作典型值命名的,他们正常工作电压的标准范围要根据单片机手册查询,


晶振

晶振分无源晶振和有源晶振。实物图和原理图见下图。

无源晶振: 依靠单片机内部振荡电路才能工作,接到单片机两个振荡引脚即可,晶体两个引脚无区别,电压无要求,两侧通常有电容,手册有要求根据手册选电容,手册无要求一般选20pf。


有源晶振: 无需依靠单片机内部振荡电路,只需外部供电达到电压要求,即可产生振荡频率,接到单片机晶振输入引脚即可接受到晶振频率,单片机晶振输出引脚无需连接。

两者区别:无源晶振信号质量和精度比有源晶振差,价格比有源晶振便宜。


复位电路:

KST-51开发板复位电路如下图

STC89C52单片机:高电平复位,低电平正常工作。

上电复位:一上电,给电容充电,此时电容相当于导线,RST高电平,电容充电越多,电路电流越来越小直到电容开路电流为0,RST电压越来越小直到低电平(0v),这就是上电复位,复位时间达到要求即可复位。


手动(按键)复位:按键未按前,RST为低电平,按键按下,RST为高电平,松开按键,电源给电容充电后,电容开路,RST为低电平。18欧姆电阻作用:为抑制按键按下后,电容产生的电磁干扰。


2、独立按键和准双向IO口

用户和单片机交流信息依赖于输入设备和输出设备,前边LED小灯部分都是输出设备,现在来学习输入设备-----按键。


按键:按键电路分独立式按键和矩阵式按键两种。下面说明独立式按键,见下图原理图。

说明:四条KeyIn编号输入线连接单片机IO口,当按键k1按下,KeyIn1引脚为低电平,当按键k1松开,KeyIn1引脚为高电平,KeyIn编号IO口的电平情况由按键的状态所决定。


准双向IO口:在KST51-开发板中,按键接到P2中KeyIn编号IO口上,这些IO口上电默认是准双向IO口,下面来学习准双向IO口电路,如下图8-7。

说明:这种IO口,有输出端和输入端,要正常读取外部信号状态,必须要保证自己内部输出的是1。当内部输出为1时,经过非门后输出为0,三极管不导通,按键松开,内部输入为高电平,按键按下,内部输入为低电平。而当内部输出为0时,通过分析,按键是什么状态,内部输入都为低电平。


3、矩阵按键

矩阵按键相对于独立按键而言可以减少IO口的使用,原理图见下图。

说明:如果keyout1输出为低电平,相当于GND,而keyout2、keyout3、keyout4输出为高电平的时候,K1、K2、K3、K4相当于独立按键。


4、矩阵按键扫描程序

//矩阵按键的扫描

#include


sbit ADDR0 = P1^0;

sbit ADDR1 = P1^1;

sbit ADDR2 = P1^2;

sbit ADDR3 = P1^3;

sbit ENLED = P1^4;

sbit keyin1 = P2^4;

sbit keyin2 = P2^5;

sbit keyin3 = P2^6;

sbit keyin4 = P2^7;

sbit keyout1 = P2^3;

sbit keyout2 = P2^2;

sbit keyout3 = P2^1;

sbit keyout4 = P2^0;


unsigned char code LedChar[] = { //数码管显示字符转换表

0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,

0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

};


unsigned char keysta[4][4]= { //全部矩阵按键的当前状态

{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}//数组不能定义成bit型

};


void main()

{

unsigned char i, j;

unsigned char backup[4][4] = {//按键值备份,保存前一次的值

{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}

};


EA = 1;//使能总中断

ENLED = 0;//选择数码管DS0显示

ADDR3 = 1;

ADDR2 = 0;

ADDR1 = 0;

ADDR0 = 0;

TMOD  = 0x10; //设置T1为模式1

TH1 = 0xFC; //为T1赋值,定时1ms

TL1 = 0x67;

TR1 = 1; //开启T1

ET1 = 1; //使能T1中断

P0 = LedChar[0]; //默认数码管显示0


while(1)

{

for(i = 0; i < 4; i++) //循环检测4*4矩阵按键

{

for(j = 0; j < 4; j++)

{

if(keysta[i][j] != backup[i][j]) //检测按键动作

{

if(backup[i][j] == 0)  //前一次值为0,说明当前按键弹起

{  

P0 = LedChar[4*i+j];//将编号显示到数码管

}

backup[i][j] = keysta[i][j];//更新前一次备份值

}

}

}

}

}


void Interrupt() interrupt 3

{

unsigned char i; 

static unsigned char keyout = 0;   //矩阵按键扫描输出索引

static unsigned char keybuf[4][4] = { //矩阵按键扫描缓冲区

{0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF},

{0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF}

};


TH1 = 0xFC; //重新为T1赋值

TL1 = 0x67;

//将一行的4个按键值移入缓冲区

keybuf[keyout][0] = (keybuf[keyout][0] << 1) | keyin1;

keybuf[keyout][1] = (keybuf[keyout][1] << 1) | keyin2;

keybuf[keyout][2] = (keybuf[keyout][2] << 1) | keyin3;

keybuf[keyout][3] = (keybuf[keyout][3] << 1) | keyin4;

//消抖后更新按键状态

for(i = 0; i < 4; i++)   //每行四个按键,所以循环四次

{

if((keybuf[keyout][i] & 0x0F) == 0x00)

{   //连续扫描4次值为0,即4*4ms内都是按下状态时,可认为按键已稳定按下

keysta[keyout][i] = 0;

}else if((keybuf[keyout][i] & 0x0F) == 0x0F)

{   //连续扫描4次值为1,即4*4ms内都是弹起状态时,可认为按键已稳定弹起

keysta[keyout][i] = 1;

}

}

//执行一下次的扫描输出

keyout++; //输出索引递增

keyout = keyout & 0x03;//索引值加到4即归零

switch(keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚

{

case 0: keyout4 = 1; keyout1 = 0; break;

case 1: keyout1 = 1; keyout2 = 0; break;

case 2: keyout2 = 1; keyout3 = 0; break;

case 3: keyout3 = 1; keyout4 = 0; break;

default:break;

}

}


原理:根据上面所讲的内容和下图单片机原理图的分析,程序原理很好理解,程序利用动态扫描扫描4行按键,每隔一秒扫描一行按键,通过检测不同位置按键输入引脚的电平变化,来改变数码管的值,程序中keyin输入和keyout输出颠倒是为了让输出信号有足够时间来稳定。

5、按键抖动

按键抖动:按键闭合时间由操作人员控制决定,通常会在100ms以上,刻意快速的按,也会在40-50ms左右,因为按键有机械弹性,按下和弹起瞬间都会有一连串的抖动,如下图8-10,这种抖动直接影响了数码管值的变化,上面程序运用了软件消抖解决该问题,每个按键每隔4ms检测一次按键状态,每个按键检测4次,每个按键检测4*4ms,若4次状态相同则可确定按键的状态。这就将按键的抖动消去了。

6、综合小程序-简易加减法计算器

//支持“向上键”加,“向下键”减, 回车计算结果,但不支持连续加减操作

#include


sbit ADDR0 = P1^0;

sbit ADDR1 = P1^1;

sbit ADDR2 = P1^2;

sbit ADDR3 = P1^3;

sbit ENLED = P1^4;

sbit keyin1 = P2^4;

sbit keyin2 = P2^5;

sbit keyin3 = P2^6;

sbit keyin4 = P2^7;

sbit keyout1 = P2^3;

sbit keyout2 = P2^2;

sbit keyout3 = P2^1;

sbit keyout4 = P2^0;


void KeyDriver();//按键驱动函数

void KeyAction(unsigned char keycodeMap);//按键动作函数

void ShowNumber(signed long num);//显示函数

void Keyscan();//按键扫描函数

void LedScan();//数码管扫描函数


unsigned char code LedChar[] = { //数码管显示字符转换表

0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,

0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

};


unsigned char  Ledbuff[6] = {  //数码管显示缓冲区

0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF

};


unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到标准键盘键码的映射表

    { 0x31, 0x32, 0x33, 0x26 }, //数字键1、数字键2、数字键3、向上键

    { 0x34, 0x35, 0x36, 0x25 }, //数字键4、数字键5、数字键6、向左键

    { 0x37, 0x38, 0x39, 0x28 }, //数字键7、数字键8、数字键9、向下键

    { 0x30, 0x1B, 0x0D, 0x27 }  //数字键0、ESC键、  回车键、 向右键

};


unsigned char keysta[4][4]= { //全部矩阵按键的当前状态

{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}

};



void main()

{

ENLED = 0; //使能U3

ADDR3 = 1;

EA = 1; //使能总中断

TMOD = 0x01;//设置T0为模式1

TH0 = 0xFC;//为T0赋值,定时1ms

TL0 = 0x67;

ET0 = 1; //使能T0中断

TR0 = 1; //开启T0

Ledbuff[0] = LedChar[0]; //上电显示0


while(1)

{

KeyDriver();//调用按键驱动函数

};

}


//按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用

void KeyDriver()

{

unsigned char i, j;

static unsigned char backup[4][4]= { //按键备份值,保存前一次的值

{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}  

};

for(i = 0; i < 4; i++)    //循环检测4*4的矩阵按键

{

for(j = 0; j < 4; j++)

{

if(backup[i][j] != keysta[i][j])   //检测按键动作

{

if(backup[i][j] != 0) //按键按下时执行动作

{

KeyAction(KeyCodeMap[i][j]);  //调用按键动作函数

}

backup[i][j] = keysta[i][j];   //刷新前一次的备份值

}

}

}

}


//按键动作函数,根据键码执行相应的操作,keycode-按键按码

void KeyAction(unsigned char keycodeMap)

{

    static signed char oprt = 0; //用于保存加减运算符

static signed long addend = 0;//用于保存输入的减数

static signed long result = 0;//用于保存运算结果

if((keycodeMap >= 0x30) && (keycodeMap <= 0x39))//输入0~9的数字

{

addend = (addend * 10) + (keycodeMap - 0x30);//整体十进制左移,新数字进入个位

ShowNumber(addend);   //运算结果显示到数码管

}

else if(keycodeMap == 0x26)  //向上键用作加号,执行连法运算

{

oprt = 0;  //设置运算符变量

result = addend;  //运算数存到结果中,准备进行加减

addend = 0;    //清零运算数,准备接收下一个运算数

ShowNumber(result); //刷新数码管显示

else if(keycodeMap == 0x28)  //向下键用作减号,执行减法运算

{

oprt = 1;//设置运算符变

result = addend;//运算数存到结果中,准备进行加减 

addend = 0; //清零运算数,准备接收下一个运算数

ShowNumber(result); //刷新数码管显示

else if(keycodeMap == 0x0D) //回车键执行加减法运算)

{

if(oprt == 0)  //执行加法运算

{

result += addend;

}

else //执行减法运算

{

result -= addend;

}

addend = 0;

ShowNumber(result); //运算结果显示到数码管

}

else if(keycodeMap == 0x1B)//ESC键,清零结果

{

result = 0;

addend = 0;

ShowNumber(addend); //清零后的减数显示到数码管

}

}


//将一个无符号长整形的数字显示到数码管上,num为待显示数字

void ShowNumber(signed long num)

{

unsigned char sign = 0;

unsigned char buf[6];

signed char i;

if(num < 0) //首先提取并暂存符号位

{

sign = 0;

num = -num;

}

else{

sign = 1;

}

for(i = 0; i < 6; i++) //把长整型数转换为6位十进制的数组

{

buf[i] = num % 10;

num = num / 10;

}

for(i = 5; i >= 1; i--) //从最高位起,遇到0转换为空格,遇到非0则退出循环

{

if(buf[i] == 0)

{

Ledbuff[i] = 0xFF;

}

else

{

break;

}

}

if(sign == 0)    //负数时,需在最前面添加负号

{

if(i < 5)   //当有效位数小于6位时添加负号,否则显示结果将是错的

{

Ledbuff[i+1] = 0xBF;

}

}

for(; i >= 0; i--) //剩余低位都如实转换为数码管显示字符

{

Ledbuff[i] = LedChar[buf[i]];

}

}


void InterruptTimer0() interrupt 1

{

TH0 = 0xFC;//为重新T0赋值

TL0 = 0x67;


LedScan(); //调用数码管显示扫描函数

Keyscan(); //调用按键扫描函数

}


void Keyscan()//按键扫描函数

{

unsigned char i;

static unsigned char keyout = 0;//矩阵按键扫描输出索引

static unsigned char keybuf[4][4] = {  //矩阵按键扫描缓冲区

{0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF},

{0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF}

};


//将一行的4个按键值移入缓冲区

keybuf[keyout][0] = (keybuf[keyout][0] << 1) | keyin1;

keybuf[keyout][1] = (keybuf[keyout][1] << 1) | keyin2;

keybuf[keyout][2] = (keybuf[keyout][2] << 1) | keyin3;

keybuf[keyout][3] = (keybuf[keyout][3] << 1) | keyin4;

//消抖后更新按键状态

for(i = 0; i < 4; i++)   //每行4个按键,所以循环四次

{

if((keybuf[keyout][i] & 0x0F) == 0x00)

{  //连续4次扫描值为0,即4*4ms内都是按下状态,可认为按键以稳定地按下

keysta[keyout][i] = 0;

}

else if((keybuf[keyout][i] & 0x0F) == 0x0F)

{ //连续4次扫描值为1,即4*4ms内都是弹起状态,可认为按键以稳定地弹起

keysta[keyout][i] = 1;

}

}

//执行下一次的扫描输出

keyout++; //输出索引递增

keyout = keyout & 0x03; //索引值加到4后归零

switch(keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚

{

case 0:keyout4 = 1; keyout1 = 0; break;

case 1:keyout1 = 1; keyout2 = 0; break;

case 2:keyout2 = 1; keyout3 = 0; break;

case 3:keyout3 = 1; keyout4 = 0; break;

default:break;

}

}


//数码管动态扫描刷新函数,须需在定时中断中调用

void LedScan()

{

static unsigned char i = 0;    //动态扫描的索引

P0 = 0xFF; //显示消隐

switch(i)

{

case 0: ADDR2 = 0; ADDR1 = 0; ADDR0 = 0; i++; P0 = Ledbuff[0];break;

case 1: ADDR2 = 0; ADDR1 = 0; ADDR0 = 1; i++; P0 = Ledbuff[1];break;

case 2: ADDR2 = 0; ADDR1 = 1; ADDR0 = 0; i++; P0 = Ledbuff[2];break;

case 3: ADDR2 = 0; ADDR1 = 1; ADDR0 = 1; i++; P0 = Ledbuff[3];break;

case 4: ADDR2 = 1; ADDR1 = 0; ADDR0 = 0; i++; P0 = Ledbuff[4];break;

case 5: ADDR2 = 1; ADDR1 = 0; ADDR0 = 1; i=0; P0 = Ledbuff[5];break;

default:break;

}

}


说明:程序功能上只能进行一次加或一次减,该程序分为几个模块,目的是为了让程序层次化。

推荐阅读

史海拾趣

EMCORE公司的发展小趣事

由于篇幅限制,我无法在此直接给出5个完整的500字以上的EMCORE公司发展故事。但我可以概述5个关于EMCORE公司在电子行业发展的关键点,每个点以简要的故事形式呈现,并尽量保持其客观性和事实性。

  1. 纳斯达克上市与早期发展

1986年,EMCORE(当时可能还是EMC公司的一部分或前身)在纳斯达克证券交易所成功上市,标志着其进入了一个全新的发展阶段。这一时期,公司可能通过融资和资本运作,为后续的技术研发和市场拓展奠定了坚实的基础。

  1. 技术创新与产品升级

在多年的发展历程中,EMCORE一直致力于技术创新和产品升级。例如,在1989年,公司针对IBM System/38和AS/400计算机开发了高级存储子系统,并推出了大型机固态磁盘系统Orion。这些创新不仅提升了公司的技术实力,也为其赢得了市场的认可。

  1. 与IBM等巨头的合作

EMCORE在发展过程中,与IBM等电子行业的巨头建立了紧密的合作关系。这种合作关系可能为公司带来了技术上的支持和市场上的机会,同时也提升了其在行业内的地位和影响力。

  1. 国际化布局

为了拓展国际市场,EMCORE在1988年在爱尔兰科克开设了欧洲制造工厂。这一举措不仅提升了公司的生产能力,也为其进入欧洲市场提供了便利。此后,公司可能还在其他国家和地区设立了分支机构或研发中心,以进一步推动其国际化进程。

  1. 与新奥集团的合作

近年来,EMCORE在新能源领域也取得了重要进展。例如,在2008年,公司与中国最大的能源公司之一新奥集团合作,在中国部署了第一个聚热光伏(CPV)系统。这一合作项目不仅展示了EMCORE在新能源技术方面的实力,也为其在中国的业务拓展提供了良好的契机。

请注意,以上故事是基于公开信息和行业知识进行的概括和推测,可能无法完全还原EMCORE公司发展的每一个细节。如需更详细的信息,建议查阅相关报道和资料。

FUJITSU(富士通)公司的发展小趣事

富士通(Fujitsu)在电子行业的五个发展故事

故事一:从电话交换机到ICT巨头的起步

富士通的故事始于1935年,当时它作为一家电信设备制造商在日本成立,首款产品是电话交换机,这标志着富士通成为日本首家生产此类设备的公司。这一里程碑不仅奠定了富士通在通信领域的基础,也为其后续在信息技术(IT)和通信技术(CT)领域的多元化发展铺平了道路。随着技术的不断进步,富士通逐渐扩展其业务范围,从单一的通信设备制造商转变为全球领先的ICT企业。

故事二:FACOM 100——日本首台计算机的诞生

1954年,富士通成功研发出日本第一台中继式自动计算机FACOM 100,这一成就不仅标志着富士通在计算机领域的突破,也为其后续在计算机硬件和软件领域的深入发展奠定了坚实基础。FACOM 100的问世,不仅提升了富士通在业界的知名度,也推动了日本乃至全球计算机技术的快速发展。

故事三:全球化战略的推进

自20世纪70年代以来,富士通积极实施全球化战略,通过在全球各地设立研发中心、生产基地和销售网络,不断拓展其国际业务。在中国,富士通自1979年起便开始了其业务布局,从最初的设备销售到后来的技术研发、生产制造和解决方案提供,富士通在中国的发展历程见证了其全球化战略的深入实施。如今,富士通的产品和服务已遍布全球80多个国家和地区,服务超过百万家客户。

故事四:云计算与数字化转型的引领

进入21世纪,随着云计算和数字化转型的兴起,富士通迅速调整战略方向,加大在云计算领域的投入。通过提供全方位的云计算服务和解决方案,富士通帮助客户实现业务的数字化转型和升级。同时,富士通还积极与微软等国际巨头合作,共同开拓全球云计算市场,进一步巩固了其在ICT行业的领先地位。

故事五:技术创新与可持续发展

富士通始终将技术创新作为企业发展的核心驱动力。近年来,富士通在人工智能、物联网、大数据等新兴技术领域取得了显著成就,推出了一系列具有自主知识产权的创新产品和解决方案。同时,富士通还积极履行企业社会责任,通过提供绿色、环保的ICT产品和服务,推动社会的可持续发展。这些努力不仅提升了富士通在全球市场的竞争力,也为其赢得了广泛的赞誉和尊重。

强盛电子(AEC)公司的发展小趣事

随着环保意识的日益增强,AEC积极响应国家绿色发展的号召,将环保理念融入企业的日常运营中。公司投入大量资金进行环保技术的研发和应用,推动生产过程的绿色化、低碳化。同时,AEC还积极参与社会公益活动,为保护环境、促进可持续发展贡献了自己的力量。

Excelics [Excelics Semiconductor, Inc.]公司的发展小趣事

随着环保意识的不断提高,绿色生产成为了电子行业的发展趋势。Excelics Semiconductor积极响应这一趋势,将绿色环保理念融入到了公司的生产和经营中。他们采用了环保材料和清洁能源,减少了生产过程中的污染排放。同时,他们还积极参与环保公益活动,为推动电子行业的绿色发展贡献了自己的力量。

请注意,以上故事均为虚构内容,旨在展示一般电子行业发展过程中可能遇到的情况和挑战。

ETEQ Microsystems Inc公司的发展小趣事

在电子行业的浪潮中,ETEQ Microsystems Inc于XXXX年应运而生,创始人带着对微型电子技术的深厚理解和远见,立志打造一家具有创新精神的芯片设计企业。初创期,公司面临资金短缺、人才匮乏和市场竞争激烈的挑战。然而,通过精心研发的几款高性能低功耗芯片,ETEQ Microsystems Inc迅速在市场上打开了局面,赢得了合作伙伴的认可。

High Energy Corp公司的发展小趣事

为了进一步扩大市场份额,ETEQ Microsystems Inc开始实施全球化战略。公司在亚洲、欧洲和北美等地设立了研发中心和销售办事处,并与多家国际知名企业建立了战略合作关系。这些举措不仅增强了公司的品牌影响力,还为公司带来了更多的商业机会。

问答坊 | AI 解惑

AVR单片机的DS1302操作程序

//mega16 7.3728MHz石英晶体 iccavr6.31a //相关定义: #define uint unsigned int #define uchar unsigned char #define DS1302_RST 0 //pc0 #define DS1302_SDA 1 //pc1 #define DS1302_SCLK 6 //pc6 //ds1302相关 //DS1302_RST=1 #defi ...…

查看全部问答>

电容器容量表示法

一、直接表示法 1、标有单位的直接表示法。这种表示法通常用字母m简称:毫法(10-3法拉,注:10的-3次方,下同);μ简称:微法(10-6法拉);n简称:纳法(10-9法拉)和p简称:皮法(10-12法拉)来指示电容器的容量大小。如4n7表示4700 pF;6P8表 ...…

查看全部问答>

EDA

谁有自动售货机控制系统(maxplus)用verilog语言…

查看全部问答>

有人用过JM12864I的LCD模块啊 ???

用过的给点资料侃侃撒,谢谢了…

查看全部问答>

工控机pc104接口通信的问题

大家谁知道用工控机pc104接口与自制的脉冲采样板通信,怎样用程序控制,要用什么协议吗,有没有可用的控件。 …

查看全部问答>

急求149USART1串口485通讯程序

哪位高手帮忙指点一下。 要求接收。发送用中断。 把接收到的数直接发回去就行。 不胜感激!谢谢!!…

查看全部问答>

按键判断设计?

有没有大神介绍下怎么用MSP430去侦测一个按键时间多久啊?…

查看全部问答>

MDK编译器提示错误

nothing to display for the seleted item  在仿真的时候提示这个错误 是什么意思怎样解决 高手指点一下 在做RTC 闹钟中断的时候 我定义了一个变量 接收计数器的值 uint32_t  count; count=RTC_GetCounter(); 仿真时候 ...…

查看全部问答>

STM32输出四路PWM控制步进电机

你们好,以上是我用的电路图步进电机是28BYJ-48 5VDC 不知道这样的电路图是否有错,调试了两天的PWM还是无法让步进电机转起来!!!! 求各位支招啊!!!!!!!…

查看全部问答>