I2C 实现
主机控制的Verilog实现
采用两级状态机架构,用于实现 I2C 总线协议的单字节读写、启动和停止操作
module i2c_master #(
parameter SYS_FREQ = 50_000_000
parameter I2C_FREQ = 100_000 // I2C时钟频率 (Hz)
) (
input wire clk,
input wire rst_n,
input wire req,
input wire [3:0] cmd, // 命令: bit0=START, bit1=WRITE, bit2=READ, bit3=STOP
input wire [7:0] din,
output reg [7:0] dout,
output reg done,
output reg slave_ack,
output wire i2c_scl,
inout wire i2c_sda
);
// 参数定义
localparam CMD_START = 4'b0001,
CMD_WRITE = 4'b0010, CMD_READ = 4'b0100, CMD_STOP = 4'b1000;
// 时钟参数
localparam CNT_MAX = SYS_FREQ / I2C_FREQ;
localparam HALF = CNT_MAX / 2;
// 主状态机
localparam M_IDLE = 0,
M_START = 1, M_WRITE = 2, M_READ = 3, M_ACK = 4, M_STOP = 5;
reg [2:0] main_state;
// 位状态机
localparam B_IDLE = 0,
B_START = 1, B_SEND = 2, B_RECV = 3, B_ACK = 4, B_STOP = 5;
reg [2:0] bit_state;
// 内部信号
reg [3:0] current_cmd;
reg [7:0] tx_data;
reg [7:0] rx_data;
reg sda_out;
reg sda_oe;
reg scl_reg;
reg [3:0] bit_cnt;
localparam TIMER_W = $clog2(CNT_MAX);
reg [TIMER_W-1:0] timer;
reg bit_done;
reg ack_recv;
// 三态门
assign i2c_sda = sda_oe ? sda_out : 1'bz;
assign i2c_scl = scl_reg;
// 边沿检测
reg req_d;
wire req_rise;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) req_d <= 1'b0;
else req_d <= req;
end
assign req_rise = req & ~req_d;
// 主状态机
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
main_state <= M_IDLE;
current_cmd <= 4'b0;
tx_data <= 8'b0;
done <= 1'b0;
slave_ack <= 1'b1;
end else begin
done <= 1'b0;
case (main_state)
M_IDLE: begin
if (req_rise) begin
current_cmd <= cmd;
tx_data <= din;
if (cmd & CMD_START) main_state <= M_START;
else if (cmd & CMD_WRITE) main_state <= M_WRITE;
else if (cmd & CMD_READ) main_state <= M_READ;
end
end
M_START: begin
if (bit_done) begin
if (current_cmd & CMD_WRITE) main_state <= M_WRITE;
else if (current_cmd & CMD_READ) main_state <= M_READ;
end
end
M_WRITE: begin
if (bit_done) main_state <= M_ACK;
end
M_READ: begin
if (bit_done) begin
dout <= rx_data;
main_state <= M_ACK;
end
end
M_ACK: begin
if (bit_done) begin
slave_ack <= ack_recv;
if (current_cmd & CMD_STOP) main_state <= M_STOP;
else main_state <= M_IDLE;
done <= 1'b1;
end
end
M_STOP: begin
if (bit_done) begin
main_state <= M_IDLE;
done <= 1'b1;
end
end
default: main_state <= M_IDLE;
endcase
end
end
// 位状态机
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
bit_state <= B_IDLE;
timer <= 0;
bit_cnt <= 0;
rx_data <= 0;
sda_out <= 1;
sda_oe <= 0;
scl_reg <= 1;
bit_done <= 0;
ack_recv <= 1;
end else begin
bit_done <= 0;
case (bit_state)
B_IDLE: begin
if (main_state != M_IDLE && !bit_done) begin
case (main_state)
M_START: bit_state <= B_START;
M_WRITE: bit_state <= B_SEND;
M_READ: bit_state <= B_RECV;
M_ACK: bit_state <= B_ACK;
M_STOP: bit_state <= B_STOP;
endcase
timer <= 0;
bit_cnt <= 0;
end
end
B_START: begin
if (timer < HALF) begin
scl_reg <= 1;
sda_out <= 1;
sda_oe <= 1;
end else begin
scl_reg <= 1;
sda_out <= 0;
sda_oe <= 1;
end
if (timer == CNT_MAX - 1) begin
bit_state <= B_IDLE;
bit_done <= 1;
scl_reg <= 0;
end else timer <= timer + 1;
end
B_SEND: begin
if (timer < HALF) scl_reg <= 0;
else scl_reg <= 1;
if (timer == 0) begin
sda_out <= tx_data[7-bit_cnt];
sda_oe <= 1;
end
if (timer == CNT_MAX - 1) begin
if (bit_cnt == 7) begin
bit_state <= B_IDLE;
bit_done <= 1;
scl_reg <= 0;
end else bit_cnt <= bit_cnt + 1;
timer <= 0;
end else timer <= timer + 1;
end
B_RECV: begin
if (timer < HALF) scl_reg <= 0;
else scl_reg <= 1;
if (timer == 0) sda_oe <= 0;
if (timer == HALF + HALF / 2) rx_data[7-bit_cnt] <= i2c_sda;
if (timer == CNT_MAX - 1) begin
if (bit_cnt == 7) begin
bit_state <= B_IDLE;
bit_done <= 1;
scl_reg <= 0;
end else bit_cnt <= bit_cnt + 1;
timer <= 0;
end else timer <= timer + 1;
end
B_ACK: begin
if (timer < HALF) scl_reg <= 0;
else scl_reg <= 1;
if (timer == 0) sda_oe <= 0;
if (timer == HALF + HALF / 2) ack_recv <= i2c_sda;
if (timer == CNT_MAX - 1) begin
bit_state <= B_IDLE;
bit_done <= 1;
scl_reg <= 0;
timer <= 0;
end else timer <= timer + 1;
end
B_STOP: begin
if (timer < HALF) begin
scl_reg <= 0;
sda_out <= 0;
sda_oe <= 1;
end else begin
scl_reg <= 1;
sda_out <= 0;
sda_oe <= 1;
end
if (timer == CNT_MAX - 1) begin
bit_state <= B_IDLE;
bit_done <= 1;
sda_oe <= 0;
timer <= 0;
end else timer <= timer + 1;
end
default: bit_state <= B_IDLE;
endcase
end
end
endmodule