FPGA Automated House Lights




Introduction: FPGA Automated House Lights

For this project we will use a BASYS3 Board by Diligent for the purpose of taking what is meant to be a learning tool and giving it a creative purpose. A common problem I've noticed across many households is energy waste via lights that remain unintentionally turned on. This FPGA Automated House Lights project serves to simulate a light control system within a house using 4 inputs and a timer. 3 switches control 6 lights and one switch acts as a "master toggle" which; upon activation, initiates a countdown to turn off the default lights such as the ones commonly found on a front porch or backyard.

Step 1: Vivado Setup

Create a new RTL project, and do not specify sources at this time.

Skip the dialogs until you reach the Default Part screen. Here is where you specify the on board chip. You can usually find this information on the packaging, the website, and even the chip itself. The Basys 3 I am using is the x7a35tcpg236-1 with 50 Bock RAMs. If you cannot find any specific information and are unsure, go with the least powerful option (I initially didn't know which specific one was mine and went with the weakest until I found it listed on the manufacture's website).

You can then finish the project and we'll start programming the board.

Step 2: Constraint File Setup

For this project we are going to simulate a house with lights in 2 outdoor lights, and 3 rooms with 2 lights in each for a total of 8 lights. To do this we are going to use the onboard LED's located above the switches. To control the system we will use 1 light switch in each "room" and one master switch to represent what could be a daylight sensor in a real world application.

The purpose of the 4th switch is to enable or disable the automatic timer for the outdoor lights. This will cause the lights to turn on automatically when the lights inside the house are on, and are automatically turned off after a set period of time. In the manner, you would never have to worry about forgetting to turn off the porch lights before bed, wasting electricity.

We will also use the 7 segment display to display the countdown to make the project more complex.

This makes the following required:

  • 8 LEDs
  • 4 Switches
  • 7 Segment Display

To do this with the Basys3 we will use a constraint file to define the ports. Instead of creating one from scratch we'll be efficient and download the file provided by Digilent on the Basys3 Github repo here: https://github.com/Digilent/Basys3/blob/master/Resources/XDC/Basys3_Master.xdc

It is also attached to this step in case they change the github repo.

Modifying the Constraint File

We will uncomment the 2 lines for the clock signal, the 8 lines for switches 0 to 3 (4 switches total), 8 LED's, the seven segment display, and the first push button. You can uncomment with the hotkey CTRL + /

I also renamed the ports to make indexing the variables easier, such as separating the SYSENABLED switch out from the rest. You'll see why this is convenient later.

Here's all we needed for 8 lights on 3 switches, with a master switch, reset button, and countdown display:

<p>## This file is a general .xdc for the Basys3 rev B board<br>## To use it in a project:
## - uncomment the lines corresponding to used pins
## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project</p><p>## Clock signal
set_property PACKAGE_PIN W5 [get_ports CLK]							
set_property IOSTANDARD LVCMOS33 [get_ports CLK]
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports CLK]
## Switches
set_property PACKAGE_PIN V17 [get_ports {LIGHTSW[0]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {LIGHTSW[0]}]
set_property PACKAGE_PIN V16 [get_ports {LIGHTSW[1]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {LIGHTSW[1]}]
set_property PACKAGE_PIN W16 [get_ports {LIGHTSW[2]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {LIGHTSW[2]}]
set_property PACKAGE_PIN W17 [get_ports {SYSENABLED}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {SYSENABLED}]</p><p>## LEDs
set_property PACKAGE_PIN U16 [get_ports {LED_LIGHT[0]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {LED_LIGHT[0]}]
set_property PACKAGE_PIN E19 [get_ports {LED_LIGHT[1]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {LED_LIGHT[1]}]
set_property PACKAGE_PIN U19 [get_ports {LED_LIGHT[2]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {LED_LIGHT[2]}]
set_property PACKAGE_PIN V19 [get_ports {LED_LIGHT[3]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {LED_LIGHT[3]}]
set_property PACKAGE_PIN W18 [get_ports {LED_LIGHT[4]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {LED_LIGHT[4]}]
set_property PACKAGE_PIN U15 [get_ports {LED_LIGHT[5]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {LED_LIGHT[5]}]
set_property PACKAGE_PIN U14 [get_ports {LED_LIGHT[6]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {LED_LIGHT[6]}]
set_property PACKAGE_PIN V14 [get_ports {LED_LIGHT[7]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {LED_LIGHT[7]}]
##7 segment display
set_property PACKAGE_PIN W7 [get_ports {SEG[7]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {SEG[7]}]
set_property PACKAGE_PIN W6 [get_ports {SEG[6]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {SEG[6]}]
set_property PACKAGE_PIN U8 [get_ports {SEG[5]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {SEG[5]}]
set_property PACKAGE_PIN V8 [get_ports {SEG[4]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {SEG[4]}]
set_property PACKAGE_PIN U5 [get_ports {SEG[3]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {SEG[3]}]
set_property PACKAGE_PIN V5 [get_ports {SEG[2]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {SEG[2]}]
set_property PACKAGE_PIN U7 [get_ports {SEG[1]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {SEG[1]}]</p><p>set_property PACKAGE_PIN V7 [get_ports SEG[0]]							
	set_property IOSTANDARD LVCMOS33 [get_ports SEG[0]]</p><p>set_property PACKAGE_PIN U2 [get_ports {AN[3]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {AN[3]}]
set_property PACKAGE_PIN U4 [get_ports {AN[2]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {AN[2]}]
set_property PACKAGE_PIN V4 [get_ports {AN[1]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {AN[1]}]
set_property PACKAGE_PIN W4 [get_ports {AN[0]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {AN[0]}]</p><p>##Buttons
set_property PACKAGE_PIN U18 [get_ports RESETBUTTON]						
	set_property IOSTANDARD LVCMOS33 [get_ports RESETBUTTON]</p>

Step 3: Open Source Code Is Your Friend

Let's be realistically, no one want to reinvent the wheel if they don't have to, and programmers are no different. So when there's a way a programmer can use code that someone else has already spent the time making work perfectly and efficiently, they will. This is the reason jquery, bootstrap, .NET, and OpenGL all exist! They are all libraries that someone else has built for programmers to take advantage of.

So the best option for making a clock divider and and a using the seven segment display is to simply find someone else's code and use theirs once you understand how it works. Efficiency is key in the modern world. My advice would be to search on github, there are thousands of open source projects available for you to borrow code and ideas from.

So the clock divider I used for this was just a modified version of the one supplied to my class. These modifications reduce the 100mhz clock down to allow us to use it as a 1 second clock for our timer.

For the 7 Segment Display, I found code written by Bryan Mealy that is super simple to use. He also has a great book online with lots of information on VHDL programming called Free Range VHDL.

Step 4: Auto Shutoff Script

The automatic light shutoff script is going to be the main brain for our project, receiving and transmitting the status of our system.

It will receive the following signals:

  • Clock divider logic signal - 1 second interval
  • Daylight sensor logic signal - in this case a switch representing a light sensor
  • Timer reset logic signal - to alert us that reset was pressed, or a light inside turned on

We will then process the data from the and output the following signals:

  • Exterior light logic signal
  • Current time remaining

A process is used to monitor the status of all the inputs. Delays were minimized by only checking for the rising edge on the timing function of the timer. The auto off is currently hardcoded as 15 seconds in binary.

architecture Behavioral of AutoShutoff is

-- Local variable to keep track of our current time // Defaults to 15 seconds
signal currentTime : STD_LOGIC_VECTOR (5 downto 0) := "001111";

    process (enabled, reset, tick)
        -- If system is disabled, reset timer and turn lights off
        if(enabled = '0') then
            extLight <= '0';
            currentTime <= "001111";
            -- Enables exterior lights if timer not 0
            if (currentTime > 0) then
                extLight <= '1';
                extLight <= '0';
            end if;
            -- Handle reset requests if needed
            if (reset = '1') then
                currentTime <= "001111";
            -- Subtract a second for ever clock tick until we hit 0 
            elsif (rising_edge(tick)) then
                if (currentTime > 0) then
                    currentTime <= currentTime - 1;
                end if;
            end if;
        end if;
    end process;
    -- Output our current time so we can display it
    ctime <= currentTime;
end Behavioral;

Step 5: Detecting a Reset

We have three ways to trigger a reset, and while the AutoShutoff script will detect the who system being disabled, we still need to check if any light is turned on and if the reset button is pressed. We do this with a new behavior that will check if the 3 interior switches are off or if the reset button is pressed. This is done with a process to monitor the events, evaluate the status, and output a logic variable with the reset status.

<p>entity ResetControl is<br>    Port (  intlSwitch : in  STD_LOGIC_VECTOR (2 downto 0);
            resetbtn   : in  STD_LOGIC;
            reset      : out STD_LOGIC );
end ResetControl;</p><p>architecture Behavioral of ResetControl is
    process (intlSwitch, resetbtn)
        -- Reset if all switches are off, or reset button pressed
        if (intlSwitch /= "000" or resetbtn = '1') then
            reset <= '1';
            reset <= '0';
        end if;
    end process;
end Behavioral;</p>

Step 6: Outputting to the LED Lights

Outputting to the correct LED was the easiest step in the end, by mapping each variable to the corresponding output port, we only needed 8 lines. This is because you can access a single logic byte on a Logic vector by using it's index location. Because we defined these as LARGE downto SMALL, these arrays are little endian and 0 indexed.

This is why it is convient to have everything cleanly named.

<p>entity OutputToLED is<br>    Port ( extLight   : in  STD_LOGIC;
           intlSwitch : in  STD_LOGIC_VECTOR (2 downto 0);
           outLED     : out STD_LOGIC_VECTOR (7 downto 0) );
end OutputToLED;</p><p>architecture Behavioral of OutputToLED is</p><p>begin
    process (extLight, intlSwitch)
--      Assign 'Daylight Detector' switch to 'outdoor' LED's
--      Assign 'Light Switch' to 2 'indoor room' LED's</p><p>        outLED(7) <= extLight;
        outLED(6) <= extLight;
        outLED(5) <= intlSwitch(2);
        outLED(4) <= intlSwitch(2);
        outLED(3) <= intlSwitch(1);
        outLED(2) <= intlSwitch(1);
        outLED(1) <= intlSwitch(0);
        outLED(0) <= intlSwitch(0);
    end process;</p><p>end Behavioral;</p>

Step 7: Bringing It All Together

Those of you who are familiar with Object Oriented Programming (OOP) might feel this is similar to how classes are set up, because that's what I thought it was closest to. There is more than one right way to do this (and even more many wrong ways), and this is just what I felt the most comfortable with, and what made the most sense.

I started off by declaring all the input/outputs I needed - these MUST be identical to what you used in the constraints file.

<p>entity LightManager is<br>    Port ( LIGHTSW   : in  STD_LOGIC_VECTOR (2 downto 0);
           SEG       : out STD_LOGIC_VECTOR (7 downto 0);
           AN        : out STD_LOGIC_VECTOR (3 downto 0);
           LED_LIGHT : out STD_LOGIC_VECTOR (7 downto 0) );
end LightManager;</p>

Then I defined the behavior, added some global variables that needed to be shared across scripts, and added each component in, linking them to their variables.

<p>architecture Behavioral of LightManager is</p><p>    -- Intermediate variables
    signal countDownTime : STD_LOGIC_VECTOR (7 downto 0) := ( others => '0');
    signal secondClock, resetStatus, ExteriorLightsON : STD_LOGIC ;</p><p>    -- Reduces clock speed to 1 tick at 1hz
    component     ClockDivider Port ( clk  : in  STD_LOGIC;
                                      sclk : out STD_LOGIC );
    end component ClockDivider;</p><p>    -- Monitors system status
    component     AutoShutoff  Port ( tick, enabled, reset : in  STD_LOGIC;
                                      extLight             : out STD_LOGIC; 
                                      ctime                : out STD_LOGIC_VECTOR (5 downto 0) ); 
    end component AutoShutoff;
    -- Detects timer Resets
    component     ResetControl Port ( intlSwitch : in  STD_LOGIC_VECTOR (2 downto 0);
                                      resetbtn   : in  STD_LOGIC;
                                      reset      : out STD_LOGIC );
    end component ResetControl;
    -- Output to the onboard LED lights
    component     OutputToLED  Port ( extLight   : in  STD_LOGIC;
                                      intlSwitch : in  STD_LOGIC_VECTOR (2 downto 0);
                                      outLED     : out STD_LOGIC_VECTOR (7 downto 0) );
    end component OutputToLED;
    -- Seven Segment Display
    component     sseg_dec     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;</p>

And lastly, we assign each component to a variable (helps clarify each script's role also), and pray we didn't make a typo somewhere as it "compiles."

<p>begin</p><p>    -- You can name these anything to help provide context about their function
    OneSecondClock: ClockDivider Port Map ( sclk => secondClock, clk => CLK);
    autoLightOff: AutoShutoff    Port Map ( tick => secondClock, enabled => SYSENABLED, reset => resetStatus, ctime => countDownTime(5 downto 0), extLight => ExteriorLightsON );
    resetManager: ResetControl   Port Map ( intlSwitch => LIGHTSW, resetbtn => RESETBUTTON, reset => resetStatus);
    outputLight: OutputToLED     Port Map ( extLight => ExteriorLightsON, intlSwitch => LIGHTSW, outLED => LED_LIGHT);
    countdownDisplay: sseg_dec   Port Map ( CLK => clk, VALID => '1', SIGN => '0', SEGMENTS => SEG, DISP_EN => AN, ALU_VAL => countDownTime);
end Behavioral;</p>

Step 8: Testing Your Code

Probably the most irritating process about working with FPGA is the amount of time it takes to see if a everything is successful or if you made a typo somewhere. Be prepared to wait and watch a youtube video while debugging your code.

After you think you've done everything successfully, check that all your files are listed in the project, then skip straight to generating the bit stream. Why? Because it will generate the everything in between on it's own for you and if it hits an error, it'll let you know.

Hope and pray that it all goes smoothly, and if it fails, check the message log to help narrow down the error source. If you google the first few words of the error, you'll likely find other people who have made the same exact mistake already.

Once you hit rock bottom and can't seem to figure out what you are doing wrong, I suggest downloading my project files and and comparing the files to see where the discrepancy lays.

and CHECK FOR SEMICOLONS, especially in the port declarations.

Be the First to Share


    • Puzzles Speed Challenge

      Puzzles Speed Challenge
    • "Can't Touch This" Family Contest

      "Can't Touch This" Family Contest
    • CNC Contest 2020

      CNC Contest 2020

    3 Discussions


    3 years ago

    We could really use one of these, my husband forgets the lights all the time. XD


    Reply 3 years ago

    You can try another one ?


    Reply 3 years ago

    Nah, I think I'll keep him :) I find it more funny than frustrating, lol.