zynq编程相关知识 - minichao9901/TangNano-20k-Zynq-7020 GitHub Wiki

存储器映射

image

cache操作

#include "xil_cache.h"

void Xil_DCacheEnable(void)
void Xil_DCacheDisable(void)

void Xil_DCacheInvalidate(void)
void Xil_DCacheInvalidateRange(INTPTR adr, u32 len)
void Xil_DCacheFlush(void)
void Xil_DCacheFlushRange(INTPTR adr, u32 len)

void Xil_ICacheEnable(void)
void Xil_ICacheDisable(void)
void Xil_ICacheInvalidate(void)
void Xil_ICacheInvalidateLine(u32 adr)
void Xil_ICacheInvalidateRange(INTPTR adr, u32 len)

说明Xil_DCacheFlush是将cache中的数据推送到ddr中一般用于像DAC这样的发送机CPU填充buffer后在执行DMA之前先用Xil_DCacheFlushXil_DCacheInvalidate是将cache的数据无效也就是将数据从ddr推送到cache一般用于像ADC这样的接收机DMA读取传感器数据后执行Xil_DCacheInvalidate将数据推送到cache然后cpu再去访问因此Flush和Invalidate是2个相反的操作对于zynqadr和len需要32bytes对其因为Cache Line是32bytes

mmu操作(例如禁止某个地址区域的Cache,需要高级的操作)

#include "xil_mmu.h"

void Xil_SetTlbAttributes(INTPTR Addr, u32 attrib);
void Xil_EnableMMU(void);
void Xil_DisableMMU(void);
void* Xil_MemMap(UINTPTR PhysAddr, size_t size, u32 flags);

例如//S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
Xil_SetTlbAttributes(SHARE_BASE,0x14de2);    //禁用Cache缓存

全局异常与中断

#include "xil_exception.h"

Xil_ExceptionEnable()
Xil_ExceptionDisable()
extern void Xil_ExceptionInit(void);
extern void Xil_ExceptionRegisterHandler(u32 Exception_id,
					 Xil_ExceptionHandler Handler,
					 void *Data);

ocm remap到高地址的方法

void Xil_Remap_OCM(void)
{
    xil_printf("%x\r\n",Xil_In32(0xf8000910));  //read slcr.ocm_cfg
    Xil_Out32(0xf8000000+0x08,0xdf0d); //slcr.unlock
    Xil_Out32(0xf8000910,0x000f);      //slcr.ocm_cfg(remap ocm from 0x00000000->0xfffc0000)
    Xil_Out32(0xf8000000+0x08,0x0);    //slcr_unlock
    xil_printf("%x\r\n",Xil_In32(0xf8000910));  //read slcr.ocm_cfg again
}

说明:ocm_remap到高地址,必须由zynq完成,才能生效。才能被其它master(例如microblaze)使用。因此如果是microblaze的程序,如果要使用ocm_remap功能,必须和zynq程序一起烧录运行才可以。

ocm关闭cache的方法

void Xil_Disable_Cache_On_OCM(void)
{
	//S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
	Xil_SetTlbAttributes(0xfffc0000,0x14de2);    //禁用Cache缓存
}

这个只适用于zynq自己本身。Xil_SetTlbAttributes函数是zynq才有的。

测量延时的函数

#include "xtime_l.h"

XTime_SetTime(0);
XTime t1,t2;
function1();
XTime_GetTime(&t1);
function2();
XTime_GetTime(&t2);

//XTime dt1 = ((t1) * 1000000) / (COUNTS_PER_SECOND);
//XTime dt2 = ((t2-t1) * 1000000) / (COUNTS_PER_SECOND);
//xil_printf("dt1=%d, dt2=%d\r\n", dt1,dt2);
//以上语句打印的结果不正确,用下面的方法是ok的

u32 dt1=(u32)t1*(1000000.0/COUNTS_PER_SECOND);
u32 dt2=(u32)(t2-t1)*(1000000.0/COUNTS_PER_SECOND);
printf("dt1=%d, dt2=%d\r\n", dt1,dt2);

zynq访问ddr/ocm的方法

image

说明:以下展示的是ocm_remap后的访问。如果不做remap, 原始地址是可以随便访问的。

#include <stdio.h>
#include "xparameters.h"
#include "xgpio.h"
#include "sleep.h"
#include "xil_cache.h"
#include "xil_exception.h"
#include "xil_mmu.h"

#define GPIO_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID // 根据实际硬件配置修改
#define LED_CHANNEL 1

void test_ddr(UINTPTR addr, u32 length, u8 mode)
{
    for(UINTPTR p=addr; p<addr+length; p+=4){
    	Xil_Out32(p,0x11223344);
    }

    for(UINTPTR p=addr; p<addr+length; p+=4){
    	int v= Xil_In32(p);
    	if(mode==0){
    		xil_printf("%p->%x\r\n",p,v);
    	}
    	else if(mode==1 && v!=11223344){
    		xil_printf("%p\r\n",p);
    	}
    }
}

void Xil_Remap_OCM(void)
{
    xil_printf("%x\r\n",Xil_In32(0xf8000910));  //read slcr.ocm_cfg
    Xil_Out32(0xf8000000+0x08,0xdf0d); //slcr.unlock
    Xil_Out32(0xf8000910,0x000f);      //slcr.ocm_cfg(remap ocm from 0x00000000->0xfffc0000)
    Xil_Out32(0xf8000000+0x08,0x0);    //slcr_unlock
    xil_printf("%x\r\n",Xil_In32(0xf8000910));  //read slcr.ocm_cfg again
}

void Xil_Disable_Cache_On_OCM(void)
{
	//S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
	Xil_SetTlbAttributes(0xfffc0000,0x14de2);    //禁用Cache缓存
}

int main() {
    XGpio gpio;
    int Status;

    Xil_DCacheDisable();
    Xil_Remap_OCM();
    Xil_Disable_Cache_On_OCM();

    xil_printf("Zynq GPIO test!\r\n");

    // 初始化GPIO
    Status = XGpio_Initialize(&gpio, GPIO_DEVICE_ID);
    if (Status != XST_SUCCESS) {
        printf("GPIO Initialization failed\n");
        return XST_FAILURE;
    }

    // 设置GPIO为输出
    XGpio_SetDataDirection(&gpio, LED_CHANNEL, 0x00);


    while (1) {
    	xil_printf("Zynq Print Now!\r\n");
        test_ddr(0x10000000,16,0); //test ddr
        test_ddr(0xfffc0000,16,0); //test ocm, remap模式才可以访问
        test_ddr(0xfffd0000,16,0); //test ocm, remap模式才可以访问
        test_ddr(0xfffe0000,16,0); //test ocm, remap模式才可以访问
        test_ddr(0xffff0000,16,0); //test ocm, remap模式才可以访问
        test_ddr(0x00000000,16,0); //test ocm0, 默认非remap模式可以访问
        test_ddr(0x00010000,16,0); //test ocm1, 默认非remap模式可以访问
        test_ddr(0x00020000,16,0); //test ocm2, 默认非remap模式可以访问
        test_ddr(0xFFFF0000,16,0); //test ocm3, 默认非remap模式可以访问

        // 依次翻转四个GPIO引脚
        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0001);
        usleep(500000); // 等待500毫秒

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0010);
        usleep(500000);

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0100);
        usleep(500000);

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b1000);
        usleep(500000);
    }

    return 0;
}

image

以上是remap成功后,对ocm的读写访问。可见,当remap成功后,可以对ocm0/1/2/3_high_addr的4个bank进行成功的读写。但是不能对ocm0/1/2_low_addr进行读写了,程序已经卡死了。

image image

当注释掉remap后,对ocm进行读写访问。可以对ocm_lower_addr和ocm4_high_addr进行读写,这是默认地址。如果对ocm0/1/2_high_addr进行读写,则程序卡死。

microblaze访问ddr/ocm的方法

1. 如果只run microblaze,则只能对ocm的默认地址进行读写

#include <stdio.h>
#include "xparameters.h"
#include "xgpio.h"
#include "sleep.h"
#include "xil_cache.h"

#define GPIO_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID // 根据实际硬件配置修改
#define LED_CHANNEL 1

void test_ddr(UINTPTR addr, u32 length, u8 mode)
{
    for(UINTPTR p=addr; p<addr+length; p+=4){
    	Xil_Out32(p,0x11223344);
    }

    for(UINTPTR p=addr; p<addr+length; p+=4){
    	int v= Xil_In32(p);
    	if(mode==0){
    		xil_printf("%p->%x\r\n",p,v);
    	}
    	else if(mode==1 && v!=11223344){
    		xil_printf("%p\r\n",p);
    	}
    }
}


int main() {
    XGpio gpio;
    int Status;

    Xil_DCacheDisable();

    xil_printf("Microblaze GPIO test!\r\n");

    // 初始化GPIO
    Status = XGpio_Initialize(&gpio, GPIO_DEVICE_ID);
    if (Status != XST_SUCCESS) {
        printf("GPIO Initialization failed\n");
        return XST_FAILURE;
    }

    // 设置GPIO为输出
    XGpio_SetDataDirection(&gpio, LED_CHANNEL, 0x00);

    while (1) {
//        scanf("%c",&c);
//        while(c!='c');

    	xil_printf("Microblaze Print Now!\r\n");
        test_ddr(0x10000000,16,0); //test ddr
//		test_ddr(0xfffc0000,16,0); //test ocm, remap模式才可以访问
//		test_ddr(0xfffd0000,16,0); //test ocm, remap模式才可以访问
//		test_ddr(0xfffe0000,16,0); //test ocm, remap模式才可以访问
//		test_ddr(0xffff0000,16,0); //test ocm, remap模式才可以访问
		test_ddr(0x00000000,16,0); //test ocm0, 默认非remap模式可以访问
		test_ddr(0x00010000,16,0); //test ocm1, 默认非remap模式可以访问
		test_ddr(0x00020000,16,0); //test ocm2, 默认非remap模式可以访问
		test_ddr(0xFFFF0000,16,0); //test ocm3, 默认非remap模式可以访问


        // 依次翻转四个GPIO引脚
        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0001);
        usleep(500000/10); // 等待500毫秒

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0010);
        usleep(500000/10);

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0100);
        usleep(500000/10);

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b1000);
        usleep(500000/10);
    }

    return 0;
}

image

2. 如果需要microblaze对ocm的remap后的地址进行读写,那么需要配合zynq一起。因为ocm_remap需要有zynq执行。

microblaze的程序

#include <stdio.h>
#include "xparameters.h"
#include "xgpio.h"
#include "sleep.h"
#include "xil_cache.h"

#define GPIO_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID // 根据实际硬件配置修改
#define LED_CHANNEL 1

void test_ddr(UINTPTR addr, u32 length, u8 mode)
{
    for(UINTPTR p=addr; p<addr+length; p+=4){
    	Xil_Out32(p,0x11223344);
    }

    for(UINTPTR p=addr; p<addr+length; p+=4){
    	int v= Xil_In32(p);
    	if(mode==0){
    		xil_printf("%p->%x\r\n",p,v);
    	}
    	else if(mode==1 && v!=11223344){
    		xil_printf("%p\r\n",p);
    	}
    }
}


int main() {
    XGpio gpio;
    int Status;

    Xil_DCacheDisable();

    xil_printf("Microblaze GPIO test!\r\n");

    // 初始化GPIO
    Status = XGpio_Initialize(&gpio, GPIO_DEVICE_ID);
    if (Status != XST_SUCCESS) {
        printf("GPIO Initialization failed\n");
        return XST_FAILURE;
    }

    // 设置GPIO为输出
    XGpio_SetDataDirection(&gpio, LED_CHANNEL, 0x00);

    while (1) {
//        scanf("%c",&c);
//        while(c!='c');

    	xil_printf("Microblaze Print Now!\r\n");
        test_ddr(0x10000000,16,0); //test ddr
		test_ddr(0xfffc0000,16,0); //test ocm, remap模式才可以访问
		test_ddr(0xfffd0000,16,0); //test ocm, remap模式才可以访问
		test_ddr(0xfffe0000,16,0); //test ocm, remap模式才可以访问
		test_ddr(0xffff0000,16,0); //test ocm, remap模式才可以访问
		test_ddr(0x00000000,16,0); //test ocm0, 默认非remap模式可以访问
		test_ddr(0x00010000,16,0); //test ocm1, 默认非remap模式可以访问
		test_ddr(0x00020000,16,0); //test ocm2, 默认非remap模式可以访问
		test_ddr(0xFFFF0000,16,0); //test ocm3, 默认非remap模式可以访问


        // 依次翻转四个GPIO引脚
        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0001);
        usleep(500000/10); // 等待500毫秒

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0010);
        usleep(500000/10);

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b0100);
        usleep(500000/10);

        XGpio_DiscreteWrite(&gpio, LED_CHANNEL, 0b1000);
        usleep(500000/10);
    }

    return 0;
}

zynq程序,只负责配置ocm_remap

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xgpio.h"

void Xil_Remap_OCM(void)
{
    xil_printf("%x\r\n",Xil_In32(0xf8000910));  //read slcr.ocm_cfg
    Xil_Out32(0xf8000000+0x08,0xdf0d); //slcr.unlock
    Xil_Out32(0xf8000910,0x000f);      //slcr.ocm_cfg(remap ocm from 0x00000000->0xfffc0000)
    Xil_Out32(0xf8000000+0x08,0x0);    //slcr_unlock
    xil_printf("%x\r\n",Xil_In32(0xf8000910));  //read slcr.ocm_cfg again
}

int main()
{
    init_platform();
    Xil_Remap_OCM();

    cleanup_platform();
    return 0;
}

image

2个核一起烧录,一起运行

image

这个结果很有意思:
1)最刚开始,microblaze先运行,因为microblaze简单,初始化速度快。这个时候ocm还没有remap,因此读出得ocm_higher_addr都是0
2)接着zynq开始运行,执行了ocm_remap
3)到了3和4的时候,ocm开始生效了,这个时候microblaze读出的是正确的值。
4)从这里看,zynq与mb的启动先后顺序还是有要求的。这里,要求zynq先起来,这种情况创建一个fsbl可能比较合适。或者,让mb程序先sleep(1),这样后执行。

⚠️ **GitHub.com Fallback** ⚠️