-
Notifications
You must be signed in to change notification settings - Fork 19
Duty cycle as integer rather than float #6
Comments
You're correct about this. I didn't think that this demanding use-case would use this library at all, but would write the registers directly using look-up table. But the idea is interesting and I'll add this function
to bypass all the calculations / displays.
If level/duty_cycle > TOP, it will be trimmed automatically to TOP. You have to prepare a lookup table similar to
then call the function manually like
Is this OK to you ?? I also need you to test using the current version to see how the I'll publish the new release within some hours. |
### Releases v1.2.0 1. Add efficient `setPWM_manual()` function to use in wafeform creation using PWM. Check [Duty cycle as integer rather than float #6](#6) 2. Add example [PWM_Waveform](https://github.com/khoih-prog/RP2040_PWM/tree/main/examples/PWM_Waveform) to demonstrate how to use new `setPWM_manual()` function in wafeform creation 3. Optimize library code and examples by using **reference-passing instead of value-passing**.
### Releases v1.2.0 1. Add efficient `setPWM_manual()` function to use in wafeform creation using PWM. Check [Duty cycle as integer rather than float #6](#6) 2. Add example [PWM_Waveform](https://github.com/khoih-prog/RP2040_PWM/tree/main/examples/PWM_Waveform) to demonstrate how to use new `setPWM_manual()` function in wafeform creation 3. Optimize library code and examples by using **reference-passing instead of value-passing**.
### Releases v1.2.0 1. Add efficient `setPWM_manual()` function to use in wafeform creation using PWM. Check [Duty cycle as integer rather than float #6](#6) 2. Add example [PWM_Waveform](https://github.com/khoih-prog/RP2040_PWM/tree/main/examples/PWM_Waveform) to demonstrate how to use new `setPWM_manual()` function in wafeform creation 3. Optimize library code and examples by using **reference-passing instead of value-passing**.
Hi @Laserjones The new RP2040_PWM releases v1.2.0 has just been published. Your contribution is noted in Contributions and Thanks Try and modify the new PWM_Waveform example according to your use-case and report back if there is any bug to fix or enhancement to add Best Regards, Releases v1.2.0
|
Wow, the three coffees I bought you really kept you awake! :-D This will surely help, thanks a lot! I'll check it out in detail later. I'm just wondering whether it is actually necessary to pass all four parameters each time (which probably means that four registers are written to each time, too?). Maybe I underestimate the scope of possible PWM applications, but all use cases I can imagine would require only the duty cycle to be changed frequently, whereas the TOP and the frequency would be set only once to initialize the PWM output (not sure about the phase correction, as I have no idea what its practical use is). So three out of four registers would unnecessarily be written to in spite of staying unchanged. So, from a humble Arduino programmer's point of view, who likes everything as simple as possible, I would wish for something like: PWM_init(uint8_t pin, uint16_t top, float frequency, uint16_t startlevel, bool phaseCorrect) // to be called once PWM_write_absolute(uint8_t pin, uint16_t level) // level as integer, 0 to counter TOP That would be close to the familiar AnalogWrite() function of Arduino, which basically does the same (but in software with only 256 duty cycle values). |
Even regardless of any performance advantage, I find it definitely useful to have this option of duty cycle as integer, because - as mentioned - in many applications the values used by the programmer will be integers of a known range anyway, so the conversion to float percent and back not only adds unnecessary computing load, but also unneccesary calculations in the program code. For example, audio waves usually are simply an array of 8-bit or 16-bit values, which can directly be handed over to the PWM output if the TOP is configured to be above the maximum wave amplitude. No need for percent values. |
This is the reason why I use khoih-prog/ESP8266_PWM#1 (comment)
Using I think I'll use either
I prefer option 2. to have perfect resolution without adding burden to the MCU |
It's possible to optimize like that, but I'm against the idea of modifying the individual register by accessing it directly. It's always better to use the SDK-provided functions to be compatible across platforms and releases. It's always possible the core's functions has been optimized to modify the registers only if changed. If the use-case is so demanding to be optimized up to each register access, you have to write the code to access the registers directly, without using the core SDK and the library. Even using assembly code, not C/C++. So. I won't go to that extreme and make the library so hard to use. Anyway, don't hesitate to propose the new ideas. Without discussion, we all have the tunnel-vision and don't have a clear picture of what's better. |
As far as I can see, there's no need to access registers directly, because the SDK provides functions for each parameter separately (https://raspberrypi.github.io/pico-sdk-doxygen/rp2__common_2hardware__pwm_2include_2hardware_2pwm_8h.html). In fact, looking at your code, it seems like you are doing just that: Your function receives all the parameters at once, but then sets each register individually with SDK calls such as pwm_set_gpio_level(...).
I hope so. But your function still has to handle all the parameters, even if the SDK functions then decide that the registers need not be touched. On the other hand, duplicating all the individual SDK functions in your library would not make a lot of sense. Though at least for the duty cycle, I'd prefer a function that takes care of only this one parameter.
Actually my thoughts were intended to make it easier to use, not harder. ;-) But I get your point, and the more I look at your code, the more I think that I might as well call the SDK functions directly, or maybe write my own PWM library, which would be a nice challenge. By the way, could you help me understand why you are able to call the SDK functions directly in your code without any #include or prefix? As far as I understand the explanation at the bottom of https://github.com/arduino/ArduinoCore-mbed, an mbed:: prefix should be required. Phew, I never thought I would dive so deeply into this stuff after only some weeks of programming the Pico, with almost no previous C++ or Arduino experience. A steep curve, but definitely fun. |
Sorry, I overlooked #include "hardware/pwm.h" in your code. I assume that's the answer? |
It's so good that you dive deeply into the SDK, core, library, etc. and had a lot of fun and experience thru it. You also make me reread them as I almost forgot after some time not looking at. The difference is just these 2 lines to program the Lines 206 to 207 in 51e1826
I can create a new function to let you not passing the
The compiler will auto selects the correct function accordingly. A new release will be published soon with these features
|
### Releases v1.3.0 1. Add `setPWM_manual(pin, level)` function for efficiency in wafeform creation using PWM. Check [Duty cycle as integer rather than float #6](#6) 2. Add example [PWM_Waveform_Fast](https://github.com/khoih-prog/RP2040_PWM/tree/main/examples/PWM_Waveform_Fast) to demonstrate how to use new `setPWM_manual(pin, level)` function. 3. Add `setPWM_Int()` function for optional `uint32_t dutycycle = real_dutycycle * 1000`. Check [Duty cycle as integer rather than float #6](#6) 4. Add example [PWM_DynamicDutyCycle_Int](https://github.com/khoih-prog/RP2040_PWM/tree/main/examples/PWM_DynamicDutyCycle_Int) to demonstrate how to use new `setPWM_Int()` function. 5. Rewrite many functions to take advantage of new features.
Hi @Laserjones The new RP2040_PWM releases v1.3.0 has just been published. Your contribution is noted in Contributions and Thanks Try and modify the new PWM_Waveform_Fast example according to your use-case and report back if there is any bug to fix or enhancement to add Best Regards, Releases v1.3.0
|
Thank you very much! I'll check it all out when I'm ready. First I'll need to implement some hardware in order to actually monitor the waves (PWM output filter, PC audio interface, software oscilloscope). Can take a while, as this is just a hobbyist project at this time. |
I don't really understand this part:
The hardware PWM counter (and thus the maximum PWM resolution) is 16-bit, so passing the duty cycle as a 32-bit value should not bring an advantage regarding resolution. So if we pass the DC directly as an integer (capture compare value), EDIT: It just dawned on me that maybe you want to provide an option to pass the DC as an integer instead of float, but still as a percentage value rather than the direct capture compare value? Example: If I want an 82.5% duty cycle, I'll pass an integer value of 82500? In that case, of course, 32 bits would be required for the full resolution. And it would avoid floating point operations. Nice idea. I hadn't thought of such an option. When creating waveforms, I already know their range of possible values. For example, if my waves have a 16-bit resolution, I'll set the TOP to 65535 (max. 16-bit range) and pass the wave samples to PWM as they are without thinking about percentage values. A level value of 32767 would then correspond to a 50% duty cycle. |
That's very good you can read my idea. Certainly you must have an investigative mind, then just take some short time to read the library code and figure out. For normal user, it's easier to specify DC, than has to calculate / map the DC to 16-bit value. And the Why DC * 1000? I'd like to take advantage of the full resolution of 16-bit TOP register. If DC * 100, we have max 100% * 100 = 10000, so we'll loose the full 16-bit resolution. IMO, with the 32-bit MCU, using the native |
I may have caused all the confusion myself by writing "Duty cycle as integer rather than float", while what I actually meant was "Pass the 16-bit capture compare value directly as an integer rather than calculating it from a float percentage value". For me, "duty cycle" and "capture compare value" were the same thing, but of course, technically the duty cycle is always a percentage value, whereas the capture compare value is what is actually written to the register. So I might have accidentally triggered an improvement to the library that I didn't even intend. ;-) |
That's true in many cases, e.g. if someone wants to control the brightness of an LED or the speed of a motor using PWM. They will automatically think in percentage values in that case. But in my case, when creating waveforms from a table of samples, I'm dealing with the actual sample values anyway, and converting them to percentage and back would just be a detour. So it's definitely good to have both options. |
You have to calculate once anyway and store in the the lookup table, not directly for every samples. So calculate either the 16-bit level or DC is almost similar task. |
I'm not sure what you mean. If I know that all my samples are 16-bit values and the TOP is set to the maximum 16-bit range, I can write the values of my samples directly to the capture compare register – no need for additional calculations. Correct me if I'm wrong. For example, for a sawtooth wave (45-degree ramp upwards), I would use an array of 65536 16-bit values, where wave[0]=0, wave[1]=1, wave[2]=2, etc. – and then repeatedly pass these as level values to the PWM without any conversion. |
That's a special use-case in waveform creation, where you can have the level ramping up value directly in your mind. 1-2-3-... How about other use-cases when you can have to calculate the But anyway, it's so easy for me to add a function for that simple |
I only used this as a simple example. Of course, in most cases, I would either need to calculate the samples or use a prepared lookup table (for a sine wave, for example). Of course, I could then as well write percentage values to that table rather than absolute values, but it would have no advantage at all – on the contrary: For full resolution, I would then need 32-bit or float values in my table rather than 16-bit, for the reasons you explained above. That would consume twice the memory.
I thought that you had already done just that with the Well, I now understand why you said at the beginning that users like me would rather use the SDK directly anyway. ;-) |
As far as I understand, currently the duty cycle needs to be specified as a float value bewteen 0.0 and 100.0 (percent), which is then turned into a counter compare value (integer) by the library. This means that several floating point operations are required each time the duty cycle is changed. This can add up to quite a lot of floating point operations if PWM is used, for example, to output analog waveforms at high frequencies, where the duty cycle can change thousands of times per second. And as far as I understand, these operations consume quite some time, as the RP2040 does not have a floating point unit.
Could it therefore make sense to add an option to provide the duty cycle as an integer directly (i.e. between 0 and the TOP value)? In many cases, e.g. when waveforms are read from a lookup table of samples, the programmer already knows the absolute integer values anyway, and it would make little sense to first convert them into a float percentage value and then immediately reverse that conversion in the library.
An error should then be issued if the duty cycle value exceeds the TOP value.
The text was updated successfully, but these errors were encountered: