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

相关推荐
oria20061 小时前
高云FPGA-新增输出管脚约束
fpga开发
每月一号准时摆烂3 小时前
数字电子技术基础(六十)——使用Digital软件绘制脉冲触发的触发器
fpga开发
可编程芯片开发15 小时前
基于FPGA的电子万年历系统开发,包含各模块testbench
fpga开发·fpga·电子万年历
爱学习的张哥18 小时前
UDP--DDR--SFP,FPGA实现之ddr读写控制模块
网络协议·fpga开发·udp
GateWorld1 天前
深入浅出IIC协议 - 从总线原理到FPGA实战开发 -- 第一篇:I2C总线协议深度解剖
fpga开发·开源协议
爱学习的张哥2 天前
UDP--DDR--SFP,FPGA实现之模块梳理及AXI读写DDR读写上板测试
单片机·fpga开发·udp·axi·ddr
白杨树田2 天前
【EDA软件】【联合Modelsim仿真使用方法】
fpga开发
搬砖的小码农_Sky2 天前
FPGA: XILINX Kintex 7系列器件的架构
fpga开发·架构·硬件架构
搬砖的小码农_Sky2 天前
FPGA:如何提高RTL编码能力?
fpga开发·硬件架构
晶台光耦2 天前
高速光耦在通信行业的应用(五) | 5Mbps通信光耦的特性
fpga开发