-
-
Notifications
You must be signed in to change notification settings - Fork 345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement HardwarePWM class for Rp2040 and update Basic PWM sample #2908
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
6fb1ea3
Fix Basic_HwPWM sample for esp32
mikee47 7ebcfb4
Add skeleton for Rp2040 HardwarePWM class.
mikee47 7a55f01
Looking at Rp2040 implementation
mikee47 e5b0932
`getChannel` method same for all architectures
mikee47 4ff2119
Rename `Hw_pwm` to just `pwm` in sample
mikee47 6ddceaa
Sample working for Rp2040
mikee47 83ccc5b
Update general comment for HardwarePWM class.
mikee47 86ef760
Implement rest of Rp2040 HardwarePWM class
mikee47 ad8f1a9
Use appropriate unsigned types in sample, improve messages
mikee47 5b0d2c7
Use full divisor range
mikee47 76b74f0
Apply const to parameters/methods
mikee47 ec644fc
Define default channel values in tables
mikee47 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I note we can get the frequency for an individual pin, added for esp32 support I believe, but no corresponding
setFrequency
orsetPeriod
for a pin. We should either do that or get rid of thepin
parameter I think.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the way it's currently implemented for the esp32 is that the frequency is global, not per pin.
While the esp32 platform can set the frequency per timer (of which it has up to eight, depending on the actual Soc), timers are mapped to Pins and there can be more pins than timers.
Getting the frequency for a sigle pin, with the current implementation, makes indeed no sense.
I think this is something that should be part of an extended implementation that exposes the better hardware of the esp32 and rp2040 platforms.
On the BasicHWPwm, I have something that works a bit better for me. I'll clean it up and push it in the next days (I'm travelling right now)