概述
程序模块图
源文件分析
主程序
entity main is
port(
clk_system,btn3,btn0,btn7,btn6:in std_logic;
col_r,col_g,row,which_digit,digit_mask:out std_logic_vector(7 downto 0);
ld6,ld1,ld2,ld3: out std_logic
);
end main;
- 输入为系统时钟,4个按键。
- 输出为点阵,数码管和LED灯。
a1:elimination_buffeting port map(clk_system,btn0,btn3,btn6,btn7,btn0_elimit,btn3_elimit,btn6_elimit,btn7_elimit);
a2:clk_division port map(clk_system,clk_half_hz,clk_1hz,clk_2hz,clk_const);
a3:mode_switch port map(clk_const,clk_half_hz,clk_1hz,clk_2hz,btn7_elimit,btn0_elimit,btn3_elimit,clk_chosen,countdown_temp,cur_mode);
a4:ventilator port map(clk_chosen,clk_system,col_r,col_g,row);
a5:digital_display port map(countdown_temp,which_digit,digit_mask);
a6:light port map(cur_mode,btn6_elimit,ld6,ld1,ld2,ld3);
- 模块调用顺序与上小节模块图相同。
- 中枢模块mode_switch将消抖后的按键输入信号和分频后的时钟信号进行处理,向负责控制输出的程序输出时钟信号、档位状态和倒计时状态,最后控制输出的程序进行输出。
按键防抖
entity elimination_buffeting is
port(
clk_system:in std_logic;
btn0,btn3,btn6,btn7 : in std_logic;
btn0_elimit,btn3_elimit,btn6_elimit,btn7_elimit: out std_logic :='0'
);
end elimination_buffeting;
architecture a of elimination_buffeting is
signal cnt0 : integer range 0 to 10000;
signal cnt3 : integer range 0 to 10000;
signal cnt6 : integer range 0 to 10000;
signal cnt7 : integer range 0 to 10000;
begin
p1:process(btn0,clk_system)
begin
if btn0 ='1' then
if rising_edge(clk_system) then
if cnt0 = 10000 then
cnt0 <= cnt0;
else cnt0 <= cnt0+1;
end if;
if cnt0<10000 then btn0_elimit <= '0';
else btn0_elimit <='1';
end if;
end if;
else
cnt0 <= 0;
btn0_elimit <= '0';
end if;
end process p1;
end a;
- 消抖思路:通过计数器计数10ms(一般抖动持续时间为10ms-20ms左右),若'1'信号可持续10ms以上,则可以确认按下了按键,进而可以将'1'信号输出。
分频(0.5hz,1hz,2hz,'1'信号)
entity clk_division is
port(
clk: in std_logic;
clk_out_half_hz, clk_out_1hz, clk_out_2hz, clk_const: out std_logic
);
end clk_division;
- 输入为系统时钟(1Mhz),输出为四个时钟,用来对应风扇的不同档位。
p1:process(clk) -- in one process, no tow edge can be used
begin
if rising_edge(clk) then -- at edge -> no else
if cnt_half = 2000000 then
cnt_half <= 0;
elsif cnt_1hz = 1000000 then
cnt_1hz <= 0;
elsif cnt_2hz = 500000 then
cnt_2hz <= 0;
else
cnt_half <= cnt_half + 1;
cnt_1hz <= cnt_1hz + 1;
cnt_2hz <= cnt_2hz + 1;
end if;
end if;
end process p1;
p2:process(cnt_half)
begin
if cnt_half < 1000000 then
clk_out_half_hz<='0';
else clk_out_half_hz<='1';
end if;
end process p2;
- 由于输出1信号不需要计数器,因此定义三个signal变量用以计数即可。
- 计算系统上升沿数量,使要得到的信号前半周期输出0,后半周期输出1。
档位控制(核心)
entity mode_switch is
port(
clk_const: in std_logic;
clk_half_hz: in std_logic;
clk_1hz: in std_logic; -- clk_1hz as delayed sig
clk_2hz: in std_logic;
btn7, btn0, btn3: in std_logic;
clk_out: out std_logic;
count_down: out std_logic_vector(2 downto 0) := "000";
cur_mode: out std_logic_vector(1 downto 0) := "00"
);
end mode_switch;
- 输入四个不同频率的时钟信号,输出档位对应的时钟信号(传至控制点阵的程序)。
- 输入三个与档位切换有关的按键(消抖后)。
- count_down表示倒计时的状态,由于倒计时有7个状态,因此需要3位二进制数表示(输出至控制数码管的程序)。
- cur_mode表示当前档位,由于档位有4个状态,因此需要2位二进制数表示(输出至控制LED灯的程序)。
signal mode: integer range 0 to 3;
signal count_down_buf: integer range 0 to 6:= 0;
signal is_counting: std_logic;
- mode用以代表档位,选择输出的时钟。
- count_down_buf表示倒计时状态。
- is_counting表示是否在倒计时。
p1: process(btn0, btn3, btn7, count_down_buf)
begin
if btn0 = '1' then
mode <= 0;
is_counting <= '0';
elsif btn3 = '1' then
is_counting <= '1';
elsif count_down_buf = 6 and is_counting = '1' then
mode <= 0;
is_counting <= '0';
elsif rising_edge(btn7) and is_counting = '0' then
if mode = 3 then
mode <= 0;
else
mode <= mode + 1;
end if;
end if;
end process p1;
- 若按下复位键,则将档位置为空档。
- 若按下延时复位键,则开启倒计时,待倒计时6s后将档位置为空档。
- 若按下换档键,且当前不在倒计时,则档位变化为下一个档位,也就是说,倒计时期间无法换档,实现预期功能。
p2: process(clk_1hz, is_counting, btn0)
begin
if btn0 = '1' then
count_down_buf <= 0;
elsif rising_edge(clk_1hz) then
if is_counting = '1' then
count_down_buf <= count_down_buf + 1;
elsif count_down_buf = 6 then
count_down_buf <= 0;
end if;
end if;
end process p2;
- 使用1hz时钟计数,每一个上升沿倒计时状态变化一次。
p3: process(mode)
begin
case mode is
when 0 =>
clk_out <= clk_const;
when 1 =>
clk_out <= clk_2hz;
when 2 =>
clk_out <= clk_1hz;
when 3 =>
clk_out <= clk_half_hz;
end case;
end process p3;
p4: process(count_down_buf)
begin
count_down <= std_logic_vector(to_unsigned(count_down_buf, count_down'length));
end process p4;
p5: process(mode)
begin
cur_mode <= std_logic_vector(to_unsigned(mode, cur_mode'length));
end process p5;
点阵
entity ventilator is
port(
clk_switch, clk_system:in std_logic;
col_r:out std_logic_vector(7 downto 0);
col_g:out std_logic_vector(7 downto 0);
row:out std_logic_vector(7 downto 0)
);
end ventilator;
- 输入两个时钟,一个用于获取上升沿切换点阵画面,另一个用于行扫描。
- col_r,col_g 分别用于控制亮红灯和亮绿灯。
- row控制亮哪一行。
signal row_num:integer range 0 to 7;
signal state_int: integer range 0 to 3;
signal state_vec: std_logic_vector(1 downto 0);
process(clk_switch)
begin
if rising_edge(clk_switch) then
if state_int = 3 then
state_int <= 0;
else
state_int <= state_int + 1;
end if;
end if;
end process;
process(clk_system) ------------对点阵8行进行高频重复扫描
begin
if rising_edge(clk_system) then
if row_num = 7 then
row_num <= 0;
else
row_num <= row_num + 1;
end if;
end if;
end process;
- 检测到低频时钟上升沿后切换画面状态。
- 检测到高频时钟上升沿后扫描下一行。
state_vec <= std_logic_vector(to_unsigned(state_int, state_vec'length));
case state_vec is
when "00"=>
case row_num is
when 0=>
col_r<="00000001";
col_g<="11100000";
row<="01111111";
when 1=>
col_r<="00000011";
col_g<="01100000";
row<="10111111";
when 2=>
col_r<="00000111";
col_g<="00100000";
row<="11011111";
when 3=>
col_r<="00001000";
col_g<="00010000";
row<="11101111";
when 4=>
col_r<="00010000";
col_g<="00001000";
row<="11110111";
when 5=>
col_r<="11100000";
col_g<="00000100";
row<="11111011";
when 6=>
col_r<="11000000";
col_g<="00000110";
row<="11111101";
when 7=>
col_r<="10000000";
col_g<="00000111";
row<="11111110";
end case;
数码管
entity digital_display is
port(
count_down: in std_logic_vector(2 downto 0);
which_digit: out std_logic_vector(7 downto 0);
digit_mask: out std_logic_vector(7 downto 0) := "11110111"
);
end digital_display;
- count_down表示倒计时状态(来自档位控制程序)。
- which_digit表示8段数码管如何点亮以显示不同的数字。
- digit_mask用以指定亮哪些数码管,本程序为亮DISP3。
architecture a of digital_display is
signal which_digit_temp: std_logic_vector(7 downto 0);
begin
process(count_down)
begin
case count_down is
when "000"=>
which_digit_temp<="00000000";
when "001"=>
which_digit_temp<="10111110";
when "010"=>
which_digit_temp<="10110110";
when "011"=>
which_digit_temp<="01100110";
when "100"=>
which_digit_temp<="11110010";
when "101"=>
which_digit_temp<="11011010";
when "110"=>
which_digit_temp<="01100000";
when others =>
which_digit_temp<="00000000";
end case;
which_digit <= which_digit_temp;
end process;
end a;
- 当不处于倒计时或计时结束时数码管不亮。
- 状态001对应数字6,状态010对应数字5,以此类推。
LED灯(照明,档位显示)
entity light is
port(
cur_mode:in std_logic_vector(1 downto 0);
btn6 : in std_logic;
ld6,ld1,ld2,ld3: out std_logic
);
end light;
- cur_mode表示当前档位(来自档位控制程序)。
- 输入按键,输出为四个LED灯,其中一个用于照明,三个用于档位显示。
signal lightflag : integer range 0 to 1:=0;
begin
p1:process(btn6)
begin
if rising_edge(btn6) then
if lightflag = 0 then lightflag <= 1;
else
lightflag <=0;
end if;
end if;
end process p1;
p2:process(lightflag)
begin
if lightflag = 1 then ld6 <= '1';
else ld6 <= '0';
end if;
end process p2;
- signal型变量lightflag用来控制是否应该亮照明灯(ld6)。
p3:process(cur_mode)
begin
case cur_mode is
when "00"=>
ld1 <= '0';
ld2 <= '0';
ld3 <= '0';
when "01"=>
ld1 <= '1';
ld2 <= '1';
ld3 <= '1';
when "10"=>
ld1 <= '1';
ld2 <= '1';
ld3 <= '0';
when "11"=>
ld1 <= '1';
ld2 <= '0';
ld3 <= '0';
end case;
end process p3;
- 不同档位对应不同亮灯(空档不亮,大档亮3个灯,中档2个,小档1个)。
问题及其解决
程序逻辑复杂
- 采取分模块的方法,将每部分分开编写,最后通过一个主程序调用各模块实现功能。
按键消抖
- 问题:由于前期不太理解按键消抖原理,故未编写按键消抖程序,导致实验时经常无法判断功能是否可以正常实现。
- 解决方法:采取使用拨片开关代替按键开关的方法对其他功能进行调试,将其他功能调试完成后再编写消抖程序进行实验,效率大幅提升。
点阵刷新问题
- 问题:实验前期使用的是1khz的系统时钟,在有些板子上会出现某个档位时点阵无法完全显示风扇四个状态的情况,即有时点阵只会在两种画面间切换,且仔细代码未发现问题。
- 解决方法:不断进行尝试和询问老师后意识到可能是扫描频率的问题,因此将系统时钟改为1Mhz并修改分频器和防抖后问题得到解决。