Wednesday, 30 September 2015

Elbert V2 Multiplexing Seven Segment Displays in VHDL

This post continues on from the previous post on controlling the Seven Segment Display and using counters on the Elbert V2 Development board.  If people haven't seen that post then it can be found here:

Elbert V2 FGPA tutorial on Seven Segments Displays using VHDL

The above post implements a simple up down counter to ten using one seven segment display.  Lets now use all three seven segment displays and count up to the maximum possible - which is 999 seconds.

The problem with doing this is that the separate seven segment displays cannot be controlled directly. The segment connections are common between all three displays and controlled by turning the PNP transistors connected to the anodes on and off.  If we turn the PNP transistors on and off very quickly we can make it look like the displays are showing different numbers at the same time.  This is known as multiplexing and this technique is quite common when driving seven segment displays. The actual technique relies on persistence of vision.  If you need more information on multiplexing or persistence of vision then the links below should help...

Multiplexed displays Wikipedia Entry

Persistence of vision Wikipedia Entry

The VHDL code needs to multiplex all three displays and display the result of a counter.  To make things easier lets explain what is needed using a flow diagram - I like flow diagrams, it makes explaining and coding things so much simpler.  Once the flow diagram is clear the coding becomes easier because each part of the code can be related to a part of the flow diagram.

That is the general idea - now for the coding!  Start a new project in Xilinx WebISE - I called mine all_three_segments_counter, you can use any name you like!


Click Next when ready...Ensure the correct settings are chosen for the Elbert V2 Board:


Click Next to display the project summary page...


Click finish to return to the main project screen in WebISE 14.7

Now we need to add a new a new VHDL module so we can write the code - right click on the hierarchy window and select 'Add Source'


Select VHDL module and give the file a suitable name...


Click Next to continue...


You could if you wanted enter the information about the inputs and outputs here...I normally do but this time I'm going to leave things blank - press Next to display the summary window...


Click finish to return to the main screen...The automatically generated VHDL code will be present with comments:


I like to delete the comments in green as they don't really provide anything useful...

Here comes the code...We start with the Entity statement:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.all;

entity all_three_segments_module is
port ( 
      clock_in : in std_logic;
      Seven_Segment_Enable : out std_logic_vector(2 downto 0);
      Seven_Segment_Display : out std_logic_vector(7 downto 0) 
     );
end all_three_segments_module;

The above code sets the inputs and outputs for the project - the input is the 12 MHz clock and the outputs are the seven segment display enable pins (the three PNP transistors controlling the displays and the control pins for the displays themselves...

architecture Behavioral of all_three_segments_module is

-- Internal signals for processing
signal refresh_count : integer := 0;
signal refresh_clk : std_logic := '1';

signal second_count : integer := 0;
signal second_clk : std_logic := '1';

signal digit_sel : unsigned(1 downto 0);
signal bcd : integer := 0;
signal Seven_Segment_Display_output : std_logic_vector (7 downto 0) := (others => '0');
signal bcd0, bcd1, bcd2 : integer := 0;

signal unit_count : integer := 0;
signal ten_count : integer := 0;

signal hundred_count : integer := 0;

Next we have all of the internal signals needed for processing the two clocks required along with counters and the signals to select which display to update and the internal signals required to drive the displays separately. Finally we have some counters for keeping track of the actual count itself.

begin

-- Divide down the 12 MHz input clock to for the refresh rate
-- and the one second clock

process(Clock_in)

begin

if(clock_in'event and clock_in='1') then 
   refresh_count <= refresh_count+1;
  second_count <= second_count+1;

if(second_count = 750000) then
second_clk <= not second_clk;
second_count <= 1;
end if;

if(refresh_count = 1200) then
refresh_clk <= not refresh_clk;
refresh_count <= 1;
end if;

end if;

end process;  

The above code is a process to read in the 12 MHz clock from the Elbert V2 development board and divide it down to create to separate clocks.  The If statement checks if the 12 MHz clock has changed state and if it is positive.  At that point two counters are incremented, the first counter sets the rate the displays refresh (every 0.0001 seconds) and the other counter counts in one second increments.

-- If the one second clock is high
-- Display the count on Seven segment
-- LED display and count in seconds
-- update the display with respect to
-- the current count
-- if the count is 999 - start again

process(second_clk)

begin

if(second_clk'event and second_clk='1') then
           bcd0 <= unit_count;
           bcd1 <= ten_count;
   bcd2 <= hundred_count;

   unit_count <= unit_count + 1;  

   if (unit_count = 9) then
               unit_count <= 0; 
       ten_count <= ten_count +1;  
   end if;
 
   if (ten_count = 9) and (unit_count = 9) then           ten_count <= 0;
               hundred_count <= hundred_count + 1;
   end if;

   if (hundred_count = 9) and (ten_count = 9) and (unit_count = 9) then   
       hundred_count <= 0;
   end if;
end if;

end process;

The above code checks if the one second clock has changed state and is high - if this is true it then controls the seven segment displays by controlling the PNP transistors connected to the seven segment displays.  The unit count increments every time a second passes.  This is displayed on the right side seven segment display.  If the unit count has reached the count of 9 then the ten count is incremented and the middle display is updated.  If the ten count has reached 9 then the hundred count is incremented and the left side seven segment display is updated.  If the count reaches 999 then the displays are cleared and the count starts again from zero...

process(refresh_clk) --period of clk is 0.0001 seconds.

begin

if(refresh_clk' event and refresh_clk='1') then
digit_sel <= digit_sel + 1;

end if;

end process;

-- multiplexer to select a BCD digit
   with digit_sel select
        bcd <= bcd0 when "00",
               bcd1 when "01",
               bcd2 when others;

-- activate selected digit's anode
   with digit_sel select
        Seven_Segment_Enable <= "110" when "00",
                                "101" when "01",
                                "011" when others;

   with bcd select

Seven_Segment_Display_output(7 downto 0) <= B"00000010" when 0,
   B"10011110" when 1,
   B"00100100" when 2,
   B"00001100" when 3,
   B"10011000" when 4,
   B"01001000" when 5,
   B"01000000" when 6,
   B"00011110" when 7,
   B"00000000" when 8,
   B"00011000" when 9,
   B"11111111" when others;
 
-- send data to seven segment display.

Seven_Segment_Display(7 downto 0) <= Seven_Segment_Display_output(7 downto 0);  

end Behavioral; 

The above code checks whether the refresh clock has changed state and is positive.  If this is true then the appropriate seven segment display is selected, the value required is passed to the display and converted from an integer value into an inverted binary value to drive the appropriate segments. Then the next display is selected and the process is repeated until all of the displays are updated.  This process happens so fast that it cannot be seen by the human eye so it appears that the displays are always on.  If you change the value of refresh_count to a larger value than 1200 say 240000 then it is possible to see the displays updating.  

Here is the complete code - copy this into the VHDL file and then save it.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.all;

entity all_three_segments_module is
port ( 
       clock_in : in std_logic;
       Seven_Segment_Enable : out std_logic_vector(2 downto 0);
       Seven_Segment_Display : out std_logic_vector(7 downto 0) 
             );
end all_three_segments_module;

architecture Behavioral of all_three_segments_module is

-- Internal signals for processing
signal refresh_count : integer := 0;
signal refresh_clk : std_logic := '1';

signal second_count : integer := 0;
signal second_clk : std_logic := '1';

signal digit_sel : unsigned(1 downto 0);
signal bcd : integer := 0;
signal Seven_Segment_Display_output : std_logic_vector (7 downto 0) := (others => '0');
signal bcd0, bcd1, bcd2 : integer := 0;

signal unit_count : integer := 0;
signal ten_count : integer := 0;
signal hundred_count : integer := 0;

begin

-- Divide down the 12 MHz input clock to for the refresh rate
-- and the one second clock
-- change the values in the counters to change the refresh rate
-- and the clock update speed.

process(Clock_in)

begin

if(clock_in'event and clock_in='1') then 
  refresh_count <= refresh_count+1;
  second_count <= second_count+1;

if(second_count = 750000) then
second_clk <= not second_clk;
second_count <= 1;
end if;

if(refresh_count = 1200) then
refresh_clk <= not refresh_clk;
refresh_count <= 1;
end if;

end if;

end process; 

-- If the one second clock is high
-- Display the count on Seven segment
-- LED display and count in seconds
-- update the display with respect to
-- the current count
-- if the count is 999 - start again

process(second_clk)

begin

if(second_clk'event and second_clk='1') then
      bcd0 <= unit_count;
      bcd1 <= ten_count;
  bcd2 <= hundred_count;

  unit_count <= unit_count + 1;  

  if (unit_count = 9) then
          unit_count <= 0; 
      ten_count <= ten_count +1;  
  end if;
 
  if (ten_count = 9) and (unit_count = 9) then         
   ten_count <= 0;
          hundred_count <= hundred_count + 1;
  end if;

  if (hundred_count = 9) and (ten_count = 9) and (unit_count = 9) then  
      hundred_count <= 0;
  end if;

end if;

end process;

process(refresh_clk) --period of clk is 0.0001 seconds.

begin

if(refresh_clk' event and refresh_clk='1') then
digit_sel <= digit_sel + 1;

end if;

end process;

-- multiplexer to select a BCD digit
   with digit_sel select
        bcd <= bcd0 when "00",
               bcd1 when "01",
               bcd2 when others;

-- activate selected digit's anode
   with digit_sel select
        Seven_Segment_Enable <= "110" when "00",
                                "101" when "01",
                                "011" when others;

   with bcd select

Seven_Segment_Display_output(7 downto 0) <= B"00000010" when 0,
   B"10011110" when 1,
   B"00100100" when 2,
   B"00001100" when 3,
   B"10011000" when 4,
   B"01001000" when 5,
   B"01000000" when 6,
   B"00011110" when 7,
   B"00000000" when 8,
   B"00011000" when 9,
   B"11111111" when others;
 
-- send data to seven segment display.

Seven_Segment_Display(7 downto 0) <= Seven_Segment_Display_output(7 downto 0);  

end Behavioral; 

Save the file!  Always save your work...Nothing is more annoying than not having the work saved if something goes wrong!

Now it's time to add the implementation constraints file - this tells the compiler which pins are connected to the 12 MHz clock and the seven segment displays and the seven segment enable pins. Right click on the hierarchy window again and add source - select implementation constraints file...



Click Next to display the summary file...



Click finish to return to the main project window to display the editor for the constraints file...

CONFIG VCCAUX = "3.3" ;

# Clock 12 MHz
NET "clock_in" LOC = P129  | IOSTANDARD = LVCMOS33 | PERIOD = 12MHz;
  
###################################
#    Seven Segment Display    #
###################################

NET "Seven_Segment_Display[7]" LOC = P117  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[6]" LOC = P116  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[5]" LOC = P115  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[4]" LOC = P113  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[3]" LOC = P112  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[2]" LOC = P111  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[1]" LOC = P110  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[0]" LOC = P114  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;

NET "Seven_Segment_Enable[2]" LOC = P124  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Enable[1]" LOC = P121  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;

NET "Seven_Segment_Enable[0]" LOC = P120  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; 

Copy and paste the above code into the new file we just created and save it.  It's the information telling the compiler which pins are connected to the input and output statements in the entity section. 

Save the file!

Next click on the green arrow to implement a top module - compile the code!



This will take a few moments - take a break until it's complete! Ignore any warnings that may appear - I'm still working out what that means!  



Next generate a bitstream file and once that is complete it's time to upload it to the development board...Connect up the development board using a USB cable and load up the configuration program - select the appropriate COM port and file and upload it!



Click Program to upload the data and 'Flash' the FPGA - Exciting times!


Once complete you should see the seven segment displays in operation displaying the current count! Here is a video of my board in operation...



If for some reason the source files for this project are required they are here:

Xilinx Project files

If you wanted to modify the operation of this project to read in the count from something external then the entity statement needs to be modified with an additional statement such as:

read_count_in : in std_logic;

Next we need to read this input and then use it to update the second count.  We would need to modify the process statement for second count by adding the following line:

if (read_count_in = '0') then
    unit_count <= unit_count + 1;

The whole section would look like this...

process(second_clk)

begin

if(second_clk'event and second_clk='1') then
      bcd0 <= unit_count;
      bcd1 <= ten_count;
  bcd2 <= hundred_count;

      if (read_count_in = '0') then
   unit_count <= unit_count + 1;

if (unit_count = 9) then
unit_count <= 0; 
ten_count <= ten_count +1;  
end if;
 
if (ten_count = 9) and (unit_count = 9) then         
ten_count <= 0;
hundred_count <= hundred_count + 1;
end if;

if (hundred_count = 9) and (ten_count = 9) and (unit_count = 9) then  
hundred_count <= 0;
end if;

end if;

end if;

end process;

The new input would have to be added to the implementation constraints file - when testing I used a button but you could use any one of the IO pins...

NET "read_count_in" LOC = P80   | PULLUP  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;

The updated code as written will update the seven segment displays with the number of times the push button zero is pressed and held.  If you were to use an external input ensure the voltage applied is at 3.3 Vdc and not 5 Vdc - you could damage the FPGA!!! 

Here is the updated code with inputs if required...

modified code with button input

That's all for now - Langster!