在上一篇文章中,我们搭建了一个AMP的环境,而且在CPU0和CPU1上分别跑了两个没有交集的线程。这篇文章中我们需要让两个CPU之间能够进行通信。而传递数据采用了共享内存,我们将共享内存设置在OCM(On Chip Memory)内。
在我们的设计中CPU0负责进行显示控制7段数码管和LED灯,显示的数据从OCM中定义的显示缓冲区读取,采用定时器中断进行刷新扫描显示;CPU1负责进行键盘扫描,通过GPIO中断扫描4位独立按键和4×4矩阵键盘的键值,将对应键值的显示码按照循环的方式写入OCM中的显示缓冲区。
#define DISPLAY_BUF ((volatile unsigned char *)(0xFFFF0000U))
//显示缓冲区按字节访问限定为8字节
我将显示缓冲区的8个字节定义在了OCM的最后一块儿中。
在没有做其他设置的情况下ZYNQ上电后地址空间分别如上图所示。OCM共256KB按照64KB分为4块,其中前三块在SDK中表述为RAM0占192KB处于地址空间的最开头和DDR共用地址空间,最后一块64KB处于地址空间的最后。ZYNQ的DDR固定占地址空间的最开头1GB字节因而ZYNQ的DDR最大容量就只有1GB。为了避开OCM从上图可知实际使用的DDR只有1023MB(最开头的1MB被保留避开OCM的前三块)。从0x40000000到0xDFFFFFFF的2GB空间留给了自定义IP或者其他IP的寄存器,从BSP的xparameters.h可以看出在PL部分添加的IP其基址都是从0x40000000开始的,而ZYNQ自己的寄存器则从0xE0000000开始编制,具体寄存器内容请查阅UG585的附录B Register Details。其实Standalone作为基础的BSP所作的工作都是在通过指针访问各个寄存器而已,在不考虑安全性的前提下可以完全不用BSP直接操作寄存器对ZYNQ进行操作。
下面是程序运行后的几张效果图:
由于工程比较大,这里就只把和OCM相关的代码贴出来和大家分享了。
在CPU0和CPU1的C源文件中均在相同的地址定义OCM的8字节为显示缓冲区:
#define DISPLAY_BUF ((volatile unsigned char *)(0xFFFF0000U))
//显示缓冲区按字节访问限定为8字节该定义应在两个CPU的源文件中均定义
CPU0中唤醒CPU1的地址定义
#define CPU1STARTADR 0x20000000U//CPU1的DDR起始地址从512MB字节开始
#define CPU1_START_UP_REG 0xFFFFFFF0U//用来存储上面地址的指针地址
CPU0中禁止OCM的Cache属性和唤醒CPU1的代码
//Disable cache on OCM
Xil_SetTlbAttributes(0xFFFF0000,0x14de2);
// S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
print("CPU0: writing startaddress for cpu1\n\r");
Xil_Out32(CPU1_START_UP_REG, CPU1STARTADR);
//该函数需包含xil_io.h
//CPU1STARTADR=0xFFFFFFF0, CPU1STARTADR=0x20000000);
dmb(); //waits until write has finished
print("CPU0: sending the SEV to wake up CPU1\n\r");
sev();
dmb(); //waits until write has finished
CPU1中禁止OCM的Cache属性
//Disable cache on OCM
Xil_SetTlbAttributes(0xFFFF0000,0x14de2);
// S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
CPU1扫描到按键后填充显示缓冲区的代码
TimerExpired=(TimerExpired+1)%BUF_SIZE;
//循环移动显示缓冲区的写入指针
DISPLAY_BUF[TimerExpired]=Display_Code[KEY_Value-1];
//在指针位置写入键值
DISPLAY_BUF[BUF_SIZE]=TimerExpired;
//记录新按键键值的指针位置
xil_printf("CPU1: New key has been pushed ,update the display buffer.\r\n");
CPU0在定时器中断中读取显示缓冲刷新显示的代码
XScuTimer_ClearInterruptStatus(TimerInstancePtr);
//清除定时器中断标志
TimerExpired=(TimerExpired+1)%4;
//循环移动显示缓冲区读取指针
XGpio_DiscreteWrite(&Gpio_LEDS, SEG_CHANNEL,
emerge_ledbus(DISPLAY_BUF[TimerExpired],
DISPLAY_BUF[TimerExpired+4],Selected_Code[TimerExpired]));
//将对应缓冲区内容送到7段数码管上
XGpio_DiscreteWrite(&Gpio_LEDS, LED_CHANNEL, DISPLAY_BUF[DISPLAY_BUF[BUF_SIZE]]);
//将最新一个按键的键值送到LED灯上
接下来是验证视频
如有问题欢迎评论留言或关注码峰社嵌入式群541931432进行讨论。