FPGA programming. Smooth LED brightness on the Spartan-3E Starter Kit using PWM (PWM)

    This article is for beginners in VHDL FPGA programming and those who want to learn how to do it. Earlier, on the hub, an article was already considered with a similar task implemented on a PIC controller. And in this article we will talk about changing the brightness of the LED using the FPGA. So, the goal of the work: To master the concept of PWM and apply it in changing the brightness of the LED. To implement, use the VHDL programming language in the Xilinx ISE Project Navigator v12.3 development environment .



    Let's move on to achieving the goal


    For implementation, we need some piece of hardware with FPGAs, I chose the Spartan-3E Starter Kit ( DataSheet ), which was on hand . It is also necessary to install Xilinx ISE Project Navigator (I have version 12.3 installed). Basically, everything is ready to work. It remains only to connect the power to the board and connect it via USB to the computer for further programming.

    Part 1. Theory of changing the brightness of the LED.


    The brightness of the LED can be adjusted by applying different values ​​of constant voltage to it (for example, a variable resistor). But on our board there are LEDs without variable resistors, which can take the value '1' and glow in full brightness, or '0'. So how then to adjust the brightness of such a simple device? The answer is PWM. The whole point is that we will “blink” this LED so quickly that blinking will not even be visible to our eye, but we will just see a dimly lit LED. To be more precise, it is just that the LED has a transient during ignition, that is, it does not light up instantly. This is what we use, giving the unit for a very short period of time, so that the LED does not have time to light up at full brightness.

    Part 2. Creating a new project.


    Download ISE Project Navigator and poke File -> New Project. We write the name of the project (I have shim_habr), select the directory to save and select Top-level source type: HDL from the bottom. Click Next.



    Next, select the FPGA. In my case, Family: Spartan3E; Device: XC3S500E; Package: FG320; Speed: -4. All these data can be seen on the chip itself, or see in the datasheet.



    Next, select Preferred Language: VHDL, click Next and then Finish.



    The project is created. Now we need to add a module to it, in which we will describe the logic of the LED. In the upper left, find the New Source button and click on it:


    In the window that appears, select the VHDL Module and write any file name (you can name it with the shim_habr project). Click Next.


    Now we need to set the legs used in the project. You can not do this now and skip this step, and then write everything by hand. But since for our project we need only three legs, I entered them right here. So, we need the reference frequency from the quartz installed on the 50 MHz board, connected to the FPGA to the leg with the name C9, and we will also use two LEDs that are already installed on the board. Suppose these are the two right LEDs connected to the FPGA legs under the names E12 and F12. Call the quartz leg clk, set Direction: IN since we will read the frequency, and the legs with LEDs are led1 and led2 with the value Direction: OUT, because we will manage them.


    Click Next, then Finish. We see the opened text editor with the I / O ports of the project already filled in.
    entity shim_habr is
        Port (clk: in STD_LOGIC;
               led1: out STD_LOGIC;
               led2: out STD_LOGIC);
    end shim_habr;

    As I said, you could skip the previous step and enter it all manually. Next, we need to map the port names to the names of the FPGA legs. Right-click on the file name shim_habr.vhd in the hierarchy and select New Source.



    In the window that opens, select Implementation Constrains File and call this pin file. Click Next, then Finish.



    In the empty file that opens, write the following:
    NET "clk" LOC = "C9";
    NET "led1" LOC = "F12";
    NET "led2" LOC = "E12";

    Save.
    You can see the numbers of the legs in the datasheet, or in our case we have simplified the search - the numbers can be viewed directly on the board next to the desired peripheral:



    Part 3. Programming the constant brightness of the LED.


    Switch to the shim_habr.vhd file and write the code:
    architecture Behavioral of shim_habr is
     
    constant clk_freq: integer: = 50_000_000; - quartz frequency
    constant shim_freq: integer: = 10_000; - PWM frequency
    constant max_count: integer: = clk_freq / shim_freq; - PWM width
     
    signal count: integer range 0 to max_count: = 0; - counter of the frequency divider
    constant porog: integer: = max_count / 48; - pulse width of a logical unit
     
    begin
     
    process (clk)
    begin
      if rising_edge (clk) then
        if count = max_count then
          count <= 0;
        else
          count <= count + 1;
        end if;
      end if;
    end process;
     
    led1 <= '1' when count <porog else '0';
    led2 <= '1';
     
    end Behavioral;

    Now let's see what this code does. First, pay attention to the line led2 <= '1'. We light the second LED at full brightness, supplying a logical unit there, so that we have something to compare the brightness of the glow of the first LED. Next, we look at the declared registers and constants. The clk_freq constant stores the frequency of quartz in hertz; shim_freq is the frequency of the PWM in hertz. Accordingly, in order to get the PWM period we need, we need to divide the clock frequency by the PWM frequency, and we will get the number of main quartz clock cycles corresponding to the PWM period. In essence, this will be the width of the PWM. The result of division is written to the constant max_count. Next, create a counter count, which will cycle from 0 to max_count at a frequency of 50 MHz. We create process process (clk). The condition if rising_edge (clk) then waits for the next “tick” from quartz, and if it happens, it adds the count to the unit, checking if it has counted to the maximum value. Next, outside the process, we write the line
    led1 <= '1' when count <porog else '0';

    That is, a logical unit hangs on the LED when our counter is less than some threshold value porog (for me it is 1/48 of the entire period, see the declaration of constants), the rest of the period on the LED hangs a logical zero. This can clearly be shown in the figure:


    Part 4. Firmware.


    We save all the changes, select the shim_habr.vhd file in the hierarchy and look for the Configure Target Device process from below under the hierarchy and start it. We wait until the project is translated into the firmware file, after which the iMPACT program window opens, with which we will sew it into the FPGA.
    Double-click on Boundary Scan, and if your board is connected to your computer via USB, you will see something like the following:



    If you were not offered to select the firmware file for xc3s500e, then right-click on the appropriate chip and select the Assign Configuration File menu item. In the file selection window, select the newly created shim_habr.bit. Then again, right-click on xc3s500e, then Program. The firmware process starts, and then Program Successful appears. If everything went just like that, then you can look at the scarf =)

    Part 5. Programming a smooth change in the brightness of the LED.


    So, we see that we have two LEDs that are on - one is bright, the other is dim. Now try to make a smooth change in brightness. To do this, we need to make porog not a constant, but a variable, and smoothly change it from minimum to maximum.
    signal porog: integer range 0 to max_count: = 0;

    In order to set the rate of change of the threshold, we again need to divide the clock frequency and get a smaller one. For example, we want the threshold to increase by 1 every 1/600 second. Create a counter:
    constant max_count_div: integer: = clk_freq / 600;
    signal count_div: integer range 0 to max_count_div: = 0;

    and add to process (clk) at the time of the next “tick” if rising_edge (clk) then another condition:
        if count_div = max_count_div then
          count_div <= 0;
          - here the frequency is divided by 600.
        else
          count_div <= count_div + 1;
        end if;

    Now we need to write in the place where the frequency is divided by 600, increase the threshold by 1 and reset to 0 if the value reaches the maximum:
          if porog = max_count then
            porog <= 0;
          else
            porog <= porog + 1;
          end if;

    As a result, the overall picture will look like this:
    architecture Behavioral of shim_habr is
     
    constant clk_freq: integer: = 50_000_000; - quartz frequency
    constant shim_freq: integer: = 10_000; - PWM frequency
    constant max_count: integer: = clk_freq / shim_freq; - PWM width
     
    signal count: integer range 0 to max_count: = 0; - counter of the frequency divider
    signal porog: integer range 0 to max_count: = 0; - pulse width of a logical unit
     
    constant max_count_div: integer: = clk_freq / 600;
    signal count_div: integer range 0 to max_count_div: = 0;
     
    begin
     
    process (clk)
    begin
      if rising_edge (clk) then
        if count = max_count then
          count <= 0;
        else
          count <= count + 1;
        end if;
     
        if count_div = max_count_div then
          count_div <= 0;
          if porog = max_count then
            porog <= 0;
          else
            porog <= porog + 1;
          end if;
        else
          count_div <= count_div + 1;
        end if;
      end if;
    end process;
     
    led1 <= '1' when count <porog else '0';
    led2 <= '1';
     
    end Behavioral;

    We broadcast, we sew, we rejoice in success. =) You may think that the LED most of the time glows brightly, and it is dim only at the beginning of the cycle. I already wrote about this effect in my previous article on color music . The fact is that brightness depends on the threshold not linearly, but logarithmically. That is, for example, so that the brightness changes smoothly from minimum to maximum using a PWM of 1024 bits, it is necessary to take successively the following values ​​of the porog variable: 0, 16, 32, 64, 128, 256, 512, 1024.

    Homework.


    As you can see, the LED gradually gains brightness, and as soon as it is dialed, it immediately resets to 0 (in fact, they wrote and got it). As a workout, you can try to make a smooth set of brightness, and upon reaching the maximum start to smoothly decrease it to zero. For those who can easily cope with this task, you can try to make "running lights" of the eight available LEDs: the first diode lights up and goes out, then the second, etc. If it doesn’t work out, but it’s interesting, then ask - I will try to answer and explain.

    Conclusion


    So, we have mastered the application of PWM to the LED and learned to adjust the brightness of the LED using FPGA. The same method can be used to control the speed of the engine, the magnetic force of the coils, etc.
    I wish you all success in mastering FPGAs!

    PS: I'm sorry, I forgot the source of the project for this article at work ... I'll post it as soon as I pick it up from there (nothing is just installed at home). However, he is not really needed here, everything can be (and preferably - it is necessary!) To do it yourself, from scratch.

    UPD: The promised sources .

    Also popular now: