目录
整体系统设计
一、串口接收模块
二、串口发送模块
三、按键消抖模块
四、ROM模块
本设计工程文件下载链接(包含注释):https://download.csdn.net/download/qq_33231534/12450178
本设计主要是对ADC和DAC的使用,主要实现功能流程为:首先通过串口向FPGA发送控制信号,控制DAC芯片tlv5618进行DA装换,转换的数据存在ROM中,转换开始时读取ROM中数据进行读取转换。其次用按键控制adc128s052进行模数转换100次,模数转换数据存储到FIFO中,再从FIFO中读取数据通过串口输出显示在pc上。其整体系统框图如下:
从图中可以看出,该系统主要包括9个模块:串口接收模块、按键消抖模块、按键控制模块、ROM模块、DAC驱动模块、ADC驱动模块、同步FIFO模块、FIFO控制模块、串口发送模块。各个模块的作用如下:
(1)串口接收模块(UART_Byte_Rx.v):完成串口数据接收,将串行数据转换成并行数据输出。
(2)按键消抖模块(key_filter.v):进行按键消抖,可输出一个脉冲按键按下标志和按键按下时间标志。
(3)按键控制模块(key_ctrl.v):当在DA一直输出模拟信号时,按下按键控制ADC转换100次。
(4)ROM模块(single_port_rom.v):存储DA转换的数据,可存放正弦波形数据。
(5)DAC驱动模块(dac_driver.v):数模转换驱动模块,与外部DAC芯片相连,提供DAC芯片时钟和数据信号等。
(6)ADC驱动模块(adc_driver.v):模数转换驱动模块,与外部ADC芯片相连,提供ADC芯片时钟和控制信号等。
(7)同步FIFO模块(sync_fifo.v):存放ADC转换后的数据。
(8)FIFO控制模块(fifo_ctrl.v):当FIFO中有数据时,将FIFO中的数据转换成可以UART串口发送的数据。
(9)串口发送模块(Uart_Byte_Tx.v):经过FIFO控制模块转换的数据通过串口发送模块发送到串口,显示在pc端。
(10)DAC控制模块(dac_ctrl.v):当接收串口指定的指令时,开始将ROM的正弦数据进行DAC转换。
前面已经写过串口接收模块,这里可以直接拿来用。这里串口发送数据格式包含:1位起始位、8位数据位、1位停止位,无校验位。该模块接口列表入下:
信号名称 | I/O | 位数 | 功能描述 |
clk | I | 1 | 系统时钟50MHz |
rst_n | I | 1 | 系统复位 |
rs232_tx | I | 1 | 串口串行数据发送数据口 |
baud_set | I | 3 | 波特率选择信号 |
data_byte | O | 8 | 并行数据输出 |
rx_done | O | 1 | 接收1字节数据完成标志 |
代码如下:UART_Byte_Rx.v
//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN
//File name: UART_Byte_Rx.v
//Last modified Date: 2020/5/22
//Last Version:
//Descriptions: 工业级别串口数据接收模块,防干扰。对每位数据内部采样16个点,
// 对中间6位数据进行判定数据是1还是0
//-------------------------------------------------------------------
module UART_Byte_Rx(
input clk ,//系统时钟50MHz
input rst_n ,//系统复位
input rs232_tx ,//串口串行数据发送数据口
input [ 2: 0] baud_set ,//波特率选择信号
output reg [ 7: 0] data_byte ,//并行数据输出
output reg rx_done //接收1字节数据完成标志,rx_done可以作为输出有效信号使用
);
reg [ 13: 0] baud_c ;//波特率对应计数次数(4800bps-10416),(9600bps-5208),(19200bps-2604),
//(38400bps-1302),(57600bps-868),(115200bps-434)
reg rs232_tx_ff0 ;
reg rs232_tx_ff1 ;
reg rs232_tx_ff2 ;
wire tx_neg_flag ;
reg add_flag ;
reg [ 13: 0] cnt0 ;
reg [ 3: 0] cnt1 ;
reg [ 9: 0] cnt2 ;
reg [ 3: 0] cnt3 ;
reg [ 2: 0] cnt_0 ;
reg [ 2: 0] cnt_1 ;
wire add_cnt0 ;
wire end_cnt0 ;
wire add_cnt1 ;
wire end_cnt1 ;
wire add_cnt2 ;
wire end_cnt2 ;
wire add_cnt3 ;
wire end_cnt3 ;
//查找表
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
baud_c <= 5208;
end
else begin
case(baud_set)
0: baud_c = 14'd10416;
1: baud_c = 14'd5208 ;
2: baud_c = 14'd2604 ;
3: baud_c = 14'd1302 ;
4: baud_c = 14'd868 ;
5: baud_c = 14'd434 ;
default:baud_c = 14'd5208 ;//默认9600bps
endcase
end
end
//打两拍 防止亚稳态,同时scan negedge
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rs232_tx_ff0 <= 1;
rs232_tx_ff1 <= 1;
rs232_tx_ff2 <= 1;
end
else begin
rs232_tx_ff0 <= rs232_tx;
rs232_tx_ff1 <= rs232_tx_ff0;
rs232_tx_ff2 <= rs232_tx_ff1;
end
end
//扫描下降沿
assign tx_neg_flag = rs232_tx_ff2 && !rs232_tx_ff1;
//计数标志信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
add_flag <= 0;
end
else if(tx_neg_flag) begin
add_flag <= 1;
end
else if(rx_done)begin
add_flag <= 0;
end
end
//计数器,计数1bit数据长度
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(add_cnt0)begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1'b1;
end
end
assign add_cnt0 = add_flag;
assign end_cnt0 = add_cnt0 && cnt0==baud_c-1;
//计数器,计数8位接收数据长度
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(add_cnt1)begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1'b1;
end
end
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1== 8;
//比特内部采样点时钟计数
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt2 <= 0;
end
else if(add_cnt2)begin
if(end_cnt2)
cnt2 <= 0;
else
cnt2 <= cnt2 + 1'b1;
end
end
assign add_cnt2 = add_flag;
assign end_cnt2 = add_cnt2 && (cnt2== (baud_c/16)-1 || end_cnt0);
//一个bit数据中16个采样点计数
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt3 <= 0;
end
else if(add_cnt3)begin
if(end_cnt3)
cnt3 <= 0;
else
cnt3 <= cnt3 + 1'b1;
end
end
assign add_cnt3 = add_cnt2 && cnt2== (baud_c/16)-1;
assign end_cnt3 = end_cnt0 || (end_cnt2 && cnt3==16-1);
//比特内选取6个采样点是0或1计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
cnt_0 <= 0;
cnt_1 <= 0;
end
else if(add_flag) begin
if(cnt3>=6 && cnt3<=11)begin
if(cnt2==baud_c/16/2 && rs232_tx_ff1==0)
cnt_0 <= cnt_0 + 1'b1;
else if(cnt2==baud_c/16/2 && rs232_tx_ff1==1)
cnt_1 <= cnt_1 + 1'b1;
end
else if(end_cnt0)begin
cnt_0 <= 0;
cnt_1 <= 0;
end
end
else begin
cnt_0 <= 0;
cnt_1 <= 0;
end
end
//输出并行数据data_byte
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
data_byte <= 0;
end
else if(end_cnt0 && cnt1>0 && cnt1 <9) begin
if(cnt_0 >= cnt_1)
data_byte[cnt1-1] = 0;
else if(cnt_0 < cnt_1)
data_byte[cnt1-1] = 1;
end
end
//输出接收完成标志信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_done <= 0;
end
else if(end_cnt1) begin
rx_done <= 1;
end
else begin
rx_done <= 0;
end
end
endmodule
其仿真图形如下:
这里和串口接收模块对应,发送的是8位数据,外加1位起始位和1位停止位。该模块接口信号列表如下:
信号名称 | I/O | 位数 | 功能描述 |
clk | I | 1 | 系统时钟50MHz |
rst_n | I | 1 | 系统复位 |
send_en | I | 1 | 发送使能 |
data_byte | I | 8 | 发送的数据 |
baud_set | I | 3 | 波特率设置 |
rs232_tx | O | 1 | FPGA将数据转换成串行数据发出 |
tx_done | O | 1 | 发送数据完毕标志 |
uart_state | O | 1 | 串口发送状态,1为忙,0为空闲 |
代码如下:Uart_Byte_Tx.v
//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 phf的CSDN
//File name: Uart_Byte_Tx.v
//Last modified Date: 2020/5/22
//Last Version:
//Descriptions: 串口发送模块,8位数据位、1位起始位和1位停止位、无校验位
//-------------------------------------------------------------------
module Uart_Byte_Tx(
input clk , //系统时钟
input rst_n , //系统复位
input send_en , //发送使能
input [ 7 : 0 ] data_byte , //发送的数据
input [ 2 : 0 ] baud_set , //波特率设置
output reg rs232_tx , //FPGA将数据转换成串行数据发出
output reg tx_done , //发送数据完毕标志
output reg uart_state //串口发送状态,1为忙,0为空闲
);
reg [ 13: 0] baud_c ;//(4800bps-10416),(9600bps-5208),(19200bps-2604),
//(38400bps-1302),(57600bps-868),(115200bps-434)
wire [ 9: 0] data_out ;
reg [ 15: 0] cnt0 ; //1bit数据长度计数
reg [ 3: 0] cnt1 ; //发送一字节数据对每个字节计数
wire add_cnt0 ; //计数器cnt0加一条件
wire add_cnt1 ; //计数器cnt1加一条件
wire end_cnt0 ; //计数器cnt0结束条件
wire end_cnt1 ; //计数器cnt1结束条件
reg [ 7: 0] data_byte_ff ; //发送使能时将发送的数据寄存下来
//波特率查找表
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
baud_c <= 5208;
end
else begin
case(baud_set)
0: baud_c = 14'd10416;
1: baud_c = 14'd5208 ;
2: baud_c = 14'd2604 ;
3: baud_c = 14'd1302 ;
4: baud_c = 14'd868 ;
5: baud_c = 14'd434 ;
default:baud_c = 14'd5208 ;//默认9600bps
endcase
end
end
//串口状态标志,0为空闲,1为忙
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
uart_state <= 0;
end
else if(send_en) begin
uart_state <= 1;
end
else if(end_cnt1)begin
uart_state <= 0;
end
else begin
uart_state <= uart_state;
end
end
//1bit数据长度计数
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(add_cnt0)begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1'b1;
end
end
assign add_cnt0 = uart_state==1;
assign end_cnt0 = add_cnt0 && cnt0== baud_c-1;
//发送一字节数据对每个字节计数
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(add_cnt1)begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1'b1;
end
end
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1== 10-1;
//串口发送结束标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
tx_done <= 0;
end
else if(end_cnt1) begin
tx_done <= 1;
end
else begin
tx_done <= 0;
end
end
//发送使能时将发送的数据寄存下来
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
data_byte_ff <= 0;
end
else if(send_en) begin
data_byte_ff <= data_byte;
end
end
//发送串行数据到串口
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rs232_tx <= 1;
end
else if(uart_state && cnt0==0) begin
rs232_tx <= data_out[cnt1];
end
end
assign data_out = {1'b1,data_byte_ff,1'b0};
endmodule
仿真波形如下:
这是使用按键必须的模块。其信号列表入下,此模块可通用。
信号名称 | I/O | 位数 | 功能描述 |
clk | I | 1 | 系统时钟50MHz |
rst_n | I | 1 | 系统复位 |
key_in | I | 1 | 按键输入 |
key_flag | O | 1 | 输出一个脉冲按键有效信号 |
key_state | O | 1 | 输出按键状态,1为未按下,0为按下 |
代码如下:key_filter.v
//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 潘洪峰的CSDN博客
//File name: key_filter.v
//Last modified Date: 2020/5/22
//Last Version:
//Descriptions: 按键消抖模块
//-------------------------------------------------------------------
module key_filter(
input clk ,//系统时钟50MHz
input rst_n ,//系统复位
input key_in ,//按键输入
output reg key_flag ,//输出一个脉冲按键有效信号
output reg key_state //输出按键状态,1为未按下,0为按下
);
parameter IDLE = 4'b0001 ;//空闲状态,读取按键按下的下降沿,读取到下降沿转到下一个状态
parameter FILTER1 = 4'b0010 ;//计数20ms状态,计数结束转到下一个状态
parameter STABLE = 4'b0100 ;//数据稳定状态,等待按键松开上升沿,读取到上升沿转到下一个状态
parameter FILTER2 = 4'b1000 ;//计数20ms状态,计数结束转到空闲状态
parameter TIME_20MS = 20'd1000_000 ;
reg [ 3: 0] state_c ;//寄存器改变状态
reg [ 3: 0] state_n ;//现在状态
wire IDLE_to_FILTER1 ;//IDLE状态转到FILTER1状态条件
wire FILTER1_to_STABLE;//FILTER1状态转到STABLE状态条件
wire STABLE_to_FILTER2;//STABLE状态转到FILTER2状态条件
wire FILTER2_to_IDLE ;//FILTER2状态转到IDLE状态条件
reg key_in_ff0 ;
reg key_in_ff1 ;
reg key_in_ff2 ;
wire key_in_pos ;//检测上升沿标志
wire key_in_neg ;//检测下降沿标志
reg [ 19: 0] cnt ;
wire add_cnt ;
wire end_cnt ;
//状态机第一段,状态转换
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//状态机第二段,状态转换条件
always @(*)begin
case(state_c)
IDLE :begin
if(IDLE_to_FILTER1)
state_n = FILTER1;
else
state_n = state_c;
end
FILTER1:begin
if(FILTER1_to_STABLE)
state_n = STABLE;
else
state_n = state_c;
end
STABLE :begin
if(STABLE_to_FILTER2)
state_n = FILTER2;
else
state_n = state_c;
end
FILTER2:begin
if(FILTER2_to_IDLE)
state_n = IDLE;
else
state_n = state_c;
end
default:state_n = IDLE;
endcase
end
//状态转换条件
assign IDLE_to_FILTER1 = key_in_neg ;
assign FILTER1_to_STABLE = state_c==FILTER1 && end_cnt;
assign STABLE_to_FILTER2 = key_in_pos ;
assign FILTER2_to_IDLE = state_c==FILTER2 && end_cnt;
//打两拍,防止亚稳态
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_in_ff0 <= 1;
key_in_ff1 <= 1;
key_in_ff2 <= 1;
end
else begin
key_in_ff0 <= key_in;
key_in_ff1 <= key_in_ff0;
key_in_ff2 <= key_in_ff1;
end
end
//下降沿和上升沿检测
assign key_in_pos = (state_c==STABLE) ?(key_in_ff1 && !key_in_ff2):1'b0;
assign key_in_neg = (state_c==IDLE) ?(!key_in_ff1 && key_in_ff2):1'b0;
//计数20ms
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1'b1;
end
else begin
cnt <= 0;
end
end
assign add_cnt = state_c==FILTER1 || state_c==FILTER2;
assign end_cnt = add_cnt && cnt== TIME_20MS-1;
//key_flag按键按下输出一个脉冲信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_flag <= 0;
end
else if(state_c==FILTER1 && end_cnt) begin
key_flag <= 1;
end
else begin
key_flag <= 0;
end
end
//key_state按键按下状态信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_state <= 1;
end
else if(state_c==STABLE || state_c==FILTER2) begin
key_state <= 0;
end
else begin
key_state <= 1;
end
end
endmodule
这里先跳过按键控制模块,先将一些基本模块叙述完,再叙述控制模块。
ROM模块是一个只读存储器,有三种方法编写这个模块。
(1)使用quartus具有的IP核(我用的是quartus prime 17.1),选择1-PORT,进行参数设置
(2)新建一个verilog HDL文件,选择Edit—>Insert Template,按照下图选择可直接生成模板,可直接使用。
(3)自己编写一个ROM文件,自己编写的和(2)方法生成代码一样,可设定数据位宽和地址宽度。
注意代码中ROM初始化语句:$readmemb("./sin_12bit.txt", rom);通过读取外部文件进行初始化,初始化文件可以自己手打或者Excel或者matlab生成。其中$readmemb("./sin_12bit.txt", rom);语句用法在本人博客下也有介绍,需要可以去看看。
其生成的verilog文件如下:single_port_rom.v
module single_port_rom
#(parameter DATA_WIDTH=12, parameter ADDR_WIDTH=12)
(
input [(ADDR_WIDTH-1):0] addr,
input clk,
output reg [(DATA_WIDTH-1):0] q
);
// Declare the ROM variable
reg [DATA_WIDTH-1:0] rom[2**ADDR_WIDTH-1:0];
initial
begin
$readmemb("./sin_12bit.txt", rom);
end
always @ (posedge clk)
begin
q <= rom[addr];
end
endmodule
里边的数据是我在matlab上生成的sin函数12位波形文件。可以看到其在clk上升沿时读出对应地址的数据。