FPGA串口通信

一、概述

        串口收发时序参考

         起始位:拉低数据线,传输一个0作为开始信号

         停止位:拉高数据线,传输一个1作为结束信号

         波特率:1秒钟传输多少位,例如波特率为115200就是一秒钟可以传输115200位数据

         设计中需要注意的点:1、严格按照时序设计,先发送低位再发送高位

         2、系统时钟计数,根据波特率来确认发送一位的时钟数。定义一个常量来保存最大的计数值,每次计数到最大值代表一位发送完成,然后接着发送下一位

parameter BAUD_RATE = 115200;						//波特率
localparam MAX_CNT = 50_000_000/BAUD_RATE;	        //得到发一位需要等待多少个系统时钟

        3、接收数据时,不能在整个位时钟计数周期内一直采集那一位数据,应该在计数中间的位置采集数据,保证数据的稳定性

        4、在接收停止位时,计数到最大计数值的一半时就可以准备下一次的接收了,要是等待全部计数完,有可能会错过起始位的下降沿


二、UART发送

module uart_send(
	input sys_clk,										//系统时钟 50m
	input rst_n,										//按键复位
	input ena_mo,										//模块使能
	input [7:0] tx_data,								//发送的数据
	output reg uart_tx								    //串口tx
);

parameter BAUD_RATE = 115200;						//波特率
localparam MAX_CNT = 50_000_000/BAUD_RATE;	        //得到发一位需要等待多少个系统时钟
localparam waiten=0;								//等待模块使能(等待发送)
localparam start=1;									//发送开始信号
localparam send_data=2;								//发送数据 注意是从低位到高位
localparam stop=3;									//发送停止信号
localparam done=4;									//一帧发送完成

reg [14:0] clk_cnt;									//系统时钟计数器
reg [2:0] state,next_state;						    //当前状态和下一个状态
reg [3:0] w_cnt;									//位计数器
reg cnt_clr;										//计数器清零

//系统时钟计数器计数
always @(posedge sys_clk,negedge rst_n)begin
	if(!rst_n) begin
		clk_cnt <= 15'd0;								
	end
	else begin
		if(cnt_clr)										    //清零信号有效时,系统时钟计数器清零
			clk_cnt <= 15'd0;
		else												//无效时,计数到最大值-1(从0开始的),然后清零
			clk_cnt <= (clk_cnt == MAX_CNT-1) ? 15'd0 : clk_cnt + 1'b1;
	end
end

//下一个状态确认
always @(*)begin
	if(!rst_n)begin
		next_state = waiten;
	end
	else begin
		case(state)
			waiten: next_state = ena_mo ? start : waiten;
			
			start: next_state = (clk_cnt == MAX_CNT-1) ? send_data : start;
			
			//发送到最后一位的时候要记得等待时钟计数器到最大值
			//但是也不能用到8来判断,这样会造成两个时钟沿的延迟,而且导致一个时钟沿的不定状态
			send_data: next_state = (w_cnt==4'd7&&clk_cnt == MAX_CNT-1) ? stop : send_data;
			
			stop: next_state = (clk_cnt == MAX_CNT-1) ? done : stop;
			
			done: next_state = waiten;
		endcase
	end
end

//状态变量赋值
always @(posedge sys_clk,negedge rst_n)begin
	if(!rst_n)begin
		cnt_clr <= 1'b1;
		uart_tx <= 1'b1;
	end
	else begin
		case(state)
			waiten:begin
				cnt_clr <= 1'b1;					//等待时计数器清零 不计数
				uart_tx <= 1'b1;					//tx空闲状态为高电平
			end
			start:begin
				cnt_clr <= 1'b0;					//开始计数
				uart_tx <= 1'b0;					//发送开始信号 tx拉低
			end
			send_data:begin
				uart_tx <= tx_data[w_cnt];		    //发送数据 从低位到高位
			end
			stop:begin
				uart_tx <= 1'b1;					//发送停止信号
			end
			done:begin
				cnt_clr <= 1'b1;					//计数器清零
				uart_tx <= 1'b1;
			end
		endcase
	end
end

//状态流转
always @(posedge sys_clk,negedge rst_n)begin
	if(!rst_n)
		state <= waiten;
	else
		state <= next_state;
end

//位计数器赋值
always @(posedge sys_clk,negedge rst_n)begin
	if(!rst_n)begin
		w_cnt <= 4'd0;
	end
	else begin
		case(state)
			stop: w_cnt <= 4'd0;				    //到停止位的时候位计数器清零
													//在发送数据状态时 计数到最大值时位计数器加1
			send_data: w_cnt <= (clk_cnt == MAX_CNT-1) ? w_cnt + 1'b1 : w_cnt;
			
			default: w_cnt <= w_cnt;
		endcase
	end
end

endmodule

三、UART接收

module uart_recv(
	input sys_clk,										//系统时钟 50m
	input rst_n,										//按键复位
	input ena_mo,										//模块使能
	input uart_rx,										//串口rx
	output reg [7:0]rev_data,						    //串口接收到的数据
	output rev_done
);

parameter BAUD_RATE = 115200;						//波特率
localparam MAX_CNT = 50_000_000/BAUD_RATE;	//发一位的需要等待的系统时钟数
localparam no_data = 0;								//没有数据状态 等待数据
localparam wait_start = 1;							//等待开始信号结束
localparam get_data = 2;							//获取数据
localparam wait_stop = 3;							//等待停止信号结束
localparam done = 4;								//完成状态

reg [14:0] clk_cnt;									//系统时钟计数器
reg [2:0] state,next_state;						    //当前状态和下一个状态
reg [3:0] w_cnt;									//位计数器
reg cnt_clr;										//系统时钟计数器清零信号	
reg uart_rx_r0;										//寄存rx前一个时钟状态

wire start_flag;									//开始的标志信号

assign start_flag = ~uart_rx & uart_rx_r0;	//rx下降沿检测 开始信号
assign rev_done = (state==done);

//rx信号寄存
always @(posedge sys_clk,negedge rst_n)begin
	if(!rst_n)
		uart_rx_r0 <= 1'b1;
	else 
		uart_rx_r0 <= uart_rx;						//rx总是比rx_r0提前一个时钟
end

//系统时钟计数器计数
always @(posedge sys_clk,negedge rst_n)begin
	if(!rst_n) begin
		clk_cnt <= 15'd0;
	end
	else begin
		if(cnt_clr)
			clk_cnt <= 15'd0;
		else
			clk_cnt <= (clk_cnt == MAX_CNT-1) ? 15'd0 : clk_cnt + 1'b1;
	end
end

//下一个状态确认
always @(*)begin
	if(!rst_n)
		next_state <= no_data;
	else begin
		case(state)
			no_data: next_state = (start_flag&&ena_mo) ? wait_start : no_data;
			
			wait_start: next_state = (clk_cnt == MAX_CNT-1) ? get_data : wait_start;
			
			get_data: next_state = (w_cnt==3'd7&&clk_cnt == MAX_CNT-1) ? wait_stop : get_data;
			
			wait_stop: next_state = (clk_cnt == (MAX_CNT-1)/2) ? done : wait_stop;//计数到一半一帧接收完毕,进入done状态产生完成信号
			
			done:next_state = no_data;//进入下一帧等待状态
		endcase
	end	
end

//状态变量赋值
always @(posedge sys_clk,negedge rst_n)begin
	if(!rst_n)begin
		cnt_clr <= 1'b1;
		rev_data <= 8'd0;
	end
	else begin
		case(state)
			no_data:begin
			cnt_clr <= 1'b1;				//计数器清零
			end
			wait_start:begin
				cnt_clr <= 1'b0;			//开始计数
			end
			get_data:begin					//每次计数到中间值时获取数据
				rev_data[w_cnt] <= (clk_cnt == (MAX_CNT-1)/2) ? uart_rx : rev_data[w_cnt];
			end
			wait_stop:begin
				cnt_clr <= 1'b0;
			end
		endcase
	end
end

//状态流转
always @(posedge sys_clk,negedge rst_n)begin
	if(!rst_n)
		state <= no_data;
	else
		state <= next_state;
end

//位计数器计数
always @(posedge sys_clk,negedge rst_n)begin
	if(!rst_n)
		w_cnt <= 4'd0;
	else begin
		case(state)
			no_data: w_cnt <= 4'd0;
			get_data: w_cnt <= (clk_cnt == MAX_CNT-1) ? w_cnt + 1'b1 : w_cnt;
			default: w_cnt <= w_cnt;
		endcase
	end
end


endmodule

四、实现效果

        发送的数据(电脑端接收)

        八位拨码开关输入数据,按键发送(没有消抖,所以一次性就发了很多)

         接收数据,使用数码管来显示,所以我发的是一些数字来验证接收模块 


五、数码管动态扫描

        时钟频率不能太高,太高可能会出现模糊的现象,当然也不能太低,这样视觉上会有停顿感,我选择1KHz的时钟。

module seg_led_show(
	input clk_1k,							//时钟信号
	input rst_n,							//按键复位
	input [23:0] data,					    //显示的数据 4位一个
	output reg [5:0] seg_sel,			    //数码管位选 低电平有效
	output reg [7:0] seg_led			    //数码管段选 低电平有效
);

localparam S0=0;							//位选状态0
localparam S1=1;							//位选状态1
localparam S2=2;							//位选状态2
localparam S3=3;							//位选状态3
localparam S4=4;							//位选状态4
localparam S5=5;							//位选状态5

reg [2:0] state,next_state;			        //当前状态和下一个状态
reg [3:0]num;								//当前显示的数字

//下一个状态确认
always @(*)begin
	if(!rst_n)begin
		next_state = S0;
	end
	else begin
		case(state)
			S0:next_state = S1;			//循环扫描
			S1:next_state = S2;
			S2:next_state = S3;
			S3:next_state = S4;
			S4:next_state = S5;
			S5:next_state = S0;
		endcase
	end
	
end

//状态变量赋值
always @(posedge clk_1k,negedge rst_n)begin
	if(!rst_n)begin
		seg_sel <= 6'd0;
		num <= 4'd0;
	end
	else begin
		case(state)
			S0:begin
				seg_sel <= 6'b111110;		//选中第0个数码管
				num <= data[3:0];			//要显示的数据赋值
			end
			S1:begin
				seg_sel <= 6'b111101;		//选中第1个数码管
				num <= data[7:4];
			end
			S2:begin
				seg_sel <= 6'b111011;		//选中第2个数码管
				num <= data[11:8];
			end
			S3:begin
				seg_sel <= 6'b110111;		//选中第3个数码管
				num <= data[15:12];
			end
			S4:begin
				seg_sel <= 6'b101111;		//选中第4个数码管
				num <= data[19:16];
			end
			S5:begin
				seg_sel <= 6'b011111;		//选中第5个数码管
				num <= data[23:20];
			end
		endcase
	end
end

//状态流转
always @(posedge clk_1k,negedge rst_n)begin
	if(!rst_n)
		state <= S0;
	else
		state <= next_state;
end

//根据num(要显示的数字)值来输出段选信号
always @(*)begin
	if(!rst_n)
		seg_led = 8'd0;
	else begin
		case (num)
			4'h0 : seg_led = 8'b1100_0000;
			4'h1 : seg_led = 8'b1111_1001;
			4'h2 : seg_led = 8'b1010_0100;
			4'h3 : seg_led = 8'b1011_0000;
			4'h4 : seg_led = 8'b1001_1001;
			4'h5 : seg_led = 8'b1001_0010;
			4'h6 : seg_led = 8'b1000_0010;
			4'h7 : seg_led = 8'b1111_1000;
			4'h8 : seg_led = 8'b1000_0000;
			4'h9 : seg_led = 8'b1001_0000;
			4'ha : seg_led = 8'b1000_1000;
			4'hb : seg_led = 8'b1000_0011;
			4'hc : seg_led = 8'b1100_0110;
			4'hd : seg_led = 8'b1010_0001;
			4'he : seg_led = 8'b1000_0110;
			4'hf : seg_led = 8'b1000_1110;
			default : seg_led = 8'b1100_0000;
		endcase
	end
end

endmodule

六、顶层模块

        这部分和之前spi是混在一起的,所以有点乱。顶层模块就是例化一下其他模块,比较简单,这部分可以选择性跳过。

module oscilloscope(
	input sys_clk,				//系统时钟 50m
	input rst_n,				//按键复位
	input uart_rx,				//串口rx信号
	input uart_send_key,		//串口发送按键
	input [7:0]uart_send_data,//串口发送数据
	input spi_miso,			//spi 主进从出信号
	output spi_mosi,			//spi 主出从进信号
	output spi_sck,			//spi 时钟信号
	output spi_nss,			//spi 片选信号(使用这个信号可以提高传输的稳定性)
	output uart_tx,
	output [5:0] seg_sel,
	output [7:0] seg_led
);

wire [7:0] spi_rdata;		//spi读取到的数据
wire [7:0] spi_tdata;		//spi要发送的数据
wire tr_done;					//一个字节发送完成信号
wire clk_1m;					//1M 时钟信号
wire clk_1k;					//1K 时钟信号
wire uart_rev_done;			//串口接收完成
wire [23:0]show_data;		//要显示的数据
wire [7:0] uart_rdata;		//串口收到的数据
wire [3:0] ydata;				//将接收到的串口数据处理一下
wire uart_send_flag;			//串口发送使能标志信号


reg [7:0] tx_data;			//给要发送的数据赋值
reg [23:0]show_data_r;		//用来给显示的数据赋值 wire类型主要用来连接模块
reg uart_send_key_r0;		//按键前一个时钟的信号

assign spi_tdata = tx_data;//要发送的数据赋值
assign show_data = show_data_r;
assign ydata = uart_rdata-8'h30;//将串口接收到的数据转换为数字
assign uart_send_flag = ~uart_send_key&uart_send_key_r0;//下降沿检测

always @(posedge clk_1m,negedge rst_n)begin
	if(!rst_n)
		uart_send_key_r0 <= 1'b0;
	else
		uart_send_key_r0 <= uart_send_key;
end

always @(posedge clk_1m,negedge rst_n)begin
	if(!rst_n)
		tx_data <= 8'd0;
	else begin
		tx_data <= tr_done ? tx_data + 1'b1 : tx_data;//每发送完成一个字节 数据的值增加1
	end
end

always @(posedge sys_clk,negedge rst_n)begin
	if(!rst_n)
		show_data_r <= 24'd0;
	else
		//通过移位操作把串口接收到的数据移到显示的数据
		show_data_r <= uart_rev_done ? {show_data_r[19:0],ydata} : show_data_r;
end

//时钟分频模块 产生1MHz时钟
clk_fenpin u_clk_fenpin(
	.sys_clk(sys_clk),
	.rst_n(rst_n),
	.clk_1m(clk_1m),
	.clk_1k(clk_1k)
);

//spi 主模式模块
spi_master u_spi_master(
	.clk_1m(clk_1m),
	.rst_n(rst_n),
	.ena_mo(1'b1),
	.spi_tdata(tx_data),
	.spi_miso(spi_miso),
	.spi_mosi(spi_mosi),
	.spi_sck(spi_sck),
	.spi_nss(spi_nss),
	.spi_rdata(spi_rdata),
	.tr_done(tr_done)
);

//uart发送模块
uart_send u_uart_send(
	.sys_clk(sys_clk),
	.rst_n(rst_n),
	.ena_mo(uart_send_flag),
	.tx_data(uart_send_data),
	.uart_tx(uart_tx)
);

//uart接收模块
uart_recv u_uart_recv(
	.sys_clk(sys_clk),
	.rst_n(rst_n),
	.ena_mo(1'b1),
	.rev_data(uart_rdata),
	.uart_rx(uart_rx),
	.rev_done(uart_rev_done)
);

//数码管显示模块
seg_led_show u_seg_led_show(
	.clk_1k(clk_1k),
	.rst_n(rst_n),
	.data(show_data),
	.seg_sel(seg_sel),
	.seg_led(seg_led)
);

endmodule

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值