Intro

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.

The Module Code

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

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:

and it can be removed from the kernel with

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:

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:

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%.

Summary

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.