一、时序
上一篇中我们已经了解了I2C的基本时序,这里我们只考虑I2C的写数据,具体时序见下图
二、信号列表
sta_flag | 开始信号,拉高时开始一次读或写 |
sda_en | 三态门控制信号,控制SDA是输出还是输入 |
ready_flag | 结束标志,传入下一个模块,提醒下一模块可继续发送 |
write | 总线正在工作信号,这里只考虑写信号 |
sda_out | 作为SDA输出时的数据 |
三、代码
module IIC_ctrl (
input wire sys_clk ,
input wire sys_rst_n ,
input wire sta_flag ,//开始标志,由外部传入
input wire [7:0] ins ,
input wire [15:0] addr ,
input wire [7:0] data ,
inout wire sda ,
output wire scl ,
output wire sda_en ,
output reg ready_flag );//结束标志,高电平时可以继续传输数据
reg [6:0] current_state ;
reg [6:0] next_state ;
reg [3:0] bit_cnt ;//数据传输位数
reg [1:0] clk_cnt ;//用于产生4分频时钟
reg sda_out ;//产生sda
reg stop_flag ;//停止信号
reg start_flag ;//开始信号
wire iic_clk ;
reg write ;//写信号,有效时表示正在写
reg sda_en_2 ;
parameter IDLE=7'b0000001,
START=7'b0000010,
INS=7'b0000100,
ADDR_1=7'b0001000,
ADDR_2=7'b0010000,
DATA_W=7'b0100000,
STOP=7'b1000000;
assign sda=sda_en?sda_out:1'bz;
assign sda_en=((bit_cnt==4'd0)&&(current_state!=IDLE)&&(current_state!=START)&&(current_state!=STOP)&&(write==1'b1))?1'b0:1'b1;
assign iic_clk=((clk_cnt>=2'd2)&&(clk_cnt<=2'd3))?1'b1:1'b0;
assign scl=((current_state==IDLE)||(current_state==START)||(current_state==STOP&&sda_en_2==1'b1))?1'b1:iic_clk;
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
sda_en_2<=1'b1;
else
sda_en_2<=sda_en;
end
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
write<=1'b0;
else if(bit_cnt==4'd1)
write<=1'b1;
else if(stop_flag==1'b1)
write<=1'b0;
else
write<=write;
end
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
start_flag<=1'b0;
else if((sda==1'b0)&&(scl==1'b1))
start_flag<=1'b1;
else
start_flag<=1'b0;
end
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
stop_flag<=1'b0;
else if(scl==1'b1&&sda==1'b1&¤t_state==STOP)
stop_flag<=1'b1;
else
stop_flag<=1'b0;
end
always@(negedge scl or negedge sys_rst_n) begin
if(!sys_rst_n)
bit_cnt<=4'b0;
else if(bit_cnt==4'd8)
bit_cnt<=4'b0;
else if(current_state!=STOP)
bit_cnt<=bit_cnt+1'b1;
else
bit_cnt<=bit_cnt;
end
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
clk_cnt<=2'b0;
else if(clk_cnt==2'b11)
clk_cnt<=2'b0;
else
clk_cnt<=clk_cnt+1'b1;
end
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
current_state<=IDLE;
else
current_state<=next_state;
end
always@(*) begin//状态机
if(!sys_rst_n)
next_state<=IDLE;
else
begin
case(current_state)
IDLE: begin
if(sta_flag==1'b1)
next_state<=START;
else
next_state<=IDLE;
end
START: begin
if(start_flag==1'b1)
next_state<=INS;
else
next_state<=START;
end
INS: begin
if(bit_cnt==4'd0&&clk_cnt==2'd3&&write==1'b1)
next_state<=ADDR_1;
else
next_state<=INS;
end
ADDR_1: begin
if(bit_cnt==4'd0&&clk_cnt==2'd3&&write==1'b1)
next_state<=ADDR_2;
else
next_state<=ADDR_1;
end
ADDR_2: begin
if(bit_cnt==4'd0&&clk_cnt==2'd3&&write==1'b1)
next_state<=DATA_W;
else
next_state<=ADDR_2;
end
DATA_W: begin
if(bit_cnt==4'd0&&clk_cnt==2'd3&&write==1'b1)
next_state<=STOP;
else
next_state<=DATA_W;
end
STOP: begin
if(stop_flag==1'b1)
next_state<=IDLE;
else
next_state<=STOP;
end
default:next_state<=IDLE;
endcase
end
end
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
ready_flag<=1'b0;
else if(current_state==IDLE)
ready_flag<=1'b1;
else
ready_flag<=1'b0;
end
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
sda_out<=1'b1;
else
begin
case(current_state)
IDLE:
sda_out<=1'b1;
START:
sda_out<=1'b0;
INS:
begin
case(bit_cnt)
4'd0:
sda_out<=1'b0;
4'd1:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=ins[7];
end
4'd2:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=ins[6];
end
4'd3:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=ins[5];
end
4'd4:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=ins[4];
end
4'd5:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=ins[3];
end
4'd6:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=ins[2];
end
4'd7:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=ins[1];
end
4'd8:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=ins[0];
end
default:sda_out<=1'b1;
endcase
end
ADDR_1:begin
case(bit_cnt)
4'd0:sda_out<=1'b0;
4'd1:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[15];
end
4'd2:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[14];
end
4'd3:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[13];
end
4'd4:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[12];
end
4'd5:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[11];
end
4'd6:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[10];
end
4'd7:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[9];
end
4'd8:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[8];
end
default:sda_out<=1'b1;
endcase
end
ADDR_2:begin
case(bit_cnt)
4'd0:sda_out<=1'b0;
4'd1:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[7];
end
4'd2:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[6];
end
4'd3:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[5];
end
4'd4:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[4];
end
4'd5:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[3];
end
4'd6:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[2];
end
4'd7:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[1];
end
4'd8:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=addr[0];
end
default:sda_out<=1'b1;
endcase
end
DATA_W:begin
case(bit_cnt)
4'd0:sda_out<=1'b0;
4'd1:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=data[7];
end
4'd2:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=data[6];
end
4'd3:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=data[5];
end
4'd4:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=data[4];
end
4'd5:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=data[3];
end
4'd6:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=data[2];
end
4'd7:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=data[1];
end
4'd8:begin
if(clk_cnt==2'd0||clk_cnt==2'd3)
sda_out<=data[0];
end
default:sda_out<=1'b1;
endcase
end
STOP:
begin
if(scl==1'b1)
sda_out<=1'b1;
else
sda_out<=1'b0;
end
endcase
end
end
endmodule
四、代码简要分析
代码主体比较简单,按照时序来写就行了,有几点要说明一下,这里输入的sta_flag来自另一个模块,只要IIC_Ctrl模块的ready_flag拉高,sta_flag就会拉高,写数据就会开始,因为那个模块是在配置摄像头的时候写的,之后讲OV5640的时候再贴代码。兄弟们可以自己写一个简单的开始信号发送的模块。
还有就是SDA的赋值这里,常用的双向信号赋值就是向我写的那样用一个assign语句,一个sda_out做跳板。实际的写法有很多,这种比较简单,他的写法也可以很复杂,感兴趣的兄弟可以自行了解。
状态机那一部分,I2C的应答ACK必须是低电平才可以接着下一步,这里是按照SCCB来写的,基本是一样,但是SCCB的ACK不关心是什么状态,也就是1或0都可以。如果要验证I2C的兄弟这里得注意一下。
五、一些话
本来打算把常用的三个协议写完了再写其他东西的,前两天翻看我很久之前写的SPI代码,有点惨不忍睹,虽然能正常跑起来,但是可读性很差。我最近也没心思去重写一遍了,网上资料很多,兄弟们自己去看吧。接下来会跟新OV5640的配置内容。
标签:wire,FPGA,clk,通信协议,flag,sda,input,I2C,reg From: https://blog.csdn.net/weixin_62966975/article/details/136727925