常见的资料传输方式有两种:
成本低
快
以资工人的角度来看,我们比较不需在意从 Tx 到 Rx 中间的资讯量传送速度 (Baud rate),像是: 如果连续传送两个 1
,在这两个 1
之间肯定不会是无间格或是无辨识讯号的,有可能会利用波型变化告诉 Rx 这是不同的讯号。因此,我们平常看到/在意的传输数率通常是 Bit rate
。
UART (Universal Asynchronous Receiver/Transmitter)是一种异步收发传输器,它可以将数据透过串列通讯和平行通讯间作传输转换。
如下图,通用异步接受器-发送器 (UART) 把资料的字节按照顺序发送。另一端的 UART 再将资料还原。每个 UART 都包含了一个移位暂存器。
在嵌入式设备上,我们所使用的 UART 都是利用写好的 IP。
若我们要自己实现 UART protocol,就必须根据 UART 的规则在 GPIO 上实现其逻辑。
在 mini-risc-v 这个迷你作业系统中,使用 UART 实作字元传输的功能,我们可以参考其原始码一探究竟。
由於 mini-riscv-os 被设计来执行在 QEMU 的 Virt 虚拟机上面,所以有关记忆体映射的操作都应该参考 Virt 原始码中的定义。
以 UART 为例,记忆体会从 0x10000000
开始映射:
0x10000000 THR (Transmitter Holding Register) 同时也是 RHR (Receive Holding Register)
0x10000001 IER (Interrupt Enable Register)
0x10000002 ISR (Interrupt Status Register)
0x10000003 LCR (Line Control Register)
0x10000004 MCR (Modem Control Register)
0x10000005 LSR (Line Status Register)
0x10000006 MSR (Modem Status Register)
0x10000007 SPR (Scratch Pad Register)
对应的原始码如下:
// os.c
#define UART 0x10000000
#define UART_THR (uint8_t *)(UART + 0x00) // THR:transmitter holding register
#define UART_LSR (uint8_t *)(UART + 0x05) // LSR:line status register
#define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty
什麽是
记忆体映射
呢?
可以参考 MMIO。
如果希望作业系统印出字元,则需要往 UART 的 THR 送资料。在这之前,我们需要检查 UART 传送区是否为空,也就是确认 LSR 是否为 1
。
// os.c
int lib_putc(char ch)
{
while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0)
;
return *UART_THR = ch;
}
有了基本的 UART 字元传送功能後,我们还需要有函式处理输出的功能:
// os.c
void lib_puts(char *s) {
while (*s) lib_putc(*s++);
}
利用指标做处理,便能将一长串资料分开来传送。
做到这步,Hello OS!
就顺利完成了:
int os_main(void)
{
lib_puts("Hello OS!\n");
while (1) {}
return 0;
}
即使程序码完成了,也不代表丢到虚拟机器上它就会自己跑起来。
如同运行在大众使用的电脑的作业系统,在开机时也同样需要有程序将作业系统引导至记忆体上才能够顺利启动,这样的程序就是 Bootloader。
mini-riscv-os 的 Bootloader 是使用 RISC-V 的组合语言所撰写:
.equ STACK_SIZE, 8192
.global _start
_start:
# setup stacks per hart
csrr t0, mhartid # read current hart id
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 stack space
add sp, sp, t0 # move the current hart stack pointer
# to its place in the stack space
# park harts with id != 0
csrr a0, mhartid # read current hart id
bnez a0, park # if we're not on the hart 0
# we park the hart
j os_main # hart 0 jump to c
park:
wfi
j park
stacks:
.skip STACK_SIZE * 4 # allocate space for the harts stacks
工作原理大致如下:
os_main()
j os_main
这条指令会让处理器跳到 C 档案中定义的 os_main()
,此时,作业系统就顺利完成载入的工作了!其他 Hart (硬体执行绪) 都会待在 park 中不断地循环。
<<: Day16 X Polyfill-less Bundling Script & File Compression
>>: [Day 16] 阿嬷都看得懂的通用 .html 档案结构
一场全球大流行的 COVID-19 疫情,以及 H264、H265、VP8、VP9等影音压缩技术加...
经然第28天了,如果没有这个活动我应该也不会每天练习 一样是六指渊的教学:https://www.s...
在 CLOUDWAYS❐ 中,提供了5大云端主机供应商的方案☞ linode 、 VULTR 、 D...
大家好,2020 铁人赛来到了最後一天。我自己是没想过竟然可以连写两年。 最後一天当然要写心得文,这...
上线之後才是开始。 第一坑 开发和部署环境不一致 如果一开始开发和部署没有「乔好」环境的话,那上线的...