当前位置:首页 >> 信息与通信 >>

AVR单片机C语言程序设计中的位操作

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

在标准 C 语言的的教材中,对于位运算的操作是基本不涉及的,但是在单片机系 统的程序中,需要经常操作各类以字节为单位的寄存器,而这些寄存器通常都是 以二进制中的位为控制单位的数据组合。 往往一个 8 位寄存器中的每一位都有各 自的控制对象,例如端口 B 的方向寄存器 DDRB,如下图所示

它实际上控制着 PB 口的 8 个端口 PB0-PB7 的方向,也就是说它的每一位都控 制一个端口的方向,如果我们要把端口 PB0-PB3 设置为输出口,而把 PB4-PB7 设置为输入口,在不用位运算符的情况下,我们可以直接使用赋值语句 DDRB=0x 0f 来实现,这样是完全可以实现的。 但是如果出现下面的情况:在程序中 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 << P E5);实际上是 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),它和 0 b00100000 进行“或”操作以后的结果变成了 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 语言的知识逐步融合到实例中!