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>
This commit is contained in:
Michael Kurz 2021-03-25 20:39:53 +01:00 committed by Kevin O'Connor
parent 39188e1cf3
commit 59c4c49893
5 changed files with 179 additions and 0 deletions

View File

@ -27,6 +27,7 @@ choice
config MACH_STM32F103 config MACH_STM32F103
bool "STM32F103" bool "STM32F103"
select MACH_STM32F1 select MACH_STM32F1
select HAVE_GPIO_HARD_PWM
config MACH_STM32F207 config MACH_STM32F207
bool "STM32F207" bool "STM32F207"
select MACH_STM32F2 select MACH_STM32F2

View File

@ -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_SERIAL) += $(serial-src-y) generic/serial_irq.c
src-$(CONFIG_CANSERIAL) += stm32/can.c ../lib/fast-hash/fasthash.c src-$(CONFIG_CANSERIAL) += stm32/can.c ../lib/fast-hash/fasthash.c
src-$(CONFIG_CANSERIAL) += generic/canbus.c src-$(CONFIG_CANSERIAL) += generic/canbus.c
src-$(CONFIG_HAVE_GPIO_HARD_PWM) += stm32/hard_pwm.c
dirs-$(CONFIG_CANSERIAL) += lib/fast-hash dirs-$(CONFIG_CANSERIAL) += lib/fast-hash

View File

@ -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); void gpio_in_reset(struct gpio_in g, int32_t pull_up);
uint8_t gpio_in_read(struct gpio_in g); 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 { struct gpio_adc {
void *adc; void *adc;
uint32_t chan; uint32_t chan;

142
src/stm32/hard_pwm.c Normal file
View File

@ -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;
}

View File

@ -134,6 +134,35 @@ gpio_peripheral(uint32_t gpio, uint32_t mode, int pullup)
stm32f1_alternative_remap(AFIO_MAPR_I2C1_REMAP_Msk, stm32f1_alternative_remap(AFIO_MAPR_I2C1_REMAP_Msk,
AFIO_MAPR_I2C1_REMAP); 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 // Add more as needed
} }