From bee493ac711224c2f3eb0db212eaf1e4e85375cf Mon Sep 17 00:00:00 2001
From: Gilles DOFFE <g.doffe@gmail.com>
Date: Fri, 23 Feb 2018 21:51:09 +0100
Subject: [PATCH] cpu/stm32_common: add qdec implementation

* Add support for the STM32 encoder interface
* The STM32 only supports X2 and X4 modes.
* Enable interrupt handler on counter overflow
* Add simple test code for QDEC based on nucleo-f401 board

Signed-off-by: Gilles DOFFE <g.doffe@gmail.com>
---
 cpu/stm32_common/include/periph_cpu_common.h |  27 +++
 cpu/stm32_common/periph/qdec.c               | 223 +++++++++++++++++++
 2 files changed, 250 insertions(+)
 create mode 100644 cpu/stm32_common/periph/qdec.c

diff --git a/cpu/stm32_common/include/periph_cpu_common.h b/cpu/stm32_common/include/periph_cpu_common.h
index b71390b373..31e454ddd9 100644
--- a/cpu/stm32_common/include/periph_cpu_common.h
+++ b/cpu/stm32_common/include/periph_cpu_common.h
@@ -60,6 +60,11 @@ extern "C" {
  */
 #define TIMER_CHAN          (4U)
 
+/**
+ * @brief   All STM QDEC timers have 2 capture channels
+ */
+#define QDEC_CHAN           (2U)
+
 /**
  * @brief   Use the shared SPI functions
  * @{
@@ -244,6 +249,28 @@ typedef struct {
     uint8_t bus;                    /**< APB bus */
 } pwm_conf_t;
 
+/**
+ * @brief   QDEC channel
+ */
+typedef struct {
+    gpio_t pin;             /**< GPIO pin mapped to this channel */
+    uint8_t cc_chan;        /**< capture compare channel used */
+} qdec_chan_t;
+
+/**
+ * @brief   QDEC configuration
+ */
+typedef struct {
+    TIM_TypeDef *dev;               /**< Timer used */
+    uint32_t max;                   /**< Maximum counter value */
+    uint32_t rcc_mask;              /**< bit in clock enable register */
+    qdec_chan_t chan[QDEC_CHAN];    /**< channel mapping, set to {GPIO_UNDEF, 0}
+                                     *   if not used */
+    gpio_af_t af;                   /**< alternate function used */
+    uint8_t bus;                    /**< APB bus */
+    uint8_t irqn;                   /**< global IRQ channel */
+} qdec_conf_t;
+
 /**
  * @brief   Structure for UART configuration data
  */
diff --git a/cpu/stm32_common/periph/qdec.c b/cpu/stm32_common/periph/qdec.c
new file mode 100644
index 0000000000..613f45128f
--- /dev/null
+++ b/cpu/stm32_common/periph/qdec.c
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2017 Gilles DOFFE <gdoffe@gmail.com>
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     cpu_cortexm_common
+ * @ingroup     drivers_periph_qdec
+ * @{
+ *
+ * @file
+ * @brief       Low-level QDEC driver implementation
+ *
+ * @author      Gilles DOFFE <gilles.doffe@gmail.com>
+ *
+ * @}
+ */
+
+#include <errno.h>
+
+#include "cpu.h"
+#include "assert.h"
+#include "periph/qdec.h"
+#include "periph/gpio.h"
+
+#ifdef QDEC_NUMOF
+
+/**
+ * @brief   Interrupt context for each configured qdec
+ */
+static qdec_isr_ctx_t isr_ctx[QDEC_NUMOF];
+
+/**
+ * @brief Read the current value of the given qdec device. Internal use.
+ *
+ * @param[in] dev           the qdec to read the current value from
+ * @param[in] dev           perform a reset of qdec counter if not 0
+ *
+ * @return                  the qdecs current value
+ */
+static int32_t _qdec_read(qdec_t qdec, uint8_t reset);
+
+static inline TIM_TypeDef *dev(qdec_t qdec)
+{
+    return qdec_config[qdec].dev;
+}
+
+int32_t qdec_init(qdec_t qdec, qdec_mode_t mode, qdec_cb_t cb, void *arg)
+{
+    /* Control variables */
+    uint8_t i = 0;
+
+    /* Verify parameters */
+    assert((qdec < QDEC_NUMOF));
+
+    /* Power on the used timer */
+    periph_clk_en(qdec_config[qdec].bus, qdec_config[qdec].rcc_mask);
+
+    /* Reset configuration and CC channels */
+    dev(qdec)->CR1 = 0;
+    dev(qdec)->CR2 = 0;
+    dev(qdec)->SMCR = 0;
+    dev(qdec)->CCER = 0;
+    for (i = 0; i < QDEC_CHAN; i++) {
+        dev(qdec)->CCR[i] = 0;
+    }
+
+    /* Count on A (TI1) signal edges, B (TI2) signal edges or both,
+     * default to EINVAL (Invalide argument).
+     */
+    switch (mode) {
+        /* X2 mode */
+        case QDEC_X2:
+            dev(qdec)->SMCR |= (0x02 << TIM_SMCR_SMS_Pos);
+            break;
+        /* X4 mode */
+        case QDEC_X4:
+            dev(qdec)->SMCR |= (0x03 << TIM_SMCR_SMS_Pos);
+            break;
+        /* X1 mode does not exist on STM32 as STM32 always counts
+         * on both rising and falling edges from encoder
+         */
+        case QDEC_X1:
+        default:
+            errno = EINVAL;
+            goto err_invalid_mode;
+    }
+
+    /* Reset configuration and CC channels */
+    for (i = 0; i < QDEC_CHAN; i++) {
+        dev(qdec)->CCR[i] = 0;
+    }
+
+    /* Configure the used pins */
+    i = 0;
+    while ((i < QDEC_CHAN) && (qdec_config[qdec].chan[i].pin != GPIO_UNDEF)) {
+        gpio_init(qdec_config[qdec].chan[i].pin, GPIO_IN);
+        gpio_init_af(qdec_config[qdec].chan[i].pin, qdec_config[qdec].af);
+        i++;
+    }
+
+    /* Set counting max value */
+    dev(qdec)->ARR = qdec_config[qdec].max;
+
+    /* Set TIMx_CNT value to half of of TIMx_ARR to permit countdown */
+    dev(qdec)->CNT = dev(qdec)->ARR / 2;
+
+    /* Remember the interrupt context */
+    isr_ctx[qdec].cb = cb;
+    isr_ctx[qdec].arg = arg;
+
+    /* Enable the qdec's interrupt */
+    NVIC_EnableIRQ(qdec_config[qdec].irqn);
+    dev(qdec)->DIER |= TIM_DIER_UIE;
+
+    /* Reset counter and start qdec */
+    qdec_start(qdec);
+
+    return 0;
+
+/* Error management */
+err_invalid_mode:
+    return errno;
+}
+
+inline int32_t qdec_read(qdec_t qdec)
+{
+    return _qdec_read(qdec, false);
+}
+
+inline int32_t qdec_read_and_reset(qdec_t qdec)
+{
+    return _qdec_read(qdec, true);
+}
+
+static int32_t _qdec_read(qdec_t qdec, uint8_t reset)
+{
+    int32_t count = 0;
+    uint32_t irq_save = 0;
+
+    /* Protect critical section */
+    irq_save = irq_disable();
+
+    /* Get counter value */
+    count = dev(qdec)->CNT;
+
+    /* Reset counter if asked */
+    if (reset)
+    {
+        dev(qdec)->CNT = dev(qdec)->ARR / 2;
+    }
+
+    /* Restore IRQ */
+    irq_restore(irq_save);
+
+    /* Substract offset before return */
+    count -= dev(qdec)->ARR / 2;
+
+    /* Return count minus offset */
+    return count;
+}
+
+void qdec_start(qdec_t qdec)
+{
+    dev(qdec)->CR1 |= TIM_CR1_CEN;
+}
+
+void qdec_stop(qdec_t qdec)
+{
+    dev(qdec)->CR1 &= ~TIM_CR1_CEN;
+}
+
+static inline void irq_handler(qdec_t qdec)
+{
+    uint32_t status = (dev(qdec)->SR & dev(qdec)->DIER);
+
+    if (status & (TIM_SR_UIF)) {
+        dev(qdec)->SR &= ~(TIM_SR_UIF);
+        isr_ctx[qdec].cb(isr_ctx[qdec].arg);
+    }
+    cortexm_isr_end();
+}
+
+
+#ifdef QDEC_0_ISR
+void QDEC_0_ISR(void)
+{
+    irq_handler(0);
+}
+#endif
+
+#ifdef QDEC_1_ISR
+void QDEC_1_ISR(void)
+{
+    irq_handler(1);
+}
+#endif
+
+#ifdef QDEC_2_ISR
+void QDEC_2_ISR(void)
+{
+    irq_handler(2);
+}
+#endif
+
+#ifdef QDEC_3_ISR
+void QDEC_3_ISR(void)
+{
+    irq_handler(3);
+}
+#endif
+
+#ifdef QDEC_4_ISR
+void QDEC_4_ISR(void)
+{
+    irq_handler(4);
+}
+#endif
+
+#endif /* QDEC_NUMOF */
-- 
GitLab