Technical Article

Reading Analog Values and PWM with LabVIEW FPGA

April 18, 2016 by Mark Narvidas

Learn LabVIEW FPGA by programming the on-board Xilinx FPGA of the student-focused embedded device NI myRIO.

Learn LabVIEW FPGA by programming the on-board Xilinx FPGA of the student-focused embedded device NI myRIO.

Related Articles

Utilizing LabVIEW FPGA on NI myRIO:

Part 1: What is LabVIEW FPGA
Part 2: Hello World (Blinking an LED)
Part 3: Reading Analog Values
Part 4: FIFOs
Part 5: 3rd-party Code


This article is the third in a series of five topics that cover the fundamentals of programming LabVIEW FPGA on the NI myRIO through simple hands-on examples. Last time, we covered how to turn a Light-Emitting Diode (LED) ON and OFF through the Digitial Line of the device. Today, we will see with several additions to our previous code, how the brightness of the LED can be adjusted as well. Moreover, we will look into the reading of analog values from a varying-resistance component such as a potentiometer. Finally, we will connect these two concepts to control the brightness of the LED through the rotation of the variable resistor. 

Recommended component list:

  1. 3.3V LED - 5V should also work, but expect less overall brightness
  2. 330 Ohm Resistor - used to limit the current flowing through the LED, see Article 2 of this series, where we talk about it in greater detail
  3. 10kOhm Potentiometer - any other varying-resistance component would also work, including a linear potentiometer or an LDR. While it is suggested to use a 10kOhm base value, most other values should be fine too
  4. (Optional) 1kOhm Pull-Down Resistor - this resistor should be used as a reference when reading varying-resistance components without a third pin (see below).
  5. (Optional) Jumper wires and a breadboard

Component Wiring

Last time, we used Port C of the myRIO which hosts two Analog Inputs, two Analog Outputs, and 8 Digital Lines. However, that's not even half of its peripherals - it has two more ports with 2x Analog Outputs, 4x Analog Inputs and 16x DIO each. To appreciate the vast number of I/O this device provides and to get more familiar with it, let's use Port B for the following example.

To keep our circuit tidy as it grows bigger, let's move to a breadboard. The 330 Ohm current-limiting resistor will go to the Anode of the LED which will then be connected to the DIO0 terminal of the myRIO Port B (see Figure 1). Connect the other pin of the LED to Digital Ground (DGND) terminal. Next, we need to wire the variable resistor (potentiometer) to the myRIO. To do so, we will connect the middle pin of the potentiometer (sometimes referred to as the "wiper") to Analogue Input (AI0) of Port B and the remaining pins should be connected to the AGND and +5V terminals (see Figure 2). For a more detailed connection layout of myRIO, refer to its datasheet (PDF). The circuit diagram is also shown in Figure 3 below for better reference.


Figure 1 Port B with all connections identified 


Figure 2 Breadboard with all connections identified

Figure 3 Circuit Diagram


Having all of our circuitry wired up, let's create a new LabVIEW project same as in previous articles. Within the newly-created project, locate the NI myRIO target and create a new VI called FPGA.VI under Chassis >> FPGA Target (see our first article for full explanation if you need a refresher). Our main FPGA VI should look similar to what is shown in Figure 4 below. The completed example code is attached at the bottom of the article if you are keen to test it straight away, but to make sure we understand what is going on here, let's analyze it as well.

This time, we will create two while loops - one for data acquisition ("DAQ Loop") and one for driving the LED ("PWM Loop"). The first one will read and scale the potentiometer data while the second will produce a Pulse-Width Modulation signal on the DIO0 pin. Here is a short but sweet article on PWM if you are feeling a bit rusty in regard to the topic. Just like before, we need to drag peripherals into our Block Diagram. We will need one for Analog Input from the potentiometer (A) and one for producing a Pulse Width Modulation signal (B) on the DIO0 line. Notice how the names used in LabVIEW match the ones we've indicated in Figure 3. Once we read in the potentiometer value, we also need to scale it (B). This scaling allows us to map the 12bit value coming in from the single-ended analog input channel (sampled through an onboard ADC) to a unit interval varying from 0 to 1. Therefore, values that would usually range from 0 to 4096 for the furthest potentiometer positions are now represented as fixed-point values between 0 to 1 on the Indicator called "Duty Cycle" (C). Any other value in between will also be scaled accordingly; this will essentially control how bright we want our LED to be.

The "PWM Loop" includes a stacked sequence structure (D), which enforces sequential behavior (remember, LabVIEW is inherently parallel!) for the four consecutive sections. The first section drives the LED Pin (DIO0) HIGH. The next section waits for a given amount of time calculated based on the Duty Cycle calculated. Notice how in (C) we are reading from the same location in multiple places, this is called a Local Variable. A Local Variable can be made in LabVIEW by right-clicking on an indicator or a control and selecting Create >> Local Variable. While useful in a number of scenarios, they can sometimes cause what's called a Race Condition, therefore, be conservative with their usage. In our case, since we are only writing to the variable in one location, this problem is avoided.

The length of time that our LED is on also depends on the frequency we're running our code at (F). Since our eyes cannot distinguish light flickering at around the 50Hz mark, we conveniently set this on our FPGA.VI Front Panel control called "Frequency (Hz)" (F). Therefore, instead of flickering, we will actually see the LED dimming proportionally to how long we keep the LED on and off (sections 3 & 4 of the stacked sequence structure (D) turn it off and wait for the remainder of time). The time that the LED is kept on in relation to it being off is determined by the Duty Cycle. In literature, you will often see Duty Cycle represented as a percentage. In our case it's a unity value varying between 0 (0%) and 1 (100%). Besides that, everything else stays the same. Let's also not forget to configure our timing (E) to be in mSec (milliseconds). At this point, we should be ready to compile our code and test it out.



Figure 4 LabVIEW FPGA Front Panel and Block Diagram explained

Running the FPGA Code

Finally, if our circuitry matches what's shown in Figures 2 and 3, as well as having our code functionality not differing from Figure 4, we should be able to adjust the brightness of the LED by rotating the potentiometer. Functionally, the LED is turning ON and OFF really quickly, however, our eyes cannot distinguish that and we actually just see different light levels. To verify this, you could change the "Frequency (Hz)" control on the Front Panel of the FPGA VI to a lesser value, say, 1Hz. Since we have made it as Control, LabVIEW allows us to tweak it on-the-fly so no additional compiling is necessary. As an additional exercise, you could try to adjust the software scaling to be different or inverted (either through "Scaling" control (B) or a custom code snippet). If you decided to control a set of LEDs from the same potentiometer, all you'd need to do is make a copy of the bottom loop and change the Digital Line since we're reading the "Duty Cycle" value from the "DAQ Loop" which is independent.

Congratulations on creating a full Analog Input and Output loop using NI myRIO. Next time, we will move to a more advanced topic of LabVIEW FPGA - Buffers. Using them, we will learn how high-speed, high-throughput applications that can be made in both hobby projects and the industry alike!

  • sieger April 27, 2016

    It would be good to have explanation about data types used for variables.
    At first I draw my own vi’s and it was not working well because default type definitions were different.
    16/16 instead 24/12 and 12 bits for ADC output.
    I know coercion, but never before faced blue dot on block output. Do you know explanation?

    Like. Reply
    • Mark Narvidas April 29, 2016
      Hi Sieger, Happy you sorted it out! Blue coercion dot means that LabVIEW will not automatically account for what the output data needs to be. For instance, if you are multiplying two fixed-point 8bit numbers your expected output should be 16bit wide. LabVIEW by default will try and automatically decide that for you, but if you manually go and set the output to be less than 16bit, or if LabVIEW cannot figure it out, a blue coercion dot will appear. Treat it like a warning, signalling that a case of overflow/rounding is potentially possible. In our example, this is not a problem as our Duty Cycle will be varying between 0 and 1 only. Cheers, Mark
      Like. Reply
      • sieger May 31, 2016
        Hello Mark, when we can expect next chapters of this valuable tutorial? Best regards
        Like. Reply