Code reusing while using interrupts
-
- Posts: 2
- Joined: Sun Dec 25, 2022 12:41 pm
Code reusing while using interrupts
I have several water flow sensors for which I want to use hardware interrupts. And I don't know how to do it.
In order not to write the same code several times, I have to write a class for working with the sensor, and then create several instances in the application environment to work with each sensor separately. Either I need to create an instance of the class in the interrupt handler, initializing it with certain data from somewhere in memory, since I need to store the value (liters counter, current water flow value for example).
I haven't tried Espressif SDK yet, only Arduino, however even there I ran into this problem: a static method must be used as handleer of hardware interrupts, but in this case, i can't access variables and instance methods.
Tell me, please, how is this problem solved, perhaps what should I read? I don't know C++ very well. It may be simple, but not for me.
In order not to write the same code several times, I have to write a class for working with the sensor, and then create several instances in the application environment to work with each sensor separately. Either I need to create an instance of the class in the interrupt handler, initializing it with certain data from somewhere in memory, since I need to store the value (liters counter, current water flow value for example).
I haven't tried Espressif SDK yet, only Arduino, however even there I ran into this problem: a static method must be used as handleer of hardware interrupts, but in this case, i can't access variables and instance methods.
Tell me, please, how is this problem solved, perhaps what should I read? I don't know C++ very well. It may be simple, but not for me.
-
- Posts: 9746
- Joined: Thu Nov 26, 2015 4:08 am
Re: Code reusing while using interrupts
It's possibly easier to create a FreeRTOS task for this. Task waits for e.g. a semaphore or queue, then handles the sensors. Either your interrupt or your normal code can raise the semaphore or put something in the queue and let the task handle the thing.
-
- Posts: 1710
- Joined: Mon Oct 17, 2022 7:38 pm
- Location: Europe, Germany
Re: Code reusing while using interrupts
The usual approach would be to create one or more instances of your class as global variables, which means they get allocated and initialized once and then their member functions can be called from an ISR or any other code. This way, the ISR does not have to allocate/initialize a new instance every time, and the instance can maintain its data independent of the ISR's scope.
The "C" approach, in lieu of object oriented features, would be to create one or more functions which receive as a parameter a "context" on which they operate. This "context" parameter is often a pointer to some struct(ure) which holds the data, for example which sensor instance is to be handled. In fact, this is exactly what C++ and other OO languages do for you "under the hood". The C-equivalent to C++ "sensorInstance.doSomething(x)" would be something like "flowsensor_doSomething(&sensorInstance, x)".
Why do I mention that? To emphasize that there is no need to create new instances just to be able to put code into their member functions to be run with variable "contexts", especially if the "context" is trivial.
The "C" approach, in lieu of object oriented features, would be to create one or more functions which receive as a parameter a "context" on which they operate. This "context" parameter is often a pointer to some struct(ure) which holds the data, for example which sensor instance is to be handled. In fact, this is exactly what C++ and other OO languages do for you "under the hood". The C-equivalent to C++ "sensorInstance.doSomething(x)" would be something like "flowsensor_doSomething(&sensorInstance, x)".
Why do I mention that? To emphasize that there is no need to create new instances just to be able to put code into their member functions to be run with variable "contexts", especially if the "context" is trivial.
-
- Posts: 131
- Joined: Tue May 17, 2016 8:12 pm
Re: Code reusing while using interrupts
Hi @ THE_KONDRAT.
As suggested here, threads and isr handler is the most probable answer, using espidf.
Config every GPIO as INPUT (with pullup I presume for NEGEDGE or pulldown for POSEDGE) and then add it as an gpio interrupt
Start the ISR handler
A thread that waits "forever" which will be fired by the ISR. The ISR will receive an Interrupt from each of the GPIOS that are handling every individual flow meter.
Here is a typical gpio isr handler, the *arg is a pointer to "something" and its the parameter you passed it when adding the
Example of a Task that waits for the ISR to perform "work", like save to storage, send http, mqtt or whatever
In your main routine, do the initializations of all your stuff (pins, mqtt drivers, http, etc) and launch the ISR queue handler task like
Hope it helps.
As suggested here, threads and isr handler is the most probable answer, using espidf.
Config every GPIO as INPUT (with pullup I presume for NEGEDGE or pulldown for POSEDGE) and then add it as an gpio interrupt
Code: Select all
io_conf.pin_bit_mask = (1ULL<<theMeters[xx].pin);
io_conf.intr_type = GPIO_INTR_POSEDGE; //or whatever edge
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pull_down_en =GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en =GPIO_PULLUP_ENABLE;
gpio_config(&io_conf);
gpio_isr_handler_add((gpio_num_t)theMeters[a].pin, gpio_isr_handler, (void*)&theMeters[a]); //add gpio isr to isr handler
Code: Select all
gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
A thread that waits "forever" which will be fired by the ISR. The ISR will receive an Interrupt from each of the GPIOS that are handling every individual flow meter.
Here is a typical gpio isr handler, the *arg is a pointer to "something" and its the parameter you passed it when adding the
Code: Select all
gpio_isr_handler_add((gpio_num_t)theMeters[a].pin, gpio_isr_handler, (void*)&theMeters[a]);
Code: Select all
void gpio_isr_handler(void * arg)
{
BaseType_t tasker;
meterType *meter=(meterType*)arg; //pointer to your structure-class-gpio number
uint8_t level=gpio_get_level(meter->pin); //get level just to confirm it is the one u expected
// using your meter structure do very little and fast, minimum calcs etc. Must not BLOCK and take too long. ISR are disabled
// all major calculations should be done by a Thread(task) that is waiting
.......
if(condition)
{
xQueueSendFromISR( mqttR, meter, &tasker );// using queues send a meter flow structure or whatever. Mind the ISR part
if (tasker)
portYIELD_FROM_ISR();
}
.....
}
Code: Select all
static void mqttManager(void* arg)
{
meterType meter;
while(1)
{
if( xQueueReceive( mqttR, &meter, portMAX_DELAY/ portTICK_RATE_MS ))
{
sendStatusMeter(&meter);
...... heavy work here
}
else
delay(100);
}
}
Code: Select all
xTaskCreate(&mqttManager,"meters",8192,NULL, 5, NULL);
-
- Posts: 2
- Joined: Sun Dec 25, 2022 12:41 pm
Re: Code reusing while using interrupts
Am i understand right that i should pass meter-class instance to isr handler through a parameter, and after that send message to queue (something like event). But i don't understand completely how messages are routed?rsimpsonbusa wrote: ↑Sun Feb 26, 2023 9:29 pmHi @ THE_KONDRAT.
As suggested here, threads and isr handler is the most probable answer, using espidf.
Config every GPIO as INPUT (with pullup I presume for NEGEDGE or pulldown for POSEDGE) and then add it as an gpio interrupt
Start the ISR handlerCode: Select all
io_conf.pin_bit_mask = (1ULL<<theMeters[xx].pin); io_conf.intr_type = GPIO_INTR_POSEDGE; //or whatever edge io_conf.mode = GPIO_MODE_INPUT; io_conf.pull_down_en =GPIO_PULLDOWN_DISABLE; io_conf.pull_up_en =GPIO_PULLUP_ENABLE; gpio_config(&io_conf); gpio_isr_handler_add((gpio_num_t)theMeters[a].pin, gpio_isr_handler, (void*)&theMeters[a]); //add gpio isr to isr handler
Code: Select all
gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
A thread that waits "forever" which will be fired by the ISR. The ISR will receive an Interrupt from each of the GPIOS that are handling every individual flow meter.
Here is a typical gpio isr handler, the *arg is a pointer to "something" and its the parameter you passed it when adding theCode: Select all
gpio_isr_handler_add((gpio_num_t)theMeters[a].pin, gpio_isr_handler, (void*)&theMeters[a]);
Example of a Task that waits for the ISR to perform "work", like save to storage, send http, mqtt or whateverCode: Select all
void gpio_isr_handler(void * arg) { BaseType_t tasker; meterType *meter=(meterType*)arg; //pointer to your structure-class-gpio number uint8_t level=gpio_get_level(meter->pin); //get level just to confirm it is the one u expected // using your meter structure do very little and fast, minimum calcs etc. Must not BLOCK and take too long. ISR are disabled // all major calculations should be done by a Thread(task) that is waiting ....... if(condition) { xQueueSendFromISR( mqttR, meter, &tasker );// using queues send a meter flow structure or whatever. Mind the ISR part if (tasker) portYIELD_FROM_ISR(); } ..... }
In your main routine, do the initializations of all your stuff (pins, mqtt drivers, http, etc) and launch the ISR queue handler task likeCode: Select all
static void mqttManager(void* arg) { meterType meter; while(1) { if( xQueueReceive( mqttR, &meter, portMAX_DELAY/ portTICK_RATE_MS )) { sendStatusMeter(&meter); ...... heavy work here } else delay(100); } }
Hope it helps.Code: Select all
xTaskCreate(&mqttManager,"meters",8192,NULL, 5, NULL);
In other words, how in the background task will I get the desired message (or is it not like routing in RabbitMQ)?
Or in the message itself, I only pass the pin number, and in the context should have a method that returns the corresponding instance?
Thanks everyone for the replies. Can you also write some sources that I can refer to for a better understanding of how to write code in the esp idf style correctly (with examples)?
-
- Posts: 131
- Joined: Tue May 17, 2016 8:12 pm
Re: Code reusing while using interrupts
Regarding your first question, better to pass as parameter the INDEXNUMBER which refers to a position in an array of structures(one for each Flowmeter). Do not send EVERY pulse received but "Bursts" and when the burst is finished, send a Msg to the processing task via a Queue.
The processing task receives the data thru a queue and does a lot of things like saving to flash, sending a mqtt msg, etc.
I did a similar project, reading water mains with flowmeters (up to 12 if close enough) and used a mqqt service to transfer data to a Node App which stores the data in a Mysql database (the ESP32s are in a mesh so the sky is the limit).
Regarding how to program in espidf, I suggest u install vscode in your computer where you will find examples regarding many protocols, gpios, ledc, adc, dac, spi, i2c,queues and many more features.
It looks as you are starting espidf which is more elaborate than Arduino, but has great features like multitasking/thread and others that are not available in Arduino (I think ). Its a rather longish road.
But yes, what u intend is absolutely possible so do not give up.
The processing task receives the data thru a queue and does a lot of things like saving to flash, sending a mqtt msg, etc.
I did a similar project, reading water mains with flowmeters (up to 12 if close enough) and used a mqqt service to transfer data to a Node App which stores the data in a Mysql database (the ESP32s are in a mesh so the sky is the limit).
Regarding how to program in espidf, I suggest u install vscode in your computer where you will find examples regarding many protocols, gpios, ledc, adc, dac, spi, i2c,queues and many more features.
It looks as you are starting espidf which is more elaborate than Arduino, but has great features like multitasking/thread and others that are not available in Arduino (I think ). Its a rather longish road.
But yes, what u intend is absolutely possible so do not give up.
Who is online
Users browsing this forum: No registered users and 84 guests