SPI in ISR

When I try to generate a wave using T2, and add an ISR for TIMER2_COMPB_vect, everything works fine. But as soon as I try to output a byte in the ISR, using SPI.transfer(data), the timer operation stops and the byte is transferred over and over again, instead of only once per interrupt :frowning:
Of course the timer interrupt rate (1ms) is much lower than it takes to transmit a single byte.

I already found a discussion of such an issue in the forum, but no solution. I already tried to clear the timer interrupt flag and enable interrupts() in the ISR, didn't change anything.

Even worse: when I only try a digitalWrite to toggle pin 2 in the ISR, the timer operation stops and the pin changes every 14µs. When I use PORTD to toggle the pin, everything works as it should!

So what's the special relationship between the T2 timer interrupt and other standard functions?

I think the transfer does not use an interrupt itself.
https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/libraries/SPI/src/SPI.h
But the SPI bus could be used with interrupts, that might cause trouble when called from an ISR.

A digitalWrite should cause no problem in a ISR.
There is nothing special about Timer2 and the standard functions. There must be something special that you do. Either with hardware connections, or in the sketch, or with a timing problem like a ISR overflow, or a very old Arduino IDE, and so on.
Some kind of ISR overflow could cause this.

Which Arduino board are you using ? What is connected to which pins ? Can you show the sketch ?

I'm using an Uno, nothing else connected, it's just a proof of concept. IDE 1.6.7.

The short ISR is

ISR(TIMER2_COMPB_vect)
{
 digitalWrite(2, !digitalRead(2)); //repeats indefinitely
 //PORTD ^= 4; //works
}

Can you give a working sketch, so I can test it ?

This is a configurable test sketch for T2 interrupts and SPI.

//Test T2 ISR

#include <SPI.h>

const int pulse = 3; //OC2B
const int test = 11; //OC2A, conflicts with SPI

int f = 0x80;
int duty = 0x10;

volatile byte data = 0; // 0xAA;

void setup() {
  pinMode(pulse,OUTPUT);
  pinMode(test,OUTPUT);
  pinMode(2, OUTPUT);

//SPI first
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV16);

//T2
#if 0
//these make it work :-)
  OCR2A = f;
  OCR2B = duty;
#endif

  //TCCR2A = 0b01100011; //fast PWM, test toggle OC2A
  TCCR2A = 0b00100011; //fast PWM
  TCCR2B = 0b00001100;
  TIMSK2 = 0b100; //OCIE2B
}

unsigned long t0 = 0;

void loop() {
  if (millis() - t0 < 200) return;
  t0 = millis();
  //noInterrupts();
#if 0
//use pots to adjust frequency and duty
  int f = analogRead(A0) >> 2;
  int duty = analogRead(A1) >> 3;
  OCR2A = f;
  OCR2B = duty;
#endif
  data++;
  interrupts();
}

ISR(TIMER2_COMPB_vect)
{
#if 0 //1 for longer ISR
  SPI.transfer(data);
  digitalWrite(2, !digitalRead(2));
#else //very short ISR
  PORTD ^= 4;
#endif
}

It may not work (intentionally), unless the #if’s are adjusted.

In the ISR a short or long version can be configured. The short version happened to work for me immediately.

In loop() an optional manual adjustment of the PWM frequency and duty cycle can be configured.
If you connect two voltage sources (pots…) to A0 and A1, you can enable this test.
Problematic is the noInterrupts(), intended to protect the update of the SPI data variable (finally an array shall be used). Please test by removing the //.

My bug resides in setup(), where I missed to initialize OCR2A and OCR2B. The sketch worked with the short ISR, initializing in loop(), and that’s why I missed this omission.

I used the sketch, and I have connected my logic analyzer, but I don't know what to do.
I don't have potentionmeters at the moment at hand.

Do you use pin 10, SS, as Chip-Select ?

This:

  PINB = bit(0);   // toggle pin 8
  digitalWrite(2, !digitalRead(2));
  PINB = bit(0);   // toggle pin 8

Gives a pulse of 9.2us at pin 8, that is normal.

The ATmega328P has an extra option to toggle a pin. When a pin is set a output, writing a '1' to its bit in PINB will toggle the output. In setup() I have pinMode ( 8 , OUTPUT ) ; and after that I can do PINB = bit ( 0 ) ; to toggle the output.

The loop runs at 2.5Hz, that is according the 200ms with millis().
I can see the burst of data for SPI.

Should the SPI be like this ?

  digitalWrite( SS, LOW);
  SPI.transfer(data);
  digitalWrite( SS, HIGH);

There is a specific order to set a timer. I think you have done it right.
I have added this to setup() :

//T2
  TCCR2A = 0;
  TCCR2B = 0;
  TIMSK2 = 0;

But that makes no difference.

The frequency stays at 1.9kHz with the default values, regardless the short or long code in the ISR.
I do have the interrupts disabled while incrementing the 'data'. And the timer registers are filled with the default values.

I don't see the problem.

The sketch is only a test, for later use with shift registers. So no SS, a test signal on pin 2, LE=BL on 3, and SCLK and MOSI.

It's intended for a multiplexed Nixie display, with (finally) 3-4 bytes shifted out in the ISR. The PWM signal must stay unaffected by whatever other code is executing, for proper timing of firing and extinguishing a digit.

The data++ revealed that data must be declared as volatile, else only the initial value is output in the ISR.