Interrupts, queues, and aperiodic events

My original situation, using an NRF24L01.
Loop as a task:
read output queue to see if data waiting to be sent. If queue has data, set to transmit mode, write it to data registers and send to addressed node on network (just write), then reset to read mode for chip.
next:
If data is available (read registers in chip or obey interrupt), then read chip data, then place that on main system queue for handling. Cycle and look at hardware again. Note that this is a polling solution without interrupts.

This works, but time delays in loops (equal level task without a delay seems not to promote task switching…) make this long.

Things needed to know: If chip is not unique on SPI interface, it is protected by a semaphore (in this case, it is not) at the SPI bus level. So we don’t have that semaphore. However, the chip is protected AT THE CHIP LEVEL from other tasks writing/reading.

The system is intended to have task driven messages through the NRF24L01 at various times. Other nodes in the mesh network can forward messages to this node at any time.
Handling messages in the mesh network is a task, and seems not to be the problem.

Problem is this. taking this to an interrupt based system works (as far as the hardware is concerned). I can cause an interrupt when data is available, and then execute a routine to put the data on the main system queue. Fine so far. except…

Remember that the chip is protected by a semaphore?

I understand (and use) the “fromISR” routines. No problem there, except that I’ll have to write routines that instead of:

// ****************************************************************************************************************************************************
// ************************************************************* SINGLE REGISTER READ *****************************************************************
// ****************************************************************************************************************************************************
// ONE BYTE READ, RETURNS REGISTER VALUE (NOT STATUS)

uint8_t RF24::read_register(uint8_t reg)
{
	uint32_t status;
	HAL_StatusTypeDef result;

	//	uint8_t stat;
	uint8_t dresult[2];

#ifdef _FREERTOS
	xSemaphoreTake(semaphore,portMAX_DELAY);
#endif
#ifdef _AZURE
	tx_mutex_get(&semaphore, TX_WAIT_FOREVER);
#endif
	reg = R_REGISTER | (reg & 0x1F);
	result =  hal_spi[interface]->receive(1,&reg,1,&dresult[0],0,NRF_SPI_PRESCALE,NRF_CS_GPIO_Port,NRF_CS_Pin);
#ifdef _FREERTOS
	xSemaphoreGive(semaphore);
#endif
#ifdef _AZURE
	tx_mutex_put(&semaphore);
#endif
//	status = get_status();
	return dresult[0];
}


The semaphore here is on the RF24 class and protects the chip. Unseen (and not included) is a semaphore that protects the interface if the interface is shared. In this case, it is not. SO thought of, but not needed

My code for the interrrupt routine is:


void NRF_NODE_RECEIVE_IRQ (void)
{
	BaseType_t					xHigherPriorityTaskWoken = false;
	uint8_t 					buffer[64];
	uint8_t						data_available;

	// ******************************************************************************************************
	// ************************* check if incoming **********************************************************
	// ******************************************************************************************************

	data_available = rf24.available();
	rf24.record_registers();
	// check incoming data through NRF24L01
	if ((data_available < 0x80) && (data_available != 0))
	{
		// data is available, read and process
//		received_packet = (union SYSTEM_PACKET_type*) &buffer;
		rf24.read(buffer, 32);
		rf24.write_register(NRF_STATUS, 0x70);					// all IRQ flags cleared
		xQueueSendToBackFromISR(system_queue,(const void*)buffer,&xHigherPriorityTaskWoken );
		// per instructions on queue
		if (xHigherPriorityTaskWoken)
		{
			taskYIELD ();
		}
	}	// end of receive check
	else
	{
		// bad interrupt for some reason
		// only interrupt is RX data, so reset all
		rf24.write_register(NRF_STATUS, 0x70);					// all IRQ flags cleared
		// no need to delay yet
	}



}


The problem is that the register reads and chip reads use semaphores to protect the chip. I can rewrite them so that the semaphores use the “fromISR” tag which makes them work.

RIght, so the routine would work, but I have another conceptual problem with all of this.

Interrupts are aperiodic, and as far as the programming is concerned, may happen at any time.

So after all this, my questions are:

  1. Assuming that the system IS in the middle of talking to the chip (meaning that the semaphore for the chip is taken), what happens in an interrupt? If I use the same semaphore that protects the chip, then the chip is already protected, and the system is frozen, and the system is in a deadlock.
  2. This happens regardless of whether or not I use “from ISR” or not, of course
  3. Since the system is frozen when in an interrupt, (seeing #1), there is no way to “unfreeze” the system, but I could always “give” the semaphore back (just occurred to me), do the interrupt, “take” the semaphore, and return to the original routine (talking to the chip). My impression is that this leaves the chip in an unknown (and highly disturbed) state. I’m not seeing this as a good idea.

Has anyone come across this before (likely) and what was the preferred solution, other than figuring a way to somehow delay the actual interrupt.

Even if I throw away the semaphores (which does not seem to be a good idea), I still have a conceptual problem with interlocking programmed (and random) chip accesses with interrupts(random) chip accesses.

OR should I put the IRQ event on a queue and handle it within the context of the rest of the tasks?

Did this make sense?

Suggestions are welcome.

Why do you need this mutex?

Your problem is not clear. Would you please describe what you are trying to achieve? Something like -

  1. I have a device connected on the SPI bus.
  2. I want multiple tasks to be able to communicate to the SPI device but one at a time.
  3. The tasks initiates the communication with the SPI device and waits for it to complete.
  4. The device informs about the completion of the above communication via an interrupt.

Thanks.

All hardware is protected for both reads and writes by semaphores since there are multiple tasks.

All code is in C++ where possible.

Reads and writes can be multibyte.

when changing the software to be interrupt driven rather than polling, I seem to need two sets of software if I want to use semaphores, one using “fromISR” and one not.

This seems inefficient, but it can be managed.

System works as follows:

  1. Message arrives to subsystem asynchronously.
  2. Subsystem generates interrupt
  3. interrupt reads data from chip and puts in on queue
  4. interrupt routine writes to chip to clear interrupt

For transmission:

  1. queue is monitored for message
  2. when message is in queue: read message
  3. place subsystem in transmit mode
  4. write message to subsystem with some address modifications if needed
  5. place subsystem in receive mode
  6. loop back for queue

programming in ISR needs “from ISR”.

Not sure if the subsystem generates an interrupt when it is in the middle of a transmission, but that may just be as easy as masking that interrupt when transmission is started.

Thanks

Thank you for the description.

You need one mutex to protect multiple tasks from attempting to send simultaneously. I do not think you need to protect register read/writes individually.

That should work.

I’ve gotten it sorted properly.
For any chip access from within the IRQ routine, I’ve duplicated the appropriate functions and use the “FromISR” variety.

In the transmit routine (from a task, reading a queue) I turn off the subsystem’s interrupts so the possibility of the NRF24L01 getting a message and generating an interrupt, which should not happen at all, but I didn’t write the firmware… That is now not possible. Once the system goes back into receive mode, the receive interrupt (all I needed) is now enabled.

The chip is protected by two mutex. One is disabled when the chip is on a dedicated interface. This is needed to keep another process from addressing a chip on the common interface. That protection of the interface (SPI interface at the processor level) is not needed.

The register reads and writes are what is protected by a mutex, I’m not seeing a good way to protect the entire subsystem, given that this is an IRQ mixed with a queue. I do depend on decent programming to restrict the access to the chip to make sense.

The multiple tasks sending simultaneously is not a problem, since the queue feeds the transmission. Queue software is controlling access to the queue.

Main problem was to figure out the most efficient scheme to make this work. Rewriting the access routines to work within the ISR was needed. Disabling the receive data interrupt may not be needed, but the chip’s documentation does not seem to mention this.

Thanks

Glad that you figured and thanks for reporting back.

I think a key point is that the ISR should be doing MINIMAL work, and recording the data from the SPI operation, and signaling a task to be doing the real work with the data. That is how RTOS’s work. Without an RTOS, you sometimes need to do more stuff in the ISR to keep things on-time, but that gets into a lot of design issues. In my opinion, your “device” code will mostly be just at task level, calling a generic SPI driver, with maybe some ISRs handling any flag pins from the device that signal interrupts, and those ISR just forward the signal to a task.