Zynq-7000 开发总结

Zynq-7000 开发总结

近期经手了使用zynq开发的项目,中途遇到了很多问题,大多都是基础性问题,最后也都一一解决了,这里挑选一些印象深刻的坑进行记录,以鉴后者。

软件版本:vivado 2017.4,及其配套的sdk。

芯片型号:xc7z020clg400-2

1、软件使用问题

(1)vivado 生成ps7的系统软核,其中FCLK_CLK0端口设置为100MHz,在使用过程中会自动变为"1e+8Hz"

以上问题,会在sdk中暴露出来。在sdk中,会根据ps7,也就是ZYNQ7 process system这个IP核的配置信息来配置芯片上两片arm核及其外设,正常情况下,时钟频率会宏定义为100000000U,在C语言其中的U是指无符号整型。由于端口变为了1e+8,则会在生成软件支持包(bsp)的时候出错,因为会被识别成浮点数,这就与U相矛盾了。进一步的,跟该时钟相连接的其他模块的时钟端口信息也会变为1e+8。图中是正常的情况。

img

解决办法:

  1. 将模块删除,重新创建IP核,并且祈祷下次综合时,不要变成1e+8。一开始我就是这样将就着用的。
  2. 点开ps软核,Clock Configuration -> PL Fabric Clocks -> FCLK_CLK0 修改为99MHz,保存并退出IP核配置,再保存一下block design,观察到FCLK_CLK0端口从1e+8Hz变为了99MHz,再进入IP核配置,修改回100MHz即可。
(2)SDK中,在软件支持包中分明存在f_closef_openf_mount等函数,甚至点击F3都能直接跳转到该函数在ff.h中的定义,但是仍旧报错,称找不到相关读写sd相关的函数

诸如此类的问题,每次都会浪费很多时间,解决办法就是将自己写的程序单独保存,然后将整个project.sdk目录删掉,然后在vivado中重新导出hdf文件,再lauch sdk,再新建app,并正确设置bsp,再将自己写的程序拷贝进新建的app中,刷新即可发现报错被解决了。

我也曾尝试单独删除bsp,并且重新生成新的bsp,但是并非每次都能解决问题,进而浪费更多时间。因此,建议遇到找不到文件或者目录的问题,重新生成一次bsp,如不能解决问题,则直接整个重来。

(3)SDK中,找不到xil_printf.h

区别于(2),(2)是找不到bsp的supported libraries中选择的xilffs相应生成的ff.h下面的程序,而(3)则是找不到常规的xilinx的库文件。这种情况则是在app右键选择properties->C/C++ General -> Paths and Symbols->GNU C->Include directories 点击添加workspace中bsp的include文件夹,即可。

需要注意,bsp的properties是没有Paths and Symbols的。

进一步的,我也遇到过string.h这样的c语言标准库找不到的情况,这种一般重新编译一下就能解决。

基本上软件问题一般都能通过重新启动软件、重新创建程序来解决问题。

2、PL端程序设计问题

这次主要碰到的问题是IP核打包问题。

这次开发中,由于需要使用一个bram控制程序。ps端可通过Axi Bram Controller这个IP核控制BRAM,另外如需将PL端的数据写入到BRAM,则需要根据BRAM的控制时序自己开发RTL程序。另外如需PS控制PL端的读写地址、读写长度、读写使能等,则需要将AXI总线的帮助。

学习使用AXI总线协议,是比较复杂繁琐的。在这里简单梳理AXI开发逻辑,详细内容不在此处展开。

通过使用Vivado的IP核打包工具,任意打开一个Vivado,Tools -> Create and Package New IP -> Create AXI4 Peripheral,即可创建相应的AXI接口。可根据需要增加或者减少AXI的数量。AXI分为Lite、Full、Stream三个版本,其中顾名思义,Stream是流控制版本,是涉及端口信号最少,也最容易上手的,无地址信号,意味着也无法去精确的读写。Lite可以控制寄存器读写,以及配置寄存器数量,适用于这种简单的读写BRAM控制程序,并使用PS传输读写控制信息。

IP打包工具则会生成相应的AXI模版,其中PS到寄存器的部分的通信逻辑已经创建好了,仅需添加自己写的BRAM的状态机即可,当然添加时需要设置一些端口。

此外,IP打包工具还会生成驱动程序,在启动SDK后,就会将其中关于寄存器、以及一些寄存器地址信息写入到xil_parameters.h中,并且还会生成相应的头文件以及IP相关的库函数,当然自带的只有读寄存函数和写寄存器函数。

通过这种方法,就能建立起PS和PL端最基本的通信,通过读写寄存器进行简单的交流,通过读写BRAM来交换更多的数据。进一步的,可以通过DDR进行更大带宽、更大内容的读写交互。

3、PS端程序设计问题

这部分主要是C语言编程,以及外设中断设计的问题。

(1)zynq中断使用

在嵌入式软件编程中,最常使用的一个技术就是中断,中断可以暂停主函数的运行,并执行中断服务,结束后再回到主函数。

一个经典的应用就是设置按键中断,通过通过设置按键中断,当按下按键触发中断,在中断服务函数中识别是哪个按键被按下,从而在主函数中执行相应的程序,比如打印状态信息、发送一些指令、结束程序等。

启动一个典型的中断程序包括以下步骤:初始化外设和中断控制器,连接中断及中断服务函数到外设控制器,设置中断在中断控制器中的优先级和触发方式,使能中断控制器。此外,为了避免一些异常发生,还需要额外注册中断异常函数(同样也需要提前初始化),并将中断异常使能。

当然,不同的外设的触发中断的事件也不同,比如UART中断,就分为发送类、接收类、异常类,发送fifo空或者满,或者将空或者将满,都可以设置为中断触发的条件,具体根据使用需要来设置UART的中断掩码实现。

比较常用的定时器中断,适用于高精度定时的周期性的应用中。在zynq-7000中的是32位递减计数的定时器。配置定时器,主要有以下步骤:初始化定时器、定时器自检、定时器载入初值、定时器启动。要实现周期性功能,还需要启动定时器的自动重启功能,否则定时器只会倒计时一次。在zynq的定义中,定时器的时钟频率是CPU系统时钟频率的一半,CPU频率默认值为2/3GHz,则定时器频率为1/3GHz,即每隔3ns定时器计数值减1,通过获取当前计数值,则能推算出距离定时器启动隔了多少时间。

一次定时,最多倒计时 2 32 / ( 1 0 9 / 3 ) = 12.885 2^{32}/(10^9/3)=12.885 232/(109/3)=12.885秒,可通过设置定时器的预分频器来提高定时器的计时最大值。比如设置十倍的分频,倒计时时间可提高到128.85秒,同时定时器精度从3ns降低到30ns。在zynq-7000中,定时器最大支持255倍的分频,PrescalerValue是一个8bit的无符号数。当然也可以通过记录重载次数来提高计数最大值。

另一方面,通过设置定时器中断,可以在每次计时器递减到0时触发中断,从而完成周期性任务。其中断设置方式并无任何不同,都是初始化、连接、优先级、使能。

(2)函数循环计数递增,最好每次增加1

图中的BRAM_BYTENUM是常数4,因此每次循环i就会递增4,在数组索引时就会出错。当然可以通过i/4作为数组索引,但是不推荐这样做。一般能够通过乘法解决的问题,就不要作除法。i递增1,读地址则写成i*4

图中这段错误代码放在中断服务函数中,会引发中断异常,并进入Xil_DataAbortHandler(),这个函数在xil_exception.c的258到278行,是一个死循环。而且由于vivado sdk在代码静态检查时,未能覆盖到非主函数这部分,因此未曾报错,最后花了很多时间才发现这里出现了数组越界。

(3)在主循环中使用了switch语句,由于case语句占用了break,故无法使用break退出循环

两个解决办法。

  1. 设置退出标志,在循环中检测退出标志,检测到之后则break退出循环。
  2. 使用goto语句。直接从switch中退出循环,简介方便,但需谨慎使用。

(4)在c语言中取绝对值,可以使用abs()函数,但它仅支持整型。

<stdlib.h>中定义了abs()函数,可以为int类型的数据取绝对值。浮点数取绝对值,需要使用<math.h>中的fabs(),更加简单方法是自己写一个取绝对值函数。这里就因为直接使用了abs()对浮点数取绝对值,导致数据类型出错,不过很快就找到原因了。

float fun_fabs(float a, float b)
{
    if(a-b>0)
        return a-b;
    else
        return b-a;    
}
(5)注意中断触发的时间,中断触发前主函数会持续执行。

如下面的示意代码,read_data打印出来,如果中断触发有所延迟,则首先打印的是0,而非1。

int read_data=0;
int main()
{
    while(1){
        fun_a();
        printf("%d",read_data);
    }
}

void fun_a()
{
    tri_interrut();
}

void intr_handler(void * CallbackRef)
{
    read_data++}

此次开发过程中就遇到了这个问题,调整执行顺序之后,read_data的数据正确了,否则read_data是上一次的读取的数据。

(6)<sleep.h>中的sleep(),只能接受整数值。

休眠时间如果输入浮点数则会出错。

(7)XUartPS_Recv()函数的接收时,每次都会少接收一个字节。这个问题debug了将近两三个小时,原因很简单,但较难发现。

现象:发送端一直发送EB 90 00 00,除了第一次接收正确外,后面每次接收都会少一个字节。

1th recv: EB 90 00 00;
2nd recv: 90 00 00;
3ed recv: 90 00 00;
4th recv: 90 00 00;
......

原因:

  1. 接收逻辑:超时分包策略,超过指定时间间隔,则视为一次接收结束。

    while(1){ //main loop
        tmp_recv_cnt = XUartPS_Recv(&UART_PS,Read_Buff[Recv_cnt],1);
        // 若未从rx-fifo中接收到数据,则计数+1
        if(tmp_recv_cnt == 0){
            tmp_cnt++;
        }else{
            Recv_cnt+=tmp_recv_cnt;
            tmp_cnt=0;
        }
        
        if (tmp_cnt>100){
            read_end();
            Recv_cnt=0;
        }
    }
    
  2. 问题:接收结束之后,UART_PS的值未重置跟初始化一样。

即图中InstancePtr->ReceiveBuffer.RequesteBytes未置零,当rx-fifo再次有数据写入时,会首先写在InstancePtr->ReceiveBuffer.NextBytePtr,也就是第一个字节0xEB写在了Read_Buff[3]中。

  1. 解决:写入结束时,将UART_PS.ReceiveBuffer.RequesteBytes=0;
(8)volatile关键字的使用
volatile int global_var;

使用该关键字修饰需要在其他文件或者中断中使用的全局变量,可以防止编译器将其优化为寄存器变量,确保其严格按照程序顺序直接从内存中访问读写。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值