13. 串口接收模块的项目应用案例
1. 使用串口来控制LED灯工作状态
使用串口发送指令到FPGA开发板,来控制第7课中第4个实验的开发板上的LED灯的工作状态。
LED灯的工作状态:让LED灯按指定的亮灭模式亮灭,亮灭模式未知,由用户指定,8个变化状态为一个循环,每个变化状态的时间值可以根据不同的应用场景来选择。
1.1 原理如下
1.2 任务简化为:
如何使用串口接收8个字节的数据,判断并输出其中的数据。
2. 写设计代码,仿真代码并仿真
2.1 设计代码(重要)
1.在完成uart_cmd解析数据模块时,要掌握移位寄存器的使用,以及延后一拍判定的方法
2.修改了uart_byte_rx1串口接收模块代码,修改了rx_data八位数据接收条件,原先是发送了在出现tx_done信号后得到输出八位数据,这样会导致我们在后续模块通过tx_done信号接收数据时接收的是前一个八位数据。现在改为了tx_done信号出现时,八位数据已准备输出了。
3.修改了counter_led灯模块代码
module uart_rx_ctrl_led(
clk,
rstn,
uart_rx,
blaud_set,
led
);
input clk;
input rstn;
input uart_rx;
input [2:0]blaud_set;
output led;
wire [7:0]data;
wire rx_done;
wire [7:0] ctrl;
wire [31:0] times;
uart_byte_rx1 uart_byte_rx1_inst(
.clk(clk),
.rstn(rstn),
.blaud_set(blaud_set),
.uart_rx(uart_rx),
.data(data),
.rx_done(rx_done)
);
uart_cmd uart_cmd_inst(
.clk(clk),
.rstn(rstn),
.rx_data(data),
.rx_done(rx_done),
.ctrl(ctrl),
.times(times)
);
counter_led4 counter_led4_inst(
.clk(clk),
.rstn(rstn),
.ctrl(ctrl),
.times(times),
.led(led)
);
endmodule
module uart_byte_rx1(
clk,
rstn,
blaud_set,
uart_rx,
data,
rx_done
);
input clk;
input rstn;
input [2:0]blaud_set;
input uart_rx;
output reg [7:0] data;
output rx_done;
reg [8:0] bps_dr;
always@(*)
case(blaud_set)
0:bps_dr = 1000000000/9600/16/20;
1:bps_dr = 1000000000/19200/16/20;
2:bps_dr = 1000000000/38400/16/20;
3:bps_dr = 1000000000/57600/16/20;
4:bps_dr = 1000000000/115200/16/20;
default : bps_dr = 1000000000/9600/16/20;
endcase
//边沿信号检测
reg [1:0] uart_rx_r; //用两位寄存器分别存储两个时间沿的uart_rx信号
always@(posedge clk) begin
uart_rx_r[0] <= uart_rx;
uart_rx_r[1] <= uart_rx_r[0];
end
//将两位寄存器的值直接通过导线输出进行判断(不需要再使用寄存器)
wire nedge_uart_rx; //掌握一下这个方法,之前一直使用的是寄存器
//法一:
//assign nedge_uart_rx = ((uart_rx_r[0] == 0)&&(uart_rx_r == 1));
//法二:
assign nedge_uart_rx = (uart_rx_r == 2'b10);
reg rx_en;
always@(posedge clk or negedge rstn)
if(!rstn)
rx_en <= 0;
else if(nedge_uart_rx)
rx_en <= 1;
else if(rx_done)
rx_en <= 0;
//周期计数器
reg [8:0] div_cnt;
always@(posedge clk or negedge rstn)
if(!rstn)
div_cnt <= 0;
else if(rx_en) begin
if(div_cnt == bps_dr - 1)
div_cnt <= 0;
else
div_cnt <= div_cnt + 1'd1;
end
else
div_cnt <= 0;
wire [3:0]bps_clk_16x; //(一定要记得加位宽)采样信号,这种写法很灵活
assign bps_clk_16x = bps_dr/2; //采样每一段的中点值,同时也可以用它来计数。
//发送一字节的数据有需要十个数据位,每位数据有16个小段供采样,共160
reg [7:0]bps_cnt;
always@(posedge clk or negedge rstn)
if(!rstn)
bps_cnt <= 0;
else if(rx_en) begin
if(bps_cnt == 159)
bps_cnt <= 0;
else if(div_cnt ==bps_clk_16x)
bps_cnt <= bps_cnt + 1'd1;
end
else
bps_cnt <= 0;
reg[2:0] r_data[7:0];//二维数据,代表八个r_data,每个r_data有3位寄存器存储数值。
reg[2:0] sta_data;
reg[2:0] sto_data;
always@(posedge clk or negedge rstn)
if(!rstn)begin
sta_data <= 0;
sto_data <= 0;
r_data[0] <= 0; //语法规定,二维数组赋值要分开赋值
r_data[1] <= 0;
r_data[2] <= 0;
r_data[3] <= 0;
r_data[4] <= 0;
r_data[5] <= 0;
r_data[6] <= 0;
r_data[7] <= 0;
end
else if(div_cnt == bps_clk_16x - 1)
case(bps_cnt) //下面合在一起的写法是允许的
0:begin
r_data[0] <= 0;
r_data[1] <= 0;
r_data[2] <= 0;
r_data[3] <= 0;
r_data[4] <= 0;
r_data[5] <= 0;
r_data[6] <= 0;
r_data[7] <= 0;
end
5,6,7,8,9,10,11: sta_data <= sta_data + uart_rx;
21,22,23,24,25,26,27: r_data[0] <= r_data[0] + uart_rx;
37,38,39,40,41,42,43: r_data[1] <= r_data[1] + uart_rx;
53,54,55,56,57,58,59: r_data[2] <= r_data[2] + uart_rx;
69,70,71,72,73,74,75: r_data[3] <= r_data[3] + uart_rx;
85,86,87,88,89,90,91: r_data[4] <= r_data[4] + uart_rx;
101,102,103,104,105,106,107: r_data[5] <= r_data[5] + uart_rx;
117,118,119,120,121,122,123: r_data[6] <= r_data[6] + uart_rx;
133,134,135,136,137,138,139: r_data[7] <= r_data[7] + uart_rx;
149,150,151,152,153,154,155: sto_data <= sto_data + uart_rx;
default:;
endcase
reg rx_done;
always@(posedge clk or negedge rstn)
if(!rstn)
rx_done <= 0;
else if(bps_cnt == 159) begin
rx_done <= 1;
end
else
rx_done <= 0;
//数据接收完成后赋值给data输出
always@(posedge clk or negedge rstn)
if(!rstn)
data <= 0;
else if(bps_cnt == 159)begin //修改了之前的判定条件(重要)
data[0] <= (r_data[0] >= 4 ) ? 1 : 0;
data[1] <= (r_data[1] >= 4 ) ? 1 : 0;
data[2] <= (r_data[2] >= 4 ) ? 1 : 0;
data[3] <= (r_data[3] >= 4 ) ? 1 : 0;
data[4] <= (r_data[4] >= 4 ) ? 1 : 0;
data[5] <= (r_data[5] >= 4 ) ? 1 : 0;
data[6] <= (r_data[6] >= 4 ) ? 1 : 0;
data[7] <= (r_data[7] >= 4 ) ? 1 : 0;
end
// data[1] <= r_data[1][2]
// 0:3'd000
// 1:3'd001
// 2:3'd010
// 4:3'd100
// 5:3'd101
// 6:3'd110
// 7:3'd111 利用第3位的区别给data赋值
endmodule
module uart_cmd(
clk,
rstn,
rx_data,
rx_done,
ctrl,
times
);
input clk;
input rstn;
input [7:0]rx_data;
input rx_done;
output reg[7:0]ctrl;
output reg[31:0]times;
reg [7:0] data_str[7:0];
always@(posedge clk)
if(rx_done)begin
data_str[7] <= rx_data;
data_str[6] <= data_str[7];
data_str[5] <= data_str[6];
data_str[4] <= data_str[5];
data_str[3] <= data_str[4];
data_str[2] <= data_str[3];
data_str[1] <= data_str[2];
data_str[0] <= data_str[1];
end
// 使判断并取数据的触发条件在存数据的后一拍
// reg r_rx_done;
// always@(posedge clk)
// if(rx_done)
// r_rx_done <= rx_done;
reg r_rx_done;
always@(posedge clk)
if(rx_done)
r_rx_done <= 1;
else
r_rx_done <= 0;
always@(posedge clk or negedge rstn)
if(!rstn)begin
times <= 0;
ctrl <= 0;
end
else
if(r_rx_done)
if((data_str[0] == 8'h55) && (data_str[1] == 8'hA5) && (data_str[7] == 8'hF0))begin
times[7:0] <= data_str[2];
times[15:8] <= data_str[3];
times[23:16] <= data_str[4];
times[31:24] <= data_str[5];
ctrl[7:0] <= data_str[6];
end
else begin
ctrl <= ctrl;
times <= times;
end
endmodule
module counter_led4(
clk,
rstn,
ctrl,
times,
led
);
input clk;
input rstn;
input [7:0] ctrl;
input [31:0] times;
output reg led;
reg[31:0] counter;
always@(posedge clk or negedge rstn)
if(!rstn)
counter <= 0;
//为了防止times为0时减1会得到非常到的数值导致归0时间很长(重要)
else if(counter >= times - 1'd1)
counter <= 0;
else
counter <= counter + 1'd1;
reg [2:0]counter2;
always@(posedge clk or negedge rstn)
if(!rstn)
counter2 <= 0;
else if(counter == times - 1'd1)
counter2 <= counter2 + 1'd1;
always@(posedge clk or negedge rstn)
if(!rstn)
led <= 0;
else case(counter2)
0 : led <= ctrl[0];
1 : led <= ctrl[1];
2 : led <= ctrl[2];
3 : led <= ctrl[3];
4 : led <= ctrl[4];
5 : led <= ctrl[5];
6 : led <= ctrl[6];
7 : led <= ctrl[7];
default : led <= led;
endcase
endmodule
2.2 仿真代码
`timescale 1ns / 1ps
module uart_rx_ctrl_led_tb();
reg clk;
reg rstn;
reg uart_rx;
wire led;
wire [2:0]blaud_set;
assign blaud_set = 3'd4;
uart_rx_ctrl_led uart_rx_ctrl_led_inst(
.clk(clk),
.rstn(rstn),
.uart_rx(uart_rx),
.blaud_set(blaud_set),
.led(led)
);
initial clk = 1;
always #10 clk = ~clk;
initial begin
rstn = 0;
uart_rx = 1;
#201;
rstn = 1;
#200;
uart_tx_byte(8'h55);
#90000;
uart_tx_byte(8'ha5);
#90000;
uart_tx_byte(8'h9a);
#90000;
uart_tx_byte(8'h78);
#90000;
uart_tx_byte(8'h56);
#90000;
uart_tx_byte(8'h34);
#90000;
uart_tx_byte(8'h21);
#90000;
uart_tx_byte(8'hf0);
#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];
end
endtask
endmodule
仿真波形
3. 调试(重要)
3.1 tx_done信号到来,data_str[]正确接收到了数据且符合协议,但times和ctrl却仍未0,并未产生正确输出。
通过波形分析原因
通过代码找分析原因
3.2 模块接口给错导致的错误
3.3 counter计数判定条件需要修改
4. 上板验证(通过串口调试助手发送数据)