初学者笔记,欢迎讨论(虽然大部分时间可能不在线)
一、iic总线时序
两根线:SDA、SCK
1、空闲状态:SDA、SCK为高电平
2、开始信号:SCK为高电平期间,SDA产生一个下降沿
3、发送数据:SCK为低电平期间,SDA可变
SCK为高电平期间,SDA不可改变,发送信号
4、应答信号:SDA改为输入(高阻状态,通过使能端改变)
下面贴张时序图
第一个传输的信号为地址位,这里用的是oled
第二个传输的信号为寄存器,一般有指令寄存器和数据寄存器(还有可能更多,小白还没接触太多的)
第三个传输的信号为想要写入的数据,如果是指令的话参考数据手册。
二、Verilog语句的实现
主要通过状态机实现,代码比较长,一般有注释,实现的波形为上方波形
reg tr_start,ack1,ack2,ack3;
reg sda_en;// 决定双向的输入输出 1 为输出 0 为高阻态 为输入
reg[3:0] cnt_count;
reg sclk,reg_sdat;
reg[7:0] iic_data;
reg[7:0] iic_cmd_data;// 写入的
reg[3:0] byte_num; // 写入的 第几个字节
always @(posedge clk_200k or negedge rst_n)
begin
if (~rst_n)
begin
tr_start <= 1;
sda_en <= 1;
ack1 <= 1;
ack2 <= 1;
ack3 <= 1;
sclk <= 1;
reg_sdat <= 1;
cnt_count <= 0;
byte_num <= 0;
state_next <= 0;
end
else
begin
cnt_count <= cnt_count + 4'b1;
case(state_next)
0://空闲状态
begin
case (cnt_count)
11:
begin
cnt_count <= 0;
state_next <= 1;
end
default:
begin
sclk <= 1;
reg_sdat <= 1;
end
endcase
end
1:// 开始传输
begin
tr_start <= 1;
case (cnt_count)
0,1,2:
begin
reg_sdat <= 1;
sclk <= 1;
end
3,4,5,6,7,8:
begin
sclk <= 1;
reg_sdat <= 0;// 拉低电位 开始传输
end
9,10:
begin
sclk <= 0;
reg_sdat <= 0;
end
default://11
begin
sclk <= 0;
reg_sdat <= 0;
state_next <= 2;
cnt_count <= 0;
end
endcase
end
2://写入地址
begin
reg_sdat <= address[7-byte_num];
case (cnt_count)
0,1,2:
begin
sclk <= 0;
end
3,4,5,6,7,8:
begin
sclk <= 1;
end
9,10:
begin
sclk <= 0;
end
default://11
begin
sclk <= 0;
cnt_count <= 0;
if (byte_num == 7)//写完所有地址位
begin
byte_num <= 0;
state_next <= 3;//进入应答状态
end
else
begin
byte_num <= byte_num + 1'b1;
end
end
endcase
end
3://第一个应答信号
begin
case (cnt_count)
0,1,2:
begin
reg_sdat <= 1;
sclk <= 0;
sda_en <= 0;//高阻态
end
3,4,5,6,7,8:
begin
reg_sdat <= 0;
sclk <= 1;
sda_en <= 0;
end
9,10:
begin
sclk <= 0;
reg_sdat <= 0;
sda_en <= 0;
ack1 <= iic_sda;//读取应答信号
end
default://11
begin
sclk <= 0;
reg_sdat <= 0;
sda_en <= 1;//重新变为输出
state_next <= 4;//下一个状态
ack1 <= iic_sda;
cnt_count <= 0;
end
endcase
end
4:// 第二个数据 读或写
begin
reg_sdat <= iic_cmd_data[7-byte_num];
case (cnt_count)
0,1,2:
begin
sclk <= 0;
end
3,4,5,6,7,8:
begin
sclk <= 1;
end
9,10:
begin
sclk <= 0;
end
default://11
begin
sclk <= 0;
cnt_count <= 0;
if (byte_num == 7)//写完所有地址位
begin
byte_num <= 0;
state_next <= 5;//进入应答状态
end
else
begin
byte_num <= byte_num + 1'b1;
end
end
endcase
end
5: // 第二个应答信号
begin
case (cnt_count)
0,1,2:
begin
reg_sdat <= 0;
sclk <= 0;
sda_en <= 0;//高阻态
end
3,4,5,6,7,8:
begin
reg_sdat <= 0;
sclk <= 1;
sda_en <= 0;
end
9,10:
begin
sclk <= 0;
reg_sdat <= 0;
sda_en <= 0;
ack2 <= iic_sda;//读取应答信号
end
default://11
begin
sclk <= 0;
reg_sdat <= 0;
sda_en <= 1;//重新变为输出
state_next <= 6;//下一个状态
ack2 <= iic_sda;
cnt_count <= 0;
end
endcase
end
6: // 第三个数据
begin
reg_sdat <= iic_data[7-byte_num];
case (cnt_count)
0,1,2:
begin
sclk <= 0;
end
3,4,5,6,7,8:
begin
sclk <= 1;
end
9,10:
begin
sclk <= 0;
end
default://11
begin
sclk <= 0;
cnt_count <= 0;
if (byte_num == 7)//写完所有地址位
begin
byte_num <= 0;
state_next <= 7;//进入应答状态
end
else
begin
byte_num <= byte_num + 1'b1;
end
end
endcase
end
7://第三个应答信号
begin
tr_start <= 0;
case (cnt_count)
0,1,2:
begin
sclk <= 0;
sda_en <= 0;//高阻态
end
3,4,5,6,7,8:
begin
sclk <= 1;
sda_en <= 0;
end
9,10:
begin
sclk <= 0;
sda_en <= 0;
ack3 <= iic_sda;//读取应答信号
end
default://11
begin
sclk <= 0;
reg_sdat <= 0;
sda_en <= 1;//重新变为输出
state_next <= 8;//下一个状态
ack3 <= iic_sda;
cnt_count <= 0;
end
endcase
end
8: //停止
begin
case (cnt_count)
0,1,2:
begin
reg_sdat <= 0;
sclk <= 0;
end
3,4,5:
begin
sclk <= 1;
reg_sdat <= 0;
end
6,7,8:
begin
sclk <= 1;
reg_sdat <= 1;//拉高电位结束传输
end
9,10:
begin
sclk <= 1;
reg_sdat <= 1;
end
default://11
begin
sclk <= 1;
reg_sdat <= 1;
state_next <= 0;
cnt_count <= 0;
end
endcase
end
default: state_next <= 0;
endcase
end
end
assign iic_sda = sda_en ? reg_sdat:1'bz;
assign iic_sck = sclk;