per1234 / PalatisSoftPWM

Software PWM library for Arduino
BSD 3-Clause "New" or "Revised" License
19 stars 5 forks source link

Support >256 PWM levels #2

Open per1234 opened 8 years ago

per1234 commented 8 years ago

When using this library with LEDs there is a large difference between PWM levels at low brightness so the option of more PWM levels than 256 would be useful.

Palatis commented 8 years ago

arduino is a 8bit mcu, if we try to use more than 8bit to store pwm levels, we have to do uint16_t arithmetic, that's like 2 times slower. http://www.avrfreaks.net/forum/multiply-divide-add-performance-various-data-types

the "timer frequency" is calculated as number of brightness levels * pwm frequency, for example, 256 levels * 60hz = 15360hz (60hz is an ideal pwm frequency to NOT cause visual flicker), on a 16Mhz mcu we have 16Mhz / 15.36Khz = 1041.66 cycles to finish the task. each channel takes 4 instructions, plus some overhead (around 10 instructions), if we're targeting 16 channels it's 16 * 4 + 10 = 74 instructions to be executed every 1041.66 cycles, yields a cpu load of ~7.1%.

and say, we now want 512 levels, 512 levels * 60hz = 30720hz, around 16Mhz / 30.72Khz = 520.83 cycles to complete each timer isr. and the timer ISR now take 16 * 8 + 20 = 148 instructions due to uint16_t arithmetic (512 can't be stored in a uint8_t), the cpu load is now 148 / 520.83 ~= 28.4%.

even if we still do 256 levels but store the levels in a uint16_t, the isr load is bumped by a factor of 2. so ~14% cpu load. well if we can make c++ automatically select optimal storage type for levels we may be able to achieve this without sacrificing performance, however I don't know how......

per1234 commented 8 years ago

Thanks for the information! I was thinking of using preprocessor directive to switch the support for >256 PWM levels so the performance hit would only be incurred if the feature is needed. I do think 256 levels is sufficient for most applications. I've made some attempts at this but haven't been successful yet. It sounds like you're saying it is actually possible to accomplish this(at a cost) so that's good to know. I've managed to get around the issue by never fully turning the LEDs off but it was previously enough of a problem that I thought it worth considering adding this functionality.

Palatis commented 8 years ago

i was thinking about something like

template<int levels> class levelsToType { public: typedef uint16_t levels_t; };
template< > class levelsToType<0> { }; // let it fail, we can't have 0 level...
template< > class levelsToType<1> { }; // let it fail, we can't have only 1 level...
template< > class levelsToType<2> { public: typedef uint8_t levels_t; };
template< > class levelsToType<3> { public: typedef uint8_t levels_t; };
...
template< > class levelsToType<254> { public: typedef uint8_t levels_t; };
template< > class levelsToType<255> { public: typedef uint8_t levels_t; };

and use levelsToType<pwm_levels>::levels_t _levels; when defining the variable.

Palatis commented 8 years ago

maybe something like this... http://ideone.com/lllWv9

for 256 levels we have to use uint16_t... because we do digitalWrite<PIN>((_current_level < _user_level) ? HIGH : LOW);

per1234 commented 8 years ago

@Palatis I really appreciate you giving me this information! Now that I know this is possible and you have pointed me in the right direction I'm sure I can achieve my goal. This is a bit above my current programming skills so it will take me a little while to get there but it will also provide an opportunity to learn new things in the process.

Palatis commented 8 years ago

this paradigm is called c++ template meta-programming.

this whole softpwm library wouldn't be possible without it.