C语言本身有较强的位处理功能,但在控制领域有时候经常需要控制某一个二进制中的某一位,为此在MCS_51的KEIL C51中扩充了两个数据类型bit和sbit。前者可以在MCS_51的位寻址区进行分配,而后者只能定义为可位寻址的特殊功能寄存器SFR中的某一位。这两个扩充为MCS_51应用C语言编程带来很大的方便。AVR的C语言中除了CodeVisionAVR定义了bit数据类型外,其余都没有类似的定义,而sbit类型所有C语言都没有定义。相比较,进行位操作运算CodeVisionAVR的功能最强,它一方面有bit类型的数据,可用于位运算;另外在访问寄存器时可以直接访问寄存器的某一位,例如访问DDRB的B2位可以这样访问:
DDRB.3 = 1;
而在IAR、ICCAVR和GCCAVR中没有bit类型的运算,当它们需要访问寄存器的某一位时,只能使用ANSI C语言的位运算功能。
C语言是为描述系统而设计,因此它具有汇编语言所能完成的一些功能,有较好的位操作指令:“&”,“|”,“~”、“<<”、“>>”。在控制领域,经常需要控制某一个二进制位,标准C有两种方法实现单个位的操作。
1. 用“读-修改-写”实现对单个位的操作
在没有单个位的位操作指令的情况下,一般是采用“读-修改-写”的方法实现单个位的位操作,即:
Ø 通过与0“与”操作,将某一位清0。例如,使i变量的第0位为0,实现方法为:i = i&0xfe;
Ø 通过与1相 “或”操作,将某一位置1。例如,使i变量的第0位为1,实现方法为 i = i|0x01;
Ø 通过与1进行“异或”操作,将某一位取反。例如,使i变量的第0位取反,实现方法为 i = i^0x01。
注意:采用“读-修改-写”的方法时不要影响其他位。即某位清0时,其他位与1相 “与”;某位置1时,其他位与0“或”;取反时,其他位与0“异或”。
很多程序员喜欢采用下面的移位方式,语句简练:
#define bit(x) (1<<(x))
#define LED 2
PORTB|= bit(LED); //将PORTB第二位置1,点亮连接在I/O口的LED
该方式下,程序运行时会增加移位操作,生成的代码较大。若按如下方式直接定义生成的代码就不会有移位操作:
#define LED 0X04
PORTB |= LED;
也有程序员采取如下宏定义的方法实现单个位的操作,使用十分方便:
#define SET_BIT(x,y) ((x)|=(0x0001<<(y)))//置x的第y位
#define CLR_BIT(x,y) ((x)&=~( 0x0001<<(y)))//清x的第y位为0 #define CPL_BIT(x,y) ((x)^= (0x0001<<(y)))//取反x的第y位
#define GET_BIT(x,y) (((x)&(1<读取x的第y位
#define LET_BIT(x,y,z) ((x)=(x)&(~(1<<(y)))|((z)<<(y)))//
将x的第y位写上z(0/1)
2. 通过位域的(Bit Field )的方法实现位操作
在系统寄存器设置时,很多时候并不需要修改完整的字节,而是只修改一个或几个位,标准C提供了一种基于结构体的数据结构——位域。位域就是把一个存储单元中的二进制划分为几个不同的区域,并说明每个区域的位数,每个域有一个域名,允许在程序中按域名进行操作。位域的定义格式如下:
Struct 位域结构名
{
位域列表;
};
位域列表格式为:类型说明符 位域名:位域长度
Struct k
{
unsigned int a:1;
unsigned int :2;
unsigned int b:3;
unsigned int :0;//空域
}k1;
说明:
Ø 各位依次从低位到高位排列,排满一个存储单元,按地址接着排下一个单元。
Ø 位域可以无域名,但不能被引用。例如,第2域,这时它只用来填充和调整位置。
Ø 第四行称为空域,目的是将目前存储单元的剩余部分分为一个域,且填充0。
位域的引用,例如;
k1.a = 1;//置k1的BO位为1
k1.b = 7;//置k1的B3-B5位为111
用位域定义位变量,操作I/O口,产生的代码紧凑、高效。定义的方法如下;
typedef struct INT8_bit_struct
{
unsigned bit0:1; unsigned bit1:1; unsigned bit2:1;
unsigned bit3:1; unsigned bit4:1; unsigned bit5:1;
unsigned bit6:1;unsigned bit7:1;
}bit_field;
再次宏定义每一个位,使用方法如下:
#define _PINB 0x23
#define _PORTB 0X25
…………
#define IOB2i (*(volatile bit_field *)(_PINB)).bit2
#define IOB2o (*(volatile bit_field *)(_PORTB)).bit2
例如:
void main(void)
{
unsigned char I;
IOB2o = 0;//B口B2位输出低电平
i = IOB2i;//读B口B2位,将B口B2位上的电平值送给i
//
}
对于没有扩展位变量的C语言环境,在汇编下没有单个位的位操作的MCU,通过位域的方法操作I/O口是最佳的方法;汇编下有单个位的位操作指令的MCU,可以嵌入式汇编,但是程序的移植性可能会下降,建议使用位域的方法进行操作比较合适。