stmhal: Add PWM capability for LED(3) and LED(4) on pyboards.

USB CDC no longer needs TIM3 (which was originally used for LED(4) PWM)
and so TIM3 has been freed for general purpose use by the user.  Hence
LED(4) lost its PWM capabilities.

This patch reinstates the PWM capabilities using a semi-generic piece
of code which allows to configure a timer and PWM channel to use for any
LED.  But the PWM capability is only configured if the LED is set to an
intensity between 1 and 254 (ie only when needed).  In that case the
relevant timer is configured for PWM.  It's up to the user to make sure
the timers are not used if PWM is active.

This patch also makes sure that PWM LEDs are turned off using standard
GPIO when calling led.off() or led.intensity(0), instead of just setting
the PWM counter to zero.
This commit is contained in:
Damien George 2016-01-29 22:44:43 +00:00
parent ea89b80ff4
commit a8a4b01af6
7 changed files with 161 additions and 15 deletions

View File

@ -70,7 +70,8 @@
#define MICROPY_HW_LED2 (pin_A14) // green
#define MICROPY_HW_LED3 (pin_A15) // yellow
#define MICROPY_HW_LED4 (pin_B4) // blue
#define MICROPY_HW_LED4_PWM (0) // TIM3 is now a user timer
#define MICROPY_HW_LED3_PWM { TIM2, 2, GPIO_AF1_TIM2 }
#define MICROPY_HW_LED4_PWM { TIM3, 3, GPIO_AF2_TIM3 }
#define MICROPY_HW_LED_OTYPE (GPIO_MODE_OUTPUT_PP)
#define MICROPY_HW_LED_ON(pin) (pin->gpio->BSRRL = pin->pin_mask)
#define MICROPY_HW_LED_OFF(pin) (pin->gpio->BSRRH = pin->pin_mask)

View File

@ -81,7 +81,8 @@
#define MICROPY_HW_LED2 (pin_A14) // green
#define MICROPY_HW_LED3 (pin_A15) // yellow
#define MICROPY_HW_LED4 (pin_B4) // blue
#define MICROPY_HW_LED4_PWM (1)
#define MICROPY_HW_LED3_PWM { TIM2, 2, GPIO_AF1_TIM2 }
#define MICROPY_HW_LED4_PWM { TIM3, 3, GPIO_AF2_TIM3 }
#define MICROPY_HW_LED_OTYPE (GPIO_MODE_OUTPUT_PP)
#define MICROPY_HW_LED_ON(pin) (pin->gpio->BSRRL = pin->pin_mask)
#define MICROPY_HW_LED_OFF(pin) (pin->gpio->BSRRH = pin->pin_mask)

View File

@ -81,7 +81,8 @@
#define MICROPY_HW_LED2 (pin_A14) // green
#define MICROPY_HW_LED3 (pin_A15) // yellow
#define MICROPY_HW_LED4 (pin_B4) // blue
#define MICROPY_HW_LED4_PWM (1)
#define MICROPY_HW_LED3_PWM { TIM2, 2, GPIO_AF1_TIM2 }
#define MICROPY_HW_LED4_PWM { TIM3, 3, GPIO_AF2_TIM3 }
#define MICROPY_HW_LED_OTYPE (GPIO_MODE_OUTPUT_PP)
#define MICROPY_HW_LED_ON(pin) (pin->gpio->BSRRL = pin->pin_mask)
#define MICROPY_HW_LED_OFF(pin) (pin->gpio->BSRRH = pin->pin_mask)

View File

@ -78,7 +78,8 @@
#define MICROPY_HW_LED2 (pin_A14) // green
#define MICROPY_HW_LED3 (pin_A15) // yellow
#define MICROPY_HW_LED4 (pin_B4) // blue
#define MICROPY_HW_LED4_PWM (1)
#define MICROPY_HW_LED3_PWM { TIM2, 2, GPIO_AF1_TIM2 }
#define MICROPY_HW_LED4_PWM { TIM3, 3, GPIO_AF2_TIM3 }
#define MICROPY_HW_LED_OTYPE (GPIO_MODE_OUTPUT_PP)
#define MICROPY_HW_LED_ON(pin) (pin->gpio->BSRRL = pin->pin_mask)
#define MICROPY_HW_LED_OFF(pin) (pin->gpio->BSRRH = pin->pin_mask)

View File

@ -1,9 +1,9 @@
/*
* This file is part of the Micro Python project, http://micropython.org/
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 Damien P. George
* Copyright (c) 2013-2016 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -36,11 +36,6 @@
#if defined(MICROPY_HW_LED1)
// default is to not PWM LED4
#if !defined(MICROPY_HW_LED4_PWM)
#define MICROPY_HW_LED4_PWM (0)
#endif
/// \moduleref pyb
/// \class LED - LED object
///
@ -85,10 +80,120 @@ void led_init(void) {
}
}
#if defined(MICROPY_HW_LED1_PWM) \
|| defined(MICROPY_HW_LED2_PWM) \
|| defined(MICROPY_HW_LED3_PWM) \
|| defined(MICROPY_HW_LED4_PWM)
// The following is semi-generic code to control LEDs using PWM.
// It currently supports TIM2 and TIM3, channel 1 only.
// Configure by defining the relevant MICROPY_HW_LEDx_PWM macros in mpconfigboard.h.
// If they are not defined then PWM will not be available for that LED.
#define LED_PWM_ENABLED (1)
#ifndef MICROPY_HW_LED1_PWM
#define MICROPY_HW_LED1_PWM { NULL, 0, 0 }
#endif
#ifndef MICROPY_HW_LED2_PWM
#define MICROPY_HW_LED2_PWM { NULL, 0, 0 }
#endif
#ifndef MICROPY_HW_LED3_PWM
#define MICROPY_HW_LED3_PWM { NULL, 0, 0 }
#endif
#ifndef MICROPY_HW_LED4_PWM
#define MICROPY_HW_LED4_PWM { NULL, 0, 0 }
#endif
#define LED_PWM_TIM_PERIOD (10000) // TIM runs at 1MHz and fires every 10ms
typedef struct _led_pwm_config_t {
TIM_TypeDef *tim;
uint8_t tim_id;
uint8_t alt_func;
} led_pwm_config_t;
STATIC const led_pwm_config_t led_pwm_config[] = {
MICROPY_HW_LED1_PWM,
MICROPY_HW_LED2_PWM,
MICROPY_HW_LED3_PWM,
MICROPY_HW_LED4_PWM,
};
STATIC uint8_t led_pwm_state = 0;
static inline bool led_pwm_is_enabled(int led) {
return (led_pwm_state & (1 << led)) != 0;
}
// this function has a large stack so it should not be inlined
STATIC void led_pwm_init(int led) __attribute__((noinline));
STATIC void led_pwm_init(int led) {
const pin_obj_t *led_pin = pyb_led_obj[led - 1].led_pin;
const led_pwm_config_t *pwm_cfg = &led_pwm_config[led - 1];
// GPIO configuration
GPIO_InitTypeDef gpio_init;
gpio_init.Pin = led_pin->pin_mask;
gpio_init.Mode = GPIO_MODE_AF_PP;
gpio_init.Speed = GPIO_SPEED_FAST;
gpio_init.Pull = GPIO_NOPULL;
gpio_init.Alternate = pwm_cfg->alt_func;
HAL_GPIO_Init(led_pin->gpio, &gpio_init);
// TIM configuration
switch (pwm_cfg->tim_id) {
case 2: __TIM2_CLK_ENABLE(); break;
case 3: __TIM3_CLK_ENABLE(); break;
default: assert(0);
}
TIM_HandleTypeDef tim;
tim.Instance = pwm_cfg->tim;
tim.Init.Period = LED_PWM_TIM_PERIOD - 1;
tim.Init.Prescaler = timer_get_source_freq(pwm_cfg->tim_id) / 1000000 - 1; // TIM runs at 1MHz
tim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
tim.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&tim);
// PWM configuration (only channel 1 supported at the moment)
TIM_OC_InitTypeDef oc_init;
oc_init.OCMode = TIM_OCMODE_PWM1;
oc_init.Pulse = 0; // off
oc_init.OCPolarity = TIM_OCPOLARITY_HIGH;
oc_init.OCFastMode = TIM_OCFAST_DISABLE;
/* needed only for TIM1 and TIM8
oc_init.OCNPolarity = TIM_OCNPOLARITY_HIGH;
oc_init.OCIdleState = TIM_OCIDLESTATE_SET;
oc_init.OCNIdleState = TIM_OCNIDLESTATE_SET;
*/
HAL_TIM_PWM_ConfigChannel(&tim, &oc_init, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&tim, TIM_CHANNEL_1);
// indicate that this LED is using PWM
led_pwm_state |= 1 << led;
}
STATIC void led_pwm_deinit(int led) {
// make the LED's pin a standard GPIO output pin
const pin_obj_t *led_pin = pyb_led_obj[led - 1].led_pin;
GPIO_TypeDef *g = led_pin->gpio;
uint32_t pin = led_pin->pin;
static const int mode = 1; // output
static const int alt = 0; // no alt func
g->MODER = (g->MODER & ~(3 << (2 * pin))) | (mode << (2 * pin));
g->AFR[pin >> 3] = (g->AFR[pin >> 3] & ~(15 << (4 * (pin & 7)))) | (alt << (4 * (pin & 7)));
led_pwm_state &= ~(1 << led);
}
#else
#define LED_PWM_ENABLED (0)
#endif
void led_state(pyb_led_t led, int state) {
if (led < 1 || led > NUM_LEDS) {
return;
}
const pin_obj_t *led_pin = pyb_led_obj[led - 1].led_pin;
//printf("led_state(%d,%d)\n", led, state);
if (state == 0) {
@ -98,6 +203,12 @@ void led_state(pyb_led_t led, int state) {
// turn LED on
MICROPY_HW_LED_ON(led_pin);
}
#if LED_PWM_ENABLED
if (led_pwm_is_enabled(led)) {
led_pwm_deinit(led);
}
#endif
}
void led_toggle(pyb_led_t led) {
@ -105,6 +216,14 @@ void led_toggle(pyb_led_t led) {
return;
}
#if LED_PWM_ENABLED
if (led_pwm_is_enabled(led)) {
// if PWM is enabled then LED has non-zero intensity, so turn it off
led_state(led, 0);
return;
}
#endif
// toggle the output data register to toggle the LED state
const pin_obj_t *led_pin = pyb_led_obj[led - 1].led_pin;
led_pin->gpio->ODR ^= led_pin->pin_mask;
@ -115,6 +234,17 @@ int led_get_intensity(pyb_led_t led) {
return 0;
}
#if LED_PWM_ENABLED
if (led_pwm_is_enabled(led)) {
TIM_TypeDef *tim = led_pwm_config[led - 1].tim;
mp_uint_t i = (tim->CCR1 * 255 + LED_PWM_TIM_PERIOD - 2) / (LED_PWM_TIM_PERIOD - 1);
if (i > 255) {
i = 255;
}
return i;
}
#endif
const pin_obj_t *led_pin = pyb_led_obj[led - 1].led_pin;
GPIO_TypeDef *gpio = led_pin->gpio;
@ -129,6 +259,20 @@ int led_get_intensity(pyb_led_t led) {
}
void led_set_intensity(pyb_led_t led, mp_int_t intensity) {
#if LED_PWM_ENABLED
if (intensity > 0 && intensity < 255) {
TIM_TypeDef *tim = led_pwm_config[led - 1].tim;
if (tim != NULL) {
// set intensity using PWM pulse width
if (!led_pwm_is_enabled(led)) {
led_pwm_init(led);
}
tim->CCR1 = intensity * (LED_PWM_TIM_PERIOD - 1) / 255;
return;
}
}
#endif
// intensity not supported for this LED; just turn it on/off
led_state(led, intensity > 0);
}

View File

@ -147,7 +147,6 @@ TIM_HandleTypeDef TIM6_Handle;
#define PYB_TIMER_OBJ_ALL_NUM MP_ARRAY_SIZE(MP_STATE_PORT(pyb_timer_obj_all))
STATIC uint32_t timer_get_source_freq(uint32_t tim_id);
STATIC mp_obj_t pyb_timer_deinit(mp_obj_t self_in);
STATIC mp_obj_t pyb_timer_callback(mp_obj_t self_in, mp_obj_t callback);
STATIC mp_obj_t pyb_timer_channel_callback(mp_obj_t self_in, mp_obj_t callback);
@ -229,7 +228,7 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
// If the APB prescaler is 1, then the timer clock is equal to its respective
// APB clock. Otherwise (APB prescaler > 1) the timer clock is twice its
// respective APB clock. See DM00031020 Rev 4, page 115.
STATIC uint32_t timer_get_source_freq(uint32_t tim_id) {
uint32_t timer_get_source_freq(uint32_t tim_id) {
uint32_t source;
if (tim_id == 1 || (8 <= tim_id && tim_id <= 11)) {
// TIM{1,8,9,10,11} are on APB2

View File

@ -31,9 +31,8 @@ extern const mp_obj_type_t pyb_timer_type;
void timer_init0(void);
void timer_tim5_init(void);
TIM_HandleTypeDef *timer_tim6_init(uint freq);
void timer_deinit(void);
uint32_t timer_get_source_freq(uint32_t tim_id);
void timer_irq_handler(uint tim_id);
TIM_HandleTypeDef *pyb_timer_get_handle(mp_obj_t timer);