【FPGA】verilog实现的i2c接口控制
i2c协议规范:
一、时钟
首先第一步是产生fast-mode的400khz的scl速率,假设方波高低电平各占一半,即1.25us,理论上不满足规范上scl低周期1.3us,但是绝大多数器件都支持稍微超过400khz的速率。
我们仍打算产生一个规范内的速率。输入时钟clk=20mhz,计数12+1次后翻转,即可产生一个周期为13x2x50ns=1.3us的方波clk_800,这个周期满足规范。
也可以改变计数方式,产生一个高低占比不一样的波形,可以适当提高速率,同时也满足规范。
// +clk2------------------------------------------ // 1) 20mhz -> clk2 -> SCL-400khz clk2==2xSCL // 2) cnt = 20m/800k = 25 /2 = 12 cnt[3:0] // -clk2------------------------------------------ always @(posedge clk or negedge rst_n) if(rst_n==0) begin cnt_clk <= 0; clk_800k <= 0; end else if(cnt_clk==12) begin cnt_clk <= 0; clk_800k <= ~clk_800k;// scl@falling sda@rising end else cnt_clk <= cnt_clk +1;
二、我们的规划是,scl在clk_800k的下降沿翻转,而sda在clk_800k的上升沿变化,这样就能保证在scl的高电平周期,sda是绝对稳定的,同时也能方便地产生start/stop条件。
根据上面clk的计数,可知scl的周期是1.3us x2=2.6us,速率约是384khz,没到400k。
// +iic------------------------------------------ // 1) // -iic------------------------------------------ always @(negedge clk_800k or negedge rst_n)// negedge if(rst_n==0) iic_scl <= 0; else iic_scl <= ~iic_scl;// 400k
三、接下来就是采用状态机控制产生协议要求的时序波形。在clk_800k的rising沿动作。
IIC_start:检测到scl的高,将sda拉低,产生start条件。占用了1/4个scl周期,0.65us,完全满足start的建立条件;
IIC_data_setup:这一节拍正好是scl的低周期,不需要判断,在sda上送出数据的MSB;
IIC_data_hold:scl的高周期,sda必须保持不变。循环传出8个bit,完成一个字节后跳到下一状态;
IIC_ack_setup:先要释放sda,让从器件去拉低响应;
IIC_ack_hold:判断响应;如果不响应,或者我这里要求传完3个byte的数据,跳到下一状态;
IIC_stop_setup:这一步主要是把sda拉低,准备做stop;
IIC_stop_hold:在scl高周期,sda拉高,产生stop条件;结束。
只要第一步的start条件切入准了,后面按照节拍来,就不需要反复判断scl的高低了。
// +iic------------------------------------------ // 1) // -iic------------------------------------------ always @(posedge clk_800k or negedge rst_n) if(rst_n==0) begin NS_iic <= IIC_idle; iic_sda <= 1; finish_iic <= 0; end else case(NS_iic) IIC_idle: begin if(start_iic==1) NS_iic <= IIC_start; else NS_iic <= IIC_idle; end IIC_start: begin if(iic_scl==1) begin NS_iic <= IIC_data_setup; iic_sda <= 0;// start condition data_iic <= bram_dout[23:0]; cnt_byte <= 2; cnt_bit <= 0;// 0->7 end // else // NS_iic <= IIC_start; end IIC_data_setup:// data setup begin NS_iic <= IIC_data_hold; iic_sda <= data_iic[23]; cnt_bit <= cnt_bit -1; end IIC_data_hold:// data hold begin if(cnt_bit==0) NS_iic <= IIC_ack_setup; else NS_iic <= IIC_data_setup; data_iic <= {data_iic[22:0],1'b0}; end IIC_ack_setup:// ack prepare begin NS_iic <= IIC_ack_hold; iic_sda <= 1'bz; end IIC_ack_hold:// ack response begin if(iic_sda==1||cnt_byte==0) NS_iic <= IIC_stop_setup; else NS_iic <= IIC_data_setup; cnt_byte <= cnt_byte -1; end IIC_stop_setup: begin NS_iic <= IIC_stop_hold; iic_sda <= 0; finish_iic <= 1; end IIC_stop_hold: begin NS_iic <= IIC_idle; iic_sda <= 1; finish_iic <= 0; end default: begin NS_iic <= IIC_idle; end endcase
每次发送3字节的数据,addr+reg+data。cnt_byte是计数3个字节的cnt_bit是计数每个字节内的bit位。蓝色是相应位。
start条件。发送的数据是AA,即10101010,跟计数位相对应。
stop条件。
//