aboutsummaryrefslogtreecommitdiff
path: root/nuttx/configs/px4io/src/drv_pwm_servo.c
diff options
context:
space:
mode:
Diffstat (limited to 'nuttx/configs/px4io/src/drv_pwm_servo.c')
-rw-r--r--nuttx/configs/px4io/src/drv_pwm_servo.c316
1 files changed, 316 insertions, 0 deletions
diff --git a/nuttx/configs/px4io/src/drv_pwm_servo.c b/nuttx/configs/px4io/src/drv_pwm_servo.c
new file mode 100644
index 000000000..7f3238da3
--- /dev/null
+++ b/nuttx/configs/px4io/src/drv_pwm_servo.c
@@ -0,0 +1,316 @@
+/****************************************************************************
+ *
+ * Copyright (C) 2012 PX4 Development Team. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. Neither the name PX4 nor the names of its contributors may be
+ * used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+/**
+ * @file Servo driver supporting PWM servos connected to STM32 timer blocks.
+ *
+ * Works with any of the 'generic' or 'advanced' STM32 timers that
+ * have output pins, does not require an interrupt.
+ */
+
+#include <nuttx/config.h>
+#include <nuttx/arch.h>
+
+#include <sys/types.h>
+#include <stdbool.h>
+
+#include <debug.h>
+#include <errno.h>
+
+#include <arch/board/board.h>
+#include <arch/board/drv_pwm_servo.h>
+
+#include "stm32_gpio.h"
+#include "stm32_tim.h"
+
+#ifdef CONFIG_PWM_SERVO
+
+static const struct pwm_servo_config *cfg;
+
+#define REG(_tmr, _reg) (*(volatile uint32_t *)(cfg->timers[_tmr].base + _reg))
+
+#define rCR1(_tmr) REG(_tmr, STM32_GTIM_CR1_OFFSET)
+#define rCR2(_tmr) REG(_tmr, STM32_GTIM_CR2_OFFSET)
+#define rSMCR(_tmr) REG(_tmr, STM32_GTIM_SMCR_OFFSET)
+#define rDIER(_tmr) REG(_tmr, STM32_GTIM_DIER_OFFSET)
+#define rSR(_tmr) REG(_tmr, STM32_GTIM_SR_OFFSET)
+#define rEGR(_tmr) REG(_tmr, STM32_GTIM_EGR_OFFSET)
+#define rCCMR1(_tmr) REG(_tmr, STM32_GTIM_CCMR1_OFFSET)
+#define rCCMR2(_tmr) REG(_tmr, STM32_GTIM_CCMR2_OFFSET)
+#define rCCER(_tmr) REG(_tmr, STM32_GTIM_CCER_OFFSET)
+#define rCNT(_tmr) REG(_tmr, STM32_GTIM_CNT_OFFSET)
+#define rPSC(_tmr) REG(_tmr, STM32_GTIM_PSC_OFFSET)
+#define rARR(_tmr) REG(_tmr, STM32_GTIM_ARR_OFFSET)
+#define rCCR1(_tmr) REG(_tmr, STM32_GTIM_CCR1_OFFSET)
+#define rCCR2(_tmr) REG(_tmr, STM32_GTIM_CCR2_OFFSET)
+#define rCCR3(_tmr) REG(_tmr, STM32_GTIM_CCR3_OFFSET)
+#define rCCR4(_tmr) REG(_tmr, STM32_GTIM_CCR4_OFFSET)
+#define rDCR(_tmr) REG(_tmr, STM32_GTIM_DCR_OFFSET)
+#define rDMAR(_tmr) REG(_tmr, STM32_GTIM_DMAR_OFFSET)
+
+static void
+pwm_timer_init(unsigned timer)
+{
+ /* enable the timer clock before we try to talk to it */
+ modifyreg32(cfg->timers[timer].clock_register, 0, cfg->timers[timer].clock_bit);
+
+ /* disable and configure the timer */
+ rCR1(timer) = 0;
+ rCR2(timer) = 0;
+ rSMCR(timer) = 0;
+ rDIER(timer) = 0;
+ rCCER(timer) = 0;
+ rCCMR1(timer) = 0;
+ rCCMR2(timer) = 0;
+ rCCER(timer) = 0;
+ rDCR(timer) = 0;
+
+ /* configure the timer to free-run at 1MHz */
+ rPSC(timer) = cfg->timers[timer].clock_freq / 1000000;
+
+ /* and update at the desired rate */
+ rARR(timer) = 1000000 / cfg->update_rate;
+
+ /* generate an update event; reloads the counter and all registers */
+ rEGR(timer) = GTIM_EGR_UG;
+
+ /* note that the timer is left disabled - arming is performed separately */
+}
+
+static void
+pwm_servos_arm(bool armed)
+{
+ /* iterate timers and arm/disarm appropriately */
+ for (unsigned i = 0; i < PWM_SERVO_MAX_TIMERS; i++) {
+ if (cfg->timers[i].base != 0)
+ rCR1(i) = armed ? GTIM_CR1_CEN : 0;
+ }
+}
+
+static void
+pwm_channel_init(unsigned channel)
+{
+ unsigned timer = cfg->channels[channel].timer_index;
+
+ /* configure the GPIO first */
+ stm32_configgpio(cfg->channels[channel].gpio);
+
+ /* configure the channel */
+ switch (cfg->channels[channel].timer_channel) {
+ case 1:
+ rCCMR1(timer) |= (6 << 4);
+ rCCR1(timer) = cfg->channels[channel].default_value;
+ rCCER(timer) |= (1 << 0);
+ break;
+ case 2:
+ rCCMR1(timer) |= (6 << 12);
+ rCCR2(timer) = cfg->channels[channel].default_value;
+ rCCER(timer) |= (1 << 4);
+ break;
+ case 3:
+ rCCMR2(timer) |= (6 << 4);
+ rCCR3(timer) = cfg->channels[channel].default_value;
+ rCCER(timer) |= (1 << 8);
+ break;
+ case 4:
+ rCCMR2(timer) |= (6 << 12);
+ rCCR4(timer) = cfg->channels[channel].default_value;
+ rCCER(timer) |= (1 << 12);
+ break;
+ }
+}
+
+static void
+pwm_channel_set(unsigned channel, servo_position_t value)
+{
+ if (channel >= PWM_SERVO_MAX_CHANNELS) {
+ lldbg("pwm_channel_set: bogus channel %u\n", channel);
+ return;
+ }
+
+ unsigned timer = cfg->channels[channel].timer_index;
+
+ /* test timer for validity */
+ if ((cfg->timers[timer].base == 0) ||
+ (cfg->channels[channel].gpio == 0))
+ return;
+
+ /* configure the channel */
+ switch (cfg->channels[channel].timer_channel) {
+ case 1:
+ rCCR1(timer) = value;
+ break;
+ case 2:
+ rCCR2(timer) = value;
+ break;
+ case 3:
+ rCCR3(timer) = value;
+ break;
+ case 4:
+ rCCR4(timer) = value;
+ break;
+ }
+}
+
+static servo_position_t
+pwm_channel_get(unsigned channel)
+{
+ if (channel >= PWM_SERVO_MAX_CHANNELS) {
+ lldbg("pwm_channel_get: bogus channel %u\n", channel);
+ return 0;
+ }
+
+ unsigned timer = cfg->channels[channel].timer_index;
+ servo_position_t value = 0;
+
+ /* test timer for validity */
+ if ((cfg->timers[timer].base == 0) ||
+ (cfg->channels[channel].gpio == 0))
+ return 0;
+
+ /* configure the channel */
+ switch (cfg->channels[channel].timer_channel) {
+ case 1:
+ value = rCCR1(timer);
+ break;
+ case 2:
+ value = rCCR2(timer);
+ break;
+ case 3:
+ value = rCCR3(timer);
+ break;
+ case 4:
+ value = rCCR4(timer);
+ break;
+ }
+ return value;
+}
+
+static int pwm_servo_write(struct file *filp, const char *buffer, size_t len);
+static int pwm_servo_read(struct file *filp, char *buffer, size_t len);
+static int pwm_servo_ioctl(struct file *filep, int cmd, unsigned long arg);
+
+static const struct file_operations pwm_servo_fops = {
+ .write = pwm_servo_write,
+ .read = pwm_servo_read,
+ .ioctl = pwm_servo_ioctl
+};
+
+static int
+pwm_servo_write(struct file *filp, const char *buffer, size_t len)
+{
+ unsigned channels = len / sizeof(servo_position_t);
+ servo_position_t *pdata = (servo_position_t *)buffer;
+ unsigned i;
+
+ if (channels > PWM_SERVO_MAX_CHANNELS)
+ return -EIO;
+
+ for (i = 0; i < channels; i++)
+ pwm_channel_set(i, pdata[i]);
+
+ return i * sizeof(servo_position_t);
+}
+
+static int
+pwm_servo_read(struct file *filp, char *buffer, size_t len)
+{
+ unsigned channels = len / sizeof(servo_position_t);
+ servo_position_t *pdata = (servo_position_t *)buffer;
+ unsigned i;
+
+ if (channels > PWM_SERVO_MAX_CHANNELS)
+ return -EIO;
+
+ for (i = 0; i < channels; i++)
+ pdata[i] = pwm_channel_get(i);
+
+ return i * sizeof(servo_position_t);
+}
+
+static int
+pwm_servo_ioctl(struct file *filep, int cmd, unsigned long arg)
+{
+ /* regular ioctl? */
+ switch (cmd) {
+ case PWM_SERVO_ARM:
+ pwm_servos_arm(true);
+ return 0;
+
+ case PWM_SERVO_DISARM:
+ pwm_servos_arm(false);
+ return 0;
+ }
+
+ /* channel set? */
+ if ((cmd >= PWM_SERVO_SET(0)) && (cmd < PWM_SERVO_SET(PWM_SERVO_MAX_CHANNELS))) {
+ /* XXX sanity-check value? */
+ pwm_channel_set(cmd - PWM_SERVO_SET(0), (servo_position_t)arg);
+ return 0;
+ }
+
+ /* channel get? */
+ if ((cmd >= PWM_SERVO_GET(0)) && (cmd < PWM_SERVO_GET(PWM_SERVO_MAX_CHANNELS))) {
+ /* XXX sanity-check value? */
+ *(servo_position_t *)arg = pwm_channel_get(cmd - PWM_SERVO_GET(0));
+ return 0;
+ }
+
+ /* not a recognised value */
+ return -ENOTTY;
+}
+
+
+int
+pwm_servo_init(const struct pwm_servo_config *config)
+{
+ /* save a pointer to the configuration */
+ cfg = config;
+
+ /* do basic timer initialisation first */
+ for (unsigned i = 0; i < PWM_SERVO_MAX_TIMERS; i++) {
+ if (cfg->timers[i].base != 0)
+ pwm_timer_init(i);
+ }
+
+ /* now init channels */
+ for (unsigned i = 0; i < PWM_SERVO_MAX_CHANNELS; i++) {
+ if (cfg->channels[i].gpio != 0)
+ pwm_channel_init(i);
+ }
+
+ /* register the device */
+ return register_driver("/dev/pwm_servo", &pwm_servo_fops, 0666, NULL);
+}
+
+#endif /* CONFIG_PWM_SERVO */ \ No newline at end of file