I am using a state machine to create a countdown timer that on startup, displays 00:00 and when Key1 is pressed, you can input a time by incrementing/decrementing minutes by 1 and if the up/down button is held for 5 cycles it will go up/down by 5. Thanks to some awesome help (@DavidKoontz) I have finished the code. There is no need to debounce the buttons in my code b/c my altera board seems to pick up the low signals just fine.Since I am only using one clock the buttons react slowly b/c the Clock is set to 1Hz.
Library ieee;
USE ieee.std_logic_1164.ALL;
ENTITY CountDownTimer IS
PORT(
CLK,RESET: IN STD_LOGIC;
a1, b1, c1, d1, e1, f1, g1 : OUT STD_LOGIC;
a2, b2, c2, d2, e2, f2, g2 : OUT STD_LOGIC;
a3, b3, c3, d3, e3, f3, g3 : OUT STD_LOGIC;
a4, b4, c4, d4, e4, f4, g4 : OUT STD_LOGIC;
--All 4 buttons for timer
BUTTON0, BUTTON1, BUTTON2, BUTTON3: IN STD_LOGIC;
--LEDR9
ON_OFF_LED: OUT BIT;
--LEDR9-R6
INPUT_LED1, INPUT_LED2, INPUT_LED3, INPUT_LED4: OUT BIT;
--LEDR0
DONE_LED: OUT BIT);
END CountdownTimer;
ARCHITECTURE Counter OF CountDownTimer IS
--Define state machine
TYPE STATE_TYPE IS (A_ON_OFF, B_INPUT, C_COUNTDOWN, D_DONE);
SIGNAL State : STATE_TYPE;
--Buttons produce 0 when pressed, signal for 1 when pressed
SIGNAL B3D0, B3D1, B3D2, B3D3: STD_LOGIC := '0';
SIGNAL B2D0, B2D1, B2D2, B2D3: STD_LOGIC := '0';
SIGNAL CLOCK: STD_LOGIC := '0';
SIGNAL Count: INTEGER:= 1;
--SIGNAL for range of integer values
SIGNAL Minute1 : INTEGER RANGE 0 TO 6;
SIGNAL Minute2 : INTEGER RANGE 0 TO 9;
SIGNAL Second1 : INTEGER RANGE 0 TO 5;
SIGNAL Second2 : INTEGER RANGE 0 TO 9;
--Output for the seven segment displays
SIGNAL OUTPUT_HEX0 : STD_LOGIC_VECTOR(6 DOWNTO 0);
SIGNAL OUTPUT_HEX1 : STD_LOGIC_VECTOR (6 DOWNTO 0);
SIGNAL OUTPUT_HEX2 : STD_LOGIC_VECTOR (6 DOWNTO 0);
SIGNAL OUTPUT_HEX3 : STD_LOGIC_VECTOR (6 DOWNTO 0);
SIGNAL B3_HOLD: STD_LOGIC := '0'; --Gets 1 if button3 was held
SIGNAL B2_HOLD: STD_LOGIC := '0'; --Gets 1 is button2 was held
BEGIN
--Segment 1 display pins
a1 <= OUTPUT_HEX0(6);
b1 <= OUTPUT_HEX0(5);
c1 <= OUTPUT_HEX0(4);
d1 <= OUTPUT_HEX0(3);
e1 <= OUTPUT_HEX0(2);
f1 <= OUTPUT_HEX0(1);
g1 <= OUTPUT_HEX0(0);
--Segment 2 display pins
a2 <= OUTPUT_HEX1(6);
b2 <= OUTPUT_HEX1(5);
c2 <= OUTPUT_HEX1(4);
d2 <= OUTPUT_HEX1(3);
e2 <= OUTPUT_HEX1(2);
f2 <= OUTPUT_HEX1(1);
g2 <= OUTPUT_HEX1(0);
--Segment 3 display pins
a3 <= OUTPUT_HEX2(6);
b3 <= OUTPUT_HEX2(5);
c3 <= OUTPUT_HEX2(4);
d3 <= OUTPUT_HEX2(3);
e3 <= OUTPUT_HEX2(2);
f3 <= OUTPUT_HEX2(1);
g3 <= OUTPUT_HEX2(0);
--Segment 4 display pins
a4 <= OUTPUT_HEX3(6);
b4 <= OUTPUT_HEX3(5);
c4 <= OUTPUT_HEX3(4);
d4 <= OUTPUT_HEX3(3);
e4 <= OUTPUT_HEX3(2);
f4 <= OUTPUT_HEX3(1);
g4 <= OUTPUT_HEX3(0);
WITH Second2 SELECT
--One's second place, 0 to 9
OUTPUT_HEX0 <= "0000001" WHEN 0,
"1001111" WHEN 1,
"0010010" WHEN 2,
"0000110" WHEN 3,
"1001100" WHEN 4,
"0100100" WHEN 5,
"0100000" WHEN 6,
"0001101" WHEN 7,
"0000000" WHEN 8,
"0001100" WHEN 9,
"0000001" WHEN OTHERS;
WITH Second1 SELECT
--Tens second place, 0 to 5
OUTPUT_HEX1 <= "0000001" WHEN 0,
"1001111" WHEN 1,
"0010010" WHEN 2,
"0000110" WHEN 3,
"1001100" WHEN 4,
"0100100" WHEN 5,
"0000001" WHEN OTHERS;
WITH Minute2 SELECT
--Ones minute place, 0 to 9
OUTPUT_HEX2 <= "0000001" WHEN 0,
"1001111" WHEN 1,
"0010010" WHEN 2,
"0000110" WHEN 3,
"1001100" WHEN 4,
"0100100" WHEN 5,
"0100000" WHEN 6,
"0001101" WHEN 7,
"0000000" WHEN 8,
"0001100" WHEN 9,
"0000001" WHEN OTHERS;
WITH Minute1 SELECT
--Tens minute place, 0 to 6
OUTPUT_HEX3 <= "0000001" WHEN 0,
"1001111" WHEN 1,
"0010010" WHEN 2,
"0000110" WHEN 3,
"1001100" WHEN 4,
"0100100" WHEN 5,
"0100000" WHEN 6,
"0000001" WHEN OTHERS;
PROCESS(CLK)
BEGIN
IF RISING_EDGE(CLK) THEN
Count <= Count + 1;
IF (Count = 30000000) THEN
CLOCK <= NOT(CLOCK);
Count <= 1;
END IF;
END IF;
END PROCESS;
PROCESS(CLOCK)
BEGIN
IF RISING_EDGE(CLOCK) THEN
B3D0 <= BUTTON3;
B3D1 <= NOT B3D0;
B3D2 <= B3D1;
B3D3 <= B3D2;
B2D0 <= BUTTON2;
B2D1 <= NOT B2D0;
B2D2 <= B2D1;
B2D3 <= B2D2;
B3_HOLD <= B3D1 AND B3D2 AND B3D3;
B2_HOLD <= B2D1 AND B2D2 AND B2D3;
END IF;
END PROCESS;
PROCESS(CLOCK)
BEGIN
IF RESET = '1' THEN --Async Reset
State <= A_ON_OFF;
ELSIF RISING_EDGE(CLOCK) THEN
CASE State IS
---------------------------------A_ON_OFF---------------------------------
WHEN A_ON_OFF =>
--Red LED9
ON_OFF_LED <= '1';
Minute1 <= 0;
Minute2 <= 0;
Second1 <= 0;
Second2 <= 0;
IF (BUTTON0 = '0') THEN
ON_OFF_LED <= '0';
State <= B_INPUT;
END IF;
---------------------------------B_INPUT/PAUSE---------------------------------
WHEN B_INPUT =>
--Light up LEDs
INPUT_LED1 <= '1';
INPUT_LED2 <= '1';
INPUT_LED3 <= '1';
INPUT_LED4 <= '1';
IF (Minute1 = 6) THEN
Minute2 <= 0;
Second1 <= 0;
Second2<= 0;
State <= B_INPUT;
END IF;
--Count up button
IF (BUTTON3 = '0' AND B3_HOLD = '0') THEN
IF (Minute1 = 6 AND Minute2 >= 0) THEN
Minute1 <= 0;
Minute2 <= 1;
Second1 <= 0;
Second2 <= 0;
State <= B_INPUT;
ELSIF (Minute2 < 9) THEN
Minute2 <= (Minute2 + 1);
State <= B_INPUT;
ELSIF (Minute2 = 9) THEN
Minute1 <= (Minute1 + 1);
Minute2 <= 0;
State <= B_INPUT;
END IF;
END IF;
IF (BUTTON3 = '0' AND B3_HOLD = '1') THEN
IF (Minute1 = 6 AND Minute2 >= 0) THEN
Minute1 <= 0;
Minute2 <= 5;
Second1 <= 0;
Second2 <= 0;
State <= B_INPUT;
ELSIF (Minute2 < 5) THEN
IF (Minute2 = 0) THEN
Minute2 <= (Minute2 + 5);
State <= B_INPUT;
ELSE
Minute2 <= (Minute2 + 1);
State <= B_INPUT;
END IF;
ELSIF (Minute2 = 5) THEN
Minute2 <= 0;
Minute1 <= (Minute1 + 1);
State <= B_INPUT;
ELSIF (Minute2 > 5) THEN
IF (Minute2 = 9) THEN
Minute2 <= 0;
Minute1 <= (Minute1 + 1);
State <= B_INPUT;
ELSE
Minute2 <= (Minute2 + 1);
State <= B_INPUT;
END IF;
END IF;
END IF;
--Count down button
IF (BUTTON2 = '0' AND B2_HOLD = '0') THEN
IF ((Minute1 = 0) AND (Minute2 = 0)) THEN
Minute1 <= 6;
Minute2 <= 0;
Second1 <= 0;
Second2 <= 0;
ELSIF (Minute2 = 0) THEN
Minute2 <= 9;
Minute1 <= (Minute1 - 1);
ELSE
Minute2 <= (Minute2 - 1);
END IF;
State <= B_INPUT;
END IF;
IF (BUTTON2 = '0' AND B2_HOLD = '1') THEN
IF (Minute1 = 0 AND Minute2 = 0) THEN
Minute1 <= 6;
Minute2 <= 0;
Second1 <= 0;
Second2 <= 0;
State <= B_INPUT;
ELSIF (Minute2 = 0 AND Minute1 > 0) THEN
Minute1 <= (Minute1 - 1);
Minute2 <= 5;
State <= B_INPUT;
ELSIF (Minute2 < 5) THEN
IF (Minute2 = 0) THEN
Minute1 <= (Minute1 - 1);
Minute2 <= 5;
State <= B_INPUT;
ELSE
Minute2 <= (Minute2 - 1);
State <= B_INPUT;
END IF;
ELSIF (Minute2 = 5) THEN
Minute2 <= (Minute2 - 5);
State <= B_INPUT;
ELSIF (Minute2 > 5) THEN
Minute2 <= (Minute2 - 1);
State <= B_INPUT;
END IF;
END IF;
--Clear button
IF (BUTTON1 = '0') THEN
Minute1 <= 0;
Minute2 <= 0;
Second1 <= 0;
Second2 <= 0;
State <= B_INPUT;
END IF;
--Start Button
IF (BUTTON0 = '0') THEN
--Turn off LEDs
INPUT_LED1 <= '0';
INPUT_LED2 <= '0';
INPUT_LED3 <= '0';
INPUT_LED4 <= '0';
State <= C_COUNTDOWN;
END IF;
---------------------------------C_COUNTDOWN---------------------------------
WHEN C_COUNTDOWN =>
IF (Second2 > 0) THEN
Second2 <= (Second2 - 1);
ELSIF (Second2 = 0) THEN
IF (Second1 > 0) THEN
Second2 <= 9;
Second1 <= (Second1 - 1);
ELSIF (Second1 = 0) THEN
IF (Minute2 > 0) THEN
Second1 <= 5;
Minute2 <= (Minute2 - 1);
IF (Second2 = 0) THEN
Second2 <= 9;
END IF;
ELSIF (Minute2 = 0) THEN
IF (Minute1 > 0) THEN
IF (Second1 = 0) THEN
Second1 <= 5;
Minute2 <= (Minute2 - 1);
IF (Second2 = 0) THEN
Second2 <= 9;
END IF;
END IF;
Minute2 <= 9;
Minute1 <= (Minute1 - 1);
ELSIF (Minute1 <= 0) THEN
State <= D_DONE;
END IF;
END IF;
END IF;
END IF;
--Reset Button
IF (BUTTON1 = '0') THEN
STATE <= A_ON_OFF;
--Input button
ELSIF (BUTTON0 = '0') THEN
STATE <= B_INPUT;
END IF;
---------------------------------D_DONE---------------------------------
WHEN D_DONE =>
--LEDR0
DONE_LED <= '1';
--Reset button
IF (BUTTON1 = '0') THEN
DONE_LED <= '0';
STATE <= A_ON_OFF;
--Input button
ELSIF (BUTTON0 = '0') THEN
DONE_LED <= '0';
STATE <= B_INPUT;
END IF;
END CASE;
END IF;
END PROCESS;
END Counter;
In choice
when b_input
in the case statement you'll incrementminute2
for everyclk
button3
is low, it's an enable. It needs to be converted to an event unless your counting on a real slowclk
(noting you're also producing a slowclock
). This enable isn't synchronous toclk
, it can cause metastability for a setup or hold time violation.In the process
This will assign
ff3
low but never high. Rising edge means a transition from '0' to '1' onbutton3
. In other words your drivingff3
low only.This is why your counter doesn't change. Besides the potential metastability issue the enable should occur for a single
clk
.For simulation integer range bound counters (e.g.
minute2
) will overflow. VHDL doesn't do range bound modular arithmetic.When
minute2
reaches10
instate
b_input
simulation will quit because of a range error.That increment in choice
b_input
should look something like:(And there's a subtle hint you should simulate this thing.)
You need to use a single
clk
enable from the button(s) for incrementing counters.The way to do this is a) debounce the buttons, b) detect the rising edge in the
clk
domain for oneclk
.All this is complicated because you're using both 'clk
and
clock` as a clock.A recommendation on how to implement de-bounce, edge detect and single
clk
enables is dependent on what your clock rate actually is.Alternatively you could contemplate a bunch of clock ORing where you use buttons to increment things and gate
clock
forc_countdown
.You didn't include the context clause before the entity declaration:
One question, should I use Falling_Edge of button 3 for ff3 because doing that my code also had issues
It would seem likely that if you had falling edge issues you'd also have rising edge issues. The root of all problems would likely be contact bounce.
And no I don't advocate using the falling edge of button 3 in this case.
The issue here is to produce a single clock enable from button 3 (or alternatively use it as a clock). It also needs debouncing. The duration of a switch or button bounce is dependent on several things - mass of the contacts, how hard it's operated and how springy the switch arm is come to mind.
There are several ways to get rid of bounce. For instance you can use a normally open and normally closed pair of contacts to operate an RS latch, requiring two switch or button signals to be in opposite binary states. You can temporally filter (with a clock interval in the 10's of milliseconds range) requiring N number of stable samples. This also filters out metastability from going from an asynchronous domain to a synchronous (clocked) domain.
To use the equivalent of FF3 as an enable for
clk
, you need to debounce the button, and detect the rising edge in the 'clk` domain.Because your buttons are almost guaranteed to be single pole switches you need some sort of temporal filtering. If the clock for that is related to 'clk' you could simply use a long interval enable produced by a counter to sample a button successively. This allows you to use an additional flip flop in the
clk
domain to detect it used to be low and it's now high, the output of that gate used whereFF3
is used now.If
clk
is 50 MHz as you say, debouncing could entail generating an enable from theCount
counter.Let's call that
debounce_en
.Note this is setup to sample some time after the falling edge of button3. An old high and either one or two succesive lows. This is milliseconds after the fall of button3 so it should be safe.
50 Mhz divided by 4000000 gives a
clock
baud of the reciprocal of 12.5 or 8 ms. That's a good starting rate for a temporal filter for debouncing button3:...
We can use
debounce_en
to samplebutton3
instead of producingFF3
:And the reason this would work is because more than likely 8 ms is long enough an interval to debounce one of these switches. (And if it's not add another stage and:
Which requires no 'ringing' be captured by three successive flip flops (30 ms).
'button3_enable would be used in place of
FF3
, all three (or more) of the new signals are type std_logic and the flip flops are used as a shift register.Using a temporal filter requiring known values for so long can potentially filter out short pulses on the buttons, you'd be hard pressed to pulse a button that fast.
And as far as operating `minute2 in the case statement:
And how you were to derive
debounce_en
fromCount
might be subject to change should you changeclock
's clock rate (I'd be tempted to get it to run real time myself). You don't have C_COUNTDOWN finished, but it looks like ideally it would operate with a one second rate enable and have the seconds, and minutes counters cascaded.Notes on DE1 claim of debouncing buttons
I found a DE1-SoC board manual and in figure 3-14 and section 3.6.1 it claims the use of a 74HC245 schmidt trigger buffer (nominally bidirectional, undoubtedly used unidirectionally) is sufficient to use buttons as clocks. That would depend on board layout, capacitance and inductance of one of the buttons, the size of the pull up resistors, and if they say so... (and it's likely it works, they've offered it in several generations of board designs), how does this impact the above description?
Well you still need to synchronize to
clk
and produce a oneclk
period enable. So not much.