菜农__曹星 https://passport2.21ic.com/?757406 [收藏] [复制] [RSS] 光辉岁月

日志

位和位域的操作

已有 1064 次阅读2011-12-14 04:35 |个人分类:菜农群课笔记|系统分类:嵌入式系统| 菜农,位,位域


用位运算,操作对象是sfr本身,


          优点是可以访问多个位。


          缺点是,编译器无法检查的合法性。


 



应该是编译器无法判断你给出的定义,是否属于该SFR的。


#define WLS  0


#define NSB  2


#define PBE  3


.....


UART0->LCR = (3 << WLS) | (1 << PBE) ...


上面的例子中,编译器并不知道WLS, PBE等,与LCR是相关的。


 



然而位域确实结构体就可以知道了


    为了解决这个问题,就可以使用结构位域的方法。



struct LCR_T {


   uint32_t WLS : 2;


   uint32_t PBE : 1;


   ...


};



UART0->LCR.WLS = 3;


UART0->LCR.PBE = 1;


...


 


位域的方法解决了一般位操作的缺点。


 



这个是怎么个顺序排列下来的???


一般的规则是:使用little endian的编译器,定义位域,从最小位开始。而big endian 则从最大位开始。


 



为什么说位域能解决位操作的缺点?


      由于位操作使用的“位定义”符号,是宏,本质上是数值。不同的SFR的位很有可能是相同的位。编译时,编译器就只看到一些数值而已,而并不清楚这些数值是否合法。使用位域的方法,操作对象就变成了位域,而不是位操作的sfr。位域是有标识符的,不是宏,并且位域的结构包含关系,也让编译器知道哪些位域是属于哪些sfr。所以,如果访问一个不属于该sfr的位域,编译器会报错的。这是位域相对于位操作的优点。


 



但是,位域的操作方法也带来了缺点:效率低下。位域操作,对象是位域,所以一次只能访问sfr中的一个位域.



每次都是一个读-修改-写的过程。而位操作,对象是sfr,就可以对多个位进行修改,最后全部写入。


 


 



总结一下:


位操作,优点:高效;缺点:不安全。


位域,优点:安全;缺点:低效


 



两者可以在同一程序中混合使用,但两者的优点不会同时体现。


 



那混合还有意义么?


当然没有意义。反而使程序的风格混乱了。


 



两者都是独立的操作,没有把两者混合取其优点的方法。能取两者优点就太牛了


 



UART0->LCR_REG = (3 << WLS) | (1 << PBE);


-----------------


UART0->LCR.WLS = 3;


UART0->LCR.PBE = 1;


 


你用哪种方法,编译器就生成哪种方法的代码。


 



你用union,可以让sfr即可以按整体访问,也可按位域访问,但在一个访问时,你只能采用一种方法。


 



下面我就要介绍另一种方法


这种方法就是结合两种方法的优点,而没有它们的缺点的方法



唯一的“缺点”,就是要求程序是c++。通过这个头文件学CPP


不知难度如何


下面先说说使用方法。


头文件中预定义了一些sfr寄存器,这个跟普通的头文件相同。


这些sfr寄存器按组分类,这个跟结构也区别不大。并且,预定义了寄存器组的变量。


例如:GPIOA, UART0, SPI0等等


访问寄存器组中sfr用结构成员访问操作符“.”。例如:GPIOA.DOUT 跟结构体一样


下面就不同了


   每个sfr的定义,都是union.虽然是union,但其实是c++的类。有构造函数,析构函数,数据成员。


 


在给出sfr后(如GPIOA.DOUT),有2种方法操作。


1、直接作为左值或右值。


2、创建sfr临时对象,访问位


 



sfr作为左值,可以接受一个数值,例如:GPIOA.DOUT = 0x1234;


这个操作直接将数值写入硬件寄存器。


 



作为右值,可以赋值给一个变量,例如:uint32_t data = GPIOA.DOUT



这个操作直接读出硬件寄存器的值,赋值到data


这些操作都是将sfr按整体操作的。


 



而有趣的操作在第2种:创建sfr临时对象,访问其位数据。


创建临时对象,需要在sfr名字后写括号().


括号后面就可以按访问结构成员的方法(使用“.)访问位数据。


如:GPIOA.DOUT().DOUT1



每个sfr中的位数据,其实都是类对象。


位数据成员的使用,是在其名称后加(),括号内可以给出值。


例如:GPIOA.DOUT().DOUT1(0)


这个操作将读取GPIOADOUT寄存器,将bit10,其它位不动,然后写回DOUT


相当于普通位域方法:


GPIOA->DOUT.DOUT1 = 0;


 



但新的方法并不止这么简单,它可以同时修改多个位数据。



上面的操作,将读取DOUT寄存器(到CPU寄存器),在CPU寄存器中,将bit10,将bit31,将bit121,然后再将CPU寄存器的内容写回到DOUT寄存器。


 


位域的方法的过程是:


1、读sfrCPU寄存器


2、修改其中一个位域


3、写回到sfr.


 



新方法的过程是:


1、读sfrCPU寄存器


2、修改多个位成员


3、写回到sfr.



待续。。。。期待老师下次讲课。。。。。






路过

鸡蛋

鲜花

握手

雷人

评论 (0 个评论)