Skip to content
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

Initial functioning version of ESP32 HardwarePWM. #2599

Merged
merged 29 commits into from
Apr 28, 2023

Conversation

pljakobs
Copy link
Contributor

@pljakobs pljakobs commented Jan 1, 2023

a few weeks ago I said I would take a swing at implementing HardwarePWM for the ESP32 Platform and here it is.
This is very preliminary and does not extend the api a lot (in fact, the only public function is curently HardwarePWM::getFrequency(uint8_t pin) which I used in debugging).
I would go ahead and extend the interface to expose the more advanced functions of ledc_ bt would appreciate your input on how to do so. My current thinking is that I put conditionals into the master HardwarePWM.h - but conditionals can make code very hard to read if the number of platforms gets too large.

@what-the-diff
Copy link

what-the-diff bot commented Jan 1, 2023

  • Added HardwarePWM.cpp to the project
  • Changed #include <HardwarePWM.h> in HardwarePWM.cpp to #include "HardwarePWM.h"
    3a) Removed all references of EspDigitalPin from code (not needed for esp32, and not available on esp8266 anyway)
    • this is a temporary fix until we have an updated digital pin map that includes both platforms!
      3b) Replaced ESP_ERROR_CHECK with debugf("error message") as it's more readable when debugging issues during development phase; will be removed later once everything works fine again...

@slaff slaff requested a review from mikee47 January 1, 2023 09:15
@slaff slaff changed the title initial functioning version of ESP32 HardwarePWM. [WIP] Initial functioning version of ESP32 HardwarePWM. Jan 1, 2023
@pljakobs
Copy link
Contributor Author

pljakobs commented Jan 1, 2023

I've added a number of toughts as a comment to the cpp file but don't want to open a new PR for just that, so I post it here for discussion

` * the ESP32 PWM Hardware is much more powerful than the ESP8266, allowing wider PWM timers (up to 20 bit)
 * as well as much higher PWM frequencies (up to 40MHz for a 1 Bit wide PWM)
 * 
 * Overview:
 * +------------------------------------------------------------------------------------------------+
 * | LED_PWM                                                                                        |
 * |  +-------------------------------------------+   +-------------------------------------------+ |
 * |  | High_Speed_Channels¹                      |   | Low_Speed_Channels                        | |
 * |  |                   +-----+     +--------+  |   |                   +-----+     +--------+  | |
 * |  |                   |     | --> | h_ch 0 |  |   |                   |     | --> | l_ch 0 |  | |
 * |  | +-----------+     |     |     +--------+  |   | +-----------+     |     |     +--------+  | |
 * |  | | h_timer 0 | --> |     |                 |   | | l_timer 0 | --> |     |                 | |
 * |  | +-----------+     |     |     +--------+  |   | +-----------+     |     |     +--------+  | |
 * |  |                   |     | --> | h_ch 1 |  |   |                   |     | --> | l_ch 1 |  | |
 * |  |                   |     |     +--------+  |   |                   |     |     +--------+  | |
 * |  |                   |     |                 |   |                   |     |                 | |
 * |  |                   |     |     +--------+  |   |                   |     |     +--------+  | |
 * |  |                   |     | --> | h_ch 2 |  |   |                   |     | --> | l_ch 2 |  | |
 * |  | +-----------+     |     |     +--------+  |   | +-----------+     |     |     +--------+  | |
 * |  | | h_timer 1 | --> |     |                 |   | | l_timer 1 | --> |     |                 | |
 * |  | +-----------+     |     |     +--------+  |   | +-----------+     |     |     +--------+  | |
 * |  |                   |     | --> | h_ch 3 |  |   |                   |     | --> | l_ch 3 |  | |
 * |  |                   |     |     +--------+  |   |                   |     |     +--------+  | |
 * |  |                   | MUX |                 |   |                   | MUX |                 | |
 * |  |                   |     |     +--------+  |   |                   |     |     +--------+  | |
 * |  |                   |     | --> | h_ch 4 |  |   |                   |     | --> | l_ch 4 |  | |
 * |  | +-----------+     |     |     +--------+  |   | +-----------+     |     |     +--------+  | |
 * |  | | h_timer 2 | --> |     |                 |   | | l_timer 2 | --> |     |                 | |
 * |  | +-----------+     |     |     +--------+  |   | +-----------+     |     |     +--------+  | |
 * |  |                   |     | --> | h_ch 5 |  |   |                   |     | --> | l_ch 5 |  | |
 * |  |                   |     |     +--------+  |   |                   |     |     +--------+  | |
 * |  |                   |     |                 |   |                   |     |                 | |
 * |  |                   |     |     +--------+  |   |                   |     |     +--------+  | |
 * |  |                   |     | --> | h_ch 6 |  |   |                   |     | --> | l_ch 6²|  | |
 * |  | +-----------+     |     |     +--------+  |   | +-----------+     |     |     +--------+  | |
 * |  | | h_timer 3 | --> |     |                 |   | | l_timer 3 | --> |     |                 | |
 * |  | +-----------+     |     |     +--------+  |   | +-----------+     |     |     +--------+  | |
 * |  |                   |     | --> | h_ch 7 |  |   |                   |     | --> | l_ch 7²|  | |
 * |  |                   |     |     +--------+  |   |                   |     |     +--------+  | |
 * |  |                   +-----+                 |   |                   +-----+                 | |
 * |  +-------------------------------------------+   +-------------------------------------------+ |
 * +------------------------------------------------------------------------------------------------+
 * ¹ High speed channels are only available when SOC_LEDC_SUPPORT_HS_MODE is defined as 1
 * ² The ESP32C3 does only support six channels, so 6 and 7 are not available on that SoC
 * 
 * The nomenclature of timers in the high speed / low speed blocks is a bit misleading as the idf api 
 * speaks of "speed mode", which, to me, implies that this would be a mode configurable in a specific timer
 * while in reality, it does select a block of timers. I am considering re-naming that to "speed mode block"
 * in my interface impmenentation.
 * 
 * As an example, I would use
 * setTimerFrequency(speedModeBlock, timer, frequency);
 * 
 * ToDo: see, how this can be implemented to provide maximum overlap with the RP2040 pwm hardware so code does 
 * not become overly SoC specific.
 * 
 * Maximum Timer width for PWM:
 * ============================
 * esp32   SOC_LEDC_TIMER_BIT_WIDE_NUM  (20)
 * esp32c3 SOC_LEDC_TIMER_BIT_WIDE_NUM  (14)
 * esp32s2 SOC_LEDC_TIMER_BIT_WIDE_NUM  (14)
 * esp32s3 SOC_LEDC_TIMER_BIT_WIDE_NUM  (14)
 * 
 * Number of Channels:
 * ===================
 * esp32   SOC_LEDC_CHANNEL_NUM         (8)
 * esp32c3 SOC_LEDC_CHANNEL_NUM         (6)
 * esp32s2 SOC_LEDC_CHANNEL_NUM         (8)
 * esp32s3 SOC_LEDC_CHANNEL_NUM 		(8)
 * 
 * Some SoSs support a mode called HIGHSPEED_MODE which is essentially another full block of PWM hardware 
 * that adds SOC_LEDC_CHANNEL_NUM channels. 
 * Those Architectures have SOC_LEDC_SUPPORT_HS_MODE defined as 1.
 * In esp-idf-4.3 that's currently only the esp32 SOC 
 * 
 * Supports highspeed mode:
 * ========================
 * esp32 SOC_LEDC_SUPPORT_HS_MODE	(1)
 *
 * ToDo: implement awareness of hs mode availablility
 * ==================================================
 * currently, the code just uses a % 8 operation on the pin index to calculate whether to assign a pin to either
 * high speed or low speed pwm blocks. This doesn't make a whole lot of sense since it makes it impossible
 * for Sming devs to actually use the functionality behind it. 
 * Also, it currently does not reflect the fact that different SOCs have a different number of channels per block
 * (specifically, the esp32c3 only has six channels and no highspeed mode). 
 * I will continue in two ways: 
 * - implement the "vanilla" Sming HardwarePWM interface that will hide the underlying architecture but allow up to 16 
 *   channels on an ESP32 
 * - implement overloads for the relevant functions that allow selecting hs mode where applicable. 
 * 
 * ToDo: implement PWM bit width control
 * =====================================
 * the current HardwarePWM implementation does not care about the PWM timer bit width. To leverage the functionality 
 * of the ESP32 hardware, it's necessary to make this configurable. As the width is per timer and all the Sming defined
 * functions are basically per pin, this needs to be an extension of the overal model, exposing at least timers. 
 * This, too, will require a compatible "basic" interface and an advanced interface that allows assiging pins (channels) 
 * to timers and the configuration of the timers themselves. 
 * The esp_idf does not provide a way to read the bit width configured for a channel, but I think it'll be useful to be able
 * to read back this value, not least to find the value for getMaxDuty() for a channel. It will either have to be stored in the
 * module or maybe read from the hardware directly (LEDC_[HL]STIMERx_CONF_REG & 0x1f)
 * 
 * hardware technical reference: 
 * =============================
 * https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf#ledpwm
 * 
 * Overview of the whole ledc-system here: 
 * https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html
 * 
 * the ledc interface also exposes some advanced functions such as fades that are then done in hardware. 
 * ToDo: implement a Sming interface for fades
 * `

Not sure what a good implementation for a more abstract interface is. Maybe a baseclass / SoC specific implementation class scheme to implement generic functions in software and provide SoC specific capabilities plus extensions where available? That's how the adafruit gfx library builds it for specific hardware.

Copy link
Contributor

@mikee47 mikee47 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great start, thank you! In general just a little tidying.

Sming/Arch/Esp32/Core/HardwarePWM.cpp Outdated Show resolved Hide resolved
Sming/Arch/Esp32/Core/HardwarePWM.cpp Outdated Show resolved Hide resolved
Sming/Arch/Esp32/Core/HardwarePWM.cpp Outdated Show resolved Hide resolved
Sming/Arch/Esp32/Core/HardwarePWM.cpp Outdated Show resolved Hide resolved
Sming/Arch/Esp32/Core/HardwarePWM.cpp Outdated Show resolved Hide resolved
Sming/Arch/Esp32/Core/HardwarePWM.cpp Outdated Show resolved Hide resolved
Sming/Arch/Esp32/Core/HardwarePWM.cpp Outdated Show resolved Hide resolved
Sming/Core/HardwarePWM.h Outdated Show resolved Hide resolved
Sming/Core/HardwarePWM.h Outdated Show resolved Hide resolved
Sming/Core/HardwarePWM.h Outdated Show resolved Hide resolved
@pljakobs
Copy link
Contributor Author

pljakobs commented Feb 1, 2023

(sorry for takign some more time, I was travelling and then sick for two weeks - will get to this soon)

@pljakobs
Copy link
Contributor Author

ok, I'm sorry if this is now very muddled, but I believe I got lost in git here - for reasons I don't understand, both, the .h as well as the .cpp file were repeatedly lost by git and no longer tracked. I think, now it's all there

Copy link
Contributor

@mikee47 mikee47 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've rebased and done some tidying but code functionality hasn't been changed.
Couple of points to consider regarding return values for invalid period/frequency and how update() works.

Sming/Arch/Esp32/Core/HardwarePWM.cpp Show resolved Hide resolved
Sming/Arch/Esp32/Core/HardwarePWM.cpp Outdated Show resolved Hide resolved
Sming/Arch/Esp32/Core/HardwarePWM.cpp Outdated Show resolved Hide resolved
@pljakobs
Copy link
Contributor Author

what's that remaining change request? I can't seem to see it.

Copy link
Contributor

@mikee47 mikee47 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, just remove those unused variables

Comment on lines 185 to 188
uint32_t io_info[SOC_LEDC_CHANNEL_NUM][3]; // pin information
uint32_t pwm_duty_init[SOC_LEDC_CHANNEL_NUM]; // pwm duty
for(uint8_t i = 0; i < no_of_pins; i++) {
pwm_duty_init[i] = 0; // Start with zero output
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

io_info and pwm_duty_init aren't used, please remove

@slaff
Copy link
Contributor

slaff commented Apr 5, 2023

@pljakobs Please run the coding style fixer. The following file has an issue:

--- a/Sming/Arch/Esp32/Core/HardwarePWM.cpp
+++ b/Sming/Arch/Esp32/Core/HardwarePWM.cpp
@@ -310,4 +310,3 @@ uint32_t HardwarePWM::getFrequency(uint8_t pin)
 {
 	return ledc_get_freq(pinToGroup(pin), pinToTimer(pin));
 }
-' ']'
++ echo '!!! Coding Style issues Found!!!'
++ echo '    Run: '\''make cs'\'' to fix them. '
++ echo 'diff --git a/Sming/Arch/Esp32/Core/HardwarePWM.cpp b/Sming/Arch/Esp32/Core/HardwarePWM.cpp
index f6aefe8..38ef1ad 1006[44](https://github.com/SmingHub/Sming/actions/runs/4582860432/jobs/8149817907?pr=2599#step:7:45)
--- a/Sming/Arch/Esp32/Core/HardwarePWM.cpp
+++ b/Sming/Arch/Esp32/Core/HardwarePWM.cpp
@@ -310,4 +310,3 @@ uint32_t HardwarePWM::getFrequency(uint8_t pin)
 {
 	return ledc_get_freq(pinToGroup(pin), pinToTimer(pin));
 }
-'
++ exit 1

@slaff
Copy link
Contributor

slaff commented Apr 5, 2023

@@ -122,6 +122,11 @@ class HardwarePWM
*/
void update();

/** @brief Get PWM Frequency
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add also
@param pin GPIO to get frequency for

@pljakobs
Copy link
Contributor Author

pljakobs commented Apr 5, 2023

you guys are thorrough! hope I caught it all by now.

Also - can I get some feedback on the other implementation that I've started (#2624)?

@slaff slaff changed the title [WIP] Initial functioning version of ESP32 HardwarePWM. Initial functioning version of ESP32 HardwarePWM. Apr 13, 2023
@slaff slaff added this to the 4.8.0 milestone Apr 13, 2023
Copy link
Contributor

@slaff slaff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good to me. There are still few coding style and return value issues that have to be fixed before merging this PR.


uint32_t periodToFrequency(uint32_t period)
{
return (period == 0) ? -1 : (1000000 / period);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, change the return value to be a valid uint32_t. -1 is not a valid unsigned int.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


uint32_t frequencyToPeriod(uint32_t freq)
{
return (freq == 0) ? -1 : (1000000 / freq);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, change the return value to be a valid uint32_t. -1 is not a valid unsigned int.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Sming/Arch/Esp32/Core/HardwarePWM.cpp Show resolved Hide resolved
{
for(uint8_t i = 0; i < channel_count; i++) {
if(channels[i] == pin) {
// debug_d("getChannel %d is %d", pin, i);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either remove the commented line or uncomment it. I would prefer to have it removed if it is not improving the debug output.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

#include <Clock.h>
#include "ESP8266EX.h"
#include <debug_progmem.h>

#include <HardwarePWM.h>

#define PERIOD_TO_MAX_DUTY(x) (x * 25)

HardwarePWM::HardwarePWM(uint8_t* pins, uint8_t no_of_pins) : channel_count(no_of_pins)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, keep close to Sming's coding style: https://sming.readthedocs.io/en/latest/information/develop/coding-style.html?highlight=Variables#naming.

Local variables, instance variables, and class variables must also be written in lowerCamelCase. Variable names must not start with, end with or contain underscore (_) or dollar sign ($) characters. 

For example in the code here uint8_t no_of_pins should be renamed to uint8_t numberOfPins, uint32_t io_info[PWM_CHANNEL_NUM_MAX][3]; -> uint32_t ioInfo[PWM_CHANNEL_NUM_MAX][3];, uint32_t pwm_duty_init[PWM_CHANNEL_NUM_MAX] -> uint32_t pwmDutyInit[PWM_CHANNEL_NUM_MAX] and so on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so no_of_pins is part of the core interface definition, if I rename that, I should probably also rename it for the other architectures - should this be part of this PR or may be a PR of it's own?
I believe, all other occurences of underscored names are defined in the esp32 ledc interface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be part of this PR or may be a PR of it's own

Leave it for another PR. This one waited already for too long to be merged.

I believe, all other occurences of underscored names are defined in the esp32 ledc interface.

Please, rename all local variables in the code that you have introduced to use lowerCamelCase . Example:

	uint32_t io_info[PWM_CHANNEL_NUM_MAX][3];	// pin information
	uint32_t pwm_duty_init[PWM_CHANNEL_NUM_MAX]; // pwm duty
  // ...
	const int initial_period = 1000;

and so on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't use those, they are part of the original ESP8266 version and are used both there and in the RP2040 implementation. I'll go ahead and rename them all and send a separate PR.

uint8_t HardwarePWM::getChannel(uint8_t pin)
{
for(uint8_t i = 0; i < channel_count; i++) {
if(channels[i] == pin) {
//debugf("getChannel %d is %d", pin, i);
// debug_d("getChannel %d is %d", pin, i);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented debug statements.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this really part of my PR? I have not touched the 8266 code

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was me! See commit eaf77f9 - there's a new getFrequency method which is required.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I was really confused for a moment now. I have renamed the local vars to comply with camelCase now, not yet removed the debug calls. I think some of them are worthy of being there and functional, so I will uncomment them

return i;
}
}
//debugf("getChannel: can't find pin %d", pin);

// debug_d("getChannel: can't find pin %d", pin);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented debug statements.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

debugf("Duty cycle value too high for current period.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace debugf with debug_w. And include also the duty cycle value to make the debug statement more useful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@pljakobs
Copy link
Contributor Author

sice I've deleted my earlier, confused comment, I'll say it again:
I really am impressed and I appreciate the diligence, professionality and support you are showing here. As I am sure you could tell, I don't do any of my coding at that level so it's an interesting experience for me, albeit I fear I've created more work for you than I actually did.

@slaff slaff removed the 3 - Review label Apr 28, 2023
@slaff slaff merged commit 5eb7426 into SmingHub:develop Apr 28, 2023
@slaff
Copy link
Contributor

slaff commented Apr 28, 2023

I really am impressed and I appreciate the diligence, professionality and support you are showing here.

@pljakobs Thank you for your contribution and congratulations for your first merged PR :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants