一、设计思路
1、怎么检测起始位?
a.采用两个D触发器进行边缘检测
截自小梅哥FPGA文档
2、采样时钟和采样频率应该怎么设计?
a.将一位数据分为16份,采样频率为波特率的16倍,假定波特率为115200,则计数器计数周期为
bps_DR:1_000_000_000 / 115200 / 20(时钟频率为50HZ) / 16
每次系统时钟上升沿到来,div_cnt开始计数,计数到bps_DR清零
b.每次采样都在一个计数周期内的中心点进行采样
assign bps_clk = (div_cnt == (bps_DR / 2))1?0
3、怎么判断接收到的数据是1还是0?
a.每次检测到bps_clk上升沿的时候,将采样的数据存在变量bps_cnt中(1位起始位8位数据位1位停止位,每位数据分成16份,所以bps_cnt一共160个数据)
截自小梅哥FPGA文档
b.起始位:将bps_cnt中的6、7、8、 9、 10、 11六位数据进行相加,结果一共有7种可能性:0,1,2,3,4,5,6。结果大于3认为是数据1,否则认为是数据0。数据位和停止位处理办法一样。
二、代码实现
`timescale 1ns / 1ps
module my_uart_rx_byte(
Clk,
Reset_n,
Uart_rx,
Baud_set,
Rx_byte,
Rx_done
);
input Clk;
input Reset_n;
input Uart_rx;
input [2:0]Baud_set;
output reg[7:0]Rx_byte;
output reg Rx_done;
reg[1:0]r_rx_data;
wire pos_edge;
wire neg_edge;
reg [8:0]bps_DR;
reg Rx_EN;
reg [8:0]bps_div;
wire bps_clk;
reg [7:0]r_rx_num;
reg [2:0]start_bit;
reg [2:0]Data_bit[7:0];
reg [2:0]stop_bit;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
r_rx_data <= 2'd0;
else begin
r_rx_data[0] <= Uart_rx;
r_rx_data[1] <= r_rx_data[0];
end
assign pos_edge = (r_rx_data == 2'b01)?1:0;
assign neg_edge = (r_rx_data == 2'b10)?1:0;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
bps_DR <= 9'd324;
else begin
case(Baud_set)
0:bps_DR <= 9'd324;
1:bps_DR <= 9'd162;
2:bps_DR <= 9'd80;
3:bps_DR <= 9'd53;
4:bps_DR <= 9'd26;
default:bps_DR <= 9'd324;
endcase
end
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Rx_EN <= 1'd0;
else if((neg_edge == 1) && (start_bit < 3'd4))
Rx_EN <= 1'b1;
else if((Rx_done) || (start_bit >= 3'd4))
Rx_EN <= 1'd0;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
bps_div <= 9'd0;
else if(Rx_EN)begin
if(bps_div == bps_DR)
bps_div <= 9'd0;
else
bps_div <= bps_div + 1'b1;
end
else
bps_div <= 9'd0;
assign bps_clk = (bps_div == (bps_DR/2))?1:0;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
r_rx_num <= 8'd0;
else if(Rx_EN)begin
if(bps_clk)begin
if(r_rx_num == 8'd159)
r_rx_num <= 8'd0;
else
r_rx_num <= r_rx_num + 1'b1;
end
end
else
r_rx_num <= 0;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
start_bit <= 3'd0;
Data_bit[0] <= 3'd0;
Data_bit[1] <= 3'd0;
Data_bit[2] <= 3'd0;
Data_bit[3] <= 3'd0;
Data_bit[4] <= 3'd0;
Data_bit[5] <= 3'd0;
Data_bit[6] <= 3'd0;
Data_bit[7] <= 3'd0;
stop_bit <= 3'd0;
end
else if(bps_clk)begin
case(r_rx_num)
0:begin start_bit <= 3'd0;
Data_bit[0] <= 3'd0;
Data_bit[1] <= 3'd0;
Data_bit[2] <= 3'd0;
Data_bit[3] <= 3'd0;
Data_bit[4] <= 3'd0;
Data_bit[5] <= 3'd0;
Data_bit[6] <= 3'd0;
Data_bit[7] <= 3'd0;
stop_bit <= 3'd0;
end
6,7,8,9,10,11:start_bit <= start_bit + Uart_rx;
22,23,24,25,26,27:Data_bit[0] <= Data_bit[0] + Uart_rx;
38,39,40,41,42,43:Data_bit[1] <= Data_bit[1] + Uart_rx;
54,55,56,57,58,59:Data_bit[2] <= Data_bit[2] + Uart_rx;
70,71,72,73,74,75:Data_bit[3] <= Data_bit[3] + Uart_rx;
86,87,88,89,90,91:Data_bit[4] <= Data_bit[4] + Uart_rx;
102,103,104,105,106,107:Data_bit[5] <= Data_bit[5] + Uart_rx;
118,119,120,121,122,123:Data_bit[6] <= Data_bit[6] + Uart_rx;
134,135,136,137,138,139:Data_bit[7] <= Data_bit[7] + Uart_rx;
150,151,152,153,154,155:stop_bit <= stop_bit + Uart_rx;
default:begin start_bit <= start_bit;
Data_bit[0] <= Data_bit[0];
Data_bit[1] <= Data_bit[1];
Data_bit[2] <= Data_bit[2];
Data_bit[3] <= Data_bit[3];
Data_bit[4] <= Data_bit[4];
Data_bit[5] <= Data_bit[5];
Data_bit[6] <= Data_bit[6];
Data_bit[7] <= Data_bit[7];
stop_bit <= stop_bit;
end
endcase
end
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Rx_byte <= 0;
else if(r_rx_num == 8'd159)begin
Rx_byte[0] <= (Data_bit[0] >= 4)?1:0;
Rx_byte[1] <= (Data_bit[1] >= 4)?1:0;
Rx_byte[2] <= (Data_bit[2] >= 4)?1:0;
Rx_byte[3] <= (Data_bit[3] >= 4)?1:0;
Rx_byte[4] <= (Data_bit[4] >= 4)?1:0;
Rx_byte[5] <= (Data_bit[5] >= 4)?1:0;
Rx_byte[6] <= (Data_bit[6] >= 4)?1:0;
Rx_byte[7] <= (Data_bit[7] >= 4)?1:0;
end
else
Rx_byte <= Rx_byte;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Rx_done <= 1'd0;
else if((bps_clk)&&(r_rx_num == 8'd159))
Rx_done <= 1'b1;
else
Rx_done <= 1'd0;
endmodule
三、遇到的问题和解决办法
问题1:
没有考虑开始接收数据的条件
解决办法:
设计一个RX_EN信号,当检测到下降沿,且起始位的数据是0,则将RX_EN信号置1,当出现Rx_done信号,或起始位的数据不为0(检测到下降沿,但不是真正的起始位)则将RX_EN信号置0
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Rx_EN <= 1'd0;
else if((neg_edge == 1) && (start_bit < 3'd4))
Rx_EN <= 1'b1;
else if((Rx_done) || (start_bit >= 3'd4))
Rx_EN <= 1'd0;
四、功能仿真
1、添加仿真文件
`timescale 1ns / 1ps
module my_uart_rx_byte_tb();
reg Clk;
reg Reset_n;
wire [2:0]Baud_set;
reg Uart_rx;
wire[7:0]Rx_byte;
wire Rx_done;
assign Baud_set = 3'd4;
my_uart_rx_byte my_uart_rx_byte_tb(
.Clk(Clk),
.Reset_n(Reset_n),
.Uart_rx(Uart_rx),
.Baud_set(Baud_set),
.Rx_byte(Rx_byte),
.Rx_done(Rx_done)
);
initial Clk = 1;
always #10 Clk = ~Clk;
initial begin
Reset_n = 0;
Uart_rx = 1;
#201;
Reset_n = 1;
#200;
uart_tx_byte(8'h5a);
@(posedge Rx_done)
#90000;
uart_tx_byte(8'ha5);
@(posedge Rx_done)
#90000;
uart_tx_byte(8'h86);
@(posedge Rx_done)
#90000;
$stop;
end
task uart_tx_byte;
input [7:0]tx_data;
begin
Uart_rx = 1;
#20;
Uart_rx = 0;
#8680;
Uart_rx = tx_data[0];
#8680;
Uart_rx = tx_data[1];
#8680;
Uart_rx = tx_data[2];
#8680;
Uart_rx = tx_data[3];
#8680;
Uart_rx = tx_data[4];
#8680;
Uart_rx = tx_data[5];
#8680;
Uart_rx = tx_data[6];
#8680;
Uart_rx = tx_data[7];
#8680;
Uart_rx = 1;
end
endtask
endmodule
2 、观察波形图
五 、设计串口接收多字节数据应用
5.1、设计分析
5.1.1、定义串口传输协议帧
55 - A5 - XX - XX - XX - XX - XX - F0
协议帧由帧头(0x55 0xA5) + 5字节数据 + 帧尾(0xF0)组成
5.1.2、串口控制LED灯原理和思路
a.接收来自电脑的串行数据并将其转化为多个单字节数据输出
b.对单字节数据进行8字节缓存,判断数据是否为有效帧,提取出正确的控制字输出
c.5字节数据对应5个LED灯,当出现AA时点亮LED灯,其他数据让LED灯保持熄灭
5.2、代码实现
`timescale 1ns / 1ps
module my_uart_rx_ctrl_led(
Clk,
Reset_n,
Uart_rx,
LED
);
input Clk;
input Reset_n;
input Uart_rx;
output reg[4:0]LED;
reg [7:0]Mem_Data[7:0];
wire [7:0]Rx_byte;
wire Rx_done;
my_uart_rx_byte my_uart_rx_byte(
.Clk(Clk),
.Reset_n(Reset_n),
.Uart_rx(Uart_rx),
.Baud_set(4),
.Rx_byte(Rx_byte),
.Rx_done(Rx_done)
);
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
Mem_Data[0] <= 0;
Mem_Data[1] <= 0;
Mem_Data[2] <= 0;
Mem_Data[3] <= 0;
Mem_Data[4] <= 0;
Mem_Data[5] <= 0;
Mem_Data[6] <= 0;
Mem_Data[7] <= 0;
end
else if(Rx_done)begin
Mem_Data[7] <= Rx_byte;
Mem_Data[6] <= Mem_Data[7];
Mem_Data[5] <= Mem_Data[6];
Mem_Data[4] <= Mem_Data[5];
Mem_Data[3] <= Mem_Data[4];
Mem_Data[2] <= Mem_Data[3];
Mem_Data[1] <= Mem_Data[2];
Mem_Data[0] <= Mem_Data[1];
end
reg r_rx_done;
always@(posedge Clk)
r_rx_done <= Rx_done;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
LED <= 0;
else if(r_rx_done)begin
if((Mem_Data[7] == 8'hF0) && (Mem_Data[1] == 8'hA5) && (Mem_Data[0] == 8'h55))begin
LED[0] = (Mem_Data[2] == 8'hAA)?1:0;
LED[1] = (Mem_Data[3] == 8'hAA)?1:0;
LED[2] = (Mem_Data[4] == 8'hAA)?1:0;
LED[3] = (Mem_Data[5] == 8'hAA)?1:0;
LED[4] = (Mem_Data[6] == 8'hAA)?1:0;
end
end
endmodule
5.3、功能仿真
5.3.1、添加仿真文件
`timescale 1ns / 1ps
module my_uart_rx_ctrl_led_tb();
reg Clk;
reg Reset_n;
reg Uart_rx;
wire [4:0]LED;
my_uart_rx_ctrl_led my_uart_rx_ctrl_led_tb(
.Clk(Clk),
.Reset_n(Reset_n),
.Uart_rx(Uart_rx),
.LED(LED)
);
initial Clk = 1;
always #10 Clk = ~Clk;
initial begin
Reset_n = 0;
Uart_rx = 1;
#201;
Reset_n = 1;
#200;
uart_tx_byte(8'h55);
#90000;
uart_tx_byte(8'ha5);
#90000;
uart_tx_byte(8'haa);
#90000;
uart_tx_byte(8'h34);
#90000;
uart_tx_byte(8'haa);
#90000;
uart_tx_byte(8'h78);
#90000;
uart_tx_byte(8'haa);
#90000;
uart_tx_byte(8'hf0);
#90000;
uart_tx_byte(8'h55);
#90000;
uart_tx_byte(8'ha5);
#90000;
uart_tx_byte(8'haa);
#90000;
uart_tx_byte(8'h78);
#90000;
uart_tx_byte(8'haa);
#90000;
uart_tx_byte(8'h34);
#90000;
uart_tx_byte(8'h12);
#90000;
uart_tx_byte(8'hf1);
#90000;
#900000;
$stop;
end
task uart_tx_byte;
input [7:0]tx_data;
begin
Uart_rx = 1;
#20;
Uart_rx = 0;
#8680;
Uart_rx = tx_data[0];
#8680;
Uart_rx = tx_data[1];
#8680;
Uart_rx = tx_data[2];
#8680;
Uart_rx = tx_data[3];
#8680;
Uart_rx = tx_data[4];
#8680;
Uart_rx = tx_data[5];
#8680;
Uart_rx = tx_data[6];
#8680;
Uart_rx = tx_data[7];
#8680;
Uart_rx = 1;
#8680;
end
endtask
endmodule
5.3.2、观察波形图
6、遇到的问题及解决办法
问题1:板机验证时需要连续发送两组八字节数据,LED灯才会产生变化
问题分析:通过观察波形图发现,LED错过了最后一次数据发送完时产生的RX_done上升沿,直到下一个数据来临时产生Rx_done上升沿时LED才发生变化
解决办法:
将RX_done延迟一拍
reg r_rx_done;
always@(posedge Clk)
r_rx_done <= Rx_done;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
LED <= 0;
else if(r_rx_done)begin
if((Mem_Data[7] == 8'hF0) && (Mem_Data[1] == 8'hA5) && (Mem_Data[0] == 8'h55))begin
LED[0] = (Mem_Data[2] == 8'hAA)?1:0;
LED[1] = (Mem_Data[3] == 8'hAA)?1:0;
LED[2] = (Mem_Data[4] == 8'hAA)?1:0;
LED[3] = (Mem_Data[5] == 8'hAA)?1:0;
LED[4] = (Mem_Data[6] == 8'hAA)?1:0;
end
end