Verilog学习笔记-Uart

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 第一和第二个 分别亮了;利用这个串口后面可以控制译码管的显示,或者是译码管的暂停以及当前的数值,总之后面可以根据需求增加各种功能。

代码包:

 https://github.com/FerroelectricPhysics/uart_demo

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值