blob: 1be8b6f67c3461cfb2806d2c773f604f25b0b579 [file] [log] [blame]
// Copyright 2024 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#define PW_LOG_MODULE_NAME "PWM"
#include "device/pico_pwm_gpio.h"
#include <algorithm>
#include <chrono>
#include <limits>
#include "hardware/irq.h"
#include "pico/stdlib.h"
#include "pico/time.h"
#include "pw_log/log.h"
namespace sense {
PicoPwmGpio::PicoPwmGpio(const GpioConfig& config) : gpio_config_(config) {
slice_num_ = pwm_gpio_to_slice_num(gpio_config_.pin);
pwm_config_ = pwm_get_default_config();
}
void PicoPwmGpio::DoEnable() {
gpio_set_function(gpio_config_.pin, GPIO_FUNC_PWM);
pwm_clear_irq(slice_num_);
if (gpio_with_callback == this) {
EnablePwmIrq();
}
pwm_init(slice_num_, &pwm_config_, true);
pwm_set_gpio_level(gpio_config_.pin, level_);
}
void PicoPwmGpio::DoDisable() {
pwm_set_enabled(slice_num_, false);
DisablePwmIrq();
gpio_deinit(gpio_config_.pin);
}
void PicoPwmGpio::DoSetLevel(uint16_t level) {
pwm_clear_irq(slice_num_);
level_ = gpio_config_.polarity == pw::digital_io::Polarity::kActiveLow
? std::numeric_limits<uint16_t>::max() - level
: level;
pwm_set_gpio_level(gpio_config_.pin, level_);
}
void PicoPwmGpio::DoSetCallback(uint16_t per_interval,
pw::chrono::SystemClock::duration interval) {
if (gpio_with_callback != nullptr) {
PW_LOG_INFO("Replacing existing callback for slice %hu with slice %hu",
gpio_with_callback->slice_num_,
slice_num_);
gpio_with_callback->DisablePwmIrq();
if (gpio_with_callback != this) {
gpio_with_callback->ClearCallbackFunction();
}
}
gpio_with_callback = this;
constexpr float kClkDivMax = 256.f;
auto interval_ms =
std::chrono::duration_cast<std::chrono::milliseconds>(interval).count();
float freq = 1000.f / interval_ms;
float clkdiv = 65536.f / (60.f * freq * per_interval);
clkdiv = std::min(clkdiv, kClkDivMax);
PW_LOG_INFO("Pulsing at frequency of %f times per second", freq);
if (clkdiv < 1.f) {
uint16_t wrap = std::numeric_limits<uint16_t>::max();
wrap = static_cast<uint16_t>(clkdiv * wrap);
pwm_config_set_wrap(&pwm_config_, wrap);
} else {
pwm_config_set_clkdiv(&pwm_config_, clkdiv);
}
EnablePwmIrq();
}
void PicoPwmGpio::DoClearCallback() {
DisablePwmIrq();
gpio_with_callback = nullptr;
}
void PicoPwmGpio::EnablePwmIrq() const {
irq_set_exclusive_handler(PWM_IRQ_WRAP, IrqHandler);
pwm_clear_irq(slice_num_);
irq_set_enabled(PWM_IRQ_WRAP, true);
pwm_set_irq_enabled(slice_num_, true);
}
void PicoPwmGpio::DisablePwmIrq() const {
irq_set_enabled(PWM_IRQ_WRAP, false);
pwm_set_irq_enabled(slice_num_, false);
pwm_clear_irq(slice_num_);
}
// The PWM block triggers callbacks by raising "wrap" interrupts at a configured
// interval. At most one exclusive IRQ handler may be installed at any one time,
// so a pointer to the active PicoPwmGpio is stored as a singleton.
void PicoPwmGpio::IrqHandler() {
if (gpio_with_callback != nullptr) {
gpio_with_callback->InvokeCallback();
}
}
PicoPwmGpio* PicoPwmGpio::gpio_with_callback = nullptr;
} // namespace sense