FPGA--UART串口通信

摘要:
如果选择了奇偶校验,UART会在数据位之后添加奇偶校验位。在接收过程中,UART从消息帧中删除起始位和结束位,对传入字节执行奇偶校验,并将数据字节从串行转换为并行。例如,在通信中,双方将统一指定在时钟信号的上升沿或下降沿对数据线进行采样。

一,串口相关知识

UART 通信 UART 首先将接收到的并行数据转换成串行数据来传输。消息帧从一个低位起始位开始,后面是 7 个或 8 个数据位,一个可用的奇偶位和一个或几个高位停止位。接收器发现开始位时它就知道数据准备发送,并尝试与发送器时钟频率同步。如果选择了奇偶校验,UART 就在数据位后面加上奇偶位。奇偶位可用来帮助错误校验。在接收过程中, UART 从消 息帧中去掉起始位和结束位,对进来的字节进行奇偶校验,并将数据字节从串行转换成并行。UART 传输时序如下图所示 :
FPGA--UART串口通信第1张

串口通讯4根线:Vcc ,Gnd , Tx , Rx;TX-TTL发送端;RX--TTL接收端;

比特率:9600bps 就是每秒中传输9600bit,也就是104us一个逻辑电平;

串行通信的分类:

1、按照数据传送方向,分为:
    单工:数据传输只支持数据在一个方向上传输;
    半双工:允许数据在两个方向上传输。但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;它不需要独立的接收端和发送端,两者可以合并一起使用一个端口。
    全双工:允许数据同时在两个方向上传输。因此,全双工通信是两个单工通信方式的结合,需要独立的接收端和发送端。
FPGA--UART串口通信第2张

2、按照通信方式,分为:

  • 同步通信:带时钟同步信号传输。比如:SPI,IIC通信接口。
  • 异步通信:不带时钟同步信号。比如:UART(通用异步收发器),单总线。

 在同步通讯中,收发设备上方会使用一根信号线传输信号,在时钟信号的驱动下双方进行协调,同步数据。例如,通讯中通常双方会统一规定在时钟信号的上升沿或者下降沿对数据线进行采样。

在异步通讯中不使用时钟信号进行数据同步,它们直接在数据信号中穿插一些用于同步的信号位,或者将主题数据进行打包,以数据帧的格式传输数据。通讯中还需要双方规约好数据的传输速率(也就是波特率)等,以便更好地同步。常用的波特率有4800bps、9600bps、115200bps等。

在同步通讯中,数据信号所传输的内容绝大部分是有效数据,而异步通讯中会则会包含数据帧的各种标识符,所以同步通讯效率高,但是同步通讯双方的时钟允许误差小,稍稍时钟出错就可能导致数据错乱,异步通讯双方的时钟允许误差较大。

常见的串行通信接口:

FPGA--UART串口通信第3张

 串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备的RXD接口,通讯双方的数据包格式要规约一致才能正常收发数据。

奇偶校验位分为奇校验和偶校验两种,是一种简单的数据误码校验方法。奇校验是指每帧数据中,包括数据位和奇偶校验位的全部9个位中1的个数必须为奇数;偶校验是指每帧数据中,包括数据位和奇偶校验位的全部9个位中1的个数必须为偶数。

 二,代码相关知识:

1,case语句是分支比较语句,也就是说,本质上就是case后面括号中的值与下面每个分支开头的值作比较,相同则执行。

case(表达式)

表达式1:代码

表达式2:代码

。。。

default:代码

endcase 

2,parameter:参数数据类型,其实就是个常量,通常出现在module内部,常被定义为状态机状态、数据位宽和延迟大小,参数的定义是局部的,只在当前模块有效
操作:
parameter N = 8'd5;
parameter P = 4'b0001;
三,程序实现

1,时钟模块:

`timescale 1ns / 1ps
/*****************--module name:CLKDIV--********************/
module CLKDIV(
                    clk50,//系统时钟
                    rst_n,//复位信号
                    clkout//输出时钟
);
input clk50;
input rst_n;
output clkout;


//寄存器型数据对象的定义
reg clkout;
reg [15:0] cnt;


//50mhz时钟326分频
always @(posedge clk50 or negedge rst_n) 
begin
    if (!rst_n)  
        begin
            clkout <=1'b0;
            cnt<=0;
        end
    else if(cnt == 16'd162) 
        begin
            clkout <= 1'b1;
            cnt <= cnt + 16'd1;
        end
    else if(cnt == 16'd325) 
        begin
            clkout <= 1'b0;
            cnt <= 16'd0;
        end
    else 
        cnt <= cnt + 16'd1;
end

endmodule

程序对 50Mhz 的系统时钟进行分频, 分频参数 326 计算如下:
这里产生的时钟 clkout 为波特率的 16 倍, 假设数据的波特率为 p,则这里的时钟 clkout的频率为 16*p。以波特率 p 为 9600 为例,系统时钟为 50MHz,则分频系数为50000000/(16*9600) = 325.52,取整为 326。
clkout 时钟取 16 倍波特率的目的为了在 uart 接收的时候对每比特接收的数据有 16 个时钟采样,取中间的采样值,以保证采样不会滑码或误码。

2,串口发送模块:

`timescale 1ns / 1ps
module uarttx(clk, rst_n, tx,wrsig,data);
input clk;//UART时钟
input rst_n;//系统复位
output tx;//发送数据信号
input wrsig;//发送命令,上升沿有效
input [7:0] data;//需要发送的数据

//寄存器定义
reg idle;//线路状态指示,高为线路忙,低为线路空闲
reg send;
reg wrsigbuf;
reg wrsigrise;
reg presult;
reg [7:0] cnt;
reg tx;
parameter paritymode = 1'b0;

//检测上升沿
always @(posedge clk)
begin
        wrsigbuf<=wrsig;
        wrsigrise<=(~wrsigbuf)&wrsig;
end

//启动串口发送程序
always @(posedge clk)
begin
        if(wrsigrise&& (~idle))//当发送命令有效且线路为空闲时,启动新的数据发送进程
            begin
                send<=1'b1;
            end
        else if(cnt==8'd168)//一帧数据发送结束
            begin
                send<=1'b0;
            end
end

//串口发送程序,16个时钟发送一个bit
always @(posedge clk or negedge rst_n)
begin
        if(!rst_n) begin
            tx<=1'b0;
            idle<=1'b0;
            cnt<=8'd0;
            presult<=1'b0;
        end
        else if(send==1'b1)begin
        case(cnt)
        8'd0:begin
                tx<=1'B0;     //产生起始位
                idle<=1'b1;
                cnt<=cnt+8'd1;
              end
        8'd16: begin
                tx <= data[0]; //发送数据 0 位
                presult <= data[0]^paritymode;
                idle <= 1'b1;
                cnt <= cnt + 8'd1;
                end
        8'd32: begin
                tx <= data[1]; //发送数据 1 位
                presult <= data[1]^presult;
                idle <= 1'b1;
                cnt <= cnt + 8'd1;
                end
        8'd48: begin
                tx <= data[2]; //发送数据 2 位
                presult <= data[2]^presult;
                idle <= 1'b1;
                cnt <= cnt + 8'd1;
                end
        8'd64: begin
                tx <= data[3]; //发送数据 3 位
                presult <= data[3]^presult;
                idle <= 1'b1;
                cnt <= cnt + 8'd1;
                end
        8'd80: begin
                tx <= data[4]; //发送数据 4 位
                presult <= data[4]^presult;
                idle <= 1'b1;
                cnt <= cnt + 8'd1;
                end
        8'd96: begin
                tx <= data[5]; //发送数据 5 位
                presult <= data[5]^presult;
                idle <= 1'b1;
                cnt <= cnt + 8'd1;
                end
        8'd112: begin
                tx <= data[6]; //发送数据 6 位
                presult <= data[6]^presult;
                idle <= 1'b1;
                cnt <= cnt + 8'd1;
                end
        8'd128: begin
                tx<= data[7]; //发送数据 7 位
                presult <= data[7]^presult;
                idle <= 1'b1;
                cnt <= cnt + 8'd1;
                end
        8'd144: begin
                tx <= presult; //发送奇偶校验位
                presult <= data[0]^paritymode;
                idle <= 1'b1;
                cnt <= cnt + 8'd1;
                end
        8'd160: begin
                tx <= 1'b1; //发送停止位
                idle <= 1'b1;
                cnt <= cnt + 8'd1;
                end
        8'd168: begin
                tx <= 1'b1;
                idle <= 1'b0; //一帧数据发送结束
                cnt <= cnt + 8'd1;
                end
        default: begin
                cnt <= cnt + 8'd1;
                end
        endcase
        end
        else begin
                tx <= 1'b1;
                cnt <= 8'd0;
                idle <= 1'b0;
            end
end

endmodule

发送数据过程:空闲状态,线路处于高电位;当收到发送数据指令后,拉低线路一个数据位的时间 T,接着数据按低位到高位依次发送,数据发送完毕后,接着发送奇偶校验位和停止 位(停止位为高电位),一帧数据发送结束。
3,接受模块

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module name uartrx.v
// 说明: 16 个 clock 接收一个 bit, 16 个时钟采样,取中间的采样值
//////////////////////////////////////////////////////////////////////////////////
module uartrx(clk, rst_n, rx, dataout, rdsig, dataerror, frameerror);
input clk; //采样时钟
input rst_n; //复位信号
input rx; //UART 数据输入
output dataout; //接收数据输出
output rdsig;//数据接收完成
output dataerror; //数据出错指示
output frameerror; //帧出错指示

reg[7:0] dataout;
reg rdsig, dataerror;
reg frameerror;
reg [7:0] cnt;
reg rxbuf, rxfall, receive;
parameter paritymode = 1'b0;
reg presult, idle;

always @(posedge clk) //检测线路的下降沿
begin
        rxbuf <= rx;
        rxfall <= rxbuf & (~rx);
end

/**********************启动串口接收程序********************************/
always @(posedge clk)
begin
        if (rxfall && (~idle)) begin//检测到线路的下降沿并且原先线路为空闲,启动接收数据进程
            receive <= 1'b1;
            end
        else if(cnt == 8'd168) begin //接收数据完成
            receive <= 1'b0;
            end
end

/********串口接收程序, 16 个时钟接收一个 bit*********/
always @(posedge clk or negedge rst_n)
begin
        if (!rst_n) begin
            idle<=1'b0;
            cnt<=8'd0;
            rdsig <= 1'b0;
            frameerror <= 1'b0;
            dataerror <= 1'b0;
            presult<=1'b0;
            end
        else if(receive == 1'b1) begin
            case (cnt)
                8'd0:begin
                    idle <= 1'b1;
                    cnt <= cnt + 8'd1;
                    rdsig <= 1'b0;
                    end
                8'd24:begin //接收第 0 位数据
                    idle <= 1'b1;
                    dataout[0] <= rx;
                    presult <= paritymode^rx;
                    cnt <= cnt + 8'd1;
                    rdsig <= 1'b0;
                    end
                8'd40:begin //接收第 1 位数据
                    idle <= 1'b1;
                    dataout[1] <= rx;
                    presult <= presult^rx;
                    cnt <= cnt + 8'd1;
                    rdsig <= 1'b0;
                    end
                8'd56:begin //接收第 2 位数据
                    idle <= 1'b1;
                    dataout[2] <= rx;
                    presult <= presult^rx;
                    cnt <= cnt + 8'd1;
                    rdsig <= 1'b0;
                    end
                8'd72:begin //接收第 3 位数据
                    idle <= 1'b1;
                    dataout[3] <= rx;
                    presult <= presult^rx;
                    cnt <= cnt + 8'd1;
                    rdsig <= 1'b0;
                    end
                8'd88:begin //接收第 4 位数据
                    idle <= 1'b1;
                    dataout[4] <= rx;
                    presult <= presult^rx;
                    cnt <= cnt + 8'd1;
                    rdsig <= 1'b0;
                    end
                8'd104:begin //接收第 5 位数据
                    idle <= 1'b1;
                    dataout[5] <= rx;
                    presult <= presult^rx;
                    cnt <= cnt + 8'd1;
                    rdsig <= 1'b0;
                    end
                8'd120:begin //接收第 6 位数据
                    idle <= 1'b1;
                    dataout[6] <= rx;
                    presult <= presult^rx;
                    cnt <= cnt + 8'd1;
                    rdsig <= 1'b0;
                    end
                8'd136:begin //接收第 7 位数据
                    idle <= 1'b1;
                    dataout[7] <= rx;
                    presult <= presult^rx;
                    cnt <= cnt + 8'd1;
                    rdsig <= 1'b1;
                    end
                8'd152:begin //接收奇偶校验位
                    idle <= 1'b1;
                    if(presult == rx)
                        dataerror <= 1'b0;
                    else
                        dataerror <= 1'b1; //如果奇偶校验位不对,表示数据出错
                    cnt <= cnt + 8'd1;
                    rdsig <= 1'b1;
                    end
                8'd168:begin
                    idle <= 1'b1;
                    if(1'b1 == rx)
                        frameerror <= 1'b0;
                    else
                        frameerror <= 1'b1; //如果没有接收到停止位,表示帧出错
                    cnt <= cnt + 8'd1;
                    rdsig <= 1'b1;
                    end
                default: begin
                    cnt <= cnt + 8'd1;
                    end
                endcase
        end
        else begin
            cnt <= 8'd0;
            idle <= 1'b0;
            rdsig <= 1'b0;
            end
end

endmodule

接收数据过程:空闲状态,线路处于高电位;当检测到线路的下降沿(线路电位由高电位变为低电位)时说明线路有数据传输,按照约定的波特率从低位到高位接收数据,数据接收完毕后,接着接收并比较奇偶校验位是否正确,如果正确则通知后续设备准备接收数据或存入缓存。由于 UART 是异步传输,没有传输同步时钟。为了能保证数据传输的正确性, UART 采用16 倍数据波特率的时钟进行采样。每个数据有 16 个时钟采样,取中间的采样值,以保证采样 不会滑码或误码。一般 UART 一帧的数据位数为 8,这样即使每个数据有一个时钟的误差,接收端也能正确地采样到数据。
4,顶层模块:

`timescale 1ns / 1ps
module LED(
                    clk, // 开发板上输入时钟: 50Mhz
                    rst_n, // 开发板上输入复位按键
                    led,// 输出 LED 灯,用于控制开发板上四个 LED(LED0~LED3)
                    TX,
                    RX
);
input clk;
input rst_n;
output [3:0] led;
output TX;
input RX;

//寄存器型数据对象的定义
reg [31:0] timer;
reg [3:0] led;
wire uart_clk;//串口使用时钟
wire receiverise_over;//接收完成信号
wire dataerror;//接受数据错误
wire frameerror;//接收帕错误

reg receivebuf,receiverise,send_flag,send;
reg [7:0] send_delay;

wire [7:0] data;

parameter data1=16'h55;




//接受完成处理  检测上升沿
always @(posedge uart_clk)
begin
        receivebuf<=receiverise_over;
        receiverise<=(~receivebuf)&receiverise_over;//接收完成信号上升沿检测
        //判断接收是否有问题
        if(receiverise==1'b1)
            send_flag<=1'b1;
        if((send_flag==1'b1)&&(send_delay<8'd5))
            send_delay<=send_delay+1'b1;
        else if((send_delay>=8'd5)&&(send_delay<8'd10)) begin
            send<=1'b0;
            send_delay<=send_delay+1'b1;
            end
        else if(send_delay>=8'd10) begin
            send_flag<=1'b0;
            send_delay<=1'b0;
            if((dataerror==1'b0)&&(frameerror==1'b0))//没问题,启动发送
                send<=1'b1;
            end
end


always @(posedge clk or negedge rst_n) 
    begin
        if(~rst_n)
            timer <=0;
        else if(timer==32'd199_999_999) 
            timer<=0;
        else 
            timer<=timer+1'b1;    
    end
    
always @(posedge clk or negedge rst_n) 
    begin
        if(~rst_n)
            led<=4'b0000;
        else if(timer==32'd49_999_999)
           led<=4'b0001;
      else if(timer==32'd99_999_999)
           led<=4'b0010;

        else if(timer==32'd149_999_999) 
           led<=4'b0100;
        else if(timer==32'd199_999_999) 
           led<=4'b1000;
    end

     
     

//例化时钟模块    
CLKDIV CLKDIV_LED(
                    .clk50(clk),
                    .rst_n(rst_n),
                    .clkout(uart_clk)
);

//例化uartrx模块
uartrx uartrx_LED(
                    .clk(uart_clk),
                    .rst_n(rst_n),
                    .rx(RX),
                    .dataout(data),
                    .rdsig(receiverise_over),
                    .dataerror(dataerror),
                    .frameerror(frameerror)
);

//例化uarttx模块
uarttx uarttx_LED(
                    .clk(uart_clk),
                    .rst_n(rst_n),
                    .tx(TX),
                    .wrsig(send),
                    .data(data)
);

 
endmodule

实现接受到数据之后,再将数据发送出去;

免责声明:文章转载自《FPGA--UART串口通信》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇asp.net 多个域名重定向,在web.Config中配置于win2008R2虽然激活,但是一个小时之后就会自动强制关机的问题下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

k8s中节点级别的日志

容器化应用程序写入到 stdout 和 stderr 中的任何信息,都将被容器引擎重定向到某个地方。例如,Docker 容器引擎将 stdout 和 stderr 这两个输出流重定向到 logging driver ,Kubernetes的默认配置中,最终 logging driver 最终把日志写入了一个 json 格式的文件。 Docker 的 js...

rocketmq学习(一) rocketmq介绍与安装

1.消息队列介绍   消息队列本质上来说是一个符合先进先出原则的单向队列:一方发送消息并存入消息队列尾部(生产者投递消息),一方从消息队列的头部取出消息(消费者消费消息)。但对于一个成熟可靠的消息队列来说,所需要解决的主要问题还包括:高效可靠的消息投递、存储;能承受高并发的流量冲击,可通过集群部署来解决单点故障等等。   由于消息队列具备了以上特点,因此在...

RabbitMQ、Kafka、RocketMQ的优劣势

今天我们一起来探讨:  全量的消息队列究竟有哪些?  Kafka、RocketMQ、RabbitMQ的优劣势比较  以及消息队列的选型 最全MQ消息队列有哪些 那么目前在业界有哪些比较知名的消息引擎呢?如下图所示 这里面几乎完全列举了当下比较知名的消息引擎,包括:  ZeroMQ  推特的Distributedlog  ActiveMQ:Apach...

由于出现操作系统错误 3,进程无法读取文件D:XXXXX.pre (源: MSSQL_REPL,错误号: MSSQL_REPL20024)

最近着手做SqlServer2008的订阅发布,起初使用推送订阅很顺利,后来改成请求订阅出现了以下问题,折腾好长时间终于搞定,留下此文备日后查阅,或供遇相同问题的道友参考: 首先阐述以下问题: 1. 错误消息: 由于出现操作系统错误 3,进程无法读取文件“C:Program FilesMicrosoft SQL ServerMSSQL10_50.MSSQ...

JAVA消息服务JMS规范及原理详解

JAVA消息服务JMS规范及原理详解 一、简介 JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。 JMS允许应用程序...

NETCore Bootstrap Admin 通用后台管理权限 [2]: Blazor 版本介绍

前言 上一篇介绍过了前后台分离的 NET Core 通用权限管理系统 在这篇文章简要的介绍了 Bootstrap Admin 后台管理框架的一些功能。本篇文章带来的是微软最新出的 Blazor 版本的 NET Core 通用权限管理系统 Blazor 简介 至于 Blazor 是什么,Blazor 的优缺点小伙伴们可以自行在园子里搜索一下,相关介绍还是非常...