Comunicación I2C basada en FPGA (dos)

Tabla de contenido

Tres, implementación FPGA del protocolo I2C

1. Diseño de interfaz I2C

2. Verificación de la simulación


 El enlace de descarga del proyecto para la realización de funciones generales del sistema de lectura y escritura EEPROM (explicado en el próximo blog, incluido el contenido de este artículo) de este tema es el siguiente:
 https://download.csdn.net/download/qq_33231534/12503289


 

Tres, implementación FPGA del protocolo I2C

El blog anterior dio una explicación general del protocolo de bus I2C, así como una explicación detallada del tiempo de lectura y escritura de la EEPROM del dispositivo de bus I2C (AT24C64). A continuación, se explica cómo leer y escribir el dispositivo EEPROM en la FPGA, así como el diseño de la interfaz y el sistema de depuración. Dé una descripción específica. Esta plataforma experimental utiliza la placa de desarrollo AC620 de Xiaomeige, y el chip FPGA es el ciclón IV EP4CE10F17C8N.

1. Diseño de interfaz I2C (EEPROM)

 

Figura 1: Diagrama de tiempo de escritura de bytes
Figura 2: Diagrama de tiempo de escritura de página
Figura 3: Diagrama de tiempos de lectura aleatoria
Figura 4: Shix de lectura continua

 De acuerdo con los cuatro diagramas de tiempo de lectura y escritura anteriores, escriba el tiempo de la interfaz I2C y el diseño de la interfaz del protocolo I2C. Aquí, la máquina de estado se utiliza para diseñar todo el diseño en 9 estados, a saber, IDLE, WR_START, WR_DEVICE, WR_ADDR, WR_DATA, RD_START, RD_DEVICE, RD_DATA, DETENER. El diagrama de transición de estado es el siguiente:

 El significado de cada estado y el nombre y función de la señal de interfaz del módulo son los siguientes:

Escriba aquí las señales de la interfaz en una tabla para que quede más claro. como sigue:

Tabla: Descripción de la señal de control del módulo de interfaz I2C
Nombre de la señal E / S Dígitos Función descriptiva
clk yo 1 Reloj del sistema 50MHz
rst_n yo 1 Reinicio de sistema
rd_data_num   yo 6 Número de datos leídos, máximo 32
wr_data_num   yo 6 Número de datos a escribir, máximo 32
word_addr yo dieciséis Dirección de palabra de datos, AT24C64 es una dirección de 16 bits
device_addr   yo 3 La dirección variable del dispositivo está determinada por la conexión del hardware. La plataforma experimental es 3'b001
reyezuelo   yo 1 Activar escritura de datos
wr_data   yo 8 Escribir datos
rd_en yo 1 Leer datos habilitar
wr_data_vaild EL 1 Escribir un bit válido de datos, leer varios datos es generar un bit válido cuando se lee cada dato
rd_data EL 8 Leer datos
rd_data_vaild EL 1 Leer datos de bits válidos, escribir varios datos es generar un bit válido cuando se escriben todos los datos
hecho EL 1 Indicador de fin de operación de lectura y escritura
scl EL 1 Línea de reloj serial
sda yo 1 Línea de datos en serie

Directamente en el código:

//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN   
//File name:           I2C_ctrl.v
//Last modified Date:  2020/6/6
//Last Version:        
//Descriptions:        EEPROM(AT24C64)接口设计
//-------------------------------------------------------------------

module I2C_ctrl(
    input                   clk             ,//系统时钟50MHz
    input                   rst_n           ,//系统复位
    input   [ 5: 0]         rd_data_num     ,//读数据个数,最大32
    input   [ 5: 0]         wr_data_num     ,//写数据个数,最大32
    input   [15: 0]         word_addr       ,//数据字地址,AT24C64是16位地址
    input   [ 2: 0]         device_addr     ,//设备可变地址,由硬件连接决定,本实验平台为3‘b001
    input                   wr_en           ,//写数据使能
    input   [ 7: 0]         wr_data         ,//写数据
    input                   rd_en           ,//读数据使能

    output reg              wr_data_vaild   ,//写数据有效位,读多个数据就是读完每个数据时产生一个有效位
    output reg [7:0]        rd_data         ,//读数据
    output reg              rd_data_vaild   ,//读数据有效位,写多个数据就是写完每个数据时产生一个有效位
    output reg              done            ,//读写操作结束标志
    output reg              scl             ,//串行时钟线

    inout                   sda              //串行数据线
);

parameter SYS_CLK   = 50_000_000  ;//系统时钟频率
parameter SCLK      = 100_000     ;//串行时钟线时钟频率

localparam SCLK_CNT = SYS_CLK/SCLK;//产生时钟线的时钟计数次数

//状态机状态定义
parameter IDLE      = 4'd0      ;//空闲状态
parameter WR_START  = 4'd1      ;//写数据启动状态
parameter WR_DEVICE = 4'd2      ;//发送设备地址状态
parameter WR_ADDR   = 4'd3      ;//发送数据字地址状态
parameter WR_DATA   = 4'd4      ;//发送数据状态
parameter RD_START  = 4'd5      ;//读数据启动
parameter RD_DEVICE = 4'd6      ;//读操作设备地址状态
parameter RD_DATA   = 4'd7      ;//读数据状态
parameter STOP      = 4'd8      ;//停止状态

reg   [  3: 0]         state_c  ;//状态机改变后的状态,时序逻辑
reg   [  3: 0]         state_n  ;//状态机当前状态,组合逻辑

reg                    add_flag      ;//计数条件标志
reg   [  8: 0]         sclk_cnt      ;//sclk时钟信号计数
reg                    scl_high      ;//scl信号高电平中间信号,1个时钟周期
reg                    scl_low       ;//scl信号低电平中间信号,1个时钟周期
reg                    wr_flag       ;//写标志
reg                    rd_flag       ;//读标志
reg                    ack           ;//响应信号
reg                    waddr_cnt     ;//数据字地址字节计数,共2字节
reg   [  5: 0]         wdata_cnt     ;//写数据字节计数,最大32个
reg   [  5: 0]         rdata_cnt     ;//读数据字节计数,最大32个
reg   [  4: 0]         bit_cnt       ;//对传输的8位数据中的scl_highhe scl_low信号计数
reg                    sda_en        ;//三态信号sda使能
reg                    sda_reg       ;//三态信号sda寄存数据
reg   [  7: 0]         device_addr_a ;//传输的8位设备地址
reg   [  2: 0]         sda_reg_cnt   ;//传输的8位数据传递数
wire  [  7: 0]         word_addr_high;//数据字地址高8位
wire  [  7: 0]         word_addr_low ;//数据字地址低8位

assign word_addr_high = word_addr[15:8];
assign word_addr_low  = word_addr[ 7:0];

assign sda = sda_en ? sda_reg : 1'bz;

//sclk计数器计数标志
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        add_flag <= 0;
    end
    else if(wr_en || rd_en) begin
        add_flag <= 1;
    end
    else if(done)begin
        add_flag <= 0;
    end
end

//scl信号计数器
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        sclk_cnt <= 0;
    end
    else if(add_flag) begin
        if((add_flag && sclk_cnt==SCLK_CNT-1) || done)
            sclk_cnt <= 0;
        else
            sclk_cnt <= sclk_cnt + 1'b1;
    end
end

//scl信号生成
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        scl <= 1;
    end
    else if(sclk_cnt==SCLK_CNT/2-1) begin
        scl <= 0;
    end
    else if(sclk_cnt==1'b0)begin
        scl<= 1;
    end
    else begin
        scl <= scl;
    end
end

//scl_high信号:scl信号高电平正中间脉冲
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        scl_high <= 0;
    end
    else if(sclk_cnt==SCLK_CNT*1/4-1) begin
        scl_high <= 1;
    end
    else begin
        scl_high <= 0;
    end
end

//scl_low信号:scl信号低电平正中间脉冲
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        scl_low <= 0;
    end
    else if(sclk_cnt==SCLK_CNT*3/4-1) begin
        scl_low <= 1;
    end
    else begin
        scl_low <= 0;
    end
end

//写信号标志
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        wr_flag <= 0;
    end
    else if(wr_en) begin
        wr_flag <= 1;
    end
    else if(done) begin
        wr_flag <= 0;
    end
end

//读信号标志
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rd_flag <= 0;
    end
    else if(rd_en) begin
        rd_flag <= 1;
    end
    else if(done) begin
        rd_flag <= 0;
    end
end

//状态机第一段
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        state_c <= IDLE;
    end
    else begin
        state_c <= state_n;
    end
end

//状态机第二段
always  @(*)begin
    case(state_c)
        IDLE      :begin
                       if(wr_en || rd_en)
                           state_n = WR_START;
                       else
                           state_n = state_c;
                   end
        WR_START  :begin
                       if(scl==0 && scl_low)
                           state_n = WR_DEVICE;
                       else
                           state_n = state_c;
                   end
        WR_DEVICE :begin
                       if(ack==1 && scl_low)
                           state_n = WR_ADDR;
                       else if(ack==0 && scl_low && bit_cnt==17)
                           state_n = IDLE;
                       else
                           state_n = state_c;
                    end
        WR_ADDR   :begin
                       if(wr_flag && ack==1 && waddr_cnt==1 && scl_low)
                           state_n = WR_DATA;
                       else if(rd_flag && ack==1 && waddr_cnt==1 &&scl_low)
                           state_n = RD_START;
                       else if(ack==0 && scl_low && bit_cnt==17)
                           state_n = IDLE;
                       else
                           state_n = state_c;
                   end
        WR_DATA   :begin
                       if(wdata_cnt==wr_data_num-1 && ack==1 && wr_flag==1 && scl_low)
                           state_n = STOP;
                       else if(ack==0 && scl_low && bit_cnt==17)
                           state_n = IDLE;
                       else
                           state_n = state_c;
                   end
        RD_START  :begin
                       if(scl==0 && scl_low)
                           state_n = RD_DEVICE;
                       else
                           state_n = state_c;
                   end
        RD_DEVICE :begin
                       if(ack==1 && scl_low)
                           state_n = RD_DATA;
                       else if(ack==0 && scl_low && bit_cnt==17)
                           state_n = IDLE;
                       else
                           state_n = state_c;
                   end

        RD_DATA   :begin
                       if(rdata_cnt==rd_data_num-1 && rd_flag==1 && bit_cnt==17 && scl_low)
                           state_n = STOP;
                       else 
                           state_n = state_c;
                   end
        STOP      :begin
                       if(scl_high)
                           state_n = IDLE;
                       else
                           state_n = state_c;
                    end
    endcase
end

//在传输设备地址、字地址和数据时要传输8位数据,这里对其计数,计数scl_low和scl_high
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        bit_cnt <= 0;
    end
    else if(state_c==WR_DEVICE || state_c==WR_ADDR || state_c==WR_DATA ||state_c==RD_DEVICE || state_c==RD_DATA) begin
        if(scl_high || scl_low)begin
            if(bit_cnt==17 && scl_low)
                bit_cnt <= 0;
            else
                bit_cnt <= bit_cnt + 1'b1;
        end
    end
    else begin
        bit_cnt <= bit_cnt;
    end
end

//ack响应信号
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        ack <= 0;
    end
    else if(scl_high && bit_cnt==16 && sda==0) begin
        ack <= 1;
    end
    else if(bit_cnt==17 && scl_low)begin
        ack <= 0;
    end
    else begin
        ack <= ack;
    end
end

//2个字地址计数器
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        waddr_cnt <= 0;
    end
    else if(state_c==WR_ADDR && bit_cnt==17 && scl_low) begin
        if(waddr_cnt==1)
            waddr_cnt <= 0;
        else
            waddr_cnt <= waddr_cnt + 1'b1;
    end
    else begin
        waddr_cnt <= waddr_cnt;
    end
end

//写数据个数计数
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        wdata_cnt <= 0;
    end
    else if(state_c==WR_DATA && bit_cnt==17 && scl_low) begin
        if(wdata_cnt==wr_data_num-1)
            wdata_cnt <= 0;
        else
            wdata_cnt <= wdata_cnt + 1'b1;
    end
    else begin
        wdata_cnt <= wdata_cnt;
    end
end

//读数据个数计数
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rdata_cnt <= 0;
    end
    else if(state_c==RD_DATA && bit_cnt==17 && scl_low) begin
        if(rdata_cnt==rd_data_num-1)
            rdata_cnt <= 0;
        else
            rdata_cnt <= rdata_cnt + 1'b1;
    end
    else begin
        rdata_cnt <= rdata_cnt;
    end
end

//sda_en信号输出
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        sda_en <= 0;
    end
    else begin
        case(state_c)
            IDLE:sda_en <= 0;
            WR_START,RD_START,STOP:sda_en <= 1;
            WR_DEVICE,WR_ADDR,WR_DATA,RD_DEVICE:
                begin
                    if(bit_cnt<16)
                        sda_en <= 1;
                    else
                        sda_en <= 0;
                end
            RD_DATA:
                //sda_en <= 0;
                begin
                    if(bit_cnt<16)
                        sda_en <= 0;
                    else
                        sda_en <= 1;
                end
            default:sda_en <= 0;
        endcase
    end
end

//wr_data_vaild信号:写数据有效信号标志
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        wr_data_vaild <= 0;
    end
    else if(state_c==WR_DATA && ack==1 && scl_low && bit_cnt==17) begin
        wr_data_vaild <= 1;
    end
    else begin
        wr_data_vaild <= 0;
    end
end

//rd_data信号:读出8位数据
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rd_data <= 0;
    end
    else if(state_c==RD_DATA && bit_cnt<15 && scl_high) begin
        rd_data <= {rd_data[6:0],sda};
    end
    else begin
        rd_data <= rd_data;
    end
end

//rd_data_vaild信号:读出8位数据有效信号
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rd_data_vaild <= 0;
    end
    else if(state_c==RD_DATA && bit_cnt==15 && scl_low) begin
        rd_data_vaild <= 1;
    end
    else begin
        rd_data_vaild <= 0;
    end
end

//done:I2C工作结束标志
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        done <= 0;
    end
    else if(state_c==STOP && scl_high) begin
        done <= 1;
    end
    else begin
        done <= 0;
    end
end

//设备地址
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        device_addr_a <= 0;
    end
    else if(rd_flag && (state_c==RD_DEVICE || state_c==RD_START || state_c==RD_DATA))begin
        device_addr_a <= {4'b1010,device_addr,1'b1};
    end
    else begin
        device_addr_a <= {4'b1010,device_addr,1'b0};
    end
end

//sda_reg_cnt
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        sda_reg_cnt <= 7;
    end
    else if(state_c==WR_DEVICE || state_c==WR_ADDR || state_c==WR_DATA ||state_c==RD_DEVICE || state_c==RD_DATA) begin
        if(bit_cnt<16 && scl_low)begin
            if(sda_reg_cnt==0)
                sda_reg_cnt <= 7;
            else
                sda_reg_cnt <= sda_reg_cnt - 1'b1;
        end
    end
    else begin
        sda_reg_cnt <= sda_reg_cnt;
    end
end


//sda_reg
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        sda_reg <= 1;
    end
    else begin
        case(state_c)
            WR_START:begin
                        if(scl_high)
                            sda_reg <= 0;
                        else
                            sda_reg <= sda_reg;
                    end
            WR_DEVICE:begin
                         sda_reg <= device_addr_a[sda_reg_cnt];
                      end
            WR_ADDR:begin
                       if(waddr_cnt==0)
                           sda_reg <= word_addr_high[sda_reg_cnt];
                       else if(waddr_cnt==1)
                           if(bit_cnt<16)
                               sda_reg <= word_addr_low[sda_reg_cnt]; 
                           else
                               sda_reg <= 1;
                    end
            WR_DATA:begin
                        sda_reg <= wr_data[sda_reg_cnt];   //输入信号wr_data新数据应该wr_data_vaild时给出
                    end          
            RD_START:begin
                        if(scl_high)
                            sda_reg <= 0;
                        else
                            sda_reg <= sda_reg;
                    end
            RD_DEVICE:begin
                         sda_reg <= device_addr_a[sda_reg_cnt];
                      end
            RD_DATA:/*begin
                        if(bit_cnt==16 || bit_cnt==17)
                            sda_reg <= 0;
                        else
                            sda_reg <= sda_reg;
                    end*/
                   if(rdata_cnt==rd_data_num-1 && bit_cnt>15)
                       sda_reg <= 1;
                   else
                       sda_reg <= 0;
            STOP:begin
                        sda_reg <= 0;
                        /*if(scl_high)
                            sda_reg <= 1;
                        else
                            sda_reg <= sda_reg;*/
                    end
            default: sda_reg <= 1;
        endcase
    end
end
endmodule

2. Verificación de la simulación

El modelo de simulación EEPROM utilizado aquí se utiliza para la verificación de la simulación, y se utiliza el modelo de simulación EEPROM de Micron. Aquellos que lo necesiten pueden descargarlo de mi proyecto. Consulte el enlace de descarga anterior para ver el código fuente completo del proyecto. No entraré en detalles aquí. El código de prueba es el siguiente:

`timescale 1 ns/ 1 ns
module I2C_ctrl_tb();
// test vector input registers
reg clk;
//reg [2:0] device_addr;
//reg [5:0] rd_data_num;
reg rd_en;
reg rst_n;
//reg treg_sda;
reg [15:0] word_addr;
reg [7:0] wr_data;
//reg [5:0] wr_data_num;
reg wr_en;
// wires                                               
wire done;
wire [7:0]  rd_data;
wire rd_data_vaild;
wire scl;
wire sda;
wire wr_data_vaild;

pullup(sda);
// assign statements (if any)                          
//assign sda = treg_sda;
parameter clk_period = 20;

I2C_ctrl i1 (
// port map - connection between master ports and signals/registers   
	.clk(clk),
	.device_addr(3'b000),
	.done(done),
	.rd_data(rd_data),
	.rd_data_num(6'd4),
	.rd_data_vaild(rd_data_vaild),
	.rd_en(rd_en),
	.rst_n(rst_n),
	.scl(scl),
	.sda(sda),
	.word_addr(word_addr),
	.wr_data(wr_data),
	.wr_data_num(6'd4),
	.wr_data_vaild(wr_data_vaild),
	.wr_en(wr_en)
);

M24LC64 u_M24LC64(
    .A0(1'b0), 
    .A1(1'b0), 
    .A2(1'b0), 
    .WP(1'b0), 
    .SDA(sda), 
    .SCL(scl), 
    .RESET(!rst_n)
);

initial clk = 0;
always #(clk_period/2) clk=~clk;

initial begin
    #2;
    rst_n = 0;
    word_addr = 0;
    wr_en = 0;
    wr_data = 0;
    rd_en = 0;

    #(clk_period*200);
    rst_n = 1;
    #(clk_period*20);

    //写入20组数据
    word_addr = 0;
    wr_data = 0;
    repeat(20)begin
        wr_en = 1;
        #(clk_period);
        wr_en = 0;

        repeat(4)begin
            @(posedge wr_data_vaild)
            wr_data = wr_data + 1;
        end

        @(posedge done);
        #(clk_period*200);
        word_addr = word_addr + 4;
    end

    #(clk_period*500);

    //读出刚写入的20组数据
    word_addr = 0;
    repeat(20)begin
        rd_en = 1;
        #(clk_period);
        rd_en = 0;

        @(posedge done);
        #(clk_period*200);
        word_addr = word_addr + 4;
    end

    #(clk_period*500);
    $stop;
end

endmodule

La siguiente figura es el resultado de la simulación de AT24C64 EEPROM:

La siguiente figura es el resultado de la simulación de la secuencia de escritura en AT24C64 EEPROM:

La siguiente figura es el resultado de la simulación de la secuencia de lectura de AT24C64 EEPROM:

Los resultados de la simulación cumplen con los resultados esperados. El próximo artículo llevará a cabo la depuración a nivel de placa del módulo de interfaz I2C y diseñará un experimento de sistema de lectura y escritura EEPROM para verificar la exactitud del diseño.

Supongo que te gusta

Origin blog.csdn.net/qq_33231534/article/details/106589690
Recomendado
Clasificación