用户工具

站点工具


icore3l_fpga_16
银杏科技有限公司旗下技术文档发布平台
技术支持电话0379-69926675-801
技术支持邮件Gingko@vip.163.com
版本 日期 作者 修改内容
V1.0 2020-12-09 gingko 初次建立

实验十六:串口通信实验——基于FPGA实现UART通信

一、 实验目的与意义

  1. 学习UART通信协议的原理和时序。
  2. 掌握UART接口的Verilog编程实现方法。

二、 实验设备及平台

  1. iCore3L 双核心板;
  2. iTool3Pro(串口TTL)或带有相同功能的串口模块;
  3. XiST USB-CABLE(或相同功能)仿真器;
  4. Micro USB线缆;
  5. 装有HqFpga开发软件的电脑一台。

三、 实验原理

  • UART(通用异步收发器)是一种通用串行数据总线,用于异步通信,可实现全双工传输和接收,是硬件开发时常用的一种通信接口。USART(通用同步异步收发器)在UART的基础上增加了同步功能,即USART是UART的增强型,异步通信的时候和UART没有区别。UART通信具有两根信号线,一根用于发送数据,一根用于接收数据。然而数据的传输是按照字节进行传输的,因此在发送时需要将并行数据转换成串行数据,接收时需要将串行数据转换成并行数据。
  • UART通信传输是以帧为单位的,每帧数据有4部分构成:起始位、数据位、奇偶校验位和停止位。以8位字长的串口发送数据帧为例,其帧协议如图16-1所示,从图中可以看到,初始状态时,传输线上为高电平,在持续一个波特的低电平之后,是发送的有效数据。之后是至少一个波特高电平的停止位。

图16-1 串口时序示意图

  • 在UART通信中有一个重要的参数,即波特率,它表征了串口的传输速度,表示1秒内传输的二进制位的个数,波特率越大表示1秒内传输的二进制位数越多,反之,越少。以115200波特率为例,表示1秒钟传输115200个二进制位。本实验代码中,每16个时钟周期发送和接收一个二进制位,则时钟频率需要为1.8432M,既:
  • 115200 * 16 =1.8432M

UART接线原则:RX↔TX、TX↔RX,如图16-2所示。 图16-2 UART信号连接示意图

四、 代码讲解

  • 本实验的整体功能是PC端串口助手发送1个字节的数据,FPGA接收完这个字节的数据后,再发送给串口助手。
  • 根据功能,本实验工程可以划分为数据发送模块和数据接收模块;数据接收模块按照串口时序,在识别到数据线上的起始位后,开始接收数据,并在接收完成后产生一个接收完成标志。
  • 数据发送模块则是将接收的数据以二进制位的模式按照串口时序发送出去,并在发送完后产生一个发送完毕标志位。
  • iCore3L双核心板的FPGA系统时钟是25MHz,而数据发送和接收模块使用的是6.9MHz的时钟信号clk2。这里的6.9M是通过如下方式计算出来的:以串口传输的波特率115200,且每60个clk2时钟周期传输1 bit数据计算,则115200*60≈6.9M。本实验通过调用PLL IP核产生6.9MHz的信号。
  • 从前面的时序图可以发现,串口发送数据时,首先发送1个bit的起始位信号(低电平),告诉接收设备开始数据传输,之后是8个bit的数据位;待数据位发送完毕,再发送1个bit的停止位,表示数据传输完毕。数据发送功能模块主要代码如下:
//发送数据,由低位到高位发送
always @ (posedge clk2 or negedge rst_n)
begin
	if(!rst_n)
		begin
			tx_cnt <= 10'd659;
			tx_r <= 1'd1;
			send_over_r <= 1'd0;
		end
	else 
		begin
			case(tx_cnt)
				10'd659:begin
					if(receive_over)
						begin
							tx_cnt <= 10'd0;
						end
					else
						begin
							tx_cnt <= tx_cnt;
						end
				end
				10'd0:begin
					tx_cnt 	<= tx_cnt + 1'd1;
					tx_r 	<= 1'd0;//发送起始位
				end
				10'd60:begin			
					tx_cnt <= tx_cnt + 1'd1;
					tx_r <= tx_data[0];//发送数据0位
				end
				10'd120:begin
					tx_cnt <= tx_cnt + 1'd1;
					tx_r <= tx_data[1];//发送数据1位
				end
				10'd180:begin
					tx_cnt <= tx_cnt + 1'd1;
					tx_r <= tx_data[2];//发送数据2位
				end
				10'd240:begin
					tx_cnt <= tx_cnt + 1'd1;
					tx_r <= tx_data[3];//发送数据3位
				end	
				10'd300:begin
					tx_cnt <= tx_cnt + 1'd1;
					tx_r <= tx_data[4];//发送数据4位
				end
				10'd360:begin
					tx_cnt <= tx_cnt + 1'd1;
					tx_r <= tx_data[5];//发送数据5位
				end
				10'd420:begin
					tx_cnt <= tx_cnt + 1'd1;
					tx_r <= tx_data[6];//发送数据6位
				end
				10'd480:begin
					tx_cnt <= tx_cnt + 1'd1;
					tx_r <= tx_data[7];//发送数据7位
				end
				10'd540:begin
					tx_cnt <= tx_cnt + 1'd1;
					tx_r <= 1'bz;		//发送校验位	
				end
				10'd600:begin
					tx_cnt <= tx_cnt + 1'd1;
					tx_r <= 1'd1;		//发送停止位
				end	
				10'd655:begin
					tx_cnt <= tx_cnt + 8'd1;
					tx_r <= 1'd1;		//一帧数据发送结束   
				end
				10'd656:begin
					tx_cnt <= tx_cnt + 8'd1;
					send_over_r <= 1'd1;
				end
				10'd658:begin
					tx_cnt <= 10'd659;
					send_over_r <= 1'd0;
				end
				default:begin
					tx_cnt <= tx_cnt + 8'd1;
					tx_r <= tx_r;
				end
			endcase
		end
end
  • 接收数据时,以clk2时钟进行检测,当检测到接收信号为低时,可能是数据的起始位;通过延时30个clk2时钟周期,规避掉电平波动造成的误判,如果检测到接收信号依然为低电平,可以认为是数据传输的起始位。因为每个bit电平持续60个clk2时钟周期,则可以通过计数的方式,在每个bit的中间位置进行采样,以保证是在数据稳定状态下进行的采样。
  • 整个过程通过case语句实现,主要代码如下:
	always @ (posedge clk2 or negedge rst_n)
	begin
		if(!rst_n)
			begin
				rx_cnt <= 10'd0;;
				uart_rdata_r <= 8'd0;
				uart_rdata <= 8'd0;
				parity_bit <= 1'd0;
				receive_over_r <= 1'd0;
			end
		else 
			begin
				case(rx_cnt)//判断信号线是否有变化,RX信号线正常情况下为高电平,有低电平表示可能是起始位
					10'd0:begin
						if(!rxd)
							rx_cnt <= rx_cnt + 1'd1;
						else
							rx_cnt <= rx_cnt;		
					end
					10'd30:begin//判断是否为起始位信号(信号线稳定后仍为低电平,为真正的数据起始位)
						if(!rxd)
							rx_cnt <= rx_cnt + 1'd1;
						else
							rx_cnt <= 8'd0;
					end
					//接收数据0位
					10'd90:begin
						uart_rdata_r[0] <= rxd;
						rx_cnt <= rx_cnt + 1'd1;
					end
					//接收数据1位
					10'd150:begin
						uart_rdata_r[1] <= rxd;
						rx_cnt <= rx_cnt + 1'd1;
					end
					//接收数据2位
					10'd210:begin
						uart_rdata_r[2] <= rxd;
						rx_cnt <= rx_cnt + 1'd1;
					end
					//接收数据3位
					10'd270:begin
						uart_rdata_r[3] <= rxd;
						rx_cnt <= rx_cnt + 1'd1;
					end
					//接收数据4位
					10'd330:begin
						uart_rdata_r[4] <= rxd;
						rx_cnt <= rx_cnt + 1'd1;
					end
					//接收数据5位
					10'd390:begin
						uart_rdata_r[5] <= rxd;
						rx_cnt <= rx_cnt + 1'd1;
					end
					//接收数据6位
					10'd450:begin
						uart_rdata_r[6] <= rxd;
						rx_cnt <= rx_cnt + 1'd1;
					end
					//接收数据7位
					10'd510:begin
						uart_rdata_r[7] <= rxd;
						rx_cnt <= rx_cnt + 1'd1;
					end
					//接收校验位(无校验)
					10'd570:begin
						parity_bit <= rxd;
						rx_cnt <= rx_cnt + 1'd1;
					end
					//接收停止位
					10'd630:begin
						rx_cnt <= rx_cnt + 1'd1;
						uart_rdata <= uart_rdata_r;
					end
					//接收完成标志拉高两个时钟
					10'd650:begin
						receive_over_r <= 1'd1;
						rx_cnt <= rx_cnt + 1'd1;
					end
					10'd655:begin
						receive_over_r <= 1'd0;
						rx_cnt <= rx_cnt + 1'd1;
					end
					//判断是否发送完成
					10'd659:begin
						if(send_over)
							rx_cnt <= 10'd0;
						else
							rx_cnt <= rx_cnt;
					end
					default:begin
						rx_cnt <= rx_cnt + 1'd1;					
					end
				endcase
			end
	end
  • 在数据接收部分,为了便于验证程序,这里通过对比接收数据和“r、g、b”三个字符的ASCII码十进制数,判断接收数据的准确性,并根据判断结果,控制LED以不同的颜色显示;代码如下:
//-----------------------------------------------//
//通过识别接收到的字符,控制LED的显示状态,
//只有接收数据和ASCII"r,g,b"的十进制值相同时,才会改变LED的状态
reg	[2:0]	led;
always@(posedge clk2	or negedge rst_n)
	if(!rst_n)
		led<=3'b111;
	else case(uart_rdata)
			8'd114	:	led<=3'b011;//"r",led显示为红色
			8'd103	:	led<=3'b101;//"g",led显示为绿色
			8'd98	: 	led<=3'b110;//"b",led显示为蓝色
		default:
			led<=3'b111;			//如果非“r,g,b”,LED灭
	endcase
assign	fpga_led=led;

五、 实验步骤及实验结果

1、点击左侧栏“新建工程”按钮,设置工程名称为uart,目标器件型号为SL2S-25E-8U213C,设计输入选择“RTL描述”,然后点击“下一步”,弹出窗口直接点击“完成”按钮。

2、将之前实验中编写的rst_n.v文件复制到工程文件夹下。点击左侧栏“设计管理”按钮,弹出窗口中点击“新建文件”按钮,并在编辑区输入工程代码;然后点击保存并给文件命名。

3、以同样的方式,分别建立uart_rxd.v文件、uart_txd.v文件以及上层文件uart_ctrl.v;建立顶层文件uart.v并在顶层模块中例化各个子模块。

4、点击主界面的“IP管理”按钮,创建PLL IP核,或者直接将工程文件夹下的PLL IP核文件pll_frac.v文件复制到工程文件夹并添加进工程;在uart_ctrl.v文件中例化PLL IP 核。

5、点击左侧边栏的“工程属性”按钮,弹出窗口中点击“源文件”栏下的“+”号,将建立的源文件及IP核文件添加到工程中。然后点击“确定”按钮。

6、点击主界面的“RTL综合”按钮,综合完成后如果无报错,点击左侧栏的“物理约束”按钮,弹出窗口中选择“约束编辑器”,点击“确定”按钮。

7、在端口信号“位置”栏通过点击分别添加引脚信息,添加完成后点击“保存”按钮,然后点击“退出”。这里的rxd和txd引脚可以是FPGA引出来的任意两个引脚信号,本实验中使用了L6和M6两个引脚,如图16-3红框中所示;接线时注意FPGA的RX信号和串口功能模块的TX相连,同样,FPGA的TX和串口功能模块的RX相连。

8、点击左侧栏的“全部运行”按钮,进行全编译,并生成下载用的bit文件。

9、将XiST下载器连接到开发板的JTAG口,将ARM下载器连接到开发板的SWD接口,给iCore3L 供电。

10、点击HqFpga界面中的“下载/编程”按钮,进入FPGA下载界面;选定生成的bit文件,然后点击“下载”按钮,将程序烧录进FPGA中。 图16-3 添加物理约束绑定信号引脚

11、打开串口软件;在“设备管理器”中查看和iCore3L核心板相连的串口端口号,并将串口软件中的Port: 配置成此端口号(本人使用的COM4),波特率设置为115200,数据位8位;然后点击右上角的“open Port”按钮,打开端口。在发送区输入对应字符(小写字符“r、g、b”),然后点击右侧“(s)Send”按钮,即可看到开发板上FPGA-LED对应的颜色亮起;同时接收区显示接收到的字符(图16-4接收区中蓝色字样)。

12、至此,本实验成功完成。 图16-4 串口软件设置及串口通信测试

六、 拓展实验

  • 通过modelsim仿真软件观察串口通信时序。
  • 通过修改参数实现其他波特率的串口通信。
  • 修改程序,使接收字符和发送字符不同。
icore3l_fpga_16.txt · 最后更改: 2022/03/19 15:28 由 sean