我正在尝试通过 DE1-SOC 板与 HTU21D(F) 湿度和温度传感器通信。传感器位于分线板上,分线板上有用于 i2c 的上拉电阻。我可以快速找到的视觉上最接近的匹配项是旧的 sparkfun 教程页面here。根据传感器本身的数据表,它有两种通信模式。第一个是保持主模式,在该模式下传感器阻塞 SCK 线,同时传感器进行测量直到完成。第二种是 no hold master 模式,需要 master 轮询传感器直到传感器完成测量。
我已经尝试过保持主模式,因为我无法找到默认模式,我能找到的唯一指示是保持主模式首先在数据表中进行了详细说明。我试图找到我可以在线阅读的 i2c 模块示例,并最终使用 this 示例作为我的基础。
一旦我用 SDA、SCK、data_in(想要测量类型的命令)、data_out(测量)、go(按下 DE1-SOC 上的按键)、reset、系统时钟,我连接了适当的模块输入/输出和注册到各自的位置,并将其加载到板上。我主要使用信号抽头来监视内部信号,但也有 SDA 和 SCK 线。出现的是 SDA 线会输出地址(0x80),应该会收到一个确认,然后输出我硬设置为温度测量值(0xE3)的命令,而 SDA 线不会收到任何响应。发送命令后,我的模块似乎卡在了 ack 状态。
预期的行为是主机将在 SCK 为高电平时取消 SDA。发送起始位后,主机将发送地址 0x80 和写入位等待确认。地址确认后,主机将根据按下的键发送命令 0xE3 或 0xE5,并等待确认。一旦收到确认,主机将发送一个起始位并发送地址 0x81 和读取位并等待确认。收到读取确认后,主机将读取 8 位的 SDA 数据,然后发送确认。发送 ack 后,主机将读取另外 8 位,然后发送 nack。
我已经尝试根据上面显示的示例创建自己的模块
module i2c(
input clk, go, rst,
input [7:0]data_in,
inout ioport,
output ioclk,
output [13:0]data_out//,
// output [3:0]State,
// output [4:0]counter,
// output [7:0]command,
// output sdat
);
// Declare state register
reg [3:0] state;
reg [7:0] addr;
reg [7:0] cmd;
reg [4:0] count;
reg sdata;
reg [15:0] io_in;
reg dir;
wire [7:0] icmd;
wire [4:0] icount;
// reg [7:0] rdata;
reg exclk;
wire idata, idir;
// Declare states
parameter Idle = 0, Start = 1, Addr = 2, Ack = 3, Cmd = 4, Ack2 = 5, Data1 = 6, Ack_s = 7, Data2 = 8, Stop = 9, Reset = 10, RAck = 11;
initial begin
dir <= 1;
sdata <= 1;
addr <= 8'h80;
cmd <= 8'h00;
exclk <= 1;
state <= Idle;
end
assign ioport = dir ? idata : 1'bz;
assign ioclk = exclk;
assign data_out = io_in[15:1];
assign icmd = data_in;
assign icount = count;
assign idata = sdata;
// assign irst = rdata;
// assign State = state;
// assign counter = count;
// assign command = icmd;
// assign idir = dir;
// assign sdat = idir;
//clk div by half for SCK
always @ (posedge clk) begin
exclk <= !exclk;
end
// direction control based on exclk and state
always @ (negedge exclk) begin
case(state)
Idle://0
dir <= 1;
Start://1
dir <= 1;
Addr://2
dir <= 1;
Ack://3
dir <= 0;
Cmd://4
dir <= 1;
Ack2://5
dir <= 0;
Data1://6
dir <= 0;
Ack_s://7
dir <= 1;
Data2://8
dir <= 0;
Stop://9
dir <= 1;
endcase
end
//data output based on state using clk and exclk to determine when to change
always @ (*) begin
case (state)
Idle: begin//0
end
Start: begin//1
if(exclk && !clk) begin
sdata <= 0;
end
end
Addr: begin//2
if(!exclk) begin
sdata <= addr[icount];
end
end
Ack: begin//3
end
Cmd: begin//4
if(!exclk) begin
sdata <= cmd[icount];
end
end
Ack2: begin//5
end
Data1: begin//6
if(!exclk && !clk) begin
io_in[icount] <= ioport;
end
end
Ack_s: begin//7
if(!exclk && !clk) begin
sdata <= 0;
end
end
Data2: begin//8
if(!exclk && !clk) begin
io_in[icount] <= ioport;
end
end
Stop: begin//9
if((!exclk && !clk) && ioport) begin
sdata <= 0;
end
if(!ioport && (exclk && !clk)) begin
sdata <= 1;
end
end
default: begin
end
endcase
end
// Determine the next state based on exclk or rst
always @ (posedge exclk or posedge rst) begin
if (rst == 1) begin
//state <= Reset;
//count <= 7;
state <= Idle;
end
else begin
case (state)
Idle: begin//0
if(go == 0) begin
state <= Start;
end
end
Start: begin//1
if(!ioport && exclk) begin
state <= Addr;
count <= 7;
end
else begin
state <= Start;
end
end
Addr: begin//2
if (count == 4'b0) begin
state <= Ack;
end
else begin
state <= Addr;
count <= count - 1'b1;
end
end
Ack: begin//3
if (!ioport && exclk) begin
if(!addr[0]) begin
state <= Cmd;
count <= 7;
cmd <= icmd;
end
else begin
state <= Data1;
count <= 15;
end
end
else begin
state <= Ack;
end
end
Cmd: begin//4
if(count == 0) begin
state <= Ack2;
end
else begin
state <= Cmd;
count <= count - 1'b1;
end
end
Ack2: begin//5
if(!ioport && exclk) begin
state <= Start;
addr[0] <= 1'b1;
count <= 7;
end
else begin
state <= Ack2;
end
end
Data1: begin//6
if(count == 7) begin
state <= Ack_s;
end
else begin
state <= Data1;
count <= count - 1'b1;
end
end
Ack_s: begin//7
if(ioport) begin
state <= Data2;
end
else begin
state <= Ack_s;
end
end
Data2: begin//8
if(count == 0) begin
state <= Stop;
end
else begin
state <= Data2;
count <= count - 1'b1;
end
end
Stop: begin//9
if(ioport) begin
state <= Idle;
end
else begin
state <= Stop;
end
end
endcase
end
end
endmodule
module Lab0(
input CLOCK2_50,
output [6:0] HEX0,
output [6:0] HEX1,
input [3:0] KEY,
inout SDA,
output SCL);
wire [15:0] dout;
wire [3:0] key;
reg [7:0] din;
wire [7:0] data;
wire go;
initial begin
din <= 8'hE3;
end
assign key = KEY;
assign data = din;
assign HEX0 = dout[7:2];
assign HEX1 = dout[15:8];
assign go = (!key[0] || !key[1]);
always @ (negedge key[0] or negedge key[1]) begin
if(!key[0])
din <= 8'hE3;
else if(!key[1])
din <= 8'hE5;
end
i2c comm(.clk(CLOCK2_50), .go(!go), .rst(!key[3]), .data_in(data), .ioport(SDA), .ioclk(SCL), .data_out(dout));
endmodule
`timescale 1ns / 1ps
module test();
wire SDA, SCL, sdata;
wire [13:0]dout;
wire [3:0]state;
wire [4:0]count;
wire [7:0]cmd;
wire [7:0]addr;
reg [7:0]data;
reg clk, rst, go, simdata;
i2c comm(.clk(clk), .go(go), .rst(rst), .data_in(data), .ioport(SDA), .ioclk(SCL), .data_out(dout), .State(state), .counter(count), .command(cmd), .sdat(sdata));
assign SDA = simdata;
initial begin
data <= 8'hE3;
clk <= 0;
rst <= 0;
go <= 1;
simdata <= 1'bz;
#20 go <= 0;
#40 go <= 1;
#340 simdata <= 0;
#50 simdata <= 1'bz;
#320 simdata <= 0;
#40 simdata <= 1'bz;
#320 simdata <= 0;
#50 simdata <= 1'bz;
end
always
begin
#10 clk <= ~clk;
end
endmodule
这是具有前面提到的问题的模块和顶级文件。我已经尝试修改我用来与传感器一起工作的示例,并收到了 SCK 线设置为始终打开的问题,以防止示例中的双向设置。我试过将模块代码(修改过的)直接放在顶层文件中,以防双向有一些层次问题并实现同样的失败。
我已经通过 Quartus 中的引脚规划器确认 SDA 引脚是双向的。当我使用修改后的示例时,我没有检查 SCK 引脚,因为 SCK 线将始终打开的警告表明它将是单向的。 RTL 查看器证明修改后的示例就是这种情况。不幸的是,我可以看到 RTL 没有明显的问题,状态视图具有正确的方向性。
我不知道接下来要尝试改变什么。我几乎要假设传感器坏了。我已经能够确认时钟信号正在到达传感器,但无法确认 SDA 信息,因为当我尝试用示波器读取它时,信号会破坏时钟和 SDA 线,这可能表明存在干扰,但我没有看看如果这个分线板有这个问题会怎样,因为它主要与 Arduino 一起使用,如 sparkfun 教程页面所示。这是我第一次明确使用 inout/tri-state,我分别研究了这些,看看我是否犯了任何明显的错误,但我可能错过了。
编辑:我更新了代码,添加了我的测试平台代码,这里是来自 modelsim 和 signaltap 的波形。测试台只到等待数据的主机为止。发送命令后我没有收到确认,这让我想也许我在地址后没有收到。