SpenceKonde / megaTinyCore

Arduino core for the tinyAVR 0/1/2-series - Ones's digit 2,4,5,7 (pincount, 8,14,20,24), tens digit 0, 1, or 2 (featureset), preceded by flash in kb. Library maintainers: porting help available!
Other
561 stars 146 forks source link

TCA0 interrupting with program problems #681

Closed Bob2777 closed 2 years ago

Bob2777 commented 2 years ago

Hi! I'm trying to run PWM in split mode on ATTiny806 using example sketch with added blinking LED:

void setup() { pinMode(PIN_PC0, OUTPUT); pinMode(PIN_PB0, OUTPUT); // PB0 - TCA0 WO0, pin7 on 14-pin parts pinMode(PIN_PA5, OUTPUT); // PA5 - TCA0 WO5, pin1 on 14-pin parts TCA0.SPLIT.CTRLB = TCA_SPLIT_LCMP0EN_bm|TCA_SPLIT_HCMP2EN_bm; // PWM on WO5, WO0 TCA0.SPLIT.LPER = 0xFF; // Count all the way down from 255 on WO0/WO1/WO2 TCA0.SPLIT.HPER = 200; // Count down from only 200 on WO3/WO4/WO5 TCA0.SPLIT.LCMP0 = 0x7F; // 50% duty cycle TCA0.SPLIT.HCMP2 = 150; // 75% duty cycle TCA0.SPLIT.CTRLA = TCA_SPLIT_CLKSEL_DIV2_gc | TCA_SPLIT_ENABLE_bm; // enable the timer with prescaler of 16 }

void loop() { digitalWrite(PIN_PC0, HIGH); delay(500); digitalWrite(PIN_PC0, LOW); delay(500); }

PWM works fine, but LED is blinking ca. 30 times a minute and it should blink just twice a minute. With this problem, you cannot do basically anything while generating PWM output. Is this a bug, or is it supposed to act this way? When I change the clock pre-division, the frequency of blinking LED changes too... Thank you for your ideas.

grandaspanna commented 2 years ago

I could be off base here, but by default, millis() runs on TCA0 with the 0-series. Because you're doing some manual configuration, it may interfere. You can choose a different timer for millis() from the menu.

Also, I may be misreading something, with a 0.5 second on/off cycle, the LED should blink 60 times a minute with that code.

Bob2777 commented 2 years ago

Yeah, I know that milis() do interfere, but I don't use that. Becuase of this problem you are completely unable to do anything while PWM is running and therefore I think it is probably not supposed to act like that or I might be missing something...

grandaspanna commented 2 years ago

PWM should run fine while you do other things.

If you add takeOverTCA0() (see https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/TakingOverTCA0.md) at the start and choose a different millis() source, does the code behave differently?

SpenceKonde commented 2 years ago

If you are providing your own abstration layer over TCA0, you shoulsd call TakeOverTCA0. That will error out, pointing out that manually configuring TCA0 is not permittted if that's the millis timer, becasue just abotu anything you'd do to it would leave you without functioning millis(); when taking over TCA0 you must either have millis disabled, or set to use a different timer.

The default timers are: 1-series: TCD0 (because it's so byzantine nobody will want to reconfigure it themselves anyway. Alternatives include TCA0, TCB0) 2-series TCB1 (that second TCB is a true blessing. Other options are TCA0 and TCB0 0-series: TCA0 (because using TCB0 would prevent tone, servo, and numerous other librariaries from working on default configuration. Only other option is TCB0

So only 0-series end up using TCA by default for millis timekeeping (of those options it can always be selected

OP has made two errorrs:

  1. He has not called TakeOverTCA0() prior taking over control ofTCA0. That can resultrs in poorly defined behavior when any analog or digital pins are used. See That must be called before making any changes to the timer configuration manually - there are only very few registers that could be manually changed on small subset of the registers can be modified breaking builtin configurations. I know the following, all of which OP's changes will break it: CTRLA, CTRLB, one of the periods, I think HPER (picked the one that couldn't be used in non-split mode, since the majority of users reconfiguring are doingso to get 16-bit non-split pwm) for non-split if they asre taking over the timer.
  2. You definition of not being ablet ro do anything results from poor definition of terms, and the above derangement of trh timer configuration PWM is generated by telling the hardware to generated it with out software intervention. Youi seem to be using delay)_to generate software PWM, which doing requires the full attention of the processor. @grandaspanna linked a document with an example of how to efficiently (albeit not in a pretty way do what you want,.

see also Timer Reference

SpenceKonde commented 2 years ago

I'll note that the new timer reference page I linked bis basicallly a revised version of the one @grandspanna linked. He can be forgiven for not knowing about it because I uploaded it between his post and mine >.>

Bob2777 commented 2 years ago

Now I added these two lines:

takeOverTCA0(); TCA0.SINGLE.CTRLD = 1;

after pins specification line and it is still not blinking twice a second, looking at the scope now there isn't any frequency and the LED is still on...

SpenceKonde commented 2 years ago

Yes, but you didn't set any of the other registers....

We execute a hard restart of the timer when you "take over" a type A timer. All of it's registers are reset to their power on reset values. Per the manufacturer datasheet this is advised when switching between split and unsplit mode to prevent unpredictable behavior - and for anyone who doesn't want split mode (est. 95% of people taking over the timers), it's greatly preferable

Yours is a bit of an awkward case, as you don't want to change the configuration of the timer at all, or at least not in any major ways - just to make analogWrite and digitalWrite unaware of those pins being PWM capable, so that digitalWrite(OriginalPWMPin,LOW) doesn't turn off pwm on AlternateDigitalPin, is that correct?

If your application can be reworked to use fast digital I/O functions instead of the normal ones, you could drop the TCA takeover, and instead make a replacement for analogWrite() which would take a pin number, and value between 0 and 255.

A 0 or 255 would execute a digitalWrite() on the pin, and otherwise, set the appropriate LCMP/HCMP register to the provided value. digitalWriteFast() doesn't know anything about PWM, all it knows how to do is set a single compile time known pin to a single compile time known state in one instruction cycle...

The normal use case for the full takeover is people who want to reconfigure the timer for 16-bit PWM - it is far less common to want to keep split mode and change which pins it's on (and as noted, the size cost to that would make people using small-flash chips gather around my house with pitchforks and brickbats.

Bob2777 commented 2 years ago

I don't want to make LED blink, it was just an simplified example of the problem. In the original code I used for example i2c communication and it didnt work at all. I previously designed and tested the code on arduino nano and worked everything just alright. So the question is still: can I simultaneously run two PWM channels and make the program acting normally (I mean not so difficult timing activities like I2C communication, delaying...)?

SpenceKonde commented 2 years ago

I think is skipped a bit on what the nature of your problem was.

You changed the clock divider on TCA0 - but that's the timer you have selected for millis, because you only have two choices on a 0 series tiny - TCA0 and TCB0.

If you use TCA0, you can do everything except change the frequency of the PWM. If you use TCB0, you cannot use tone() or the Servo library or anything else that usest TCB0. If you need both of those timers, you need to move to a part with more timers.

The 1-series timers have a heinously complicated timer, TCD0, which we use for millis by default (even though it's objectively worse) because it's such a pain in the ass to program that 99% of people wouldn't do it and i'd been maintaining this core for like a year when I finally figured out how to make it do PWM and millis at the same thing. It's the peripheral with the longest chapter of all modern AVR peripherals if i'm not mistaken, and it's still insufficient to explain how to use the damned thing. Hence, once I did, I made it the default to give people who don't want to learn the worlds wierdest timer as many sane timers (TCA/TCB's) as possible that won't break timekeeping.

The 1-series with 16k+ flash also have a TCB1, as do all 2-series (but they don't have a TCD0)

You can change the selected timer in the tools -> millis timer submenu; you'll get a compil;e error saying as mcuh if you pick one that doesn't exist on your part.

Bob2777 commented 2 years ago

So if I understand correctly, I am not able to run 2 PWM channels using Split mode with constant frequency (one of the will have variable duty cycle) and simultaneously communicate with other devices through I2C on ATTiny 806? If so, it is a pity... Probably will have to change to ATMega then. Thank you for your time and explanation.

SpenceKonde commented 2 years ago

That is completely untrue, I don't understand how you get communication from I2C from changing the millis timer...

Bob2777 commented 2 years ago

Well that is same thing what I dont understand. Millis just count miliseconds since the program started running and changes in TCA0 which is only responsible for millis should not affect the program in any way as millis don't affect the program too...

SpenceKonde commented 2 years ago

But your demonstration uses delay..... ? Delay is based on the same underlying mechanism...

Bob2777 commented 2 years ago

Yes, the code uses delay, too. Does that mean that the TCA0 directly affects delay function? And is there any alternative to it then? I finally understand what is probably going on here. Just delay function seems to interfere.

SpenceKonde commented 2 years ago

If you can use TCB0 instead of TCA0 for millis, that solves it at a stroke.

Otherwise #include <util/delay.h> and use _delay_ms() - the advantage of delay is that it give the same length delay if halkf tht time is spent one interruipt or another and the delay can be determiend at runtime, while _delay_ms() and _delay_us() do not count time spent within an ISR, and the us version (like delayMicroseconds) does not count time spent in ISRs./

Bob2777 commented 2 years ago

Thank you for explanation. I firstly didn't realize that just delay causes the problem. I used util/delay.h and everything works fine now. Thank you again for your time!

SpenceKonde commented 2 years ago

Yeah all Arduino API timelkeeping functions except delayMicroseconds rely on the same timer. If you are doing anything else with that timer you want to be sure to disable millis from the tools menu.