Bootstrap

1、操控UART寄存器实现输出功能

        在这一章里,重点需要了解如何通过寄存器操控GPIO、UART,使得MCU通过UART总线输出字符,实现打印功能。

一、GPIO相关寄存器

        如果开发板上引脚资源够用的话,并不需要额外配置GPIO的复用功能。但如果想要复用GPIO为某一路UART的功能引脚,就需要了解如何操作GPIO的相关寄存器了。

        GPIO 寄存器通常属于外设寄存器的一部分,其在芯片的数据手册中会详细列出对应的内存映射地址。比如,GPIO 寄存器组的基地址可能为 0x10012000。在实际操作中,通过该基地址及各个寄存器的偏移量,可以访问和控制对应的硬件功能。这些偏移量通常以特定的单位(如字节或字)表示,允许程序通过对特定寄存器地址的读写操作来控制 GPIO 引脚的状态、模式、配置等功能。

#define GPIOA_BASE 0x10012000

         在查询到 GPIO 外设的基地址后,还需要了解各个寄存器的具体偏移地址。例如,用于控制 GPIO 引脚输入输出方向的寄存器通常位于偏移地址 0x00 处,用于读取 GPIO 引脚输入电平的寄存器通常位于偏移地址 0x04 处,而配置引脚复用功能的寄存器可能位于偏移地址 0x1C 处。通过了解这些偏移地址,可以定义适当的宏来访问和修改相应寄存器的值。通过基地址与偏移地址的组合,能够精确地对对应寄存器进行读写操作,以实现 GPIO 引脚的配置与控制。

#define GPIOA_REG(reg) ((volatile uint32_t *)(GPIOA_BASE + reg))

        其中volatile关键字主要用于告知编译器某个变量的值可能会被外部因素改变,因此不应对该变量进行优化。确保每次访问时都是直接从内存中读取变量的值。

        因此在配置复用功能时,只需要对应的修改0x1C处 管理GPIO复用功能的寄存器即可。

*(GPIOA_REG(MUXCFG)) &= (~((uint32_t)3<<gpio_mux_cfg_shift[gpio_pin]));
*(GPIOA_REG(MUXCFG)) |= (af_fun[gpio_pin]<<gpio_mux_cfg_shift[gpio_pin]);

 二、UART相关寄存器

        一般来说,MCU 通常会配备多个 UART 外设,每个 UART 外设都拥有独立的寄存器组。根据实际需求,你需要查阅芯片文档,确定对应 UART 外设的寄存器基地址,以便进行相应的配置和操作。

#define UART1_BASE 0x10023000

         UART有几个必须要配置的参数,这是收发端能够正常通信的关键。包括波特率(每秒发送bit数)、数据位、停止位、奇偶校验位。这4个参数需要收发端一致,否则无法正常收发数据。在配置波特率时,涉及到三个寄存器,分别是DLL(偏移地址:0x00)、DLM(偏移地址:0x04)、LCR(偏移地址:0x0C)。

        根据数据手册得,我们需要先将LCR寄存器第7位置1,从而允许对DLL和DLM寄存器的操控。 随后根据波特率计算公式得到DLL、DLM寄存器的值。其中DLL存储除数低8位,DLM存储除数高8位。

uint32_t mcu_clk = 2000000;
uint32_t divisor = mcu_clk / baud - 1;
*UART_REG(LCR)= 0x80;//使能操控DLL寄存器
*UART_REG(DLM) = (divisor >> 8 )& 0xFF;
*UART_REG(DLL) = (divisor) & 0xFF;

        对于数据位、停止位、奇偶校验位的操作如下:

    uint32_t stopbit_val = (stopbit == 1) ? 0x0 : 0x1;
    uint32_t parity_val = (parity_en==1) ? 1 : 0;
    *UART_REG(LCR) = 0X03 | (stopbit_val << 2);
    *UART_REG(LCR) |= (parity_val << 3);

三、收发字符 

        UART收发字符也需要操控相关寄存器,即RBR(偏移地址:0x00)、THR(偏移地址:0x00)、LSR(偏移地址:0x14)。可以看到RBR、THR、和上述DLL寄存器共用一个偏移地址。MCU通过LCR寄存器的第7位判断具体操纵的哪个寄存器。为1则操纵DLL配置波特率。为0则操纵RBR、THR控制数据收发。

最简单的发送方式如下,即 往RBR寄存器里写入字符:

void Uart_putc(char c)
{
    *(UART_REG(THR)) = (uint8_t)c;
    return;
}
void Uart_puts(char *s)
{
    while (*s) {
        Uart_putc(*s++);
    }
}

最简单的回显输入字符的过程如下:首先从接收缓冲区读取输入字符,然后将该字符发送到发送缓冲区。在此过程中,可以通过读取 LSR(Line Status Register) 寄存器的 第 0 位(Receiver Data Ready, RDR)来判断是否有新字符已被接收。只有在接收到字符后,才能继续进行后续的处理和发送。

uint32_t Uart_CheckLSR()
{

    return *(UART_REG(LSR)) & 1;
}    
while (1)
{
    while (Uart_CheckLSR() == 0);
    Uart_putc(Uart_getc());
}
;