Intro

This short post is an extension to my earlier post . If you read that post you will have noticed that the calculations of <b>tusec_On </b> and <b>tusec_Off</b>, which or the on- and off times of the PWM dutycycle, were calculated in the same loop as the bit banging of the GPIO pin was done to generate the PWM signal. While these are two relatively small calculations, that will not affect noticeably the PWM signal, it is still a better approach no to have other things one in the PWM loop, besides the bit banging of the GPIO pin.

To have the dutycyle and the PWM frequency updated in real-time, they need to be calculated in a parallel (concurrent) thread. This is the goal for this post.

The Module Code

Following is the slightly modified kernel code for the PWM driver. Note that now linux/kthread.h is being included:

#include 
#include 
#include 
#include 
#include 

#include 

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 
int tusec_On  = 0;			// duty-cycle HIGH time suring one period in [usec]
int tusec_Off = 0;			// duty-cycle LOW time during one period in [usec]

module_param(duty, int, 0644);
module_param(frequency, int, 0644);
module_param(enable, int, 0644);
module_param(tusec_On, int, 0);
module_param(tusec_Off, int, 0);

/*--------------------------------------------------*/
/*          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)
{
	printk(KERN_ALERT "STARTING PWM: Frequency is %dMHz, and dutycycle is %d percent.\n", frequency, duty);
	
	/* Run PWM */
	while(enable){	
		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__);
}

/*--------------------------------------------------*/
/*         Thread running calculations              */
#define THREAD_NAME "pwm"

struct task_struct *task;

int pwm_thread(void *data)
{

	while(1)
	{
		/* 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
		
		if (kthread_should_stop()) break;
	}
	return 0;
}

void pwm_thread_init(void)
{
	printk(KERN_INFO "PWM: started thread...");
	task = kthread_run(pwm_thread, NULL, THREAD_NAME);
}

void pwm_thread_exit(void)
{
	printk(KERN_INFO "PWM: stopping thread...");
	kthread_stop(task);
}

/*--------------------------------------------------*/
/*         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();
	pwm_thread_init();
	pwn_run_init();

	return 0;
}

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

module_init(pwm_init);
module_exit(pwm_exit);

The difference between this code and the one from my earlier post is the threading block between line 67 and line 98. The calculations for the on- and off-times of the dutycycle are removed now from the pwm_run_init() function and moved to a thread function pwm_thread().
When pwm_thread_inti() is called on line 107, it sets off the function pwm_thread() from line 90 as a thread. The call kthread_run(threadfn, data, Namefmt, …) generates a new thread and has generally three arguments:

  1. Threadfn is the function name to run
  2. Data is a pointer to the function arguments
  3. Namefmt is the name of the thread (in ps) – Specified in a printf formatting string

The function kthread_should_stop() returns a 1 when kthread_stop(task) is called on the thread task. This is done when pwm_thread_exit() is called from the main function and executes kthread_stop(task) in line 96.

The Makefile

The Make file for this project is identical to the one from my earlier post (just the module name is changed:

obj-m := pwmPinDriver02.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

Summary

This short post introduced threading for a “smoother” PWM driver. A simple thread was introduced to update in real-time the dutycycle parameters.