本代码参考了野火的相关教程,实现了发送和接收回环,同时可以通过串口数据控制LED灯的亮灭,在电脑发送数据时要选择HEX发送模式,发送16进制的数据进行控制。
UART协议中,在空闲时为高电平。在常用的一位停止位和无校验位的设置中,起始位为低电平,紧接着是8位的数据位,最后是一位高电平的停止位。
接收模块的主要设计是将起始位下降沿的检测作为接收系统的开始信号,通过起始信号和bit位的计数信号相结合就可以得到一个贯穿整体运行过程的使能信号,通过对使能信号的判断就可以实现对系统的控制。
另一个关键问题是电平采集的时间,应该 在一个bit的中间位置进行采集,这样可以有效避免采集电平不稳定的问题。
接收模块的代码如下:
module uart_receive (
input clk,
input rst_n,
input uart_rx,
output reg receive_done,
output reg [7:0] uart_data
);
parameter SYSTERM_CLK = 50_000_000; //系统时钟频率
parameter UART_BPS = 115200; //串口波特率
localparam BPS_COUNT_MAX = SYSTERM_CLK/UART_BPS; //为得到指定波特率
//需要对系统时钟计数BPS_COUNT次
reg [7:0] reg_data;//接受数据缓存
reg [3:0] bit_count;//接收数据时用于计数接收到了多少位
reg [12:0] bps_count;//用于按照时钟计算一个字节的时间
reg start_bit;//检测到起始位的下降沿之后触发一个时钟的高电平
reg reg1 ;
reg reg2 ;
reg reg3 ;
reg bit_flag ;//在一个电平的中间位置产生高电平标志
reg work_en ;//在本标志位高电平时,接受工作开始,在低电平时工作结束
reg rx_flag ;//在数据缓存器存满了之后产生一个高电平
//插入两级寄存器进行数据同步,用来消除亚稳态
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
reg1 <= 1'b1;//因为UART的空闲状态是高电平,所以复位时电平要设置为高电平
end
else begin
reg1 <= uart_rx;
end
end
//插入两级寄存器进行数据同步,用来消除亚稳态
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
reg2 <= 1'b1;//因为UART的空闲状态是高电平,所以复位时电平要设置为高电平
end
else begin
reg2 <=reg1;
end
end
//插入两级寄存器进行数据同步,用来消除亚稳态
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
reg3 <= 1'b1;//因为UART的空闲状态是高电平,所以复位时电平要设置为高电平
end
else begin
reg3 <=reg2;
end
end
//start_bit用来检测起始位的下降沿
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
start_bit <= 1'b0;
end
else if ((reg3)&&(!reg2)) begin
start_bit <= 1'b1;
end
else
start_bit <= 1'b0;
end
//work_en,在本标志位高电平时,接受工作开始,在低电平时工作结束
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
work_en <= 1'b0;
end
else if (start_bit) begin
work_en <= 1'b1;
end
else if ((bit_count == 4'd8) && (bit_flag == 1'b1)) begin
work_en <= 1'b0;
end
else
work_en <= work_en;
end
//bps_count用来计数计算波特率
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
bps_count <= 13'b0;
end
else if ((bps_count == BPS_COUNT_MAX -1) || (work_en == 0)) begin
bps_count <= 13'b0;//只有在工作使能的状态下才会计数
end
else if (work_en == 1) begin
bps_count <= bps_count + 1'b1;
end
else
bps_count <= bps_count;
end
//bit_flag,在一个字节的中点输出一个时钟的高电平
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
bit_flag <= 1'b0;
end
else if (bps_count == BPS_COUNT_MAX/2-1) begin
bit_flag <= 1'b1;
end
else
bit_flag <= 1'b0;
end
//bit_count,用于计数当前接收到了第几bit
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
bit_count <= 4'b0;
end
else if ((bit_count == 4'd8) && (bit_flag == 1'b1)) begin
bit_count <= 4'b0;
end
else if (bit_flag == 1'b1) begin
bit_count <= bit_count + 1'b1;
end
else
bit_count <= bit_count;
end
//reg_data表示接收数据的缓存
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
reg_data <= 8'b0;
end
else if ((bit_flag == 1) && (work_en == 1) && (bit_count <= 4'd8) && (bit_count >= 4'd1)) begin
reg_data <= {reg3,reg_data[7:1]};
end
else
reg_data <= reg_data;
end
//rx_flag在接收缓存满了之后输出一个时钟的高电平
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_flag <= 1'b0;
end
else if ((bit_count == 4'd8) && (bit_flag == 1'b1)) begin
rx_flag <= 1'b1;
end
else
rx_flag <= 1'b0;
end
//uart_data将数据缓存器中的数据导出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
uart_data <= 8'b0;
end
else if (rx_flag == 1'b1) begin
uart_data <= reg_data;
end
else
uart_data <= uart_data;
end
//receive_done导出接收完成的信号
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
receive_done <= 1'b0;
end
else if (rx_flag == 1'b1) begin
receive_done <= 1'b1;
end
else
receive_done <= 1'b0;
end
endmodule //uart_receive
UART的发送模块的实现过程比接收过程稍简单,因为发送模块无需考虑采集电平的位置,只需要在接收到发送使能之后,通过计算每一个bit的时间,按照顺序将数据发送出去即可。
代码如下:
module uart_send (
input wire clk,
input wire rst_n,
input wire [7:0] uart_in_data,
input wire uart_in_flag,
output reg uart_tx
);
parameter SYSTERM_CLK = 50_000_000; //系统时钟频率
parameter UART_BPS = 115200; //串口波特率
localparam BPS_COUNT_MAX = SYSTERM_CLK/UART_BPS; //为得到指定波特率
//需要对系统时钟计数BPS_COUNT次
reg [12:0] bps_count;//用于按照时钟计算一个字节的时间
reg [3:0] bit_count;//接收数据时用于计数接收到了多少位
reg bit_flag ;//在一个电平的中间位置产生高电平标志
reg work_en ;//在本标志位高电平时,接受工作开始,在低电平时工作结束
//bps_count波特率计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
bps_count <= 13'b0;
end
else if ((bps_count ==BPS_COUNT_MAX - 1'b1) || (work_en == 1'b0)) begin
bps_count <= 13'b0;
end
else if (work_en == 1'b1) begin
bps_count <= bps_count + 1'b1;
end
else
bps_count <= bps_count;
end
//work_en发送工作使能
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
work_en <= 1'b0;
end
else if ((bit_count == 4'd9) && (bit_flag == 1'b1)) begin
work_en <= 1'b0;
end
else if (uart_in_flag == 1'b1) begin
work_en <= 1'b1;
end
else
work_en <= work_en;
end
//bit_flag每一个bit的信号
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
bit_flag <= 1'b0;
end
else if (bps_count == 13'b1) begin
bit_flag <= 1'b1;
end
else
bit_flag <= 1'b0;
end
//bit_count字节计数
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
bit_count <= 4'b0;
end
else if ((bit_flag == 1'b1) && (work_en == 1'b1)) begin
bit_count <= bit_count + 1'b1;
end
else if ((bit_count == 4'd9) && (bit_flag == 1'b1)) begin
bit_count <= 4'b0;
end
else
bit_count <= bit_count;
end
//uart_tx
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
uart_tx <= 1'b1;//串口传输,空闲时为高电平
end
else if (bit_flag == 1'b1) begin
case (bit_count)
0: uart_tx <= 1'b0 ;
1: uart_tx <= uart_in_data[0];
2: uart_tx <= uart_in_data[1];
3: uart_tx <= uart_in_data[2];
4: uart_tx <= uart_in_data[3];
5: uart_tx <= uart_in_data[4];
6: uart_tx <= uart_in_data[5];
7: uart_tx <= uart_in_data[6];
8: uart_tx <= uart_in_data[7];
9: uart_tx <= 1'b1 ;
default uart_tx <= 1'b1 ;
endcase
end
end
endmodule //uart_send
顶层只需将发送和接收模块例化即可,同时加入了LED管脚,可以在发送和接收回环的过程中实现对4个LED灯的控制。
代码如下:
module uart_top (
input wire clk,
input wire rst_n,
input wire uart_rx,
output wire uart_tx,
output wire [3:0] led
);
parameter SYSTERM_CLK = 26'd50_000_000; //系统时钟频率
parameter UART_BPS = 17'd115200; //串口波特率
wire flag;
wire [7:0] data;
assign led = data[3:0];
uart_receive
#(
.SYSTERM_CLK (SYSTERM_CLK ),
.UART_BPS (UART_BPS )
)
u_uart_receive(
.clk (clk ),
.rst_n (rst_n ),
.uart_rx (uart_rx ),
.receive_done (flag ),
.uart_data (data )
);
uart_send
#(
.SYSTERM_CLK (SYSTERM_CLK ),
.UART_BPS (UART_BPS )
)
u_uart_send(
.clk (clk ),
.rst_n (rst_n ),
.uart_in_data (data ),
.uart_in_flag (flag ),
.uart_tx (uart_tx )
);
endmodule //uart_top