93ee54a2fb
It doesn't need never reset because the status LED is only active when user code isn't. This also fixes PWM never reset on espressif so that deinit will undo it. Fixes #6223
249 lines
8.6 KiB
C
249 lines
8.6 KiB
C
/*
|
|
* This file is part of the MicroPython project, http://micropython.org/
|
|
*
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2020 Lucian Copeland for Adafruit Industries
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
#include <math.h>
|
|
|
|
#include "common-hal/pwmio/PWMOut.h"
|
|
#include "shared-bindings/pwmio/PWMOut.h"
|
|
#include "py/runtime.h"
|
|
#include "components/driver/include/driver/ledc.h"
|
|
|
|
#define INDEX_EMPTY 0xFF
|
|
|
|
STATIC uint32_t reserved_timer_freq[LEDC_TIMER_MAX];
|
|
STATIC bool varfreq_timers[LEDC_TIMER_MAX];
|
|
STATIC uint8_t reserved_channels[LEDC_CHANNEL_MAX] = { [0 ... LEDC_CHANNEL_MAX - 1] = INDEX_EMPTY};
|
|
STATIC bool never_reset_tim[LEDC_TIMER_MAX];
|
|
STATIC bool never_reset_chan[LEDC_CHANNEL_MAX];
|
|
|
|
STATIC uint32_t calculate_duty_cycle(uint32_t frequency) {
|
|
uint32_t duty_bits = 0;
|
|
uint32_t interval = LEDC_APB_CLK_HZ / frequency;
|
|
for (size_t i = 0; i < 32; i++) {
|
|
if (!(interval >> i)) {
|
|
duty_bits = i - 1;
|
|
break;
|
|
}
|
|
}
|
|
if (duty_bits >= LEDC_TIMER_14_BIT) {
|
|
duty_bits = LEDC_TIMER_13_BIT;
|
|
}
|
|
return duty_bits;
|
|
}
|
|
|
|
void pwmout_reset(void) {
|
|
for (size_t i = 0; i < LEDC_CHANNEL_MAX; i++) {
|
|
if (reserved_channels[i] != INDEX_EMPTY && !never_reset_chan[i]) {
|
|
ledc_stop(LEDC_LOW_SPEED_MODE, i, 0);
|
|
reserved_channels[i] = INDEX_EMPTY;
|
|
}
|
|
}
|
|
for (size_t i = 0; i < LEDC_TIMER_MAX; i++) {
|
|
if (reserved_timer_freq[i] && !never_reset_tim[i]) {
|
|
ledc_timer_rst(LEDC_LOW_SPEED_MODE, i);
|
|
reserved_timer_freq[i] = 0;
|
|
varfreq_timers[i] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
pwmout_result_t common_hal_pwmio_pwmout_construct(pwmio_pwmout_obj_t *self,
|
|
const mcu_pin_obj_t *pin,
|
|
uint16_t duty,
|
|
uint32_t frequency,
|
|
bool variable_frequency) {
|
|
|
|
// check the frequency (avoid divide by zero below)
|
|
if (frequency == 0) {
|
|
return PWMOUT_INVALID_FREQUENCY;
|
|
}
|
|
|
|
// Calculate duty cycle
|
|
uint32_t duty_bits = calculate_duty_cycle(frequency);
|
|
if (duty_bits == 0) {
|
|
return PWMOUT_INVALID_FREQUENCY;
|
|
}
|
|
|
|
// Find a viable timer
|
|
size_t timer_index = INDEX_EMPTY;
|
|
size_t channel_index = INDEX_EMPTY;
|
|
for (size_t i = 0; i < LEDC_TIMER_MAX; i++) {
|
|
// accept matching freq timers unless this instance is varfreq or a prior one was
|
|
if ((reserved_timer_freq[i] == frequency) && !variable_frequency && !varfreq_timers[i]) {
|
|
// prioritize matched frequencies so we don't needlessly take slots
|
|
timer_index = i;
|
|
break;
|
|
} else if (reserved_timer_freq[i] == 0) {
|
|
timer_index = i;
|
|
break;
|
|
}
|
|
}
|
|
if (timer_index == INDEX_EMPTY) {
|
|
// Running out of timers isn't pin related on ESP32S2.
|
|
return PWMOUT_ALL_TIMERS_IN_USE;
|
|
}
|
|
|
|
// Find a viable channel
|
|
for (size_t i = 0; i < LEDC_CHANNEL_MAX; i++) {
|
|
if (reserved_channels[i] == INDEX_EMPTY) {
|
|
channel_index = i;
|
|
break;
|
|
}
|
|
}
|
|
if (channel_index == INDEX_EMPTY) {
|
|
return PWMOUT_ALL_CHANNELS_IN_USE;
|
|
}
|
|
|
|
// Run configuration
|
|
self->tim_handle.timer_num = timer_index;
|
|
self->tim_handle.duty_resolution = duty_bits;
|
|
self->tim_handle.freq_hz = frequency;
|
|
self->tim_handle.speed_mode = LEDC_LOW_SPEED_MODE;
|
|
self->tim_handle.clk_cfg = LEDC_AUTO_CLK;
|
|
|
|
if (ledc_timer_config(&(self->tim_handle)) != ESP_OK) {
|
|
return PWMOUT_INITIALIZATION_ERROR;
|
|
}
|
|
|
|
self->chan_handle.channel = channel_index;
|
|
self->chan_handle.duty = duty >> (16 - duty_bits);
|
|
self->chan_handle.gpio_num = pin->number;
|
|
self->chan_handle.speed_mode = LEDC_LOW_SPEED_MODE; // Only LS is allowed on ESP32-S2
|
|
self->chan_handle.hpoint = 0;
|
|
self->chan_handle.timer_sel = timer_index;
|
|
|
|
if (ledc_channel_config(&(self->chan_handle))) {
|
|
return PWMOUT_INITIALIZATION_ERROR;
|
|
}
|
|
|
|
// Make reservations
|
|
reserved_timer_freq[timer_index] = frequency;
|
|
reserved_channels[channel_index] = timer_index;
|
|
|
|
if (variable_frequency) {
|
|
varfreq_timers[timer_index] = true;
|
|
}
|
|
self->variable_frequency = variable_frequency;
|
|
self->pin = pin;
|
|
self->deinited = false;
|
|
self->duty_resolution = duty_bits;
|
|
claim_pin(pin);
|
|
|
|
// Set initial duty
|
|
common_hal_pwmio_pwmout_set_duty_cycle(self, duty);
|
|
|
|
return PWMOUT_OK;
|
|
}
|
|
|
|
void common_hal_pwmio_pwmout_never_reset(pwmio_pwmout_obj_t *self) {
|
|
never_reset_tim[self->tim_handle.timer_num] = true;
|
|
never_reset_chan[self->chan_handle.channel] = true;
|
|
|
|
never_reset_pin_number(self->pin->number);
|
|
}
|
|
|
|
void common_hal_pwmio_pwmout_reset_ok(pwmio_pwmout_obj_t *self) {
|
|
never_reset_tim[self->tim_handle.timer_num] = false;
|
|
// Search if any other channel is using the timer and is never reset.
|
|
// Otherwise, we clear never_reset for the timer as well.
|
|
bool other_never_reset = false;
|
|
for (size_t i = 0; i < LEDC_CHANNEL_MAX; i++) {
|
|
if (i != self->tim_handle.timer_num &&
|
|
reserved_channels[i] == self->tim_handle.timer_num &&
|
|
never_reset_chan[i]) {
|
|
other_never_reset = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!other_never_reset) {
|
|
never_reset_chan[self->chan_handle.channel] = false;
|
|
}
|
|
}
|
|
|
|
bool common_hal_pwmio_pwmout_deinited(pwmio_pwmout_obj_t *self) {
|
|
return self->deinited == true;
|
|
}
|
|
|
|
void common_hal_pwmio_pwmout_deinit(pwmio_pwmout_obj_t *self) {
|
|
if (common_hal_pwmio_pwmout_deinited(self)) {
|
|
return;
|
|
}
|
|
|
|
if (reserved_channels[self->chan_handle.channel] != INDEX_EMPTY) {
|
|
ledc_stop(LEDC_LOW_SPEED_MODE, self->chan_handle.channel, 0);
|
|
}
|
|
reserved_channels[self->chan_handle.channel] = INDEX_EMPTY;
|
|
never_reset_chan[self->chan_handle.channel] = false;
|
|
// Search if any other channel is using the timer
|
|
bool taken = false;
|
|
for (size_t i = 0; i < LEDC_CHANNEL_MAX; i++) {
|
|
if (reserved_channels[i] == self->tim_handle.timer_num) {
|
|
taken = true;
|
|
break;
|
|
}
|
|
}
|
|
// Variable frequency means there's only one channel on the timer
|
|
if (!taken || self->variable_frequency) {
|
|
ledc_timer_rst(LEDC_LOW_SPEED_MODE, self->tim_handle.timer_num);
|
|
reserved_timer_freq[self->tim_handle.timer_num] = 0;
|
|
// if timer isn't varfreq this will be off aleady
|
|
varfreq_timers[self->tim_handle.timer_num] = false;
|
|
never_reset_tim[self->tim_handle.timer_num] = false;
|
|
}
|
|
common_hal_reset_pin(self->pin);
|
|
self->deinited = true;
|
|
}
|
|
|
|
void common_hal_pwmio_pwmout_set_duty_cycle(pwmio_pwmout_obj_t *self, uint16_t duty) {
|
|
ledc_set_duty(LEDC_LOW_SPEED_MODE, self->chan_handle.channel, duty >> (16 - self->duty_resolution));
|
|
ledc_update_duty(LEDC_LOW_SPEED_MODE, self->chan_handle.channel);
|
|
}
|
|
|
|
uint16_t common_hal_pwmio_pwmout_get_duty_cycle(pwmio_pwmout_obj_t *self) {
|
|
return ledc_get_duty(LEDC_LOW_SPEED_MODE, self->chan_handle.channel) << (16 - self->duty_resolution);
|
|
}
|
|
|
|
void common_hal_pwmio_pwmout_set_frequency(pwmio_pwmout_obj_t *self, uint32_t frequency) {
|
|
// Calculate duty cycle
|
|
uint32_t duty_bits = calculate_duty_cycle(frequency);
|
|
if (duty_bits == 0) {
|
|
mp_arg_error_invalid(MP_QSTR_frequency);
|
|
}
|
|
self->duty_resolution = duty_bits;
|
|
ledc_set_freq(LEDC_LOW_SPEED_MODE, self->tim_handle.timer_num, frequency);
|
|
}
|
|
|
|
uint32_t common_hal_pwmio_pwmout_get_frequency(pwmio_pwmout_obj_t *self) {
|
|
return ledc_get_freq(LEDC_LOW_SPEED_MODE, self->tim_handle.timer_num);
|
|
}
|
|
|
|
bool common_hal_pwmio_pwmout_get_variable_frequency(pwmio_pwmout_obj_t *self) {
|
|
return self->variable_frequency;
|
|
}
|
|
|
|
const mcu_pin_obj_t *common_hal_pwmio_pwmout_get_pin(pwmio_pwmout_obj_t *self) {
|
|
return self->pin;
|
|
}
|