FPGA学习系列:28. 串口uart设计

描述

设计背景: 

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

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

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


设计原理: 

    uart的示意图如下:

    


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


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

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

        FPGA


注解:

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

主要特点:

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

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

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

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

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

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

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

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

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

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

停止位:停止位的长度可以是11.52中的任意一个,它为高电平;

空闲位:持续的高电平;

波特率:每秒传输的数据位(bit)数为波特率。RS-232-C的波特率可以是5075100150300600120024004800960019200波特。

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

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

FPGA


设计架构图: 

设计架构图如下

FPGA


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

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

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


UART发送器(transmitter)设计

UART发送器的时序如下图:

FPGA


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

FPGA


UART接收器(receiver)设计

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

FPGA


设计代码: 

根据上述的两个状态转移表则可得到如下的代码,transmitter模块代码:

0   //uart发送模块LSM(线性序列机)

1   module transmitter(clk, rst_n, empty, data, rdreq, txd);

2 

3       input clk, rst_n;  //输入时钟复位

4       input empty;       //来自fifo的输入空标志信号

5       input [7:0] data;  //来自fifo的输入数据

6       output reg rdreq;  //输出到fifo的读请求

7       output reg txd;    //输出发送线信号

8 

9       reg [7:0] temp;      //中间寄存器

10      reg [7:0] count;    //8位计数

11      

12      `define EP 192   //终止符

13      

14      always @ (posedge clk or negedge rst_n)

15      begin   : lsm_2s1   //线性序列机一段闭节点

16          if (!rst_n)   //复位

17              count <= `EP;

18          else if ((count >= `EP) && !empty)  //计数大于终止符和非空(empty=0

19              count <= 0;

20          else if (count < `EP) //计数小于终止符

21              count <= count + 1;

22      end 

23      

24      always @ (posedge clk or negedge rst_n)

25      begin   : lsm_2s2  //线性序列机一段闭节点

26          if (!rst_n)  //复位

27              begin

28                  txd <= 1;   //发送线为高

29                  rdreq <= 0;  //读请求为0

30                  temp <= 0;  //中间寄存器为0

31              end 

32          else if ((count >= `EP) && !empty) //计数大于终止符fifo为非空,读请求拉高

33                  rdreq <= 1;

34          else 

35              case (count)

36                  0   :   begin

37                              rdreq <= 0;  //读请求拉低

38                              txd <= 0;

39                          end 

40                  1   :   temp[7:0] <= data[7:0];  //输入数据给中间寄存器

41                  1*16    :   txd <= temp[0];   //中间寄存器按位给发送线发送

42                  2*16    :   txd <= temp[1];

43                  3*16    :   txd <= temp[2];

44                  4*16    :   txd <= temp[3];

45                  5*16    :   txd <= temp[4];

46                  6*16    :   txd <= temp[5];

47                  7*16    :   txd <= temp[6];

48                  8*16    :   txd <= temp[7];

49                  9*16    :   txd <= 1;    //拉高

50              endcase         

51      end     

52

53  endmodule 


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

0   `include "uart_lsm_head.v"

1 

2   module transmitter_tb;

3 

4       reg clk, rst_n;

5       reg empty;

6       reg [7:0] data;

7       wire rdreq;

8       wire txd;

9       

10      reg [7:0] temp;

11

12      transmitter transmitter_dut(

13              .clk(clk),

14              .rst_n(rst_n),

15              .empty(empty),

16              .data(data),

17              .rdreq(rdreq),

18              .txd(txd)

19          );

20

21      initial begin

22          clk = 1;

23          rst_n = 0;

24          data = 0;

25          empty = 1;

26          temp = 0;

27          #200.1 rst_n = 1;

28          

29          #200.1 empty=1;temp=8'h55;

30          #`TBAUD_RATE

31          data[0] = temp[0];  //发送第一个信息位(LSB

32          #`TBAUD_RATE

33          data[1] = temp[1];

34          #`TBAUD_RATE

35          data[2] = temp[2];

36          #`TBAUD_RATE

37          data[3] = temp[3];

38          #`TBAUD_RATE

39          data[4] = temp[4];

40          #`TBAUD_RATE

41          data[5] = temp[5];

42          #`TBAUD_RATE

43          data[6] = temp[6];

44          #`TBAUD_RATE

45          data[7] = temp[7];

46          #`TBAUD_RATE

47          empty = 0;  

48          #2000 $stop;        

49          end

50      

51      always #`TUART_CLK_HALF clk = ~clk;

52      

53  endmodule 


receiver模块代码:

0   `include "uart_lsm_head.v"

1 

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

3       

4       input clk, rst_n;  //输入时钟复位

5       output reg [7:0] data;  //输出数据

6       output reg wrreq;   //输出写请求

7       input rxd;    //输入接收线信号

8 

9       reg [7:0] count;

10      //宏定义

11      `define EP   184   //终止符

12      `define GET0 24

13      `define GET1 `GET0+16

14      `define GET2 `GET1+16

15      `define GET3 `GET2+16

16      `define GET4 `GET3+16

17      `define GET5 `GET4+16

18      `define GET6 `GET5+16

19      `define GET7 `GET6+16

20      `define GETW `GET7+16  //wrreq=1

21      `define GLRW `GETW+1   //wrreq=0

22      

23      always @ (posedge clk or negedge rst_n)

24      begin : lsm_2s1   //线性序列机一段闭节点

25          if(!rst_n)

26              count <= `EP;

27          else if((count >= `EP) && !rxd) //rxd=0

28              count <= 0;

29          else if (count < `EP)

30              count <= count + 1;

31      end

32      

33      always @ (posedge clk or negedge rst_n)

34          begin   : lsm_2s2   //线性序列机二段闭节点        

35              if(!rst_n)

36                  begin           

37                      data <= 0;

38                      wrreq <= 0;      //写请求为0

39                  end 

40              else

41                  case(count)

42                      `GET0   :   data[0] <= rxd;   //将接收的数据通过data输出

43                      `GET1   :   data[1] <= rxd;

44                      `GET2 :     data[2] <= rxd;

45                      `GET3 :     data[3] <= rxd;

46                      `GET4 :     data[4] <= rxd;

47                      `GET5 :     data[5] <= rxd;

48                      `GET6 :     data[6] <= rxd;

49                      `GET7 :     data[7] <= rxd;

50                      `GETW :     wrreq <= 1;   //写请求拉高一拍,写进fifo

51                      `GLRW :     wrreq <= 0;   //一拍后写请求为0

52                  endcase

53          end

54          

55  endmodule 


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

0   `include "uart_lsm_head.v"

1 

2   module receiver_tb;

3 

4       reg clk, rst_n;

5       reg rxd;

6       wire [7:0] data;

7       wire wrreq;

8       

9       reg [7:0] temp; //8位的中间寄存器,产生激励

10      

11      receiver receiver_dut(

12          .clk(clk),

13          .rst_n(rst_n),

14          .data(data),

15          .wrreq(wrreq),

16          .rxd(rxd)

17      );

18      

19      initial begin

20          clk = 1;

21          rst_n = 0;

22          temp = 0;

23          rxd = 1;

24          #`TEN_TUART_CLK  //*代表异步 //10uart_clk周期

25          rst_n = 1;

26          

27          #`TEN_TUART_CLK  //启动一个停止位    

28          rxd = 0;    

29          temp = 8'h55;   

30          #`TBAUD_RATE    //数据使用波特率的周期

31          rxd = temp[0];  //发送一个信息位(LSB

32          #`TBAUD_RATE

33          rxd = temp[1];

34          #`TBAUD_RATE

35          rxd = temp[2];

36          #`TBAUD_RATE

37          rxd = temp[3];

38          #`TBAUD_RATE

39          rxd = temp[4];

40          #`TBAUD_RATE

41          rxd = temp[5];

42          #`TBAUD_RATE

43          rxd = temp[6];

44          #`TBAUD_RATE

45          rxd = temp[7];   //发送最后一个信息位(HSB

46          #`TBAUD_RATE

47          rxd = 1;

48          

49          #`TUART_CLK100 $stop;   //100uart_clk周期

50      end

51          

52      always #`TUART_CLK_HALF clk = ~clk;  // uart_clk 的时钟,使用uart_clk的半周期

53

54  endmodule 


参数宏的头文件代码:

0   /////uart_lsm_head.v

1 

2   //////////定义时标////////////

3   `timescale 1us/1ns

4 

5   /////////定义设计参数/////////

6   `define BAUD_RATE 9600   //波特率=9600

7   `define SYS_CLK 100000000 //系统时钟sys_clk 频率=100M

8   `define REF_CLK 50000000 //系统时钟ref_clk频率=50M

9 

10  //////////使用宏自动计算的诸参数////////////

11  `define TBAUD_RATE (1000000.0/`BAUD_RATE)//波特率周期

12  `define UART_CLK (16*`BAUD_RATE)        //uart_clk 等于16倍波特率

13  `define TUART_CLK (1000000.0/`UART_CLK) //uart_clk周期

14  `define TEN_TUART_CLK (10.0*`TUART_CLK) //10uart_clk周期

15  `define TUART_CLK100 (100.0*`TUART_CLK) //100uart_clk周期

16

17  `define TUART_CLK_HALF (`TUART_CLK/2.0) //uart_clk半周期

18  `define TREF_CLK (1000000.0/`REF_CLK)  //参考时钟周期

19  `define TREF_CLK_HALF (`TREF_CLK/2.0)  //参考时钟半周期

20

21  //////////使用宏自动计算的分频数(占空比50%////////////

22  `define DW (`SYS_CLK/(2*`UART_CLK))


顶层文件代码:

0   `include "uart_lsm_head.v"

1 

2   module uart_lsm(ref_clk, global_reset,tdata, twrreq, 

3               tfull, rdata, rrdreq, rempty, uart_txd, uart_rxd);

4       

5       input ref_clk, global_reset;  //全局时钟复位

6       input [7:0] tdata;  //发送fifo输入数据

7       input twrreq;  //发送fifo写请求

8       output tfull;   //发送fifo输出写满

9       output [7:0] rdata;  //接收fifo输出数据

10      input rrdreq;   //接收fifo的输入读请求

11      output rempty;   //接收fifo的输出入空

12      output uart_txd;  //输出发送线信号

13      input uart_rxd;  //输入接收线信号

14      

15      wire trxd;

16      

17      wire [7:0] tf_data, rf_data;

18      wire tf_rdreq, tf_empty, rf_wrreq;

19      wire sys_clk, uart_clk, rst_n;

20

21      

22      assign rst_n = ~global_reset;

23       

24      trans_fifo t_fifo(    //发送fifo

25          .data(tdata),

26          .rdclk(uart_clk),

27          .rdreq(tf_rdreq),

28          .wrclk(sys_clk),

29          .wrreq(twrreq),

30          .q(tf_data),

31          .rdempty(tf_empty),

32          .wrfull(tfull)

33      );

34      

35      transmitter trans(   //发送模块

36          .clk(uart_clk), 

37          .rst_n(rst_n), 

38          .empty(tf_empty), 

39          .data(tf_data), 

40          .rdreq(tf_rdreq), 

41          .txd(trxd)

42      );

43

44      rec_fifo r_fifo(     //接收fifo

45          .data(rf_data),

46          .rdclk(sys_clk),

47          .rdreq(rrdreq),

48          .wrclk(uart_clk),

49          .wrreq(rf_wrreq),

50          .q(rdata),

51          .rdempty(rempty)

52      );

53      

54      receiver rece(     //接收模块

55          .clk(uart_clk), 

56          .rst_n(rst_n), 

57          .data(rf_data), 

58          .wrreq(rfwrreq), 

59          .rxd(trxd)

60      );

61

62      uart_pll u_pll(         //锁相环产生系统时钟,作用于fifodivider

63          .areset(global_reset),

64          .inclk0(ref_clk),

65          .c0(sys_clk)

66      );

67      

68      divider_ebd_1s_mealy   //分频模块分频uart_clk,作用于receiver transmitter

69      #(.HW(`DW), .LW(`DW))

70      div(

71          .clk_in(sys_clk), 

72          .rst_n(rst_n), 

73          .clk_out(uart_clk)

74      );

75      

76  endmodule 

 

 

仿真图: 

分别为发送和接收做仿真测试,发送的仿真波形如下:

FPGA


接收的仿真波形如下:

FPGA


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




打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分