FPGA开发--基于SPI接口的RTC时钟
文章摘要:
本文主要描述了DS1302实时时钟(RTC,Real-Time Clock)芯片的应用方法;
硬件平台:EP4CE6F17C8
开发环境:Quartus II 13.1
重点内容:
SPI(Master)控制时序;
SPI接口芯片读写时序;
双向IO(inout)的处理(特别重要,以后还要用到);
时序图:
CE信号:高电平有效;
写操作:低电平准备,上升沿送出;
读操作:设备在下降沿时送出数据(在上升沿或者下一个下降沿之前读取);
SPI模式: CPOL = 0, CPHA = 0
时钟极性CPOL: 即SPI空闲时,时钟信号SCLK的电平(1:空闲时高电平; 0:空闲时低电平)
时钟相位CPHA: 即SPI在SCLK第几个边沿开始采样(0:第一个边沿开始; 1:第二个边沿开始)
注意事项:
读寄存器操作只有15个时钟,发送字地址结束后的下降沿,DS1302就开始将第一个数据送出,主机应该在下一个下降沿之前读取数据即可;之前因为时序图看的不仔细,这里折腾了很久;
SPI模块:
/*
* 功能描述:简单的SPI通信模块
*/
module spi_master(
clk, // 模块时钟
rst_n, // 模块复位
sclk, // SPI_CLK
mosi, // 主发从收
miso, // Master In Slave Out
wdata, // 待发的数据
rdata, // 收到的数据
wr_req, // SPI发送
rd_req, // SPI接收
done, // SPI操作完成(高电平有效)
busy // 繁忙标志 1 - 忙, 0 - 空闲
);
input clk;
input rst_n;
output sclk;
input miso;
output mosi;
input[7:0] wdata;
output[7:0] rdata;
input wr_req;
input rd_req;
output reg done;
output busy;
reg[7:0] wdata_r; // 保存待写入的data值
reg[7:0] rdata_r; // 接收数据寄存器
reg sclk_r;
reg mosi_r;
//--------------------------------------------------
//--------------------------------------------------
// 状态机
parameter S_SPI_IDLE = 4'd0;
parameter S_SPI_SEND = 4'd1;
parameter S_SPI_RECV = 4'd2;
// 500KHz = 2us
parameter SPI_BAUD_CNT = 8'd49;
reg[7:0] mstate; // 主状态机
reg[7:0] nstate; // 次状态机
reg[7:0] spi_cnt; // SPI波特率计时器
reg[4:0] bits; // 数据位计数
//--------------------------------------------------
assign rdata = rdata_r;
assign sclk = sclk_r;
assign mosi = mosi_r;
assign busy = (mstate == S_SPI_IDLE) ? 1'b0 : 1'b1;
//--------------------------------------------------
always @(posedge clk or negedge rst_n)
begin
if(~rst_n) begin
spi_cnt <= 0; // 复位
end
else if(spi_cnt == SPI_BAUD_CNT) begin
spi_cnt <= 0; // 达到最大值,从新计数
end
else if(mstate != S_SPI_IDLE) begin
spi_cnt <= spi_cnt + 1'b1; // 非空闲状态才计数
end
// 空闲状态,清0计数
else begin
spi_cnt <= 0;
end
end
//--------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
mstate <= S_SPI_IDLE;
done <= 0;
sclk_r <= 0;
end
else begin
if(mstate == S_SPI_IDLE) begin
// 只有空闲时刻才能切换模式
if(wr_req)
mstate <= S_SPI_SEND;
else if(rd_req)
mstate <= S_SPI_RECV;
else
mstate <= S_SPI_IDLE;
nstate <= 0; // 子状态机复位
done <= 0;
end
else if(mstate == S_SPI_SEND) begin
case(nstate)
0: begin
nstate <= nstate + 1'b1;
bits <= 0;
end
1,3,5,7,9,11,13,15: begin
if(spi_cnt == SPI_BAUD_CNT) begin
sclk_r <= 1'b1; // 上跳发送数据
nstate <= nstate + 1'b1; // 转向下步
bits <= bits + 1'b1;
end
else begin
sclk_r <= 0; // 保持低电平准备数据
mosi_r <= wdata[bits]; // LSB
end
end
2,4,6,8,10,12,14,16:begin
if(spi_cnt == SPI_BAUD_CNT) begin
sclk_r <= 1'b0; // 低电平为下步做准备
nstate <= nstate + 1'b1; // 转向下步
end
else begin
sclk_r <= 1; // 保持高电平
end
end
17: begin
done <= 1'b1;
mstate <= S_SPI_IDLE; // 操作结束,转向空闲
end
default: begin
mstate <= S_SPI_IDLE; // 未知状态转向空闲
end
endcase
end
// 注意事项:读取时只有15个时钟信号,
// 发送信号结束后的后边沿(下降沿),时钟芯片已经输出
else if(mstate == S_SPI_RECV)begin
case(nstate)
0: begin
nstate <= nstate + 1'b1;
bits <= 0;
end
1,3,5,7,9,11,13,15: begin
if(spi_cnt == SPI_BAUD_CNT) begin
rdata_r[bits] <= miso; // 上升沿之前读出
bits <= bits + 1'b1;
sclk_r <= 1'b1;
nstate <= nstate + 1'b1;
end
else begin
sclk_r <= 1'b0; // 保持低电平
end
end
2,4,6,8,10,12,14,16: begin
if(spi_cnt == SPI_BAUD_CNT) begin
nstate <= nstate + 1'b1;
sclk_r <= 1'b0; // 下降沿返回数据
end
else begin
sclk_r <= 1'b1; // 高电平准备数据
end
end
17: begin
done <= 1'b1;
mstate <= S_SPI_IDLE;
end
default: begin
mstate <= S_SPI_IDLE;
end
endcase
end
else begin
mstate <= S_SPI_IDLE; // 不可识别的命令
end
end
end
//--------------------------------------------------
endmodule
DS1302模块
/*
* 功能描述: ds1302读写模块
*/
module ds1302(
clk, // 模块时钟
rst_n, // 模块复位
sclk, // SPI_CLK
sio, // SPI_SIO 输出输出复用
ce, // 芯片使能脚:高电平有效
addr, // 字地址
wdata, // 输入的数据
rdata, // 读到的数据
reg_wr, // 寄存器写
reg_rd, // 寄存器读
done // 操作完成标识
);
input clk;
input rst_n;
output sclk;
inout sio;
output ce;
input[7:0] addr;
input [7:0] wdata;
output[7:0] rdata;
input reg_wr;
input reg_rd;
reg ce_r;
reg spi_rd_req; // SPI读请求
reg spi_wr_req; // SPI写请求
reg [7:0]spi_wdata;
wire [7:0]spi_rdata;
wire spi_done;
//output
wire busy;
//input en;
output reg done;
wire mosi;
wire miso;
reg is_out;
reg[7:0] rdata_r;
//--------------------------------------------
assign ce = ce_r;
assign sio = is_out? mosi:1'bz;
assign miso = sio;
assign rdata = rdata_r;
//--------------------------------------------
reg[31:0] timer;
//--------------------------------------------
parameter S_IDLE = 4'd0;
parameter S_READ_CE = 4'd1;
parameter S_READ = 4'd2;
parameter S_WRITE_CE = 4'd5;
parameter S_WRITE = 4'd3;
parameter S_DONE = 4'd4;
parameter S_DONE2 = 4'd8;
reg[3:0] state;
parameter BYTE_TIMEOUT = 32'd25_000; // 500us
parameter T1SEC = 32'd50_000_000; // 1s
parameter T10US = 32'd50_000; // 1ms
//--------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
state <= S_IDLE;
timer <= 0;
ce_r <= 1'b0;
done <= 1'b0;
is_out <= 0;
end
else begin
case(state)
S_IDLE: begin
done <= 1'b0;
if(reg_wr) begin
state <= S_WRITE_CE;
ce_r <= 1'b1;
is_out<= 1'b1;
timer <= 0;
end
else if(reg_rd) begin
state <= S_READ_CE;
ce_r <= 1'b1;
is_out<= 1'b1;
timer <= 0;
end
else begin
state <= S_IDLE;
timer <= 0;
ce_r <= 0;
is_out<= 1'b0;
end
end
S_READ_CE: begin
// CE必须保持4us以上
if(timer == 32'd400) begin
spi_wdata <= addr; // 设置字地址
spi_wr_req <= 1'b1; // 写使能
state <= S_READ;
timer <= 0;
end
else
timer <= timer + 1'b1;
end
// 发送字地址
S_READ: begin
if(timer == BYTE_TIMEOUT) begin // 超时
state <= S_IDLE;
timer <= 0;
is_out<= 1'b0;
end
else if(spi_done) begin // 发送地址完成
is_out <= 1'b0; // 设置端口方向为输入
spi_rd_req <= 1'b1; // 启动读使能
state <= S_DONE;
timer <= 0;
end
else begin
spi_wr_req <= 1'b0;
timer <= timer + 1'b1;
end
end
S_WRITE_CE: begin
// CE必须保持4us以上
if(timer == 32'd400) begin
spi_wdata <= addr; // 设置字地址
spi_wr_req <= 1'b1; // 写使能
state <= S_WRITE;
timer <= 0;
end
else
timer <= timer + 1'b1;
end
S_WRITE: begin
if(timer == BYTE_TIMEOUT) begin
state <= S_IDLE;
timer <= 0;
end
else if(spi_done) begin
spi_wdata <= wdata; // 设置数据
spi_wr_req <= 1'b1; // 写使能
state <= S_DONE; // 还要进行收尾工作
timer <= 0;
end
else begin
timer <= timer + 1'b1;
end
end
S_DONE: begin
if(timer == BYTE_TIMEOUT) begin // 超时
state <= S_IDLE;
timer <= 0;
end
else if(spi_done) begin
rdata_r <= spi_rdata;
state <= S_DONE2;
timer <= 0;
is_out<= 1'b0;
end
else begin
spi_rd_req <= 1'b0; // 撤销读写信号
spi_wr_req <= 1'b0;
timer <= timer + 1'b1;
end
end
S_DONE2: begin
if(timer == 32'd400) begin // 延时8us
state <= S_IDLE;
timer <= 0;
done <= 1'b1; // 操作完成
end
else begin
ce_r <= 0;
timer <= timer + 1'b1;
end
end
default: begin
state <= S_IDLE;
timer <=0;
end
endcase
end
end
//--------------------------------------------
// 例化SPI接口
spi_master spi0(
.clk(clk), // 模块时钟
.rst_n(rst_n), // 模块复位
.sclk(sclk), // SPI_CLK
.mosi(mosi), // SPI_SIO
.miso(miso), // 主收
.wdata(spi_wdata), // 待发的数据
.rdata(spi_rdata), // 收到的数据
.wr_req(spi_wr_req),
.rd_req(spi_rd_req),
.done(spi_done),
.busy(busy) // 繁忙标志 1 - 忙, 0 - 空闲
);
endmodule