FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)

二、常用通信协议,摸索探究: 3.RS232通信缓存数据 UART、SPI、IIC这三个基础通信协议在现实工作中经常被使用,这一章节我们来更加系统性地实践它们,一方面掌握这些基本的通信协议不仅仅是FPGA学习道路上不可缺少的重要一环同时为后期相对复杂的设计打下良好的基础,另一方面在理解这些协议后对嵌入式软件的整体设计也会有更深一步的认识,豌豆开发板也同样站在实战的角度考虑,通过MAX3232、TLC5615、LM75芯片构建的硬件平台,并为大家逐一详细介绍UART、SPI、IIC通信协议。
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片


在“FPGA基础知识”专栏中,笔者也为大家详细地介绍了串口的底层逻辑实现,在这里我们先简单地复习一下,毕竟串口通信在实战项目开发中是最为常见的通信方式,
还是从底层说起,其中串口的时序主要包括:空闲位、起始位、数据位、校验位、停止位,如图1所示是串口通信底层实现方式。
在空闲状态时,串口的数据线会一直保持在高电平状态;当主机要发送数据时,会将数据线拉低一个波特率的时间,从而告诉从机有数据要传输了,要做好准备;起始位之后是数据位,数据位的位数由双方之前约定好即可,双方约定后才能正确地传输和解析报文。每个数据位传输时都会占用一个波特率的时间,在这里是从低位到高位进行传输,比如要传输数据4'b0101,在串口传输时是先传输最低位的“1”,数据传输完成后,发送检验位:奇偶校验是一种非常简单常用的数据校验方式,又细分为奇校验和偶校验,但是一般在实际项目工程中使用的不多,所以不做详细介绍,在校验位后即为最后一位停止位,主机必须保证有停止位,即把数据线拉高一个波特率的时间。
因为数据在传输线上传输,硬件上可能会有一定的干扰,每一个设备内部又有自己的时钟,很可能在通信中两台设备间出现了一些细微的不同步,停止位的到来表示整个报文传输的结束,也使得从机可以正确地识别下一轮报文数据的起始位。在串口传输当中,有一个非常重要的概率即波特率,串口中常用到的波特率有9600、19200、57600、115200,波特率表示每个数据在传输线上的传输速率,比如在9600的波特率下,每位数据需要传输1/9600s=104166ns的时间,所以在两个设备之间串口通信之前,都需要事先约定相同的波特率、是否有校验位、报文的长度或者报文的结束字节等等。
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片

图1 串口通信底层实现方式
如图2所示是豌豆开发板上的RS232硬件电路,这里沿用了经典的MAX3232芯片外围电路,通过RS232串口线连接即可以实现上位机和开发板之间的通信,在这个例程中我们主要去实现如下一个基本功能:通过配套上位机串口助手,向豌豆开发板发送任意长度的报文数据,然后这些数据都通过FPGA的FIFO缓存,在用户按下开发板按键时,数据统一从FIFO中取出并依次打印到上位机的串口助手上,借着这个例程大家一方面可以加深对串口底层逻辑实现的理解,另一方面也可以熟悉FIFO在实际工程中的使用,更为后期使用到串口通信的例程打下基础!
接着再按照功能划分去编写每一个模块,大家可以想想看,除了在在“FPGA基础知识”专栏中介绍到的串口发送、接收模块uart_transfer、uart_receive外,以及在第一章学习到的key_scan按键检测模块,这里显然还需要一个串口数据缓存模块uartfifo_rd_wr把上位机串口助手发来的数据缓存到FIFO中,再在用户按下按键后通过串口助手打印到上位机显示,最后在顶层模块uart_fifo_top中把各个功能模块之间的相关信号量例化到一起即可。
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片

图2 豌豆开发板Artix7上MAX3232电路
在这里重述串口发送、接收模块的逻辑设计,如表1和表2所示是两个模块对应的信号列表,因为大部分情况下报文是以字节为基本单位发送的,当然如果有特殊的设计需求可以根据项目背景直接改写模块中的计数器即可,这里只需要把图1所示的串口通信底层逻辑还原成verilog代码即可,如图3所示是串口接收模块的代码设计,我们用两个计数器cnt0和cnt1去分别计数一个波特率的时间即接收1bit时间和计数9bit时间,因为这里我们把起始位1bit计算在了接收数据当中,所以cnt1需要计数9次,rxd_data在cnt1计数在1-9的时刻,使用数据拼接的办法即可得到一字节接收数据,但需要注意的一点是,为了防止硬件上的干扰,在串口接收的设计中通常会采用计数到一半波特率的时间去做赋值操作,程序上在数完end_cnt1即已经接收完8bit数据后,拉高rxd_data_vld一个时钟周期即可。
类似的原理,再去动手编写串口发送模块和串口接收模块思路基本一致,在发送模块中只用事先打包好需要发送了报文即可,即1bit的起始位,8bit的数据位,1bit的停止位,再按照顺序逐一送到外部引脚txd_uart,串口发送模块的代码设计如图4所示,这个模块的设计简单明了。



信号列表
信号名
I/O
位宽
clk
I
1
rst_n
I
1
rxd_uart
I
1
rxd_data
O
8
rxd_data_vld
O
1
表1 uart_receive模块信号列表
信号列表
信号名
I/O
位宽
clk
I
1
rst_n
I
1
txd_data
I
8
txd_data_vld
I
1
txd_uart
O
1
表2 uart_transfer模块信号列表
如下表3所示是串口缓存模块的信号列表,这个模块是整个工程的中间模块,即上游的串口接收模块会把数据通过din和din_vld写入本模块,同时本模块也需要通过dout和dout_vld把缓存到的串口发送数据送到下游的串口发送模块,这里就用到了之前在“FPGA基础知识”专栏中所详细介绍的FIFO知识,在这个模块中,我们需要把两个关键性信号即uart_fifo_rdy和uart_fifo_empty例化出来,前者是读FIFO的指示信号,这里大家可以思考下,因为首先串口发送模块每个字节发送的过程中是需要时间的,所以需要在串口发送模块处于空闲状态下再把FIFO中缓存的数据扔给下游模块,这样才不会造成数据丢失,其次在按下按键后,例程中设计把FIFO中所有缓存的数据都统一通过RS232串口线统一打印到串口助手上,这就需要让顶层模块知道FIFO中已被读空而不再触发uart_fifo_rdy信号,防止FIFO一直在处在读空的状态,如图5所示是串口数据缓存模块的代码设计,供大家参考。
信号列表
信号名
I/O
位宽
clk
I
1
rst_n
I
1
uart_fifo_rdy
I
1
din
I
8
din_vld
I
1
dout
O
8
dout_vld
O
1
uart_fifo_empty
O
1
表3 uartfifo_rd_wr模块信号列表
如图6所示是RS232通信缓存数据顶层文件的例化,这里我们定义了txd_data_flag信号,当按下按键时候置高,当串口缓存模块的FIFO读空信号为高时置低,用来和串口发送模块的txd_data_rdy信号一起送至串口缓存模块。
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片

图2-3串口接收模块的代码设计
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片

图2-4串口发送模块的代码设计
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片

图2-5串口数据缓存模块的代码设计
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片
FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)
文章图片

图2-6 RS232通信缓存数据顶层文件的例化


源工程代码下载链接:
【FPGA20个例程|FPGA 20个例程篇(3.RS232通信缓存数据)】链接:https://pan.baidu.com/s/1zdkPkaoZRsXTc0NKv2bFvg
提取码:ev5y

    推荐阅读