[Evaluation of domestic FPGA Gaoyun GW1N series development board]——(4) Serial port experiment
[Copy link]
This post was last edited by chg0823 on 2022-1-13 18:43
1. Introduction
Serial port is a common communication interface, which can be divided into synchronous serial interface and asynchronous serial interface. The main difference between synchronous and asynchronous serial interfaces is that asynchronous serial communication is transmitted in bytes, that is, data is transmitted one byte at a time, and the transmission speed is relatively low; synchronous serial communication requires data to be combined into bytes and sent together, that is, it is transmitted in the form of information blocks. At the same time, synchronous serial communication needs to communicate with the clock signal of the device to achieve the effect of "synchronization".
Usually asynchronous serial communication (UART) includes TTL level serial port and RS232 level serial port. TTL level is 3.3V, while RS232 is negative logic level, which defines +5~+12V as low level and -12~-5V as high level; synchronous serial communication is also a commonly used industrial communication interface. According to electrical standards and protocols, serial interfaces include RS-232-C, RS-422, RS485 and other commonly used interfaces.
This example is based on the TTL level experiment of asynchronous serial communication, and then an external TTL to USB module is connected to realize UART communication.
2. Principle
Common serial communication baud rates of UART are 9600, 38400, 115200, etc., and the sending and receiving baud rates must be consistent for correct communication. The baud rate refers to the maximum number of data bits transmitted in 1 second, including the start bit, data bit, check bit, and stop bit. If the communication baud rate is set to 9600, the time length of a data bit is 1/9600 seconds.
The UART transmission timing is shown in the following figure:
3. Programming
(1) Data transmission module
assign uart_tx_busy = tx_flag;
//捕获uart_en上升沿,得到一个时钟周期的脉冲信号
assign en_flag = (~uart_en_d1) & uart_en_d0;
//对发送使能信号uart_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_en;
uart_en_d1 <= uart_en_d0;
end
end
//当脉冲信号en_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_flag拉高
tx_data <= uart_din; //寄存待发送的数据
end
//计数到停止位结束时,停止发送过程
else if ((tx_cnt == 4'd9) && (clk_cnt == BPS_CNT - (BPS_CNT/16))) begin
tx_flag <= 1'b0; //发送过程结束,标志位tx_flag拉低
tx_data <= 8'd0;
end
else begin
tx_flag <= tx_flag;
tx_data <= tx_data;
end
end
//进入发送过程后,启动系统时钟计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
clk_cnt <= 16'd0;
else if (tx_flag) begin //处于发送过程
if (clk_cnt < BPS_CNT - 1)
clk_cnt <= clk_cnt + 1'b1;
else
clk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零
end
else
clk_cnt <= 16'd0; //发送过程结束
end
//进入发送过程后,启动发送数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
tx_cnt <= 4'd0;
else if (tx_flag) begin //处于发送过程
if (clk_cnt == BPS_CNT - 1) //对系统时钟计数达一个波特率周期
tx_cnt <= tx_cnt + 1'b1; //此时发送数据计数器加1
else
tx_cnt <= tx_cnt;
end
else
tx_cnt <= 4'd0; //发送过程结束
end
//根据发送数据计数器来给uart发送端口赋值
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
uart_txd <= 1'b1;
else if (tx_flag)
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
else
uart_txd <= 1'b1; //空闲时发送端口为高电平
end
(2) Data receiving module
assign start_flag = uart_rxd_d1 & (~uart_rxd_d0);
//对UART接收端口的数据延迟两个时钟周期
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
//当脉冲信号start_flag到达时,进入接收过程
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
rx_flag <= 1'b0;
else begin
if(start_flag) //检测到起始位
rx_flag <= 1'b1; //进入接收过程,标志位rx_flag拉高
//计数到停止位中间时,停止接收过程
else if((rx_cnt == 4'd9) && (clk_cnt == BPS_CNT/2))
rx_flag <= 1'b0; //接收过程结束,标志位rx_flag拉低
else
rx_flag <= rx_flag;
end
end
//进入接收过程后,启动系统时钟计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
clk_cnt <= 16'd0;
else if ( rx_flag ) begin //处于接收过程
if (clk_cnt < BPS_CNT - 1)
clk_cnt <= clk_cnt + 1'b1;
else
clk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零
end
else
clk_cnt <= 16'd0; //接收过程结束,计数器清零
end
//进入接收过程后,启动接收数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
rx_cnt <= 4'd0;
else if ( rx_flag ) begin //处于接收过程
if (clk_cnt == BPS_CNT - 1) //对系统时钟计数达一个波特率周期
rx_cnt <= rx_cnt + 1'b1; //此时接收数据计数器加1
else
rx_cnt <= rx_cnt;
end
else
rx_cnt <= 4'd0; //接收过程结束,计数器清零
end
//根据接收数据计数器来寄存uart接收端口数据
always @(posedge sys_clk or negedge sys_rst_n) begin
if ( !sys_rst_n)
rxdata <= 8'd0;
else if(rx_flag) //系统处于接收过程
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
rxdata <= rxdata;
else
rxdata <= 8'd0;
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
(3) Top-level module
//捕获recv_done上升沿,得到一个时钟周期的脉冲信号
assign recv_done_flag = (~recv_done_d1) & recv_done_d0;
//对发送使能信号recv_done延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
recv_done_d0 <= 1'b0;
recv_done_d1 <= 1'b0;
end
else begin
recv_done_d0 <= recv_done;
recv_done_d1 <= recv_done_d0;
end
end
//判断接收完成信号,并在串口发送模块空闲时给出发送使能信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_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
//串口接收模块
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 (recv_done),
.uart_data (recv_data)
);
//串口发送模块
uart_send #(
.CLK_FREQ (CLK_FREQ), //设置系统时钟频率
.UART_BPS (UART_BPS)) //设置串口发送波特率
u_uart_send(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_en (send_en),
.uart_din (send_data),
.uart_tx_busy (tx_busy),
.uart_txd (uart_txd)
);
4. Verification
Tested with the PC serial port, the results are as follows:
|