In this post I presented a Linux kernel module that drives a GPIO pin on the Raspberry Pi as a PWM pin. That version allowed the user to change dutycycle and frequency of the PWM pulses by writing into module parameters.

In this post I improved the previous kernel module by putting calculations regarding the on- and off-times of the PWM cycle into a thread. However the communication from user-space with the kernel module was still over the module parameters. This was not ideal. Another shortcoming was that the PWM (bit-banging) loop was not in a thread, so when you inserted it into the kernel from a terminal using sudo insmod, the terminal would stay occupied until you remove the module with sudo rmmod.

In this post I am moving the bit-banging PWM part into a thread, so that the terminal where you insert the module, is no more occupied.

Also, I am using the more commonly used SYSFS method to generate a virtual file in the /sys/ directory, for the communication with the user-space.


The Makefile

Beginning with the make file — which actually does not change. Just find the file where you have the build link to your linux build folder. That build link is somewhere in /lib/modules/..
You might have it set up so that uname -rb points to it. In that case you can replace the path part in my below Makefile 4.14.69-v7+ with $(shell uname -r). Otherwise you will have to manually find the Linux version in your system that contains the build link.
Note: The Linux kernel development environment needs to be re-installed every-time you update your Linux version. So after upgrading your Linux distribution, you will have to remove the local Linux build directory and reinstall as shown in my previous post.

obj-m := pwmPinDriver04.o
	make -C /lib/modules/4.14.69-v7+/build M=$(PWD) modules
	make -C /lib/modules/4.14.69-v7+/build M=$(PWD) clean

The Module Code

Below is the module code that I have saved as pwmPinDriver04.c.



/* GPIO Assignment */
#define pwm1 	17 // Kernel PWM channel 0 on GPIO17

/* Global Parameters */	
unsigned int duty 			= 50;			// Dutycycle in %...
unsigned int frequency 	= 10000;		// Frequency in Hz... 
unsigned int enable		= 1;			// global eneable parameter
								// 0 = disable, 1 = enable
unsigned int tusec_On	= 0;			//duty-cycle HIGH time during one period in [usec]
unsigned int tusec_Off	= 0;			// duty-cycle LOW time during one period in [usec]

/* GPIO Configuration */
int pwm_gpio_init(void)
	printk(KERN_INFO "Started GPIO initialization in %s\n", __FUNCTION__);
	gpio_request(pwm1, "pwm1");
	gpio_direction_output(pwm1, 0);
	return 0;

void pwm_gpio_exit(void)
	printk(KERN_INFO "Releasing GPIO ports in %s \n", __FUNCTION__);

/* Run PWM on the GPIO port using a second thread*/
#define THREAD_NAME0 "pwmPin"	// define a thread name...
struct task_struct *task0;		// task_struct is to link all processes together...
/* Subfuncyion */
int pwm_threadRun(void *data) 
	//unsigned short flag = 1;
	while(1)  {
			gpio_set_value(pwm1, 1);
			usleep_range(tusec_On, tusec_On);
			gpio_set_value(pwm1, 0);
			usleep_range(tusec_Off, tusec_Off);
			//flag = 0;
		} else {
			usleep_range(10000, 10000);
			//flag = 1;
		if (kthread_should_stop()) break;		// Exit from the thread...
	return 0;
/* INIT function */
int pwm_runThread_init(void)
	printk(KERN_ALERT "STARTING PWM: Frequency is %dMHz, and dutycycle is %d percent.\n", frequency, duty);
	/* Run PWM */
	task0 = kthread_run(pwm_threadRun, NULL, THREAD_NAME0);
	return 0;
/* EXIT Function */
void pwm_runThread_exit(void)
	printk(KERN_ALERT "STOPPING PWM in %s.\n", __FUNCTION__);

/* SYSFS function for communication with user-space */
static struct kobject *pwm_kobject;

static ssize_t set_pwm(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t count)
	sscanf(buff, "%d %d %d", &enable, &frequency, &duty);
	printk(KERN_INFO "Read values: %d %d %d", enable, frequency, duty);
	/* Calcuate from frequency and dutycycle the on- and off times */
	if(enable)  {
		tusec_On = (1000000*duty)/(frequency*100);			// Duration of on-cycle...
		tusec_Off = (1000000*(100-duty))/(frequency*100);	// Duration of off-cycle...
	return count;

static struct kobj_attribute pwm_attribute = __ATTR(pwm, (S_IWUSR | S_IRUGO), NULL, set_pwm);

int pwm_sysfs_init(void) 
	printk(KERN_INFO "PWM: starting sysfs in %s\n", __FUNCTION__);
	pwm_kobject = kobject_create_and_add("pwm", NULL);
	if(sysfs_create_file(pwm_kobject, &pwm_attribute.attr)) {
		pr_debug("failed to create pwm sysfs!\n");
	return 0;

void pwm_sysfs_exit(void)
	printk(KERN_INFO "PWM: stopping sysfs in %s\n", __FUNCTION__);
/* Main Calling Functions */
int pwm_init(void){
	printk(KERN_INFO "\nStarting GPIO kernel in %s\n", __FUNCTION__);
	return 0;

void pwm_exit(void){
	printk(KERN_INFO "\nExiting kernel in %s\n", __FUNCTION__);


The code above starts with the inclusion of the libraries required for this code. Besides the standard libraries required for Linux kernel modules (init.h and module.h), we need here the gpio.h, kthread.h and delay.h libraries.
I am using next in the lines 14 to 19 global variables declarations for the duty-cycle, the frequency and the enable-flag. This might not be best practice, but an easy approach for the communication between the different functions of this module – including the communication with the thread.

Lines 24 to 37 are the initialization/configuration of the GPIO pins at start (init) and the release of the GPIO pins at exit.
The lines 64 to 76 initialize the thread at start and terminate the thread at exit. The thread executes the function pwm_threadRun(). This function does the actual GPIO bit-banging to generate the PWM drive if enable=1. Otherwise the pin is set to LOW. Note that the LOW writing in the case that enable=0 needs to have the delay function usleep_range with a long enough delay, so that the GPIO writing does not lead to an overload.

Lines 80 to 111 configure the SYSFS file system at initialization and remove the file system at exit. At init the function set_pwm() is called, which checks for changes in the virtual file pwm and takes the new values for the calculation of the on- and off-times of the PWM duty-cycle (tusec_On and tusec_Off, respectively).

Lines 115 to 130 are the main calling functions for initialization and for the exit of the module functions. These two functions are called by the special Linux kernel calls module_init() and module_exit(), which do the initial starting call and the exit call, respectively. These two last function calls are key calls needed in every Linux kernel module.

User-Space Control

As usual, the kernel module can be inserted into the kernel with:

sudo insmod pwmPinDriver04.ko

and it can be removed from the kernel with

sudo rmmod pwmPinDriver04

To communicate with the kernel from user-space, i.e. to change the duty-cycle or and/or frequency or to disable the PWM river, one can set off shell commands by e.g. typing at the terminal prompt:

echo 1 5 50 | sudo tee /sys/pwm/pwm

The shell command above sets enable=1, frequency=5Hz and duty-cycle=50%.

Python Script for Kernel Module Control

At this point the kernel module can also be controlled with a Python script:

import time
import subprocess

# Small script to control the PWM paramaters...
def run():
		with open('/sys/pwm/pwm', 'w+') as fb:["echo", "1 5 50", "sudo tee"], stdout=fb)
			print("Turned on..." )
			time.sleep(3)["echo", "0 5 50", "sudo tee"], stdout=fb)
			print("OFF..." )

# Call main ...
if __name__=="__main__":

Here I am using subprocess to set off shell commands from Python. In line 9 I am opening inside the loop in overwrite (w+) mode the SYSFS file /sys/pwm/pwm.
With the file-handle fb to this SYSFS file I can now use subprocess to pipe the string containing the values for enable, frequency and duty-cycle into the sysfs file using stdout=fb in the calls of line 10 and 13.
So this Python code enables and disables the LED every 3 seconds, sets the frequency to 5Hz (5 blinks per second) and sets the duty-cycle to 50%.


The kernel code in this post uses SYSFS as the communication channel with the user-space. It generates a virtual file in /sys/ that can be written to using e.g. a Python script to modify the parameters of a kernel module. More information on the SYSFS file-system can be found on the man pages for SYSFS or here.
In this example we are using this communication to modify the PWM frequency and duty-cycle on a chosen pin of the Raspberry Pi. This PWM signal can be used to drive e.g. LEDs or DC motors.

Please leave me your comments below.