aboutsummaryrefslogtreecommitdiffstats
path: root/src/stm32/hard_pwm.c
diff options
context:
space:
mode:
authorMichael Kurz <michi.kurz@gmail.com>2021-03-25 20:39:53 +0100
committerKevin O'Connor <kevin@koconnor.net>2021-03-26 12:04:51 -0400
commit59c4c498938f2c85c735034ed8f7b188725f07c9 (patch)
tree2f164d8a1ed33e75028158058dc3d9b1f3a9d0ca /src/stm32/hard_pwm.c
parent39188e1cf3f84a8bbf039be4b327a032f551d3bd (diff)
downloadkutter-59c4c498938f2c85c735034ed8f7b188725f07c9.tar.gz
kutter-59c4c498938f2c85c735034ed8f7b188725f07c9.tar.xz
kutter-59c4c498938f2c85c735034ed8f7b188725f07c9.zip
stm32: Add hardware PWM support for STM32F1
This adds hardware PWM support for STM32F1 processors. This should also work for STM32 F0/F2/F4 processors, but I don't have one of those to test it. Signed-off-by: Michael Kurz <michi.kurz@gmail.com> Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
Diffstat (limited to 'src/stm32/hard_pwm.c')
-rw-r--r--src/stm32/hard_pwm.c142
1 files changed, 142 insertions, 0 deletions
diff --git a/src/stm32/hard_pwm.c b/src/stm32/hard_pwm.c
new file mode 100644
index 00000000..8367fb05
--- /dev/null
+++ b/src/stm32/hard_pwm.c
@@ -0,0 +1,142 @@
+// Hardware PWM support on stm32
+//
+// Copyright (C) 2021 Michael Kurz <michi.kurz@gmail.com>
+//
+// This file may be distributed under the terms of the GNU GPLv3 license.
+
+#include "board/irq.h" // irq_save
+#include "command.h" // shutdown
+#include "gpio.h" // gpio_pwm_write
+#include "internal.h" // GPIO
+#include "sched.h" // sched_shutdown
+
+#define MAX_PWM 255
+DECL_CONSTANT("PWM_MAX", MAX_PWM);
+
+struct gpio_pwm_info {
+ TIM_TypeDef* timer;
+ uint8_t pin, channel, function;
+};
+
+static const struct gpio_pwm_info pwm_regs[] = {
+ {TIM2, GPIO('A', 0), 1, GPIO_FUNCTION(2)},
+ {TIM2, GPIO('A', 1), 2, GPIO_FUNCTION(2)},
+ {TIM2, GPIO('A', 2), 3, GPIO_FUNCTION(2)},
+ {TIM2, GPIO('A', 3), 4, GPIO_FUNCTION(2)},
+ {TIM2, GPIO('A', 15), 1, GPIO_FUNCTION(1)},
+ {TIM2, GPIO('B', 3), 2, GPIO_FUNCTION(1)},
+ {TIM2, GPIO('B', 10), 3, GPIO_FUNCTION(1)},
+ {TIM2, GPIO('B', 11), 4, GPIO_FUNCTION(1)},
+ {TIM3, GPIO('A', 6), 1, GPIO_FUNCTION(1)},
+ {TIM3, GPIO('A', 7), 2, GPIO_FUNCTION(1)},
+ {TIM3, GPIO('B', 0), 3, GPIO_FUNCTION(1)},
+ {TIM3, GPIO('B', 1), 4, GPIO_FUNCTION(1)},
+ {TIM3, GPIO('C', 6), 1, GPIO_FUNCTION(2)},
+ {TIM3, GPIO('C', 7), 2, GPIO_FUNCTION(2)},
+ {TIM3, GPIO('C', 8), 3, GPIO_FUNCTION(2)},
+ {TIM3, GPIO('C', 9), 4, GPIO_FUNCTION(2)},
+ {TIM4, GPIO('D', 12), 1, GPIO_FUNCTION(2)},
+ {TIM4, GPIO('D', 13), 2, GPIO_FUNCTION(2)},
+ {TIM4, GPIO('D', 14), 3, GPIO_FUNCTION(2)},
+ {TIM4, GPIO('D', 15), 4, GPIO_FUNCTION(2)},
+ {TIM4, GPIO('B', 6), 1, GPIO_FUNCTION(2)},
+ {TIM4, GPIO('B', 7), 2, GPIO_FUNCTION(2)},
+ {TIM4, GPIO('B', 8), 3, GPIO_FUNCTION(2)},
+ {TIM4, GPIO('B', 9), 4, GPIO_FUNCTION(2)}
+};
+
+struct gpio_pwm
+gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val){
+ // Find pin in pwm_regs table
+ const struct gpio_pwm_info* p = pwm_regs;
+ for (;; p++) {
+ if (p >= &pwm_regs[ARRAY_SIZE(pwm_regs)])
+ shutdown("Not a valid PWM pin");
+ if (p->pin == pin)
+ break;
+ }
+
+ // Map cycle_time to pwm clock divisor
+ uint32_t pclk = get_pclock_frequency((uint32_t)p->timer);
+ uint32_t pclock_div = CONFIG_CLOCK_FREQ / pclk;
+ if (pclock_div > 1)
+ pclock_div /= 2; // Timers run at twice the normal pclock frequency
+ uint32_t prescaler = cycle_time / (pclock_div * (MAX_PWM - 1));
+ if (prescaler > 0) {
+ prescaler -= 1;
+ } else if (prescaler > UINT16_MAX) {
+ prescaler = UINT16_MAX;
+ }
+
+ gpio_peripheral(p->pin, p->function, 0);
+
+ // Enable clock
+ if (!is_enabled_pclock((uint32_t) p->timer)) {
+ enable_pclock((uint32_t) p->timer);
+ }
+
+ if (p->timer->CR1 & TIM_CR1_CEN) {
+ if (p->timer->PSC != (uint16_t) prescaler) {
+ shutdown("PWM already programmed at different speed");
+ }
+ } else {
+ p->timer->PSC = (uint16_t) prescaler;
+ p->timer->ARR = MAX_PWM - 1;
+ p->timer->EGR |= TIM_EGR_UG;
+ }
+
+ struct gpio_pwm channel;
+ switch (p->channel) {
+ case 1: {
+ channel.reg = (void*) &p->timer->CCR1;
+ p->timer->CCER &= ~TIM_CCER_CC1E;
+ p->timer->CCMR1 &= ~(TIM_CCMR1_OC1M | TIM_CCMR1_CC1S);
+ p->timer->CCMR1 |= (TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2 |
+ TIM_CCMR1_OC1PE | TIM_CCMR1_OC1FE);
+ gpio_pwm_write(channel, val);
+ p->timer->CCER |= TIM_CCER_CC1E;
+ break;
+ }
+ case 2: {
+ channel.reg = (void*) &p->timer->CCR2;
+ p->timer->CCER &= ~TIM_CCER_CC2E;
+ p->timer->CCMR1 &= ~(TIM_CCMR1_OC2M | TIM_CCMR1_CC2S);
+ p->timer->CCMR1 |= (TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2 |
+ TIM_CCMR1_OC2PE | TIM_CCMR1_OC2FE);
+ gpio_pwm_write(channel, val);
+ p->timer->CCER |= TIM_CCER_CC2E;
+ break;
+ }
+ case 3: {
+ channel.reg = (void*) &p->timer->CCR3;
+ p->timer->CCER &= ~TIM_CCER_CC3E;
+ p->timer->CCMR2 &= ~(TIM_CCMR2_OC3M | TIM_CCMR2_CC3S);
+ p->timer->CCMR2 |= (TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_2 |
+ TIM_CCMR2_OC3PE | TIM_CCMR2_OC3FE);
+ gpio_pwm_write(channel, val);
+ p->timer->CCER |= TIM_CCER_CC3E;
+ break;
+ }
+ case 4: {
+ channel.reg = (void*) &p->timer->CCR4;
+ p->timer->CCER &= ~TIM_CCER_CC4E;
+ p->timer->CCMR2 &= ~(TIM_CCMR2_OC4M | TIM_CCMR2_CC4S);
+ p->timer->CCMR2 |= (TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4M_2 |
+ TIM_CCMR2_OC4PE | TIM_CCMR2_OC4FE);
+ gpio_pwm_write(channel, val);
+ p->timer->CCER |= TIM_CCER_CC4E;
+ break;
+ }
+ default:
+ shutdown("Invalid PWM channel");
+ }
+ // Enable PWM output
+ p->timer->CR1 |= TIM_CR1_CEN;
+
+ return channel;
+}
+
+void
+gpio_pwm_write(struct gpio_pwm g, uint32_t val) {
+ *(volatile uint32_t*) g.reg = val;
+}