一、串口接收
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