之前在学习各大开发板的时候,都是只浏览了一遍,没有深入研究。最近回头再看一遍,刚开始看hello world就一直存在一个疑问,从C语言的角度入手,似乎没有对UART进行配置,为什么上来就有波特率115200。
首先直接分析hello world的代码:
/*
* helloworld.c: simple test application
*
* This application configures UART 16550 to baud rate 9600.
* PS7 UART (Zynq) is not initialized by this application, since
* bootrom/bsp configures it to baud rate 115200
*
* ------------------------------------------------
* | UART TYPE BAUD RATE |
* ------------------------------------------------
* uartns550 9600
* uartlite Configurable only in HW design
* ps7_uart 115200 (configured by bootrom/bsp)
*/
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
int main()
{
init_platform();
print("Hello World\n\r");
cleanup_platform();
return 0;
}
从中看到,C代码只有一个init_platform()函数,然后接直接输出了。
void init_platform()
{
/*
* If you want to run this example outside of SDK,
* uncomment one of the following two lines and also #include "ps7_init.h"
* or #include "ps7_init.h" at the top, depending on the target.
* Make sure that the ps7/psu_init.c and ps7/psu_init.h files are included
* along with this example source files for compilation.
*/
/* ps7_init();*/
/* psu_init();*/
enable_caches();
init_uart();
}
enable_caches()这个函数从字面意思就知道,是跟芯片内部高速缓存有关,配置一些SRAM,而外设肯定不会在SRAM中运行的。而另外一个函数init_uart(),里面只是定义了uartns550这个串口的波特率是9600,也不可能是我们调试的串口配置。看起来C语言部分根本没有对串口进行配置。
void disable_caches()
{
#ifdef __MICROBLAZE__
#ifdef XPAR_MICROBLAZE_USE_DCACHE
Xil_DCacheDisable();
#endif
#ifdef XPAR_MICROBLAZE_USE_ICACHE
Xil_ICacheDisable();
#endif
#endif
}
void init_uart()
{
#ifdef STDOUT_IS_16550
XUartNs550_SetBaud(STDOUT_BASEADDR, XPAR_XUARTNS550_CLOCK_HZ, UART_BAUD);
XUartNs550_SetLineControlReg(STDOUT_BASEADDR, XUN_LCR_8_DATA_BITS);
#endif
/* Bootrom/BSP configures PS7/PSU UART to 115200 bps */
}
那么换个思路,参考I.MIX6U等一些ARM处理器系统运行前,BOOT ROM的BCD代码部分会对DDR等外设进行初始化,先在汇编中进行一些寄存器初始化,再配合链接脚本,最终通过Makefile将一些外设提前进行了配置。那么不妨去汇编中找一下代码,看看有没有对串口外设进行初始化。(Makefile部分就先不研究了,毕竟Linux菜鸟一枚)。
有事没有先问百度。百度一下,果然看到有类似文章: SDK\SDK_Export\hello_world_bsp_0\ps7_cortexa9_0\libsrc\standalone_v3_05_a\路径下的SRC文件夹,里面有asm_vectors.S、boot.S、cpu_init.S、translation_table.s、xil-crt0.S等5个文件。其中boot.s就有代码初始化了。
/* Initialize STDOUT to 115200bps */
ldr r0, =UART_BAUDRATE
bl Init_Uart
#ifdef PROFILING /* defined in Makefile */
但我打开我的2016、2017、2019版本的Vivado SDK并没有发现有这一段代码。不管是不是版本原因还是其他问题,那么这条路肯定也是走不通了。
那么回想创建工程前,我们从Vivado中导入了硬件平台,那么可能是在这部分代码中。打开后发现果然,ps7_init.c和ps7_init.h代码中对寄存器进行了写操作,很可能就是在这里进行了配置。ps7_init() 这个函数就是根据平台不同配置,写入不同对参数。然而,在init_platform()这个函数中ps7_init() 被屏蔽了,也就是说并没有生效,再次迷惑了。注意到上面有一段话说了:
If you want to run this example outside of SDK,uncomment one of the following two lines and also #include “ps7_init.h”
说明这个可能还是有些作用的。参考官方的ug821-zynq-7000-swdev文档:
ps7_init.tcl这个文件和ps7_init.c是一样的效果,那么猜想,当使用JTAG烧录可能是不需要调用ps7_init.c文件,而是直接运行了ps7_init.tcl脚本。直接再次百度,果然已经有人问到这个问题了。
链接: Xilinx 论坛.
再次找到CSDN上面的文章,果然,在JTAG的时候,是用脚本文件ps7_init.tcl初始化外设的,而这个文件是从硬件平台导出的。可见没有什么是无缘无故产生的。
写到最后,其实我们可以去看一下ps7_init.c这个文件究竟写了哪些寄存器,进行了哪些操作。涉及到C语言的一些基础知识,这里就不详细介绍了。而SOC外设寄存器的地址,可以从平台生成的文件ps7_init.html中打开,比如寄存器UART_CLK_CTRL:
应用工程中的其他BSP文件,这些才是SDK(新版本叫做Vitis)生成的板级库文件,类似于STM32的官方库、HAL库,我们可以在SDK中直接调用,就像使用Keil一样。
SDK还有一个作用就是将硬件平台(Vivado)生成的FPGA运行文件.bit文件和硬件启动文件fsbl.elf,以及SDK工程生成的.elf打包生成BOOT.BIN。当然,对于Linux系统也是这样,只不过这个时候要用到的是Linux系统生成的uboot.elf。有时间和兴趣的话,不妨用二进制文档编辑器打开BOOT.bin,看一下相关的启动过程是怎样的。
参考文档:
[1] https://blog.csdn.net/pingis58/article/details/103425981
[2] https://blog.csdn.net/zhaoxinfan/article/details/54958641
[3] https://blog.csdn.net/wangbuu/article/details/80047791
[4] https://blog.csdn.net/claudedy/article/details/90757593