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

日志

新的sfr访问方法

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


新的sfr访问方法,昨天只是大概提了一下。


今天再详细讲讲用法。


 




头文件里预定义了特殊功能寄存器组(外设)的变量。


 


预定义变量在头文件中以extern定义


例如gpio文件中:


extern gpio_t GPIOA; SFR_ADDR(&GPIOA, GPIOA_ADDR);


extern gpio_t GPIOB; SFR_ADDR(&GPIOB, GPIOB_ADDR);


extern gpio_t GPIOC; SFR_ADDR(&GPIOC, GPIOC_ADDR);


extern gpio_t GPIOD; SFR_ADDR(&GPIOD, GPIOD_ADDR);


extern gpio_t GPIOE; SFR_ADDR(&GPIOE, GPIOE_ADDR);


 


这些变量都是gpio_t类型的。


gpio_t类型:


struct gpio_t {


volatile sfr_t<pmd_t> PMD; // GPIO Port I/O Mode Control (GPIO_PMD)


volatile sfr_t<offd_t> OFFD; // GPIO Port Pin OFF Digital Resistor Enable (GPIO_OFFD)


volatile sfr_t<dout_t> DOUT; // GPIO Port Data Output Value (GPIO_DOUT)


volatile sfr_t<dmask_t> DMASK; // GPIO Port Data Output Write Mask (GPIO_DMASK)


const volatile sfr_t<pin_t> PIN; // GPIO Port Pin Value (GPIO_PIN)


volatile sfr_t<dben_t> DBEN; // GPIO Port De-bounce Enable (GPIO_DBEN)


volatile sfr_t<imd_t> IMD; // GPIO Port Interrupt Mode Control (GPIO_IMD)


volatile sfr_t<ien_t> IEN; // GPIO Port Interrupt Enable Control (GPIO_IEN)


volatile sfr_t<isrc_t> ISRC; // GPIO Port Interrupt Trigger Source (GPIO_ISRC)


uint32_t :32;


uint64_t :64;


uint64_t :64;


uint64_t :64;


};


这是一个结构体,成员是各个特殊功能寄存器,这与新唐trm中定义的一致。


 


那么,访问GPIOA外设的DOUT寄存器就应该这样写:


GPIOA.DOUT


这是普通的C语言访问结构体成员的方法。


 


DOUT寄存器的类型是:volatile sfr_t<dout_t>


这是一个c++模板类对象,模板类是sfr_t,模板类的参数是dout_t。我们先不管它,只需知道,这是一个“类”即可。既然是“类”,那么就可能有:成员函(member )和数据成员(data member)。而这个类:sfr_t<dout_t>其中就有:构造函数,析构函数,类型转换操作符重载函数。例如,昨天讲到的,GPIOA.DOUT可以作为表达式的“左值”或“右值”。作为左值时,可以接受一个整形数值,GPIOA.DOUT = 0x1234;


 


我们想想,DOUT是一个类“sfr_t<dout_t>”的对象,相当于结构体,怎么能直接被赋值为一个整形呢?


例如在c语言中:


struct type_t {


  ...


};



struct type_t data;


data = 0x1234


这样赋值,行吗?


不行,“结构体”和“整形”是两个完全不同的类型,而且也没有隐含的转换方法,所以编译会报错:类型不匹配。在c++中,可以为这个“结构体”定义一个赋值操作符的重载函数,来进行这种在c中不合法的操作。我们在类“sfr_t<dout_t>”中,就定义了一个这样的赋值操作符:可以接受一个整形的值。编译时,编译器就可以按我们定义“特殊方法”,来对DOUT赋值了。


 


DOUT作为右值时,可以赋值给一个整形(表达式左值是一个整形变量)。uint32_t data = GPIOA.DOUT;这个按c的语法,也是不允许的,在c++中,我们需要为其定义一个“类型转换操作符”函数。让编译器“知道”如何把这个类“sfr_t<dout_t>”的对象DOUT,转换为一个整形。


 


所以,c++并不神秘,它也是必须按你定义好的套路来走,不会无中生有的。


 


继续


上面说了,DOUT是如何作为“左值”或“右值”的。作为“左值”或“右值”,只能对寄存器进行“整体”访问,并不能访问其中的各个“位”。要访问寄存器中的各个“位”,需要使用“位对象”。


 


下面我们来看dout_t的定义:


union dout_t { // GPIO Port Data Output Value (GPIO_DOUT)


__SFR(dout_t, uint32_t, 0)


sfb_t<dout_t, uint32_t, 0, 1> DOUT0;


sfb_t<dout_t, uint32_t, 1, 1> DOUT1;


sfb_t<dout_t, uint32_t, 2, 1> DOUT2;


sfb_t<dout_t, uint32_t, 3, 1> DOUT3;


sfb_t<dout_t, uint32_t, 4, 1> DOUT4;


sfb_t<dout_t, uint32_t, 5, 1> DOUT5;


sfb_t<dout_t, uint32_t, 6, 1> DOUT6;


sfb_t<dout_t, uint32_t, 7, 1> DOUT7;


sfb_t<dout_t, uint32_t, 8, 1> DOUT8;


sfb_t<dout_t, uint32_t, 9, 1> DOUT9;


sfb_t<dout_t, uint32_t, 10, 1> DOUT10;


sfb_t<dout_t, uint32_t, 11, 1> DOUT11;


sfb_t<dout_t, uint32_t, 12, 1> DOUT12;


sfb_t<dout_t, uint32_t, 13, 1> DOUT13;


sfb_t<dout_t, uint32_t, 14, 1> DOUT14;


sfb_t<dout_t, uint32_t, 15, 1> DOUT15;


};


 


dout_t是一个联合,其中定义了一些“位对象”,这些“位对象”也是模板类的实例。这里可以讲讲“位对象”定义中的两个数据的意义。


 


我们可以看到:sfb_t<dout_t, uint32_t, 11, 1> DOUT11;


其中有两个数值:111


1个数值是该“位对象”在寄存器中的起始位,第2个是位宽度。为了访问到这些“位对象”,先必须访问到这些“位对象”的联合dout_t


 


union是有玄机的。这个在讲实现内幕的时候再讲。


要访问这个union,需要创建这个union的“临时对象”。这个临时对象生成后,存在于CPU寄存器中,生成后,我们就可以访问这个临时对象中的各个“位对象”,置0,置1等等。由于临时对象在CPU寄存器中,那么所有的置0,置1操作都转化为了对CPU寄存器的操作。最后当操作完成时,临时对象将会“析构”,而我们在其析构函数中,定义了一个操作:将CPU寄存器值写回到外设寄存器。


 


而什么时候算是“操作完成”呢?


答案是:“;”,分号,语句结束。 就是说:临时对象的生命期,就只在本语句。


 


 



转回来,我们如何创建这个“临时对象”呢?



为此,我们在DOUT的模板类“sfr_t<dout_t>”中,定义一个重载操作符“()”。


在重载的操作符中,我们创建了一个union的临时对象。



具体的程序在sfr文件的164


__INLINE SFR operator ()() { return SFR(val); }


__INLINE SFR operator ()() const { return SFR(val); }


__INLINE SFR operator ()() volatile { return SFR(val); }


__INLINE SFR operator ()() const volatile { return SFR(val); }


__INLINE SFR operator ()(decltype(SFR::val) v) { return SFR(val, v); }


__INLINE SFR operator ()(decltype(SFR::val) v) const { return SFR(val, v); }


__INLINE SFR operator ()(decltype(SFR::val) v) volatile { return SFR(val, v); }


__INLINE SFR operator ()(decltype(SFR::val) v) const volatile { return SFR(val, v); }


 


 


这个现在也不讲,等讲内幕时吧。


 


 


我们只需知道,在特殊功能寄存器对象“GPIOA.DOUT”后使用()即可创建临时对象。



()的使用可以有两种情况:


1、空的( )


2( )内带一个数值。


空的()意思是:临时对象的初值,从特殊功能寄存器读取。


带值的():意思是:临时对象的初值,有数值提供。


 


既然是“赋值”,()就不能为空了。


GPIOA.DOUT().DOUT2(0);


同时,位变量也提供了 重载操作符,用来对其赋值。


GPIOA.DOUT().DOUT2 = 0;


这个语句与上一个语句,完全是一样的。


 


 



考考大家一个问题,为什么我们要为一个目的,提供“两种”方法?


为了连续赋值



那为何还要GPIOA.DOUT().DOUT2 = 0;


为了意义上的完整性,了解语义


毕竟,这个DOUT2,名义上是个对象啊,没有“左值”“右值”的操作,好像不完整。有点心理作用。等我哪天想通了,把 删了。呵呵


 


 



刚才说到,用()可以实现连续的“位对象”访问。


GPIOA.DOUT().DOUT2(0).DOUT5(1).DOUT10(0);


而赋值操作符,就没有这个方便性了。例如:GPIOA.DOUT().DOUT2 = 0.DOUT5 = 1;


编译通不过的。


 



后面的.为啥还是在这个临时对象内,有这种语法


问得好!!


这也是内幕。


这个连续的语法,和上面提到的“union”,一起构成了最大的内幕。


 


 


内幕的请听下回分解


 


 



好,刚才说到:GPIOA.DOUT().DOUT2 = 0.DOUT5 = 1;


这种形式的连续赋值,不能通过编译


 


 


GPIOA.DOUT(0).DOUT2(1).DOUT5(1).DOUT10(1);


这句什么意思?


 



GPIOA.DOUT(0).DOUT2(1).DOUT5(1).DOUT10(1);


我给出c等效语法:


GPIOA.DOUT = (1 << 2) | (1 << 5) | (1 << 10);


 



GPIOA.DOUT().DOUT2(1).DOUT5(1).DOUT10(1);


的等效语法:


GPIOA.DOUT |= (1 << 2) | (1 << 5) | (1 << 10);


 



GPIOA.DOUT().DOUT2(1).DOUT5(0).DOUT10(1);


的等效语法:


GPIOA.DOUT = (GPIOA.DOUT | (1 << 2) | (1 << 10)) & ~(1 << 5);


 


 



~(1 << 5)与(0<<5)有区别吧


~(1 << 5) = ~0xffffffef


(0 << 5) = 0



好,基本的用法是这样了。


 


 


例如:读PIN时,我希望读2个单独的位。怎么办?



PIN3 读到变量 data1, PIN5 读到 data2


容易想到的方法是:


int data1 = GPIOA.PIN().PIN3;


int data2 = GPIOA.PIN().PIN5;


 


 



但这样做会实际读两次PIN寄存器。能不能只读1次?


 


全部读出来


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


是否这样:


uint32_t pin = GPIOA.PIN;


int data1 = (pin >> 3) & 1;


int data2 = (pin >> 5) & 1;


效率是高了,但安全性呢?


 


 



搞一个位域,是吧?


你还是使用了立即数或宏。


用位域,那就没有效率,还是会读两次。


 


 


 



那是用啥啊??


一个变通的办法是读出整个PIN,然后把这个值强制为位域的那个结构体。


例如:


int pin = GPIOA.PIN;


int data1 = ((PIN_T) pin).PIN3;


...


 


 


各位可以试试。


两种写法:


1int data1 = ((PIN_T) pin).PIN3;


2int data1 = ((PIN_T*) &pin)->PIN3; 


下面的肯定可以通过编译,上面的大家自己试试。 


 


 


 



 我给出我的方法:


auto pin = GPIOA.PIN();


int data1 = pin.PIN3;


int data2 = pin.PIN5;


这个是真正的高效方法。


 


 


使用了一个自动变量(auto),将PIN的临时对象,显式化了。相当于给那个“临时对象”安上了一个名字。安上名字后,这个对象就不在是临时对象了,它的生命期,也不会只限于创建临时对象的那条语句了。它的生命期,会直到你不使用它时,为止。


 


 


 



 auto pin = GPIOA->PIN;


    int data1 = pin.PIN3;


    int data2 = pin.PIN5; 编译不过


-----------


你需要使用这个“环境”才行。


#include "numicro/sfr/gpio",gcc加上我写的一整套相关头文件。具体你看看菜农的lookdemos.zip中的例程。但新的c++标准:c++11(原称为c++0x),将其语义改变了


 


 


 



GPIOA.DOUT().DOUT2(0).DOUT5(1).DOUT10(0);()可以实现连续的位对象访问
是因为这些位对象本身就是dout_t类型的吧?和DOUT是同类型的uino



这些位对象是dout_t的成员。
相当于组合,用一个不恰当的比喻,dout_tparent,而DOUTx对象是child


关于那个sfr,我还有一些比较遗憾的地方。就是那个__SFR宏,目前没有替代的办法。
主要问题在于c++union的限制太多。不能继承,不能派生。并且其成员不能有构造析构函数































































路过

鸡蛋

鲜花

握手

雷人

评论 (0 个评论)