Intro


In this post I am presenting the code for a linux kernel module that makes a GPIO pin of the Raspberry Pi act as a PWM pin using simple bit banging. The module is running in the Raspbian linux kernel on a Raspberry Pi.

The below picture shows the pins of a Raspberry Pi and which pin I am going to use for this PWM driver. For a demo I will connect this pin to an LED, change its blinking frequency and adjust its brightness using PWM. Note: in the photo above I have a small resistor (10Ohms) in series to the Anode of the LED.

As shown I am using GPIO17 for the Anode of the LED and the GND right above GPIO17 for the Cathode of the LED (of course you can take any GND pin).

Preparations

To prepare for the kernel programming I ran through the installation procedure described on this Raspberry Pi site. For my purposes the local building environment on the Raspberry Pi was perfect and actually did not take too much time. My installation of the local build environment was on a Raspberry Pi 2.
Just to make sure — here are the steps as a listing, which is just a subset taken from this Raspberri Pi article. Note, if you have issues with this install you might need to do a sudo apt-get update first. :

  1. Install git and its dependencies:
    sudo apt-get install git bc
    
  2. Get the sources
    git clone --depth=1 https://github.com/raspberrypi/linux
    
  3. Run the following commands, depending on your Raspberry Pi version.
    RASPBERRY PI 1, PI 0, PI 0W, AND COMPUTE MODULE DEFAULT BUILD CONFIGURATION (this was my choice)

    cd linux
    KERNEL=kernel
    make bcmrpi_defconfig
    

    RASPBERRY PI 2, PI 3, PI 3+, AND COMPUTE MODULE 3 DEFAULT BUILD CONFIGURATION

    cd linux
    KERNEL=kernel7
    make bcm2709_defconfig
    
  4. Build and install the kernel, modules, and Device Tree blob
    make -j4 zImage modules dtbs
    sudo make modules_install
    sudo cp arch/arm/boot/dts/*.dtb /boot/
    sudo cp arch/arm/boot/dts/overlays/*.dtb* /boot/overlays/
    sudo cp arch/arm/boot/dts/overlays/README /boot/overlays/
    sudo cp arch/arm/boot/zImage /boot/$KERNEL.img
    

I ran these steps in a folder that I created called ‘~/kernel’. So the linux/ directory with the build environment is on my system in ~/kernel/linux/.

Bit Banging PWM Driver in Kernel

Makefile

The build-library on the Raspberry Pi Raspbian is in a build folder under /lib/modules/. To determine the complete path it is often recommended to run the following at your Terminal prompt:

uname -r

However I tried the setup on two Raspberry Pis and uname -r was giving me as a result 4.4.38-v7+, which was not the directory with the build link. Instead I had to go into /lib/modules and search from there in all sub-directories to find the build link. For example in below screenshot I found the build link at /lib/modules/4.14.69-v7+/.

The link build points to the linux local build folder that was installed earlier. You can see the link by typing ls -la at the command prompt as shown in the screenshot above.
I can now use the path /lib/modules/4.14.69-v7+/ in my Makefile.

obj-m := pwmPinDriver01.o

all:
	make -C /lib/modules/4.14.69-v7+/build M=$(PWD) modules
	
clean:
	make -C /lib/modules/4.14.69-v7+/build M=$(PWD) clean

Note: In this Makefile line 1 I am using :=. If you want to add further modules to this module (e.g. submodules that contain other functions) then you would add lines with a +=.

Main code

Every kernel C code needs the #include <linux/module.h>  inclusion. Also every kernel module needs to have a module_init part that stars all main initialization functions and a module_exit part that terminates all functions.

#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/delay.h>

#include <linux/moduleparam.h>

MODULE_LICENSE("GPL");

/* GPIO Pin Assignment*/

#define pwm1		17 // Kernel PWM channel 0

/* Global PWM parameters */
int duty      = 50;			// Dutycycle in %
int frequency = 10000;		// Frequency in Hz...
int enable    = 1;			// 0 = disable, 1 = enable 

module_param(duty, int, 0644);
module_param(frequency, int, 0644);
module_param(enable, int, 0644);

/*--------------------------------------------------*/
/*          Configure the GPIO port(s)              */

int __init pwm_gpio_init(void)
{
	printk(KERN_INFO "PWM: starting %s. \n", __FUNCTION__);
	gpio_request(pwm1, "pwm1");
	gpio_direction_output(pwm1, 0);
	
	return 0;
}

void __exit pwm_gpio_exit(void)
{
	printk(KERN_INFO "PWM: stopping %s.\n", __FUNCTION__);
	gpio_free(pwm1);
}
/*--------------------------------------------------*/
/*           Run PWM on the GPIO port               */

int pwn_run_init(void)
{
	int tusec_On;
	int tusec_Off;
	printk(KERN_ALERT "STARTING PWM: Frequency is %dMHz, and dutycycle is %d percent.\n", frequency, duty);
	
	/* Run PWM */
	while(enable){	
		/* Calculate from frequency and dutycycle the delay-times */
		tusec_On  = (1000000*duty)/(frequency*100);			// Duration of on-cycle
		tusec_Off = (1000000*(100-duty))/(frequency*100);	// Duration of off-cycle
		gpio_set_value(pwm1, 1);
		usleep_range(tusec_On, tusec_On);
		gpio_set_value(pwm1, 0);
		usleep_range(tusec_Off, tusec_Off);
	}
	return 0;
}

void pwm_run_exit(void)
{
	printk(KERN_ALERT "STOPPING PWM in %s.\n", __FUNCTION__);
}

/*--------------------------------------------------*/
/*         The Module starting function             */

int __init pwm_init(void)
{
	printk(KERN_INFO " Starte the %s function.\n", __FUNCTION__);
	/* Configure initial state of the PWM pins */
	pwm_gpio_init();
	pwn_run_init();

	return 0;
}

void __exit pwm_exit(void)  
{
	printk(KERN_INFO " Ending the function %s. \n", __FUNCTION__);
	pwm_run_exit();
	pwm_gpio_exit();
	
}

module_init(pwm_init);
module_exit(pwm_exit);

Note: The line MODULE_LICENSE(“GPL”) specifies that this module code is compliant with the GPL free license regulations. If you leave this line out, you will see some “taint” messages in the syslog. You will then not be allowed to access some features of the kernel. So better include it in there.

The above code has 3 blocks:

  1. pwm_init() and pwm_exit(): These are the “main” functions, which are called by module_init() and module_exit(). They both call in sequence the other blocks for start-up and for termination.
  2. pwm_gpio_init() and pwm_gpio_exit(): These are the configuration and release functions for the GPIO pin.
  3. pwm_run_init() and pwm_run_exit(): The init function here is the code that does the actual bit banging on GPIO 17 to produce the PWM drive. Note: floating number operations in the kernel space are not recommended. Hence when calculating something that requires divisions with uneven results it is to apply a so called fix-point calculation  that does all calculations that can be done as integers first and do divisions (and other calculations) that can produce floating point results in the end. That is why e.g. the calculation (1000000*duty)/(frequency*100) is set up so, that first (1000000*duty) and (frequency*100) are calculated, and then the division is executed.

To compile this run make in the folder where you have the Makefile and the main code pwmPinDriver01.c.
(Note: To remove the compilation run make clean.)
Running the make file produces an output similar to the following:

make -C /lib/modules/4.14.69-v7+/build M=/home/pi/kernel/pwmTest1 modules
make[1]: Entering directory ‘/home/pi/linux’
CC [M] /home/pi/kernel/pwmTest1/pwmPinDriver01.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/pi/kernel/pwmTest1/pwmPinDriver01.mod.o
LD [M] /home/pi/kernel/pwmTest1/pwmPinDriver01.ko
make[1]: Leaving directory ‘/home/pi/linux’

Check in the folder where you ran the make command. There should be now multiple .c .o and one .ko file. The .ko file is the kernel module file.
To execute this module in the kernel run

sudo insmod pwmPinDriver01.ko

Note: Running insmod copies the object file of the module into the kernel and that can be viewed (and accesses) in the folder /sys/module/.

If you now execute

dmesg

you can see the print-outs of the mudule that you just started in the kernel.
Another way to see that your module is implemented in the kernel is done by executing

lsmod

This call gives you a listing of all the modules, that are running in the kernel space. If you want to see only your module, you can use lsmod | grep pwm, which will print out all modules that contain “pwm” in their names.

My preferred method is this one: If you want to see what is happening in the kernel in “real-time”, you can open a terminal and type in the following command:

sudo tail -f /var/log/syslog

This will start the survice tail that keeps on printing any new entry that is being done in syslog. So if you e.g. start the kernel module with sudo insmod pwmPinDriver01.ko you will see the result of the print statements of the module_init function of your kernel module.

 

Control of the Kernel Module

In above code I am using three global variables: duty, frequency and enable. The two first ones are used to calculate on- and off-times (or high and low times) of the GPIO pin 17. The GPIO pin is specified in line 12. The variable enable is a flag that enables the PWM output and stops the pwm output when = 0.

So when you first start the module in the kernel enable is initialized with 1 and the loop in line 50 to 58 is continuously executed. You will need to open another terminal to change the global variables.

You can change the blinking frequency with

echo 10000 | sudo tee /sys/module/pwmPinDriver01/parameters/frequency

Here I am setting the blinking frequency as an example to 10kHz.
Obviously the duty cycle can be changed with

echo 80 | sudo tee /sys/module/pwmPinDriver01/parameters/duty

Here I am changing the duty-cycle to 80%.

And as mentioned, to disable the PWM port, execute in the terminal:

echo 0 | sudo tee /sys/module/pwmPinDriver01/parameters/enable

 

If you want to stop your kernel module just type sudo rmmod pix_mod. Executing again dmesg will show you at the end of the list the termination message in the kernel code you wrote.

Summary and further Readings

In this post I am showing a simple linux kernel module, that modifies a GPIO pin of the Raspberry Pi to a PWM pin. The PWM frequency and dutycycle can both be modified from the console (or from any other user-space program) by changing the values of the global variables in the kernel module.
An example for a user-space program would be a Python script, that does the global variable writings, using subprocess or os.

Great sources for further information on Raspberry Pi linux kernel programming are the following:

  1. this great post from Michał Kalbarczyk
  2. this book written by Peter Jay Salzman, Michael Burian and Ori Pomerantz.
  3. I can very much recommend the video series from Karthik M “Linux Device Driver Training 01 to 06”. Karthik’s videos are very instructive and well made.