• 学习开发一个RISC-V上的操作系统(汪辰老师) — 01-helloRVOS程序讲解


    前言

    (1)此系列文章是跟着汪辰老师的RISC-V课程所记录的学习笔记。
    (2)该课程相关代码gitee链接
    (3)PLCT实验室实习生长期招聘:招聘信息链接
    (4)

    start.S

    (1)qemu模拟的板子有8个内核,为了让我们跟方便理解,汪辰老师只使用了一个内核。
    (2)如下是进行判断,当前执行任务的是否是第一个内核。如果是的,往下执行,如果不是第一个内核,跳转到park任务中。

    csrr	t0, mhartid		# read current hart id
    mv	tp, t0			# keep CPU's hartid in its tp for later usage.
    bnez	t0, park		# if we're not on the hart 0
    
    • 1
    • 2
    • 3

    (3)这里的wfi是让内核进入低功耗状态。如果没有这条语句,其他7个内核不进行任何操作,只是空转没必要,因此直接让他们进入休眠即可。这样省电。

    park:
    	wfi
    	j	park
    
    • 1
    • 2
    • 3

    (4)这里主要是进行一些堆栈操作,最后的j,是跳转到c程序中。start_kernel你可以修改为任意名字,只要的c程序入口函数也修改为对应的函数名即可。

    slli	t0, t0, 10		# shift left the hart id by 1024
    la	sp, stacks + STACK_SIZE	# set the initial stack pointer
    				# to the end of the first stack space
    add	sp, sp, t0		# move the current hart stack pointer
    				# to its place in the stack space
    
    j	start_kernel		# hart 0 jump to c
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    kernel.c

    (1)这里实际上就是在串口上打印一个Hello, RVOS!,之后进入空转状态。

    extern void uart_init(void);
    extern void uart_puts(char *s);
    
    void start_kernel(void)
    {
    	uart_init();
    	uart_puts("Hello, RVOS!\n");
    
    	while (1) {}; // stop here!
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    uart.c

    宏定义

    (1)因为qemu的串口0地址为0x10000000,所以这里以0x10000000为基地址进行偏移操作。这里建立一个宏的目的是为了后续方便操作。

    /* --- 这个是在platform.h中 ---*/
    #define UART0 0x10000000L
    /* --- uart.c中 ---*/
    #define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg))
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    (2)这里的宏定义其实就是对照者如下表格来进行的宏定义的。这个时候肯定会有人有疑问,怎么偏移0地址和偏移1地址都是有多个寄存器的呢?
    <1>对于偏移0地址而言,这个是串口的数据收发寄存器,一般来说,单片机的发送寄存器和接受寄存器都是同一个寄存器,这样可以保证资源的重复利用。那么单片机是如何知道这个寄存器的数据,到底是发送出去的,还是接受过来的呢?这个就是硬件层面的配置了对于写软件程序的我们,只需要知道,如果向这个寄存器写入数据,那么单片机就会发送数据。如果你要读取这个寄存器的数据,那么就是接收数据。 个人感觉这个可能和51单片机的IO准双向电路设计原理类似,感兴趣的可以去了解这部分电路设计。
    <2>DLLDLM 又是什么呢?当LCR寄存器的bit7为1时候,偏移地址0和偏移地址1的两个寄存器就是被当成了波特率发生器寄存器,DLL就是波特率发生器的低8位,DLM就是高8位。(不理解的话,后面有更详细的介绍)

    #define RHR 0	// Receive Holding Register (read mode)
    #define THR 0	// Transmit Holding Register (write mode)
    #define DLL 0	// LSB of Divisor Latch (write mode)
    #define IER 1	// Interrupt Enable Register (write mode)
    #define DLM 1	// MSB of Divisor Latch (write mode)
    #define FCR 2	// FIFO Control Register (write mode)
    #define ISR 2	// Interrupt Status Register (read mode)
    #define LCR 3	// Line Control Register
    #define MCR 4	// Modem Control Register
    #define LSR 5	// Line Status Register
    #define MSR 6	// Modem Status Register
    #define SPR 7	// ScratchPad Register
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    (3)LSR寄存器的bit0是用于检测接受数据是否已经过来了,如果接收到了数据bit0 == 1LSR寄存器的bit5是用于判断串口数据是否发送出去,如果发送出去了bit5 == 0

    #define LSR_RX_READY (1 << 0)
    #define LSR_TX_IDLE  (1 << 5)
    
    • 1
    • 2

    在这里插入图片描述
    在这里插入图片描述

    (4)上面说了,硬件上已经做好了处理,如果你写入数据,软件层面直接赋值。读取数据,直接获取这个寄存器数据即可。因此写法如下:

    #define uart_read_reg(reg) (*(UART_REG(reg)))
    #define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v))
    
    • 1
    • 2

    uart_init()

    失能中断

    (1)首先,我们对IER寄存器全部写入0,意思是关闭所有的中断。

    IER BIT 7-4IER BIT-3IER BIT-2IER BIT-1IER BIT-0
    全部置0modem状态寄存器中断接收中断发送完成中断接收就绪中断

    在这里插入图片描述

    // 失能所有中断
    uart_write_reg(IER, 0x00);
    
    • 1
    • 2

    波特率配置

    (1)因为我们需要配置波特率,而配置波特率需要使用到DLLDLM寄存器。所以需要设置LCR寄存器的bit7为高电平。为了防止其他位被破坏,所以先读取LCR寄存器的值,再对bit7进行操作。

    uint8_t lcr = uart_read_reg(LCR);
    
    • 1

    (2)使能内部波特率计数器锁存(DLAB),因为如果要配置波特率,需要先将这一位置为高电平。之后0地址和1地址的寄存器就变成了波特率设置寄存器。

    uart_write_reg(LCR, lcr | (1 << 7));
    
    • 1

    (3)因为我们的仿真器是使用的1.8432MHZ的晶振进行的计算,最终需要生成的波特率是38.4K,就需要向DLL中写入3。至于为什么需要波特率是38.4K,这个我就不太清楚了。

    uart_write_reg(DLL, 0x03);
    
    • 1

    在这里插入图片描述

    (4)从上面的表我们可以看出,如果波特率为50,需要存入2304。而tb16550这款芯片的寄存器只有8位,明显是存放不下来的。因此IER寄存器在DLAB被使能的时候变成DLM,作为波特率生成器的高八位。因为我们需要写入的是3,所以DLM直接写0。

    uart_write_reg(DLM, 0x00);
    
    • 1

    异步通讯格式配置

    (1)这里就是设置有效数据为8位,停止位为1,不进行校验(当bit3为0时候,bit4--bit5都没有用)。因为不需要中断,所以bit6为0,波特率设置已经完成了,所以bit7为0。
    (2)通过上面的分析,即可得出,LCR寄存器写入一个0000 0011即可。

    lcr = 0;
    uart_write_reg(LCR, lcr | (3 << 0));
    
    • 1
    • 2

    在这里插入图片描述
    在这里插入图片描述

    uart_putc()

    (1)这个函数就是用于发送一个字符数据的,如果玩过51单片机的同学都会知道,你进行串口发送数据的时候,都需要等待第一个数据发送完成,才可以开始第二个数据发送。否则就会出现,第一个数据还没有发送完,就被第二个数据覆盖了。因此第一个数据就无法成功的输出。
    (2)通过查阅数据手册可知,当LSR寄存器的bit5 == 0时候,表示数据发送完成,因此这里需要进行一次while死循环。
    (3)当LSR寄存器的bit5 == 0条件满足,退出while()循环,就可以发送数据了。

    int uart_putc(char ch)
    {
    	while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0);
    	return uart_write_reg(THR, ch);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    uart_puts()

    (1)因为我们需要发送一个字符串,而字符串的最后一位是'/0',所以写法如下。

    void uart_puts(char *s)
    {
    	while (*s) {
    		uart_putc(*s++);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    参考文章

    (1)TECHNICAL DATA ON 16550
    (2)https://gitee.com/unicornx/riscv-operating-system-mooc/blob/main/code/os/01-helloRVOS/uart.c
    (3)https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c

  • 相关阅读:
    Python 套接字编程完整指南
    搭建Prometheus+Grafana框架监控Hyperledger Fabric的运行
    JSP webshell免杀——webshell免杀
    UnrealEngine iOS 打包 —— 签名证书(cer、p12)生成
    机器学习的医疗乳腺癌数据的乳腺癌疾病预测
    Rancher 部署 DataKit 最佳实践
    元宇宙改变人类工作模式的四种方式
    HBase原理深入
    python自动化测试(一):操作浏览器
    SpringBoot+Vue实现前后端分离的个性化课程推荐系统
  • 原文地址:https://blog.csdn.net/qq_63922192/article/details/133581500