【LabVIEW FPGA图形化】IP集成节点:串口通信

一、前情提要

上一节内容介绍了ngc、edf文件导入LabVIEW,Labview IP集成节点导入与编写方法,基本的按键控制流水灯,以及LabVIEW图形化的优点,本节内容承前节,对验证嵌入式中最基础的通信功能——串口通信。

【LabVIEW FPGA图形化】 ngc、edf网表文件的编写:LED流水灯
【LabVIEW FPGA图形化】 IP集成节点:按键控制LED

二、FPGA蔡氏定律

FPGA有两条蔡氏定律:
1、FPGA不仅仅是FPGA。
2、FPGA的最终目的是做出可用的电路。

FPGA的学习脱离不了硬件电路,但协议篇却并不要求外部的硬件电路,或者说外部的硬件电路如CP2102那些只是为了USB转TTL,方便电脑通信,对FOGA的逻辑没有实质影响。同理,外部用CH340、SP3232、或者转换成485、CAN通信都是可以做到的。

黑金ALINX在硬件上也提供了USB转TTL的驱动芯片:

CP2102驱动电路

开发板包含了Silicon Labs CP2102GM的USB-UAR芯片, USB接口采用MINI USB接口,可以用一根USB线将它连接到上PC的USB口进行串口数据通信 。
USB转串口的原理图
有了这个芯片,开发板就能通过MicroUSB与电脑进行通信:
在这里插入图片描述
当然了CP2102比较容易烧,要是自己做项目的话,建议用CH340更可靠一点。

串口通信上面也有指示灯,指示当前的通信状态,比较直观。

三、LabVIEW FPGA IP集成节点网表文件的编写

编写网表,首先我们脑子里要有电路,也就是常常说的RTL视图,知道哪个端口输入和哪个端口输出是比较关键的,对于串口通信,不用想,一个串口输入模块、一个串口输出模块,如果要实现环回功能的话,还应该有一个环回模块。RTL视图:
在这里插入图片描述
原谅我偷懒直接生成截图的,没有在Visio里边画出来,写程序的时候,我是直接画在纸上的,这个图也勉强能看、大意是接收模块接收串口发送的数据后,给环回模块,环回模块拿到数据后进行存储,当发送模块空闲时将数据给发送模块,构成一个环回。当然了,去掉串口环回模块之后,可以直接当作普通的串口环回模块使用。这个我们下一节会讲。
RTL视图反映出硬件之间的连接关系,有了这个,我们可以很清楚的定义输入和输出变量,根据每个变量之间的关系写出对应模块:
以下是串口接收模块

module uart_recv(
    input clk,
    input rst_n,
    input uart_rxd,
    output reg [7:0] uart_data,
    output reg uart_done
	
//	output reg rx_flag,
//	output reg uart_rxd_d0,
//	output reg uart_rxd_d1,
//	output reg [7:0] index
	
);

parameter CLK_FREQ= 50000000;
parameter UART_BAUD= 115200;
localparam UART_BAUD_CNT=CLK_FREQ/UART_BAUD;
reg [15:0] cnt;
reg baud_pulse;
reg [7:0] index;
reg uart_rxd_d0;
reg uart_rxd_d1;
reg	rx_flag;



always @(posedge clk or negedge rst_n) begin	//捕捉时钟下降沿  可以寄存两拍的值 
    if(!rst_n) begin
    uart_rxd_d0<=1'b0;
    uart_rxd_d1<=1'b0;
    end
    else begin
    uart_rxd_d0<=uart_rxd;
    uart_rxd_d1<=uart_rxd_d0;
    end
end

always @(posedge clk or negedge rst_n) begin	//起始信号使能接收标志 
    if(!rst_n) 
	rx_flag<=1'b0;
	else if(index==9&&cnt==UART_BAUD_CNT/2)
	rx_flag<=1'b0;
	else if(uart_rxd_d0==0&&uart_rxd_d1==1'b1)//judge negedge
	rx_flag<=1'b1;
	else 
	rx_flag<=rx_flag;
end
	

always @(posedge clk or negedge rst_n) begin	//rx_flag有效 计数值加一 
    if(!rst_n)
       cnt<=1'b0;
     else if(rx_flag&&cnt!=UART_BAUD_CNT)
	 cnt<=cnt+1'b1;
	 else
	 cnt<=1'b0;
end
       
        
always @(posedge clk or negedge rst_n) begin	//波特率时间到 开始处理数据 辅助标志
    if(!rst_n)
	baud_pulse<=1'b0;
    else if(cnt==UART_BAUD_CNT)begin
    baud_pulse<=1'b1;
    end
    else begin
    baud_pulse<=1'b0;
    end
end

always @(posedge clk or negedge rst_n) begin	//波特率计数 0起始位  9停止位
    if(!rst_n)
    index<=1'b0;
	else if(rx_flag==0)	//清零在标志位拉低完成
    index<=1'b0;
    else if(rx_flag==1&&baud_pulse==1'b1)	
		index<=index+1'b1;
    else 
		index<=index;
end

always @(posedge clk or negedge rst_n) begin	//停止位拉高输出 下一级可读取
    if(!rst_n)
    uart_done<=1'b0;
    else if(index==8'd9&&cnt==2)
    uart_done<=1'b1;
    else
    uart_done<=1'b0;
   end




always @(posedge clk or negedge rst_n) begin	//将1-8 index 放入 0-7 uart——data数据暂存 done拉高后可一并读出
    if(!rst_n)
    uart_data<=8'b0;
    else if(cnt==UART_BAUD_CNT/2 && index>0 && index<9)begin  //从稳定状态读取比较稳妥  亚稳态用寄存两拍的值
    uart_data[index-1]<=uart_rxd_d1;//要保证原始异步脉冲信号至少要保证两个周期
    end
    else begin
    uart_data[index]<=uart_data[index];
    end
end



endmodule


有的初学者理解不了,串口通信的本质在接收端其实是将串行的数据转换成并行的一个过程,8位数据准备好了,用握手协议给下一级,需要特别注意的地方就是需要根据波特率调整分频而已。
发送模块的代码如下:

module uart_send(
	input 		clk,
	input 		rst_n,
	input [7:0]	uart_in,
	input 		uart_en,
	output reg tx_flag,
	output 	reg 	uart_txd
);



reg [7:0] uart_temp;

parameter CLK_FREQ=50000000;
parameter UART_BPS=115200;
localparam uart_cnt=CLK_FREQ/UART_BPS;
reg [31:0] cnt;
reg	[7:0]	index;


always @(posedge clk or negedge rst_n) begin	//发送开始工作标志
	if(!rst_n) 
	tx_flag<=1'b0;
	else if(uart_en)
	tx_flag<=1'b1;
	else if(index==8'd10)
	tx_flag<=1'b0;
	else
	tx_flag<=tx_flag;
end
	

always @(posedge clk or negedge rst_n) begin	//先将数据读进来
	if(!rst_n) 
	uart_temp<=8'd0;
	else if(uart_en==1'b1)
	uart_temp<=uart_in;
	else if(tx_flag==1'b0)
	uart_temp<=8'd0;
	else
	uart_temp<=uart_temp;
end

always @(posedge clk or negedge rst_n) begin	//产生串口时钟
	if(!rst_n) 
	cnt<=1'b0;
	else if(tx_flag) begin
        if(cnt==uart_cnt)
        cnt<=1'b0;
        else
        cnt<=cnt+1'b1;
	end
	else
	cnt<=1'b0;
end	
	
always @(posedge clk or negedge rst_n) begin	//输出索引+1
	if(!rst_n) 
	index<=8'd0;
	else if(tx_flag==0)
	index<=1'b0;
	else if(cnt==uart_cnt-1)
	index<=index+1'b1;
	else 
	index<=index;
end

always @(posedge clk or negedge rst_n) begin	//输出数据
	if(!rst_n) 
	uart_txd<=1'b1;
	else if(tx_flag) begin
		case(index)
			4'd0 :	uart_txd<=1'b0			;
			4'd1 :	uart_txd<=uart_temp[0]	;
			4'd2 :	uart_txd<=uart_temp[1]	;
			4'd3 :	uart_txd<=uart_temp[2]	;
			4'd4 :	uart_txd<=uart_temp[3]	;
			4'd5 :	uart_txd<=uart_temp[4]	;
			4'd6 :	uart_txd<=uart_temp[5]	;
			4'd7 :	uart_txd<=uart_temp[6]	;
			4'd8 :	uart_txd<=uart_temp[7]	;
	        4'd9 :	uart_txd<=1'b1			;
			default: ;
		endcase
	end
	else
	uart_txd<=1'b1;
end	


endmodule

发送模块的工作原理和接收模块刚好相反,将需要发送的并行数据转换成串行一位一位的发送,这样就不难理解,他们的速率不一样需要匹配的问题了。
环回模块像是关联接收和发送,进行握手的,代码如下:

module uart_loop(
	input 			clk,
	input 			rst_n,
	input 			recv_done,
	input 			[7:0]recv_data,
	input 			tx_busy,
	output 	reg 	[7:0]send_data,
	output 	reg		send_en

);
	
reg [7:0]temp_data;
reg rdy_send;	

reg recv_done_d0;
reg recv_done_d1;

assign recv_done_flag=recv_done_d0&(~recv_done_d1);

always @(posedge clk or negedge rst_n) begin	//捕捉时钟下降沿  可以寄存两拍的值 
    if(!rst_n) begin
    recv_done_d0<=1'b0;
    recv_done_d1<=1'b0;
    end
    else begin
    recv_done_d0<=recv_done;
    recv_done_d1<=recv_done_d0;
    end
end


always @(posedge clk or negedge rst_n) begin	//存储位判断
	if(!rst_n)
	rdy_send<=1'b0;
	else if(recv_done_flag==1'b1)
	rdy_send<=1'b1;
	else if(send_en==1'b1)
	rdy_send<=1'b0;
	else 
	rdy_send<=rdy_send;
end

always @(posedge clk or negedge rst_n) begin	//将数据存储
	if(!rst_n) 
	temp_data<=8'd0;
	else if(recv_done_flag==1'b1)
	temp_data<=recv_data;
	else 
	temp_data<=temp_data;
end

always @(posedge clk or negedge rst_n) begin  //输出使能
	if(!rst_n)
	send_en<=1'b0;
	else if(rdy_send==1&&tx_busy==0)
	send_en<=1'b1;
	else
	send_en<=1'b0;
end

always @(posedge clk or negedge rst_n) begin  //使能有效 输出数据
	if(!rst_n)
	send_data<=8'd0;
	else if(send_en==1'b1)
	send_data<=temp_data;
	else
	send_data<=8'd0;
end
	
endmodule 

这几个模块完成后,知道他们的连接关系,顶层文件其实也很好编写,只需要把各个模块串起来就可以了。

//`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2022/11/02 10:48:07
// Design Name: 
// Module Name: uart_loopback_top
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module uart_loopback_top(
    input   clk,
    input   rst_n,
    input   rxd,
    output   txd
    );
 
 wire [7:0] uart_in;
 wire send_en;
 wire tx_busy;
 wire uart_done; 
 wire [7:0] recv_data;
    
 uart_recv uart_recv_u(
    .clk        (clk),
    .rst_n      (rst_n),
    .uart_rxd   (rxd),
    .uart_data  (recv_data),
    .uart_done  (uart_done)
    
 );
 uart_loop uart_loop(
    .clk        (clk),
    .rst_n      (rst_n),
    .recv_done  (uart_done),
    .recv_data  (recv_data),
    .tx_busy    (tx_busy),
    .send_data  (uart_in),
    .send_en    (send_en)
 
 );
 uart_send uart_send(
    .clk        (clk),
    .rst_n      (rst_n),
    .uart_in  (uart_in),
    .uart_en  (send_en),
    .tx_flag    (tx_busy),
    .uart_txd  (txd)

 );
 
endmodule

编写好顶层文件后需要修改综合参数,在Xilinx Specific Options中去掉 Add I/O Buffer选项,不添加I/O buffer并综合。

四、FPGA图形化程序编写

将上面程序生成的uart_loopback_top.ngc 添加到vi所在的工程目录下,调用IP节点,对其各个端口进行连线。
在这里插入图片描述
NI官方提供的串口通信例程比较复杂,是用纯图形化语言编写的,占用资源比较多,反而观之底层代码实现起来有它的优势。看着程序也比较简单,我们编译下载试试。

添加网表文件后,由于复位为低电平复位,因此需要在程序首次调用后或非门取反,下载至开发板后我们用串口助手发送数据。
在这里插入图片描述

发送什么数据就可以接收到什么数据,串口环回实验的VI、ngc,代码文件都在串口环回通信资源文件

五、串口通信IP核

有了前面串口环回实验的基础,去掉中间的环回模块,将数据直接输出,就构成了串口通信IP核。
在这里插入图片描述
串口通信IP核除了包含之前环回实验的复位、RXD、TXD,还增加了数据的输入data_in和握手协议。为了验证其功能,我们在输出dataout增加了前面板显示控件以及计数控件,验证发送与接收功能,定时4秒发送一个0x55,验证IP核的发送功能。
在这里插入图片描述

可以看到,发送一个0x85,接收到123,ASCII码中的U对应的是0x55,串口通信功能实现,之后需要在项目中用到串口通信功能的话,只需要在IP核上接FIFO就可以实现跨时钟域的通信功能了。

总结

FPGA图形化编程其优势在于逻辑图形化,若不去考虑底层verliog,在顶层设计上FPGA的图形化编程是有优势的,可以更有效率的完成FPGA的设计,本节实验的Verliog网表文件和LabVIEW的vi均开源在 我的资源 如有需要可以下载学习。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值