前言
前两个完结篇介绍了B码的结构,B码保护程序和B码的1PPS产生程序,下面介绍B码的UTC时间产生。当然B码中含有UTC时间和UTC时间的关键信息。程序的整体思路是差不多的,翻过来调过去也就是那点东西,就看怎么去一步一步的去解析里面的信息帧。解析过程和程序无关,和B码的结构有关。当然顺序执行的程序都是这个逻辑,只不过现在用顺序解析的办法去编写并行程序。
写在前面
从FPGA的双精度double数加和乘,整数转双精度double数,再到串口发送,再到SPI采集的主机和从机程序,再到现在的B码解析。感觉FPGA的编程模式已经差不多浮现出来,至于怎么精简程序和一个clk都不出现问题,这个倒没有太过在意。可能大神的程序就是一个clk都不出现错误吧。想着咱们用FPGA编程,一般的容错率还是挺强的,因为FPGA是一个周期一个周期的,而咱们需要的输出量是在很长一段时间内输出就行。所以只要最后结果能输出就好,管他几个clk呢。当然如果需要非常精确的场合,还是得一个clk一个clk的去看得到的值。
疑问:都说一个领域需要去沉淀,需要好好去做,小伙伴们说做到现在这个程度算是精通么?我始终都不太理解说的这个要有深度是需要深度到什么程度。你说做变频器,做到什么程序是精通呢?各个问题都能知道在哪,都知道怎么解决?现在编写STM32的程序,做到什么程度呢?里面的每个状态都能知道?每个寄存器都知道怎么用?如果只是这个程序,那每个都精通了。但是大神眼里的精通应该不是这个程度。所以这个概念非常模糊,也可能自己做的东西太杂,已经不知道精通是什么意思了,也挺无奈的!
第一章:输出介绍
先看UTC时间输出报文吧。
先说下报文格式:报头:5AA5,光电口标志:55,UTC时间:62 FD A9 BE,UTC其他关键信息:08 00,校验:22。
只看UTC时间和UTC关键信息,62 FD A9 BE转化为十进制为:1660791230,对应标准时间为:
通过转化可以看出转化的时间是正确的。
后面的0800,08代表东八区,也就是北京时间,之后的0代表UTC时间精确度,之后的0代表闰秒什么的,当然需要什么关键信息可以自己添加。不过UTC时间主要的关键信息也就这几个:时区,闰秒,精度,其他的解析出来也没太大作用,也可能是我自己不用吧。
关于闰秒问题:这个地方考虑了,但是很鸡肋。产品能用几年呢,等不到闰秒就淘汰了,还管啥闰秒啊,这个我就没有很好的解析!但是从严格的角度还是需要考虑的,看小伙伴的自己的需求。对于军工之类的,这个肯定得好好解析,对于其他的,还是算了,太麻烦!
第二章:流程介绍
前篇文章介绍了2个P标志位锁存,前面是一样的!
详细说下锁存之后做的事情。检测到两个连续P标志电平后,flag置1,并且将保存的cnt置成2。当flag等于1之后,下一次B码下降沿来的时候,cnt会++,也就是下一个B码单元存到第3个单元里面。所以设置100个寄存器,1和2是预留给2个P标志帧的,而真正的存放B码单元值是从3开始的。从3存到100,所以之后解析的时候也是按照这个顺序解析的,当然你也可以按照自己的方式去存储这些值,最后解析的时候对应就可以了。
放入寄存器的时候肯定是从3开始存放,当检测不到2个P帧的时候cnt是不++的,也不会存放到cnt对应的寄存器,这点非常重要。
每次检测到2个P标志之后才开始存储和计数,无论中间出现什么状况,总是从2个P帧标志开始,所以确保了程序1s内存放是正确的。
当flag置1之后,启动time计数器,对应时间到最后1个B码的时候,level置1,并且一定时间后置0。这个时间是固定的,level的下降沿falling时候,time计数器复位。这样计数器和flag就完成了闭环。
在下降沿之后,开始计算后面的数据。正好time计数器走到1s的最后1帧。开始和结尾都是可控的,所以故障不会出现在下1s。
此时如果丢失B码,根据上篇文章中的11ms下降沿,将flag复位,同时将time计数器复位。这样出现错误B码的时候也达到了闭环。
另一个就是cnt问题:如果B码都是正确的,开始是2,那么下次开始也肯定是2,都是从2个P帧标志开始的,相当于有一个复位。但是如果B码中间消失,那么下次开始时,就不一定是2了。所以cnt在B码出现丢失或者错误时也必须形成闭环。也就是11ms下降沿时,也需要cnt复位成2。这样cnt参数也形成了闭环。
当计数器的下降沿,也就是1sB码的最后一帧采集完毕后,开始对所有3到100的B码进行01转换,然后计算UTC时间,计算闰年天数,计算UTC秒数,计算校验,然后给串口发送。具体怎么计算下面程序会详细介绍。
以上就是UTC的解析过程,里面的各个参数都形成闭环,能把故障控制在1s之内。
第三章:程序介绍
下面详细介绍下程序的编写,这部分比较绕,有很多延迟下降沿,这部分也是没办法,顺序执行的程序,只能一个参数准备好,才能进行下一步计算。当然能不能一下子计算,我自己试验了下,也是没问题的,但是为了将来自己能读懂自己的程序,还是一步一步的去计算,并且能保证数据的准确性。
//B码接收
//接收到2个P标志位后,bcodevalidflag置1,根据时间,B码最后一位结束后置0
(*noprune*)reg bcodevalidflag;
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
bcodevalidflag <= 1'd0;
else if (resettime1_falling_flag == 1'b1)begin
if((bcodelevellatch1 == 2'd3)&&(bcodelevellatch2 == 2'd3))
bcodevalidflag <= 1'd1;
end
else if (bcodevalidtime_falling_flag == 1'b1)begin
bcodevalidflag <= 1'd0;
end
else if (code10ms_time_falling_flag == 1'b1)begin
bcodevalidflag <= 1'd0;
end
end
(*noprune*)reg [31:0] bcodevalidtimer;
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
bcodevalidtimer <= 32'd0;
else if (bcodevalidflag == 1'b1)
bcodevalidtimer <= bcodevalidtimer + 32'd1;
else if (bcodevalidflag == 1'b0)
bcodevalidtimer <= 32'd0;
else if (code10ms_time_falling_flag == 1'b1)
bcodevalidtimer <= 32'd0;
end
reg bcodevalidlevel;
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
bcodevalidlevel <= 1'd0;
else if (bcodevalidtimer == 32'd49025000) // 991ms 置高,994ms置低
bcodevalidlevel <= 1'b1;
else if (bcodevalidtimer == 32'd49075000)
bcodevalidlevel <= 1'b0;
end
reg bcodevalidtime_en_d0;
reg bcodevalidtime_en_d1;
wire bcodevalidtime_falling_flag;
wire bcodevalidtime_rasing_flag;
assign bcodevalidtime_falling_flag = (~bcodevalidtime_en_d0) & bcodevalidtime_en_d1;
assign bcodevalidtime_rasing_flag = (~bcodevalidtime_en_d1) & bcodevalidtime_en_d0;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
bcodevalidtime_en_d0 <= 1'b0;
bcodevalidtime_en_d1 <= 1'b0;
end
else begin
bcodevalidtime_en_d0 <= bcodevalidlevel;
bcodevalidtime_en_d1 <= bcodevalidtime_en_d0;
end
end
reg bcodebegin_en_d0;
reg bcodebegin_en_d1;
wire bcodebegin_falling_flag;
wire bcodebegin_rasing_flag;
assign bcodebegin_falling_flag = (~bcodebegin_en_d0) & bcodebegin_en_d1;
assign bcodebegin_rasing_flag = (~bcodebegin_en_d1) & bcodebegin_en_d0;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
bcodebegin_en_d0 <= 1'b0;
bcodebegin_en_d1 <= 1'b0;
end
else begin
bcodebegin_en_d0 <= bcodevalidlevel;
bcodebegin_en_d1 <= bcodebegin_en_d0;
end
end
2个P标志位之后,flag置1,加上B码错误判断。计数器到最后一帧停止复位。
//100个计数单元
(*noprune*)reg [6:0] bcodebitcnt; /*synthesis noprune*/
(*noprune*)reg bcodeflag;
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
bcodebitcnt <= 7'd0;
else if ((bpluse_falling_flag == 1)&&(bcodevalidflag == 1'b1))
bcodebitcnt <= bcodebitcnt + 1'b1;
else if (bcodebegin_rasing_flag == 1)
bcodebitcnt <= 7'd2;
else if (code10ms_time_falling_flag == 1'b1)begin
bcodebitcnt <= 7'd2;
end
else
bcodebitcnt <= bcodebitcnt;
end
cnt的单独拿出来介绍,flag等于1之后开始增长++,2个P标志后变成2,B码错误之后也置成2,形成cnt闭环。
(*noprune*)reg [1:0] b_data_rec1;
(*noprune*)reg [1:0] b_data_rec2;
(*noprune*)reg [1:0] b_data_rec3;
(*noprune*)reg [1:0] b_data_rec4;
......
(*noprune*)reg [1:0] b_data_rec100;
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)begin
b_data_rec1 <= 2'd0;
b_data_rec2 <= 2'd0;
b_data_rec3 <= 2'd0;
......
b_data_rec100 <= 2'd0;
end
else if (resettime2_falling_flag == 1'b1)begin
if(bcodebitcnt == 7'd3) b_data_rec3 <= bcodelevel;
if(bcodebitcnt == 7'd4) b_data_rec4 <= bcodelevel;
......
if(bcodebitcnt == 7'd100)b_data_rec100 <= bcodelevel;
end
end
根据cnt的值,将B码的值放入100个寄存器中间,中间部分省略了,需要的可以自己加上。当然这个下降沿在2个P标志后才会起作用。
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
TimeOtherStatus <= 16'd0;
else if (bcodebegin_falling_flag == 1'b1)begin
if(b_data_rec72 >= 2'd1)TimeOtherStatus[3] <= 1'b0;
if(b_data_rec72 >= 2'd2)TimeOtherStatus[3] <= 1'b1;
if(b_data_rec73 >= 2'd1)TimeOtherStatus[4] <= 1'b0;
if(b_data_rec73 >= 2'd2)TimeOtherStatus[4] <= 1'b1;
if(b_data_rec74 >= 2'd1)TimeOtherStatus[5] <= 1'b0;
if(b_data_rec74 >= 2'd2)TimeOtherStatus[5] <= 1'b1;
if(b_data_rec75 >= 2'd1)TimeOtherStatus[6] <= 1'b0;
if(b_data_rec75 >= 2'd2)TimeOtherStatus[6] <= 1'b1;
if(b_data_rec76 >= 2'd1)TimeOtherStatus[7] <= 1'b0;
if(b_data_rec76 >= 2'd2)TimeOtherStatus[7] <= 1'b1;
if(b_data_rec67 >= 2'd1)TimeOtherStatus[8] <= 1'b0;
if(b_data_rec67 >= 2'd2)TimeOtherStatus[8] <= 1'b1;
if(b_data_rec68 >= 2'd1)TimeOtherStatus[9] <= 1'b0;
if(b_data_rec68 >= 2'd2)TimeOtherStatus[9] <= 1'b1;
if(b_data_rec69 >= 2'd1)TimeOtherStatus[10] <= 1'b0;
if(b_data_rec69 >= 2'd2)TimeOtherStatus[10] <= 1'b1;
if(b_data_rec70 >= 2'd1)TimeOtherStatus[11] <= 1'b0;
if(b_data_rec70 >= 2'd2)TimeOtherStatus[11] <= 1'b1;
if(b_data_rec66 >= 2'd1)TimeOtherStatus[13] <= 1'b0;
if(b_data_rec66 >= 2'd2)TimeOtherStatus[13] <= 1'b1;
if(b_data_rec63 >= 2'd1)TimeOtherStatus[14] <= 1'b0;
if(b_data_rec63 >= 2'd2)TimeOtherStatus[14] <= 1'b1;
if(b_data_rec62 >= 2'd1)TimeOtherStatus[15] <= 1'b0;
if(b_data_rec62 >= 2'd2)TimeOtherStatus[15] <= 1'b1;
end
end
根据每个位的大小,判断各个标志位的0和1,变为可以输出的01信号。
(*noprune*)reg [6:0] utc_sec;
(*noprune*)reg [6:0] utc_min;
(*noprune*)reg [5:0] utc_hou;
(*noprune*)reg [9:0] utc_day;
(*noprune*)reg [7:0] utc_yea;
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)begin
utc_sec <= 7'd0;
utc_min <= 7'd0;
utc_hou <= 6'd0;
utc_day <= 10'd0;
utc_yea <= 8'd0;
end
else if (bcodebegin_falling_flag == 1'b1)begin
if(b_data_rec3 >= 2'd1)utc_sec[0] <= 1'b0;
if(b_data_rec3 >= 2'd2)utc_sec[0] <= 1'b1;
if(b_data_rec10 >= 2'd1)utc_sec[6] <= 1'b0;
if(b_data_rec10 >= 2'd2)utc_sec[6] <= 1'b1;
if(b_data_rec12 >= 2'd1)utc_min[0] <= 1'b0;
if(b_data_rec12 >= 2'd2)utc_min[0] <= 1'b1;
if(b_data_rec19 >= 2'd1)utc_min[6] <= 1'b0;
if(b_data_rec19 >= 2'd2)utc_min[6] <= 1'b1;
if(b_data_rec22 >= 2'd1)utc_hou[0] <= 1'b0;
if(b_data_rec22 >= 2'd2)utc_hou[0] <= 1'b1;
if(b_data_rec28 >= 2'd1)utc_hou[5] <= 1'b0;
if(b_data_rec28 >= 2'd2)utc_hou[5] <= 1'b1;
if(b_data_rec32 >= 2'd1)utc_day[0] <= 1'b0;
if(b_data_rec32 >= 2'd2)utc_day[0] <= 1'b1;
if(b_data_rec43 >= 2'd1)utc_day[9] <= 1'b0;
if(b_data_rec43 >= 2'd2)utc_day[9] <= 1'b1;
if(b_data_rec52 >= 2'd1)utc_yea[0] <= 1'b0;
if(b_data_rec52 >= 2'd2)utc_yea[0] <= 1'b1;
if(b_data_rec60 >= 2'd1)utc_yea[7] <= 1'b0;
if(b_data_rec60 >= 2'd2)utc_yea[7] <= 1'b1;
end
end
这部分根据B码的值,判断utc的各个位的01大小,这部分没有什么可以介绍的了。主要是需要主要B码和寄存器对应,对应对了就可以得出每个数据大小。
(*noprune*)reg [5:0] utc_SecondData;
(*noprune*)reg [5:0] utc_MinuteData;
(*noprune*)reg [4:0] utc_HourData;
(*noprune*)reg [8:0] utc_DayData;
(*noprune*)reg [5:0] utc_YearData;
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)begin
utc_SecondData <= 6'd0;
utc_MinuteData <= 6'd0;
utc_HourData <= 5'd0;
utc_DayData <= 9'd0;
utc_YearData <= 6'd0;
end
else if (resettime3_falling_flag == 1'b1)begin
utc_SecondData <= utc_sec[0] + 2*utc_sec[1] + 4*utc_sec[2] + 8*utc_sec[3]
+(utc_sec[4] + 2*utc_sec[5] + 4*utc_sec[6])*10;
utc_MinuteData <= utc_min[0] + 2*utc_min[1] + 4*utc_min[2] + 8*utc_min[3]
+(utc_min[4] + 2*utc_min[5] + 4*utc_min[6])*10;
utc_HourData <= utc_hou[0] + 2*utc_hou[1] + 4*utc_hou[2] + 8*utc_hou[3]
+(utc_hou[4] + 2*utc_hou[5])*10;
utc_DayData <= utc_day[0] + 2*utc_day[1] + 4*utc_day[2] + 8*utc_day[3]
+(utc_day[4] + 2*utc_day[5] + 4*utc_day[6] + 8*utc_day[7])*10
+(utc_day[8] + 2*utc_day[9])*100;
utc_YearData <= utc_yea[0] + 2*utc_yea[1] + 4*utc_yea[2] + 8*utc_yea[3]
+(utc_yea[4] + 2*utc_yea[5] + 4*utc_yea[6] + 8*utc_yea[7])*10;
end
end
根据每个位的大小计算具体的时间数据。这部分是B码的定义,怎么计算看看第一篇里面对应的数据就可以了。具体的自己也做了一张表。
具体的可以自己根据自己存放的寄存器去计算。
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
TimeStamp <= 32'd0;
else if (resettime5_falling_flag == 1'b1)begin
TimeStamp <= utc_LeapYear * 31622400
+ utc_Year * 31536000
+ utc_DayData * 86400
+ utc_HourData * 3600
+ utc_MinuteData * 60
+ utc_SecondData
- 115200;
end
end
然后计算utc时间。具体的也不会占用多少资源,这个计算应该挺方面,直接放进去就行。
为啥不直接传输各个量呢?计算出来utc的秒数,采用32位就可以传输了,单独每个传输的话,会需要很多,32位是绝对搞不定的,所以计算出来秒数是最好的。
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
TimeStampVerify <= 16'd0;
else if (resettime3_falling_flag == 1'b1)begin
TimeStampVerify <= 16'd0;
end
else if (resettime6_falling_flag == 1'b1)begin
TimeStampVerify <= TimeStampVerify
+ 8'h5A + 8'hA5 + 8'h55
+ TimeStamp[ 7: 0] + TimeStamp[15: 8]
+ TimeStamp[23:16] + TimeStamp[31:24]
+ TimeOtherStatus[ 7: 0] + TimeOtherStatus[15: 8];
end
end
然后进行校验,当然这里我采用和校验,你自己编写CRC校验也可以,主要是为了传输过程中担心传输错误。接收方只要去解析这个校验码就行了。当然里面也需要闭环,在前面的一个下降沿置0,后面计算。
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
uartsendpluse <= 1'd0;
else if (resettime6_falling_flag == 1'b1)begin
uartsendpluse <= 1'd1;
end
else if (resettime7_falling_flag == 1'b1)begin
uartsendpluse <= 1'd0;
end
end
然后是串口发送使能,当然串口这部分可以自己编写,网上很多这样的例子,将全部的数据放进串口的数据区里,就可以得到文章第一章的数据了。
第四章:总结
B码的解析程序和以前的串口发送、SPI接收与发送程序感觉步骤是一样的,只要知道自己什么时候干什么事,哪一个时刻需要做什么就行,下面的就是绕来绕去的逻辑问题。整体来说B码的解析程序没有想象的那么麻烦。
程序最重要的是需要最初的架构做的好点,此时刻不能影响下一个时刻。避免错误时的错误传输,这个逻辑最重要的。
第五章:展望
B码解析到此已经全部完成。大概需要时间为15天。如果有相关代码的话,差不多一个星期可以完成全部程序。如果有小伙伴需要的请自己关注,至于那里有能关注的图片,请移步之前的文章,有的能显示有的不能显示,所以小伙伴自己去斟酌。关注后请私信,到时候会将编写好的代码放到百度网盘中。采用这种方式的原因前面的文章也有说,所以就不赘述了。如果有什么疑问也可以留言,如果能帮助小伙伴在B码解析和FPGA的编程方面,也请关注下。以后做什么项目或者那个软件也会写文章给大家分享!