作者:数字站
1. 概括

驱动模块直接查看前面即可,本文不再赘述。
2. 程序设计
2.1数据处理模块
always@(posedge clk or negedge rst_n)begin r_din_vld <= {r_din_vld[0],din_vld}; end //对应通道数据有效时,把补码数据转换为原码且保留符号位; always@(posedge clk or negedge rst_n)begin if(~rst_n)begin r_ch_data <= 'd0; r_ch_sig <= 'd0; end else if(din_vld)begin r_ch_data <= din[15] ? (~din[14:0] + 1) : din[14:0]; r_ch_sig <= din[15]; end end
电压值y,ad7606采集的15位数据x的转换关系为y=x/(2^15)。因此通过下面的方式转换,为了保留计算精度,电压值ch_voltage为真实值的2^15*1000倍,单位mv。ch_voltage = ch_data * 1000的计算方式采用移位和加法计算。
//对应通道数据乘以5000,得到真实电压的2^15倍,单位mv; always@(posedge clk)begin if(r_din_vld[0])begin//5/32768≈0.15mv,ADC采集数据乘以0.15得电压,0.15*2^15*1000=5000 r_ch_voltage <= {r_ch_data,12'd0} + {r_ch_data,9'd0} + {r_ch_data,8'd0} + {r_ch_data,7'd0} + {r_ch_data,3'd0}; end end
舍弃ch_voltage的低15位数据,等效除以2^15,电压值r_voltage计算就是转换结果,单位mv。
always@(posedge clk)begin r_voltage <= r_din_vld[1] ? r_ch_voltage[27:15] : r_voltage;//除以2^15次方,得到mv电压; r_voltage_vld <= r_din_vld[1]; voltage_sig <= r_ch_sig; end
最后通过hex2bcd模块把转换后的电压值转换为BCD码,该模块全部采用参数化设计,前面有一篇文章也讲了具体实现原理,有兴趣的可以点击查看。
//--############################################################################################### //--# //--# File Name : data_dispose //--# Designer : 数字站 //--# Tool : Quartus 2018.1 //--# Design Date : 2024.10.10 //--# Description : //--# Version : 0.0 //--# Coding scheme : UTF-8(If the Chinese comment of the file is garbled, please do not save it and check whether the file is opened in GBK encoding mode) //--# //--############################################################################################### module data_dispose ( input clk ,//系统时钟信号; input rst_n ,//系统复位信号,低电平有效; input [15 : 0] din ,//输入数据; input din_vld ,//输入数据有效指示信号,高电平有效; output [15 : 0] voltage ,//输出电压,单位mv output reg voltage_sig = 'd0 ,//输出电压的正负,低电平表示正; output voltage_vld //输出数据有效指示信号,高电平有效; ); reg [1 : 0] r_din_vld = 'd0 ; reg [14 : 0] r_ch_data ;// reg [27 : 0] r_ch_voltage = 'd0 ; reg r_ch_sig = 'd0 ; reg [12 : 0] r_voltage = 'd0 ; reg r_voltage_vld = 'd0 ; always@(posedge clk or negedge rst_n)begin r_din_vld <= {r_din_vld[0],din_vld}; end //对应通道数据有效时,把补码数据转换为原码且保留符号位; always@(posedge clk or negedge rst_n)begin if(~rst_n)begin r_ch_data <= 'd0; r_ch_sig <= 'd0; end else if(din_vld)begin r_ch_data <= din[15] ? (~din[14:0] + 1) : din[14:0]; r_ch_sig <= din[15]; end end //对应通道数据乘以5000,得到真实电压的2^15倍,单位mv; always@(posedge clk)begin if(r_din_vld[0])begin//5/32768≈0.15mv,ADC采集数据乘以0.15得电压,0.15*2^15*1000=5000 r_ch_voltage <= {r_ch_data,12'd0} + {r_ch_data,9'd0} + {r_ch_data,8'd0} + {r_ch_data,7'd0} + {r_ch_data,3'd0}; end end //产生输出数据; always@(posedge clk)begin r_voltage <= r_din_vld[1] ? r_ch_voltage[27:15] : r_voltage;//除以2^15次方,得到mv电压; r_voltage_vld <= r_din_vld[1]; voltage_sig <= r_ch_sig; end //调用十六进制转BCD模块,将13位十六进制数据转换成16位BCD码; hex2bcd #( .IN_DATA_W ( 13 )//输入数据位宽; ) u_hex2bcd ( .clk ( clk ),//系统时钟; .rst_n ( rst_n ),//系统复位,低电平有效; .din ( r_voltage ),//输入二进制数据; .din_vld ( r_voltage_vld ),//输入数据有效指示信号,高电平有效; .rdy ( ),//忙闲指示信号,该信号高电平时才能输入有效数据; .dout ( voltage ),//输出8421BCD码; .dout_vld ( voltage_vld ) //输出数据有效指示信号,高电平有效; ); endmodule
2.2 uart字节发送模块
首先通过存储器保存八路adc转换后的bcd数据,保存的时候还要将数据加48转换为ascii编码,因为0对应的ascii码对应数值为48。
always@(posedge clk)begin if(i_voltage_vld[0])begin r_voltage_sig[0] <= i_voltage_sig[0];//保存通道0的符号位; r_voltage_g[0] <= i_voltage0[15 : 12] + 8'd48;//将通道0的个位数据转换为ASCII对应字符; r_voltage_s[0] <= i_voltage0[11 : 8] + 8'd48;//将通道0的十分位数据转换为ASCII对应字符; r_voltage_b[0] <= i_voltage0[7 : 4] + 8'd48;//将通道0的百分位数据转换为ASCII对应字符; r_voltage_q[0] <= i_voltage0[3 : 0] + 8'd48;//将通道0的千分位数据转换为ASCII对应字符; end end always@(posedge clk)begin if(i_voltage_vld[1])begin r_voltage_sig[1] <= i_voltage_sig[1];//保存通道1的符号位; r_voltage_g[1] <= i_voltage1[15 : 12] + 8'd48;//将通道1的个位数据转换为ASCII对应字符; r_voltage_s[1] <= i_voltage1[11 : 8] + 8'd48;//将通道1的十分位数据转换为ASCII对应字符; r_voltage_b[1] <= i_voltage1[7 : 4] + 8'd48;//将通道1的百分位数据转换为ASCII对应字符; r_voltage_q[1] <= i_voltage1[3 : 0] + 8'd48;//将通道1的千分位数据转换为ASCII对应字符; end end always@(posedge clk)begin if(i_voltage_vld[2])begin r_voltage_sig[2] <= i_voltage_sig[2];//保存通道2的符号位; r_voltage_g[2] <= i_voltage2[15 : 12] + 8'd48;//将通道2的个位数据转换为ASCII对应字符; r_voltage_s[2] <= i_voltage2[11 : 8] + 8'd48;//将通道2的十分位数据转换为ASCII对应字符; r_voltage_b[2] <= i_voltage2[7 : 4] + 8'd48;//将通道2的百分位数据转换为ASCII对应字符; r_voltage_q[2] <= i_voltage2[3 : 0] + 8'd48;//将通道2的千分位数据转换为ASCII对应字符; end end always@(posedge clk)begin if(i_voltage_vld[3])begin r_voltage_sig[3] <= i_voltage_sig[3];//保存通道3的符号位; r_voltage_g[3] <= i_voltage3[15 : 12] + 8'd48;//将通道3的个位数据转换为ASCII对应字符; r_voltage_s[3] <= i_voltage3[11 : 8] + 8'd48;//将通道3的十分位数据转换为ASCII对应字符; r_voltage_b[3] <= i_voltage3[7 : 4] + 8'd48;//将通道3的百分位数据转换为ASCII对应字符; r_voltage_q[3] <= i_voltage3[3 : 0] + 8'd48;//将通道3的千分位数据转换为ASCII对应字符; end end always@(posedge clk)begin if(i_voltage_vld[4])begin r_voltage_sig[4] <= i_voltage_sig[4];//保存通道4的符号位; r_voltage_g[4] <= i_voltage4[15 : 12] + 8'd48;//将通道4的个位数据转换为ASCII对应字符; r_voltage_s[4] <= i_voltage4[11 : 8] + 8'd48;//将通道4的十分位数据转换为ASCII对应字符; r_voltage_b[4] <= i_voltage4[7 : 4] + 8'd48;//将通道4的百分位数据转换为ASCII对应字符; r_voltage_q[4] <= i_voltage4[3 : 0] + 8'd48;//将通道4的千分位数据转换为ASCII对应字符; end end always@(posedge clk)begin if(i_voltage_vld[5])begin r_voltage_sig[5] <= i_voltage_sig[5];//保存通道5的符号位; r_voltage_g[5] <= i_voltage5[15 : 12] + 8'd48;//将通道5的个位数据转换为ASCII对应字符; r_voltage_s[5] <= i_voltage5[11 : 8] + 8'd48;//将通道5的十分位数据转换为ASCII对应字符; r_voltage_b[5] <= i_voltage5[7 : 4] + 8'd48;//将通道5的百分位数据转换为ASCII对应字符; r_voltage_q[5] <= i_voltage5[3 : 0] + 8'd48;//将通道5的千分位数据转换为ASCII对应字符; end end always@(posedge clk)begin if(i_voltage_vld[6])begin r_voltage_sig[6] <= i_voltage_sig[6];//保存通道6的符号位; r_voltage_g[6] <= i_voltage6[15 : 12] + 8'd48;//将通道6的个位数据转换为ASCII对应字符; r_voltage_s[6] <= i_voltage6[11 : 8] + 8'd48;//将通道6的十分位数据转换为ASCII对应字符; r_voltage_b[6] <= i_voltage6[7 : 4] + 8'd48;//将通道6的百分位数据转换为ASCII对应字符; r_voltage_q[6] <= i_voltage6[3 : 0] + 8'd48;//将通道6的千分位数据转换为ASCII对应字符; end end always@(posedge clk)begin if(i_voltage_vld[7])begin r_voltage_sig[7] <= i_voltage_sig[7];//保存通道7的符号位; r_voltage_g[7] <= i_voltage7[15 : 12] + 8'd48;//将通道7的个位数据转换为ASCII对应字符; r_voltage_s[7] <= i_voltage7[11 : 8] + 8'd48;//将通道7的十分位数据转换为ASCII对应字符; r_voltage_b[7] <= i_voltage7[7 : 4] + 8'd48;//将通道7的百分位数据转换为ASCII对应字符; r_voltage_q[7] <= i_voltage7[3 : 0] + 8'd48;//将通道7的千分位数据转换为ASCII对应字符; end end
always@(posedge clk or negedge rst_n)begin if(~rst_n)begin//初始值为0; r_rdy_cnt <= 'd0; end else if(i_uart_tx_rdy)begin if(r_end_rdy_cnt) r_rdy_cnt <= 'd0; else r_rdy_cnt <= r_rdy_cnt + 'd1; end end always@(posedge clk)begin r_end_rdy_cnt <= i_uart_tx_rdy && (r_rdy_cnt == 2*BPS_CNT-2); end
通过两个计数器分别记录发送当前通道的第几个数据、发送的数据属于第几个通道。
always@(posedge clk or negedge rst_n)begin if(~rst_n)begin//初始值为0; r_byte_cnt <= 'd0; end else if(r_end_rdy_cnt)begin if(r_byte_cnt == 12) r_byte_cnt <= 'd0; else r_byte_cnt <= r_byte_cnt + 'd1; end end //一轮需要传输8个通道的数据到PC端,使用一个8进制计数器对传输数据的通道数计数; //当一个通道数据传输结束时加1,计数器采用溢出清零; always@(posedge clk or negedge rst_n)begin if(~rst_n)begin//初始值为0; r_ch_cnt <= 'd0; end else if(r_end_rdy_cnt && (r_byte_cnt == 12))begin r_ch_cnt <= r_ch_cnt + 'd1; end end
通过译码器转换数据,根据字节计数器的值发送对应的数据,有部分数据根据通道不同发送不同数据。比如通道7数据发送结束后,需要发送回车和换行字符,不需要发送空格字符。
always@(posedge clk)begin if(r_end_rdy_cnt)begin case(r_byte_cnt) 4'd0 : o_uart_txdata <= 8'd65;//发送字符A对应的ASCCI码值; 4'd1 : o_uart_txdata <= 8'd68;//发送字符D对应的ASCCI码值; 4'd2 : o_uart_txdata <= r_ch_cnt + 'd49;//发送通道r_ch_cnt对应的ASCCI码值; 4'd3 : o_uart_txdata <= 8'd58;//发送字符:对应的ASCCI码值; //电压的正负值对应的ASCII码,r_voltage_sig为高电平表示对应通道电压为负数。 4'd4 : o_uart_txdata <= r_voltage_sig[r_ch_cnt] ? 8'd45 : 8'd43; 4'd5 : o_uart_txdata <= r_voltage_g[r_ch_cnt];//发送个位电压对应的ASCCI码值; 4'd6 : o_uart_txdata <= 8'd46;//发送字符.对应的ASCCI码值; 4'd7 : o_uart_txdata <= r_voltage_s[r_ch_cnt];//发送十分位对应的ASCCI码值; 4'd8 : o_uart_txdata <= r_voltage_b[r_ch_cnt];//发送百分位对应的ASCCI码值; 4'd9 : o_uart_txdata <= r_voltage_q[r_ch_cnt];//发送千分位对应的ASCCI码值; 4'd10 : o_uart_txdata <= 8'd86 ;//发送字符V对应的ASCCI码值; 4'd11 : o_uart_txdata <= (&r_ch_cnt) ? 8'd10 : 8'd32;//如果是发送最后一个通道的数据,则发送换行,否则发送空格; 4'd12 : o_uart_txdata <= (&r_ch_cnt) ? 8'd13 : 8'd32;//如果是发送最后一个通道的数据,则发送回车,否则发送空格; default : o_uart_txdata <= 8'hff; endcase end end always@(posedge clk)begin o_uart_txdata_vld <= r_end_rdy_cnt;//生成并行数据有效指示信号; end
该模块参考代码如下所示:
//--############################################################################################### //--# //--# File Name : uart_byte //--# Designer : 数字站 //--# Tool : Quartus 2018.1 //--# Design Date : 2024.10.10 //--# Description : //--# Version : 0.0 //--# Coding scheme : UTF-8(If the Chinese comment of the file is garbled, please do not save it and check whether the file is opened in GBK encoding mode) //--# //--############################################################################################### module uart_byte #( parameter FLCK = 50_000_000 ,//系统时钟频率,默认50MHZ; parameter BPS = 9600 //串口波特率; )( input clk ,//系统时钟信号; input rst_n ,//系统复位信号,低电平有效; input [15 : 0] i_voltage0 ,//输入数据; input [15 : 0] i_voltage1 ,//输入数据; input [15 : 0] i_voltage2 ,//输入数据; input [15 : 0] i_voltage3 ,//输入数据; input [15 : 0] i_voltage4 ,//输入数据; input [15 : 0] i_voltage5 ,//输入数据; input [15 : 0] i_voltage6 ,//输入数据; input [15 : 0] i_voltage7 ,//输入数据; input [7 : 0] i_voltage_sig ,//各通道数据的正负数据指示信号; input [7 : 0] i_voltage_vld ,//输入数据有效指示信号,高电平有效; input i_uart_tx_rdy ,//uart发送模块空闲指示信号; output reg [7 : 0] o_uart_txdata = 'd0 ,//需要uart发送的并行数据; output reg o_uart_txdata_vld = 'd0 //需要的发送的并行数据有效指示信号; ); localparam BPS_CNT = FLCK/BPS ;//波特率为9600bit/s,当波特率为115200bit/s时,DATA_115200==434; localparam BPS_CNT_W = $clog2(2*BPS_CNT-1) ;//根据BPS_CNT调用函数自动计算计数器bps_cnt位宽; reg [7 : 0] r_voltage_sig = 'd0 ; reg [BPS_CNT_W-1:0] r_rdy_cnt ; reg r_end_rdy_cnt ; reg [7 : 0] r_voltage_g [7 : 0] ; reg [7 : 0] r_voltage_s [7 : 0] ; reg [7 : 0] r_voltage_b [7 : 0] ; reg [7 : 0] r_voltage_q [7 : 0] ; reg [3 : 0] r_byte_cnt ;// reg [2 : 0] r_ch_cnt ; /********** 存储数据 *************/ always@(posedge clk)begin if(i_voltage_vld[0])begin r_voltage_sig[0] <= i_voltage_sig[0];//保存通道0的符号位; r_voltage_g[0] <= i_voltage0[15 : 12] + 8'd48;//将通道0的个位数据转换为ASCII对应字符; r_voltage_s[0] <= i_voltage0[11 : 8] + 8'd48;//将通道0的十分位数据转换为ASCII对应字符; r_voltage_b[0] <= i_voltage0[7 : 4] + 8'd48;//将通道0的百分位数据转换为ASCII对应字符; r_voltage_q[0] <= i_voltage0[3 : 0] + 8'd48;//将通道0的千分位数据转换为ASCII对应字符; end end always@(posedge clk)begin if(i_voltage_vld[1])begin r_voltage_sig[1] <= i_voltage_sig[1];//保存通道1的符号位; r_voltage_g[1] <= i_voltage1[15 : 12] + 8'd48;//将通道1的个位数据转换为ASCII对应字符; r_voltage_s[1] <= i_voltage1[11 : 8] + 8'd48;//将通道1的十分位数据转换为ASCII对应字符; r_voltage_b[1] <= i_voltage1[7 : 4] + 8'd48;//将通道1的百分位数据转换为ASCII对应字符; r_voltage_q[1] <= i_voltage1[3 : 0] + 8'd48;//将通道1的千分位数据转换为ASCII对应字符; end end always@(posedge clk)begin if(i_voltage_vld[2])begin r_voltage_sig[2] <= i_voltage_sig[2];//保存通道2的符号位; r_voltage_g[2] <= i_voltage2[15 : 12] + 8'd48;//将通道2的个位数据转换为ASCII对应字符; r_voltage_s[2] <= i_voltage2[11 : 8] + 8'd48;//将通道2的十分位数据转换为ASCII对应字符; r_voltage_b[2] <= i_voltage2[7 : 4] + 8'd48;//将通道2的百分位数据转换为ASCII对应字符; r_voltage_q[2] <= i_voltage2[3 : 0] + 8'd48;//将通道2的千分位数据转换为ASCII对应字符; end end always@(posedge clk)begin if(i_voltage_vld[3])begin r_voltage_sig[3] <= i_voltage_sig[3];//保存通道3的符号位; r_voltage_g[3] <= i_voltage3[15 : 12] + 8'd48;//将通道3的个位数据转换为ASCII对应字符; r_voltage_s[3] <= i_voltage3[11 : 8] + 8'd48;//将通道3的十分位数据转换为ASCII对应字符; r_voltage_b[3] <= i_voltage3[7 : 4] + 8'd48;//将通道3的百分位数据转换为ASCII对应字符; r_voltage_q[3] <= i_voltage3[3 : 0] + 8'd48;//将通道3的千分位数据转换为ASCII对应字符; end end always@(posedge clk)begin if(i_voltage_vld[4])begin r_voltage_sig[4] <= i_voltage_sig[4];//保存通道4的符号位; r_voltage_g[4] <= i_voltage4[15 : 12] + 8'd48;//将通道4的个位数据转换为ASCII对应字符; r_voltage_s[4] <= i_voltage4[11 : 8] + 8'd48;//将通道4的十分位数据转换为ASCII对应字符; r_voltage_b[4] <= i_voltage4[7 : 4] + 8'd48;//将通道4的百分位数据转换为ASCII对应字符; r_voltage_q[4] <= i_voltage4[3 : 0] + 8'd48;//将通道4的千分位数据转换为ASCII对应字符; end end always@(posedge clk)begin if(i_voltage_vld[5])begin r_voltage_sig[5] <= i_voltage_sig[5];//保存通道5的符号位; r_voltage_g[5] <= i_voltage5[15 : 12] + 8'd48;//将通道5的个位数据转换为ASCII对应字符; r_voltage_s[5] <= i_voltage5[11 : 8] + 8'd48;//将通道5的十分位数据转换为ASCII对应字符; r_voltage_b[5] <= i_voltage5[7 : 4] + 8'd48;//将通道5的百分位数据转换为ASCII对应字符; r_voltage_q[5] <= i_voltage5[3 : 0] + 8'd48;//将通道5的千分位数据转换为ASCII对应字符; end end always@(posedge clk)begin if(i_voltage_vld[6])begin r_voltage_sig[6] <= i_voltage_sig[6];//保存通道6的符号位; r_voltage_g[6] <= i_voltage6[15 : 12] + 8'd48;//将通道6的个位数据转换为ASCII对应字符; r_voltage_s[6] <= i_voltage6[11 : 8] + 8'd48;//将通道6的十分位数据转换为ASCII对应字符; r_voltage_b[6] <= i_voltage6[7 : 4] + 8'd48;//将通道6的百分位数据转换为ASCII对应字符; r_voltage_q[6] <= i_voltage6[3 : 0] + 8'd48;//将通道6的千分位数据转换为ASCII对应字符; end end always@(posedge clk)begin if(i_voltage_vld[7])begin r_voltage_sig[7] <= i_voltage_sig[7];//保存通道7的符号位; r_voltage_g[7] <= i_voltage7[15 : 12] + 8'd48;//将通道7的个位数据转换为ASCII对应字符; r_voltage_s[7] <= i_voltage7[11 : 8] + 8'd48;//将通道7的十分位数据转换为ASCII对应字符; r_voltage_b[7] <= i_voltage7[7 : 4] + 8'd48;//将通道7的百分位数据转换为ASCII对应字符; r_voltage_q[7] <= i_voltage7[3 : 0] + 8'd48;//将通道7的千分位数据转换为ASCII对应字符; end end /********* 发送数据 ************/ //空闲计数器,发送一字节数据后,暂停一段时间在发送下字节数据; always@(posedge clk or negedge rst_n)begin if(~rst_n)begin//初始值为0; r_rdy_cnt <= 'd0; end else if(i_uart_tx_rdy)begin if(r_end_rdy_cnt) r_rdy_cnt <= 'd0; else r_rdy_cnt <= r_rdy_cnt + 'd1; end end always@(posedge clk)begin r_end_rdy_cnt <= i_uart_tx_rdy && (r_rdy_cnt == 2*BPS_CNT-2); end //每个通道需要发送13字节串口数据到PC端,使用一个13进制计数器对发送数据计数; //当下游模块空闲时表示发送完成1字节数据,计数器加1. always@(posedge clk or negedge rst_n)begin if(~rst_n)begin//初始值为0; r_byte_cnt <= 'd0; end else if(r_end_rdy_cnt)begin if(r_byte_cnt == 12) r_byte_cnt <= 'd0; else r_byte_cnt <= r_byte_cnt + 'd1; end end //一轮需要传输8个通道的数据到PC端,使用一个8进制计数器对传输数据的通道数计数; //当一个通道数据传输结束时加1,计数器采用溢出清零; always@(posedge clk or negedge rst_n)begin if(~rst_n)begin//初始值为0; r_ch_cnt <= 'd0; end else if(r_end_rdy_cnt && (r_byte_cnt == 12))begin r_ch_cnt <= r_ch_cnt + 'd1; end end //产生下游uart模块需要发送的并行数据; always@(posedge clk)begin if(r_end_rdy_cnt)begin case(r_byte_cnt) 4'd0 : o_uart_txdata <= 8'd65;//发送字符A对应的ASCCI码值; 4'd1 : o_uart_txdata <= 8'd68;//发送字符D对应的ASCCI码值; 4'd2 : o_uart_txdata <= r_ch_cnt + 'd49;//发送通道r_ch_cnt对应的ASCCI码值; 4'd3 : o_uart_txdata <= 8'd58;//发送字符:对应的ASCCI码值; //电压的正负值对应的ASCII码,r_voltage_sig为高电平表示对应通道电压为负数。 4'd4 : o_uart_txdata <= r_voltage_sig[r_ch_cnt] ? 8'd45 : 8'd43; 4'd5 : o_uart_txdata <= r_voltage_g[r_ch_cnt];//发送个位电压对应的ASCCI码值; 4'd6 : o_uart_txdata <= 8'd46;//发送字符.对应的ASCCI码值; 4'd7 : o_uart_txdata <= r_voltage_s[r_ch_cnt];//发送十分位对应的ASCCI码值; 4'd8 : o_uart_txdata <= r_voltage_b[r_ch_cnt];//发送百分位对应的ASCCI码值; 4'd9 : o_uart_txdata <= r_voltage_q[r_ch_cnt];//发送千分位对应的ASCCI码值; 4'd10 : o_uart_txdata <= 8'd86 ;//发送字符V对应的ASCCI码值; 4'd11 : o_uart_txdata <= (&r_ch_cnt) ? 8'd10 : 8'd32;//如果是发送最后一个通道的数据,则发送换行,否则发送空格; 4'd12 : o_uart_txdata <= (&r_ch_cnt) ? 8'd13 : 8'd32;//如果是发送最后一个通道的数据,则发送回车,否则发送空格; default : o_uart_txdata <= 8'hff; endcase end end always@(posedge clk)begin o_uart_txdata_vld <= r_end_rdy_cnt;//生成并行数据有效指示信号; end endmodule
uart发送模块依旧使用以前模块,前文详细讲解过uart接收模块全模式设计方式,本文就不再赘述,参考代码如下所示:
//--############################################################################################### //--# //--# File Name : uart_tx //--# Designer : 数字站 //--# Tool : Quartus 2018.1 //--# Design Date : 2024.10.10 //--# Description : //--# Version : 0.0 //--# Coding scheme : UTF-8(If the Chinese comment of the file is garbled, please do not save it and check whether the file is opened in GBK encoding mode) //--# //--############################################################################################### module uart_tx #( parameter FLCK = 50_000_000 ,//系统时钟频率,默认50MHZ; parameter BPS = 9600 ,//串口波特率; parameter DATA_W = 8 ,//发送数据位数以及输出数据位宽; parameter START_W = 1 ,//1位起始位; parameter CHECK_W = 2'b00 ,//校验位,2'b00代表无校验位,2'b01表示奇校验,2'b10表示偶校验,2'b11按无校验处理。 parameter STOP_W = 2'b01 //停止位,2'b01表示1位停止位,2'b10表示2位停止位,2'b11表示1.5位停止位; )( input clk ,//系统工作时钟50MHZ input rst_n ,//系统复位信号,低电平有效 input [DATA_W-1:0] tx_data ,//数据输入信号。 input tx_data_vld ,//数据有效指示信号,高电平有效。 output reg uart_tx ,//uart接口数据输出信号。 output reg tx_rdy //模块忙闲指示信号; ); localparam BPS_CNT = FLCK/BPS ;//波特率为9600bit/s,当波特率为115200bit/s时,DATA_115200==434; localparam BPS_CNT_W = $clog2(BPS_CNT-1);//根据BPS_CNT调用函数自动计算计数器bps_cnt位宽; localparam DATA_CNT = START_W + DATA_W + (^CHECK_W) + ((STOP_W==2'b11) ? 2 : STOP_W);//计数器计数值; localparam DATA_CNT_W= $clog2(DATA_CNT-1);//根据计数器cnt的值,利用函数自动计算此计数器的位宽; reg flag ; reg tx_rdy_ff0 ;// reg [DATA_CNT-1:0] tx_data_tmp ; reg [BPS_CNT_W-1:0] bps_cnt ; reg [DATA_CNT_W-1:0]data_cnt ; wire add_bps_cnt ; wire end_bps_cnt ; wire end_data_cnt ; /*发送一位数据所需要的时间*/ always @(posedge clk or negedge rst_n)begin if(!rst_n)begin bps_cnt <= {{BPS_CNT_W}{1'b0}}; end else if(add_bps_cnt)begin if(end_bps_cnt || end_data_cnt) bps_cnt <= {{BPS_CNT_W}{1'b0}}; else bps_cnt <= bps_cnt + {{{BPS_CNT_W-1}{1'b0}},1'b1}; end end assign add_bps_cnt = flag; assign end_bps_cnt = add_bps_cnt && bps_cnt == BPS_CNT-1; /*发送一组数据所用时间*/ always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin// data_cnt <= {{DATA_CNT}{1'b0}}; end else if(end_data_cnt)begin data_cnt <= {{DATA_CNT}{1'b0}}; end else if(end_bps_cnt)begin data_cnt <= data_cnt + {{{DATA_CNT-1}{1'b0}},1'b1}; end end //根据停止位的不同生成不同的计数器结束条件; generate if(STOP_W == 2'b11)//1.5位停止位,因为CNT_NUM没有包含起始位,所以要等计数器计数到CNT_NUM时清零; assign end_data_cnt = data_cnt == DATA_CNT-1 && add_bps_cnt && bps_cnt == BPS_CNT/2-1; else//停止位为1位或者2位; assign end_data_cnt = end_bps_cnt && data_cnt == DATA_CNT-1; endgenerate //这个期间UART在发送数据; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin flag <= 1'b0; end else if(tx_data_vld)begin flag <= 1'b1; end else if(end_data_cnt)begin flag <= 1'b0; end end //UART模块处于忙时期,收到上游模块数据或者正在处理上游模块所发数据; always@(*)begin if(tx_data_vld || flag)begin tx_rdy = 1'b0; end else begin tx_rdy = 1'b1; end end always@(posedge clk)begin tx_rdy_ff0 <= tx_rdy; end //将上游模块所发并行数据转化为串行数据; generate if(CHECK_W == 2'b01)begin//奇校验; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin tx_data_tmp <= {{DATA_CNT+1}{1'b1}}; end else if(tx_rdy_ff0 && tx_data_vld)begin tx_data_tmp <= {{{STOP_W}{1'b1}},~(^tx_data),tx_data,{{START_W}{1'b0}}}; end end end else if(CHECK_W == 2'b10)begin//偶校验 always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin tx_data_tmp <= {{DATA_CNT+1}{1'b1}}; end else if(tx_rdy_ff0 && tx_data_vld)begin tx_data_tmp <= {{{STOP_W}{1'b1}},(^tx_data),tx_data,{{START_W}{1'b0}}}; end end end else begin//无校验 always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin tx_data_tmp <= {{DATA_CNT+1}{1'b1}}; end else if(tx_rdy_ff0 && tx_data_vld)begin tx_data_tmp <= {{{STOP_W}{1'b1}},tx_data,{{START_W}{1'b0}}}; end end end endgenerate //将串行数据按9600波特率送出,先发低位; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin uart_tx <= 1'b1; end else if(add_bps_cnt && bps_cnt==0)begin uart_tx <= tx_data_tmp[data_cnt]; end end endmodule
2.3 顶层模块
顶层模块如下所示,单独列出来是因为代码使用for循环例化了8个通道的数据处理模块。参考代码如下所示:
//--############################################################################################### //--# //--# File Name : top //--# Designer : //--# Tool : Quartus 2018.1 //--# Design Date : 2024.10.10 //--# Description : //--# Version : 0.0 //--# Coding scheme : UTF-8(If the Chinese comment of the file is garbled, please do not save it and check whether the file is opened in GBK encoding mode) //--# //--############################################################################################### module top #( parameter FLCK = 100_000_000 ,//系统时钟频率,默认50MHZ; parameter BPS = 115200 ,//串口波特率; parameter UART_DATA_W = 8 ,//发送数据位数以及输出数据位宽; parameter START_W = 1 ,//1位起始位; parameter CHECK_W = 2'b00 ,//校验位,2'b00代表无校验位,2'b01表示奇校验,2'b10表示偶校验,2'b11按无校验处理。 parameter STOP_W = 2'b01 //停止位,2'b01表示1位停止位,2'b10表示2位停止位,2'b11表示1.5位停止位; )( input clk ,//系统时钟,50MHz; input rst_n ,//系统复位,低电平有效; input busy ,//转换完成指示信号,下降沿有效; input frstdata ,//指示采集到的第一个数据; input [15 : 0] adc_din ,//AD7606所采集到的十六位数据信号; input uart_rx , output cs ,//AD7606片选信号,读数据时拉低; output rd ,//AD7606读使能信号,读数据时拉低,下降沿时AD7606将数据发送到数据线上,上升沿时可以读出数据; output reset ,//AD7606复位信号,高电平有效,每次复位至少拉高50ns; output [2 : 0] os ,//AD7606过采样模式信号,默认不使用过采样; output convst ,//AD7606采样启动信号,无效时高电平,采样计数器完成时拉低两个时钟; output uart_tx //uart接口数据输出信号。 ); wire clk_100m ;//锁相环输出时钟; wire [15 : 0] w_7606_data ;//AD7606通道采集的补码数据; wire [7 : 0] w_7606_data_vld ;//指示AD7606输出的数据来自哪个数据通道; wire [15 : 0] w_voltage [7 : 0] ;//输出电压,单位mv wire [7 : 0] w_voltage_sig ;//输出电压的正负,低电平表示正; wire [7 : 0] w_voltage_vld ;//输出数据有效指示信号,高电平有效; wire [UART_DATA_W-1:0] w_tx_data ;//数据输入信号。 wire w_tx_data_vld ;//数据有效指示信号,高电平有效。 wire w_tx_rdy ;//模块忙闲指示信号; //例化锁相环 pll u_pll ( .areset ( ~rst_n ), .inclk0 ( clk ), .c0 ( clk_100m ) ); //例化ad7606驱动模块 ad7606_drive #( .FCLK ( FLCK ),//系统时钟频率,单位Hz,默认100MHz; .SMAPLE ( 200_000 ) //AD7606采样频率,单位Hz,默认200KHz; ) u_ad7606_drive ( .clk ( clk_100m ),//系统时钟,100MHz; .rst_n ( rst_n ),//系统复位,低电平有效; .busy ( busy ),//转换完成指示信号,下降沿有效; .frstdata ( frstdata ),//指示采集到的第一个数据; .adc_din ( adc_din ),//AD7606所采集到的十六位数据信号; .cs ( cs ),//AD7606片选信号,读数据时拉低; .rd ( rd ),//AD7606读使能信号,读数据时拉低,下降沿时AD7606将数据发送到数据线上,上升沿时可以读出数据; .reset ( reset ),//AD7606复位信号,高电平有效,每次复位至少拉高50ns; .os ( os ),//AD7606过采样模式信号,默认不使用过采样; .convst ( convst ),//AD7606采样启动信号,无效时高电平,采样计数器完成时拉低两个时钟; .data ( w_7606_data ),//AD7606采集到的数据.数据均为补码; .data_vld ( w_7606_data_vld ) //指示AD7606输出的数据来自哪个数据通道; ); genvar i; //使用for循环例化8个数据处理模块,将ad7606采集的数据转换为bcd码的mv电压; generate for(i=0 ; i<8 ; i=i+1)begin : DATA data_dispose u_data_dispose( .clk ( clk_100m ),//系统时钟信号; .rst_n ( rst_n ),//系统复位信号,低电平有效; .din ( w_7606_data ),//输入数据; .din_vld ( w_7606_data_vld[i]),//输入数据有效指示信号,高电平有效; .voltage ( w_voltage[i] ),//输出电压,单位mv .voltage_sig ( w_voltage_sig[i] ),//输出电压的正负,低电平表示正; .voltage_vld ( w_voltage_vld[i] ) //输出数据有效指示信号,高电平有效; ); end endgenerate //例化数据处理模块 uart_byte #( .FLCK ( FLCK ),//系统时钟频率,默认50MHZ; .BPS ( BPS ) //串口波特率; ) u_uart_byte( .clk ( clk_100m ),//系统时钟信号; .rst_n ( rst_n ),//系统复位信号,低电平有效; .i_voltage0 ( w_voltage[0] ),//输入数据; .i_voltage1 ( w_voltage[1] ),//输入数据; .i_voltage2 ( w_voltage[2] ),//输入数据; .i_voltage3 ( w_voltage[3] ),//输入数据; .i_voltage4 ( w_voltage[4] ),//输入数据; .i_voltage5 ( w_voltage[5] ),//输入数据; .i_voltage6 ( w_voltage[6] ),//输入数据; .i_voltage7 ( w_voltage[7] ),//输入数据; .i_voltage_sig ( w_voltage_sig ),//各通道数据的正负数据指示信号; .i_voltage_vld ( w_voltage_vld ),//输入数据有效指示信号,高电平有效; .i_uart_tx_rdy ( w_tx_rdy ),//uart发送模块空闲指示信号; .o_uart_txdata ( w_tx_data ),//需要uart发送的并行数据; .o_uart_txdata_vld ( w_tx_data_vld ) //需要的发送的并行数据有效指示信号; ); //例化串口发送模块; uart_tx #( .FLCK ( FLCK ),//系统时钟频率,默认50MHZ; .BPS ( BPS ),//串口波特率; .DATA_W ( UART_DATA_W ),//发送数据位数以及输出数据位宽; .START_W ( START_W ),//1位起始位; .CHECK_W ( CHECK_W ),//校验位,2'b00代表无校验位,2'b01表示奇校验,2'b10表示偶校验,2'b11按无校验处理。 .STOP_W ( STOP_W ) //停止位,2'b01表示1位停止位,2'b10表示2位停止位,2'b11表示1.5位停止位; ) u_uart_tx ( .clk ( clk_100m ),//系统工作时钟50MHZ .rst_n ( rst_n ),//系统复位信号,低电平有效 .tx_data ( w_tx_data ),//数据输入信号。 .tx_data_vld( w_tx_data_vld ),//数据有效指示信号,高电平有效。 .uart_tx ( uart_tx ),//uart接口数据输出信号。 .tx_rdy ( w_tx_rdy ) //模块忙闲指示信号; ); endmodule
3. 工程仿真
仿真的数据来源依旧使用前文ad7606驱动模块的测试数据,对应参考代码如下所示。
`timescale 1 ns/1 ns //--############################################################################################### //--# //--# File Name : test //--# Designer : 数字站 //--# Tool : Quartus 2018.1 //--# Design Date : 2024.11.3 //--# Description : //--# Version : 0.0 //--# Coding scheme : GBK(If the Chinese comment of the file is garbled, please do not save it and check whether the file is opened in GBK encoding mode) //--# //--############################################################################################### module test(); localparam CYCLE = 20 ;//系统时钟周期,单位ns,默认10ns; localparam RST_TIME = 10 ;//系统复位持续时间,默认10个系统时钟周期; reg clk = 'd1 ;//系统时钟,100MHz; reg rst_n = 'd1 ;//系统复位,高电平有效; reg busy = 'd0 ;//转换完成指示信号,下降沿有效; reg frstdata = 'd0 ;//指示采集到的第一个数据; reg [15 : 0] adc_din = 'd0 ;//AD7606所采集到的十六位数据信号; wire cs ;//AD7606片选信号,读数据时拉低; wire rd ;//AD7606读使能信号,读数据时拉低,下降沿时AD7606将数据发送到数据线上,上升沿时可以读出数据; wire reset ;//AD7606复位信号,高电平有效,每次复位至少拉高50ns; wire [2 : 0] os ;//AD7606过采样模式信号,默认不使用过采样; wire convst ;//AD7606采样启动信号,无效时高电平,采样计数器完成时拉低两个时钟; wire uart_tx ;//uart接口数据输出信号。 //待测试的模块例化 top u_top ( .clk ( clk ),//系统时钟,50MHz; .rst_n ( rst_n ),//系统复位,低电平有效; .busy ( busy ),//转换完成指示信号,下降沿有效; .frstdata ( frstdata ),//指示采集到的第一个数据; .adc_din ( adc_din ),//AD7606所采集到的十六位数据信号; .cs ( cs ),//AD7606片选信号,读数据时拉低; .rd ( rd ),//AD7606读使能信号,读数据时拉低,下降沿时AD7606将数据发送到数据线上,上升沿时可以读出数据; .reset ( reset ),//AD7606复位信号,高电平有效,每次复位至少拉高50ns; .os ( os ),//AD7606过采样模式信号,默认不使用过采样; .convst ( convst ),//AD7606采样启动信号,无效时高电平,采样计数器完成时拉低两个时钟; .uart_tx ( uart_tx ) //uart接口数据输出信号。 ); initial begin forever #(CYCLE/2) clk = ~clk;//生成本地时钟50M end //产生复位信号 localparam DATA_NUM = 400; reg [15:0] stimulus[1:DATA_NUM]; integer Pattern; initial begin $readmemb("waveform_bit.txt",stimulus);//从外部TX文件(waveform_bit.txt)读入数据作为测试激励; Pattern = 1; #1; rst_n = 1'b0; #(CYCLE*RST_TIME); rst_n = 1'b1; repeat(20)@(posedge clk); repeat(DATA_NUM)begin @(posedge convst); busy <= 1'b1; repeat(30)@(posedge clk); busy <= 1'b0; @(negedge rd); adc_din <= stimulus[Pattern]; Pattern <= Pattern + 1; @(posedge clk); end repeat(20)@(posedge clk);//延迟20个时钟周期; $stop; end endmodule
采用Quartus 18.1和modelsim联合仿真,打开modelsim仿真界面wave后,先删除该界面自动添加的信号。然后如下图所示,添加我提前设置好的波形文件wave.do。
由于TestBench给ad7606八个通道赋值相同数据,因此最终采集的数据如下图所示。
而uart_byte模块把八个通道计算结果根据含义保存到对应存储器中,同时根据计数器的值向下游模块传输数据。下图所示,每当上游模块完成一次数据转换,均会将存储器的数据刷新。
4. 上板实测
由于手里这块板子的设计问题,导致最终没办法上板,具体问题如下所示。板子上有一个uart的公头,如下所示。
如下图所示,ad7606的八个通道全部悬空,然后下载程序到板子中。
与下图译码器部分代码对比,可以验证上图的正确性。