一. 简介
这是FPGA之旅的第三个设计实例了,通过串口协议,就可以和电脑上的串口调试助手进行通信啦。串口一般用来输出程序中的一些信息,用来调试,也可以用来进行信息交互。在stm32中是可以通过串口下载程序的,是不是很nice。本例将实现一个基本的串口模块。
感兴趣的话,欢迎关注微信公众号 FPGA之旅。
二. 串口电路
串口电路一般使用的是CH340芯片,将串口协议中的RX和TX数据线,转换成电脑上可以识别的COM口,让电脑知道这个外部设备是串口设备。当然不弄硬件哈,只简单了解一下即可,还是重点学习下面的串口协议。
三. 串口协议
串口协议的全程为串行异步通信协议,UART。
异步,就说明通信双方需要提前约定好数据传输的速率,即波特率,每秒传输bit的数量。串行,就说明数据是按bit一个一个发送出去的(其他的有可能是2bit,3bit,或者更多)。
串口协议只需要两个数据线,一个用来发送,一个用来接收。具体协议规定如下
空闲位 :数据线都为高电平。
起始位:数据线拉低1bit,也就是拉低1bit所占用的时间。
数据位: 一比特一比特的将数据发送出去,先发送低位,后发送高位,数据位宽可以为5,6,7,8。一般为8bit。
校验位: 可以设置为奇校验,偶校验和无校验位。一般设置为无校验位,即去掉。
停止位: 数据线拉高0.5、1、1.5bit。一般设置为1bit。
以上就是串口协议的全部。
四. 实现思路
按照协议的规定,可以非常轻松得到状态机一共用那几个状态
空闲态
起始态
数据态
停止态
一共四个状态,每个状态所需要做的事情,在协议中已经明确指出来了。
还有一个问题就波特率怎么计算。FPGA的时钟一般为50Mhz,也就是一个时钟周期为20ns。可以先把波特率换成发送一个bit需要多少ns。最后再除以20,得到发送一个bit需要多少个时钟周期。例如波特率为115200,则时钟周期数计算方法如下
K = ((1 / 115200)* 1000 * 1000 * 1000) /20
这样就可以开始安心写代码啦!!代码一共分为两个部分,分别为TX和RX两个。
五. TX发送模块编写
先来定义端口信号.
请求信号来了之后,就开始发送idat数据
module UART_TX( input sys_clk, /*系统时钟 50M*/ input rst_n, /*系统复位 低电平有效*/ input uart_tx_req, /*串口发送请求*/ output uart_tx_done, /*串口发送完成*/ input[7:0] idat, /*发送数据*/ output uarttx /*uart tx数据线*/ );
再来定义波特率以及各个状态
时钟周期这里有一点点小改动,避免出现小数,而产生数值错误。
parameter UARTBaud = 'd115200; /*波特率*/ localparam UARTCLKPer = (('d1000_000_000 / UARTBaud) /20) -1; /*每Bit所占的时钟周期*/ localparam UART_Idle = 4'b0001; /*空闲态*/ localparam UART_Start = 4'b0010; /*起始态*/ localparam UART_Data = 4'b0100; /*数据态*/ localparam UART_Stop = 4'b1000; /*停止态*/
六. RX接收模块编写
定义端口信号。
module UART_RX( input sys_clk, /*系统时钟 50M*/ input rst_n, /*系统复位*/ output uart_rx_done, /*串口接收完成*/ output[7:0] odat, /*接收数据*/ input uartrx /*uart rx数据线*/ );
边沿检测。在接收数据的时候,需要判断数据的边沿,用来确定起始信号。通过缓存前两个时钟周期获取到的数据信号。然后通过取反再与的操作,是否发生了跳变,也就是上升沿和下降沿。
/*缓存rx数据*/ reg uartrxd0,uartrxd1,uartrxd2; /*检测rx 上下边沿*/ wire uartrxPosedge , uartrxNegedge; assign uartrxPosedge = (uartrxd1) & ( ~uartrxd2); assign uartrxNegedge = (~uartrxd1) & ( uartrxd2);
由于两个模块的总代码量一共有300行左右,就不全部粘贴出来了,需要的自行在微信公众号中获取,私聊即可。
七. testbeach编写
代码编写好了,怎么能不去仿真,验一验呢!万一有bug呢?
`timescale 1ns/1ps /*仿真文件编写*/ module testbeach(); reg clk; reg rst_n; wire uart_rx_done; wire[7:0] uart_rx_data; wire uart; reg[7:0] data; reg uart_tx_req; wire uart_tx_done; always #50 clk <= ~clk; initial begin clk = 1'b0; rst_n = 1'b1; data = 'd23; uart_tx_req = 1'b0; #100 /*手动复位一下,不然仿真会有高阻态*/ rst_n = 1'b0; #100 rst_n = 1'b1; #100 uart_tx_req = 1'b1; end /*数据发送完成后,左一位,进行发送*/ always@(posedge clk) begin if(uart_rx_done == 1'b1) data <= data << 1; end UART_RX UART_RX_HP( .sys_clk (clk), /*系统时钟 50M*/ .rst_n (rst_n), /*系统复位*/ .uart_rx_done (uart_rx_done), /*串口接收完成*/ .odat (uart_rx_data), /*接收数据*/ .uartrx (uart) /*uart rx数据线*/ ); UART_TX UART_TX_HP( .sys_clk (clk), /*系统时钟 50M*/ .rst_n (rst_n), /*系统复位*/ .uart_tx_req (uart_tx_req), /*串口发送请求*/ .uart_tx_done (uart_tx_done), /*串口发送完成*/ .idat (data), /*发送数据*/ .uarttx (uart) /*uart tx数据线*/ ); endmodule
通过仿真可以看到,代码正确的运行了,下面就是上板测试了,如果有条件的话。
不会以为串口就这样完了吧,怎么可能,这才仅仅实现了最基本的功能。更多高级东西还在后头呢!
公众号:FPGA之旅