Changing output pin in assembly with Due ARM+asm? (meeting ~1us timing)

Hello there~

I've recently picked up an Arduino Due and I'm currently trying to make it work as a gamecube controller (so it writes the controller outputs rather than the more common reading inputs). According to the timing of the GC. I need to be able to send data pulses over 4 microseconds, but each pulse is only high for either 1us or 3us, which is a little easier to time with than, say, the Uno.

However, in order to minimize any possible timing issues, I'm planning on using asm to properly time the pulses. At the moment, I have a fair idea of how to do this (basing off of this wonderful piece of code), but I'm not sure how to write to a pin in ARM without cbi.

As a side note, I apologize if the solution obvious- I've only found out about asm a few days ago, so I'm not sure if this is even the proper route to go (as the Due had a higher learning curve than I thought with less examples to learn from). Currently, my plan is to take digitalwritedirect (found here and here), compile it, and then check the assembly to see the exact timing then to use the assembly itself. However, I'm not even sure if that's applicable here at all with asm.

Finally, I did this digitalWrite for the SAMD core, and the relevant parts with the pull-up resistor worries me. Is a pull-up resistor relevant at all to the Due's SAM38XE CPU and - I apologize for throwing this here on top of everything else - is a pull-up resistor necessary at all for this application? I don't see how it's exactly relevant for the application and timing (then again, I've never really understood entirely the purpose of pull-up resistors).

Thanks for at least reading this post through! This is the one key obstacle I've been having trouble getting over, and I appreciate any and all help!

I apologize about the pull-up resistor question, that was a terrible question. First off, I shouldn't need the pull-up resistor as I can choose the line to be high (and I don't need to care about the line before setup() runs). Furthermore, the SAM38XE spec states (section 31.5.6 Multi Drive Control (Open Drain)) that it is possible by writing to the PIO_MDER register, but it shouldn't be necessary (albeit I'll need to look into that separately).
(Edit2: In case anyone is curious, I was incorrect as the communication is bidirectional over a single line, so it needs to be open drain and I need to access the PIO_MDER register for the pin)

As for the primary topic, I found this beautiful question. Thus, I should be able to use the following to enable port 13:

asm volatile("mov r0, %[val]"::[val] "r" (&REG_PIOB_SODR));
asm volatile("mov r1, #1, lsl #27");
asm volatile("str r1, [r0]");

Two questions:

  • Can I access all registers via REG_(REGNAME) (such as %REG_PIO_MDER)? And in the spec I found PIO_SODR instead of PIOB_SODR- I'm not sure where this discrepancy comes from at all or where to find out how it applies to the Due.
  • Why is the 1 left shifted by 27 instead of, say, 12?

Thank you!

In order to minimize any possible timing issues, I'm planning on using asm to properly time the pulses. At the moment, I have a fair idea of how to do this ... but I'm not sure how to write to a pin in ARM without cbi.

That's probably silly. ARM registers are simply memory locations, and the compiler can probably write to them just as efficiently as you can in assembler. Besides, ARMs are generally not set up to be programmed in assembler (ie, I don't know if you can find symbol definitions for the peripherals in assembler syntax.)

inline void digitalWriteDirect(int pin, boolean val){
  if(val) g_APinDescription[pin].pPort -> PIO_SODR = g_APinDescription[pin].ulPin;
  else    g_APinDescription[pin].pPort -> PIO_CODR = g_APinDescription[pin].ulPin;

g_APinDescription is an array that translates Arduino "pin" numbers to peripheral port (a structure) definitions (pPort) and bit masks (ulPin). PIO_SODR and PIO_CODR are fields in the structures for setting a clearing bits. If I wanted to quickly set and unset them, I'd do something like:

  pPort = g_APinDescription[pin].pPort;
  pBit = g_APinDescription[pin].ulPin;
  for (...) { // loop of some kind.
    pPort->PIO_SODR = pBit;   // set bit
    pPort->PIO_CODR = pBit;

(alas, delay_us() seems to be an ASF function that the arduino code doesn't include, so you may have to copy it in from elsewhere...)

This will produce code (inside the loop) that looks like:

pPort->PIO_SODR = pBit;
   8014e:       631a            str     r2, [r3, #48]   ; 0x30
   80150:       bf00            nop     ;; (Fake "delay" code)
   80152:       bf00            nop
   80154:       bf00            nop
  pPort->PIO_CODR = pBit;
   80156:       635a            str     r2, [r3, #52]   ; 0x34

(Oh, I forgot to mention that even if you program in assembler, it's really hard to get cycle-accurate delays on ARM chips. At higher clock rates, there are usually wait states added to flash memory accesses, and those interact with assorted "flash acceleration" schemes in mysterious ways. The delay_us() function I mentioned runs in RAM to avoid this, but THAT seems overly complicated too...) (Still, at 84MHz, you should be able to come pretty close to "long" delays like a whole 4us - that's like 300 cycles!)

See also Samd21 Delay functions | AVR Freaks

Thank you for the info!

For digitalWriteDirect, that makes sense, and the 84 MHz processor should give plenty of room to avoid timing issues. Guess I should go down the easier route for now before assuming there'll be significant issues.

As for delay_us, I did find this delayMicroseconds native Arduino library function, but I put it aside as there's a note near the bottom that states that delays less than 3us may be imprecise. However, the documentations are usually written in mind with the more common 16MHz Atmega processors, so this function should certainly work for me (and if not, I can always glance at the source code to see if I can modify anything).

Thank you for making me back away from assembly when it's unnecessary (as well as explaining how that digitalWriteDirect works)!

On a side note to see if I properly understand this, to enable open drain on a pin would this work:

g_APinDescription[pin].pPort -> PIO_MDER = g_APinDescription[pin].ulPin;

(PIO_MDER is the register that's 0x0 on reset to disable all pins for using this feature, and I'm assuming ulPin is just the proper bitmask)