一、通信分类
(一)串行通信
- 工作方式: 在串行通信中,数据位按照顺序依次传输,也就是说,每一位数据都依次发送或接收。
- 优点: 相对简单,不容易出现数据冲突。由于只有一个通信通道,线路的布线较为简单。
- 缺点: 速度相对较慢,因为每次只能传输一个位。不适合大量数据的快速传输。
- 典型应用:USB,UART,光纤通信。
(二)并行通信
- 工作方式: 在并行通信中,多个数据位同时传输。每个位都有自己的通道,可以独立传输,从而在同一时刻传输多个位。
- 优点: 速度相对较快,尤其适合大量数据的快速传输。适用于需要同时处理多个数据位的情况。
- 缺点: 复杂度较高,占用引脚资源多。容易出现数据同步问题
- 典型应用:适用于短距离传输,长距离传输成本高
(三)同步通信
- 工作方式: 在同步通信中,数据的发送和接收是在预定的时钟信号下进行的。数据的发送方和接收方需要共享相同的时钟源,以确保数据的同步。
- 优点: 数据传输的时序清晰,易于管理和理解。适用于需要实时性和精确同步的应用。
- 缺点: 高频时钟衰减较快,通信过程易发生信号畸变,相位偏移,因此传输距离不宜过长。
- 典型应用:I2C,HDMI,CPU与内存接口等。
(四)异步通信
- 工作方式: 在异步通信中,数据的发送和接收不依赖于共享时钟信号。每个数据块之间有一定的间隔,而不需要严格的时序同步。通信设备会使用起始位和停止位来标识数据块的开始和结束。
- 优点: 较为灵活,不需要精确的时钟同步。传输媒介简单,传输距离长
- 缺点: 在高速传输和长距离通信时,由于没有同步的时钟,可能会出现数据失真。接收端解码复杂性增加,可靠性降低
- 典型应用:UART,USB1.1,USB2.0,光纤通信
(五)单工通信
- 定义: 单工通信是一种单向通信模式,数据只能在一个方向上传输。通常,一个设备是发送者,而另一个设备是接收者。
- 特点: 通信双方只能在一个特定的方向上传输信息,其中一个设备只能发送,而另一个只能接收。
- 例子: 无线电广播、电视广播等。
(六)半双工通信
- 定义: 半双工通信是一种双向通信模式,但是通信双方不能同时进行数据传输。在任意时刻,一个设备可以发送,而另一个设备可以接收,但不能同时进行。
- 特点: 数据传输是双向的,但是不能同时发生,需要在发送和接收之间切换。
- 例子: 对讲机、传统的无线电通信等。
(七)全双工通信
- 定义: 全双工通信是一种双向通信模式,其中通信的双方能够同时进行发送和接收操作,允许双方同时传输数据。
- 特点: 双方可以在同一时间内同时发送和接收数据,实现真正的双向通信。
- 例子: 电话通话、互联网语音通话、典型的计算机网络通信等。
二、UART简介
UART ( universal asynchronous receiver-transmitter )是一种采用全双工异步串行通信方式的通用异步收发传输器。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
(一)协议层
1.波特率
波特率是指每秒钟传输的比特数,是通信双方事先约定好的。通信的双方必须使用相同的波特率才能正常通信。单位是 bps (位 / 秒)。常用的波特率有 9600 、 19200 、 38400 、 57600 以及115200 等。如波特率为9600bps的计数器需要计数(8/9600/时钟周期)次才可以传输1Byte数据。
2.数据帧格式
数据帧的格式包括起始位、数据位、奇偶校验位和停止位的组合。
空闲:线路上为高电平,即逻辑值为1。
起始位:逻辑值为0,表示字符帧的开始。
数据:先发低位再发高位。
校验位:奇偶校验。
停止位:高电平代表一个字符帧结束。
(二)物理层
1.电气连接
UART至少需要两根数据线,即发送线(TX)和接收线(RX)。TX用于发送数据,RX用于接收数据。
2.接口标准
常见的有TTL,RS232,RS422,RS485等标准。
(三)存在的问题
1.电气接口不统一
UART 一般直接使用处理器使用的电平,即TTL电平,但是不同处理器之间使用的电平存在差异,所以不同的处理器使用UART通信一般不能直接相连。
UART没有规定不同器件连接时的连接标准,所以不同器件之间通过UART连接不太方便
2.抗干扰能力差
UART一般直接使用TTL电平表示0和1,因此抗干扰能力弱,在传输过程中容易出错。
3.传输距离短
UART通信对于时钟同步非常敏感。在长距离传输中,时钟信号可能会因为传播延迟而引起时序问题。同时随着传输距离的增加,电信号可能会受到噪声、信号失真和衰减等影响,导致信号质量下降。这可能导致接收端无法正确解读发送端的信号,从而限制了传输距离。
三、实验目标
用开发板与上位机通过串口通信,完成数据环回实验
上位机是指可以直接发送操作指令的计算机或单片机,一般提供用户操作交互界面并向用户展示反馈数据。典型设备类型:电脑,手机,平板,面板,触摸屏。
数据环回实验是一种用于测试和验证数据通信链路的方法。在数据环回测试中,发送的数据经过通信链路后被重新接收,然后进行比较以确保数据传输的正确性。
四、硬件设计
上位机通过USB接口CH340N/P将差分信号传输出去,使用CH340C芯片将USB通信协议转成UART串口通信协议,从RXD端口将数据输送到FPGA,FPGA将数据处理完成后将数据发送回TXD,然后经过CH340芯片和USB将数据传回到上位机,将发送的数据和返回的数据进行对比,如果一样,则说明UART串口通信实验正常。
五、程序设计
(一)时序图
假设波特率为115200bps,系统时钟为50Mhz,则每传输一比特数据需要(1/115200/20ns=434)个时钟周期,rx_cnt用于统计接受了多少位数据,他每隔434个时钟周期自增1。
(二)接受模块
需要有外界端口的输入数据uart_rxd,还要有数据寄存器rx_data。当捕捉到uart_rxd的下降沿时,说明有效数据已经可以开始输入了,这个时候对clk_cnt和rx_cnt时钟进行计数。最后将数据给到uart_data端口,uart_data端口将数据给到回环模块。
module receive(
input clk,
input rst_n,
input uart_rxd,
output start_flag,
output reg rx_flag,
output reg [3:0] rx_cnt,
output reg uart_done,
output reg [7:0] uart_data
);
reg [8:0] clk_cnt;
reg uart_rxd1;
reg uart_rxd2;
reg [7:0] rx_data;//接受数据寄存器
wire add_clk_cnt;
wire end_clk_cnt;
wire add_rx_cnt;
wire end_rx_cnt;
//捕捉输入数据的下降沿,若捕捉到uart_rxd下降沿,则start_flag拉高
//对输入的uart_rxd数据打两拍,消除亚稳态
always @(posedge clk or negedge rst_n)begin
if(! rst_n)begin
uart_rxd1 <= 1'b0;
uart_rxd2 <= 1'b0;
end
else begin
uart_rxd1 <= uart_rxd;
uart_rxd2 <= uart_rxd1;
end
end
//捕捉输入数据的下降沿,若捕捉到uart_rxd下降沿,则start_flag拉高
assign start_flag = (~uart_rxd) & uart_rxd2;
//当star_flag被拉高时,接受过程标志信号rx_flag被拉高,直到uart_rxd到停止位
always @(posedge clk or negedge rst_n)begin
if(! rst_n)
rx_flag <= 1'b0;
else begin
if(start_flag)
rx_flag <= 1'b1;
else if((rx_cnt == 4'd9) && (clk_cnt == 9'd267))//计数到停止位中间时,停止接收过程
rx_flag <= 1'b0;
else
rx_flag <= rx_flag;
end
end
//clk_cnt计数模块,计数到434
always @(posedge clk or negedge rst_n)begin
if(! rst_n)
clk_cnt <= 9'd0;
else if(add_clk_cnt)begin
if(end_clk_cnt)
clk_cnt <= 9'd0;
else
clk_cnt <= clk_cnt +1'b1;
end
else
clk_cnt <= 9'd0;
end
assign add_clk_cnt = rx_flag && (clk_cnt < 9'd434);
assign end_clk_cnt = (clk_cnt == 9'd434) ? 1'b1 : 1'b0;
//rx_cnt计数模块,当clk_cnt增加到434时,rx_cnt加1,直到9
always @(posedge clk or negedge rst_n)begin
if(! rst_n)
rx_cnt <= 4'd0;
else if(add_rx_cnt)begin
if(end_rx_cnt)
rx_cnt <= 4'd0;
else
rx_cnt <= rx_cnt + 1'b1;
end
else
rx_cnt <= 4'd0;
end
assign add_rx_cnt = rx_flag && (rx_cnt < 4'd9);
assign end_rx_cnt = (rx_cnt == 4'd9) ? 1'b1 : 1'b0;
//当rx_cnt计数到9时,说明该帧数据已传输完毕,uart_done拉高
always @(posedge clk or negedge rst_n)begin
if(! rst_n)
uart_done <= 1'b0;
else if (rx_cnt == 4'd9)
uart_done <= 1'b1;
else
uart_done <= 1'b0;
end
//接受数据到寄存器,串行数据转并行
always @(posedge clk or negedge rst_n)begin
if(! rst_n)
rx_data <= 8'd0;
else if(rx_flag)//由于数据传输是串行的,所以无需等到uart_done拉高后再赋值
if(clk == 8'd267)begin//到时钟中间进行数据赋值
case(rx_cnt)
4'd1 : rx_data[0] <= uart_rxd2;
4'd2 : rx_data[1] <= uart_rxd2;
4'd3 : rx_data[2] <= uart_rxd2;
4'd4 : rx_data[3] <= uart_rxd2;
4'd5 : rx_data[4] <= uart_rxd2;
4'd6 : rx_data[5] <= uart_rxd2;
4'd7 : rx_data[6] <= uart_rxd2;
4'd8 : rx_data[7] <= uart_rxd2;
endcase
end
else
rx_data <= rx_data;
else
rx_data <= 8'd0;
end
//将寄存器中的数据传输到输出端口
always @(posedge clk or negedge rst_n)begin
if(! rst_n)
uart_data <= 8'd0;
else if (rx_cnt == 4'd9)//寄存器接受数据完成
uart_data <= rx_data;
else
uart_data <= 8'd0;
end
endmodule
(三)发送模块
module send(
input clk,
input rst_n,
input send_en,
input [7:0] data,//从接收端来的数据
output uart_tx_busy, //发送忙状态标志
output reg [7:0] tx_data,
output en_flag,
output reg tx_flag,
output reg [3:0] tx_cnt,
output reg uart_tx
);
reg [8:0]clk_cnt;
reg send_en1;
reg send_en2;
wire add_clk_cnt;
wire end_clk_cnt;
wire add_tx_cnt;
wire end_tx_cnt;
//对发送使能信号打两拍
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
send_en1 <= 1'b0;
send_en2 <= 1'b0;
end
else begin
send_en1 <= send_en;
send_en2 <= send_en1;
end
end
//捕捉使能信号send_en上升沿后
assign en_flag = send_en && (~send_en2);
//当捕捉到使能信号后,发送标志信号被拉高,直到数据被发送完且时钟到停止位
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_flag <= 1'b0;
else if(en_flag)
tx_flag <= 1'b1;
else if((tx_cnt == 4'd9) && (clk_cnt == 9'd400))
tx_flag <= 1'b0;
else
tx_flag <= tx_flag;
end
//当tx_flag信号被拉高后,说明信号正在在发送
assign uart_tx_busy =tx_flag;
//当捕捉到使能信号后,将输入的数据给到输出寄存器tx_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_data <= 8'b0;
else if(en_flag)
tx_data <= data;
else if((tx_cnt == 4'd9) && (clk_cnt == 9'd400))
tx_data <= 1'b0;
else
tx_data <= tx_data;
end
//当发送标志信号开始拉高后,计数器clk_cnt开始计数,直到434
always @(posedge clk or negedge rst_n)begin
if(! rst_n)
clk_cnt <= 9'd0;
else if(add_clk_cnt)begin
if(end_clk_cnt)
clk_cnt <= 9'd0;
else
clk_cnt <= clk_cnt +1'b1;
end
else
clk_cnt <= 9'd0;
end
assign add_clk_cnt = tx_flag && (clk_cnt < 9'd434);
assign end_clk_cnt = (clk_cnt == 9'd434) ? 1'b1 : 1'b0;
//当发送标志信号被拉高后,计数器tx_cnt开始计数直到9
always @(posedge clk or negedge rst_n)begin
if(! rst_n)
tx_cnt <= 4'd0;
else if(add_tx_cnt)begin
if(end_tx_cnt)
tx_cnt <= 4'd0;
else
tx_cnt <= tx_cnt + 1'b1;
end
else
tx_cnt <= 4'd0;
end
assign add_tx_cnt = tx_flag && (tx_cnt < 4'd9);
assign end_tx_cnt = (tx_cnt == 4'd9) ? 1'b1 : 1'b0;
//将寄存器tx_data中的数据传给端口uart_tx。并行数据转串行
always @(posedge clk or negedge rst_n)begin
if(! rst_n)
uart_tx <= 1'b0;
else if(tx_flag)begin
case(tx_cnt)
4'd0 : uart_tx <= 1'd0;
4'd1 : uart_tx <= tx_data[0];
4'd2 : uart_tx <= tx_data[1];
4'd3 : uart_tx <= tx_data[2];
4'd4 : uart_tx <= tx_data[3];
4'd5 : uart_tx <= tx_data[4];
4'd6 : uart_tx <= tx_data[5];
4'd7 : uart_tx <= tx_data[6];
4'd8 : uart_tx <= tx_data[7];
4'd9 : uart_tx <= 1'd1;
default:;
endcase
end
else
uart_tx <= 1'd1;
end
endmodule
(四)回环模块
module loop(
input clk,
input rst_n,
input recv_done, //接收一帧数据完成标志
input [7:0] recv_data, //接收的数据
input tx_busy, //发送忙状态标志
output reg send_en, //发送使能信号
output reg [7:0] send_data //待发送数据
);
reg recv_done1;
reg recv_done2;
reg tx_ready;
//对recv_done打两拍
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
recv_done1 <= 1'b0;
recv_done2 <= 1'b0;
end
else begin
recv_done1 <= recv_done;
recv_done2 <= recv_done1;
end
end
//捕获recv_done上升沿
assign recv_done_flag = (~recv_done2) & recv_done;
//判断接收完成信号,并在串口发送模块空闲时给出发送使能信号
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx_ready <= 1'b0;
send_en <= 1'b0;
send_data <= 8'd0;
end
else begin
if(recv_done_flag)begin //检测串口接收到数据
tx_ready <= 1'b1; //准备启动发送过程
send_en <= 1'b0;
send_data <= recv_data; //寄存串口接收的数据
end
else if(tx_ready && (~tx_busy)) begin //检测串口发送模块空闲
tx_ready <= 1'b0; //准备过程结束
send_en <= 1'b1; //拉高发送使能信号
end
end
end
endmodule
(五)顶层模块
module uart(
input sys_clk,
input sys_rst_n,
input uart_rxd, //UART接收端口
output uart_txd //UART发送端口
);
wire send_en_tmp;
wire tx_busy_tmp;
wire uart_data_tmp;
wire send_data_tmp;
wire uart_done_tmp;
//对输入模块例化
receive u_receive(
.clk (sys_clk),
.rst_n (sys_rst_n),
.uart_rxd (uart_rxd),//在顶层模块的端口接受数据
.uart_done (uart_done_tmp),//将接受完数据信号给到回环模块
.uart_data (uart_data_tmp)//将数据输出给回环模块
);
//对输出模块进行例化
send u_send(
.clk (sys_clk),
.rst_n (sys_rst_n),
.send_en (send_en_tmp),//从回环模块接受发送使能信号
.data (send_data_tmp),//从回环模块接受数据
.uart_tx_busy (tx_busy_tmp),//将发送忙给到回环模块
.uart_tx (uart_txd)//输出数据给顶层模块
);
//对数据回环模块进行例化
loop u_loop(
.clk (sys_clk),
.rst_n (sys_rst_n),
.recv_done (uart_done_tmp),//从输入模块接受输入完成信号
.recv_data (uart_data_tmp),//从输出模块接受数据
.tx_busy (tx_busy_tmp),//从输出模块接受发送忙信号
.send_en (send_en_tmp),//将发送使能信号给到输出模块
.send_data (send_data_tmp)//把数据输出到输出模块
);
endmodule
六、仿真
(一)测试文件
`timescale 1 ns/ 1 ps
module tb_uart();
reg sys_clk;
reg sys_rst_n;
reg uart_rxd;
wire uart_txd;
uart u_uart (
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.uart_rxd(uart_rxd),
.uart_txd(uart_txd)
);
initial
begin
sys_clk <= 1'b0;
sys_rst_n <= 1'b0;
uart_rxd <= 1'b1;
#40 sys_rst_n <=1'b1;
#20 uart_rxd <= 1'b0; //起始位
//有效数据位
#8680 uart_rxd <= 1'b1;//每比特数据需要持续434×20ns
#8680 uart_rxd <= 1'b0;
#8680 uart_rxd <= 1'b1;
#8680 uart_rxd <= 1'b1;
#8680 uart_rxd <= 1'b0;
#8680 uart_rxd <= 1'b1;
#8680 uart_rxd <= 1'b0;
#8680 uart_rxd <= 1'b0;
#8680 uart_rxd <= 1'b1;//停止位
end
always #10 sys_clk = ~sys_clk;
endmodule
(二)管脚约束文件
package require ::quartus::project
#system clock:50Mhz
set_location_assignment PIN_M2 -to sys_clk
#system reset
set_location_assignment PIN_M1 -to sys_rst_n
#USB UART
set_location_assignment PIN_N5 -to uart_rxd
set_location_assignment PIN_M7 -to uart_txd
(三)仿真波形图
由于在例化文件中给的uart_rxd数据按比特给0和1 ,因此波形图uart_rxd也会以8680ns为周期进行跳变。
参考正点原子视频,最后调试结果如下:
七、写在最后
不知不觉已经到了2023年的尾声了,先在这里祝各位朋友新年快乐。我现在坐在日本北九州市早稻田IPS的宿舍里面敲下这段话。无数人质疑我出国念书的决定,无数人质疑我将专业从电气工程及其自动化换到IC领域,这段时间也确实吃了不少的苦头,孤独,不被理解,被质疑,但既然选好了的路,那还是要尽量去把他走好,况且我认为结果并不会差。一开始是觉得数字IC设计HC太少,且学习难度高,因此想先以FPGA入门,我想着把FPGA学的差不多了还是要学学IC设计,毕竟行业天花板更高一些。经过半个来月的系统学习,发现自己对于代码逻辑,硬件设计等方面有极大的欠缺,能听懂顶层模块的设计原理,但是具体到实际的代码,往往感觉力不从心。我知道我学习方法有问题,不喜欢刨根问底,不喜欢反复去看,去重复代码,别人不催我我很难主动学习。因此开始在优快云上面写博客的初衷就在于希望自己能够不断更,这样也有一个外部的力量催着自己赶紧学,再有就是想要以后可以比较方便的复习,当然,如果能够帮到各位朋友那就再好不过啦。接下来的一年中要好好努力呀。在新的一年里,希望能够尽快补全自己的技术栈,然后找两个项目来练练,最好回国找一段实习,希望明年这个时候,我已经手握心仪的offer!
###本文参考正点原子视频,如有侵权,请联系删除。