串口Uart code

一、串口接收

1、Uart接收模块Verilog Code

//
// Module Name:    UART_RX_CORE 模块 BPS = 115200
//
module UART_RX_CORE(
		input	clk,		   	//主时钟
		input	rst_n,		    //复位信号,低电平时有效,系统不工作
		input	rx_pin_in,	    //读输入信号
		
		input	rx_en,		    //读(串口接收)模块使能信号,为1时,读模块工作,外部控制模块提供
							
		output 	reg	rx_done,	//帧数据结束信号,为1时表明一帧数据接收完成
		output 	reg[7:0]rx_data //信号寄存器,记录8位输入信号,先发送的为低位
	   );
	wire	rx_control_en;
	wire	bps_clk;
//
//UART_H2L_FIG 模块 UART_RX下降沿检测
//
reg H2L_F1;
reg H2L_F2;
always @ (posedge clk or negedge rst_n)begin 	//复位信号有效时输出为低
	if(!rst_n)begin
		H2L_F1 <= 1'b1;
		H2L_F2 <= 1'b1;
	end
	else begin	//检测下降沿
		H2L_F1 <= rx_pin_in;
		H2L_F2 <= H2L_F1;
	end
end	
assign rx_control_en = ~H2L_F1 & H2L_F2;	//下降沿触发,检测到起始位,开始进行信号读取
//
// UART_RX_BPS 模块 UART_RX波特率产生模块	434 = 50M / 115200
//
reg[8:0] cnt;
always @ (posedge clk or negedge rst_n)begin
if(!rst_n)
	cnt <= 9'd0;
else if((reg_rx_bps_en) && (cnt != 9'd434))begin//加cnt != 434这条语句意思是如果加到了434,cnt就变为0
	cnt <= cnt + 1'b1;
end
else
	cnt <= 9'd0;
end
assign bps_clk = (cnt == 9'd217) ? 1'b1 : 1'b0;//cnt=217时uart通信,数据最为稳定(因为在中间)
//
// UART_RX_S2P 模块 串行转并行模块
//
reg [3:0]i;
reg reg_rx_bps_en;
always @ ( posedge clk or negedge rst_n )
if(!rst_n)
	begin
		i <= 4'd0;
		reg_rx_bps_en <= 1'b0;
		rx_data <= 8'd0;
		rx_done <= 1'b0;
	end
else if(rx_en)//外部启动读模块,读模块才开始工作触发
	case(i)
		4'd0:
			if(rx_control_en) begin //当UART接收引脚接收到下降沿,UART接收协议使能开启
			i <= i + 1'b1;
			reg_rx_bps_en <= 1'b1;	//下个时钟启动波特率发生模块
			end
		4'd1:
			if(bps_clk) begin 
			i <= i + 1'b1;				//起始位固定为0,无需记录
			end
		4'd2, 4'd3, 4'd4, 4'd5, 4'd6, 4'd7, 4'd8, 4'd9 :
			if(bps_clk) begin 
			i <= i + 1'b1;
			rx_data[i - 2] <= rx_pin_in;	//将串口发送的8位值存储在寄存器中
			end
		4'd10:
			if(bps_clk) begin 		//数据位最后一接收位
			i <= i + 1'b1;
			end
		4'd11:	
			begin 
			i <= i + 1'b1;				//奇偶校验位,我们没有就不用了
			rx_done <= 1'b1;
			reg_rx_bps_en <= 1'b0;	//产生done信号,表明一帧数据接收完毕
			end
		4'd12:
			begin 
			i <= 4'd0;					//停止位
			rx_done <= 1'b0;
			end
	endcase
endmodule

2、互锁信号

在uart接收模块中,模块使能的标志是rx_en信号,模块一次通信完成的标志位是rx_done信号。rx_done信号是输出到串口通信的控制模块里的,而rx_en是串口通信控制模块输出到串口接收模块的。二者的关系是互锁(借助PLC里的说法)关系,即当rx_done为1时,rx_en <= 1’b0;而当rx_done为0时,rx_en <= 1’b1。

下面给出串口接收模块的控制命令代码。本代码中,串口接收模块只让其接收固定的一串字节命令,仔细体会代码意义。

//
// Module Name:    UART_RX_ORDER 模块 
// FF 01 00 0x					//采样模式,0为正常采样,1为测试棋盘格采样	,,,,串口相关软件发送的顺序从左往右:FF_10_无所谓啥值_X0		

// FF 02 0x 0x					//校准模式(此模式是用作16通道正常工作或单通道校准的选择),高位[0]进行校准判断,低位[3:0]校准通道

// FF 03 XX XX YY YY			//写EEPROM地址和数据
//
//
//
module UART_RX_ORDER(
							input	clk,				//主时钟
							input	rst_n,				//复位信号,低电平时有效,系统不工作
							
							input rx_done,				//帧数据结束信号,为1时表明一帧数据接收完成
							input [7:0]rx_data,			//信号寄存器,记录8位输入信号,先发送的为低位
							output reg rx_en,			//读模块使能信号,为1时,读模块工作
							
							output reg eeprom_w_en,		//EEPROM写使能
							output reg [15:0]eeprom_w_addr,	//EEPROM写地址
							output reg [15:0]eeprom_w_data,	//EEPROM写数据
							
							output reg sample_mod,		//采样模式,0为正常采样,1为测试棋盘格采样							
							output reg cal_mod,			//校准模式 0为正常,1为校准
							output reg [3:0]cal_ch		//校准通道
						 );
						 
reg [5:0]i;	 
reg [7:0]rx_addr;
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		i <= 6'd0;
		rx_en <= 1'b0;
		rx_addr <= 8'd0;
		sample_mod <= 1'b0;
		cal_mod <= 1'b0;
		cal_ch <= 4'd0;
		eeprom_w_en <= 1'b0;
		eeprom_w_addr <= 16'd0;
		eeprom_w_data <= 16'd0;
	end
	else begin
	case(i)						 
	6'd0:
	if(rx_done)begin						
		rx_en <= 1'b0;
		i <= i + 1'b1;
	end
	else begin
		rx_en <= 1'b1;
		i <= 6'd0;
	end
	6'd1:
	if(rx_data == 8'hFF)begin
		i <= i + 1'b1;
	end
	else begin
		i <= 6'd0;
	end
	6'd2:
	if(rx_done)begin						
		rx_en <= 1'b0;
		i <= i + 1'b1;
	end
	else begin
		rx_en <= 1'b1;
		i <= i;
	end
	6'd3:
	begin
		rx_addr <= rx_data;
		i <= i + 1'b1;
	end
	6'd4:
	if(rx_done)begin						
		rx_en <= 1'b0;
		i <= i + 1'b1;
	end
	else begin
		rx_en <= 1'b1;
		i <= i;
	end
	6'd5:
	if(rx_addr == 8'h01)begin串口指令第二个字节决定着工作模式
		i <= i + 1'b1;
	end
	else if(rx_addr == 8'h02)begin			进入校准模式选择
		cal_mod <= rx_data[0];				以整个指令的第四个字节的最低位来进行是否进入校准来进行判断:0为正常,1为校准
		i <= i + 1'b1;						
	end
	//写EEPROM
	else if(rx_addr == 8'h03)begin
		eeprom_w_addr[15:8] <= rx_data;
		i <= 6'd9;
	end
	else begin
		i <= 6'd0;
	end
	6'd6:
	if(rx_done)begin						
		rx_en <= 1'b0;
		i <= i + 1'b1;
	end
	else begin
		rx_en <= 1'b1;
		i <= i;
	end
	6'd7:
	if(rx_addr == 8'h01)begin					
		sample_mod <= rx_data[0];			我:这是用串口调试助手可以发送;0:代表ADC配置为正常采样;1:代表是ADC输出为棋盘格模式。
		i <= 6'd0;							在RTL视图中,sample_mod未与其他信号连接,此信号只是在SPI配置时用来调试用的而已吧。									 
	end
	else if(rx_addr == 8'h02)begin			//我:校准模式,
		cal_ch[3:0] <= rx_data[3:0];		//校准通道号
		i <= 6'd0;
	end
//	else if(rx_addr == 8'h03)begin
//		sample_threshold[7:0] <= rx_data;
//		i <= 6'd0;
//	end
	else begin										
		i <= 6'd0;
	end
//	6'd8:
//	if(rx_done)begin						
//		rx_en <= 1'b0;
//		eeprom_w_addr[15:8] <= rx_data;
//		i <= i + 1'b1;
//	end
//	else begin
//		rx_en <= 1'b1;
//		i <= i;
//	end
	6'd9:
	if(rx_done)begin						
		rx_en <= 1'b0;
		eeprom_w_addr[7:0] <= rx_data;
		i <= i + 1'b1;
	end
	else begin
		rx_en <= 1'b1;
		i <= i;
	end
	6'd10:
	if(rx_done)begin						
		rx_en <= 1'b0;
		eeprom_w_data[15:8] <= rx_data;
		i <= i + 1'b1;
	end
	else begin
		rx_en <= 1'b1;
		i <= i;
	end
	6'd11:
	if(rx_done)begin						
		rx_en <= 1'b0;
		eeprom_w_data[7:0] <= rx_data;
		i <= i + 1'b1;
	end
	else begin
		rx_en <= 1'b1;
		i <= i;
	end
	6'd12:
	begin
	eeprom_w_en <= 1'b1;
	i <= i + 1'b1;
	end
	6'd13:
	begin
	eeprom_w_en <= 1'b0;
	i <= 6'd0;
	end
	default:begin
		i <= 6'd0;
		rx_en <= 1'b0;
		rx_addr <= 8'd0;
		sample_mod <= 1'b0;
		cal_mod <= 1'b0;
		cal_ch <= 4'd0;
	end
	endcase
end
end
endmodule						 

下图是Uart串口接收模块及控制模块的RTL视图。

在这里插入图片描述

3、握手协议

上面所说的一组互锁信号是在同一时钟域中。不禁要问,如果在不同时钟域的话,模块之间信号如何传递。所以引出握手协议。握手协议定义异步模块在通信时的接口信号时序,保证异步电路各个组件之间数据流动并且不发生冲突的一种机制。主要包括两类:四段握手协议(Four-Phase)和两段握手协议(Two-Phase)。
四段握手协议是基于电平的,只有高电平表示控制信号的请求和应答,因此控制信号有归零的动作。四段握手协议因此也被称为归零(Return-to-Zero,RTZ)握手协议,归零信号也被称为“电平信号”。这里的“四段”(Four-Phase)是指通信动作的次数:

1	发送端准备好数据后会将请求信号置高;
2	接收端接收数据后将应答信号置高;
3	发送端将请求信号置低作为响应(此时数据可以不再保持有效);
4	接收端通过将应答信号置低来做出应答。

此时,发送端就可以开始下一个通信周期。四段握手协议的缺点是多余的归零翻转造成了不必要的时钟消耗。

两段握手协议是基于事件的,控制信号的请求和应答通过上升沿或者下降沿来表示。两段握手协议中,请求和应答信号使用信号线上的电平翻转沿来进行编码。在这种编码中的。0→1和1→0翻转是没有区别的,它们都代表一次信号事件。理想情况下,两段握手协议应该比四段握手协议电路速度更快,但是由于不同问题所对应的电路往往复杂多变,因此不能简单地说哪种协议是最好的。

我们选择脉冲检测方法来进行时钟域的同步。在跨时钟传输时,只需要对双方的握手信号(req和ack)分别使用脉冲检测方法进行同步。在具体实现中,假设req、ack、data总线在初始化时都处于无效状态,发送域先把数据放入总线,随后发送有效的请求信号req给接收域;接受域在监测到有效的req信号后锁存数据总线,然后回送一个有效的ack信号表示读取完成应答;发送域在检测到有效ack信号后,撤销当前的req信号,接受域在检测到req撤销后也相应撤销ack信号,此时完成一次正常的握手通信。此后,发送域可以继续开始下一个的握手通信,如此循环。

下面是接受域的简单工程代码。大家细细体会。

module HAND_REC(
	input	clk,
	input	rst_n,
	input	req,
	input	[7:0]	datain,
	output	ack,
	output	[7:0]	dataout
	);
//
//上升沿沿检测
//
reg	reqr1,reqr2,reqr3;

always @ ( posedge clk or negedge rst_n )	begin
		if( !rst_n )begin
			reqr1 <= 1'b1;
			reqr2 <= 1'b1;
			reqr3 <= 1'b1;
		end
		else begin
			reqr1 <= req;
			reqr2 <= reqr1;
			reqr3 <= reqr2;
		end
	end
	//pos_req2比pos_req1延后一个时钟,确保数据被稳定锁存,
	//req一个上升沿通过D触发器组合可以引出俩个上升沿,reqr2是req1的前一个状态,req1是req的的前一个状态。
	wire pos_req1 = reqr1 & ~reqr2;//req上升沿标志位,高有效一个时钟周期(接收域)
	wire pos_req2 = reqr2 & ~reqr3;//req上升沿标志位,高有效一个时钟周期(接收域)


//
//数据锁存
//
reg	[7:0]	dataoutr;
always @ ( posedge clk or negedge rst_n )begin
		if( !rst_n )begin
			dataoutr <= 8'd0;
		end
		else if( pos_req1 )begin
			dataoutr <= datain;
		end
		else begin
			dataoutr <= dataoutr;
		end
end
assign	dataout = dataoutr;



//
//产生应答信号ack
//
reg	ackr;
always @ ( posedge clk or negedge rst_n )begin
		if( !rst_n )begin
			ackr <= 1'b0;
		end
		else if( pos_req2 )begin
			ackr <= 1'b1;
		end
		else if( !req )begin
			ackr <= 1'b0;
		end
		else begin
			ackr <= ackr;
		end
end
assign	ack = ackr;
endmodule

二、串口发送模块

下面是串口发送模块UART_TX_CORE的Verilog代码,对于如何互锁,可参照接收模块。

//
// Module Name:    UART_TX_CORE 模块 BPS = 115200
//
module UART_TX_CORE(
		input	clk,						//50M系统主时钟
		input rst_n,						//复位信号,低电平时有效,系统不工作
		input [7:0] tx_data,				//待发送的8位数据,从低位开始发送
		input tx_en,						//写模块使能信号,为1时,写模块工作
		
		output reg tx_done,					//帧数据结束信号,为1时表明一帧数据发送完成
		output reg tx_pin_out				//数据发送线
		);

wire bps_clk;
//
// UART_TX波特率产生模块
//
reg[8:0] cnt;
always @ ( posedge clk or negedge rst_n )begin
if( !rst_n )
	cnt <= 9'd0;
else if((tx_en) && (cnt != 9'd434))begin
	cnt <= cnt + 1'b1;
end
else
	cnt <= 9'd0;
end
assign bps_clk = (cnt == 9'd217) ? 1'b1 : 1'b0;
//
// UART_TX并行转串行模块
//
reg [3:0]i;
always @ ( posedge clk or negedge rst_n )
if(!rst_n)
begin
	i <= 4'd0;
	tx_pin_out <= 1'b1;
	tx_done <= 1'b0;
end
else if(tx_en)
	case(i)
		4'd0:
		if( bps_clk ) begin
			i <= i + 1'b1;
			tx_pin_out <= 1'b0;			//起始位信号置低
		end
		4'd1, 4'd2, 4'd3, 4'd4, 4'd5, 4'd6, 4'd7, 4'd8:
		if( bps_clk ) begin
			i <= i + 1'b1;
			tx_pin_out <= tx_data[i-1];
		end
		4'd9:
		if( bps_clk ) begin
			i <= i + 1'b1;
			tx_pin_out <= 1'b1;
		end
		4'd10:
		if( bps_clk ) begin
			i <= i + 1'b1;
			tx_pin_out <= 1'b1;			//结束发送恢复高电平
		end
		4'd11:
		if(bps_clk) begin
			i <= i + 1'b1;
			tx_done<= 1'b1;				//发送结束标志位
		end
		4'd12:
		begin
			i <= 1'b0;
			tx_done<= 1'b0;
		end
	endcase

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值