Problems defining static volatile method variables while using ISR in .cpp file

Hi. Hope everyone’s keeping healthy…at least we have more time to work with Arduino at the moment…

Quick breakdown of the problem - Using the internal watchdog timer to check for timeout between rising edges on an input pin.
I’ve previously used ISR vectors defined in the sketch .ino file to use the WDT to light up an LED. Now I’m trying to get some friends into Arduino so trying to convert the .ino file into a library .h with .cpp source.

It all compiles fine until the ISR is added into the library - declaring the variables to be called from the ISR as extern in the .h file, then defining them in the .cpp file also works OK with int and bool datatypes, but not a union struct typedef…

Have attached code in the following order:

  1. .ino sketch file with WDT ISR

  2. What i want the .ino file to look like to my friends

  3. .cpp source to implement the clean .ino file

  4. .h header for wdtTest class

const uint32_t TimeoutTimer = 1500UL;

uint32_t timeoutCounter;

typedef union
{
 struct {
 uint32_t msg[8];
 uint8_t len;
 } d32;
 struct {
 uint8_t dat[32];
 uint8_t len;
 } d8;
} MessageDataTypeDef;

volatile MessageDataTypeDef testmsg;

volatile bool testflag;

ISR(WDT_vect){
 //wdt.inthandle();
 pinMode(13,OUTPUT);
 digitalWrite(13,OUTPUT);
 testmsg.d8.len=45;
 testmsg.d8.dat[0]=SPCR;
 testflag=1;
 //reset WDRF
 MCUSR &= ~(1<<WDRF);
 //end WDT
 WDTCSR = (1<<WDCE);
}

void setup() {
  
  while(millis()<500UL);

 Serial.begin(115200);
  delay(250);
 pinMode(13,OUTPUT);
 digitalWrite(13,HIGH);
  delay(1000);
 Serial.println("Starting...");
 digitalWrite(13,LOW);
  delay(1250);

 //setup wdt
  noInterrupts();
  //reset WDRF
  MCUSR &= ~(1<<WDRF);
  WDTCSR = (1<<WDCE)|(1<<WDE);
  //set prescaler ~ 2000ms - interrupt enabled - clear WDE for int
  WDTCSR = (1<<WDP2)|(1<<WDP1)|(1<<WDP0)|(1<<WDIE);//|(1<<WDCE);//2000ms
  //WDTCSR = (1<<WDP3)|(1<<WDP0)|(1<<WDIE);//|(1<<WDCE);//8000ms
  asm ("wdr \n");
 interrupts();
 asm ("wdr \n");

 //test 1
 timeoutCounter=millis();
  asm ("wdr \n");
 while((millis()-timeoutCounter)<TimeoutTimer) asm ("wdr \n");
 Serial.println("WDT reset stopped...");
 delay(250);
 while(!testflag);
 Serial.println("WDT interrupt...");
 delay(250);
 Serial.println(testmsg.d8.len);
 delay(250);

 Serial.println("Done!");

}
void loop() {}
#include "wdtTest.h"

const uint32_t TimeoutTimer = 1500UL;

uint32_t timeoutCounter;

MessageDataTypeDef testmsg;

wdtTest wdt = wdtTest();

void setup() {

 wdt.begin();

 Serial.begin(115200);
 delay(250);
 Serial.println("Starting...");

 //setup wdt
 wdt.wdT_init();

 //test 1
 timeoutCounter=millis();
 while((millis()-timeoutCounter)<TimeoutTimer) wdt.wdT_reset();
 Serial.println("WDT reset stopped...");
 delay(250);
 while(!wdt.retFlag());
 Serial.println("WDT interrupt...");
 delay(250);
 testmsg.d8.len=wdt.retLen();
 Serial.println(testmsg.d8.len);
 delay(250);

 Serial.println("Done!");

}
void loop() {}
#if ARDUINO >= 100
 #include "Arduino.h"
#else
 #include "WProgram.h"
#endif

#include "wdtTest.h"

static volatile MessageDataTypeDef wdtTest::spimsg={
 .d32.msg[0]=0,
 .d32.msg[1]=0,
 .d32.msg[2]=0,
 .d32.msg[3]=0,
 .d32.msg[4]=0,
 .d32.msg[5]=0,
 .d32.msg[6]=0,
 .d32.msg[7]=0,
 .d32.len=0
};
static volatile bool wdtTest::spiflag=0;

wdtTest::wdtTest(){}

ISR(WDT_vect){
 pinMode(13,OUTPUT);
 digitalWrite(13,OUTPUT);
 wdtTest::spimsg.d8.len=45;//test var
 wdtTest::spimsg.d8.dat[0]=105;//test var
 wdtTest::spiflag=1;
 wdtTest::wdT_end();
 //wdtTest::inthandle();
}

bool wdtTest::begin(){
 wdtTest::spimsg.d8.len=0;
 for(byte i=0; i<32; i++) wdtTest::spimsg.d8.dat[i]=0;
 wdtTest::spiflag=0;
 return 1;
}

bool wdtTest::wdT_init(){
 //timed sequence
 noInterrupts();
 //reset WDRF
 //MCUSR &= ~(1<<WDRF);
 MCUSR = 0;
 WDTCSR = (1<<WDCE)|(1<<WDE);
 //set prescaler ~ 2000ms - interrupt enabled - clear WDE for int only
 WDTCSR = (1<<WDP2)|(1<<WDP1)|(1<<WDP0)|(1<<WDIE);
 interrupts();
 return 1;
}

static volatile bool wdtTest::wdT_end(){
 //reset WDRF
 //MCUSR &= ~(1<<WDRF);
 MCUSR = 0;
 //end WDT
 WDTCSR = (1<<WDCE);
 return 1;
}

bool wdtTest::wdT_reset(){
 asm ("wdr \n");
 return 1;
}

uint8_t wdtTest::retLen(){
 uint8_t ret;
 ret = wdtTest::spimsg.d8.len;
 return ret;
}

bool wdtTest::retFlag(){
 bool ret;
 ret = wdtTest::spiflag;
 wdtTest::spiflag=0;
 return ret;
}

static volatile void wdtTest::inthandle(){
 pinMode(13,OUTPUT);
 digitalWrite(13,OUTPUT);
 wdtTest::spimsg.d8.len=45;
 wdtTest::spimsg.d8.dat[0]=SPCR;
 wdtTest::spiflag=1;
 wdtTest::wdT_end();
}
#ifndef _WDTTESTH_
#define _WDTTESTH_

#if ARDUINO >= 100
 #include "Arduino.h"
#else
 #include "WProgram.h"
#endif

//extern bool spiflag;
//extern MessageDataTypeDef spimsg;

typedef union
{
 struct {
 uint32_t msg[8];
 uint8_t len;
 } d32;
 struct {
 uint8_t dat[32];
 uint8_t len;
 } d8;
} MessageDataTypeDef;

class wdtTest{

 public:

 extern MessageDataTypeDef spimsg;
 extern bool spiflag;

 //class constructor
 wdtTest(void);

 bool begin(void);

 bool wdT_init(void);
 bool wdT_reset(void);

 uint8_t retLen(void);
 bool retFlag(void);

 static volatile void inthandle(void);
 
 //friend void WDT_vect(void);

 private:

 //extern MessageDataTypeDef spimsg;
 //extern bool spiflag;

 static volatile bool wdT_end(void);
 

};

#endif

The problems seems to come from my declaration/definition of the the typedef union struct which needs to be accessed from within the ISR.

Have tried a bunch of different things, with the following error messages showing up:

Variable Declaration Location Definition Location ID Error
spimsg static volatile BmcMessageDataTypeDef _spimsg=NULL; .h class A{ public: 1 conversion from ‘int’ to non-scalar type ‘volatile BmcMessageDataTypeDef’ requested
spiflag static volatile bool _spiflag=0; .h class A{ public: 1 ISO C++ forbids in-class initialization of non-const static member ‘A::_spiflag’
spimsg static volatile BmcMessageDataTypeDef _spimsg; .h class A{ public: A::_spimsg.d8.len=0; for(uint8_t i=0; i<32; i++) A::_spimsg.d8.dat*=0;[/td]
.cpp bool A::begin(){
3
In function begin': undefined reference to A::_spimsg’
[/tr]
spiflag
static volatile bool _spiflag;
.h class A{ public:
A::_spiflag=0;
.cpp bool A::begin(){
3
In function begin': undefined reference to A::_spiflag’
spimsg
extern BmcMessageDataTypeDef _spimsg;
.h [global]
static volatile BmcMessageDataTypeDef A::_spimsg={.d32.msg[0]=0; …;.d32.len=0;};
.cpp [global]
4
’_spimsg’ is not a member of ‘A’
spiflag
extern bool _spiflag;
.h [global]
static volatile bool A::_spiflag=0;
.cpp [global]
4
’_spiflag’ is not a member of 'A’
spimsg
extern BmcMessageDataTypeDef _spimsg;
.h class A{ public:
static volatile BmcMessageDataTypeDef A::_spimsg={.d32.msg[0]=0; …;.d32.len=0;};
.cpp [global]
5
storage class specified for '_spimsg’
spiflag
extern bool _spiflag;
.h class A{ public:
static volatile bool A::_spiflag=0;
.cpp [global]
5
storage class specified for ‘_spiflag’
[/table]*
Sorry about the shoddy formatting and inconsistency between variable names (above table was run as a test script) but in essence the bits that changed are:
ID 1 - static volatile variables declared and defined as public in .h class
ID 2 - identical to ID 5
ID 3 - static volatile variable declared public in . class; defined in A::begin() method
ID 4 - extern variables declared in .h at global scope; defined as static volatile in .cpp at global scope
ID 5 - extern variables declared public in .h class; defined as static volatile in .cpp at global scope
Feels like I’m just going round in circles and like there’s something fundamental I’m missing.
If anyone could help out with this it would actually be amazing!

Don't use external variables, define set/get methods to access the global variables of your library.