FPGA-数字时钟
总体设计
用FPGA驱动数码管按照HH-MM-SS
的格式显示时间,每秒用串口向上位机发送当前时间,当串口收到@HH:MM:SS
,对时间进行校准。由于年月要考虑到大小月,闰年等。为了简单起见,只考虑时分秒。

数码管模块驱动
数码管硬件电路

数码管分为段选SEG
端和位选SEL
端,其中SEG
是一个八位的字段,每一位用于abcdefgh
所表式的灯条在使能时是否亮起;SEL
端也是一个八位的字段,用于使能对应数码管,在循环扫描时,字段中八位只有一位是有效的。(一次只使能一个数码管)
同时显示八个数码管的基本原理是,用一定频率依次单独控制一个数码管显示内容,当刷新频率在人眼余辉效应之内,肉眼看到的现象就是八个数码管同时亮起。(若用帧率比较高的摄像头去拍,则可以看到数码管是依次循环亮起的)
为了节约FPGA的端口资源,使用74HC595
串转并模块,使需要的控制线的数量减少到三根。其工作原理就是,将SEG[7:0]
和SEL[7:0]
共十六位数据,通过Data[15:0]
输入。但这十六位数据,只需通过FPGA的三根引脚(两根时钟线和一根数据线),串行的数据经过十六个时钟周期转存到两个74HC595
模块的十六个输出引脚中。这样就不需要FPGA引出16根线来输出SEG
和SEL
的十六位数据。

前面提到使用74HC595
模块需要两根时钟线,其中SHCP
控制数据在移位寄存器上移位,而当移位完成后,通过控制STCP
将数据转入到输出寄存器中。假如移位寄存器初始值为0000_0000
有个8位数据1100_1100
,则在第1-8个时钟信号的上升沿,移位寄存器的值为0000_0000
0000_0000
1000_0000
1100_0000
0110_0000
0011_0000
1001_1000
1100_1100
,在数据准备完成时,控制STCP
,则1100_1100
这个数据才能被送到74HC595
的输出端口。
数码管软件设计
根据数码管硬件电路的原理,将驱动数码管的过程分为两大模块。
前面提到,显示八个数码管的基本原理是,用一定频率依次单独控制一个数码管显示内容。其本质就是以一定频率去改变SEG
字段和SEL
字段的值,而这也是我们第一个模块需要完成的功能。(hex8.v
)
而为了节约FPGA的端口资源用到了74HC595
串转并模块。它由两根时钟线和一根数据线作为输入。首先,将第一个模块生产的SEL
和SEG
值,封装成{SEG, SEL}
的形式,再生成SHCP
时钟信号对数据进行移位,当数据准备完成时生成STCP
将数据送到输出端口。这就是我们第二个模块需要完成的功能。(HC595_Driver.v
)
hex8.v
matlab
// 八位数码管 扫描周期 20ms / 8 晶振为50MHz
module hex8(
Clk,
Reset_n,
Data_IN[31: 0], // 要显示的内容
Sel[7:0], // 位选
Seg[7:0], // 段选
Ch_Flag // {SEG, SEL}值发送改变
);
input Clk;
input Reset_n;
input [31: 0] Data_IN;
output reg [7: 0] Sel;
output reg [7: 0] Seg;
output reg Ch_Flag;
// 计数器 1000000/8 20ms/8
reg [31: 0] cnt;
reg [2: 0] sel_idx;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
cnt <= 0;
sel_idx <= 0;
end
else if(cnt == 1000000/8- 1)begin
cnt <= 0;
sel_idx <= sel_idx + 1;
end
else begin
cnt <= cnt + 1;
end
// 位选
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Sel <= 0;
else begin
case(sel_idx)
0: Sel <= 8'b0000_0001;
1: Sel <= 8'b0000_0010;
2: Sel <= 8'b0000_0100;
3: Sel <= 8'b0000_1000;
4: Sel <= 8'b0001_0000;
5: Sel <= 8'b0010_0000;
6: Sel <= 8'b0100_0000;
7: Sel <= 8'b1000_0000;
endcase
end
// 数据缓存
reg [3: 0] data_temp;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
data_temp <= 0;
else begin
case(sel_idx)
0: data_temp <= Data_IN[3: 0];
1: data_temp <= Data_IN[3+4: 0+4];
2: data_temp <= Data_IN[3+4*2: 0+4*2];
3: data_temp <= Data_IN[3+4*3: 0+4*3];
4: data_temp <= Data_IN[3+4*4: 0+4*4];
5: data_temp <= Data_IN[3+4*5: 0+4*5];
6: data_temp <= Data_IN[3+4*6: 0+4*6];
7: data_temp <= Data_IN[3+4*7: 0+4*7];
endcase
end
// 段选
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Seg <= 0;
else begin
case(data_temp)
4'h0: Seg <= 8'b1100_0000; // 0xc0
4'h1: Seg <= 8'b1111_1001; // 0xf9
4'h2: Seg <= 8'b1010_0100; // 0xa4
4'h3: Seg <= 8'b1011_0000; // 0xb0
4'h4: Seg <= 8'b1001_1001; // 0x99
4'h5: Seg <= 8'b1001_0010; // 0x92
4'h6: Seg <= 8'b1000_0010; // 0x82
4'h7: Seg <= 8'b1111_1000; // 0xf8
4'h8: Seg <= 8'b1000_0000; // 0x80
4'h9: Seg <= 8'b1001_0000; // 0x90
4'ha: Seg <= 8'b1000_1000; // 0x88
4'hb: Seg <= 8'b1000_0011; // 0x83
4'hc: Seg <= 8'b1100_0110; // 0xc6
4'hd: Seg <= 8'b1010_0001; // 0xa1
4'he: Seg <= 8'b1000_0110; // 0x86
// 4'hf: Seg <= 8'b1000_1110; // 0x8e
4'hf: Seg <= 8'b1011_1111; // 显示 -
endcase
end
// Ch_Flag
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Ch_Flag <= 0;
else if(cnt == 2) // Seg 成功赋值需要两个机器周期
Ch_Flag <= 1;
else
Ch_Flag <= 0;
endmodule
HC595_Driver.v
matlab
// 移动数据的频率为12.5MHz 则需要脉冲频率为25MHz
module HC595_Driver(
Clk,
Reset_n,
Data[15: 0],
En,
SHCP, // SHCP 移位时钟
DS, // 数据线,SHCP下降沿时改变数据
STCP // STCP 停止时钟
);
input Clk;
input Reset_n;
input [15: 0] Data;
input En;
output reg SHCP;
output reg DS;
output reg STCP;
parameter MCNT = 2;
// 缓存数据
reg [15:0] r_data;
always@(posedge Clk)
if(En)begin
r_data <= Data;
end
// 生成脉冲信号
reg [31:0] div_counter;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
div_counter <= 0;
else if(En)begin
div_counter <= 0;
end
else if(div_counter == MCNT - 1)
div_counter <= 0;
else
div_counter <= div_counter + 1;
wire plus;
assign plus = (div_counter == MCNT - 1);
// SHCP 移位时钟边缘计数 每周期32个边缘
reg [5: 0] shcp_edge_cnt;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
shcp_edge_cnt <= 0;
else if(En)begin
shcp_edge_cnt <= 0;
end
else if(plus)begin
// 16比特的数据 要16个周期才能准备完,共32个边缘 还有一个边缘等待将移位寄存器中的数据转移到输出端口
if(shcp_edge_cnt == 32)
shcp_edge_cnt <= 0;
else
shcp_edge_cnt <= shcp_edge_cnt + 1;
end
// 根据SHCP时钟边缘 产生对应时序
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
SHCP <= 0;
DS <= 0;
STCP <= 0;
end
else if(En)begin
STCP <= 0;
DS <= 0;
SHCP <= 0;
end
else begin
// 根据时序图 生产时钟信号 在下降沿改变DS的数据(上升沿时,芯片会读取数据)
// 16比特的数据 要16个周期才能准备完,共32个边缘 还有一个边缘等待将移位寄存器中的数据转移到输出端口
case(shcp_edge_cnt)
0: begin SHCP <= 0; DS <= r_data[15]; STCP <= 0; end
1: begin SHCP <= 1; end
2: begin SHCP <= 0; DS <= r_data[14]; end
3: begin SHCP <= 1; end
4: begin SHCP <= 0; DS <= r_data[13]; end
5: begin SHCP <= 1; end
6: begin SHCP <= 0; DS <= r_data[12]; end
7: begin SHCP <= 1; end
8: begin SHCP <= 0; DS <= r_data[11]; end
9: begin SHCP <= 1; end
10: begin SHCP <= 0; DS <= r_data[10]; end
11: begin SHCP <= 1; end
12: begin SHCP <= 0; DS <= r_data[9]; end
13: begin SHCP <= 1; end
14: begin SHCP <= 0; DS <= r_data[8]; end
15: begin SHCP <= 1; end
16: begin SHCP <= 0; DS <= r_data[7]; end
17: begin SHCP <= 1; end
18: begin SHCP <= 0; DS <= r_data[6]; end
19: begin SHCP <= 1; end
20: begin SHCP <= 0; DS <= r_data[5]; end
21: begin SHCP <= 1; end
22: begin SHCP <= 0; DS <= r_data[4]; end
23: begin SHCP <= 1; end
24: begin SHCP <= 0; DS <= r_data[3]; end
25: begin SHCP <= 1; end
26: begin SHCP <= 0; DS <= r_data[2]; end
27: begin SHCP <= 1; end
28: begin SHCP <= 0; DS <= r_data[1]; end
29: begin SHCP <= 1; end
30: begin SHCP <= 0; DS <= r_data[0]; end
31: begin SHCP <= 1; end
32: begin STCP <= 1; end
default:
begin
STCP <= 0;
DS <= 0;
SHCP <= 0;
end
endcase
end
endmodule
UART串口模块驱动
UART通信原理
UART通信基本原理不再赘述,注意以下要点即可:
- 不发送时保持停止位
- 发送时先发送一个起始位
- 起始位后接八个Bit的数据
- 以停止位结束
- 发送一个字节的数据实际有10位(包含起始位和停止位)
- 异步通信,通信双方需要实现约定通信速率(波特率)
对于0基础读者通信原理可以参考STM32入门笔记10_USART串口通信+案例:上位机控制LED亮灭(USART串口通信、TIM定时器、EXTI综合案例)-CSDN博客

UART发送模块
uart_tx_byte.v
UART发送一个字节,重点讲下波特率计数值的计算方法:晶振为50MHz,则一个时钟周期为20ns;若波特率为9600,即将1s也就是1000000000ns分为9600份,每份时间为1000000000/9600,而计数1次是20ns,所以需要计数的次数为1000000000/9600/20=5208
matlab
module uart_tx_byte(
Clk,
Reset_n,
Data[7:0],
send_en,
baud_set[2:0],
START_BIT,
STOP_BIT,
uart_tx,
tx_done,
uart_state
);
input Clk;
input Reset_n;
input [7:0] Data;
input send_en;
input [2:0] baud_set;
input START_BIT;
input STOP_BIT;
output reg uart_tx;
output reg tx_done;
output reg uart_state;
// 波特率时钟设置
reg [16:0] bps_DR; // 13位
always@(baud_set)
case(baud_set)
3'd0: bps_DR <= 5208 - 1; // 9600
3'd1: bps_DR <= 2604 - 1; // 19200
3'd2: bps_DR <= 1302 - 1; // 38400
3'd3: bps_DR <= 868 -1; // 57600
3'd4: bps_DR <= 434 - 1; // 115200
default: bps_DR <= 5208 - 1;
endcase
// 波特率时钟生成
reg [16:0] div_cnt;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
div_cnt <= 0;
else if(uart_state)begin
if(div_cnt == bps_DR)
div_cnt <=0;
else
div_cnt <= div_cnt + 1;
end
else
div_cnt <= 0;
// bps_clk gen
reg bps_clk;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
bps_clk <= 0;
else if(div_cnt == 1)
bps_clk <=1;
else
bps_clk <= 0;
// bps counter
reg [3:0] bps_cnt;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
bps_cnt <= 4'd0;
else if(bps_cnt == 4'd11)
bps_cnt <= 4'd0;
else if(bps_clk)
bps_cnt <= bps_cnt + 1;
else
bps_cnt <= bps_cnt;
// 传送结束标志
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
tx_done <= 0;
else if(bps_cnt == 4'd11)
tx_done <= 1;
else
tx_done <= 0;
// 传输状态
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
uart_state <= 0;
else if(send_en)
uart_state <= 1;
else if(bps_cnt == 4'd11) // bps_cnt 计数到11, 确保有十个完整的波形
uart_state <= 0;
else
uart_state <= uart_state;
// 数据缓存
reg [7:0] data_byte_reg;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
data_byte_reg <= 8'd0;
else if(send_en)
data_byte_reg <= Data;
else
data_byte_reg <= data_byte_reg;
// 发送数据
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
uart_tx = STOP_BIT;
else begin
case(bps_cnt)
0:uart_tx <= STOP_BIT;
1:uart_tx <= START_BIT;
2:uart_tx <= data_byte_reg[0];
3:uart_tx <= data_byte_reg[1];
4:uart_tx <= data_byte_reg[2];
5:uart_tx <= data_byte_reg[3];
6:uart_tx <= data_byte_reg[4];
7:uart_tx <= data_byte_reg[5];
8:uart_tx <= data_byte_reg[6];
9:uart_tx <= data_byte_reg[7];
10:uart_tx <= STOP_BIT;
default:uart_tx <= STOP_BIT;
endcase
end
endmodule
uart_tx_data
若想发送多个字节的数据,则需事先确定需要发送数据的字节数,然后使用状态机的方法,在不同状态下执行不同操作。
matlab
module uart_tx_data(
Clk,
Reset_n,
send_go,
Data8[8*9-1: 0],
uart_tx,
trans_done
);
input Clk;
input Reset_n;
input send_go;
input [8*9-1:0] Data8;
output uart_tx;
output reg trans_done;
reg [7: 0] Data;
reg [8*9-1: 0] Data8_reg;
wire tx_done;
wire uart_state;
reg send_en;
uart_tx_byte uart_tx_byte_instance(
.Clk(Clk),
.Reset_n(Reset_n),
.Data(Data),
.send_en(send_en),
.baud_set(0),
.START_BIT(0),
.STOP_BIT(1),
.uart_tx(uart_tx),
.tx_done(tx_done),
.uart_state(uart_state)
);
reg [3:0] state;
// 状态改变
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
state <= 0;
else if(state == 10)
state <= 0;
else if(state == 0)begin // 等待使能信号
if(send_go)
state <= state + 1;
end
else if(tx_done) // 若发送完成
state <= state + 1;
// 发送使能
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
send_en <= 0;
else if(state == 0)begin
if(send_go)
send_en <= 1;
end
else if(state == 10)
send_en <= 0;
else begin
if(tx_done)
send_en <= 1; // 发送完成 send_en 置 1,开始发送下一个字节
else
send_en <= 0;
end
// Data_reg 缓存数据
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Data8_reg <= 0;
else if(state == 0)
Data8_reg <= Data8;
else if(tx_done) // 每发送完一个字节向右移动八位
Data8_reg <= Data8_reg >> 8;
else
Data8_reg <= Data8_reg;
// 根据state更新Data中的数据
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Data <= 0;
else if(state == 10)
Data <= 0;
else if(state == 0)
Data <= Data;
else
Data <= Data8_reg[7: 0];
// trans_done
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
trans_done <= 0;
else if(state == 0)
trans_done <= 0;
else if(state == 10)
trans_done <= 1; // 所有数据发送完成 trans_done 置 1
endmodule
UART接收模块
uart_rx_byte
接收模块,只需接收一个字节即可,在上层模块中完成对多个字节数据的接收
matlab
// uart 接收模块, 每个比特数据采样16次,取中间八次进行统计判断高低电平
module uart_rx_byte(
Clk,
Reset_n,
uart_rx,
baud_set[2:0],
Data[7:0],
rx_done,
rx_state
);
input Clk;
input Reset_n;
input uart_rx;
input [2:0] baud_set;
output reg[7:0] Data;
output reg rx_done;
output reg rx_state;
reg [15:0] bsp_DR;
// 设置波特率
always@(baud_set)
case(baud_set)
0: bsp_DR <= 5208; // 9600
1: bsp_DR <= 2604; // 19200
2: bsp_DR <= 1302; // 38400
3: bsp_DR <= 868; // 57600
4: bsp_DR <= 434; // 115200
default:
bsp_DR <= 5208;
endcase
// 下降沿检测器和上升沿检测器
reg [1:0] edge_reg;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
edge_reg <= 0;
end
else begin
edge_reg[0] <= uart_rx;
edge_reg[1] <= edge_reg[0];
end
wire pos_reg;
assign pos_reg = (edge_reg == 2'b01);
wire neg_reg;
assign neg_reg = (edge_reg == 2'b10);
// rx_state
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
rx_state <= 0;
else if(neg_reg) // 检测到下降沿
rx_state <= 1; // 正在接收
else if(rx_done) // 检测到发送结束
rx_state <= 0; // 空闲中
// 采样时钟生成
reg [8: 0] bsp_clk;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
bsp_clk <= 0;
else if(rx_state)begin
if(bsp_clk == (bsp_DR / 16)-1)
bsp_clk <= 0;
else
bsp_clk <= bsp_clk + 1;
end
else
bsp_clk <= 0;
// 采样计数器
reg [5: 0] bsp_cnt;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
bsp_cnt <= 0;
else if(rx_state)begin
if(bsp_cnt == 15+1)
bsp_cnt <= 0;
// 这里要减1 若忘记减1 波特率会有比较大的误差 导致不能连续收到数据
else if(bsp_clk == (bsp_DR / 16)-1)
bsp_cnt <= bsp_cnt + 1;
end
else
bsp_cnt <= 0;
// 收到的第几个字节的个数 data_idx
reg [3: 0] data_idx;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
data_idx <= 0;
else if(rx_state)begin
if(bsp_cnt == 15+1)
data_idx <= data_idx + 1;
end
else if(rx_done)
data_idx <= 0;
else
data_idx <= 0;
// 统计采样到的数据
reg [3: 0] StartBit;
reg [3: 0] StopBit;
reg [3: 0] bsp_Data_reg[7:0];
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
StartBit <= 0;
bsp_Data_reg[0] <= 0;
bsp_Data_reg[1] <= 0;
bsp_Data_reg[2] <= 0;
bsp_Data_reg[3] <= 0;
bsp_Data_reg[4] <= 0;
bsp_Data_reg[5] <= 0;
bsp_Data_reg[6] <= 0;
bsp_Data_reg[7] <= 0;
StopBit <= 0;
end
else if(rx_done) begin // 接收完清零
StartBit <= 0;
bsp_Data_reg[0] <= 0;
bsp_Data_reg[1] <= 0;
bsp_Data_reg[2] <= 0;
bsp_Data_reg[3] <= 0;
bsp_Data_reg[4] <= 0;
bsp_Data_reg[5] <= 0;
bsp_Data_reg[6] <= 0;
bsp_Data_reg[7] <= 0;
StopBit <= 0;
end
else if(rx_state)begin // 如果正在接收
// 这里要减1 若忘记减1 波特率会有比较大的误差 导致不能连续收到数据
if(bsp_clk == (bsp_DR / 16 / 2)-1)begin // 采样时间到
case(bsp_cnt)
4,5,6,7,8,9,10,11:
if(data_idx == 0)
StartBit <= StartBit + uart_rx;
else if(data_idx == 9)
StopBit <= StopBit + uart_rx;
else
bsp_Data_reg[data_idx-1] <= bsp_Data_reg[data_idx-1] + uart_rx;
default:;
endcase
end
end
// 当data_idx == 11时, 发送rx_done信号
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
rx_done <= 0;
else if(rx_done)
rx_done <= 0;
else if(data_idx == 10)begin
if(rx_state)
rx_done <= 1;
end
// 根据统计结果保存数据
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Data <= 0;
else if((neg_reg) && (!rx_state))
Data <= 0;
else if(bsp_cnt == 15)
if((data_idx != 0) || (data_idx != 9)) // 不是起始位或停止位
Data[data_idx - 1] <= (bsp_Data_reg[data_idx - 1] >= 4);
endmodule
顶层模块设计
顶层模块中,对时分秒进行运算,例化hex8
和HC595_Driver
用于将数据显示到数码管,例化uart_rx_byte
接收数据,例化uart_tx_data
发送数据
digital_clock.v
hour
minute
second
为时分秒对应的十进制数据,通过s_cnt
计时器,每秒对 second
进行自增,每60秒对minute
进行自增,每60分对hour
进行自增;
hour_hex
minute_hex
second_hex
为十分秒对应的十进制数据,这些数据位宽为八位,高低四位分别存储十进制下的十位和个位值。该数据最终在hex8
模块中,被用于生成段选数据;
hour_str_x
minute_str_x
second_str_x
字符串形式的时间量,每位通过一个字节的字符串表示 如10:11:12
,有效数值部分共6个字节。该数据用:
分隔打包好后,通过uart_tx_data
模块,发送到上位机;
数据接受状态用rx_flag
表示,接收时判断包头和分隔符,从而保证接收数据正确;
rx_hour
rx_minute
rx_second
用于保存接收到的校准数据,根据rx_flag
接收到的数据是时分或秒,若接收过程出错则不会对数据更新,只有当成功接收到正确数据时(rx_flag==9
),才会对 hour
minute
和second
执行赋值操作。
matlab
// 数字时钟 串口校时
module digital_clock(
Clk,
Reset_n,
uart_tx,
uart_rx,
SHCP,
DS,
STCP
);
input Clk;
input Reset_n;
input uart_rx;
output uart_tx;
output SHCP;
output DS;
output STCP;
reg [4:0] hour;
reg [7:0] hour_hex;
reg [5:0] minute;
reg [7:0] minute_hex;
reg [5:0] second;
reg [7:0] second_hex;
reg [3:0] rx_flag;
reg [4:0] rx_hour;
reg [5:0] rx_minute;
reg [5:0] rx_second;
// 1s计时器
parameter MCNT = 50000000;
reg [31: 0] s_cnt;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
s_cnt <= 0;
else if(rx_flag == 9)
s_cnt <= 0;
else if(s_cnt == MCNT)
s_cnt <= 0;
else
s_cnt <= s_cnt + 1;
// second
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
second <= 0;
else if(rx_flag == 9)
second <= rx_second;
else if(second == 60)
second <= 0;
else if(s_cnt == MCNT - 1)
second <= second + 1;
// minute
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
minute <= 0;
else if(rx_flag == 9)
minute <= rx_minute;
else if(minute == 60)
minute <= 0;
else if(second == 60)
minute <= minute + 1;
// hour
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
hour <= 0;
else if(rx_flag == 9)
hour <= rx_hour;
else if(hour == 24)
hour <= 0;
else if(minute == 60)
hour <= hour + 1;
// second_hex
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
second_hex <= 0;
else begin
second_hex[7: 4] <= second / 10;
second_hex[3: 0] <= second % 10;
end
// minute_hex
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
minute_hex <= 0;
else begin
minute_hex[7: 4] <= minute / 10;
minute_hex[3: 0] <= minute % 10;
end
// hour_hex
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
hour_hex <= 0;
else begin
hour_hex[7: 4] <= hour / 10;
hour_hex[3: 0] <= hour % 10;
end
// hex8 show
wire [31: 0] Data_IN;
wire [7: 0] Sel;
wire [7:0] Seg;
wire Ch_Flag;
assign Data_IN = {hour_hex, 4'hf, minute_hex, 4'hf, second_hex};
hex8 hex8_ins(
.Clk(Clk),
.Reset_n(Reset_n),
.Data_IN(Data_IN),
.Sel(Sel), // 位选
.Seg(Seg), // 段选
.Ch_Flag(Ch_Flag)
);
HC595_Driver HC595_Driver_ins(
.Clk(Clk),
.Reset_n(Reset_n),
.Data({Seg, Sel}),
.En(Ch_Flag),
.SHCP(SHCP), // SHCP 移位时钟
.DS(DS), // 数据线,SHCP下降沿时改变数据
.STCP(STCP) // STCP 停止时钟
);
// 串口发送当前时间
reg [7: 0] Data;
reg send_go;
wire [7: 0] second_str_h;
wire [7: 0] second_str_l;
wire [7: 0] minute_str_h;
wire [7: 0] minute_str_l;
wire [7: 0] hour_str_h;
wire [7: 0] hour_str_l;
wire [7: 0] div_sig;
wire [7: 0] end_sig;
wire [8*9-1: 0] Data_Send;
wire trans_done;
assign second_str_l = second % 10 + "0";
assign second_str_h = second / 10 + "0";
assign minute_str_l = minute % 10 + "0";
assign minute_str_h = minute / 10 + "0";
assign hour_str_l = hour % 10 + "0";
assign hour_str_h = hour / 10 + "0";
assign div_sig = ":"; // 分隔符
assign end_sig = "\n"; // 结束符
assign Data_Send = {end_sig, second_str_l, second_str_h, div_sig, minute_str_l, minute_str_h, div_sig, hour_str_l, hour_str_h};
// 串口发送
uart_tx_data uart_tx_data_ins(
.Clk(Clk),
.Reset_n(Reset_n),
.send_go(send_go),
.Data8(Data_Send),
.uart_tx(uart_tx),
.trans_done(trans_done)
);
// send_go
reg uart_tx_state;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
send_go <= 0;
else if(s_cnt == MCNT - 1)begin
if(uart_tx_state == 0)
send_go <= 1;
else
send_go <= 0;
end
else
send_go <= 0;
// uart_tx_state
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
uart_tx_state <= 0;
else if(s_cnt == MCNT - 1)begin
if(uart_tx_state == 0)
uart_tx_state <= 1;
end
else if(trans_done) // 数据全部发送完成 进入空闲态
uart_tx_state <= 0;
// 串口接收并授时
wire [7: 0] Rx_Data_Reg;
wire rx_done;
wire rx_state;
uart_rx_byte uart_rx_byte_ins(
.Clk(Clk),
.Reset_n(Reset_n),
.uart_rx(uart_rx),
.baud_set(0),
.Data(Rx_Data_Reg),
.rx_done(rx_done),
.rx_state(rx_state)
);
// rx_flag 状态机
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
rx_flag <= 0;
else if(rx_flag == 9)
rx_flag <= 0;
else if(rx_done)begin
case(rx_flag)
0:begin
if(Rx_Data_Reg == "@")
rx_flag <= rx_flag + 1;
end
1: rx_flag <= rx_flag + 1;
2: rx_flag <= rx_flag + 1;
3: begin
if(Rx_Data_Reg == ":")
rx_flag <= rx_flag + 1;
else rx_flag <= 0;
end
4: rx_flag <= rx_flag + 1;
5: rx_flag <= rx_flag + 1;
6:begin
if(Rx_Data_Reg == ":")
rx_flag <= rx_flag + 1;
else rx_flag <= 0;
end
7: rx_flag <= rx_flag + 1;
8: rx_flag <= rx_flag + 1;
default:
rx_flag <= 0;
endcase
end
// rx_hour
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
rx_hour <= 0;
else if(rx_flag == 0)
rx_hour <= 0;
else if(rx_flag == 1)begin
if(rx_done)
rx_hour <= rx_hour + (Rx_Data_Reg - "0") * 10;
end
else if(rx_flag == 2)begin
if(rx_done)
rx_hour <= rx_hour + (Rx_Data_Reg - "0");
end
// rx_minute
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
rx_minute <= 0;
else if(rx_flag == 0)
rx_minute <= 0;
else if(rx_flag == 4)begin
if(rx_done)
rx_minute <= rx_minute + (Rx_Data_Reg - "0") * 10;
end
else if(rx_flag == 5)begin
if(rx_done)
rx_minute <= rx_minute + (Rx_Data_Reg - "0");
end
// rx_second
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
rx_second <= 0;
else if(rx_flag == 0)
rx_second <= 0;
else if(rx_flag == 7)begin
if(rx_done)
rx_second <= rx_second + (Rx_Data_Reg - "0") * 10;
end
else if(rx_flag == 8)begin
if(rx_done)
rx_second <= rx_second + (Rx_Data_Reg - "0");
end
endmodule
digital_clock_tb.v
要看子模块的数据波形,自行Add to Wave
matlab
`timescale 1ns / 1ns
module digital_clock_tb();
reg Clk;
reg Reset_n;
wire uart_tx;
reg uart_rx;
wire SHCP;
wire DS;
wire STCP;
digital_clock digital_clock_ins(
.Clk(Clk),
.Reset_n(Reset_n),
.uart_tx(uart_tx),
.uart_rx(uart_rx),
.SHCP(SHCP),
.DS(DS),
.STCP(STCP)
);
initial Clk = 1;
always #10 Clk = !Clk;
initial begin
Reset_n = 0;
#201;
Reset_n = 1;
#20;
uart_tx_byte_sim("@");
@(posedge digital_clock_ins.rx_done);
uart_tx_byte_sim("2");
@(posedge digital_clock_ins.rx_done);
uart_tx_byte_sim("3");
@(posedge digital_clock_ins.rx_done);
uart_tx_byte_sim(":");
@(posedge digital_clock_ins.rx_done);
uart_tx_byte_sim("5");
@(posedge digital_clock_ins.rx_done);
uart_tx_byte_sim("9");
@(posedge digital_clock_ins.rx_done);
uart_tx_byte_sim(":");
@(posedge digital_clock_ins.rx_done);
uart_tx_byte_sim("5");
@(posedge digital_clock_ins.rx_done);
uart_tx_byte_sim("5");
@(posedge digital_clock_ins.rx_done);
#1000000000;
#1000000000;
$stop;
end
task uart_tx_byte_sim;
input [7: 0] tx_data;
begin
uart_rx = 1;
#20;
uart_rx = 0;
#52083;
#52083;
uart_rx = tx_data[0];
#52083;
#52083;
uart_rx = tx_data[1];
#52083;
#52083;
uart_rx = tx_data[2];
#52083;
#52083;
uart_rx = tx_data[3];
#52083;
#52083;
uart_rx = tx_data[4];
#52083;
#52083;
uart_rx = tx_data[5];
#52083;
#52083;
uart_rx = tx_data[6];
#52083;
#52083;
uart_rx = tx_data[7];
#52083;
#52083;
uart_rx =1;
end
endtask
endmodule
实验现象
FPGA-数字时钟
开发板型号
小梅哥FPGA ACX720
芯片型号: XC7A35TFGG484ABX2l