Interrupt service routine within a c++ class

jwalters1955
Posts: 1
Joined: Sun Nov 10, 2024 3:07 pm

Interrupt service routine within a c++ class

Postby jwalters1955 » Sun Nov 10, 2024 4:47 pm

I am building an application which receives a Manchester Encoded data stream from a simple 433MHz radio receiver. The code was originally written in 'C' for an ATMega328p using the Arduino IDE and uses a pin change interrupt and free running timer. It worked well.
For my new project I have migrated to VSCode/PlatformIO and am using an ESP32 S3. With a few small adaptations, the 'C' coded receiver is now working well on the ESP32 in a test application. However, in order to simplify the integration of the receiver into the main project, I want to create the receiver as a C++ class.
The main challenge here is including the Interrupt Service Routine within the class as a class method. The function must be declared static as must any other methods and attributes referenced by the ISR. As an added complication, I want to make the class into a library and so to avoid having to modify the code for each project I want to be able to set the size of the receive buffer when the object is created.
In order to achieve this, I have declared an array for the buffer which is not static so can be created by the constructor and a static pointer to that array which is initialised by the constructor.
I believe I have created the class in accordance with the rules. The code compiles fine but the linker returns an "Undefined Reference" error for each of the class attributes referenced within the ISR function.

The error is as follows;
Linking .pio\build\esp32dev\firmware.elf
c:/users/john/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld.exe: .pio\build\esp32dev\src\McrRx_Lib.cpp.o:(.literal._ZN11mcrReceiver19sigInterruptHandlerEv+0x8): undefined reference to `mcrReceiver::pulseTimer'
c:/users/john/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld.exe: .pio\build\esp32dev\src\McrRx_Lib.cpp.o:(.literal._ZN11mcrReceiver19sigInterruptHandlerEv+0x10): undefined reference to `mcrReceiver::rxPort'
c:/users/john/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld.exe: .pio\build\esp32dev\src\McrRx_Lib.cpp.o:(.literal._ZN11mcrReceiver19sigInterruptHandlerEv+0x18): undefined reference to `mcrReceiver::readyFlag'
c:/users/john/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld.exe: .pio\build\esp32dev\src\McrRx_Lib.cpp.o:(.literal._ZN11mcrReceiver19sigInterruptHandlerEv+0x2c): undefined reference to `mcrReceiver::receiveStatus'
c:/users/john/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld.exe: .pio\build\esp32dev\src\IS_Server.cpp.o:(.literal.startup._GLOBAL__sub_I_rtc+0x0): undefined reference to `mcrReceiver::pRxBuffer'

Can someone give me some idea about what I may have got wrong.

The class declaration in McrRx_Lib.h is as follows, Note I have altered the way some elements are declared static or public/private in an attempt to fix the problem but nothing has worked.
  1. class mcrReceiver {
  2.   public:
  3.  
  4. // Now for some enumerations used by the ISR
  5. // The pulse categorisations, the data stream is edge driven.
  6.   typedef enum {
  7.     invalidPulse,   // Pulse length outside of acceptable limits
  8.     shortRising,    // Rising edge after a short low
  9.     shortFalling,   // Falling edge after a short high
  10.     longRising,     // Rising edge after a long low
  11.     longFalling,    // Falling edge after a long high
  12.     msgComplete     // The long high stop pulse which marks the end of a sequence
  13.   } validPulse ;    
  14.  
  15. // The stages the receiver will go through during a sequence.
  16.   typedef enum {
  17.   //  rxStRunning,  // Not currently used
  18.     rxStSyncLo,   // Sync stages before actual message starts
  19.     rxStSyncHi,
  20.     rxStSyncStart,// Still receiving sync pulses but expecting a start marker
  21.     rxStStart,    // Start marker received and awaiting the first data pulse
  22.     rxStPhase1,   // The phases of receiving a data bit
  23.     rxStPhase2,
  24.     rxStMsgEnd,   // Indicates we've received the stop pulse
  25.     rxStError,    // oops, Terminate reception, reset receiver and report error to user
  26. //    rxStIdle,     // Not currently used
  27.     rxStOverrun,  // Buffer overrun. Too many valid pulses but no stop received
  28. //    rxStCount     // Not currently used
  29.   } rxStage ; //
  30.  
  31. // A structure to report the completion status of a receive sequence
  32.   typedef struct {
  33.       rxStage     exitStage;    // The stage of the sequence when we terminated.
  34.       rxStage     exitStatus;   // Indicator of where it went wrong if status is an error
  35.       validPulse  lastPulse;    // The type of the last pulse we received
  36.       int         exitCount;    // The number of good bytes we had received when we terminated the receive sequence
  37.   } exitData ;
  38.  
  39.   static  volatile  bool  readyFlag ; // To signify a complete message in the receive buffer
  40.   static  volatile uint8_t   *pRxBuffer;       // Pointer to the receive buffer. used by the ISR so must be static
  41.   volatile  uint8_t *rxBuffer ;                 // The actual receive buffer which is sized by the constructor so can't be static
  42.   static  volatile exitData  receiveStatus; // A record of how a receive sequence ended
  43.   static  hw_timer_t  *pulseTimer ;   // The timer which will tell us the lenghts of the pulses
  44.   static  int  rxPort ;               // The port to which we will attach the receiver interrupt
  45.  
  46.   mcrReceiver (int bufLen) : rxBuffer(new uint8_t[bufLen]) {pRxBuffer = &rxBuffer[0] ;};    // Constructor. Sizes the receive buffer and sets a static pointer to it
  47.   void  startReceiver(int  portNum);    // Allows the user to start up the receive process (May consider a function to stop it if necessary)
  48.   int   getMessage (char*  msgBuffer, int bufLen);  // Allow the user task to check for and collect a new message
  49.   exitData getRxStatus () ;                         // Allow user task to see how it ended
  50.  
  51. //  private:
  52. // Start with some values to categorise the incoming pulses. See above for value definitions
  53.   static const int minSync = MIN_SYNC;
  54.   static const uint64_t shortMaxHigh = SHORT_MAX_HIGH ;
  55.   static const uint64_t shortMinHigh = SHORT_MIN_HIGH ;
  56.   static const uint64_t shortMaxLow = SHORT_MAX_LOW ;
  57.   static const uint64_t shortMinLow = SHORT_MIN_LOW ;
  58.   static const uint64_t longMaxHigh = LONG_MAX_HIGH ;
  59.   static const uint64_t longMinHigh = LONG_MIN_HIGH ;
  60.   static const uint64_t longMaxLow = LONG_MAX_LOW ;
  61.   static const uint64_t longMinLow =LONG_MIN_LOW ;
  62.   static const uint64_t minEnd = MIN_END ;
  63.   static const uint64_t maxEnd = MAX_END ;
  64.  
  65.   static validPulse validatePulse (uint64_t len, bool state); // To categorise the pulses
  66.   static void sigInterruptHandler() ; // The ISR function that implements the receiver
  67.  
  68. };
The abbreviated ISR function in McrRx_Lib.cpp is....
  1.  
  2. void ICACHE_RAM_ATTR  mcrReceiver::sigInterruptHandler()
  3. {
  4. // Variables that must hold their values between interrupts
  5. static  rxStage     rxState = rxStSyncLo ;      // SyncLow is our starting point. a low to high transition starts everything off
  6. static  uint8_t     bitMask = 0x01 ;            // To set bits in the current byte if required
  7. static  volatile uint8_t*    pCurrentByte = pRxBuffer;   // A pointer to the current byte in the receive buffer
  8. static  int         rxIndex = 0;                // The index of the current byte in the buffer
  9. static  int         syncCount = 0;              // Tally of the number of sync bits we have received
  10. static  bool        needsReset = true ;         // Tag to tell us when we need to reset the receive parameters
  11.  
  12. // Things we'll only use once per call
  13. rxStage exitStage ;                             // A record of the receiver state when we exit. Mainly for error detection by the main task.
  14. validPulse  pulseType;                          // To categorise the pulse we've just received.
  15.  
  16.     // First get all of the information about the interrupt
  17.     uint64_t intPulseLen = timerRead(pulseTimer);       // Get the length of the pulse
  18.     timerWrite(pulseTimer,0);                           // Restart the timer ASAP in order to time the next pulse
  19.     bool  intPulseState = !digitalRead(rxPort);         // the sense of the pulse is always opposite to the current state of the pin
  20.  
  21.     pulseType = validatePulse(intPulseLen, intPulseState) ; // Work out if the pulse meets the timing criteria
  22.  
  23. /* For brevity, I have no included here the entire function. The function continues from here to implement the detection of a pulse, convert the pulse stream to data bits and add those bits to the receiver buffer. Not all of the referenced attributes are included within this snippet but if we fix one, we'll fix them all.*/
  24. } // end sigInterruptHandler
  25.  
  26. void mcrReceiver::startReceiver(int  portNum)       // Function to start recognising the incomming pulse stream
  27. {
  28.     rxPort = portNum ;          // Index the correct IO port
  29.     attachInterrupt(portNum, sigInterruptHandler, CHANGE);  // Attach the ISR to the port. Need an interrupt for any state change
  30.     pulseTimer = timerBegin(PULSE_TIMER, 80, true);     // Instatiate the timer to time the incoming pulses
  31.     timerWrite(pulseTimer,0);  // and clear the timer. Probably not necessary as the first pulse will be rubbish anyway!!
  32. } // end startReceiver
  33.  
  34.  
The important bit of the main program file, where the object of type 'McrReceiver' is instantiated is as follows;
  1. // Receive test V1 - Measures pulse timings with trigger from transmitter output.
  2.  
  3. #include    <Arduino.h>
  4. #include    "IS_Server.h"
  5. #include    "../../Common/McrCommon.h"
  6. #include    "McrRx_Lib.h"
  7. #include    "RTClib.h"
  8. #include    "LiquidCrystal_I2C.h"
  9. #include    <esp_task_wdt.h>
  10.  
  11. RTC_DS1307  rtc ;                       // Instantiate the RTC
  12. LiquidCrystal_I2C lcd(0x27, 16, 2);     // and the LCD
  13. mcrReceiver     sensorRx(sizeof(packetData));   // and the radio receiver
  14.  
  15. TaskHandle_t    controlTask ;   // Declare the task handle for the main controller which handles the
  16.                                 // data from the receiver
  17. TaskHandle_t    networkTask ;   // and a task to handle the web server
  18.  
  19. void    controller(void*    pControlParams) ;   // Forward declare the task functions
  20. void    webServer(void*    pNetParams) ;
  21.  
  22. void setup()
  23. {
  24.     Serial.begin(115200);
  25.     pinMode(LED_OUT_PIN, OUTPUT);
  26.  
  27.     taskPad.msgCount = 0;
  28.     taskPad.update = false;
  29.  
  30.     createSemaphores();
  31.  
  32.     xTaskCreatePinnedToCore(
  33.                     controller,   /* Task function. */
  34.                     "Controller",     /* name of task. */
  35.                     10000,       /* Stack size of task */
  36.                     (void*)&taskPad,        /* parameter of the task */
  37.                     1,           /* priority of the task */
  38.                     &controlTask,      /* Task handle to keep track of created task */
  39.                     1);          /* pin task to core 0 */  
  40.      
  41.  
  42.     delay(500) ;
  43.  
  44.     xTaskCreatePinnedToCore(
  45.                     webServer,   /* Task function. */
  46.                     "Server",     /* name of task. */
  47.                     10000,       /* Stack size of task */
  48.                     (void*)&taskPad,        /* parameter of the task */
  49.                     1,           /* priority of the task */
  50.                     &networkTask,      /* Task handle to keep track of created task */
  51.                     0);          /* pin task to core 0 */
  52.  
  53.     delay (500);      
  54. } // end setup
  55.  
  56. /* Rest of code follows on from here but is not relevant to the current issue
  57. */
  58.  

MicroController
Posts: 1724
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Interrupt service routine within a c++ class

Postby MicroController » Tue Nov 12, 2024 11:16 pm

Try declaring your class's static member variables inline.

Who is online

Users browsing this forum: No registered users and 69 guests