通过上篇文章描述IIC原理可知IIC工作原理。使用FPGA控制iic总线,接口可定义如下:
图示信号意义如下:
clk为系统时钟;
Rst_n为系统复位信号;
[5:0]Wrdata_num为写数据的字节数;
[5:0]Rddata_num为读数据的字节数;
[1:0]Wdaddr_num为器件地址字节数;
[2:0]Device_addr为器件地址;
[15:0]Word_addr为寄存器地址;
Wr为写操作使能信号;
[7:0]Wr_data为写数据;
Rd为读使能信号;
[7:0]Rd_data为读数据;
Wr_data_vaild为写数据有效信号;
Rd_data_vaild为读数据有效信号;
SCL为时钟总线信号;
SDA为数据总线信号;
Done为读写完成标志位;
设计思想:IIC控制总线只有两条输出信号线,即SCL和SDA。IIC在进行读写操作时候都是在SCL的高电平或者低电平读取数据和改变数据的。
首先解决SCL信号的产生,SCL总线采用400KHz频率,因此可以采用系统时钟计数产生SCL信号。同时设计两个标志位scl_high和scl_low,方便在SCL高低电平有效期间进行数据操作。对于scl_low和scl_high信号,只需要在计数到四分之一和四分之三的位置产生标志位即可。
接着,按照上篇所述的读写数据时序分析,可以看出无论是读或者写均分为几个状态进行,因此可以设计一个状态机列出读/写过程中各个状态情况,可以分为一下几种状态:1、空闲状态; 2、写开始状态; 3、写控制状态; 4、写地址状态; 5、写数据状态; 6、读开始状态; 7、读控制状态; 8、读数据状态; 9、停止状态;
localparam IDLE = 9'b0_0000_0001, //空闲状态
WR_START = 9'b0_0000_0010, //写开始状态
WR_CTRL = 9'b0_0000_0100, //写控制状态
WR_WADDR = 9'b0_0000_1000, //写地址状态
WR_DATA = 9'b0_0001_0000, //写数据状态
RD_START = 9'b0_0010_0000, //读开始状态
RD_CTRL = 9'b0_0100_0000, //读控制状态
RD_DATA = 9'b0_1000_0000, //读数据状态
STOP = 9'b1_0000_0000; //停止状态
所述的九种状态中属3、4、5、7、8状态需要发送或接受8位数据,因此可以以任务的方式展现出来,通过定义内部待接收和待发送寄存器,以及SDA输出临时寄存器,如此,在3、4、5、7、8状态即可在状态机中特定条件下赋值相应的器件地址、寄存器地址、待发送/接受的数据给内部待接收或待发送寄存器进行总线传输即可,也省去了主状态机的工作量。对于发送/接受数据均需要响应信号ack,且ack也是在SCL控制下响应,故可以对SCL高低电平进行计数,在特定计数值下进行数据操作和信号控制。
//输出串行数据任务
reg FF; //串行数据输出、输入执行位
reg [7:0]sda_data_out; //待输出的串行数据
reg sda_reg; //sda数据输出寄存器
reg [7:0]sda_data_in; //待输入的串行数据
task send_8bit_data;
if(scl_high && (halfbit_cnt == 8'd16))
FF <= 1;
else if(halfbit_cnt < 8'd17)begin
sda_reg <= sda_data_out[7];
if(scl_low)
sda_data_out <= {sda_data_out[6:0],1'b0};
else
sda_data_out <= sda_data_out;
end
else
;
endtask
//串行数据大输入任务
task receive_8bit_data;
if(scl_low && (halfbit_cnt == 8'd15))
FF = 1;
else if(halfbit_cnt < 8'd15)begin
if(scl_high)
sda_data_in <= {sda_data_in[6:0],SDA};
else begin
sda_data_in <= sda_data_in;
end
end
else
;
endtask
SCL信号高低电平的计数值下控制的ack信号的产生。发送/接受8位数据和一位ack信号共9bit数据,需要9个低电平改变数据,9个高电平输入/输出数据,一共需要高低电平计数值=18即可完成。
//数据接受方对发送方响应检测标志位
reg ack;
always@(posedge clk or negedge Rst_n)
begin
if(!Rst_n)
ack <= 1'b0;
else if((halfbit_cnt == 8'd16) && (scl_high) && (SDA == 1'b0))
ack <= 1'b1;
else if((halfbit_cnt == 8'd17) && (scl_low))
ack <= 1'b0;
else
ack <= ack;
end
SDA信号控制:总线采用的三态使能输出。
assign SDA = sda_en ? sda_reg : 1'bz;
简单而粗糙的理解即是:空闲状态保持三态输入;主机向从机传输数据设置SDA为输出状态,由sda_reg临时寄存器将数据给SDA进行送到总线上;从机向主机发送数据和信号时,SDA设置为三态输入,等待主机读取从机通过总线传输来的数据。
//SDA三态使能信号sda_en
always@(*)
begin
case(main_state)
IDLE:
sda_en = 1'b0;
WR_START,RD_START,STOP:
sda_en = 1'b1;
WR_CTRL,WR_WADDR,WR_DATA,RD_CTRL:
if(halfbit_cnt < 16)
sda_en <= 1'b1;
else
sda_en <= 1'b0;
RD_DATA:
if(halfbit_cnt < 16)
sda_en = 1'b0;
else
sda_en = 1'b1;
default:
sda_en = 1'b0;
endcase
end
最后,可以设计读取/写入的标志信号和主机读出有效数据控制。