【Sipeed Tang Primer 25K】评测:4、I2C搞起来
[复制链接]
//声明,定义了输入输出端口。clk是时钟信号,start是开始信号,DCn用于区分数据和命令,Data是要传输的8位数据。输出包括busy表示模块是否忙碌,scl和sda分别是I2C协议的时钟线和数据线。//
module I2C(
input clk, // 时钟输入
input start, // 开始信号
input DCn, // 1 -> 数据 / 0 -> 命令
input [7:0]Data, // 8位数据
output reg busy=0, // I2C忙碌状态
output reg scl=1, // 串行时钟
output reg sda=1); // 串行数据
// 定义一些参数,用于表示状态机的各个状态和等待时间。//
parameter IDEL = 0;
parameter START = 1;
parameter ADDR = 2;
parameter CBYTE = 3;
parameter DATA = 4;
parameter STOP = 5;
parameter T_WAIT= 6;
// 定义一些寄存器用于存储状态、计数器、延迟、从设备地址、控制字节和数据。//
reg DCn_r=0;
reg [2:0]state=0;
reg [3:0]i=0;
reg [3:0]step=0;
reg [12:0]delay=1;
reg [7:0]slave= 8'b01111000; // 从设备地址
reg [7:0]cbyte= 8'b10000000; // 命令的控制字节
reg [7:0]dbyte= 8'b01000000; // 数据的控制字节
reg [7:0]data= 0;
//通过有限状态机(FSM)的方式实现了I2C通信协议。在每个状态中,根据子状态(step)的不同,控制时钟线(scl)和数据线(sda)的电平,以及处理延迟和计数器,从而实现了I2C协议的各个阶段:开始、地址发送、控制字节发送、数据发送和停止。//
always @(posedge clk)
begin
// 时钟上升沿触发
if(delay != 1) // 如果延迟不为1
begin
delay <= delay - 1; // 延迟计数器减1
end
else begin // 如果延迟为1
case(state) // 根据当前状态执行相应操作
IDEL:begin // 空闲状态
scl <= 1; // 保持时钟线高
sda <= 1; // 保持数据线高
if(start) // 如果接收到开始信号
begin
DCn_r <= DCn; // 读取数据/命令标志
data <= Data; // 读取数据
busy <= 1; // 设置忙碌标志
state <= START; // 转移到开始状态
step <= 0; // 子状态设置为0
end
end
START:begin // 开始状态
case(step)
0:begin
sda <= 0; // 数据线拉低,表示开始条件
delay <= T_WAIT; // 等待一定周期
step <= step + 1; // 转到下一个子状态
end
1:begin
scl <= 0; // 时钟线拉低,准备发送数据
state <= ADDR; // 转移到地址发送状态
step <= 0; // 子状态设置为0
end
endcase
end
ADDR:begin // 地址发送状态
case(step)
0:begin
if(i < 8) // 如果还没有发送完8位地址
begin
scl <= 0; // 时钟线拉低
step <= 1; // 转到下一个子状态
end
else if(i == 8) // 如果地址发送完毕
begin
scl <= 0; // 时钟线拉低
sda <= 0; // 数据线拉低,准备接收应答位
delay <= T_WAIT;// 等待一定周期
i <= i + 1; // 计数器加1
step <= 2; // 转到下一个子状态
end
end
1:begin
sda <= slave[7 - i]; // 发送地址位
delay <= T_WAIT - 1; // 等待一定周期
i <= i + 1; // 计数器加1
step <= 2; // 转到下一个子状态
end
2:begin
if(i < 9) // 如果还没有接收完应答位
begin
scl <= 1; // 时钟线拉高,锁存数据
delay <= T_WAIT; // 等待一定周期
step <= 0; // 转到下一个子状态
end
else begin
scl <= 1; // 时钟线拉高
delay <= T_WAIT; // 等待一定周期
step <= 3; // 转到下一个子状态
end
end
3:begin
scl <= 0; // 时钟线拉低
sda <= 0; // 数据线拉低
delay <= T_WAIT; // 等待一定周期
step <= 4; // 转到下一个子状态
end
4:begin
step <= 0; // 子状态设置为0
i <= 0; // 计数器清零
state <= CBYTE; // 转移到控制字节发送状态
end
endcase
end
CBYTE:begin // 控制字节发送状态
case(step)
0:begin
if(i < 8) // 如果还没有发送完8位控制字节
begin
scl <= 0; // 时钟线拉低
step <= 1; // 转到下一个子状态
end
else if(i == 8) // 如果控制字节发送完毕
begin
scl <= 0; // 时钟线拉低
sda <= 0; // 数据线拉低,准备接收应答位
delay <= T_WAIT;// 等待一定周期
i <= i + 1; // 计数器加1
step <= 2; // 转到下一个子状态
end
end
1:begin
if(DCn_r) // 如果是数据
begin
sda <= dbyte[7 - i]; // 发送数据控制字节
end
else begin
sda <= cbyte[7 - i]; // 发送命令控制字节
end
delay <= T_WAIT - 1; // 等待一定周期
i <= i + 1; // 计数器加1
step <= 2; // 转到下一个子状态
end
2:begin
if(i < 9) // 如果还没有接收完应答位
begin
scl <= 1; // 时钟线拉高,锁存数据
delay <= T_WAIT; // 等待一定周期
step <= 0; // 转到下一个子状态
end
else begin
scl <= 1; // 时钟线拉高
delay <= T_WAIT; // 等待一定周期
step <= 3; // 转到下一个子状态
end
end
3:begin
scl <= 0; // 时钟线拉低
sda <= 0; // 数据线拉低
delay <= T_WAIT; // 等待一定周期
step <= 4; // 转到下一个子状态
end
4:begin
step <= 0; // 子状态设置为0
i <= 0; // 计数器清零
state <= DATA; // 转移到数据发送状态
end
endcase
end
DATA:begin // 数据发送状态
case(step)
0:begin
if(i < 8) // 如果还没有发送完8位数据
begin
scl <= 0; // 时钟线拉低
step <= 1; // 转到下一个子状态
end
else if(i == 8) // 如果数据发送完毕
begin
scl <= 0; // 时钟线拉低
sda <= 0; // 数据线拉低,准备接收应答位
delay <= T_WAIT;// 等待一定周期
i <= i + 1; // 计数器加1
step <= 2; // 转到下一个子状态
end
end
1:begin
sda <= data[7 - i]; // 发送数据位
delay <= T_WAIT - 1; // 等待一定周期
i <= i + 1; // 计数器加1
step <= 2; // 转到下一个子状态
end
2:begin
if(i < 9) // 如果还没有接收完应答位
begin
scl <= 1; // 时钟线拉高,锁存数据
delay <= T_WAIT; // 等待一定周期
step <= 0; // 转到下一个子状态
end
else begin
scl <= 1; // 时钟线拉高
delay <= T_WAIT; // 等待一定周期
step <= 3; // 转到下一个子状态
end
end
3:begin
scl <= 0; // 时钟线拉低
sda <= 0; // 数据线拉低
delay <= T_WAIT; // 等待一定周期
step <= 4; // 转到下一个子状态
end
4:begin
step <= 0; // 子状态设置为0
i <= 0; // 计数器清零
state <= STOP; // 转移到停止状态
end
endcase
end
STOP:begin // 停止状态
case(step)
0:begin
scl <= 1; // 时钟线拉高
sda <= 0; // 数据线拉低
delay <= T_WAIT; // 等待一定周期
step <= step + 1; // 转到下一个子状态
end
1:begin
state <= IDEL; // 转移到空闲状态
busy <= 0; // 清除忙碌标志
step <= 0; // 子状态设置为0
end
endcase
end
endcase
end
end
endmodule
|