作者:Hello,Panda
熊猫君这次分享的是如何通过CrosslinkNX的RISC-V软核来访问I2CFIFO软核。IIC是最常用的外设总线,通常一块板子上要挂好些个IIC器件,如果完全使用RTL状态机来实现总线控制的话,无疑非常的繁琐且非常的不友好,调试和使用起来也很不方便。熊猫君的这次分享就是为了解决这个问题,将Lattice的I2CFIFO模块挂到RISC-V软核的APB总线上,通过软件来实现IIC总线,I2CFIFO模块就相当于是RISC-V处理器的一个外设,按地址访问就行了。 在阅读这个例子之前,读者需要了解以下基本知识: (1)lattice 基于RISC-V处理器的SOC基本开发流程; (2)Arm的APB总线协议《AMBA 3APB Protocol Specification》; (3)Lattice的lmmi总线协议《Lattice Memory Mapped Interface and Lattice Interrupt Interface》,lattice内部文档编码UG-02039; (4)I2CFIFO模块使用手册《I2CFIFO Module- Lattice Radiant Software》,lattice内部文档编码IPUG-02064; 一、电路板硬件 熊猫君用的公司自制的评估板,板上集成了若干个IIC从设备,硬件原理图见下图1。 图1 评估板硬件原理图 由图中可以看出,IIC设备有扩展IO芯片、MIPI接口的图像传感器、时钟芯片、触摸屏等,通过一个IIC Switch芯片来进行通道选择,这样的硬件结构如果直接用RTL状态机实现,那无疑是非常复杂,因此通过挂到APB总线上作为一个RISC-V外设使用就相对简单多了。 二、软件设计 本案软件至少需要包括RISC-V处理器、程序运行RAM、总线仲裁器、I2CFIFO模块等,系统结构见图2所示。 图2 软件系统结构框图 (1)SoC方框内所有模块是在Propel中搭建的,其中APB IF是一个自定义的接口IP,目的是将APB总线引出并给其在RISC-V处理器中分配访问地址,自定义的IP的封装方法在本文中不再赘述; (2)APB REG:这个模块是熊猫君自己写的,目的是解析APB总线数据并将其分发到工程中的各个模块,图2中只画了一个I2CFIFO,实际上挂了很多IP的; (3)LMMI Master:这个模块是熊猫君自己写的,目的是为了实现LMMI总线访问逻辑,用于配置I2CFIFO。 其实,在本案中最关键的一步是如何将I2CFIFO模块挂载到APB总线上去,让RISC-V处理器可以访问它。 2.1 第一步:搭建Propel工程 软件设计的第一步是在Propel下搭建一个RISC-V软核工程,搭建的流程可以参考本系列分享3,搭建好的工程见下图3所示。 图3搭建好的Propel工程图 如图3所示,标记红色方框的就是自定义封装的一个APB接口IP,里面没有什么逻辑,就是将APB总线的若干信号引出SOC工程供顶层的Radiant模块调用,并且定义了地址空间范围,如下图4所示。 图4 给APB总线接口分配地址空间 如果做到这图4这一步,那么恭喜你,RISC-V处理器核已经可以正常访问该段地址对应的外设寄存器了,这个应用也就成功了一多半。 2.2第二步:例化I2CFIFO模组 Lattice I2CFIFO模组是无需额外的License授权的,在Radiant开发环境下可以免费使用,我们把这个模组例化成为FIFO模式,总线速率400KHz,例化配置见下图5所示,其他图中不所见的配置按默认即可。 图5 I2CFIFO例化配置界面 关于这个IP模块的使用请参见官方User Guide文档,配置成为FIFO模式的操作比较简单,软件编程时只需按照下图6所示的步骤配置寄存器即可完成I2C的读写访问。 图6 FIFO模式下I2C读写访问流程表 这里需要特别提醒注意的是:fifo模式下的寄存器是10bit位宽,需要分两次访问,如写fifo操作,bit[7:0]写入到偏移地址为0x12的寄存器中,bit[9:8]控制命令写入到偏移地址为0x13的寄存器中。 2.3第三步:搭建APB到LMMI总线的转换逻辑 这一步在APB_REG模块和LMMI_Master模块中实现,当然也可以直接使用一个总线桥来做,但是这样做的话,这个APB总线接口就是专有的了,不利于系统的扩展使用。 这两个模块的代码如下: //------------------------------------------------------------ //File Name: apb_reg_v1 //Project : mipi dphy //Module :apb_reg //Content : //Description : apb reg mapping //Spec. : //Author : Hello,Panda //------------------------------------------------------------ //History : //20210205: V1.0 -Initial Creation //------------------------------------------------------------ `timescale 1ns / 1ps module apb_reg_v1 #( parameter P_MAJOR_VERSION = 4'd1 ,parameter P_MINOR_VERSION = 4'd0 ,parameter P_BUILD_YEAR = 12'd2021 ,parameter P_BUILD_MON = 4'd2 ,parameter P_BUILD_DAY = 8'd6 )( input wire i_clk ,input wire i_rst /**************apbbus****************/ ,input wire i_apb_penable ,input wire i_apb_psel ,input wire i_apb_pwrite ,input wire [9 : 0] i_apb_paddr ,input wire [31: 0] i_apb_pwdata
,output wire o_apb_pready ,output wire o_apb_pslverr ,output wire [31: 0] o_apb_prdata ,output wire o_iicfifo_lmmi_start ,output wire o_iicfifo_lmmi_mode ,output wire [7 : 0] o_iicfifo_lmmi_offset ,output wire [31: 0] o_iicfifo_lmmi_wdata ,input wire i_iicfifo_lmmi_busy ,input wire i_iicfifo_lmmi_done ,input wire [31: 0] i_iicfifo_lmmi_rdata ,output wire o_iicfifo_reset ); reg [9 : 0] r_apb_addr ; reg [31: 0] r_apb_pwdata ; reg r_apb_reg_wren ; reg r_apb_reg_rden ; reg r_p1_apb_reg_wren; reg r_p1_apb_reg_rden; reg r_app_reg_rdout ; reg [31 : 0] r_apb_prdata ; reg r_iicfifo_reset ;
reg [31 : 0] r_apb_slv_reg1 ; reg [31 : 0] r_apb_slv_reg2 ; reg r_apb_pready ;
reg r_iicfifo_lmmi_start; reg r_iicfifo_lmmi_rready; reg r_iicfifo_lmmi_rflag ; reg [31 : 0] r_iicfifo_lmmi_rdata; reg r_iicfifo_lmmi_clean ; wire [3 : 0] w_major_ver ; wire [3 : 0] w_minor_ver ; wire [11: 0] w_build_year ; wire [3 : 0] w_build_mon ; wire [7 : 0] w_build_day ; wire w_apb_reg_wren ; wire w_apb_reg_rden ; wire [31: 0] w_apb_slv_reg0 ; wire [31: 0] w_apb_slv_reg1 ; assign o_apb_pready = r_apb_pready ; assign o_apb_pslverr = 1'b0 ; assign o_apb_prdata = r_apb_prdata ; assign w_major_ver = P_MAJOR_VERSION; assign w_minor_ver = P_MINOR_VERSION; assign w_build_year = P_BUILD_YEAR ; assign w_build_mon = P_BUILD_MON ; assign w_build_day = P_BUILD_DAY ; assign w_apb_slv_reg0 = {w_build_year[11:0],w_build_mon[3:0], w_build_day[7:0],w_major_ver[3:0],w_minor_ver[3:0]}; assign w_apb_reg_wren = ((~r_p1_apb_reg_wren) & r_apb_reg_wren); assign w_apb_reg_rden = ((~r_p1_apb_reg_rden) & r_apb_reg_rden);
assign o_iicfifo_lmmi_start = (r_apb_slv_reg1[9] &(~r_iicfifo_lmmi_start)); assign o_iicfifo_lmmi_mode = r_apb_slv_reg1[8]; assign o_iicfifo_lmmi_offset =r_apb_slv_reg1[7:0]; assign o_iicfifo_lmmi_wdata = r_apb_slv_reg2 ; assign o_iicfifo_reset = (r_iicfifo_reset|r_apb_slv_reg1[10]);
assign w_apb_slv_reg1 = {i_iicfifo_lmmi_busy,r_iicfifo_lmmi_rready,r_apb_slv_reg1[29:0]};
begin if(i_rst) begin r_apb_addr <= 10'd0; r_apb_reg_wren <= 1'b0 ; r_apb_reg_rden <= 1'b0 ; r_p1_apb_reg_wren <= 1'b0; r_p1_apb_reg_rden <= 1'b0; r_apb_pwdata <= 32'd0; r_app_reg_rdout <= 1'b0 ; end else begin r_apb_reg_wren <= (i_apb_psel &i_apb_penable & i_apb_pwrite) ? 1'b1 : 1'b0 ; r_apb_addr <= (i_apb_psel & (~i_apb_penable)) ? i_apb_paddr : r_apb_addr ; //latch addr r_apb_reg_rden <= (i_apb_psel &(~i_apb_penable) & (~i_apb_pwrite)) ? 1'b1 : 1'b0 ; //begin read with wait r_p1_apb_reg_wren <= r_apb_reg_wren ; r_p1_apb_reg_rden <= r_apb_reg_rden ; r_apb_pwdata <= (i_apb_psel & i_apb_penable& i_apb_pwrite & r_apb_pready) ? i_apb_pwdata : r_apb_pwdata; r_app_reg_rdout <= w_apb_reg_rden ? 1'b1 : 1'b0; end end
begin if(i_rst) begin r_apb_pready <= 1'b0; end else begin if(i_apb_psel & (~i_apb_pwrite)) begin if(~i_apb_penable) begin r_apb_pready <= 1'b0; end else if(r_app_reg_rdout) begin r_apb_pready <= 1'b1; end end else begin r_apb_pready <= 1'b1; end end end
always @ (posedge i_clk) begin if(i_rst) begin r_apb_slv_reg1 <= 32'h00000000; r_apb_slv_reg2 <= 32'h00000000; end else begin if(w_apb_reg_wren)begin case(r_apb_addr[9:2])
8'd1: begin r_apb_slv_reg1 <= r_apb_pwdata; end
8'd2: begin r_apb_slv_reg2 <= r_apb_pwdata; end
default: begin r_apb_slv_reg1 <= r_apb_slv_reg1; r_apb_slv_reg2 <= r_apb_slv_reg2; end
endcase end elsebegin r_apb_slv_reg1[10:9] <= 2'b00; end end end
always @ (posedge i_clk) begin if(i_rst) begin r_apb_prdata <= 32'd0; r_iicfifo_lmmi_clean <= 1'b0 ; end else begin if(r_app_reg_rdout) begin case (r_apb_addr[9:2]) 8'd0 : begin r_apb_prdata <= w_apb_slv_reg0; end 8'd1 : begin r_apb_prdata <= w_apb_slv_reg1; end 8'd2 : begin r_apb_prdata <= r_apb_slv_reg2; end 8'd3 : begin r_apb_prdata <= r_iicfifo_lmmi_rdata ; r_iicfifo_lmmi_clean <= 1'b1 ; end
default : begin r_apb_prdata <= 32'd0; end
endcase end else begin r_iicfifo_lmmi_clean <= 1'b0; end end end
always @ (posedge i_clk) begin if(i_rst) begin r_iicfifo_lmmi_start <= 1'b0; r_iicfifo_lmmi_rready<= 1'b0; r_iicfifo_lmmi_rflag <= 1'b0; r_iicfifo_lmmi_rdata <= 32'd0; r_iicfifo_reset <= 1'b0; end else begin r_iicfifo_lmmi_start <= r_apb_slv_reg1[9] ; r_iicfifo_reset <= r_apb_slv_reg1[10];
if(r_apb_slv_reg1[9]& (~r_apb_slv_reg1[8])) begin r_iicfifo_lmmi_rflag <= 1'b1; end elseif(i_iicfifo_lmmi_done) begin r_iicfifo_lmmi_rflag <= 1'b0; end
if(r_iicfifo_lmmi_rflag& i_iicfifo_lmmi_done) begin r_iicfifo_lmmi_rdata <= i_iicfifo_lmmi_rdata; end
if(r_iicfifo_lmmi_rflag& i_iicfifo_lmmi_done) begin r_iicfifo_lmmi_rready <= 1'b1; end elseif(r_iicfifo_lmmi_clean) begin r_iicfifo_lmmi_rready <= 1'b0; end
end end endmodule //------------------------------------------------------------ //file name: lmmi_master_v1 //project : mipi dphy //module : //content : //description : generate lmmi acess logic //spec. : //author : hello,panda //------------------------------------------------------------ //history : //20210205: v1.0 -initial creation //------------------------------------------------------------ `timescale 1ns / 1ps module lmmi_master_v1( input wire i_clk ,input wire i_rst
,input wire [7 : 0] i_addr_offset ,input wire [31: 0] i_reg_wdata ,output wire [31: 0] o_reg_rdata ,input wire i_access_start ,input wire i_access_mode //1-write mode; 0-read mode ,output wire o_access_done ,output wire o_access_busy
,output wire o_lmmi_request ,output wire o_lmmi_wr_rdn ,output wire [7 : 0] o_lmmi_offset ,output wire [31: 0] o_lmmi_wdata ,input wire [31: 0] i_lmmi_rdata ,input wire i_lmmi_rdata_valid ,input wire i_lmmi_ready );
reg r_access_start; reg [7 : 0] r_addr_offset ; reg [31: 0] r_reg_wdata ; reg [31: 0] r_reg_rdata ; reg r_access_mode ; reg r_access_busy ; reg r_access_done ; reg r_lmmi_request; reg [3 : 0] r_lmmi_state ; reg r_lmmi_wr_rdn ;
localparam st_idel = 4'b0000; localparam st_wr = 4'b0001; localparam st_rd = 4'b0010; localparam st_wait = 4'b0100; localparam st_done = 4'b1000;
assign o_reg_rdata = r_reg_rdata ; assign o_access_done = r_access_done ; assign o_access_busy = (r_access_start|r_access_busy); assign o_lmmi_request = r_lmmi_request ; assign o_lmmi_wr_rdn = r_lmmi_wr_rdn ; assign o_lmmi_offset = r_addr_offset ; assign o_lmmi_wdata = r_reg_wdata ;
always @ (posedge i_clk) begin if(i_rst) begin r_access_start <= 1'b0 ; r_addr_offset <= 8'd0 ; r_reg_wdata <= 32'd0; r_access_mode <= 1'b0 ; end else begin if(i_access_start & (~r_access_busy))begin r_addr_offset <= i_addr_offset ; r_reg_wdata <= i_reg_wdata ; r_access_mode <= i_access_mode ; end r_access_start <= i_access_start; end end
always @ (posedge i_clk) begin if(i_rst) begin r_lmmi_state <= st_idel ; r_lmmi_request <= 1'b0 ; r_reg_rdata <= 32'd0 ; r_access_done <= 1'b0 ; r_lmmi_wr_rdn <= 1'b0 ; end else begin case (r_lmmi_state)
st_idel : begin if(r_access_start) begin r_lmmi_state <= st_wr ; end r_lmmi_request <= 1'b0; r_access_done <= 1'b0; r_lmmi_wr_rdn <= 1'b0; end
ST_WR : begin if(i_lmmi_ready) begin r_lmmi_state <= r_access_mode ? ST_WAIT :ST_RD; r_lmmi_request <= 1'b1 ; r_lmmi_wr_rdn <= r_access_mode; end end
ST_RD : begin r_lmmi_request <= 1'b0; r_lmmi_wr_rdn <= 1'b0; if(i_lmmi_rdata_valid) begin r_reg_rdata <= i_lmmi_rdata; r_lmmi_state<= ST_WAIT ; end end
ST_WAIT : begin r_lmmi_request <= 1'b0; r_lmmi_wr_rdn <= 1'b0; if(i_lmmi_ready) begin r_lmmi_state <= ST_DONE; r_access_done<= 1'b1 ; end end
ST_DONE : begin r_lmmi_request <= 1'b0; r_lmmi_wr_rdn <= 1'b0; r_lmmi_state <= ST_IDEL; r_access_done <= 1'b0; end
default : begin r_lmmi_request <= 1'b0; r_lmmi_wr_rdn <= 1'b0; r_lmmi_state <= ST_IDEL; r_access_done <= 1'b0; end endcase end end
always @ (posedge i_clk) begin if(i_rst) begin r_access_busy <= 1'b0; end else begin if(r_access_start) begin r_access_busy <= 1'b1; end else if(r_lmmi_state == ST_DONE) begin r_access_busy <= 1'b0; end end end endmodule 好了,这两个模块的代码已经贴出来了,这里就不再多作解释。 2.4第四步:在Propel SDK下编写C代码 这一步就是在软件访问I2CFIFO模块寄存,根据2.3第三步描述的逻辑,写好读写寄存器的底层函数即可,比如写I2CFIFO模块寄存器的语句如下: void iiclmmiwrite(uint8_t offset,uint32_t wdata) { uint32_t reg_val; //check lmii bus idle reg_val = (APB_REG->IIC_LMII_CTRL &IIC_LMII_CTRL_BUSY_MASK); while(reg_val){ reg_val = (APB_REG->IIC_LMII_CTRL &IIC_LMII_CTRL_BUSY_MASK); } //write wdata to register APB_REG->IIC_LMII_WDATA = wdata; //write ctrl to start reg_val = offset; reg_val |= IIC_LMII_CTRL_PWRITE_MASK; reg_val |= IIC_LMII_CTRL_START_MASK ; APB_REG->IIC_LMII_CTRL = reg_val; } 至于后面的逻辑代码怎么写,调用底层的寄存器读写函数就可以了。 好了,今天的分享到此结束,欢迎大家加入QQ群或微信公众号交流讨论,同时也祝朋友们在新的一年里更上一层楼,牛得牛气冲天!
|