Error: invalid use of void expression

timb0:
...why it needs to then call upon the blinks function again

When you execute <sTimer2::set(5000, blinksXY), it sets up Timer 2 to generate an overflow interrupt every 5000 milliseconds. The function whose name (pointer) you give it will be executed every time Timer 2 overflows. The way that the Timer2::set() function is defined, the function that you give it can not have any arguments. Therefore the blinksXY() function calls the real worker function, (your original blinks()), with arguments that you have previously set up. After that function completes its task, it returns to blinksXY(), which eventually returns from the interrupt.

Now, getting the stuff to compile is one thing; getting it to "work" is another.

Since the blinksXY() function is part of an interrupt service routine, all interrupts are automatically disabled by the time it is entered (when Timer2 overflows). That means that anything that uses millis() or delay() won't work, since they depend on Timer 0 interrupts.

Now, if all that your program is going to do is blink the lights and it doesn't matter whether everything else is frozen out until blinksXY() (and blinks()) have completed their tasks, then you can make a simple change by enabling the interrupts when you first enter blnksXY(). It may be satisfactory for demonstration purposes, but "real" applications may require some re-organization. Most programmers will recommend that interrupt service routines should not do anything that requires any significant delays (including print statements). Generally speaking, there is a good reason for this.

However...

A simple-minded demo:

#include <MsTimer2.h>

const int ledPin =  13;

// Global variables that will be parameters for
// blinksXY() so that it can call blinks() from
// a Timer 2 interrupt service routine
int blinksX;
int blinksY;

// blinksXY is called from the Timer2 overflow Interrupt
// Serivce Routine
void blinksXY(void)
{
    // Enable interrupts so that Timer 0 can generate timing
    // for delay() and millis()
    sei();
    Serial.print("blinksXY: ");Serial.println(millis());
    blinks(blinksX, blinksY);
}

void blinks(unsigned long time, int amount)
{
    Serial.print("blinks(");Serial.print(time);
    Serial.print(",");Serial.print(amount);
    Serial.println(")");
    for(int i = 0; i < amount; i++) {
        digitalWrite(ledPin, HIGH); 
        Serial.print("1:");Serial.println(millis());
        delay(time);
        Serial.print("2:");Serial.println(millis());
        digitalWrite(ledPin, LOW);
        delay(time);
        Serial.print("3:");Serial.println(millis());
        Serial.println();
    }

}


void setup()
{
    Serial.begin(115200);
    pinMode(ledPin, OUTPUT);      
    blinksX = 200;
    blinksY = 3;
    MsTimer2::set(5000, blinksXY); // 
    Serial.print("Starting MsTimer2 at ");
    Serial.println(millis());
    Serial.println();
    MsTimer2::start();
}

void loop() {
   // loop does nothing.  All of the action
   // is in the Timer 2 interrupt service
   // routine.
}

Output


Starting MsTimer2 at 1

blinksXY: 5001
blinks(200,3)
1:5003
2:5203
3:5404

1:5405
2:5606
3:5807

1:5808
2:6008
3:6209

blinksXY: 10001
blinks(200,3)
1:10003
2:10204
3:10405

1:10406
2:10607
3:10808

1:10809
2:11010
3:11210
.
.
.

Regards,

Dave

Footnote:
The MsTimer2 library functions are good certain things, but they hide some important information. Namely that you are now dealing with interrupts, and things in the "callback" function that you give to Timer2::set() can affect lots of other things in meaningful ways that we might not have known about when we started.

Bottom line: Libraries like MsTimer2 are tools that have been crafted and shared so that some of us won't have to deal with the intricacies of timer registers and other somewhat arcane details of the processor. The downside is that, by protecting us from having to know this to get started, it also may lead us into unknown territory through which we will, somehow, have to learn to navigate. In particular, it has hidden from us the fact that the function that we give it will be executed as part of the timer's overflow interrupt service routine.

That's pretty much a valid description for the entire Arduino Way: Easy to get started without having to learn a lot of the details that people are usually exposed to in programming classes.

It's kind of like learning to ride a bike with training wheels.

But...

After leaving the simple examples and simple applications behind, we may want to have more fun than is possible with the kid's bike. The training wheels eventually may have to come off and we may actually need to learn more than we thought we did.

By that time, of course, we are hooked. There is no turning back once our creative juices have started flowing and we can actually see some fun uses for this stuff.