本文是学习小梅哥串口模块教程后的整理总结。
用于数据接收与发送的常用通信协议: UART(通用异步收发传输器)、I2C(集成电路总线)、USB2.0/3.0(通用串行总线)、SPI(串行外围总线)、Ethernet(以太网) 等。
串行通信是指利用一条传输线将资料一位位地顺序传送。特点是通信线路简单,利用简单的线缆就可实现通信,降低成本,适用于远距离通信,但传输速度慢的应用场合。
异步通信以一个字符为传输单位,通信中两个字符间的时间间隔多少是不固定的,然而在同一个字符中的两个相邻位间的时间间隔是固定的。
这里讲解基于RS232接口标准规范的uart_tx异串口发送模块。
发送并行数据,转为串行数据送入上位机orPC等。
UART 是异步串行通信的总称。而** RS232、 RS449、 RS423、 RS422
和 RS485 等,是对应各种异步串行通信口的接口标准和总线标准,它们具体规定了通
信口的电气特性、传输速率、连接特性和接口的机械特性**等内容。
具体每个针脚的功能不再赘述,这里重点研究uart的传输时序图
需要设置数据位数、波特率、奇偶校验类型和停止位数
该参数定义单个 UART 数据传输在开始到停止期间发送的数据位数。 可选择为: 5、 6、 7 或者 8(默认)。
是指从一设备发到另一设备的波特率,即每秒钟可以通信数据**比特(bit)**个数。 典型的波特率有 300, 1200, 2400, 9600, 19200, 115200 等,单位bit/s,也即是bps。一般通信两端设备都要设为相同的波特率,有些设备也可设置为自动检测波特率。
是用来验证数据的正确性。如果用户选择数据长度为 8 位,是因为没有多余的比特可被用来作为奇偶校验位,因此就叫做“无奇偶校验( Non)”。此处不再赘述。
在每个字节的数据位发送完成之后,发送停止位,来标志着一次数据传输完成,同时用来帮助接受信号方硬件重同步。 可选择为: 1( 默认)、 1.5 或者 2 位。
在 RS-232 标准中, 最常用的配置是 8N1(即8数据位+无奇偶校验+1停止位), 其发送一个字节时序图如图
一个完整的字节包括1位起始位、8 位数据位、1位停止即总共10位数据来算,要想完整的实现这10位数据的发送,就需要11个波特率时钟脉冲, 第1个脉冲标记一次传输的起始, 第 11 个脉冲标记一次传输的结束。
BPS_CLK 信号的第一个上升沿到来时,字节发送模块开始发送起始位,接下来的 2 到 9 个上升沿, 发送 8 个数据位,第 10 个上升沿到第 11 个上升沿为停止位的发送。
也可以采用在每个起始位之前添加一位传 “1’b1” 的位,用来为START_BIT的最初变化显示为下降。,以便串口接收模块识别下降沿,开始接收数据。这样也就不用在最后再添加第11个脉冲来标记传输结束了。
以9600bps为例:bps_DR=[(10^9 ns)/(9600bps*20ns)]-1=5207(9600bps的分频计数最大值)
//baud_set
reg [15:0] bps_DR;//分频计数最大值
reg bps_clk; //波特率时钟
reg [15:0]div_cnt;//分频计数器
reg [15:0]bps_DR;//分频计数最大值
reg [3:0]bps_cnt;//波特率时钟计数器
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_DR <= 16'd5207;
else
begin
case(baud_set)//之后要为baudset 设置LUT查找表
0:bps_DR <= 16'd5207; //9600bps
1:bps_DR <= 16'd2603; //19200bps
2:bps_DR <= 16'd1301; //38400bps
3:bps_DR <= 16'd867; //57600bps
4:bps_DR <= 16'd433; //115200bps
default:bps_DR <= 16'd5207;//默认为9600bps
endcase
end
//counter
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
div_cnt <= 16'd0;
else if(uart_state)begin
if(div_cnt == bps_DR)
div_cnt <= 16'd0;
else
div_cnt <= div_cnt + 1'b1;
end
else
div_cnt <= 16'd0;
// bps_clk gen
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1)
bps_clk <= 1'b1;
else
bps_clk <= 1'b0;
//bps counter,对每个字节数据传输位数进行计数,1~11位
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_cnt <= 4'd0;
else if(bps_cnt == 4'd11)
bps_cnt <= 4'd0;
else if(bps_clk)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
output reg Tx_Done;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Tx_Done <= 1'b0;
else if(bps_cnt == 4'd11)
Tx_Done <= 1'b1;
else
Tx_Done <= 1'b0;
由低位到高位依次发送,由10-1MUX构成;
根据 bps_cnt 的值来确定数据传输的状态。 不同的波特率时钟计数值时,有不同的传输数据对应。
1’b1;
START_BIT;(1’b0)
r_data_byte[0];
r_data_byte[1];
r_data_byte[2];
r_data_byte[3];
r_data_byte[4];
r_data_byte[5];
r_data_byte[6];
r_data_byte[7];
STOP_BIT;(1’b1)
localparam START_BIT = 1'b0;
localparam STOP_BIT = 1'b1;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Rs232_Tx <= 1'b1;
else begin
case(bps_cnt)
0:Rs232_Tx <= 1'b1;
1:Rs232_Tx <= START_BIT;
2:Rs232_Tx <= r_data_byte[0];
3:Rs232_Tx <= r_data_byte[1];
4:Rs232_Tx <= r_data_byte[2];
5:Rs232_Tx <= r_data_byte[3];
6:Rs232_Tx <= r_data_byte[4];
7:Rs232_Tx <= r_data_byte[5];
8:Rs232_Tx <= r_data_byte[6];
9:Rs232_Tx <= r_data_byte[7];
10:Rs232_Tx <= STOP_BIT;
default:Rs232_Tx <= 1'b1;
endcase
end
由于 RS232 是一个异步的收发器, 因此为了保证发送的数据在时钟到来的时候是稳定的,这里也需要对输入数据进行寄存.可以一级也可以两级,这里一级就足够了
reg [7:0]r_data_byte;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
r_data_byte <= 8'd0;
else if(send_en)
r_data_byte <= data_byte;
else
r_data_byte <= r_data_byte;
output reg uart_state;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(bps_cnt == 4'd11)
uart_state <= 1'b0;
else
uart_state <= uart_state;
将所有数据变化与系统时钟错开 1ns,是为了能更清楚看到输入输出数据与时钟的时序关系.
根据需要,生成复位信号、使能信号send_en、待传输数据。
initial begin
Rst_n = 1'b0;
data_byte = 8'd0;
send_en = 1'd0;
baud_set = 3'd4;
#(`clk_period*20 + 1 )
Rst_n = 1'b1;
#(`clk_period*50);
data_byte = 8'haa;
send_en = 1'd1;
#`clk_period;
send_en = 1'd0;
@(posedge Tx_Done)
#(`clk_period*5000);
data_byte = 8'h55;
send_en = 1'd1;
#`clk_period;
send_en = 1'd0;
@(posedge Tx_Done)
#(`clk_period*5000);
$stop;
end