一、概述:
我本身没有很仔细的去学习SPI的时序,而是参照了之前STM32驱动OLED时模拟的时序来写的,其中写一个字节的时序如下
/*************************************************************************/
/*函数功能: 通过SPIO软件模拟SPI通信协议,向模块(SSD1306)写入一个字节 */
/*入口参数: */
/*data:要写入的数据/命令 */
/*cmd :数据/命令标志 0,表示命令;1,表示数据; */
/*************************************************************************/
void SPI_WriteByte(unsigned char data,unsigned char cmd)
{
unsigned char i=0;
OLED_DC =cmd;
OLED_CLK=0;
for(i=0;i<8;i++)
{
OLED_CLK=0;
if(data&0x80)OLED_MOSI=1; //从高位到低位
else OLED_MOSI=0;
OLED_CLK=1;
data<<=1;
}
OLED_CLK=1;
OLED_DC=1;
}
二、Verilog代码编写
参考上述的写时序,使用Verilog代码去实现
module spi_writebyte(
input clk, //时钟信号 1m的时钟
input rst_n, //复位信号 按键复位
input ena_write, //spi写使能信号
input [7:0]data, //spi写的数据
output reg sclk, //oled的时钟信号(d0)
output reg mosi, //oled的数据信号(d1)
output write_done //spi写完成信号
);
parameter S0=0,S1=1,S2=2,Done=3;
reg[1:0] state,next_state;
reg[3:0] cnt; //写数据的位计数器
//状态机下一个状态确认
always @(*) begin
if(!rst_n) begin
next_state <= 2'd0;
end
else begin
case(state)
S0: //等待写使能信号
next_state = ena_write ? S1 : S0;
S1:
next_state = S2;
S2: //从s1到s2的位置cnt才加1所以需要cnt到8再到下一个状态
next_state = (cnt == 4'd8) ? Done : S1;
Done://这个状态主要用来产生done信号输出
next_state = S0;
endcase
end
end
//赋值和状态转换分开
//解决reg输出Latch的问题
always @(posedge clk,negedge rst_n) begin
if(!rst_n) begin
sclk = 1'b1;
mosi = 1'b0;
end
else begin
case(state)
S0: begin//等待写使能信号
sclk = 1'b1;
mosi = 1'b0;
end
S1: begin
sclk = 1'b0;
mosi = data[3'd7-cnt] ? 1'b1 : 1'b0;
end
S2: begin//从s1到s2的位置cnt才加1所以需要cnt到8再到下一个状态
sclk = 1'b1;
end
endcase
end
end
//状态流转
always @(posedge clk,negedge rst_n) begin
if(~rst_n)
state <= S0;
else
state <= next_state;
end
//计数器计数
always @(posedge clk,negedge rst_n) begin
if(~rst_n) begin
cnt <= 4'd0;
end
else begin
if(state == S1)
cnt <= cnt + 1'b1;
else if(state == S0)
cnt <= 4'd0;
else
cnt <= cnt;
end
end
assign write_done = (state==Done);//done信号输出
endmodule
测试用的testbench
`timescale 1ns/1ns //仿真单位为1ns,精度为1ns
module spi_writebyte_tb ( );
//输出用wire
//输入用reg
reg clk;
reg ena;
reg rst;
reg [7:0]data;
wire sclk;
wire mosi;
wire done;
//实例化模块
spi_writebyte spi_writebyte_inst(
.clk(clk),
.ena_write(ena),
.rst_n(rst),
.data(data),
.sclk(sclk),
.mosi(mosi),
.write_done(done)
);
//赋初始值
initial begin
#0 clk = 0;//clk初始为0
data = 8'b11010011;
ena = 0;
rst = 0;//先复位
#20 ena = 1;//使能写
rst = 1;//停止复位
end
always #5 clk = ~clk;//每5个时钟单位 clk取反一次
endmodule