cuter

[Zedboard测评] 波特率,我偏要9600——Zynq串口详细分析

0
阅读(11060) 评论(7)

    之前的一篇博文已经写了一些文字,主要讨论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

 */

#ifndef MY_UART_H_

#define MY_UART_H_

 

#include "xil_types.h"             // 数据类型定义

#include "xil_io.h"                // Xil_Out32声明

 

/* Register offsets */

#define UART_CR_OFFSET      0x00   // 串口控制寄存器偏移地址

#define UART_MR_OFFSET      0x04   // 串口模式寄存器偏移地址

#define UART_BAUDGEN_OFFSET0x18   //

#define UART_BAUDDIV_OFFSET0x34   //

#define UART_BAUDRATE       9600   // 波特率,程序中没有用到

#define UART_BASE_ADDRESS   0xE0001000// 串口1基地址

#define UART_BDIV        0x07       // 7 BDIV和CD共同产生波特率

#define UART_CD             0x28B      // 651

void uart_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"

 

void uart_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);    // 写串口模式寄存器

 

    /* enable Tx/Rx and reset Tx/Rx data 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

  1. 学习啦!感谢分享

  2. 博主 好人一生平安
  3. 感谢博主的详细解说,非常有价值
  4. 讲的很细致!辛苦了!

  5. 回复:回复

    这么高端的板子,有没有高速串口外设啊,921600bps这样的

    这个还真不知道,ug585中给的数据,文中给了截图了。这块板子的时钟默认的sel_clk是50MHz的,但这个时钟可以自己在XPS中进行配置,从原理上讲, 应该可以产生这么高的波特率。

  6. 这么高端的板子,有没有高速串口外设啊,921600bps这样的

  7. ug585是Zynq EPP Technical Reference Manual,可以到官网下载,也可以参考我的博客《Zynq启动过程探讨--进入main函数前我不了解的事》里面有下载地址,不过建议去官网下载,因为时不时会有更新~