主机模块是参考这里一位大佬写的
- 主机驱动
module i2c_master#(
parameter SYS_CLOCK = 50_000_000, //系统时钟频率
parameter SCL_CLOCK = 400_000 //iic时钟频率
)(
input i_clk , //系统时钟
input i_rst_n , //系统复位
input i_wr_en , //写使能
input i_rd_en , //读使能
input [6:0] i_dev_addr , //器件地址
input [15:0] i_reg_addr , //寄存器地址,单字节时从低字节有效
input [1:0] i_reg_addr_num , //寄存器地址字节数 1: 1字节,其他:2字节
input [7:0] i_wr_data , //写数据
input [3:0] i_wr_data_num , //写数据字节数,默认为1
output reg o_rd_data_vaild , //读数据有效
output reg [7:0] o_rd_data , //读数据
input [3:0] i_rd_data_num , //读数据字节数,默认为1
output reg o_cfg_done , //一次读写操作完成标志
inout io_SDA , //IIC--SDA
output reg o_SCL //IIC--SCL
);
//================================================================================
localparam SCL_CNT_M = SYS_CLOCK / SCL_CLOCK;
localparam IDLE = 9'b0_0000_0001; //独热编码
localparam WR_START = 9'b0_0000_0010;
localparam WR_CTRL = 9'b0_0000_0100;
localparam WR_REG_ADDR = 9'b0_0000_1000;
localparam WR_DATA = 9'b0_0001_0000;
localparam RD_START = 9'b0_0010_0000;
localparam RD_CTRL = 9'b0_0100_0000;
localparam RD_DATA = 9'b0_1000_0000;
localparam STOP = 9'b1_0000_0000;
//-------------------------------------------------
// 变量声明
//-------------------------------------------------
reg [15:0] scl_cnt; //clk计数器,用于产生scl时钟
reg scl_high; //scl高电平中部标志
reg scl_low; //scl低电平中部标志
reg scl_vaild; //scl有效标志
reg [8:0] main_state; //状态寄存器
reg sda_reg; //sda输出寄存器
reg sda_en; //sda三态使能
reg sda_task_flag; //串行输出输入任务执行标志位
reg w_flag; //写标志
reg r_flag; //读标志
reg [7:0] scl_level_cnt; //scl高低电平计数器
reg ack; //应答信号
reg [3:0] wdata_cnt; //写数据字节数计数器
reg [3:0] rdata_cnt; //读数据字节数计数器
reg [1:0] reg_addr_cnt; //地址字节数计数器
reg [7:0] sda_data_out; //数据输出buffer
reg [7:0] sda_data_in; //数据输入buffer
wire [7:0] wr_ctrl_word; //写控制字
wire [7:0] rd_ctrl_word; //读控制字
wire rdata_vaild; //读数据有效前寄存器
assign wr_ctrl_word = {i_dev_addr,1'b0};
assign rd_ctrl_word = {i_dev_addr,1'b1};
//----------------------------------------------------
// iic时钟信号生成
//----------------------------------------------------
/* iic 非空闲状态产生 scl_vaild */
always @(posedge i_clk) begin
if(!i_rst_n)
scl_vaild <= 1'b0;
else begin
if(i_wr_en | i_rd_en)
scl_vaild <= 1'b1;
else if(o_cfg_done)
scl_vaild <= 1'b0;
end
end
/* o_SCL 计数器*/
always @(posedge i_clk) begin
if(!i_rst_n)
scl_cnt <= 16'd0;
else begin
if(scl_vaild) begin
if(scl_cnt == SCL_CNT_M-1'b1)
scl_cnt <= 16'd0;
else
scl_cnt <= scl_cnt + 16'd1;
end
else
scl_cnt <= 16'd0;
end
end
/* o_SCL 时钟产生*/
always @(posedge i_clk) begin
if(!i_rst_n)
o_SCL <= 1'b1;
else begin
if(scl_cnt == SCL_CNT_M >> 1)
o_SCL <= 1'b0;
else if(scl_cnt == 16'd0)
o_SCL <= 1'b1;
end
end
/*o_SCL 高低电平中部标志*/
always @(posedge i_clk) begin
if(!i_rst_n) begin
scl_high <= 1'b0;
scl_low <= 1'b0;
end
else begin
if(scl_cnt == (SCL_CNT_M >> 2))
scl_high <= 1'b1;
else
scl_high <= 1'b0;
if(scl_cnt == ((SCL_CNT_M >> 1) + (SCL_CNT_M >> 2)))
scl_low <= 1'b1;
else
scl_low <= 1'b0;
end
end
//----------------------------------------------------
// iic主程序状态机
//----------------------------------------------------
always @(posedge i_clk) begin
if(!i_rst_n) begin
main_state <= IDLE;
sda_reg <= 1'b1;
w_flag <= 1'b0;
r_flag <= 1'b0;
o_cfg_done <= 1'b0;
reg_addr_cnt <= 2'd1;
wdata_cnt <= 4'd0;
rdata_cnt <= 4'd0;
end
else begin
case(main_state)
IDLE: begin
sda_reg <= 1'b1;
w_flag <= 1'b0;
r_flag <= 1'b0;
o_cfg_done <= 1'b0;
reg_addr_cnt <= 2'd1;
wdata_cnt <= 4'd0;
rdata_cnt <= 4'd0;
if(i_wr_en) begin
main_state <= WR_START;
w_flag <= 1'b1;
end
else if(i_rd_en) begin
main_state <= WR_START;
r_flag <= 1'b1;
end
end
//------------------- iic起始信号 ---------------------------
WR_START: begin
if(scl_high) begin
main_state <= WR_START;
sda_reg <= 1'b0;
end
else if(scl_low) begin
main_state <= WR_CTRL;
sda_data_out <= wr_ctrl_word; // 准备要发送的控制字
sda_task_flag <= 1'b0; // 开始串行传输任务
end
end
//---------------- 写设备地址、寄存器地址 -------------------
WR_CTRL: begin
if(sda_task_flag == 1'b0) // 发送数据
send_8bit_data;
else begin // 等待响应
if(ack == 1'b1) begin // 收到响应
if(scl_low) begin // 准备发送的寄存器地址数据
main_state <= WR_REG_ADDR;// 转换到寄存器地址
sda_task_flag <= 1'b0;
if(i_reg_addr_num == 2'b1)
sda_data_out <= i_reg_addr[7:0];
else
sda_data_out <= i_reg_addr[15:8];//如果寄存器地址为2个字节,要保证先发的最高位
end
end
else // 未收到响应
main_state <= IDLE;
end
end
WR_REG_ADDR: begin
if(sda_task_flag == 1'b0)
send_8bit_data;
else begin
if(ack == 1'b1) begin //收到响应
if(reg_addr_cnt == i_reg_addr_num) begin // 寄存器地址数据发送完成
if(w_flag && scl_low) begin
main_state <= WR_DATA; //状态转移
sda_data_out <= i_wr_data[7:0];//数据准备
sda_task_flag <= 1'b0;
reg_addr_cnt <= 2'd1;
end
else if(r_flag && scl_low) begin
main_state <= RD_START;
sda_reg <= 1'b1; //sda拉高
end
end
else begin // 寄存器地址数据没有发送完成
if(scl_low) begin
main_state <= WR_REG_ADDR;
reg_addr_cnt <= reg_addr_cnt + 2'd1;
sda_data_out <= i_reg_addr[7:0]; // 准备低8位寄存器地址
sda_task_flag <= 1'b0;
end
end
end
else // 未收到响应
main_state <= IDLE;
end
end
//----------------- 写iic数据 ---------------------------------
WR_DATA: begin
if(sda_task_flag == 1'b0)
send_8bit_data;
else begin
if(ack == 1'b1) begin // 收到响应
if(wdata_cnt == i_wr_data_num) begin //发送完成
if(scl_low) begin
main_state <= STOP;
sda_reg <= 1'b0;
wdata_cnt <= 4'd0;
end
end
else begin //未发送完成
if(scl_low) begin
main_state <= WR_DATA;
sda_data_out <= i_wr_data[7:0];
wdata_cnt <= wdata_cnt + 4'd1;
sda_task_flag <= 1'b0;
end
end
end
else // 未收到响应
main_state <= IDLE;
end
end
//----------------- 读iic数据 ---------------------------------
RD_START: begin
if(scl_high) begin
main_state <= RD_START;
sda_reg <= 1'b0;
end
else if(scl_low) begin
main_state <= RD_CTRL;
sda_data_out <= rd_ctrl_word; // 准备要发送的控制字
sda_task_flag <= 1'b0; // 开始串行传输任务
end
end
RD_CTRL: begin
if(sda_task_flag == 1'b0) // 发送数据
send_8bit_data;
else begin // 等待响应
if(ack == 1'b1) begin // 收到响应
if(scl_low) begin // 准备发送的寄存器地址数据
main_state <= RD_DATA; // 转换到寄存器地址
sda_task_flag <= 1'b0;
end
end
else // 未收到响应
main_state <= IDLE;
end
end
RD_DATA: begin
if(sda_task_flag == 1'b0)
receive_8bit_data;
else begin
if(rdata_cnt == i_rd_data_num) begin // 接收完成
sda_reg <= 1'b1; //发送 NACK,不读了
if(scl_low) begin
main_state <= STOP;
sda_reg <= 1'b0;
end
end
else begin
sda_reg <= 1'b0; // 发送ACK,继续读下一个字节
if(scl_low) begin
rdata_cnt <= rdata_cnt + 4'd1;
sda_task_flag <= 1'b0;
end
end
end
end
//------------------- iic停止信号 ---------------------------
STOP: begin
if(scl_high) begin
sda_reg <= 1'b1;
o_cfg_done <= 1'b1;
end
else if(scl_low)
main_state <= IDLE;
end
default: main_state <= IDLE;
endcase
end
end
//----------------------------------------------------
// 串行数据任务,收/发8bit数据
//----------------------------------------------------
/*发送接收数据时 o_SCL 时钟计数*/
always @(posedge i_clk) begin
if(!i_rst_n)
scl_level_cnt <= 8'd0;
else begin
//这几个状态需要执行数据发送接收任务
if(main_state == WR_CTRL || main_state == WR_REG_ADDR || main_state == WR_DATA ||
main_state == RD_CTRL || main_state == RD_DATA) begin
if(scl_low | scl_high) begin
if(scl_level_cnt == 8'd17)
scl_level_cnt <= 8'd0;
else
scl_level_cnt <= scl_level_cnt + 8'd1;
end
end
end
end
/*数据接收对发送的响应标志位*/
always @(posedge i_clk) begin
if(!i_rst_n)
ack <= 1'b0;
else begin
if((scl_level_cnt == 8'd16) && scl_high && (io_SDA == 1'd0))
ack <= 1'b1;
else if((scl_level_cnt == 8'd17) && scl_low)
ack <= 1'b0;
end
end
/* 输出串行数据任务 */
task send_8bit_data;
if(scl_high && (scl_level_cnt == 8'd16)) //8bit data send o_cfg_done
sda_task_flag <= 1'b1;
else if(scl_level_cnt < 8'd17) begin
sda_reg <= sda_data_out[7];
if(scl_low)
sda_data_out <= {sda_data_out[6:0],1'b0};
end
endtask
/* 接收串行数据任务 */
task receive_8bit_data;
if(scl_low && (scl_level_cnt == 8'd15))
sda_task_flag <= 1'b1;
else if(scl_level_cnt < 8'd15) begin
if(scl_high)
sda_data_in <= {sda_data_in[6:0],io_SDA};
end
endtask
//----------------------------------------------------
// SDA三态门控制输出
//----------------------------------------------------
/*io_SDA 三态门输出*/
assign io_SDA = sda_en ? sda_reg : 1'bz;
always @(*) begin
case(main_state)
IDLE: sda_en <= 1'b1; //输入
WR_START,RD_START,STOP: sda_en <= 1'b1; //输出
WR_CTRL,WR_REG_ADDR,WR_DATA,RD_CTRL: begin
if(scl_level_cnt < 8'd16)
sda_en <= 1'b1;
else
sda_en <= 1'b0;
end
RD_DATA: begin
if(scl_level_cnt < 8'd16)
sda_en <= 1'b0;
else
sda_en <= 1'b1;
end
default: sda_en <= 1'b0;
endcase
end
//----------------------------------------------------
// 读有效数据
//----------------------------------------------------
/*读出数据有效标志位*/
assign rdata_vaild = (main_state == RD_DATA) && (scl_level_cnt == 8'd15) && scl_low;
/*读出的有效数据*/
always @(posedge i_clk) begin
if(!i_rst_n) begin
o_rd_data_vaild <= 1'b0;
o_rd_data <= 8'd0;
end
else begin
if(rdata_vaild) begin
o_rd_data_vaild <= 1'b1;
o_rd_data <= sda_data_in;
end
else
o_rd_data_vaild <= 1'b0;
end
end
endmodule
- 从机驱动
module i2c_slave#(
parameter SYS_CLOCK = 50_000_000, //系统时钟频率
parameter SCL_CLOCK = 400_000 //iic时钟频率
)(
input scl,
input clk,
input rstn,
input [7:0]data_s2m,
inout sda,
output reg[7:0]data_m2s
);
reg sda_en,sda_reg;
reg [2:0]state;
reg [3:0]send_cnt;
assign sda = sda_en ? sda_reg : 1'bZ;
localparam SCL_CNT_M = SYS_CLOCK / SCL_CLOCK;
reg scl_high,scl_low;
reg [15:0] scl_cnt;
reg [7:0]scl_level_cnt;
reg [7:0]slave_data_in,slave_data_out;
reg scl_start;
/* o_SCL 计数器*/
always @(posedge clk) begin
if(!rstn || state == 3'd0)
scl_cnt <= 16'd0;
else begin
if(scl_start) begin
if(scl_cnt == SCL_CNT_M-1'b1)
scl_cnt <= 16'd0;
else
scl_cnt <= scl_cnt + 16'd1;
end
else
scl_cnt <= 16'd0;
end
end
always@(negedge scl)begin //scl时钟起始位
if(!rstn)
scl_start <= 1'b0;
else
scl_start <= 1'b1;
end
//scl高低电平中部的标志位
always @(posedge clk) begin
if(!rstn) begin
scl_high <= 1'b0;
scl_low <= 1'b0;
end
else begin
if(scl_cnt == (SCL_CNT_M >> 2))
scl_low <= 1'b1;
else
scl_low <= 1'b0;
if(scl_cnt == ((SCL_CNT_M >> 1) + (SCL_CNT_M >> 2)))
scl_high <= 1'b1;
else
scl_high <= 1'b0;
end
end
//scl高低电平计数器
always @(posedge clk) begin
if(!rstn)
scl_level_cnt <= 8'd0;
else begin
//这几个状态需要执行数据发送接收任务
if(state == 3'd2 ||state == 3'd4||state == 3'd5) begin
if(scl_low | scl_high) begin
if(scl_level_cnt == 8'd17)
scl_level_cnt <= 8'd0;
else
scl_level_cnt <= scl_level_cnt + 8'd1;
end
end
else if(state == 3'd1 ||state == 3'd0||state == 3'd3)
scl_level_cnt <= 8'd0;
end
end
//START位
always@(negedge sda )begin
if(!rstn)
state <= 3'd0;
else if(scl == 1)begin
state <= state +3'd1;
end
end
//stop位
reg stop;
always@(posedge sda)begin
if(scl && (scl_level_cnt != 8'd0))
stop <= 1'd1;
end
//主程序状态机
always@(posedge clk or negedge rstn)begin
if(!rstn)begin
sda_en <= 0;
sda_reg <= 0;
data_m2s <= 8'd0;
stop <= 1'd0;
end
else begin
case(state)
3'd0:begin
stop <= 0;
slave_data_in <= 8'd0;
slave_data_out <= 8'd0;
scl_start <= 1'b0;
sda_en <= 0;
end
3'd1:begin
if(scl_low)
state <= 3'd2;
end
3'd2:begin //状态1,接受来自主机的数据
receive_8bit_data;
if(scl_level_cnt >= 8'd16)begin
data_m2s <= slave_data_in;
sda_en <= 1;
sda_reg <= 0;
if(scl_level_cnt == 8'd17 && scl_low)
sda_en <= 0; //使得sda_en在17到0的时刻拉低,否则sda_en会延长一个clk,与主机冲突
end
else if(stop) //若收到stop信号,则表示一次写入完成,返回状态0
state <= 3'd0;
else begin
sda_en <= 0;
end
end
3'd3:begin //状态2,若再次收到START信号,表示主机处于读数据模式
if(scl_low)
state <= 3'd4;
end
3'd4:begin //状态3,接受来自主机的 {器件地址,1'b1}
receive_8bit_data;
if(scl_level_cnt >= 8'd16 )begin
data_m2s <= slave_data_in;
sda_en <= 1;
sda_reg <= 0;
if(scl_level_cnt == 8'd17 && scl_low)begin
state <= 3'd5;
end
end
end
3'd5:begin //状态4,从机发送数据,发送完后,主机接受,接受完后主机发送nack(高电平),因此sda en拉低
sda_en <= 1;
send_8bit_data;
if((scl_level_cnt >8'd15))
sda_en <= 0;
if((scl_level_cnt==8'd16)&& scl_high && (sda==0))begin
state <= 3'd5;
slave_data_out <= data_s2m;
end
else if(stop)
state <= 3'd0;
end
default:state <= 3'd0;
endcase
end
end
//接受sda数据
task receive_8bit_data;
if(scl_level_cnt <= 8'd15) begin
if(scl_high)
slave_data_in <= {slave_data_in[6:0],sda};
end
endtask
//输出串行数据
task send_8bit_data;
if(scl_level_cnt < 8'd17) begin
sda_reg <= slave_data_out[7];
if(scl_low)
slave_data_out <= {slave_data_out[6:0],1'b0};
end
endtask
endmodule
- tb
`timescale 1ns/1ns
module i2c_master_tb;
wire o_rd_data_vaild,o_cfg_done;
wire io_SDA,o_SCL;
wire [7:0]o_rd_data,data_m2s;
reg i_clk,i_rst_n,i_wr_en,i_rd_en;
reg [7:0]wrdata,rddata;
i2c_master#(
.SYS_CLOCK(50_000_000), //系统时钟频率
.SCL_CLOCK(400_000) //iic时钟频率
)
uu(
.i_clk (i_clk), //系统时钟
.i_rst_n (i_rst_n), //系统复位
.i_wr_en (i_wr_en), //写使能
.i_rd_en (i_rd_en), //读使能
.i_dev_addr (7'd115), //器件地址 e6
.i_reg_addr (16'h2f), //寄存器地址,单字节时从低字节有效 2f
.i_reg_addr_num(2'd1) , //寄存器地址字节数 1: 1字节,其他:2字节
.i_wr_data (wrdata), //写数据 9c
.i_wr_data_num (4'd8), //写数据字节数,默认为1
.o_rd_data_vaild(o_rd_data_vaild), //读数据有效
.o_rd_data (o_rd_data), //读数据
.i_rd_data_num (4'd8), //读数据字节数,默认为1
.o_cfg_done (o_cfg_done), //一次读写操作完成标志
.io_SDA (io_SDA), //IIC--SDA
.o_SCL (o_SCL) //IIC--SCL
);
i2c_slave#(
.SYS_CLOCK(50_000_000), //系统时钟频率
.SCL_CLOCK(400_000) //iic时钟频率
)
uy(
.scl(o_SCL),
.clk(i_clk),
.rstn(i_rst_n),
.data_s2m(rddata),
.sda(io_SDA), //读到的数据
.data_m2s(data_m2s)
);
initial i_clk = 1;
always#2 i_clk = ~i_clk;
initial begin
i_rst_n = 0; i_wr_en= 0; i_rd_en = 0; wrdata=8'h00; rddata=8'h00;
#101;
i_rst_n = 1;
i_wr_en= 1; //主机对从机写入数据
#8891;
wrdata=8'hc1;
#4500;wrdata=8'hc2;
#4500;wrdata=8'hc3;
#4500;wrdata=8'hc4;
#4500;wrdata=8'hc5;
#4500;wrdata=8'hc6;
#4500;wrdata=8'hc7;
#4500;wrdata=8'hc8;
#4500;
i_wr_en= 0;
#400;
i_rd_en = 1;
#15000;
#4500 rddata=8'hd1;
#4500;rddata=8'hd2;
#4500;rddata=8'hd3;
#4500;rddata=8'hd4;
#4500;rddata=8'hd5;
#4500;rddata=8'hd6;
#4500;rddata=8'hd7;
#4500;rddata=8'hd8;
#9000;
i_rd_en = 0;
#2000;
$stop;
end
endmodule
- 波形
问题就是sda有时候会出现高组态和不定态,这主要是因为主机和从机的scl计数器差一个clk时钟,而且我设置clk50Mhz,scl400Khz,除以下就是125,所以scl高低电平持续时间是差一个时钟的。不过能保证scl高电平时sda保持(除了起始和结束位)