串口输出东西太重要了,因此,再来仔细地看看串口调用的其它函数,这样做到一目了然,没有别的疑问在里面,就可以做到庖丁解牛游刃有余。像下面的函数:
PRINTSTR(" CONFIG=")
上面这句,就是输出一串字符到串里显示出来。其实它是一个宏定义,那么它是怎么样实现输出字符串到串口上的呢?立即去找到它的宏定义,然后把它展开,最后看看它是怎么样的。宏定义如下:
#define PRINTSTR(x) .rdata;98: .asciz x; .text; la a0, 98b; bal stringserial; nop
把它写得好看一些,如下:
.rdata
98: .asciz x
.text
la a0,98b
bal stringserial
nop
第一、二行是定义一串字符串保存的空间,放在只读数据段里。第三行是定义代码段开始,然后就是通过la指令获取98标号处的字符串首地址。最后跳到子函数stringserial里运行输出字符串。一定要在跳转的指令后面加入一条空指令,否则其它指令就会被执行。
现在又去查看stringserial的代码,它如下:
LEAF(stringserial)
move a2, ra
addu a1, a0, s0
lbu a0, 0(a1)
1:
beqz a0, 2f
nop
bal tgt_putchar
addiu a1, 1
b 1b
lbu a0, 0(a1)
2:
j a2
nop
END(stringserial)
上面代码,先把返回地址ra保存到a2,然后计算字符串的地址,接着通过lbu获取第一个字符,在beqz里判断是否到了字符串结束,如果没有结束就继续调用显示字符函数显示。使用addiu指令来移动字符串地址指针。
在b 1b后面还有一行lbu a0,0(a1)。由于龙芯是流水线的CPU,当跳转发生时,已经把后面那一条指令解释执行完成了,所以a0里总是保存最新的字符。
还用到一个子函数hexserial以16进制的方式输出寄存器的值,它的代码如下:
LEAF(hexserial)
move a2, ra
move a1, a0
li a3, 7
1:
rol a0, a1, 4
move a1, a0
and a0, 0xf
la v0, hexchar
addu v0, s0
addu v0, a0
bal tgt_putchar
lbu a0, 0(v0)
bnez a3, 1b
addu a3, -1
j a2
nop
END(hexserial)
在函数的开始处,先保存返回地址,保存显示的寄存器值,然后把32位的值分成8个字符显示,li a3,7就是做8次的计数。
bnez a3,1b
addu a3,-1
同时也是利用跳转同时改变a3的值。
要显示16进制的值,是使用查表法实现的。hexchar是表格的首地址。
下面再看怎么样实现显示一个字符的代码:
LEAF(tgt_putchar)
- la v0, COM1_BASE_ADDR
la v0, COM3_BASE_ADDR
1:
lbu v1, NSREG(NS16550_LSR)(v0)
and v1, LSR_TXRDY
beqz v1, 1b
nop
sb a0, NSREG(NS16550_DATA)(v0)
move v1, v0
la v0, COM3_BASE_ADDR
bne v0, v1, 1b
nop
j ra
nop
END(tgt_putchar)
上面的代码是从串口3显示字符出来。先要判断串口是否可以发送数据,如果不能发送,就循环查询,直到可以发送为止。
到这里已经把串口看完了