Verilog HDL实现智能药盒
一、简介
我们知道,当前,老年人的数量是比较多的,从而导致了照顾老年人成为了一个比较大的社会问题。由于老年人的记忆力相比之前可能是会有所下降的,而且与此同时,由于老年人的子女们也有上班工作等一些其他的生活的需求,所以说,老年人的身边并不是总能有人在陪伴的,这就导致了一些社会性的问题。例如,很多老年人随着年龄的增长,会伴随着健忘的问题。对于老年人来说,在生病的时候,甚至是日常生活中按时吃药都是一个难题。如果需要在不同时间吃不同的药,几乎很难独立完成。而设计实现一个具有计时提醒功能的智能药盒就是为这些老年人所设计的。
我们前面已经说了,其中的问题之一,就是老年人如果生病需要吃药的话,很有可能会由于自己的疏忽,忘记了吃药这件事,而同时身边也没有人来即是的提醒,从而会使得老年人错过正确的吃药的时间,这就有可能对老年人的病情产生一些不利的影响,更严重的话,甚至有可能会带来生命危险的。因此,我们需要制作一种可以自动的提醒老年人进行在设置的时间去吃药的操作的一种智能药盒。这种药盒,我们在设计的时候,可以一次性设定最多三种药的吃药时间,对老年人进行提醒吃药的操作,如果老年人吃过了药,那么提醒的警报就会消失,否则,会一直进行提醒。这样一来就可以在正确的时间来提醒老年人吃药,从而减小了错过吃药的时间的可能性了,更好的保证了老年人的安全与健康。
本实验旨在设计一种模拟的智能药盒,主要功能是进行模拟智能药盒的设定提醒时间以及定时的提醒吃药的功能。也就是说,本实验模拟的药盒可以实现定时的操作,设计的时候由于器件等原因,一次最多可以设置三种药的吃药的时间,然后在启动装置以后,装置就会自动的计时,等达到了某一种药的吃药的时间的时候,系统就会自动提醒吃药啦,吃过药以后,提醒关闭。就这样的一直循环下去,直到人为的关闭系统为止。当系统再一次启动的时候,仍然可以重新设置定时以及后面的提醒工作可以正常的进行。
由于后面内容比较多,这里先来一个现象:
https://www.bilibili.com/video/BV1MY411x7Xw?spm_id_from=333.999.0.0
Verilog HDL实现智能药盒
下面开始正式的项目的制作了啦:
二、代码
1、主文件(主模块的代码)
代码比价多、比较长,但是里面有详细的注释。
module test_little_foot(clk,rst,btn1,btn2,btn3,btn4,btn5,btn6,
rled1, rled2, rled3,rled4,seg_led_1,seg_led_2,
row, col);// row, col 是点阵的设置。
// 进行小脚丫上面的所有的代码的一个整合啦。
output reg [7:0] row;
output reg [7:0] col;
// Dot Matrix.---点阵。
localparam s0 = 3'd0, s1 = 3'd1, s2 = 3'd2, s3 = 3'd3,
s4 = 3'd4, s5 = 3'd5, s6 = 3'd6, s7 = 3'd7;
// 这个状态是进行扫描的时候使用的啦。
// Dot Matrix 显示的时候所需要的控制状态。
reg [12:0] cnt;
always @(posedge clk or negedge rst)
if(!rst) cnt <= 1'b0;
else if(cnt >= 13'd7499) cnt <= 1'b0;
else cnt <= cnt + 1'b1;
reg clk_800hz;
// 扫描的频率是 800 Hz。
always @(posedge clk or negedge rst)
if(!rst) clk_800hz <= 1'b0;
else if(cnt == 13'd7499) clk_800hz <= ~clk_800hz;
// 时钟分频。
else clk_800hz <= clk_800hz;
// 产生扫描的频率哦。
//reg [12:0] cnt;
//always @(posedge clk or negedge rst_n)
// if(!rst_n) cnt <= 1'b0;
// else if(cnt >= 13'd7499) cnt <= 1'b0;
// else cnt <= cnt + 1'b1;
//
//reg clk_800hz;
//always @(posedge clk or negedge rst_n)
// if(!rst_n) clk_800hz <= 1'b0;
// else if(cnt == 13'd7499) clk_800hz <= ~clk_800hz;
// else clk_800hz <= clk_800hz;
//
input clk;
// 时钟信号。
input rst;
// 重置信号,在2.0版本无使用。
input btn1;
// 设置盒子开机。
input btn2;
// 设置三个药箱中具体操作哪一个。
input btn3; // 按下一次,时间加一秒;
input btn4; // 按下一次,时间减一秒。
// 设置时间。
input btn5;
// 设置完毕时间以后,需要有一个按键来开启装置的工作状态。
input btn6;
// 关闭挺提醒的警报的按键。
output rled1;
output rled2;
output rled3;
output rled4;
// 输出四个led灯,前三个是对应于三个药盒,第四个原本要接蜂鸣器,但是小脚丫没有。
reg led1;
reg led2;
reg led3;
reg led4 = 1;
// led4 = 1,表示在没有正式进入工作转态的时候,是处于熄灭的状态。
// 记录在没有正式进入工作状态的时候的信号。
reg led10;
reg led20;
reg led30;
reg led40;
// 记录在工作状态下的时候的信号。
reg times;
// 设置一个数字来判断是否需要进行 counting 置零的操作啦。
reg times0;
// 设置一个数字来判断是否进行了置零的操作了。
wire times_ = times;
// 连线相接。
wire times0_ = times0;
// 连线相接。
output [8:0] seg_led_1;
output [8:0] seg_led_2;
// 数码管的显示。
wire key_pulse1;
wire key_pulse2;
wire key_pulse3;
wire key_pulse4;
wire key_pulse5;
wire key_pulse6;
// 对应于 6 个按键的按键消抖的输出线。
reg state_of_start_or_nor = 0;
// 判断是否开机了。
reg really_start = 0;
// 判断是否进行提醒吃药的功能了。
reg [3:0] location = 1'd0;
// 记录设置时间的时候的位置。
wire clkout1;
wire clkout2;
wire clkout3;
wire clkout4;
// 有四个时钟分频,因此有四个输出。
reg [1:0] led_position = 2'b11;
// 判断是哪一个药盒需要进行闪烁以及蜂鸣器的提醒。
reg [3:0] seg_data_1= 1'd0;
reg [3:0] seg_data_2= 1'd0;
// 记录没有正式工作的时候的数码管对应的需要显示的数字。
reg [3:0] seg_data_10= 1'd0;
reg [3:0] seg_data_20= 1'd0;
// 记录正式开始工作以后的数码管对应的所需要显示的数字。
reg [8:0] seg [10:0];
// 这个是数码管的显示部分内容,我们会在初始化的之后直接给它赋值。
reg [5:0] first_medicine;
reg [5:0] second_medicine;
reg [5:0] third_medicine;
// 存储服药的时间,因为有三个药箱,所以需要三个数据进行寄存。
reg [5:0] counting;
// 计时器的时间的记录,这个是一秒计一次数字。
initial
begin
seg[0] = 9'h3f;
// 对存储器中第一个数赋值9'b00_0011_1111,相当于共阴极接地,DP点变低不亮;
// 7段显示数字 0。
seg[1] = 9'h06;
// 7段显示数字 1。
seg[2] = 9'h5b;
// 7段显示数字 2。
seg[3] = 9'h4f;
// 7段显示数字 3。
seg[4] = 9'h66;
// 7段显示数字 4。
seg[5] = 9'h6d;
// 7段显示数字 5。
seg[6] = 9'h7d;
// 7段显示数字 6。
seg[7] = 9'h07;
// 7段显示数字 7。
seg[8] = 9'h7f;
// 7段显示数字 8。
seg[9] = 9'h6f;
// 7段显示数字 9。
seg[10] = 9'b0_0111_0110;
// 显示H,表示说明是处于关机的状态,为什么是H呢,因为我的名字的首字母是H。
end
initial
begin
first_medicine <= 2'd0;
second_medicine <= 2'd0;
third_medicine <= 2'd0;
// 让初始的三个计数都是零。
counting <= 2'd00;
times <= 0;
times0 <= 0;
end
reg [63:0] mem___;
// 设置点阵什么都不显示的状态。(初始状态,以及已经吃过药的状态。)
initial
begin
mem___={8'b0000_0000,
8'b0000_0000,
8'b0000_0000,
8'b0000_0000,
8'b0000_0000,
8'b0000_0000,
8'b0000_0000,
8'b0000_0000};
// 所有的点都处于熄灭的状态。
end
// Dot Matrix 有关的显示
reg [63:0] mem;
reg [63:0] mem1;
reg [63:0] mem2;
reg [63:0] mem3;
// 开机了,但是没有正式开始工作时的闪烁现象。
initial
begin
// 初始状态。
mem1={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第一个灯
// 所有的该亮的显示灯都凉亮了。
mem2={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第二个灯
mem3={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第三个灯
end
always@(posedge clk)
begin
// 通过时钟信号来控制,高电平的时候灯灭,低电平的时候灯亮,从而实现闪烁。
if(clkout1)
// 频率是 2 Hz
begin
// 熄灭状态。
mem1={8'b0000_0000,
8'b0000_0000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第一个灯
mem2={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0000_0000,
8'b0000_0000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第二个灯
mem3={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0000_0000,
8'b0000_0000};
// 第三个灯
end
else
begin
// 恢复状态。
mem1={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第一个灯
mem2={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第二个灯
mem3={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第三个灯
end
end
reg [63:0] mem0;
reg [63:0] mem10;
reg [63:0] mem20;
reg [63:0] mem30;
// 正式开始工作的时候的闪烁的现象。
initial
begin
// 初始状态。
mem10={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第一个灯
mem20={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第二个灯
mem30={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第三个灯
end
always@(posedge clk)
begin
// 也是更上面的那个一样的道理。
if(clkout3)
// 这个是 4 Hz 的频率。
begin
// 熄灭状态。
mem10={8'b0000_0000,
8'b0000_0000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第一个灯
mem20={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0000_0000,
8'b0000_0000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第二个灯
mem30={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0000_0000,
8'b0000_0000};
// 第三个灯
end
else
begin
// 恢复状态。
mem10={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第一个灯
mem20={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第二个灯
mem30={8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000,
8'b0000_0000,
8'b0001_1000,
8'b0001_1000};
// 第三个灯
end
end
// 按键消抖,需要给使用到的 6 个按键都进行消抖,这里rst我们暂时没有用到,也许后续扩展的时候会使用。
debounce_button u1 (
.clk (clk),
.rst (rst),
.key (btn1),
.key_pulse (key_pulse1)
);
debounce_button u2 (
.clk (clk),
.rst (rst),
.key (btn2),
.key_pulse (key_pulse2)
);
debounce_button u3 (
.clk (clk),
.rst (rst),
.key (btn3),
.key_pulse (key_pulse3)
);
debounce_button u4 (
.clk (clk),
.rst (rst),
.key (btn4),
.key_pulse (key_pulse4)
);
debounce_button u5 (
.clk (clk),
.rst (rst),
.key (btn5),
.key_pulse (key_pulse5)
);
debounce_button u6 (
.clk (clk),
.rst (rst),
.key (btn6),
.key_pulse (key_pulse6)
);
// 时钟分频,我们需要一些不同的信号,所以需要不同的分频模块。
time_split #(.N(6000000),
.WIDTH(23)) t1 (
.clk(clk),
.rst_n(rst),
.clkout(clkout1));
// 频率为 2 Hz。
time_split #(.N(12000000),
.WIDTH(24)) t2 (
.clk(clk),
.rst_n(rst),
.clkout(clkout2));
// 频率为 1 Hz。
time_split #(.N(3000000),
.WIDTH(22)) t3 (
.clk(clk),
.rst_n(rst),
.clkout(clkout3));
// 频率为 4 Hz。
time_split #(.N(1500000),
.WIDTH(21)) t4 (
.clk(clk),
.rst_n(rst),
.clkout(clkout4));
// 频率为 8 Hz。
// 显示Dot Matrix.
reg [63:0] memory;
reg [2:0] state;
// 确定采用哪一种方法来对Dot Matrix进行显示。
always@(posedge clk)
begin
if(state_of_start_or_nor && ~really_start)
// 开机了,但是没有完全开始工作,这个时候是由 location 来进行决定的。
begin
// location.
case(location)
4'b0000:
// 第一个位置
begin
mem <= mem1;
end
4'b0001:
// 第二个位置
begin
mem <= mem2;
end
4'b0010:
// 第三个位置
begin
mem <= mem3;
end
default:
// 一共只有三个位置,因此这个也就是显示第三个位置
begin
mem <= mem3;
end
endcase
end
if(state_of_start_or_nor && really_start)
// 开机了,而且是完全开始了。
begin
// led_position.
case(led_position)
2'b00:
// 第一个位置
begin
if(btn_true_or_not1)
// 如果吃过药了。
begin
mem0 <= mem___;
end
else
begin
mem0 <= mem10;
end
end
2'b01:
// 第二个位置
begin
if(btn_true_or_not2)
// 如果吃过药了。
begin
mem0 <= mem___;
end
else
begin
mem0 <= mem20;
end
end
2'b10:
// 第三个位置
begin
if(btn_true_or_not3)
// 如果吃过药了。
begin
mem0 <= mem___;
end
else
begin
mem0 <= mem30;
end
end
default:
// 已经吃过了所有的药了。
begin
mem0 <= mem___;
end
endcase
end
memory = state_of_start_or_nor ? (really_start ? mem0 : mem) : mem___;
// 设置最后的输出的点阵。
end
always @(posedge clk_800hz or negedge rst)
// 扫描监测。
if(!rst) begin state <= s0; col <= 8'hff; row = 8'hff; end
// 如果按下了重置键以后的操作。
else
begin
// 采取扫描的形式进行状态的监测。
case(state)
// 共有 8 个状态,进行循环的扫描。
s0: begin col <= 8'b1111_1110; row = ~memory[56+:8]; state <= s1; end
s1: begin col <= 8'b1111_1101; row = ~memory[48+:8]; state <= s2; end
s2: begin col <= 8'b1111_1011; row = ~memory[40+:8]; state <= s3; end
s3: begin col <= 8'b1111_0111; row = ~memory[32+:8]; state <= s4; end
s4: begin col <= 8'b1110_1111; row = ~memory[24+:8]; state <= s5; end
s5: begin col <= 8'b1101_1111; row = ~memory[16+:8]; state <= s6; end
s6: begin col <= 8'b1011_1111; row = ~memory[ 8+:8]; state <= s7; end
s7: begin col <= 8'b0111_1111; row = ~memory[ 0+:8]; state <= s0; end
default: begin state <= s0; col <= 8'hff; row = 8'hff; end
endcase
end
// 开机或者关机以及确定修改哪一个位置的药箱的提醒时间。
always@(posedge clk)
// 时钟的上升沿到来。
begin
if(times_ && times0_)
begin
times <= 0;
end
if(key_pulse1)
begin
state_of_start_or_nor <= ~state_of_start_or_nor;
times <= 1;
// 如果发生了开关机,那么就把times置为一(1)。
// 如果按键 1 了,那么就需要翻转状态。
end
else
begin
state_of_start_or_nor <= state_of_start_or_nor;
// 如果没有按键 1 ,状态保持不变。
end
if(state_of_start_or_nor && ~really_start)
// 开机了,但是,并没有完全进入到工作状态的时候执行。
begin
if(key_pulse2)
// 如果按下了按钮 2,那么就需要把设置时间的当前药盒后移一位。
begin
location <= (location + 1) % 3;
// 总共有 3 个药盒,所以 % 3。
end
else
begin
location <= location;
// 没有按键 2 的话,状态保持不变。
end
case(location)
// 判断是哪一个药箱在设置时间。
1'd0:begin
led1 = clkout1; // 闪烁。
// 当前的位置需要,以 2Hz 的频率进行闪烁,我们让其他位置一直亮着不变。
led2 = 0;
led3 = 0;
end
1'd1:begin
led1 = 0;
led2 = clkout1;
// 当前的位置需要,以 2Hz 的频率进行闪烁,我们让其他位置一直亮着不变。
led3 = 0;
end
default:begin
// 这个其实就是只能是最后一个位置了。
led1 = 0;
led2 = 0;
led3 = clkout1;
// 当前的位置需要,以 2Hz 的频率进行闪烁,我们让其他位置一直亮着不变。
end
endcase
end
end
// always@(posedge clk or negedge rst)
// begin
// if(~rst)
// begin
// if(~state_of_start_or_nor && ~really_start)
// begin
//
// if(counting == 0)
// begin
// end
// else
// begin
// counting <= 0;
// end
// // 使得counting为0,这样子的话,下一次开始就也是从0开始了啦,目的是为了保证使用的合理性与方便性。
// end
// end
// end
// 通过按键来决定是否已经正式开始工作了,即就是判断是否开始提醒吃药的功能了。
always@(posedge clk)
begin
if(state_of_start_or_nor)
begin
if(key_pulse5 && ~really_start)
// 按键 5 是控制正式开始工作的。
begin
really_start <= 1;
// times <= 1;
// // 如果发生了正式开始工作,那么就把times置为一(1)。
// if(counting == 0)
// begin
// end
// else
// begin
// counting <= 0;
// end
// // 使得counting为0,这样子的话,下一次开始就也是从0开始了啦,目的是为了保证使用的合理性与方便性。
//设置状态为正式开始工作了,即就是 really_start 由 0 变为了 1。
/**
这个时候,由于已经正式开始工作了,
于是,之前的切换药箱的位置,以及设置提醒的时间的操作就被禁止了。
*/
end
if(key_pulse1 && really_start)
begin
really_start <= 0;
// if(counting == 0)
// begin
// end
// else
// begin
// counting <= 0;
// end
// // 使得counting为0,这样子的话,下一次开始就也是从0开始了啦,目的是为了保证使用的合理性与方便性。
// 如果在正式工作的条件之下,按下了按键 1 ,
// 那么,就需要把正式工作的状态由 0 变为 1,这时,可以保证下一次开机,不会直接工作
end
end
end
// 正式开始工作以后的计数操作。
reg btn_true_or_not1 = 0;
reg btn_true_or_not2 = 0;
reg btn_true_or_not3 = 0;
// 这三个变量是用来判断是否已经吃过药了的。
always@(posedge clkout2)
// 每个一秒钟都会加一次数字,相当于一秒加一次一,一秒加一的计数器啦。
begin
if(state_of_start_or_nor && really_start)
// 如果已经开机了而且是在已经处于正式开始工作了的状态了。
begin
counting <= (counting + 1) % 60;
// 计数。
if(times_)
// 如果发生了开机以及关机的相应的操作,就需要执行以下的代码。
begin
counting <= 2'd00;
// 如果发生了开机以及关机的相应的操作,就需要把counting置为零。
seg_data_10 <= 0;
seg_data_20 <= 0;
times0 <= 1;
// 为了防止一直处于置零的状态,这里必须把 times 置零,这里是表示进行了置零的操作。
end
else
begin
if(times0_)
begin
times0 <= 0;
// 置零,表示整个交互过程的结束。
end
end
// counting <= (counting + 1) % 60;
// // 计数。
// 计算表达的数字应该是多少。
seg_data_10 <= counting / 10;
if(counting % 10 == 0)
begin
seg_data_20 <= 0;
end
else
begin
seg_data_20 <= counting - 10 * seg_data_10;
// 这里与之前的其实是一样的了啦。
end
// 记录究竟是哪一个药箱该拿取药物了。
if(counting >= 0 && counting < first_medicine)
begin
led_position = 2'b11;
// 药盒 1 、 2 、 3 都没有达到取药的条件啦,不取药,因此是不需要提醒的状态。
end
if(counting >= first_medicine && counting < second_medicine)
begin
led_position = 2'b00;
// 药盒 1 需要进行取药。
end
if(counting >= second_medicine && counting < third_medicine)
begin
led_position = 2'b01;
// 药盒 2 需要取药。
end
if(counting >= third_medicine && counting < 61)
begin
led_position = 2'b10;
// 药盒 3 需要取药。
end
end
end
// 由于计时器和闪烁提醒的时间周期不一样,所以说,我们需要把这两个给它独立起来操作,前一个是进行计数器的操作。
// 这里进行闪烁提醒的操作。
always@(posedge clk)
begin
if(state_of_start_or_nor && really_start)
// 如果说是,已经开机了,而且是正式的进入了工作的状态了,就开始执行下面的执行的代码了啦。
begin
case(led_position)
// 如果说是第一个药盒该开始提醒吃药的 。
2'b00:begin
if(btn_true_or_not3)
begin
btn_true_or_not3 = 0;
// 置0 。
end
// 将前一个已经吃过药的状态设置为 0 ,即就是设置上一个 3 为未吃药的状态。
if(~btn_true_or_not1)
// 如果没有吃药。
begin
led10 = clkout3;
led20 = 1;
led30 = 1;
// 该吃药的对应的那个药盒的等闪烁,其他的处于熄灭的状态。
led40 = clkout4;
// 蜂鸣器。
if(key_pulse6)
// 按下了按键 6 ,表示吃药了。
begin
btn_true_or_not1 = 1;
led40 = 1;
// 如果已经吃过药了,那么,设置吃药的状态为 1 ,而且蜂鸣器不再响了。
end
end
else
begin
led10 = 1;
led20 = 1;
led30 = 1;
led40 = 1;
// 虽然前面已经设置过了,但是在写一遍不影响的额。
end
end
// 第二个药盒该吃药了。
2'b01:begin
if(btn_true_or_not1)
begin
btn_true_or_not1 = 0;
// 置0 。
end
// 将前一个已经吃过药的状态设置为 0 ,即就是设置上一个 1 为未吃药的状态。
if(~btn_true_or_not2)
// 如果没有吃药。
begin
led10 = 1;
led20 = clkout3;
led30 = 1;
// 该吃药的对应的那个药盒的等闪烁,其他的处于熄灭的状态。
led40 = clkout4;
// 蜂鸣器。
if(key_pulse6)
// 按下了按键 6 ,表示吃药了。
begin
btn_true_or_not2 = 1;
led40 = 1;
// 如果已经吃过药了,那么,设置吃药的状态为 1 ,而且蜂鸣器不再响了。
end
end
else
begin
led10 = 1;
led20 = 1;
led30 = 1;
led40 = 1;
// 虽然前面已经设置过了,但是在写一遍不影响的额。
end
end
// 第三个药盒该吃药了。
2'b10:begin
if(btn_true_or_not2)
begin
btn_true_or_not2 = 0;
// 置0 。
end
// 将前一个已经吃过药的状态设置为 0 ,即就是设置上一个 2 为未吃药的状态。
if(~btn_true_or_not3)
// 如果没有吃药。
begin
led10 = 1;
led20 = 1;
led30 = clkout3;
// 该吃药的对应的那个药盒的等闪烁,其他的处于熄灭的状态。
led40 = clkout4;
// 蜂鸣器。
if(key_pulse6)
// 按下了按键 6 ,表示吃药了。
begin
btn_true_or_not3 = 1;
led40 = 1;
// 如果已经吃过药了,那么,设置吃药的状态为 1 ,而且蜂鸣器不再响了。
end
end
else
begin
led10 = 1;
led20 = 1;
led30 = 1;
led40 = 1;
// 虽然前面已经设置过了,但是在写一遍不影响的额。
end
end
default:begin
// 其他的情况,也就是说,不需要吃药的情况啦。
led10 = 1;
led20 = 1;
led30 = 1;
led40 = 1;
end
endcase
end
end
// 在开机之后进行设置三个药箱提醒吃药的时间。
always@(posedge clk)
begin
if(state_of_start_or_nor && ~really_start)
// 这里也是开机了,但是没有正式开始工作。
begin
// counting <= 0;
// // 使得counting为0,这样子的话,下一次开始就也是从0开始了啦,目的是为了保证使用的合理性与方便性。
case(location)
1'd0:begin
if(key_pulse3)
begin
first_medicine <= (first_medicine + 1) % 60;
// 如果按下了 3 按键,那么,时间加一。
end
else
begin
if(key_pulse4)
begin
first_medicine <= (first_medicine -1 + 60) % 60;
// 防止减出负数来了。
// 如果按下了 4 按键,那么,时间减一。
end
else
begin
first_medicine <= first_medicine;
// 如果 3 、 4都没有按的话,那么,就保持不变。
end
end
seg_data_1 <= first_medicine / 10;
// 10位数字。
if(first_medicine % 10 == 0)
begin
seg_data_2 <= 0;
// 本来没有必要这么操作,但不知道为啥两种情况综合在一起就无法显示0,所以才分开来了。
end
else
begin
seg_data_2 <= first_medicine - 10 * seg_data_1;
end
// 个位数字。
end
1'd1:begin
if(key_pulse3)
begin
second_medicine <= (second_medicine + 1) % 60;
// 如果按下了 3 按键,那么,时间加一。
end
else
begin
if(key_pulse4)
begin
second_medicine <= (second_medicine -1 + 60) % 60;
// 如果按下了 4 按键,那么,时间减一。
end
else
begin
second_medicine <= second_medicine;
// 如果 3 、 4都没有按的话,那么,就保持不变。
end
end
// 计算两位数字分别是多少。
seg_data_1 <= second_medicine / 10;
if(second_medicine % 10 == 0)
begin
seg_data_2 <= 0;
end
else
begin
seg_data_2 <= second_medicine - 10 * seg_data_1;
end
end
default:begin
// 这个其实就是位置 3 了。
if(key_pulse3)
begin
third_medicine <= (third_medicine + 1) % 60;
// 如果按下了 3 按键,那么,时间加一。
end
else
begin
if(key_pulse4)
begin
third_medicine <= (third_medicine -1 + 60) % 60;
// 如果按下了 4 按键,那么,时间减一。
end
else
begin
third_medicine <= third_medicine;
// 如果 3 、 4都没有按的话,那么,就保持不变。
end
end
// 计算两位数字分别是多少。
seg_data_1 <= third_medicine / 10;
if(third_medicine % 10 == 0)
begin
seg_data_2 <= 0;
end
else
begin
seg_data_2 <= third_medicine - 10 * seg_data_1;
end
end
endcase
end
else
begin
end
end
assign seg_led_1 = state_of_start_or_nor ? (really_start?seg[seg_data_10]:seg[seg_data_1]) : seg[10];
assign seg_led_2 = state_of_start_or_nor ? (really_start?seg[seg_data_20]:seg[seg_data_2]) : seg[10];
// 数码管的显示。
assign rled1=state_of_start_or_nor ? (really_start?led10:led1) :1;
assign rled2=state_of_start_or_nor ? (really_start?led20:led2) :1;
assign rled3=state_of_start_or_nor ? (really_start?led30:led3) :1;
assign rled4=state_of_start_or_nor ? (really_start?led40:led4) :1;
// 总共有三种状态,未开机,开机,正式工作,因此需要有三种方式来进行显示提醒以及时间的内容啦。
endmodule
2、主文件中使用的按键消抖模块
// ********************************************************************
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// ********************************************************************
// File name : debounce.v
// Module name : debounce
// Author : STEP
// Description :
// Web : www.stepfpga.com
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date: |Changes Made:
// V1.0 |2017/03/02 |Initial ver
// --------------------------------------------------------------------
// Module Function:按键消抖
module debounce_button (clk,rst,key,key_pulse);
parameter N = 1; //要消除的按键的数量
input clk;
input rst;
input [N-1:0] key; //输入的按键
output [N-1:0] key_pulse; //按键动作产生的脉冲
reg [N-1:0] key_rst_pre; //定义一个寄存器型变量存储上一个触发时的按键值
reg [N-1:0] key_rst; //定义一个寄存器变量储存储当前时刻触发的按键值
wire [N-1:0] key_edge; //检测到按键由高到低变化是产生一个高脉冲
//利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中
always @(posedge clk or negedge rst)
begin
if (!rst) begin
key_rst <= {N{1'b1}}; //初始化时给key_rst赋值全为1,{}中表示N个1
key_rst_pre <= {N{1'b1}};
end
else begin
key_rst <= key; //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre
key_rst_pre <= key_rst; //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值
end
end
assign key_edge = key_rst_pre & (~key_rst);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平
reg [17:0] cnt; //产生延时所用的计数器,系统时钟12MHz,要延时20ms左右时间,至少需要18位计数器
//产生20ms延时,当检测到key_edge有效是计数器清零开始计数
always @(posedge clk or negedge rst)
begin
if(!rst)
cnt <= 18'h0;
else if(key_edge)
cnt <= 18'h0;
else
cnt <= cnt + 1'h1;
end
reg [N-1:0] key_sec_pre; //延时后检测电平寄存器变量
reg [N-1:0] key_sec;
//延时后检测key,如果按键状态变低产生一个时钟的高脉冲。如果按键状态是高的话说明按键无效
always @(posedge clk or negedge rst)
begin
if (!rst)
key_sec <= {N{1'b1}};
else if (cnt==18'h3ffff)
key_sec <= key;
end
always @(posedge clk or negedge rst)
begin
if (!rst)
key_sec_pre <= {N{1'b1}};
else
key_sec_pre <= key_sec;
end
assign key_pulse = key_sec_pre & (~key_sec);
endmodule
3、主文件中使用的时钟分频模块
// ********************************************************************
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// ********************************************************************
// File name : divide.v
// Module name : divide
// Author : STEP
// Description : clock divider
// Web : www.stepfpga.com
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date: |Changes Made:
// V1.0 |2017/03/02 |Initial ver
// --------------------------------------------------------------------
// Module Function:任意整数时钟分频
module time_split#(parameter WIDTH = 24, parameter N = 12000000)(clk,rst_n,clkout);
input clk,rst_n; //输入信号,其中clk连接到FPGA的C1脚,频率为12MHz
output clkout; //输出信号,可以连接到LED观察分频的时钟
//parameter是verilog里常数语句
// parameter WIDTH = 24; //计数器的位数,计数的最大值为 2**WIDTH-1
// parameter N = 12000000; //分频系数,请确保 N < 2**WIDTH-1,否则计数会溢出
//
reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器
reg clk_p,clk_n; //clk_p为上升沿触发时分频时钟,clk_n为下降沿触发时分频时钟
//上升沿触发时计数器的控制
always @ (posedge clk or negedge rst_n ) //posedge和negedge是verilog表示信号上升沿和下降沿
//当clk上升沿来临或者rst_n变低的时候执行一次always里的语句
begin
if(!rst_n)
cnt_p<=0;
else if (cnt_p==(N-1))
cnt_p<=0;
else cnt_p<=cnt_p+1; //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器
end
//上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50%
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
clk_p<=0;
else if (cnt_p<(N>>1)) //N>>1表示右移一位,相当于除以2去掉余数
clk_p<=0;
else
clk_p<=1; //得到的分频时钟正周期比负周期多一个clk时钟
end
//下降沿触发时计数器的控制
always @ (negedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_n<=0;
else if (cnt_n==(N-1))
cnt_n<=0;
else cnt_n<=cnt_n+1;
end
//下降沿触发的分频时钟输出,和clk_p相差半个时钟
always @ (negedge clk)
begin
if(!rst_n)
clk_n<=0;
else if (cnt_n<(N>>1))
clk_n<=0;
else
clk_n<=1; //得到的分频时钟正周期比负周期多一个clk时钟
end
assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p; //条件判断表达式
//当N=1时,直接输出clk
//当N为偶数也就是N的最低位为0,N(0)=0,输出clk_p
//当N为奇数也就是N最低位为1,N(0)=1,输出clk_p&clk_n。正周期多所以是相与
endmodule
三、后续操作
编译
设置引脚
(注意设置引脚以后需要重新进行编译才可以实现修改项目的操作。)
烧录
四、实验现象
我们仍然采用视频的形式展示实验的现象:
https://www.bilibili.com/video/BV1MY411x7Xw?spm_id_from=333.999.0.0
Verilog HDL实现智能药盒
五、总结
以上就是使用Verilog HDL来实现智能药盒的全部流程,希望对大家有一些帮助啦。
最后,谢谢大家的阅读与支持了啦。