Glitch-free PWM in split mode #833
Replies: 3 comments 4 replies
-
These are the kinds of posts I like to see, worked examples of using peripheral x to achieve goal y. Do you have a silly scope? It would be sweet to see some sample waveforms, before and after your software buffering. |
Beta Was this translation helpful? Give feedback.
-
You cannot get glitch free pwm in split mode. That is an explicit sacrifice
that the mode makes In order do double the number of pwm outputs
A near future version will give tools submenus to switch the analog Write
mediated pwm a) between a few sets of pins chosen for each pincount to
prioritize one thing or another and b) and a set that turns off split mode,
and uses it in buffered mode. This may also unlock a few function calls to
turn off analogWrite() (would error 8f known at compiletime that it was a
that pin, else do nothing) and enable a group of of functions tht will go
into the megaTinyCore library and let you change the TOP and and use as
many bits as you want as long as you're OK with the frequency of the output.
That menu option will also include options for using tcd on woa and wob for
the no -split-mode setting.
Note that on a 1- series the two tcd outputs are glitch free (with the
caveat of having to analogWrite() them both to 0 or 255, which leaves the
timer in control of the pin; digital Write has to briefly disable the timer
because the selection of which channels are output can't be changed while
it is enabled
…____________
Spence Konde
Azzy’S Electronics
New products! Check them out at tindie.com/stores/DrAzzy
GitHub: github.com/SpenceKonde
ATTinyCore: Arduino support for almost every ATTiny microcontroller
Contact: ***@***.***
On Tue, Sep 27, 2022, 22:39 Isaac Nygaard ***@***.***> wrote:
I don't have a scope unfortunately... have it on my shopping list but
haven't got around to shopping yet.
—
Reply to this email directly, view it on GitHub
<#833 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABTXEW5XNM3EQZ3RCL2QTJLWAOVUXANCNFSM6AAAAAAQXFCZSY>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***
.com>
|
Beta Was this translation helpful? Give feedback.
-
I think you get only 63 because you need it all ready before the timer next ticks. You lose 10 right off the bat for entering and exiting the interrupt.. 53 left. Now let's imagine we do a normal ISR, that means the prologue will consist of push push in push eor to save r0, save r1, save sreg, and zero r1. 5 clocks there And the epilogue, pop out pop pop reti. that's 7 more (I counted the reti in the 10 above. You will need a register you can ldi into, which mean 3 more clocks 1 in prologue one in epilogue 38 left. naive implementation would be to lds, and sts the 6 values in sequence, taking 30 clocks. Now you also need an efficient way to signal to the ISR that there are values to update, so you think "Oh super, I'll just store a value in memory and clear it. loading it with LDS and writing 0 back to it with STS is 5 clocks though. implying that a naive but not stupid way to do it will work. barely. But you also need to compare it, and branch over something to skip the ISR if it's not set. Well, that is damned close isnt it? First off, we have 4 magic registers called the GPIORs (or GPR.GPRn's on Dx and later - my cores make GPIOR an alias for them on Dx-series and classic AVRs where they were called GPIOn instead of GPIORn and on the Dx-series so GPIORn works everywhere. GPIOR0 is used to track reset cause (we always check and clear the reset flags literally as soon as we have initialized enough that we can see if a number is equal to zero every time the code that runs after reset is executed. It checkes to make sure that at least one reset flag is set, clears the reset flags and then if what it found was 0, that means a dirty reset or wild pointer or an interrupt that doesn't exist was the cause of ending up at the initialization code again. That is to say, a reset has NOT occurred, but rather a "dirty reset" where due to a defect in the code execution wound up at the reset vector. That's a bad thing. Since all API implementation and libraries assume that at startup the chip has successfully reset, so registers hold their reset values until set otherwise, the assumptions of the whole core are violated, and we can make no assumptions about what the behavior of the system would be if we were to continue like that, though the principle of entropy suggests that the chances of the behavior not being wrong are effectively nil. So continuing is a non-starter. Instead, we immediately issue a software reset command, to get a clean reset. The only problem with this is that when we do have a clean reset, we are forced to clear the reset flags (only a POR clears reset flags other than PORF- maybe BOR clears everything other than BORF and PORF - thus we would not be able to tell the difference between a clean power on reset, and a dirty reset, unless we clear the flags). But user code might want to use those reset flags. So at startup, assuming we don't detect a dirty reset, after clearing the reset flags, we also output the value held in the reset flag register to GPIOR0 (this means one or more of the 6 low bits will be set). If using tuned internal oscillator clocking option, the two highest bits get used later in initialization to report error conditions: All that use of GPIOR0 happens before setup runs, and none of the other GPIOR's (GPOIR1-3) are touched by the core - so any of them could be used for what I describe below. If GPIOR0 is used it should be set to 0 in setup, after you've looked at it to see the reset cause (or ignored it because like 99.99% of the world aren't much concerned with it). If using a tuned oscillator option with it, you really ought to check the two high bits - one of them is a warning flag that you forgot to tune the thip, and the other is a "so sorry, this chip cannot do what you asked, and that's why timekeeping is blatantly hosed" bit. Most people don't care the reset reason except when debugging suspicious resets, and few people use tuning. So most people would just have to make sure that if using GPIOR0, they set it to 0 in setup, and everyone could use any other GPIOR without taking any action (provided that their own code isn't making use of it. Which is very rare. I sometimes use the GPIORs myself, but very few people do; any author of a library that doesn't clearly document use of a GPIOR should be hunted down and beaten up for his sins. Anyway... Each of these is a 1 byte register, and does nothing on it's own, they're located in main address spaceat addresses below 0x0020 - that is, within the coveted low I/O space. Why do we care in this case? Well, they get SBI and CBI, single clock instructions that set or clear a bit, for one thing (this is how digitalWriteFast() works - the other 28 low IO registers are the VPORT registers to control pins). That means that GPIOR0 |= 0x01; is a single clock, as is GPIOR0 &= ~(0x01). Better still, there's a Skip next instruction If Bit in Io Set/Clear (sbrs/sbrc). And the SREG is unchanged. So now let's reimagine our isr HUZZAH!! But it gets better So get the ISR naked, and thing can move faster... That's 3 + 30 + 1 + 2 +10 (overhead including reti). 46 clocks. Another approach - this time, after pushing that upper register, we then also push r28, 29, 30 and 31. (4 clocks). We LDI values into them so one points to the array in SRAM containing the buffered values, and the other points to the start of the TCA peripheral. (4 more clocks) If that we re NOT an ISR.... we could just pass in the addresses as operands to the asm (we'd use the X and Z registers, X for the sram with buffered duty cycles, since we'd be accessing them in order and could read with ld X+, since we only need displacement on the register we're using to access the TCA registers - because we want to use the same pointer to clear the intflags that we're using to write the duty cycles, In the best case non-isr context, it would still save 13 clocks, while costing just 4 for the LDI's, net of 9. If we used Y there, to meet the constraints the compiler would almost certainly end up needing to push (2 clocks) and pop (4 clock) the values in Y - that's because while X (r26 and r27) and Z (r30, r31) are call used, meaning that any called function is permitted to shit on them, Y is call saved, and the function needs to save and restore it. So 46 clocks is currently my best guess for the execution time of an asm implementation of that, so it is viable, with 18 clocks to spare. But no prescaler lower than 64 could work, and I don't see any ways to further rev it up. |
Beta Was this translation helpful? Give feedback.
-
As you may know, TCA0's normal mode is buffered, preventing glitches when changing PWM values. I was playing around with ways to do software buffering when TCA0 is in split mode, and it was actually fairly simple. Feel free to use however you like. This was for Attiny424, and unfortunately that is the only AVR I have; if someone wants to throw together a library that works for all AVR's, be my guest. Also if you have any ideas for improvements, I'd love to hear them.
Usage:
smooth_pwm::init
: very similar toinit_TCA0()
smooth_pwm::ready
: check that we can write new PWM values to the buffersmooth_pwm::WO
arraysmooth_pwm::update
: triggers flushing the buffers to TCA0 registers in a glitch free mannerBeta Was this translation helpful? Give feedback.
All reactions