uart介绍:
PC与微机或者单片机的通信都是通过串口来进行的,常见串口通信协议有USB、I2C、RS232、485等;一般工业上的设备都基本是以RS232、485为主,主要有四根线:VCC、GND、RXD、TXD;本次从软件层面出发,无需考虑硬件层面的,如RS232,是负电平信号,-15V~-3V是‘1’,3V~15V是'0';RS485,是差分信号,+2~6V是‘1’,-2~-6V是‘0’;以数据传输切入,数据传输以8位数据位传输,如图:
数据输出开始,RX端电平拉低两个时钟计算周期,接收结束后,RX端拉高电平,并保持两个时钟周期以上。
本次软件逻辑为时钟计数,即是通过计算在不同的接收端状态所需的时间来实现,高级点可以通过状态机来实现。
代码实现
config.v:
`define CLK_FRE 50_000_000 //输入的时钟频率
`define BAUD_RATE 115200 //波特率
uart.v代码:
//uart top
module uart(
input sys_clk, //外部50M时钟
input sys_rst_n, //外部复位信号,低有效
input uart_rxd, //UART接收端口
output uart_txd, //UART发送端口
output [3:0]led
);
//wire define
wire uart_rx_done; //UART接收完成
wire [7:0] rx_data; //UART接收数据
wire send_en; //UART发送使能
wire [7:0] tx_data; //UART发送数据
wire uart_tx_busy; //UART发送忙状态标志
//*****************************************************
//** main code
//*****************************************************
//串口接收模块
uart_recv u_uart_recv(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.rx (uart_rxd), //串口接收端
.rx_finish (uart_rx_done),
.uart_data (rx_data) //并行串口数据输出
);
//串口发送模块
uart_send u_uart_send(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_en (send_en),
.uart_register (tx_data),
.uart_tx_busy (uart_tx_busy),
.tx (uart_txd)
);
//串口环回模块
uart_loop u_uart_loop(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.recv_done (uart_rx_done),
.recv_data (rx_data), //串口发送装载
.tx_busy (uart_tx_busy),
.send_en (send_en), //rx接收并使能tx发送
.send_data (tx_data)
);
//串口功能添加
led u_led(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.rx_data (rx_data),
.led (led)
);
endmodule
uart_loop:
此代码是为了让发送的数据在PC端接收到并显示出来,如果无需此功能可以删掉,内置tx_en=1‘b1,即可。
module uart_loop(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位,低电平有效
input recv_done, //接收一帧数据完成标志
input [7:0] recv_data, //接收的数据
input tx_busy, //发送忙状态标志
output reg send_en, //发送使能信号
output reg [7:0] send_data //待发送数据
);
//reg define
reg recv_done_d0;
reg recv_done_d1;
reg tx_ready;
//wire define
wire recv_done_flag;
//*****************************************************
//** main code
//*****************************************************
//捕获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
endmodule
uart_recv:
通过rx_register <= {uart_rxd_d1,rx_register[7:1]};将接收到的数据从低位到高位装载并且并行输出;
`include "config.v"
module uart_recv(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位,低电平有效
input rx, //UART接收端口
output reg rx_finish, //接收一帧数据完成标志
output reg [7:0] uart_data //接收的数据
);
//parameter define
localparam BPS_CNT = `CLK_FRE/`BAUD_RATE; //为得到指定波特率,
//需要对系统时钟计数BPS_CNT次
//reg define
reg uart_rxd_d0;
reg uart_rxd_d1;
reg [15:0] clk_cnt; //系统时钟计数器
//wire define
wire start_flag;
reg [3:0] rx_cnt;
reg [7:0] rx_register;
reg rx_flag;
//*****************************************************
//** main code
//*****************************************************
//捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
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 <= rx;
uart_rxd_d1 <= uart_rxd_d0;
end
end
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)
rx_register <= 8'd0;
else if(rx_flag) //系统处于接收过程
if((clk_cnt == BPS_CNT /2) &&((rx_cnt>= 'd1 ) && (rx_cnt <= 'd8)))
rx_register <= {uart_rxd_d1,rx_register[7:1]};
// if (clk_cnt == BPS_CNT/2) begin //判断系统时钟计数器计数到数据位中间
// case ( rx_cnt )
// 4'd1 : rx_register[0] <= uart_rxd_d1; //寄存数据位最低位
// 4'd2 : rx_register[1] <= uart_rxd_d1;
// 4'd3 : rx_register[2] <= uart_rxd_d1;
// 4'd4 : rx_register[3] <= uart_rxd_d1;
// 4'd5 : rx_register[4] <= uart_rxd_d1;
// 4'd6 : rx_register[5] <= uart_rxd_d1;
// 4'd7 : rx_register[6] <= uart_rxd_d1;
// 4'd8 : rx_register[7] <= uart_rxd_d1; //寄存数据位最高位
// default:;
// endcase
// end
else
rx_register <= rx_register;
else
rx_register <= 8'd0;
end
//数据接收完毕后给出标志信号并寄存输出接收到的数据
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_data <= 8'd0;
rx_finish <= 1'b0;
end
else if(rx_cnt == 4'd9) begin //接收数据计数器计数到停止位时
uart_data <= rx_register; //寄存输出接收到的数据
rx_finish <= 1'b1; //并将接收完成标志位拉高
end
else begin
uart_data <= 8'd0;
rx_finish <= 1'b0;
end
end
endmodule
uart_send:
通过case语句来将并行的数据转化成线输出;
`include "config.v"
module uart_send(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位,低电平有效
input uart_en, //发送使能信号
input [ 7:0] uart_register, //待发送数据
output uart_tx_busy, //发送忙状态标志
output reg tx //UART发送端口
);
//parameter define
localparam BPS_CNT = `CLK_FRE/`BAUD_RATE; //为得到指定波特率,
//reg define
reg uart_en_d0;
reg uart_en_d1;
reg [15:0] clk_cnt; //系统时钟计数器
reg [ 7:0] tx_data; //寄存发送数据
reg [ 3:0] tx_cnt; //发送数据计数器
reg [ 7:0] tx_datal;
reg tx_flag; //发送过程标志信号
wire en_flag;
//*****************************************************
//** main code
//*****************************************************
//在串口发送过程中给出忙状态标志
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_register; //寄存待发送的数据
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)
tx <= 1'b1;
else if (tx_flag)
case(tx_cnt)
4'd0: tx <= 1'b0; //起始位
4'd1: tx <= tx_data[0]; //数据位最低位
4'd2: tx <= tx_data[1];
4'd3: tx <= tx_data[2];
4'd4: tx <= tx_data[3];
4'd5: tx <= tx_data[4];
4'd6: tx <= tx_data[5];
4'd7: tx <= tx_data[6];
4'd8: tx <= tx_data[7]; //数据位最高位
4'd9: tx <= 1'b1; //停止位
default: ;
endcase
else
tx <= 1'b1; //空闲时发送端口为高电平
end
endmodule
led:
代码,此为功能代码,输入指定的16位进制,来实现对单片机进行控制,如不需要可以删除。
module led(
input sys_clk,
input sys_rst_n,
input [7:0] rx_data,
output reg[3:0] led
);
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
led<=4'b0;
else
begin
case(rx_data)
8'h01: led <= 4'b0001;
8'h02: led <= 4'b0010;
8'h03: led <= 4'b0100;
8'h04: led <= 4'b1000;
default: ;
endcase
end
end
endmodule
FPGA针脚管理图:
案例实现:
发送01,02 对应的led 第一和第二个 分别亮了;利用这个串口后面可以控制译码管的显示,或者是译码管的暂停以及当前的数值,总之后面可以根据需求增加各种功能。