串口UART学习笔记(一)

        买了一个开发板学习FPGA,找到的各种东西就记录在这个博客里了,同时也方便把自己不会的问题找到的结果记录一下,都是自己手打,所以可能说的话不那么严谨,不那么精准,看到的人要带着自己的思考去看,记住尽信书不如无书,哈哈哈。。。。。。

         一、UART是什么?

         UART是一种通用串行数据总线,也就是用于数据传输。是用于主机与辅助设备进行通信。这里的主机理解为计算机,计算机内部采用并行数据,辅助设备采用串行数据。中间需要设备进行数据转换,这也决定了UART工作原理是将传输数据的每个字符一位接一位地传输。UART采用异步传输模式,异步传输将比特分成小组进行传送,小组可以是8位的1个字符或更长。发送方可以在任何时刻发送这些比特组,而接收方从不知道它们会在什么时候到达。例如计算机键盘与主机的通信。UART用于远距离传输较为适合。可以数据同时发送和接收。

                                                1,异步传输是面向字符的传输,而同步传输是面向比特的传输。

                                                2,异步传输的单位是字符同步传输的单位是帧。
                                                3,异步传输通过字符起始和停止码抓住再同步的机会,而同步传输则是在数据中抽取同步信息。
                                                4,异步传输对时序的要求较低,同步传输往往通过特定的时钟线路协调时序。
                                                5,异步传输相对于同步传输效率较低。
我的理解是对时序要求是采用UART的原因,数据到达需要给出到达信号也解释了UART采用8位1字符的串行数据输出模式,也解释了协议中起始位与停止位的重要性。
uart指通用异步收发传输器,本质上是硬件,用来异步传输数据。RS232是一种物理层协议,规定了特定的接口标准。https://www.zhihu.com/question/22632011 
 
进行数据传输时,需要考虑时钟同步问题,传输速率有一个很重要的概念。波特率是衡量资料传送速率的指标。表示每秒钟传送的符号数(symbol)。一个符号代表的信息量(比特数)与符号的阶数有关。例如资料传送速率为120字符/秒,传输使用256阶符号,每个符号代表8bit,则波特率就是120baud,比特率是120*8=960bit/s。位 bit (比特)(Binary Digits):存放一位二进制数,即 0 或 1,最小的存储单位。字节Byte:8个二进制位为一个字节(B),最常用的单位。
常用波特率9600,19200,38400,115200等。
其中各位的意义如下: 
起始位:先发出一个逻辑”0”信号,表示传输字符的开始。 
数据位:可以是5~8位逻辑”0”或”1”。如ASCII码(7位),扩展BCD码(8位)。 
校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验)。 
停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 
空闲位:处于逻辑“1”状态,表示当前线路上没有资料传送。

     二、UART串口通信一般包括部分
    任何程序都包括一个主控制程序,因为是双向通信,还需要串口发送程序,串口接收程序,时钟控制程序。举例来分别解释四个程序的代码。
      这里采用50MHz的系统时钟,产生UART时钟信号产生和发送的波特率为9600bps。为了保证采样的时候不会发生误码或者滑码,我们对于一位数据不只采用一个时钟周期进行数据采集,而采用16个时钟周期。那么就需要对时钟进行分频处理。50,000,000/(16*9600),分频系数取整为326 。偶数分频比较简单,此处采用了326,至于奇数分频以及如何做出占空比为50%以后整理。
 
 
 1 `timescale 1ns / 1ps
 2 //////////////////////////////////////////////////////////////////////////////////
 3 // Module Name:    clkdiv
 4 // 产生一个波特率9600的16倍频的时钟,9600*16= 153600
 5 // 相当于50MHz的326分频,50000000/153600=326
 6 //////////////////////////////////////////////////////////////////////////////////
 7 module clkdiv(clk50, clkout);
 8 input clk50;              //系统时钟
 9 output clkout;          //采样时钟输出
10 reg clkout;
11 reg [15:0] cnt;
12 
13 //分频进程,对50Mhz的时钟326分频
14 always @(posedge clk50)   
15 begin
16   if(cnt == 16'd162)
17   begin
18     clkout <= 1'b1;
19     cnt <= cnt + 16'd1;
20   end
21   else if(cnt == 16'd325)
22   begin
23     clkout <= 1'b0;
24     cnt <= 16'd0;
25   end
26   else
27   begin
28     cnt <= cnt + 16'd1;
29   end
30 end
31 endmodule

分频比较简单,我写的另一个,

 1  `timescale 1ns / 1ps
 2 
 3  module clkdiv(clk50, clkout);
 4  input clk50;              //系统时钟
 5  output clkout;          //采样时钟输出
 6  reg clkout;
 7  reg [15:0] cnt;
 8 
 9 always @(posedge clk50)
10     begin
11       if (cnt < 16'd162)
12       cnt <= cnt + 16'b1;
13      else  if  (cnt == 16'd162)
14       begin
15           clkout <= ~clkout;
16           cnt <= 16'b0;
17       end
18      end

三、串口发送程序

     UART采用异步传输,就涉及起始位与停止位,下面是代码例子,我的总结用红笔标出。

  1 `timescale 1ns / 1ps
  2 //////////////////////////////////////////////////////////////////////////////////
  3 // Module Name:    uarttx 
  4 // 说明:16个clock发送一个bit,
  5 //////////////////////////////////////////////////////////////////////////////////
  6 module uarttx(clk, datain, wrsig, idle, tx);
  7 input clk;                //UART时钟
  8 input [7:0] datain;       //需要发送的数据
  9 input wrsig;              //发送命令,上升沿有效
 10 output idle;              //线路状态指示,高为线路忙,低为线路空闲
 11 output tx;                //发送数据信号
 12 reg idle, tx;
 13 reg send;
 14 reg wrsigbuf, wrsigrise;
 15 reg presult;
 16 reg[7:0] cnt;             //计数器
 17 parameter paritymode = 1'b0;
 18 
 19 //检测发送命令是否有效,判断wrsig的上升沿            //先检测发送命令是否有效,然后判断线路状态
 20 always @(posedge clk)
 21 begin
 22    wrsigbuf <= wrsig;
 23    wrsigrise <= (~wrsigbuf) & wrsig;         //边沿检测,检测发送命令
 24 end
 25 
 26 always @(posedge clk)
 27 begin
 28   if (wrsigrise &&  (~idle))  //当发送命令有效且线路为空闲时,启动新的数据发送进程
 29   begin
 30      send <= 1'b1;
 31   end
 32   else if(cnt == 8'd168)      //一帧资料发送结束
 33   begin
 34      send <= 1'b0;
 35   end
 36 end
 37 
 38 /////////////////////////////////////////////////////////////////////////
 39 //使用168个时钟发送一个数据(起始位、8位数据、奇偶校验位、停止位),每位占用16个时钟//    //停止位为8个时钟周期
 40 ////////////////////////////////////////////////////////////////////////
 41 always @(posedge clk)
 42 begin
 43   if(send == 1'b1)  begin
 44     case(cnt)                 //tx变低电平产生起始位,0~15个时钟为发送起始位
 45     8'd0: begin
 46          tx <= 1'b0;
 47          idle <= 1'b1;
 48          cnt <= cnt + 8'd1;
 49     end
 50     8'd16: begin
 51          tx <= datain[0];    //发送数据位的低位bit0,占用第16~31个时钟
 52          presult <= datain[0]^paritymode;     //奇偶校验位的获取
53 idle <= 1'b1; 54 cnt <= cnt + 8'd1; 55 end 56 8'd32: begin 57 tx <= datain[1]; //发送数据位的第2位bit1,占用第47~32个时钟 58 presult <= datain[1]^presult; 59 idle <= 1'b1; 60 cnt <= cnt + 8'd1; 61 end 62 8'd48: begin 63 tx <= datain[2]; //发送数据位的第3位bit2,占用第63~48个时钟 64 presult <= datain[2]^presult; 65 idle <= 1'b1; 66 cnt <= cnt + 8'd1; 67 end 68 8'd64: begin 69 tx <= datain[3]; //发送数据位的第4位bit3,占用第79~64个时钟 70 presult <= datain[3]^presult; 71 idle <= 1'b1; 72 cnt <= cnt + 8'd1; 73 end 74 8'd80: begin 75 tx <= datain[4]; //发送数据位的第5位bit4,占用第95~80个时钟 76 presult <= datain[4]^presult; 77 idle <= 1'b1; 78 cnt <= cnt + 8'd1; 79 end 80 8'd96: begin 81 tx <= datain[5]; //发送数据位的第6位bit5,占用第111~96个时钟 82 presult <= datain[5]^presult; 83 idle <= 1'b1; 84 cnt <= cnt + 8'd1; 85 end 86 8'd112: begin 87 tx <= datain[6]; //发送数据位的第7位bit6,占用第127~112个时钟 88 presult <= datain[6]^presult; 89 idle <= 1'b1; 90 cnt <= cnt + 8'd1; 91 end 92 8'd128: begin 93 tx <= datain[7]; //发送数据位的第8位bit7,占用第143~128个时钟 94 presult <= datain[7]^presult; 95 idle <= 1'b1; 96 cnt <= cnt + 8'd1; 97 end 98 8'd144: begin 99 tx <= presult; //发送奇偶校验位,占用第159~144个时钟 //将计算结果作为奇偶校验码输出 100 presult <= datain[0]^paritymode; //无意思,此行计算结果无用,多余
101 idle <= 1'b1; 102 cnt <= cnt + 8'd1; 103 end 104 8'd160: begin 105 tx <= 1'b1; //发送停止位,占用第160~167个时钟 106 idle <= 1'b1; 107 cnt <= cnt + 8'd1; 108 end 109 8'd168: begin 110 tx <= 1'b1; 111 idle <= 1'b0; //一帧资料发送结束 112 cnt <= cnt + 8'd1; 113 end 114 default: begin 115 cnt <= cnt + 8'd1; 116 end 117 endcase 118 end 119 else begin 120 tx <= 1'b1; 121 cnt <= 8'd0; 122 idle <= 1'b0; 123 end 124 end 125 endmodule

四、串口接收程序

      成为UART接收信号,将一位一位的串口数据转化为并行数据。

  1 `timescale 1ns / 1ps
  2 //////////////////////////////////////////////////////////////////////////////////
  3 // Module name    uartrx.v
  4 // 说明:          16个clock接收一个bit,16个时钟采样,取中间的采样值
  5 //////////////////////////////////////////////////////////////////////////////////
  6 module uartrx(clk, rx, dataout, rdsig, dataerror, frameerror);
  7 input clk;             //采样时钟
  8 input rx;              //UART数据输入
  9 output dataout;        //接收数据输出
 10 output rdsig;          //接收数据有效,高说明接收到一个字节 ,来区分数据处于接收状态或者接收控制信号
 11 output dataerror;      //数据出错指示
 12 output frameerror;     //帧出错指示
 13 
 14 reg[7:0] dataout;
 15 reg rdsig, dataerror;
 16 reg frameerror;
 17 reg [7:0] cnt;
 18 reg rxbuf, rxfall, receive;
 19 parameter paritymode = 1'b0;
 20 reg presult, idle;
 21 
 22 always @(posedge clk)   //检测线路rx的下降沿, 线路空闲的时候rx为高电平
 23 begin
 24   rxbuf <= rx;
 25   rxfall <= rxbuf & (~rx);      //下降沿检测,检测是否接收到接收信号
26 end 27 28 always @(posedge clk) 29 begin 30 if (rxfall && (~idle)) //检测到线路的下降沿并且原先线路为空闲,启动接收数据进程 31 begin 32 receive <= 1'b1; //开始接收数据 33 end 34 else if(cnt == 8'd168) //接收数据完成 35 begin 36 receive <= 1'b0; 37 end 38 end 39 40 ///////////////////////////////////////////////////////////////////////// 41 //使用176个时钟接收一个数据(起始位、8位数据、奇偶校验位、停止位),每位占用16个时钟// 42 //////////////////////////////////////////////////////////////////////// 43 always @(posedge clk) 44 begin 45 if(receive == 1'b1) 46 begin 47 case (cnt) 48 8'd0: //0~15个时钟为接收第一个比特,起始位 49 begin 50 idle <= 1'b1; 51 cnt <= cnt + 8'd1; 52 rdsig <= 1'b0; 53 end 54 8'd24: //16~31个时钟为第1个bit数据,取中间第24个时钟的采样值 //在发送程序中,第0位数据在16个时钟周期后开始传输,接收过程中, 55 begin //从第24个时钟周期开始接收第0位数据,保证信号被采集。 56 idle <= 1'b1; 57 dataout[0] <= rx; 58 presult <= paritymode^rx; 59 cnt <= cnt + 8'd1; 60 rdsig <= 1'b0; 61 end 62 8'd40: //47~32个时钟为第2个bit数据,取中间第40个时钟的采样值 63 begin 64 idle <= 1'b1; 65 dataout[1] <= rx; 66 presult <= presult^rx; 67 cnt <= cnt + 8'd1; 68 rdsig <= 1'b0; 69 end 70 8'd56: //63~48个时钟为第3个bit数据,取中间第56个时钟的采样值 71 begin 72 idle <= 1'b1; 73 dataout[2] <= rx; 74 presult <= presult^rx; 75 cnt <= cnt + 8'd1; 76 rdsig <= 1'b0; 77 end 78 8'd72: //79~64个时钟为第4个bit数据,取中间第72个时钟的采样值 79 begin 80 idle <= 1'b1; 81 dataout[3] <= rx; 82 presult <= presult^rx; 83 cnt <= cnt + 8'd1; 84 rdsig <= 1'b0; 85 end 86 8'd88: //95~80个时钟为第5个bit数据,取中间第88个时钟的采样值 87 begin 88 idle <= 1'b1; 89 dataout[4] <= rx; 90 presult <= presult^rx; 91 cnt <= cnt + 8'd1; 92 rdsig <= 1'b0; 93 end 94 8'd104: //111~96个时钟为第6个bit数据,取中间第104个时钟的采样值 95 begin 96 idle <= 1'b1; 97 dataout[5] <= rx; 98 presult <= presult^rx; 99 cnt <= cnt + 8'd1; 100 rdsig <= 1'b0; 101 end 102 8'd120: //127~112个时钟为第7个bit数据,取中间第120个时钟的采样值 103 begin 104 idle <= 1'b1; 105 dataout[6] <= rx; 106 presult <= presult^rx; 107 cnt <= cnt + 8'd1; 108 rdsig <= 1'b0; 109 end 110 8'd136: //143~128个时钟为第8个bit数据,取中间第136个时钟的采样值 111 begin 112 idle <= 1'b1; 113 dataout[7] <= rx; 114 presult <= presult^rx; 115 cnt <= cnt + 8'd1; 116 rdsig <= 1'b1; //接收数据有效 117 end 118 8'd152: //159~144个时钟为接收奇偶校验位,取中间第152个时钟的采样值 119 begin 120 idle <= 1'b1; 121 if(presult == rx) 122 dataerror <= 1'b0; 123 else 124 dataerror <= 1'b1; //如果奇偶校验位不对,表示数据出错 125 cnt <= cnt + 8'd1; 126 rdsig <= 1'b1; 127 end 128 8'd168: //160~175个时钟为接收停止位,取中间第168个时钟的采样值 129 begin 130 idle <= 1'b1; 131 if(1'b1 == rx) 132 frameerror <= 1'b0; 133 else 134 frameerror <= 1'b1; //如果没有接收到停止位,表示帧出错 135 cnt <= cnt + 8'd1; 136 rdsig <= 1'b1; 137 end 138 default: 139 begin 140 cnt <= cnt + 8'd1; 141 end 142 endcase 143 end 144 else 145 begin 146 cnt <= 8'd0; 147 idle <= 1'b0; 148 rdsig <= 1'b0; 149 end 150 end 151 endmodule

五、控制程序

     这里主要学习程序的调用,如何用总的控制程序完成UART数据传输。

 1 `timescale 1ns / 1ps
 2 //////////////////////////////////////////////////////////////////////////////////
 3 // Module Name:    uart_test 
 4 // 
 5 //////////////////////////////////////////////////////////////////////////////////
 6 module uart_test(clk50, rx, tx, reset);
 7 input clk50;
 8 input reset;
 9 input rx;
10 output tx;
11 
12 wire clk;       //clock for 9600 uart port
13 wire [7:0] txdata,rxdata;     //串口发送数据和串口接收数据
14 
15 
16 
17 //产生时钟的频率为16*9600
18 clkdiv u0 (
19         .clk50                   (clk50),               //50Mhz的晶振输入                     
20         .clkout                  (clk)                  //16倍波特率的时钟                        
21  );
22 
23 //串口接收程序
24 uartrx u1 (
25         .clk                     (clk),                 //16倍波特率的时钟 
26       .rx                       (rx),                     //串口接收
27         .dataout                 (rxdata),              //uart 接收到的数据,一个字节                     
28       .rdsig                   (rdsig),               //uart 接收到数据有效 
29         .dataerror               (),
30         .frameerror              ()
31 );
32 
33 //串口发送程序
34 uarttx u2 (
35         .clk                     (clk),                  //16倍波特率的时钟  
36        .tx                      (tx),                      //串口发送
37         .datain                  (txdata),               //uart 发送的数据   
38       .wrsig                   (wrsig),                //uart 发送的数据有效  
39       .idle                    ()     
40     
41  );

54 endmodule

就像c语言里调用子函数一样,每个 模块是并行运行的, 各个模块连接完成整个系统需要一个顶层文件(top-module) 。 顶层文件 通过调用、连接低层模块的实例来实现复杂的功能。

学习UART通信,最主要还是理解异步和串口这两个东西,方便远距离传输,串口一位一位传输,牺牲了时间降低时序要求。

 
 
 
 
 
 
 
 
 
 
原文地址:https://www.cnblogs.com/uiojhi/p/7531478.html