前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >源码系列:基于FPGA的串口UART设计(附源工程)

源码系列:基于FPGA的串口UART设计(附源工程)

作者头像
FPGA技术江湖
发布2020-12-29 17:47:46
1.2K0
发布2020-12-29 17:47:46
举报
文章被收录于专栏:FPGA技术江湖FPGA技术江湖

大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。

今天给大侠带来基于FPGA的UART设计,附源码,获取源码,请在“FPGA技术江湖”公众号内回复“UART设计源码”,可获取源码文件。话不多说,上货。

设计背景

串口的出现是在1980年前后,数据传输率是115kbps~230kbps。串口出现的初期是为了实现连接计算机外设的目的,初期串口一般用来连接鼠标和外置Modem以及老式摄像头和写字板等设备。串口也可以应用于两台计算机(或设备)之间的互联及数据传输。由于串口(COM)不支持热插拔及传输速率较低,部分新主板和大部分便携电脑已开始取消该接口。串口多用于工控和测量设备以及部分通信设备中。

串口是串行接口的简称,也称串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口。串行接口(Serial Interface)是指数据一位一位地顺序传送。其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢。

通信协议是指通信双方的一种约定。约定包括对数据格式、同步方式、传送速度、传送步骤、检纠错方式以及控制字符定义等问题做出统一规定,通信双方必须共同遵守。串口通信的两种最基本的方式为:同步串行通信方式和异步串行通信方式。

同步串行通信是指SPI(Serial Peripheral interface)的缩写,顾名思义就是串行外围设备接口。SPI是一种高速的全双工通信总线。封装芯片上总共有四根线,PCB布局布线也简单,所以现在很多芯片集成了这个协议。主要用于CPU和各种外围器件进行通信,TRM450是SPI接口。

异步串行通信是指UART(Universal Asynchronous Receiver/Transmitter),通用异步接收/发送。UART是一个并行输入成为串行输出的芯片,通常集成在主板上。UART包含TTL电平的串口和RS232电平的串口。RS232也称标准串口,也是最常用的一种串行通讯接口。RS-232-C 标准对两个方面作了规定,即信号电平标准和控制信号线的定义。RS-232-C 采用负逻辑规定逻辑电平,信号电平与通常的TTL电平也不兼容,RS-232-C 将-5V~-15V 规定为“1”,+5V~+15V 规定为“0”。

设计原理

uart的示意图如下:

其端口对应的功能表如下:

在设计过程中只需要关心RS232_TXD和RS232_RXD两个信号, RS232_TXD是数据发送端口,RS232_RXD是数据接收端口。

本设计将通过串口建立起计算机和实验板(ZX_1)之间的通信和控制关系,也就是通常所说的上下位机通信。要实现这样的通信,首先需要用到一个外部的电平转换芯片MAX232,其具体配置电路原理图如下:

解析:

MAX232芯片是美信(MAXIM)公司专为RS-232标准串口设计的单电源电平转换芯片,使用+5v单电源供电。

主要特点:

1、符合所有的RS-232C技术标准;

2、只需要单一+5V电源供电;

3、片载电荷泵具有升压、电压极性反转能力,能够产生+10V和-10V电压V+、V-;

4、功耗低,典型供电电流5mA;

5、内部集成2个RS-232C驱动器;

6、高集成度,片外最低只需4个电容即可工作。

本设计还需要分析在通信过程中,UART所对应的数据格式如下:

起始位:线路空闲时为高电平,当截获第一个低电平比特时,则为起始位;

信息位:在起始位之后,按照低位首发原则,顺序发送信息位的最低位到最高位,信息位的宽度可以是4、5、6、7、8中的一个;

奇偶校验位:信息位之后则是一个可选的奇偶校验位,它可以是无校验(NONE)、奇校验(ODD)、偶校验(EVEN)中的任意一个,无校验时,信息位之后就是停止位。奇偶校验是,使得信息位和校验位的所有1的个数保持奇数或者偶数位;

停止位:停止位的长度可以是1、1.5或2中的任意一个,它为高电平;

空闲位:持续的高电平。

波特率:每秒传输的数据位(bit)数为波特率。RS-232-C的波特率可以是50、75、100、150、300、600、1200、2400、4800、9600、19200波特。

通过分析上述的数据格式,在本设计中,将波特率设置为9600,起始位设置为1比特,信息位设置为8比特,奇偶校验位设置为0比特,停止位设置为2比特,空闲位设置为1比特。

因为在设计中只需要关注RS232_TXD和RS232_RXD这两个信号,既然只有两条线,所以只需要关注其数据收发时序即可,时序图如下:

设计架构

设计总架构图如下:

uart_pll模块是一个锁相环,通过50M的外部时钟(ref_clk),倍频得到100M的上游接口的100M系统时钟(sys_clk);divider模块为UART的分频模块,通过用100M的sys_clk作为输入,分频得到波特率为9600的uart_clk时钟。

transmitter模块为串口发送模块,并配合与其对应的trans_fifo发送数据缓存FIFO进行使用,将储存在FIFO中的数据通过RS232-C协议发送出去;

receiver模块为串口接收模块,并配合与其对应的rec_fifo接收数据缓存FIFO进行使用,将储存在FIFO中的数据通过RS232-C协议接收进来;

UART发送器(transmitter)设计

UART发送器的时序如下图:

UART接收器(receiver)设计

根据对UART时序的分析可以得到如下的状态转移表(SMF):

设计代码

顶层uart_lsm模块代码:

代码语言:javascript
复制
`include "uart_lsm_head.v"

module uart_lsm(ref_clk, global_reset,tdata, twrreq, 
      tfull, rdata, rrdreq, rempty, uart_txd, uart_rxd);

  input ref_clk, global_reset;  //全局时钟复位
  input [7:0] tdata;  //发送fifo输入数据
  input twrreq;  //发送fifo写请求
   output tfull;   //发送fifo输出写满
  output [7:0] rdata;  //接收fifo输出数据
  input rrdreq;   //接收fifo的输入读请求
  output rempty;   //接收fifo的输出入空
   output uart_txd;  //输出发送线信号
  input uart_rxd;  //输入接收线信号

  wire trxd;

  wire [7:0] tf_data, rf_data;
  wire tf_rdreq, tf_empty, rf_wrreq;
  wire sys_clk, uart_clk, rst_n;


  assign rst_n = ~global_reset;

  trans_fifo t_fifo(    //发送fifo
    .data(tdata),
    .rdclk(uart_clk),
    .rdreq(tf_rdreq),
    .wrclk(sys_clk),
    .wrreq(twrreq),
    .q(tf_data),
    .rdempty(tf_empty),
    .wrfull(tfull)
  );

  transmitter trans(   //发送模块
    .clk(uart_clk), 
    .rst_n(rst_n), 
    .empty(tf_empty), 
    .data(tf_data), 
    .rdreq(tf_rdreq), 
    .txd(trxd)
  );

  rec_fifo r_fifo(     //接收fifo
    .data(rf_data),
    .rdclk(sys_clk),
    .rdreq(rrdreq),
    .wrclk(uart_clk),
    .wrreq(rf_wrreq),
    .q(rdata),
    .rdempty(rempty)
  );

  receiver rece(     //接收模块
    .clk(uart_clk), 
    .rst_n(rst_n), 
    .data(rf_data), 
    .wrreq(rfwrreq), 
    .rxd(trxd)
  );

  uart_pll u_pll(         //锁相环产生系统时钟,作用于fifo、divider
    .areset(global_reset),
    .inclk0(ref_clk),
    .c0(sys_clk)
  );

  divider_ebd_1s_mealy   //分频模块分频uart_clk,作用于receiver transmitter
  #(.HW(`DW), .LW(`DW))
  div(
    .clk_in(sys_clk), 
    .rst_n(rst_n), 
    .clk_out(uart_clk)
  );

endmodule 

transmitter模块代码:

代码语言:javascript
复制
//uart发送模块LSM(线性序列机)
module transmitter(clk, rst_n, empty, data, rdreq, txd);

  input clk, rst_n;  //输入时钟复位
  input empty;       //来自fifo的输入空标志信号
  input [7:0] data;  //来自fifo的输入数据
  output reg rdreq;  //输出到fifo的读请求
  output reg txd;    //输出发送线信号

  reg [7:0] temp;      //中间寄存器
  reg [7:0] count;    //8位计数

  `define EP 192   //终止符

  always @ (posedge clk or negedge rst_n)
  begin  : lsm_2s1   //线性序列机一段闭节点
    if (!rst_n)   //复位
      count <= `EP;
    else if ((count >= `EP) && !empty)  //计数大于终止符和非空(empty=0)
      count <= 0;
    else if (count < `EP) //计数小于终止符
      count <= count + 1;
  end 

  always @ (posedge clk or negedge rst_n)
  begin  : lsm_2s2  //线性序列机一段闭节点
    if (!rst_n)  //复位
      begin
        txd <= 1;   //发送线为高
        rdreq <= 0;  //读请求为0
        temp <= 0;  //中间寄存器为0
      end 
    else if ((count >= `EP) && !empty) //计数大于终止符fifo为非空,读请求拉高
        rdreq <= 1;
    else 
      case (count)
        0  :  begin
              rdreq <= 0;  //读请求拉低
              txd <= 0;
            end 
        1  :  temp[7:0] <= data[7:0];  //输入数据给中间寄存器
        1*16  :  txd <= temp[0];   //中间寄存器按位给发送线发送
        2*16  :  txd <= temp[1];
        3*16  :  txd <= temp[2];
        4*16  :  txd <= temp[3];
        5*16  :  txd <= temp[4];
        6*16  :  txd <= temp[5];
        7*16  :  txd <= temp[6];
        8*16  :  txd <= temp[7];
        9*16  :  txd <= 1;    //拉高
      endcase     
  end   

endmodule

接收模块receiver代码:

代码语言:javascript
复制
`include "uart_lsm_head.v"

module receiver(clk, rst_n, data, wrreq, rxd);   //uart接收模块LSM(线性序列机)

  input clk, rst_n;  //输入时钟复位
  output reg [7:0] data;  //输出数据
  output reg wrreq;   //输出写请求
  input rxd;    //输入接收线信号

  reg [7:0] count;
  //宏定义
  `define EP   184   //终止符
  `define GET0 24
  `define GET1 `GET0+16
  `define GET2 `GET1+16
  `define GET3 `GET2+16
  `define GET4 `GET3+16
  `define GET5 `GET4+16
  `define GET6 `GET5+16
  `define GET7 `GET6+16
  `define GETW `GET7+16  //wrreq=1
  `define GLRW `GETW+1   //wrreq=0

  always @ (posedge clk or negedge rst_n)
  begin : lsm_2s1   //线性序列机一段闭节点
    if(!rst_n)
      count <= `EP;
    else if((count >= `EP) && !rxd) //rxd=0
      count <= 0;
    else if (count < `EP)
      count <= count + 1;
  end

  always @ (posedge clk or negedge rst_n)
    begin  : lsm_2s2   //线性序列机二段闭节点     
      if(!rst_n)
        begin      
          data <= 0;
          wrreq <= 0;      //写请求为0
        end 
      else
        case(count)
          `GET0  :  data[0] <= rxd;   //将接收的数据通过data输出
          `GET1  :  data[1] <= rxd;
          `GET2 :   data[2] <= rxd;
          `GET3 :   data[3] <= rxd;
          `GET4 :   data[4] <= rxd;
          `GET5 :   data[5] <= rxd;
          `GET6 :   data[6] <= rxd;
          `GET7 :   data[7] <= rxd;
          `GETW :   wrreq <= 1;   //写请求拉高一拍,写进fifo
          `GLRW :   wrreq <= 0;   //一拍后写请求为0
        endcase
    end

endmodule 

参数宏的头文件代码:

代码语言:javascript
复制
/////uart_lsm_head.v

//////////定义时标////////////
`timescale 1us/1ns

/////////定义设计参数/////////
`define BAUD_RATE 9600   //波特率=9600
`define SYS_CLK 100000000 //系统时钟sys_clk 频率=100M
`define REF_CLK 50000000 //系统时钟ref_clk频率=50M

//////////使用宏自动计算的诸参数////////////
`define TBAUD_RATE (1000000.0/`BAUD_RATE)//波特率周期
`define UART_CLK (16*`BAUD_RATE)        //uart_clk 等于16倍波特率
`define TUART_CLK (1000000.0/`UART_CLK) //uart_clk周期
`define TEN_TUART_CLK (10.0*`TUART_CLK) //10倍uart_clk周期
`define TUART_CLK100 (100.0*`TUART_CLK) //100倍uart_clk周期

`define TUART_CLK_HALF (`TUART_CLK/2.0) //uart_clk半周期
`define TREF_CLK (1000000.0/`REF_CLK)  //参考时钟周期
`define TREF_CLK_HALF (`TREF_CLK/2.0)  //参考时钟半周期

//////////使用宏自动计算的分频数(占空比50%)////////////
`define DW (`SYS_CLK/(2*`UART_CLK))

仿真测试

transmitter(发送)模块的测试代码:

代码语言:javascript
复制
`include "uart_lsm_head.v"

module transmitter_tb;

  reg clk, rst_n;
  reg empty;
  reg [7:0] data;
  wire rdreq;
  wire txd;
  
  reg [7:0] temp;

  transmitter transmitter_dut(
      .clk(clk),
      .rst_n(rst_n),
      .empty(empty),
      .data(data),
      .rdreq(rdreq),
      .txd(txd)
    );

  initial begin
    clk = 1;
    rst_n = 0;
    data = 0;
    empty = 1;
    temp = 0;
    #200.1 rst_n = 1;
    
    #200.1 empty=1;temp=8'h55;
    #`TBAUD_RATE
    data[0] = temp[0];  //发送第一个信息位(LSB)
    #`TBAUD_RATE
    data[1] = temp[1];
    #`TBAUD_RATE
    data[2] = temp[2];
    #`TBAUD_RATE
    data[3] = temp[3];
    #`TBAUD_RATE
    data[4] = temp[4];
    #`TBAUD_RATE
    data[5] = temp[5];
    #`TBAUD_RATE
    data[6] = temp[6];
    #`TBAUD_RATE
    data[7] = temp[7];
    #`TBAUD_RATE
    empty = 0;  
    #2000 $stop;    
    end
  
  always #`TUART_CLK_HALF clk = ~clk;
  
endmodule 

receiver(接收)模块的测试代码:

代码语言:javascript
复制
`include "uart_lsm_head.v"

module receiver_tb;

  reg clk, rst_n;
  reg rxd;
  wire [7:0] data;
  wire wrreq;
  
  reg [7:0] temp; //8位的中间寄存器,产生激励
  
  receiver receiver_dut(
    .clk(clk),
    .rst_n(rst_n),
    .data(data),
    .wrreq(wrreq),
    .rxd(rxd)
  );
  
  initial begin
    clk = 1;
    rst_n = 0;
    temp = 0;
    rxd = 1;
    #`TEN_TUART_CLK  //*代表异步 //10倍uart_clk周期
    rst_n = 1;
    
    #`TEN_TUART_CLK  //启动一个停止位  
    rxd = 0;  
    temp = 8'h55;  
    #`TBAUD_RATE    //数据使用波特率的周期
    rxd = temp[0];  //发送一个信息位(LSB)
    #`TBAUD_RATE
    rxd = temp[1];
    #`TBAUD_RATE
    rxd = temp[2];
    #`TBAUD_RATE
    rxd = temp[3];
    #`TBAUD_RATE
    rxd = temp[4];
    #`TBAUD_RATE
    rxd = temp[5];
    #`TBAUD_RATE
    rxd = temp[6];
    #`TBAUD_RATE
    rxd = temp[7];   //发送最后一个信息位(HSB)
    #`TBAUD_RATE
    rxd = 1;
    
    #`TUART_CLK100 $stop;   //100倍uart_clk周期
  end
    
  always #`TUART_CLK_HALF clk = ~clk;  // uart_clk 的时钟,使用uart_clk的半周期

endmodule 

仿真图:分别为发送和接收做仿真测试。

发送的仿真波形如下:

接收的仿真波形如下:

根据以上两个仿真波形,可以发现设计是正确的,之后则可利用串口猎人的上位机软件,实现自发自收。

END

后续会持续更新,带来Vivado、 ISE、Quartus II 、candence等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。

大侠们,江湖偌大,继续闯荡,愿一切安好,有缘再见!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FPGA技术江湖 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档