FPGA-数字时钟

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根线来输出SEGSEL的十六位数据。

​ 前面提到使用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串转并模块。它由两根时钟线和一根数据线作为输入。首先,将第一个模块生产的SELSEG值,封装成{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

顶层模块设计

​ 顶层模块中,对时分秒进行运算,例化hex8HC595_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 minutesecond执行赋值操作。

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

相关推荐
nanxl114 分钟前
FPGA-DDS信号发生器
fpga开发·verilog·vivado
黄埔数据分析2 小时前
RecoNIC 入门:SmartNIC 上支持 RDMA 的计算卸载-FPGA-智能网卡-AMD-Xilinx
fpga开发
尤老师FPGA18 小时前
LVDS系列9:Xilinx 7系可编程输入延迟(二)
单片机·嵌入式硬件·fpga开发
内有小猪卖21 小时前
时序约束 记录
fpga开发
Cao1234567893211 天前
FPGA时钟设计
fpga开发
JNTeresa1 天前
锁存器知识点详解
fpga开发
Cao1234567893211 天前
FPGA基础之基础语法
fpga开发
一大Cpp1 天前
通过Quartus II实现Nios II编程
fpga开发
7yewh1 天前
Verilog 语法 (二)
fpga开发