本帖最后由 正点原子运营官 于 2021-1-11 15:10 编辑
1)实验平台:正点原子达芬奇FPGA开发板
2)购买链接:https://detail.tmall.com/item.htm?id=624335496505
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_dafenqi.html
4) 正点原子官方B站:https://space.bilibili.com/394620890
5)对正点原子FPGA感兴趣的同学可以加群讨论:905624739
6)关注正点原子公众号,获取最新资料
第十一章RGB LCD彩条显示实验
在《达芬奇之FPGA开发指南》中“RGB_LCD彩条显示实验”介绍了如何在LCD液晶显示屏上显示彩条,本章我们将使用MicroBlaze软核处理器在RGB TFT-LCD液晶屏上显示彩条。本章包括以下几个部分:
1111.1 简介
11.2 实验任务
11.3 硬件设计
11.4 软件设计
11.5 下载验证
11.1简介
本章将通过MicroBlaze处理器实现RGB LCD的彩条显示,其实现思路是:MicroBlaze处理器产生彩条数据,并通过AXI4接口将数据写入外部DDR3中。FPGA负责读取外部DDR3中的彩条数据,然后将数据传输给内部LCD屏驱动模块,以驱动外部RGB LCD屏显示彩条。MicroBlaze只进行DDR3的写操作,而FPGA负责DDR3的读操作。我们知道,在MicroBlaze的开发中,无论是对DDR3进行写操作或者读操作,都是将数据通过AXI4接口传输到DDR3控制器,然后DDR3控制器和外部DDR3做数据交互,因此需要我们熟悉掌握AX14的接口协议和时序,才能实现对DDR3的读写操作。
AXI4接口总共有三种类型,它们分别是AXI4(AXI4-Full)、AXI4-Lite和AXI4-Stream,不同的接口类型适用于不同的应用场景,下面对这三个接口做简要说明。
AXI4-Full:最高性能的接口,适合存储器映射的通信,支持每个地址阶段最高256个数据传输周期的批量传输,最适合于更需要持久、高速性能的IP。
AXI4-Lite:AXI4-Full接口的轻量级版本,用于存储器映射的单次数据通信会话。这个版本的好处是简化了的接口占用较少的逻辑部分面积。这个版本不支持批量数据,只支持每次传输单个数据,适合于需要最小硬件消耗的较低性能的IP。
AXI4-Stream:它没有地址阶段,因此不是存储器映射。为流式数据的传输定义了单个通道,支持无数量限制的批量传输,最适合于需要持续固定数据流的应用。连接只能是从主机到从机,所以如果需要双向传输的话,两个外围设备都必须是主机/从机兼容类型的。
本次实验需要通过AXI4接口源源不断地从DDR3中读出彩条数据,因此数据量较大,由于AXI4-Lite速度稍慢,不适合本实验;AX14-Stream虽然本身占用资源不多,但是为了和AXI互联模块连接,需要接AXI DMA或者其它模块,占的资源反而更多,而达芬奇开发板资源有限,因此AXI4-Stream接口也不适合;AXI4(AXI4-Full)接口无论在资源占用还是速度方面均能满足本实验的需求,故本实验采用的接口为AXI4。
AXI4协议具有5个独立的通道,分别为:读地址通道、读数据通道、写地址通道、写数据通道和写响应通道,通道之间相互独立且存在差别。通信是由主机发起的,主机可以对从机进行数据的读或写操作。每次读或写操作都需要相应的读地址通道或写地址通道传输一个地址。数据传输使用写数据通道来实现主机到从机的写数据传输,数据传输使读数据通道用来实现从机到主机的读数据传输。下面以AXI4 IP核为例,详细介绍AXI4协议的各通道和通道接口。
在本实验中FPGA从外部DDR3中读取数据,可视为主机,所以在例化、封装IP时应选择“Master”作为主机,接口名字以m_为前缀(具体步骤会在本章中详细描述,我们先直接拿来为大家讲解相关的通道和接口)。AXI4 IP核未展时开如下图所示:
图 11.1.1 未展开时接口
上图红标1处为IP核的时钟和复位信号,红标2处M_AXI为通道接口,其他为数据验证信号。下面展开红标2处,介绍M_AXI接口,如下图所示:
图 11.1.2 展开时通道和接口
在上图中,以m_axi_aw_为前缀的接口属于写地址通道;以m_axi_w_为前缀的接口属于写数据通道;以m_axi_b_为前缀的接口属于写响应通道;以m_axi_ar_为前缀的接口属于读地址通道;以m_axi_r_为前缀的接口属于读数据通道。
写地址通道包含的信号及信号含义如下表所示:
表11.1.1 通道信号
写数据通道包含的信号及信号含义如下表所示:
表11.1.2 通道信号
写响应通道包含的信号及信号含义如下表所示:
表11.1.3 通道信号
读地址通道包含的信号及信号含义如下表所示:
表11.1.4 通道信号
表11.1.5 通道信号
在进行数据传输的过程中,传输通道均使用valid/ready信号对传输过程的地址、数据、控制信号进行握手。使用双向握手机制,valid和ready都有效的时候表示握手成功。下面将以地址通道为例介绍几种常见的握手方式。
valid在ready前有效:主机先给出数据和控制信息,同时驱动valid为高电平。一旦主机驱动valid为高,地址将保持不变,直到从机驱动ready信号为高。一旦从机驱动ready为高,则握手成功,在时钟上升沿T3时刻开始进行地址。如下图所示:
图 11.1.3 valid在ready之前
valid在ready后有效:从机在主机驱动valid前,就驱动了ready信号为高。一旦主机确定valid信号为高,则握手成功,在T3时刻开始传输地址。如下图所示:
图 11.1.4 valid在ready之后
valid和ready同时有效:主机驱动valid和从机驱动ready同时发生,在T2时刻开始传输地址。如下图所示:
图 11.1.5 valid和ready同时有效
AXI4读通道结构如下图所示:
图 11.1.6读通道结构
由上图可知,主机首先传递地址和控制信息给从机,之后从机将有效的地址上对应的读数据批量读数据发送给主机。在突发读过程中,读通道上典型的信号交互过程如下图所示:
图 11.1.7 突发读信号交互
由上图可知在读地址通道中:主机在T0时间段内先提供地址m_axi_araddr,同时将m_axi_arvalid拉高;从机在T1时间段内将m_axi_arready拉高,表明从机可以接收地址;m_axi_aradd和m_axi_arready均为高时则握手成功,在T2时钟上升沿时刻,开始传输地址。
在读数据通道中:主机在T3时间段内将m_axi_rready拉高,表明主机可以接收数据;从机在T5时间段内提供读数据同时将m_axi_rvalid拉高,表明数据D0有效可以读出;m_axi_rvalid和m_axi_rready均为高时则握手成功,在T6时钟上升沿时刻,开始传输数据D0。同理在T9、T10时钟上升沿时刻,分别输数据D1、D2。在T13时钟上升沿时刻,从机拉高m_axi_rlast,表明D3是此次突发最后一个需要传输的数据。
AXI4写通道结构如下图所示:
图 11.1.8写通道结构
由上图可知,主机首先传递地址和控制信息,再发送批量写数据给从机。从机接收完所有的数据后,从机发送一个写响应信号给主机。突发写过程中写通道上典型的信号交互过程如下图所示:
图 11.1.9 突发写信号交互
由上图可知在写地址通道中:主机在T0时间段内先提供地址m_axi_awaddr,同时将m_axi_awvalid拉高;从机在T1时间段内将m_axi_awready拉高,表明从机可以接收地址;m_axi_awaddr和m_axi_awready均为高时则握手成功,在T2时钟上升沿时刻,开始传输地址。
在写数据通道中:主机在T2时间段内先提供写数据D0,同时将m_axi_awvalid拉高,表明该数据有效;从机在T3时间段内将m_axi_wready拉高,表明从机可以接收数据;m_axi_awvalid和m_axi_wready均为高时则握手成功,T4时钟上升沿时刻,开始传输数据D0。同理,在T6、T8、T9时钟上升沿时刻,分别传输数据D1、D2、D3;
在写响应通道中:主机在T2时间段内将m_axi_bready拉高,表示主机可以接收来自从机的写响应信号;从机接收此次突发最后一个传输的写数据D3时,在T9时间段提供些响应信号OKAY,同时将m_axi_bvalid拉高,表明写响应信号有效。m_axi_bvalid和m_axi_bready均为高时则握手成功,T10时钟上升沿时刻,开始传输OKAY。注意:写响应信号必须跟随最后一次突发的写传输数据。
多个具有AXI4协议的设备或模块可以通过互联模块进行数据的交互。如下图所示:
图 11.1.10 多机互联
11.2实验任务
本章的实验任务是Microblaze处理器向DDR3中写彩条数据,然后通过自定义的带AXI接口的IP核将彩条数据从DDR3中读出来,并显示在RGB LCD液晶屏上(支持正点原子推出的所有RGB LCD屏)。
11.3硬件设计
根据简介及实验任务可知,彩条数据先由MicroBlaze写入DDR3,之后被读出并在LCD RGB液晶显示屏上以彩条的形式呈现出来。由此我们可以画出本次实验的系统框图,如下图所示:
图 11.3.1系统框图
在上图中,LCD_RGB_TOP、AXI4_DDR_READ、AXI4_Lite_COMM都是自定义的IP核。
具体的数据流走向:LCD_RGB_TOP首先识别屏幕ID(LCD_ID),MicroBlaze软核根据屏幕ID将对应的彩条数据写入DDR3中,接着AXI4_DDR_READ通过AXI4协议并根据屏幕ID将彩条数据读出至FIFO缓存,数据最后经过驱动模块LCD_RGB_TOP后在LCD屏幕上显示出彩条。
LCD_RGB_TOP模块是由《达芬奇FPGA开发指南》之“RGB_LCD彩条显示实验”的代码模块改动后封装而来,具体的修改会在之后详细说明。此模块用于识别外部接入的的RGB888 TFT-LCD屏幕产生屏幕ID,并将屏幕ID传递给AXI4_Lite_COMM模块和AXI4_DDR_READ模块;另外接收并且驱动被读出的彩条数据在屏幕上展示彩条。
AXI4_Lite_COMM是自定义的带有AXI4接口的IP核,用来传输外设的参数,在本实验中传输的是屏幕ID。AXI4_Lite_COMM可使用AXI GPIO IP核进行代替,但AXI4_Lite_COMM的优势在于今后能连接更多的外设进行更多的参数传输,不用为每一个外设都例化AXI GPIO IP核,因此可以大大减少资源使用量。考虑到达芬奇的资源量以及后续工程的开发,建议使用AXI4_Lite_COMM IP核进行参数传输。
AXI4_DDR_READ IP核是先例化一个带有AXI4接口IP核,然后根据我们的需求修改后再进行封装而成的IP核。该IP核根据屏幕ID,确定DDR3的帧缓存空间大小,通过AXI4接口读出显示数据并传输给后级FIFO。注意,因为本实验只需要读取DDR3中的彩条数据,所以在修改时只启用读功能即可,无需启用写功能。如果在后续的开发中,比如使用摄像头采集图像并将图像数据写入DDR3,最后在LCD屏显示实时图像,这时此IP核的写功能也要被启用。
在硬件环境搭建之前,先进行上述3个IP核的封装和自定义。
(1)AXI4_DDR_READ IP核的封装
首先创建axi4_ddr_read文件夹。打开Vivado软件,软件启动后在“Tasks”一栏选择“Manage IP”,然后点击“New IP Location…”,将工程定位在axi4_ddr_read下,点击“Finish”。接着在菜单栏中点击“Tools”,然后在下拉列表中选择“Create and Package New IP”,如下图所示:
图 11.3.2创建IP核
在弹出的对话框中点击“Next”,选择“Create a new AXI4 Peripheral”,点击“Next”,IP核命名为axi4_ddr_read,位置自动填充在ip_repo下,点击“Next”。如下图所示:
图 11.3.3 IP名称
将接口命名为“M_AXI”,接口类型选择“Full”(AXI4-Full),模式选择“Master”(本实验FPGA通过AXI4协议读取外部DDR3中的数据,所以选择Master作为主机),点击“Next”。如下图所示:
图 11.3.4接口设置
选择“Edit IP”,点击“Finish”。如下图所示:
图 11.3.5 Edit IP核
稍等片刻,软件就会创建一个IP核封装工程,工程包含两个具有AXI4协议完整读写功能的模块。工程如下图所示:
图 11.3.6 axi4_ddr_read IP核封装工程
工程的两个文件模块文件位置如下图所示:
图 11.3.7 模块文件位置
接下来用Notepad++打开两个文件,以便进行修改,依据实验任务可知主要修改点为:彩条数据是由MicroBlaze产生并写入DDR3中,不需要通过axi4_ddr_read写数据,因此代码可屏蔽写相关逻辑。axi4_ddr_read根据屏幕ID从DDR3读取彩条数据后并将彩条数据缓存到FIFO中,所以需要新增FIFO和屏幕ID的接口信号。本实验不再采用状态机的方式控制读功能。
为了代码的整洁和可读性,我们对源代码进行了修改和整理,只保留了突发读相关的代码。下面将解释说明修改后的AXI4协议实现读功能的逻辑。打开axi4_ddr_read_v1_0_M_AXI模块代码:
- 33 // Users to add ports here
- 34 input [15:0] lcd_id,
- 35 input [11:0] fifo_wr_cnt,
- 36 output fifo_wr_en,
- 37 output [31:0] fifo_wr_data,
如上所示,模块端口位置新增FIFO信号有fifo_wr_cnt、fifo_wr_en和fifo_wr_data,新增幕ID信号lcd_id。
- 570 //根据LCD ID,计算单帧图像突发的次数
- 571 always @ (posedge M_AXI_ACLK) begin
- 572 if(M_AXI_ARESETN == 1'b0) begin
- 573 burst_end_counter <= 12'd750;
- 574 end
- 575 else begin
- 576 case (lcd_id)
- 577 16'h4342 : burst_end_counter <= 12'd255; //480*272*16/32/256 = 255
- 578 16'h4384 : burst_end_counter <= 12'd750; //800*480*16/32/256 = 750
- 579 16'h7084 : burst_end_counter <= 12'd750; //800*480*16/32/256 = 750
- 580 16'h7016 : burst_end_counter <= 12'd1200; //1024*600*16/32/256 = 1200
- 581 16'h1018 : burst_end_counter <= 12'd2000; //1280*800*16/32/256 = 2000
- 582 default : burst_end_counter <= 12'd750;
- 583 endcase
- 584 end
- 585 end
本段代码,实现了由屏幕ID(lcd_id)计算单帧图像对应的读突发次数的功能。不同分辨率屏幕的lcd_id不同,对应的突发次数也不同,但计算方法是一样的,如800*480*16/32/256 = 750。其中,800*480为屏幕分辨率;16为RGB565格式的彩条数据位宽;32为突发传输数据的位宽;256为每次突发要传输的数据个数;最终得出计算结果分辨率为800*480的屏幕对应的突发次数为750次。
- 587 //根据FIFO数据个数,产生开始突发读信号
- 588 always @ (posedge M_AXI_ACLK) begin
- 589 if(M_AXI_ARESETN == 1'b0 || reads_done == 1'b1) begin
- 590 start_single_burst_read <= 1'b0;
- 591 end
- 592 else begin
- 593 if((~axi_arvalid) && (~burst_read_active) && (~start_single_burst_read)
- 594 && (fifo_wr_cnt <= 12'd256)) begin
- 595 start_single_burst_read <= 1'b1;
- 596 end
- 597 else begin
- 598 start_single_burst_read <= 1'b0;
- 599 end
- 600 end
- 601 end
本段代码用于驱动突发读开始信号tart_single_burst_read,第593至596行代码表明:在读地址通道的axi_arvalid为低、未正在进行突发读数据传输、缓存FIFO非满的时将该信号拉高,此时进行突发读数据传输,往缓存FIFO中写入数据。由于每次突发读传输的数据个数为256(C_M_AXI_BURST_LEN),因此一次突发读往缓存FIFO中写入256个数据。第一次突发读写入FIFO的256个数据,被读出1个后,FIFO非满,即可允许下一次突发读,此时假设极端情况,下一次突发读的数据全部存入FIFO,因此缓存FIFO至少容纳的数据个数为256-1+256 = 511。保险起见,在后续设计例化缓存FIFO时,其深度设置为1024或更大。
- 613 //读数据通道axi_rready
- 614 always @(posedge M_AXI_ACLK)
- 615 begin
- 616 if (M_AXI_ARESETN == 0 || reads_done == 1'b1 )
- 617 begin
- 618 axi_rready <= 1'b0;
- 619 end
- 622 else if (M_AXI_RVALID)
- 623 begin
- 624 if (M_AXI_RLAST && axi_rready)
- 625 begin
- 626 axi_rready <= 1'b0;
- 627 end
- 628 else
- 629 begin
- 630 axi_rready <= 1'b1;
- 631 end
- 632 end
- 634 end
本段代码驱动读数据通道的主机axi_rready信号,以此表明主机是否可以接收来自从机的读数据。第622行代码表示握手信号M_AXI_RVALID在axi_rready之前有效。第624行至627行代码表明本次突发读传输最后一个数据完成后将axi_rready拉低,主机不再接收数据。第629行至631行代码表明突发传输不是最后一个数据,axi_rready一直拉高。ILA中axi_rready信号波形如下图所示:
图 11.3.8 axi_rready信号波形
由上图可知,在读数据通道上,主机在从机RVALID拉高后将RREADY拉高,数据开始突发传输,之后在突发传输最后数据标志RLAST为低的过程中,突发数据一直有效。
- 637 //读地址通道axi_arvalid
- 638 always @(posedge M_AXI_ACLK)
- 639 begin
- 640 if (M_AXI_ARESETN == 0 || reads_done == 1'b1 )
- 641 begin
- 642 axi_arvalid <= 1'b0;
- 643 end
- 645 else if (~axi_arvalid && start_single_burst_read)
- 646 begin
- 647 axi_arvalid <= 1'b1;
- 648 end
- 649 else if (M_AXI_ARREADY && axi_arvalid)
- 650 begin
- 651 axi_arvalid <= 1'b0;
- 652 end
- 653 else
- 654 axi_arvalid <= axi_arvalid;
- 655 end
本段代码驱动读地址通道的主机axi_arvalid信号,表明地址是是否有效。第645行至648行代码表明未进行地址传输时允许进行传输。第649行至652行代码表明地址通道握手信号握手成功后,将axi_arvalid拉低,axi_arvalid持续一个周期的高状态。在ILA中观察axi_arvalid波形如下图所示:
图 11.3.9 axi_arvalid信号波形
由上图可知,在读地址通道上,主机在从机将ERREADY拉高后将AVALID拉高,此时地址有效,地址通道突发传输地址0x85016800。
- 658 //Next address after ARREADY indicates previous address acceptance
- 659 always @(posedge M_AXI_ACLK)
- 660 begin
- 661 if (M_AXI_ARESETN == 0 || reads_done == 1'b1 )
- 662 begin
- 663 axi_araddr <= 'b0;
- 664 end
- 665 else if (M_AXI_ARREADY && axi_arvalid)
- 666 begin
- 667 axi_araddr <= axi_araddr + burst_size_bytes;
- 668 end
- 669 else
- 670 axi_araddr <= axi_araddr;
- 671 end
本段代码用来计算在地址通道上的下次突发读数据的地址。burst_size_bytes为每次突发读256个数据的总字节数。
674 assign rnext = M_AXI_RVALID && axi_rready;
675 assign fifo_wr_en = rnext;
676 assign fifo_wr_data = {M_AXI_RDATA[15:0],M_AXI_RDATA[31:16]};
本段代码驱动缓存FIFO的端口信号,功能是将突发读数据写入到外部缓存FIFO中。由第674可知,数据通道的M_AXI_RVALID与axi_rready握手成功,此时突发读数据有效,可以被写入至外部缓存FIFO。握手信号与突发读传输数据波形如下图所示:
图 11.3.10 突发读数据有效
结合上图和FIFO的端口信号可知,在读数据通道上,当从机RREADY和主机RVALD同时为高时,即可将突发数据写入到缓存FIFO中。
- 683 //突发次数计数
- 684 always @(posedge M_AXI_ACLK)
- 685 begin
- 686 if (M_AXI_ARESETN == 0 || reads_done == 1'b1 )
- 687 begin
- 688 read_burst_counter <= 'b0;
- 689 end
- 690 else if (M_AXI_ARREADY && axi_arvalid)
- 691 begin
- 692 if (read_burst_counter <= burst_end_counter - 1'b1)
- 693 begin
- 694 read_burst_counter <= read_burst_counter + 1'b1;
- 695 end
- 696 end
- 697 else
- 698 read_burst_counter <= read_burst_counter;
- 699 end
本段代码用来统计已经突发的次数read_burst_counter,由第690至第696行代码可知,突发次数在地址通道握手信号握手成功时进行累加,一个有效地址对应一次突发。 - 704 //突发已经传输的的数据个数计数
- 705 always @(posedge M_AXI_ACLK)
- 706 begin
- 707 if (M_AXI_ARESETN == 0 || reads_done == 1'b1 || start_single_burst_read)
- 708 begin
- 709 read_index <= 0;
- 710 end
- 711 else if (rnext && (read_index != C_M_AXI_BURST_LEN-1))
- 712 begin
- 713 read_index <= read_index + 1;
- 714 end
- 715 else
- 716 read_index <= read_index;
- 717 end
本段代码用于对一次突发传输的数据个数进行统计。由第711行至714行代码可知,数据通道上握手信号握手成功时read_index进行累加,一次突发传输的数据最大个数为256(C_M_AXI_BURST_LEN值)。
- 722 //突发传输有效
- 723 always @(posedge M_AXI_ACLK)
- 724 begin
- 725 if (M_AXI_ARESETN == 0 || reads_done == 1'b1 || start_single_burst_read)
- 726 burst_read_active <= 0;
- 728 else if (start_single_burst_read)
- 729 burst_read_active <= 1'b1;
- 730 else if (M_AXI_RVALID && axi_rready && M_AXI_RLAST)
- 731 burst_read_active <= 0;
- 732 end
本段代码用于驱动burst_read_active信号,表明突发传输是否有效。由第728至729行代码可知,在突发读开始信号为高时将burst_read_active拉高,表明突发传输有效;由第730至731行代码可知,如果本次突发读传输的最后一个数据传输完毕,则将其拉低,表明传输无效,突发传输停止。 - 738 //所有次突发传输完成
- 739 always @(posedge M_AXI_ACLK)
- 740 begin
- 741 if (M_AXI_ARESETN == 0)
- 742 reads_done <= 1'b0;
- 743 else if (M_AXI_RVALID && axi_rready && (read_index == C_M_AXI_BURST_LEN-1) 744 &&(read_burst_counter == burst_end_counter))
- 745 reads_done <= 1'b1;
- 746 else
- 747 reads_done <= 1'b0;
- 748 end
本段代码用于判断所有突发次数的所有数据是否传输完成。第743行至745行代码可知,read_index == C_M_AXI_BURST_LEN-1用于判断一次突发所有数据传输完成,read_burst_counter == burst_end_counter用于判断突发的次数达到要求。比如,lcd_id为7084,需要750次突发,从第0次开始突发一直到第750次突发的第256个数据传输完成时,拉高reads_done,表明所有数据全部突发完毕。
修改完axi4_ddr_read_v1_0_M_AXI模块后注意要保存!在其例化时新增FIFO和屏幕ID的信号,屏蔽不需要的数据验证信号。顶层模块axi4_ddr_read_v1_0做对应的接口处理。修改后注意要保存!axi4_ddr_read_v1_0模块修改后如下图所示:
图 11.3.11 axi4_ddr_read_v1_0接口
至此,经过如上代码的修改,我们希望该代码能够完成只启用AXI4协议的读功能,并且将读出的数据缓存到了FIFO的功能。但是究竟能不能达到我们的理想,就需要封装该IP并且进行验证,那么接下来我们就进行IP核的封装。(注意,为避免大家在修改遗漏或错误导致后续封装的IP核不能正常使用,所以建议使用例程中的代码文件封装IP核或使用例程中封装好的IP核进行设计),例程中代码文件如下图所示:
图 11.3.12 例程中封装IP核代码文件
拷贝例程中的代码文件替换原来的文件后,回到之前的工程界面,并刷新代码,如下图所示:
图 11.3.13 刷新代码文件
此时右面的封装界面还是替换之前的代码文件对应的封装界面,需要关闭重新打开,重新打开后才是最新代码文件对应的封装界面。点击右上角Package IP红标处,关闭原来的封装界面,如下图所示:
图 11.3.14 关闭原来的封装界面
点击左侧菜单IP Catalog下的“Edit Package IP”,重新打开新代码文件对应的封装界面,如下图所示:
图 11.3.15 点击Edit Package IP
打开新的封装界面后,参考第十章IP核封装过程在Identification中设置信息、在Compatibility中设置兼容性、在File Groups更新文件组、在Customization Parameters刷新定制变量。刷新定制变量如下图所示:
图 11.3.16 刷新变量
紧接着在“Ports and interface”界面可以看到,红标1处的为通道接口;红标2处的为时钟和复位信号接口;红标3处为我们添加的屏幕ID和FIFO信号接口。如下图所示:
图 11.3.17 信号接口
在下图“Customization GUI”界面,红标1处为封装后的的IP核预览图;红标2处为IP核传递的参数,这些参数值可在例化IP核时依据具体的需求设置。
图11.3.18 IP核预览
最后在“Review and Package”界面点击“Package IP”封装IP,封装IP核完成之后,在今后的工程如果需要调用该IP核,可以直接拷贝文件夹axi4_ddr_read_1.0到工程中,并添加至工程即可。封装完成后的IP核在如下路径:
图11.3.19 IP核路径
(2)LCD_RGB_TOP IP核的封装
封装该IP核用到的模块文件如下:
图 11.3.20 模块文件
需要说明的是,这些模块是在《达芬奇之FPGA开发指南》中“RGB_LCD彩条显示实验”的模块代码文件基础上修改而来,每个模块可参考《达芬奇之FPGA开发指南》中的说明解释。
由于本实验在硬件设计是在Block Design中进行的,有些端口就会受到限制,比如双向接口lcd_rgb(LCD数据)。在Block Design中设计硬件时,双向接口会被综合程单向输出接口,所以我们就需要对lcd_rgb进行修改,由一个双向端口修改为三个接口:一个方向接口,一个输入接口和一个输出接口。修改后如下图所示:
图 11.3.21 修改后的lcd_rgb接口
上图红标1处,lcd_rgb_i是数据输入,主要向后级模块传输屏幕ID;lcd_rgb_o是传递给LCD屏的彩条输出数据,用来显示图像;lcd_rgb_t则用来控制数据流的方向。
另外,为了兼容以后可能使用摄像头的设计,如摄像头OV5640,因为OV5640采集的数据格式是RGB565(16位),所以本实验在软件设计中写入DDR的数据格式也必须是RGB565,因此在LCD驱动模块中输入的数据为RGB565格式。但是,由于使用的LCD屏幕是RGB888格式(24位),所以LCD驱动模块输出的数据就必须由RGB565格式转换为RGB888格式,转换代码如上图红标2所示。
图 11.3.22添加接口
默认接口类型为aximm_rtl,点击右侧红框内进行选择,如下图所示:
图 11.3.23 选择接口
在搜索框内“gpio”,点击选中GPIO接口类型“gpio_rtl”,点击“OK”,如下图所示:
图 11.3.24 搜索GPIO接口类型
接着就可以观察到接口已指定为GPIO类型,如下图所示:
图 11.3.25 接口指定GPIO类型
接口命名为“lcd_rgb”,映射如下图所示:
图 11.3.26 端口映射
映射完毕,即可在“Ports and interface”界面看到三个信号指定接口类型成功,如下图所示:
图 11.3.27 端口类型指定成功
由于系统错误地将lcd_rst和lcd_clk信号识别成了复位和时钟,所以在时钟和复位接口中要移除这两个信号,如下图所示:
图 11.3.28 移除错误识别的信号
最后IP核的接口如下图所示:
图 11.3.29 IP核接口
上图红标1处三个信号是gpio类型的端口信号,红标2处为时钟和复位,红标3处为其他接口。继续接着封装,封装过程参考第十章,这里不再赘述。最后封装成功后如下图所示:
图 11.3.30 lcd_rgb_top IP核
(3)自定义AXI4_Lite_COMM IP核
为了节省FPGA内部资源的使用,我们自定义一个具有AXI4-Lite协议的一个接口IP核,今后开发使用的外设参数都可连接在此IP核上,这样就不用为每个外设分配AXI GPIO接口资源。
首先创建axi4_lite_comm文件夹。打开Vivado软件,软件启动后在Tasks一栏选择“Manage IP”,点击“Next”,将工程定位在axi4_lite_comm下,点击“Next”,如下图所示:
图 11.3.31 新建工程
在菜单栏中点击“Tools”,然后在下拉列表中选择“Create and Package New IP”。接着选择“Create a new AXI4 Peripheral”,点击“Next”,命名为“axi4_lite_comm”,点击“Next”创建一个具有AXI4协议的IP核axi4_lite_comm。因为该IP核是用于传递外设的参数,可视为从机,故选择“Slave”;本实验传递外设参数lcd_id,低速即可,故接口类型选择“Lite”;寄存器数量与外设数量一一对应,可根据具体需求灵活设置,本实验设置为4。IP核信息如下图所示:
图 11.3.32 IP核信息
紧接着选择“Edit IP”,点击“Finish”。自动创建IP核封装工程如下图所示:
图 11.3.33 IP核封装工程
使用Notepad++打开上图红框内两个模块文件,路径如下图所示:
图 11.3.34 模块文件路径
由实验任务核系统框图可知,需要与该IP核连接的外设为屏幕,传递的参数是lcd_id,在axi4_lite_comm_v1_0_S_AXI文件中,所以我们将lcd_id存储在寄存器0位置,若今后有其他外设参数,可依次存储在寄存器1、2、3中,如下图所示:
图 11.3.35 lcd_id存储位置
lcd_id信号引入的过程就不再赘述了,请大家自行修改,修改完之后注意保存!(注意,为避免大家在修改时遗漏或者错误导致后续封装的IP核不能正常使用,所以建议使用例程中的代码文件封装IP核或使用例程中封装好的IP核进行设计)。回到之前的工程界面,并刷新代码,如下图所示:
图 11.3.36 刷新代码
刷新代码后先关闭原来的封装界面Package IP,接着点击左侧菜单IP Catalog下的“Edit Package IP”,打开新的封装界面。
参考第十章IP核封装过程在Identification中设置信息、在Compatibility中设置兼容性、在File Groups中更新文件组、在Customization Parameters刷新定制变量,在Ports and interface即可看到我们在代码中新加的端口信号lcd_id,如下图所示:
图 11.3.37 lcd_id
本IP核所有的端口不用指定类型,保持默认即可,之后的IP核封装过程不再赘述。今后工程需要调用此IP时,拷贝axi4_lite_comm_1.0文件夹即可。IP核封装完成后如下图所示:
图 11.3.38 axi4_lite_comm IP核
至此,本实验所有需要封装三个的IP核均已完成,接下来进行硬件设计。
打开《Hello World》实验的Vivado工程,打开后依次依次点击菜单栏的“File-> Project->Save As...”,将工程名改为“lcd_rgb_colorbar_top”。在工程目录下创建一个文件夹并命名为“ip_repo”,这个文件夹用以存放封装的IP核及相关模块代码文件,如下图所以:
图 11.3.39 ip_repo文件夹
将之前封装的三个IP核拷贝至此文件夹下(为保证后续实验的准确性,建议大家直接使用这里面的IP核进行后续设计),如下图所示:
图 11.3.40拷贝IP核
值得说明的是,拷贝IP核后,相关C代码库也会被拷贝进工程,在Vitis中进行嵌入式C代码编写的时候可在直接调用相应的库和函数。相关库如下图所示:
图 11.3.41相关库
接下来添加上述三个IP核,添加时一一为IP核选中路径,最后依次点击“APPLY”和“OK”,如下图所示:
图 11.3.42 添加IP核
添加完IP核之后,打开Block Design继续进行硬件设计。
首先双击clk_wiz_1 IP核,设置3个时钟分别为100MHz、200MHz和50MHz。100MHz给MicroBlaze软核处理器,200MHz给MIG核,50MHz给lcd_rgb_top模块。三个时钟如下图所示:
图 11.3.43 时钟
接着添加MIG IP核,该IP核的配置及添加具体步骤请参考第九章相关内容,这里不再赘述。注意:复位信号sys_rst连接到系统复位;时钟clk_ref_i和sys_clk_i连接到时钟200MHz;最后引出DDR接口;部分连线如下图所示:
图 11.3.44 MIG核端口连线
接下来添加我们自定义的3个IP核。
先添加LCD_RGB_TOP IP核,过程不再赘述。时钟接50MHz;sys_init_done连接MIG核的DDR3初始化完成接口init_calib_complete;复位信号接clk_wiz_1的locked;引出下图展示的六个端口;如下图所示:
图 11.3.45 lcd_rgb_top模块
接着添加AXI4_Lite_COMM IP核,过程不再赘述。点击自动连线“Run Connection Automation”,然后将lcd_id连接到lcd_rgb_top模块的lcd_id;引出下图管脚;如下图所示:
图 11.3.46 axi4_lite_comm模块
最后添加AXI4_DDR_READ IP核,点击自动连线,然后后将lcd_id连接到lcd_rgb_top的lcd_id;选中复位信号m_axi_aresetn,右键选择“Disconnect Pin”,手动连接至clk_wiz_1的locked(如果不手动修改连接为locked,将会导致屏幕上显示彩条出现偏移或不显示彩条的情况);部分连线如下图所示:
图 11.3.47 axi4_ddr_read模块
双击AXI4_DDR_READ IP核,参数C_M_AXI_TARGET_SLAVE_BASE_ADDR为DDR3读彩条起始地址,我们在此将值设置为0x85000000,如下图所示:
图 11.3.48 读写彩条起始地址
接下来例化一个FIFO,用来缓冲读取出来的彩条显示数据。
在“Basic”界面中:interface Type选择“Native”;Fifo implementation选择“Independent Clocks Block RAM”;Synchronization Stages选择“2”;如下图所示:
图 11.3.49 例化Fifo信息
在“Native Ports”界面中:Read Mode选择“Standard FIFO”;写位宽32,深度1024;读位宽16(写彩条显示数据格式为RGB565,所以读出时必须为16位);Initialization勾选异步复位;如下图所示:
图 11.3.50 例化Fifo信息
“Data Counts”界面:Data Counts Width选择“10”,如下图所示:
图 11.3.51 例化Fifo信息
添加完成进行连线:写接口与axi4_ddr_read模块的FIFO接口对应连接;写时钟连接100MHz;读接口与lcd_rgb_top模块的FIFO接口对应连接;读时钟与lcd_rgb_top模块的lcd_clk接口连接;复位信号通过一个1位反向器与locked连接;如下图所示:
图 11.3.52 FIFO模块连接
最后,我们对MicroBlaze软核处理器的配置做修改。
在第一页中:配置模板选择“Maximum Frequency” (最大性能);处理器位宽“32”;Select implementation optimization选择“FREQUENCY”;启动调试和指令数据缓存;如下图所示:
图 11.3.53 MicroBlaze配置
第二页不勾选,如下图所示:
图 11.3.54 MicroBlaze配置
第三页数据和指令的配置信息,如下图所示:
图 11.3.55 MicroBlaze配置
第四页Debug和第五页Buses进行总线设置默认即可。
至此,工程所需模块均已成功添加完成,验证无误后,保存设计。最后硬件系统如下图所示:
图 11.3.56最终布局图
在Source窗口中右键点击Block Design设计文件“system.bd”,然后执行“Generate Output Products”,编译IP时选择“Global”,编译完成后执行“Create HDL Wrapper”。
紧接着添加约束文件:打开system_wrapper.xdc文件删除之前的约束文件,添加如下管脚约束:
- create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
- set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS33} [get_ports sys_clk]
- set_property -dict {PACKAGE_PIN U2 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
- #UART
- set_property -dict {PACKAGE_PIN U5 IOSTANDARD LVCMOS33} [get_ports UART_rxd]
- set_property -dict {PACKAGE_PIN T6 IOSTANDARD LVCMOS33} [get_ports UART_txd]
- #LCD
- set_property -dict {PACKAGE_PIN AB18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[16]}]
- set_property -dict {PACKAGE_PIN AA18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[17]}]
- set_property -dict {PACKAGE_PIN Y19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[18]}]
- set_property -dict {PACKAGE_PIN Y18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[19]}]
- set_property -dict {PACKAGE_PIN W20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[20]}]
- set_property -dict {PACKAGE_PIN W17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[21]}]
- set_property -dict {PACKAGE_PIN V18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[22]}]
- set_property -dict {PACKAGE_PIN V17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[23]}]
- set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[8]}]
- set_property -dict {PACKAGE_PIN U17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[9]}]
- set_property -dict {PACKAGE_PIN V19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[10]}]
- set_property -dict {PACKAGE_PIN T18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[11]}]
- set_property -dict {PACKAGE_PIN V20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[12]}]
- set_property -dict {PACKAGE_PIN R18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[13]}]
- set_property -dict {PACKAGE_PIN N17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[14]}]
- set_property -dict {PACKAGE_PIN P17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[15]}]
- set_property -dict {PACKAGE_PIN R16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[0]}]
- set_property -dict {PACKAGE_PIN P15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[1]}]
- set_property -dict {PACKAGE_PIN R14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[2]}]
- set_property -dict {PACKAGE_PIN P14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[3]}]
- set_property -dict {PACKAGE_PIN N14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[4]}]
- set_property -dict {PACKAGE_PIN N13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[5]}]
- set_property -dict {PACKAGE_PIN V9 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[6]}]
- set_property -dict {PACKAGE_PIN W9 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[7]}]
管脚分配完成后按快捷键Ctrl+S保存管脚约束,点击 “Generate Bitstream”,对设计进行综合、实现、并生成Bit文件。
至此,本实验的硬件设计已经全部完成。Bit文件生成后导出至工程的vitis文件夹,并启动Vitis。
为了便于理解AXI4接口协议和时序,以及调试代码,在Block Design中可添加Debug。如果只是验证功能不调试,可以不用做下面的步骤,因为编译会比较慢。选中封装的axi4_ddr_read IP核的M_AXI信号线,右击选择“Debug”,如下图所示:
图 11.3.57添加Debug
接着就会发现下图红标1处信号线上出现一个绿色的类似于爬虫的标志,点击在红标2处的“Run Connection Automation”,然后勾选接口自动连接。
图 11.3.58自动连接接口
接口连接完毕后,会增加一个模块“system_ila_0”,即用来抓信号的ILA核,如下图所示:
图 11.3.59 ILA核
保存,重新执行Generate Bitstream,生成Bit文件,同时会在同目录下生成ltx文件。
接着进行下板,如下图所示:
图 11.3.60 下板
选择加载ltx文件,如下图所示:
图 11.3.61 加载ltx文件
在下图中添加所示的触发信号即从机的RREADY,值为“R”(由0变化到1),点击触发即可。
图 11.3.62 触发信号
稍等片刻,触发完成后弹出波形,可以看到下图中的读地址通道的相关信号信息,这里不再过多解释,前面有具体的介绍。
图 11.3.63 突发读地址通道
读数据通道正在突发读传输数据,如下图所示:
图 11.3.64 突发读数据通道
突发读最后一个数据时,相关信号如下图所示:
图 11.3.65最后一个数据传输
在观察信号或调试完毕后可将Debug删除,即选中信号线并右击,点击“Clear Debug”,如下图所示:
图 11.3.66删除Debug
保存,重新编译工程,生成Bit流文件。
11.4软件设计
打开Vitis开发环境后,创建应用工程的步骤都是一样的,这里不再赘述,新创建的空白应用工程命名为“lcd_rgb_colorbar”。然后为应用工程新建一个源文件“main.c”,我们在新建的main.c文件中输入本次实验的代码,代码如下所示:
- 48 #include
- 49 #include "platform.h"
- 50 #include "xil_printf.h"
- 51 #include "xparameters.h"
- 52 #include "xil_types.h"
- 53 #include "xil_cache.h"
- 54 #include "xil_io.h"
- 55 #include "axi4_lite_comm.h" //自定义接口IP核
- 56
- 57 //宏定义
- 58 #define BYTES_PIXEL 2
- 59 #define AXI4_LITE_COMM_BASEADDR XPAR_AXI4_LITE_COMM_0_S_AXI_BASEADDR
- 60 #define AXI4_LITE_COMM_REG0 AXI4_LITE_COMM_S_AXI_SLV_REG0_OFFSET
- 61
- 62 //函数声明
- 63 void colorbar(u8 *frame, u32 width, u32 height, u32 stride);
- 64
- 65 unsigned int const frame_buffer_addr = (XPAR_MIG_7SERIES_0_BASEADDR);
- 66 int main()
- 67 {
- 68 u16 hor_res = 0,ver_res = 0;
- 69 int lcd_id = 0;
- 70
- 71 init_platform();
- 72
- 73 lcd_id = AXI4_LITE_COMM_mReadReg(AXI4_LITE_COMM_BASEADDR,
- 74 AXI4_LITE_COMM_REG0);
- 75
- 76 if(lcd_id == 0x4342 || lcd_id == 0x4384 || lcd_id == 0x7084
- 77 || lcd_id == 0x7016 || lcd_id == 0x1018)
- 78 printf("LCD ID : %x nr",lcd_id);
- 79 else
- 80 printf("LCD ID : %x,No LCD detectednr",lcd_id);
- 81
- 82 switch(lcd_id){
- 83 case 0x4342 : hor_res = 480; ver_res = 272; break;
- 84 case 0x4384 : hor_res = 800; ver_res = 480; break;
- 85 case 0x7084 : hor_res = 800; ver_res = 480; break;
- 86 case 0x1016 : hor_res = 1024; ver_res = 600; break;
- 87 case 0x1018 : hor_res = 1280; ver_res = 800; break;
- 88 default : hor_res = 800; ver_res = 480;
- 89 }
- 90 xil_printf("width: %drn",hor_res);
- 91 xil_printf("height: %drn",ver_res);
- 92
- 93 //写彩条
- 94 colorbar((u8*)frame_buffer_addr, hor_res,ver_res,hor_res*BYTES_PIXEL);
- 95
- 96 cleanup_platform();
- 97 return 0;
- 98 }
- 99
- 100
- 101//写彩条函数
- 102void colorbar(u8 *frame, u32 width, u32 height, u32 stride)
- 103{
- 104 u32 color_edge, colorbar_2, colorbar_3, colorbar_4, colorbar_5;
- 105 u32 x_pos, y_pos;
- 106 u32 y_stride = 0;
- 107 u16 rgb = 0;
- 108
- 109 color_edge = width * BYTES_PIXEL / 5;
- 110 colorbar_2 = color_edge * 2;
- 111 colorbar_3 = color_edge * 3;
- 114 colorbar_4 = color_edge * 4;
- 113 colorbar_5 = color_edge * 5;
- 114
- 115 //开始写彩条
- 116 xil_printf("write color star rn");
- 117 for (y_pos = 0; y_pos < height; y_pos++) {
- 118 for (x_pos = 0; x_pos < width * BYTES_PIXEL; x_pos += BYTES_PIXEL) {
- 119 if (x_pos < color_edge) { //白色
- 120 rgb = 0xffff;
- 121 } else if ((x_pos >= color_edge) && (x_pos < colorbar_2)) { //黑色
- 122 rgb = 0x0;
- 123 } else if ((x_pos >= colorbar_2) && (x_pos < colorbar_3)) { //红色
- 124 rgb = 0xf800;
- 125 } else if ((x_pos >= colorbar_3) && (x_pos < colorbar_4)) { //绿色
此代码实现了一个五色(白黑红绿蓝)的彩条。
在代码的第58行至第60行,我们对BYTES_PIXEL像素字节数、AXI4_LITE_COMM_BASEADDR自定义接口IP核基地址、AXI4_LITE_COMM_REG0自定义接口IP核存放lcd_id的寄存器进行了宏定义。代码的第63行,对写彩条函数colorbar进行了声明。
第65行获取写彩条的基地址,按“Ctrl”键点击宏“XPAR_MIG_7SERIES_0_BASEADDR”进入该值设置文件xparameters.h,我们将其设置为0x85000000(必须与硬件系统中AXI4_DDR_READ IP核设置的读地址相同),保存。地址设置如下图所示:
图 11.4.1写彩条基地址设置
主函数中,第73行AXI4_LITE_COMM_mReadReg是读寄存器函数,用于获取16位的参数lcd_id。代码82行至91行根据屏幕ID来对写彩条的两个参数width和height(分辨率)进行赋值;代码第94行则执行写彩条函数。代码从第102行到最后,是具体的写彩条函数。第109行至113行用来计算各彩条的边界,我们将数据在for循环外部进行计算,然后在函数中直接调用计算结果,有利于减少程序的运行时间。代码117至134行为写彩条的for循环函数,将RGB565格式的彩条数据写入缓存中。代码第135行在函数的最后通过调用Xil_DCacheFlush()函数将缓存在DataCache中的数据更新到DDR3中。
保存,编译整个工程,生成ELF文件。
11.5下载验证
首先我们将下载器与达芬奇开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将USB_UART接口与电脑连接,用于串口通信。将LCD屏幕通过排线连接在开发板上。最后连接开发板的电源,并打开电源开关。
打开Vitis的Terminal并连接,开始下载程序。随着程序的执行,串口逐渐打印信息出如下图所示:
图 11.5.1正在写彩条
当串口打印出“show color bar”时,说明程序执行完毕,此时我们可以在LCD屏幕上观察到彩条,如下图所示:
图 11.5.2彩条显示
至此,彩条成功写入DDR3并被读出显示在了LCD屏上,证明了我们修改封装的具有AXI4协议只读
|