|||
用位运算,操作对象是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)
这个操作将读取GPIOA的DOUT寄存器,将bit1置0,其它位不动,然后写回DOUT。
相当于普通位域方法:
GPIOA->DOUT.DOUT1 = 0;
但新的方法并不止这么简单,它可以同时修改多个位数据。
上面的操作,将读取DOUT寄存器(到CPU寄存器),在CPU寄存器中,将bit1置0,将bit3置1,将bit12置1,然后再将CPU寄存器的内容写回到DOUT寄存器。
位域的方法的过程是:
1、读sfr到CPU寄存器
2、修改其中一个位域
3、写回到sfr.
新方法的过程是:
1、读sfr到CPU寄存器
2、修改多个位成员
3、写回到sfr.
待续。。。。期待老师下次讲课。。。。。