[Zedboard测评] 波特率,我偏要9600——Zynq串口详细分析
0赞之前的一篇博文已经写了一些文字,主要讨论Zynq启动流程:在实现一个简单的串口打印“Hello Zedboard!”前,开发工具已经为我们做了很多工作,其中当然包括串口初始化,具体如何实现串口打印“Hello World!”已经有很多人写过,我不再重复了,不了解的同学呢,我在本文结束提供一个教程的链接。
这里首先简单说一下我在串口调试时使用的SDK自带的串口终端如何使用,然后单步调试分析源代码,最后讲一下串口相关寄存器,并利用库函数自主地操作串口。
1、使用SDK自带的串口终端
step1:在SDK中、底部位置找到如图所示的窗口,点击图1所示的按钮,新建一个终端,弹出的对话框如图2所示。
图1 新建终端
图2 终端类型选择对话框
step2:选择Connection Type为Serial,设置相应的Settings参数:波特率为115200,8数据位,1停止位,无校验等。结果如图3所示。这里我没有改终端的名字,你可以在View Title文本框里把终端改成自己喜欢的名字。设置完毕后点击OK,串口自动打开,效果如图4所示。
图3 串口终端参数设置
图4 串口终端连接成功
下面就可以运行程序了,这样就可以在这个终端查看串口打印结果,而不必频繁切换到“超级终端”或是“串口调试助手”。程序运行效果如图5所示:
图5 SDK自带串口终端打印效果
2、串口示例程序的单步调试
首先在print()处设置断点,启动调试,程序跳转至main()函数后暂停,如图6所示:
step into(快捷键F5)进入init_platform(),看看这个函数做了什么。
stop over(快捷键F6)跳过enable_caches(),因为看到了init_uart(),从名字来看,应该是串口初始化函数,果断F5,结果发现直接跳至init_uart()结束位置:它什么都没做……
从程序上来看,显然是由于我们没有#define STDOUT_IS_16550。F6返回上一层的init_platform(),F6返回主程序,马上执行print函数。
F5进入print函数后发现它利用一个while循环完成字符串输出,调用了一个outbyte函数实现单个字符的输出,看名字,outbyte()显然是输出一个字节的意思,它又是怎么实现一个字节输出?输出为什么是送到串口呢?那么,接着进outbyte()看看吧。
……outbyte()又调用了一个XUartPs_SendByte()函数,参数分别为:STDOUT_BASEADDRESS、c,那从函数名字来看,应该是将c送到STDOUT_BASEADDRESS这个地址。没办法,还是要“深入”……F5看一下XUartPs_SendByte()函数到底做了什么。写到这,我真是快吐了,自己分析的时候感觉挺快的,一步一步写下了,就受不了啦,终于明白为什么大家的文章都是做到看到打印结果就结束了……下面我就不写软件操作了,无非就是step into,step over和step return,还是分析程序吧。
/****************************************************************************/
/**
*
* This function sends one byte using the device. This function operates in
* polled mode and blocks until the data has been put into the TX FIFO register.
*
* @param BaseAddress contains the base address of the device.
* @param Data contains the byte to be sent.
*
* @return None.
*
* @note None.
*
*****************************************************************************/
void XUartPs_SendByte(u32 BaseAddress, u8 Data)
{
/*
* Wait until there is space in TX FIFO
*/
while (XUartPs_IsTransmitFull(BaseAddress));
* Write the byte into the TX FIFO
*/
XUartPs_WriteReg(BaseAddress, XUARTPS_FIFO_OFFSET, Data);
}
这个函数来自xuartps_hw.c文件,功能方面的注释很详细,其中XUartPs_IsTransmitFull是一个宏,用来检测串口发送FIFO是否为满,满等待,非满将数据送入发送FIFO。FIFO状态检测和串口寄存器相关,后续再进行分析。
XUartPs_IsTransmitFull定义如下:(\ 用于换行,下同)
#define XUartPs_IsTransmitFull(BaseAddress) \
((Xil_In32((BaseAddress) + XUARTPS_SR_OFFSET) & \
XUARTPS_SR_TXFULL) == XUARTPS_SR_TXFULL)
该参数宏调用standalone BSP中的Xil_In32()双字输入函数,实现从某个地址读取双字的操作,同时利用按位与操作完成了FIFO状态的检测。
XUartPs_WriteReg也是一个宏,定义如下:
#define XUartPs_WriteReg(BaseAddress, RegOffset, RegisterValue) \
Xil_Out32((BaseAddress) + (RegOffset), (RegisterValue))
该参数宏调用standalone BSP中的Xil_Out32()双字输出函数,实现向某个地址写入双字的操作。
Xil_In32()和Xil_Out32()具体实现,我就不多分析了,有兴趣的可以自行查看源码。
一个简单的串口打印,经历了诸多周折,已经分析到底层寄存器,相信同学们也明白的差不多了,想要更加清晰明了地分析操作寄存器的函数的作用,就要看串口相关的寄存器了,本来想分析一下,发现貌似也没什么可说的,使用寄存器最重要的就是要搞清楚每一位的作用,这需要自己看手册,Zynq所有寄存器的定义都在UG585的附录B中进行了详细描述,同学们自己去看吧。
通过上述分析可以发现,SDK通过BSP为用户提供了API,使用户不必关心如何操作底层寄存器也能实现快速开发,由此看见,学习BSP的源码是很重用的,想要实现快速地、自主地控制串口,最好的方法还是参考BSP相关函数完成设计。
BSP源文件在哪呢?请看下图:
上文提到的print()所在源文件print.c,outbyte()所在的源文件outbyte.c,Xil_Out32和Xil_In32所在的xil_io.c都是在standalone bsp的src文件夹下,同学们可以自己看一下,别忘了看看自己感兴趣的其他源文件。
最后再分析一下standalone bsp中的串口初始化函数(位于uart.c文件中),设计串口初始化函数如下:
----------------------------------------------------------my_uart.h开始------------------------------------------------------------
/*
* my_uart.h
*
* Created on: 2012-12-8
* Author: cuter
*/
#ifndefMY_UART_H_
#defineMY_UART_H_
#include"xil_types.h" // 数据类型定义
#include"xil_io.h" // Xil_Out32声明
/* Register offsets */
#defineUART_CR_OFFSET 0x00 // 串口控制寄存器偏移地址
#defineUART_MR_OFFSET 0x04 // 串口模式寄存器偏移地址
#defineUART_BAUDGEN_OFFSET0x18 //
#defineUART_BAUDDIV_OFFSET0x34 //
#defineUART_BAUDRATE 9600 // 波特率,程序中没有用到
#defineUART_BASE_ADDRESS 0xE0001000// 串口1基地址
#defineUART_BDIV 0x07 // 7 BDIV和CD共同产生波特率
#defineUART_CD 0x28B // 651
voiduart_init(void);
#endif/* MY_UART_H_ */
----------------------------------------------------------my_uart.h结束------------------------------------------------------------
----------------------------------------------------------my_uart.c开始------------------------------------------------------------
/*
* my_uart.c
*
* Created on: 2012-12-8
* Author: cuter
*/
#include"my_uart.h"
voiduart_init(void)
{
/* set CD and BDIV */
Xil_Out32(UART_BASE_ADDRESS + UART_BAUDGEN_OFFSET, UART_CD); // 写CD
Xil_Out32(UART_BASE_ADDRESS + UART_BAUDDIV_OFFSET, UART_BDIV); // 写BDIV
/*
* 8 data, 1 stop, 0 parity bits
* sel_clk=uart_clk=APB clock
*/
Xil_Out32(UART_BASE_ADDRESS + UART_MR_OFFSET, 0x20); // 写串口模式寄存器
/* enableTx/Rxand resetTx/Rxdata path */
Xil_Out32((UART_BASE_ADDRESS + UART_CR_OFFSET), 0x17); // 写串口控制寄存器
}
----------------------------------------------------------my_uart.c结束------------------------------------------------------------
波特率计算公式为:baud_rate = sel_clk/(CD*(BDIV+1))。
生成波特率用到的数据参考自UG585,如下图所示:
运行结果如图所示:
至此,相信同学们已经能够自己编写自己想要的控制函数了。本文没有使用串口中断,同学们可以参考串口中断寄存器自行设计相关控制函数,另外对串口的操作函数,还可以参考bsp xuartps的操作函数,实现其他串口操作,原理相同,本文不再详述。
ps~HelloWorld图文教程链接:http://www.cnblogs.com/surpassal/archive/2012/09/08/ZedBoard_Lab1.html