1.硬件特征:
呼吸灯,渐亮渐暗的灯,实现原理为PWM(脉冲宽度调制)
PWM:参考资料,其中说明了PWM在呼吸灯,电机转速和舵机控制中发挥的作用。
2.原理图:
略
3.端口以及模块结构:
我再此基础上额外加了按键控制功能,具体实现功能为:小灯以呼吸灯的方式亮灭,按一次按键之后转换为500ms亮,500ms灭 ,再按一次重新变为呼吸灯方式亮灭。
这次我重写了key_filter逻辑,尽可能减少了中间变量,除了必要的亚稳态处理的buffer信号,cnt信号和edge_flag信号,没有其他的中间变量了
呼吸灯模块我的代码有重大问题,后面细说:
4.代码技巧:
正点原子代码:
module breath_led(
input sys_clk , //系统时钟 50MHz
input sys_rst_n , //系统复位,低电平有效
output reg led //LED灯
);
//parameter define
parameter CNT_2US_MAX = 7'd100;
parameter CNT_2MS_MAX = 10'd1000;
parameter CNT_2S_MAX = 10'd1000;
//reg define
reg [6:0] cnt_2us;
reg [9:0] cnt_2ms;
reg [9:0] cnt_2s;
reg inc_dec_flag; //亮度递增/递减 0:递增 1:递减
//*****************************************************
//** main code
//*****************************************************
//cnt_2us:计数2us
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_2us <= 7'b0;
else if(cnt_2us == (CNT_2US_MAX - 7'b1 ))
cnt_2us <= 7'b0;
else
cnt_2us <= cnt_2us + 7'b1;
end
//cnt_2ms:计数2ms
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_2ms <= 10'b0;
else if(cnt_2ms == (CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
cnt_2ms <= 10'b0;
else if(cnt_2us == CNT_2US_MAX - 7'b1)
cnt_2ms <= cnt_2ms + 10'b1;
else
cnt_2ms <= cnt_2ms;
end
//cnt_2s:计数2s
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_2s <= 10'b0;
else if(cnt_2s == (CNT_2S_MAX - 10'b1) && cnt_2ms == (CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
cnt_2s <= 10'b0;
else if(cnt_2ms == (CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
cnt_2s <= cnt_2s + 10'b1;
else
cnt_2s <= cnt_2s;
end
//inc_dec_flag为低电平,led灯由暗变亮,inc_dec_flag为高电平,led灯由亮变暗
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
inc_dec_flag <= 1'b0;
else if(cnt_2s == (CNT_2S_MAX - 10'b1) && cnt_2ms ==( CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
inc_dec_flag <= ~inc_dec_flag;
else
inc_dec_flag <= inc_dec_flag;
end
//led:输出信号连接到外部的led灯
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
led <= 1'b0;
else if((inc_dec_flag == 1'b1 && cnt_2ms >= cnt_2s) || (inc_dec_flag == 1'b0 && cnt_2ms <= cnt_2s))
led <= 1'b1;
else
led <= 1'b0;
end
endmodule
我的代码:
module top_breath_led(
input sys_clk,
input sys_rst_n,
input key,
output led
);
wire key_filter;
parameter CNT_MAX = 20'd1000_000; //计数20ns*1000_000 =20ms
parameter CNT_MXA_2US = 100; //计数时间2us
parameter CNT_MXA_2MS = 1000; //计数时间2ms
key_filter #(
.CNT_MAX(CNT_MAX)
) //lihua canshu
key_filter_U1(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.key(key),
.key_filter(key_filter)
);
breath_led
#(
.CNT_MXA_2US(CNT_MXA_2US),
.CNT_MXA_2MS(CNT_MXA_2MS)
)
breath_led_U1(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.key(key_filter),
.led(led)
);
endmodule
/**************************************************************************************/
/*
function:按键消抖输出
author:ZHY
time:2024年4月5日
*/
module key_filter(
input sys_clk,
input sys_rst_n,
input key,
output reg key_filter
);
parameter CNT_MAX = 20'd1000_000; //计数20ns*1000_000 =20ms
reg buffer1,buffer2,buf_res;
wire edge_flag;
reg [19:0] cnt;
//边沿检测模块_亚稳态处理
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
buffer1 <= 1'b0;
buffer2 <= 1'b0;
end
else begin
buffer2 <= buffer1; //同步打拍子
buf_res <= buffer2;
end
end
//边沿检测模块_边沿检测
assign edge_flag = (buf_res != buffer2)?1:0; //前后时刻不同,说明出现边沿
//消抖模块_计数2ms
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt <= 20'b0;
else if(cnt >= CNT_MAX -1'b1) //记满则请0
cnt <= 20'b0;
else if(edge_flag) //出现边沿
cnt <= 20'b0;
else
cnt <= cnt + 1'b1;
end
//消抖模块_滤除抖动产生的短时信号
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
key_filter <= 1'b0;
else if(cnt == CNT_MAX -1'b1)
key_filter <= key; //这里的处理应该是正确的,而且几乎没引入多余的变量
else //只要计数到20ms,就判断一次稳定的按键来了,就可以
//进行信号更新了,否则就保持原信号(滤除短时信号)
key_filter <= key_filter; //结合时序图就能理解此处了,
end
endmodule
/**************************************************************************************/
/*
function:呼吸灯+按键切换状态
author:ZHY
time:2024年4月5日
*/
module breath_led(
input sys_clk,
input sys_rst_n,
input key,
output reg led
);
parameter CNT_MXA_2US = 100; //计数时间2us
parameter CNT_MXA_2MS = 1000; //计数时间2ms
reg [6:0] cnt_us;
reg [9:0] cnt_ms,cnt_s;
reg inv_flag;
/*******************额外设计******************************************************/
reg [8:0] cnt_500ms;
reg buffer_test;
reg negedge_flag;
reg key_ctl;
/*******************额外设计******************************************************/
//计数时间2us
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt_us <= 7'b0;
else if(cnt_us >= CNT_MXA_2US -1'b1) //记满2us
cnt_us <= 7'b0;
else
cnt_us <= cnt_us+1'b1;
end
//计数时间2ms
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt_ms <= 10'b0;
// else if(cnt_ms >= CNT_MXA_2MS -1'b1) //加到1000次之后,计时达到2ms,此处的逻辑稍有误
else if((cnt_ms >= CNT_MXA_2MS -1'b1) && (cnt_us == CNT_MXA_2US -1'b1))//这样最后一个2us才完整
cnt_ms <= 10'b0;
else if(cnt_us == CNT_MXA_2US -1'b1) //每次计数满2us,则加一
cnt_ms <= cnt_ms+1'b1;
else
cnt_ms <= cnt_ms;
end
//计数时间2s
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt_s <= 10'b0;
// else if(cnt_s >= CNT_MXA_2MS -1'b1) //加到1000次之后,计时达到2s,清零重新开始
// !!!! else if((cnt_s >= CNT_MXA_2MS -1'b1) &&(cnt_ms == CNT_MXA_2MS -1'b1))//这样最后一个2ms才完整,又错了,
else if((cnt_s >= CNT_MXA_2MS -10'b1) &&(cnt_ms == CNT_MXA_2MS -10'b1)&& (cnt_us == CNT_MXA_2US -7'b1))
//这样才完整,这样的cnt_s的清零赋值只持续一个周期,而上面的代码的话,赋值会持续2us和2ms
cnt_s <= 10'b0;
//else if((cnt_ms == CNT_MXA_2MS -10'b1) && (cnt_us == 7'b0)) //每次计数满2ms,则加一
else if((cnt_ms == CNT_MXA_2MS -10'b1) && (cnt_us == CNT_MXA_2US -7'b1))
cnt_s <= cnt_s+1'b1;
else
cnt_s <= cnt_s;
end
//递增or递减的标志信号,2s一次反转
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
inv_flag <= 1'b1; //递增
//else if(cnt_s == CNT_MXA_2MS -1'b1) //加到1000次之后,计时达到2s,信号翻转
else if((cnt_s >= CNT_MXA_2MS -10'b1) &&(cnt_ms == CNT_MXA_2MS -10'b1)&& (cnt_us == CNT_MXA_2US -7'b1))
inv_flag <= ~inv_flag; //翻转
else
inv_flag <= inv_flag; //不变
end
/*最抽象同时也是必须要掌握的一个地方来了,对于每次记满2us之后的处理,也就是
输出递增or递减的调制信号,eg:100000000...,110000000...,111000000...,111100000...,111110000...
这里纯思考如何设计会发现很麻烦,但是只要动手花了波形图,就会发现有个极其简单的数学关系
所以有时候思考逻辑关系但是思考半天也想不出的情况下,一个简单的时序波形图可能就能给你
提供直白的数学关系了,
*/
//输出控制逻辑,要理解数学关系
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
led <= 1'b0;
else if(key_ctl == 1'b1) begin //key_ctl==1 呼吸灯
if(inv_flag) begin //判断标志信号为递增信号
if(cnt_ms > cnt_s) //此处是数学关系,
led <= 1'b0;
else
led <= 1'b1;
end
else begin //判断标志信号为递减 信号
if(cnt_ms > cnt_s) //此处是数学关系,
led <= 1'b1;
else
led <= 1'b0;
end
end
else
begin if(cnt_500ms == 9'd249)//key_ctl==0 0.5s亮灭灯
led <= ~led;
else
led <= led;
end
end
/*******************************************************************************************/
//额外设计按键切换状态//
/*******************************************************************************************/
//计数时间0.5s
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt_500ms <= 9'b0;
else if(cnt_500ms >= 9'd249) //加到249次之后,计时达到0.5s,清零重新开始
cnt_500ms <= 9'b0;
//此处有问题!!!
//else if(cnt_ms == CNT_MXA_2MS -1'b1) //每次计数满2ms,则加一
else if((cnt_ms == CNT_MXA_2MS -1'b1) && (cnt_us == CNT_MXA_2US -1'b1)) //每次计数满2ms,则加一
cnt_500ms <= cnt_500ms+1'b1;
else
cnt_500ms <= cnt_500ms;
end
//下边沿检测
//边沿检测模块_亚稳态处理
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
buffer_test <= 1'b0;
end
else begin
buffer_test <= key; //同步打拍子
end
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
negedge_flag <= 1'b0;
end
else if((buffer_test == 1'b1)&& (key == 1'b0)) //下降沿检测
negedge_flag <= 1'b1;
else
negedge_flag <= 1'b0;
end
//下降沿信号接收并自锁
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
key_ctl <= 1'b0;
end
else if(negedge_flag) //
key_ctl <= ~key_ctl; //1为呼吸灯,0为亮灭亮灭
else
key_ctl <= key_ctl;
end
endmodule
其中呼吸灯的的2us计数,2ms计数和2s计数的清零和加一条件均有错误,包括后面我的翻转标志位inv_flag也出现错误,全部是同一种错误,如下:
else if((cnt_s >= CNT_MXA_2MS -10'b1) &&(cnt_ms == CNT_MXA_2MS -10'b1)&& (cnt_us == CNT_MXA_2US -7'b1))
inv_flag <= ~inv_flag; //翻转
//满足这三个条件之后才翻转,但是我这样写的:
else if(cnt_s == CNT_MXA_2MS -1'b1)
inv_flag <= ~inv_flag; //翻转
这样写的话就会在cnt_s = 计数最大值的时刻无数次翻转,来一个时钟翻转一次,
这样导致的仿真结果会出现错误:
可以看到我的inv_flag信号在cnt_s=9的时候翻转了很多次,在2ms内多次翻转!
类似的错误下面出现了很多次!!!如:
// else if(cnt_ms >= CNT_MXA_2MS -1'b1) //加到1000次之后,计时达到2ms,
//此处是清零的条件错误,上面这种写法会导致在计数到2ms的时刻一直清零,而且清零操作会保持2us才结束!
else if((cnt_ms >= CNT_MXA_2MS -1'b1) && (cnt_us == CNT_MXA_2US -1'b1))//这样最后一个2us才完整
cnt_ms <= 10'b0;...
// else if(cnt_s >= CNT_MXA_2MS -1'b1) //加到1000次之后,计时达到2s,清零重新开始
// !!!! else if((cnt_s >= CNT_MXA_2MS -1'b1) &&(cnt_ms == CNT_MXA_2MS -1'b1))//这样最后一个2ms才完整,///又错了,这样的话清零操作依然会多持续2us才结束,而不是一个时钟周期
else if((cnt_s >= CNT_MXA_2MS -10'b1) &&(cnt_ms == CNT_MXA_2MS -10'b1)&& (cnt_us == CNT_MXA_2US -7'b1))
//这样才完整,这样的cnt_s的清零赋值只持续一个周期,而上面的代码的话,赋值会持续2us和2ms
cnt_s <= 10'b0;
//else if((cnt_ms == CNT_MXA_2MS -10'b1) && (cnt_us == 7'b0)) //每次计数满2ms,则加一
else if((cnt_ms == CNT_MXA_2MS -10'b1) && (cnt_us == CNT_MXA_2US -7'b1))
cnt_s <= cnt_s+1'b1;
类似的错误我几乎全犯了一遍,我将此种错误称为:“递进计数条件错误”,这些错误一般都是出现在递进计数情况下,即cnt_us记满之后,cnt_ms+1;cnt_ms记满之后,cnt_s+1这样的递进计数关系情况下,条件不完备导致的错误,而且这些错误不是语法错误,也能完成综合,只有在仿真时才能看到异常,需要注意!!(如果cnt_s,cnt_ms,cnt_us的计数相互独立的情况下,则一般不会出现这样的条件不完备错误,但是会多占用逻辑资源)
5.仿真波形
略·
6.总结与易错点:
(1)消抖模块优化:
对比之前我写的消抖逻辑,这次我减少了一些不必要变量,比如pose_flag,nege_flag(直接用edge_flag代替即可,不必判断是上升还是下降沿),优化了key_filter的输出逻辑(稳定后可以直接用key赋值给key_filter),具体对比可看代码。
(2)递进计数条件错误(不完备):
详情如上,需要注意的是这种错误仅能在仿真中看出端倪,综合和实现过程都是可以通过的。
(3)参数重定义与参数传递:
补充之前遇到的问题,如CNT_MAX,CNT_MAX_US,为节省仿真时间需要改变参数,多次例化的情况下,如tb_breath_led例化了top_breath_led,,top_breath_led例化了breath_led,,那么要在tb中进行参数重定义,改变breath_led中定义的参数的话,tb_breath_led和top_breath_led模块都要遵循
parameter 参数名 = xxx;(先)
然后例化底层模块时使用
#( .源参数名(参数名) )
这样的格式。
over