pcm2pwm

USING THE PCM PERIPHERAL AS A THIRD PWM OUTPUT ON THE PI

IN PROGRESS, NOT FINISHED...

The pcm peripheral can be used as a third PWM output without using any processor resources. All it takes is a little kernel module.

The following commented code is for a kernel module that creates /dev/pcm2pwm. A file, pcm2pwm.c, is in the files subpage and can be used to compile the module. It will not work on a Pi 2 without changing the peripheral addresses.

The servo signal input should be connected to gpio19. Of course the servo also needs power.

After the module is loaded, a byte written to this device with a decimal value from 0 to 100 will move the servo. For example, the following will move it to mid range:

echo -n -e '\x32' > /dev/servo2

First the header stuff:

 /* servo2.c -- Raspberry Pi servo driver using the PCM peripheral.

 *

 * Copyright 2015 the pi hacker https://sites.google.com/site/thepihacker

 *

 * This file is subject to the terms and conditions of the GNU General Public

 * License. See the file COPYING in the Linux kernel source for more details.

 *

 * Based on thepihacker's apfb.c and servo3.c modules

 *

 * You don't need to do anything special to compile this module.

 */

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/delay.h>

#include <linux/io.h>

#include <linux/cdev.h>

#include <linux/syscalls.h>

#define BASE BCM2708_PERI_BASE

volatile unsigned *(gpio) = 0;

volatile unsigned *(pcm) = 0;

volatile unsigned *(clock) = 0;

static dev_t Device;

static struct class* Class;

static struct cdev c_dev;

You need a routine to get the byte that was written to the device node, check it, and change the fs pulse width accordingly.

Of special note is that the pulse width can only be changed while the pcm peripheral isn't running.

//handle writes to device node and change frame pulse width

static ssize_t write(struct file *filp, const char *buffer, size_t length, loff_t * offset)

{

    static u8 value;

    //get value

    get_user(value, buffer);

    //range check

    if (value > 100) value = 100;

    //modify pcm frame pulse width

    iowrite32(ioread32(pcm) & ~(1), pcm);

    udelay(50);

    iowrite32(0x3ff<<10 | (value + 100), pcm + 2);

    iowrite32(ioread32(pcm) | 1, pcm);

    udelay(50);

    return 1;

}

//device handler table

    static struct file_operations fops = {

    .owner = THIS_MODULE,

    .write = write,

};

On module load you need to set up the character device, set up the gpio to output the fs signal, initialize the pcm clock, and initialize the pcm peripheral.

The pcm clock generator has to be set up in a specific sequence per the data sheet:

//initialize module

int init_module(void)

{

    //set up device node

    alloc_chrdev_region(&Device, 0, 1, "servo2");

    Class = class_create(THIS_MODULE, "servo2");

    device_create(Class, NULL, MKDEV(MAJOR(Device), MINOR(Device)), NULL, "servo2");

    cdev_init(&c_dev, &fops);

    cdev_add(&c_dev, Device, 1);

    //set up gpio 19 for pcm fs out

    gpio = ioremap(0x20200000, 8);

    iowrite32((ioread32(gpio + 1) & ~(7<<27)) | (4<<27), gpio + 1);

    iounmap(gpio);

    //set up pcm clock

    clock = ioremap(0x20101098, 8);

    iowrite32(0x5a<<24 | (ioread32(clock) & ~(0xff<<24 | 1<<4)), clock);

    while ((ioread32(clock) & 1<<7) == 1){}

    iowrite32(0x5a<<24 | 192<<12, clock + 1);

    iowrite32(0x5a<<24 | 1, clock);

    udelay(10);

    iowrite32(0x5a<<24 | 1<<4 | 1, clock);

    while ((ioread32(clock) & 1<<7) == 0){}

    iounmap(clock);

    //set up pcm peripheral

    pcm = ioremap(0x20203000, 28);

    iowrite32(ioread32(pcm) & ~(1), pcm);

    udelay(100);

    iowrite32(0x3ff<<10 | 150, pcm + 2);

    iowrite32(1<<30, pcm + 4);

    iowrite32(0, pcm + 6);

    iowrite32(1<<25 | 1<<2 | 1, pcm);

    udelay(50);

    printk(KERN_INFO "servo2: loaded\n");

    return 0;

}

Tear down everything in reverse order on module unload:

//tear down module

void cleanup_module(void)

{

    //stop pcm peripheral

    iowrite32(ioread32(pcm) & ~(1), pcm);

    udelay(100);

    iounmap(pcm);

    //stop pcm clock

    clock = ioremap(0x20101098, 8);

    iowrite32(0x5a<<24 | (ioread32(clock) & ~(0xff<<24 | 1<<4)), clock);

    iounmap(clock);

    //set gpio 19 as input, default pulldown will keep output low

    gpio = ioremap(0x20200000, 8);

    iowrite32(ioread32(gpio + 1) & ~(7<<27), gpio + 1);

    iounmap(gpio);

    //remove device node

    device_destroy(Class, MKDEV(MAJOR(Device), MINOR(Device)));

    class_destroy(Class);

    unregister_chrdev_region(Device, 1);

    cdev_del(&c_dev);

    printk(KERN_INFO "servo2: unloaded\n");

}

MODULE_DESCRIPTION("servo2");

MODULE_LICENSE("GPL");

This site has been tested to display correctly using Epiphany on the Raspberry Pi