Introduction: Dimmable LED Using Basys 3 Board

In this guide we are going to build and control an external LED dimming system. With the available buttons, the user can dim the LED bulb to any desired brightness. The system uses the Basys 3 board, and it is connected to a breadboard that contains a resistor and the LED bulb. Pressing the designated "up" button will increase the brightness, and pressing the "down" button will decrease the brightness all the way to zero. Not only does this prevent the user from being blinded by bright-as-the-sun light bulbs but it also conserves energy!

Step 1: Create Input Counter

For this step we create the component that determines the level of brightness (through a clock) by using two switches: one to increase and one to decrease. Using VHDL, we produced the counter through the use of D flip-flops. Pressing the "up" button pushes the next state to the present state, outputting to the seven segment display and the LED bulb.

entity updown_counter is
Port ( present_state : out STD_LOGIC_VECTOR (3 downto 0);
       previous_state : in STD_LOGIC_VECTOR (3 downto 0);
           next_state : in STD_LOGIC_VECTOR (3 downto 0);
           clk : in STD_LOGIC;
           down_enable : in STD_LOGIC;
           up_enable : in STD_LOGIC);
end updown_counter; 

architecture Behavioral of updown_counter is 

begin 
flop : process(next_state, clk, up_enable, down_enable, previous_state)
begin
if (rising_edge(clk)) then
    if (up_enable = '1' and not(next_state="0000")) then
        present_state <= next_state;
        
    elsif (down_enable = '1' and not(previous_state= "1111")) then
        present_state <= previous_state;
    end if;
end if;
end process flop; end Behavioral;

We also need a clock for each input to be latched onto (when it rises), so we also created a clock divider that determines how fast the buttons can be pressed between each level of brightness. This clock divider allows us to properly display the right level on the seven segment display and produce the right level of intensity for each level.

entity counter_clkDiv is

   Port (  clk : in std_logic;
           sclk : out std_logic);
end counter_clkDiv;  architecture my_clk_div of counter_clkDiv is
   constant max_count : integer := (10000000);  
   signal tmp_clk : std_logic := '0'; 
begin
   my_div: process (clk,tmp_clk)              
      variable div_cnt : integer := 0;   
   begin
      if (rising_edge(clk)) then   
         if (div_cnt >= MAX_COUNT) then 
            tmp_clk <= not tmp_clk; 
            div_cnt := 0; 
         else
            div_cnt := div_cnt + 1; 
         end if; 
      end if; 
      sclk <= tmp_clk; 
   end process my_div; 
end my_clk_div;

Step 2: Create LED Clock Divider

For this step we create a clock divider for the LED bulb to determine 16 different levels of intensity. With 0 being off to 15 displaying maximum brightness, the clock divider increments each button press by what we set to be the levels of brightness. Each increasing level meant an increase in the clock for the LED bulb. Remembering that brightness doesn't increase linearly, we cranked the clock to the highest it could go and decreased our clocks accordingly.

Note: we are using a blue LED. Using a different color (like red) will require slightly different clocks altogether; a medium brightness setting for blue could already be max brightness for red. This happens because different wavelengths of light will require different amounts of energy, with the cooler colors like purple and blue requiring more energy, while the warmer colors like red and orange require less energy.

entity led_clkDiv is
Port ( present_state : in STD_LOGIC_VECTOR (3 downto 0); clk : in STD_LOGIC; led_clk : out STD_LOGIC); end led_clkDiv; architecture Behavioral of led_clkDiv is signal tmp_clk : std_logic := '0'; shared variable max_count : integer;begin count_stuff : process (present_state) begin case present_state is when "0000" => max_count := 0 ; when "0001" => max_count := 2; when "0010" => max_count := 4; when "0011" => max_count := 6; when "0100" => max_count := 8; when "0101" => max_count := 10; when "0110" => max_count := 12; when "0111" => max_count := 14; when "1000" => max_count := 16; when "1001" => max_count := 25; when "1010" => max_count := 50; when "1011" => max_count := 100; when "1100" => max_count := 150; when "1101" => max_count := 200; when "1110" => max_count := 250; when "1111" => max_count := 300; end case; end process count_stuff; my_div: process (clk, tmp_clk, present_state) variable div_cnt : integer := 0; begin if (rising_edge(clk)) then if (div_cnt >= max_count) then tmp_clk <= not tmp_clk; div_cnt := 0; else div_cnt := div_cnt + 1; end if; end if; led_clk <= tmp_clk; end process my_div; end Behavioral;

Step 3: Creating the LED Controller

Now that we've made it this far, it's time to finally combine all the components we have created so far into the LED Controller file.

To summarize, the components used are the following:

  • Input counter (updown_counter)
  • Clock divider (counter_clkDiv)
  • LED clock divider (led_clkDiv)
  • Seven-segment display driver (sseg_dec) (attached file)

The seven-segment display driver was not actually previously discussed because we actually borrowed the VHDL file from Dr. Bryan Mealy due to its long and complicated code. What it essentially does is drive our button inputs to the seven-segment display on the Basys 3 board so that we know what level of brightness on.

Moving forward, the LED Controller uses flip flops to increase or decrease the count which controls both the seven segment display and the level of brightness of the LED bulb simultaneously.

entity counter is
Port ( clk : in STD_LOGIC; up_enable : in STD_LOGIC; down_enable : in STD_LOGIC; SEGMENTS : out STD_LOGIC_VECTOR (7 downto 0); DISP_EN : out STD_LOGIC_VECTOR (3 downto 0); led_clk : out STD_LOGIC); end counter; architecture Behavioral of counter is component updown_counter is Port ( present_state : out STD_LOGIC_VECTOR (3 downto 0); previous_state : in STD_LOGIC_VECTOR (3 downto 0); next_state : in STD_LOGIC_VECTOR (3 downto 0); clk : in STD_LOGIC; down_enable :in STD_LOGIC; up_enable : in STD_LOGIC); end component updown_counter; component counter_clkDiv is Port ( clk : in std_logic; sclk : out std_logic); end component counter_clkDiv; component sseg_dec is Port ( ALU_VAL : in std_logic_vector(7 downto 0); SIGN : in std_logic; VALID : in std_logic; CLK : in std_logic; DISP_EN : out std_logic_vector(3 downto 0); SEGMENTS : out std_logic_vector(7 downto 0)); end component sseg_dec; component led_clkDiv is Port ( present_state : in STD_LOGIC_VECTOR (3 downto 0); clk : in STD_LOGIC; led_clk : out STD_LOGIC); end component led_clkDiv; signal present_state : STD_LOGIC_VECTOR (3 downto 0) := "0000"; signal next_state : STD_LOGIC_VECTOR (3 downto 0) := "0000"; signal previous_state : STD_LOGIC_VECTOR (3 downto 0) := "0000"; signal Alu_Val : STD_LOGIC_VECTOR (7 downto 0); signal sclk : STD_LOGIC; begin Alu_Val(7 downto 4) <= "0000"; Alu_Val(3 downto 0) <= present_state; next_state(0) <= not(present_state(0)); next_state(1) <= present_state(0) xor present_state(1); next_state(2) <= (present_state(0) and present_state(1)) xor present_state(2); next_state(3) <= (present_state(0) and present_state(1) and present_state(2)) xor present_state(3); previous_state(0) <= not(present_state(0)); previous_state(1) <= present_state(0) xnor present_state(1); previous_state(2) <= (present_state(0) nor present_state(1)) xor present_state(2); previous_state(3) <= ((present_state(0) or present_state(1) or present_state(2))) xnor present_state(3); count : updown_counter port map( clk => sclk, next_state => next_state, previous_state => previous_state, up_enable => up_enable, down_enable => down_enable, present_state => present_state); display : sseg_dec port map( ALU_VAL => Alu_Val, SIGN => '0', VALID => '1', CLK => clk, DISP_EN => DISP_EN, SEGMENTS => SEGMENTS); led_div : led_clkDiv port map( clk => clk, present_state => present_state, led_clk => led_clk); clk_div : counter_clkDiv port map( clk => clk, sclk => sclk); end Behavioral;

Attachments

Step 4: Establishing Constraints and Assembly

Constraints

To properly setup and program the Basys 3 board, we must first setup our constraints file which is attached to this step. The following settings have been adjusted:

Buttons

  • Changed T18 to "up_enable" (increase brightness)
  • Changed U17 to "down_enable" (decrease brightness)

7 segment display

  • W7, W6, U8, V8, U5, V5, U7, V7 represent each segment of one display
  • U2, U4, V4, W4 represent each anode displayed ( only 2 are active because our highest number is 15)

PMOD Header JC

  • JC7 is where we connect one of the wires of the LED bulb, and the other wire leads to GROUND.

After setting this all up, all you have to do is generate your bitstream (with whatever software you're using i.e Vivado), program your board, and boom! You got yourself a working board.

Note: The pin mapping can be found on the Basys 3 Datasheet here.

Assembly

Step 5: Using Your Dimmer Switch

If all goes well, you should have a fully functioning dimmer system. To summarize, pressing the top button will increase your brightness (all the way to 15), and pressing the down button will decrease your brightness (all the way to 0). Hope all goes well for your now-relaxed eyesight!