前言
由于项目制作时间有限,考虑到改变方案的风险,我们在遇到许多问题时并没有选择改变路线,而是在现有成果上缝缝补补,造就了现在看来十分笨重的通信模块,不过错误也是宝贵的学习经验,对于电子领域的工作者更是如此,因而笔者保留了我们制作时的失误和思考历程,供广大读者参考借鉴。
总体思路
一般FPGA不适合作为一个完整系统,因为FPGA更擅长流水处理,而不擅长控制,并且资源有限,像DDS信号发生器这种需要多个IP核的项目,on chip memory很容易写满。因而我们选择使用MCU作为控制端,一方面减轻FPGA负担,另一方面可以利用MCU的OLED外设提供用户交互界面。
FPGA需要从MCU接收10k到10M的载波信号频率,1k到5k调制信号频率,20到100调幅系数,10到20调频系数,五种波形选择,总计五个数据。分别对应24位、13位,8位,7位,3位二进制数。我们选用的UART配置为9600的波特率,一次发送一个起始位,八个数据位,一个终止位,无校验位。
我们在初步测试时,利用singal tap发现接收FPGA接收到许多杂乱无章的数据(后来发现时MCU发送端接错了管脚。。。),当时初步判断是噪声干扰,因而在之后的绝大部分工作都花在了排除噪声上。当时已经写好的FPGA接收代码和MCU发送代码都没有加校验位,因而我们提出的排除噪声的方案是:FPGA添加一个FIFO模块用来暂时储存MCU发送过来的数据,MCU在用户输入了所需的所有数据后,将所有数据打包成十个八位码元连续不间断发送,这十个码元中只有中间八个是数据位(24位的载波信号频率需要三个八位码元发送,一次类推,五种数据共需要八个码元),前后两位都固定发送0xff,当且仅当FPGA接收到首位都位0xff的数据时,才进行拆包,一点出现噪声,首位的数据将不再是0xff,如此就有效避免了噪声的干扰。
FPGA接收部分
UART接收部分
Verilog代码
端口及变量定义
module uart1(clk,rst,uart_rx,r_rx_data,rx_done);
input clk;
input rst;
input uart_rx;//输入信号
output reg rx_done;//接收完成标志
output reg [7:0]r_rx_data;//接收到的数据
parameter clk_fre = 50000000;
parameter baud = 9600;//接受信号波特率
parameter MCNT_BAUD = clk_fre / baud - 1;//波特率计数最大值
reg [29:0]baud_div_cnt;//波特率计数
reg en_baud_cnt;//波特率计数使能
reg [3:0]bit_cnt;//位计数
reg [7:0]rx_data;//接收数据暂存
reg r_uart_rx;//最终接收数据
reg dff0_uart_rx;
reg dff1_uart_rx;//打拍
wire negedge_uart_rx;//下降沿标志,用于检测数据起始位
wire w_rx_done;//接收完成标志
波特率计数模块
always @(posedge clk)
dff0_uart_rx <= uart_rx;
always @(posedge clk)
dff1_uart_rx <= dff0_uart_rx;//若在时钟上升沿附近uart_rx触发则会出现亚稳态问题,故进行两次同步,以将uart_rx同步到clk时钟域上,俗称打拍
always @(posedge clk)
r_uart_rx <= dff1_uart_rx;//相当于一个D触发器,暂存当前状态
assign negedge_uart_rx = ((dff1_uart_rx == 0) && (r_uart_rx == 1));
打拍及下降沿判断
always @(posedge clk or negedge rst)//波特率计数模块
if(!rst)
baud_div_cnt <= 0;
else if(en_baud_cnt)
begin
if(baud_div_cnt == MCNT_BAUD)
baud_div_cnt <= 0;
else
baud_div_cnt <= baud_div_cnt + 1'd1;
end
else
baud_div_cnt <= 0;
波特率计数使能模块
always @(posedge clk or negedge rst)
if(!rst)
en_baud_cnt <= 0;
else if(negedge_uart_rx)
en_baud_cnt <= 1;
else if((baud_div_cnt == MCNT_BAUD/2) && (bit_cnt == 0) && (dff1_uart_rx == 1))//如果是毛刺则停止计数
en_baud_cnt <= 0;
else if((baud_div_cnt == MCNT_BAUD) && (bit_cnt == 9))//计数完成清零
位计数模块
always @(posedge clk or negedge rst)//位计数器模块
if(!rst)
bit_cnt <= 0;
else if(baud_div_cnt == MCNT_BAUD)
begin
if(bit_cnt == 9)
bit_cnt <= 0;
else
bit_cnt <= bit_cnt + 1'd1;
end
位接收模块
always @(posedge clk or negedge rst)//位接受逻辑
if(!rst)
rx_data <= 8'd0;
else if(baud_div_cnt == MCNT_BAUD/2)
begin
case(bit_cnt)
1:rx_data[0] <= dff1_uart_rx;
2:rx_data[1] <= dff1_uart_rx;
3:rx_data[2] <= dff1_uart_rx;
4:rx_data[3] <= dff1_uart_rx;
5:rx_data[4] <= dff1_uart_rx;
6:rx_data[5] <= dff1_uart_rx;
7:rx_data[6] <= dff1_uart_rx;
8:rx_data[7] <= dff1_uart_rx;
//8:rx_data[8] <= dff1_uart_rx;
default:rx_data<=rx_data;
endcase
end
接收完成逻辑
assign w_rx_done = (baud_div_cnt == MCNT_BAUD) && (bit_cnt == 9);
always @(posedge clk)//接受完成标志信号
rx_done <= w_rx_done;
always @(posedge clk)
if(w_rx_done)
r_rx_data<=rx_data;
串口仿真
用test bench写的,大概修改了一下
timescale 1 ns/ 1 ns
module uart1_vlg_tst();
// constants
// general purpose registers
reg eachvec;
// test vector input registers
reg clk;
reg rst;
reg uart_rx;
// wires
wire [7:0] rx_data;
wire rx_done;
// assign statements (if any)
uart1 i1 (
// port map - connection between master ports and signals/registers
.clk(clk),
.rst(rst),
.rx_data(rx_data),
.rx_done(rx_done),
.uart_rx(uart_rx)
);
initial clk = 1;
always #10 clk = ~clk;
initial
begin
clk = 1;
rst = 0;
uart_rx = 1;
#201;
rst = 1;
#200;
//8'b01010101
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 1; #(5208*20);
#(5208*20*10);
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 1; #(5208*20);
#(5208*20*10);
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 1; #(5208*20);
#(5208*20*10);
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 0; #(5208*20);
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 1; #(5208*20);
#(5208*20*10);
uart_rx = 0; #(5208*20);
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 0; #(5208*20);
uart_rx = 1; #(5208*20);
uart_rx = 1; #(5208*20);
#(5208*20*10);
// code that executes only once
// insert code here --> begin
// --> end
$display("Running testbench");
end
/*always
// optional sensitivity list
// @(event1 or event2 or .... eventn)
begin
// code executes for every event on sensitivity list
// insert code here --> begin
@eachvec;
// --> end
end */
endmodule
仿真波形
FIFO IP核
这篇文章中有非常详细的IP核配置教程,我们选用普通单时钟模式
端口定义以及变量使用
module decide
(
input clk,
input rst,
input uart_tx, // 输入信号
input data_valid,
output reg [23:0] car_wave_fre, // 10k-10M载波信号频率
output reg [12:0] mod_wave_fre, // 1k-5k调制信号频率
output reg [7:0] ma, // 20-100调幅系数
output reg [6:0] kf, // 10-50调频系数
output reg [2:0] wave_select // 信号选择
);
uart_rx uart_rx_inst
(
.clk(clk),
rst_n(rst),
.TX(data),
.RX(uart_rx),
.done(data_valid),
);
// FIFO实例化
wire rdreq, wrreq, empty, full;
wire [7:0] q;
wire [4:0] usedw;
wire [7:0] data;//输入FPGA的数据
FIFO FIFO_normal_inst (
.clock(clk),
.data(uart_tx),
.rdreq(rdreq), //读使能
.wrreq(!empty && !full), // 写入使能
.empty(empty), //读空标志
.full(full), //写满标志
.q(q),
.usedw(usedw) //当前存储了多少字符
);
reg [7:0] buffer[9:0]; // 用于储存数据
reg [1:0] index; // 用于追踪当前储存的数据在数组中的位置
reg packet_start; // 检测到0xff,开始
reg valid_packet;
具体代码
always @(posedge clk or posedge rst)
begin
if (rst)
begin
index <= 0;
packet_start <= 0;
valid_packet <= 0;
end else
begin
// 接收了十个数据时进行检测
if (index == 9)
begin
if (buffer[0] == 8'hff && buffer[9] == 8'hff)
begin
valid_packet <= 1; // Valid packet detected
car_wave_fre <= {buffer[1],buffer[2],buffer[3]};
mod_wave_fre <= {buffer[4],buffer[5]};
ma <= buffer[6];
kf <= buffer[7];
wave_select <= buffer[8];
end else
begin
valid_packet <= 0; // 如果不满足规定的首位都为0xff的条件,停止传输
end
index <= 0;
packet_start <= 0;
end else if (data_valid)
begin
if (!packet_start && uart_tx == 8'hff)
begin
packet_start <= 1; // 有效数据检测到
index <= 0;
end
if (packet_start)
begin
buffer[index] <= q; // 将数据存在数组中
index <= index + 1; // 当前数组位数
end
end
end
end
endmodule
MCU发送部分
本项目使用的是MSPM01306单片机
sysconfig配置
需要主义的一点,在Advanced Configuration中Oversampling选择16x保证传输精度,并且选择PA11,PA10这一组管脚
发送代码
由于主程序中涉及到OLED显示等库函数,非笔者所写,故在此仅分享发送函数和矩阵键盘函数
发送函数。值得注意的是,UART只能发送二进制数,故我们需要将用户输入的十进制数先转化为二进制数,并且将其分为若干段八位二进制数再进行发送,为了节省CPU资源(曾经我也认为MCU不需要节省CPU资源,直到有一次我在代码里写了指针,编译花了整整一分钟),我们使用逻辑右移运算符,并且与0xff进行位与运算,由此得到我们想要的八位数据
void transmit()
{
unsigned char byte1 = (csfre >> 16) & 0xFF; // 次高8位
unsigned char byte2 = (csfre >> 8) & 0xFF; // 次低8位
unsigned char byte3 = csfre & 0xFF; // 最低8位
unsigned char byte4 = (msfre >> 8) & 0xFF; // 次低8位
unsigned char byte5 = msfre & 0xFF; // 最低8位
unsigned char byte6 = ma & 0xFF; // 最低8位
unsigned char byte7 = kf & 0xFF; // 最低8位
unsigned char byte8 = wave_select & 0xFF;
// 最低8位
DL_UART_Main_transmitData(UART1,0xff);
delay(1);
DL_UART_Main_transmitData(UART1,byte1);
delay(1);
DL_UART_Main_transmitData(UART1,byte2);
delay(1);
DL_UART_Main_transmitData(UART1,byte3);
delay(1);
DL_UART_Main_transmitData(UART1,byte4);
delay(1);
DL_UART_Main_transmitData(UART1,byte5);
delay(1);
DL_UART_Main_transmitData(UART1,byte6);
delay(1);
DL_UART_Main_transmitData(UART1,byte7);
delay(1);
DL_UART_Main_transmitData(UART1,byte8);
delay(1);
DL_UART_Main_transmitData(UART1,0xff);
delay(1);
}
delay函数。时钟频率为32M
void delay(int x)
{
delay_cycles(CLK_HZ / 1000 * x);
}
矩阵键盘函数
uint32_t Key()
{
uint8_t a =15;
static uint8_t flag = 0;
if (flag)
{
delay(300);
flag = 0;
}
DL_GPIO_clearPins(MAT_PORT, MAT_ROW1_PIN);
DL_GPIO_setPins(MAT_PORT, MAT_ROW2_PIN |MAT_ROW3_PIN | MAT_ROW4_PIN);
delay(10);
if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL1_PIN)))
{
a = 1;
flag = 1;
return a;
}
else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL2_PIN)))
{
a = 2;
flag = 1;
return a;
}
else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL3_PIN)))
{
a = 3;
flag = 1;
return a;
}
else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL4_PIN)))
{
a = 4;
flag = 1;
return a;
}
DL_GPIO_clearPins(MAT_PORT, MAT_ROW2_PIN);
DL_GPIO_setPins(MAT_PORT, MAT_ROW1_PIN |MAT_ROW3_PIN | MAT_ROW4_PIN);
delay(10);
if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL1_PIN)))
{
a = 5;
flag = 1;
return a;
}
else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL2_PIN)))
{
a = 6;
flag = 1;
return a;
}
else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL3_PIN)))
{
a = 7;
flag = 1;
return a;
}
else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL4_PIN)))
{
a = 8;
flag = 1;
return a;
}
// Row 4
DL_GPIO_clearPins(MAT_PORT, MAT_ROW3_PIN);
DL_GPIO_setPins(MAT_PORT, MAT_ROW1_PIN |MAT_ROW2_PIN | MAT_ROW4_PIN);
delay(10);
if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL1_PIN)))
{
a = 9;
flag = 1;
return a;
}
else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL2_PIN)))
{
a = 10;
flag = 1;
return a;
}
else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL3_PIN)))
{
a = 11;
flag = 1;
return a;
}
else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL4_PIN)))
{
a = 12;
flag = 1;
return a;
}
DL_GPIO_clearPins(MAT_PORT, MAT_ROW4_PIN);
DL_GPIO_setPins(MAT_PORT, MAT_ROW1_PIN |MAT_ROW2_PIN | MAT_ROW3_PIN);
delay(10);
if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL1_PIN)))
{
a = 13;
flag = 1;
return a;
}
else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL2_PIN)))
{
a = 14;
flag = 1;
return a;
}
else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL3_PIN)))
{
a = 15;
flag = 1;
return a;
}
else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL4_PIN)))
{
a = 0;
flag = 1;
return a;
}
return a;
}
矩阵键盘sysconfig配置
行设置,我们只需要将PORT设置为PORTA,Direction设为output,initial value设为set,并在Assigned Pin中配置相应管脚
列设置,仅需要将Direction改为intput,并配置相应管脚
标签:DL,20,MAT,FPGA,DDS,rx,uart,5208,MCU From: https://blog.csdn.net/m0_74180999/article/details/140186071