最近对嵌入式中的地址有了更深入的认识,简单记录一下。
以STM32F103为例,在寄存器模板中我们都知道只要对寄存器简单地进行赋值就可以完成流水灯等操作,那么这些寄存器是怎么封装的呢?
首先,先通过一串的宏定义确定了硬件的基地址,以STM32F103中的GPIOB为例:
在stm32f10x.h这个头文件中首先定义:
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define PERIPH_BASE ((uint32_t)0x40000000)
这样,GPIOB_BASE的基地址就是0x40010C00,这和手册中记录的地址是一致的:
接着,是寄存器的定义。其实,向寄存器中写值从本质上说就是往那个地址上赋值,只不过为了方便理解,程序员们把每个不同的地址给抽象成了不同的寄存器,还是以GPIOB为例,手册中有:
于是偏移量为000h开始的32位地址,这一块被定义为GPIOx_CRL,偏移量为004h开始的32位地址,这一块被定义位GPIOx_CRH,以此类推。
那么有了手册,我们在Keil中是怎么操作这些寄存器的呢?它们又是怎么定义的呢?回到软件,在stm32f10x_h中,有这样的宏定义:
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
GPIOB_BASE是地址,那么它前面的那个一串是什么呢?继续找:
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
原来这是一个结构体,这个结构体中的每个寄存器都是按照手册中的顺序排列的,每一个寄存器都是32位的,这样你就可以开始你熟悉的操作了,比如:
GPIOB->CRL&=0XFF0FFFFF;
GPIOB->CRL|=0X00300000;
GPIOB->ODR|=1<<5;
那么可能又有人想问了,手册中的寄存器的顺序是定死的吗?当然不是了,这是根据做芯片的时候设计人员的喜好决定的,比如拿我在FPGA上移植的一个Cortex-M3内核的工程为例,我在给内核添加UART接口时,我这么定义了一串寄存器:
if (PADDR[11:5] == 7'h00) begin
case (PADDR[4:2])
3'h0: read_mux_byte0 = reg_rx_buf;
3'h1: read_mux_byte0 = {{4{1'b0}},uart_status};
3'h2: read_mux_byte0 = {{1{1'b0}},reg_ctrl};
3'h3: read_mux_byte0 = {{4{1'b0}},intr_state};
3'h4: read_mux_byte0 = reg_baud_div[7:0];
endcase
end
可以看出,不同的地址我给命名了不同的寄存器。
那么我在Keil中,就将这么定义这些寄存器:
typedef struct
{
__IO uint32_t DATA; /*!< Offset: 0x000 Data Register (R/W) */
__IO uint32_t STATE; /*!< Offset: 0x004 Status Register (R/W) */
__IO uint32_t CTRL; /*!< Offset: 0x008 Control Register (R/W) */
union {
__I uint32_t INTSTATUS; /*!< Offset: 0x00C Interrupt Status Register (R/ ) */
__O uint32_t INTCLEAR; /*!< Offset: 0x00C Interrupt Clear Register ( /W) */
};
__IO uint32_t BAUDDIV; /*!< Offset: 0x010 Baudrate Divider Register (R/W) */
} UART_TypeDef;
除这以外,当初还有一个概念始终困扰着我,就是在I2C通信中的地址。
在I2C通信的写控制中,首先写的是I2C器件的地址,这个地址是I2C器件挂在I2C总线上的地址,然后发送的寄存器地址,是寄存器在I2C器件的位置。