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.