概述
本次将探讨串口收发字符串的最后一部分,所谓调控,不过是我取得一个名字罢了。因为FPGA要通过串口发送数据给HMI,而我们的数据并不是人为自定义随意发送,我们最终的目的是将整个系统(如电机伺服控制系统)中的某些参数(来自系统的其他模块的数据)传递给串口模块,由串口模块进行数据整合(整合成特殊格式,HMI的指令格式所要求的),然后再发送至HMI进行数据的显示,谓之调控。
调控模块也是整个串口模块中最复杂的一部分(实际上整个串口的发送和接收并不是很难,难的是如何按照规定的格式规定的时序进行正确发送)。
USART_Ctrl模块(调控模块)整体说明:
这个模块的功能是对用户数据和固定数据按照HMI的数据格式进行重组和数据流调度。如何改变HMI中的文本控件的txt属性,在第四部分会介绍。故可以考虑,定义一个Memory,深度为7+2+5=14,其中,7代表7位固定格式t0.txt=,共有7个字符,2表示1对双引号"",为字符串数据类型的标识符,5表示用户数据的最大深度,即表示用户数据最多5个字符,如1.234,此外,指令结束符FF FF FF(以HEX16进制格式,占用3个字符深度)单独发送,在最后一个"之后在发送。用户数据的长度是不定的,故需要用一个reg变量记录下接收到的用户字符个数,在代码中定义为bit_sum。该部分代码采用框图和时序图进行描述。
以下进行详细说明:
给出一张框图方便理解,如下:
转化为时序控制框图,如下所示:
解释一下,data_high&&datain_en表示在接收区间内由数据到来,当数据到来时,将数据写入到Memory中,另一方面bit_sum用来计接收的总字符个数,以方便发送部分准确的将数据顺序移位输出,其次,在接收完成时(判断data_high的下降沿),置位send_en,程序调度进入发送区间,最后将字符从dataout送出。
仿真波形如下给出:发送了一个用户数据——字符‘1’,对应ASCII码为16进制31。
细节说明:
PART1:字符输出部分的代码描述
//需要再发送3个0XFF,表征指令生效
/*本节解释:
在send_en && time10_cnt == TIME_10BITS条件满足时,置位req_flag并移出一个字符,
在4'd8 + bit_sum范围内,移位输出写入Memory中的数据(0-8是固定的,bit_sum表示接收到的字符的个数),
故在send_bit_cnt >= 4'd9 + bit_sum且send_bit_cnt <= 4'd10 + bit_sum之间(在发送完用户数据之后),
就发送两个双引号,此后,再发送三个0XFF作为数据指令的结束标志(HMI指令要求)。
*/
always @ (posedge clk, negedge rst_n)
begin
if (!rst_n)
dataout <= {CTRL_WID{1'b1}};
else if (send_en && time10_cnt == TIME_10BITS)
begin
if (send_bit_cnt <= 4'd8 + bit_sum)
dataout <= store[send_bit_cnt];
else if (send_bit_cnt >= 4'd9 + bit_sum)
begin
if (send_bit_cnt <= 4'd10 + bit_sum)
dataout <= 8'h22;
else
dataout <= 8'hFF;
end
end
end
因为本次向HMI串口屏发送的数据具有特定格式,包括双重字符串以及指令结束标志FF等特殊要求,在接收到来自其他模块的数据后,与已知的特定格式(提前写入到Mem中的字符)需要进行重组打包,然后在send_en区间将字符按照规定时序送出。其中reg多1少1,对最终发出的数据影响颇大。比如:
else if (send_bit_cnt == NUM_CHAR + bit_sum) //发送完最后一个字符拉低发送区间状态标志(包含FF)
send_en <=1'b0;
如果写成send_bit_cnt == NUM_CHAR + bit_sum – 1,那么最终送出的FF字符就只有两个,虽然表现上看没多大问题,但实际上,将上述指令发送给HMI后,HMI屏幕上是不会显示正确信息的。而不像一般的串口发送(主要能够向外发送即可),这里对代码的要求甚高。以上一条,足以体现调度模块的要求之高。
PART2:经验总结
在第二节中说到,具体的时序控制逻辑需要完善,因为在调试过程中,没有找到关键修改代码的方式。(05.07)
事实上,针对这个问题,这两天我一直在对这个问题进行优化。最开始写出的代码是可以用,但在调试过程中,发现了一个问题:verilog中reg型变量由于初始化的原因会导致对整体时序的控制产生不良影响(比如会导致数据的错位输出),所以这期间着手在解决这个问题。就比如:由于是reg型变量,一般会进行一步复位,这样他就有了初始值,而对我们的数据输出端dataout(reg型),最开始,我是采用在send_en区间time10_cnt == TIME_10BITS时产生发送请求脉冲req_flag,然后在req_flag下将数据移位输出,这样就可能导致第一个请求脉冲对应的移位输出可能是复位值。其二,还有,这样就会导致第一个请求时钟脉冲对应send_bit_cnt==1,实际上应该是对应0(因为store是从0下标开始的),等等,如果不搞清楚(虽然第一次的代码误打误撞能够使用,有需要的话可以将第一次的代码发给你们),但实际上我并不理解这样为什么就能满足时序要求(我是一个一个修改到达要求的)。所以,通过后面的调试,基本上弄清楚了上述问题,也就此将代码重新修改了,增加了代码的可读性。总结一下就是:
-->采用触发器构建的时序逻辑电路,很多脉冲信号对应的边沿往往会对齐时钟信号,这个时候你要告诉自己,实际上在该时刻时钟边沿采到的信号对应被采对象的上一历史值(即被采信号实际上相对时钟边沿会有一个延后)。
-->代码描述电路时,尽量使用参数化描述方式,方便代码移植也方便代码的整体阅读感。
-->多思考,比如复位值的影响,何时加1何时减1.
关于加1减1,如下举例说明:
这是改进后(send_bit_cnt == NUM_CHAR + bit_sum时才将send_en置0,明显的延后一个时钟周期),开始时是send_bit_cnt == NUM_CHAR + bit_sum – 1,其实开始这样写是合情理的,因为定义的数组是从下标0开始的,发送也是从0下标开始,这样按照计数的个数(即从0开始算)发送的话,那么对应数字的话应该是要减1的,但很明显,如果用最开始减1的写法,最后FF的请求脉冲就会少一个,只有换成send_bit_cnt == NUM_CHAR + bit_sum后,如上图也可知,在send_bit_cnt==15的时候,也只持续了一个时钟周期,这样既不会多增加req_flag,又能将少发的FF增加。由上知,这个1很关键。
还有,说到这,请看上图的send_inter信号(发送完中断信号),同样是send_bit_cnt == NUM_CHAR + bit_sum,可是它为什么是在send_bit_cnt==15就立即输出标志位(而不是延后一个周期)。哈哈,看到这应该知道我想表达什么了吧,其实就是阻塞与非阻塞赋值的差异了(这时可以结合我之前的一篇博客对比一下),这里可是给出了活生生的例子(因为单纯看之前的,并不一定能理解我表达的是什么意思)。还有,上述说的采用触发器构建的时序逻辑电路,应该用错位的思想去理解(我的博客:竞争与冒险那一篇中也有提到),本质上讲还是由非阻塞赋值的特点所决定的。故综上,可以郑重给出:阻塞赋值在本周期完成,而非阻塞赋值往往要延后一个周期。人总说延后一个周期,开始我只是记住它,实际上并不理解,通过本次,终于有了一个新的认识,这就是所谓实践是检验真理的唯一标准。
上述send_inter 的代码描述:(采用连续性赋值语句)
//发送完成中断标志位
assign send_inter = (send_bit_cnt == NUM_CHAR + bit_sum) ? 1'b1 : 1'b0;
至此,有关FPGA串口收发字符串控制HMI串口屏的代码到这就暂告以段落,调度部分的代码我稍后会上传,为了方便大家,后续也会将整个工程代码和modelsim仿真文件也一并打包上传。
补充一哈,差一点忘记了波特率模块。
没有特别说明,只是发送模块例化时,采样时刻可以为中点也可以为终点(时序控制不好在终点时容易出错);例化接收模块,采样时刻为中点时刻(数据稳定且时序上不易出错)。
如下图,这个模块其实比较简单,需要注意的就是不同波特率在特定时钟下对应的计数值是多少,bps_start为模块的输入信号,以及bps_flag(输出对应的数据采样标志),这就基本上能够完成BAUD_Generator模块了。
代码如下:
/*************BAUD_Generator计数值***************/
always @ (posedge clk, negedge rst_n)
begin
if (!rst_n)
bps_cnt <= {2 * BAUD_WID{1'b0}};
else
bps_cnt <= bps_cnt_n;
end
//bps_cnt_n在BAUD_rate_cnt和!bps_start(波特率发生器关闭)时清零
always @ (*)
begin
if (bps_cnt == BAUD_rate_cnt)
bps_cnt_n = {2 * BAUD_WID{1'b0}};
else if (bps_start)
bps_cnt_n = bps_cnt + 1'b1;
else if (!bps_start)
bps_cnt_n = {2 * BAUD_WID{1'b0}};
else
bps_cnt_n = bps_cnt;
end
/*************中点采样信号置位****************/
always @ (posedge clk, negedge rst_n)
begin
if (!rst_n)
bps_flag <= 1'b0;
else if (bps_cnt == BAUD_rate_mid_cnt)
bps_flag <= 1'b1;
else
bps_flag <= 1'b0;
end
endmodule
打完收工。。。这里给出整个FPGA串口发送字符串指令控制HMI串口屏在其上显示1的的代码的RTL视图如下:各个模块都在我的博客中进行了说明
调度模块代码链接:
https://download.csdn.net/download/huigeyu/11165365
整个工程代码链接:
https://download.csdn.net/download/huigeyu/11260920
有关Usart_hmi的相关信息,在第四部分单独作介绍。
不断学习,不断进步,我思故我在。这句话送给大家,也希望大家也和我一样一同学习进步。加油!
上述我的理解,如有不同意见可以相互讨论,希望大家多多关注!
最后
以上就是阔达方盒为你收集整理的FPGA之串口收发字符串控制HMI串口屏之(三)——调控模块及波特率发生模块的全部内容,希望文章能够帮你解决FPGA之串口收发字符串控制HMI串口屏之(三)——调控模块及波特率发生模块所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复