Exynos4412特殊功能寄存器的封装

作者:秦老师,华清远见教育科技集团讲师。

特殊功能寄存器英文缩写为SFR,是Special Function Register的缩写。特殊功能寄存器是芯片功能实现的载体,可以理解为芯片厂商留给嵌入式开发人员的控制接口,用于控制片内外设,比如GPIO、UART、ADC等等。每个片内外设都有对应的特殊寄存器,用于存放相应功能部件的控制命令,数据或者状态。

对于特殊功能寄存器的封装是每个嵌入式工程师都应该掌握的。那如何封装芯片的特殊控制寄存器呢,我们以Exynos4412的GPIO片内外设为例说明。

(1)查看Exynos4412芯片的地址映射表

查看Exynos4412芯片手册的第二章2 Memory Map(30页)地址映射表,我们可以看到Exynos4412的特殊功能寄存器绝大部分都放到了0x1000_0000到0x1400_0000的地址空间。

(2)查看GPIO模块的寄存器描述表

查看芯片手册,第四章4 General Purpose Input/Output (GPIO) Control,4.3节(41页)寄存器描述,有完整的GPIO模块的寄存器描述表。我们可以得到GPIO模块的基地址和每个寄存器相对基地址的偏移量。

GPIO模块的基地址是:0x1140_0000
        GPA0组的配置寄存器GPA0CON的地址是: 基地址 + 偏移量
        0x11400000 + 0x0000 = 0x11400000

(3)封装寄存器的第一种方式是直接一对一封装

例如:

#define GPA0CON (*(volatile unsigned int *)0x11400000)

分析:

这是一个宏定义,宏定义在预处理阶段进行直接替换,为了方便理解,我们可以先把volatile去掉,关键是理解(*(unsigned int *)0x11400000)。

0x11400000 是一个16进制的数据,前面用(unsigned int *)修饰,表示把0x11400000强制转换成了一个指向unsigned int型变量的指针。简单的说,(unsigned int *)0x11400000指向了内存中从0x11400000开始的连续的4个字节空间。(0x11400000—0x11400003)。

(*(unsigned int *)0x11400000)是在(unsigned int *)0x11400000又加了一个指针运算符*,表示取内存单元里的数据。

volatile是C语言的32个关键字之一,是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件中断或者线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,每次读取这个变量的值都是要从内存单元里读取,而不是直接使用放在高速缓存或寄存器里的备份。从而可以提供对特殊地址的稳定访问。

使用:

我们像unsigned int变量一样访问特殊功能寄存器。

GPA0CON = (GPA0CON & ~(0xf<<4))| 1<<4; //将GPA0_1引脚设置为输出功能

(4)封装寄存器的第二种方式是结构体封装

例如:

/* GPA0 */
        typedef struct {
                unsigned int CON;
                unsigned int DAT;
                unsigned int PUD;
                unsigned int DRV;
                unsigned int CONPDN;
                unsigned int PUDPDN;
        }gpa0;
        #define GPA0 (* (volatile gpa0 *)0x11400000)

分析:

typedef 关键字声明了名为gpa0的结构体类型,结构体内又定义了 6个unsigned int类型的变量。unsigned int 类型变量为 32 位,在内存内存空间 中占4 个字节。

#define GPA0 (* (volatile gpa0 *)0x11400000)声明了一个gpa0类型结构体的宏,结构体名是结构体首成员的地址,GPA0这个结构体的首成员CON地址为0x11400000,占4个字节,在c语言中结构体内的成员变量是连续的,那么GPA0结构体的第二个成员DAT得地址为:0x11400000+0x04 = 0x11400004。这个 0x04 偏移量,正是GPA0DAT寄存器相对于GPIO基地址的偏移地址。

结构体内其它成员的偏移量,也和相应的寄存器偏移地址相符。因此,我们匹配了结构体的首地址,就可以确定各寄存器的具体地址了。

使用:

我们用访问结构体变量成员的方式,访问寄存器。

GPA0.CON = (GPA0.CON & ~(0xf<<4))| 1<<4; //将GPA0_1引脚设置为输出功能

(5)使用集成开发环境的芯片寄存器封装

嵌入式开发中我们把常用的寄存器写到一个头文件中,每次使用的时候直接包含就可以。也有很多集成开发环境会提供这样的芯片特殊寄存器头文件,比如在keil中创建arm7三星s3c2410芯片相关的工程,点击鼠标右键可以添#include <S3C2410A.H>头文件。但不是所有芯片都支持。