Over the last couple of weeks the pace of development for Python programmers who like to dabble in GPIO has gone up a gear. Both main GPIO systems for Python on the Raspberry Pi have been getting some enhancements.
RPi.GPIO, now at version 0.5.2a, has interrupts and threaded callback capability. You will probably have seen my three recent posts showing how to use those.
WiringPi for Python version 2 is in Beta testing. I discovered two bugs while I was trying it out. Both of those have been squashed. One by Gordon “Drogon” Henderson in the C source code and another by Phil “Gadgetoid” Howard in the Python install script. There’s a lot more stuff to test, but that’s not what I’m writing about today, so I’ll save that for a future post. Suffice it to say that the Python Gertboard software all works with WiringPi2 (at least it will when I release it soon with very minor tweaks).
RPi.GPIO PWM slipped in almost under the radar
As well as
breaking my interrupt examples – now fixed :-P adding further enhancements to the interrupt facility, e.g. incorporating debounce code, Ben Croston has also added software PWM to RPi.GPIO (0.5.2a onwards).
So what? Why would I care about PWM?
It’s kind of useful. Let’s back up for a minute in case anyone doesn’t know what it is/does. PWM is pulse-width modulation. Put simply, this is a signal that is switched between on and off, usually rather quickly.
Do you remember when you were a kid, how you sometimes flicked the light switch on and off repeatedly, really quickly, to see what would happen? And you were told not to do it in case something bad happened (and it never did)? That was crude PWM.
It’s a “square” waveform (on or off most of the time) and looks something like this…
It’s used for controlling a variety of devices – motor speed, servo* positions and other things. You can even use PWM to vary the brightness of leds by switching them on and off at varying frequency and duration.
There are two important parameters that determine PWM…
- Duty cycle
Frequency, in Hertz (Hz) is the number of times per second that a pulse is generated. This counts from the start of one pulse to the start of the next. i.e. from when the pulse starts to rise, to the next time it starts to rise. So it includes all the “on” time and “off” time and “in between” time for one complete wave cycle.
Duty cycle is the percentage of time between pulses that the signal is “high” or “On”. So if you have a frequency of 50 Hz and a duty cycle of 50%, it means that every 1/50th (0.02) of a second a new pulse starts and that pulse is switched off half way through that time period (after 1/100th or 0.01s).
Frequency 50 Hz, duty cycle 50%.
This gives a pulse 50 times per second (or every 0.02 seconds if you prefer). During each 0.02 second time period, the port will be “High” (3.3V) half the time and “Low” (0V) the other half (ignoring transition time). The oscilloscope trace looks like this…
…you can easily see that the signal is high (3.3V or 3V3) half of the time. If you don’t have an oscilloscope, a multimeter will let you measure the duty cycle if you set it on Volts. 50% duty cycle should read 50% of 3.3 Volts = 1.65V, although it might fluctuate a bit.
Frequency 50 Hz, duty cycle 80%.
This one has a duty cycle of 80%, so the 3V3 pulse is “High” 80% of the time and the average overall voltage delivered is 2.64V (80% of 3.3V).
If duty cycle is 100 or 0, frequency becomes irrelevant as the signal is either completely on or completely off.
Now let’s see how to control this with Python and RPi.GPIO
Requires RPi.GPIO 0.5.2a or higher
The following code contains all the elements you need to be able to control PWM on any available GPIO port on the Pi. The best way to see how it all works is in a live Python environment. To get there, open a terminal window and type…
then you can try out the code interactively, as you will see in the video. If you want to measure the output, you’ll need to attach an oscilloscope or multimeter to your chosen GPIO port (25 in my case) and GND like this…
Then type out or copy across (if logged in by ssh) snippets of code into the live python environment to see what they do. When you’re done, don’t forget to cleanup with
GPIO.cleanup(), then hit
CTRL+Z to exit the Python live environment. If any of that is unclear, you can see what I mean in the video. :)
# Don't try to run this as a script or it will all be over very quickly # it won't do any harm though. # these are all the elements you need to control PWM on 'normal' GPIO ports # with RPi.GPIO - requires RPi.GPIO 0.5.2a or higher import RPi.GPIO as GPIO # always needed with RPi.GPIO GPIO.setmode(GPIO.BCM) # choose BCM or BOARD numbering schemes. I use BCM GPIO.setup(25, GPIO.OUT)# set GPIO 25 as an output. You can use any GPIO port p = GPIO.PWM(25, 50) # create an object p for PWM on port 25 at 50 Hertz # you can have more than one of these, but they need # different names for each port # e.g. p1, p2, motor, servo1 etc. p.start(50) # start the PWM on 50 percent duty cycle # duty cycle value can be 0.0 to 100.0%, floats are OK p.ChangeDutyCycle(90) # change the duty cycle to 90% p.ChangeFrequency(100) # change the frequency to 100 Hz (floats also work) # e.g. 100.5, 5.2 p.stop() # stop the PWM output GPIO.cleanup() # when your program exits, tidy up after yourself
I scoped it for you
Many people won’t have an oscilloscope, so I made a little video using a live Python environment. This shows the scope trace – what happens to the PWM signal – when we change different parameters in Python.
That was the basics, what next?
So that was the basics of how to use the new PWM facility in RPi.GPIO (0.5.2a onwards). I will write another article with some more complex program controlled PWM operations in. And perhaps we’ll get it controlling a motor or dimming leds or something? Watch this space.
The Official RPi.GPIO PWM documentation is here if you wish to press on ahead.
* Software PWM in RPi.GPIO doesn’t seem to be able to control servos as they require very precise PWM timings. More on that in the next article.