Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add project files to the repository. Actual initial commit.

  • Loading branch information...
commit 387d4f9d5b3973fba2e1a95c1ffca542d2bbb11d 1 parent 593a31a
@andreynech andreynech authored
View
10 Makefile
@@ -0,0 +1,10 @@
+obj-m += xeno-pwm.o
+xeno-pwm-objs := xeno-pwm-drv.o pwm-task-proc.o
+
+EXTRA_CFLAGS += -I/usr/include/xenomai/
+
+all:
+ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
+
+clean:
+ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
View
17 Makefile.cli
@@ -0,0 +1,17 @@
+
+CFLAGS += -I/usr/include/xenomai/
+
+LDFLAGS += -lxenomai
+LDFLAGS += -lrtdm
+
+all: xeno-pwm-app
+
+xeno-pwm-app: xeno-pwm-app.o
+ $(CC) xeno-pwm-app.o $(LDFLAGS) -o xeno-pwm-app
+
+xeno-pwm-app.o: xeno-pwm-app.c
+ $(CC) -c $(CFLAGS) xeno-pwm-app.c
+
+clean:
+ rm -f xeno-pwm-app.o xeno-pwm-app
+
View
8 README.md
@@ -1,4 +1,10 @@
rtdm-pwm
========
-Xenomai RTDM driver to generate standard RC PWMs with GPIO on BeagleBoard xM
+Xenomai RTDM driver to generate standard RC PWMs with GPIO on BeagleBoard xM.
+
+There are sources for the kernel module and test program which is implemented in xeno-pwm-app.c. This program changes pwm duty cycle from 0 to 100% with 10% step and finally sets the duty cycle to 50%. It should be the middle position of the servo motor.
+
+To build the kernel module xeno-pwm.ko, just run make out of the kernel source tree (i.e. in the directory where where you cloned git repository). To build test application, run make -f Makefile.cli . If you need more instructions how we set-up our (cross)compilation environment, please refer the following article: http://veter-project.blogspot.com/2012/03/comfortable-kernel-workflow-on.html . If everything goes fine, there will xeno-pwm.ko and xeno-pwm-app binary files.
+
+To test the application, first insert module with insmod xeno-pwm.ko. Make sure to check dmesg for possible error messages. All messages should be preffixed with "PWM:". If there are no error messages, and you can see the xeno_pwm in the output of the lsmod, then you can run xeno-pwm-app to change PWM duty cycles.
View
7 div100.c
@@ -0,0 +1,7 @@
+int
+div100(long long dividend)
+{
+ long long divisor = 0x28f5c29;
+ return ((divisor * dividend)>>32) & 0xffffffff;
+}
+
View
8 div100.h
@@ -0,0 +1,8 @@
+#ifndef DIV100_H
+#define DIV100_H
+
+// Efficiently divides by 100 using
+// multiplication and shift only
+int div100(long long dividend);
+
+#endif
View
193 pwm-task-proc.c
@@ -0,0 +1,193 @@
+#include <rtdm/rtdm_driver.h>
+#include "pwm-task-proc.h"
+#include "div100.h"
+
+
+//extern lldiv_t_rr __aeabi_ldivmod(long long, long long);
+
+/*
+ * IEN - Input Enable
+ * IDIS - Input Disable
+ * PTD - Pull type Down
+ * PTU - Pull type Up
+ * DIS - Pull type selection is inactive
+ * EN - Pull type selection is active
+ * M0 - Mode 0
+ */
+
+#define IEN (1 << 8)
+
+#define IDIS (0 << 8)
+#define PTU (1 << 4)
+#define PTD (0 << 4)
+#define EN (1 << 3)
+#define DIS (0 << 3)
+
+#define M0 0
+#define M1 1
+#define M2 2
+#define M3 3
+#define M4 4
+#define M5 5
+#define M6 6
+#define M7 7
+
+#define OFFSET(OFF) (OFF / sizeof(ulong))
+#define RANGE_MAP100(c, d, x) (c + div100((d - c) * x))
+
+static int RC_NUM = 0;
+static rtdm_task_t pwm_task[8];
+static nanosecs_rel_t up_period[8];
+
+// Initialized with default pulse ranges
+static int ranges[8][2] = {
+ {950, 2050},
+ {950, 2050},
+ {950, 2050},
+ {950, 2050},
+ {950, 2050},
+ {950, 2050},
+ {950, 2050},
+ {950, 2050}
+};
+
+// Memory for triggering gpio
+static void __iomem *gpio = NULL;
+
+
+void
+pwm_task_proc(void *arg)
+{
+ const int which = (int)arg;
+
+ // Toggling the pins
+ for(;;)
+ {
+ //set_data_out has offset 0x94
+ iowrite32(0x40000000, gpio + 0x6094);
+ if(0 != rtdm_task_sleep(up_period[which]))
+ rtdm_printk("PWM: rtdm_task_sleep() returns error\n");
+
+ //clear_data_out has offset 0x90
+ iowrite32(0x40000000, gpio + 0x6090);
+ if(0 != rtdm_task_wait_period())
+ rtdm_printk("PWM: rtdm_task_wait_period() returns error\n");
+ }
+
+}
+
+
+void
+setpwmwidth(int channel, int percentage)
+{
+ //rtdm_printk("PWM: %i -> %i\n", channel, percentage);
+ up_period[channel] = 1000 * RANGE_MAP100(ranges[channel][0],
+ ranges[channel][1],
+ percentage);
+}
+
+
+nanosecs_rel_t
+getpwmwidth(int channel)
+{
+ return up_period[channel];
+}
+
+
+int
+initpwm(pwm_desc_t *channels, int nchannels)
+{
+ int i;
+ int retval;
+ void __iomem *pinconf;
+
+ if(nchannels < 0 || nchannels > 8)
+ {
+ rtdm_printk("PWM: number of channels should be between 1 and 8\n");
+ return 1;
+ }
+
+ RC_NUM = nchannels;
+ for(i = 0; i < RC_NUM; i++)
+ {
+ ranges[channels[i].channel][0] = channels[i].pwmMinWidth;
+ ranges[channels[i].channel][1] = channels[i].pwmMaxWidth;
+ up_period[channels[i].channel] =
+ 1000 * RANGE_MAP100(ranges[i][0], ranges[i][1], 50);
+ }
+
+ rtdm_printk("PWM: pulse lengths initialized\n");
+
+ rtdm_printk("PWM: configuring I/O pads mode\n");
+ pinconf = ioremap(0x48000000, 0x05cc);
+ if(!pinconf)
+ {
+ rtdm_printk("PWM: pinconf mapping failed\n");
+ return 0;
+ }
+ // set lower 16 pins to GPIO bank5
+ // (EN | PTD | M4) is 0x0C (0b01100)
+ iowrite16((EN | PTD | M4), pinconf + 0x2190);
+ iounmap(pinconf);
+
+ rtdm_printk("PWM: configuring GPIO bank 5\n");
+
+ gpio =
+ ioremap(0x49050000, 0x05cc);
+ if(!gpio)
+ {
+ rtdm_printk("PWM: GPIO mapping failed\n");
+ return 0;
+ }
+
+ // 0x4000 0000 - bit 30 is set
+ // 0x8000 0000 - bit 31 is set
+
+ // First set all output on bank5 to high
+ // (set_data_out has offset 0x94)
+ iowrite32(0xFFFFFFFF, gpio + 0x6094);
+
+ // Configure low 16 GPIO pins on bank 5 as output.
+ // GPIO 5 is at physical address 0x49056000 = 0x49050000+0x6000
+ // GPIO Output enable (GPIO_OE) is offset by 0x34 for each bank
+ // (set low for output)
+ iowrite32(0x00000000, gpio + 0x6034);
+ // Also disable the wakeupenable and irqenable intertupts
+ // GPIO clear_Wakeupenable is offset by 0x80 for each bank
+ iowrite32(0x0000FFFF, gpio + 0x6080);
+
+ // GPIO clear_irqenable1 is offset by 0x60 for each bank
+ iowrite32(0x0000FFFF, gpio + 0x6060);
+ // GPIO clear_irqenable2 is offset by 0x70 for each bank
+ iowrite32(0x0000FFFF, gpio + 0x6070);
+
+ rtdm_printk("PWM: Starting PWM generation tasks.\n");
+
+ for(i = 0; i < RC_NUM; i++)
+ {
+ retval = rtdm_task_init(&pwm_task[i],
+ "pwm-task",
+ pwm_task_proc,
+ 0,
+ RTDM_TASK_HIGHEST_PRIORITY,
+ 20000000); // 20ms period
+ if(retval)
+ {
+ rtdm_printk("PWM: error creating RTDM task: %i\n", retval);
+ return retval;
+ }
+ }
+ rtdm_printk("PWM: RTDM tasks created\n");
+
+ return 0;
+}
+
+
+void
+cleanuppwm(void)
+{
+ int i = 0;
+ for(; i < RC_NUM; ++i)
+ rtdm_task_destroy(&pwm_task[i]);
+ iounmap(gpio);
+}
View
53 pwm-task-proc.h
@@ -0,0 +1,53 @@
+#ifndef __PWM_TASK_PROC_H
+#define __PWM_TASK_PROC_H
+
+typedef struct pwm_desc_s
+{
+ int channel;
+ int pwmMinWidth;
+ int pwmMaxWidth;
+} pwm_desc_t;
+
+/**
+ * Initialize PWM generation infrastructure.
+ *
+ * @param nchannels - number of required channels. The value must be
+ * between 1 and 8.
+ *
+ * @return 0 on success or error code in case of failure.
+ */
+int initpwm(pwm_desc_t *channels, int nchannels);
+
+
+/**
+ * Set the width of the pulse for certain channel.
+ *
+ * @param channel - specify the output channel to work with.
+ *
+ * @param percentage - pulse width specified in percents of the
+ * maximal allowed width. Should be in range between 0 and 100. The
+ * actual width which will be set is implementation specific and may
+ * vary depending on devices to be controlled. Current implementation
+ * will set the width in range between 600 and 2000 usec which is the
+ * typical range for model servos such as for example Futaba servos.
+ */
+void setpwmwidth(int channel, int percentage);
+
+
+/**
+ * Return current PWM width in percents of the maximum width.
+ *
+ * @param channel - specify the output channel of interest.
+ *
+ * @return PWM width withing 0 to 100 range
+ */
+nanosecs_rel_t getpwmwidth(int channel);
+
+
+/**
+ * Release acquired resources and stops real-time threads started for
+ * PWM generation.
+ */
+void cleanuppwm(void);
+
+#endif
View
54 xeno-pwm-app.c
@@ -0,0 +1,54 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <rtdm/rtdm.h>
+
+#define DEVICE_NAME "gpio-pwm"
+
+
+int
+main(int argc, char *argv)
+{
+ char buf[64];
+ int c;
+ ssize_t bufsize = sizeof(buf);
+ ssize_t size;
+ int device;
+ int ret;
+
+ // open the device
+ device = rt_dev_open(DEVICE_NAME, 0);
+ if (device < 0)
+ {
+ printf("ERROR : can't open device %s (%s)\n",
+ DEVICE_NAME, strerror(-device));
+ return 1;
+ }
+
+ for(c = 0; c <= 100; c += 10)
+ {
+ size = sprintf(buf, "%u", c);
+ size = rt_dev_write (device, (const void*)buf, size);
+ printf("Write to device %s\t: %d bytes - %s\n", DEVICE_NAME, size, buf);
+
+ sleep(1);
+ memset(buf, 0, bufsize);
+
+ size = rt_dev_read (device, (void*)&buf, bufsize);
+ printf("Read from device %s\t: %d bytes - %s\n", DEVICE_NAME, size, buf);
+ }
+
+ size = sprintf(buf, "50");
+ size = rt_dev_write (device, (const void*)buf, size);
+
+ // close the device
+ ret = rt_dev_close(device);
+ if (ret < 0)
+ {
+ printf("ERROR : can't close device %s (%s)\n",
+ DEVICE_NAME, strerror(-ret));
+ return 2;
+ }
+
+ return 0;
+}
View
196 xeno-pwm-drv.c
@@ -0,0 +1,196 @@
+#include <linux/module.h>
+#include <rtdm/rtdm_driver.h>
+#include "pwm-task-proc.h"
+#include "div100.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Andrey Nechypurenko <andreynech@gmail.com>");
+MODULE_DESCRIPTION("Generates RC PWMs with GPIO");
+
+#define DEVICE_NAME "gpio-pwm"
+#define SOME_SUB_CLASS 4711
+
+/**
+ * The context of a device instance
+ *
+ * A context is created each time a device is opened and passed to
+ * other device handlers when they are called.
+ *
+ */
+typedef struct context_s
+{
+} context_t;
+
+
+/**
+ * Open the device
+ *
+ * This function is called when the device shall be opened.
+ *
+ */
+static int
+pwm_rtdm_open_nrt(struct rtdm_dev_context *context,
+ rtdm_user_info_t *user_info,
+ int oflags)
+{
+ //context_t *ctx = (context_t*)context->dev_private;
+
+ return 0;
+}
+
+/**
+ * Close the device
+ *
+ * This function is called when the device shall be closed.
+ *
+ */
+static int
+pwm_rtdm_close_nrt(struct rtdm_dev_context *context,
+ rtdm_user_info_t * user_info)
+{
+ return 0;
+}
+
+/**
+ * Read from the device
+ *
+ * This function is called when the device is read in non-realtime
+ * context.
+ *
+ */
+static ssize_t
+pwm_rtdm_read_nrt(struct rtdm_dev_context *context,
+ rtdm_user_info_t * user_info, void *buf,
+ size_t nbyte)
+{
+ //context_t *ctx = (context_t*)context->dev_private;
+ size_t size;
+ char uptime[32];
+ size = sprintf(uptime, "%u", div100(getpwmwidth(0)));
+ if(rtdm_safe_copy_to_user(user_info, buf, uptime, size))
+ rtdm_printk("ERROR : can't copy data from driver\n");
+
+ return size;
+}
+
+/**
+ * Write in the device
+ *
+ * This function is called when the device is written in non-realtime context.
+ *
+ */
+static ssize_t
+pwm_rtdm_write_nrt(struct rtdm_dev_context *context,
+ rtdm_user_info_t * user_info,
+ const void *buf, size_t nbyte)
+{
+ //context_t *ctx = (context_t*)context->dev_private;
+
+ int duty_perc = simple_strtoul(buf, NULL, 0);
+ setpwmwidth(0, duty_perc);
+ return nbyte;
+}
+
+/**
+ * This structure describe the simple RTDM device
+ *
+ */
+static struct rtdm_device device = {
+ .struct_version = RTDM_DEVICE_STRUCT_VER,
+
+ .device_flags = RTDM_NAMED_DEVICE,
+ .context_size = sizeof(context_t),
+ .device_name = DEVICE_NAME,
+
+ .open_nrt = pwm_rtdm_open_nrt,
+
+ .ops = {
+ .close_nrt = pwm_rtdm_close_nrt,
+ .read_nrt = pwm_rtdm_read_nrt,
+ .write_nrt = pwm_rtdm_write_nrt,
+ },
+
+ .device_class = RTDM_CLASS_EXPERIMENTAL,
+ .device_sub_class = SOME_SUB_CLASS,
+ .profile_version = 1,
+ .driver_name = "GPIO-PWM",
+ .driver_version = RTDM_DRIVER_VER(0, 1, 2),
+ .peripheral_name = "GPIO PWM generator",
+ .provider_name = "Andrey Nechypurenko",
+ .proc_name = device.device_name,
+};
+
+
+/**
+ * This function is called when the module is loaded
+ *
+ * It simply registers the RTDM device.
+ *
+ */
+int __init pwm_rtdm_init(void)
+{
+ pwm_desc_t pwm[1];
+ int res;
+
+ res = rtdm_dev_register(&device);
+ if(res == 0)
+ rtdm_printk("PWM driver registered without errors\n");
+ else
+ {
+ rtdm_printk("PWM driver registration failed: \n");
+ switch(res)
+ {
+ case -EINVAL:
+ rtdm_printk("The device structure contains invalid entries. "
+ "Check kernel log for further details.");
+ break;
+
+ case -ENOMEM:
+ rtdm_printk("The context for an exclusive device cannot be allocated.");
+ break;
+
+ case -EEXIST:
+ rtdm_printk("The specified device name of protocol ID is already in use.");
+ break;
+
+ case -EAGAIN: rtdm_printk("Some /proc entry cannot be created.");
+ break;
+
+ default:
+ rtdm_printk("Unknown error code returned");
+ break;
+ }
+ rtdm_printk("\n");
+ }
+
+ // Initialize with default values
+ pwm[0].channel = 0;
+ pwm[0].pwmMinWidth = 950;
+ pwm[0].pwmMaxWidth = 2050;
+
+ res = initpwm(pwm, sizeof(pwm) / sizeof(pwm[0]));
+ if(res != 0)
+ rtdm_printk("PWM: initialization error: %i was returned\n", res);
+ else
+ setpwmwidth(0, 50); // 50% duty is middle position
+
+ return res;
+}
+
+/**
+ * This function is called when the module is unloaded
+ *
+ * It unregister the RTDM device, polling at 1000 ms for pending users.
+ *
+ */
+void __exit pwm_rtdm_exit(void)
+{
+ rtdm_printk("PWM: stopping pwm tasks\n");
+ cleanuppwm();
+ rtdm_dev_unregister(&device, 1000);
+ rtdm_printk("PWM: uninitialized\n");
+}
+
+
+module_init(pwm_rtdm_init);
+module_exit(pwm_rtdm_exit);
Please sign in to comment.
Something went wrong with that request. Please try again.