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