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.

Code: McrRx_Lib.h Select all


class mcrReceiver {
public:

// Now for some enumerations used by the ISR
// The pulse categorisations, the data stream is edge driven.
typedef enum {
invalidPulse, // Pulse length outside of acceptable limits
shortRising, // Rising edge after a short low
shortFalling, // Falling edge after a short high
longRising, // Rising edge after a long low
longFalling, // Falling edge after a long high
msgComplete // The long high stop pulse which marks the end of a sequence
} validPulse ;

// The stages the receiver will go through during a sequence.
typedef enum {
// rxStRunning, // Not currently used
rxStSyncLo, // Sync stages before actual message starts
rxStSyncHi,
rxStSyncStart,// Still receiving sync pulses but expecting a start marker
rxStStart, // Start marker received and awaiting the first data pulse
rxStPhase1, // The phases of receiving a data bit
rxStPhase2,
rxStMsgEnd, // Indicates we've received the stop pulse
rxStError, // oops, Terminate reception, reset receiver and report error to user
// rxStIdle, // Not currently used
rxStOverrun, // Buffer overrun. Too many valid pulses but no stop received
// rxStCount // Not currently used
} rxStage ; //

// A structure to report the completion status of a receive sequence
typedef struct {
rxStage exitStage; // The stage of the sequence when we terminated.
rxStage exitStatus; // Indicator of where it went wrong if status is an error
validPulse lastPulse; // The type of the last pulse we received
int exitCount; // The number of good bytes we had received when we terminated the receive sequence
} exitData ;

static volatile bool readyFlag ; // To signify a complete message in the receive buffer
static volatile uint8_t *pRxBuffer; // Pointer to the receive buffer. used by the ISR so must be static
volatile uint8_t *rxBuffer ; // The actual receive buffer which is sized by the constructor so can't be static
static volatile exitData receiveStatus; // A record of how a receive sequence ended
static hw_timer_t *pulseTimer ; // The timer which will tell us the lenghts of the pulses
static int rxPort ; // The port to which we will attach the receiver interrupt

mcrReceiver (int bufLen) : rxBuffer(new uint8_t[bufLen]) {pRxBuffer = &rxBuffer[0] ;}; // Constructor. Sizes the receive buffer and sets a static pointer to it
void startReceiver(int portNum); // Allows the user to start up the receive process (May consider a function to stop it if necessary)
int getMessage (char* msgBuffer, int bufLen); // Allow the user task to check for and collect a new message
exitData getRxStatus () ; // Allow user task to see how it ended

// private:
// Start with some values to categorise the incoming pulses. See above for value definitions
static const int minSync = MIN_SYNC;
static const uint64_t shortMaxHigh = SHORT_MAX_HIGH ;
static const uint64_t shortMinHigh = SHORT_MIN_HIGH ;
static const uint64_t shortMaxLow = SHORT_MAX_LOW ;
static const uint64_t shortMinLow = SHORT_MIN_LOW ;
static const uint64_t longMaxHigh = LONG_MAX_HIGH ;
static const uint64_t longMinHigh = LONG_MIN_HIGH ;
static const uint64_t longMaxLow = LONG_MAX_LOW ;
static const uint64_t longMinLow =LONG_MIN_LOW ;
static const uint64_t minEnd = MIN_END ;
static const uint64_t maxEnd = MAX_END ;

static validPulse validatePulse (uint64_t len, bool state); // To categorise the pulses
static void sigInterruptHandler() ; // The ISR function that implements the receiver

};
The abbreviated ISR function in McrRx_Lib.cpp is....

Code: McrRx_Lib.cpp Select all



void ICACHE_RAM_ATTR mcrReceiver::sigInterruptHandler()
{
// Variables that must hold their values between interrupts
static rxStage rxState = rxStSyncLo ; // SyncLow is our starting point. a low to high transition starts everything off
static uint8_t bitMask = 0x01 ; // To set bits in the current byte if required
static volatile uint8_t* pCurrentByte = pRxBuffer; // A pointer to the current byte in the receive buffer
static int rxIndex = 0; // The index of the current byte in the buffer
static int syncCount = 0; // Tally of the number of sync bits we have received
static bool needsReset = true ; // Tag to tell us when we need to reset the receive parameters

// Things we'll only use once per call
rxStage exitStage ; // A record of the receiver state when we exit. Mainly for error detection by the main task.
validPulse pulseType; // To categorise the pulse we've just received.

// First get all of the information about the interrupt
uint64_t intPulseLen = timerRead(pulseTimer); // Get the length of the pulse
timerWrite(pulseTimer,0); // Restart the timer ASAP in order to time the next pulse
bool intPulseState = !digitalRead(rxPort); // the sense of the pulse is always opposite to the current state of the pin

pulseType = validatePulse(intPulseLen, intPulseState) ; // Work out if the pulse meets the timing criteria

/* 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.*/
} // end sigInterruptHandler

void mcrReceiver::startReceiver(int portNum) // Function to start recognising the incomming pulse stream
{
rxPort = portNum ; // Index the correct IO port
attachInterrupt(portNum, sigInterruptHandler, CHANGE); // Attach the ISR to the port. Need an interrupt for any state change
pulseTimer = timerBegin(PULSE_TIMER, 80, true); // Instatiate the timer to time the incoming pulses
timerWrite(pulseTimer,0); // and clear the timer. Probably not necessary as the first pulse will be rubbish anyway!!
} // end startReceiver

The important bit of the main program file, where the object of type 'McrReceiver' is instantiated is as follows;

Code: IS_Server.cpp Select all


// Receive test V1 - Measures pulse timings with trigger from transmitter output.

#include <Arduino.h>
#include "IS_Server.h"
#include "../../Common/McrCommon.h"
#include "McrRx_Lib.h"
#include "RTClib.h"
#include "LiquidCrystal_I2C.h"
#include <esp_task_wdt.h>

RTC_DS1307 rtc ; // Instantiate the RTC
LiquidCrystal_I2C lcd(0x27, 16, 2); // and the LCD
mcrReceiver sensorRx(sizeof(packetData)); // and the radio receiver

TaskHandle_t controlTask ; // Declare the task handle for the main controller which handles the
// data from the receiver
TaskHandle_t networkTask ; // and a task to handle the web server

void controller(void* pControlParams) ; // Forward declare the task functions
void webServer(void* pNetParams) ;

void setup()
{
Serial.begin(115200);
pinMode(LED_OUT_PIN, OUTPUT);

taskPad.msgCount = 0;
taskPad.update = false;

createSemaphores();

xTaskCreatePinnedToCore(
controller, /* Task function. */
"Controller", /* name of task. */
10000, /* Stack size of task */
(void*)&taskPad, /* parameter of the task */
1, /* priority of the task */
&controlTask, /* Task handle to keep track of created task */
1); /* pin task to core 0 */


delay(500) ;

xTaskCreatePinnedToCore(
webServer, /* Task function. */
"Server", /* name of task. */
10000, /* Stack size of task */
(void*)&taskPad, /* parameter of the task */
1, /* priority of the task */
&networkTask, /* Task handle to keep track of created task */
0); /* pin task to core 0 */

delay (500);
} // end setup

/* Rest of code follows on from here but is not relevant to the current issue
*/

MicroController
Posts: 2668
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: Bytespider and 2 guests