RPi – PWM Tutorial

HardwareRaspberry Pi 4 Model B (2Gb)
Operating SystemRaspbian GNU/Linux 10 (buster)
LanguagePython 3
RPi.GPIO Revision3
GPIO InteraceGPIO18
Linux DeviceN/A
Parts neededRaspberry PI, Breadboard, Jumpers, 220ohm Resistor, LED, Logic Analyzer.
Date Last Revised22nd February 2020
Changes MadeCompletely revised (& tested) for Pi 4 and Python 3

Learning OutcomesHow to utilise both hardware and software PWM, Understand Duty Cycle, Use of Logic Analyzer

Our first one is to turn on a LED with a duty cycle of 50% for a period of 60 seconds. In terms of excitement it does not do much, but as mentioned before, we are keeping it simple so that we can understand what we are looking at when we use a Logic Analyzer.

The circuit is quite simple. We are using GPIO18 (BCM) and one of the GPIO GND pins. The LED is a standard LED, and the resistor is 220ohm.

Schematic

and for the more visually inclined, the breadboard looks as follows

Breadboard

Now for the code – Just scrape the code and save as a file. You can see the filename I have used, but you can make it shorter, just remember the .py extension.

The Code

#!/usr/bin/env python3
#####################################################################################
# Filename    : LogicAnalyzer_SoftwarePWM.py
# Description : Logic_Analyzer_With_Software_PWM
# Author      : Bob Fryer / Digital Shack
# modification: 22 Feb 2020
#####################################################################################

#####################################################################################
# Import the required Python Libraries
#####################################################################################

import RPi.GPIO as GPIO
import time

#####################################################################################
# Define the Variables Needed and the GPIO initialisation
#####################################################################################


global pwmobj                    # declare the pmwobj as a global variable
RPI_Pin = 18                     # define the RPI GPIO Pin we will use with PWM (PWM)
RPI_DutyCycle = 50               # define the Duty Cycle in percentage  (50%)
RPI_Freq = 500                   # define the frequency in Hz (500Hz)
RPI_LEDTime = 60                 # the time you want the LED to stay lit for (secs)
GPIO.setmode(GPIO.BCM)              # set actual GPIO BCM Numbers
GPIO.setup(RPI_Pin, GPIO.OUT)         # set RPI_PIN as OUTPUT mode
GPIO.output(RPI_Pin, GPIO.LOW)        # set RPI_PIN LOW to at the start
pwmobj = GPIO.PWM(RPI_Pin, RPI_Freq)  # Initialise instance and set Frequency
pwmobj.start(0)                       # set initial Duty cycle to 0 & turn on PWM

#####################################################################################
# Define our main task
#####################################################################################

def light():
    pwmobj.ChangeDutyCycle(RPI_DutyCycle)               # Set PWM Duty Cycle to 50%
    time.sleep(RPI_LEDTime)                             # Keep Led lit for 60 secs

#####################################################################################
# Define our DESTROY Function
#####################################################################################

def destroy():
    pwmobj.stop()                                               # stop PWM

#####################################################################################
# Finally the code for the MAIN program
#####################################################################################

if __name__ == '__main__':                                      # Program entry point
    print ('LED Turned on with Duty Cycle of ', RPI_DutyCycle)  # Print duty cycle
    try:
        light()                                                 # call light function
    except KeyboardInterrupt:                                   # Watches for Ctrl-C
        destroy()                                               # call destroy funct
    finally:
        destroy()                                               # call destroy funct

Now run the code, and if you have it setup correctly you should see your LED light up for 180 Seconds and then extinguish (you can safely press ctrl-c to terminate which will clean up, the same as waiting 180 seconds. The brightness of the LED is controlled by the Duty Cycle, which in case of the code above is running with a 50% Duty Cycle.

Now that is working we are going to use your Logic Analyzer and confirm that we seeing the following:

  • A duty cycle of 50%
  • A frequency of 500Hz

You will notice both on the schematic and the visual view two red talk bubbles. You will see them through out these tutorials, and they will show the logic analyzer connections used and where you need to place them to obtain the correct readings.

As each Logic Analyzer is different, you will need to refer to the manufacturers instructions, or look for a web site that shows your particular mode and how to install it on Windows or Linux or your Mac.

As previously mentioned, for most of these tutorials we are using PulseView (Open Source Software) which is available for many of the Logic Analyzer models around. Even if it is not, you will find that the Logic Analyzer software operates very similarly.

Once you start the software up, there are two critical selections you need to make to get a reading and this is the number of samples and the sample rate. The general rule is 4-5 times the highest frequency you want to capture.

I won’t try and explain everything, but I do recommend reading the Pulseview manual at https://sigrok.org/doc/pulseview/0.4.1/manual.html

If you run the program as shown above, you should end up with output similar to below showing roughly a 50% duty cycle, what we expected

Go into the python code and change the duty cycle to 25%, and as expected we will see in Pulseview showing close to 25%

Finally change it to Duty Cycle in the code and typically we expect 75% duty cycle.

If you have similar screens coming up, congratulations, you have proven that your Logic Analyzer is functioning. However, if you have been astute, you will notice our duty cycles are not exactly the values we set, and if you look closer, the frequency, is not exactly what we set.

There is a good reason for this, in fact, test this yourself by setting it back to 50% duty cycle, run the code, and at the same time, open your browser on the Raspberry PI and use a website that tests the speed of your internet connection. Basically we are going to push the CPU a little, and the Ethernet port or wireless port. And while this is all happening, we are going to capture the duty cycle again via your Logic Analyzer

What you will end up with would be similar to the following where the duty cycle is not constant and no where near the settings we set of 50%.

So the reason for this is that the Raspberry PI can use two methods to perform PWM which is broadly called software PWM and the other called hardware PWM, with the latter being far more accurate. However Hardware PWM is only available on GPIO12 / 13 / 18 & 19, where as software PVM is available on all pins. Another issue is that the hardware PVM is shared with the Audio, so if you are going to use the Audio Output, you are all out of luck. There are now HATS that can provide large numbers of hardware PWM if you have the need

We are only using it to demonstrate that it is far more accurate.

So using the same circuit, to use the hardware PWM, we change the code to use pigio library. Before you run the code, you need to start the pigpio daemon which you do by the following command

sudo pigpiod <enter>

You can set this as part of the Raspberry boot config if you need it on a permanent basis. For the moment, we need it just to conduct the test. When you reboot your Pi, the daemon will not longer be loaded.

So the code is as follows

#!/usr/bin/env python3
#####################################################################################
# Filename    : LogicAnalyzer_HardwarePWM.py
# Description : Logic_Analyzer_With_Hardware_PWM
# Author      : Bob Fryer / Digital Shack
# modification: 22 Feb 2020
#####################################################################################

#####################################################################################
# Import the required Python Libraries
#####################################################################################

import pigpio                                   # import the pigpio library
import time                                     # Utilise the python time library

#####################################################################################
# Define the Variables Needed
#####################################################################################

pigpio_pin = 18         # define the GPIO Pin for PWM - pigpio only uses BCM numbers
RPI_DutyCycle = 50      # define the Duty Cycle in percentage  (50%)
RPI_Freq = 500          # define the frequency in Hz (500Hz)
RPI_LEDTime = 180       # define the time you want the LED to stay lit for

#####################################################################################
# Initialisation
#####################################################################################

pwmobj=pigpio.pi()                              # Define pwmobj as pigpio.pi()
pwmobj.set_mode(pigpio_pin, pigpio.OUTPUT)      # Set GPIO pin as output


#####################################################################################
# Define our LOOP Function
#####################################################################################

def light():
    pwmobj.hardware_PWM(pigpio_pin, RPI_Freq, RPI_DutyCycle * 10000) # Call PWM Func
    time.sleep(RPI_LEDTime)                                          # Stay lit

#####################################################################################
# Define our DESTROY Function
#####################################################################################

def destroy():
    pwmobj.hardware_PWM(pigpio_pin, 0, 0)               # turn off the LED/PWM
    pwmobj.stop()                                       # release pigpio resources

#####################################################################################
# Finally the code for the MAIN program
#####################################################################################

if __name__ == '__main__':                              # Program entry point
    print ('LED Duty Cycle of ', RPI_DutyCycle)         # Print duty cycle
    try:
        light()                                         # Call light function
    except KeyboardInterrupt:                           # Watches for Ctrl-C
        destroy()                                       # Call destroy function
    finally:
        destroy()                                       # Call destroy function

You will notice that I have tried, as much as possible to keep a similar structure to the code, so you can compare.

Once you have loaded the code, run the code, again performing a trace using your Logic Analyzer, and you will notice a dramatic improvement in our duty cycle and frequency as you can see from the following image.

So that brings us to the end of this tutorial. So what have we learnt?

  • Raspberry Pi has both hardware and soft PWM
  • Software PVM is available on all Pins, whereas needing multiple Hardware PVM is only available with on a few select pins as mentioned. If you need more, then you will need a HAT.
  • Learnt to use and identify signals on your Logic Analyzer and confirm it is working
  • Raspberry PI has two methods of numbering the GPIO pins, BCM and BOARD. Rpi.GPIO can use either method, where as pigpio only uses BCM. It should be noted that WiringPi (a library we have not used) uses different pin numbering again.
dsadmin
Author: dsadmin