今天,我们要来完成整个 I2C 的最後一个部份了!
先来看看这个 I2C Master write 模块该有哪些输入输出脚吧:
输入:
(如果是 write 模式,那前 8 bit 传 address,而後 8 bit 传 data_i)
输出:
(如果是 read 模式,那前 8 bit 传 address,而後面都传 "1" (高阻抗),因为要接收 data 所以断开,由 slave 去给信号)
输入+输出:
module usage_I2C_write_R_W(
en,
clk_sys,
rst_n,
addr,
data_i,
SCLK,
SDA,
data_o
);
/*------ports declaration------*/
input clk_sys;
input rst_n;
input en;
input [7:0] addr;
input [7:0] data_i;
inout SDA;
output SCLK;
output [15:0] data_o;
reg [15:0] data_o;
wire SCLK;
再来,因为我打算收资料也做个状态机,先来定义三个状态:
/*------parameter------*/
parameter IDLE = 2'd0;
parameter SHIFT = 2'd1;
parameter STOP = 2'd2;
宣告变数:
引用上次写好的模组
/*------module I2C_write instantiate------*/
I2C_write_R_W U0(
.clk_sys(clk_sys),
.rst_n(rst_n),
.en(en),
.data_R({addr, 1'b1, 8'hff, 1'b0, 8'hff, 1'b1}),//27bit
.data_W({addr, 1'b1, data_i, 1'b1}),//18bit
.ACK(ACK),
.ACK1(),
.ACK2(),
.ACK3(),
.rstACK(rstACK),
.SCLK(SCLK),
.SDA(SDA),
.ldnACK1(ldnACK1),
.ldnACK2(ldnACK2),
.ldnACK3(ldnACK3),
.R_W(addr[0]),
.tick_I2C_neg(tick_I2C_neg)
);
状态逻辑:
/*------fstate state------*/
always@(posedge clk_sys or negedge rst_n)begin
if(!rst_n)fstate <= IDLE;
else begin
case(fstate)
IDLE:begin
if(ldnACK1&&(addr[0])&&tick_I2C_neg)fstate <= SHIFT;
else fstate <= IDLE;
end
SHIFT:begin
if(count==4'd15&&tick_I2C_neg)fstate <= STOP;
else fstate <= SHIFT;
end
STOP:begin
fstate <= IDLE;
end
default:begin
fstate <= IDLE;
end
endcase
end
end
输出逻辑:
/*------fstate output------*/
always@(posedge clk_sys or negedge rst_n)begin
if(!rst_n)begin
count <= 4'd0;
data_temp <= 16'd0;
data_o <= 16'd0;
end
else begin
case(fstate)
IDLE:begin
count <= 4'd0;
data_temp <= data_temp;
end
SHIFT:begin
if(!ldnACK2&&tick_I2C_neg)begin
count <= count + 4'd1;
data_temp <= {data_temp[14:0], SDA};
end
else begin
count <= count;
data_temp <= data_temp;
end
end
STOP:begin
count <= 4'd0;
data_o <= data_temp;
end
default:begin
count <= 4'dx;
data_temp <= 16'hxx;
data_o <= 16'hxx;
end
endcase
end
end
endmodule
这样就结束了我们的 I2C 整个模块
再来是 TestBench 的部分~~~~
`timescale 1ns / 10ps
module i2c_tb();
localparam durTime = 5*1000;
reg sysClk, rst_n, tick_tx, sda_i;
reg [7:0]addr_i, data_i;
wire [15:0]data_o;
wire SCL;
wire SDA;
integer i;
assign SDA = (sda_i==1'b1)?1'bz:1'b0;
pullup(SDA);
usage_I2C_write_R_W UUT(
.en(tick_tx),
.clk_sys(sysClk),
.rst_n(rst_n),
.addr(addr_i),
.data_i(data_i),
.SCLK(SCL),
.SDA(SDA),
.data_o(data_o)
);
always@(negedge SCL, negedge rst_n)begin
if(!rst_n)sda_i <= 1'b1;
else if(i==8||i==17||i==27||i==28||i==36||i==35||i==43)#2000 sda_i <= 1'b0;//<27 for write
else#2000 sda_i <= 1'b1;
end
always@(posedge SCL, negedge rst_n)begin
if(!rst_n)i <= 0;
else i <= i + 1;
end
always #10 sysClk = ~sysClk;
initial begin
sysClk = 0;
rst_n = 1;
tick_tx = 0;
sda_i = 1'b1;
addr_i = 8'hA6;
data_i = 8'h81;
#50 rst_n = 0;
#50 rst_n = 1;
#10 tick_tx = 1;
#10 tick_tx = 0;
repeat(50) #durTime;
addr_i = 8'hA7;
#10 tick_tx = 1;
#10 tick_tx = 0;
repeat(100) #durTime;
$stop;
end
endmodule
值得注意的是 Verilog 本身就有 "pullup" 语法可以模拟上拉电阻的效果
Wave
可以看到上半部的部分:
前半部是 write 模式,先传送 addr_i 的 10100110(最後一 bit 是 "0" ,所以是 write)再来是 ACK,再接着 data_i 的 10000001,最後也是 1 bit 的 ACK。
後半部是 read 模式,先传送 addr_i 的 10100111(最後一 bit 是 "1" ,所以是 read ),再来是 ACK,接着的 16 bit 是由 tb 发送的 01111110 和 11111101,而最後再搭配一个 master 端的 non-ACK 完成通讯。
而由此波形也可以看见 SDA 确实有在 SCLK 为 LOW 时才改变值。
注:虚线是因为 pullup 的关系,所以 HIGH 才变成虚线
而这里可以看到 tick_I2C_neg(红色那条)位於 SDA 的正中间,以此来取样 SDA 信号可以确保值的正确性~~~~
来看看 State Machine Viewer 是否如预期
状态确实都有照着我们的想法去跑呢~
那麽 I2C 的教学也到这边结束喽~~~~
【前言】 本系列为个人前端学习之路的学习笔记,在过往的学习过程中累积了很多笔记,如今想藉着IT邦帮忙...
Gradle 的其中一个强大特点,就是它了解任务间的相依性,可以在核心建立出图或树。这对於开发者来说...
-具有关键风险因素的通用风险模型(NIST SP 800-30 R1) 关键风险因素(Key Ri...
之前在添加资料时,都是手动去资料库添加,这样很不合理,也没有效率 如果只是为了方便,当然可以使用以下...
前面我们介绍了非常多关於节点、客体机的相关设定与管理,在铁人赛的最後一篇,我们回头来看最上层的资料...