一.UART串口简介
- 串口通信是将数据字节分成一位一位的形式在一条数据线上逐个传送,主要特点时通信线路简单,但传输速度较慢。
- 串口通信分类:
- 同步串行通信:需要通信双方在同一时钟的控制下,同步传输数据;
- 异步串行通信:指通信双方使用各自的时钟控制数据的发送和接收过程。
- UART:是一种采用异步串行通信方式的通用异步收发传输器,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
- UART在发送或接收过程中的一帧数据由4部分组成, 起始位、 数据位、奇偶校验位和停止位。
- 起始位标志着一帧数据的开始
- 停止位标志着一帧数据的结束
- 数据位是一帧数据中的有效数据
- 校验位分为奇校验和偶校验,用于检验数据在传输过程中是否出错(检查1的个数是奇数还是偶数) 异步串行通信数据格
- 串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是bps(位/秒),常用的波特率有9600、19200、38400、57600、115200.
- 电信号的传输过程有着不同的电平标准和接口规范,针对异步串行通信的接口标准由RS232、RS422、RS485等.
- RS232是单端输入输出
- RS422/485是差分输入输出
- DB9接口定义
二.实验任务
本节实验任务是上位机通过串口调试助手发送数据给FPGA, FPGA通过串口接收数据并将接收到的数据发送给上位机,完成串口数据环回。
三.硬件设计
- 硬件电路
由于DB9接口类型的RS232串口占用空间较大, 很多系统已经选择USB转TTL方案, 利用CH340芯片实现USB总线转UART功能,通过Mini USB接口实现与上位机通信。
- 开拓者开发板串口通信实验引脚分配
四.程序设计
-
程序框图
在编写代码之前,我们首先要确定串口通信的数据格式及波特率。在这里我们选择串口比较常用的一种模式,数据位为8位,停止位为1位,无校验位,波特率为115200bps。则传输一帧数据的时序图如下
串口通信时序图
- 由系统总体框图可知, FPGA部分包括三个模块,
- 顶层模块 (uart_top)
- 接收模块 (uart_recv)
- 发送模块(uart_send)
顶层模块原理图
-
代码
-
顶层代码定义了实验的输入输出接口 ,同时例化了发送模块和接收模块,并定义了串口相关传递参数,定义模块间的连接线型网络。
-
module uart_top(
input sys_clk, /*绯荤粺鏃堕挓*/
input sys_rst_n,
input uart_rxd,
output uart_txd
);
//parameter define
parameter CLK_FREQ=50000000;
parameter UART_BPS=115200;
//reg define
//wire define
wire uart_en_w;
wire [7:0] uart_data_w;
wire clk_1m_W;
//**********************************************//
//************ main code **********//
//**********************************************//
uart_recv #( //涓插彛鎺ユ敹妯″潡
.CLK_FREQ (CLK_FREQ ), //璁剧疆绯荤粺鏃堕挓棰戠巼
.UART_BPS (UART_BPS )) //璁剧疆涓插彛鎺ユ敹娉㈢壒鐜
u_uart_recv(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.uart_rxd(uart_rxd),
.uart_done(uart_en_w),
.uart_data(uart_data_w)
);
uart_send #( //涓插彛鎺ユ敹妯″潡
.CLK_FREQ (CLK_FREQ ), //璁剧疆绯荤粺鏃堕挓棰戠巼
.UART_BPS (UART_BPS )) //璁剧疆涓插彛鎺ユ敹娉㈢壒鐜
u_uart_send(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.uart_data_en(uart_en_w),
.uart_data(uart_data_w),
.uart_txd(uart_txd)
);
endmodule
- 串口接收模块 代码
- 先通过接收的信号下降沿判断数据的到来
- 进行数据的接收寄存
- 区分接收 和 非接受状态 rx_flag(主要通过数每一帧的数据个数是否到达停止位)
- 通过波特率对应时钟的计数个数到达来对数据接收计数的自加
- 数据接收的同时进行数据按位寄存
- 每一帧数据接收完毕此时使能发送信号,并将寄存器中存储的数据传输出去
module uart_recv(
input sys_clk,
input sys_rst_n,
input uart_rxd,
output reg uart_done, /*鎺ユ敹涓€甯ф暟鎹畬鎴愭爣蹇*/
output reg [7:0] uart_data
);
//parameter define
parameter CLK_FREQ=50000000;
parameter UART_BPS=9600;
localparam BPS_CNT=CLK_FREQ/UART_BPS;
//reg define
reg uart_rxd_d0;
reg uart_rxd_d1;
reg [15:0] clk_cnt; //绯荤粺鏃堕挓璁℃暟鍣
reg [3:0] rx_cnt; //鎺ユ敹鏁版嵁璁℃暟鍣
reg rx_flag; //鎺ユ敹杩囩▼鏍囧織淇″彿
reg [7:0] rxdata; //鎺ユ敹鏁版嵁瀵勫瓨鍣
//wire define
wire start_flag;
//*****************************************************
//** main code
//*****************************************************
//鎹曡幏鎺ユ敹绔彛涓嬮檷娌璧峰浣锛屽緱鍒颁竴涓椂閽熷懆鏈熺殑鑴夊啿淇″彿
assign start_flag=(~uart_rxd_d0)&uart_rxd_d1;
//瀵筓ART鎺ュ彈绔彛鐨勬暟鎹欢鏃朵袱涓懆鏈
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_rxd_d0<=1'b0;
uart_rxd_d1<=1'b0;
end
else begin
uart_rxd_d0<=uart_rxd;
uart_rxd_d1<=uart_rxd_d0;
end
end
//褰撹剦鍐蹭俊鍙穝tart_flag鍒拌揪鏃讹紝杩涜鎺ユ敹杩囩▼
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
rx_flag<=1'b0;
end
else begin
if (start_flag) begin
rx_flag<=1'b1;
end
else if ((rx_cnt==4'd9)&&(clk_cnt==BPS_CNT/2) )begin
rx_flag<=1'b0;
end
else begin
rx_flag<=rx_flag;
end
end
end
//杩涘叆鎺ユ敹杩囩▼鍚庯紝鍚姩绯荤粺鏃堕挓璁℃暟鍣ㄤ笌鎺ユ敹鏁版嵁璁℃暟鍣
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
clk_cnt<=16'd0;
rx_cnt <= 4'd0;
end
else if (rx_flag) begin
if(clk_cnt<BPS_CNT-1)begin
clk_cnt<=clk_cnt+1'b1;
rx_cnt <=rx_cnt;
end
else begin
clk_cnt<=16'd0;
rx_cnt <=rx_cnt+1'b1;;
end
end
else begin
clk_cnt<=16'd0;
rx_cnt <= 4'd0;
end
end
//鏍规嵁鎺ユ敹鏁版嵁璁℃暟鍣ㄦ潵瀵勫瓨UART鎺ユ敹绔彛鏁版嵁
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
rxdata<=8'd0;
end
else if (rx_flag) begin
if (clk_cnt == BPS_CNT/2) begin
case(rx_cnt)
4'd1:rxdata[0] <= uart_rxd_d1; //瀵勫瓨鏁版嵁浣嶆渶浣庝綅
4'd2:rxdata[1] <= uart_rxd_d1;
4'd3:rxdata[2] <= uart_rxd_d1;
4'd4:rxdata[3] <= uart_rxd_d1;
4'd5:rxdata[4] <= uart_rxd_d1;
4'd6:rxdata[5] <= uart_rxd_d1;
4'd7:rxdata[6] <= uart_rxd_d1;
4'd8:rxdata[7] <= uart_rxd_d1;
default:;
endcase
end
else begin
rxdata<=rxdata;
end
end
else begin
rxdata<=8'd0;
end
end
//鏁版嵁鎺ユ敹瀹屾瘯鍚庣粰鍑烘爣蹇椾俊鍙峰苟瀵勫瓨鍣ㄨ緭鍑烘帴鏀跺埌鐨勬暟鎹
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_data<=8'd0;
uart_done<=1'b0;
end
else if (rx_cnt==4'd9) begin
uart_data<=rxdata;
uart_done<=1'b1;
end
else begin
uart_data<=8'd0;
uart_done<=1'b0;
end
end
endmodule
- 串口发送模块代码
module uart_send(
input sys_clk,
input sys_rst_n,
input uart_data_en,
input [7:0] uart_data,
output reg uart_txd
);
//parameter define
parameter CLK_FREQ=50000000;
parameter UART_BPS=9600;
localparam BPS_CNT=CLK_FREQ/UART_BPS;
//reg define
reg uart_en_d0;
reg uart_en_d1;
reg [15:0] clk_cnt;
reg [3:0] tx_cnt; //鍙戦€佹暟鎹鏁板櫒
reg tx_flag;
reg [7:0] tx_data;
//wire define
wire en_flag;
//*****************************************************
//** main code **//
//*****************************************************
//鎹曡幏uart_en涓婂崌娌匡紝寰楀埌涓€涓椂閽熷懆鏈熺殑鑴夊啿淇″彿
assign en_flag=(~uart_en_d1)&uart_en_d0;
//瀵瑰彂閫佷娇鑳戒俊鍙穟art_en寤惰繜涓や釜鏃堕挓鍛ㄦ湡
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_en_d0<=1'b0;
uart_en_d1<=1'b0;
end
else begin
uart_en_d0<=uart_data_en;
uart_en_d1<=uart_en_d0;
end
end
//褰撹剦鍐蹭俊鍙積n_flag鍒拌揪鏃瀵勫瓨寰呭彂閫佺殑鏁版嵁锛屽苟杩涘叆鍙戦€佽繃绋
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
tx_flag<=1'b0;
tx_data<=8'd0;
end
else if(en_flag) begin
tx_flag<=1'b1;
tx_data<=uart_data;
end
else begin
if ((tx_cnt==4'd9)&&(clk_cnt==BPS_CNT/2)) begin //璁℃暟鍒板仠姝綅涓棿鏃讹紝鍋滄鍙戦€佽繃绋
tx_flag<=1'b0;
tx_data<=8'd0;
end
else begin
tx_flag<=tx_flag;
tx_data<=tx_data;
end
end
end
//杩涘叆鍙戦€佽繃绋嬪悗锛屽惎鍔ㄧ郴缁熸椂閽熻鏁板櫒涓庡彂閫佹暟鎹鏁板櫒
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
clk_cnt<=16'd0;
tx_cnt <=4'd0;
end
else if(tx_flag) begin
if (clk_cnt<BPS_CNT-1) begin
clk_cnt<=clk_cnt+1'b1;
tx_cnt <= tx_cnt;
end
else begin
clk_cnt <= 16'd0; //瀵圭郴缁熸椂閽熻鏁拌揪涓€涓尝鐗圭巼鍛ㄦ湡鍚庢竻闆
tx_cnt <= tx_cnt + 1'b1; //姝ゆ椂鍙戦€佹暟鎹鏁板櫒鍔
end
end
else begin
clk_cnt<=16'd0;
tx_cnt <=4'd0;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_txd<=1'b1;
end
else if(tx_flag) begin
case(tx_cnt)
4'd0: uart_txd<=1'b0;
4'd1: uart_txd <= tx_data[0]; //鏁版嵁浣嶆渶浣庝綅
4'd2: uart_txd <= tx_data[1];
4'd3: uart_txd <= tx_data[2];
4'd4: uart_txd <= tx_data[3];
4'd5: uart_txd <= tx_data[4];
4'd6: uart_txd <= tx_data[5];
4'd7: uart_txd <= tx_data[6];
4'd8: uart_txd <= tx_data[7];
4'd9: uart_txd<=1'b1;
default:;
endcase
end
else begin
uart_txd<=1'b1;
end
end
endmodule
- 仿真结果
- 调试问题总结
(PS:开始仿真的时候接收的 数据计数等于9后,仍然不清零,我修改了大于等于9,依然 不清零,所以定位到是后面的数周期的参数写错了,所以出现的无法清零问题;第二个问题是发送数据 是对的,但是接收一直显示的是00,后来检查数据流发现,是我在例化接收模块的时候 ,未将数据端口引出 ,因此发送模块无法拿到接收模块接收到的数据,因此数据一直为00,因此重新例化模块后,能发送接收到的数据了。)
- 安装了sublime 可以进行语法检查了,另外写了一些快捷脚本例如:
例1:
直接生成
例2:
自己也可以写一些状态机方面的,按照自己的风格,以后有什么需要再添加吧。