Skip to content

Commit

Permalink
Implement HardwarePWM class for Rp2040 and update Basic PWM sample (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
mikee47 authored Nov 5, 2024
1 parent 9054e94 commit 6d94935
Show file tree
Hide file tree
Showing 10 changed files with 259 additions and 298 deletions.
18 changes: 4 additions & 14 deletions Sming/Arch/Esp32/Core/HardwarePWM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ uint32_t maxDuty(ledc_timer_bit_t bits)

} //namespace

HardwarePWM::HardwarePWM(uint8_t* pins, uint8_t no_of_pins) : channel_count(no_of_pins)
HardwarePWM::HardwarePWM(const uint8_t* pins, uint8_t no_of_pins) : channel_count(no_of_pins)
{
assert(no_of_pins > 0 && no_of_pins <= SOC_LEDC_CHANNEL_NUM);
no_of_pins = std::min(uint8_t(SOC_LEDC_CHANNEL_NUM), no_of_pins);
Expand Down Expand Up @@ -206,17 +206,7 @@ HardwarePWM::~HardwarePWM()
}
}

uint8_t HardwarePWM::getChannel(uint8_t pin)
{
for(uint8_t i = 0; i < channel_count; i++) {
if(channels[i] == pin) {
return i;
}
}
return -1;
}

uint32_t HardwarePWM::getDutyChan(uint8_t chan)
uint32_t HardwarePWM::getDutyChan(uint8_t chan) const
{
// esp32 defines the frequency / period per timer
return (chan == PWM_BAD_CHANNEL) ? 0 : ledc_get_duty(pinToGroup(chan), pinToChannel(chan));
Expand Down Expand Up @@ -245,7 +235,7 @@ bool HardwarePWM::setDutyChan(uint8_t chan, uint32_t duty, bool update)
return false;
}

uint32_t HardwarePWM::getPeriod()
uint32_t HardwarePWM::getPeriod() const
{
// Sming does not know how to handle different frequencies for channels: this will require an extended interface.
// For now, just report the period for group 0 channel 0.
Expand All @@ -268,7 +258,7 @@ void HardwarePWM::update()
// ledc_update_duty();
}

uint32_t HardwarePWM::getFrequency(uint8_t pin)
uint32_t HardwarePWM::getFrequency(uint8_t pin) const
{
return ledc_get_freq(pinToGroup(pin), pinToTimer(pin));
}
20 changes: 4 additions & 16 deletions Sming/Arch/Esp8266/Core/HardwarePWM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ static const uint8_t gpioPinFunc[]{
FUNC_GPIO15, //
};

HardwarePWM::HardwarePWM(uint8_t* pins, uint8_t noOfPins) : channel_count(noOfPins)
HardwarePWM::HardwarePWM(const uint8_t* pins, uint8_t noOfPins) : channel_count(noOfPins)
{
if(noOfPins == 0) {
return;
Expand Down Expand Up @@ -85,19 +85,7 @@ HardwarePWM::~HardwarePWM()
// There is no function in the SDK to stop PWM output, yet.
}

uint8_t HardwarePWM::getChannel(uint8_t pin)
{
for(uint8_t i = 0; i < channel_count; i++) {
if(channels[i] == pin) {
return i;
}
}

debug_d("getChannel: can't find pin %d", pin);
return PWM_BAD_CHANNEL;
}

uint32_t HardwarePWM::getDutyChan(uint8_t chan)
uint32_t HardwarePWM::getDutyChan(uint8_t chan) const
{
return (chan == PWM_BAD_CHANNEL) ? 0 : pwm_get_duty(chan);
}
Expand All @@ -120,7 +108,7 @@ bool HardwarePWM::setDutyChan(uint8_t chan, uint32_t duty, bool update)
return false;
}

uint32_t HardwarePWM::getPeriod()
uint32_t HardwarePWM::getPeriod() const
{
return pwm_get_period();
}
Expand All @@ -137,7 +125,7 @@ void HardwarePWM::update()
pwm_start();
}

uint32_t HardwarePWM::getFrequency(uint8_t pin)
uint32_t HardwarePWM::getFrequency(uint8_t pin) const
{
(void)pin;
auto period = pwm_get_period();
Expand Down
20 changes: 10 additions & 10 deletions Sming/Arch/Host/Core/HardwarePWM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,36 +25,36 @@

#include <HardwarePWM.h>

HardwarePWM::HardwarePWM(uint8_t* pins, uint8_t no_of_pins) : channel_count(no_of_pins)
HardwarePWM::HardwarePWM(const uint8_t*, uint8_t no_of_pins) : channel_count(no_of_pins)
{
}

HardwarePWM::~HardwarePWM() = default;

uint8_t HardwarePWM::getChannel(uint8_t pin)
{
return PWM_BAD_CHANNEL;
}

uint32_t HardwarePWM::getDutyChan(uint8_t chan)
uint32_t HardwarePWM::getDutyChan(uint8_t) const
{
return 0;
}

bool HardwarePWM::setDutyChan(uint8_t chan, uint32_t duty, bool update)
bool HardwarePWM::setDutyChan(uint8_t, uint32_t, bool)
{
return false;
}

uint32_t HardwarePWM::getPeriod()
uint32_t HardwarePWM::getPeriod() const
{
return 0;
}

void HardwarePWM::setPeriod(uint32_t period)
void HardwarePWM::setPeriod(uint32_t)
{
}

void HardwarePWM::update()
{
}

uint32_t HardwarePWM::getFrequency(uint8_t) const
{
return 0;
}
86 changes: 3 additions & 83 deletions Sming/Arch/Rp2040/Components/driver/include/driver/pwm.h
Original file line number Diff line number Diff line change
@@ -1,88 +1,8 @@
#pragma once

#if defined(__cplusplus)
extern "C" {
#endif

// #include <pwm.h>

#define PWM_CHANNEL_NUM_MAX 16

/**
* @defgroup pwm_driver PWM driver
* @ingroup drivers
* @{
*/

/**
* @fn void pwm_init(uint32 period, uint32 *duty,uint32 pwm_channel_num,uint32 (*pin_info_list)[3])
* @brief Initialize PWM function, including GPIO selection, period and duty cycle
* @param period PWM period
* @param duty duty cycle of each output
* @param pwm_channel_num PWM channel number
* @param pin_info_list Array containing an entry for each channel giving
* @note This API can be called only once.
*
* Example:
*
* uint32 io_info[][3] = {
* {PWM_0_OUT_IO_MUX, PWM_0_OUT_IO_FUNC, PWM_0_OUT_IO_NUM},
* {PWM_1_OUT_IO_MUX, PWM_1_OUT_IO_FUNC, PWM_1_OUT_IO_NUM},
* {PWM_2_OUT_IO_MUX, PWM_2_OUT_IO_FUNC, PWM_2_OUT_IO_NUM}
* };
*
* pwm_init(light_param.pwm_period, light_param.pwm_duty, 3, io_info);
*
*/

/**
* @fn void pwm_start(void)
* @brief Starts PWM
* @brief Maximum number of active PWM channels.
*
* This function needs to be called after PWM configuration is changed.
* The Pico has 8 PWM 'slices', each of which can drive two outputs.
*/

/**
* @fn void pwm_set_duty(uint32 duty, uint8 channel)
* @brief Sets duty cycle of a PWM output
* @param duty The time that high-level single will last, duty cycle will be (duty*45)/(period*1000)
* @param channel PWM channel, which depends on how many PWM channels are used
*
* Set the time that high-level signal will last.
* The range of duty depends on PWM period. Its maximum value of which can be Period * 1000 / 45.
*
* For example, for 1-KHz PWM, the duty range is 0 ~ 22222.
*/

/**
* @fn uint32 pwm_get_duty(uint8 channel)
* @brief Get duty cycle of PWM output
* @param channel PWM channel, which depends on how many PWM channels are used
* @retval uint32 Duty cycle of PWM output
*
* Duty cycle will be (duty*45) / (period*1000).
*/

/**
* @fn void pwm_set_period(uint32 period)
* @brief Set PWM period
* @param period PWM period in us. For example, 1-KHz PWM period = 1000us.
*/

/**
* @fn uint32 pwm_get_period(void)
* @brief Get PWM period
* @retval uint32 Return PWM period in us.
*/

/**
* @fn uint32 get_pwm_version(void)
* @brief Get version information of PWM
* @retval uint32 PWM version
*/

/** @} */

#if defined(__cplusplus)
}
#endif
#define PWM_CHANNEL_NUM_MAX 16
135 changes: 135 additions & 0 deletions Sming/Arch/Rp2040/Core/HardwarePWM.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* Rp2040/Core/HardwarePWM.cpp
*
*/

#include <HardwarePWM.h>
#include <hardware/pwm.h>
#include <hardware/gpio.h>
#include <hardware/clocks.h>
#include <muldiv.h>
#include <algorithm>
#include <debug_progmem.h>

/*
* Note on use of divisor
* ----------------------
*
* Divisor is 8:4 fractional value, so 1 <= int <= 255, 0 <= frac <= 15
* Simplest way to use full range is to factor calculations by 16.
*
* Using default CSR_PH_CORRECT=0:
*
* F_PWM = 16 * F_SYS / (TOP + 1) / DIV
*
*/

#define PWM_FREQ_DEFAULT 1000

HardwarePWM::HardwarePWM(const uint8_t* pins, uint8_t noOfPins) : channel_count(noOfPins)
{
assert(noOfPins > 0 && noOfPins <= PWM_CHANNEL_NUM_MAX);
noOfPins = std::min(uint8_t(PWM_CHANNEL_NUM_MAX), noOfPins);
std::copy_n(pins, noOfPins, channels);
setPeriod(1e6 / PWM_FREQ_DEFAULT);

for(unsigned i = 0; i < noOfPins; ++i) {
auto pin = channels[i];
gpio_set_function(pin, GPIO_FUNC_PWM);
gpio_set_dir(pin, GPIO_OUT);
}
}

HardwarePWM::~HardwarePWM()
{
for(unsigned i = 0; i < channel_count; ++i) {
auto slice_num = pwm_gpio_to_slice_num(channels[i]);
pwm_set_enabled(slice_num, false);
}
}

uint32_t HardwarePWM::getDutyChan(uint8_t chan) const
{
if(chan >= channel_count) {
return 0;
}
auto pin = channels[chan];
auto slice_num = pwm_gpio_to_slice_num(pin);
auto value = pwm_hw->slice[slice_num].cc;
value >>= pwm_gpio_to_channel(pin) ? PWM_CH0_CC_B_LSB : PWM_CH0_CC_A_LSB;
return value & 0xffff;
}

bool HardwarePWM::setDutyChan(uint8_t chan, uint32_t duty, bool)
{
if(chan >= channel_count) {
return false;
}
auto pin = channels[chan];
duty = std::min(duty, maxduty);
pwm_set_gpio_level(pin, duty);
return true;
}

uint32_t HardwarePWM::getPeriod() const
{
// All channels configured with same clock
auto slice_num = pwm_gpio_to_slice_num(channels[0]);
uint32_t top = pwm_hw->slice[slice_num].top;
uint32_t div = pwm_hw->slice[slice_num].div;
return muldiv(62500ULL, (top + 1) * div, clock_get_hz(clk_sys));
}

void HardwarePWM::setPeriod(uint32_t period)
{
const uint32_t topMax{0xffff};
const uint32_t divMin{0x10}; // 1.0
const uint32_t divMax{0xfff}; // INT + FRAC
auto sysFreq = clock_get_hz(clk_sys);
// Calculate divisor assuming maximum value for TOP: ensure value is rounded UP
uint32_t div = ((uint64_t(period) * sysFreq / 62500) + topMax) / (topMax + 1);
uint32_t top;
if(div > divMax) {
// Period too big, set to maximum
top = topMax;
div = divMax;
} else {
if(div < divMin) {
// Period is very small, set div to minimum
div = divMin;
}
top = (uint64_t(period) * sysFreq / 62500 / div) - 1;
}

debug_d("[PWM] %s(%u): div %u, top %u", __FUNCTION__, period, div, top);

pwm_config cfg = pwm_get_default_config();
cfg.div = div;
cfg.top = top;

for(unsigned i = 0; i < channel_count; ++i) {
auto pin = channels[i];
auto slice_num = pwm_gpio_to_slice_num(pin);
pwm_init(slice_num, &cfg, true);
}

maxduty = top;
}

void HardwarePWM::update()
{
// Not implemented
}

uint32_t HardwarePWM::getFrequency(uint8_t pin) const
{
auto slice_num = pwm_gpio_to_slice_num(pin);
auto top = pwm_hw->slice[slice_num].top;
auto div = pwm_hw->slice[slice_num].div;
return 16UL * clock_get_hz(clk_sys) / (div * (top + 1));
}
Loading

0 comments on commit 6d94935

Please sign in to comment.