Software PWM on a Raspberry Pi

If you want to control the brightness of a LED, the speed of a DC motor or the direction of a servo, you will need PWM.

The video shows PWM being used to control the brightness of some LEDs.

Pulse-width modulation (PWM) is used to control the amount of power supplied to electrical devices, especially to DC motors, servos and LEDs.

PWM is able to achieve this by quickly turning off and on the power to the device. The measurement for this is duty cycle.

Duty cycles describes the proportion of 'on'; a low duty cycle corresponds to low power, because the power is off for most of the time.  A high duty cycle corresponds to high power, because the power is on most of the time.

Duty cycle is expressed in percent, 50% is when the power is on half the time and 100% being fully on.

pwm

[wp_ad_camp_1]

PWM can be performed in a number of ways on the Raspberry Pi.

Inbuilt hardware;
The Pi can perform PWM in hardware, but this can only be done on one pin (GPIO18) and when enabled it interferes with the audio jack. It is also hard to get working. Last time I looked, you had to recompile the kernel.

Software;
PWM can be performed in software. This is a very easy option.  It isn’t as precise  as hardware PWM however in most instances software PWM will do.

External hardware;
There are external components that can be used to perform PWM.  Eg  Adafruit 16-Channel 12-bit PWM/Servo Driver - I2C interface - PCA96855

Software PWM with pi-blaster

In this post I will demonstrate how to use  a modified version of Pi-Blaster, a software based PWM to control the brightness of some LEDs. I modified it to allow the pins for PWM to be specified at startup.

1. Download and compile pi-blaster.

pi@raspberrypi ~ $ git clone https://github.com/mwilliams03/pi-blaster.git
pi@raspberrypi ~ $ cd pi-blaster
pi@raspberrypi ~ $ make pi-blaster

2. Start Pi-Blaster.

We are going to use pi-blaster in the user space. Once started, it runs as a background process.
If you start pi-blaster without any parameters, it will enable PWM on the default pins.

pi@raspberrypi ~ $ sudo ./pi-blaster

The default pins are;

Channel number    GPIO number   Pin in P1 header
      0               4             P1-7
      1              17             P1-11
      2              18             P1-12
      3              21             P1-13
      4              22             P1-15
      5              23             P1-16
      6              24             P1-18
      7              25             P1-22

You can also specify the pins at start that you only want to use for PWM. To enable PWM only on pins 22, 24, 17 and 16;

pi@raspberrypi ~ $ sudo ./pi-blaster 22 24 17 16

3. Configure PWM on a pin.

[wp_ad_camp_4]

Pi-blaster creates a special file (FIFO) in /dev/pi-blaster. Any application on your Raspberry Pi can write to it (this means that only pi-blaster needs to be root, your application can run as a normal user).

  • To set pin1 to a PWM of 20%
    echo "1=0.2" > /dev/pi-blaster
  • To completely turn off pin0:
    echo "0=0" > /dev/pi-blaster
  • To completely turn on pin1:
    echo "1=1" > /dev/pi-blaster

4. Stop pi-blaster

Use killall to kill the pi-blaster process

pi@raspberrypi ~ $ sudo killall pi-blaster

5. Sample program used in the above video

PWM-LED

You can write your own programs to control PWM just by writing to /dev/pi-blaster.
The code below was used to control the LEDs in the above video. And which is also shown to the right

Copy the code below into a file. E.g. pwd-led.c. And then compile;

pi@raspberrypi ~ $ gcc -o pwd-led pwd-led.c
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

#define DELAY 70000
#define LEVELS 7
#define INCREMENT  0.15
	FILE *fp;
void  INThandler(int sig)
{
	int pin;
        signal(sig, SIG_IGN);
	//turn all pins off
        for (pin = 0; pin < 8; pin++){
		fprintf(fp, "%i=%f\n", pin,0.0);
		fflush(fp);
	}
	fclose(fp);
        exit(0);
}
int main(void)
{
	signal(SIGINT, INThandler);
	// arrays used to control brightness of a row of LEDs. left then right animation.
	float left[] = {0,0,0,0,0,0,0,0,1,0.75,0.375,0.18,0.09,0.04,0.005,0,0,0,0,0,0,0,0};
	float right[] = {0,0,0,0,0,0,0,0,0.005,0.04,0.09,0.18,0.375,0.75,1,0,0,0,0,0,0,0,0};
	int pin = 0;
	float pwm = 0.0;
	// open the pi-blaster device file. If it fails, confirm that pi-blaster has been started
	fp = fopen("/dev/pi-blaster", "w");
	if (fp == NULL) {
		printf("Error opening file\n");
		exit(0);
	 }
	// create the PWM values which are used for the animation going in one direction
	float pwdPower[LEVELS*4];
	float i;
	int j;
	int count = 7;
	for (j = 0; j < LEVELS ;  j++){
		pwdPower[j] = 0.0;
	}
	for (i = 0.0; i < 1 ; i +=INCREMENT){ 		if (i>1.0)i=1.0;
		pwdPower[count] = i;
	        count++;
	}
	for (i = 1; i >0.00001 ; i -=INCREMENT){
		if (i<0.0)i=0.0;
		pwdPower[count] = i;
        	count++;
	}
        for (j = 0; j < (LEVELS) ;  j++){
                pwdPower[count] = 0.0;
		count++;
        }
///////////////////////////////////////////////////////////////////
	//This section illustrates different brightness levels
	// using PWM with a 2 second puase between levels.
	// Enable all pins using the value 0.002 for PWM
	for (pin = 0; pin < 8; pin++){
               	fprintf(fp, "%i=%f\n", pin,0.002);
                       fflush(fp);
	}
	sleep(2);
	// Enable all pins using the value 0.03 for PWM
	for (pin = 0; pin < 8; pin++){
                	fprintf(fp, "%i=%f\n", pin,0.03);
                        fflush(fp);
	}
	sleep(2);
	// Enable all pins using the value 0.05 for PWM
	for (pin = 0; pin < 8; pin++){
                	fprintf(fp, "%i=%f\n", pin,0.5);
                        fflush(fp);
	}
	sleep(2);
///////////////////////////////////////////////////////////////////
	// This section will cycle through the power levels 5 times.
	for (j = 0; j < 5 ; j++){
		for (i = 0.0; i < 1 ; i +=INCREMENT){
			for (pin = 0; pin < 8; pin++){                 		fprintf(fp, "%i=%f\n", pin,i ); 	                        fflush(fp); 			} 		usleep(DELAY); 		}        		for (i = 1; i >0.00001 ; i -=INCREMENT){
			for (pin = 0; pin < 8; pin++){
                		fprintf(fp, "%i=%f\n", pin,i );
                        	fflush(fp);
			}
		usleep(DELAY);
		}
	}
///////////////////////////////////////////////////////////////////
	// The below section performs the animations
	int c = 0;
	int g;
	for(g = 0; g < 2; g++){ 		for(c = 16; c>= 0; c--){
			for (pin = 0; pin < 8  ; pin++){
                	       	fprintf(fp, "%i=%f\n", pin, right[pin+c]);
                        	fflush(fp);
			}
		usleep(DELAY);
		}
		for(c = 0; c < 16; c++){
			for (pin = 0; pin < 8; pin++){
                		fprintf(fp, "%i=%f\n", pin, left[pin+c]);
                        	fflush(fp);
			}
		usleep(DELAY);
		}
	}
	for(g = 0; g < 4; g++){
		for(c = 0; c < (LEVELS*3); c++){
			for (pin = 0; pin < 8; pin++){
                	        fprintf(fp, "%i=%f\n", pin, pwdPower[pin+c]);
	                	fflush(fp);
			}
			usleep(DELAY);
		}
	}
	//turn all pins off
        for (pin = 0; pin < 8; pin++){
		fprintf(fp, "%i=%f\n", pin,0.0);
		fflush(fp);
	}
	fclose(fp);
	return 0;
}

[wp_ad_camp_3]

21 thoughts on “Software PWM on a Raspberry Pi”

    1. I think I have found out what my issue was. After guessing it was C, I found out that the “#include” were empty. I filled this in then performed the following.

      gcc -o program markspwmdemo.c
      chmod +x program
      ./program

      🙂

    1. Use ./pi-blaster to view the current frequency;
      sudo ./pi-blaster
      Using hardware: PWM
      Number of channels: 8
      PWM frequency: 100 Hz
      PWM steps: 1000
      Maximum period (100 %): 10000us
      Minimum period (0.100%): 10us

      You can adjust these by changing a few defines at the top of the source code:

      NUM_SAMPLES: The number of steps
      SAMPLE_US: The time of one step (minimum period)

      If you do not neet a resolution of 1000 steps (approximately equivalent to a 10 bit DAC), then you can reduce the number of samples or increase the duration of the steps.

      1. After retrying and deleting everything again the “nothing to make” error dissepeared. So it’s working!
        I did all the time the same, so I still don’t know what went wrong…

  1. Hi I’ve been playing with motor control in Python with my pi. I was formerly using GPIO direct pin addressing, but wanted to try pi-blaster to reduce CPU usage.

    I have pi-blaster installed, and I can see that it’s running, as it reports its details, as per mwilliams03’s post above.

    Also, when I execute my Python code, I can “cat /dev/pi-blaster” and see the appropriate values written to the file that I expect from my code. The values don’t seem to generate output on the designated pins though.

    That is, the output voltages for my motor control pins, according to my multimeter, appear to be zero, where I would expect them to be corresponding voltage values.

    So, pi-blaster seems to be writing to it’s file correctly, but no corresponding outputs. Anyone have any thoughts?

    I’d post the code here, but I’m not sure if WordPress supports “code” tags. 😉

  2. Do oyu have some test result, on how exact the timings are? Are there any timing varities if you’ve got more clients on it? e.g 20 servos?
    greetings

  3. Ok complete noob here. Trying to test this out. So I wish to try the above code. I’m guessing that I need something in front of the incules lines at the top. And we’re are you writing this in the Python she’ll or the terminal? Thanks

  4. Hello,

    Ran across your fork while investigating linear actuator control options for a solar array using my new Raspberry Pi.
    Everything went well until I tried the Make Install command :

    pi@raspberrypi:~/pi-blaster-master $ sudo make install
    make[1]: Entering directory ‘/home/pi/pi-blaster-master’
    /bin/mkdir -p ‘/usr/sbin’
    /usr/bin/install -c pi-blaster ‘/usr/sbin’
    /bin/mkdir -p ‘/lib/systemd/system’
    /usr/bin/install -c -m 644 pi-blaster.service ‘/lib/systemd/system’
    make install-data-hook
    make[2]: Entering directory ‘/home/pi/pi-blaster-master’
    systemctl enable pi-blaster
    Failed to execute operation: Bad message
    Makefile:846: recipe for target ‘install-data-hook’ failed
    make[2]: *** [install-data-hook] Error 1
    make[2]: Leaving directory ‘/home/pi/pi-blaster-master’
    Makefile:771: recipe for target ‘install-data-am’ failed
    make[1]: *** [install-data-am] Error 2
    make[1]: Leaving directory ‘/home/pi/pi-blaster-master’
    Makefile:719: recipe for target ‘install-am’ failed
    make: *** [install-am] Error 2
    pi@raspberrypi:~/pi-blaster-master $

    If you have any ideas on what I can do or should do differently, please let me know.
    Thank you,
    Jim Julian

  5. Invalid channel number 25
    echo “25=0.2” > /dev/pi-blaster
    I just wanted one pin pwm gpio 25
    before I start digging in to source code, anyone know why am i getting is error?

    1. OK, I figure it out.
      sudo pi-blaster/pi-blaster
      Using hardware: PWM
      Number of channels: 8
      PWM frequency: 100 Hz
      PWM steps: 1000
      Maximum period (100 %): 10000us
      Minimum period (0.100%): 10us
      By using default it shows ‘Number of channels: 8’, so I want to control gpio 25 as above table shows channel 7, pin 22, gpio 25,
      echo “7=0.01” > /dev/pi-blaster write out channel number instead pin number it seems works. But default controls eight gpio pins and I just need one pwm gpio25 and other gpio s needed for other purpose, so little more fiddling, “pi-blaster 25” returns ‘Number of channels 1’ so writing file echo “0=0.01” > /dev/pi-blaster channel index 0 as single gpio or the first gpio number, so I can use other gpio’s for other purpose and have one pwm channel.
      Thanks.

  6. Thank you for sharing this.

    Does anyone know what percentage of the Raspberry Pi 3B CPU this uses? I’m currently using a daemon to control a fan, and it uses about 5% CPU on my 1.3GHz Raspberry Pi 3B. I’d like something that uses a bit less. I will probably try it and see how it works.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.