基于MCU和FPGA的DDS信号发生器——MCU与FPGA通信部分

avatar
作者
猴君
阅读量:0

前言

由于项目制作时间有限,考虑到改变方案的风险,我们在遇到许多问题时并没有选择改变路线,而是在现有成果上缝缝补补,造就了现在看来十分笨重的通信模块,不过错误也是宝贵的学习经验,对于电子领域的工作者更是如此,因而笔者保留了我们制作时的失误和思考历程,供广大读者参考借鉴。

总体思路

一般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核

http://t.csdnimg.cn/g1y0F

这篇文章中有非常详细的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,并配置相应管脚

结语

 现在看来,UART所使用的TTL电平已经可以有效的排除噪声的干扰,也就是说,将所有数据收集好后再进行发送是十分多余的,这使得用户想改变一种数据时都要重新输入所有数据。希望读者阅读完这篇文章后能够有所启发,设计出更为简单高效的通信系统

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!