5、Log输出

这一小节中,要为我们的程序增加log输出能力。
最终是依赖于板子的串口来输出的

5.1 UART 硬件

我们的硬件平台是MPS2,使用的UART IP是 APB UART

参考:
DDI0479D_m_class_processor_system_r1p1_trm.pdf
DAI0385D_cortex_m3_on_v2m_mps2.pdf
../_images/uart1.PNG
../_images/uart2.PNG
../_images/uart3.PNG

我们关心得几个寄存器,设置波特率(BAUDDIV),使能控制(CTRL),数据输出(DATA)

../_images/uart4.PNG

我们使用UART0,基地址为0x40004000

5.2 UART 软件实现

/*
* Copyright (c) 2021-2031, Jinping Wu. All rights reserved.
*
* SPDX-License-Identifier: MIT
*/

#include <apbuart.h>

typedef struct UART_t {
        volatile unsigned int DATA;
        volatile unsigned int STATE;
        volatile unsigned int CTRL;
        volatile unsigned int INTSTATUS;
        volatile unsigned int BAUDDIV;
} UART_t;

#define UART0_ADDR ((UART_t *)(0x40004000))

#define UART_CTRL_TX_EN (1 << 0)
#define UART_CTRL_RX_EN (1 << 1)

/**
* Initializes the UART
*/
void uart_init(void)
{
        UART0_ADDR->BAUDDIV = 16;
        UART0_ADDR->CTRL = UART_CTRL_TX_EN;
}

/**
* Output a char to the UART TX
*/
void uart_putc(char c)
{
        UART0_ADDR->DATA =  c;
}

Note

代码很简单,初始化设置波特率,使能,输出字符通过往输出寄存器写内容来实现。之后在main里面先调用uart_init,再调用uart_putc(‘h’)就能在qemu屏幕上输出’h’字符了。

5.3 Console 软件实现

之前的代码只能一个个输出字符,而实际使用中,我们需要输出字符串,打印变量值等,一般这部分功能都是在C库中实现的。
但是我们没有C库,所以我们需要自己写
/*
* Copyright (c) 2021-2031, Jinping Wu. All rights reserved.
*
* SPDX-License-Identifier: MIT
*/

#include <apbuart.h>

#define MAX_DIGIT_BITS 11  /* 8 char for number, 2 for '0x' and 1 for '\n' */
static const char HEX_TABLE[] = {'0', '1', '2', '3', '4', '5', '6', '7',
                                                                '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

void bm_printf(char *s)
{
        while (*s) {
                uart_putc(*s);
                s++;
        }
}

static void to_hex(unsigned int value, char msg[])
{
        int i = MAX_DIGIT_BITS - 1;

        msg[i--] = '\n';
        for (; i > 1; i--, value >>= 4)
                msg[i] = HEX_TABLE[value & 0xF];
        msg[i--] = 'x';
        msg[i--] = '0';
}

static void bm_printf_hex(unsigned int value)
{
        char value_str[MAX_DIGIT_BITS];

        for (int i = 0; i < MAX_DIGIT_BITS; i++)
                value_str[i] = 0;

        to_hex(value, value_str);
        bm_printf(value_str);
}

void bm_printf_value(char *s, unsigned int value)
{
        bm_printf(s);
        bm_printf_hex(value);
}

void console_init(void)
{
        uart_init();
}
提供三个函数供外部调用:
console_init 初始化串口,开机时调用
bm_printf 打印字符串
bm_printf_value 打印字符串和16进制变量值

5.4 代码结构

.
├── bm.lds
├── build.sh
├── Makefile
├── README.md
├── rungdb.sh
├── runqemu.sh
└── src
        ├── board
        │   └── apbuart
        │       ├── apbuart.c
        │       └── apbuart.h
        ├── common
        │   ├── console.c
        │   └── console.h
        ├── core
        │   └── start.S
        ├── main.c
        └── test

5.5 运行结果

int main(void)
{
        unsigned int test = 0x12345678;

        console_init();
        bm_printf("Enter main!\n");
        bm_printf_value("This is test value:", test);
        return 0;
}
root@iZj6ccyu2ndokc2ujnox0tZ:~/workspace/code/baremetal/baremetal-m3# ./runqemu.sh
not use gdb, just run
Enter main!
This is test value:0x12345678

到这里我们的代码不仅支持C函数,还可以打印log了。

__Vectors:
        .long           0x1000                          /*     Top of Stack */
        .long           Reset_Handler           /*     Reset Handler */

        .thumb_func
        .globl   Reset_Handler
Reset_Handler:
        ldr r0,=0x10
        ldr r1,=0x40004010
        str r0,[r1]
        ldr r0,=0x1
        ldr r1,=0x40004008
        str r0,[r1]
        ldr r0,='h'
        ldr r1,=0x40004000
        str r0,[r1]

考虑最小系统,让m3跑起来并输出log,实际上只需要上面这十几行代码就够了,对应到到 binary 几十个bytes。