串口协议简介
串口通讯(Universal Asynchronous Receiver/Transmitter,UART)是一种异步传输的通用数据总线,包括了RS232、RS485等接口标准和总线标准,传输简单,将数据在串行通信与并行通信之间转换,可以实现全双工发送和接收。 UART的工作原理是将数据的每一个bit从低到高,依次输出到总线上,其通信协议时序如下:
如上图所示,为串口协议进行数据传输的时序状态。一个完整的字节传输包括了1个起始位,8个数据位和1个停止位,总共10位数据来计算。其中,数据位可设置为5、6、7或者8(默认)。波特率(Baud)表示串口设备数据传输的速率,在图中,垂直虚线表示进行数据的切换,中间的黑点表示数据采样点。典型的波特率设置一般为9600、19200、38400、57600和115200,波特率越高,数据的传输越快。
串口发送
根据串口通信的时序,在FPGA上实现串口的发送设计。
波特率时钟计算
在进行串口发送时,需要进行波特率的时钟计算,来确定当前发送的数据时钟。首先计算出对应波特率的时钟周期,假设波特率为115200,其时钟周期为1000000000/115200=8680.6ns,根据当前的系统时钟(sys_clk)频率,算出时钟计数值。波特率时钟计数值的代码如下:
localparam BUAT_RATE9600 = 3'd0,
BUAT_RATE19200 = 3'd1,
BUAT_RATE38400 = 3'd2,
BUAT_RATE57600 = 3'd3,
BUAT_RATE115200 = 3'd4;
localparam BUAT_SENDRATE9600 = 17'd104167/SYS_CLOCK_PERIOD - 1,
BUAT_SENDRATE19200 = 16'd52083/SYS_CLOCK_PERIOD - 1,
BUAT_SENDRATE38400 = 15'd26041/SYS_CLOCK_PERIOD - 1,
BUAT_SENDRATE57600 = 15'd17361/SYS_CLOCK_PERIOD - 1,
BUAT_SENDRATE115200 = 14'd8680/SYS_CLOCK_PERIOD - 1;
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)
bsp_DR <= BUAT_SENDRATE9600;
else begin
case(buad_set_i)
BUAT_RATE9600: bsp_DR <= BUAT_SENDRATE9600;
BUAT_RATE19200: bsp_DR <= BUAT_SENDRATE19200;
BUAT_RATE38400: bsp_DR <= BUAT_SENDRATE38400;
BUAT_RATE57600: bsp_DR <= BUAT_SENDRATE57600;
BUAT_RATE115200: bsp_DR <= BUAT_SENDRATE115200;
default:bsp_DR <= BUAT_SENDRATE9600;
endcase
end
end
分频计数
在计算出波特率的计数值后,利用计数器来生成波特率的时钟,实现代码如下:
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)
bsp_div_cnt <= 13'd0;
else if(uart_state == 1'b0)
bsp_div_cnt <= 13'd0;
else if(bsp_div_cnt == bsp_DR)
bsp_div_cnt <= 13'd0;
else
bsp_div_cnt <= bsp_div_cnt + 13'd1;
end
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)
bsp_clk <= 1'b0;
else if(bsp_div_cnt == 13'd1)
bsp_clk <= 1'b1;
else
bsp_clk <= 1'b0;
end
状态输出
在每一次串口发送完成之后,发送一个时钟周期的完成信号,代码如下:
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)
bsp_cnt <= 4'd0;
else if(bsp_cnt == 4'd11)
bsp_cnt <= 4'd0;
else if(bsp_clk)
bsp_cnt <= bsp_cnt + 4'd1;
else
bsp_cnt <= bsp_cnt;
end
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)
tx_done <= 1'b0;
else if(bsp_cnt == 4'd11)
tx_done <= 1'b1;
else
tx_done <= 1'b0;
end
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)
uart_state <= 1'b0;
else if(send_en_i)
uart_state <= 1'b1;
else if(bsp_cnt == 4'd11)
uart_state <= 1'b0;
else
uart_state <= uart_state;
end
数据输出
发送数据的输出则是依靠一个十选一的多路选择器,将数据发送到总线上。在进行传输之前,获取到传输的数据,在选择器中选择输出。代码实现如下:
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)
tx_data <= 8'b0;
else if(send_en_i)
tx_data <= send_data_i;
else
tx_data <= tx_data;
end
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)
uart_tx <= 1'b1;
else begin
case(bsp_cnt)
0: uart_tx <= 1'b1;
1: uart_tx <= START_BIT;
2: uart_tx <= tx_data[0];
3: uart_tx <= tx_data[1];
4: uart_tx <= tx_data[2];
5: uart_tx <= tx_data[3];
6: uart_tx <= tx_data[4];
7: uart_tx <= tx_data[5];
8: uart_tx <= tx_data[6];
9: uart_tx <= tx_data[7];
10: uart_tx <= STOP_BIT;
default uart_tx <= 1'b1;
endcase
end
end
串口接收
根据串口协议的时序图,可知在每一位的中点即图中黑点位置进行采样,数据是最准确的。但在实际的电路中,可能因为干扰等其他因素的影响,只采样一次可能采集到错误的数据,因此,在进行串口接收时,对每一位进行多次采样求概率的方式判定状态,如图所示:
如图所示,将每bit数据分成16份,只在中间的6份中进行采样,采样6次,取采样次数多的电平作为采样结果。
边沿获取
输入的数据作为异步信号,需要同步到系统时钟的时钟域,处理方式如下:
//信号同步
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)begin
s0_uart_rx <= 1'b0;
s1_uart_rx <= 1'b0;
end
else begin
s0_uart_rx <= uart_rx_i;
s1_uart_rx <= s0_uart_rx;
end
end
//边沿获取
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)begin
tmp0_uart_rx <= 1'b0;
tmp1_uart_rx <= 1'b0;
end
else begin
tmp0_uart_rx <= s1_uart_rx;
tmp1_uart_rx <= tmp0_uart_rx;
end
end
assign uart_rx_falling = (!tmp0_uart_rx) & tmp1_uart_rx;
采样时钟计数
按照之前的发送波特率计数的方式,需要在每一个周期再除以16,作为采样时钟的计数值,例如波特率115200,周期为8680ns,除以16后在除以系统时钟周期,作为波特率115200的采样计数值,代码如下:
localparam BUAT_RATE9600 = 3'd0,
BUAT_RATE19200 = 3'd1,
BUAT_RATE38400 = 3'd2,
BUAT_RATE57600 = 3'd3,
BUAT_RATE115200 = 3'd4;
localparam CNT_NUM_9600 = 13'd6510/SYS_CLOCK_PERIOD - 1,
CNT_NUM_19200 = 12'd3225/SYS_CLOCK_PERIOD - 1,
CNT_NUM_38400 = 11'd1627/SYS_CLOCK_PERIOD - 1,
CNT_NUM_57600 = 11'd1085/SYS_CLOCK_PERIOD - 1,
CNT_NUM_115200 = 10'd542/SYS_CLOCK_PERIOD - 1;
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)
bsp_DR <= CNT_NUM_9600;
else begin
case(buad_set_i)
BUAT_RATE9600: bsp_DR <= CNT_NUM_9600;
BUAT_RATE19200: bsp_DR <= CNT_NUM_19200;
BUAT_RATE38400: bsp_DR <= CNT_NUM_38400;
BUAT_RATE57600: bsp_DR <= CNT_NUM_57600;
BUAT_RATE115200: bsp_DR <= CNT_NUM_115200;
default: bsp_DR <= CNT_NUM_9600;
endcase
end
end
分频计数
串口接收的分频计数与发送的分频计数基本相同,唯一的差别是在获取到下降沿时,进行连续的采样,若采样到两次高电平,则表示当前的下降沿时干扰信号,并非实际的传输信号,则停止计数。代码实现如下:
localparam BSP_CNT_NUM = 8'd159;
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)
bsp_cnt <= 8'd0;
else if((bsp_cnt == BSP_CNT_NUM) | ((bsp_cnt == 8'd12) && (start_bit > 2)))
bsp_cnt <= 8'd0;
else if(bsp_clk)
bsp_cnt <= bsp_cnt + 8'd1;
else bsp_cnt <= bsp_cnt;
end
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)
rx_done <= 1'b0;
else if(bsp_cnt == BSP_CNT_NUM)
rx_done <= 1'b1;
else
rx_done <= 1'b0;
end
数据接收
对数据的接收,也是采用多路选择器的方式,对6次采样的数据叠加,判断每一bit的电平状态,实现代码如下:
reg [2:0]start_bit,stop_bit; //起始位停止位
reg [2:0]r_data_collect[7:0]; //采集到的数据
reg [7:0]tmp_r_data;
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)begin
start_bit <= 3'd0;
r_data_collect[0] <= 3'd0;
r_data_collect[1] <= 3'd0;
r_data_collect[2] <= 3'd0;
r_data_collect[3] <= 3'd0;
r_data_collect[4] <= 3'd0;
r_data_collect[5] <= 3'd0;
r_data_collect[6] <= 3'd0;
r_data_collect[7] <= 3'd0;
stop_bit <= 3'd0;
end
else if(bsp_clk)begin
case(bsp_cnt)
0:
begin
start_bit <= 3'd0;
r_data_collect[0] <= 3'd0;
r_data_collect[1] <= 3'd0;
r_data_collect[2] <= 3'd0;
r_data_collect[3] <= 3'd0;
r_data_collect[4] <= 3'd0;
r_data_collect[5] <= 3'd0;
r_data_collect[6] <= 3'd0;
r_data_collect[7] <= 3'd0;
stop_bit <= 3'd0;
end
6,7,8,9,10,11: start_bit <= start_bit + s1_uart_rx;
22,23,24,25,26,27: r_data_collect[0] <= r_data_collect[0] + s1_uart_rx;
38,39,40,41,42,43: r_data_collect[1] <= r_data_collect[1] + s1_uart_rx;
54,55,56,57,58,59: r_data_collect[2] <= r_data_collect[2] + s1_uart_rx;
70,71,72,73,74,75: r_data_collect[3] <= r_data_collect[3] + s1_uart_rx;
86,87,88,89,90,91: r_data_collect[4] <= r_data_collect[4] + s1_uart_rx;
102,103,104,105,106,107: r_data_collect[5] <= r_data_collect[5] + s1_uart_rx;
118,119,120,121,122,123: r_data_collect[6] <= r_data_collect[6] + s1_uart_rx;
134,135,136,137,138,139: r_data_collect[7] <= r_data_collect[7] + s1_uart_rx;
150,151,152,153,154,155: stop_bit <= stop_bit + s1_uart_rx;
default:
begin
start_bit <= start_bit;
r_data_collect[0] <= r_data_collect[0];
r_data_collect[1] <= r_data_collect[1];
r_data_collect[2] <= r_data_collect[2];
r_data_collect[3] <= r_data_collect[3];
r_data_collect[4] <= r_data_collect[4];
r_data_collect[5] <= r_data_collect[5];
r_data_collect[6] <= r_data_collect[6];
r_data_collect[7] <= r_data_collect[7];
stop_bit <= stop_bit;
end
endcase
end
end
always@(posedge sys_clk_i or negedge rst_n)begin
if(!rst_n)
tmp_r_data <= 8'b0;
else if(bsp_cnt == BSP_CNT_NUM)begin
tmp_r_data[0] <= r_data_collect[0][2];
tmp_r_data[1] <= r_data_collect[1][2];
tmp_r_data[2] <= r_data_collect[2][2];
tmp_r_data[3] <= r_data_collect[3][2];
tmp_r_data[4] <= r_data_collect[4][2];
tmp_r_data[5] <= r_data_collect[5][2];
tmp_r_data[6] <= r_data_collect[6][2];
tmp_r_data[7] <= r_data_collect[7][2];
end
else
tmp_r_data <= tmp_r_data;
end
仿真测试
创建激励文件,对串口功能进行仿真测试,仿真结果如下:
如图所示,TX发送的数据为01,tx的发送时序在uart_tx_o上可看出,bsp_clk作为数据bit位的传输计数。在RX中,rx_data_o表示接收到的数据,rx_done_o表示接收完成信号。