进阶项目UART串口通信程序设计讲解
Posted mengyi1989
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进阶项目UART串口通信程序设计讲解相关的知识,希望对你有一定的参考价值。
写在前面的话
UART串行接口简称串口,是我们各类芯片最常用的一种异步通信接口,通过串口我们就可以建立起计算机和我们实验板之间的通信和控制关系,也就是我们通常所说的上下位机通信。串口可以说是不同平台互相通信、控制的一个最基本的接口。
项目需求
设计一个UART控制器,当控制器从上位机接收到数据以后,马上将数据输出,发送回上位机,完成“回环测试”。
UART的原理分析
要实现UART通信,首先我们需要用到一个外部的电平转换芯片MAX232,其具体配置电路如下:
注解:
MAX232芯片是美信(MAXIM)公司专为RS_232标准串口设计的单电源电平转换芯片,使用+5V单电源供电
主要特点:
符合所有的RS_232技术标准
只需要单一+5V电源供电
片载电荷泵具有升压、电源极性翻转能力,能够产生+10V和-10V电压
功耗低,典型供电电流5MA
内部集成2个RS_232驱动器
高集成度,片外最低只需四个电容即可工作
由原理图可以看出,最终我们FPGA需要控制的其实也就是两条信号线:
RXD和TXD,分别为数据接收线和数据发送线。
那么接下来,问题就变得简单了,既然只有两条线,那么我们只需要关注其数据收发时序即可,时序图如下:
UART数据格式:
说明:在此实验中,无奇偶校验位,则一帧数据为十位。(奇偶校验是一种校验代码传输正确性的方法。根据被传输的一组二进制代码的数位中“1”的个数是奇数或偶数来进行校验。采用奇数的称为奇校验,反之,称为偶校验。采用何种校验是事先规定好的。通常专门设置一个奇偶校验位,用它使这组代码中“1”的个数为奇数或偶数。若用奇校验,则当接收端收到这组代码时,校验“1”的个数是否为奇数,从而确定传输代码的正确性。)
在UART接收时,采集一帧数据的中间8位有效位,忽略开始位与停止位;在UART发送时,将发送的并行8位数据转为串行数据,并添加开始位与停止位。
UART中的一帧数据为10位,空闲时均为高电平,在检测到开始位(低电平)之后,开始采集8位有效数据位(低位在前),再将停止位置为高电平即可。
通过前面的学习,我们已经了解了UART的数据格式,那么,传输速率如何控制呢?这就涉及到了一个波特率的概念:
波特率是衡量数据传输速率的指针。表示为每秒钟传送的二进制位数(bit),例如资料传送速率为120字符/秒,而每一个字符为10位,则其传送的波特率为10*120=1200波特(bit)。此实验中设置波特率为9600bit/s。
系统架构
模块功能说明:bps_rx模块为串口接收数据的速率控制模块,当使能信号rx_en为高电平时,bps_rx模块内部计数器开始计数,按照设定好的波特率,输出控制数据采集的尖峰脉冲信号rx_sel_data和有效数据位的计数值rx_num。
模块功能说明:uart_rx为串口串行数据的接收模块,数据从端口rs232_rx输入,在采集控制信号rx_sel_data和有效位计数器rx_num的控制下,进行串并转换,从端口rx_d[7:0]输出。tx_en为发送控制模块的使能信号,当Uart_rx模块接收数据完毕以后,置高信号tx_en启动数据发送,将采集到的数据rx_d[7:0]发送到上位机。
模块功能说明:bps_tx模块为串口发送数据的速率控制模块,当使能信号tx_en为高电平时,bps_tx模块内部计数器开始计数,按照设定好的波特率,输出控制数据发送的尖峰脉冲信号tx_sel_data和有效数据位的计数值tx_num。
模块功能说明:uart_tx为串口串行数据的发送模块,并行数据从端口rx_d[7:0]输入,在采集控制信号tx_sel_data和有效位计数器tx_num的控制下,进行并串转换,从端口rs232_tx输出。
模块功能介绍
模块名 |
功能描述 |
bps_rx |
控制串口接收数据的速率 |
uart_rx |
接收串口串行数据 |
bps_tx |
控制串口发送数据的速率 |
uart_tx |
发送串口串行数据 |
uart |
顶层连接 |
端口和内部连线描述
顶层模块端口介绍
端口名 |
端口说明 |
clk |
系统时钟输入 |
rst_n |
系统复位 |
rs232_tx |
数据输出端口 |
rs232_rx |
数据输入端口 |
内部连线
连线名 |
连线说明 |
rx_en |
bps_rx开始计数的使能信号 |
rx_sel_data |
控制数据采集的尖峰脉冲信号 |
rx_num |
接收有效数据位的计数值 |
rx_d |
接收到的数据 |
tx_en |
发送控制模块的使能信号 |
tx_sel_data |
控制数据发送的尖峰脉冲信号 |
tx_num |
发送有效数据位的计数值 |
代码解释
数据接收模块波特率生成
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function:控制串口接收数据的速率 *****************************************************/ 00 module bps_rx( 01 clk, //系统时钟50MHz 02 rst_n, //低电平复位 03 rx_en, //使能信号:串口接收或发送开始 04 rx_sel_data, //波特率计数的中心点(采集数据的使能信号) 05 rx_num //一帧数据0-9 06 ); 07 //模块输入 08 input clk; //系统时钟50MHz 09 input rst_n; //低电平复位 10 input rx_en; //使能信号:串口接收开始 11 //模块输出 12 output reg rx_sel_data; //波特率计数的中心点(采集数据的使能信号) 13 output reg [3:0] rx_num; //一帧数据0-9 14 //设置参数 15 parameter bps_div = 13‘d5207, 16 bps_div_2 = 13‘d2603; 17 18 //接收标志位:接收到使能信号rx_en后,将标志位flag拉高,当信号rx_num计完一帧数据后拉低 19 reg flag; 20 always@(posedge clk or negedge rst_n) 21 if(!rst_n) 22 flag <= 0; 23 else if(rx_en) 24 flag <= 1; 25 else if(rx_num == 4‘d10) 26 flag <= 0; 27 28 //波特率计数 29 reg [12:0] cnt; 30 always@(posedge clk or negedge rst_n) 31 if(!rst_n) 32 cnt <= 13‘d0; 33 else if(flag && cnt < bps_div) 34 cnt <= cnt + 1‘b1; 35 else 36 cnt <= 13‘d0; 37 38 //规定接收数据的范围:即一帧数据(10位:1位开始位,8位数据位,1位结束位) 39 always@(posedge clk or negedge rst_n) 40 if(!rst_n) 41 rx_num <= 4‘d0; 42 else if(rx_sel_data && flag) 43 rx_num <= rx_num + 1‘b1; 44 else if(rx_num == 4‘d10) 45 rx_num <= 1‘d0; 46 47 //数据在波特率的中间部分采集:即接收数据的使能信号 48 always@(posedge clk or negedge rst_n) 49 if(!rst_n) 50 rx_sel_data <= 1‘b0; 51 else if(cnt == bps_div_2)//中间取数是为了产生尖峰脉冲,尖峰脉冲为采集数据的使能信号,用来把握速率 52 rx_sel_data <= 1‘b1; 53 else 54 rx_sel_data <= 1‘b0; 55 56 endmodule |
本模块20~26行负责控制flag的值,flag为高电平代表一帧数据正在传输。29~36行为波特率计数器,当flag有效时计数器开始计数。48~54行负责在波特率计数值的中间部位生成尖峰脉冲rx_sel_data,两个尖峰脉冲之间的间隔为5207个系统时钟周期,满足9600bps波特率。39~45在尖峰脉冲作用下接收数据计数器开始标定有效帧比特位。
数据接收模块
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function:接收串口串行数据 *****************************************************/ 00 module uart_rx( 01 clk, //50MHZ时钟 02 rst_n, //低电平复位 03 rs232_rx, //输入串行数据 04 rx_num, //一帧数据控制位 05 rx_sel_data, //波特率计数的中心点(采集数据的使能信号) 06 rx_en, //使能信号:启动接收波特率计数 07 tx_en, //使能信号:在接收完数据后,开始启动发送数据模块 08 rx_d //将采集数据的有效8位串行数据转化为并行数据 09 ); 10 //模块输入 11 input clk; //50MHZ时钟 12 input rst_n; //低电平复位 13 input rs232_rx; //输入串行数据 14 input [3:0] rx_num; //一帧数据控制位 15 input rx_sel_data; //波特率计数的中心点(采集数据的使能信号) 16 //模块输出 17 output rx_en; //使能信号:启动接收波特率计数 18 output reg tx_en; //使能信号:在接收完数据后,开始启动发送数据模块 19 output reg [7:0] rx_d; //将采集数据的有效8位串行数据转化为并行数据 20 //检测低电平(开始位) 21 reg in_1,in_2; 22 always@(posedge clk or negedge rst_n) 23 if(!rst_n) 24 begin 25 in_1 <= 1‘b1; 26 in_2 <= 1‘b1; 27 end 28 else 29 begin 30 in_1 <= rs232_rx; 31 in_2 <= in_1; 32 end 33 34 assign rx_en = in_2 &(~in_1); //当检测由高变低的过程后,使能信号拉高 35 36 //确保在一帧数据的中间8位进行数据的读取,读取完成后,使能信号tx_en控制串口发送模块 37 reg [7:0] rx_d_r; 38 always@(posedge clk or negedge rst_n) 39 if(!rst_n) 40 begin 41 rx_d_r <= 8‘d0; 42 rx_d <= 8‘d0; 43 end 44 else if(rx_sel_data) 45 case(rx_num) 46 0:; //忽略开始位 47 1: rx_d_r[0] <= rs232_rx;//采集中间8位有效数据 48 2: rx_d_r[1] <= rs232_rx; 49 3: rx_d_r[2] <= rs232_rx; 50 4: rx_d_r[3] <= rs232_rx; 51 5: rx_d_r[4] <= rs232_rx; 52 6: rx_d_r[5] <= rs232_rx; 53 7: rx_d_r[6] <= rs232_rx; 54 8: rx_d_r[7] <= rs232_rx; 55 9: rx_d <= rx_d_r; //锁存采集的8位有效位(忽略停止位) 56 default:; 57 endcase 58 //使能信号:在完成接收以后立即拉高tx_en(启动发送模块) 59 always@(posedge clk or negedge rst_n) 60 if(!rst_n) 61 tx_en <= 0; 62 else if(rx_num == 9 && rx_sel_data) //在接收停止位之后拉高一个时钟 63 tx_en <= 1; 64 else 65 tx_en <= 0; 66 endmodule |
22~34行的作用是下降沿检测,电路检测到rs232_rx有下降沿出现说明一帧数据开始传输。38~57行在波特率采样控制信号rx_sel_data的控制下,将串行数据进行串并转换,逐位存储到中间并行寄存器rx_d_r。59~65行当数据采集完毕以后,负责输出发送使能信号tx_en,触发数据发送模块启动,将采集到的数据发送到上位机。
数据发送模块波特率生成
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function:控制串口发送数据的速率 *****************************************************/ 00 module bps_tx( 01 clk, //系统时钟50MHz 02 rst_n, //低电平复位 03 tx_en, //使能信号:串口发送开始 04 05 tx_sel_data, //波特率计数的中心点(采集数据的使能信号) 06 tx_num //一帧数据0-9 07 ); 08 //模块输入 09 input clk; //系统时钟50MHz 10 input rst_n; //低电平复位 11 input tx_en; //使能信号:串口发送开始 12 //模块输出 13 output reg tx_sel_data; //波特率计数的中心点 14 output reg [3:0] tx_num; //一帧数据0-9 15 //设置参数 16 parameter bps_div = 13‘d5207, 17 bps_div_2 = 13‘d2603; 18 19 //发送标志位:接收到使能信号tx_en后,将标志位flag拉高,当信号tx_num计完一帧数据后拉低 20 reg flag; 21 always@(posedge clk or negedge rst_n) 22 if(!rst_n) 23 flag <= 0; 24 else if(tx_en) 25 flag <= 1; 26 else if(tx_num == 4‘d10) 27 flag <= 0; 28 29 //波特率计数 30 reg [12:0] cnt; 31 always@(posedge clk or negedge rst_n) 32 if(!rst_n) 33 cnt <= 13‘d0; 34 else if(flag && cnt < bps_div) 35 cnt <= cnt + 1‘b1; 36 else 37 cnt <= 13‘d0; 38 39 //规定发送数据的范围:即一帧数据(10位:1位开始位,8位数据位,1位结束位) 40 always@(posedge clk or negedge rst_n) 41 if(!rst_n) 42 tx_num <= 4‘d0; 43 else if(tx_sel_data && flag) 44 tx_num <= tx_num + 1‘b1; 45 else if(tx_num == 4‘d10) 46 tx_num <= 1‘d0; 47 48 //数据在波特率的中间部分采集:即发送数据的使能信号 49 always@(posedge clk or negedge rst_n) 50 if(!rst_n) 51 tx_sel_data <= 1‘b0; 52 else if(cnt == bps_div_2)//中间取数是为了产生尖峰脉冲,尖峰脉冲为采集数据的使能信号,用来把握速率 53 tx_sel_data <= 1‘b1; 54 else 55 tx_sel_data <= 1‘b0; 56 57 endmodule |
本模块确定了发送数据与接收数据的有效范围并且进行分频计数(在此例中,使用的波特率为9600bps,时钟为50MHZ,则分频计数值为5207)。该模块功能和bps_rx模块功能类似。
数据发送模块
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function:发送串口串行数据 *****************************************************/ 00 module uart_tx( 01 clk, //50MHZ时钟 02 rst_n, //低电平复位 03 tx_num, //一帧数据控制位 04 tx_sel_data, //波特率计数的中心点(采集数据的使能信号) 05 rx_d, //8位数据(即输入数据) 06 rs232_tx //uart发送信号(一帧数据) 07 ); 08 //模块输入 09 input clk; //50MHZ时钟 10 input rst_n; //低电平复位 11 input [3:0] tx_num; //一帧数据控制位 12 input tx_sel_data; //波特率计数的中心点(采集数据的使能信号) 13 input [7:0] rx_d; //8位数据(即输入数据) 14 //模块输出 15 output reg rs232_tx; //uart发送信号(一帧数据) 16 //在串口发送的过程中,确保发送1位开始位,8位有效数据位,1位结束位 17 always@(posedge clk or negedge rst_n) 18 if(!rst_n) 19 rs232_tx <= 1‘b1; 20 else if(tx_sel_data) 21 case(tx_num) 22 0: rs232_tx <= 1‘b0; //开始位为低电平 23 1: rs232_tx <= rx_d[0]; 24 2: rs232_tx <= rx_d[1]; 25 3: rs232_tx <= rx_d[2]; 26 4: rs232_tx <= rx_d[3]; 27 5: rs232_tx <= rx_d[4]; 28 6: rs232_tx <= rx_d[5]; 29 7: rs232_tx <= rx_d[6]; 30 8: rs232_tx <= rx_d[7]; 31 9: rs232_tx <= 1‘b1; //结束位为高电平 32 default: rs232_tx <= 1‘b1; //串口的其它空闲位均要拉至高电平 33 endcase 34 35 endmodule |
20~31行在发送使能信号tx_sel_data作用下,rs232_tx首先发送启动信号,然后将并行数据逐位输出,完成并串转换,八位数据发送完毕以后发送停止信号,结束数据传输。
顶层模块
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function:负责顶层连接 *****************************************************/ 00 module uart( 01 clk, //系统时钟50MHz 02 rst_n, //低电平复位 03 rs232_rx, //uart发送信号 04 rs232_tx //uart接收信号 05 ); 06 //系统输入 07 input clk; //系统时钟50MHz 08 input rst_n; //低电平复位 09 input rs232_rx; //uart发送信号 10 //系统输出 11 output rs232_tx; //uart接收信号 12 13 //内部信号:模块内部的接口信号,比如模块bps_rx的输入信号en,通过内部信号rx_en与模块uart_rx的输出信号en相连 14 15 wire rx_en; 16 wire tx_en; 17 wire [7:0] rx_d; 18 wire [3:0] rx_num,tx_num; 19 20 //模块例化 21 bps_rx bps_rx( //串口接收波特率计数模块 22 .clk(clk), 23 .rst_n(rst_n), 24 .rx_en(rx_en), 25 .rx_num(rx_num), 26 .rx_sel_data(rx_sel_data) 27 ); 28 29 uart_rx uart_rx( //串口接收模块 30 .clk(clk), 31 .rx_d(rx_d), 32 .rst_n(rst_n), 33 .rs232_rx(rs232_rx), 34 .rx_en(rx_en), 35 .rx_num(rx_num), 36 .rx_sel_data(rx_sel_data), 37 .tx_en(tx_en) 38 ); 39 40 bps_tx bps_tx( //串口发送波特率计数模块 41 .clk(clk), 42 .rst_n(rst_n), 43 .tx_en(tx_en), 44 .tx_num(tx_num), 45 .tx_sel_data(tx_sel_data) 46 ); 47 uart_tx uart_tx( //串口发送模块 48 .clk(clk), 49 .rst_n(rst_n), 50 .rx_d(rx_d), 51 .rs232_tx(rs232_tx), 52 .tx_num(tx_num), 53 .tx_sel_data(tx_sel_data) 54 ); 55 56 endmodule |
编写完可综合代码之后,查看RTL视图如下:
由RTL视图可以看出,代码综合以后得到的电路结构和我们设计的系统框图一致,说明顶层逻辑连接正确,接下来编写测试代码如下:
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function:测试uart *****************************************************/ 00 `timescale 1ns/1ps //设置仿真时间单位与精度分别为1ns/1ns 01 02 module uart_tb; 03 //系统输入 04 reg clk; //系统时钟50MHz 05 reg rst_n; //低电平复位 06 reg rs232_rx; //uart发送信号 07 //系统输出 08 wire rs232_tx;//uart接收信号 09 //例化 10 uart uart( 11 .clk(clk), //系统时钟50MHz 12 .rst_n(rst_n), //低电平复位 13 .rs232_rx(rs232_rx), //uart发送信号 14 .rs232_tx(rs232_tx) //uart发送信号 15 ); 16 17 initial 18 begin 19 clk = 0; rst_n = 0; rs232_rx= 1; //在复位阶段,将激励赋初值 20 #200.1 rst_n = 1; //延时200ns后停止复位 21 //模拟发送一帧数据(发送时间的延时根据所设定的波特率计算) 22 #200 rs232_rx= 0;//开始位 23 #110000 rs232_rx= 0;//发送数据8‘ha4 (8‘b0110_0100) 24 #110000 rs232_rx= 1; 25 #110000 rs232_rx= 1; 26 #110000 rs232_rx= 0; 27 #110000 rs232_rx= 0; 28 #110000 rs232_rx= 1; 29 #110000 rs232_rx= 0; 30 #110000 rs232_rx= 0; 31 #110000 rs232_rx= 1;//结束位 32 #1500000 $stop; //仿真1500000ns后停止仿真 33 end 34 always #10 clk = ~clk; //时钟的表示,即每隔10ns翻转一次,一个周期的时间即为20ns,时钟为1/20ns = 50MHZ 35 endmodule |
22~31行模拟了上位机数据的输入,用来测试我们设计的串口模块是否能够进行正常的数据收发。
仿真分析
从波形上可以很清楚的看到,接收和发送的波形完全相同,说明我们的设计正确。
以上是关于进阶项目UART串口通信程序设计讲解的主要内容,如果未能解决你的问题,请参考以下文章
Linux——Linux驱动之iMX6ULL平台下串口UART驱动实现RS232数据通信开发实战(UART驱动框架源码分析串口应用程序编写)
Linux——Linux驱动之iMX6ULL平台下串口UART驱动实现RS232数据通信开发实战(UART驱动框架源码分析串口应用程序编写)
Linux——Linux驱动之iMX6ULL平台下串口UART驱动实现RS232数据通信开发实战(UART驱动框架源码分析串口应用程序编写)