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 +// +// 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 }