In this guide we will be controlling a tower pro sg90 servo motor using software pwm module.
Step1:
export ARCH & CROSS_COMPILE to environment path.
in my case its like below, yours might be different.
$ export ARCH=arm
$ export CROSS_COMPILE=<PATH_TO_TOOLCHAIN>/fsl-linaro-toolchain/bin/arm-fsl-linux-gnueabi-
Step2:
create a directory
mkdir driver
create a file named "Makefile" and replace with your KDIR value, below is mine.
obj-m := soft_pwm.o
KDIR := /home/tushar/riot_github/kernel_out/lib/modules/3.0.35-02887-g731b440/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean
create a file named "soft_pwm.c" with below contents in it.
// Generic software-pwm driver
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/gpio.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Driver for kernel-generated PWM signals");
static struct hrtimer hr_timer;
/* pwm_desc
*
* This structure maintains the information regarding a
* single PWM signal: its wave period and pulse length
* are user-definable via sysfs. The counter is also
* shown in sysfs as a debug helper.
*/
struct pwm_desc {
unsigned int pulse; // pulse width, in microseconds
unsigned int period; // wave period, in microseconds
unsigned int pulses; // number of pwm pulses before stopping; -1 never stops, 0 stops immediately
unsigned long counter; // "interrupt" counter - counts each value toggle
int value; // current GPIO pin value (0 or 1 only)
ktime_t next_tick; // timer tick at which next toggling should happen
unsigned long flags; // only FLAG_SOFTPWM is used, for synchronizing inside module
#define FLAG_SOFTPWM 0
};
/* pwm_table
*
* The table will hold a description for any GPIO pin available
* on the system. It's wasteful to preallocate the entire table,
* but avoiding race conditions is so much easier this way ;-)
*/
static struct pwm_desc pwm_table[ARCH_NR_GPIOS];
/* lock protects against pwm_unexport() being called while
* sysfs files are active.
*/
static DEFINE_MUTEX(sysfs_lock);
int pwm_export(unsigned gpio); // forward definition
int pwm_unexport(unsigned gpio); // forward definition
/* Show attribute values for PWMs */
static ssize_t pwm_show(struct device *dev, struct device_attribute *attr, char *buf){
const struct pwm_desc *desc = dev_get_drvdata(dev);
ssize_t status;
mutex_lock(&sysfs_lock);
if(!test_bit(FLAG_SOFTPWM, &desc->flags)){
status = -EIO;
}else{
if(strcmp(attr->attr.name, "pulse")==0){
status = sprintf(buf, "%d usec\n", desc->pulse);
}else if(strcmp(attr->attr.name, "period")==0){
status = sprintf(buf, "%d usec\n", desc->period);
}else if(strcmp(attr->attr.name, "pulses")==0){
status = sprintf(buf, "%d usec\n", desc->pulses);
}else if(strcmp(attr->attr.name, "counter")==0){
status = sprintf(buf, "%lu\n", desc->counter);
}else{
status = -EIO;
}
}
mutex_unlock(&sysfs_lock);
return status;
}
/* Store attribute values for PWMs */
static ssize_t pwm_store(
struct device *dev, struct device_attribute *attr, const char *buf, size_t size
){
struct pwm_desc *desc = dev_get_drvdata(dev);
ssize_t status;
mutex_lock(&sysfs_lock);
if(!test_bit(FLAG_SOFTPWM, &desc->flags)){
status = -EIO;
}else{
unsigned long value;
status = strict_strtoul(buf, 0, &value);
if(status==0){
if(strcmp(attr->attr.name, "pulse")==0){
if(value<=desc->period){ desc->pulse = (unsigned int)value; }
}else if(strcmp(attr->attr.name, "period")==0){
desc->period = (unsigned int)value;
}else if(strcmp(attr->attr.name, "pulses")==0){
if (value>0)
desc->pulses = (unsigned int)value*2;
else
desc->pulses = (unsigned int)value;
}
desc->next_tick = ktime_get();
//printk(KERN_INFO "Starting timer (%s).\n", attr->attr.name);
hrtimer_start(&hr_timer, ktime_set(0,1), HRTIMER_MODE_REL);
}
}
mutex_unlock(&sysfs_lock);
return status ? : size;
}
/* Sysfs attributes definition for PWMs */
static DEVICE_ATTR(pulse, 0644, pwm_show, pwm_store);
static DEVICE_ATTR(period, 0644, pwm_show, pwm_store);
static DEVICE_ATTR(pulses, 0644, pwm_show, pwm_store);
static DEVICE_ATTR(counter, 0444, pwm_show, NULL);
static const struct attribute *soft_pwm_dev_attrs[] = {
&dev_attr_pulse.attr,
&dev_attr_period.attr,
&dev_attr_pulses.attr,
&dev_attr_counter.attr,
NULL,
};
static const struct attribute_group soft_pwm_dev_attr_group = {
.attrs = (struct attribute **) soft_pwm_dev_attrs,
};
/* Export a GPIO pin to sysfs, and claim it for PWM usage.
* See the equivalent function in drivers/gpio/gpiolib.c
*/
static ssize_t export_store(struct class *class, struct class_attribute *attr, const char *buf, size_t len){
long gpio;
int status;
status = strict_strtol(buf, 0, &gpio);
if(status<0){ goto done; }
status = gpio_request(gpio, "soft_pwm");
if(status<0){ goto done; }
status = gpio_direction_output(gpio,0);
if(status<0){ goto done; }
status = pwm_export(gpio);
if(status<0){ goto done; }
set_bit(FLAG_SOFTPWM, &pwm_table[gpio].flags);
done:
if(status){
gpio_free(gpio);
pr_debug("%s: status %d\n", __func__, status);
}
return status ? : len;
}
/* Unexport a PWM GPIO pin from sysfs, and unreclaim it.
* See the equivalent function in drivers/gpio/gpiolib.c
*/
static ssize_t unexport_store(struct class *class, struct class_attribute *attr, const char *buf, size_t len){
long gpio;
int status;
status = strict_strtol(buf, 0, &gpio);
if(status<0){ goto done; }
status = -EINVAL;
if(!gpio_is_valid(gpio)){ goto done; }
if(test_and_clear_bit(FLAG_SOFTPWM, &pwm_table[gpio].flags)){
status = pwm_unexport(gpio);
if(status==0){ gpio_free(gpio); }
}
done:
if(status){ pr_debug("%s: status %d\n", __func__, status); }
return status ? : len;
}
/* Sysfs definitions for soft_pwm class */
static struct class_attribute soft_pwm_class_attrs[] = {
__ATTR(export, 0200, NULL, export_store),
__ATTR(unexport, 0200, NULL, unexport_store),
__ATTR_NULL,
};
static struct class soft_pwm_class = {
.name = "soft_pwm",
.owner = THIS_MODULE,
.class_attrs = soft_pwm_class_attrs,
};
/* Setup the sysfs directory for a claimed PWM device */
int pwm_export(unsigned gpio){
struct pwm_desc *desc;
struct device *dev;
int status;
mutex_lock(&sysfs_lock);
desc = &pwm_table[gpio];
desc->value = 0;
desc->pulses = -1;
dev = device_create(&soft_pwm_class, NULL, MKDEV(0, 0), desc, "pwm%d", gpio);
if(dev){
status = sysfs_create_group(&dev->kobj, &soft_pwm_dev_attr_group);
if(status==0){
printk(KERN_INFO "Registered device pwm%d\n", gpio);
}else{
device_unregister(dev);
}
}else{
status = -ENODEV;
}
mutex_unlock(&sysfs_lock);
if(status){ pr_debug("%s: pwm%d status %d\n", __func__, gpio, status); }
return status;
}
/* Used by pwm_unexport below to find the device which should be freed */
static int match_export(struct device *dev, void *data){
return dev_get_drvdata(dev) == data;
}
/* Free a claimed PWM device and unregister the sysfs directory */
int pwm_unexport(unsigned gpio){
struct pwm_desc *desc;
struct device *dev;
int status;
mutex_lock(&sysfs_lock);
desc = &pwm_table[gpio];
dev = class_find_device(&soft_pwm_class, NULL, desc, match_export);
if(dev){
put_device(dev);
device_unregister(dev);
printk(KERN_INFO "Unregistered device pwm%d\n", gpio);
status = 0;
}else{
status = -ENODEV;
}
mutex_unlock(&sysfs_lock);
if(status){ pr_debug("%s: pwm%d status %d\n", __func__, gpio, status); }
return status;
}
/* The timer callback is called only when needed (which is to
* say, at the earliest PWM signal toggling time) in order to
* maintain the pressure on system latency as low as possible
*/
enum hrtimer_restart soft_pwm_hrtimer_callback(struct hrtimer *timer){
unsigned gpio;
struct pwm_desc *desc;
ktime_t now = ktime_get();
ktime_t next_tick = ktime_set(0,0);
now = ktime_get();
for(gpio=0;gpio<ARCH_NR_GPIOS;gpio++){
desc = &pwm_table[gpio];
if(
test_bit(FLAG_SOFTPWM,&desc->flags) &&
(desc->period>0) &&
(desc->pulse<=desc->period) &&
(desc->pulses!=0)
){
if(desc->next_tick.tv64<=now.tv64){
desc->value = 1-desc->value;
__gpio_set_value(gpio,desc->value);
desc->counter++;
if(desc->pulses>0){ desc->pulses--; }
if((desc->pulse==0)||(desc->pulse==desc->period)||(desc->pulses==0)){
desc->next_tick.tv64 = KTIME_MAX;
}else{
desc->next_tick=ktime_add_ns(
desc->next_tick,
(desc->value? desc->pulse : desc->period-desc->pulse)*1000
);
}
}
if((next_tick.tv64==0)||(desc->next_tick.tv64<next_tick.tv64)){
next_tick.tv64 = desc->next_tick.tv64;
}
}
}
if(next_tick.tv64>0){
hrtimer_start(&hr_timer, next_tick, HRTIMER_MODE_ABS);
}else{
//printk(KERN_INFO "Stopping timer.\n");
}
return HRTIMER_NORESTART;
}
/* module initialization: init the hr-timer and register a driver class */
static int __init soft_pwm_init(void){
struct timespec tp;
int status;
printk(KERN_INFO "SoftPWM v0.2-acme initializing.\n");
hrtimer_get_res(CLOCK_MONOTONIC, &tp);
printk(KERN_INFO "Clock resolution is %ldns\n", tp.tv_nsec);
hrtimer_init(&hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
hr_timer.function = &soft_pwm_hrtimer_callback;
status = class_register(&soft_pwm_class);
if(status<0){ goto fail_no_class; }
printk(KERN_INFO "SoftPWM initialized.\n");
return 0;
fail_no_class:
return status;
}
/* module finalization: cancel the hr-timer, switch off any PWM
* signal and give back to GPIO the pin, then deregister our class
*/
static void __exit soft_pwm_exit(void){
unsigned gpio;
int status;
hrtimer_cancel(&hr_timer);
for(gpio=0;gpio<ARCH_NR_GPIOS;gpio++){
struct pwm_desc *desc;
desc = &pwm_table[gpio];
if(test_bit(FLAG_SOFTPWM,&desc->flags)){
__gpio_set_value(gpio,0);
status = pwm_unexport(gpio);
if(status==0){ gpio_free(gpio); }
}
}
class_unregister(&soft_pwm_class);
printk(KERN_INFO "SoftPWM disabled.\n");
}
module_init(soft_pwm_init);
module_exit(soft_pwm_exit);
Now hit "make" command to compile the above driver.
Step3:
connect the PWM pin (yellow) to Pin7 in J13.
connect the PWR pin (red) to Pin2 in J13.
connect the GND pin (brown) to Pin2 in J13.
Step4:
Now create and compile a file "servo_ctrl.c" in riotboard terminal:
$ cc servo_ctrl.c -o servo_ctrl
servo_ctrl.c contents
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
/*void sig_handler(int signo)*/
/*{*/
/* if (signo == SIGINT)*/
/* { */
/* system("echo 0 > /sys/class/soft_pwm/pwm113/pulse");*/
/* system("cat /sys/class/soft_pwm/pwm113/pulse");*/
/* }*/
/*}*/
void main(int argc,char argv[])
{
/* if (signal(SIGINT, sig_handler) == SIG_ERR)*/
/* printf("\ncan't catch SIGINT : CTRL-C \n");*/
system("devmem 0x20E00A0 w 0x5");
printf("\ndevmem SET");
sleep(1);
system("cd /opt/soft_pwm/");
printf("\nEntering /opt/soft_pwm/");
sleep(1);
system("rmmod soft_pwm.ko");
printf("\nrmmod soft_pwm");
sleep(1);
system("insmod soft_pwm.ko");
printf("\ninsmod soft_pwm");
sleep(1);
system("echo 113 > /sys/class/soft_pwm/export");
printf("\nexporting gpio 113");
sleep(1);
system("echo 20000 > /sys/class/soft_pwm/pwm113/period");
printf("\nsetting period: 20000");
sleep(1);
while(1)
{
system("echo 10 > /sys/class/soft_pwm/pwm113/pulse");
printf("\nMiddle");
sleep(1);
system("echo 0 > /sys/class/soft_pwm/pwm113/pulse");
sleep(0.5);
system("echo 1200 > /sys/class/soft_pwm/pwm113/pulse");
printf("\nRight_30degree");
usleep(100000);
system("echo 1600 > /sys/class/soft_pwm/pwm113/pulse");
printf("\nLeft_30degree");
usleep(100000);
system("echo 1400 > /sys/class/soft_pwm/pwm113/pulse");
printf("\nLeft");
sleep(1);
system("echo 0 > /sys/class/soft_pwm/pwm113/pulse");
sleep(0.5);
system("echo 2500 > /sys/class/soft_pwm/pwm113/pulse");
printf("\nRight");
sleep(1);
system("echo 0 > /sys/class/soft_pwm/pwm113/pulse");
sleep(0.5);
}
}
Now copy the compiled soft_pwm.ko to /opt/soft_pwm in riotboard.
With all connections ready, run the servo_ctrl program compile earlier:
$ ./servo_ctrl
The servo will move according to the pulse entry in servo_ctrl.c file.
Note: as the code is a software pwm , you will be noticing jitter.
Have fun.


