新的sfr访问方法,昨天只是大概提了一下。
今天再详细讲讲用法。
头文件里预定义了特殊功能寄存器组(外设)的变量。
这些头文件是:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/ksohtml/wps_clip_image-8886.png
预定义变量在头文件中以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 function)和数据成员(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;
其中有两个数值:11和1。
第1个数值是该“位对象”在寄存器中的起始位,第2个是位宽度。为了访问到这些“位对象”,先必须访问到这些“位对象”的联合dout_t
union是有玄机的。这个在讲实现内幕的时候再讲。
要访问这个union,需要创建这个union的“临时对象”。这个临时对象生成后,存在于CPU寄存器中,生成后,我们就可以访问这个临时对象中的各个“位对象”,置0,置1等等。由于临时对象在CPU寄存器中,那么所有的置0,置1操作都转化为了对CPU寄存器的操作。最后当操作完成时,临时对象将会“析构”,而我们在其析构函数中,定义了一个操作:将CPU寄存器值写回到外设寄存器。
而什么时候算是“操作完成”呢?
答案是:“;”,分号,语句结束。 就是说:临时对象的生命期,就只在本语句。
|
|