Pi IoT In C Using Linux Drivers - The PWM Driver
Written by Harry Fairhead   
Monday, 10 May 2021
Article Index
Pi IoT In C Using Linux Drivers - The PWM Driver
Simple PWM Functions
Complete PWM Program

Many programmers are unaware that there is a perfectly good PWM driver that will let you do just about anything you want to, without having to resort to any clever programming.

This content comes from my newly published book:

Raspberry Pi IoT In C Using Linux Drivers

By Harry Fairhead

Cdrivers360

Buy from Amazon.

Contents

  1.  Choosing A Pi For IoT

  2. C and Visual Studio Code

  3.  Drivers: A First Program

  4.  The GPIO Character Driver
         Extract: GPIO Character Driver

  5. GPIO Using I/O Control

  6.  GPIO Events

  7.  The Device Tree
        Extract: The DHT22

  8.  Some Electronics

  9.  Pulse Width Modulation
    Extract:  The PWM Driver 

  10. SPI Devices
    Extract: The SPI Driver ***NEW!!!

  11. I2C Basics

  12. The I2C Linux Driver

  13. Advanced I2C

  14. Sensor Drivers – Linux IIO & Hwmon

  15. 1-Wire Bus

  16. Going Further With Drivers

  17. Appendix I

 <ASIN:1871962641>

<ASIN:B08W9V7TP9>

One way around the problem of getting a fast response from a microcontroller is to move the problem away from the processor. In the case of the Pi's processor there are some built-in devices that can use GPIO lines to implement protocols without the CPU being involved. In this chapter we take a close look at pulse width modulation (PWM) including generating sound and driving LEDs.

When performing their most basic function, i.e. output, the GPIO lines can be set high or low by the processor. How quickly they can be set high or low depends on the speed of the processor.

Using the GPIO line in its Pulse Width Modulation (PWM) mode you can generate pulse trains up to 4.8MHz, i.e. pulses just a little more than 0.08µs. The reason for the increase in speed, a factor of at least 100, is that the GPIO is connected to a pulse generator and, once set to generate pulses of a specific type, the pulse generator just gets on with it without needing any intervention from the GPIO line or the processor. In fact, the pulse output can continue after your program has ended if you forget to reset it.

Of course, even though the PWM line can generate pulses as short as 0.1µs, it can only change the pulses it produces each time that the processor can modify them. For example, you can't use PWM to produce a single 0.1µs pulse because you can't disable the PWM generator in just 0.1µs. This said, hardware generated PWM is available on the Pi and there is a good PWM driver that makes it very easy to use.

In book, but not in this extract

  • Some Basic Pi PWM Facts
  • Software PWM

The PWM Driver

All Pi models have two PWM channels implemented in hardware, but on models earlier than the Pi 4 these are also used to generate audio. What this means is that if you want to use hardware PWM on a Pi Zero or earlier you have to disable or at least not use audio at the same time. You can use both on a Pi 4, but notice that the PWM channels and the audio share the same clock signal and this can still cause problems.

The two PWM hardware modules can be configured to drive different GPIO lines. For the Pi the standard configuration is to have PWM0 drive either GPIO18 or GPIO12 and PWM1 drive either GPIO13 or GPIO19.

There are two PWM drivers available. One that activates a single PWM channel and one that activates both the available channels. The documentation for both is:

Name:  pwm
Info:  Configures a single PWM channel
       Legal pin,function combinations for each channel:
       PWM0:12,4(Alt0) 18,2(Alt5) 40,4(Alt0) 52,5(Alt1)
       PWM1:13,4(Alt0) 19,2(Alt5) 41,4(Alt0) 45,4(Alt0)
53,5(Alt1) N.B.: 1) Pin 18 is only one available on all platforms, and it is the one used by the I2S audio interface. Pins 12 & 13 might be better choices on A+/B+/Pi2. 2) The onboard analogue audio output uses both PWM channels.
        3) So be careful mixing audio and PWM.
        4) Currently the clock must have been enabled and        configured by other means. 

Load: dtoverlay=pwm,<param>=<val>
Params:pin Output pin (default 18) - see table func Pin function (default 2 = Alt5) 
 - see above clock PWM clock frequency (informational) 
Name: pwm-2chan 
Info: Configures both PWM channels Legal pin,function combinations for each channel: PWM0: 12,4(Alt0) 18,2(Alt5) 40,4(Alt0) 52,5(Alt1) PWM1: 13,4(Alt0) 19,2(Alt5) 41,4(Alt0) 45,4(Alt0)
53,5(Alt1) N.B.: 1) Pin 18 is the only one available on all
platforms, and it is the one used by the I2S audio interface. Pins 12 and 13 might be better choices on an
A+/B+/Pi2. 2) The onboard analogue audio output uses both
PWM channels. 3) So be careful mixing audio and PWM. 4) Currently the clock must have been enabled
and configured by other means. Load: dtoverlay=pwm-2chan,<param>=<val> Params: pin Output pin (default 18) - see table pin2 Output pin for other channel
(default 19) func Pin function (default 2 = Alt5)
- see above func2 Function for pin2 (default 2 = Alt5) clock PWM clock frequency (informational)

Note: There is a relatively recent (late 2019) patch to the driver that fixes a problem at high PWM frequencies. Use:

sudo apt update

followed by

sudo apt full-upgrade

to make sure you have the up-to-date driver.

In simple terms you can use one or two channels of PWM and you would be well advised to use GPIO18 for PWM0 and GPIO19 for PWM1 on all modern Pis. Notice that you cannot currently use the driver to set the frequency of the PWM clock, but this is automatically enabled at a default frequency. You can find what the frequency is using:

vcgencmd measure_clock pwm

at the command prompt. It only reports an accurate value if the PWM driver is loaded and enabled. On a Pi Zero and a Pi 4 it reports 99995000, i.e. approximately 100Mhz, and empirical measurements give the effective clock rate, after division by 5, as 20MHz for both the Pi 4 and Pi Zero. The clock rate is important because it determines the resolution of the duty cycle – see later.

If you load either driver by adding:

dtoverlay=pwm

or

dtoverlay=pwm-2chan

to boot/config.txt, you will discover that on reboot you have a new pwmchip0 folder in the /sys/pwm folder.

The pi driver is configured to see the PWM hardware as a single PWM “chip”. To work with either PWM channel you have to export it. In this context exporting means that you claim sole use of the channel. To do this you have to write a “0” or a “1” to the export file in the pwmchip0 folder. To unexport you do do the same to the unexport file in the pwmchip0 folder.

After you have exported the channel you will see new folders, pwm0 and pwm1 in the pwmchip0 folder. Of course you only see the folders you have exported and you can only export pwm0 if you have use the PWM driver.

Within the pwmx folder you will find the following important files:

period  	period in nanoseconds
duty_cycle 	duty cycle in nanoseconds
enable 	        write 1 to enable, 0 to disable

So all you have to do is:

  1. export the channel

  2. write to period

  3. write to duty_cycle

  4. write “1” to enable

Notice that as this is hardware PWM, once you have set and enabled the channel, the PWM generation continues after the program ends.

A simple program to use PWM0 is:

#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <fcntl.h>
FILE *doCommand(char *cmd)
{
    FILE *fp = popen(cmd, "r");
    if (fp == NULL)
    {
        printf("Failed to run command %s \n\r", cmd);
        exit(1);
    }
    return fp;
}
void checkPWM()
{
    FILE *fd = doCommand("sudo  dtparam -l");
    char output[1024];
    int txfound = 0;
    char indicator[] = "pwm-2chan";
    char command[] = "sudo dtoverlay pwm-2chan";
    while (fgets(output, sizeof(output), fd) != NULL)
    {
        printf("%s\n\r", output);
        fflush(stdout);
        if (strstr(output, indicator) != NULL)
        {
            txfound = 1;
        }
    }
    if (txfound == 0)
    {
        fd = doCommand(command);
        sleep(2);
    }
    pclose(fd);
}
int main(int argc, char **argv)
{
    checkPWM();    
    int fd = open("/sys/class/pwm/pwmchip0/export",
O_WRONLY); write(fd, "0", 1); close(fd); sleep(2); fd = open("/sys/class/pwm/pwmchip0/pwm0/period",
O_WRONLY); write(fd, "10000000", 8); close(fd); fd = open("/sys/class/pwm/pwmchip0/pwm0/duty_cycle",
O_WRONLY); write(fd, "8000000", 7); close(fd); fd = open("/sys/class/pwm/pwmchip0/pwm0/enable",
O_WRONLY); write(fd, "1", 1); close(fd); }

The checkPWM function dynamically loads the pwm-2chan driver – you can change it to pwm if you only need one channel. It exports the channel and then sets the period to 100Hz with an 80% duty cycle. A delay of 1 second is included after the export to allow the system to create the folders and files.
A better solution is to test for an error on the first open and keep looping until it works. This program doesn’t need root permissions to run, only to dynamically install the driver. You can use the other channel in the same way.

<ASIN:B06XFZC3BX>

<ASIN:B0748MPQT4>

<ASIN:B08R87H4RR>

<ASIN:B07V5JTMV9>



Last Updated ( Wednesday, 12 May 2021 )