verilog怎么控制iic的读写,怎么控制多个数据的。。求大神

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了verilog怎么控制iic的读写,怎么控制多个数据的。。求大神相关的知识,希望对你有一定的参考价值。

虚心求教。。困扰了很久。不知道怎么破
网上有个是可以处理单个数据的貌似,但是多个数据的读写不知道怎么操作

参考技术A 什么是单个数据?IIC总线就是把所需的数据经sda数据线串行传入芯片中,需要多少传多少就是 了,我这有个以前写的IIC控制WM8731的例程,你可以参考下
//`timescale 1ns / 1ps
module i2c_control(
clk,
i2c_sclk,
i2c_sdat,
i2c_data,
start,
tr_end,
ack,
rst,
counter,
sdo);

input clk;
input [23:0]i2c_data;
input start;
input rst;
// input w_r;
inout i2c_sdat;
output i2c_sclk;
output tr_end;
output ack;
output [5:0]counter;
output sdo;

reg sdo;
reg sclk;
reg tr_end;
reg [23:0]sd;
reg [5:0]counter;

assign i2c_sclk=sclk |(((counter>=4)&(counter<=30))?~clk:0);
assign i2c_sdat=sdo?1'bz:0;

reg ack1,ack2,ack3;
wire ack=ack1 |ack2 |ack3;

always@(negedge rst or posedge clk)begin
if(!rst)counter<=6'b111111;
else begin
if(start==0)
counter<=0;
else
if(counter<6'b111111)counter<=counter+1;
end
end

always@(negedge rst or posedge clk)begin
if(!rst)begin sclk<=1;sdo<=1;ack1<=0;ack2<=0;ack3<=0;tr_end<=0;end
else
case(counter)
6'd0 :begin ack1<=0;ack2<=0;ack3<=0;tr_end<=0;sdo<=1;sclk<=1;end
6'd1 :begin sd<=i2c_data;sdo<=0;end
6'd2 :sclk=0;
6'd3 :sdo<=sd[23];
6'd4 :sdo<=sd[22];
6'd5 :sdo<=sd[21];
6'd6 :sdo<=sd[20];
6'd7 :sdo<=sd[19];
6'd8 :sdo<=sd[18];
6'd9 :sdo<=sd[17];
6'd10 :sdo<=sd[16];
6'd11 :sdo<=1'b1;

6'd12 :begin sdo<=sd[15];ack1<=i2c_sdat;end
6'd13 :sdo<=sd[14];
6'd14 :sdo<=sd[13];
6'd15 :sdo<=sd[12];
6'd16 :sdo<=sd[11];
6'd17 :sdo<=sd[10];
6'd18 :sdo<=sd[9];
6'd19 :sdo<=sd[8];
6'd20 :sdo<=1'b1;

6'd21 :begin sdo<=sd[7];ack2<=i2c_sdat;end
6'd22 :sdo<=sd[6];
6'd23 :sdo<=sd[5];
6'd24 :sdo<=sd[4];
6'd25 :sdo<=sd[3];
6'd26 :sdo<=sd[2];
6'd27 :sdo<=sd[1];
6'd28 :sdo<=sd[0];
6'd29 :sdo<=1'b1;

6'd30 :begin sdo<=1'b0;sclk<=1'b0;ack3<=i2c_sdat;end
6'd31 :sclk<=1'b1;
6'd32 :begin sdo<=1'b1;tr_end<=1;end
endcase
end
endmodule本回答被提问者采纳
参考技术B IIC都是两根线,只不过这两根线可能跟很多芯片的管脚复用。那么多芯片怎么办?加地址线当片选信号吧!

简易SDRAM控制器的verilog代码实现

SDRAM是每隔15us进行刷新一次,但是如果当SDRAM需要进行刷新时,而SDRAM正在写数据,这两个操作之间怎么进行协调呢?

需要保证写的数据不能丢失,所以,如果刷新的时间到了,先让写操作把正在写的4个数据(突发长度为4)写完,然后再去进行刷新操作;

而如果在执行读操作也遇到需要刷新的情况,也可以先让数据读完,再去执行刷新操作。

思路:SDRAM控制器包括初始化、读操作、写操作及自动刷新这些操作,给每一个操作写上一个模块独立开来,也便于我们每个模块的调试,显然这种思路是正确的;

虽然都是独立的模块,但很显然这几个模块之间又是相互关联的。如果SDRAM需要刷新了,而SDRAM却正在执行写操作,为了控制各个模块之间的工作关系,引入仲裁机制。


仲裁状态机 ↓

仲裁机工作原理框图 ↓

在仲裁模块中,初始化操作完成之后便进入到了“ARBIT”仲裁状态,只有处于仲裁状态的时候,仲裁机才能向其他模块发送命令。

当状态机处于“WRITE”写状态时,如果SDRAM刷新的时间到了,刷新模块同时向写模块和仲裁模块发送刷新请求ref_req信号,当写模块接受到ref_req之后,写模块在写完当前4个数据(突发长度为4)之后,写模块的写结束标志flag_wr_end拉高,然后状态机进入“ARBIT”仲裁状态;

处于仲裁状态之后,此时有刷新请求ref_req,然后状态机跳转到“AREF”状态并且仲裁模块发送ref_en刷新使能,然后刷新模块将刷新请求信号ref_req拉低并给sdram发送刷新的命令。

等刷新完毕之后,刷新模块给仲裁模块发送flag_ref_end刷新结束标志,状态机跳转到“ARBIT”仲裁状态。

当刷新完跳转到“ARBIT”仲裁状态之后,如果之前全部数据仍然没有写完(指的是全部数据,并不是一个突发长度的4个数据),那么此时仍然要给仲裁模块写请求“wr_req”,然后仲裁模块经过一系列判断之后,如果符合写操作的时机,那就给写模块一个写使能信号“wr_en”,然后跳转到“WRITE”写状态并且写模块开始工作。


 

 仲裁模块中状态机定义 ↓

//state
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            state    <=    IDLE;
        else case(state)
            IDLE:
                if(key[0] == 1\'b1)
                    state    <=    INIT;
                else
                    state    <=    IDLE;
            INIT:
                if(flag_init_end == 1\'b1)    //初始化结束标志
                    state    <=    ARBIT;
                else
                    state    <=    INIT;
            ARBIT:
                if(ref_req == 1\'b1)    //刷新请求到来且已经写完
                    state    <=    AREF;
                else if(ref_req == 1\'b0 && rd_en == 1\'b1)    //默认读操作优先于写操作
                    state    <=    READ;            
                else if(ref_req == 1\'b0 && wr_en == 1\'b1)    //无刷新请求且写请求到来
                    state    <=    WRITE;
                else
                    state    <=    ARBIT;
            AREF:
                if(flag_ref_end == 1\'b1)
                    state    <=    ARBIT;
                else
                    state    <=    AREF;
            WRITE:
                if(flag_wr_end == 1\'b1)
                    state    <=    ARBIT;
                else
                    state    <=    WRITE;
            READ:
                if(flag_rd_end == 1\'b1)
                    state    <=    ARBIT;
                else
                    state    <=    READ;
            default:
                state    <=    IDLE;            
        endcase    

key[0]作为我们初始化的一个使能信号,如果是实际下板子的时候,我们还需要给按键加一个按键消抖模块。当按键0按下之后,代表我们的SDRAM的初始化使能信号来了;

所以状态机从“IDLE”跳转到了“INIT”状态。在初始化状态,如果我们的初始化模块传来了初始化结束标志“flag_init_end”,那状态机跳转到“ARBIT”仲裁状态;

在仲裁状态中,第一个“if”是判断刷新请求的,这也就说明了我们刷新的优先级最高。

之后,如果处于仲裁状态,来了读使能信号或者写使能信号并且没有刷新请求,那状态机就跳转到对应的状态。

如果处于读或写的状态,当读结束标志或者写结束标志来临的时候(这里的写结束标志和读结束标志都是指突发读或突发写的结束标志),那么就会跳转到仲裁状态。


初始化模块 ↓

module    sdram_init(
        input    wire        sclk,        //系统时钟为50M,即T=20ns
        input    wire        s_rst_n,
        
        output    reg    [3:0]    cmd_reg,    //sdram命令寄存器
        output    reg    [11:0]    sdram_addr,    //地址线
        output    reg    [1:0]    sdram_bank,    //bank地址
        output    reg        flag_init_end    //sdram初始化结束标志    
        );
        
    parameter    CMD_END        =    4\'d11,        //初始化结束时的命令计数器的值
            CNT_200US    =    14\'d1_0000,    
            NOP        =    4\'b0111,    //空操作命令
            PRECHARGE    =    4\'b0010,    //预充电命令
            AUTO_REF    =    4\'b0001,    //自刷新命令
            MRSET        =    4\'b0000;    //模式寄存器设置命令
 
    reg    [13:0]    cnt_200us;        //200us计数器
    reg        flag_200us;        //200us结束标志(200us结束后,一直拉高)
    reg    [3:0]    cnt_cmd;        //命令计数器,便于控制在某个时候发送特定指令
    reg        flag_init;        //初始化标志:初始化结束后,该标志拉低
//flag_init 
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            flag_init    <=    1\'b1;
        else if(cnt_cmd == CMD_END)
            flag_init    <=    1\'b0;    
//cnt_200us    
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            cnt_200us    <=    14\'d0;
        else if(cnt_200us == CNT_200US)
            cnt_200us    <=    14\'d0;
        else if(flag_200us == 1\'b0)
            cnt_200us    <=    cnt_200us + 1\'b1;
//flag_200us
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            flag_200us    <=    1\'b0;
        else if(cnt_200us == CNT_200US)
            flag_200us    <=    1\'b1;
//cnt_cmd
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            cnt_cmd    <=    4\'d0;
        else if(flag_200us == 1\'b1 && flag_init == 1\'b1)
            cnt_cmd    <=    cnt_cmd + 1\'b1;
//flag_init_end
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            flag_init_end    <=    1\'b0;
        else if(cnt_cmd == CMD_END)
            flag_init_end    <=    1\'b1;
        else
            flag_init_end    <=    1\'b0;
//cmd_reg
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            cmd_reg    <=    NOP;
        else if(cnt_200us == CNT_200US)
            cmd_reg    <=    PRECHARGE;
        else if(flag_200us)
            case(cnt_cmd)
                4\'d0:
                    cmd_reg    <=    AUTO_REF;    //预充电命令
                4\'d6:
                    cmd_reg    <=    AUTO_REF;
                4\'d10:
                    cmd_reg    <=    MRSET;        //模式寄存器设置
                default:
                    cmd_reg    <=    NOP;
            endcase
//sdram_addr
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            sdram_addr    <=    12\'d0;
        else case(cnt_cmd)
            4\'d0:
                sdram_addr    <=    12\'b0100_0000_0000;    //预充电时,A10拉高,对所有Bank操作
            4\'d10:
                sdram_addr    <=    12\'b0000_0011_0010;    //模式寄存器设置时的指令:CAS=2,Burst Length=4;
            default:
                sdram_addr    <=    12\'d0;
        endcase
//sdram_bank
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            sdram_bank    <=    2\'d0;    //这里仅仅只是初始化,在模式寄存器设置时才会用到且其值为全零,故不赋值
 
//sdram_clk
    assign    sdram_clk    =    ~sclk;
            
endmodule

首先,我们需要有200us的稳定期,所以我们便有了一个200us的计数器cnt_200us,而这个计数器是根据flag_200us的低电平来工作的。

falg_200us在200us计时之后一直拉高。在200us计满,即flag_200us拉高之后,我们就需要先给一个“NOP”命令,然后给两次“Precharge”命令,同时选中ALL Banks。


写操作模块 ↓

module    sdram_write(
        input    wire        sclk,
        input    wire        s_rst_n,
        input    wire        key_wr,
        input    wire        wr_en,        //来自仲裁模块的写使能
        input    wire        ref_req,    //来自刷新模块的刷新请求
        input    wire    [5:0]    state,        //顶层模块的状态
        
        output    reg    [15:0]    sdram_dq,    //sdram输入/输出端口
        //output    reg    [3:0]    sdram_dqm,    //输入/输出掩码
        output    reg    [11:0]    sdram_addr,    //sdram地址线
        output    reg    [1:0]    sdram_bank,    //sdram的bank地址线
        output    reg    [3:0]    sdram_cmd,    //sdram的命令寄存器
        output    reg        wr_req,        //写请求(不在写状态时向仲裁进行写请求)
        output    reg        flag_wr_end    //写结束标志(有刷新请求来时,向仲裁输出写结束)
        );
        
    parameter    NOP    =    4\'b0111,    //NOP命令
            ACT    =    4\'b0011,    //ACT命令
            WR    =    4\'b0100,    //写命令(需要将A10拉高)
            PRE    =    4\'b0010,    //precharge命令
            CMD_END    =    4\'d8,
            COL_END    =    9\'d508,        //最后四个列地址的第一个地址
            ROW_END    =    12\'d4095,    //行地址结束
            AREF    =    6\'b10_0000,    //自动刷新状态
            WRITE    =    6\'b00_1000;    //状态机的写状态
            
    reg        flag_act;            //需要发送ACT的标志            
    reg    [3:0]    cmd_cnt;            //命令计数器
    reg    [11:0]    row_addr;            //行地址
    reg    [11:0]    row_addr_reg;            //行地址寄存器
    reg    [8:0]    col_addr;            //列地址
    reg        flag_pre;            //在sdram内部为写状态时需要给precharge命令的标志
 
//flag_pre
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            flag_pre    <=    1\'b0;
        else if(col_addr == 9\'d0 && flag_wr_end == 1\'b1)
            flag_pre    <=    1\'b1;
        else if(flag_wr_end == 1\'b1)
            flag_pre    <=    1\'b0;
    
//flag_act
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            flag_act    <=    1\'b0;
        else if(flag_wr_end)
            flag_act    <=    1\'b0;
        else if(ref_req == 1\'b1 && state == AREF)
            flag_act    <=    1\'b1;
//wr_req
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            wr_req    <=    1\'b0;
        else if(wr_en == 1\'b1)
            wr_req    <=    1\'b0;
        else if(state != WRITE && key_wr == 1\'b1)
            wr_req    <=    1\'b1;
//flag_wr_end
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            flag_wr_end    <=    1\'b0;
        else if(cmd_cnt == CMD_END)
            flag_wr_end    <=    1\'b1;
        else
            flag_wr_end    <=    1\'b0;
//cmd_cnt
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            cmd_cnt    <=    4\'d0;
        else if(state == WRITE)
            cmd_cnt    <=    cmd_cnt + 1\'b1;
        else 
            cmd_cnt    <=    4\'d0;
        
//sdram_cmd
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            sdram_cmd    <=    4\'d0;
        else case(cmd_cnt)
            3\'d1:
                if(flag_pre == 1\'b1)
                    sdram_cmd    <=    PRE;
                else
                    sdram_cmd    <=    NOP;
            3\'d2:
                if(flag_act == 1\'b1 || col_addr == 9\'d0)
                    sdram_cmd    <=    ACT;
                else
                    sdram_cmd    <=    NOP;
            3\'d3:         
                sdram_cmd    <=    WR;
 
            default:
                sdram_cmd    <=    NOP;        
        endcase
//sdram_dq
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            sdram_dq    <=    16\'d0;
        else case(cmd_cnt)
            3\'d3:
                sdram_dq    <=    16\'h0012;
            3\'d4:
                sdram_dq    <=    16\'h1203;
            3\'d5:
                sdram_dq    <=    16\'h562f;
            3\'d6:
                sdram_dq    <=    16\'hfe12;
            default:
                sdram_dq    <=    16\'d0;
        endcase
/* //sdram_dq_m
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            sdram_dqm    <=    4\'d0; */
//row_addr_reg
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            row_addr_reg    <=    12\'d0;
        else if(row_addr_reg == ROW_END && col_addr == COL_END && cmd_cnt == CMD_END)
            row_addr_reg    <=    12\'d0;
        else if(col_addr == COL_END && flag_wr_end == 1\'b1)
            row_addr_reg    <=    row_addr_reg + 1\'b1;
        
//row_addr
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            row_addr    <=    12\'d0;
        else case(cmd_cnt)
        //因为下边的命令是通过行、列地址分开再给addr赋值,所以需要提前一个周期赋值,以保证在命令到来时能读到正确的地址
            3\'d2:
                row_addr    <=    12\'b0000_0000_0000;    //在写命令时,不允许auto-precharge    
            default:
                row_addr    <=    row_addr_reg;
        endcase
//col_addr
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            col_addr    <=    9\'d0;
        else if(col_addr == COL_END && cmd_cnt == CMD_END)
            col_addr    <=    9\'d0;
        else if(cmd_cnt == CMD_END)
            col_addr    <=    col_addr + 3\'d4;
//sdram_addr
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            sdram_addr    <=    12\'d0;
        else case(cmd_cnt)
            3\'d2:
                sdram_addr    <=    row_addr;
            3\'d3:
                sdram_addr    <=    col_addr;
            
            default:
                sdram_addr    <=    row_addr;
        endcase
//sdram_bank
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            sdram_bank    <=    2\'b00;
endmodule

在模块端口列表中,用key_wr来接收写请求信号,这个写请求信号,是在没有写完之前一直拉高的,在写完了全部数据之后才拉低的。

在写模块中,让SDRAM循环着写16’h0012,16’h1203,16’h562f,16’hfe12这四个数据。

另外一点,在这个写模块中,每写完4个数据,也就是突发结束后,有一个写完标志,从而使状态机跳转到仲裁状态,然后如果数据没写完,由于写请求是拉高的,所以如果此时没有刷新请求,那状态机还是会跳转到写状态继续写的。


读操作模块 ↓

module    sdram_read(
        input    wire        sclk,
        input    wire        s_rst_n,
        input    wire        rd_en,
        input    wire    [5:0]    state,
        input    wire        ref_req,        //自动刷新请求
        input    wire        key_rd,            //来自外部的读请求信号
        input    wire    [15:0]    rd_dq,        //sdram的数据端口
        
        output    reg    [3:0]    sdram_cmd,
        output    reg    [11:0]    sdram_addr,
        output    reg    [1:0]    sdram_bank,
        output    reg        rd_req,            //读请求
        output    reg        flag_rd_end        //突发读结束标志
        );
 
    parameter    NOP    =    4\'b0111,
            PRE    =    4\'b0010,
            ACT    =    4\'b0011,
            RD    =    4\'b0101,        //SDRAM的读命令(给读命令时需要给A10拉低)
            CMD_END    =    4\'d12,            //
            COL_END    =    9\'d508,            //最后四个列地址的第一个地址
            ROW_END    =    12\'d4095,        //行地址结束
            AREF    =    6\'b10_0000,        //自动刷新状态
            READ    =    6\'b01_0000;        //状态机的读状态
        
    reg    [11:0]    row_addr;
    reg    [8:0]    col_addr;
    reg    [3:0]    cmd_cnt;
    reg        flag_act;                //发送ACT命令标志(单独设立标志,便于跑高速)
 
//flag_act
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            flag_act    <=    1\'b0;
        else if(flag_rd_end == 1\'b1 && ref_req == 1\'b1)
            flag_act    <=    1\'b1;
        else if(flag_rd_end == 1\'b1)
            flag_act    <=    1\'b0;
//rd_req
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1\'b0)
            rd_req    <=    1\'b0;
        else if(rd_en == 1\'b1)
            rd_req  

以上是关于verilog怎么控制iic的读写,怎么控制多个数据的。。求大神的主要内容,如果未能解决你的问题,请参考以下文章

IIC读写AT24C02代码2——串口命令控制多页读写

verilog双端口读写德ram怎么写

arduino怎么用spi读从机寄存器数据

stm32是所有型号都有iic吗?没有at24c02能怎么配置iic?

FPGA基础设计:IIC协议

用MCU通过IIC控制ADV7611BSWZ-RL的寄存器,可是无法读取与无法写入,是啥原因?要怎么解决?