aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/stm32/Kconfig1
-rw-r--r--src/stm32/Makefile1
-rw-r--r--src/stm32/gpio.h6
-rw-r--r--src/stm32/hard_pwm.c142
-rw-r--r--src/stm32/stm32f1.c29
5 files changed, 179 insertions, 0 deletions
diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig
index f2b16d4d..386083f1 100644
--- a/src/stm32/Kconfig
+++ b/src/stm32/Kconfig
@@ -27,6 +27,7 @@ choice
config MACH_STM32F103
bool "STM32F103"
select MACH_STM32F1
+ select HAVE_GPIO_HARD_PWM
config MACH_STM32F207
bool "STM32F207"
select MACH_STM32F2
diff --git a/src/stm32/Makefile b/src/stm32/Makefile
index 1587d937..31fefb6c 100644
--- a/src/stm32/Makefile
+++ b/src/stm32/Makefile
@@ -48,6 +48,7 @@ serial-src-$(CONFIG_MACH_STM32F0) := stm32/stm32f0_serial.c
src-$(CONFIG_SERIAL) += $(serial-src-y) generic/serial_irq.c
src-$(CONFIG_CANSERIAL) += stm32/can.c ../lib/fast-hash/fasthash.c
src-$(CONFIG_CANSERIAL) += generic/canbus.c
+src-$(CONFIG_HAVE_GPIO_HARD_PWM) += stm32/hard_pwm.c
dirs-$(CONFIG_CANSERIAL) += lib/fast-hash
diff --git a/src/stm32/gpio.h b/src/stm32/gpio.h
index 13ab74bd..281e3a8d 100644
--- a/src/stm32/gpio.h
+++ b/src/stm32/gpio.h
@@ -21,6 +21,12 @@ struct gpio_in gpio_in_setup(uint32_t pin, int32_t pull_up);
void gpio_in_reset(struct gpio_in g, int32_t pull_up);
uint8_t gpio_in_read(struct gpio_in g);
+struct gpio_pwm {
+ void *reg;
+};
+struct gpio_pwm gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val);
+void gpio_pwm_write(struct gpio_pwm g, uint32_t val);
+
struct gpio_adc {
void *adc;
uint32_t chan;
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;
+}
diff --git a/src/stm32/stm32f1.c b/src/stm32/stm32f1.c
index 6480b678..ae8fcd67 100644
--- a/src/stm32/stm32f1.c
+++ b/src/stm32/stm32f1.c
@@ -134,6 +134,35 @@ gpio_peripheral(uint32_t gpio, uint32_t mode, int pullup)
stm32f1_alternative_remap(AFIO_MAPR_I2C1_REMAP_Msk,
AFIO_MAPR_I2C1_REMAP);
}
+ } else if ((gpio == GPIO('A', 15)
+ || gpio == GPIO('B', 3)) && (func == 1)) {
+ // TIM2 CH1/2
+ stm32f1_alternative_remap(AFIO_MAPR_TIM2_REMAP_PARTIALREMAP1_Msk,
+ AFIO_MAPR_TIM2_REMAP_PARTIALREMAP1);
+ } else if ((gpio == GPIO('B', 10)
+ || gpio == GPIO('B', 11)) && (func == 1)) {
+ // TIM2 CH3/4
+ stm32f1_alternative_remap(AFIO_MAPR_TIM2_REMAP_PARTIALREMAP2_Msk,
+ AFIO_MAPR_TIM2_REMAP_PARTIALREMAP2);
+ } else if ((gpio == GPIO('B', 4)
+ || gpio == GPIO('B', 5)) && (func == 2)) {
+ // TIM3 partial remap
+ stm32f1_alternative_remap(AFIO_MAPR_TIM3_REMAP_PARTIALREMAP_Msk,
+ AFIO_MAPR_TIM3_REMAP_PARTIALREMAP);
+ } else if ((gpio == GPIO('C', 6)
+ || gpio == GPIO('C', 7)
+ || gpio == GPIO('C', 8)
+ || gpio == GPIO('C', 9)) && (func == 2)) {
+ // TIM3 full remap
+ stm32f1_alternative_remap(AFIO_MAPR_TIM3_REMAP_FULLREMAP_Msk,
+ AFIO_MAPR_TIM3_REMAP_FULLREMAP);
+ } else if ((gpio == GPIO('D', 12)
+ || gpio == GPIO('D', 13)
+ || gpio == GPIO('D', 14)
+ || gpio == GPIO('D', 15)) && (func == 2)) {
+ // TIM4
+ stm32f1_alternative_remap(AFIO_MAPR_TIM4_REMAP_Msk,
+ AFIO_MAPR_TIM4_REMAP);
}
// Add more as needed
}