2

I want to produce quick square waveform for testing purposes. If I will use this code:

void setup()
{
 pinMode(2, OUTPUT);
}

void loop() 
{
 bitSet(PORTD, 2);
 bitClear(PORTD, 2);
} 

I will get expected pulses but I also will get these 6.25 us dips in waveform roughly every 1 ms.

I use latest Arduino 1.6.9 environment.

Whole loop cycle take 1 us which mean 16 instructions (62.5 ns per instruction). So I will get 125 ns high and rest of the time low signal.

Any idea what I'm doing wrong?

Same results if I use asm sbi and sci instead of macros bitSet/bitClear.

Thank you!

Oscilloscope waveform - 62.5 us detail

/*

So after yours advices and little bit further searching I came up with solution to produce clean square waveform. It make 2.66 Mhz and it's probably fastest what you can get:

void setup()
{
 pinMode(2, OUTPUT);
 noInterrupts();   
}

void loop() 
{
 while(true)
 {
 bitSet(PORTD, 2);
 bitClear(PORTD, 2);
 }
} 
6
  • On the Uno, the Timer 0 overflow interrupt kicks in every 2014 µs. This is probably the glitch you are seeing. If you want to avoid it, do not use Arduino core, just plain avr-libc. – Edgar Bonet Apr 6 '16 at 20:35
  • Or disable interrupts. – Majenko Apr 6 '16 at 22:20
  • Whole loop cycle take 1 us which mean 16 instructions”. There are actually only 6 instructions. Two of them take 4 cycles each (call and ret), the others take 2 cycles each (sbi, cbi, sbiw and breq). – Edgar Bonet Apr 7 '16 at 10:19
  • Yes. I should have write "cycles" instead instructions. – Jirka Mikes Apr 7 '16 at 17:53
  • The fastest you can get is 8 MHz: take the “Use a hardware timer” code I posted and change the OCR2x registers to OCR2A = 1; OCR2B = 0;. – Edgar Bonet Apr 7 '16 at 20:04
4

As I explained in my comment, the reason for this glitch is the Timer 0 overflow interrupt kicking in every 1024 µs. This interrupt is used by the Arduino core library for timekeeping. It is the basis of millis(), micros() and delay(). The interrupt is enabled by the init() function in wiring.c. There are several approaches to avoid that:

Avoid the Arduino core initialization

This may be overkill, but since it seems you are already familiar with directly accessing the IO ports of the ATmega chip, it may well suit you. You can use the Arduino IDE without the Arduino core initialization simply by defining your own main():

#include <util/delay.h>

int main(void)
{
    DDRD |= _BV(PD2);        // PD2 as output
    for (;;) {
        PORTD |= _BV(PD2);   // PD2 high
        PORTD &= ~_BV(PD2);  // PD2 low
        _delay_us(0.625);    // 10 CPU cycles, loop time = 16 cycles
    }
}

Notice the extra delay: without it, the loop would complete in 6 CPU cycles (2 per port access, 2 more to loop back). Not doing the Arduino core initialization means you have no timekeeping and no analogRead() or analogWrite(). Other Arduino functions should work normally but some, like Serial.begin() will bring their own interrupts.

Disable interrupts

As suggested by Majenko, this may be the simplest way to have a steady waveform:

void setup()
{
    pinMode(2, OUTPUT);
    noInterrupts();
}

void loop()
{
    PORTD |= _BV(PD2);   // PD2 high
    PORTD &= ~_BV(PD2);  // PD2 low
}

Without interrupts, you have no timekeeping, no serial communications and, obviously, no attachInterrupt(). Most other Arduino functions should work.

Use a hardware timer

This allows you to generate the signal without relying on the CPU. You can keep interrupts enabled and do other work in the program while the waveform goes undisturbed. The simplest way to generate such a waveform is with analogWrite(). The resulting frequency is quite low though, like 0.5 to 1 kHz. If you want 1 MHz, you will have to configure the timer the hard way. For example:

void setup()
{
    DDRD |= _BV(PD3);     // OC2B = PD3 as output
    OCR2A = 15;           // period = 16 clock cycles
    OCR2B = 1;            // high during 2 clock cycles
    TCCR2A = _BV(COM2B1)  // non-inverting PWM
           | _BV(WGM20)   // fast PWM, TOP = OCR2A
           | _BV(WGM21);  // ditto
    TCCR2B = _BV(WGM22)   // ditto
           | _BV(CS20);   // clock at f_CPU
}

void loop() { }

The output is on pin PD3 = digital 3. This configuration will disable analogWrite() on pins 3 (PD3) and 11 (PB3).

5
  • What piece of Arduino starts this timer / implements it's interrupt (you should kill it at it's roots? So you can still use other interrupts)? And is there an easy way to see which pieces of code depend on the timed interrupt of Arduino? – Paul Apr 7 '16 at 11:27
  • 1
    @Paul: It's all in the wiring.c file I already linked to. The interrupt service routine only updates three variables: timer0_millis, timer0_fract and timer0_overflow_count. These are only used inside wring.c. You can see what depends on the interrupt by searching for these variables through the file: only millis() and micros(). But then delay() depends on micros(), Stream on millis(), and some USB stuff (not on the Uno) on delay(). – Edgar Bonet Apr 7 '16 at 12:18
  • Cool, so I could disable this interrupt and still use libraries that use interrupts (such as servo/softserial). Or well, I would have to check if they rely on millis() or micros(). – Paul Apr 7 '16 at 14:46
  • @Paul: That's correct, but any enabled interrupt would cause the kind of glitch seen here. – Edgar Bonet Apr 7 '16 at 15:10
  • So I added noInterrupts(); in my code and now the waveform is perfect! Thank you very much @Edgar Bonet! And I will try other methods too. There is still so much to learn. I am familiar with Arduino timer but I thought it doesn't affect running program. BTW You all probably know it but these microcontrollers are so cool :D haha – Jirka Mikes Apr 7 '16 at 17:46
1

If you just want a rough square wave to test and you don't care about the frequency then try this:

void setup()
{
   pinMode(2, OUTPUT);
}

void loop() 
{
   digitalWrite(2, true);
   delay(2);
   digitalWrite(2, false);
   delay(2);
} 
3
  • 1
    If you don't care about the frequency, analogWrite(3, 128); is simpler. – Edgar Bonet Apr 7 '16 at 12:31
  • @sa_leinad: I care about frequency so I used bitSet/bitClear to make it quicker. Now I have clean 1 Mhz waveform (thanks to Edgar) which I will never achieve with standard digitalWrite. – Jirka Mikes Apr 7 '16 at 18:00
  • Could you please edit your question to reflect this information. – sa_leinad Apr 8 '16 at 3:50
1

I want to produce quick square waveform for testing purposes.

Whole loop cycle take 1 us which mean 16 instructions (62.5 ns per instruction). So I will get 125 ns high and rest of the time low signal. Any idea what I'm doing wrong?

You are assuming that what goes on outside loop() and the call to loop() takes no time at all.

This is what the Arduino core main() looks like.

int main(void)
{
    init();

    initVariant();

#if defined(USBCON)
    USBDevice.attach();
#endif

    setup();

    for (;;) {
        loop();
        if (serialEventRun) serialEventRun();
    }

    return 0;
}

There is both the call to loop(), if-branch, and for-branch. This all adds up. On top of this there are a number of Interrupt Service Routines (ISR) such as for the Timer which is used for the micros() and millis() count. These will interleave periodically (as you have observed).

Now the question is how to fix this and I think @Edgar Bonet has done an excellent job with that.

Cheers!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.