Introduction
Quick little project to make an egg timer using the MAX II board that I bought over the summer.
Initially, I'll just have it counting down from three minutes. If I feel suitably inspired later, I
might extend it to allow setting the start time, have an alarm go off at the end, and stuff like
that. This is just me having a little fun and hacking some logic together. It won't be very
polished or rigourous, and I won't be bothering with simulation - I'll get it going the same way as
you would with a board full of logic chips, ie use the oscilloscope if I get stuck.
Hardware
I already had a 4-digit 7-segment display (Tru-Opto OSL40562-IY), so I'm using that. That part is
'common anode'. The anodes of the eight LEDs that make up a single digit are connected together and
brought out to a single pin on the package, so the display has four anode drive pins in total. The
cathodes are commoned, so all the 'a' segments share a single pin, all the 'b' segments have a pin,
and so on. There are actually 8 segment pins because each digit also has a decimal point. The
practical consequence of all that is that the display will need to be scanned (the drive signals
for the segments of a particular display digit will be presented whilst the common-anode connection
for the digit we are going to illuminate will be connected to the supply via a PNP transistor, and
the hardware will cycle round all four digits in turn).
I'm doing the connection of the anodes to the supply with some BC461 transistors, with a 1k base
resistor (I found 4 in an old antistatic bag, which was a stroke of luck). I need the transistors
because, if all the segments for a digit are on, the combined current far exceeds what it's
reasonable to take from a single IO pin on the CPLD. The segments I'll drive directly from the CPLD
with a 94R resistor [2x 47R in series] to limit the current to around 10 or 11mA.
This is what the top of the prototype looked like
The apple was a Red Windsor (and very nice it tasted, too).
Here's the underside
I simply soldered all the components in place and made the connections with wire-wrap wire.
Later, I added three buttons, which you'll see on the video. The buttons are to ground with a 10k
pull-up to the 3.3V supply.
CPLD Logic Design
Here's the VHDL. This is the new version with an alarm at the end of the timing period.
---------------------------------------------------------------
--- Filename: CPLD-egg-timer.vhd ---
--- Target device: EPM240T100C5 ---
--- ---
--- 3-minute egg timer ---
--- scans multiplexed 7-seg display ---
--- ---
--- Jon Clift 27th October 2021 ---
--- ---
---------------------------------------------------------------
--- Rev Date Comments ---
--- 1.0 27-Oct-21 ---
--- 2.0 18-Nov-21 Added alarm at end of timing ---
---------------------------------------------------------------
library IEEE;
use IEEE.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
entity CPLD_egg_timer is
port (
clki: in std_logic; --- input clock (50MHz osc).
button_1: in std_logic; --- start/stop button
button_2: in std_logic; --- start/stop button
button_3: in std_logic; --- start/stop button
beep1: out std_logic; --- alarm output
beep2: out std_logic; --- alarm output
anode: out std_logic_vector(3 downto 0); --- common anode drives
seg: out std_logic_vector(7 downto 0) --- segment drives (7=dp, 6=g, ..., 1=b, 0=a)
);
end CPLD_egg_timer;
architecture arch of CPLD_egg_timer is
signal prescaler1: std_logic_vector(15 downto 0);
signal prescaler2: std_logic_vector(9 downto 0);
signal digit_count: std_logic_vector(1 downto 0);
signal milli_en: std_logic := '0';
signal timer_en: std_logic := '0';
signal alarm_en: std_logic := '0';
signal second_en: std_logic := '0';
signal minutes_count: std_logic_vector(3 downto 0) := "0000";
signal tens_count: std_logic_vector(3 downto 0) := "0000";
signal seconds_count: std_logic_vector(3 downto 0) := "0000";
signal digit_value: std_logic_vector(3 downto 0);
signal debounce_1: std_logic_vector(3 downto 0) := "1111";
signal debounce_2: std_logic_vector(3 downto 0) := "1111";
signal debounce_3: std_logic_vector(3 downto 0) := "1111";
signal switch_1_state, switch_1_state_del: std_logic := '0';
signal switch_2_state: std_logic := '0';
signal switch_3_state: std_logic := '0';
begin
clocked_stuff: process (clki)
begin
if (clki'event and clki='1') then
--- prescaler down to 1ms period (divide by 50k from 50MHz clock)
if (prescaler1(15 downto 0) = "1100001101001111") then --- if 49999
prescaler1(15 downto 0) <= "0000000000000000"; --- reset to 0
milli_en <= '1'; --- and set enable (for one cycle)
else --- else
prescaler1 <= prescaler1 + 1; --- count down
milli_en <= '0'; --- and keep enable low rest of time
end if;
--- digit scan count (modulo arithmetic: just let it wrap round)
--- gives refresh rate of 250Hz
if (milli_en = '1') then --- if enabled, count
digit_count <= digit_count + 1;
end if;
--- switch debouncing
if (milli_en = '1') then
debounce_1(3 downto 1) <= debounce_1(2 downto 0);
debounce_1(0) <= button_1;
if(debounce_1 = "1111") then
switch_1_state <= '0';
elsif(debounce_1 = "0000") then
switch_1_state <= '1';
end if;
switch_1_state_del <= switch_1_state;
debounce_2(3 downto 1) <= debounce_2(2 downto 0);
debounce_2(0) <= button_2;
if(debounce_2 = "1111") then
switch_2_state <= '0';
elsif(debounce_2 = "0000") then
switch_2_state <= '1';
end if;
debounce_3(3 downto 1) <= debounce_3(2 downto 0);
debounce_3(0) <= button_3;
if(debounce_3 = "1111") then
switch_3_state <= '0';
elsif(debounce_3 = "0000") then
switch_3_state <= '1';
end if;
end if;
--- timer enable
if (milli_en = '1') then
if (switch_1_state = '1' and switch_1_state_del = '0') then --- if 'start/stop' button pressed
if (alarm_en = '1') then --- alarm sounding
alarm_en <= '0'; --- clear alarm
else
prescaler2(9 downto 0) <= "0000000000";
tens_count(3 downto 0) <= "0000"; --- else preset time to 3 minutes
seconds_count(3 downto 0) <= "0000";
minutes_count(3 downto 0) <= "0011";
timer_en <= '1'; --- and start timing
end if;
end if;
if (timer_en = '1') then
if (((tens_count(3 downto 0) = "0000") and --- finish on 0:00
seconds_count(3 downto 0) = "0000") and
minutes_count(3 downto 0) = "0000") then
timer_en <= '0'; --- disable timing
alarm_en <= '1'; --- set alarm going
end if;
end if;
end if;
--- second prescaler down to 1s period (divide by 1000)
if (milli_en = '1' and timer_en = '1') then --- if enabled (once every millisecond)
if (prescaler2(9 downto 0) = "1111100111") then --- if reached 999
prescaler2(9 downto 0) <= "0000000000"; --- reset to 0
second_en <= '1'; --- and set enable for one
else --- else count down
prescaler2 <= prescaler2 + 1; --- count up
second_en <= '0'; --- keeping enable low rest of time
end if;
end if;
--- the timer
if (milli_en = '1' and (second_en = '1' and timer_en = '1')) then --- if enabled
if (seconds_count(3 downto 0) = "0000") then --- if seconds = 0
seconds_count(3 downto 0) <= "1001"; --- set back to 9
if (tens_count(3 downto 0) = "0000") then --- if tens = 0
tens_count(3 downto 0) <= "0101"; --- set back to 5
minutes_count <= minutes_count - 1;
else --- else count down
tens_count <= tens_count - 1;
end if;
else --- else count down
seconds_count <= seconds_count - 1;
end if;
end if;
end if;
end process clocked_stuff;
--- beep
audio_beep: process (alarm_en,prescaler1)
begin
if (alarm_en = '1') then
beep1 <= prescaler1(15);
beep2 <= not prescaler1(15);
else
beep1 <= '0';
beep2 <= '0';
end if;
end process audio_beep;
--- 7-segment multiplex
seven_seg_mux: process (digit_count,minutes_count,tens_count,seconds_count)
begin
case digit_count is
when "00" => digit_value <= "0000"; --- 0
when "01" => digit_value <= minutes_count; --- 1
when "10" => digit_value <= tens_count; --- 2
when "11" => digit_value <= seconds_count; --- 3
when others => digit_value <= "0000";
end case;
end process seven_seg_mux;
--- 7-segment decode (I've included the hex digits, too)
--- seg is inverted because the segment drive is low to illuminate
seven_seg_decode: process (digit_value)
begin
case digit_value is
when "0000" => seg <= "11000000"; --- 0
when "0001" => seg <= "11111001"; --- 1
when "0010" => seg <= "10100100"; --- 2
when "0011" => seg <= "10110000"; --- 3
when "0100" => seg <= "10011001"; --- 4
when "0101" => seg <= "10010010"; --- 5
when "0110" => seg <= "10000010"; --- 6
when "0111" => seg <= "11111000"; --- 7
when "1000" => seg <= "10000000"; --- 8
when "1001" => seg <= "10010000"; --- 9
when "1010" => seg <= "10001000"; --- a
when "1011" => seg <= "10000011"; --- b
when "1100" => seg <= "11000110"; --- c
when "1101" => seg <= "10100001"; --- d
when "1110" => seg <= "10000110"; --- e
when "1111" => seg <= "10001110"; --- f
when others => seg <= "11111111";
end case;
end process seven_seg_decode;
--- anode drive decode
--- anode is inverted because the anode transistor drive is low to enable
anode_drive_decode: process (digit_count)
begin
case digit_count is
when "00" => anode <= "1110"; --- 0
when "01" => anode <= "1101"; --- 1
when "10" => anode <= "1011"; --- 2
when "11" => anode <= "0111"; --- 3
when others => anode <= "1111";
end case;
end process anode_drive_decode;
end arch;
Hopefully that's fairly easy to understand with the comments. If anyone would like me to explain
what it's doing in more detail, I could have a go.
The way I've done the actual timing part is a bit clunky - the nested IFs work, but it was thrown
together quickly and it's not as 'designed' as it should be. That was the bit that didn't work when
I first tried it - I forgot to qualify the count with milli_en, so it was all over in 180ms [a very
soft and runny egg].
The design as it stands at the moment uses 101 out of the 240 logic elements of the device (42%),
so plenty of room to elaborate my egg timer if I want to. For any of you not used to programmable
logic, it gives an idea of the kind of complexity you can achieve with even a small, modest CPLD:
probably equivalent to a board full of logic chips, if you were to do it discretely with CMOS or
TTL logic.
Video
Here's a video of the start when I press the left button. The display goes to 3.00 and then counts
down at one second intervals.
I decided to spare you the whole 3 minutes, exciting though it was, so here's the last 15 seconds
before it finishes and sits on zero. This is the revised version with an alarm sounding at the end.
With 10mA for the segment drive, multiplexed that gives an average of only 2.5mA, but the display
is quite visible. In real life, the contrast is much better than it appears on the video, though
obviously it would improve further with a piece of filter material in front of the display. My
decision to go fairly fast with the scanning (250 times a second) paid off with the video - I can't
see any evidence at all of strobing on the pictures.
If you found this interesting and would like to see other blogs I've written, a list can be found here:


Top Comments
-
genebren
-
Cancel
-
Vote Up
+1
Vote Down
-
-
Sign in to reply
-
More
-
Cancel
-
jc2048
in reply to genebren
-
Cancel
-
Vote Up
+5
Vote Down
-
-
Sign in to reply
-
More
-
Cancel
Comment-
jc2048
in reply to genebren
-
Cancel
-
Vote Up
+5
Vote Down
-
-
Sign in to reply
-
More
-
Cancel
Children