打印
[AVR单片机]

动手学AVR单片机十二、AVR单片机C语言程序设计中的位操作

[复制链接]
4303|12
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
wangwo|  楼主 | 2009-12-23 22:21 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
在标准C语言的的教材中,对于位运算的操作是基本不涉及的,但是在单片机系统的程序中,需要经常操作各类以字节为单位的寄存器,而这些寄存器通常都是以二进制中的位为控制单位的数据组合。往往一个8位寄存器中的每一位都有各自的控制对象,例如端口B的方向寄存器DDRB,如下图所示

       它实际上控制着PB口的8个端口PB0-PB7的方向,也就是说它的每一位都控制一个端口的方向,如果我们要把端口PB0-PB3设置为输出口,而把PB4-PB7设置为输入口,在不用位运算符的情况下,我们可以直接使用赋值语句DDRB=0x0f来实现,这样是完全可以实现的。
      但是如果出现下面的情况:在程序中PB口的8位端口的状态本来是1、3、5、7为输入。0、2、4、6为输出(即DDRB=0x55),接下来要将PB口的第1位设置为输出,其它端口的状态不变,然后又要将第2位设置为输入,其它端口的状态不变。该怎么实现?也许我们仍然可以使用赋值语句来实现,比如DDRB=0x55;接下来设置DDRB=0x57;然后再设置DDRB=0x53;首先要肯定的是,这种做法是绝对正确的。但是我们可能有没有注意到,在改变其中一位的值的时候,我们同时还要考虑其它7位的状态,并且要小心翼翼的避免不小心改变了其它位的值。
       那么有没有一种方法,可以简单的实现修改某一位的状态,同时不会改变其它位的状态呢?
       这就牵出了单片机C语言程序设计中的位运算的概念。
      我们来看这个语句:DDRE |= (1 << PE5);   这个语句实现的功能是将PE口的第5位设置为输出口,其余口的状态不变。它是怎么实现的?首先我们来看1 << PE5这个表达式,我们前面已经介绍了,AVR各寄存器的宏定义是在头文件io.h中定义好的,我们可以直接调用,现在我们就来看看在io.h中PE5是如何定义的(在WINAVR的安装目录下查找iom64.h),我们可以看到PE口的8位分别定义如下:
/* Port E Data Register - PORTE */
#define    PE7       7
#define    PE6       6
#define    PE5       5
#define    PE4       4
#define    PE3       3
#define    PE2       2
#define    PE1       1
#define    PE0       0
       可以看出,实际上PE5=5;那么1 << PE5,就很容易理解了,它的作用是把1左移5位,最后的结果按照二进制表示就是0b00100000,而DDRE |= (1 << PE5);实际上是DDRE= DDRE | (1 << PE5);首先“|”表示的是“或”操作DDRE是端口E的方向寄存器,在iom64.h中定义为:#define DDRE      _SFR_IO8(0x02);它实际上是定义了一个标识符,这个标识符对应数据存储区RAM中的某个地址,这个我们暂且不去深究。我们还是回过头来看DDRE= DDRE | (1 << PE5);这句话实现的功能,这句话实际上是将寄存器DDRE中的内容(数据)跟二进制数0b00100000进行或操作,我们知道两个数的或操作的结果是:只要有一个是1,结果就是1.那么假如DDRE中本来的值是0b10001010(0x8a),它和0b00100000进行“或”操作以后的结果变成了0b10101010(0xaa)。我们可以看出,相或以后DDRE中的第5位以外的各位的值都没有改变,而第5位变成了1,我们的目的就是要将第5位设为输出口(即将第5位设置为1)。
       现在我们来看一下从语言中有几种位运算符:
移位运算符:左移<<,右移>>
与运算符:&
或运算符:|
取反运算符:~
异或运算符:^
     就这些了,总共只有6中位运算符。现在我们来看一下这些运算符都起什么作用;
左移运算符:表达形式为x<<n,意思是将数据x向左移动n位,在这里x和n都必须是无符号整形数据(所有的位运算符的操作对象都是无符号整形);
     例如,x是一个unsigned char类型的数据(即x是一个单字节数据,共有8位),设x的初值为0x00000001,执行x<<n的操作:
n=0时,x<<n表示x左移0位,实际就是不移动,x的值不改变
n=1时,x<<n表示x左移1位,运算结果为0b00000010
n=2时,x<<n表示x左移2位,运算结果为0b00000100
...
n=7时,x<<n表示x左移7位,运算结果为0b10000000
n=8时,x<<n表示x左移8位,运算结果为0b00000000
      从结果来看,当n在1-7之间取值时,运算的结果总是1依次向左移动一位,但是当n>=8以后,x的值就一直是0了,这是因为x是一个只有8位的整形变量,它的最大二进制长度是8位,当n超过8以后,移位操作的结果已经超出8位的范围了,产生了溢出现象。
       右移的原理跟左移相似,只是它的运算结果跟左移刚好相反。
      在单片机C语言编程中,经常使用移位操作来实现将数据乘以(左移)或除以(右移)2的n次方的乘除运算,利用移位操作实现乘除运算可以显著提高单片机的运算速度和效率。其详细原理我们可以翻阅相关的C语言书籍来进行更深了解。
      “取反”、“与”、“或”、“非”运算经常用于对寄存器的某一位进行操作,
例如,使端口B的第二位输出高电平,同时不改变其余端口的状态,我们可以采用如下方法:
PORTB |= (1<<PB2),
         实际上,想将一个8位寄存器的某一位设置为1,可以采用这样的语句:寄存器名(如PORTB) |= (1 << X),式中X表示第X位。
        相反的,如果想将一个8位寄存器的某一位设置为0,可以采用这样的语句:寄存器名(如PORTB) &= ~(1 << X),式中X表示第X位。

       写着的时候,总感觉心里清楚,但是表达不出来,本来是相结合单片机来讲解如何用C语言来开发单片机程序的,但总是没有办法将两者的结合很直观的表达出来。有些困惑!
       这或许就是现在市面上相当多的讲解单片机C语言开发的书为什么总是将C语言的讲解和具体的程序设计分开来讲的原因吧。大家都没有更好的办法在讲解具体的单片机开发的同时把C语言的知识逐步融合到实例中!

相关帖子

沙发
Karlshen| | 2009-12-25 12:00 | 只看该作者
寄存器设置我觉得不好掌握

使用特权

评论回复
板凳
杜专| | 2009-12-25 12:07 | 只看该作者
还好啊

使用特权

评论回复
地板
gallop_chen| | 2010-2-4 22:33 | 只看该作者
不用那么麻烦,贴一些应用过程中的定义,端口置位,清零,取反,移位,数据交换等就可以了。

使用特权

评论回复
5
maxking| | 2010-2-19 08:28 | 只看该作者
讲得真透彻,明白晒了。谢谢!

使用特权

评论回复
6
lovelyegle| | 2010-2-22 19:46 | 只看该作者
看着很复杂

使用特权

评论回复
7
linhai1986| | 2010-2-22 20:47 | 只看该作者
好东西,下下来慢慢看

使用特权

评论回复
8
麻辣鸭脖子| | 2010-2-23 20:29 | 只看该作者
进来学习

使用特权

评论回复
9
xiaoxin1986| | 2010-2-23 20:31 | 只看该作者
好东西,看看先

使用特权

评论回复
10
ls5000| | 2010-2-25 14:12 | 只看该作者
学习学习

使用特权

评论回复
11
sanshuishousi| | 2010-2-28 11:27 | 只看该作者
谢谢,讲得蛮好的,很清楚啊,AVR简单的IO输出应用过之后,就看得一目了然了!

使用特权

评论回复
12
yidou| | 2010-2-28 22:37 | 只看该作者
AVR的位操作网上有人反应很不好,看着好像真不好掌握

使用特权

评论回复
13
zzseven| | 2010-4-9 16:37 | 只看该作者
讲的很透彻啊 非常感谢

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

99

主题

806

帖子

2

粉丝