文章目录
-
-
-
- 1、IIC协议
-
- 1.1、什么是iic
- 1.2、EEPROM
- 1.3、iic传输特点
- 1.4、时序特点
-
- 1.4.1、起始信号
- 1.4.2、停止信号
- 1.4.3、读操作
- 1.4.4、写操作
- 2、模块设计
-
- 2.1.总体设计理念
- 2.2、 模块状态图
- 2.3、代码部分
-
- 注:完善中 \color{red}{注:完美中} 注:完善中
-
-
1、IIC协议
1.1、什么是iic
IC即为Inter-Inegrated Circuit(集成电路总线)在20世纪80年代左右从Philips公司(即现做NXP半导体公司)设计的简单、双向、二进制总线标准。它只需要两条线(SDA数据线和SCL时钟线)可以实现连接到总线的设备之间的通信,但同时只能有一个主机,即一个主从模式到半双工通信。SDA和SCL都是双向I/O因此,主要适用于数据量小、距离短的主从通信。 数据传输模式分为三种:
- 标准模式:100kbit/s
- 快速模式:400kbit/s
- 高速模式:3.4Mbit/s
1.2、EEPROM
1、EEPROM,可编程只读存储器 (Electrically-Erasable Programmable Read-Only Memory),它是一种半导体存储设备,可以通过电子方式多次复制。相比EPROM,EEPROM可以用特定的电压擦除芯片上的信息,写入新数据,无需紫外线照射或取下。 我们用的是C4开发板型号为24LC04B的EEPROM存储芯片。24LC04B存储容量为512Bytes/4Kbits,里面有两个Block,每个Block有256个字节(8个字节)bit)。读写操作以字节为基础(8bit)基本单位。 3、24LC04B EEPROM 存储芯片的设备地址包括:
- 制造商设置的高度为4位1010,代码
- 用户需要独立设置低3位 x、x、B0 选择块地址
- 字节存储地址共8个bit
4、在IIC主从设备通讯时,主机在发送了起始信号后,接着会向从机发送控制命令。控制命令长度为1个字节,它的高7位为上文讲解的 IIC读写控制位是设备的最低设备地址。EEPROM具体见下图。
- 读写控制位为 0 时,表示主机(FPGA)要对从机(EEPROM)数据写入操作:{7’b1010xxx,1’b0}
- 读写控制位为 1 时,表示主机(FPGA)要对从机(EEPROM)数据读取操作:{7’b1010xxx,1’b1}
1.3、iic传输特点
由于IIC是由数据线(SDA)和时钟线(SCL)因此,两者相互配合,完成数据传输。当时钟线(SCL)高电平时要求数据线(SDA)数据稳定,从机器开始接收主机数据或从机器接收响应;当时钟线(SCL)低电平时数据线(SDA)数据可据,即更新数据以备下次接收。
1.4、时序特点
1.4.1、起始信号
1、IIC传输主要由开始信号、停止信号、应答/非应答、读、写信号五部分组成。 2.起始信号: 当时钟线(SCL)在高电平期间,数据线(SDA)时序图如下图所示:
1.4.2、停止信号
停止信号: 当时钟线(SCL)为高电平期数据线(SDA)从低电平恢复到高电平是终止信号,时序图如下图所示:
1.4.3、读操作
1、读数据: 阅读数据的时间顺序与响应/非响应相同,在时钟线上(SCL)在高电平期间读取数据线(SDA)值,数据线(SDA)高低电平代表1或0 2.当前地址阅读:当前读写操作完成后,24XX04中有一个地址计数器,它将自行添加一个,因此当前地址读取是阅读下一个地址(当前地址读取开始位置-然后编写控制字节,从机器接收响应信号,然后读取数据,没有响应信号-最终结束位置):
3.随机读取(起始位-写控制字节,从机接收响应信号-然后dummuy write 虚写、编写数据地址、从机接收响应信号-再读起始位-读控制字节,从机接收响应信号-读数据-停止位)
4、顺序读: 顺序阅读后随机阅读
1.4.4、写操作
1机在时钟线(SCL)高电平读取数据线(SDA)的数据,因此写数据时需要在时钟线(SCL)在低电平期间改变数据线(SDA)传输数据的值分为字节写和页写。 2.字节写一次只能写一个字节(写开始-写控制字节,从机器接收到响应信号-写数据地址,从机器接收到响应信号。如果数据地址是2位,继续写数据地址,从机器接收到响应信号-写数据,从机器接收到响应信号-停止)
3.页写一次最多写16个字节(每个字节8bit)数据,(写起始位-写控制字节,从机接收响应信号-写数据地址,从机接收响应信号。如果数据地址是2位,继续写数据地址,从机接收响应信号-写数据,从机接收响应信号-继续写数据。直到完成所有数据-停止)
2、模块设计
2.1.总体设计理念
1.使用芯片外设按钮模拟读取请求,收到读写请求信号时,FPGA通过I2C协议向EEPROM将芯片写入单字节数据或E2PROM芯片读取单字节数据,然后使用串口接收需要写入的数据,显示被读取的数据。需要读写控制模块和I2C接口模块:
- 读写控制模块负责发送读写命令和地址,并使用两个fifo分别寄存uart写入数据和EEPROM读出数据。
- 2C接口模块负责这一点指令翻译为I2C协议允许的格式再传输EEPROM中,并将接收到的信息翻译再反馈给FPGA(串并转换)。
- 串口收发模块以及收发分别对应的2个fifo。
2、读:
- 按下key,经过按键消抖模块后输出稳定的key_out信号到eeprom_rw模块中
- eeprom_rw模块接收到读请求信号后向i2c_interface模块发送读请求
- i2c_interface模块接收到读请求后发送读控制字和读地址,然后将接收到的SDA信号进行一个串并转换发送到eeprom_rw模块中
- eeprom_rw模块接收到的rd_data[7:0]缓存到rdfifo中,然后以先进先出的原则依次发送给uart_tx模块
- uart_tx模块将接收到的每一个rd_data[7:0]进行并串转换,然后发送到上位机中,在发送途中拉高busy信号,表示tx线已被占用,等发完这个字节再继续传下一个进来
3、写:
- 由上位机串行发送 x byte的数据到串口接收模块uart_rx中
- uart_rx模块将接收到的串行信号进行串并转换然后发送到eeprom_rw模块中
- eeprom_rw模块,每接收到一次din_vld(数据接收有效信号)后,开启wrfifo的写请求,写入一个字节,直到接收完为止
- wrfifo缓存完成后,eeprom_rw模块向i2c_interface模块发送写请求和需要写入的数据,每次发送1个字节的数据,在收到应答信号后再次发送1个字节
- i2c_interface模块接收到写命令后便开始发送写控制字和写地址,然后将接收到的并行数据wr_data[7:0]进行一个并串转换来写入到eeprom中
2.2、 模块状态图
1、iic模块状态图 WRITE:写完一个字节 READ:读完一个字节继续写数据发一个字节 START:发起始位 STOP:发停止位 SACK:发送应答,分为有效应答和无效应答 RACK:接收应答,分为有效应答和无效应答
2、EEPROM读写模块状态图
3、总体框架图
2.3、代码部分
1、顶层模块代码
`include "param.v"
module i2c_top (
input clk_50 , //时钟信号
input rst_n , //复位信号
input key ,
input rx , //串口数据接收总线
inout i2c_sda , //i2c的数据线
output tx , //串口数据发送总线
output i2c_scl //i2c的时钟线
);
//信号定义
wire [1:0] baud_sel ; //波特率选择
wire key_out ; //按键消抖输出
wire [7:0] rx_byte ; //串口接收到的值经过串并转换eeprom_control模块
wire rx_byte_vld ;
wire tx_busy ; //串口忙状态标志
wire [7:0] tx_data ; //EEPROM的数据输出到串口发送模块
wire tx_data_vld ;
assign baud_sel = 3; //选择波特率为115200
//例化按键消抖模块
key_debounce #(.KEY_W(1)) u_key(
/*input */.clk_50 (clk_50 ),
/*input */.rst_n (rst_n ),
/*input */.key_in (key ),
/*output */.key_out (key_out )
);
//例化串口发送模块
uart_tx u_tx(
/*input */.clk_50 (clk_50 ),
/*input */.rst_n (rst_n ),
/*input [1:0] */.baud_sel (baud_sel ), //选择波特率
/*input [7:0] */.din (tx_data ),
/*input */.din_vld (tx_data_vld ),
/*output */.busy (tx_busy ), //忙状态指示
/*output */.tx (tx ) //数据发送总线
);
//例化串口接收模块
uart_rx u_rx(
/*input */.clk_50 (clk_50 ),
/*input */.rst_n (rst_n ),
/*input */.rx (rx ), //数据接收总线
/*input [1:0] */.baud_sel (baud_sel ), //选择波特率
/*output [7:0] */.rx_byte (rx_byte ),
/*output */.rx_byte_vld (rx_byte_vld )
);
//例化EEPROM控制模块
eeprom_control#(.WR_LEN(`WR_BYTE), .RD_LEN(`RD_BYTE)) u_eeprom(
/*input */.clk_50 (clk_50 ),
/*input */.rst_n (rst_n ),
/*input [7:0] */.din (rx_byte ),
/*input */.din_vld (rx_byte_vld ),
/*input */.rd_en (key_out ),
/*input */.tx_busy (tx_busy ), //串口发送忙标志
/*inout */.i2c_sda (i2c_sda ),
/*output [7:0] */.dout (tx_data ),
/*output */.dout_vld (tx_data_vld ),
/*output */.i2c_scl (i2c_scl )
);
endmodule //i2c_top
3、IIC接口模块
`include "param.v" module iic_interface ( input clk , //时钟信号 input rst_n , //复位信号 input req , //输入请求信号 input [3:0] cmd , //输入命令 input [7:0] din , //数据 input iic_sda_i , //输入串行数据 output iic_scl , //iic的时钟信号(200kbit/s)速率 output iic_sda_o , //输出IIC串行数据总线 output iic_sda_oe , //输出使能信号 output done , //传输完成信号 output [7:0] dout //输出数据 ); //状态机传输定义 parameter IDLE = 7'b000_0001 , //空闲状态 START = 7'b000_0010 , //发起始位状态 WRITE = 7'b000_0100 , //写状态:FPGA向EEPROM写入数据 RACK = 7'b000_1000 , //接收应答状态:FPGA接收EEPROM发送的应答 READ = 7'b001_0000 , //读状态:FPGA向EEPROM读出数据 SACK = 7'b010_0000 , //发送应答状态:FPGA发送应答给EEPROM(接收方发应答) STOP = 7'b100_0000 ; //发停止位状态 //信号定义 reg [6:0] cstate ; reg [6:0] nstate ; reg iic_scl_r ; reg iic_sda_o_r ; reg iic_sda_oe_r ; reg [7:0] dout_r ; reg [7:0] cnt_scl ; //iic的时钟频率计数寄存器 wire add_cnt_scl ; wire end_cnt_scl ; reg [3:0] cnt_bit ; //
比特计数寄存器 wire add_cnt_bit ; wire end_cnt_bit ; reg [7:0] tx_data ; //寄存数据输入(din) reg [7:0] rx_data ; //寄存iic的数据总线数据 reg [3:0] bit_num ; wire idle_to_start ; wire idle_to_write ; wire idle_to_read ; wire start_to_write ; wire start_to_read ; wire write_to_rack ; wire read_to_sack ; wire rack_to_idle ; wire rack_to_stop ; wire sack_to_idle ; wire sack_to_stop ; wire stop_to_idle ; //iic的时钟频率计数实现 always @(posedge clk or negedge rst_n) begin if (!rst_n) cnt_scl <= 0; else if (add_cnt_scl) begin if (end_cnt_scl) cnt_scl <= 0; else cnt_scl <= cnt_scl + 1'b1; end end assign add_cnt_scl = cstate != IDLE; assign end_cnt_scl = add_cnt_scl && cnt_scl == `SCL_PERIOD - 1; //比特计数实现 always @(posedge clk or negedge rst_n) begin if (!rst_n) cnt_bit <= 0; else if (add_cnt_bit) begin if (end_cnt_bit) cnt_bit <= 0; else cnt_bit <= cnt_bit + 1'b1; end end assign add_cnt_bit = end_cnt_scl; assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_num - 1; //bit_num always @(posedge clk or negedge rst_n) begin if (!rst_n) bit_num <= 0; else if (cstate == READ | cstate == WRITE) bit_num <= 8; else bit_num <= 1; end //状态机跳转 always @(posedge clk or negedge rst_n) begin if (!rst_n) cstate <= IDLE; else cstate <= nstate; end always @(*) begin case (cstate) IDLE: begin if (idle_to_start) nstate = START; else if (idle_to_write) nstate = WRITE; else if (idle_to_read) nstate = READ; else nstate = cstate; end START: begin if (start_to_read) nstate = READ; else if (start_to_write) nstate = WRITE; else nstate = cstate; end WRITE: begin if (write_to_rack) nstate = RACK; else nstate = cstate; end READ: begin if (read_to_sack) nstate = SACK; else nstate = cstate; end RACK: begin if (rack_to_stop) nstate = STOP; else if (rack_to_idle) nstate = IDLE; else nstate = cstate; end SACK: begin if (sack_to_stop) nstate = STOP; else if (sack_to_idle) nstate = IDLE; else nstate = cstate; end STOP: begin if (stop_to_idle) nstate = IDLE; else nstate = cstate; end default: ; endcase end //状态机跳转条件判断 assign idle_to_start = nstate == IDLE & (req & (cmd&`CMD_START)) ; assign idle_to_write = nstate == IDLE & (req & (cmd&`CMD_WRITE)) ; assign idle_to_read = nstate == IDLE & (req & (cmd&`CMD_READ )) ; assign start_to_write = nstate == START & (end_cnt_bit & (cmd&`CMD_WRITE)) ; assign start_to_read = nstate == START & (end_cnt_bit & (cmd&`CMD_READ)) ; assign write_to_rack = nstate == WRITE & (end_cnt_bit) ; assign read_to_sack = nstate == READ & (end_cnt_bit) ; assign rack_to_idle = nstate == RACK & (end_cnt_bit & ~(cmd&`CMD_STOP)) ; assign rack_to_stop = nstate == RACK & (end_cnt_bit & (cmd&`CMD_STOP)) ; assign sack_to_idle = nstate == SACK & (end_cnt_bit & ~(cmd&`CMD_STOP)) ; assign sack_to_stop = nstate == SACK & (end_cnt_bit & (cmd&`CMD_STOP)) ; assign stop_to_idle = nstate == STOP & end_cnt_bit ; //tx_data always @(posedge clk or negedge rst_n) begin if (!rst_n) tx_data <= 0; else if (req) tx_data <= din; end //iic_scl_r, iic时钟总线数据寄存待输出 always @(posedge clk or negedge rst_n) begin if (!rst_n) iic_scl_r <= 1'b1; else if (idle_to_start | idle_to_write | idle_to_read) //发送数据时拉低时钟总线 iic_scl_r <= 1'b0; else if (add_cnt_scl & cnt_scl == `SCL_HALF) iic_scl_r <= 1'b1; else if (add_cnt_scl && ~stop_to_idle) //停止信号是scl保持高电平时sda恢复到高电平 iic_scl_r <= 1'b0; end //iic_sda_o_r, iic数据总线数据寄存待输出 always @(posedge clk or negedge rst_n) begin if (!rst_n) iic_sda_o_r <= 1'b1; else if (cstate == START) //发送起始位 begin if (add_cnt_scl & cnt_scl == `LOW_HLAF) //先在时钟低电平时拉高sda数据线,保证能够检测到起始位 iic_sda_o_r <= 1'b1; else if (add_cnt_scl & cnt_scl == `HIGH_HALF) //检测到起始位 iic_sda_o_r <= 1'b0; end else if (cstate == WRITE & cnt_scl == `LOW_HLAF) //时钟低电平时进行数据发送 iic_sda_o_r <= tx_data[7-cnt_bit]; else if (cstate == RACK & cnt_scl == `LOW_HLAF) iic_sda_o_r <= (cmd&`CMD_STOP) ? 1'b1:1'b0; //是否发送应答 else if (cstate == STOP) //发送停止位 begin if (add_cnt_scl & cnt_scl == `LOW_HLAF) //先在时钟低电平时拉低sda数据线,保证能够检测到停止位 iic_sda_o_r <= 1'b0; else if (add_cnt_scl & cnt_scl == `HIGH_HALF) //检测到停止位 iic_sda_o_r <= 1'b1; end end //iic_sda_oe_r always @(posedge clk or negedge rst_n) begin if (!rst_n) iic_sda_oe_r <= 0; else if (idle_to_start | idle_to_write | rack_to_stop | read_to_sack) //发送数据时使能信号拉高 iic_sda_oe_r <= 1'b1; else if (idle_to_read | start_to_read | write_to_rack | stop_to_idle) iic_sda_oe_r <= 1'b0; end //iic_sda_i 读入数据寄存 always @(posedge clk or negedge rst_n) begin if (!rst_n) rx_data <= 0; else if (cstate == READ & cnt_scl == `HIGH_HALF) rx_data[7-cnt_bit] <= iic_sda_i; end //输出 assign iic_scl = iic_scl_r ; assign iic_sda_o = iic_sda_o_r ; assign iic_sda_oe = iic_sda_oe_r ; assign dout = rx_data ; assign done = rack_to_idle | sack_to_idle | stop_to_idle ; endmodule //iic_interface
注:完善中 \color{red}{注:完善中} 注:完善中
参考: https://blog.csdn.net/xs_sd/article/details/114534036 https://blog.csdn.net/weixin_45888898/article/details/122889135