aboutsummaryrefslogtreecommitdiffstats
path: root/libmaple/timer.c
diff options
context:
space:
mode:
authorMarti Bolivar <mbolivar@leaflabs.com>2011-03-22 16:59:29 -0400
committerMarti Bolivar <mbolivar@leaflabs.com>2011-03-24 07:25:14 -0400
commit61db54f52f32e63c895d775982fbcdcb67f2dde6 (patch)
treeb0513c712b5888ab0a4e6613fdee3db606b61aaf /libmaple/timer.c
parent6bc8cb7c1181e8005019e4ce1f2bea956c44e044 (diff)
downloadlibrambutan-61db54f52f32e63c895d775982fbcdcb67f2dde6.tar.gz
librambutan-61db54f52f32e63c895d775982fbcdcb67f2dde6.zip
Initial timer refactor.
Basic PWM works. Had some problems in testing that might be due to USART bugs. HardwareTimer has been removed from the build for now; I will re-implement it in terms of the new libmaple API, but consider it deprecated. Let's come up with something better. Servo is implemented in terms of HardwareTimer, so it also has been temporarily removed from the build. pwmWrite() likely got a little bit less inefficient due to indirection, but the PIN_MAPs shrank by a pointer per PinMapping.
Diffstat (limited to 'libmaple/timer.c')
-rw-r--r--libmaple/timer.c449
1 files changed, 449 insertions, 0 deletions
diff --git a/libmaple/timer.c b/libmaple/timer.c
new file mode 100644
index 0000000..ad0ec6f
--- /dev/null
+++ b/libmaple/timer.c
@@ -0,0 +1,449 @@
+/******************************************************************************
+ * The MIT License
+ *
+ * Copyright (c) 2011 LeafLabs, LLC.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *****************************************************************************/
+
+/**
+ * @file timer.c
+ * @author Marti Bolivar <mbolivar@leaflabs.com>
+ * @brief New-style timer interface
+ */
+
+#include "timer.h"
+
+#if defined(STM32_MEDIUM_DENSITY)
+#define NR_TIMERS 4
+#elif defined(STM32_HIGH_DENSITY)
+#define NR_TIMERS 8
+#endif
+
+/* Just like the corresponding DIER bits:
+ * [0] = Update handler;
+ * [1,2,3,4] = capture/compare 1,2,3,4 handlers, respectively;
+ * [5] = COM;
+ * [6] = TRG;
+ * [7] = BRK. */
+#define NR_ADV_HANDLERS 8
+/* Update, capture/compare 1,2,3,4; <junk>; trigger. */
+#define NR_GEN_HANDLERS 6
+/* Update only. */
+#define NR_BAS_HANDLERS 1
+
+static timer_dev timer1 = {
+ .regs = {.adv = TIMER1_BASE},
+ .clk_id = RCC_TIMER1,
+ .type = TIMER_ADVANCED,
+ .handlers = { [NR_ADV_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER1 = &timer1;
+
+static timer_dev timer2 = {
+ .regs = {.gen = TIMER2_BASE},
+ .clk_id = RCC_TIMER2,
+ .type = TIMER_GENERAL,
+ .handlers = { [NR_GEN_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER2 = &timer2;
+
+static timer_dev timer3 = {
+ .regs = {.gen = TIMER3_BASE},
+ .clk_id = RCC_TIMER3,
+ .type = TIMER_GENERAL,
+ .handlers = { [NR_GEN_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER3 = &timer3;
+
+static timer_dev timer4 = {
+ .regs = {.gen = TIMER4_BASE},
+ .clk_id = RCC_TIMER4,
+ .type = TIMER_GENERAL,
+ .handlers = { [NR_GEN_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER4 = &timer4;
+
+#ifdef STM32_HIGH_DENSITY
+static timer_dev timer5 = {
+ .regs = {.gen = TIMER5_BASE},
+ .clk_id = RCC_TIMER5,
+ .type = TIMER_GENERAL,
+ .handlers = { [NR_GEN_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER5 = &timer5;
+
+static timer_dev timer6 = {
+ .regs = {.bas = TIMER6_BASE},
+ .clk_id = RCC_TIMER6,
+ .type = TIMER_BASIC,
+ .handlers = { [NR_BAS_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER6 = &timer6;
+
+static timer_dev timer7 = {
+ .regs = {.bas = TIMER7_BASE},
+ .clk_id = RCC_TIMER7,
+ .type = TIMER_BASIC,
+ .handlers = { [NR_BAS_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER7 = &timer7;
+
+static timer_dev timer8 = {
+ .regs = {.adv = TIMER8_BASE},
+ .clk_id = RCC_TIMER8,
+ .type = TIMER_ADVANCED,
+ .handlers = { [NR_ADV_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER8 = &timer8;
+#endif
+
+/*
+ * Convenience routines
+ */
+
+static void disable_channel(timer_dev *dev, uint8 channel);
+static void pwm_mode(timer_dev *dev, uint8 channel);
+static void output_compare_mode(timer_dev *dev, uint8 channel);
+
+static inline void enable_irq(timer_dev *dev, uint8 interrupt);
+
+/**
+ * Initialize a timer, and reset its register map.
+ * @param dev Timer to initialize
+ */
+void timer_init(timer_dev *dev) {
+ rcc_clk_enable(dev->clk_id);
+ rcc_reset_dev(dev->clk_id);
+}
+
+/**
+ * @brief Disable a timer.
+ *
+ * The timer will stop counting, all DMA requests and interrupts will
+ * be disabled, and no state changes will be output.
+ *
+ * @param dev Timer to disable.
+ */
+void timer_disable(timer_dev *dev) {
+ (dev->regs).bas->CR1 = 0;
+ (dev->regs).bas->DIER = 0;
+ switch (dev->type) {
+ case TIMER_ADVANCED: /* fall-through */
+ case TIMER_GENERAL:
+ (dev->regs).gen->CCER = 0;
+ break;
+ case TIMER_BASIC:
+ break;
+ }
+}
+
+/**
+ * Sets the mode of an individual timer channel.
+ *
+ * Note that not all timers can be configured in every mode. For
+ * example, basic timers cannot be configured to output compare mode.
+ * Be sure to use a timer which is appropriate for the mode you want.
+ *
+ * @param dev Timer whose channel mode to set
+ * @param channel Relevant channel
+ * @param mode New timer mode for channel
+ */
+void timer_set_mode(timer_dev *dev, uint8 channel, timer_mode mode) {
+ ASSERT(channel > 0 && channel <= 4);
+
+ /* TODO decide about the basic timers */
+ ASSERT(dev->type != TIMER_BASIC);
+ if (dev->type == TIMER_BASIC)
+ return;
+
+ switch (mode) {
+ case TIMER_DISABLED:
+ disable_channel(dev, channel);
+ break;
+ case TIMER_PWM:
+ pwm_mode(dev, channel);
+ break;
+ case TIMER_OUTPUT_COMPARE:
+ output_compare_mode(dev, channel);
+ break;
+ }
+}
+
+/**
+ * @brief Call a given function on all timers.
+ * @param fn Function to call on each timer.
+ */
+void timer_foreach(void (*fn)(timer_dev*)) {
+ fn(TIMER1);
+ fn(TIMER2);
+ fn(TIMER3);
+ fn(TIMER4);
+#ifdef STM32_HIGH_DENSITY
+ fn(TIMER5);
+ fn(TIMER6);
+ fn(TIMER7);
+ fn(TIMER8);
+#endif
+}
+
+/**
+ * @brief Attach a timer interrupt.
+ * @param dev Timer device
+ * @param interrupt Interrupt number to attach to; this may be any
+ * timer_interrupt_id or timer_channel value appropriate
+ * for the timer.
+ * @param handler Handler to attach to the given interrupt.
+ * @see timer_interrupt_id
+ * @see timer_channel
+ */
+void timer_attach_interrupt(timer_dev *dev,
+ uint8 interrupt,
+ voidFuncPtr handler) {
+ dev->handlers[interrupt] = handler;
+ timer_enable_interrupt(dev, interrupt);
+ enable_irq(dev, interrupt);
+}
+
+/**
+ * @brief Detach a timer interrupt.
+ * @param dev Timer device
+ * @param interrupt Interrupt number to detach; this may be any
+ * timer_interrupt_id or timer_channel value appropriate
+ * for the timer.
+ * @see timer_interrupt_id
+ * @see timer_channel
+ */
+void timer_detach_interrupt(timer_dev *dev, uint8 interrupt) {
+ timer_disable_interrupt(dev, interrupt);
+ dev->handlers[interrupt] = NULL;
+}
+
+/*
+ * IRQ handlers
+ */
+
+static inline void dispatch_adv_brk(timer_dev *dev);
+static inline void dispatch_adv_up(timer_dev *dev);
+static inline void dispatch_adv_trg_com(timer_dev *dev);
+static inline void dispatch_adv_cc(timer_dev *dev);
+static inline void dispatch_general(timer_dev *dev);
+static inline void dispatch_basic(timer_dev *dev);
+
+void __irq_tim1_brk(void) {
+ dispatch_adv_brk(TIMER1);
+}
+
+void __irq_tim1_up(void) {
+ dispatch_adv_up(TIMER1);
+}
+
+void __irq_tim1_trg_com(void) {
+ dispatch_adv_trg_com(TIMER1);
+}
+
+void __irq_tim1_cc(void) {
+ dispatch_adv_cc(TIMER1);
+}
+
+void __irq_tim2(void) {
+ dispatch_general(TIMER2);
+}
+
+void __irq_tim3(void) {
+ dispatch_general(TIMER3);
+}
+
+void __irq_tim4(void) {
+ dispatch_general(TIMER4);
+}
+
+#if defined(STM32_HIGH_DENSITY) || defined(STM32_XL_DENSITY)
+
+void __irq_tim5(void) {
+ dispatch_general(TIMER5);
+}
+
+void __irq_tim6(void) {
+ dispatch_basic(TIMER6);
+}
+
+void __irq_tim7(void) {
+ dispatch_basic(TIMER7);
+}
+
+void __irq_tim8_brk(void) {
+ dispatch_adv_brk(TIMER8);
+}
+
+void __irq_tim8_up(void) {
+ dispatch_adv_up(TIMER8);
+}
+
+void __irq_tim8_trg_com(void) {
+ dispatch_adv_trg_com(TIMER8);
+}
+
+void __irq_tim8_cc(void) {
+ dispatch_adv_cc(TIMER8);
+}
+#endif
+
+static inline void dispatch_irq(timer_dev *dev, uint8 iid, uint8 sr_bit);
+static inline void dispatch_cc_irqs(timer_dev *dev);
+
+static inline void dispatch_adv_brk(timer_dev *dev) {
+ dispatch_irq(dev, TIMER_BREAK_INTERRUPT, TIMER_SR_BIF_BIT);
+}
+
+static inline void dispatch_adv_up(timer_dev *dev) {
+ dispatch_irq(dev, TIMER_UPDATE_INTERRUPT, TIMER_SR_UIF_BIT);
+}
+
+static inline void dispatch_adv_trg_com(timer_dev *dev) {
+ dispatch_irq(dev, TIMER_TRG_INTERRUPT, TIMER_SR_TIF_BIT);
+ dispatch_irq(dev, TIMER_COM_INTERRUPT, TIMER_SR_COMIF_BIT);
+}
+
+static inline void dispatch_adv_cc(timer_dev *dev) {
+ dispatch_cc_irqs(dev);
+}
+
+static inline void dispatch_general(timer_dev *dev) {
+ dispatch_irq(dev, TIMER_TRG_INTERRUPT, TIMER_SR_TIF_BIT);
+ dispatch_cc_irqs(dev);
+ dispatch_irq(dev, TIMER_UPDATE_INTERRUPT, TIMER_SR_UIF_BIT);
+}
+
+static inline void dispatch_basic(timer_dev *dev) {
+ dispatch_irq(dev, TIMER_UPDATE_INTERRUPT, TIMER_SR_UIF_BIT);
+}
+
+static inline void dispatch_irq(timer_dev *dev, uint8 iid, uint8 sr_bit) {
+ __io uint32 *sr = &(dev->regs).bas->SR;
+ if (bb_peri_get_bit(sr, sr_bit)) {
+ if (dev->handlers[iid])
+ (dev->handlers[iid])();
+ bb_peri_set_bit(sr, sr_bit, 0);
+ }
+}
+
+static inline void dispatch_cc_irqs(timer_dev *dev) {
+ uint32 sr = (dev->regs).gen->SR;
+ uint32 sr_clear = 0;
+ uint32 b;
+
+ ASSERT(sr & (TIMER_SR_CC1IF | TIMER_SR_CC2IF |
+ TIMER_SR_CC3IF | TIMER_SR_CC4IF));
+
+ for (b = TIMER_SR_CC1IF_BIT; b <= TIMER_SR_CC4IF_BIT; b++) {
+ uint32 mask = BIT(b);
+ if (sr & mask) {
+ if (dev->handlers[b])
+ (dev->handlers[b])();
+ sr_clear |= mask;
+ }
+ }
+
+ (dev->regs).gen->SR &= ~sr_clear;
+}
+
+/*
+ * Utilities
+ */
+
+static void disable_channel(timer_dev *dev, uint8 channel) {
+ timer_detach_interrupt(dev, channel);
+ timer_cc_disable(dev, channel);
+}
+
+static void pwm_mode(timer_dev *dev, uint8 channel) {
+ timer_disable_interrupt(dev, channel);
+ timer_oc_set_mode(dev, channel, TIMER_OC_MODE_PWM_1, TIMER_OC_PE);
+ timer_cc_enable(dev, channel);
+}
+
+static void output_compare_mode(timer_dev *dev, uint8 channel) {
+ timer_oc_set_mode(dev, channel, TIMER_OC_MODE_ACTIVE_ON_MATCH, 0);
+ timer_cc_enable(dev, channel);
+}
+
+static void enable_advanced_irq(timer_dev *dev, timer_interrupt_id id);
+static void enable_nonmuxed_irq(timer_dev *dev);
+
+static inline void enable_irq(timer_dev *dev, timer_interrupt_id iid) {
+ if (dev->type == TIMER_ADVANCED) {
+ enable_advanced_irq(dev, iid);
+ } else {
+ enable_nonmuxed_irq(dev);
+ }
+}
+
+static void enable_advanced_irq(timer_dev *dev, timer_interrupt_id id) {
+ uint8 is_timer1 = dev->clk_id == RCC_TIMER1;
+
+ switch (id) {
+ case TIMER_UPDATE_INTERRUPT:
+ nvic_irq_enable(is_timer1 ? NVIC_TIMER1_UP : NVIC_TIMER8_UP);
+ break;
+ case TIMER_CC1_INTERRUPT:
+ case TIMER_CC2_INTERRUPT:
+ case TIMER_CC3_INTERRUPT:
+ case TIMER_CC4_INTERRUPT:
+ nvic_irq_enable(is_timer1 ? NVIC_TIMER1_CC : NVIC_TIMER8_CC);
+ break;
+ case TIMER_COM_INTERRUPT:
+ case TIMER_TRG_INTERRUPT:
+ nvic_irq_enable(is_timer1 ? NVIC_TIMER1_TRG_COM : NVIC_TIMER8_TRG_COM);
+ break;
+ case TIMER_BREAK_INTERRUPT:
+ nvic_irq_enable(is_timer1 ? NVIC_TIMER1_BRK : NVIC_TIMER8_BRK);
+ break;
+ }
+}
+
+static void enable_nonmuxed_irq(timer_dev *dev) {
+ switch (dev->clk_id) {
+ case RCC_TIMER2:
+ nvic_irq_enable(NVIC_TIMER2);
+ break;
+ case RCC_TIMER3:
+ nvic_irq_enable(NVIC_TIMER3);
+ break;
+ case RCC_TIMER4:
+ nvic_irq_enable(NVIC_TIMER4);
+ break;
+#ifdef STM32_HIGH_DENSITY
+ case RCC_TIMER5:
+ nvic_irq_enable(NVIC_TIMER5);
+ break;
+ case RCC_TIMER6:
+ nvic_irq_enable(NVIC_TIMER6);
+ break;
+ case RCC_TIMER7:
+ nvic_irq_enable(NVIC_TIMER7);
+ break;
+#endif
+ default:
+ ASSERT(0);
+ break;
+ }
+}