该可编程器件实验板是以 Altera 公司的 MAX II 系列可编程器件
EPM1270T144C5 为核心芯片,是一款具有多种外部接口和显示器件的通用数字电路实验平台。选用1kHz。
一、 设计课题的任务要求
1、 智能药盒有三个药格,分别对应老年人每天早中晚的三次服药,每个药格有对应的提醒灯。按下BTN0 键进入开机初始状态,三个药格的提醒灯在 8×8 点阵中显示红、绿、黄图案。
2、 三个药格可通过 BTN7 键切换设定服药时间,切换到哪个药格,相应药格的提醒图案以 2Hz 的频率进行闪烁显示。服药时间的设定采用 BTN6 和 BTN5 键,BTN6 每按 1 下,时间加 1 秒,BTN5 每按 1 下,时间减 1 秒,每个药格可设定一个时间,设定时间范围在 0~59 秒之间,在设定过程中采用数码管 DISP1 和 DISP0 显示所设定的服药时间。
3、 服药时间设定结束后,按下智能药盒工作键 BTN2 后,药格提醒图案停止闪烁,药 盒开始工作,并从 0 开始进行 60 秒计时,数码管 DISP1 和 DISP0 显示计时时间。当计时计到各个药格所设定的服药时间时,点阵上相应药格的提醒图案以 4Hz 的频率进行闪烁提醒,同时蜂鸣器报警,按 BTN1 键后报警和闪烁停止。
4、 智能药盒计到 60 秒后重复开始服药计时和提醒,直到按下 BTN0 键后智能药盒关闭,点阵全灭,计时停止。
提高要求:
1、 用 4*4 小键盘设定服药时间;
2、 采用液晶 LCD1602 显示设定的服药时间;
3、 增加高级模式,每个药格可设定 1~2个服药时间进行提醒; 4、 自拟其他功能。
二、 系统设计
设计思路
该智能药盒类似于一个闹钟,也就是计时器。核心内容就是设定几个时间,然后计时到所设定的时间时报警。8×8 点阵用于显示药格,数码管显示时间,蜂鸣器报警。
总体框图
分块设计
1). 计时与计数:这是实验的核心内容。由于计时和计数都涉及到对时间的操作,因此这两个功能必须集合到同一个模块并且是同一个always块里。刚开机后,药盒应处于计数状态,用来设定时间;按下BTN2后进入计时状态,开始60秒循环计时。所以使用一个work信号来控制计时与计数的切换。work初始为0,对应计数状态,按BTN2使work翻转,状态切换。计数时按BTN4获取当前时间值并输出,在这个模块的外部会有其他模块负责将这个获取的时间存起来。这里需要注意获取时间和存储时间的先后问题,否则会存入无效时间。因此,选择在时钟上升沿获取时间,然后在按键信号下降沿存储,二者相差一个时钟周期。
2). 8*8点阵显示:点阵用红、绿、黄分别表示3个药格。在设定时间以及报警时都要求对应的点阵要闪烁,因此,它可以分为4种状态:都不闪,红色闪,绿色闪,黄色闪。这样我们就可以用一个2位的二进制数来表示它们:00,01,10,11.同时选用BTN7驱动模4计数器,按一下,二进制数加一,状态切换,这样就实现了药格切换。药格的显示是采用行扫描的形式,由于只扫描2行,所以将行扫描信号和时钟信号绑定,当时钟为高电平时,扫第3行,时钟为低电平时扫第4行,这样之后就不用重复对行信号进行处理了。
3). 数码管显示:由于只用两个数码管,所以将数码管的阴极信号与时钟绑定,当时钟为高电平时,亮十位的数码管,同时阳极信号赋值为时间的十位,时钟为低电平时亮个位的数码管,同时阳极信号赋值为时间的个位,这样就完成了数码管的显示。
三、 功能说明及资源利用情况
1.功能说明
该药盒实现了课题要求的基本功能,包括按BTN0开机关机,按BTN7切换设定服药时间,切换时,对应的药格会以2Hz闪烁。按BTN6和BTN5可分别实现加计数和减计数,按下BTN4即可保存时间。之后,按下BTN2便可进入计时工作状态。药盒能够循环进行60秒计时,当到达设定的时间后,对应药格4Hz闪烁,同时蜂鸣器报警。报警和闪烁可按下BTN1手动关闭,或者计时到60秒后自动关闭。按下BTN2又可切换到设定时间状态,设定新的时间。再按下BTN0后,药盒关机,数据清空。此外,还实现了提高功能之一,即每个药盒可以设定一到两个时间。
2.资源利用情况
总共使用了200个逻辑门和50个管脚。
四、 故障及问题分析
1.在代码编写过程中,运行代码调试时,经常报错说重复赋值。比如计数与计时重复对时间负值;关机将数据清零时,又对这些数据重复进行了赋值。所以,需要将这些对变量的赋值都综合到对应的同一个模块里,以此避免冲突。
2.在测试功能的时候,发现保存的时间总是会出现错位。分析发现是获取时间与存储时间同时进行导致的。在获取时间的同时存时间,存储的并不是获取的时间,而是初始化的0时间,然后在下一次存时间时,才会存储这次获取的时间,也就是发生了错位。因此,选择在时钟上升沿获取时间,然后在按键信号下降沿存储,二者相差一个时钟周期。这样错开后,功能正常实现。
3.在测试功能时,发现在刚开始计时时就报警。分析发现,原本能设置两个时间的药格,如果只设置一个时间,剩下的那个时间就会保持初始化的0时间,这样刚开始计时就会报警。因此,增加一个判断条件,来取消对初始0时间的报警。
module imbox( //上层模块
input clk, //时钟信号,需选用1kHz
input btn0, //开机关机键
input btn1, //关闭报警键
input btn2, //开始或停止计时
input btn4, //保存时间
input btn5, //减计数键
input btn6, //加计数键
input btn7, //切换药格键
output [7:0]col_r, //8*8点阵的列信号,红色
output [7:0]col_g, //8*8点阵的列信号,绿色
output beep, //蜂鸣器
output [7:0] seg, //数码管阳极信号
output reg [7:0] row, //8*8点阵的行信号,用于进行行扫描
output reg [7:0] cat, //数码管阴极信号
output led //led灯,在BTN4按下时响应
);
wire [1:0] change; //药格切换信号
reg [1:0] remind; //报警状态下的药格信号
wire clk_2, clk_4,clk_500; //不同频率的时钟
wire key0, key1, key2, key4, key5, key6, key7; //消抖后的按键信号
reg clk_2_r,clk_500_r, work,start,rmd; //生成时钟的中间量;工作、开始、提醒信号
wire [5:0] total; //计数与计时
wire [5:0] time_e; //获取设定时间
reg [11:0] timee [2:0]; //存储设定时间
reg [3:0] data; //数码管显示的数据
wire [3:0] data1, data2; //时间的十位和个位
always@(posedge key0) //开机于关机
begin
start<=~start;
end
divide d0(clk,clk_4); //时钟分频
always@(posedge clk_4)
clk_2_r<=~clk_2_r;
assign clk_2=clk_2_r;
always@(posedge clk)
clk_500_r<=~clk_500_r;
assign clk_500=clk_500_r;
always@(clk)
if(~start) //关机状态
begin
row<=8'b11111111;
cat<=8'b11111111;
end
else if(clk) //将row,cat,data与时钟绑定,随时钟变化而自动扫描
begin
row<=8'b11101111;
cat<=8'b11111101;
data<=data1;
end
else
begin
row<=8'b11110111;
cat<=8'b11111110;
data<=data2;
end
always@(negedge clk)
if(~start)
work<=0;
else if(key2) //非工作工作(计数与计时切换)
work<=~work;
else
work<=work;
always@(negedge key4 or posedge key0)
if(key0)
begin
timee[0][11:0]<=0;
timee[1][11:0]<=0;
timee[2][11:0]<=0;
end
else if(change>0)
timee[change-1][11:0]<={timee[change-1][5:0],time_e}; //移位寄存设定的时间
//按键消抖
debounce d1(clk,{btn0,btn1,btn2,btn4,btn5,btn6,btn7},{key0,key1,key2,key4,key5,key6,key7});
//切换药格
counter4 c1(start,key7,change);
//8*8点阵显示
show s0(col_r,col_g,clk,clk_500,((work&rmd)?clk_4:clk_2),((work&rmd)?remind:change));
//计数与计时
timer t0(start,work,clk,key6,key5,key4,key2,led,total,time_e);
//计算十位和个位
div_rill d2(total,data1,data2);
//数码管显示
segment s1(clk,data,seg);
//控制报警
always@(posedge clk )
if(key1|key0|(~start)|(~work))
rmd<=0;
else
case(total)
timee[0][11:6]:begin remind<=2'b01;rmd<=timee[0][11:6];end//
timee[0][5:0] :begin remind<=2'b01;rmd<=1;end
timee[1][11:6]:begin remind<=2'b10;rmd<=timee[1][11:6];end//
timee[1][5:0] :begin remind<=2'b10;rmd<=1;end
timee[2][11:6]:begin remind<=2'b11;rmd<=timee[2][11:6];end//
timee[2][5:0] :begin remind<=2'b11;rmd<=1;end
default:begin remind<=remind;rmd<=rmd;end
endcase
assign beep=rmd&clk; //蜂鸣器报警
endmodule
//计数与计时
module timer (
input start, //开始信号(开机或关机)
input work, //工作信号(计数或计时)
input clk1, //时钟
input key6, //加计数
input key5, //减计数
input key4, //保存时间
input key2, //切换计数与计时
output reg led, //用于响应key4
output reg [5:0]total, //时间
output reg [5:0]time_e //获取设定的时间用以保存
);
reg [9:0] cnt;
always @(posedge clk1)
if((~start)|key2)
total<=6'b000000;
else if(~work)
begin
if (key6&(total<59))
total <= total+1'b1; //计数
else if (key5&(total>0))
total <= total-1'b1;
else if(key4&(total>0))
begin
time_e<=total; //获取设定的时间用以保存
led<=key4;
end
else
begin
total <= total;
led<=0;
end
end
else if (cnt==999)
begin
cnt<=0;
if(total==60)
total<=1'b0;
else
total<=total+1'b1; //计时
end
else
cnt<=cnt+1;
endmodule
//数码管显示
module segment(
input clk, //时钟
input [3:0]data, //数码管阳极
output reg [7:0]seg //数码管
);
always @(clk)
case (data) //将阳极的数据译码显示为数字
4'b0000: begin seg<=8'h3f;end
4'b0001: begin seg<=8'h06;end
4'b0010: begin seg<=8'h5b;end
4'b0011: begin seg<=8'h4f;end
4'b0100: begin seg<=8'h66;end
4'b0101: begin seg<=8'h6d;end
4'b0110: begin seg<=8'h7d;end
4'b0111: begin seg<=8'h07;end
4'b1000: begin seg<=8'h7f;end
4'b1001: begin seg<=8'h6f;end
endcase
endmodule
//除法和取余(二进制转十进制)
module div_rill
(
input [5:0] a, //需要处理的二进制数
output reg [3:0] yshang, //十位(除以十后的商)
output reg [3:0] yyushu //个位(除以十后的余数)
);
reg[5:0] tempa;
reg[8:0] temp_a;
integer i;
always @(a )
tempa <= a;
always @(tempa)
begin
temp_a = {3'b000,tempa};
for(i = 0;i < 3;i = i + 1)
begin
temp_a = {temp_a[7:0],1'b0};
if(temp_a[8:3] >= 4'b1010)
temp_a = temp_a - 7'b1010000 + 1'b1;
else
temp_a = temp_a;
end
yshang <= {1'b0,temp_a[2:0]};
yyushu <= temp_a[6:3];
end
endmodule
//8*8点阵显示
module show(
output reg[7:0]col_r, //8*8点阵的列信号,红色
output reg[7:0]col_g, //8*8点阵的列信号,绿色
input clk, //1kHz时钟
input clk500, //500Hz,让橙色和绿色交替,从而调出黄色
input clkout, //闪烁时钟,调用时的参数可在2Hz和4Hz间切换
input [1:0]cnt //药格信号,切换药格
);
always @ (clk)
if(clkout)
case (clk500)
0: begin col_r<=8'b00000011;col_g<=8'b11011000; end //绿|绿|红
1: begin col_r<=8'b11000011;col_g<=8'b11011000; end //橙|绿|红(上面的绿色与下面的橙色叠加出黄色)
endcase
else
case(cnt)
2'b00:begin
case (clk500) //初始状态
0: begin col_r<=8'b00000011;col_g<=8'b11011000; end
1: begin col_r<=8'b11000011;col_g<=8'b11011000; end
endcase
end
2'b01:begin
case (clk500) //红灯闪烁
0: begin col_r<=8'b00000000;col_g<=8'b11011000; end
1: begin col_r<=8'b11000000;col_g<=8'b11011000; end
endcase
end
2'b10:begin
case (clk500) //绿灯闪烁
0: begin col_r<=8'b00000011;col_g<=8'b11000000; end
1: begin col_r<=8'b11000011;col_g<=8'b11000000; end
endcase
end
2'b11:begin
case (clk500) //黄灯闪烁
0: begin col_r<=8'b00000011;col_g<=8'b00011000; end
1: begin col_r<=8'b00000011;col_g<=8'b00011000; end
endcase
end
endcase
endmodule
//模4计数(分别对应3个药格和初始状态,用来控制切换药格)
module counter4 (
input start, //开机信号
input btn, //按键计数
output reg [1:0]count //模4计数
) ;
always @ (posedge btn or negedge start)
if(~start)
count<=2'b00;
else if(count==2'b11)
count<=2'b00;
else
count<=count+1'b1;
endmodule
//时钟分频
module divide (clk,clkout);
input clk; //输入时钟
output clkout; //输出时钟
reg [6:0] cnt_p; //cnt_p为上升沿触发的计数器
reg clk_p; //clk_p为上升沿触发的分频时钟
always @ (posedge clk )
begin
if (cnt_p==124) //250分频,1kHz转4Hz
begin
cnt_p<=0;
clk_p<=~clk_p;
end
else
cnt_p<=cnt_p+1;
end
assign clkout = clk_p;
endmodule
//按键消抖
module debounce (
input clk, //时钟
input [6:0] key, //需要消抖的按键
output [6:0] key_pulse //消抖后的按键信号
);
reg [6:0] key_rst_pre; //按键前一状态
reg [6:0] key_rst; //按键当前状态
wire [6:0] key_edge; //按键上升沿检测
reg [6:0] key_sec_pre; //按键前一电平状态
reg [6:0] key_sec; //按键后一电平状态
reg [3:0] cnt; //计数
always @(posedge clk )
begin
key_rst <= key;
key_rst_pre <= key_rst;
end
assign key_edge = (~key_rst_pre) & key_rst; //检测上升沿
always @(posedge clk )
begin
if(key_edge)
cnt <= 4'b0000;
else
cnt <= cnt + 1'b1;
end
always @(posedge clk)
if (cnt==4'b1010) //延时10ms后检测电平
key_sec <= key;
else
key_sec<=key_sec;
always @(posedge clk)
key_sec_pre <= key_sec;
assign key_pulse = (~key_sec_pre) & key_sec;
endmodule