单片机
键盘接口电路
简介
Copyleft2009 by高飞电子经营部
P.S.
文档由高飞整理。能力有限,疏漏在所难免!这里说声抱歉了。
若对文档所描述的观点存在疑问,欢迎交流。
QQ:1275701567
键盘是单片机应用系统中不可缺少的输入设备,是实心人机对话的纽带,是操作人员控制干预单片机应用系统的主要手段。如用手机键盘发送短信息、遥控器键盘控制家用电器等。各种仪器仪表的小键盘系统,则可以显示各种信息。例如,数字式频率计、数字式扫频仪、数字式测量仪等。通过键盘向单片机应用系统输入数据和控制命令,实现对应用系统的认为控制,可以提高应用系统的灵活性。每种单片机应用系统的小键盘系统会实现不同的功能。比如,需要按键进行清零、预置值、改变测量范围等。这些功能是由一系列键盘电路通过编程实现的。因此,键盘在控制系统中得到了广泛的应用。本文档介绍了单片机系统中键盘接口电路及其相应的实现方式。
包含以下内容:
●键盘的组成和分类;
●键盘实现的硬件接口电路;
●4x4键盘与单片机的接口实例;
●二进制编码器键盘与单片机的接口实例。
1.1键盘设计的组成和分类
说说键盘的发展史。键盘发展至今,已经有100多年的时间。伴随着材料科学和电子技术的发展,键盘的物理构成和物理构造也不断变化发展。这些不同结构的键盘,各有特点,适用于不同的场合,但是控制方法都是类似的。
1.1.1键盘的物理结构
1.机械式结构键盘
机械式结构键盘,一般使用类似金属接触式开关的原理,实现触点导通或断开。在实际应用中,机械开关的结构形式很多。最常用的是交叉接触式。它的优点是结实耐用,缺点是不防水,敲击比较费力。交叉接触式机械开关,在单片机应用系统中最为常用。轻触开关也属于这一类。
2.电容式结构键盘
电容式结构键盘是一种类似电容式开关原理键盘。它通过按键改变电极的间距而产生电容量的变化,暂时形成震荡脉冲允许通过的条件。电容的容量是由介质、两极间的距离及两极的面积来决定的。当键帽按下时,两极的距离就发生变化,就引起电容容量发生变化。当参数设计合适时,按键时就有输出,而不按键就无输出。这个输出再经过整形放大,去驱动编码器。由于电容器无接触,所以这种按键在工作过程中不存在磨损、接触不良等问题,耐久性、灵敏度和稳定性都比较好。另外,为了避免电极间进入灰尘,电容式按键开关采用了密闭封装,比较便于保养。优良的特性带来的缺点就是代价高昂,标准PC键盘淘宝卖价不低于1500RMB!
3.塑料薄膜式键盘
塑料薄膜式键盘内有4层,塑料薄膜一层有凸起的导电橡胶,当中一层为隔离层,上下两层有触点。通过按键使橡胶凸起按下,使其上下两层触点接触,输出编码。这种按键无机械磨损,可靠性较高,最大的特点就是低价格、低噪声、低成本。
4.导电橡胶式键盘
导电橡胶式键盘触点通过导电的橡胶接通,其结构是有一层带有凸起的导电橡胶,凸起部分导电,而这凸起部分对准每个按键,互相连接的平面部分不导电。当键帽按下去时,由于凸起部分导电,把下面的触点接通;不按时,凸起部分会弹起。
1.1.2键盘的组成形式
在不同的系统中,键盘的数量有很大的差别。如电子表一般只需要3或4个控制键,手机一般需要20个控制键,而PC键盘的按键则要多很多。各种系统按键的数量有很大区别,但是键盘与单片机的接口线不可能和按键数量呈正比增长。于是很多有趣的方法就被提了出来。
1.独立式键盘
独立式键盘的各个按键之间彼此独立,每个按键均连接单片机的一根I/O口。独立式键盘的硬件接口电路简单,软件设计也比较方便,但由于每一个按键均需要单片机的一根I/O口,当键盘按键数量比较多时,需要的I/O口也较多。因此,独立式键盘只适合按键较少的应用场合。一般情况下,按键数等于占用的I/O口端数。
多时,可采用矩阵式键盘。一般情况下,按键数等于矩阵行数和列键闭合的位置。常用的按键位置判断方法,有扫描法和线反转法。稍后介绍。
2.矩阵式键盘
矩阵式键盘是一种扫描式键盘。其工作过程要比独立式按键复杂。矩阵式键盘由行线、列线及位于行列线交叉点上的按键等部分组成。当单片机应用系统需要的按键数量比较数的乘积。
矩阵式键盘由于采用矩阵式结构,单片机的一根I/O 口状态已经不能确定是哪个键被按下,需要通过连接到按键上的2跟I/O 口线的状态共同来确定键的状态。同时,按键的两端均接到I/O 口线上,不能一端接I/O 口线一端接地。因此,必须采用行线和列线信号状态分别处理,综合考虑才能判断按
3.编码式键盘有些场合矩阵式键盘也不能满足要求。比如,要设计一个100个按键的键盘(听起来数量巨大),用矩阵式键盘最少需要占用单片机的20个I/O 端口。对很多单片机来说,这是很难实现的。因此便有了编码方式的结构键盘,每一个按键对应一个编码,只需要查询键盘与单片机的接口状态(即按键所对应的编码),即可以获得相应的按键值。编码方式的键盘示意图如下。
2^3=8。在本电路中,三根I/O线可以表示8中状态,这8种状态就分别对应了编码键盘的各个按键情况。当编码键盘没有被按下时,各端口呈现高电平。下表是其对应的真值表。
P1.0、P1.1、P1.2三个端口的电平状态对应按键状态
0、0、0 无键被按下
0、0、1 按键S3被按下
0、1、0 按键S2被按下
0、1、1 按键S6被按下
1、0、0 按键S1被按下
1、0、1 按键S5被按下
1、1、0 按键S4被按下
1、1、1 按键S7被按下
综上,编码式键盘占用了单片机n个端口,可以接出2^n-1个按键。
4.高可靠性PWM式键盘
近年来,有许多有关单片机的开发应用人员,研究并提出了各种键盘接口方法。试图节省宝贵的I/O口。PWM键盘接口电路由此诞生了。它只需要一根I/O 口线,就能实现多位按键输入,但这种键盘接口电路和编程稍嫌复杂,成本也相应提高。
PWM式键盘接口电路原理图见下图。如图所示,采用一个运放实现多谐振荡器,电路的震荡周期T正比于R k C。如果改变R k的值,可以得到不同震荡周期的T值。将振荡器的输出接到单片机,利用单片机的脉冲测量功能,可以得到R k的值。而R k实际采用下下图电路实现。得到了R k的值也就得到了键值。
下图
下下图
先来个总结。 独立式键盘
矩阵式键盘
编码式键盘
PWM 键盘
键代
码产生
由软件完成
由软件完成
自动生成
由单片机识别
优点 硬件电路简单,成
本低 硬件电路比较简单,成本低,占用I/O 口数较少 使用方便,键码产生速度快,占用CPU 时间少 占用I/O 口线只需1根 缺点 占用CPU 时间较长,占用I/O 口多
占用CPU 的时间较长
硬件电路复杂,成本高 硬件电路较复杂,成本较高 适用场合
按键数量少
按键数量较多
大系统中,要求占用CPU
时间较少
I/O 口线比较紧
缺场合
1.2键盘接口的工作过程和工作方式
由于机械触点的弹性振动,按键在按下时不会马上稳定地接通,而在弹起时也不能一下子完全断开。因此,在按键闭合和断开的瞬间均会出现一连串的抖动。这就是按键的抖动。
1.2.1键盘的抖动干扰和消除方法
当按键被按下时会产生前沿抖动,当按键弹起时会产生后沿抖动,所产生的波形见下图。这是所有机械触点式按键在状态输出时的共性问题。
按键闭合稳定时间由操作人员的按键时间决定,一般为零点几秒到几秒之间。为了消除抖动的影响,可以采用软件或硬件方法。
硬件削抖是采用添加削抖电路的方法,对键盘的按下抖动及释放抖动进行削抖。经过削抖电路后,使按键的电平信号只有两种稳定状态。常用的削抖电路有触发器削抖电路、滤波削抖电路两种。硬件削抖电路,如下图。
硬件削抖电路能够解决按键抖动的问题,但如果应用系统所需要的按键比较多,硬件削抖电路将变得复杂,成本也比较高。因此,硬件削抖电路一般只适用于按键比较少的应用系统。当系统中需要按键比较多的时候,可以采用软件削抖方法来对键盘抖动进行消除。
软件削抖的基本原理是当检测出有按键按下时,先执行一个延时子程序产生数毫秒的延时(通常10~20ms),待接通时间的前沿抖动消失后再判断是否有键按下。当按键释放时,也要经过数毫秒延时,待后沿抖动消失后再判断按键是否释放。
1.2.2键盘接口电路工作过程
键盘接口电路的工作过程一般包含下面的5个过程:
(1)判断是否有按键被按下;
(2)消除按键抖动;
(3)确定有键按下和确定被按下键的位置,即获得按键的行列编码;
(4)单片机处理同时有两个或以上的按键按下有2中方法。一是双键锁定法,即在所有的键操作中,只承认闭合的第一个按键,对其他按键均不识别,直到所有的按键释放之后,才读入下一个键值。二是按键轮回法,即当前面所有识别的按键被释放以后,才可以对其他闭合的按键识别。
(5)进行键值译码。没一个按键都有一定的功能定义,将读取的键值解释为定义键功能成为键值译码。
1.2.3键盘的工作方式
独立式键盘可以工作在多种方式下,如中断方式、程序查询方式、定时查询方式和中断查询方式。
在中断模式下,按键的数量受到外部中断源的限制。在有特殊需要的场合,还可以借用内部的定时器中断。所以在这种模式下,按键的数目小于外部中断源和单片机定时器数量之和。
程序查询和定时查询类似,都是通过读I/O状态,当有按键被按下时相应的I/O口线变为低电平,而未被按下的键对应的I/O口保持高电平。这样,通过读I/O口状态就可以判断是否有键按下和哪一个键被按下。
两者的区别在于扫描的时间和组织形式不同。程序查询是在需要键盘输入的时候,调用扫描程序,获得扫描的状态值。由于采用这种结构,在不要输入的时候,CPU是不对键盘进行相应的扫描查询的,键盘被旁路。这种结构的缺点,是需要使用者判断什么时候输入才可以被响应。定时查询是利用定时器产生中断,每次查询一次I/O端口。这样做的优点是实时性比较强,缺点是占用一个定时器。
中断扫描方式需要占用一个外部中断源,只要有按键按下就会发出中断请求。然后CPU响应中断,查询各按键对应I/O的状态。中断查询方式和中断模式,是各种方法中实时性最好的。中断扫描方式相对中断方式来说可以接更多的按键,但是需要额外的赢家电路来实现中断请求。
1.3键位置的判断方法
因为独立式键盘每个键位对应单片机的一个独立的I/O口,所以单片机识别的I/O口的状态便判别了相应键的状态。
编码式键盘的按键信息和其他键盘的编码真值表是一一对应的。因此只要在单片机内建立起这样一个真值表,在单片机获得了按键的编码信息后利用查表程序,就可以判断是哪个键被按下了。
PWM式键盘是通过单片机测量到的脉冲宽度来测量电阻值,从而得到键值。而矩阵式键盘的按键位置判断方法相对比较复杂,以下稍作介绍。
1.扫描法
扫描法的基本过程是快速判断是否有键按下。先使输出端口的各位都为低电平状态,再在列线(一般的做法,可以使用别的方式)加高电平,从输入端口读取数据。如果读取的数据为高电平,则说明当前行线上没有按键被按下,程序应该在循环中等待。如果并行输入端口读取的数据有低电平,则说明有键被按下。
2.线反转法
线反转法的第一步也是把列线置低电平,行线置高电平然后读行线状态。第二步与第一步相反把行线置低电平,列线置高电平后读列线状态。若有键被按下,则两次所读状态的记过,即为按键所在的位置。这样,通过两次输出和两次读入,即可完成按键的识别。
和独立键盘一样,矩阵键盘也具有程序扫描方式、定时扫描方式和中断扫描,但不具有中断方式。
程序扫描方式,是指在特定的程序位置段循环,能够安排键盘扫描程序读取键盘状态。
定时扫描方式是指利用单片机内部或扩展的定时器产生定时中断,在中断中进行键盘的扫描工作方式。无论哪一种扫描方式,键盘程序都应当完成判断按键是否被按下、按键削抖处理、去诶的那个按键位置等操作。
中断扫描方式是指当无按键被按下时,CPU处理其他工作而不必进行按键扫描。当有按键被按下时,通过硬件电路向CPU申请键盘中断,在键盘中断服务程序中完成键盘处理。该方法实时性最好,CPU的工作效率最高。
1.4键盘接口小例子
上图是键盘电路的Proteus图,代码与之对应。
关于代码的几点说明:
1、代码在Protues(V7.5 SP3)上仿真通过;编译器是Keil μVision3 V3.6。
2、4*4矩阵式键盘,采用线反转法完成按键识别。第一次按键有效。
3、为了节省篇幅,个别语句进行了合并,如在循环语句中同时进行计算和比较。看起来代码简短了一些,变量也少了;编译后查看汇编代码还是一样的。这不是个好的做法。类似的代码如:
while(_key_table[_key_no]!=temp_row+(_KEY_STATUE&0xF0))
4、关于键处理函数。如果仅是对数码管操作,可以用同一个函数体完成;这里调用了16个键处理函数完成近乎一样的功能,是为了介绍一种比较少用的键散转处理方式----函数指针数组进行键散转,(*_key_ptr[_key_word])()。这样替代了switch-case语句,作为代价是要计算键号(_key_word)。这也可能是本文档最有价值的一部分了。
5、code void (code* _key_ptr[16])(void)={}有点不好难理解。按照优先级,”_key_ptr”和”[16]”结合成数组;”_key_ptr[16]”和”code*”结合成指向code区的指针数组(代码存放在code区);”(code* _key_ptr[16])” 和”(void)”结合成指向
形参为”void”的函数指针数组;”(code* _key_ptr[16])(void)”和”void”结合成指向
形参为”void”,返回值类型为”void”的函数指针数组;最后的”code”表明该函数指针数组存放在code区。
6、返回类型为”void”的函数体中以”return ;”作为结束语句。这不是不小心,这并没有错。
7、削抖程序只对前沿抖动进行处理。
8、代码结构还不是很好,有待改进。
#include
#define uchar unsigned char
#define _KEY_STATUE P1//P1口状态
#define _ROW_INPUT P1=0x0F//行输入,行在低4位
#define _COL_INPUT P1=0xF0//列输入,列在高4位
#define _ERROR 0//错误代码
void _key_delay(uchar mult);//延时,软件消除抖动。mult*100μs uchar _key_scan(void);//键扫描,线反转法
void _key_0(void);//键处理函数。示例七段共阴数码管显示十六进制void _key_1(void);
void _key_2(void);
void _key_3(void);
void _key_4(void);
void _key_5(void);
void _key_6(void);
void _key_7(void);
void _key_8(void);
void _key_9(void);
void _key_A(void);
void _key_B(void);
void _key_C(void);
void _key_D(void);
void _key_E(void);
void _key_F(void);
code uchar _key_table[16]={ 0xEE,0xDE,0xBE,0x7E,
0xED,0xDD,0xBD,0x7D,
0xEB,0xDB,0xBB,0x7B,
0xE7,0xD7,0xB7,0x77,
};//键值表
code uchar _char_table[16]={ 0x3F,0x06,0x5B,0x4F,
0x66,0x6D,0x7D,0x07,
0x7F,0x6F,0x77,0x7C,
0x39,0x5E,0x79,0x71,
};//七段数码管共阴字符表,"0"~"F"
code void (code* _key_ptr[16])(void)={ _key_0,_key_1,_key_2,_key_3, _key_4,_key_5,_key_6,_key_7, _key_8,_key_9,_key_A,_key_B, _key_C,_key_D,_key_E,_key_F,
};//函数指针数组,用于键散转处理void _key_0(void) //键处理函数。示例七段共阴数码管显示十六进制
{
P2=_char_table[0];//取字符表
;
return
}
●
●
●
void _key_F(void)
{
P2=_char_table[15];//取字符表
;
return
}
void _key_delay(uchar mult) //延时,软件消除抖动。mult*100μs
{
local_mult;
uchar
for(;mult>0;mult--)
for(local_mult=100;local_mult>0;local_mult--)
;
;
return
}
uchar _key_scan(void) //键扫描,线反转法
{
temp_row;//行状态暂存
uchar
_key_no=0;//键号暂存
uchar
_ROW_INPUT;//置行输入
temp_row=_KEY_STATUE;//读行状态
temp_row&=0x0F;//屏蔽高4位
if(temp_row!=0x0F)
{
_key_delay(100);
if(temp_row!=0x0F)
{
_COL_INPUT;//置列输入
_key_delay(1);
while(_key_table[_key_no]!=temp_row+(_KEY_STATUE&0xF0))//键值表查找
{
_key_no++;
if(_key_no>15)//没有对应的键值
return _ERROR;//错误
}
return _key_no+0x30;
}
}
else
return _ERROR;//无按键或抖动
}
int main(void)
{
get_key_word;
uchar
while(1)
{
get_key_word=_key_scan();//按键号暂存
if(get_key_word==_ERROR)//错误不作处理
continue ;
while(get_key_word==_key_scan())//等待按键释放
;
(*_key_ptr[get_key_word-0x30])();//键处理
}
0;
return
}
Attention
﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡矩阵式键盘可以配合外部中断使用,这样提高了CPU的使用效率。这里不作说明。二进制编码键盘一般需要配合编码器,这样更加节省了CPU的使用时间,不过这样子增加了成本。
﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡﹡
方法 指针函数和函数指针的区别 关于函数指针数组的定义 为函数指针数组赋值 函数指针的声明方法为: 数据类型标志符 (指针变量名) (形参列表); 注1:“函数类型”说明函数的返回类型,由于“()”的优先级高于“*”,所以指针变量名外的括号必不可少,后面的“形参列表”表示指针变量指向的函数所带的参数列表。例如: int func(int x); /* 声明一个函数 */ int (*f) (int x); /* 声明一个函数指针 */ f=func; /* 将func函数的首地址赋给指针f */ 赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。 注2:函数括号中的形参可有可无,视情况而定。 下面的程序说明了函数指针调用函数的方法: 例一、 #include
231 1、引言 《汇编语言程序设计》是高等院校计算机及相近专业学生必修的专业基础课程之一,它不仅是《嵌入式开发》、《操作系统》、《单片机》、《接口技术》等基础课程的先修课程,而且也十分有助于学生系 统掌握计算机基础知识和提高编程能力[1] 。作为一门直接控制计算机硬件和cpu结合最为紧密的一门语言,执行起来时最为有效和速度最快的。但是区别于高级语言他又自身的弱点,比如可读性差,需要更深入地熟悉硬件结构,编程和调试过程繁琐,而且没有便捷的开发调试环境。在讲授《汇编语言程序设计》过程中,如果能够结合或者转化为高级语言如C语言的内容那学生接受和学习起来就能增加不少的兴趣,提高学生的学习效率。 2、C 语言数组和指针的使用 2.1 数组 数组是在程序设计中为了处理方便,把具有相同类型的若干变量按有序的形式组织起来的一种形式。这些按序排列的同类数据元 素的集合称为数组[2] 。在C语言中,数组属于构造数据类型。一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。因在汇编语言中主要把指令系统中的寻址方式转换为一维数组或指针,所以下面就简要介绍一下一维数组和指针的特点 定义一维数组的格式为: 类型说明符 数组名[整型常量表达式],…;例如:int a[10],b[5];说明: (1)它表示定义了两个一维数组,一个数组名为a,另一个数组名为b。数组名是按照“标识符”的规则构成的。(2)a数组含有10个数组元素,即a[0]、a[1]、a[2]、…、a[9];b数组含有5个数组元素,即b[0]、b[1]、b[2]、b[3]和b[4]。注意,不能使用a[10]和b[5],否则即出现数组超界现象,并且需要注意的是数组的小标是从0开始的。(3)类型说明符int 说明a数组和b数组中的每个元素均占2个字节,只能存放整型数据。(4)整型常量表达式可以是整型常量或符号常量。最常见的是整型常量。不允许为变量。(5)C编译程序(如Turbo C)为a数组在内存中分配了10个连续的数组单元(共占20个字节),为b数组在内存中分配了5个连续的数组单元(共占10个字节)。(6)C编译程序还指定数组名a为数组的首地址,即a与&a[0]等价;指定数组名b为b数组的首地址,即b与&b[0]等价。 2.2 指针 指针是一个特殊的变量,它里面存储的数值被解释成为内存里 的一个地址。计算机内存中的每个内存单元,都有相应的内存地址。在程序中对变量进行存取操作有两种方式,一种叫“直接存取”,就是指在程序中对变量进行存取操作时是按变量的地址来存取的方法,另一种叫“间接存取”,就是通过另外定义一个指针变量来保存 需要访问的数据的地址[3] 。 (1)指向简单变量的指针。(2)指向数组的指针。指针所指的数组既可以是一维数组,也可是多维数组。(3)指针数组。数组的元素值为指针,指针数组是一组有序的指针集合。(4)指向指针的指针。如 果一个指针变量存放的是另一个指针变量的地址,则称这个指针变 量为指向指针的指针。(5)指向函数的指针。在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。我们可以把函数的这个首地址赋予一个指针变量,通过指针变量就可以找到并调用这个函数。 3、数组和指针在汇编语言指令系统寻址方式中的应用和转换 3.1 汇编语言指令系统的寻址方式[4] (1)立即寻址。(2)寄存器寻址。(3)直接寻址。(4)寄存器间接寻址:指令中指出一个基址寄存器BX、BP或变址寄存器SI、DI,并以其内容做为操作数的有效地址,ADD AX,[BP]物理地址=10H×(SS)+(BP)。(5)寄存器相对寻址:指令中指出一个基址或变址寄存器,同时给出一个位移量, 寄存器内容与位移之和做为操作数的有效地址。MOV AX,[DI+100H],有效地址EA=(DI)+100H,为物理地址=10H×(DS)+(DI)+100H。(6)基址变址寻址:指令同时指出一个基址寄存器和一个变址寄存器,两寄存器内容的和为操作数的有效地址。ADD AX,[BX][SI],有效地址EA=(BX)+(SI)。物理地址=10H×(DS)+(BX)+(SI)。(7)相对基址变址寻址:指令中给出一个基址寄存器一个变址寄存器和一个位移量。两个寄存器的内容及位移量三者之和做为操作数的有效地址。例:MOV DX,100H [BX] [SI,物理地址=10H×(DS)+(BX)+(SI)+100H。 3.2 间接寻址方式转换为数组或指针 3.2.1 寄存器间接寻址转成一维数组来理解 形式:ADD AX,[BP]物理地址=10H×(SS)+(BP)。我们就可以认为,在此定义了一个数组SS,即SS中的值为这个数组的首地址,当然我们知道这个数组的最大元素个数为64K个。刚才谈到偏移量和数组下标都是从0开始的,所以偏移量BP就可以认为是这个数组的一个下标,在这寻址操作数的时候是要把这个下标作为一个内存地址,其所存储的内容就是我们所要找的操作数。 在数组中形如I=A[10]就是把A数组的第10个元素赋值给I,在ADD AX,[BP]语句中BP也有一个中括号,只是在这个地方省略了数组名;并且也是把SS数组的第BP个元素赋值给AX。 所以无论从形式还是从本质上就把寄存器间接寻址转换成了一个一维数组。 3.2.2 寄存器间接寻址转成指针来理解因为指针和数组有时间是可以相互转换的,所以在这也可以转换成指针来理解。 形式:ADD AX,[BP]物理地址=10H×(SS)+(BP)。BP在汇编语言中本身就定义为一个基址“指针”用来和堆栈段配对使用,其中存放的数据是堆栈段的某一个存储单元地址。这就和指针吻合了,前面说到指针变量名与地址间具有一一对应关系,在存取操作时是按变量的地址来进行的一种“间接存取”的方法。那么这个地方我们可以认为BP是一个指向堆栈段中某一个存储单元的C语言意义上的指针。 这样就把寄存器间接寻址方式可以理解成C语言意义上的指针。对于寄存器相对寻址、基址变址寻址、基址变址寻址我们也都 C语言中数组指针在汇编语言寻址方式中的应用 马耀锋 李红丽 (中州大学信息工程学院 河南郑州 450044) 摘要:因高级语言不需要熟悉低层软件和硬件知识,所以学生有很大的学习兴趣,数组指针是C 语言中的重点内容,学生们都能熟练掌握。而汇编语言因与硬件紧密相连,所以学生学习兴趣不大。为了更好的培养学生的学习兴趣,提高教学效率,本文通过分析数组指针与寻址方式的异同,提出了如何把寻址方式转化成数组指针来学习的方法。 关键词:数组 指针 寻址方式中图分类号:TP312.1-4文献标识码:A 文章编号:1007-9416(2012)04-0231-02 ??????下转第232页
强制类型转换类型的本质指针的本质函数指针 昨天给朋友讲了 3个问题, A〉什么是函数指针,本质是?指针的本质? B〉什么是类型,类型的本质是? C〉什么是强制类型转换,本质是? 作者张世光亿通科技软件部 060913 QQ:87895224 MSN:Z_55@https://www.doczj.com/doc/2610659916.html, A〉什么是函数指针,本质是?指针的本质? 如 void fun(); //函数声明 void (*ptrfun)();//函数指针变量ptrfun 的声明,表明ptrfun这个指针指向一个 void fun()函数类型的函数地址。 〉函数的名字代表函数的地址; 所以,可以这样 ptrfun =fun ;//给ptrfun赋值。此时,ptrfun 就是 fun 函数的别名; 在需要调用 fun()的地方,可以这样写 (*ptrfun)(); //等同于 fun(); //这就是别名的意义。 指针的本质是一个固定长度(32位系统上是4个byte)的空间中放置的内容是一个内存地址。 而指针类型的本质就是对这个地址的解释方式。不同的指针类型,对即使相同的内存地址的解释是不同的,受影响的空间大小也不同。受影响的空间的大小等于sizeof(类型)。 如 short i=0x1234; short * psort = &i; char *pchar = &i; //此时 pshort ==pchar;即指向了同一个地址; 那么 (*pshort) 和 (*pchar) 的值各是什么? 假设 sizeof(short)==2 sizeof(char)==1 假如内存中这样: ---地址 0x1000-- | 0x34 | ---------------- | 0x12 | ---------------- 那么,psort==0x1000 ,同时,pchar==0x1000 ; 那么,(*psort) 代表是地址0x1000 和 0x1001 这两个字节的内容; <-----由其类型决定 (*pchar)代表地址0x1000 这一个字节的内容; <<-----由其类型决定
C++指针函数习题 一、选择题 1.以下程序的运行结果是()。 sub(int x, int y, int *z) { *z=y-x; } void main() { int a,b; sub(10,5,&a); sub(7,a,&b); cout< #include<>
让你不再害怕指针 前言:复杂类型说明 要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,所以我总结了一下其原则: 从变量名处起,根据运算符优先级结合,一步一步分析. 下面让我们先从简单的类型开始慢慢分析吧: int p; //这是一个普通的整型变量 int *p; //首先从P处开始,先与*结合,所以说明P是一个指针,然后再与int结合, //说明指针所指向的内容的类型为int型.所以P是一个返回整型数据的指针int p[3]; // 首先从P处开始,先与[]结合,说明P是一个数组,然后与int结合, // 说明数组里的元素是整型的,所以P是一个由整型数据组成的数组int *p[3]; //首先从P处开始,先与[]结合,因为其优先级比*高,所以P是一个数组, //然后再与*结合,说明数组里的元素是指针类型, 然后再与int结合, //说明指针所指向的内容的类型是整型的,所以P是一个由返回整型数据 //的指针所组成的数组 int (*p)[3]; //首先从P处开始,先与*结合,说明P是一个指针,然后再与[]结合 //(与"()"这步可以忽略,只是为了改变优先级), 说明指针所指向的 //内容是一个数组,然后再与int结合, 说明数组里的元素是整型的. //所以P是一个指向由整型数据组成的数组的指针 int **p; //首先从P开始,先与*结合,说是P是一个指针,然后再与*结合, 说明指 //针所指向的元素是指针,然后再与int结合, 说明该指针所指向的元素 //是整型数据.由于二级指针以及更高级的指针极少用在复杂类型中, 所 //以后面更复杂的类型我们就不考虑多级指针了, 最多只考虑一级指针. int p(int); //从P处起,先与()结合,说明P是一个函数,然后进入()里分析,说明该 //函数有一个整型变量的参数,然后再与外面的int结合, 说明函数的 //返回值是一个整型数据 Int (*p)(int); //从P处开始,先与指针结合,说明P是一个指针,然后与()结合,
关于强制类型转换的问题,很多书都讨论过,写的最详细的是C++ 之父的《C++ 的设计和演化》。最好的解决方法就是不要使用C风格的强制类型转换,而是使用标准C++的类型转换符:static_cast, dynamic_cast。标准C++中有四个类型转换符:static_cast、dynamic_cast、reinterpret_cast、和const_cast。下面对它们一一进行介绍。 static_cast 用法:static_cast < type-id > ( expression ) 该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法: ?用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。 ?用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。 ?把空指针转换成目标类型的空指针。 ?把任何类型的表达式转换成void类型。 注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。 dynamic_cast 用法:dynamic_cast < type-id > ( expression ) 该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;如果type-id是类指针类型,那么expression也必须是一个指针,如果ty pe-id是一个引用,那么expression也必须是一个引用。 dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
C++语言程序设计中函数指针的分析与研究摘要:指针作为c++语言程序设计中的一个重要概念,其应用也是c++语言程序设计中的非常重要的一个内容。指针作为一种特殊的数据结构类型,它可以有效地表示数据之间复杂的逻辑结构关系。灵活正确地运用指针可以给程序的设计带很多的便捷,其中效果最为显著的就是函数指针的应用,通过使用函数指针,可以在调用函数时可以获得多个返回值以及实现对内存地址的直接处理等。本文从对c++语言程序设计中的函数指针的介绍谈起,然后详细说明了使用c++语言程序设计函数指针需要注意的问题,最后就c++语言程序设计中函数指针的应用技巧进行了系统的分析。 关键词:c++语言程序设计;函数指针;分析研究 中图分类号:tp311.11 文献标识码:a文章编号:1007-9599 (2011) 24-0000-01 analysis and research of function pointers for c++ language programming zhang suxia (shandong rural credit cooperatives,qingdao266550,china) abstract:pointer as c++ language programming is an important concept,its application is the c++ programming language is very important content.pointer as a special type of data structure,which can effectively express complex data between the logical structure of the relationship.flexible
全国计算机等级考试二级C语言真题题库3 2015年9月 (总分:43.00,做题时间:120分钟) 一、选择题(每小题1分,共40分)(总题数:40,分数:40.00) 1.下列叙述中正确的是()。 (分数:1.00) A.程序可以作为算法的一种表达方式√ B.算法的有穷性是指算法的规模不能太大 C.算法的复杂度用于衡量算法的控制结构 D.算法的效率与数据的存储结构无关 解析:[解析]算法的有穷性是指操作步骤有限且能在有限时间内完成,如果一个算法执行耗费的时间太长,即使最终得出了正确结果,也是没有意义的,故B选项错误。一个算法复杂度的高低体现在运行该算法所需要的计算机资源的多少,故C选项错误。算法的效率是指算法执行的时间,算法执行时间需通过依据该算法编制的程序在计算机上运行时所消耗的时间来度量,这就与算法本身设计的优劣以及具体的编程实现有关,数据的存储结构会极大的影响程序的执行时间,故D选项错误。算法在实现时需要用具体的程序设计语言来实现,所以程序可以作为实现算法的一种表达方式。故选A选项。 2.某循环队列的存储空间为Q(1:m),初始状态为front=rear=m。现经过一系列的入队操作和退队操作后,front=m-1,rear=m,则该循环队列中的元素个数为()。 (分数:1.00) A.m-1 B.1 √ C.m D.0 解析:[解析]循环队列长度为m,由初始状态为front=rear=m可知此时循环队列为空。入队运算时,首先队尾指针(rear)进1(即rear+1),然后在rear指针指向的位置插入新元素。特别的,当队尾指针rear =m+1时,置rear=1。迟队运算时,排头指针(front)进1(即front+1),然后删除front指针指向的位置上的元素,当排头指针front=m+1时,置front=1。从排头指针front指向的后一个位置直到队尾指针rear指向的位置之间所有的元素均为队列中的元素,则该循环队列中的元素个数为m-(m-1)=1。故选择B 选项。 3.能从任意一个节点开始没有重复地扫描到所有节点的数据结构是()。 (分数:1.00) A.循环链表√ B.双向链表 C.二叉链表 D.有序链表 解析:[解析]在单链表的第一个节点前增加一个表头节点,队头指针指向表头节点,最后一个节点的指针域的值由NULL改为指向表头节点,这样的链表称为循环链表。在循环链表中,只要指出表中任何一个节点的位置,就可以从它出发没有重复地扫描到表中其他所有的节点。双向链表与二叉链表都可以扫描到所有节点,但是会有重复。有序链表如果是单链表的情况下,不能扫描到所有节点。故A选项正确。 4.某棵树中共有25个节点,且只有度为3的节点和叶子节点,其中叶子节点有7个,则该树中度为3的节点数为()。 (分数:1.00) A.不存在这样的树√ B.7 C.8 D.6 解析:[解析]树是一种简单的非线性结构,直观地来看,树是以分支关系定义的层次结构。在树结构中,一个节点所拥有的后件个数称为该节点的度,所有节点中最大的度称为树的度。首先假设这样的树是存在
指针函数与函数指针的区别 一、 在学习arm过程中发现这“指针函数”与“函数指针”容易搞错,所以今天,我自己想一次把它搞清楚,找了一些资料,首先它们之间的定义: 1、指针函数是指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针 类型标识符 *函数名(参数表) int *f(x,y); 首先它是一个函数,只不过这个函数的返回值是一个地址值。函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有函数返回值,而且,在主调函数中,函数返回值必须赋给同类型的指针变量。 表示: float *fun(); float *p; p = fun(a); 注意指针函数与函数指针表示方法的不同,千万不要混淆。最简单的辨别方式就是看函数名前面的指针*号有没有被括号()包含,如果被包含就是函数指针,反之则是指针函数。来讲详细一些吧!请看下面 指针函数: 当一个函数声明其返回值为一个指针时,实际上就是返回一个地址给调用函数,以用于需要指针或地址的表达式中。 格式: 类型说明符* 函数名(参数) 当然了,由于返回的是一个地址,所以类型说明符一般都是int。 例如:int *GetDate(); int * aaa(int,int); 函数返回的是一个地址值,经常使用在返回数组的某一元素地址上。 int * GetDate(int wk,int dy); main() { int wk,dy; do { printf(Enter week(1-5)day(1-7)\n); scanf(%d%d,&wk,&dy); } while(wk<1||wk>5||dy<1||dy>7); printf(%d\n,*GetDate(wk,dy));
单片机 键盘接口电路 简介 Copyleft2009 by高飞电子经营部 P.S. 文档由高飞整理。能力有限,疏漏在所难免!这里说声抱歉了。 若对文档所描述的观点存在疑问,欢迎交流。 QQ:1275701567
键盘是单片机应用系统中不可缺少的输入设备,是实心人机对话的纽带,是操作人员控制干预单片机应用系统的主要手段。如用手机键盘发送短信息、遥控器键盘控制家用电器等。各种仪器仪表的小键盘系统,则可以显示各种信息。例如,数字式频率计、数字式扫频仪、数字式测量仪等。通过键盘向单片机应用系统输入数据和控制命令,实现对应用系统的认为控制,可以提高应用系统的灵活性。每种单片机应用系统的小键盘系统会实现不同的功能。比如,需要按键进行清零、预置值、改变测量范围等。这些功能是由一系列键盘电路通过编程实现的。因此,键盘在控制系统中得到了广泛的应用。本文档介绍了单片机系统中键盘接口电路及其相应的实现方式。 包含以下内容: ●键盘的组成和分类; ●键盘实现的硬件接口电路; ●4x4键盘与单片机的接口实例; ●二进制编码器键盘与单片机的接口实例。 1.1键盘设计的组成和分类 说说键盘的发展史。键盘发展至今,已经有100多年的时间。伴随着材料科学和电子技术的发展,键盘的物理构成和物理构造也不断变化发展。这些不同结构的键盘,各有特点,适用于不同的场合,但是控制方法都是类似的。 1.1.1键盘的物理结构 1.机械式结构键盘 机械式结构键盘,一般使用类似金属接触式开关的原理,实现触点导通或断开。在实际应用中,机械开关的结构形式很多。最常用的是交叉接触式。它的优点是结实耐用,缺点是不防水,敲击比较费力。交叉接触式机械开关,在单片机应用系统中最为常用。轻触开关也属于这一类。 2.电容式结构键盘 电容式结构键盘是一种类似电容式开关原理键盘。它通过按键改变电极的间距而产生电容量的变化,暂时形成震荡脉冲允许通过的条件。电容的容量是由介质、两极间的距离及两极的面积来决定的。当键帽按下时,两极的距离就发生变化,就引起电容容量发生变化。当参数设计合适时,按键时就有输出,而不按键就无输出。这个输出再经过整形放大,去驱动编码器。由于电容器无接触,所以这种按键在工作过程中不存在磨损、接触不良等问题,耐久性、灵敏度和稳定性都比较好。另外,为了避免电极间进入灰尘,电容式按键开关采用了密闭封装,比较便于保养。优良的特性带来的缺点就是代价高昂,标准PC键盘淘宝卖价不低于1500RMB!
指针与函数 1.掌握指针变量为参数在调用函数和被调用函数之间的数据传递。 2.掌握函数返回地址值的方法。 3.掌握指向函数的指针及其运算。 4.能熟练运用指针变量完成C程序的编写。 1. 指针变量作为参数时实现数据传递 2.指向函数的指针及运算 3.函数返回地址值的方法
(一)导课 在C语言函数调用中,参数传递可以是一般变量的传递,也可以是地址的传递(指针)。 (二)课程要点 一、指针变量作为函数的参数 使用指针类型做函数的参数,实际向函数传递的是变量的地址。【例1】定义一个函数,用指针变量作参数实现两个数据的交换。 #include
void swap(int *pa,int *pb) { int t; t=*pa; *pa=*pb; *pb=t; } 其数据交换的变化过程如下图所示: 思考:将上面swap 函数作以下修改,a,b 的值是否发生了交换? void swap(int *pa,int *pb) { int *t; t=pa; pa=pb; pb=t; } 程序运行结果: before swap a=15,b=20 after swap a=20,b=15 程序运行结果: before swap a=15,b=20 after swap a=15,b=20
1、指针类型转换请使用static_cast、dynamic_cast等操作 2、指针变量(不管那种指针),大小都是四个字节,内容就是指向的对象的首地址。不同类型指针不同点就是对首地址之后的那块内存会做不同解释。 建议你看看《Inside C++ Object Model》里面关于指针的内容,里面有清晰的说明 int * pi = new int; pi 指向了一块内存的首地址,这块内存应该有sizeof(int)个字节, 由于pi是int *类型,所以通过pi进行的操作,编译器都认为pi的内容是一个有sizeof(int)个字节的int 型变量的首地址,所以*pi = 4,遇到这样的操作,编译器产生的代码就是把4这个数付到pi所指的四个字节里。 char * pc = (char *)pc; //建议这样写char * pc = static_cast