CH32V003在MRS中的头文件定义

发布时间 2023-08-17 22:32:52作者: fxzq

在MRS的头文件core_riscv.h中有如下的定义。 

#define     __I     volatile const
#define     __O     volatile
#define     __IO    volatile 

上述定义,通过define语句把__IO等效为volatile,把__O等效为volatile,把__I等效为volatile const。一般来说宏定义都用大写形式,但因为这里用的字母比较少(只有I或O或IO),所以在其前面添加双下划线来进行区分,这样做可以有效避免命名冲突问题。volatile本身是一个关键字,表示其后面定义的变量不让编译器进行优化,即每次读取或者修改值的时候,都必须重新从内存或者寄存器中读取或者修改。例如在单片机开发中,经常会用到软件延时,但若想让软件延时不被编译器优化掉,就必须在变量定义前加上关键字volatile,如“for(volatile unsigned int k=0;k<60000;k++);”。volatile const则表示其后面定义的变量为只读,比如用它来定义一个只读的状态寄存器。定义为volatile是因为它的值可能会被硬件意想不到地改变,而定义为const则是因为程序不应该试图去修改它的值。通俗的说,就是它定义的是一个“只读变量”而不是常量,它的值是由硬件来改变的,不能通过程序写入来改变。
归纳一下:
__I:定义输入口。既然是输入,那么寄存器的值就随时会被外部修改,所以不能对它进行优化,每次都必须从寄存器中读取。也不能写(只读),否则就不是输入而是输出了。
__O:定义输出口,也不能对它进行优化,不然端口连续两次输出相同的值,编译器就会认为没有变化,而忽略后那一次输出,假如外部在两次输出中间修改了值,那就会影响输出的正确性。可写,否则就不能称为输出了。
__IO:定义输入输出口,也不能对它进行优化,原因同上。可读可写。 

在头文件ch32v00x.h中,使用结构体的形式封装了GPIO端口所有的寄存器,代码如下所示。

typedef struct
{
    __IO uint32_t CFGLR;
    __IO uint32_t CFGHR;
    __IO uint32_t INDR;
    __IO uint32_t OUTDR;
    __IO uint32_t BSHR;
    __IO uint32_t BCR;
    __IO uint32_t LCKR;
} GPIO_TypeDef;

在以同样的方式,封装了时钟配置相关的寄存器,代码如下所示。

typedef struct
{
    __IO uint32_t CTLR;
    __IO uint32_t CFGR0;
    __IO uint32_t INTR;
    __IO uint32_t APB2PRSTR;
    __IO uint32_t APB1PRSTR;
    __IO uint32_t AHBPCENR;
    __IO uint32_t APB2PCENR;
    __IO uint32_t APB1PCENR;
    __IO uint32_t RESERVED0;
    __IO uint32_t RSTSCKR;
} RCC_TypeDef;

封装完成后,还需要把它与具体的物理地址关联起来。下图给出了CH32V003的整个32位寻址空间及其相关设备地址的分配情况。 

从大体上来看,从0x00000000到0x20000000的空间分配给了FLASH_ROM,在其中又进行了细分,这里先不作讨论。从0x20000000到0x20000800的空间分配给了RAM,共计2KB。从0x40000000到0x50050400的空间分配给了外设(Peripherals)。从0xE0000000到0xE0100000的空间被分配给了内核私有外设(Core Private Peripherals)它包含了属于内核的一些外设,如SysTick定时器,全局中断系统等等。除了以上分配的地址空间之外,其余地址均为保留地址。在外设空间Peripherals中,被细分出了很多设备地址,在MRS中把它们归成3个大类,即APB1、APB2和AHB。 APB1的基址为0x40000000,APB2的基址为0x40010000(即Peripherals的基址+0x10000的偏移量),AHB的基址为0x40020000(即Peripherals的基址+0x20000的偏移量)。一般来说,APB属于低速总线,AHB属于高速总线。从上图中可见,RCC被划分到AHB中,而GPIO则被划分到APB2中。

对照上面的地址分布,就很容易弄清外设在头文件中如与地址进行关联了。在ch32v00x.h中,RCC的地址关联通过以下代码实现(提取整理过)。 

#define PERIPH_BASE                             ((uint32_t)0x40000000)
#define AHBPERIPH_BASE                          (PERIPH_BASE + 0x20000)
#define RCC_BASE                                (AHBPERIPH_BASE + 0x1000)
#define RCC                                     ((RCC_TypeDef *)RCC_BASE) 

上述代码中,第1行定义了Peripherals外设的基地址,第2行定义了AHB的基地址,第3行定义了RCC的基地址,第4行对RCC进行地址映射(地址关联)。通过define语句来给前面定义的RCC_TypeDef结构体指针取个“别名”(即RCC),这样RCC就成为了这个结构体指针类型,通过“RCC->”这样的方式就可以引用它的内部成员变量(即时钟配置模块的各个寄存器)了。这样一来,就把芯片底层的地址用高级语言名称的形式来表示,既直观又方便易用。

同理,GPIO的地址关联通过以下代码实现(提取整理过)。 

#define PERIPH_BASE                             ((uint32_t)0x40000000)
#define APB1PERIPH_BASE                         (PERIPH_BASE)
#define APB2PERIPH_BASE                         (PERIPH_BASE + 0x10000)
#define GPIOA_BASE                              (APB2PERIPH_BASE + 0x0800)
#define GPIOC_BASE                              (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE                              (APB2PERIPH_BASE + 0x1400)
#define GPIOA                                   ((GPIO_TypeDef *)GPIOA_BASE)
#define GPIOC                                   ((GPIO_TypeDef *)GPIOC_BASE)
#define GPIOD                                   ((GPIO_TypeDef *)GPIOD_BASE)

上述代码中,第1行定义了Peripherals外设的基地址,第2行定义了APB1的基地址,第3行定义了APB2的基地址,第4~6行分别定义了GPIOA、GPIOC和GPIOD的基地址,第7~9行对3个GPIO端口进行地址映射(地址关联)。

通过以上的结构体封装及地址映射,在C语言中就可以很方便的引用设备中的寄存器了。比如要选择外部晶体振荡,执行语句“RCC->CFGR0 |= 0x00010000;”就可以了。上面只针对RCC及GPIO这两个模块的结构体进行了分析,没有提及其他模块的结构体,但它们所采用的方法是完全一样的,可参考上面的分析方法来研究其他模块结构体的定义,就不一一赘述了。