rs232串口通信
前言:
本文主要介绍了集成电路EDA这门课程的相关实验及代码。使用的软件是Quartus Ⅱ,该实验使用fpga芯片为cyclone IV EP4CE115F29C7。
1. 实验要求
串口接发送,设计实现接收PC机发送的信息,并发送信息给PC机
FPGA接收到hello后,回复ni,hao给PC机
2. 实验原理
实验采用用UART数据通信协议实现串口通信,UART是一种通用的数据通信协议,也是异步串行通信口(串口)的总称。
它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。包括RS232、RS499、RS423、RS422和RS485等接口标准规范和总线标准规范。
本次实验采用RS232接口规范实现串口通信。
• PC机通过串口调试助手(上位机)向FPGA发送8bit数据,一位一位发送,首先接收最低位,最后最高位,之后FPGA拼凑成8bit数据
• FPGA向PC机发送8bit数据,通过串口调试助手(上位机),一位一位发送,首先接收最低位,最后最高位,之后串口调试助手拼凑成8bit数据
通信基于帧结构,一帧一帧发送数据
tx–发送端 串行发送
rx–接收端 并行接收
一帧总共10bit:
• 起始位/停止位-一个波特时间
• 每bit数据持续连续1个波特时间,可能发送很多次
3. 实验过程及源码
3.1 顶层模块rs232
对接收模块uart_rx与发送模块uart_tx的调用,并且存储接收的数据,当接收数据满足hello的ASCII码时,控制发送ni,hao的ASCII码给PC机通信
`timescale 1ns/1ns
module rs232
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
input wire rx , //串口接收数据
output wire tx //串口发送数据
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter UART_BPS = 20'd9600 , //比特率
CLK_FREQ = 26'd50_000_000 ; //时钟频率
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;
//wire define
wire en_h_flag;
wire [7:0] po_data; //接收的数据
wire po_flag; //接收完1字节数据标志位,高电平有效
wire flag; //识别到接收数据与密码对应标志位
wire tx_flag; //发送完1字节数据标志位,高电平有效
reg [39:0] datain_reg; //存储接收的数据,5字节
reg [47:0] dataout_reg;//存储的要发送的数据,6字节
reg [1:0] state; //状态位
reg [7:0] data_tx; //发送的1字节数据
reg en_tx; //发送允许标志位
reg [2:0] tx_cnt; //发送字节计数器,发送6个后置0
reg en; //发送控制开关
reg [12:0] baud_cnt; //收到发送成功的tx_flag后延迟1个波特
reg bit_flag; //计满1baud有效
reg work; //波特计数器baud_cnt有效
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------------------ uart_rx_inst ------------------------
uart_rx
#(
.UART_BPS (UART_BPS ), //串口波特率
.CLK_FREQ (CLK_FREQ ) //时钟频率
)
uart_rx_inst
(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.rx (rx ), //input rx
.po_data (po_data ), //output [7:0] po_data
.po_flag (po_flag ) //output po_flag
);
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
en <= 1'b1;
else if(en_h_flag)
en <= 1'b1;
else if(tx_cnt>=3'd5)
en <= 1'b0;
//接收数据寄存
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
datain_reg <= 40'd0;
else if(po_flag)
datain_reg <= {datain_reg[31:0],po_data[7:0]};
//接收到tx_flag后,延迟一个baud时间再发送下一个
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
work <= 1'b0;
else if(tx_flag)
work <= 1'b1;
else if(state != 2'd2)
work <= 1'b0;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
baud_cnt <= 13'd0;
else if((baud_cnt == BAUD_CNT_MAX - 1) || en_tx)
baud_cnt <= 13'b0;
else if(work)
baud_cnt <= baud_cnt + 1'd1;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX - 1)
bit_flag <= 1'b1;
else if(state != 2'd2)
bit_flag <= 1'b0;
//hello的ASCII码
assign flag = (datain_reg == 40'h68656c6c6f)? 1'b1:1'b0;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
begin
state <= 2'd0;
dataout_reg <= 48'h6e692c68616f;//ni,hao的ASCII码
data_tx <= 8'd0;
en_tx <= 1'b0;
tx_cnt <= 3'd0;
end
else
case(state)
2'd0:
begin
if(flag && en)
state <= 2'd1;
else
state <= 2'd0;
end
2'd1://发送数据
begin
state <= 2'd2;
data_tx <= dataout_reg[47:40];
en_tx <= 1'b1;
dataout_reg <= dataout_reg << 8;
end
2'd2://等待数据发送完成,并计数+1
begin
if(bit_flag)
begin
if(tx_cnt>=3'd5)begin
state <= 2'd0;
tx_cnt <= 3'd0;
end
else begin
state <= 2'd1;
tx_cnt <= tx_cnt + 1'd1;
end
end
else
begin
en_tx <= 1'b0;
state <= 2'd2;
end
end
default : state <= 2'd0;
endcase
//------------------------ uart_tx_inst ------------------------
uart_tx
#(
.UART_BPS (UART_BPS ), //串口波特率
.CLK_FREQ (CLK_FREQ ) //时钟频率
)
uart_tx_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.pi_data (data_tx ),
.pi_flag (en_tx ),
.tx (tx ),
.tx_flag (tx_flag )
);
endmodule
3.2 接收模块uart_tx
时序图如下:
`timescale 1ns/1ns
//FPGA-->PC端 发送
module uart_tx
#(
parameter UART_BPS = 'd9600, //串口波特率
parameter CLK_FREQ = 'd50_000_000 //时钟频率
)
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
input wire [7:0] pi_data , //模块输入的8bit数据
input wire pi_flag , //并行数据有效标志信号
output reg tx , //串转并后的1bit数据
output reg tx_flag //发送一个字节完毕标志位
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//localparam define
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;
//reg define
reg [12:0] baud_cnt;
reg bit_flag;
reg [3:0] bit_cnt ;
reg work_en ;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
work_en <= 1'b0;
else if(pi_flag == 1'b1)
work_en <= 1'b1;
else if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
work_en <= 1'b0;
//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
baud_cnt <= 13'b0;
else if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
baud_cnt <= 13'b0;
else if(work_en == 1'b1)
baud_cnt <= baud_cnt + 1'b1;
//bit_flag:当baud_cnt计数器计数到1时让bit_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == 13'd1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
//bit_cnt:数据位数个数计数,10个有效数据(含起始位和停止位)到来后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_cnt <= 4'b0;
else if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
bit_cnt <= 4'b0;
else if((bit_flag == 1'b1) && (work_en == 1'b1))
bit_cnt <= bit_cnt + 1'b1;
//tx:输出数据在满足rs232协议(起始位为0,停止位为1)的情况下一位一位输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
tx <= 1'b1; //空闲状态时为高电平
else if(bit_flag == 1'b1)
case(bit_cnt)
0 : tx <= 1'b0;
1 : tx <= pi_data[0];
2 : tx <= pi_data[1];
3 : tx <= pi_data[2];
4 : tx <= pi_data[3];
5 : tx <= pi_data[4];
6 : tx <= pi_data[5];
7 : tx <= pi_data[6];
8 : tx <= pi_data[7];
9 : tx <= 1'b1;
default : tx <= 1'b1;
endcase
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
tx_flag <= 1'b0;
else if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
tx_flag <= 1'b1;
else
tx_flag <= 1'b0;
endmodule
3.3 发送模块uart_rx
时序图:
//rx对于时钟信号是异步信号,经过寄存器同步之后,会引入亚稳态问题,多次打拍解决
`timescale 1ns/1ns
module uart_rx
#(
parameter UART_BPS = 'd9600, //串口波特率
parameter CLK_FREQ = 'd50_000_000 //时钟频率
)
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
input wire rx , //串口接收数据
output reg [7:0] po_data , //串转并后的8bit数据
output reg po_flag //串转并后的数据有效标志信号
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//localparam define
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;
//reg define
reg rx_reg1 ;
reg rx_reg2 ;
reg rx_reg3 ;
reg start_nedge ;
reg work_en ;
reg [12:0] baud_cnt ;
reg bit_flag ;
reg [3:0] bit_cnt ;
reg [7:0] rx_data ;
reg rx_flag ;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//插入两级寄存器进行数据同步,用来消除亚稳态
//rx_reg1:第一级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg1 <= 1'b1;
else
rx_reg1 <= rx;
//rx_reg2:第二级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg2 <= 1'b1;
else
rx_reg2 <= rx_reg1;
//rx_reg3:第三级寄存器和第二级寄存器共同构成下降沿检测
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg3 <= 1'b1;
else
rx_reg3 <= rx_reg2;
//start_nedge:检测到下降沿时start_nedge产生一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
start_nedge <= 1'b0;
else if((~rx_reg2) && (rx_reg3))
start_nedge <= 1'b1;
else
start_nedge <= 1'b0;
//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
work_en <= 1'b0;
else if(start_nedge == 1'b1)
work_en <= 1'b1;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
work_en <= 1'b0;
//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
baud_cnt <= 13'b0;
else if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
baud_cnt <= 13'b0;
else if(work_en == 1'b1)
baud_cnt <= baud_cnt + 1'b1;
//bit_flag:当baud_cnt计数器计数到中间数时采样的数据最稳定,
//此时拉高一个标志信号表示数据可以被取走
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX/2 - 1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
//bit_cnt:有效数据个数计数器,当8个有效数据(不含起始位和停止位)
//都接收完成后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_cnt <= 4'b0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
bit_cnt <= 4'b0;
else if(bit_flag ==1'b1)
bit_cnt <= bit_cnt + 1'b1;
//rx_data:输入数据进行移位
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_data <= 8'b0;
else if((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(bit_flag == 1'b1))
rx_data <= {rx_reg3, rx_data[7:1]};
//rx_flag:输入数据移位完成时rx_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_flag <= 1'b0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
rx_flag <= 1'b1;
else
rx_flag <= 1'b0;
//po_data:输出完整的8位有效数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_data <= 8'b0;
else if(rx_flag == 1'b1)
po_data <= rx_data;
//po_flag:输出数据有效标志(比rx_flag延后一个时钟周期,为了和po_data同步)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_flag <= 1'b0;
else
po_flag <= rx_flag;
endmodule
4. SignalTap仿真图
SignalTap图:
接收图:
发送图:
5. 实现现象
PC机发送hello,板子返回ni,hao