2.1 实验目的
为os添加uart功能,通过串口实现开发板与PC交互。
2.1 硬件信息
QEMU虚拟SoC含有 虚拟NS16550A设备 。
不同的地址线组合(A2、A1、A0)对应的读写模式和寄存器如下所示:
2.2 NS16550a 的初始化
线路控制寄存器(LCR)中的bit7位来实现复用DLL、DLM两个寄存器拼起来作为16位波特率寄存器。当bit7位被设置为1时,地址0和1用于访问除数锁存寄存器(DLL和DLM),用于设置波特率。
- 关闭中断
- 设置波特率
- 设置异步数据通信格式
void uart_init()
{
/* disable interrupts. */
uart_write_reg(IER, 0x00);
/*
* Setting baud rate. Just a demo here if we care about the divisor,
* but for our purpose [QEMU-virt], this doesn't really do anything.
*
* Notice that the divisor register DLL (divisor latch least) and DLM (divisor
* latch most) have the same base address as the receiver/transmitter and the
* interrupt enable register. To change what the base address points to, we
* open the "divisor latch" by writing 1 into the Divisor Latch Access Bit
* (DLAB), which is bit index 7 of the Line Control Register (LCR).
*
* Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE".
* We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3.
* And due to the divisor register is two bytes (16 bits), so we need to
* split the value of 3(0x0003) into two bytes, DLL stores the low byte,
* DLM stores the high byte.
*/
uint8_t lcr = uart_read_reg(LCR);
uart_write_reg(LCR, lcr | (1 << 7));
uart_write_reg(DLL, 0x03);
uart_write_reg(DLM, 0x00);
/*
* Continue setting the asynchronous data communication format.
* - number of the word length: 8 bits
* - number of stop bits:1 bit when word length is 8 bits
* - no parity
* - no break control
* - disabled baud latch
*/
lcr = 0;
uart_write_reg(LCR, lcr | (3 << 1));
}
原代码这里是这样,感觉不太对,应该是左移一位的。
lcr = 0;
uart_write_reg(LCR, lcr | (3 << 0));
2.3 NS16550a 的数据读写
在NS16550A UART中,区分读写模式是通过控制信号(如读/写控制线)来实现的,而不是通过寄存器地址。这些控制信号通常由CPU或其他主控设备提供。以下是区分读写模式的一般步骤:
- 当CPU或其他主控设备想要从UART读取数据时,它会将读控制线置为有效状态(低电平)。同时将芯片选择信号置为有效状态,以选中UART设备。
- 当CPU或其他主控设备想要向UART写入数据时,它会将写控制线置为有效状态(低电平)。同样将芯片选择信号置为有效状态,以选中UART设备。
读:
/*
* LINE STATUS REGISTER (LSR)
* LSR BIT 0:
* 0 = no data in receive holding register or FIFO.
* 1 = data has been receive and saved in the receive holding register or FIFO.
* ......
* LSR BIT 5:
* 0 = transmit holding register is full. 16550 will not accept any data for transmission.
* 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character.
* ......
*/
#define LSR_RX_READY (1 << 0)
#define LSR_TX_IDLE (1 << 5)
int uart_putc(char ch)
{
while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0);
return uart_write_reg(THR, ch);
}
void uart_puts(char *s)
{
while (*s) {
uart_putc(*s++);
}
}
写:
练习 7-2
要求:参考code/os/01-helloRVOS,在此基础上增加采⽤轮询⽅式读取控制台上输入的字符并 回显 在控制台上。另外⽤户按下回⻋后能够另起⼀⾏从头开始。
int uart_getc()
{
char ch;
while ((uart_read_reg(LSR) & LSR_RX_READY) == 0);
ch = uart_read_reg(RHR);
return ch;
}
void uart_gets(char *s, int len)
{
int i = 0;
char ch;
while (i < len - 1) {
ch = uart_getc();
if (ch == '\r') {
break;
}
s[i++] = ch;
}
s[i] = '\0';
}
/**
* 回显功能:读取用户输入并回显到控制台
*/
void uart_echo()
{
char buffer[100];
uart_puts("UART Echo Ready:\r\n");
while (1) {
uart_gets(buffer, sizeof(buffer));
uart_putc('\r');
uart_putc('\n');
uart_puts("--kernel收到数据--\n");
uart_puts(buffer);
uart_putc('\r');
uart_putc('\n');
}
}
最后一定记得在kernel.c
添加extern
声明:
extern void uart_echo(void);
void start_kernel(void)
{
uart_init();
uart_puts("Hello, RVOS!\n");
uart_echo(); // 开始回显
while (1) {}; // stop here!
}
运行结果:
存在一个问题就是在终端输入的内容无法显示,且无法删除。
问题解决:在当前实现中,输入的字符虽然被回显,但无法正确处理删除键(Backspace)的功能。这是因为 uart_gets 函数没有对删除键 (‘\b’ 或 ASCII 8) 进行处理。以下是改进方案:
我们需要在 uart_gets 中添加对删除键的处理逻辑。当用户按下删除键时,应该从缓冲区中移除最后一个字符,并在终端上删除回显的字符。
void uart_gets(char *s, int len)
{
int i = 0;
char ch;
while (i < len - 1) {
ch = uart_getc(); // 读取一个字符
if (ch == '\r') { // 如果是回车符,结束读取
break;
} else if (ch == '\b' || ch == 127) { // 处理删除键('\b' 或 ASCII 127)
if (i > 0) {
i--; // 从缓冲区中移除最后一个字符
uart_putc('\b'); // 回显删除键
uart_putc(' '); // 用空格覆盖已删除的字符
uart_putc('\b'); // 将光标移回一格
}
} else {
s[i++] = ch; // 存储字符
uart_putc(ch); // 回显输入的字符
}
}
s[i] = '\0'; // 添加字符串结束符
}
问题完美解决!!
【[完结] 循序渐进,学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春】 https://www.bilibili.com/video/BV1Q5411w7z5/?p=19&share_source=copy_web&vd_source=d63943fdb26087d14a536adf35c52d6b