基础工作
type.h
利用编译器内置的宏,可以精确的生成有无符号的 8、16、32等数据类型,这样就不用包含别人的库了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #ifndef __TYPES_H__ #define __TYPES_H__
typedef __INT8_TYPE__ int8_t; typedef __INT16_TYPE__ int16_t; typedef __INT32_TYPE__ int32_t; typedef __INT64_TYPE__ int64_t;
typedef __UINT8_TYPE__ uint8_t; typedef __UINT16_TYPE__ uint16_t; typedef __UINT32_TYPE__ uint32_t; typedef __UINT64_TYPE__ uint64_t;
typedef __SIZE_TYPE__ size_t; typedef __UINTPTR_TYPE__ uintptr_t; typedef __INTPTR_TYPE__ intptr_t;
typedef int8_t boolean; #define true 1 #define false 0
#define NULL ((void *)0)
#endif
|
uart.h
由于打印字符在指令层面上就是给某一个地址写数据。如果要是用 uart 来实现字符的输出,那么其实就是给 uart 控制器的接受缓冲地址写数据。
当时 uart 在使用之前,我们需要初始化它,这样才能使用它。于是我们对 uart 的一系列地址进行读写,这就是所谓的初始化。
FCR - FIFO 控制寄存器 (偏移 0x02,只写)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ┌───┬───┬───┬───┬───┬───┬───┬───┐ │ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │ ├───┴───┼───┴───┼───┼───┼───┼───┤ │ RCVR │ saved │DMA│TXSR│RXSR│FIFO│ │Trigger│ │Mode│ │ │ EN │ └───────┴───────┴───┴───┴───┴───┘
┌─────┬──────────────┬────────────────────────────────────────────┐ │ bit │ name │ info │ ├─────┼──────────────┼────────────────────────────────────────────┤ │ 0 │ FIFO_EN │ 1=enable FIFO │ ├─────┼──────────────┼────────────────────────────────────────────┤ │ 1 │ RXSR │ 1=reset recieve FIFO │ ├─────┼──────────────┼────────────────────────────────────────────┤ │ 2 │ TXSR │ 1=reset trans FIFO │ ├─────┼──────────────┼────────────────────────────────────────────┤ │ 7:6 │ RCVR Trigger │ trigger level (00=1byte, 01=4, 10=8, 11=14)│ └─────┴──────────────┴────────────────────────────────────────────┘
|
启动的时候,需要使能 FIFO,以及复位发送 FIFO、接收 FIFO。
LCR - 线控制寄存器 (偏移 0x03)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ┌───┬───┬───┬───┬───┬───┬───┬───┐ │ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │ ├───┼───┼───┼───┼───┼───┴───┴───┤ │DLAB│Break│Stick│EPS│PEN│ WLS │ └───┴───┴───┴───┴───┴───────────┘ ┌─────┬──────┬───────────────────────────────────────┐ │ 位 │ 名称 │ 说明 │ ├─────┼──────┼───────────────────────────────────────┤ │ 1:0 │ WLS │ 字长 (00=5位, 01=6位, 10=7位, 11=8位) │ ├─────┼──────┼───────────────────────────────────────┤ │ 2 │ STB │ 停止位 (0=1位, 1=2位) │ ├─────┼──────┼───────────────────────────────────────┤ │ 3 │ PEN │ 校验使能 │ ├─────┼──────┼───────────────────────────────────────┤ │ 7 │ DLAB │ 除数锁存访问位 (1=访问波特率寄存器) │ └─────┴──────┴───────────────────────────────────────┘
|
设置波特率的时候,需要将 LCR 的 DLAB 位拉高,拉高后才能写入 divisor。
难点机制: 16550 UART 存在寄存器地址复用。
- DLL (低8位除数) 和 RBR/THR (数据寄存器) 共用同一个地址 0x00。
- DLM (高8位除数) 和 IER (中断使能) 共用同一个地址 0x01。
DLAB (Divisor Latch Access Bit): UART_LCR 的第 7 位。
- 当 DLAB = 1 时:访问 0x00 和 0x01 是在读写除数;
- 当 DLAB = 0 时:访问 0x00 和 0x01 是在读写数据/中断。
操作流程:
- 先写 UART_LCR = 0x80,把开关拨到“分频”档;
- 写 DLM (高位);
- 写 DLL (低位)。
设置完 divisor 后,就可以把 UART_LCR 的 DLAB 关了,并且可以设置数据传送模式,比如 8N1。
关闭 Modem 控制以及中断控制
1 2
| #define UART_MCR 0x04 #define UART_IER 0x01
|
正式初始化
这是一个非常规范的 UART 初始化流程:
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
| #define UART_REG(offset) (*(volatile uint8_t *)(UART_BASE + (offset))) int bsp_uart_init(uint8_t uart_id, uint32_t baudrate) { uint16_t divisor;
divisor = UART_CLK / (16 * baudrate);
UART_REG(UART_FCR) = FCR_FIFO_EN | FCR_RXSR | FCR_TXSR;
UART_REG(UART_LCR) = LCR_DLAB;
UART_REG(UART_DLM) = (divisor >> 8) & 0xFF; UART_REG(UART_DLL) = divisor & 0xFF;
UART_REG(UART_LCR) = LCR_8N1;
UART_REG(UART_MCR) = 0x00;
return 0; }
|
仿真系统打印字符
打印出信息,是用来 debug 的重要途径。因此得准备两套打印机制:
- 写到某一个缓冲区,这个缓冲区未来可供某些算法处理后发送到 framebuffer 显示到 HDMI;
- 直接写到 uart,通过 uart 串口屏直接显示信息。
由于现在在初始阶段,目前只能先在 uart 上输出,而且在仿真系统中,只能在通过数据截获来输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void uart_read_handling() { if (top->apb_uart_wvalid && !last_apb_uart_wvalid) { uint32_t uart_addr = top->apb_uart_awaddr; uint8_t uart_char = top->apb_uart_wdata & 0xFF; if (uart_addr == 0x1fe00000) { if ((uart_char >= 32 && uart_char <= 126) || uart_char == '\n' || uart_char == '\r' || uart_char == '\t') {
putchar(uart_char); fflush(stdout); } } }
|
写一个测试函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <myprintf.h> #include <types.h> #include <uart.h>
int main(void) { bsp_uart_init(0, BAUDRATE);
myprintf("Hello, LoongArch!\n"); myprintf("Number: %d\n", 123456789); myprintf("Negative: %d\n", -987654321); myprintf("Hex: 0x%08x\n", 0xDEADBEEF); myprintf("Pointer: %p\n", (void *)0x1FE001E0); myprintf("String: %s\n", "LoongArch32"); myprintf("Char: %c\n", 'X'); myprintf("Done!\n"); while (1) ; return 0; }
|
myprintf()其实是包装了 bsp_uart_puts() 的一个模仿 printf() 的函数,其中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void bsp_uart_putc(uint8_t uart_id, char ch) { #ifndef SIMULATION while (!(UART_REG(UART_LSR) & LSR_THRE)) ; #endif UART_REG(UART_THR) = ch; } char bsp_uart_getc(uint8_t uart_id) { while (!(UART_REG(UART_LSR) & LSR_DR)) ; return UART_REG(UART_RBR); } void bsp_uart_puts(uint8_t uart_id, const char *str) { while (*str) { bsp_uart_putc(uart_id, *str++); } }
|
测试一下:
1 2 3 4 5 6 7 8 9 10
| Hello, LoongArch! Number: 123456789 Negative: -987654321 Hex: 0xdeadbeef Pointer: 0x1fe001e0 String: LoongArch32 Char: X Done!
[SIM] Received signal 2, stopping...
|
嗯嗯,非常不错,经历了漫长的调 Bug,总算测试成功了!