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);
 }
} 
asked Apr 6, 2016 at 20:17
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. Commented Apr 6, 2016 at 20:35
  • Or disable interrupts. Commented Apr 6, 2016 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). Commented Apr 7, 2016 at 10:19
  • Yes. I should have write "cycles" instead instructions. Commented Apr 7, 2016 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;. Commented Apr 7, 2016 at 20:04

3 Answers 3

3

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).

answered Apr 7, 2016 at 9:12
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? Commented Apr 7, 2016 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(). Commented Apr 7, 2016 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(). Commented Apr 7, 2016 at 14:46
  • @Paul: That's correct, but any enabled interrupt would cause the kind of glitch seen here. Commented Apr 7, 2016 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 Commented Apr 7, 2016 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);
} 
answered Apr 7, 2016 at 12:13
3
  • 1
    If you don't care about the frequency, analogWrite(3, 128); is simpler. Commented Apr 7, 2016 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. Commented Apr 7, 2016 at 18:00
  • Could you please edit your question to reflect this information. Commented Apr 8, 2016 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!

answered Apr 7, 2016 at 9:33

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.