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
544 stars 141 forks source link

Not an issue / but a Question #1008

Closed bbhatt1080 closed 9 months ago

bbhatt1080 commented 9 months ago

Hi Dr. Azzy, Thanks for the megaTinyCore and megaCoreX, i use them a lot. A quick question.

What is the trick to insert direct register / IO access code like AVR Studio while using your libraries?

DDRx, PINx, PORTA etc?

Thank you =bb=

SpenceKonde commented 9 months ago

See https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/Ref_DirectPortManipulation.md

There is no DDRx or PINx in modern AVR. There are the VPORTx.DIR/OUT/IN/INTFLAGS - VPORTx.DIR/OUT/IN work like DDRx, PORTx and PINx did before, including writing 1's to VPORTx.IN toggling the output value. INTFLAGS works like all the modern AVR flags registers (write 1 to clear a bit, 0 to leave unchanged, bits can only be set by hardware). These registers are in the low IO and things like VPORTC.OUT |= 1<<x (as long as x is constant and only impacts one bit) compile to a single instruction just like classic AVR (and cbi and sbi are now single clock!). The VPORTx registers are actually though just a subset of the functionality of the ports, to provide easy high performance access to pins. The full features are exposed in the PORTx structures. These are not in the low I/O space, and hence something like PORTA.OUT |= 1 would turn into a (non-interrupt safe) RMW cycle = but don't worry, they give you DIRSET, DIRCLR, DIRTGL, and the same for OUT - these strobe registers always read as the register they effect, but when written to, the bits written 1 are either set , cleared, or toggled. Here writing IN does nothing (use OUTTGL), it's got intflags, I think it's got an option to limit the slew rate (per-port basis), then it's got the 8 PINnCTRL registers, which handle additional per-pin configuration.: First three bits control input sensing - 6 options so far - normal, 4 interrupt modes (rising/falling/low/change), and digital input disabled. The next bit controls that pin's pullup - pullups don't come on if OUT is 1 and DIR is 0 anymore, they're controlled by this bit. Then 3 unused bits, then "INVEN" - "Inverty Enable" - it inverts the pin. Writing it HIGH makes the pin go low, HIGH reads as 0, LOW reads as 1. Pullup still pulls up (Why the hell would anyone do that you may be wondering - two main reasons: First, you may want to invert the output of some peripheral. (say you've got a morse-code like protocol you're emulating to control some thingamajig, with a 1 being - I dunno, a 800us high then 200us low and a 0 being say 200us. high 800ms low; set the UART to 5k baud (200us per bit). encode two bits per character (remember, LSB is sent first, and there's a start bit, so the low nybble is either 0b1111 (for a 0) or 0b1000 for a 1. Then the second nybble handles the second bit, now we don't have a start bit, but we do have a stop bit. So those get encoded 0b1110 and 0000 fo on the second nybble. As you may notice, this doesn't give us what we want - it gets us the inverse of what we want, but that's just as good, because we can simply invert the pin! The other application applies to a small number of things that involve putting a pin's signal onto the event channels, and piping them to certain peripherals (the ones that come to mind are the TCA and TCB clock on event options, which only clock on rising events, TCB input capture (on most or all parts*) can either trigger on a rising edge (EDGE = 0) or both edges (EDGE = 1). But sometimes you need to capture based on only falling edges: Invert the pin)).

On the Dx and Ex series, there is a slightly awkward way to set them on masse documented in the datasheets and in the Ref_Digital for DxCore.

Note that direct register manipulation with the VPORTs, when you're setting or clearing one bit at a time, is not encouraged, because if you can meet the constraints required to use the VPORTs and get it to compile to CBI/SBI, you meet the constraints required to use Fast Digital I/O API extension (also found on the better third party cores maintained by others, and by others I mean MCUdude - between the two of us, I think we cover over 95% of the AVR devices that make sense to use with arduino) See https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/Ref_Digital.md - you can do digitalWriteFast(PIN_PA3, HIGH) for example, and it compiles to the exact same thing that VPORTA.OUT |= 1<< 3 would, while being about 2 orders of magnitude more obvious what it does.

It has the added advantage preventing people who might see your code later and not know bout the "one bit at a time" thing might try to modify it and change two bits at once, but of course just like on classic AVR, that's a no-no - VPORTA.OUT |= 1<<3 is an SBI (1 word, 1 clock, atomic). VPORTA.OUT |= 0x0C (with 2 1-bits) would be rendered by the compiler as in ori out (3 words, 3 clocks, not atomic, so it would need to be wrapped in cli/sei if it's outside of an interrupt, and PORTA is also manipulated from inside any interrupt so to make it atomic would leave you with a 5-word 5 clock contruction (6 and 6 if it's in a function and you do the oldsreg thing), plus (though it also acts as a memory barrier, which usually imposes little penalty, but can potentially pummel you if conditions are adverse - So the overhead of the oldsreg cli sei is between 3 words and 3 clocks, and 67 words and 102 clocks, but what's seen in practice is very strongly biased towards the low end of that range). Meanwhile, doing the same thing using a PORTx.OUTSET/OUTCLR/etc is going to be 3 words 3 clocks no matter how many pins you write, and the set/clr/toggle naturally give you atomicity.

So yeah those documents and that comment should give you more than you wanted to know.

Since you're used to referring to pins by port and bit, you will love the fact that we supply constants named PIN_Pxn (where x is port and n is bit position within it) which always are defined as the arduino pin number of that pin. I've argued about whether my pinout diagrams should have numbers on the pins. I say no, only the names (PIN_Pxn) - I'm seriously trying to ram that convention down everyone's throat - once people start using it, it usually gets converts, and the more people who view that as normal, the more likely it will be used on other projects. Not only does it allow fully unambiguous references to pins in discussions if you're familiar with the Pxn notation, it also makes reading the datasheet easier (no more "Okay, so this is going to come out of PB3, okay, that's the 6th pin on the left side, what pin number does that have (looking at pinout chart) "one, two three, four, five six.... that is pin... 4", instead, "It comes out PB3" is all you need to know, especially if you breakout board is labeled with Pxn notation (like the Rev C 3226/3216 and Rev, D 3227/3217 and 3224/1614 breakout boards I sell on Tindie; I also sell them with 3216/3226/1626. 3216/3227, or 3224/1624/1614 mounted fully assembled if you happen to be in the market)

SpenceKonde commented 9 months ago

Also converting to discussion, as there is no defect in the core.