FPGA:EP4CE30E22C8N
实时时钟:DS1302 实现功能:按键1按下一次,以突发模式写入一次初始化日期和时间。按键2按下一次,突发读取一次DS1302中的日期和时间,并通过串口发送到上位机串口助手显示。 Verilog代码主要分为4个模块,RTL图如下和模块例化如下,主要包含DS1302模块,按键控制模块,串口通讯模块和DS1302数据向串口模块间的数据准备。
//-------------------------------------------------------------------- //--逻辑实现 //-------------------------------------------------------------------
//例化按键控制模块
KEY_Module KEY_EEPROM_Inst
(
.clk_50M (clk_50M), //一次起振是0.02us
.rst_n (rst_n),
.KEY (KEY),
//模块间输出到RW_modlue
.data_out (data_in), //输出一字节需要发送的数据到RW_module(IIC)
.plus_key (plus_key), //按键按下一次产生的命令,只有一个时钟的值
.KEY_ctl (KEY_ctl) //按键控制IIC读写,0代表写,1代表读
);
//例化DS1302模块
RTC_ds1302 RTC_ds1302_Inst
(
//输入端口
.clk_50M (clk_50M), //PLL后是9M
.rst_n (rst_n),
//内部key_EEPROM_Module输入
.KEY_ctl (KEY_ctl), //按键模块输入的读还是写
.plus_key (plus_key), //按键按下一次产生的命令,只有一个时钟的值
//输出端口
.RTC_EN (RTC_EN),
.RTC_SCL (RTC_SCL),
//内部输出端口,输出给RTC_to_Uart
.Data_RTC (Data_RTC), //输出到RTC_to_Uart
.Stop_flag (Stop_flag), //读出一次RTC后的标志位
//双向端口
.RTC_SDA (RTC_SDA)
);
RTC_to_Uart RTC_to_Uart_Inst //输入输出端口声明,和模块定义,只有下面这里是逗号
(
//输入端口
.clk_50M (clk_50M),
.rst_n (rst_n), //硬件复位
//由RTC_ds1302模块输入
.RTC_Time (Data_RTC),
.Stop_flag (Stop_flag),
//输出端口
//fpga内部输出
.ctl (ctl), //数据控
.data_out (Data_Uart) //数据出
);
Uart_TX_Module Uart_TX_Module_Inst //输入输出端口声明,和模块定义,只有下面这里是逗号
(
//输入端口
.clk_50M (clk_50M),
.rst_n (rst_n),
//fpga内部输入
.ctl (ctl), //数据控
.data_in (Data_Uart), //数据入
//输出端口
.TXD (TXD) //输出的端口
);
程序核心如下,主要为有限状态机部分,通过判断按键状态进入突发读或者是写DS1302。 //时序电路,----------有限状态机状态切换------------------- always @(posedge clk_50M or negedge rst_n) begin if(!rst_n) fsm_s<=IDLE_RTC; //默认是空闲状态 else fsm_s<=fsm_s_n; end
//组合电路 ---有限状态机核心
always@(*) //状态机内部仅仅执行状态的跳转,不进行状态的内部操作
begin
case(fsm_s)
IDLE_RTC: //发送空闲态
if(plus_key) //按键按下进入
fsm_s_n=START_RTC; //进入下一态
else
fsm_s_n=fsm_s; //保持当前状态
START_RTC: //使能RTC
if(RTC_EN==1'b1&&time_cnt==RTC_GAP&&(!KEY_ctl)) //拉高RTC_EN
fsm_s_n=ADDR_RTC_WP; //进入写态
else if(RTC_EN==1'b1&&time_cnt==RTC_GAP&&KEY_ctl) //拉高RTC_EN
fsm_s_n=ADDR_RTC_R; //进入读态
else
fsm_s_n=fsm_s; //保持当前状态
ADDR_RTC_WP: //写入写保护地址
if(fsm_time_cnt==8'd8) //8个SCL时钟的时候写完了8个位
fsm_s_n=DATA_RTC_WP; //进入到写
else
fsm_s_n=fsm_s; //保持当前状态
DATA_RTC_WP: //关闭写保护
if(fsm_time_cnt==8'd8) //写了8个字节
fsm_s_n=WAIT_RTC; //进入到下一个态
else
fsm_s_n=fsm_s; //保持当前状态
WAIT_RTC: //等待突发写
if(RTC_EN==1'b1&&time_cnt==RTC_GAP) //拉低CE250个时钟后拉高
fsm_s_n=ADDR_RTC_W; //进入到下一个态
else
fsm_s_n=fsm_s;
ADDR_RTC_W: //写入DS1302的寄存器地址
if(fsm_time_cnt==8'd8) //8个SCL时钟的时候写完了8个位
fsm_s_n=DATA_RTC_W; //进入到写
else
fsm_s_n=fsm_s; //保持当前状态
DATA_RTC_W: //
if(fsm_time_cnt==8'd64) //写了8个字节
fsm_s_n=STOP_RTC; //进入到下一个态
else
fsm_s_n=fsm_s; //保持当前状态
ADDR_RTC_R: //写入DS1302的寄存器地址
if(fsm_time_cnt==8'd7&&time_cnt==8'd60)
fsm_s_n=DATA_RTC_R; //进入到读
else
fsm_s_n=fsm_s; //保持当前状态
DATA_RTC_R: //
if(fsm_time_cnt==8'd56) //读了7个字节
fsm_s_n=STOP_RTC; //进入到下一个态
else
fsm_s_n=fsm_s; //保持当前状态
STOP_RTC:
fsm_s_n=IDLE_RTC; //直接回到空闲态
default:fsm_s_n=IDLE_RTC; //默认在空闲态
endcase
end
问题总结:这里有一个大坑,就是突发模式写时间寄存器时必须写入8个字节,否则会发生数据怎么也写不进去。我当时就认为我只需要初始化7个时间寄存器,最后一个保护位寄存器就不写了吧。但是实际发现如果不写最后一个写保护寄存器,时间寄存器的数据写不进去。,即使你的写保护寄存器一直处于非保护状态。
verilog代码主要采用时序电路和组合电路严格分开的方式,总觉得这样的程序和综合出来的RTL电路更加易读,当然实际占用的资源也会更少。