digitalWriteFast() unexpected behavior

Status
Not open for further replies.

zike

Member
Greetings-

I'd like to toggle a pin a bit faster than the ~ 2 MHz I get with successive digitalWrite() calls in a for() loop, to get a specific-length
burst out quickly.

I stumbled on the function digitalWriteFast() in some other threads, and it compiles without warnings, but it doesn't appear to work (at least not in what I'd consider a rational way).

If I substitute digitalWrite() inside the for() loop in the attached code, I get exactly the specified number of pulses a little
over 500ns apart (mostly; a few pulses per thousand are delayed a µs or two, but there's always the correct total number).

With digitalWriteFast() as shown, I get from 0 to 10 pulses, each 750 or 1200 ns long, at seemingly random intervals each time the loop executes.

Inserting a µs delay between the digitalWriteFast() calls does not cure the problem.

Changing the *first* call to a plain digitalWrite() results in a larger, but still random number of pulses less than requested.

Changing the *second* (LOW) call to plain digitalWrite() completes the specified number, but pulse time is only slightly faster than with both plain calls.

I could not easily find 'official' docs on digitalWriteFast(), maybe it's no longer supported? Sorry, I'm new to this!
Any guidance appreciated,

zike

Code:
#include <TimeLib.h>
#define ledPin 13               // to counter input
#define resetPin 37             // to counter reset 

elapsedMillis r = 0;            // update timer
elapsedMicros w = 0;

void setup() {
  Serial.begin(115200);
  pinMode(resetPin, OUTPUT);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  long nout = 235959;
  if (r > 1000) {
    r = 0;                          // reset update timer
    w = 0;
    digitalWrite(resetPin,  HIGH);  // send reset pulse to counter
    digitalWrite(resetPin,  LOW);
    while(r<30){} ;                 // delay to re-arm counter after reset pulse
    for (long p = 0; p < nout; p++) {
      digitalWriteFast(ledPin, HIGH);
      digitalWriteFast(ledPin, LOW);
    }
    Serial.println(w/1000);
  }
}

(using Teensy 3.5, Arduino 1.8.2, Teensyduino 1.40)
 
I experienced a similar problem some time ago. digitalWriteFast() is basically translated into just a write in the Port register. The compiler sees that you are writing a 1 and then a 0 to the same place in the next cycle and I suspect that it optimizes this "nonsense" (in his eyes) away. I remember that disabling the Fast and LTO optimizations solved that problem.

You might also try to do a digitalReadFast in-between, so that the compiler understands that you are doing something useful.

Code:
digitalWriteFast(ledPin, HIGH);
bool z = digitalReadFast(ledPin); // assign the read value to a variable
digitalWriteFast(ledPin, (z?!z:z)); // use the variable to not to be optimized away
 
You need to turn off slew rate limiting, which is on by default.
[/url]

Thanks Paul, that indeed did the trick, in particular this line
Code:
CORE_PIN14_CONFIG = PORT_PCR_MUX(1); // no slew rate limit
after the pinMode() declaration. Doing this, I get nice 10ns pulses at 20MHz. I suppose this strange throttling action is some kind of EMC measure? Very annoying.

On deeper investigation, without the switch the teensy was just putting out little 50mV runts buried in the noise; the chaotic behavior reported above was just due to unrelated processor interrupts, which occasionally allowed a runt to 'grow up.' I verified this by setting noInterrupts(), which only leaves the uncountable runts.

@ manitou -- Tx, just using an oscilloscope and frequency counter, nothing special. BTW before posting I did read many of the 455 (!!) forum threads including the term "digitalWriteFast()"; and googled it outside, as well; but honestly, even if I did read every message, I may have missed the magic sauce buried in message #18 of the 74th thread ...

@Theremingenieur -- As noted, Paul's suggestion has it working without this. By whatever lucky accident my compiler was not quite so smart. That said, a certain antique Nixie-tube counter I want to use (for artistic reasons) can't quite do 20 MHz; so I used your trick of dummy digitalReadFast() reads (two of them) to slow it down to about 12 MHz, and now everyone is happy. Thanks!

Which raises the question: has anyone considered providing a delayNanoseconds() function?

Cheers,

zike
 
Status
Not open for further replies.
Back
Top