基于FPGA的IIC通讯-MASTER接收篇

(三)、主机读

        上一篇文章完成了IIC写程序代码,这篇文章主要是要实现IIC读程,有了IIC写程序的经验,对于IIC读来说就不会有一开始的迷茫,话不多说,先看下IIC读所需要的过程如何,如下图示:

        如图所示,上述包含了单字节读和多字节连续读的时序过程,分别为开始信号+从机地址+写信号+应答+寄存器地址+应答+再次的开始信号+从机地址+读信号+应答+data0+...+datan+PEC+应答+停止信号。

        对于PEC,他是IIC得一种校验码,这里先不用管它,因为大多数场景不需要使用,但是这个d代码也可以接收,此外,相对于IIC写代码来说,读代码会比写代码要复杂一点,另外还需注意中间得起始信号时序。

        话不多说,直接上代码,请各位注意版权问题,其次,代码在仿真和项目中已经初步实现读取功能,但是是否可应用于大多数场景还是未知得,若出现bug还需再次优化。

// ********************************************************
// Copyright: 优快云 account juningc
// The email address is: junpingc@noreply.gitcode.com
// Unauthorized copying is prohibited. If you need to use it, please contact the author
// All interpretation rights belong to the copyright author
// 2025年04月11日
// ********************************************************
module iic_read_master #
(
   parameter               DWID       = 8       ,
   parameter               UCNT       = 25      ,
   parameter               BUILD_TIME = 5       ,  //us
   parameter               HOLD_TIME  = 4          //us
)
(
   input                   clk_sys              ,
   input                   rst_sys              ,
   //
   input                   write_dena           ,
   input      [6:0]        slave_addr           ,
   input      [7:0]        regis_addr           ,

   input      [2:0]        read_mode            ,
   output reg              read_valid           ,
   output reg [7:0]        read_tdata           ,
   //
   input                   i2c_scl_m_i          ,
   output                  i2c_scl_m_o          ,
   output                  i2c_scl_m_t          ,
   input                   i2c_sda_m_i          ,
   output                  i2c_sda_m_o          ,
   output                  i2c_sda_m_t
);
// ********************************************************
// localparam
// ********************************************************
localparam                 IDLE    = 4'd0       ;
localparam                 START   = 4'd1       ;
localparam                 SADDR   = 4'd2       ;
localparam                 RADDR   = 4'd3       ;
localparam                 AGAIN   = 4'd4       ;
localparam                 DADDR   = 4'd5       ;
localparam                 RDATA   = 4'd6       ;
localparam                 RACK    = 4'd7       ;
localparam                 STOP    = 4'd8       ;


// ********************************************************
// signal
// ********************************************************
reg   [3:0]                cstate = IDLE        ;
reg   [3:0]                nstate = IDLE        ;

wire                       write_start          ;
reg                        write_sack           ;
wire                       pedge_sack           ;

reg   [15:0]               cnt_us = 0           ;
reg   [7:0]                cnt_div = 0          ;
reg   [3:0]                cnt_stop = 0         ;
reg   [3:0]                cnt_tags = 0         ;

reg   [2:0]                shift_wcnt = 0       ;
reg   [3:0]                shift_rcnt = 0       ;
reg   [7:0]                shift_dat = 0        ;

reg                        reg_str = 0          ;
reg                        reg_scl              ;
reg                        reg_sda              ;

wire                       scl_pedge            ;
wire                       scl_nedge            ;
wire                       sda_pedge            ;
wire                       sda_nedge            ;
// ********************************************************
// process
// ********************************************************

always @ ( posedge clk_sys )
begin
   if( rst_sys || &cnt_stop )
      reg_str <= 0 ;
   else if( write_start )
      reg_str <= 1 ;
   else ;
end

always @ ( posedge clk_sys ) // 1s == 25M    1us == 25次
begin
   if( rst_sys || &cnt_stop )
      cnt_div <= 0 ;
   else if( cnt_div == UCNT - 1 )
      cnt_div <= 0 ;
   else if( reg_str )
      cnt_div <= cnt_div + 1 ;
   else ;
end

always @ ( posedge clk_sys )
begin
   if( rst_sys || cstate == STOP )
      cnt_us <= 0 ;
   else if( reg_str == 1 && cnt_div == UCNT - 1 )
      cnt_us <= cnt_us + 1 ;
   else ;
end

// ========================================================
//
// ========================================================

always @ ( posedge clk_sys )
begin
   if( rst_sys )
      cstate = IDLE ;
   else
      cstate = nstate ;
end

always @ ( * )
begin
   case( cstate )
      IDLE : begin
         if( write_start )
            nstate = START ;
         else
            nstate = IDLE ;
      end
      START : begin
         if( cnt_us == BUILD_TIME + HOLD_TIME )
            nstate = SADDR ;
         else
            nstate = START ;
      end
      SADDR : begin
         if( pedge_sack )
            nstate = RADDR ;
         else
            nstate = SADDR ;
      end
      RADDR : begin
         if( pedge_sack )
            nstate = AGAIN ;
         else
            nstate = RADDR ;
      end
      AGAIN : begin
         if( cnt_us == 16'h2f + 16'h5 + 16'h4 )
            nstate = DADDR ;
         else
            nstate = AGAIN ;
      end
      DADDR : begin
         if( pedge_sack )
            nstate = RDATA ;
         else
            nstate = DADDR ;
      end
      RDATA : begin
         if( shift_rcnt == 0 && scl_nedge && read_mode[2] == 0 )
            nstate = RACK ;
         else if( shift_rcnt == 0 && scl_nedge && read_mode[2] == 1 && cnt_tags == read_mode[1:0] + 1 )
            nstate = RACK ;
         else
            nstate = RDATA ;
      end
      RACK : begin
         if( scl_pedge )
            nstate = STOP ;
         else
            nstate = RACK ;
      end
      STOP : begin
         if( &cnt_stop )
            nstate = IDLE ;
         else
            nstate = STOP ;
      end
      default:nstate = IDLE ;
   endcase
end
// ========================================================
//
// ========================================================
always @ ( posedge clk_sys )
begin
   if( rst_sys || cstate == STOP )
      reg_scl <= 1 ;
   else if( cnt_us <= BUILD_TIME + HOLD_TIME )
      reg_scl <= 1 ;
   else if( cstate == AGAIN && cnt_us >= 16'h2f && cnt_us < 16'h38 )
      reg_scl <= 1 ;
   else if( cstate == AGAIN && ( cnt_us == 16'h2f + 16'h5 + 16'h4 ) )
      reg_scl <= 0 ;
   else if( cstate == RACK && scl_pedge )
      reg_scl <= 1 ;
   else if( cnt_us % 2 == 0 )
      reg_scl <= 0 ;
   else if( cnt_us % 2 == 1 )
      reg_scl <= 1 ;
   else ;
end

always @ ( posedge clk_sys )
begin
   if( rst_sys || pedge_sack )
      shift_wcnt <= 0 ;
   else if( i2c_sda_m_t == 1'b0 && i2c_sda_m_i == 1'b0 )
      shift_wcnt <= shift_wcnt ;
   else if(( cstate == SADDR || cstate == RADDR || cstate == DADDR ) && scl_nedge )
      shift_wcnt <= shift_wcnt + ~&shift_wcnt ;
   else ;
end

always @ ( posedge clk_sys )
begin
   if( rst_sys )
      reg_sda <= 1 ;
   else if(( cstate == IDLE || cstate == START || cstate == SADDR || cstate == RADDR || cstate == AGAIN || cstate == DADDR ) && ( i2c_sda_m_i == 0 ))
      reg_sda <= 1'bz ;
   else if( reg_str == 1 && cnt_us == BUILD_TIME )
      reg_sda <= 0 ;
   else if( cstate == AGAIN && cnt_us == 16'h2e )
      reg_sda <= 1 ;
   else if( cstate == AGAIN && cnt_us == 16'h33 )
      reg_sda <= 0 ;
   else if( cstate == SADDR && scl_nedge )
   begin
      case ( shift_wcnt )
         0 : reg_sda <= slave_addr[6];
         1 : reg_sda <= slave_addr[5];
         2 : reg_sda <= slave_addr[4];
         3 : reg_sda <= slave_addr[3];
         4 : reg_sda <= slave_addr[2];
         5 : reg_sda <= slave_addr[1];
         6 : reg_sda <= slave_addr[0];
         7 : reg_sda <= 0 ; // WRITER
         default : reg_sda <= 0 ;
      endcase
   end
   else if( cstate == RADDR )
   begin
      case ( shift_wcnt )
         0 : reg_sda <= regis_addr[7];
         1 : reg_sda <= regis_addr[6];
         2 : reg_sda <= regis_addr[5];
         3 : reg_sda <= regis_addr[4];
         4 : reg_sda <= regis_addr[3];
         5 : reg_sda <= regis_addr[2];
         6 : reg_sda <= regis_addr[1];
         7 : reg_sda <= regis_addr[0] ;
         default : reg_sda <= 0 ;
      endcase
   end
   else if( cstate == DADDR && scl_nedge )
      case ( shift_wcnt )
         0 : reg_sda <= slave_addr[6];
         1 : reg_sda <= slave_addr[5];
         2 : reg_sda <= slave_addr[4];
         3 : reg_sda <= slave_addr[3];
         4 : reg_sda <= slave_addr[2];
         5 : reg_sda <= slave_addr[1];
         6 : reg_sda <= slave_addr[0];
         7 : reg_sda <= 1 ; // READ
         default : reg_sda <= 0 ;
      endcase
   else if( cstate == RDATA && shift_rcnt == 9 && scl_nedge && read_mode[2] == 0)
      reg_sda <= 1 ;
   else if( cstate == RDATA && shift_rcnt == 0 && scl_nedge && read_mode[2] == 1)
      reg_sda <= 1'hz ;
   else if( cstate == RDATA && shift_rcnt == 9 && scl_nedge && read_mode[2] == 1 && cnt_tags != read_mode + 1 )
      reg_sda <= 0 ;
   else if( cstate == RACK && scl_nedge )
      reg_sda <= 0 ;
   else if( cstate == STOP && cnt_stop == 4 )
      reg_sda <= 1 ;
   else ;
end

always @ ( posedge clk_sys )
begin
   if( rst_sys )
      write_sack <= 0 ;
   else if(( cstate != IDLE )&& ( i2c_sda_m_t == 0 && i2c_sda_m_i == 0 ))
      write_sack <= 1 ;
   else
      write_sack <= 0 ;
end

always @ ( posedge clk_sys )
begin
   if( rst_sys || cstate == IDLE )
      cnt_tags <= 0 ;
   else if( cstate == RDATA && shift_rcnt == 9 && scl_pedge && read_mode[2] == 1 )
      cnt_tags <= cnt_tags + ~&cnt_tags ;
   else ;
end

// ========================================================
// READ
// ========================================================
always @ ( posedge clk_sys )
begin
   if( rst_sys )
      shift_rcnt <= 0 ;
   else if(shift_rcnt == 9 && scl_pedge )
      shift_rcnt <= 0 ;
   else if( cstate == RDATA && scl_pedge && ( cnt_tags == 0 || read_mode[2] == 0 ))
      shift_rcnt <= shift_rcnt + 1 ;
   else if( cstate == RDATA && (cnt_tags > 0 && scl_nedge && read_mode[2] == 1 ))
      shift_rcnt <= shift_rcnt + 1 ;
end

always @ ( posedge clk_sys )
begin
   if( rst_sys )
      shift_dat <= 0 ;
   else if( cstate == RDATA && scl_pedge )
      shift_dat <= {shift_dat[6:0],i2c_sda_m_i};
   else ;
end

// always @ ( posedge clk_sys )
// begin
   // if( rst_sys || ( cstate == RDATA && shift_edge == 2 && scl_pedge ))
      // shift_edge <= 0 ;
   // else if( cstate == RDATA && shift_rcnt == 9 && scl_pedge )
      // shift_edge <= shift_edge + 1 ;
   // else if( cstate == RDATA && shift_rcnt == 0 && scl_pedge && shift_edge == 1 )
      // shift_edge <= shift_edge + ~&shift_edge ;
   // else ;
// end

always @ ( posedge clk_sys )
begin
   if( rst_sys || cstate == IDLE )
      cnt_stop <= 0 ;
   else if( cstate == STOP && reg_scl && cnt_div == UCNT - 1 )
      cnt_stop <= cnt_stop + ~&cnt_stop ;
   else ;
end

assign i2c_scl_m_o = reg_scl ;
assign i2c_sda_m_o = reg_sda ;
assign i2c_sda_m_t = i2c_sda_m_i ;
// ========================================================
//
// ========================================================

always @ ( posedge clk_sys )
begin
   if( rst_sys )
      read_valid <= 0 ;
   else if( cstate == RDATA && shift_rcnt == 9 )
      read_valid <= 1 ;
   else
      read_valid <= 0 ;
end

always @ ( posedge clk_sys )
begin
   if( rst_sys )
      read_tdata <= 0 ;
   else if( cstate == RDATA && shift_rcnt == 9 )
      read_tdata <= shift_dat ;
   else ;
end



// ========================================================
//
// ========================================================
edge_detect u0_edge_detect
(
   //input
   .clk                          ( clk_sys               ),
   .sig_in                       ( write_dena            ),
   //output
   .p_edge                       ( write_start           ),
   .n_edge                       (                       )
);

edge_detect u2_edge_detect
(
   //input
   .clk                          ( clk_sys               ),
   .sig_in                       ( write_sack            ),
   //output
   .p_edge                       ( pedge_sack            ),
   .n_edge                       (                       )
);

edge_detect u1_edge_detect
(
   //input
   .clk                          ( clk_sys               ),
   .sig_in                       ( reg_scl               ),
   //output
   .p_edge                       ( scl_pedge             ),
   .n_edge                       ( scl_nedge             )
);

edge_detect u3_edge_detect
(
   //input
   .clk                          ( clk_sys               ),
   .sig_in                       ( cstate == RDATA       ),
   //output
   .p_edge                       ( sda_pedge             ),
   .n_edge                       ( sda_nedge             )
);




endmodule

        具体得仿真等到IIC-Slave读取代码书写完成后会发布上传

### FPGA实现IIC Master的代码示例 以下是基于Verilog语言的一个简单IIC Master模块实现代码。该代码实现了基本的IIC通信功能,包括启动条件、停止条件、发送字节和接收应答等功能。 ```verilog module iic_master ( input wire clk, // 主时钟 input wire reset_n, // 复位信号,低电平有效 output reg sda, // 数据线SDA output reg scl, // 时钟线SCL input wire start_iic, // 启动IIC传输请求 input wire [7:0] data_out,// 要发送的数据 output reg ack_received, // 接收到ACK的状态标志 output reg busy // 总线忙状态指示 ); parameter IDLE = 2'b00; parameter START = 2'b01; parameter SEND_BYTE = 2'b10; parameter STOP = 2'b11; reg [1:0] state_reg, state_next; // 状态寄存器 reg [3:0] bit_count_reg, bit_count_next; // 计数器用于跟踪当前处理的比特位置 reg [7:0] byte_reg, byte_next; // 当前要发送的字节缓存 reg sda_reg, sda_next; // SDA内部寄存器 reg scl_reg, scl_next; // SCL内部寄存器 // 默认初始化 always @(posedge clk or negedge reset_n) begin if (!reset_n) begin state_reg <= IDLE; bit_count_reg <= 4'd0; byte_reg <= 8'h00; sda_reg <= 1'b1; scl_reg <= 1'b1; ack_received <= 1'b0; busy <= 1'b0; end else begin state_reg <= state_next; bit_count_reg <= bit_count_next; byte_reg <= byte_next; sda_reg <= sda_next; scl_reg <= scl_next; ack_received <= (state_reg == STOP && !scl_reg); busy <= !(state_reg == IDLE || state_reg == STOP); end end // 下一状态逻辑 always @(*) begin case(state_reg) IDLE: begin if(start_iic) begin state_next = START; bit_count_next = 4'd7; byte_next = data_out; sda_next = 1'b0; // 开始条件:拉低SDA scl_next = 1'b1; end else begin state_next = IDLE; bit_count_next = bit_count_reg; byte_next = byte_reg; sda_next = 1'b1; scl_next = 1'b1; end end START: begin state_next = SEND_BYTE; bit_count_next = bit_count_reg; byte_next = byte_reg; sda_next = sda_reg; scl_next = 1'b0; // 结束开始条件:释放SDA并保持SCL高 end SEND_BYTE: begin if(bit_count_reg >= 0) begin state_next = SEND_BYTE; bit_count_next = bit_count_reg - 1; byte_next = {byte_reg[6:0], 1'b0}; sda_next = byte_reg[7]; scl_next = ~scl_reg; // 切换SCL高低电平完成一位数据传输 end else begin state_next = STOP; bit_count_next = 4'd0; byte_next = 8'h00; sda_next = 1'b1; // 准备结束条件 scl_next = 1'b1; end end STOP: begin state_next = IDLE; bit_count_next = 4'd0; byte_next = 8'h00; sda_next = 1'b1; // 停止条件:先拉高SDA再拉高SCL scl_next = 1'b1; end default: begin state_next = IDLE; bit_count_next = 4'd0; byte_next = 8'h00; sda_next = 1'b1; scl_next = 1'b1; end endcase end assign sda = sda_reg; assign scl = scl_reg; endmodule ``` 此代码展示了如何通过有限状态机(FSM)来管理IIC协议中的各种操作阶段[^1]。它还涉及到了物理层的设计细节,例如SDA和SCL信号的具体驱动方法[^2]。 #### 关键点说明 - **FSM结构**:采用了一个简单的四态有限状态机(IDLE, START, SEND_BYTE, STOP),用来控制整个IIC事务流程。 - **同步逻辑**:所有的寄存器更新都在`clk`上升沿触发,并受`reset_n`复位信号影响。 - **异步事件响应**:当检测到`start_iic`信号激活时,立即进入START状态发起新的IIC事务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值