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