FPGA初级项目3——UART串口发送逻辑(UART_TX)

FPGA初级项目3——UART串口发送逻辑(UART_TX)


URAT相关介绍


首先观众老爷有问题:啥玩意儿是UART串口?

答:在 FPGA 中,UART 串口即通用异步收发传输器(Universal Asynchronous Receiver/Transmitter)串口,是一种常用的串行通信接口

那在芯片哪个位置呢?怎么用呢?

答:UART 串口并非物理意义上固定在 FPGA 芯片某一处的实体结构,而是一种功能模块,可通过多种方式(软核+硬核,我们重点介绍软核)在 FPGA 芯片上得以实现。在 FPGA 中,开发者可以使用硬件描述语言(如 Verilog 或 VHDL)来编写 UART 串口的代码,利用 FPGA 内部的可编程逻辑资源(如查找表 LUT、触发器 FF 等)来实现 UART 的功能。这些逻辑资源分布在 FPGA 芯片的各个区域,UART 功能模块就是由众多这样的基本逻辑单元组合而成。

一般来说,UART 串口通信至少需要两个引脚,即发送引脚(TX)和接收引脚(RX)。这些引脚在 FPGA 芯片的封装上有明确的位置标识,开发者可以根据芯片的数据手册将这些引脚连接到外部设备的相应引脚上,从而实现数据的发送和接收。


1.基本原理


数据格式:UART 串口以字符为单位进行数据传输,每个字符通常由起始位、数据位、奇偶校验位和停止位组成。起始位为一个逻辑 0 电平信号,用于表示一个字符传输的开始;数据位可以是 5 位、6 位、7 位或 8 位,用于传输实际的数据;奇偶校验位用于检验数据传输的正确性,可选择奇校验、偶校验或无校验;停止位为逻辑 1 电平信号,其位数可以是 1 位、1.5 位或 2 位,用于表示一个字符传输的结束。


通信方式:UART 串口采用异步通信方式,即通信双方不需要使用同一个时钟信号来同步数据传输,而是通过约定好的波特率来实现数据的收发。发送方按照约定的波特率(就是数据传输的速率的一个新指标,表示每秒传输的码元数)将数据一位一位地发送出去,接收方也以相同的波特率进行数据接收,并根据数据格式进行解析。


2.工作模式


发送模式:当 FPGA 需要通过 UART 串口发送数据时,首先将待发送的数据按照设定的数据格式进行组装,然后将数据逐位从发送引脚输出,在发送过程中,会根据波特率产生相应的时钟信号,以控制数据的发送速度。
接收模式:在接收数据时,FPGA 通过接收引脚检测输入信号的电平变化,当检测到起始位的下降沿时,开始按照波特率进行数据采样,将接收到的每一位数据存储起来,直到接收到停止位,完成一个字符的接收,然后对数据进行校验和解析,获取有效数据。


3.在 FPGA 中的作用


实现设备间通信:FPGA 可以通过 UART 串口与其他设备,如微控制器、传感器、计算机等进行通信,实现数据的传输和交互。例如,FPGA 与传感器连接,通过 UART 串口接收传感器采集的数据,进行处理后再通过 UART 串口将结果发送给微控制器。


调试与配置:在 FPGA 的开发和调试过程中,UART 串口常用于输出调试信息,方便开发人员了解系统的运行状态,查找和解决问题。此外,也可以通过 UART 串口对 FPGA 进行配置,加载不同的程序或参数。
扩展功能:利用 UART 串口可以方便地扩展 FPGA 的功能,如连接蓝牙模块、Wi-Fi 模块等,实现无线通信功能;或者连接存储设备,实现数据的存储和读取。


4.在 FPGA 中的优势


灵活性高:FPGA 中的 UART 串口可以通过编程进行灵活配置,如设置波特率、数据位、奇偶校验位和停止位等参数,以适应不同的通信需求。


资源占用少:UART 串口的实现相对简单,在 FPGA 中占用的逻辑资源较少,可以在不影响系统整体性能的前提下,为系统增加串口通信功能。
兼容性好:UART 串口是一种广泛应用的通信接口,具有良好的兼容性,能够与各种不同类型的设备进行通信,方便 FPGA 与其他设备进行集成。


那了解完UART串口之后,重头戏来了!我们这篇文章就先来编写UART发送方(UART_TX)的逻辑!同时可以打包封装成模块方便的用在之后的FPGA设计中,因此这个项目的学习也是非常重要的。


URAT_TX模块代码设计

问题:我需要设计一个串口发送模块,从FPGA开发板上的数据(即用户输入拨码开关SW的数值)发送给电脑。波特率为9600(1秒发送9600个码元),1位开始位,8位数据位,1位停止位,无校验位。每1秒发送一次当前8位拨码开关的值,并且发送完成后将LED灯的状态翻转代表发送完成。


问题中要点分析:


1.由图可知,1/960秒用来发送数据,1秒中其余的时间保持UART_TX空闲。也就是说,发送一轮花费1秒时间,而这一轮串口发送因为设置波特率为9600的要求,所以这1秒可以发送9600个数据(码元),但是我们只要前10个数据,剩下的时间保持静止即可。


同样的问题来了,9600波特率(每1/9600秒发送一位)在设计中需要几位来表示呢?(参考我的FPGA初级项目1里面有计数器的详细计算),由图可知,计算结果为5207这个数值,需要13位的二进制数来表示,因此代码中可为[12:0]counter0


 

2.那我怎么知道过了1秒时间呢?此时可以再来一个计数器用于延时1秒!同时结合上一篇文章中的,在同一个设计中使用多个计数器,我们要规定使能信号来触发(不能CLK一来就全部触发),因此还要设计相关的使能信号
3.发送的数据为用户输入的8位拨码开关的数值,那如果用户的手速足够快,在将要发送某一位数据的时候,飞快的将SW的数值更改了呢?该怎么办?假设串口即将要发送SW5,而这一位的数值是0,就在即将要发送的前一刻,用户将其更改为1。那串口发送的是0还是1呢?所以综上,我们还需要在设计中设计一个寄存器,用来保留用户设定的开关SW值,以防逻辑混乱。

好!理清上述逻辑,我们直接上代码:

module uart_tx(
       clk,
       reset_n,
       data,
       led,
       uart_tx      
    );
       input clk;
       input reset_n;
       input [7:0]data;
       output reg led;
       output reg uart_tx ; 
   
       
//定义波特率计数器counter0
      reg [12:0]counter0;
      reg en_counter0;
always@(posedge clk or negedge reset_n)
if(!reset_n)
      counter0 <= 0;
else if(en_counter0) begin
     if(counter0 == 5207)//需芯片发送5207个频率波
      counter0 <= 0;
     else
      counter0 <= counter0 +1'd1;
     end
else
     counter0 <= 0;
 
     
//定义位计数器counter1,用于拨码开关的数值对应     
     reg [3:0]counter1;
always@(posedge clk or negedge reset_n)
if(!reset_n)
      counter1 <= 0; 
else if(counter0 == 5207) begin
     if(counter1 == 9)
      counter1 <= 0;
     else
      counter1 <= counter1 +1'd1;
     end
 
 
//定义计数延时1秒的计数器counter2
     reg [25:0]counter2;
always@(posedge clk or negedge reset_n)
if(!reset_n)
      counter2 <= 0; 
else if(counter2 == 50_000_000 - 1)//计数1秒芯片需要发送的频率波次数
      counter2 <= 0;
else
      counter2 <= counter2 + 1'd1;
      
      
//定义储存开关SW的寄存器
     reg [7:0]r_data;
always@(posedge clk or negedge reset_n)
if(!reset_n)
     r_data <= 0;
else if(counter2 == 50_000_000 - 1)    
     r_data <= data;
else
     r_data <= r_data;
     
     
//位发送逻辑
always@(posedge clk or negedge reset_n)
if(!reset_n)     
  uart_tx <= 1'd1;
else if(en_counter0 == 0)
  uart_tx <= 1'd1;
else begin
  case(counter1)
  0: uart_tx <= 1'd0;//起始位
  1: uart_tx <= r_data[0];
  2: uart_tx <= r_data[1];
  3: uart_tx <= r_data[2];
  4: uart_tx <= r_data[3];
  5: uart_tx <= r_data[4];
  6: uart_tx <= r_data[5];
  7: uart_tx <= r_data[6];
  8: uart_tx <= r_data[7];
  9: uart_tx <= 1'd1;
  default: uart_tx <= uart_tx;
  endcase
end
    
    
//LED翻转逻辑
always@(posedge clk or negedge reset_n)
if(!reset_n)   
    led <= 0;
else if((counter1 == 9)&&(counter0 == 5207))
    led <= !led;
    
    
//别忘了en_counter0 使能信号的控制编写    
always@(posedge clk or negedge reset_n)
if(!reset_n)   
     en_counter0 <= 0;
else if(counter2 == 50_000_000 - 1)
     en_counter0 <= 1;
else if((counter1 == 9)&&(counter0 == 5207))
     en_counter0 <= 0;  
    
    
endmodule


从代码中,我们也可以发现其中一些细节
1.在对counter0定义时,用到en_counter0使能信号,那为什么没有en_counter1呢?或者说根本没有必要定义en_counter1。这是因为counter1的变化是基于counter0的!当en_counter0不工作时,counter0不工作,自然counter1也不工作,因此无需定义。


2.在代码中的”位发送逻辑“模块,有这么一条指令:else if(en_counter0 == 0)
  uart_tx <= 1'd1;那如果删掉这行代码会造成什么后果?
如果删掉,进行仿真可以发现在UART串口发送的1秒空闲时间里,波形应该为1,但是波形为数据0,也就是在本应该空闲的状态出现了起始位的标志。这是因为当counter1为0时,我们代码中设置为起始位;但是在空闲状态时,counter1同样为0,UART串口发送的也是0,出现了巧合的情况,因此这行代码是必不可少的。


3.在进行代码的撰写时,希望读者有整体化模块的思想。先想好整个FPGA设计需要几个模块,然后分别编写每个模块,相互之间有联系的再补充小模块编写,就像博主最后才编写的en_counter0模块(因为当时用的时候只是定义,还没想好触发条件)

其综合出来的底层系统逻辑图schematic如下:

那我们设计好的UART_TX就可以了吗?逻辑清楚,结构清晰。


但可惜,还不能作为一个独立串口发送的模块使用,还需要进一步的优化(其实也很简单,看下回分解!)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值