嵌入式开发设计模式——寄存器访问

嵌入式软件和PC软件一个很大的不同就是存在寄存器访问。

从软件的视角来看,寄存器就是一个Memory地址。现在使用最多的是32位CPU,因此寄存器大多也是32位,当然32位CPU也可以按照8位或64位宽度访问寄存器,但这一点和本文讨论的重点无关。

寄存器的值会作用到硬件上,反之硬件的状态更新也会改变寄存器值,因此读写寄存器必须使用volatile关键字,保证CPU确实访问该地址了。一般会使用类型定义

1
typedef volatile unsigned int REG32;

这里不考虑寄存器读写的一般形式,此设计模式考虑的是对于重复寄存器访问的情况如何降低软件代码的复杂度。

重复寄存器来源于有的硬件存在多个实例的情况,例如一个芯片有多路串口、一个DMA有多个channel等。这些多个实例其功能是一样的,但存在不同的ID。如果对每个实例都单独实现一个软件接口,则会产生大量近似接口(其差异仅是ID或index不同)。

以下寄存器map仅为示意,非真实或完整的寄存器表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef struct {
REG32 uart0_dr; /* UART0 data register */
REG32 uart0_status; /* UART0 status register */
REG32 uart1_dr; /* UART1 data register */
REG32 uart1_status; /* UART0 status register */
REG32 uart2_dr; /* UART2 data register */
REG32 uart2_status; /* UART0 status register */
} uart_reg_t;

#define hd_uart ((uart_reg_t *)UART_ADDRESS)

void uart0_send(u32 value)
{
hd_uart->uart0_dr = value;
}

void uart1_send(u32 value)
{
hd_uart->uart1_dr = value;
}

void uart2_send(u32 value)
{
hd_uart->uart2_dr = value;
}

重新定义寄存器结构体简化接口,使用数组方式访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct {
REG32 dr;
REG32 status;
} uart_inst_t;

typedef struct {
uart_inst_t inst[3];
} uart_reg_t;

static uart_reg_t *const reg_uart = (uart_reg_t *)UART_ADDRESS;

void uart_send(u8 id, u32 value)
{
reg_uart->inst[id].dr = value;
}

另一种寄存器的组织形式及重新定义结构体的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct {
REG32 uart0_dr; /* UART0 data register */
REG32 uart1_dr; /* UART1 data register */
REG32 uart2_dr; /* UART2 data register */
REG32 uart0_status; /* UART0 status register */
REG32 uart1_status; /* UART0 status register */
REG32 uart2_status; /* UART0 status register */
} uart_reg_t;

typedef struct {
REG32 dr[3];
REG32 status[3];
} uart_reg_t;

#define hd_uart ((uart_reg_t *)UART_ADDRESS)

void uart_send(u8 id, u32 value)
{
hd_uart->dr[id] = value;
}

以上设计的基本原则还是DRY(Don’t Repeat Yourself),提取重复代码中变化的部分作为参数传递。