赞
踩
串口通信是一种通过串行传输数据的通信方式。它使用单个数据线将数据位逐个传输,而不是同时传输多个数据位。串口通信常用于连接计算机与外部设备,如打印机、调制解调器、传感器等。
串口通信一般使用的是异步传输方式,即发送方和接收方的时钟不同步。数据传输时,发送方将数据位、起始位、停止位和校验位按照一定的规则组合成数据帧,然后逐位地通过数据线发送。接收方在接收到起始位后开始接收数据位,并在接收到停止位后完成接收。校验位用于检测数据传输的错误。
串口通信有多种标准,常见的包括RS-232、RS-485、UART等。RS-232是一种常见的串口通信标准,它定义了电气特性、信号级别和连接器类型等。RS-485是一种多点通信标准,可以连接多个设备进行通信。UART是一种通用异步收发传输器,用于实现串口通信。
串口通信具有以下特点:
串口通信在许多应用中广泛使用,特别是在嵌入式系统、工业自动化、通信设备等领域。它可以实现设备之间的数据交换和控制,提供了一种简单可靠的通信方式。
- `timescale 1ns / 1ps
-
-
-
- module uart_tx #
- (parameter
- CLK_FREQ = 50_000_000,
- BAUD_RATE = 9600
- )
- (
- input clk,
- input rst_n,
- input wire [7:0] pi_data,
- input wire pi_flag,
- output reg tx
- );
- localparam BAUD_CNT_MAX = CLK_FREQ/BAUD_RATE;
- reg [12:0] baud_cnt;
- reg [3:0] bit_cnt;
- reg work_en;
-
-
- always @(posedge clk or negedge rst_n)
- if(rst_n == 1'b0)
- baud_cnt <= 13'b0;
- else if ((baud_cnt == BAUD_CNT_MAX-1) || (work_en == 1'b0))
- baud_cnt <= 13'd0;
- else if(work_en == 1'b1)
- baud_cnt <= baud_cnt + 13'd1;
- else
- baud_cnt <= 13'd0;
-
- always @(posedge clk or negedge rst_n)
- if(rst_n == 1'b0)
- bit_cnt <= 4'b0;
- else if ((baud_cnt == BAUD_CNT_MAX/2-1) && (work_en == 1'b1))
- bit_cnt <= bit_cnt + 4'd1;
- else if ((bit_cnt == 4'd9)&& (baud_cnt == 13'd1))
- bit_cnt <= 4'd0;
- else
- bit_cnt <= bit_cnt;
-
- always @(posedge clk or negedge rst_n)
- if(rst_n == 1'b0)
- work_en <= 1'b0;
- else if (pi_flag == 1'b1)
- work_en <= 1'b1;
- else if ((bit_cnt == 4'd9) && (baud_cnt == 13'd1))
- work_en <= 1'b0;
- else
- work_en <= work_en;
-
- always @(posedge clk) //并转串发送
- if(work_en == 1'b1)begin
- case(bit_cnt)
- 1: tx <= 1'b0;
- 2: tx <= pi_data[0];
- 3: tx <= pi_data[1];
- 4: tx <= pi_data[2];
- 5: tx <= pi_data[3];
- 6: tx <= pi_data[4];
- 7: tx <= pi_data[5];
- 8: tx <= pi_data[6];
- 9: tx <= pi_data[7];
- default
- tx <= 1'b1;
- endcase
- end
-
- else
- tx <= 1'b1; //空闲
-
- //调用ila模块IP核进行debug
- ila_0 uart_tx (
- .clk(clk), // input wire clk
-
-
- .probe0(clk ), // input wire [0:0] probe0
- .probe1(rst_n ), // input wire [0:0] probe1
- .probe2(pi_data), // input wire [7:0] probe2
- .probe3(pi_flag), // input wire [0:0] probe3
- .probe4(tx ) // input wire [0:0] probe4
- );
-
- endmodule
- `timescale 1ns / 1ps
-
-
-
- module uart_tx_tb();
-
- reg sys_clk,rst_n;
- reg pi_flag;
- reg [7:0] pi_data;
- wire tx;
- initial
- begin
- sys_clk <= 1'b1;
- rst_n <= 1'b0;
- #20
- rst_n <= 1'b1;
- end
- //模拟发送7次数据,值为1~7
- initial
- begin
- pi_data <= 8'd0;
- pi_flag <= 1'd0;
- #200
- //发送数据1
- pi_data <= 8'd1;
- pi_flag <= 1'b1;
- #20
- pi_flag <= 1'b0;
- #(5208*20*10) //每发送 1bit 数据需要 5208 个时钟周期,一帧数据为 10bit
- //发送数据2
- pi_data <= 8'd2;
- pi_flag <= 1'b1;
- #20
- pi_flag <= 1'b0;
- #(5208*20*10)
- //发送数据3
- pi_data <= 8'd3;
- pi_flag <= 1'b1;
- #20
- pi_flag <= 1'b0;
- #(5208*20*10)
- //发送数据4
- pi_data <= 8'd4;
- pi_flag <= 1'b1;
- #20
- pi_flag <= 1'b0;
- #(5208*20*10)
- //发送数据5
- pi_data <= 8'd5;
- pi_flag <= 1'b1;
- #20
- pi_flag <= 1'b0;
- #(5208*20*10)
- //发送数据6
- pi_data <= 8'd6;
- pi_flag <= 1'b1;
- #20
- pi_flag <= 1'b0;
- #(5208*20*10)
- //发送数据7
- pi_data <= 8'd7;
- pi_flag <= 1'b1;
- #20
- pi_flag <= 1'b0;
-
- end
-
- always #10 sys_clk = ~sys_clk;
-
- uart_tx #
- (
- .CLK_FREQ (50_000_000),
- .BAUD_RATE( 9600 )
- )
- uart_tx_tb(
- .clk (sys_clk),
- .rst_n (rst_n),
- .pi_data (pi_data),
- .pi_flag (pi_flag),
- .tx (tx)
- );
- endmodule
仿真结果分析
仿真运行10ms后的结果,可以看到在每次位传输计数bit_cnt=9时,输出tx产生高电平,表示空闲状态,之后在bit_cnt计数1~9的区间内发送9个数据(第1位为低电平,数据开始信号),以输入pi_data=3为例,分别在bit_cnt为2、3时拉高,即对应输出数据的第1、2位,即8'b0000 0011(8'd3)。在整个仿真时间内发送了数据1~7,仿真结果正确。
- `timescale 1ns / 1ps
-
-
-
- module uart_rx
- #(parameter
- CLK_FREQ = 50_000_000, //50MHz的时钟频率,每秒产生50,000,000个周期
- BAUD_RATE = 9_600 //9600的波特率,每秒发送9600个码元
- )
- (
- input clk,
- input rst_n,
- input rx,
- output reg [7:0] po_data,
- output reg po_flag
- );
- localparam BAUD_cnt_max = CLK_FREQ/BAUD_RATE; //每5208个周期发送一个码元
-
- reg [2:0] rx_reg3;
- reg work_en;
- reg [12:0] baud_cnt;
- reg [3:0] bit_cnt;
- reg [7:0] rx_data;
- reg rx_end_flag;
-
-
- always @ (posedge clk or negedge rst_n)//拼接在右,每时钟取值后右移一位
- if (rst_n == 1'b0)
- rx_reg3 <= 3'b111;
- else
- rx_reg3 <= {rx,rx_reg3[2:1]}; //跨时钟域传输,打两拍,rx_reg3保存了原始信号、第1、2拍,共三位数据
-
- always @ (posedge clk or negedge rst_n)
- if (rst_n == 1'b0)
- work_en <= 1'b0;
- else if (rx_reg3[1:0] == 2'b01)//打两拍后第1拍为低、第2拍为高时
- work_en <= 1'b1;
- else if ((baud_cnt == BAUD_cnt_max/2-1) && (bit_cnt == 4'd8)) //最后一个码元计数周期的中间位置处拉低,与bit_cnt一致
- work_en <= 1'b0;
- else
- work_en <= work_en;
-
- always @ (posedge clk or negedge rst_n)
- if (rst_n == 1'b0)
- baud_cnt <= 13'd0;
- else if((baud_cnt == BAUD_cnt_max-1) || (work_en == 1'b0))
- baud_cnt <= 13'd0;
- else if (work_en == 1'b1)
- baud_cnt <= baud_cnt + 13'd1;
- else
- baud_cnt <= 13'd0;
-
- always @ (posedge clk or negedge rst_n)
- if (rst_n == 1'b0)
- bit_cnt <= 4'd0;
- else if((bit_cnt == 4'd8) && (baud_cnt == BAUD_cnt_max/2-1))
- bit_cnt <= 4'd0;
- else if(baud_cnt == BAUD_cnt_max/2-1) //每次在计数周期的中间位置采数据最稳定
- bit_cnt <= bit_cnt + 4'd1;
- else
- bit_cnt <= bit_cnt;
-
- always @ (posedge clk or negedge rst_n)
- if (rst_n == 1'b0)
- rx_data <= 8'b0;
- else if ((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(baud_cnt == BAUD_cnt_max/2-1)) //拼接在右,每时钟取值后右移一位
- rx_data <= {rx_reg3[0],rx_data[7:1]}; //rx_reg3中的第1位为打两拍后的数据
- else
- ;
-
- always @ (posedge clk or negedge rst_n)
- if (rst_n == 1'b0)
- rx_end_flag <= 1'b0;
- else if((bit_cnt == 4'd8) && (baud_cnt == BAUD_cnt_max/2-1))
- rx_end_flag <= 1'b1;
- else
- rx_end_flag <= 1'b0;
-
- always @ (posedge clk or negedge rst_n)
- if (rst_n == 1'b0)
- po_data<= 8'h00;
- else if (rx_end_flag == 1'b1)
- po_data<= rx_data;
- else
- ;
-
- always @ (posedge clk or negedge rst_n)
- if (rst_n == 1'b0)
- po_flag<= 1'b0;
- else
- po_flag<= rx_end_flag;
-
- ila_0 uart_tx (
- .clk(clk), // input wire clk
-
-
- .probe0(clk ), // input wire [0:0] probe0
- .probe1(rst_n ), // input wire [0:0] probe1
- .probe2(po_data), // input wire [7:0] probe2
- .probe3(po_flag), // input wire [0:0] probe3
- .probe4(rx ) // input wire [0:0] probe4
- );
- endmodule
发送部分的代码相对复杂,主要原因是使用了打2拍的方法消除跨时钟域传输数据的亚稳态情况。
- `timescale 1ns / 1ps
-
-
-
- module uart_rx_tb( );
- reg sys_clk;
- reg rst_n;
- reg rx;
- wire po_data;
- wire po_flag;
-
- initial
- begin
- sys_clk <= 1'b1;
- rst_n <= 1'b0;
- rx <= 1'b1;
- #20
- rst_n <= 1'b1;
- end
-
- always #10 sys_clk = ~sys_clk;
-
- //定义一个名为 rx_bit 的任务,每次发送的数据有 10 位
- task rx_bit(
- input [7:0] data
- );
- integer i; //定义一个常量
- for(i=0; i<10; i=i+1)begin //不可以写成C语言 i=i++的形式
- case(i)
- 0: rx <= 1'b0;
- 1: rx <= data[0];
- 2: rx <= data[1];
- 3: rx <= data[2];
- 4: rx <= data[3];
- 5: rx <= data[4];
- 6: rx <= data[5];
- 7: rx <= data[6];
- 8: rx <= data[7];
- 9: rx <= 1'b1;
- default
- ;
- endcase
- #(5208*20); //每发送 1 位数据延时 5208 个时钟周期
- end
- endtask
-
- initial
- begin
- #400
- rx_bit(8'd0); //发送0000 0000
- rx_bit(8'd1); //发送0000 0001
- rx_bit(8'd2);
- rx_bit(8'd3);
- rx_bit(8'd4);
- rx_bit(8'd5);
- rx_bit(8'd6);
- rx_bit(8'd7); //发送0000 0111
- rx_bit(8'd8);
- rx_bit(8'd9);
- end
-
- uart_rx
- #(
- .CLK_FREQ (50_000_000),
- .BAUD_RATE (9600 )
- )
- uart_rx_tb(
- .clk (sys_clk),
- .rst_n (rst_n),
- .rx (rx),
- .po_data (po_data),
- .po_flag (po_flag)
- );
-
-
- endmodule
在仿真程序中,我们给输入信号赋0~9共十个数据。
仿真运行结果:
po_data成功接收了0~9的数据,仿真结果正确。
写一个顶层模块将uart发送和接收模块引用,从而实现FPGA的rx端接收到上位机的数据后马上“串转并”,传给FPGA的发送模块进行“并转串”通过tx端一位一位发送给上位机,并在上位机上显示。以上过程使用串口助手完成。
- `timescale 1ns / 1ps
-
-
- module uart_top(
- input sys_clk,
- input rst_n,
-
- input wire rx,
- output wire tx
- );
- wire [7:0] pi_data;
- wire pi_flag;
- parameter
- CLK_FREQ = 26'd50_000_000,
- BAUD_RATE = 14'd9_600 ;
-
- uart_rx
- #(
- .CLK_FREQ (CLK_FREQ), //50MHz的时钟频率,每秒产生50,000,000个周期
- .BAUD_RATE ( BAUD_RATE ) //9600的波特率,每秒发送9600个码元
- )
- uart_rx_top(
- .clk (sys_clk),
- .rst_n (rst_n),
- .rx (rx), //将rx引脚接收的数据作为输入赋给rx模块
-
- .po_data(pi_data), //串转并后输出给8位pi_data寄存器
- .po_flag(pi_flag)
- );
-
- uart_tx #
- (
- .CLK_FREQ (CLK_FREQ ),
- .BAUD_RATE (BAUD_RATE)
- )
- uart_tx_top(
- .clk (sys_clk),
- .rst_n (rst_n),
- .pi_data (pi_data), //将8位的pi_data寄存器的值再作为输入给tx模块
- .pi_flag (pi_flag),
-
- .tx (tx) //并转串后输出给上位机
- );
-
- endmodule
使用ARTIX-7 xc7A35T fgg484芯片,用ila模块进行debug。
在串口助手里进行数据发送。
观察串口接收端数据。
观察串口发送端数据。
使用状态机来实现串口通讯协议:
- module uart(
- input clk,
- input reset,
- input rx,
- output tx
- );
-
- reg [7:0] data;
- reg [2:0] state;
- reg [3:0] count;
- reg start_bit;
- reg [7:0] tx_data;
- reg tx_busy;
-
- parameter IDLE = 0;
- parameter START_BIT = 1;
- parameter DATA_BITS = 2;
- parameter STOP_BIT = 3;
-
- always @(posedge clk or posedge reset) begin
- if (reset) begin
- state <= IDLE;
- count <= 0;
- start_bit <= 0;
- tx_busy <= 0;
- end
- else begin
- case (state)
- IDLE: if (rx == 0) begin
- state <= START_BIT;
- count <= 0;
- start_bit <= 1;
- end
- START_BIT: if (count == 7) begin
- state <= DATA_BITS;
- count <= 0;
- end
- else begin
- count <= count + 1;
- end
- DATA_BITS: if (count == 7) begin
- state <= STOP_BIT;
- count <= 0;
- end
- else begin
- count <= count + 1;
- end
- STOP_BIT: if (count == 3) begin
- state <= IDLE;
- count <= 0;
- start_bit <= 0;
- end
- else begin
- count <= count + 1;
- end
- endcase
- end
- end
-
- always @(posedge clk) begin
- if (state == DATA_BITS) begin
- data <= rx;
- end
- end
-
- always @(posedge clk) begin
- if (tx_busy) begin
- if (count == 0) begin
- tx <= start_bit;
- end
- else if (count >= 1 && count <= 8) begin
- tx <= tx_data[count - 1];
- end
- else if (count == 9) begin
- tx <= 1;
- end
- else if (count == 10) begin
- tx <= 1;
- tx_busy <= 0;
- end
- end
- end
-
- always @(posedge clk) begin
- if (state == START_BIT && count == 7) begin
- tx_data <= data;
- tx_busy <= 1;
- end
- end
-
- endmodule
在上面的UART模块中,具有时钟输入、复位输入、接收输入和发送输出。在时钟上升沿触发的时候,根据当前状态和计数器的值,模块会执行相应的操作。
模块中定义了几个寄存器,用于存储数据、状态和计数器的值。还定义了一些参数,用于表示不同的状态。
在always块中,根据时钟和复位信号的边沿,根据当前状态执行相应的操作。例如,在IDLE状态下,如果接收到了起始位(rx == 0),则切换到START_BIT状态,并设置计数器和起始位的值。在START_BIT状态下,计数器递增,直到达到7,然后切换到DATA_BITS状态。在DATA_BITS状态下,将接收到的数据存储到data寄存器中。在STOP_BIT状态下,计数器递增,直到达到3,然后切换回IDLE状态,并清除起始位。
在另一个always块中,根据时钟的上升沿,根据当前状态和计数器的值,设置发送输出tx的值。如果tx_busy为1,则表示正在发送数据。在计数器为0时,发送起始位。在计数器为1到8时,发送数据位。在计数器为9时,发送停止位。在计数器为10时,停止发送,并将tx_busy设置为0。
最后一个always块用于在START_BIT状态下,将接收到的数据存储到tx_data寄存器中,并将tx_busy设置为1,表示开始发送数据。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。