简介
裸机串口代码调试记录
代码
代码链接
https://github.com/copyright1999/raspi4b-project/tree/b0aabc649e0c5cd6c8ec4a42edd0d295af89231e/baremental
过程
主要的过程分为如下几个步骤
-
链接脚本
-
入口代码
-
管脚复用
-
串口驱动
-
Makefile
链接脚本
- 确定好起始地址
- 按照
text
,data
,bss
的位置来分布(暂时还不考虑堆的事情) -
起点
text
段 - 字节对齐
入口代码
主要抓住这几个点
-
声明为
text.boot
段(因为前面的脚本指定了第一个text
段就是text.boot
段) - 设置所有寄存器初值为0
- 清
bss
段 - 设置栈指针
- 跳转C入口
尤其是设置栈指针,下面就是碰到一个栈问题,就是这里没设置,导入跳转C函数而出现异常
管脚复用
从树莓派的BCM2711 ARM Peripherals
文档中可知,树莓派的UART1
是mini UART
,其他都是PL011 UART
。这里我们选择UART0
来做这次的实验。
我们找到UART0
的RXD,TXD
的管脚
可以看到有GPIO14
跟GPIO15
,他的ALT0
也就是第一种复用方式是作为UART0
的RXD,TXD
。
实物图对应如下
至于怎么选择上下拉,可以看看这个文章
GPIO
内部三种状态:上拉,下拉,不拉- 上下拉针对输入而言,输出一般选择不拉
- 默认输入为0,最好配置下拉。默认输入为1,最好默认上拉
- 上下拉是为了
GPIO
有个确定的输入状态,不然悬空很难保证初始默认电平
串口驱动
波特率的配置,从海思的手册上截取的
通过配置寄存器 UART_IBRD 和 UART_FBRD 可以设置 UART 工作的波特率,波特率计算公式为: 当前波特率=UART 参考时钟频率(100MHz、50MHz、24MHz 或 3MHZ)/(16 x 分频系数) 分频系数有整数和小数两部分组成,分别对应寄存器 UART_IBRD 和 UART_FBRD。
例如:UART 参考时钟频率为 24MHz,如果配置 UART_IBRD 为 0x1E,UART_FBRD 为 0x00,按照波特率计算公式,则当前的波特率为 24/(16 x 30)=0.05Mbit/s。 UART 波特率配置的典型值为:9,600bit/s、14,400bit/s、19,200bit/s、38,400bit/s、 57,600bit/s、76,800bit/s、115,200bit/s、230,400bit/s、460,800bit/s。
分频系数值的计算以及分频系数寄存器的配置举例如下: 如果要求波特率为 230,400bit/s,并且 UART 参考时钟频率为 24MHz,那么分频系数 为(24 x 106)/(16 x 230400)= 6.5104,因此 IBRD(整数部分)为 6,FBRD(小 数部分)为 0.5104。
计算 6bitUART_FBRD 寄存器中的数值:根据 m=integer(FBRDx2 n+0.5) (n=UART_FBRD 寄存器的宽度),计算出 m=integer(0.5104x2 6+0.5)=33,在 UART_IBRD 寄存器中配置 0x0006,UART_FBRD 寄存器中配置 0x21。 当分频系数小数部分配置成 33 时,波特率除数的实际数值为 6+33/2 6=6.5156,产生 的波特率为(24 x 106)/(16 x 6.5156)=230216.7107,误差率为(230216.7107– 230400)/230400x100=-0.07956%。 使用 6bitUART_FBRD 寄存器最大的误差率为 1/2 6 x 100=1.56%,当 m=1 时会出现, 误差率累计超过 64 个时钟周期。
等有空再来研究下吧
Makefile
这个写的比较简单,主要是注意要链接上我们的链接脚本就行
$(CROSS_COMPILE)ld -nostdlib start.o $(OBJS) -T link.ld -o baremetal.elf
调试遇到的问题
回车换行的问题
首先要知道两个定义
- 回车符:
\r,0x0D
,使光标移到行首 - 换行符:
\n,0x0A
,使光标下移一格
一开始无法正常换行,后来代码里面参考了别人的方法,只要是发送\r
就都换成\n
,只要是接收到\r
就都换成\n
。貌似PL011
这个IP
要这么操作,看DW
的好像不用。
从uboot
的PL011
的代码看,确实也是这么处理的。
栈问题
调试的时候,一开始代码没有初始化相关的寄存器,包括SP
寄存器,导致在图中红框的汇编出,进入main
函数,压栈的时候出错了,然后PC
又跑飞了。
#将x29 x30存到 sp-16 所指向的内存地址上,x29在低地址,同时sp = sp -16
stp x29,x30,[sp,#-16]!
那要怎么初始化SP
寄存器呢,这样子操作就行了
其他关于栈指针的汇编举例
ldr x0, [x1] ; 从 x1 指向的地址中区一个 64 为数据存入 x0
ldp x1, x2, [x10, #10] ; 从 x10+10 指向的地址中取 2 个 64 位的数,分别存入 x1, x2 低地址的存入 x1
str x5, [sp, #24] ; 将 x5 内容存入 sp+24 所指向的内存地址上
stp x29, x30, [sp, #-16]! ; 将 x29, x30 存到 sp-16 所指向的内存地址上,x29 在低地址,同时 sp = sp - 16 (感叹号的意思)
ldp x29, x30, [sp], #16 ; 从 sp 指向的内存地址中区两个 64 位数据存入 x29, x30 中,并且 sp += 16
地址映射的原因
反正我也不知道这个树莓派映射的道理,手册中的地址都是不对的,这个文档中提到外设的起始地址是0xFE000000
,反正我也不打算去纠结这个事了,外设手册中UART
的基地址是0x7e200000
,实际代码用的是0xFE200000
。
也可以看linux
下的io memory
分布
参考链接
参考代码
文档链接
下一步代办
- 封装代码,比如设置波特,设置停止位数据位等接
- DMA模式,中断模式,
inner loopback
模式(若支持),outer loopback
模式(若支持) - 流控研究
- 示波器抓波形