SMBus和I2C通信协议,verilog代码,SCL时钟频率50kHz。
SMBCLK对应SCL,IO_SDA对应SDA。
smbus_2.v
`timescale 1ns / 1ps
//
// Company:
// Engineer: Mark Mo Swift
//
// Create Date: 2022/10/24 21:25:07
// Design Name:
// Module Name: smbus_2
// Project Name: IOBus
// Target Devices:
// Tool Versions: Vivado 2018.2
// Description:
//
// Dependencies: crc_8.v
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module smbus_2
(
input sys_clk, //系统时钟500kHZ(2us)
input sys_rst_n, //0硬件复位
output SMBCLK, //SMBus工作时钟50KHZ
inout IO_SDA, //SMBus数据
input SMBus_en, //SMBus使能 1使能
input rd_or_wr, //读写标志:读1,写0
input [6:0] device_addr, //SMBus器件地址
input [15:0] SMBus_reg_addr, //寄存器地址
input [15:0] SMBus_reg_data, //寄存器数据
output reg [15:0] read_data, //接收数据
output reg SMBus_done = 0 //SMBus完成标志 0完成
);
//---------------------------------------------
//分频部分
reg [2:0] cnt; // cnt=0:SMBCLK上升沿,cnt=1:SMBCLK高电平中间,cnt=2:SMBCLK下降沿,cnt=3:SMBCLK低电平中间
reg [8:0] cnt_delay; //100循环计数,产生iic所需要的时钟
reg cnt_pulse = 1; //0 pulse
wire SMBCLK_en; //低电平有效
reg SCL_org; //不随外界影响的SCL方波
reg IDLE_con; //处于IDLE状态,为高
always @ (posedge sys_clk or negedge sys_rst_n)
if( !sys_rst_n ) cnt_delay <= 9'd0;
else if( !cnt_pulse ) cnt_delay <= cnt_delay;
else if( cnt_delay == 9'd99 ) cnt_delay <= 9'd0; //计数到10us为SMBCLK的周期,即100KHz
else cnt_delay <= cnt_delay + 1'b1; //时钟计数
always @ (posedge sys_clk or negedge sys_rst_n) begin
if( !sys_rst_n ) cnt <= 3'd5;
else begin
case ( cnt_delay )
9'd24: cnt <= 3'd1; //cnt=1:SMBCLK高电平中间,用于数据采样
9'd49: cnt <= 3'd2; //cnt=2:SMBCLK下降沿
9'd74: cnt <= 3'd3; //cnt=3:SMBCLK低电平中间,用于数据变化
9'd99: cnt <= 3'd0; //cnt=0:SMBCLK上升沿
default: cnt <= 3'd5;
endcase
end
end
`define SMBCLK_LOW_MID (cnt_delay == 9'd76)
`define SMBCLK_POS (cnt==3'd0) //cnt=0:SMBCLK上升沿
`define SMBCLK_HIG_MID (cnt==3'd1) //cnt=1:SMBCLK高电平中间,用于数据采样
`define SMBCLK_NEG (cnt==3'd2) //cnt=2:SMBCLK下降沿
`define SMBCLK_LOW_END (cnt==3'd3) //cnt=3:SMBCLK低电平中间,用于数据变化
assign SMBCLK_en = (~SMBus_en) | IDLE_con; //处于不使能 或 IDLE状态 SCL不应当输出
assign SMBCLK = SCL_org | SMBCLK_en;
//产生SMBus所需要的时钟
always @ (posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n) SCL_org <= 1'b0;
else if(cnt_delay == 9'd01) SCL_org <= 1'b1;
else if(cnt==3'd0) SCL_org <= 1'b1; //SMBCLK信号上升沿
else if(cnt==3'd2) SCL_org <= 1'b0; //SMBCLK信号下降沿
else SCL_org <= SMBCLK;
//---------------------------------------------
//读、写时序
parameter IDLE = 4'h0; //空闲状态
parameter START1 = 4'h1; //Start状态
parameter DEV_ADDR = 4'h2; //送slave address器件地址
parameter ACK1 = 4'h3; //第一次读外设发送的ACK
parameter COMMAND = 4'h4; //写命令command
parameter ACK2_1 = 4'h5;
parameter MEM_ADDR_2= 4'h6; //写数据
parameter START2 = 4'h7; //第二次Start状态:仅限读
parameter DEV_ADDR_2= 4'h8; //第二次送slave address器件地址
parameter ACK2_2 = 4'h9; //读操作的ACK代码
parameter TRANS_PROC= 4'hA; //传送数据:读或者写
parameter ACK3 = 4'hB; //主机读数据 发ACK
parameter ACK4 = 4'hC; //主机写数据 收ACK
parameter STOP1 = 4'hD;
parameter STOP2 = 4'hE;
reg[7:0] BUFF_REG = 8'b0000_0000; //SMBus上传输的数据寄存器,缓存作用
reg[3:0] SMBus_State; //状态寄存器
reg[3:0] SMBus_State_Turn;
reg IO_SDA_REG; //输出数据寄存器
reg IO_SDA_DIR; //输出数据IO_SDA信号inout方向控制位,1'b0为input.1'b1为output
reg[3:0] num; // 计数作用
wire IN_SDA; //记录IO_SDA输入数据
reg [4:0]dataNum = 4'h0;
reg [7:0]pecIn;
reg [7:0]pecOut;
wire[7:0]crc_o;
reg [7:0]read_data_buf; //即将存入read_data的数据
assign IO_SDA = IO_SDA_DIR ? IO_SDA_REG : 1'bz;
assign IN_SDA = IO_SDA;
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
SMBus_State <= IDLE; //状态寄存器
num <= 4'd0;
IO_SDA_DIR <= 1'b0; //输出数据IO_SDA信号inout方向控制位 1'b0为input
IO_SDA_REG <= 1'b1; //输出数据寄存器,IO_SDA线保持高电平
read_data <= 8'b0000_0000; //接收数据
SMBus_done <= 1'b0; //IIC完成标志 未完成
cnt_pulse <= 1;
end
else
case (SMBus_State)
IDLE: begin //空闲状态
IDLE_con <= 1;
IO_SDA_DIR <= 1'b0; //数据线IO_SDA为input
IO_SDA_REG <= 1'b1; //IO_SDA拉高电平
num <= 4'd0;
if(`SMBCLK_LOW_END && SMBus_en) begin //SMBus_en==1,开始操作
BUFF_REG <= {device_addr,1'b0}; //从设备地址(1'b0 写操作 1'b1读操作)
SMBus_State <= START1;
IDLE_con <= 0;
SMBus_done <= 1'b0; //smbus clear完成标志 未完成
end
else SMBus_State <= IDLE; //没有任何键被按下
end
START1: begin //Start状态
if(`SMBCLK_HIG_MID) begin //`SMBCLK_HIG_MID == 1, SMBCLK为高电平期间
IO_SDA_DIR <= 1'b1; //数据线IO_SDA为output
IO_SDA_REG <= 1'b0; //IO_SDA_REG输出数据寄存器,拉低数据线IO_SDA,产生起始位信号
num <= 4'd0; //num计数清零
end
else if(`SMBCLK_LOW_END) begin
SMBus_State <= DEV_ADDR;
end
else SMBus_State <= START1; //等待SMBCLK高电平中间位置到来
end
DEV_ADDR: begin
if(`SMBCLK_LOW_MID) begin
if(num == 4'd7) begin
IO_SDA_REG <= BUFF_REG[0];
end
else begin
IO_SDA_DIR <= 1'b1;
case (num)
4'd0: IO_SDA_REG <= BUFF_REG[7];//BUFF_REG此时存的从设备地址,SMBus先传MSB
4'd1: IO_SDA_REG <= BUFF_REG[6];
4'd2: IO_SDA_REG <= BUFF_REG[5];
4'd3: IO_SDA_REG <= BUFF_REG[4];
4'd4: IO_SDA_REG <= BUFF_REG[3];
4'd5: IO_SDA_REG <= BUFF_REG[2];
4'd6: IO_SDA_REG <= BUFF_REG[1];
default: IO_SDA_REG <= 8'b0000_0000 ;
endcase
end
end
else if(`SMBCLK_HIG_MID) num <= num + 4'b1;
else if(`SMBCLK_LOW_END && (num == 8)) begin
num = 0;
SMBus_State <= ACK1;
end
else SMBus_State <= DEV_ADDR;
end
ACK1: begin//*******
if(`SMBCLK_LOW_MID) begin //SMBCLK低电平,此即ACK1开始时的低电平
IO_SDA_DIR <= 1'b0; //IO_SDA置为高阻态(input)
IO_SDA_REG <= 1'b0;
BUFF_REG <= SMBus_reg_addr[15:8]; // 高八位寄存器地址
end
else if(`SMBCLK_HIG_MID) begin //SMBCLK高电平
if(IN_SDA) begin
SMBus_State_Turn <= IDLE;
IO_SDA_REG <= 1'b1;
end
else begin //从机返回了ACK
SMBus_State_Turn <= COMMAND;
BUFF_REG <= SMBus_reg_addr[15:8]; // 高八位寄存器地址
end
end
else if(`SMBCLK_LOW_END) begin
IO_SDA_DIR = 1'b1; //IO_SDA置为通态(output)
SMBus_State <= SMBus_State_Turn; //响应信号
end
else SMBus_State <= ACK1; //等待从机响应
end
COMMAND: begin
if(`SMBCLK_LOW_MID) begin
if(num == 4'd7) begin
IO_SDA_REG <= BUFF_REG[0];
num <= 4'd0; //num计数清零
end
else begin
IO_SDA_DIR <= 1'b1; //IO_SDA作为output
case (num)
4'd0: IO_SDA_REG <= BUFF_REG[7]; // 高八位寄存器地址
4'd1: IO_SDA_REG <= BUFF_REG[6];
4'd2: IO_SDA_REG <= BUFF_REG[5];
4'd3: IO_SDA_REG <= BUFF_REG[4];
4'd4: IO_SDA_REG <= BUFF_REG[3];
4'd5: IO_SDA_REG <= BUFF_REG[2];
4'd6: IO_SDA_REG <= BUFF_REG[1];
default: IO_SDA_REG <= 8'b0000_0000 ;
endcase
num <= num + 1'b1;
SMBus_State <= COMMAND;
end
end
else if(`SMBCLK_LOW_END && (num == 0)) begin
SMBus_State <= ACK2_1;
end
else SMBus_State <= COMMAND;
end
ACK2_1: begin
if(`SMBCLK_LOW_MID) begin
IO_SDA_DIR <= 1'b0; //IO_SDA置为高阻态(input)
IO_SDA_REG <= 1'b0; //返回ACK
BUFF_REG <= SMBus_reg_addr[15:8]; // 1地址
end
else if(`SMBCLK_HIG_MID) begin //SMBCLK高电平*******
if(IN_SDA) begin
SMBus_State <= IDLE;
IO_SDA_REG <= 1'b1;
end
else begin //从机返回ACK
if(rd_or_wr) SMBus_State_Turn <= MEM_ADDR_2; //rd_or_wr == 1 读
else begin //rd_or_wr == 0 写
SMBus_State_Turn <= TRANS_PROC;
pecOut <= crc_o; //装载校验数据包
dataNum <= 4'h0;
BUFF_REG <= SMBus_reg_data[7:0];// 低八位寄存器数据
end
end
end
else if(`SMBCLK_LOW_END) begin
IO_SDA_DIR = ~rd_or_wr; //1'b0 写操作 需要发送数据 1'b1读操作 需要重新开始
SMBus_State <= SMBus_State_Turn; //状态转移
end
else SMBus_State <= ACK2_1; //等待从机响应
end
MEM_ADDR_2: begin //空闲状态
IDLE_con <= 1;
IO_SDA_DIR <= 1'b0; //数据线IO_SDA为input
IO_SDA_REG <= 1'b1; //IO_SDA拉高电平
SMBus_done <= 1'b0; //smbus clear完成标志
num <= 4'd0;
if(`SMBCLK_LOW_END && SMBus_en) begin //SMBus_en==1,开始操作
//BUFF_REG <= {device_addr,1'b1}; //从设备地址(1'b0 写操作 1'b1读操作)
SMBus_State <= START2;
IDLE_con <= 0;
end
else SMBus_State <= MEM_ADDR_2; //没有任何键被按下
end
START2: begin //第二次Start状态
if(`SMBCLK_LOW_MID) IO_SDA_REG <= 1'b1;
if(`SMBCLK_HIG_MID) begin //`SMBCLK_HIG_MID == 1, SMBCLK为高电平期间
IO_SDA_DIR <= 1'b1; //数据线IO_SDA为output
IO_SDA_REG <= 1'b0; //IO_SDA_REG输出数据寄存器,拉低数据线IO_SDA,产生起始位信号
//SMBus_State <= DEV_ADDR_2;
num <= 4'd0; //num计数清零
BUFF_REG <= {device_addr,1'b1}; //从设备地址(1'b0 写操作 1'b1读操作)
end
else if(`SMBCLK_LOW_END) begin
SMBus_State <= DEV_ADDR_2; //从机响应信号
end
else SMBus_State <= START2; //等待SMBCLK高电平中间位置到来
end
DEV_ADDR_2: begin
if(`SMBCLK_LOW_MID) begin
if(num == 4'd7) begin
IO_SDA_REG <= BUFF_REG[0]; //读操作
//num <= 4'd0; //num计数清零
//SMBus_State <= ACK2_2;
end
else begin
IO_SDA_DIR <= 1'b1; //IO_SDA置为通态(input)
case (num)
4'd0: IO_SDA_REG <= BUFF_REG[7];//BUFF_REG此时存的从设备地址,SMBus先传MSB
4'd1: IO_SDA_REG <= BUFF_REG[6];
4'd2: IO_SDA_REG <= BUFF_REG[5];
4'd3: IO_SDA_REG <= BUFF_REG[4];
4'd4: IO_SDA_REG <= BUFF_REG[3];
4'd5: IO_SDA_REG <= BUFF_REG[2];
4'd6: IO_SDA_REG <= BUFF_REG[1];
default: IO_SDA_REG <= 8'b0000_0000 ;
endcase
//num <= num + 1'b1;
end
end
else if(`SMBCLK_HIG_MID) num <= num + 4'b1;
else if(`SMBCLK_LOW_END && (num == 8)) begin
num <= 0;
SMBus_State <= ACK2_2; //从机响应信号
end
else SMBus_State <= DEV_ADDR_2;
end
ACK2_2: begin//*******
if(`SMBCLK_LOW_MID) begin //从机响应信号
IO_SDA_DIR <= 1'b0; //IO_SDA置为高阻态(input)
IO_SDA_REG <= 1'b0; //返回ACK
//SMBus_State <= SMBus_State_Turn;
BUFF_REG <= {device_addr,1'b1}; //送器件地址(读操作),特定地址读需要执行该步骤以下操作
end
else if(`SMBCLK_HIG_MID) begin //SMBCLK高电平*******
if(IN_SDA) begin
//SMBus_State_Turn <= IDLE;
SMBus_State <= IDLE;
IO_SDA_REG <= 1'b1;
end
else begin //从机返回ACK
SMBus_State_Turn <= TRANS_PROC;
dataNum <= 4'h0;
end
end
else if(`SMBCLK_LOW_END) begin
IO_SDA_DIR = ~rd_or_wr; //IO_SDA置为通态(output)
SMBus_State <= SMBus_State_Turn; //状态转移
end
else SMBus_State <= ACK2_2; //等待从机响应
end
TRANS_PROC: begin
if(rd_or_wr) begin //读操作
if(num <= 4'd7) begin
if(`SMBCLK_LOW_MID) begin
SMBus_State <= TRANS_PROC;
IO_SDA_DIR <= 1'b0; //IO_SDA置为高阻态(input)
end
else if(`SMBCLK_HIG_MID) begin
case (num)
4'd0: read_data_buf[7] <= IN_SDA;//assign IO_SDA = IO_SDA_DIR ? IO_SDA_REG:1'bz;
4'd1: read_data_buf[6] <= IN_SDA;
4'd2: read_data_buf[5] <= IN_SDA;
4'd3: read_data_buf[4] <= IN_SDA;
4'd4: read_data_buf[3] <= IN_SDA;
4'd5: read_data_buf[2] <= IN_SDA;
4'd6: read_data_buf[1] <= IN_SDA;
4'd7: read_data_buf[0] <= IN_SDA;
default: IO_SDA_REG <= 8'b0000_0000;
endcase
num <= num + 1'b1;
end
else SMBus_State <= TRANS_PROC;
end
else if(`SMBCLK_LOW_END && num == 4'd8) begin
num <= 4'd0;
SMBus_State <= ACK4; //状态转移
end
else SMBus_State <= TRANS_PROC;
end
else begin //写操作
if(num <= 4'd7) begin
SMBus_State <= TRANS_PROC;
if(`SMBCLK_LOW_MID) begin
IO_SDA_DIR <= 1'b1; //数据线IO_SDA作为output
case (num)
4'd0: IO_SDA_REG <= BUFF_REG[7];
4'd1: IO_SDA_REG <= BUFF_REG[6];
4'd2: IO_SDA_REG <= BUFF_REG[5];
4'd3: IO_SDA_REG <= BUFF_REG[4];
4'd4: IO_SDA_REG <= BUFF_REG[3];
4'd5: IO_SDA_REG <= BUFF_REG[2];
4'd6: IO_SDA_REG <= BUFF_REG[1];
4'd7: IO_SDA_REG <= BUFF_REG[0];
default: IO_SDA_REG <= 8'b0000_0000;
endcase
num <= num + 1'b1;
end
end
else if(`SMBCLK_LOW_END) begin
num <= 4'd0;
SMBus_State <= ACK4; //状态转移
end
else SMBus_State <= TRANS_PROC;
end
end
ACK4: begin
if(`SMBCLK_LOW_MID) begin
IO_SDA_DIR <= rd_or_wr; //1'b0 写操作 收ACK 1'b1读操作 发ACK
IO_SDA_REG <= 1'b0;
case(dataNum)
4'h0: begin
read_data[7:0] <= read_data_buf;
BUFF_REG <= SMBus_reg_data[15:8];
SMBus_State_Turn <= TRANS_PROC;
end
4'h1:begin
read_data[15:8] <= read_data_buf;
BUFF_REG <= pecOut;
SMBus_State_Turn <= TRANS_PROC;
end
4'h2:begin
pecIn <= read_data_buf;
SMBus_State_Turn <= STOP1;
dataNum <= 4'hF;
end
default: dataNum <= 4'hF;
endcase
dataNum <= dataNum + 4'h1;
end
else if(`SMBCLK_LOW_END) begin
IO_SDA_DIR <= ~rd_or_wr; //1'b0 写操作 1'b1读操作
SMBus_State <= SMBus_State_Turn;
end
else SMBus_State <= ACK4;
end
STOP1: begin
if(`SMBCLK_LOW_MID) IO_SDA_DIR <= 1'b1;
else if(`SMBCLK_HIG_MID) IO_SDA_REG <= 1'b1;//SMBCLK为高时,IO_SDA产生上升沿(结束信号)
else if(`SMBCLK_LOW_END) SMBus_State <= STOP2;
else SMBus_State <= STOP1;
end
STOP2: begin //
if(`SMBCLK_LOW_MID) begin
IO_SDA_DIR <= 1'b0;
SMBus_done <= 1'b1;
#20_0000 SMBus_State <= IDLE;
end
else SMBus_State <= STOP2;
end
default: SMBus_State <= IDLE;
endcase
end
crc_8 pec
(
.clk(sys_clk),
.din0({device_addr,1'b0}),
.din1(SMBus_reg_addr[15:8]),
.din2(SMBus_reg_data[7:0]),
.din3(SMBus_reg_data[15:8]),
.crc_o(crc_o)
);
endmodule
crc_8.v
`timescale 1ns / 1ps
//
// Company:
// Engineer: Mark Mo Swift
//
// Create Date: 2022/11/04 20:31:08
// Design Name:
// Module Name: crc_8
// Project Name: IOBus
// Target Devices:
// Tool Versions: Vivado 2018.2
// Description:
//
// Dependencies: Null
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module crc_8
(
input wire clk,
(* MARK_DEBUG="true" *)input wire [7:0] din0,
(* MARK_DEBUG="true" *)input wire [7:0] din1,
input wire [7:0] din2,
input wire [7:0] din3,
(* MARK_DEBUG="true" *)output wire [7:0] crc_o
);
parameter [7:0] poly = 8'b0000_0111;
(* MARK_DEBUG="true" *)reg [3:0] crc_cnt;
//除数为1_0000_0111,第一位为1。
//因为我们需要的余数,最高为8位,所以实际上最高位我们用不到。于是为节省资源,可在运算时将整个数组向前移动一位。并且在存储除数时,可忽略第一位1。
//运算时我们先判断被除数最高位是否为1,若为1,剩下8位进行亦或操作后,将其填入寄存器前8位。若为0,则将被除数直接落下,填入寄存器前8位。
(* MARK_DEBUG="true" *)reg [15:0] din_temp;//寄存器,存储din值进行处理
(* MARK_DEBUG="true" *)reg [1:0] times;
// reg crc_en;//运算使能信号,当该信号使能时,将din_temp向左移动一格,将其与poly进行"模2除法"
initial
begin
crc_cnt [3:0] = 4'd0;
times = 0;
end
(* MARK_DEBUG="true" *)wire crc_en;
assign crc_en = ~crc_cnt[3];
//一般一个语言块中只给一个变量赋值
always@(posedge clk)
if(crc_en == 1 && times < 2)
begin
if(crc_cnt [3] == 0 && crc_cnt [2] == 0 && crc_cnt [1] == 0 && crc_cnt [0] == 0)
begin
case(times)
0:din_temp = {din0,8'b0000_0000};
1:din_temp = {crc_o^din1,8'b0000_0000};
2:din_temp = {crc_o^din2,8'b0000_0000};
3:din_temp = {crc_o^din3,8'b0000_0000};
endcase
end
if(crc_cnt < 8)
begin
repeat(8)
begin
din_temp[15:8] <= din_temp[15] ? din_temp[14:7]^poly : din_temp[14:7];//实际上,这步我们做了运算与移动操作
din_temp[7:0] <= {din_temp[6:0],1'b0};//最后位补0
end
end
end
//计数器模块
always@(posedge clk)
begin
case(crc_cnt)
0:crc_cnt = crc_cnt + 1'b1;
1:crc_cnt = crc_cnt + 1'b1;
2:crc_cnt = crc_cnt + 1'b1;
3:crc_cnt = crc_cnt + 1'b1;
4:crc_cnt = crc_cnt + 1'b1;
5:crc_cnt = crc_cnt + 1'b1;
6:crc_cnt = crc_cnt + 1'b1;
7:crc_cnt = crc_cnt + 1'b1;
8:crc_cnt = 0;
endcase
end
always@(posedge clk)
begin
if(crc_cnt == 8)
begin
case(times)
0:times = 1;
1:times = 2;
2:times = 3;
3:times = 4;
endcase
end
end
assign crc_o = din_temp[15:8];
endmodule
已经在MLX90614DCI(GY-906-DCI)红外线温度传感器上实验成功,CRC-8算法没有做额外的测试。读数据时,该传感器传回的数据为2bit的温度数据和1bit的CRC-8校验码。写数据时,传出的同样为2bit数据和1bit的CRC-8校验码。如果要对传送的数据量进行修改,请修改smbus_2.v文件的358-446行TRANS_PROC状态和ACK4状态。
感谢博主@unique小酒馆的博客基于SUMBus或I2C通信协议,使用vivado2017 modsim,循环执行写操作_unique小酒馆的博客-CSDN博客以我对SMBus肤浅的见解,他的这篇博客有一定的问题,与传感器通信的时候也没有成功。但是他的算法框架很优秀,所以我在他的基础上进行了修改和验证,采取了相似的结构和命名风格。他提供了一份仿真文件,不出意外的话与我的代码兼容,大家有疑问的话可以用于测试写数据功能。
如果我的代码有错误,欢迎大家批评指正。