6、吴鉴鹰单片机项目详细解析系列连载之基于单片机的电子秤设计(二)——源代码设计介绍
/******************************************************
*程序:吴鉴鹰基于单片机的电子秤的设计程序
** 这一讲的主要内容: 电子称是什么,讲的专业点就是:
利用物体的重力作用来确定物体质量的测量仪器,也可用来确定与物体质量相关的其他量的大小,
参数,或特性用我们自己话讲就是测重量的。
** 功能描述:
** 输 入:
** 内容:
** 输 出:
**
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.22
备注:有什么错误的地方,欢迎各大烧友指正
O(∩_∩)O~
*******************************************************/
#include <reg51.h>
#include <intrins.h>
#include<stdlib.h>
#include<math.h>
#define uchar unsigned char //宏的定义变量类型 uchar 代替 unsigned char
#define uint unsigned int //宏的定义变量类型 uint 代替 unsigned int
#define delay_time_max 50 //按键去抖动延时阀值
sbit RS = P2^0;
sbit RW = P2^1;
sbit SCLK = P2^2;
sbit LCDRST = P2^3;
sbit ST=P3^6; //启动信号
sbit EOC=P3^3; //转换结束信号,连到外部中断1口,转换结束后进入外部中断
sbit OE=P3^7; //输出使能
uchar KEY_VALUE;
uchar key_data;
uchar dis_buf; //显示缓存
uchar temp;
uchar key; //键顺序码
uchar result;
uint f;
void delay(uchar x); //x*0.14MS
// 此表为 LED 的字模 0 1 2 3 4 5 6 7 8 9 a b c d e f
unsigned char code LED7Code[] = {~0x3F,~0x06,~0x5B,~0x4F,~0x66,~0x6D,~0x7D,~0x07,~0x7F,~0x6F,~0x77,~0x7C,~0x39,~0x5E,~0x79,~0x71};
unsigned char ADC0809[],KONGBVAI[],DANJIA[];
void ADC0809_change();
/*************************************************************************************
** 函数名称: delayms
** 功能描述: 译码功能,将需要显示的数字转成相应的七段译码表
如要显示的字符为“0”,则为7e ---0111 1111,就中间的一个LED不亮
** 输 入: ms:需要延时的长度
**
**
** 输 出:
**
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.22
**-----------------------------------------------------------------------------------
** 修改人:吴鉴鹰
** 日 期:
**----------------------------------------------------------------------------------
****************************************************************************************/
void delayms(uint ms)
{
uchar i;
while(ms--) for(i=0;i<120;i++);
}
/*********************************************************************************
** 函数名称: SendByte
** 功能描述: 串口给液晶发送数据
** 输 入: Dbyte
**
**
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.28
************************************************************************************/
void SendByte(uchar Dbyte)
{
uchar i;
for(i=0;i<8;i++)
{
SCLK = 0;
Dbyte=Dbyte<<1; //左移一位
RW = CY; //移出的位给RW
SCLK = 1;
SCLK = 0;
}
}
/***********************************************************************************
** 函数名称: TransferCom
** 功能描述: 串口给液晶发送命令
** 输 入: data0
**
**
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.28
**********************************************************************************/
void TransferCom(uchar data0)
{
RS=1;
SendByte(0xf8); //11111ABC,RW(0),RS(1),0
SendByte(0xf0&data0); //高四位
SendByte(0xf0&data0<<4); //低四位(先执行<<)
RS=0;
}
/*******************************************************************************
** 函数名称: lcd_mesg
** 功能描述: 写数据,
** 输 入: data1
**
**
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.28
*****************************************************************************/
void TransferData(uchar data1)
{
RS=1;
SendByte(0xfa); //11111ABC,RW(0),RS(1),0
SendByte(0xf0&data1); //高四位
SendByte(0xf0&data1<<4); //低四位(先执行<<)
RS=0;
}
/************************************************************************
** 功能描述: 12864初始化程序,为液晶屏的正常工作做准备
** 输 入:
**
**
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.28
***********************************************************************/
void initinal(void) //LCD字库初始化程序
{
delay(40); //大于40MS的延时程序
LCDRST=0; //复位
delay(1); //延时
LCDRST=1; //复位置高
delay(10);
TransferCom(0x30); //Extended Function Set :8BIT设置,RE=0: basic instruction set, G=0 :graphic display OFF
delay(100); //大于100uS的延时程序
TransferCom(0x0C); //Display Control,D=1,显示开
delay(100); //大于100uS的延时程序
TransferCom(0x01); //Display Clear
delay(10); //大于10mS的延时程序
TransferCom(0x06); //Enry Mode Set,光标从右向左加1位移动
delay(100); //大于100uS的延时程序
}
/***********************************************************************
** 函数名称: lcd_mesg
** 功能描述: 汉字的显示,输入要显示汉字的地址,以及个数,已经要显示的字符的数组地址
** 输 入: han,lie ,k ,*chn
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.28
*************************************************************************/
void lcd_mesg(uchar han,uchar lie,uchar k,uchar *chn)
{
uchar i;
switch(han)
{
case 1:TransferCom(0x80+lie);break;
case 2:TransferCom(0x90+lie);break;
case 3:TransferCom(0x88+lie);break;
case 4:TransferCom(0x98+lie);break;
default:;
}
delay(100);
for(i=0;i<k;i++)
{
TransferData(chn);
}
}
/***************************************************************************
** 函数名称: JZkeyscan (这里采用的是线反转法)
** 功能描述: 矩阵式键扫描子程序 (4*4 的矩阵) P1.4 P1.5 P1.6 P1.7为行
P1.3 P1.2 P1.1 P1.0为列
独立式按键扫描子程序(4个独立式按键) P2.4 P2.5 P2.6 P2.7
** 输 入:
**
**
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.28
*******************************************************************************/
uchar JZkeyscan(void)
{
uchar sccode,recode,key_times,key_lock,JZ_FLAG;
P1=0xf0; //置所有列为低电平,列扫描,行线输入(此时)
if((P1&0xf0) == 0xf0)
{
key_lock = 0; //如果在这个时候有个毛刺,在这个时候将标志位key_lock置为0
key_times = 0; //将key_times置为0,这样可以有效的预防干扰,吴鉴鹰自己用过,效果很好
JZ_FLAG = 0; //取代纯延时的标志位,如果该标志位置为1,相当于原来的delay(0执行完成
}
if(((P1&0xf0)!=0xf0)&&(key_lock == 0)) //判断是否有有键按下(读取列的真实状态,若第4列有键按下则P1的值会变成0111 0000),有往下执行
{
if(key_times < delay_time_max) //用这种方法代替过去的delay()延时去抖动
{ //这样就不会在按键处理时影响干别的事情
key_times++; //由于很多工业控制项目对时间的要求是很高的,是不允许
} //纯延时的,所以吴鉴鹰采用这种方法解决这种问题
else //我也是工作后才意识到的,所以分享给大家
{
key_times = 0; //如果满足这个条件,表示相当于delay()延时完成了
JZ_FLAG = 1;
}
if(((P1&0xf0)!=0xf0)&&(key_lock == 0)&&(JZ_FLAG == 1)) //再次判断列中是否是干扰信号,不是则向下执行
{
sccode=0xFE; //逐行扫描初值(即先扫描第1行)
while((sccode&0x10) != 0) //行扫描完成时(即4行已经全部扫描完成)sccode为1110 1111停止while
{
P1=sccode; //输出行扫描码
if ((P1&0xf0) != 0xf0) //本行有键按下(即P1(真实的状态)的高四位不全为1)
{
recode = (P1&0xf0)|0x0f; //输出列扫描码 按位或运算
JZ_FLAG = 0; //将该变量置为1,防止反复进入
key_lock = 1; //将该变量置为1,防止反复进入
return(sccode&recode); //返回行和列
}
else //所扫描的行没有键按下,则扫描下一行,直到4行都扫描,此时sccode值为1110 1111 退出while程序
{
sccode=(sccode<<1)|0x01;//行扫描码左移一位
}
}
}
else
{
return 0; //无键按下,返回0
}
}
}
/*****************************************************************************
** 函数名称: DLkeyscan (这里采用的是线反转法)
** 功能描述: 独立式按键扫描子程序(4个独立式按键) P2.4 P2.5 P2.6 P2.7
** 输 入:
**
**
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.28
******************************************************************************/
uchar DLkeyscan()
{
uchar DLkey_lock,DLkey_times,DL_FLAG,DL_VALUE;
if((P2&0XF0) == 0XF0)
{
DLkey_lock = 0; //如果在这个时候有个毛刺,在这个时候将标志位key_lock置为0
DLkey_times = 0; //将DLkey_times置为0,这样可以有效的预防干扰,吴鉴鹰自己用过,效果很好
DL_FLAG = 0; //取代纯延时的标志位,如果该标志位置为1,相当于原来的delay(0执行完成
}
if((P2&0Xf0 != 0xf0)&&(DLkey_lock == 0))
{
if(DLkey_times < delay_time_max) //用这种方法代替过去的delay()延时去抖动
{ //这样就不会在按键处理时影响干别的事情
DLkey_times++;
}
else
{
DLkey_times = 0; //如果满足这个条件,表示相当于delay()延时完成了
DL_FLAG = 1;
}
}
if(((P2&0Xf0) != 0xf0)&&(DL_FLAG == 1))
{
DL_VALUE = P2&0XF0;
switch(DL_VALUE)
{
case 0x70: return 16;break; //通过检测P2口不同的值得到不同的按键值,进行不同的处理
case 0xb0: return 17;break;
case 0xd0: return 18;break;
case 0xe0: return 19;break;
}
DL_FLAG = 0; //防止反复进入
}
}
/*********************************************************************************
** 函数名称: Delay
** 功能描述: 纯延时函数
** 输 入: MS 需要延时的时间
** 输 出: num
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.28
***********************************************************************************/
void Delay(unsigned int MS)
{
unsigned char us,usn;
while(MS!=0) //for 12M
{
usn = 2;
while(usn!=0)
{
us=0xf5;
while (us!=0){us--;};
usn--;
}
MS--;
}
}
/*********************************************************************************
** 函数名称: KEY_SACN_VALUE
** 功能描述: 将按键测试程序得到的值转化成相应的键值
** 输 入: key_data
**
**
** 输 出: num
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.28
**********************************************************************************/
uchar KEY_SACN_VALUE(key_data)
{
switch(key_data)
{
case 0xee: KEY_VALUE=1;break;
case 0xde: KEY_VALUE=2;break;
case 0xbe: KEY_VALUE=3;break;
case 0x7e: KEY_VALUE=4;break;
case 0xed: KEY_VALUE=5;break;
case 0xdd: KEY_VALUE=6;break;
case 0xbd: KEY_VALUE=7;break;
case 0x7d: KEY_VALUE=8;break;
case 0xeb: KEY_VALUE=9;break;
case 0xdb: KEY_VALUE=10;break;
case 0xbb: KEY_VALUE=11;break;
case 0x7b: KEY_VALUE=12;break;
case 0xe7: KEY_VALUE=13;break;
case 0xd7: KEY_VALUE=14;break;
case 0xb7: KEY_VALUE=15;break;
case 0x77: KEY_VALUE=16;break;
default: break;
}
return KEY_VALUE;
key_data = 0;
}
/************************************************************************************
** 函数名称: show
** 功能描述: 显示函数,其中Fdata为状态为第一位,Sdata状态为第二位,Tdata为状态为第三位。
Fdata为状态为第四位。degdata为显示的具体数值degdata 为4位 例如23.21(要求degdata输入格式)
** 输 入: Fdata Sdata Ddata Hdata
**
**
** 输 出:
**
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.22
******************************************************************************/ //
void Data_change(uchar uidata)
{
uchar AD_FLAG,i,data_calue;
data_calue = 10*uidata;
if(data_calue > 100)
{
ADC0809[2] = (uidata*10)/100;
ADC0809[1] = (uidata*10)%100/10;
ADC0809[0] = (uidata*10)%10;
}
else if(data_calue > 10)
{
ADC0809[2] = 0;
ADC0809[1] = (uidata*10)/10;
ADC0809[0] = (uidata*10)%10;
}
else
{
ADC0809[2] = 0;
ADC0809[1] = 0;
ADC0809[0] = (uidata*10)%10;
}
}
/***************************************************************************
** 函数名称: ADC0809
** 功能描述: ADC0809转换函数
** 输 入:
**
**
** 输 出:
**
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.22
*******************************************************************************/
void ADC0809_change()
{
ST=0; //启动A/D转换
ST=1; //启动A/D转换
ST=0; //启动A/D转换
while(EOC==0); //等待转换完成
OE=1;
if(f==1)
{
delayms(1000); // 延时1s
lcd_mesg(1,1,3,ADC0809); //液晶显示数据
}
else
{ //这是鹰哥的风格,不管几句话都加上大括号,这样看起不会乱
lcd_mesg(1,1,3,KONGBVAI); //液晶显示数据 //如果初始化未成功则显示空白
}
//Display_Result(P3);
OE=0;
}
/*******************************************************************************
** 函数名称: void main()
** 功能描述: 主程序
** 输 入:
**
**
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.28
*****************************************************************************/
void main()
{
unsigned char i = 0,zongjia_Value,key_data,zongjia[8];
unsigned char danjia[]= {"单价:"};
unsigned char zongjia_led[] = {"总价:"};
P0=0xFF; //置P0口
P1=0xFF; //置P1口
TMOD=0x02; //T1工作模式2
TH0=0x14; //定时器初始值高位
TL0=0x00;
IE=0x82; //IE.7位为1表示CPU开放中断,IE.1位也为1,表示允许定时器T0溢出中断
TR0=1; //使能定时器
P1=0x00; //选择ADC0809的通道1(000)(P1.0~P1.2)
delay(10); //延时
initinal(); //液晶的初始化函数
while(1)
{
key_data = JZkeyscan();
if(key_data!=0)
{
KEY_SACN_VALUE(key_data);
}
KEY_VALUE = DLkeyscan();
if( i < 5)
{
DANJIA[i] = KEY_VALUE;
i++;
}
else
{
i = 0;
}
if(KEY_VALUE == 13)
{
zongjia_Value = result*(DANJIA[4]*10000+DANJIA[3]*1000+DANJIA[2]*100+DANJIA[1]*10+DANJIA[0]);
zongjia[7] = zongjia_Value/10000000;
zongjia[6] = zongjia_Value%10000000/1000000;
zongjia[5] = zongjia_Value%1000000/100000;
zongjia[4] = zongjia_Value%100000/10000;
zongjia[3] = zongjia_Value%10000/1000;
zongjia[2] = zongjia_Value%1000/100;
zongjia[1] = zongjia_Value%100/10;
zongjia[0] = zongjia_Value%10;
lcd_mesg(2,1,8,zongjia_led);
}
ADC0809_change();
}
}
/******************************************************************************
** 函数名称: counter1
** 功能描述: 当数据采集完毕,出发单片机的外部中断进行数据的存储显示以及报警
** 输 入:
**
**
** 输 出:
**
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.02.22
*******************************************************************************/
void counter1(void) interrupt 2 using 2
{
EX1=0;
result = P0;
EX1=1;
Data_change(result);
}
7、吴鉴鹰单片机项目详细解析系列(连载)之基于单片机的微机键盘的运用 (一)——原理介绍
有的时候我们总是抱怨键盘太少,做起来太麻烦了,所以就有人就比较聪明,直接运用电脑的键盘,也就是人们常说的“微机键盘”。下面吴鉴鹰就针对这个微机键盘的给大家做简单的介绍,已解决大家的键盘太少之忧,让大家做人机界面更加畅通无阻。
图 1
本文通过分析微机键盘的工作原理及数据格式, 从单片机应用系统的实际需出发, 对微机键盘与单片机的接口及软件进行了设计。用中断方式实现了微机键盘与单片机之间的通信。
大家知道微机键盘是微机系统必不可少的外设, 其品种很多, 既有最经常使用的101、102 等键盘, 又有各种适用于金融、证券等领域的小键盘。实际上, 单片机应用系统中也可用微机键盘作为人机交互工具。然而, 由于微机键盘的数据格式较多, 要开发它的完整功能编程量大。因此,在实际使用中, 往往要求厂家做成适合于单片机应用接口要求的特殊键盘, 如R S 一2 32 串口格式, 这对应用是很不方便的。
本文通过开发实践体会到: 微机键盘应用于单片机系统时, 只要满足实际要求, 硬件电路和软件设计完全可以简化, 并在这方面作了许多工作。
1 键盘功能及数据格式
键盘通过五芯插头与主机系统相连。一条数据线、一条时钟线均是双向的
(既可向系统发键盘数据, 又可接收系统发来的键盘命令), 一条是复位线, 另两条分别是+ SV 电源线和地线。五芯插头的布局如图1 所示。
图 2
微机键盘尽管形式各异, 但一般都符合两种标准川; 一种是Pc / XT 标准, 另一种是AT标准。前者数据位为9 位(较老式), 后者数据n 位, 是较常用的一种标准, 目前许多微机键盘都兼容这一标准。下面对后者作一简要介绍:
1.1 键盘功能
键盘通过数据线和时钟线与系统通信, 它接收系统发来的键盘命令码, 送出键盘数据。
在接通电源后键盘首先进行复位等初始化工作, 然后进行键盘扫描, 作好接收命令和发送键盘数据的准备工作。
键盘和系统相互间的数据通信均采用n 位格式的串行方式: 第一位起始位, 第2 一9 位是8 位数据位(低位在前高位在后), 第10 位是奇偶校验位, 第n 位是停止位, 数据的起始位为低电平。系统接收的键盘数据和键盘接收的键盘命令均在移位时钟的下降沿同步输人, 时钟周期为50 u s 。其时序如图2 所示。
图
2 微机键盘与系统通信帧格式时序
图 3
1.2 键盘命令
键盘命令是由系统发送给键盘的命令码。键盘在接收到这些命令时, 在20 ms 内予以响应, 即送回一个响应(FA ll)。
键盘命令的含义如下:
(l) FFH 复位键盘一一使键盘进行复位和内部自测试操作;
(2)FE H 重新发送一检测到键盘数据错误时, 要求键盘重新输出原来的内容;
(3) F6 H 设置键盘缺省值一一使键盘复位到初始状态, 并停止键盘扫描;
(4) FS H 设置缺省值并停止键盘一一使键盘复位到初始状态, 并停止键盘扫描;
(5) F4 H 启动键盘一一清除键盘输出缓冲器, 并启动键盘扫描和输出数据;
(6) F3 H 设置拍发速率/ 延时参数;
(7) E E H 回响命令一一要求键盘接收到此命令时也回送E E H 予以响应;
(8) E l〕H 置位/ 复位LE D 指示器。
1.3 键盘数据
键盘数据包括键盘扫描码和命令响应码。键盘扫描码是用户按下键盘按键时, 键盘发送给系统的数据, 不同的键其码值是不同的。可通过查阅有关资料〔‘〕或测试获得。命令响
应码则是键盘对系统命令的响应。其含义如下:
(1) FE H 请求重发一一对收到一个奇偶校验错或无效命令时, 请求系统重发命令;
(2) FA H 正常应答一一对任何一个有效的键盘命令, 均以该响应作答;
(3 ) OOH 超限应答一一当用户按键速度超出键盘内缓冲器存放的16 个扫描码时, 发
出该应答;
(4 ) FD H 诊断故障应答一一键盘在自测试过程中, 若有故障则以FD H 应答;
(5) A A I硕诊断正常应答一一键盘在自测试过程中, 若无故障则以A A H 应答;
(6) E E H 回响命令应答一一键盘收到系统的回响命令(E E H ) 时, 也发一个E E H 作
答;
(7 ) FO H 断开扫描码前缀字节一一断开扫描码占两个字节, 第一个字节总是刊H , 第
二个字节和接通扫描码相同。
2、接口及软件设计
微机键盘与单片机的接口如图
3 所示。
图 4
在此对单片机无特殊要求, 可采用
A T M[E L 公司的A尸I名9 C5 1 单片机, 其指令系统和IN J、E L 公司的MCS -51 系列单片机完全兼容。单片机Pl.7 连数据线1)A T,P3.2 (IN ,I’0 )连时钟C LK。微机键盘与单片机只有建立正常的通信关系, 才能控制键盘及处理键盘数据
。因此在软件设计上本文着重于介绍键盘数据的接收、键盘命令的发送、键盘的自检和复位这三部分。在软件设计时键盘命令只需使用一个,即初始化键(F FH )。对键盘数据的接收不作奇偶校验, 以简化编程。
2.1 键盘数据的接收
键盘数据的接收用IN TO 外部中断方法来实现; 在时钟线C LK 为低电平时产生一次中断, 读取数据线D A I,一位数据。键盘通过时钟线C L K 产生n 个时钟脉冲, 使单片机产生1 次中断, 才接收完一帧信息。中断次数的识别由单元E IC 来完成,E IC 的初值为1 1, 每中断一次减1。当E IC 为0 时, 表明一帧信息接收完, 同时禁止外部中断(E XO = O)、禁止键盘发数据(C LK = 0 )、置数据接收完成标志bR ()K = 1 并使E IC 的初值为1 1 。
键盘数据存放单元在K B D 中; 其初值为# o fh, 每中断一次数据线1)A T 向K B D 左移一位数据, 同时K B D 也左移出一位, 当检测到K丑D 左移出起始位“ 0 ” 时, K B D 中的内容即为要接收的键盘数据。当主程序处理完键盘数据K BD 后, 为了接收下一帧信息, 应置C LK= 1,EXO = 1 及
bR O K = 0。
2.2.2 键盘命令的发送
键盘命令的发送采用刊中断方法来实现; 每次中断, 都对时钟线C LK 求反, 以产生n个脉冲, 把发送缓冲区SB L、S13H 中的信息经数据线1〕A T 送给键盘。
中断次数由单元T IC来控制, T IC 的初值为2, 每中断一次减1。当T IC 为0 时, 一帧n 位信息发送完, 置D A T= 1、CL K = 1、命令发送完成标志b R ()K = 1, 置T IC 的初值为2, 并禁止定时器工作(T R O= 0 ), 为发送键盘命令或接收键盘数据作好准备。
由SB H、SB L 组成的发送缓冲区, 用于存放要发送的一帧n 位信息。其中S 13H 为高字
节,SB L 为低字节, 一帧信息在该缓冲区中的存放格式如图4 所示。
图 5
其中: d0 位为一帧信息的起始位;
功,d7 一d l 为键盘命令(田为最高位,d1 为最低位) ;D l 为奇偶校验位;砚为停止位;其余未定义。如单片机时钟频率为12 M llz , 定时器工作方式1, 要确保键盘对时钟周期为5 0 u s 的要求, 则时间常数取值为TL0 = # ocdh,THO = # offh。
8、吴鉴鹰单片机项目详细解析系列(连载)之基于单片机的微机键盘的运用 (二)——相关的程序编写
吴鉴鹰在本例中使用时一种标准的通讯程序,各位烧友可以参考参考一下Atmel公司发布的单片机在键盘中的应用笔记,这个程序我也是参考该应用笔记
给各位烧友分享的。
除了有关的字符库外,吴鉴鹰的这个程序主要为4个部分:①、初始化 ②、串口通讯 ③、键盘 ④、数据处理
1.程序功能
该程序的主要功能是处理外接的微机键盘和单片机的数据通讯,这里吴鉴鹰就分为4个部分进行介绍
①Main.C:主程序,主要处理单片机和键盘通讯的初始化
②Low_level_init:键盘的初始化和通信协议的初始化
③Serial.c:串行通讯程序,处理数据的传输和收发
④Kb.C:键盘的按键处理程序,控制按键的谁和发送
2、主要器件很函数的说明
⑴.Low_level_init.c: 初始化键盘和单片机的通信方式
⑵.Main(): 主程序,初始化参数并等待中断产生
⑶.init_uart(): 初始化串口的寄存器
⑷.init_kb(): 初始化键盘的收发
⑸.getchar(): 读取字符函数
⑹.putchar(): 发送字符函数
⑺.delay(): 延时函数
⑻.init_uart(void): 初始化串口
⑼.clr(void): 定义清屏函数
⑽.init_kb(void): 初始化键盘
⑾.decode(unsigned sc):键盘按键码解码函数
⑿.put_kbbuff(uchar): 键盘的输入字符函数
/*初始化程序*/
/*********************************************************************************************************
** 函数名称: _Low_level_init
** 功能描述: 这个函数的主要功能是对单片机的端口和串行通讯的波特率、通信方式等作一系列的规定很处理
** 输 入:
**
**
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.03.2
********************************************************************************************************/
//Low_level_init.c
#include<ina90.h>
#include<io8585.h>
int _Low_level_init(void)
{
UBRR = 12;
UCR = 0X08;
GIMSK = 0X40;
_SEI();
return 1;
}
/*主函数*/
#include<pgmspace.h>
#include<stdio.h>
#include<stdlib.h>
#include "io8515.h"
#include "serial.h"
#include "gpr.h"
#include "kb.h"
/*********************************************************************************************************
** 函数名称: main
** 功能描述: 串口的初始化、初始化键盘的收发
** 输 入:
**
**
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.03.2
********************************************************************************************************/
void main()
{
unsigned char key;
init_usart(); //串口的初始化程序
init_kb(); //初始化键盘的收发
while(1)
{
key = getchar();
putchar(key);
delay(100); //延时子程序
}
}
/*键盘处理子程序*/
#include<pgmspace.h>
#include "kb.h"
#include "serial.h"
#include "qpr.h"
#include "scancodes.h"
#define BUFF_SIZE 64
unsigned char dege,bitcount; //0 :符号,1 :正号
unsigned char kb_buffer[BUFFER_SIZE];
unsigned char *inpt,*outpt;
unsigned char buffercnt;
/*********************************************************************************************************
** 函数名称: init_kb
** 功能描述: 初始化键盘
** 输 入:
**
**
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.03.2
********************************************************************************************************/
void init_kb(void)
{
inpt = kb_buffer; //初始化缓冲区
outpt = kb_buffer;
buffcnt = 0;
MCUCR = 2; //INT0中断,下降沿触发
edge = 0; //0:下降沿,1 :上升沿
bitcount = 11;
}
/*********************************************************************************************************
** 函数名称: INT0_interrupt
** 功能描述: 按键接收中断函数
** 输 入:
**
**
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.03.2
********************************************************************************************************/interrupt [INT0_vect] void INT0_interrupt(void)
{
static unsigned char data; //保持接收扫描码的状态
if(!edge) //下降沿触发中断服务程序
{
if(bitcount < 11 && bitcount > 2) //3~10位是数据
{ //删除起始位和停止位
data = (data >> 1);
if(PIND & 8)
data = data | 0x80; //如果为“1”则存储
}
MCUCR = 3; //设置该寄存器是为了用上升沿触发中断
edge = 1;
}
else
{
MCUCR = 2;
edge = 0;
if(--bitcount == 0) //该判断是接收到所有的中断的时候
{
decode(data);
bitcount = 11;
}
}
}
/*********************************************************************************************************
** 函数名称: decode
** 功能描述: 键盘按键码解码函数
** 输 入:
**
**
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.03.2
********************************************************************************************************/
void decode(unsigned char sc)
{
static unsigned char is_up = 0,shift = 0,mode = 0;
unsigned char i;
if(!is_up) //最后一位数据的接收
{
switch(sc)
{
case 0xf0: is_up = 1;break; //确定完成了的按键
case 0x12: shift = 1;break; //左边的shift键
case 0x59: shift = 1;break; //右边的shift键
case 0x05: //F1键
if(mode == 0)
{
mode = 1; //进入按键扫描模式
}
if(mode == 2) //离开按键扫描模式
{
mode = 3;
}
break;
default:
if(mode == 0 || mode == 3) //ASCΙΙ模式
{
if(!shift) //如果没有shift键被按下
{ //查表
for(i=0;unshifted[0] != sc && unshifted[0];i++);
if(unshifted[0] == sc)
{
put_kbuffer(unshifted[1]);
}
}
else
{ //如果shift键被按下
for(i = 0;shifted[0] != sc && shifted[0];i ++);
if(shifted[0] == sc)
{
put_kbbuffer(shifted[1]);
}
}
}
else
{
print_hexbyte(sc); //扫描键盘吗模式
put_kbbuffer(' '); //显示模式
put_kbbuffer(' ');
}
break;
}
}
else
{
is_up = 0; //2个0xf0在一列中不允许的
switch(sc)
{
case 0x12:shift = 0;break; //左shift
case 0x59:shift = 0;break; //右shift
case 0x05: //F1
{
if(mode == 1)
{
mode = 2;
}
if(mode == 3)
{
mode = 0;
}
break;
case 0x06: //F2
clr();
break;
}
}
}
}
/*********************************************************************************************************
** 函数名称: put_kbbuff
** 功能描述: 键盘输入字符的函数
** 输 入:
**
**
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.03.2
********************************************************************************************************/
void put_kbbuff(unsigned char c)
{
if(buffcnt < BUFF_SIZE) //若缓冲区为空
{
*inpt = C; //在缓冲区中输入一个字符
inpt++; //指针加1
buffcnt ++;
if(inpt >= kb_buffer + BUFF_SIZE) //指针判断
{
inpt = kb_buffer;
}
}
}
/*********************************************************************************************************
** 函数名称: getchar
** 功能描述: 读取键盘输入字符的函数
** 输 入:
**
**
** 输 出:
** 全局变量:
** 调用模块:
**
** 作 者: 吴鉴鹰
** 日 期: 14.03.2
********************************************************************************************************/
int getchar(void)
{
int byte;
while(buffcnt == 0); //等待数据
byte = *outpt; //取数据
outpt++; //指针加1
if(outpt >= kb_buffer + BUFFER_SIZE) //指针比较
outpt = kb_buffer;
buffcnt--; //缓冲区减去1
return byte;
}
9、吴鉴鹰单片机项目详细解析系列(连载)如何提高单片机的抗干扰能力(一)
(一)、抗干扰方法综述
(二)、钟信号也会产生干扰
(三)、设备与设备之间如何抗干扰
(四)、引脚抗干扰的方法
(五)、上下电干扰
(一)、抗干扰方法综述:
在工业控制 智能仪表都采用了单片机,单片机干扰措施提到重要议事日程上来单片机抗干扰措施
不解决,其它工作也是白费劲要解决单片机抗干扰措施,必须先找出干扰源,然后采用单片机软硬件技术
来解决
干扰源:主要来自外部电源内部电源,印制板自制干扰,空中周围电磁场干扰,外部干扰通过I/O 口输
入等为叙述方便,我们分硬件软件抗干扰措施来讲:
(一) 硬件抗干扰措施
1. 交流程稳压:使电网电压稳定
2. 交流端用电感电容滤波:去掉高频低频干扰脉冲
3. 变压器双隔离措施:变压器初级输入端串接电容,初次级线圈间屏蔽层与初级间电容中心接点接大
地,次级外屏蔽层接印板地,这是硬件抗干扰的关键手段
4. 次级加低通滤波器:吸收变压器产生的浪涌电压
5. 采用集成式直流稳压电源:因为有过流过压过热等保护
6. I/O 口光电磁电继电器隔离:去掉公共地
7. 通讯线用双绞线:排除平行互感
8. 防雷电用光纤隔离最为有效
9. A/D 转换,用隔离放大器或采用现场转换:减少误差
10. 外壳接大地:解决人身安全及防外界电磁场干扰
11. 加复位电压检测电路:仿止复位不充份,CPU 就工作,尤其有EEPROM 的器件,复位不充份会改变EEPROM 的内容
12. MCU能够适应的电压范围一般在3V-5.5V,但电源的波动对MCU而言却很敏感,比如说MCU可以在3.3V电压下稳定工作,但却不能在电压在3V-5.5V波动的情况下稳定工作。
13.通常是用电源稳压块,做好电源的滤波等工作,提示:一定要在电源旁路并上0.1UF的瓷片电容来滤除高频干扰,因为电解电容对超过几十KHZ的高频干扰不起作用。这就是为什么经常在电源并上电容的原因
14.确实很重要,比如涉及模拟信号处理的,滤波电容一定要加足,宁大勿小,保证电源的纯净,信号不受干扰;电流大的线路,铜线必须得足够粗。
15.一般单片机项目我们都是在电源端口处一个10uf电解电容和一个104瓷介电容,作双重保证
16.通常隔离有:电源隔离,输入输出口隔离,空间隔离
(二)、时钟信号也会产生干扰
在MCU中时钟信号也会产生干扰,时钟信号不仅是受噪声干扰最敏感的部位,同时也是CPU 对外发射辐射干扰和引起内部干扰的噪声源。
辐射干扰的产生主要与时钟信号的上升和下降时间有关,即门电路的跳变时间Tr 。时钟频率越高,信息传输线上信息变换频率也就越高,致使干扰加剧,故在满足系统功能要求的前提下,要尽量降低时钟频率。这样有助于提高系统的抗干扰性能。为了避免时钟信号被干扰,可以采取以下措施:
(1) 时钟脉冲电路配置时应注意靠近CPU ,引线要短而粗。
(2) 外部时钟源用的芯片VCC与GND之间可接1μF 左右的去耦电容。
(3) 在可能的情况下,用地线包围振荡电路,晶体外壳接地。
(4) 若时钟还做其它芯片的脉冲源,要注意隔离和驱动措施。
(三)、设备与设备之间如何抗干扰
设备与设备之间这个就比较复杂了,涉及到电磁干扰,这个可能更需要整个系统的抗干扰处理,比如屏蔽线或者隔离器等
屏蔽线对静电干扰有较强的抑制作用,而双胶线有抵消电磁感应干扰的作用.
开关信号检测线和模拟信号检测线可以使用屏蔽双胶线,来抵御静电和电磁感应干扰;特殊的干扰源也可以用屏蔽线连接,屏蔽了干扰源向外施加干扰。
离这块,隔离目的之一是从电路上把干扰源和易干扰的部分隔离出来,使监控装置与现场仅保持信号联系,但不直接发生电的联系。
隔离的实质是把引进的干扰通道切断。从而达到隔离现场干扰的目的。一般单片机应用系统既有弱电控制系统又有强电控制系统,通常实行弱电和强电隔离,是保证系统工作稳定、设备与操作人员安全的重要措施。
(四)、引脚抗干扰的方法
看到这个我又想起之前,我们做的一个产品上,蜂鸣器的鸣叫响声达不到标准要求,就特意把蜂鸣器驱动部分做了实验,研究改变参数对蜂鸣器的鸣叫影响。蜂鸣器是感性器件,在通断电时候会产生反向电动势能,这个可能导致损坏元件,而且会干扰其它电路,通过电源直接进入到MCU中。
有些芯片由于内部电路的关系确实这样子的,因为如果引脚输入悬空,在感应电的情况下,输出有可能是高,也有可能是低,还可能在振荡,就有可能对其他引脚造成影响了
另外说道这个加电容,记得以前做产品时,看到MCU datasheet上有这么一说,某个引脚没有被用到,不能直接NC,要通过一个电容连接到地GND
(五)、上下电干扰
上下电干扰:但每个MCU 系统在上电时候都要经过这样一个过程,所以要尤其注意。
MCU 虽然可以在3V 电压下稳定工作,但并不是说它不能在3V 以下的电压下工作,当然在如此低的电压下MCU 是超不稳定状态的。
在系统加电时候,系统电源电压是从0V 上升到额定电压的,比如当电压到2V 时候,MCU 开始工作了,但这时是超不稳定的工作,极容易跑飞。
10、吴鉴鹰单片机项目详细解析系列(连载)如何提高单片机的抗干扰能力(二)
完整文档下载:
(六)、隔离法抗干扰
(七)、软件干扰
(八)、印制板工艺抗干扰
(九)、地线的处理
(六)、隔离法抗干扰
如果把电源电压变化持续时间定为Δt,那么,根据Δt的大小可以把电源干扰分为四种情况:
1)过压、欠压、停电: 当Δt>1s时产生的干扰,解决办法是使用各种稳压器、电源调节器,对短时停电可用不间断电源(UPS)供电。
2)浪涌、下陷、半周降出:当1s>Δt> 10ms时产生
的干扰,可使用快速响应的交流电源调压器克服。
(3)尖峰电压:当Δt为μs量级时产生的干扰,
解决办法是使用具有噪声抑制能力的交流电源
调节器、参数稳压器或超隔离变压器。
(4)射频干扰:当Δt为ns量级时产生的干扰,可加2~3节低通滤波器消除干扰。
在单片机系统中,为了提高供电系统的质量,防止窜入干扰,建议采用如下措施:
(1)单片机输入电源与强电设备动力电源分开。
(2)采用具有静电屏蔽和抗电磁干扰的隔离电源变压器。
隔离变压器的初级和次级之间均采用隔离屏蔽层(可用漆包线或铜等非导磁材料在初级和次级绕一层,但电气上不能与初级、次级线圈短路,而后引出一个头接地)。各初级、次级间的静电屏蔽与初级间的零电位线相接,再用电容耦合接地。如图所示:
图片1
过程通道是系统输入、输出以及单片机之间进行信息传输的路径。由于输入输出对象与单片机之间的连接线长,容易串入干扰,必须采用隔离技术、双绞线传输、阻抗匹配等措施抑制。
2、开关量隔离
有一种就是接光耦,我们做电机类项目的时候,MCU与大功率电机控制区之间是要用光耦进行隔离的,防止大电流烧坏MCU引脚,常用的开关量隔离器有光电隔离器、继电器、光电隔离固态继电器(SSR)。
(1)光电隔离器
光电耦合器是把一个发光二极管和一个光敏三极管封装在一个外壳里的器件,光电耦合器的电路符号如图8—3所示。输入信号使发光二极管发光,其光线又使光敏三极管产生电信号输出,从而既完成了信号的传递,又实现了电气上的隔离,如图8—4所示。对启动或停止负荷不太大的设备,常采用光电耦合器来抑制输出通道的干扰。
图片2
图片3
如果输出开关量是用于控制大负荷设备时,就需采用继电器隔离输出。因为继电器触点的负载能力远远大于光电隔离的负载能力,它能直接控制动力回路。在采用继电器做开关量隔离输出时,要在单片机输出端的锁存器74LS273与继电器间设置一个
图片4
OC门驱动器。用以提供较高的驱动电流。
平时的干扰一般是信号高低跳变是产生的尖峰,一般在芯片旁边加上一个旁路电容,而且是尽可能靠近芯片的旁路电容就能解决,但是在工业应用或者室外,久不久还是会出现干扰的情况,你的加上拉方法试过,有改善,但是貌似改善的算是一半多点,还是有一半的情况出现误动作,也不确定是不是程序不够完善,反正还是有很多地方要注意检查
(七)、软件干扰
看了上面的,都是在讲硬件干扰,我来说说软件干扰吧
软件抗干扰主要有亮点: 一是消除模拟输入信号的嗓声(如数字滤波技术);二是程序运行混乱时使程序重入正轨的方法。
看门狗抗干扰:失控的程序进入“死循环”,通常都是采用“看门狗”技术使程序脱离“死循环”。通过不断检测程序循环运行时间,若发现程序循环时间超过最大循环运行时间,则认为系统陷入“死循环”,需进行出错处理。
“看门狗”技术可由硬件实现,也可由软件实现。 在工业应用中,严重的干扰有时会破坏中断方式控制字,关闭中断。系统无法定时“喂狗”,硬件看门狗电路失效。而软件看门狗可有效地解决这个问题
(八)、印制板工艺抗干扰:
. 电源线加粗,合理走线接地,三总线分开: 减少互感振荡
. CPU RAM ROM等主芯片,VCC 和GND间接电解电容及瓷片电容:去掉高低频干扰脉冲
. 独立系统结构,减少接插件与连线:提高可靠性,减少构障率
. 集成块与插座接触可靠,用双簧插座,最好集成块直接焊在印制板上:防止器件接触不良故障
. 有条件采用四层以上印制板,中间两层为电源及地
(二). 软件抗干扰措施:
1. 多用查询代替中断,把中断源减到最少:中断信号连线不大于0.1 米,防止误触发感应触发
2. A/D 转换采用数字滤波:平均法,比较平均法等:防止突发性干扰
3. MCS-51 单片机空单元写上00H,最后放跳转指令到ORG 0000H:因干扰程序走飞,可能抓回去
4. 多次重复输出,输出信号保持在RAM 中:止因干扰信止输出
5. 开机自检 自诊断,RAM 中重要内容要分区存放,经常进行比较检查,机器不能带病工作
6. 表格参数放在 EPROM 中,检验和存于最后单元,防止EPROM
7. 加看门狗,软件走飞可从头开始
8. 开关信号延时去抖动
9. I/O 口正确操作,必须检查口执行命令情况防止外部故障不执行控制命令
10. 通讯应加奇偶校验或查询 表决比较等措施,防止通讯出错
(三).这个滤波电容主要是在PCB layout时,要注意位置,基本上所有小电容都要靠近引脚
(九)、地线的处理
MCU的抗干扰,那就不得不说地线的处理
1、选择单点接地与多点接地。当信号频率小于1MHz时,应尽量采用单点并联接地,实际布线有困难时,可以部分串联后再并联接地;当频率大于10MHz时,适合采用多点串联接地;当信号频率在1~10MHz之间时,若地线长度不超过波长的1/20,可用单点接地
图片1
2、数字地、模拟地、电源地等分开走线,在一个点上可靠连接
图片2
MCU的抗干扰,那就不得不说地线的处理
1、选择单点接地与多点接地。当信号频率小于1MHz时,应尽量采用单点并联接地,实际布线有困难时,可以部分串联后再并联接地;当频率大于10MHz时,适合采用多点串联接地;当信号频率在1~10MHz之间时,若地线长度不超过波长的1/20,可用单点接地
图片1
2、数字地、模拟地、电源地等分开走线,在一个点上可靠连接
图片
3、接地线应尽量加粗,使它能通过三倍于印制板上的允许电流。一般接地线宽度应在2~3mm以上。地线、电源线与信号线的关系是:地线>电源线>信号线
4、 使数字电路的接地线形成闭环路。
5、高频部分尽量采用大面积包围式地线
以上若有不当之处,还请各位大侠指教
3、接地线应尽量加粗,使它能通过三倍于印制板上的允许电流。一般接地线宽度应在2~3mm以上。地线、电源线与信号线的关系是:地线>电源线>信号线
4、 使数字电路的接地线形成闭环路。
5、高频部分尽量采用大面积包围式地线
回复
| 有奖活动 | |
|---|---|
| 硬核工程师专属补给计划——填盲盒 | |
| “我踩过的那些坑”主题活动——第002期 | |
| 【EEPW电子工程师创研计划】技术变现通道已开启~ | |
| 发原创文章 【每月瓜分千元赏金 凭实力攒钱买好礼~】 | |
| 【EEPW在线】E起听工程师的声音! | |
| 高校联络员开始招募啦!有惊喜!! | |
| 【工程师专属福利】每天30秒,积分轻松拿!EEPW宠粉打卡计划启动! | |
| 送您一块开发板,2025年“我要开发板活动”又开始了! | |
我要赚赏金
