ZYNQ 图像处理之千兆网传(一)【寄存器级操作】

    xiaoxiao2023-10-24  155

     

     

     最近在做图像处理和加速,芯片是xilinx 的 zynq xc7z020。需要用到千兆网,由于网上很多都是直接移植lwip,但是个人感觉用着不爽(个人感觉),不能实现随心所欲的控制,代码量又多,所以自己研究了一下,直接操作寄存器,实现千兆网通信。以下是个人的理解及操作,仅供大家参考。

    要实现千兆网,在xc7z020需要做到以下几点:

    目录

    要实现千兆网,在xc7z020需要做到以下几点:

    一 配置网络寄存器

    二 配置PYH 芯片

    三 配置中断寄存器(如果要用中断的话)

    四 创建Descriptors

    以下效果图,可以达到970多Mpbs的速度


    一 配置网络寄存器

    在配置网络寄存器之前,先对控制器进行复位操作,代码如下

    //Initialize the Controller void Initialize_Controller() { //1. Clear the Network Control register. Write 0x0 to gem.net_ctrl register. Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_NWCTRL_OFFSET,0x00000000); //2. Clear the Statistics registers. Write a 1 to gem.net_ctrl[clear_stat_regs]. Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_NWCTRL_OFFSET,XEMACPS_NWCTRL_STATCLR_MASK); //3. Clear the Status registers. Write a 1 to the Status registers. gem.rx_status = 0x0F andgem.tx_status = 0xFF. Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_RXSR_OFFSET,0x0f); Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_TXSR_OFFSET,0xff); //4. Disable all interrupts. Write 0x7FF_FEFF to the gem.intr_dis register. Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_IDR_OFFSET,0x7FFFEFF); //5. Clear the buffer queues. Write 0x0 to the gem.rx_qbar and gem.tx_qbar registers. Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_RXQBASE_OFFSET,0x00000000); Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_TXQBASE_OFFSET,0x00000000); }

    接下来 寄存器配置

    //Configure the Controller void Configure_Controller() { unsigned int cfg_value; unsigned int Divisor = 0x07;//<< XEMACPS_NWCFG_MDC_SHIFT_MASK //1. Program the Network Configuration register (gem.net_cfg) cfg_value = XEMACPS_NWCFG_FDEN_MASK | XEMACPS_NWCFG_1000_MASK | XEMACPS_NWCFG_LENERRDSCRD_MASK | XEMACPS_NWCFG_RXCHKSUMEN_MASK | XEMACPS_NWCFG_PAUSEEN_MASK | XEMACPS_NWCFG_FCSREM_MASK | XEMACPS_NWCFG_UCASTHASHEN_MASK | (Divisor << XEMACPS_NWCFG_MDC_SHIFT_MASK); Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_NWCFG_OFFSET,cfg_value); xil_printf("cfg_value = %x \r\n",cfg_value); //2. Set the MAC address Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR+XEMACPS_LADDR1L_OFFSET,LOCAL_MAC0); Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR+XEMACPS_LADDR1H_OFFSET,LOCAL_MAC1); xil_printf("LADDR1L = %x \r\n",Xil_In32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_LADDR1L_OFFSET)); xil_printf("LADDR1H = %x \r\n",Xil_In32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_LADDR1H_OFFSET)); //3. Program the DMA Configuration register cfg_value = XEMACPS_DMACR_RXBUF_MASK | XEMACPS_DMACR_RXSIZE_MASK | XEMACPS_DMACR_TXSIZE_MASK | XEMACPS_DMACR_DISC_WHEN_NO_AHB | XEMACPS_DMACR_TCPCKSUM_MASK | XEMACPS_DMACR_ENDIAN_MASK | XEMACPS_DMACR_INCR16_AHB_BURST; xil_printf("cfg_value_dma = %x \r\n",cfg_value); Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_DMACR_OFFSET,cfg_value); //4. Program the Network Control Register (gem.net_ctrl) cfg_value = Xil_In32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_NWCTRL_OFFSET); cfg_value = cfg_value | XEMACPS_NWCTRL_MDEN_MASK; //+ XEMACPS_NWCTRL_TXEN_MASK //+ XEMACPS_NWCTRL_RXEN_MASK; Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_NWCTRL_OFFSET,cfg_value); }

    中断寄存器配置

    void Configure_Interrupts() { //Enable interrupts /* Enable TX and RX interrupts */ u32 mask = XEMACPS_IXR_TX_ERR_MASK |XEMACPS_IXR_RX_ERR_MASK |XEMACPS_IXR_FRAMERX_MASK |XEMACPS_IXR_TXCOMPL_MASK; Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_IER_OFFSET,mask); }

     

    二 配置PYH 芯片

    PYH 芯片配置,需要根据芯片的手册,我用的是KSZ9031RNX-EVA,因为代码比较多,这里的代码就不贴了,可以根据你使用的芯片型号,在网上找对应的配置程序。

     

    三 配置中断寄存器(如果需要用中断的话)

    中断对于嵌入式来说,是一个比较复杂的梗,首先要开启系统级中断(PS中断系统),其次要使能个体中断(eth0中断),这里把代码贴出来,如果用需要用到的朋友可以作为简单的参考。

    系统级中断控制代码(PS中断系统)

    //定义中断向量表结构体,Handler为函数,Data为函数Handler的参数 typedef struct { Xil_ExceptionHandler Handler; void *Data; } XExc_VectorTableEntry; //申明中断向量表 extern XExc_VectorTableEntry XExc_VectorTable[]; // 复位中断 void IntAllDisabled(void) { /* Remove current CPU from interrupt target register */ Xil_Out32(ICDICER0, 0xffffffff);//不使能中断 Xil_Out32(ICDICER1, 0xffffffff);//不使能中断 Xil_Out32(ICDICER2, 0xffffffff);//不使能中断 //disables the distributor Xil_Out32(ICDDCR,0x00); } //分发中断 void DistributorInit(void) { u32 reg_addr; reg_addr = ICDICFR0; u32 default_priority = 0xa0a0a0a0; //清除触发模式 for(char i=0;i<6;i++) { reg_addr += i*4; Xil_Out32(reg_addr,0x00000000); } // 设置default 优先级 reg_addr = ICDIPR0; for(char i=0;i<24;i++) { reg_addr += i*4; Xil_Out32(reg_addr,default_priority); } // enables the distributor Xil_Out32(ICDDCR,0x01); } // CPU 中断设置 void CPU_Init(void) { //中断优先级都是A0,优先级高于F0,CPU可接受这些中断 Xil_Out32(ICCPMR,0xF0); //处理器能接收IRQ,使能中断信号连接到处理器 Xil_Out32(ICCICR,0x07); }

    个体中断使能(eth0中断)

    void Ethernet0_Int_Init(void) { Xil_Out32(ICDICER1, 0x400000);//不使能 #54 Xil_Out32(ICDICFR3, 0x1000);//电平触发 Xil_Out32(ICDIPR13, 0xA00000);//优先级A0 Xil_Out32(ICDIPTR13,0x010000);//处理器为CPU1 Xil_Out32(ICDISER1, 0x400000);//使能 #54 }

    初始化中断的代码

    void Int_init(void) { u32 data = 30; Xil_ExceptionInit(); XExc_VectorTable[5].Handler =(Xil_ExceptionHandler)InterruptHandler_IRQ;// 中断处理函数 XExc_VectorTable[5].Data = data; //initialize IntAllDisabled(); DistributorInit(); CPU_Init(); Ethernet0_Int_Init(); xil_printf("begin\r\n"); Xil_ExceptionEnable(); }

     

    四 创建Descriptors

    要实现千兆网的收发需要一个叫 Buffer Descriptors List 的东西,在这里暂且把它叫做“缓存描述表”,收发各需要一个。这里只单独讨论发的时候,其实“缓存描述表”可以理解为结构体(本人是按结构体来申请和定义),这个结构体里面包含多个Buffer Descriptors(简称BD),每个BD包含两个word(word0 和 word1, 一个word = 4bytes)。word0 里存放了一个地址,指向需要发送数据的起始地址。word1是一些状态位。对于更详细的描述,可以看 《ug585-Zynq-7000-TRM(Technical Reference Manual).pdf》手册p495的 Rx Buffer描述。

    以下是代码

    // Descriptor strcut struct DESC_DEF { u32 word0; u32 word1; }; // Descriptor List strcut struct DESC_LIST_DEF { struct DESC_DEF Descriptor[32]; }; struct DESC_LIST_DEF Tx_List __attribute__ ((aligned(64))); void BdBaseAddr(struct DESC_LIST_DEF *p_List) { p_List->Descriptor[0].word0 = (u32)p_header->eth_remote_mac;// 以太网数据报头 p_List->Descriptor[1].word0 = eth0_ctrl.frame_addr[0];// 需要发送的数据的首地址 //write gem.tx_qbar register Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_TXQBASE_OFFSET,(u32)Tx_List.Descriptor); Xil_DCacheFlushRange((UINTPTR)(u32)p_header->eth_remote_mac, sizeof(struct HEAD_DEF)); } void BdSetLength(struct DESC_LIST_DEF *p_List) { p_List->Descriptor[0].word1 = sizeof(struct HEAD_DEF);//p_header->ip_IHL*5 + 14 + 8; p_List->Descriptor[1].word1 = PAYLOAD_BYTES; } void BdClearTxUsed(struct DESC_LIST_DEF *p_List) { p_List->Descriptor[0].word1 &=(~XEMACPS_TXBUF_USED_MASK); p_List->Descriptor[1].word1 &=(~XEMACPS_TXBUF_USED_MASK); } void BdSetLast(struct DESC_LIST_DEF *p_List) { p_List->Descriptor[1].word1 = p_List->Descriptor[1].word1 | XEMACPS_TXBUF_LAST_MASK |XEMACPS_TXBUF_WRAP_MASK; }

     

     

    到这里,初始化阶段几乎算是完成了,只是以太网数据报头这些需要自己去组装,这里涉及到以太网的知识,知识点比较多就不一一表述了,大家可以网上查

     

    整体的初始化代码

    void eth0_init() { Initialize_Controller(); Configure_Controller(); init_phy(); Configure_Interrupts(); BdBaseAddr(&Tx_List); BdSetLength(&Tx_List); BdSetLast(&Tx_List); }

     

    接下来是发送代码

    void Enable_Controller() { u32 value; //1. Enable the Transmitter. Write a 1 to gem.net_ctrl[tx_en] //2. Enable the Receiver. Write a 1 to gem.net_ctrl[rx_en]. value = Xil_In32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_NWCTRL_OFFSET); value = value | XEMACPS_NWCTRL_TXEN_MASK | XEMACPS_NWCTRL_RXEN_MASK; Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_NWCTRL_OFFSET,value); } void start_trasnission() { u32 value; value = Xil_In32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_NWCTRL_OFFSET); Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_NWCTRL_OFFSET,value |XEMACPS_NWCTRL_STARTTX_MASK); } // 发送一帧图像 void Trans_Frames() { u32 txsr; //u32 *p_dptr; txsr = Xil_In32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_TXSR_OFFSET); txsr &= XEMACPS_TXSR_TXCOMPL_MASK; if(eth0_ctrl.flg | txsr) { eth0_ctrl.flg = 0; // clear trans compelet bit Xil_Out32(XEMACPS_GEM0_BAYSE_ADDR + XEMACPS_TXSR_OFFSET,(txsr & XEMACPS_TXSR_TXCOMPL_MASK)); BdClearTxUsed(&Tx_List); u32 value = sizeof(struct DESC_LIST_DEF); Xil_DCacheFlushRange((UINTPTR)Tx_List.Descriptor, value); Enable_Controller(); start_trasnission(); } }

    由于篇幅,里面还有很多知识点没有写,比如以太网数据结构、网络字节序、crc验证等,有需要的朋友可以到网上查询。将来也许我会把这些知识点作为一个系列写出来,不过这是以后的事了。

    今天这个只是实现从相机发送数据到PC端,至于从PC端发到相机,留在下一节分享。

    由于个人不太爱写注释(很坏很坏的习惯),以上代码注释比较少,可能阅读起来不是很爽,不过好在代码量不是很多,如果此代码有用得上的朋友,若有疑问的地方可以留言,我会尽量回复。

    以下效果图,可以达到970多Mpbs的速度

     

    因能力所限,难免有理解不到位之处,欢迎大家批评指正

    更多分享,请关注微信公众号:FPGA历险记

     

    最新回复(0)