NachtRaveVL / PCA9685-Arduino

Arduino Library for the PCA9685 16-Channel PWM Driver Module.
GNU General Public License v3.0
85 stars 32 forks source link

[Improvement] Make setChannelsPWM work inside Timer0 CMP interrupt. #9

Open ErikZandboer opened 6 years ago

ErikZandboer commented 6 years ago

Hi,

First of all thank you for the great library! Everything worked perfectly in my project that uses serial input to control PWM. At some point I got fed up with the loop(); including a delay(10); to run around 100 times per second.

I then moved all of the code that executes at 100x per second to a Timer0 CMP interrupt function. This works great, until I execute any of your library calls like pwm.setChannelPWM(); INSIDE the interrupt.

The Arduino (UNO) seems to completely freeze at this point. I even built a flashing LED in loop(); to see if that would continue to run, but that also freezes. I am clueless as to what is happening; might the I2C implementation break something when called from an interrupt?

If I call upon your library from loop(); that works perfectly. But as soon as I execute from the interrupt; then the Arduino dies again and hard reset seems the only way out.

Do you have any idea what might be going on?

NachtRaveVL commented 4 years ago

Again leaving this up as help wanted - I honestly haven't really played much with any of the interrupt stuff, and not sure where to begin with this one. :S

ErikZandboer commented 4 years ago

Hi, it is a big problem with many libraries. Very often they're blocking so the code will just hang while some hardware fetches values or times out.

I need to run my software (many software timers) at EXACTLY 100x per second. So initially I put all code inside the INT0 ISR. But that is just too tricky to do, not just with your library.

So now I am using the TIM0 ISR just to set a volatile global to TRUE. The main loop just circles round until that global turns TRUE and makes it FALSE again. This runs the software exactly 100x per second, outside the INT ISR, plus I have the ability to detect an overrun (when the TIM0 ISR starts and the global was still TRUE). This has been working well.

How you'd really want to build libraries is to NEVER have a delay or a wait (non-blocking). I'm not sure why your library fails inside INT0 btw, I don't think you are waiting anywhere for responses?

NachtRaveVL commented 4 years ago

Well, I think that getting yourself out of the ISR asap and having the main loop pick that up is a fine way about it. Another way that it could be done, or more specifically a common way it's done in a more application development environment, would be through the use of enqueueing anonymous functions on a function list that runs on whichever priority queue/thread.

As far as waits - there is a 500ms wait that is done upon setting the PWM frequency, which requires setting the pre-scaler value and then doing (essentially) a reset. That is because the hardware forces you to only be able to edit the pre-scaler while in SLEEP mode (in the mode1 register), and then expects a reset so that it can configure its oscillator to that value, and it says in the datasheet a minimum of 500ms is needed for that to come online.

ErikZandboer commented 4 years ago

Yep I love the way things work currently. As many functions in my application are timed to the 1/100th of a second (I have LOADS of timers that count down to time relays, PWM-walkspeed etc) so having the execution exact worked great for me.

The 500ms wait would definitely be qualified as "blocking". Half a second!!! The way you would have to solve that is to split up that function call into two function calls and make the caller responsible for firing both at the proper time. For example, I have a "hunderds", "tenths", "seconds", "minutes" counter etc etc. It would be easy for me build a state machine that runs ever so often and introduces yet another timer to time that away. Both calls would be very short and you library is non-blocking.

But it DOES put more complexity on the user of the library. Alternatively you could maybe implement two flavors of the PWM frequency setting? One where you just call the function (500ms blocking), and then have two additional function calls available for "setting" and one for "reset" separate (If I understood your explanation correctly). These two would be non-blocking but the user would have to have the "intellect" to call them both with at least 500ms between them.