【电子DIY作品】基于fpga的GPS授时日历时钟

[复制链接]
查看97 | 回复9 | 2024-9-14 16:18:39 | 显示全部楼层 |阅读模式

本帖最后由 一只呆头鹅 于 2024-9-14 16:45 编辑

本帖最后由 一只呆头鹅 于 2024-9-14 16:40 编辑

本帖最后由 一只呆头鹅 于 2024-9-14 16:38 编辑

本帖最后由 一只呆头鹅 于 2024-9-14 16:35 编辑

实验目的

使用安信可科技的GP-01模块实现卫星授时,显示在数码管上。

程序设计思路

GP01模块有24个接口,我们写代码的时候只需要关注TX引脚,GP-01模块获得的数据送到FPGA,使用状态机获取我们想要的年月日、实时时间。此时得到的时间是UTC时间,并不是我们想要的北京时间,我们只需要在小时上加八,然后就把数据传到数码管显示就可以了。

代码

串口接收模块

把GP01的单比特数据转换成八比特的数据。

module  uart_rx
#(
    parameter   UART_BPS    =   'd9600,         //串口波特率
    parameter   CLK_FREQ    =   'd50_000_000    //时钟频率
)
(
    input   wire            sys_clk     ,   //系统时钟50MHz
    input   wire            sys_rst_n   ,   //全局复位
    input   wire            rx          ,   //串口接收数据

    output  reg     [7:0]   po_data     ,   //串转并后的8bit数据
    output  reg             po_flag         //串转并后的数据有效标志信号
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//localparam    define
localparam  BAUD_CNT_MAX    =   CLK_FREQ/UART_BPS   ;

//reg   define
reg         rx_reg1     ;
reg         rx_reg2     ;
reg         rx_reg3     ;
reg         start_nedge ;
reg         work_en     ;
reg [12:0]  baud_cnt    ;
reg         bit_flag    ;
reg [3:0]   bit_cnt     ;
reg [7:0]   rx_data     ;
reg         rx_flag     ;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//插入两级寄存器进行数据同步,用来消除亚稳态
//rx_reg1:第一级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg1 <= 1'b1;
    else
        rx_reg1 <= rx;

//rx_reg2:第二级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg2 <= 1'b1;
    else
        rx_reg2 <= rx_reg1;

//rx_reg3:第三级寄存器和第二级寄存器共同构成下降沿检测
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg3 <= 1'b1;
    else
        rx_reg3 <= rx_reg2;

//start_nedge:检测到下降沿时start_nedge产生一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        start_nedge <= 1'b0;
    else    if((~rx_reg2) && (rx_reg3))
        start_nedge <= 1'b1;
    else
        start_nedge <= 1'b0;

//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        work_en <= 1'b0;
    else    if(start_nedge == 1'b1)
        work_en <= 1'b1;
    else    if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
        work_en <= 1'b0;

//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        baud_cnt <= 13'b0;
    else    if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
        baud_cnt <= 13'b0;
    else    if(work_en == 1'b1)
        baud_cnt <= baud_cnt + 1'b1;

//bit_flag:当baud_cnt计数器计数到中间数时采样的数据最稳定,
//此时拉高一个标志信号表示数据可以被取走
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_flag <= 1'b0;
    else    if(baud_cnt == BAUD_CNT_MAX/2 - 1)
        bit_flag <= 1'b1;
    else
        bit_flag <= 1'b0;

//bit_cnt:有效数据个数计数器,当8个有效数据(不含起始位和停止位)
//都接收完成后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_cnt <= 4'b0;
    else    if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
        bit_cnt <= 4'b0;
     else    if(bit_flag ==1'b1)
         bit_cnt <= bit_cnt + 1'b1;

//rx_data:输入数据进行移位
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_data <= 8'b0;
    else    if((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(bit_flag == 1'b1))
        rx_data <= {rx_reg3, rx_data[7:1]};

//rx_flag:输入数据移位完成时rx_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_flag <= 1'b0;
    else    if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
        rx_flag <= 1'b1;
    else
        rx_flag <= 1'b0;

//po_data:输出完整的8位有效数据
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_data <= 8'b0;
    else    if(rx_flag == 1'b1)
        po_data <= rx_data;

//po_flag:输出数据有效标志(比rx_flag延后一个时钟周期,为了和po_data同步)
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_flag <= 1'b0;
    else
        po_flag <= rx_flag;

endmodule

获取日历、时间模块

因为GP01模块获取的数据不光只有日历和时间数据,所以我们需要用一个状态机来判断接收到的数据是不是日历、时间数据。 EMEA协议中的时间与日历信息格式如图所示:

f0655269291e7e719bc26eb61b1832db.png

module Data_processing (
    input   wire            sys_clk       ,
    input   wire            sys_rst_n     , 
    input   wire            uart_recv_done,
    input   wire  [7:0]     uart_recv_data,

    output  reg   [31:0]    time_data     ,
    output  reg   [31:0]    calendar_data ,
    output  reg             led3          ,
    output  reg             led2          ,
    output  reg             led  

);
    reg     [8:0]       state         ;
    reg     [31:0]      time_data_temp;
    reg     [31:0]      calendar_temp ;
    wire    [7:0]       recv_time     ;
    wire    [7:0]       recv_calendar ;

    parameter      WATI = 9'b0000_0000_0;
    parameter      IDLE = 9'b0000_0000_1;
    parameter      ONE  = 9'b0000_0001_0;
    parameter      TWO  = 9'b0000_0010_0;
    parameter      THREE= 9'b0000_0100_0;
    parameter      FOUR = 9'b0000_1000_0;
    parameter      FIVE = 9'b0001_0000_0;
    parameter      SIX  = 9'b0010_0000_0;
    parameter      SIX_1  = 9'b0010_0000_1;
    parameter      SIX_2  = 9'b0010_0001_0;
    parameter      SIX_3  = 9'b0010_0001_1;
    parameter      SIX_4  = 9'b0010_0010_0;
    parameter      SIX_5  = 9'b0010_0010_1;
    parameter      SIX_6  = 9'b0010_0011_0;
    parameter      SIX_7  = 9'b0010_0011_1;
    parameter      SIX_8  = 9'b0010_0100_0;
    parameter      SIX_9  = 9'b0010_0100_1;
    parameter      SEVEN  = 9'b0100_0000_0;
    parameter      SEVEN_1  = 9'b0100_0000_1;
    parameter      SEVEN_2  = 9'b0100_0001_0;
    parameter      SEVEN_3  = 9'b0100_0001_1;
    parameter      SEVEN_4  = 9'b0100_0010_0;
    parameter      SEVEN_5  = 9'b0100_0010_1;
    parameter      SEVEN_6  = 9'b0100_0011_0;
    parameter      SEVEN_7  = 9'b0100_0011_1;
    parameter      SEVEN_8  = 9'b0100_0100_0;
    parameter      SEVEN_9  = 9'b0100_0100_1;
    parameter      SEVEN_10 = 9'b0100_1001_0;
    parameter      EIGHT= 9'b1000_0000_0    ;
    parameter      TEN  = 9'b1000_0000_1    ;
    parameter      CNT_MAX = 23'd7_500_000  ;

    assign recv_time     = uart_recv_data - 8'd48;              //ascll码 0-9
    assign recv_calendar = uart_recv_data - 8'd48;


    always @(posedge sys_clk or negedge sys_rst_n) begin        //这里的状态机用来接收日历和时间
        if (sys_rst_n == 1'b0) begin
            state <= WATI;
        end
        else  if( uart_recv_done == 1'd1) begin
            case (state)
                WATI    :  begin
                            if ((uart_recv_data   == 8'h24))   state <= IDLE ;   else if ((uart_recv_data == 8'h47) ) state <= ONE; else state <= WATI;         //固定格式 $GPZDA,
                            end
                IDLE    :  state <= ((uart_recv_data == 8'h47)) ? ONE    :  WATI;
                ONE     :  state <= ((uart_recv_data == 8'h4E)) ? TWO    :  WATI;   
                TWO     :  state <= ((uart_recv_data == 8'h5A)) ? THREE  :  WATI;   
                THREE   :  state <= ((uart_recv_data == 8'h44)) ? FOUR   :  WATI;   
                FOUR    :  state <= ((uart_recv_data == 8'h41)) ? FIVE   :  WATI;   
                FIVE    :  state <= ((uart_recv_data == 8'h2C)) ? SIX    :  WATI;  

                SIX     :  state <= ((uart_recv_data != 8'h2C)) ? SIX_1  :  WATI;                      //时间                                                      //
                SIX_1   :  state <= ((uart_recv_data != 8'h2C)) ? SIX_2  :  WATI;
                SIX_2   :  state <= ((uart_recv_data != 8'h2C)) ? SIX_3  :  WATI;
                SIX_3   :  state <= ((uart_recv_data != 8'h2C)) ? SIX_4  :  WATI;
                SIX_4   :  state <= ((uart_recv_data != 8'h2C)) ? SIX_5  :  WATI;
                SIX_5   :  state <= ((uart_recv_data != 8'h2C)) ? SIX_6  :  WATI;

                SIX_6   :  state <= ((uart_recv_data == 8'h2E)) ? SIX_7  :  WATI;                       //.  
                SIX_7   :  state <= ((uart_recv_data == 8'h30)) ? SIX_8  :  WATI;                       //000
                SIX_8   :  state <= ((uart_recv_data == 8'h30)) ? SIX_9  :  WATI;
                SIX_9   :  state <= ((uart_recv_data == 8'h30)) ? SEVEN  :  WATI;
                SEVEN     :  state <= ((uart_recv_data == 8'h2C) ) ? SEVEN_1  :  WATI;                  //,
                SEVEN_1   :  state <= ((uart_recv_data != 8'h2C) ) ? SEVEN_2  :  WATI;                  //日
                SEVEN_2   :  state <= ((uart_recv_data != 8'h2C) ) ? SEVEN_3  :  WATI;
                SEVEN_3   :  state <= ((uart_recv_data == 8'h2C) ) ? SEVEN_4  :  WATI;
                SEVEN_4   :  state <= ((uart_recv_data != 8'h2C) ) ? SEVEN_5  :  WATI;                  //月
                SEVEN_5   :  state <= ((uart_recv_data != 8'h2C) ) ? SEVEN_6  :  WATI;
                SEVEN_6   :  state <= ((uart_recv_data == 8'h2C) ) ? SEVEN_7  :  WATI;
                SEVEN_7   :  state <= ((uart_recv_data != 8'h2C) ) ? SEVEN_8  :  WATI;                  //年
                SEVEN_8   :  state <= ((uart_recv_data != 8'h2C) ) ? SEVEN_9  :  WATI;
                SEVEN_9   :  state <= ((uart_recv_data != 8'h2C) ) ? SEVEN_10 :  WATI;
                SEVEN_10  :  state <= ((uart_recv_data != 8'h2C) ) ? EIGHT    :  WATI;
                EIGHT     :  state <= ((uart_recv_data == 8'h2C) ) ? TEN      :  WATI;                  
                TEN       :  state <= ((uart_recv_data == 8'h30) ) ? WATI     :  WATI;
            default: state <= WATI; 
        endcase
        end
    end 

    always @(posedge sys_clk or negedge sys_rst_n) begin                        
        if (sys_rst_n == 1'b0) begin
            led <= 1'b0;
        end
        else    if((state == FIVE) && (uart_recv_done == 1'b1))
            led <= ~led;
        else
            led <= led;
    end

    always @(posedge sys_clk or negedge sys_rst_n) begin                       
        if (sys_rst_n == 1'b0) begin
            led2 <= 1'b0;
        end
        else    if((state == SEVEN) && (uart_recv_done == 1'b1))
            led2 <= ~led2;
        else
            led2 <= led2;
    end

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if (sys_rst_n == 1'b0) begin
            led3 <= 1'b0;
        end
        else    if((state == EIGHT) && (uart_recv_done == 1'b1))
            led3 <= ~led3;
        else
            led3 <= led3;
    end

    always @(posedge sys_clk or negedge sys_rst_n) begin                                    //
        if (sys_rst_n == 1'b0) 
            time_data_temp <= 32'b0;
        else    if(state == SIX )   
            time_data_temp <= {recv_time[3:0],time_data_temp[27:0]};                    
        else    if(state == SIX_1 )
            time_data_temp <= {time_data_temp[31:28],recv_time[3:0],time_data_temp[23:0]};
        else    if(state == SIX_2 )
            time_data_temp <= {time_data_temp[31:20],recv_time[3:0],time_data_temp[15:0]};
        else    if(state == SIX_3 )
            time_data_temp <= {time_data_temp[31:16],recv_time[3:0],time_data_temp[11:0]};
        else    if(state == SIX_4 )
            time_data_temp <= {time_data_temp[31:8],recv_time[3:0],time_data_temp[3:0]};
        else    if(state == SIX_5 )
            time_data_temp <= {time_data_temp[31:4],recv_time[3:0]};
        else
            time_data_temp <= {time_data_temp[31:24],4'd10,time_data_temp[19:12],4'd10,time_data_temp[7:0]};
    end

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if (sys_rst_n == 1'b0) 
            calendar_temp   <= 32'd0;
        else    if(state == SEVEN_1)
            calendar_temp <= {calendar_temp[31:8],recv_calendar[3:0],calendar_temp[3:0]};
        else    if(state == SEVEN_2)
            calendar_temp <= {calendar_temp[31:4],recv_calendar[3:0]};
        else    if(state == SEVEN_4)
            calendar_temp <= {calendar_temp[31:16],recv_calendar[3:0],calendar_temp[11:0]};
        else    if(state == SEVEN_5)
            calendar_temp <= {calendar_temp[31:12],recv_calendar[3:0],calendar_temp[7:0]};
        else    if(state == SEVEN_7)
            calendar_temp <= {recv_calendar[3:0],calendar_temp[27:0]};
        else    if(state == SEVEN_8)
            calendar_temp <= {calendar_temp[31:28],recv_calendar[3:0],calendar_temp[23:0]};
        else    if(state == SEVEN_9)
            calendar_temp <= {calendar_temp[31:24],recv_calendar[3:0],calendar_temp[19:0]};
        else    if(state == SEVEN_10)
            calendar_temp <= {calendar_temp[31:20],recv_calendar[3:0],calendar_temp[15:0]};
    end

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if (sys_rst_n == 1'b0) begin
            calendar_data <= 32'b0;
        end
        else    if((state == TEN) && (uart_recv_done == 1'b1) )
            calendar_data <= calendar_temp;
        else
            calendar_data <= calendar_data;
    end

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if (sys_rst_n == 1'b0) begin
            time_data <= 32'b0;
        end
        else    if((state == SEVEN) && (uart_recv_done == 1'b1) )
            time_data <= time_data_temp;
        else
            time_data <= time_data;
    end

endmodule //Data_processing

UTC处理模块

这里是将UTC时间转换成北京时间,日历与时间每隔五秒切换一次。

module display (
    input   wire            sys_clk       ,
    input   wire            sys_rst_n     , 
    input   wire    [31:0]  time_data     ,
    input   wire    [31:0]  calendar_data ,

    output  wire    [31:0]  display_data  
);
    localparam          COUNT_MAX = 50_000_000   ;
    localparam          CNT_10S_MAX  = 6'd5      ;

    reg         [26:0]      count                ; 
    reg         [3:0]       cnt_500ms            ;
    reg                     display_flag         ;
    reg         [31:0]      time_data_temp       ;

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if (sys_rst_n == 1'b0) begin
            count <= 0;
        end else begin
            if (count == COUNT_MAX - 1) begin
                count <= 0;
            end 
            else begin
                count <= count + 27'd1;
            end
        end
    end

    always @(posedge sys_clk or negedge sys_rst_n) begin                            
        if (sys_rst_n == 1'b0) begin
            time_data_temp <= 32'd0;
        end                                                                                                     
        else    if (display_flag == 1'd1 && ((time_data[27:24] + 4'd8) >= 4'd10 ))begin         //因为获取的时间是UTC时间转换成北京时间加上八就可以了
            time_data_temp[31:28] <= time_data[31:28] + 4'd1;
            time_data_temp[27:24] <= (time_data[27:24] + 4'd8) % 4'd10;
        end
        else    if (display_flag == 1'd1 && time_data[27:24] + 4'd8 < 4'd10 )begin
            time_data_temp[31:28] <= time_data[31:28];
            time_data_temp[27:24] <= time_data[27:24]+ 4'd8;
        end
        else   
            time_data_temp <= {time_data_temp[31:24],time_data[23:0]};
    end

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if (sys_rst_n == 1'b0) begin
            cnt_500ms <= 4'd0;
        end
        else    if (cnt_500ms == CNT_10S_MAX)       //5S
            cnt_500ms <= 4'd0;
        else    if  (count == COUNT_MAX - 1)
            cnt_500ms <= cnt_500ms + 4'd1;
    end

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if (sys_rst_n == 1'b0) begin
            display_flag  <= 1'd0;
        end
        else    if (cnt_500ms == CNT_10S_MAX) begin
            display_flag  <= ~display_flag;
        end
        else
            display_flag  <= display_flag;
    end

    assign  display_data = display_flag ? calendar_data :time_data_temp;
endmodule //display

数码管显示模块

module smg (
    input   wire            sys_clk     ,
    input   wire            sys_rst_n   ,
    input   wire  [31:0]    smg_seg_in  ,

    output  reg   [7:0]     smg_sel     ,
    output  reg   [7:0]     smg_seg
);
    localparam CNT_1MS_MAX  = 16'd50_000 ;

    reg     [15:0]  cnt_1ms     ;
    reg     [3:0]   flag_1ms    ;
    reg     [3:0]   data_disp   ;  
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n==1'b0)
            cnt_1ms<=16'b0;
        else    if (cnt_1ms==CNT_1MS_MAX-1)
            cnt_1ms<=16'b0;
        else  
            cnt_1ms<=cnt_1ms+16'b1;
    end

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n==1'b0)
            flag_1ms<=4'b0;
        else    if  ((cnt_1ms==CNT_1MS_MAX-1)&&(flag_1ms==7))
            flag_1ms<=4'b0;
        else    if  (cnt_1ms==CNT_1MS_MAX-1)
            flag_1ms<=flag_1ms+4'b1;
        else  
            flag_1ms<=flag_1ms;
    end

    always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data_disp    <=  4'b0;
    else    case(flag_1ms)
        4'd0:   data_disp    <=  smg_seg_in[3:0]  ;  
        4'd1:   data_disp    <=  smg_seg_in[7:4]  ;  
        4'd2:   data_disp    <=  smg_seg_in[11:8] ;  
        4'd3:   data_disp    <=  smg_seg_in[15:12];  
        4'd4:   data_disp    <=  smg_seg_in[19:16];  
        4'd5:   data_disp    <=  smg_seg_in[23:20];  
        4'd6:   data_disp    <=  smg_seg_in[27:24];  
        4'd7:   data_disp    <=  smg_seg_in[31:28];  
        default:data_disp    <=  4'b0        ;
        endcase

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n ==1'b0)
            smg_sel<=8'b1111_1110;
        else    case (flag_1ms)
            0: smg_sel<=~8'b1111_1110;
            1: smg_sel<=~8'b1111_1101;
            2: smg_sel<=~8'b1111_1011;
            3: smg_sel<=~8'b1111_0111;
            4: smg_sel<=~8'b1110_1111;
            5: smg_sel<=~8'b1101_1111;
            6: smg_sel<=~8'b1011_1111;
            7: smg_sel<=~8'b0111_1111;
            default:smg_sel<=8'b1111_1110;
        endcase
    end

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n ==1'b0)
            smg_seg<=8'b1111_1100;
        else    case (data_disp)
            1: smg_seg<=~8'b0110_0000;
            2: smg_seg<=~8'b1101_1010;
            3: smg_seg<=~8'b1111_0010;
            4: smg_seg<=~8'b0110_0110;
            5: smg_seg<=~8'b1011_0110;
            6: smg_seg<=~8'b1011_1110;
            7: smg_seg<=~8'b1110_0000;
            8: smg_seg<=~8'b1111_1110;
            9: smg_seg<=~8'b1111_0110;
            0: smg_seg<=~8'b1111_1100;
            10:smg_seg<=~8'b0000000001;
            default:smg_seg<=8'b1111_1100;
        endcase 
    end

endmodule //smg

实物演示

1.视频演示

FPGA实现卫星授时_哔哩哔哩_bilibili

回复

使用道具 举报

一只呆头鹅 | 2024-9-14 16:31:14 | 显示全部楼层
工程源码

GP01.zip

2.72 MB, 下载次数: 2

回复

使用道具 举报

bzhou830 | 2024-9-14 17:31:44 | 显示全部楼层
厉害,大佬教教我怎么玩FPGA
选择去发光,而不是被照亮
回复 支持 反对

使用道具 举报

WangChong | 2024-9-14 18:11:37 | 显示全部楼层
鹅哥 主控要用M61
回复 支持 反对

使用道具 举报

iiv | 2024-9-14 20:21:57 | 显示全部楼层
我要玩FPGA,我要玩FPGA!
回复 支持 反对

使用道具 举报

qhsj | 2024-9-14 20:38:09 | 显示全部楼层
大佬!
回复

使用道具 举报

粉肠 | 2024-9-15 10:43:39 | 显示全部楼层
我要玩FPGA!我要玩FPGA!
回复 支持 反对

使用道具 举报

七九 | 2024-9-15 12:04:22 | 显示全部楼层
大佬诶
回复

使用道具 举报

WildboarG | 2024-9-15 13:01:18 | 显示全部楼层
我要玩FPGA!我要玩FPGA
回复 支持 反对

使用道具 举报

爱笑 | 2024-9-18 10:40:58 | 显示全部楼层
鹅哥开始发力了
用心做好保姆工作
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则