本文主要是介绍【JokerのZYNQ7020】AMP。,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
软件环境:vivado 2017.4 硬件平台:XC7Z020
首先吧,说说这篇文章标题AMP是个啥。这个AMP(非对称多处理)是相对于SMP(对称多处理)来说的,耍ZYNQ的朋友们都很清楚,PS这边是个双核A9,而之前的大多数SDK程序,除非特别指定,一般会默认只跑在核0上,而今天这个AMP程序,是核0和核1一起跑,类似于真正的两个进程同时进行,但彼此之间还有软中断相互触发,有一片共享的数据区域,也体现了与之前SDK中跑的程序的不同。再直白一点的形容两个的差别,SMP是在两个核上跑一个系统,资源两个核共享,核怎么调度由系统来完成,而AMP更像是在两个核上跑两个系统,每个核应该怎么做,资源应该怎么规划,全由自己来决定,有着更大的自由度的同时,对规划的要求也更高。今天的AMP程序是基于之前1080p图像显示那个程序上面改出来的,所以PL这边的结构与之前一模一样。
而今天这个程序与之前程序的主要差别或者说主要想实现的是,从SD卡中读图这件事仅由核0完成,4张图像读取完毕后,核1显示1图2秒,然后将2图数据放入共享内存,触发软中断,让核1读取共享内存中的2图内容,然后显示出来,核1显示2秒完毕后同样产生软中断,通知核0,然后于此相同完成图3图4在核0核1的交替显示。 所以这篇的重点也就自然而然的有两个,一个是如何让核0和核1一起跑,另一个是如何利用软中断彼此触发。先开手册UG585 P229。
可以看到,总共有16个软中断可以利用,我这里只用到了两个,另外需要说明的一点是,软中断不但可以核之间相互触发,核本身也可以利用软中断触发自己。话不多说,开搞,先从核1的说起,核1不像核0要从卡里读图,所以BSP这边并不要什么额外的库的支持。只是创建程序的时候需要注意一下。
下拉框中,一定要选择应用是跑在核1上的,切记切记!左侧就是核1程序结构,依次来说一下。
main.c
#include <stdio.h>
#include "xparameters.h"
#include "xsdps.h"
#include "xil_printf.h"
#include "sleep.h"#include "share_memory.h"
#include "soft_interrupt.h"#define SHARED_BASE_ADDR 0x30000000#define H_STRIDE 1920
#define H_ACTIVE 1920
#define V_ACTIVE 1080
#define VIDEO_LENGTH (H_STRIDE*V_ACTIVE)#define VDMA_BASEADDR XPAR_AXI_VDMA_0_BASEADDR
#define VIDEO_BASEADDR0 0x05000000
#define Buffer_Size 1920*1080*3XScuGic Intc; //GICu8 share_memory_data[Buffer_Size] __attribute__ ((aligned(32)));
volatile u8 software_interrupt_flag = 0;void Xil_DCacheFlush(void);void Show_BMP_Picture( const unsigned char * addr, u32 size_x, u32 size_y)
{u32 x=0;u32 y=0;u32 r,g,b;for(y=0;y<size_y;y++){for(x=0;x<size_x;x++){r = *(addr++);g = *(addr++);b = *(addr++);Xil_Out32((VIDEO_BASEADDR0+((y*size_x)+size_x-x)*4),((r<<16)|(g<<8)|(b<<0)));}}Xil_DCacheFlush();
}void VDMA_init()
{int i;for(i=0;i<VIDEO_LENGTH;i++){Xil_Out32(VIDEO_BASEADDR0+i*4,0);}Xil_DCacheFlush();Xil_Out32((VDMA_BASEADDR + 0x000), 0x3);Xil_Out32((VDMA_BASEADDR + 0x05c), VIDEO_BASEADDR0);Xil_Out32((VDMA_BASEADDR + 0x060), VIDEO_BASEADDR0);Xil_Out32((VDMA_BASEADDR + 0x064), VIDEO_BASEADDR0);Xil_Out32((VDMA_BASEADDR + 0x058), (H_STRIDE*4));Xil_Out32((VDMA_BASEADDR + 0x054), (H_ACTIVE*4));Xil_Out32((VDMA_BASEADDR + 0x050), V_ACTIVE);
}int main()
{u8 *share_region = (u8 *)SHARED_BASE_ADDR;VDMA_init();Init_Intr_System(&Intc);Init_Intr_Software(&Intc, Software_Interrupt_Hanedler, Cpu0_To_Cpu1_Interrupt, 1);Setup_Intr_Exception(&Intc);while(1){if(software_interrupt_flag){export_data(share_memory_data, share_region,Buffer_Size);Show_BMP_Picture(share_memory_data,1920,1080);sleep(2);Software_Interrupt_Generate(&Intc, Cpu1_To_Cpu0_Interrupt, XSCUGIC_SPI_CPU0_MASK);software_interrupt_flag = 0;}}return 0;
}
SHARED_BASE_ADDR划分的内存共享区域,这里先不谈,一会儿结合地址划分再说。software_interrupt_flag软中断触发标志,当接收到软中断时置1。主函数中,首先进行中断初始化,Init_Intr_Software是在soft_interrupt.c中定义的,下面说。循环内部,通过不断检测软中断触发标志,一旦接收到软中断,就说明核0已经把图片内容放进共享内存区域,此时,核1从共享内存区域中导出数据,并显示2秒,而后产生触发核0的软中断,回馈给核0,最后清除软中断。
share_memory.c
#include "share_memory.h"void export_data(u8 *buffer, u8 *p, u32 length)
{Xil_DCacheInvalidateRange((INTPTR)p, length);memcpy(buffer, p, length);
}void import_data(u8 *src_buffer, u8 *dst_buffer, u32 length)
{memcpy(dst_buffer, src_buffer, length);Xil_DCacheFlushRange((INTPTR)src_buffer, length);Xil_DCacheFlushRange((INTPTR)dst_buffer, length);
}
share_memory中,主要是共享内存中的数据拷贝和缓冲区的刷新,一定要进行缓冲区刷新,不然数据有可能会出错,原因下面会说。
share_memory.h
#ifndef SHARE_MEMORY_H_
#define SHARE_MEMORY_H_#include "xil_types.h"
#include "xil_cache.h"
#include "string.h"void export_data(u8 *buffer, u8 *p, u32 length);
void import_data(u8 *src_buffer, u8 *dst_buffer, u32 length);#endif
soft_interrupt.c
#include "soft_interrupt.h"extern u8 software_interrupt_flag;void Software_Interrupt_Hanedler(void *Callback)
{xil_printf("receive soft-interrupt!\r\n");software_interrupt_flag = 1;
}void Setup_Intr_Exception(XScuGic * IntcInstancePtr)
{Xil_ExceptionInit();Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,(Xil_ExceptionHandler)XScuGic_InterruptHandler,(void *)IntcInstancePtr);Xil_ExceptionEnable();
}int Init_Intr_System(XScuGic * IntcInstancePtr)
{int Status;XScuGic_Config *IntcConfig;IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);if (NULL == IntcConfig) {return XST_FAILURE;}Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,IntcConfig->CpuBaseAddress);if (Status != XST_SUCCESS) {return XST_FAILURE;}return XST_SUCCESS;
}void Init_Intr_Software(XScuGic *GicInstancePtr, Xil_InterruptHandler IntrHanedler, u16 SoftwareIntrId, u8 CpuId)
{int Status;XScuGic_SetPriorityTriggerType(GicInstancePtr, SoftwareIntrId, 0xB0, 0x2);Status = XScuGic_Connect(GicInstancePtr, SoftwareIntrId,(Xil_InterruptHandler)IntrHanedler, NULL);if (Status != XST_SUCCESS) {xil_printf("Cpu%d: software interrupt %d set fail!\r\n", XPAR_CPU_ID, SoftwareIntrId);return;}XScuGic_InterruptMaptoCpu(GicInstancePtr, CpuId, SoftwareIntrId);XScuGic_Enable(GicInstancePtr, SoftwareIntrId);}void Software_Interrupt_Generate(XScuGic *GicInstancePtr, u16 SoftwareIntrId, u32 CpuId)
{int Status;Status = XScuGic_SoftwareIntr(GicInstancePtr, SoftwareIntrId, CpuId);if (Status != XST_SUCCESS) {xil_printf("Cpu%d: software interrupt %d generate fail!\r\n", XPAR_CPU_ID, SoftwareIntrId);return;}
}
soft_interrupt.h
#ifndef SOFT_INTRRUPT_H_
#define SOFT_INTRRUPT_H_#include "xil_cache.h"
#include <stdio.h>
#include "xil_types.h"
#include "Xscugic.h"
#include "Xil_exception.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xdebug.h"#define Cpu0_To_Cpu1_Interrupt 0x01
#define Cpu1_To_Cpu0_Interrupt 0x02
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_IDvoid Software_Interrupt_Hanedler(void *Callback);
void Init_Intr_Software(XScuGic *GicInstancePtr, Xil_InterruptHandler IntrHanedler, u16 SoftwareIntrId, u8 CpuId);
void Software_Interrupt_Generate(XScuGic *GicInstancePtr, u16 SoftwareIntrId, u32 CpuId);
int Init_Intr_System(XScuGic * IntcInstancePtr);
void setup_Intr_Exception(XScuGic * IntcInstancePtr);#endif
软中断这里首先是中断服务函数Software_Interrupt_Hanedler,这个根据自身实际设计需求来填充,我因为主函数是轮询的,所以这里只产生标志就足够了。而后在Init_Intr_Software中利用XScuGic_SetPriorityTriggerType给对应中断设置优先级和触发方式,利用XScuGic_Connect将中断与中断服务函数绑定,这里由于使用的是AMP模式,2个CPU,所以需要利用XScuGic_InterruptMaptoCpu明确的告知,当前设置的中断,是与哪一个CPU进行映射绑定的,最后,利用XScuGic_Enable使能。
至此,核1的程序基本就这样了,由于核0的程序是在上一篇1080p图像显示基础上改过来的,所以BSP中依旧需要使能对于内存卡的支持,share_memory与soft_interrupt与核1程序是一样的,所以这里只说主函数怎么改的。
main.c
#include <stdio.h>
#include "xparameters.h"
#include "xsdps.h"
#include "xil_printf.h"
#include "ff.h"
#include "bmp.h"
#include "sleep.h"#include "share_memory.h"
#include "soft_interrupt.h"#define BUFFER_BASE_ADDR 0x30000000
#define SHARED_BASE_ADDR 0x32000000#define H_STRIDE 1920
#define H_ACTIVE 1920
#define V_ACTIVE 1080
#define VIDEO_LENGTH (H_STRIDE*V_ACTIVE)#define VDMA_BASEADDR XPAR_AXI_VDMA_0_BASEADDR
#define VIDEO_BASEADDR0 0x05000000
#define Buffer_Size 1920*1080*3XScuGic Intc;
static FATFS SD_Card_Dev;
char *SD_Card_Path = "0:/";u8 *data_buffer;u8 Original_Buf1[Buffer_Size] __attribute__ ((aligned(32)));
u8 Original_Buf2[Buffer_Size] __attribute__ ((aligned(32)));
u8 Original_Buf3[Buffer_Size] __attribute__ ((aligned(32)));
u8 Original_Buf4[Buffer_Size] __attribute__ ((aligned(32)));u8 Show_Buf1[Buffer_Size] __attribute__ ((aligned(32)));
u8 Show_Buf2[Buffer_Size] __attribute__ ((aligned(32)));
u8 Show_Buf3[Buffer_Size] __attribute__ ((aligned(32)));
u8 Show_Buf4[Buffer_Size] __attribute__ ((aligned(32)));volatile u8 software_interrupt_flag = 0;void Xil_DCacheFlush(void);void Show_BMP_Picture( const unsigned char * addr, u32 size_x, u32 size_y)
{u32 x=0;u32 y=0;u32 r,g,b;for(y=0;y<size_y;y++){for(x=0;x<size_x;x++){r = *(addr++);g = *(addr++);b = *(addr++);Xil_Out32((VIDEO_BASEADDR0+((y*size_x)+size_x-x)*4),((r<<16)|(g<<8)|(b<<0)));}}Xil_DCacheFlush();
}void VDMA_init()
{int i;for(i=0;i<VIDEO_LENGTH;i++){Xil_Out32(VIDEO_BASEADDR0+i*4,0);}Xil_DCacheFlush();Xil_Out32((VDMA_BASEADDR + 0x000), 0x3);Xil_Out32((VDMA_BASEADDR + 0x05c), VIDEO_BASEADDR0);Xil_Out32((VDMA_BASEADDR + 0x060), VIDEO_BASEADDR0);Xil_Out32((VDMA_BASEADDR + 0x064), VIDEO_BASEADDR0);Xil_Out32((VDMA_BASEADDR + 0x058), (H_STRIDE*4));Xil_Out32((VDMA_BASEADDR + 0x054), (H_ACTIVE*4));Xil_Out32((VDMA_BASEADDR + 0x050), V_ACTIVE);
}int SD_init()
{FRESULT result;result = f_mount(&SD_Card_Dev,SD_Card_Path, 0);if (result != 0) {return XST_FAILURE;}return XST_SUCCESS;
}int main()
{data_buffer = (u8 *)BUFFER_BASE_ADDR;VDMA_init();SD_init();Init_Intr_System(&Intc);Init_Intr_Software(&Intc, Software_Interrupt_Hanedler, Cpu1_To_Cpu0_Interrupt, 0);Setup_Intr_Exception(&Intc);BMP_Picture_Processor((u8 *)"1.bmp" , Original_Buf1 ,Buffer_Size);BMP_Picture_Processor((u8 *)"2.bmp" , Original_Buf2 ,Buffer_Size);BMP_Picture_Processor((u8 *)"3.bmp" , Original_Buf3 ,Buffer_Size);BMP_Picture_Processor((u8 *)"4.bmp" , Original_Buf4 ,Buffer_Size);u32 i;for(i = 0;i < Buffer_Size ;i++ ){Show_Buf1[i] = Original_Buf1[Buffer_Size-i-1];Show_Buf2[i] = Original_Buf2[Buffer_Size-i-1];Show_Buf3[i] = Original_Buf3[Buffer_Size-i-1];Show_Buf4[i] = Original_Buf4[Buffer_Size-i-1];}while(1){Show_BMP_Picture(Show_Buf1,1920,1080);sleep(2);import_data(Show_Buf2, data_buffer, Buffer_Size);Software_Interrupt_Generate(&Intc, Cpu0_To_Cpu1_Interrupt, XSCUGIC_SPI_CPU1_MASK);while(!software_interrupt_flag);software_interrupt_flag = 0;Show_BMP_Picture(Show_Buf3,1920,1080);sleep(2);import_data(Show_Buf4, data_buffer, Buffer_Size);Software_Interrupt_Generate(&Intc, Cpu0_To_Cpu1_Interrupt, XSCUGIC_SPI_CPU1_MASK);while(!software_interrupt_flag);software_interrupt_flag = 0;}return 0;
}
可以看到,除了图像交叉显示之外,其实也没啥变化,while(1)里,就跟最开始说的一样,图1先显示2秒,然后将从卡里读到的图2的数据放到数据共享区域,产生软中断触发核1读取显示,然后自己一直等核1给自己的显示结束的软中断,接收到以后,说明核1显示结束,然后自己再进行图3的显示,显示2秒后,图4的操作与前一致,循环进行核0和核1的图像显示。
程序其实很简单,接下来说的才是重中之重了,就是调AMP的时候需要注意的事项。
1.软中断的注册,映射,和产生函数,XScuGic_Connect、XScuGic_InterruptMaptoCpu、XScuGic_SoftwareIntr。还有中断服务函数,由于都是自己定义的,所以想要中断做什么,和中断的方式,一定要有个整体的规划。
2.内存地址分配,这个非常重要,我强烈的建议,最好把核0、核1和内存共享的区域,三者之间独立开!!!以下分别是我核0、核1和共享内存之间的划分。
3.Xil_DCacheFlushRange内存强行刷新。如果在调此类AMP应用时候,发现花屏、中断能触发但是数据不对等等问题的时候,首先考虑的应该是cache一致性问题造成的,我在调这AMP程序时候,发现图像能交叉显示出来,但是显示时候总有一块是花屏的,不是上面就是下面,就类似下面这个示例图一样。
查了以后发现zynq的512k的L2 cache是两个核共享的,详细内容可以看ug585手册第3章,这里就不截了。这里只说这种问题怎么解决,一方面是在进行数据写入共享内存后,进行Xil_DCacheFlushRange,让写入内存的数把缓存也刷了,另一方面干脆在核1的BSP中添加-DUSE_AMP=1的编译选项。
至于这个DUSE_AMP选项影响的是哪里,看下CACHE这边你就明白了,这里只是拿Xil_DCacheFlushRange来举例而已,还有很多约束L2 Cache的地方,感兴趣的可以自己打开来看。
4.debug的时候,注意要把两个核全都勾上。看图。
5.至于两个核怎么一起跑,一起选中,然后run就好了呀。
这篇关于【JokerのZYNQ7020】AMP。的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!