Code reusing while using interrupts

THE_KONDRAT
Posts: 2
Joined: Sun Dec 25, 2022 12:41 pm

Code reusing while using interrupts

Postby THE_KONDRAT » Fri Feb 24, 2023 7:12 am

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.

ESP_Sprite
Posts: 9619
Joined: Thu Nov 26, 2015 4:08 am

Re: Code reusing while using interrupts

Postby ESP_Sprite » Sat Feb 25, 2023 2:17 am

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.

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

Re: Code reusing while using interrupts

Postby MicroController » Sat Feb 25, 2023 10:52 pm

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.

rsimpsonbusa
Posts: 124
Joined: Tue May 17, 2016 8:12 pm

Re: Code reusing while using interrupts

Postby rsimpsonbusa » Sun Feb 26, 2023 9:29 pm

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

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 
Start the 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();
	}
	.....
	
	

	}
Example of a Task that waits for the ISR to perform "work", like save to storage, send http, mqtt or whatever

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);
	}
}
In your main routine, do the initializations of all your stuff (pins, mqtt drivers, http, etc) and launch the ISR queue handler task like

Code: Select all

   xTaskCreate(&mqttManager,"meters",8192,NULL, 5, NULL);
Hope it helps.

THE_KONDRAT
Posts: 2
Joined: Sun Dec 25, 2022 12:41 pm

Re: Code reusing while using interrupts

Postby THE_KONDRAT » Sun Feb 26, 2023 10:01 pm

rsimpsonbusa wrote:
Sun Feb 26, 2023 9:29 pm
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

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 
Start the 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();
	}
	.....
	
	

	}
Example of a Task that waits for the ISR to perform "work", like save to storage, send http, mqtt or whatever

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);
	}
}
In your main routine, do the initializations of all your stuff (pins, mqtt drivers, http, etc) and launch the ISR queue handler task like

Code: Select all

   xTaskCreate(&mqttManager,"meters",8192,NULL, 5, NULL);
Hope it helps.
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?
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)?

rsimpsonbusa
Posts: 124
Joined: Tue May 17, 2016 8:12 pm

Re: Code reusing while using interrupts

Postby rsimpsonbusa » Mon Feb 27, 2023 1:18 am

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 :roll: ). Its a rather longish road. :shock:

But yes, what u intend is absolutely possible so do not give up. :D

Who is online

Users browsing this forum: Google [Bot] and 87 guests