对于串口收发辑编程学习的总结
基础准备
串口的帧格式
- 串口的帧格式 {.mindmap}
- 格式组成:起始位,帧头,数据位,循环码,校验位,帧尾
- 起始位低电平有效
- 数据采集的clk为串口clk的8倍,16倍,推荐8倍即可。
- 数据抓取在中间位置
- 定义采集计数器cnt=0
- 起始位来时计数,每来一次clk计数器就+1
- 经过4个clk,cnt=4时取到第一个数
- 后面的0<i=i+1<9个数就应该在cnt=4+i*8的时候取
- 取完8位数后计数器归零
- 取完一帧数后将起始拉高,等待下一帧数据
串口的波特率
波特率是指一个位的周期,即一个位所逗留的时间,波特率115200bps表示一秒可以传输115200位。那么一位所需时间为t秒:
$$t=\frac1{115200}$$
系统时钟和波特率的关系
$$分频系数p=\frac{系统时钟clk}{波特率bps}$$
最大分频计数器
n
=
分频系数
p
2
−
1
∣
最大分频计数器n=\frac{分频系数p}2-1|
最大分频计数器n=2分频系数p−1∣
clk_p = (cnt==n) ? ~clk_p:0 ;
数据抓取
对系统时钟要根据串口波特率进行分频处理,发送数据时的分频频率和波特率一致,采集数据时用8倍采样率采集数据的中间位置可保证数据的准确性。
串口数据收模块的解析
各端口的接收模块由uart_rx例化而来,由于各波特率和数据的字节数不同所以各uart_rx略有不同
状态机图
有关逻辑(state)
初始化(Init): 初始化
Init:
begin
state <= Idle;
tally <= 8'd15;
trig_r <= 1'b0;
trig_v <= 1'b0;
rx0<=1'b1; rx1<=1'b1; rx2<=1'b1;rx3<=1'b1;
cnt <= 32
data_r <=8'h00;
rxd_r <=9'b0000_0000_0;
end
状态1(Idle) :起始位判断
Idle:begin
rx0<=rxd;rx1<=rx0;rx2<=rx1;rx3<=rx2;//输入打4拍,取反做与处理判断为真开始采集
tirg_r<=0; //字节数收完标记
trig_v<=0; //数据正确校验码ok标记
cnt<=0; //取一个字节数计数
tall<=0; //取所有字节数计数
if (~rx3 & ~rx2 & ~rx1 & ~rx0)
state <= Syn_First;
else
state <= Idle;
end
状态2(Syn_Firs) :取8bit数及帧头判断
Syn_Firs:begin
cnt <= cnt+1;
case(cnt)
4+0*8:
rxd_r(0) <= rxd; //串行数据rxd保存在并行变量rxd_r[9:0]
state <= Syn_Firs;
...
4+7*8:
rxd_r(7) <= rxd; //第8位数值
state <= Syn_Firs;
4+57:
data_r <= rxd_r[7:0] //保存该字节数在变量data_r[7:0]
state <= Syn_Firs;
4+8*8:begin //起始位拉高判断该字节数是否是帧头是就进取值状态不是返回重来
rxd_r(8) <= rxd;
rx0<=1'b1; rx1<=1'b1; rx2<=1'b1; rx3<=1'b1;
if(data_r==帧头) begin //帧头判断逻辑
state <= Start_rec; //开始进入采集逻辑
end else begin
state <= Idle; //返回继续判断起始位
end
end
end
状态3(Start_rec) :采集及校验码判断
Start_rec: begin
case(cnt)
...
4+57: data_r <= rxd_r[7:0] //取一个字节数
4+8*8:begin
rxd_r(8) <= rxd;
rx0<=1'b1; rx1<=1'b1; rx2<=1'b1; rx3<=1'b1;
tally<=tally+1; //通过tally将一帧所有字节数据取完
if(tall<8‘d17) //数据为16个字节
case(tally)
0: re_data0 <= data_r;
trig_r <= 0;
1: re_data1 <= data_r;
trig_r <= 0;
...
16: re_data[i]<= data_r;
trig_r <= 1;
default: trig_r <=0;
sata <= Start_rec;
end
4+65:begin
if(trig_r) begin //全部字节是否完整
if(re_data[i]==前字节和) //比较校验位
data_buf <={帧头,re_data0,...re_datai}; 有效数据
trig_v <= 1; //数据有效标记
state <= Receive_over;
状态4(Receive_over) :解析结束
Receive_over:begin
rx0<=1'b1; rx1<=1'b1; rx2<=1'b1; rx3<=1'b1;
trig_r <= 0;
trig_v <= 0;
state <= Init;
end
完成解析赋值
always @(negedge trig_v)
begin
re_data <= data_buf;
end
采集完成标记信号的处理
为了能准确的采集到采集完成标记信号,要将trig_v做拉长处理,拉长时间宽度80us。示意图如下:80us的判断方法依然是用采用计数器,trig_v信号来开始计数,clock计数=299拉低trig_vv信号。
串口数据的发送
触发信号的判断。用下面代码可以用trig_spor_Re抓取strig_spot的上升沿.依此来判断触发信号是否有效。当触发信号是上升沿有效时开始发送一帧数据
always @ (posedge clk460k)
begin
trig_spot_d <= trig_spot;
end
assign trig_spot_Re = (!trig_spot_d) & trig_spot;
顶层逻辑
由于对方给与的整个逻辑需求不明确,导致实际写逻辑的时候疑问太多,直接导致了工作量的不必要增加。今后对于类似项目的处理就要有前车之鉴。
数据传输与赋值
- 直接将需要的数据assign就行
- 数字运算时将除法换算为乘法
- {}里可以直接调用函数
assign data = {rx1,rx2,dov(rx3[5:0]),rx0}
- 访达
调试中的注意事项
- 帧头位置是一帧数据的最高位
- 有校验位的必须算正确,否则认为命令无效
调试
模块的优化
preamble[0] <= recdata[303:296];
preamble[1] <= recdata[295:288];
。。。
preamble[12] <= recdata[207:200];
preamble[13] <= recdata[199:192];
。。。
如上代码可用for循环代替矢量做为索引的时候:“+:” 相当于"+=" (DW-1)-ii*8选定了起始,然后从起始开始每次减8,8个取为一组 in[i +: 4]; 特别的,起始和终点都随着i变化,这是不允许的,verilog中只允许起始或终点发生变化,其他保持常量,这样做是为了保证宽度不发生改变
for ( ii=0; ii<10; ii=ii+1) begin
preamble[ii] <= recdata[(DW-1)-ii*8-:8];
end
直接用参数代替,
module uart_rx #(parameter
integer DW = 224 ,
FH1 = 8'h7e,
FH2 = 8'ha0,
FF = 8'he7,
N_DATA = 25 //去帧头节数从零开始
) 。。。
(value + 32'd65): begin
if (yxyyjb == 1'b1) begin
if ((recdat_r[N_DATA] == FF) && (recdat_r[N_DATA-1] == sum_p)) begin
for (kk=0; kk<DW/8-2; kk=kk+1) begin
yk_buf[N_DATA-kk] <= recdat_r[kk];
end
yk_buf[DW/8-1:DW/8-2] <= {FH1,FH2};
yk_trig <= 1'b1;
state <= Receive_over;
end else begin
state <= Idle;
yk_trig <= 1'b0;
end
对于向case语句里面的重复代码用
8'd1 :
begin
Txd <= 1'b0; //Start_bit 0
state <= Start_Tx;
end
8'd2,8'd3,8'd4,8'd5,8'd6,8'd7,8'd8,8'd9:begin
Txd <= tx_data[Tx_cnt-2];
state <= Start_Tx;
end
功能的修改
bug调试
pp值修改用assign代码如下情况时pp1和pp2的值不受条件1和条件2的控制,直接表现为一荣俱荣一损俱损。使用ila功能编译时提示这种组合逻辑环有问题,应该用寄存器变量定义中间变量。
assign pp1 = (条件1)?pp:pp1;
assign pp2 = (条件2)?pp:pp2:
其它
函数定义的中间变量用reg写法和modele类似。
function [31:0] outdata ;
input [31:0] a;
reg [31:0] temp1;
begin
...
end
16位数转换成32位浮点数
- 转换依据编码器的原始数据角度转换不同编码器设置不同
- 编码器位偏移量输出7fff时,角度是0°;输出ffff时,角度是90°,输出0000时,角度是-90°
即目的就是将x转换成至少小数点后4位的angle。
方法一首先想到如下计算函数实现,但仿真结果误差较大。
function [31:0] conv_dat32;
input [15:0] data_i;
reg [15:0] conv_dat1;
begin
conv_dat1 = (data_i / 364 - 90);
if (data_i >= 16'h7fff) begin
conv_dat32 = {16'h0,conv_dat1};
end else begin
conv_dat32 = {16'hffff, conv_dat1};
end
end
endfunction
方法二 将16位定点数的转换成32位定点数,然后就行浮点数的A*B-C
B=180/65535的浮点数,C为90的浮点数
//16位定点转32位定点
assign data32 = {16'h0, data16};
//32位定点数转成32位浮点数
floating_point_0 floating_point_0_dut (
.aclk(clk8xuart),
.s_axis_a_tvalid(1'b1),
.s_axis_a_tready(),
.s_axis_a_tdata(data32),
.m_axis_result_tvalid(),
.m_axis_result_tdata(float_data_o)
);
//A*B-C,B和C为转换成浮点数的常数
floating_point_2 floating_point_2_dut (
.aclk(clk8xuart),
.s_axis_a_tvalid(1'b1),
.s_axis_a_tready(),
.s_axis_a_tdata(float_dataA),
.s_axis_b_tvalid(1'b1),
.s_axis_b_tready(),
.s_axis_b_tdata(32'h3B340B41), //0.00274658203125 32'h3B340B41
.s_axis_c_tvalid(1'b1),
.s_axis_c_tready(),
.s_axis_c_tdata(32'h42B40000),
.m_axis_result_tvalid(),
.m_axis_result_tdata(float_data_o)
);
//浮点数的加法
floating_point_1 floating_point_1_dut3 (
.aclk(clk8xuart),
.s_axis_a_tvalid(1'b1),
.s_axis_a_tready(),
.s_axis_a_tdata(float_a),
.s_axis_b_tvalid(1'b1),
.s_axis_b_tready(),
.s_axis_b_tdata(float_b),
.m_axis_result_tvalid(),
.m_axis_result_tdata(float_data_o)
);
仿真后的数据截图
仿真命令
写了do文件后 在quesat—>file—>change director …指定do文件所在路径
.do文件写法
if [file exists work] {vdel -all}
vlib work
vmap work work
vlog -f list.f
vopt +acc float_top_tb -o float_top_tb_opt -debugdb
vsim -vopt float_top_tb_opt
log -r /*
add wave /*
run -all
.f文件写法
./src/float_top_tb.sv
./src/float_top.v
./src/fix2float.v
./src/floatingadder.v