串口的学习

注释:这段串口是自己学习特权老师后的一个总结,由于特权的视频很短(视频和具体代码可到网站上下载),讲得也不是很详细,自己学习后就整理总结了哈,并把代码中自己的一些理解写了出来,希望能够帮助初学者的学习,由于自己也才是初学,希望大家给予更多的建议。

串口的学习

相关理论:

串口通信是目前比较重要的一种通信方式,主要是用于计算机和外部的通信。首先简单的介绍一下串口通信的原理:

串口用于ASCII码字符的传输。通信使用3根线完成:(1)地线,(2)发送,(3)接收。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通行的端口,这些参数必须匹配: a,波特率:这是一个衡量通信速度的参数。它表示每秒钟传送的bit的个数。例如300波特表示每秒钟发送300个bit。当我们提到时钟周期时,我们就是指波特率例如如果协议需要4800波特率,那么时钟是4800Hz。这意味着串口通信在数据线上的采样率为4800Hz。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是GPIB设备的通信。 b,数据位:这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。 c,停止位:用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。 d,奇偶校验位:在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位位1,这样就有3个逻辑高位。高位和低位不真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步

对于CPLD/FPGA设计者而言,只需要关心与其接口的RS232_TX 和RS232_RX两个信号,RS232_TX是数据发送端,RS232_RX是数据接收端口(接收PC机发过来的信息),串口通信的数据传输时序如图:奇偶校验位是根据前面8个数据的1的个数判断,对于奇校验,如果前面有奇数个1,相应的校验位为0.

该实验实现的功能是CPLD实时监测RS232_RX信号是否有数据。若接收到数据,则把接收到的数据通过RS232_TX发回给对方。上位机用的是串口调试助手。在代码设计中,发送数据的波特率是可选择的,这部分在模块speed_select里,可以根据需要进行配置。发送的数据帧格式为:1bit起始位(保持一个传输位周期的低电平),8bit数据,无校验位。

该设计分为四个模块,4个模块的划分主要是依据数据流的方向。my_uart_rx模块完成数据的接收,speed_select(speed_rx)模块主要是响应my_uart_rx模块发出的使能信号进行波特率计数,并且送回一个数据采样使能信号。my_uart_tx模块在my_uart_rx模块接收好一个完整的数据帧启动,将接收到的数据返回给对方,他的波特率控制是由speed_select(speed_tx)模块产生。

逻辑电路图:

设计整体说明:

该设计分为三个字模块和一个顶层模块,子模块包括波特率模块、接收模块和发送模块,波特率模块的作用是控制数据的传输速度,当接收模块发现串口的接收端检测到数据的时候,就发送一个信号告诉波特率模块有数据了,这时波特率模块就开始工作,进行采样,每次采样到它规定速率的一半的时候就告诉接收模块开始进行数据的接收,一旦接收完一位,接收模块又会停止工作,直到波特率模块计数到下一个位的一半的时候,又告诉接收模块开始接收,直到接收完为止,这样就起到了速度的控制。而发送模块受到波特率模块和接收模块共同的控制,只有它们两个告诉发送模块工作时,发送模块才能工作。

接收模块,该模块可以分为三步走:

  1. 检测到数据,立刻给波特率发送信号,让波特率模块和发送模块开始开始工作。
  2. 收到波特率模块的信号后,一位一位的接收数据。
  3. 接收完数据后,停止接收,并且让波特率模块停止工作。

发送模块,该模块课分为两步走:

  1. 接收到波特率模块和发送模块的有效信号后开始工作。
  2. 发送完数据后,告诉波特率模块,让其停止工作。

部分代码分析:

1.      边沿检测法:

检测下降沿:

always @ (posedge clk or negedge rst_n) begin

if(!rst_n) begin

               rx_int0 <= 1'b0;

               rx_int1 <= 1'b0;

               rx_int2 <= 1'b0;

        end

else begin

               rx_int0 <= rx_int;

               rx_int1 <= rx_int0;

               rx_int2 <= rx_int1;

        end

end

assign neg_rx_int =  ~rx_int1 & rx_int2;//捕捉到下降沿后,neg_rx_int拉高保持一个主时钟周期 

 

  

如图,经过1,、2、3三个时钟后,rx_int2变为高电平,而此时经过2、3两个时钟后,rx_int1变为低电平,这时neg_rx_int获得一个脉冲的高电平,为什么只有一个高脉冲的值,因为rx_int2获得的值是rx_int1上一个时钟的值,当rx_int由高变为低的时候,rx_int1先得到低电平,而此时rx_int2还是rx_int1上一个时钟的值高电平,neg_rx_int为高,而等到下一个时钟到来时,rx_int2变低电平,neg_rx_in也跟着变低了,所以只有一个时钟的高脉冲。

其作用:获得一个稳定的有效该电平

检测上升沿:

always @ (posedge clk or negedge rst_n) begin

if(!rst_n) begin

               rx_int0 <= 1'b0;

               rx_int1 <= 1'b0;

               rx_int2 <= 1'b0;

        end

else begin

               rx_int0 <= rx_int;

               rx_int1 <= rx_int0;

               rx_int2 <= rx_int1;

        end

end

assign neg_rx_int =  rx_int1 & ~rx_int2;//捕捉到上升沿后,neg_rx_int拉高保持一个主时钟周期。

说白了,边沿检测法就是获得一个时钟的高脉冲。

 2.      neg_rx_int在代码中起的作用

       else if(neg_rx_int) begin    //接收数据完毕,准备把接收到的数据发回去

                     bps_start_r <= 1'b1;

                     tx_data <= rx_data;   //把接收到的数据存入发送数据寄存器

                     tx_en <= 1'b1;          //进入发送数据状态中

              end

在接收到上一个模块的有效命令后,就将输入的数据保存,并由tx_en代替了neg_rx_int,因为neg_rx_int只有一个高脉冲,所以为了让其在我们想要的时间内控制后面数据的发送,让tx_en在这段时间内一直有效;并行让neg_rx_int只有一个高电平,防止上一个模块来干扰。数据的发送,按照串口数据发送的协议来写

reg rs232_tx_r;

always @ (posedge clk or negedge rst_n) begin

      if(!rst_n) begin

                    num <= 4'd0;

                    rs232_tx_r <= 1'b1;//未发送前,串口总线保持高

             end

      else if(tx_en) begin

                    if(clk_bps)begin

                           num <= num+1'b1;

                           case (num)

                           4'd0: rs232_tx_r <= 1'b0;        //发送起始位

                           4'd1: rs232_tx_r <= tx_data[0];      //发送bit0

                           4'd2: rs232_tx_r <= tx_data[1];      //发送bit1

                           4'd3: rs232_tx_r <= tx_data[2];      //发送bit2

                           4'd4: rs232_tx_r <= tx_data[3];      //发送bit3

                           4'd5: rs232_tx_r <= tx_data[4];      //发送bit

                           4'd6: rs232_tx_r <= tx_data[5];      //发送bit5

                           4'd7: rs232_tx_r <= tx_data[6];      //发送bit6

                           4'd8: rs232_tx_r <= tx_data[7];      //发送bit7

                           4'd9: rs232_tx_r <= 1'b1;  //发送结束位

                           default: rs232_tx_r <= 1'b1;

                           endcase

                           end

                    else if(num==4'd11) num <= 4'd0;//发送共11位

             end

end

 

 

 

波特率模块:控制数据的传输速度

 

//-------------------控制计数多少,也就是传输速度-----------------------------------

always @ (posedge clk or negedge rst_n)

      if(!rst_n)

                    cnt <= 13'd0;

      else if (cnt < `BPS_SPEED && bps_start)

             cnt <= cnt +1'b1;//收到发送或者接收模块的bps_start命令开始计数

      else (!bps_start) cnt <= 13'd0;//收到停止命令计数结束,停止工作

 

//--------------记到一半,就告诉发送或者接收模块开始发送或者接收数据-----

always @ (posedge clk or negedge rst_n)

      if(!rst_n)

             clk_bps_r <= 1'b0;

      else if(cnt == `BPS_SPEED_HALF)

             clk_bps_r <= 1'b1;

      else clk_bps_r <= 1'b0;    

assign clk_bps = clk_bps_r;

波特率计算方法:

bps9600 = 5207表示对于9600bit/s的速度在50MHZ的时钟下需要计数5027次,计算:1s=1000000000ns,则接收或者发送一位所需时间是1000000000/9600=104166ns/位,50Mhz=20ns。则接收一位需要计数:N=104166/20(次)

 

//------------------------------------------------------------------------------------------------

接收模块

1.      边沿检测:

always @ (posedge clk or negedge rst_n) begin

      if(!rst_n) begin

                    rs232_rx0 <= 1'b0;

                    rs232_rx1 <= 1'b0;

                    rs232_rx2 <= 1'b0;

                    rs232_rx3 <= 1'b0;

             end

      else begin

                    rs232_rx0 <= rs232_rx;

                    rs232_rx1 <= rs232_rx0;

                    rs232_rx2 <= rs232_rx1;

                    rs232_rx3 <= rs232_rx2;

             end

end

assign neg_rs232_rx = rs232_rx3 & rs232_rx2 & ~rs232_rx1 & ~rs232_rx0;

道理和开始的一样,但是这里检测更严格,高低电平都至少要保持2个时钟,目的是滤掉毛刺,获得更稳定的值。

 

2.        数据接收:

这里比较特别的就是定义了两个寄存器来获取输入的值,rx_data_r和tx_temp_data,tx_temp_data的目的是为了一位一位的存取,存取完毕后再将值给rx_data_r,最后rx_data_r再把数据赋给输出端口。这样做也是为了能让输出直接获得最终的值,而不是接收一位变化一位。还有就是串口总线的第一位没接收,因为是起始位,我们需要的是八位数据,所以没接收。

//-----------------------------------------------------------------------------------------------

reg[7:0] rx_data_r;           //串口接收数据寄存器,保存直至下一个数据来到

//----------------------------------------------------------------------------------------------

reg[7:0] rx_temp_data;    //当前接收数据寄存器

always @ (posedge clk or negedge rst_n)

      if(!rst_n) begin

                    rx_temp_data <= 8'd0;

                    num <= 4'd0;

                    rx_data_r <= 8'd0;

             end

      else if(rx_int) begin    //接收数据处理

             if(clk_bps) begin //读取并保存数据,接收数据为一个起始位,8bit数据,1或2个结束位           

                           num <= num+1'b1;

                           case (num)

                                         4'd1: rx_temp_data[0] <= rs232_rx;      //锁存第0bit

                                         4'd2: rx_temp_data[1] <= rs232_rx;      //锁存第1bit

                                         4'd3: rx_temp_data[2] <= rs232_rx;      //锁存第2bit

                                         4'd4: rx_temp_data[3] <= rs232_rx;      //锁存第3bit

                                         4'd5: rx_temp_data[4] <= rs232_rx;      //锁存第4bit

                                         4'd6: rx_temp_data[5] <= rs232_rx;      //锁存第5bit

                                         4'd7: rx_temp_data[6] <= rs232_rx;      //锁存第6bit

                                         4'd8: rx_temp_data[7] <= rs232_rx;      //锁存第7bit

                                         default: ;

                                  endcase

                    end

             else if(num == 4'd12) begin            //我们的标准接收模式下只有1+8+1(2)=11bit的有效数据

                           num <= 4'd0;                     //接收到STOP位后结束,num清零

                           rx_data_r <= rx_temp_data;    //把数据锁存到数据寄存器rx_data中

                    end

             end

assign rx_data = rx_data_r;

     

posted @ 2012-02-10 10:39  慢慢来,别慌  阅读(1274)  评论(0编辑  收藏  举报