ZYNQ有两个CPU?(二)——OCM共享内存

在上一篇文章中,我们搭建了一个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进行讨论。

<think>好的,用户之前询问了ZYNQ中的IRQ是什么意思,现在接着问ZYNQ驱动该怎么理解。我需要先回顾一下之前的对话,确保回答的连贯性。用户可能是在学习ZYNQ开发,或者是做相关项目,需要理解驱动层面的知识。 首先,用户可能对ZYNQ的整体架构不太熟悉,尤其是处理系统(PS)和可编程逻辑(PL)的交互。他们可能想知道驱动在ZYNQ中的作用,以及如何编写或配置驱动来操作硬件。因此,我需要从ZYNQ的架构入手,解释驱动的角色,以及PS和PL部分驱动的关系。 接下来,用户可能对驱动开发的具体步骤感兴趣,比如如何通过Vivado配置硬件,如何在Linux或裸机环境下编写驱动程序。需要区分裸机驱动和Linux驱动的不同,特别是设备树(Device Tree)的作用,以及用户空间与内核空间的交互。 另外,用户可能存在的误区是认为ZYNQ驱动和普通嵌入式驱动完全一样,需要指出其特殊性,比如PL部分需要生成IP核,以及硬件配置对驱动的影响。还要提到常见的驱动类型,如GPIO、DMA、中断等,并结合之前关于IRQ的知识,说明中断在驱动中的应用。 需要确保回答结构清晰,分点说明,避免使用过于专业的术语,但又要准确。同时,要给出实际应用场景,帮助用户理解驱动的实际用途,比如工业控制或图像处理中的例子。最后,总结驱动的核心作用,强调软硬件协同的重要性,呼应之前的IRQ机制,保持连贯。</think>在Xilinx的ZYNQ SoC(System-on-Chip)中,**ZYNQ驱动**(Driver)是指用于控制和管理ZYNQ硬件资源的软件模块,它充当了**硬件(PS端外设或PL端逻辑)与软件(操作系统或裸机程序)之间的桥梁**。ZYNQ的独特之处在于其结合了ARM处理器(PS端)和FPGA逻辑(PL端),因此驱动设计需要兼顾两者的协同工作。 --- ### **ZYNQ驱动的核心概念** 1. **驱动的本质** ZYNQ驱动是软件代码,通过操作寄存器、中断、DMA等硬件资源,实现以下功能: - **硬件初始化**(如配置时钟、引脚复用、中断控制器)。 - **数据传输**(如通过UART发送数据、从PL端读取传感器值)。 - **事件响应**(如处理GPIO按键中断、PL端完成信号)。 2. **PS端与PL端的驱动差异** - **PS端驱动**: 控制PS端内置硬件外设(如GPIO、UART、SPI、DMA等),通常基于标准接口(如Linux内核驱动或裸机库函数)。 - **PL端驱动**: 控制PL端自定义逻辑(如自定义IP核或加速器),需通过AXI总线与PS通信,驱动需自行实现(如操作AXI-Lite寄存器或处理中断)。 --- ### **ZYNQ驱动的实现方式** #### **1. 裸机驱动(Bare-Metal)** - **适用场景**:无操作系统的简单嵌入式应用(如实时控制)。 - **实现方法**: - 直接操作PS外设的寄存器(参考《ZYNQ Technical Reference Manual》)。 - 使用Xilinx提供的库(如`Xilinx Standalone Library`)。 - 示例:通过GPIO驱动点亮LED: ```c #include "xgpio.h" XGpio gpio; XGpio_Initialize(&gpio, XPAR_GPIO_0_DEVICE_ID); // 初始化GPIO XGpio_SetDataDirection(&gpio, 1, 0x00); // 设置引脚为输出 XGpio_DiscreteWrite(&gpio, 1, 0x01); // 输出高电平 ``` #### **2. Linux内核驱动** - **适用场景**:复杂系统(如基于Linux的应用,需多任务管理)。 - **实现方法**: - 编写内核模块,通过`Platform Device`和`Device Tree`绑定硬件资源。 - 示例:PL端自定义IP核的驱动框架: ```c static int my_driver_probe(struct platform_device *pdev) { // 1. 从设备树获取寄存器地址和中断号 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); reg_base = devm_ioremap_resource(&pdev->dev, res); irq = platform_get_irq(pdev, 0); // 2. 注册中断处理函数 request_irq(irq, my_isr, IRQF_TRIGGER_RISING, "my_ip", NULL); // 3. 初始化硬件 writew(0x01, reg_base + CONTROL_REG); return 0; } static const struct of_device_id my_driver_ids[] = { { .compatible = "xlnx,my-custom-ip" }, // 设备树中的兼容性标识 { } }; static struct platform_driver my_driver = { .probe = my_driver_probe, .driver = { .name = "my_driver", .of_match_table = my_driver_ids }, }; module_platform_driver(my_driver); ``` #### **3. PL端逻辑的驱动关键点** - **AXI总线交互**: PL端IP核通常通过AXI-Lite或AXI-Stream与PS通信,驱动需读写AXI寄存器或管理DMA传输。 - **中断处理**: PL端触发中断后,PS需在驱动中注册ISR(中断服务程序),清除中断标志并处理数据(见前文IRQ机制)。 --- ### **ZYNQ驱动的开发流程** 1. **硬件配置** - 在Vivado中设计PL端逻辑,生成IP核并分配AXI地址空间。 - 导出硬件描述文件(`.hdf`或`.xsa`),供软件开发工具(如Vitis或Petalinux)使用。 2. **软件环境搭建** - **裸机**:使用Vitis创建工程,包含驱动库和应用程序。 - **Linux**:使用Petalinux生成设备树,并编写内核驱动。 3. **驱动调试** - 通过JTAG或UART查看寄存器状态。 - 使用`XSCT`(Xilinx System Console)动态调试硬件。 --- ### **典型应用场景** 1. **PL加速器控制** - 驱动负责将数据从PS内存传输到PL端加速器,并接收计算结果(如图像处理、加密解密)。 2. **混合系统通信** - 通过AXI-DMA驱动实现PS与PL高速数据流传输(如ADC采样数据实时处理)。 3. **多外设管理** - 同时驱动多个外设(如以太网、USB、摄像头),需协调中断和DMA资源。 --- ### **总结** ZYNQ驱动是软硬件协同设计的核心环节: - **对PS外设**:需遵循标准接口(如Linux驱动模型)。 - **对PL逻辑**:需自定义驱动,紧密结合硬件设计。 - **核心挑战**:平衡性能与灵活性,确保PS和PL高效通信(如通过中断、DMA或共享内存)。 合理设计驱动可以充分发挥ZYNQ的异构计算优势,适用于工业控制、通信、AI边缘计算等领域。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值