Lesson ADC + PWM

Pin Selection and Pin Mode

Objective

Know how to select a specific functionality of a given LPC40xx pin. Know how to select a pin mode.

Pin Selection

Every GPIO pin of the LPC40xx is capable of other alternative functionalities. Pin selection is the method by which a user is able to designate the functionality of any given pin. For example, GPIO Pin 0.0 can alternatively be used for CAN channel 1 receive, UART channel 3 transmit, and I2C channel 1 data line. 

Screen-Shot-2020-09-22-at-6.58.24-PM.png

Figure 1A. LPC40xx User Manual IOCON

 

Figure 1B. I/O Pin Select Mux (from LPC2148, for illustration purposes only)

In order to select the I2C2_SDA functionality of pin 0.10, one must set bit 1, reset bit 0 & 3 of the IOCON register function field to 010.

// Using LPC40xx.h pointers
LPC_IOCON->P0_10 &= ~0b010		// reset all bits of function[2:0]
LPC_IOCON->P0_10 |= 0b010;		// set the function bit for I2C2

Pin Mode

The LPC17xx has several registers dedicated to setting a pin's mode. Mode refers to enabling/disabling pull up/down resistors as well as open-drain configuration. PINMODE registers allow users to enable a pull-up (00), enable pull up and pull down (01), disable pull up and pull down (10), and enable pull-down (11). PINMODE_OD registers allow users to enable/disable open-drain mode. 

 

Figure 2. LPC17xx User Manual PINMODE & PINMODE_OD

 

 

Figure 3. LPC17xx User Manual PINMODE0 

 

Figure 4. LPC17xx User Manual PINMODE_OD0

For example, if one desires to configure pin 0.09 to enable a pull-up resistor and open drain mode, one must clear bits 18 & 19 of PINMODE0 register, and set bit 9 of register PINMODE_OD0.

// Using the memory address from the datasheet
*(0x4002C040) &= ~(0x3 << 18);		// Clear bits 18 & 19
*(0x4002C068) |= (0x1 << 9);		// Set bit 9

// Using LPC17xx.h pointers
LPC_PINCON->PINMODE0 &= ~(0x3 << 18);		// Clear bits 18 & 19
LPC_PINCON->PINMODE_OD0 |= (0x1 << 9);		// Set bit 9

You may find it helpful to automate register setting and/or clearing. Per our Coding Standards, inline functions should be used (not Macros).

 

Figure 5. LPC17xx Pin Registers & Circuit (credit: https://sites.google.com/site/johnkneenmicrocontrollers/input_output/io_1768) 

ADC (Analog to Digital Converter)

Objective

To learn about the use of ADCs, their different types, their related parameters, and how to set up an ADC driver for the LPC40xx.

    What does ADC accomplish?

    An Analog to Digital Converter is needed whenever one needs to interface a digital system with an analog device. For example, if one needs to read the voltage across a resistor, and use the value within an algorithm running on the SJOne board, an ADC circuit is needed to convert the analog voltage to a discrete digital value. Luckily, the LPC40xx, like most microcontrollers, includes an ADC circuit that we can utilize.

    Different types of ADC circuits

    Flash ADC

    The simplest and fastest ADC circuit relies on a series of comparators that compare the input voltage to a range of voltage reference values. The digital output of the comparators is wired to a priority encoder. The output of the priority encoder represents the binary value of the input voltage.

    Note that the number of bits of the binary output (n) requires (2n-1) comparators. Therefore, the circuit complexity grows exponentially with respect to the number of bits used to represent the converted value (resolution). 

      

     

    Figure 1. Flash ADC Circuit (credit: allaboutcircuits.com)

     

      

    Figure 2. Flash ADC Timing (credit: allaboutcircuits.com)

     

    Digital Ramp ADC

    This type of ADC utilizes an up counter, a comparator, a DAC, and a register. DACs (Digital Analog Converters), as their name suggests, perform the inverse operation of an ADC, i.e. They convert a binary input into an analog voltage output. The up counter starts at zero and counts up synchronously. The output of the counter is wired to the DAC. The analog output of the DAC is compared to the analog input signal. As long as the comparator indicates that the input voltage is larger than the DAC's value, the counter continues to increment. Eventually, the DAC's output will exceed the input voltage, and the comparator will activate the counter's reset signal as well as the register's load signal. The register's output represents the binary value of the input analog signal.

    Note that because the counter starts from zero with every sample, the time it takes the circuit to produce the digital output is inconsistent. 

     

    Figure 3. Digital Ramp ADC Circuit (credit: allaboutcircuits.com)

     

      

     

    Figure 4a. Digital Ramp ADC Timing (credit: allaboutcircuits.com)

      

    Figure 4b. Digital Ramp ADC Timing Variance (credit: allaboutcircuits.com)

    Successive Approximation ADC

    A successive approximation ADC works very similarly to a digital ramp ADC, except it utilizes a successive approximation register (SAR) in place of the counter. The SAR sets each bit from MSB to LSB according to its greater/less than logic input signal.

    This type of ADC is more popular than flash and digital ramp due to its consistent timing and relatively scalable design.

      

    Figure 5. Successive Approximation ADC Circuit (credit: allaboutcircuits.com)

      

    Figure 6. Successive Approximation ADC Timing (credit: allaboutcircuits.com)

    Tracking ADC

    A Tracking ADC works similarly to the Digital Ramp ADC, except instead of an up counter, it utilizes an up-down counter. The output of the comparator determines whether the counter increments or decrements. It doesn't use a register to hold the processed value since it's constantly tracing the input value.

    Note that this type of ADC does not respond well to large changes of the input analog signal. Additionally, it suffers from constantly going over and under the input value. This is known as bit bobble. 

      

    Figure 7. Tracking ADC Circuit (credit: allaboutcircuits.com)

     

      

     

    Figure 8. Tracking ADC Timing (credit: allaboutcircuits.com)

    DAC-free ADCs

    Besides Flash ADC, all previous ADC circuits rely on using DACs to convert an estimated digital value to an analog one and compare it to the input signal. There are other types of ADC technologies that do not use DACs. They rely on the known time it takes an RC circuit to discharge to match the input analog signal. Single Slope, Dual Slope, and Delta-Sigma ADCs implement this concept.

    ADC Parameters

    Resolution

    This is typically the most-highlighted aspect of any ADC technology. Resolution refers to the number of bits of the ADC's output. It's a measurement of how coarse/fine the converted value is. A four bit 5V ADC offers 16 values for the voltage range 0 V to 5 V (i.e. roughly 312 mV per bit increment). A 10 bit 5V ADC offers 1024 values for the same voltage range (roughly 5 mV per bit increment). 

    Sampling Frequency

    This is simply the circuit's latency (i.e. the rate of converting an analog input signal to digital bits). The highest frequency of an analog signal that a given ADC circuit is able to adequately capture is known as Nyquist frequency. Nyquist frequency is equal to one-half of the circuits sampling frequency. Therefore, to adequately convert an analog signal of frequency n Hz, one must have an ADC circuit with 2n Hz sampling frequency. Otherwise, aliasing happens. Aliasing occurs when an ADC circuit samples an input signal too slowly, thus producing an output signal that is not the true input signal, but rather an alias of it.

       

    Figure 9. ADC Aliasing

    Step Recovery

    This is a measurement of how quickly an ADC's output is able to respond to a sudden change in input. For example, flash and successive approximation ADCs are able to adjust relatively quickly to input changes while tracking ADC struggles with large input changes.

    Range

    This is a measurement of the range of voltages that an ADC circuit is able to capture and output. For example, the LPC40xx has a range of 0V to 3.3V. Other ADCs may have bigger ranges or even variable ranges that a user can select, such as this device: https://www.mouser.com/ds/2/609/AD7327-EP-916882.pdf

    Error

    This is a measurement of the systematic error of any given ADC circuit. This is measured by comparing the actual input signal to its digital output equivalent. Note that, this error measurement is only valid within the range of the ADC in question. 

    ADC Driver for LPC40xx

    Screen-Shot-2020-09-22-at-7.14.55-PM.png

    Figure 10. LPC40xx User Manual ADC Instructions

    Screen-Shot-2020-09-22-at-7.15.06-PM.png

    Figure 11. LPC40xx User Manual ADC Control Register

    PWM (Pulse Width Modulation)

    Objective

    To learn about the use of PWM signals, their related parameters, and how to set up an ADC driver for the LPC40xx.

      What is a PWM signal?

      A Pulse Width Modulation (PWM) signal is simply a digital signal that is on (high) for part of its period and off (low) for the remainder of its period. If such a signal is on half the time and off the other half, then it's a square wave. 

       pwm.jpg 

      Figure 1. PWM Signal (credit: www.bvsystems.be)

      PWM Parameters

      Duty Cycle

      A duty cycle of a certain PWM signal is given as a percentage, and it represents the ratio of the signal "on" time to the signal's full period. In other words, if the duty cycle of a signal is said to be 75%, it means that this signal is high for 75% of its period and low for the remaining 25%. 100% duty cycle implies a constantly high signal, and a 0% duty cycle implies a constantly grounded signal.

      Frequency

       The frequency of a PWM signal (just like any electrical signal) refers to the rate at which the signal repeats per second. A 1 Hz signal repeats every 1 second while a 1 kHz signal repeats every 1 millisecond.

        pwm-signal.png 

      Figure 2. Parameters of a PWM signal

      PWM Signal Applications

      Generally speaking, a PWM signal is a way for a digital system to interface with an analog device. 

      DC Motors

      DC Motors are controllable via a PWM signal. The duty cycle of the signal is typically linearly proportional to the velocity of the motor. For example, a 60 RPM motor driven by a 50% duty cycle PWM signal will rotate at a 30 RPM velocity. It's worth noting that such a signal needs to run at a high enough frequency (10 kHz for example) so the motor can rotate smoothly. A low-frequency PWM signal (say 10 Hz) will result in an observable choppy motor motion. 

      LEDs

      The brightness of an LED can be controlled via a reasonably high-frequency PWM signal. A 5V 50% PWM signal applied to an LED will have the same brightness effect as a constant 2.5V signal applied to the same LED. 

      Servos

       Servos are typically controlled by a 50 Hz PWM signal, where the duty cycle of the signal determines the angle of the servo. Typically, the duty cycle ranges from 5% to 10%, causing the servo to rotate to its smallest and largest angles, respectively. 

      PWM Driver for LPC40xx

      Screen-Shot-2020-09-22-at-7.37.44-PM.png

      Theory of Operation

      Behind every PWM is a Peripheral (HW) counter (TC).  For "Single Edge" PWM, when the counter starts from zero, the output of the PWM (GPIO) can reset back to logical 1.  Then, when the value of the "Match Register (MR)" occurs, then the PWM output can set to logical 0.  Therefore, the maximum limit of the TC controls the frequency of the PWM signal, and the MR registers control the duty cycle.

      LPC Microcontroller PWM Module Timing Diagram

      Software PWM

      This section demonstrates the LPC PWM operation in software.  The LPC processor implements similar code, but in the hardware.

      void lpc_pwm(void)
      {
        bool GPIO_PWM1 = true; // Hypothetical GPIO that this PWM channel controls
        uint32_t TC = 0;    // Hardware counter
        uint32_t MR0 = 500; // TC resets when it matches this
        
        // Assumptions: SW instructions add no latency, and delay_us() is the only instruction that takes time
        while (1) 
        {
          if (++TC >= MR0) {
            TC = 0;
            GPIO_PWM1 = true;  // GPIO is HIGH on the reset of TC
          }
          
          if (TC >= MR1) {
            GPIO_PWM1 = false; // GPIO resets upon the match register
          }
          
          // 1uS * 500 = 500uS, so 2Khz PWM
          delay_us(1);
        }
      }

      Registers of relevance

      What you are essentially trying to control is the PWM frequency and the PWM duty cycle.  For instance, a 50% duty cycle with just a 1Hz PWM will blink your LED once a second.  But a 50% duty cycle 1Khz signal will dim your LED to 50% of the total brightness it is capable of.  There are "rules" that the PWM module uses to alter a GPIO pin's output and these rules are what you are trying to understand.  So read up on "Rules of Single Edge Conrolled PWM" in your datasheet and overall the LPC PWM chapter at minimum 10 times to understand it.  You may skip the sections regarding "capture", and "interrupts".  Furthermore, to use the simplified PWM, you can use the Single Edge PWM rather than the more complex Double Edge because the Single Edge edge PWM is controlled by a dedicated MR register.

      TC, MR0, MCR and PR: The Prescalar (PR) register controls the tick rate of the hardware counter that can alter the frequency of your PWM.  For instance, when the CPU clock is 10Mhz, and the PR = 9, then the TC counts up at the rate of 10/(9+1) = 1 Mhz.  Hence, the PR affects the frequency, but we still need a "max count" to set the frequency with precision.  So if the TC increments at 1Mhz, and MR0 is set to 100, then you will have 1000Khz/100 = 10Khz PWM.

      The MCR register controls what happens to the TC when a match occurs.  The one subtle, but important thing we need to do is that when the MR0 match occurs, we need the TC to reset to zero to be able to use MR0 as a frequency control.

      TCR and PCR:  The PCR register enables the channels, so if you have PWM1.4 as an output, that means you need to enable channel 4.  The TCR register is a key register that will enable your PWM module.

       

      Lab: ADC + PWM

      Objective

      Improve an ADC driver, and use an existing PWM driver to design and implement an embedded application, which uses RTOS queues to communicate between tasks.

      This lab will utilize:

      • ADC Driver
        • You will improve the driver functionality
        • You will use a potentiometer that controls the analog voltage feeding into an analog pin of your microcontroller
      • PWM Driver
        • You will use an existing PWM Driver to control a GPIO
        • An led brightness will be controlled, or you can create multiple colors using an RGB LED
      • FreeRTOS Tasks
        • You will use FreeRTOS queues


      Assignment

      Preparation:
      Before you start the assignment, please read the following in your LPC User manual (UM10562.PDF)
      - Chapter 7: I/O configuration
      - Chapter 32: ADC

       


      Part 0: Use PWM1 driver to control a PWM output pin

      The first thing to do is to select a pin to function as a PWM signal. This means that once you select a pin function correctly, then the pin's function is controlled by the PWM peripheral and you cannot control the pin's HIGH or LOW using the GPIO peripheral. By default, a pin's function is as GPIO, but for example, you can disconnect this function and select the PWM function by using the IOCON_P2_0

      1. Re-use the PWM driver
        • Study the pwm1.h and pwm1.c files under l3_drivers directory
      2. Locate the pins that the PWM peripheral can control at Table 84: FUNC values and pin functions
        • These are labeled as PWM1[x] where PWM1 is the peripheral, and [x] is a channel
          • So PWM1[2] means PWM1, channel 2
        • Now find which of these channels are available as a free pin on your SJ2 board and connect the RGB led
          • Set the FUNC of the pin to use this GPIO as a PWM output
      3. Initialize and use the PWM-1 driver
        • Initialize the PWM1 driver at a frequency of your choice (greater than 30Hz for human eyes)
        • Set the duty cycle and let the hardware do its job :)
      4. You are finished with Part 0 if you can demonstrate control over an LED's brightness using the HW based PWM method

      adc_pwm__pwm_block.png

      #include "pwm1.h"
      
      #include "FreeRTOS.h"
      #include "task.h"
      
      void pwm_task(void *p) {
        pwm1__init_single_edge(1000);
        
        // Locate a GPIO pin that a PWM channel will control
        // NOTE You can use gpio__construct_with_function() API from gpio.h
        // TODO Write this function yourself
        pin_configure_pwm_channel_as_io_pin();
        
        // We only need to set PWM configuration once, and the HW will drive
        // the GPIO at 1000Hz, and control set its duty cycle to 50%
        pwm1__set_duty_cycle(PWM1__2_0, 50);
        
        // Continue to vary the duty cycle in the loop
        uint8_t percent = 0;
        while (1) {
          pwm1__set_duty_cycle(PWM1__2_0, percent);
          
          if (++percent > 100) { 
            percent = 0; 
          }
          
          vTaskDelay(100);
        }
      }
      
      void main(void) {
        xTaskCreate(pwm_task, ...);
        vTaskStartScheduler();
      }

       


      Part 1: Alter the ADC driver to enable Burst Mode
      • Study adc.h and adc.c files in l3_drivers directory and correlate the code with the ADC peripheral by reading the LPC User Manual.
        • Do not skim over the driver, make sure you fully understand it.
      • Identify a pin on the SJ2 board that is an ADC channel going into your ADC peripheral.
        • Reference the I/O pin map section in Table 84,85,86: FUNC values and pin functions
      • Connect a potentiometer to one of the ADC pins available on SJ2 board. Use the ADC driver and implement a simple task to decode the potentiometer values and print them. Values printed should range from 0-4095 for different positions of the potentiometer.
      // TODO: Open up existing adc.h file
      // TODO: Add the following API
      
      /**
       * Implement a new function called adc__enable_burst_mode() which will
       * set the relevant bits in Control Register (CR) to enable burst mode.
       */
      void adc__enable_burst_mode(void);
      
      /**
       * Note: 
       * The existing ADC driver is designed to work for non-burst mode
       * 
       * You will need to write a routine that reads data while the ADC is in burst mode
       * Note that in burst mode, you will NOT read the result from the GDR register
       * Read the LPC user manual for more details
       */
      uint16_t adc__get_channel_reading_with_burst_mode(uint8_t channel_number);

      adc_pwm__adc_block.png

      #include "adc.h"
        
      #include "FreeRTOS.h"
      #include "task.h"
      
      void adc_pin_initialize(void) {
        // TODO: Ensure that you also set ADMODE to 0
        // TODO: Ensure you set pull/up and pull/down bits 0
        // TODO: Then use gpio__construct_with_function(...)
      }
      
      void adc_task(void *p) {
        adc_pin_initialize();
        adc__initialize();
        
        // TODO This is the function you need to add to adc.h
        // You can configure burst mode for just the channel you are using
        adc__enable_burst_mode();
        
        // Configure a pin, such as P1.31 with FUNC 011 to route this pin as ADC channel 5
        // You can use gpio__construct_with_function() API from gpio.h
        pin_configure_adc_channel_as_io_pin(); // TODO You need to write this function
        
        while (1) {
          // Get the ADC reading using a new routine you created to read an ADC burst reading
          // TODO: You need to write the implementation of this function
          const uint16_t adc_value = adc__get_channel_reading_with_burst_mode(ADC__CHANNEL_2);
          
          vTaskDelay(100);
        }
      }
      
      void main(void) {
        xTaskCreate(adc_task, ...);
        vTaskStartScheduler();
      }

       


      Part 2: Use FreeRTOS Queues to communicate between tasks
      • Read this chapter to understand how FreeRTOS queues work
      • Send data from the adc_task to the RTOS queue
      • Receive data from the queue in the pwm_task

      adc_pwm__data_flow.png

      #include "adc.h"
        
      #include "FreeRTOS.h"
      #include "task.h"
      #include "queue.h"
      
      // This is the queue handle we will need for the xQueue Send/Receive API
      static QueueHandle_t adc_to_pwm_task_queue;
      
      void adc_task(void *p) {
        // NOTE: Reuse the code from Part 1
        
        int adc_reading = 0; // Note that this 'adc_reading' is not the same variable as the one from adc_task
        while (1) {
          // Implement code to send potentiometer value on the queue
          // a) read ADC input to 'int adc_reading'
          // b) Send to queue: xQueueSend(adc_to_pwm_task_queue, &adc_reading, 0);
          vTaskDelay(100);
        }
      }
      
      void pwm_task(void *p) {
        // NOTE: Reuse the code from Part 0
        int adc_reading = 0;
      
        while (1) {
          // Implement code to receive potentiometer value from queue
          if (xQueueReceive(adc_to_pwm_task_queue, &adc_reading, 100)) {
          }
          
          // We do not need task delay because our queue API will put task to sleep when there is no data in the queue
          // vTaskDelay(100);
        }
      }
      
      void main(void) {
        // Queue will only hold 1 integer
        adc_to_pwm_task_queue = xQueueCreate(1, sizeof(int));
      
        xTaskCreate(adc_task, ...);
        xTaskCreate(pwm_task, ...);
        vTaskStartScheduler();
      }

       


      Part 3: Allow the Potentiometer to control the RGB LED

      At this point, you should have the following structure in place:

      • ADC task is reading the potentiometer ADC channel, and sending its values over to a queue
      • PWM task is reading from the queue

      Your next step is:

      • PWM task should read the ADC queue value, and control the an LED

       



      Final Requirements

      Minimal requirement is to use a single potentiometer, and vary the light output of an LED using a PWM. For extra credit, you may use 3 PWM pins to control an RGB led and create color combinations using a single potentiometer.

      • Make sure your Part 3 requirements are completed
      • pwm_task should print the values of MR0, and the match register used to alter the PWM LEDs
        • For example, MR1 may be used to control P2.0, so you will print MR0, and MR1
        • Use memory mapped LPC_PWM registers from lpc40xx.h
      • Make sure BURST MODE is enabled correctly.
      • adc_task should convert the digital value to a voltage value (such as 1.653 volts) and print it out to the serial console
        • Remember that your VREF for ADC is 3.3, and you can use ratio to find the voltage value
        • adc_voltage / 3.3 = adc_reading / 4095