In this guide we will be controlling a tower pro sg90 servo motor using software pwm module.

 

2vj8z8z.jpg

 

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.

 

2egfz2e.jpg

 

156v1tw.jpg

 

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.