[X]关闭

[米联客-XILINX-H3_CZ08_7100] FPGA基础篇连载-10 UART串口发送驱动设计

文档创建者:FPGA课程
浏览次数:365
最后更新:2024-08-23
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-FPGA部分 » 2_FPGA实验篇(仅旗舰) » 1-FPGA基础入门实验
本帖最后由 FPGA课程 于 2024-8-23 17:20 编辑

​ 软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!

1概述
UART串口通信是应用非常广泛的一种串行异步通信方式,常用的异步串口包括RS232\RS482\RS485。
RS232的逻辑1的电平为-3~-15V,逻辑0的电平为+3~+15V,下图是老式的DB9串口线。
e3e88a57bb404abe93e38a0a3944dd2e.jpg
标准的DB9接口定义如下:
                        
序号
                        
                        
DB9公头
                        
                        
DB9母头
                        
                        
1
                        
                        
CD
                        
                        
载波检测
                        
                        
CD
                        
                        
载波检测
                        
                        
2
                        
                        
RXD
                        
                        
接收端接收
                        
                        
TXD
                        
                        
发送端发送数据
                        
                        
3
                        
                        
TXD
                        
                        
发送数据
                        
                        
RXD
                        
                        
接收端接收数据
                        
                        
4
                        
                        
DTR
                        
                        
数据终端就绪
                        
                        
DTR
                        
                        
数据终端就绪
                        
                        
5
                        
                        
GND
                        
                        
系统接地
                        
                        
GND
                        
                        
系统接地
                        
                        
6
                        
                        
DSR
                        
                        
数据准备就绪
                        
                        
DSR
                        
                        
数据准备就绪
                        
                        
7
                        
                        
RTS
                        
                        
发送请求
                        
                        
CTS
                        
                        
清除请求
                        
                        
8
                        
                        
CTS
                        
                        
清除发送
                        
                        
RTS
                        
                        
发送请求
                        
                        
9
                        
                        
RI
                        
                        
振铃指示器
                        
                        
RI
                        
                        
振铃指示器
                        
实际上一般RS232只使用到TXD RXD两个信号。
411ef4b944024eeba9d512f38c0f1b49.jpg
RS485/422采用差分信号负逻辑,逻辑“1”以两线间的电压差为-(2~6)V表示;逻辑“0”以两线间的电压差为+(2~6)V表示,采用差分方式可以具有更高的速度和更强的抗干扰能力,具有更远的传输距离。由于RS485/RS422更多出现在工业场合,因此一般采用接线端子方式接线。
老式DB9串口基本上已经消失了,但是UART串口更加广泛地使用,比如我们常见的USB串口,就是通过USB接口芯片,实现了以TTL电平方式的UART串口通信。数据通过USB接口进行传输,通过UART串口芯片完成USB协议到UART串口协议的相互转换。
实验目的:
1:实现UART串口发送控制器的设计
2:实现主程序中调用串口发送控制器发送字符“HELLO FPGA”
3:完成仿真验证
4:编译并且固化程序到FPGA验证
2UART发送驱动设计2.1系统框图
如下图所示,米联客设计的UART发送控制器包含3个主要模块:波特率发生器,发送使能模块,移位模块,其中移位模块中包含了移位计数器和移位控制器。
c19bc6a67b6440ffb7e14a297c63f526.jpg
2.2 UART发送时序
下图中,UART串口通信数据格式包括1bit起始位、8bits数据位、1bit停止位,不包含奇偶校验位。
624153f7bd214ae98dee1c9da15a6d31.jpg
在开始编写串口发送驱动前,我们需要了解以下概念:
波特率UART采用异步通信方式,数据收发双方只有在同一波特率才能正常通信。波特率代表了UART完成1个时间单位数据位或者控制位的时间。通常,我们需要对系统时钟进行分频来产生正确的波特率,所以计算分频系数尤为重要,比如系统时钟是100_000_000HZ,波特率是115200,那么分频系数为=100000000/115200-1
起始位UART数据总线由高电平变低电平并且持续1个波特率时间代表数据的起始。
数据位每个数据位占用1个波特率时间,本文实验发送1BYTE字节需要占用8个波特率时间。
停止位如果没有奇偶校验位,数据位结束后,保持1/1.5/2个波特率的高电平代表了停止位。
奇偶校验用于校对数据,对于UART通信,可以根据实际情况选择是否需要支持奇偶校验。
2.3驱动接口时序图
米联客设计了一种通用简洁的驱动接口,包含以下信号:
xxx_wreq:数据发送请求
xxx_wdata:需要发送的数据
xxx_wbusy:数据发送忙状态指示
这里xxx代表了uart
69c4cd27cdab493d934eb4ef4a48162d.jpg
2.4驱动源码
代码如下:
  1. /*******************************UART发送驱动器*********************

  2. --以下是米联客设计的UART发送驱动器
  3. --1.代码简洁,占用极少逻辑资源,代码结构清晰,逻辑设计严谨
  4. --2.I_uart_wreq,用户程序通过设置I_uart_wreq为高,请求UART发送驱动器,发送数据,UART发送驱动器把I_uart_wdata数据通过UART TX总线发送出去
  5. --3.O_uart_wbusy,表示当前UART发送总线整忙,这个时候用户程序需要等待非忙的时候,请求发送数据。*********************************************************************/
  6. `timescale 1ns / 1ns//仿真时间刻度/精度

  7. module uiuart_tx#
  8. (
  9. parameter integer BAUD_DIV     = 10416                           //设置采样系数 (时钟/采样率-1)
  10. )
  11. (
  12.     input        I_clk,//系统时钟输入
  13.     input        I_uart_rstn,//系统复位输入
  14.     input        I_uart_wreq, //发送数据请求  
  15.     input [7:0] I_uart_wdata, //发送数据      
  16.     output      O_uart_wbusy,//发送状态忙,代表正在发送数据  
  17.     output      O_uart_tx//uart tx 发送总线
  18. );

  19. localparam  UART_LEN = 4'd10; //设置uart 发送的bit数量为10,代表1bit起始位,8bits数据,1bit停止位
  20. wire        bps_en ; //发送使能
  21. reg         uart_wreq_r   = 1'b0;//寄存一次I_uart_wreq
  22. reg         bps_start_en    = 1'b0; //波特率计数器启动使能,也是发送启动使能
  23. reg [13:0]  baud_div        = 14'd0;//波特率计数器
  24. reg [9 :0]  uart_wdata_r  = 10'h3ff;//寄存I_uart_wreq
  25. reg [3 :0]  tx_cnt          = 4'd0;//计数发送了多少bits

  26. assign O_uart_tx = uart_wdata_r[0];//总线上的数据,始终是uart_wdata_r[0]
  27. assign O_uart_wbusy = bps_start_en;//总线忙标志,即是bps_start_en为有效,即当总线忙于发送,总线忙

  28. // 发送使能
  29. assign bps_en = (baud_div == BAUD_DIV);                 //产生一次发送使能信号,条件是baud_div == BAUD_DIV,波特率计数达成

  30. //波特率计数器
  31. always@(posedge I_clk )begin
  32.     if((I_uart_rstn== 1'b0) || (I_uart_wreq==1'b1&uart_wreq_r==1'b0))begin
  33.         baud_div <= 14'd0;
  34.     end
  35.     else begin
  36.         if(bps_start_en && baud_div < BAUD_DIV)        //bps_start_en的信号拉高,表示开始发送
  37.            baud_div <= baud_div + 1'b1;      //且baud_div < BAUD_DIV波特率计算,未达到波特率baud_div+1
  38.         else
  39.             baud_div <= 14'd0;                         //达到清零
  40.     end
  41. end

  42. always@(posedge I_clk)begin
  43.     uart_wreq_r <= I_uart_wreq;                           //寄存一次I_uart_wreq信号
  44. end

  45. //当I_uart_wreq从低电平变为高电平,启动发送
  46. always@(posedge I_clk)begin
  47.     if(I_uart_rstn == 1'b0)
  48.         bps_start_en    <= 1'b0;                           //复位,计数清零
  49.     else if(I_uart_wreq==1'b1&uart_wreq_r==1'b0)          //I_uart_wreq上升沿激活
  50.         bps_start_en    <= 1'b1;                           //激活后将 bps_start_en拉高,传输开始
  51.     else if(tx_cnt == UART_LEN)          //tx_cnt用于计数当前发送的bits数量,当达到预定值UART_LEN
  52.         bps_start_en    <= 1'b0;                           //将 bps_start_en拉低,传输结束
  53.     else
  54.         bps_start_en    <= bps_start_en;                    
  55. End

  56. //发送bits计数器
  57. always@(posedge I_clk)begin
  58.     if(((I_uart_rstn== 1'b0) || (I_uart_wreq==1'b1&uart_wreq_r==1'b0))||(tx_cnt == 10))//当复位、启动发送、发送完成,重置tx_cnt
  59.         tx_cnt <=4'd0;
  60.     else if(bps_en && (tx_cnt < UART_LEN))   //tx_cnt计数器,每发送一个bit加1
  61.         tx_cnt <= tx_cnt + 1'b1;
  62. End

  63. //uart发送并串移位控制器
  64. always@(posedge I_clk)begin
  65.     if((I_uart_wreq==1'b1&uart_wreq_r==1'b0)) //当发送请求有效,寄存需要发送的数据到uart_wdata_r
  66.         uart_wdata_r  <= {1'b1,I_uart_wdata[7:0],1'b0};//寄存需要发送的数据,包括1bit 起始位,8bits数据,1bit停止位
  67.     else if(bps_en && (tx_cnt < (UART_LEN - 1'b1)))                               //shift 9 bits
  68.         uart_wdata_r <= {uart_wdata_r[0],uart_wdata_r[9:1]};    //并串转换,将并行数据依次传输
  69.     else
  70.         uart_wdata_r <= uart_wdata_r;
  71. end  

  72. endmodule
复制代码


3UART用户数据发送模块3.1发送流程图
用户发送模块负责把数据听过UART驱动模块发送出去。
用户发送模块
37f2773c6c704fe7b933f57d3f193a97.jpg
3.2用户数据发送源码
  1. /************************uart_top 用户程序发送演示*********************

  2. --以下是米联客设计的UART发送驱动器,使用演示
  3. --1.I_uart_wreq,用户程序通过设置I_uart_wreq为高,请求UART发送驱动器,发送数据,UART发送驱动器把I_uart_wdata数据通过UART TX总线发送出去
  4. --2.O_uart_wbusy,表示当前UART发送总线整忙,这个时候用户程序需要等待非忙的时候,请求发送数据。
  5. --3.演示发送hello fpga字符,通过串口调试助手观察收到的字符*********************************************************************/

  6. `timescale 1ns / 1ns

  7. module uart_top
  8. (
  9. input  I_sysclk_p, //系统时钟输入
  10. input  I_sysclk_n,
  11. output O_uart_tx //UART串口发送总线
  12. );


  13. wire     I_uart_rstn; //内部同步复位
  14. wire     uart_wbusy;  //UART发送驱动器正忙

  15. wire I_clk;
  16. IBUFGDS CLK_U(
  17. .I(I_sysclk_p),
  18. .IB(I_sysclk_n),
  19. .O(I_clk)
  20. );

  21. reg      t1s_dly_en;  //1S延迟
  22. reg[1:0] S_UART_TX;   //UART 发送状态机
  23. reg[3:0] tx_index;    //发送index计数器
  24. reg      uart_wreq;   //UART发送请求  
  25. reg[7:0] uart_wdata;  //UART发送数据寄存器
  26. reg[7:0] uart_tx_buf[0:11]; //发送缓存

  27. reg [15:0]rst_cnt = 16'd0; //复位计数器
  28. reg [24:0]delay_cnt = 25'd0; //延迟计数器

  29. assign I_uart_rstn = rst_cnt[15]; //复位

  30. //上电通过计数器计数,实现复位
  31. always @(posedge I_clk)begin
  32.     rst_cnt <= (rst_cnt[15] == 1'b0) ? (rst_cnt + 1'b1) : rst_cnt ;
  33. End


  34. //帧延迟计数器
  35. always @(posedge I_clk)begin
  36.    if(t1s_dly_en == 1'b0)
  37.       delay_cnt <= 25'd0;
  38.     else
  39.       delay_cnt <= delay_cnt + 1'b1;
  40. end



  41. //数据发送状态机
  42. always @(posedge I_clk)begin
  43.     if(I_uart_rstn==1'b0)begin //初始化uart_tx_buf,为hello fpga等字符共计12 BYTES,以及其他寄存器
  44.         uart_tx_buf[0]  <=8'h48;//h
  45.         uart_tx_buf[1]  <=8'h45;//e
  46.         uart_tx_buf[2]  <=8'h4c;//l
  47.         uart_tx_buf[3]  <=8'h4c;//l
  48.         uart_tx_buf[4]  <=8'h4f;//o
  49.         uart_tx_buf[5]  <=8'h20;//space
  50.         uart_tx_buf[6]  <=8'h46;//f
  51.         uart_tx_buf[7]  <=8'h50;//p
  52.         uart_tx_buf[8]  <=8'h47;//g  
  53.         uart_tx_buf[9]  <=8'h41;//a      
  54.         uart_tx_buf[10] <=8'h0d;//Enter
  55.         uart_tx_buf[11] <=8'h0a;//newline

  56.         uart_wdata      <= 8'd0;
  57.         uart_wreq       <= 1'b0;
  58.         S_UART_TX       <= 2'd0;
  59.         t1s_dly_en      <= 1'b0;
  60.         tx_index        <= 4'd0;
  61.     end
  62.     else begin
  63.         case(S_UART_TX)
  64.         0:begin
  65.             if(!uart_wbusy)begin//如果UART发送驱动器不忙
  66.                 uart_wdata <= uart_tx_buf[tx_index];//准备发送数据,发送tx_index所指向的数据
  67.                 uart_wreq <= 1'b1; //设置uart_wreq为高电平,请求发送数据
  68.             end
  69.             else begin //当总线忙
  70.                 uart_wreq <= 1'b0; //重置uart_wreq
  71.                 S_UART_TX <= 2'd1; //进入下一状态
  72.             end
  73.         end
  74.         1:begin//该状态等待总线空闲
  75.             S_UART_TX <= (uart_wbusy == 1'b0) ? 2'd2: S_UART_TX;
  76.         end
  77.         2:begin//更新tx_index计数器
  78.             if(tx_index < 11)begin //每一帧发送12个字节
  79.                 tx_index <= tx_index + 1'b1; //tx_index 加计数
  80.                 S_UART_TX  <= 2'd0; //进入下一状态
  81.             end
  82.             else begin //如果tx_index==11 代表所有数据发送完毕
  83.                 tx_index   <= 4'd0; //重置tx_index
  84.                 t1s_dly_en <= 1'b1; //1s 延迟计数器开始计数
  85.                 S_UART_TX  <= 2'd3; //下一状态
  86.             end
  87.         end
  88.         3:begin//登台延迟计数器计数
  89.             if(delay_cnt[24] == 1'b1)begin //这里的1S不是精确的,使用delay_cnt[24]可以节省逻辑资源
  90.                 S_UART_TX <= 2'd0;   //回到状态0
  91.                 t1s_dly_en <= 1'b0;  //关闭1S延迟计数器
  92.             end
  93.             else  //否则还是在当前状态等待
  94.                S_UART_TX <= S_UART_TX;  
  95.         end        
  96.         endcase
  97.     end   

  98. end


  99. //例化UART 发送驱动器模块
  100. uiuart_tx#
  101. (
  102. .BAUD_DIV(100_000_000/115200-1)  //波特率计算    BAUD_DIV = 系统时钟/波特率-1
  103. )
  104. uart_tx_u
  105. (
  106. .I_clk(I_clk), //系统时钟输入
  107. .I_uart_rstn(I_uart_rstn), //系统复位输入
  108. .I_uart_wreq(uart_wreq), //UART发送(写)数据请求
  109. .I_uart_wdata(uart_wdata), //UART发送(写)数据
  110. .O_uart_wbusy(uart_wbusy),//UART发送驱动器忙
  111. .O_uart_tx(O_uart_tx) //UART 发送串行总线
  112. );

  113.    

  114. endmodule
复制代码

4 FPGA工程
fpga工程的创建过程不再重复,如有不清楚的请看前面实验
36e1ad0011774e288a0bfb8d279699ef.jpg
米联客的代码管理规范,在对应的FPGA工程路径下创建uisrc路径,并且创建以下文件夹
01_rtl:放用户编写的rtl代码
02_sim:仿真文件或者工程
03_ip:放使用到的ip文件
04_pin:放fpga的pin脚约束文件或者时序约束文件
05_boot:放编译好的bit或者bin文件(一般为空)
06_doc:放本一些相关文档(一般为空)
e7b64040144f4eeba3b4a3fba4476a30.jpg
5RTL仿真5.1添加仿真测试源码
仿真测试文件存放在工程目录uisrc\02_sim中,源码如下:
  1. `timescale 1ns/1ns   //定义仿真时间刻度/精度

  2. module uart_top_tb();

  3. localparam      SYS_TIME   =  'd10;//时钟周期,以ns为单位
  4. reg          I_sysclk_p;         //系统时钟
  5. reg          I_sysclk_n;

  6. //例化顶层模块
  7. uart_top uart_top_inst
  8. (
  9. .I_sysclk_p(I_sysclk_p),
  10. .I_sysclk_n(I_sysclk_n),
  11. .O_uart_tx(uart_rx)
  12. );

  13. //仿真初始化
  14. initial begin  
  15. I_sysclk_p =0;
  16. I_sysclk_n =1;

  17. #2000000 $finish;        
  18. end

  19. always #(SYS_TIME/2) I_sysclk_p = ~I_sysclk_p;    //产生主时钟
  20. always #(SYS_TIME/2) I_sysclk_n = ~I_sysclk_n;

  21. endmodule
复制代码
5.2开始仿真
0796d7daabf04981b780d8345bf6e585.jpg

选择我们主程序信号
a1ae2e43135f40519362cf649177b914.jpg
添加进观察窗
cbf1317f6d0a45e3955080eb78e5ea94.jpg
删除多余的信号
e96de734b6424606bcc490ae3d11c5ce.jpg
点击run按键,开始仿真
172dc861438849faaae8f46a7172278a.jpg
观察发送程序的仿真结果准确无误
7ad1b8a77e9d44449d1a5867350f9cb6.jpg

6添加PIN脚XDC文件
配套工程的 FPGA PIN 脚定义路径为 fpga_prj/uisrc/04_pin/ fpga_pin.xdc。
7下载演示
下载程序前,先确保FPGA工程已经编译。
7.1硬件连接
(该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。)
由于我们的开发板没有PL部分 UART 串口资源,所以我们此处使用了1.8V的FEP-BASE-CARD。请确保下载器和开发板已经正确连接,另外需要把核心板上的2P模式开关设置到JTAG模式,即ON ON,并且开发板已经上电。(注意JTAG端子不支持热插拔,而USB接口支持,所以在不通电的情况下接通好JTAG后,再插入USB到电脑,之后再上电,以免造成JTAG IO损坏)
04102a955d5146d98ee6474fbbf92db3.jpg
7.2运行结果
3f5ba505cefa46e3afc2d48f0ccda545.jpg




您需要登录后才可以回帖 登录 | 立即注册

本版积分规则