blob: 46db1b2d6a5389cc6cbce1892fad59a857a39eec [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.
#include "device/pico_pwm_gpio.h"
#include <algorithm>
#include <array>
#include <chrono>
#include <limits>
#include "hardware/irq.h"
#include "pico/stdlib.h"
#include "pico/time.h"
#include "pw_assert/check.h"
#include "pw_log/log.h"
namespace sense {
// 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 callback is stored as a singleton. The IRQ handler must
// not take any parameters or return any value, so an `IrqHandler` "trampoline"
// function is provided.
static const PicoPwmGpio::Callback* current_callback = nullptr;
static void IrqHandler() {
if (current_callback != nullptr) {
(*current_callback)();
}
}
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 (current_callback != nullptr) {
pwm_set_irq_enabled(slice_num_, true);
irq_set_exclusive_handler(PWM_IRQ_WRAP, IrqHandler);
irq_set_enabled(PWM_IRQ_WRAP, true);
}
pwm_init(slice_num_, &pwm_config_, true);
pwm_set_gpio_level(gpio_config_.pin, level_);
}
void PicoPwmGpio::DoDisable() {
pwm_set_enabled(slice_num_, false);
irq_set_enabled(PWM_IRQ_WRAP, false);
pwm_set_irq_enabled(slice_num_, false);
pwm_clear_irq(slice_num_);
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(const Callback& callback,
uint16_t per_interval,
pw::chrono::SystemClock::duration interval) {
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);
}
PW_CHECK_PTR_EQ(current_callback,
nullptr,
"Unable to set PWM_IRQ_WRAP handler: already set. "
"Call `ClearCallback` before setting a new handler.");
current_callback = &callback;
}
void PicoPwmGpio::DoClearCallback() {
irq_set_enabled(PWM_IRQ_WRAP, false);
pwm_set_irq_enabled(slice_num_, false);
pwm_clear_irq(slice_num_);
current_callback = nullptr;
}
} // namespace sense