原则:1s的B码无论错误正确不能影响下1s的B码解析和1PPS输出。
解决办法:每次锁定2个P标志位,无论B码中间正确与否,都会在下一个2个P标志位的地方输出一个对应第2个P标志位的高电平与之相与。如果锁定2个P标志位之后,下1s没有B码,那么只输出高电平,由于没有B码,那么就不会输出1PPS信号。
原则适用范围:1PPS,和B码的全部解析。
需要修改的地方:前面的B码的utc解析没有遵守这个原则,所以需要修改。
文章解决问题:1PPS产生。
先放上解析的1PPS的波形:

左边:B码存在时,示波器处于TD状态,能一直锁定到1PPS信号的波形;
右边:B码不存在时,示波器处于WAIT状态,不输出1PPS信号。
再次将B码恢复,再次捕捉到输出的1PPS信号。所以程序是没什么问题。
- //脉宽的上升沿和下降沿检测
- reg bpluse_en_d0;
- reg bpluse_en_d1;
- wire bpluse_falling_flag;
- wire bpluse_rasing_flag;
- assign bpluse_falling_flag = (~bpluse_en_d0) & bpluse_en_d1;
- assign bpluse_rasing_flag = (~bpluse_en_d1) & bpluse_en_d0;
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- bpluse_en_d0 <= 1'b0;
- bpluse_en_d1 <= 1'b0;
- end
- else begin
- bpluse_en_d0 <= bcodein;
- bpluse_en_d1 <= bpluse_en_d0;
- end
- end
- //置位time是否开启
- (*noprune*)reg timebeginflag;
- always@(posedge clk or negedge rst_n)
- begin
- if (rst_n == 1'b0)
- timebeginflag <= 1'd0;
- else if (bpluse_rasing_flag == 1'b1)
- timebeginflag <= 1'd1;
- else if (bpluse_falling_flag == 1'b1)
- timebeginflag <= 1'd0;
- end
- //上升沿之后计数器工作,下降沿之后计数器停止
- (*noprune*)reg [31:0] timer;
- always@(posedge clk or negedge rst_n)
- begin
- if (rst_n == 1'b0)
- timer <= 32'd0;
- else if (timebeginflag == 1'b1)
- timer <= timer + 32'd1;
- else if (timebeginflag == 1'b0)
- timer <= 0;
- end
- (*noprune*)reg [3:0] bcodelevel; /*synthesis noprune*/
- always@(posedge clk or negedge rst_n)
- begin
- if (rst_n == 1'b0)begin
- bcodelevel <= 0;
- end
- else if (bpluse_falling_flag == 1'b1)begin
- if((timer >= 32'd350000)&&(timer <= 32'd450000))begin
- bcodelevel <= 4'd3; //P电平
- end
- else if(timer >= 32'd200000)begin
- bcodelevel <= 4'd2; //1电平
- end
- else if(timer >= 32'd50000 )begin
- bcodelevel <= 4'd1; //0电平
- end
- else begin
- bcodelevel <= 0; //0电平
- end
- end
- else begin
- bcodelevel <= bcodelevel; //0电平
- end
- end
此处修改:严格了判断区间,防止误判;其他时间对应电平为0;
此处注意:在B码下降沿时立刻判断time的值,而不是标志位为0时判断,FPGA的工作原理,没办法。直接在timeflag处也试验了下,没什么问题,但是为了严谨,在这个地方判断了!
- reg resettime_en_d0;
- reg resettime_en_d1;
- wire resettime_falling_flag;
- wire resettime_rasing_flag;
- assign resettime_falling_flag = (~resettime_en_d0) & resettime_en_d1;
- assign resettime_rasing_flag = (~resettime_en_d1) & resettime_en_d0;
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- resettime_en_d0 <= 1'b0;
- resettime_en_d1 <= 1'b0;
- end
- else begin
- resettime_en_d0 <= timebeginflag;
- resettime_en_d1 <= resettime_en_d0;
-
- end
- end
此时time已经锁存,并且B码的电平已经锁定。然后下面再判断锁定值。
- (*noprune*)reg [3:0] bcodelevellatch1;
- (*noprune*)reg [3:0] bcodelevellatch2;
- always@(posedge clk or negedge rst_n)
- begin
- if (rst_n == 1'b0)begin
- bcodelevellatch1 <= 4'd0;
- bcodelevellatch2 <= 4'd0;
- end
- else if (resettime_falling_flag == 1'b1)begin
- bcodelevellatch1 <= bcodelevel;
- bcodelevellatch2 <= bcodelevellatch1;
- end
- else if (ppstimer == 32'd49050000)begin
- bcodelevellatch1 <= 4'd0;
- bcodelevellatch2 <= 4'd0;
- end
- end
此处49050000对应981ms,至于这个是为什么,程序完毕之后再解释。这个地方是将锁存的两个值复位。目的是不影响下一次的2个P标志位的采集。也就是将1PPS的产生效果控制在1s之内。
- reg resettime1_en_d0;
- reg resettime1_en_d1;
- wire resettime1_falling_flag;
- wire resettime1_rasing_flag;
- assign resettime1_falling_flag = (~resettime1_en_d0) & resettime1_en_d1;
- assign resettime1_rasing_flag = (~resettime1_en_d1) & resettime1_en_d0;
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- resettime1_en_d0 <= 1'b0;
- resettime1_en_d1 <= 1'b0;
- end
- else begin
- resettime1_en_d0 <= resettime_falling_flag;
- resettime1_en_d1 <= resettime1_en_d0;
- end
- end
- (*noprune*)reg ppsreadyflag;
- always@(posedge clk or negedge rst_n)
- begin
- if (rst_n == 1'b0)
- ppsreadyflag <= 1'd0;
- else if (resettime1_falling_flag == 1'b1)begin
- if((bcodelevellatch1 == 4'd3)&&(bcodelevellatch2 == 4'd3))
- ppsreadyflag <= 1'd1;
- end
- else if (ppstime_falling_flag == 1'b1)begin
- ppsreadyflag <= 1'd0;
- end
- end
此处:锁存值都为P标志的话,flag置1。等到产生1PPS之后的下降沿,flag置0。
下面的(9)中,flag等于1时,计数器加,flag等于0时,计数器复位。并且是在高电平产生之后计数器归0,保证每次信号的产生时间。
- (*noprune*)reg [31:0] ppstimer;
- always@(posedge clk or negedge rst_n)
- begin
- if (rst_n == 1'b0)
- ppstimer <= 32'd0;
- else if (ppsreadyflag == 1'b1)
- ppstimer <= ppstimer + 32'd1;
- else if (ppsreadyflag == 1'b0)
- ppstimer <= 32'd0;
- end
- always@(posedge clk or negedge rst_n)
- begin
- if (rst_n == 1'b0)
- ppshighlevel <= 1'd0;
- else if (ppstimer == 32'd49550000) // 991ms 置高,994ms置低
- ppshighlevel <= 1'b1;
- else if (ppstimer == 32'd49700000)
- ppshighlevel <= 1'b0;
- end
此处即为与B码与的电平信号,使得高电平刚好落到下1s的第二个P电平的上升沿。
- reg ppstime_en_d0;
- reg ppstime_en_d1;
- wire ppstime_falling_flag;
- wire ppstime_rasing_flag;
- assign ppstime_falling_flag = (~ppstime_en_d0) & ppstime_en_d1;
- assign ppstime_rasing_flag = (~ppstime_en_d1) & ppstime_en_d0;
- always @(posedge clk or negedge rst_n) begin
- if (!rst_n) begin
- ppstime_en_d0 <= 1'b0;
- ppstime_en_d1 <= 1'b0;
- end
- else begin
- ppstime_en_d0 <= ppshighlevel;
- ppstime_en_d1 <= ppstime_en_d0;
- end
- end
这个下降沿就是前面介绍的扫尾脉冲,可以延后一个周期,也可以像前面介绍的定时之后产生的下降沿。
编写到这里突然发现编写FPGA程序的原则。
原则1:每个模块只改变一个变量;
原则2:适时延迟一个周期;
原则3:变量闭环。
解释下:
原则1,这个是FPGA的规则,要不会报错。当然为了自己的程序好看并且以后能看懂自己编写的程序,一个模块只有一个变量。当然高手是将多个变量放到一个模块里,这个我就不奢求了。
原则2,这个为了规避FPGA的并行逻辑,单片机while(1)过来的,没有办法,思维方式就是顺序执行,但是由于解析B码这种,必须有顺序执行的逻辑,所以也是没办法的事情。这和嵌入式QT中有点像,虽然是面对对象,但是也是面向过程的。具体对象和过程定义,可以看看以前的文章,里面介绍的很详细。
原则3,这个是现在刚刚发现的原则。由于FPGA是并行逻辑,但是加入顺序执行之后,像B码这种时间顺序的东西,必须将每个量都在一定时间产生,并且在一定时间关闭,形成一个闭环,然后等待下一个循环。这样此刻不影响下一刻。这样应该比较好,出了任何问题都不会一直出现。
解析B码的基本程序已经告一段落,能正确解析出utc时间和1PPS,这个才是最基本的应用,对于不同状况下的B码解析还没有考虑,这是下一步考虑的事情。当然程序中存在的bug也存在,下面主要根据原则3修改utc时间的程序。
