[X]关闭
6

S01-CH10 UART串口通信FPGA程序设计

摘要: 软件版本:VIVADO2017.4操作系统:WIN10 64bit硬件平台:适用米联客ZYNQ系列开发板米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!10.1 概述 串口自诞生以来由于其简单可靠的传输方式,被大 ...

软件版本:VIVADO2017.4

操作系统:WIN10 64bit

硬件平台:适用米联客ZYNQ系列开发板

米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!

10.1 概述

       串口自诞生以来由于其简单可靠的传输方式,被大量使用,对于现在这个充满高速通信设备的时代来说,串口依然不过时。老早以前的台式机和笔记本可都是有串口的,虽然现在很多台式机串口没有了,笔记本也没有串口了,但是串口从来没有被丢弃,只是以另外一种形式存在,这就是USB转串口芯片。串口只需要2根线就可以实现一收一发,使用简单,可靠方便,在低速场合大量使用。比如一些工控的人机界面、我们用的一些单片机的USB下载器都是用USB转串口来实现的。

10.2 串口连线

       对于米联客开发板,有些开发板没有FPGA部分扩展串口的,使用USB转串口模式,来实现在FPGA部分的串口通信实验。(请不要和ZYNQ PS ARM端的串口混淆,ARM的串口是不需要你写FPGA程序就能工作的,而FPGA部分实现串口需要自己写串口收发器。)

       对于初学者来说,连线完成后,还要注意,FPGA工程里面的PIN脚约束,如果有不清楚的,看前面课程CH04。

10.2 时序设计

       在进行具体的串口设计之前,先了解串口通信协议。通常串口的一次发送或接收由四个部分组成:起始位S(“一般为逻辑‘0’)、数据位D0~D7(一般为6位~8位之间可变,数据低位在前)、校验位(奇校验、偶检验或不需要校验位)、停止位(通常为1位、1.5位、2位)。停止位必须为逻辑1。在一次串口通信过程中,数据接收与发送双方没有共享时钟,因此,双方必须协商好数据传输波特率。波特率即数据传输速率。根据双方协议好的传输速率,接收端即可对发送端的数据进行采样。常见的波特率标准为300bps,600bps,800bps,9600bps,19200bps等。当然更块的速度意味着对采样的要求更高,有可能误码率会逐渐提高。

      通常对串口进行数据采样,采用更高频的时钟。这样做的目的是采用高频时钟来锁存低频时钟,减少数据的误码率,增加接收模块的自纠错能力。

具体的工作流程为:

      发送端按照预先设定好的波特率,发送起始位(Start)+数据位(data)+奇偶校验位+结束位。其中,起始位为逻辑0,结束位为逻辑1,发送端在空闲状态为1。发送数据包格式如下图所示。

       数据的奇偶校验位是可以选择的,如果不使用奇偶,那么就没有这个数据位,我们本课程中没有用到奇偶校验位。

       接收端通过检测电平‘1’到‘0’的跳变来确定一个数据包的开始。确定开始位接收完成之后,依次接收数据,使用更高的采样时钟,完成数据采集。接收完数据位后,继续接收奇偶校验位和停止位。

       串口的接收与发送,其主要时序设计包括两个部分:1、波特率的产生时序;2、数据传输时序,包括接收与发送。

       波特率产生时序设计:假设FPGA输入时钟100Mhz,为得到常用的波特率,仍然采用计数分频来得到。BAUD_DIV=100_000000/波特率。其中采样中心点为发送或接收时钟的中心点,即BAUD_DIV_CAP=100_000000/(2*波特率)。该部分在数据接收和发送部分均单独完成。

       数据接收模块:在设置好传输波特率的情况下,根据串口传输时序,进行解串。空闲状态时,接收 数据为逻辑高电平,等待起始位逻辑低电平的到来。当起始位到达后,由低位到高位,依次采集8位数据,并进行相应的解串,存入临时寄存器。接收有效数据完成后,判断结束位,接收完毕。

       数据发送模块:设置发送使能信号和待发送的数据。通过计数器,表示10个数据发送的周期。这10个数据,依次为起始位+8位数据位+1位结束位,实现数据位的逐个发送。

本设计中,采用PC机的串口调试助手,发送数据位至FPGA,FPGA接收到数据位之后,立即回传至PC机。具体的设计原理和代码思路,在后续章节逐一介绍。

10.3 程序分析

       接收端和发送端分别通过波特率启动信号和数据发送标志信号启动波特率时钟。

10.3.1 uart_rx_path.v

`timescale 1ns / 1ps

//////////////////////////////////////////////////////////////////////////////////

// Company:

// Engineer:

//

// Create Date: 2017/07/25 13:35:17

// Design Name:

// Module Name: uart_rx_path

// Project Name:

// Target Devices:

// Tool Versions:

// Description:

//

// Dependencies:

//

// Revision:

// Revision 0.01 - File Created

// Additional Comments:

//

//////////////////////////////////////////////////////////////////////////////////



module uart_rx_path(

input clk_i,

input uart_rx_i,


output [7:0] uart_rx_data_o,

output uart_rx_done,

output baud_bps_tb //for simulation

 );

  

parameter [13:0] BAUD_DIV     = 14'd10416;//波特率时钟,9600bps,100Mhz/9600=5208

parameter [13:0] BAUD_DIV_CAP = 14'd5208;//波特率时钟中间采样点,100Mhz/9600/2=2604


reg [13:0] baud_div=0; //波特率设置计数器

reg baud_bps=0; //数据采样点信号

reg bps_start=0; //波特率启动标志

always@(posedge clk_i)

begin

if(baud_div==BAUD_DIV_CAP) //当波特率计数器计数到采样点时,产生采样信号baud_bps

begin

baud_bps<=1'b1;

baud_div<=baud_div+1'b1;

end

else if(baud_div<BAUD_DIV && bps_start)//当波特率计数器启动时,计数器累加

begin

baud_div<=baud_div+1'b1;

baud_bps<=0;

end

else

begin

baud_bps<=0;

baud_div<=0;

end

end


reg [4:0] uart_rx_i_r=5'b11111; //数据接收缓存器

always@(posedge clk_i)

begin

uart_rx_i_r<={uart_rx_i_r[3:0],uart_rx_i};

end

//数据接收缓存器,当连续接收到五个低电平时,即uart_rx_int=0时,作为接收到起始信号

wire uart_rx_int=uart_rx_i_r[4] | uart_rx_i_r[3] | uart_rx_i_r[2] | uart_rx_i_r[1] | uart_rx_i_r[0];


reg [3:0] bit_num=0; //接收数据个数计数器

reg uart_rx_done_r=0; //数据接收完成寄存器

reg state=1'b0;


reg [7:0] uart_rx_data_o_r0=0;//数据接收过程中,数据缓存器

reg [7:0] uart_rx_data_o_r1=0;//数据接收完成,数据寄存器


always@(posedge clk_i)

begin

uart_rx_done_r<=1'b0;

case(state)

1'b0 :

if(!uart_rx_int)//当连续接收到五个低电平时,即uart_rx_int=0时,作为接收到起始信号,启动波特率时钟

begin

bps_start<=1'b1;

state<=1'b1;

end

1'b1 :

if(baud_bps) //每次等待波特率采样中心时,接收数据,放入数据缓存器中

begin

bit_num<=bit_num+1'b1;

if(bit_num<4'd9) //接收1bit起始信号,8bit有效信号,1bit结束信号

uart_rx_data_o_r0[bit_num-1]<=uart_rx_i;

end

else if(bit_num==4'd10) //接收完成时候,接收数据个数计数器清零,产生接收完成标志位,并将数据写入数据寄存器,关闭波特率时候

begin

bit_num<=0;

uart_rx_done_r<=1'b1;

uart_rx_data_o_r1<=uart_rx_data_o_r0;

state<=1'b0;//进入状态0,再次循环检测

bps_start<=0;

end

default:;

endcase

end

assign baud_bps_tb=baud_bps;//for simulation

assign uart_rx_data_o=uart_rx_data_o_r1;

assign uart_rx_done=uart_rx_done_r;

endmodule

      串口接收数据路径,当无数据接收是,串口接收端uart_rx_i保持高电平。通过高频时钟采集uart_rx_i端信号,当连续监测五个低电平时候,表示有数据过来,准备接收。此时,启动接收波特率计数器,一次接收8位有效数据。跳过1bit结束位后,清除数据接收指示,并将接收寄存器放入数据缓存器uart_rx_data_o_r1里面,并关闭波特率计数器。

10.3.2 uart_tx_path.v

`timescale 1ns / 1ps

//////////////////////////////////////////////////////////////////////////////////

// Company:

// Engineer:

//

// Create Date: 2017/07/25 13:36:08

// Design Name:

// Module Name: uart_tx_path

// Project Name:

// Target Devices:

// Tool Versions:

// Description:

//

// Dependencies:

//

// Revision:

// Revision 0.01 - File Created

// Additional Comments:

//

//////////////////////////////////////////////////////////////////////////////////



module uart_tx_path(

input clk_i,


input [7:0] uart_tx_data_i, //待发送数据

input uart_tx_en_i, //发送发送使能信号


output uart_tx_o

);


parameter BAUD_DIV     = 14'd10416;//波特率时钟,9600bps,100Mhz/9600=10416,波特率可调

parameter BAUD_DIV_CAP = 14'd5208;//波特率时钟中间采样点,100Mhz/9600/2=d208,波特率可调


reg [13:0] baud_div=0; //波特率设置计数器

reg baud_bps=0; //数据发送点信号,高有效

reg [9:0] send_data=10'b1111111111;//待发送数据寄存器,1bit起始信号+8bit有效信号+1bit结束信号

reg [3:0] bit_num=0; //发送数据个数计数器

reg uart_send_flag=0; //数据发送标志位

reg uart_tx_o_r=1; //发送数据寄存器,初始状态位高


always@(posedge clk_i)

begin

if(baud_div==BAUD_DIV_CAP) //当波特率计数器计数到数据发送中点时,产生采样信号baud_bps,用来发送数据

begin

baud_bps<=1'b1;

baud_div<=baud_div+1'b1;

end

else if(baud_div<BAUD_DIV && uart_send_flag)//数据发送标志位有效期间,波特率计数器累加,以产生波特率时钟

begin

baud_div<=baud_div+1'b1;

baud_bps<=0;

end

else

begin

baud_bps<=0;

baud_div<=0;

end

end


always@(posedge clk_i)

begin

if(uart_tx_en_i) //接收数据发送使能信号时,产生数据发送标志信号

begin

uart_send_flag<=1'b1;

send_data<={1'b1,uart_tx_data_i,1'b0};//待发送数据寄存器装填,1bit起始信号0+8bit有效信号+1bit结束信号

end

else if(bit_num==4'd10) //发送结束时候,清楚发送标志信号,并清楚待发送数据寄存器内部信号

begin

uart_send_flag<=1'b0;

send_data<=10'b1111_1111_11;

end

end


always@(posedge clk_i)

begin

if(uart_send_flag) //发送有效时候

begin

if(baud_bps)//检测发送点信号

begin

if(bit_num<=4'd9)

begin

uart_tx_o_r<=send_data[bit_num]; //发送待发送寄存器内数据,从低位到高位

bit_num<=bit_num+1'b1;

end

end

else if(bit_num==4'd10)

bit_num<=4'd0;

end

else

begin

uart_tx_o_r<=1'b1; //空闲状态时,保持发送端位高电平,以备发送时候产生低电平信号

bit_num<=0;

end

end


assign uart_tx_o=uart_tx_o_r;


endmodule

       串口环路测试顶层设计,接收数据完成后,数据接收8位寄存器作为待发送数据,当接收完成后,接收完成信号作为发送使能信号,立即开始发送已接收的数据。需要注意的是,数据发送和接收波特率保持一致,且接收数据和发送数据所用的时间保持一致,接收完成立即发送,否则会在PC串口调试助手接收到错误数据。本例子波特率设置为9600bps,连续测试时始终保持数据发送和接收正确,工作稳定可靠。

10.3.3 uart_top.v

module uart_top(

input clk_i,

input rst_n_i,


input uart_rx_i,


output uart_tx_o

    );


wire [7:0] uart_rx_data_o;

wire uart_rx_done;

    

uart_rx_path uart_rx_path_u (

    .clk_i(clk_i),

    .uart_rx_i(uart_rx_i),


    .uart_rx_data_o(uart_rx_data_o),

    .uart_rx_done(uart_rx_done)

    );

    

uart_tx_path uart_tx_path_u (

    .clk_i(clk_i),

    .uart_tx_data_i(uart_rx_data_o),

    .uart_tx_en_i(uart_rx_done),

    .uart_tx_o(uart_tx_o)

    );

    

endmodule

10.3.4 uart_top_TB.v

module uart_top_TB;


reg clk_i;

reg rst_n_i;

reg uart_rx_i;

wire [7:0]  uart_tx_o;


uart_top u_uart_top

(

.clk_i  (clk_i),

.rst_n_i    (rst_n_i),

.uart_rx_i  (uart_rx_i),

.uart_tx_o  (uart_tx_o)

);


initial

begin

        clk_i = 0;

rst_n_i = 0;

uart_rx_i = 1'b1;


// Wait 100 ns for global reset to finish

#96;

rst_n_i=1;

#104170

uart_rx_i = 1'b1;

#104170

        uart_rx_i = 1'b0;//start

        //1001_0101

        #104170

        uart_rx_i = 1'b1;

         #104170

        uart_rx_i = 1'b0;

         #104170

        uart_rx_i = 1'b1;

         #104170

        uart_rx_i = 1'b0;

         #104170

        uart_rx_i = 1'b1;

          #104170

        uart_rx_i = 1'b0;

          #104170

        uart_rx_i = 1'b0;

          #104170

        uart_rx_i = 1'b1;        

           #104170

        uart_rx_i = 1'b1;//stop             

          #808320

        //00000101

        uart_rx_i = 1'b0;//start

        #104170

        uart_rx_i = 1'b1;

         #104170

        uart_rx_i = 1'b0;

         #104170

        uart_rx_i = 1'b1;

         #104170

        uart_rx_i = 1'b0;

         #104170

        uart_rx_i = 1'b0;

          #104170

        uart_rx_i = 1'b0;

          #104170

        uart_rx_i = 1'b0;

          #104170

        uart_rx_i = 1'b0;        

           #104170

        uart_rx_i = 1'b1;//stop   

        

           #808320

      //10000100

      uart_rx_i = 1'b0;//start

      #104170

      uart_rx_i = 1'b0;

       #104170

      uart_rx_i = 1'b0;

       #104170

      uart_rx_i = 1'b1;

       #104170

      uart_rx_i = 1'b0;

       #104170

      uart_rx_i = 1'b0;

        #104170

      uart_rx_i = 1'b0;

        #104170

      uart_rx_i = 1'b0;

        #104170

      uart_rx_i = 1'b1;           

          #104170

      uart_rx_i = 1'b1;//stop                     

end


always

    begin

        #5 clk_i = ~clk_i;

    end

    

endmodule

仿真文件第产生测试波形10010101、00000101和10000100给串口接收模块。

10.4 综合布线前仿真时序

      为保证数据接收和发送的正确性,分别对串口接收模块和串口发送模块,进行了仿真。其对应的仿真测试文件,参见工程文件夹里面的源文件。

      接收端仿真如下所示,预先接收到低电平,之后再接收8位有效信号,接收寄存器得到8bit数据分别为10010101、00000101、10000100。

      串口回路仿真,进行此项仿真,必须注意,保持和接收发送文件中设置的波特率一致,即在底层文件中,我们设置波特率为9600bps,那么定义的BAUD_DIV和BAUD_DIV_CAP分别为10416(PL 时钟100M)和5208(PL 时钟100M。则仿真测试文件中,每个数据写入的时间间隔为104160。具体请查看工程中的Testbench。其仿真图如下所示,从中可以看出,接收数据和发送数据保持一致。

10.5 输出结果

首先确保USB 转串口接入到PC的USB串口接口,并且正确安装好驱动。

为了更具体的说明接收和发送数据的正确性,在顶层设计了串口回传实验。选用波特率为9600bps,通过大量测试,发现该设计准确可靠,暂未发现误码。实验结果如下。



路过

雷人

握手

鲜花

鸡蛋
发表评论

最新评论

引用 江流 2021-6-6 10:41
请问怎么使用两个物理串口uart进行接收和发送数据,一个接收,一个发送,我买的是米联7020的板子,没找到相关的教程
引用 AndyArthur 2021-4-2 16:29
乔木: 好
请问您是怎么理解我上面提的问题的啊?
引用 AndyArthur 2021-4-2 16:29
猪猪: 不错,感谢
请问您是怎么理解我上面提的问题的啊?
引用 AndyArthur 2021-4-2 16:25
楼主您好,uart_rx_path.v有下面这段:

if(bit_num<4'd9) //接收1bit起始信号,8bit有效信号,1bit结束信号
uart_rx_data_o_r0[bit_num-1]<=uart_rx_i;

我觉得当bit_num<4'd9条件下存入的数据是起始位加上7位有效数据啊;我的理解是baud_bps第一次等于1时,采样的位置是起始位的中间,请楼主纠正我的错误。
引用 猪猪 2021-3-11 10:26
不错,感谢
引用 乔木 2019-12-22 10:36

查看全部评论(6)

本文作者
2019-9-6 19:11
  • 7
    粉丝
  • 4599
    阅读
  • 6
    回复

关注米联客

扫描关注,了解最新资讯

联系人:汤经理
电话:0519-80699907
EMAIL:270682667@qq.com
地址:常州溧阳市天目云谷3号楼北楼201B
热门评论
排行榜