FPGA:EP4CE30E22C8N

实时时钟:DS1302 实现功能:按键1按下一次,以突发模式写入一次初始化日期和时间。按键2按下一次,突发读取一次DS1302中的日期和时间,并通过串口发送到上位机串口助手显示。 Verilog代码主要分为4个模块,RTL图如下和模块例化如下,主要包含DS1302模块,按键控制模块,串口通讯模块和DS1302数据向串口模块间的数据准备。

FPGA突发模式读写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电路更加易读,当然实际占用的资源也会更少。