Interrupt Handling and a Task

It‘s not a bug when cache and physical RAM are not in sync. That‘s the way it works.
The FAQ just gives some advises how to deal with hi-performance beasts like STM32H7 to avoid common software pitfalls.
I don’t know the context of Andrew Morton‘s statement but DMA hardware was and often is usually not aware of processor caches which requires manual cache management by software. This might be hidden by a higher level DMA API but internally it’s still done.
Nowadays there are SoCs with cache-coherent links between processing system and DMA hardware cores supporting coherent DMA transfers without software interaction.
But also this otherwise great feature has drawbacks like cache pollution it might be useful to avoid it.

1 Like

#1 portEND_SWITCHING_ISR(xSwitchRequired)
Ah! you meant to say it should be called with xSwitchRequired = True.
Thanks for pointing it out.

#2 With the Global buffer, thinking of a semaphore for control.
To describe it a bit more, the idea follows thus:

  • Use 2x memory required (double buffer)
  • Enable Half Transfer and Transfer Complete DMA flags
  • Use a semaphore to control the consumer thread behaviour (which buffer it reads and when to sleep).
  • While DMA writes to buffer #1, CPU reads buffer #2 and vice versa.

That should work ?

I couldn’t agree with you more, on the sync issue.

The DMA hardware issue was with a PCI/PCIe multimedia bridge. (It was more than a decade back ? probably, could be the Mantis or SAA716x.) By default the bridge was configured to use a Cached mem region, IIRC. The ROM was fixed finally.

I am unsure about the solution you proposed. How would you determine whether the data written by the ISR has been consumed?

uint8_t buffer1[1024];
uint8_t buffer1[1024];

void consumer( void * params )
{
}

void interrupt_handler( void )
{
}

What will you fill in the above two functions?

Thanks.

(I got rate limited. Forum application believes I am a spammer. :-/)

Let me put the thought in a NO OS fashion. I am still a FreeRTOS newbie. :slight_smile:
It would be easier for me to describe the thought in that way.

Something like this ?

uint16_t Dbuf[20];
uint8_t buf_flag;

void DMAz_StreamX_IRQHandler(void)
{
	if (DMA_IsActiveFlag_TCy(DMAz)) {
		DMA_ClearFlag_TCy(DMAz);

		// We know here that the second half of the
		// DMA transfer has been complete

	}
	if (DMA_IsActiveFlag_HTy(DMAz)) {
		DMA_ClearFlag_HTy(DMAz);

		// We know here that the first half of the 
		// DMA transfer has been complete

	}
}

void dma_init()
{
	// We need lets say 10 bytes totally,
	// Provide 20 bytes instead to the Stream engine
}

void consumer_thread()
{
	while (1) {
		// access buf1 or buf2,
		// depending on buf_flag, choose access to the relevant buffer
		//
		// Just that we should not Read the buffer to which DMA is writing
		// We will need to sleep, in case the buffer is not yet ready
		//
		// The idea is that the consumption rate is higher
		// than the producer rate. Which is easy with additional sleep
		// for the consumer.
	}
}

int main(void)
{
	dma_init()
	dma_run()
	create_thread(consumer_thread())

}

Not read whole thread but if sending data from an interrupt in FreeRTOS then a stream buffer is an option.

Separately manual cache management should only be required when a dma transfers need to be synced with the CPU’s view of memory as they are both bus masters. Switching tasks, by itself, doesn’t need that as it is done from a single bus master. Using dma to move data might.

Hi Richard,

Thanks for the confirmation.
Tried without switching tasks, “it does appear to work”.

Thanks again for clearing it up.
I will play around with it a bit and see how it goes.
Will post updates and or any subsequent queries that do arise.

Thank you for clarifying. I think you are trying to do something like below:

void consumer_thread()
{
	while (1) {
		if( buf_flag == 1 )
		{
			// consume buf 1.
			buf_flag = 0;
		}
		else
		{
			// consume buf 2.
			buf_flag = 1;
		}
	}
}

void DMAz_StreamX_IRQHandler(void)
{
	if( buf_flag == 1 )
	{
		//Buf 1 is being consumed. Write to buf 2.
	}
	else
	{
		//Buf 2 is being consumed. Write to buf 1.
	}
}

You would need to avoid race conditions when accessing/updating buf_flag. In addition, you need to handle the case if ISR fires in succession without the task getting a chance to run.

I’d say, as Richard suggested, try using stream buffer as it may be easier.

Thanks.

Assuming you have a steady stream of data coming in via DMA, and you want to process it in chunks. The DMA’s half-transfer-complete interrupt is perfect for this.

I am a big fan of task notifications, especially using eSetBits. It would look something like this (starting from your pseudocode just to illustrate eSetBits):

#define BUF_ITEM_COUNT 10
uint16_t Dbuf[BUF_ITEM_COUNT * 2];

#define BUFFER_A  (Dbuf)
#define BUFFER_B  (Dbuf + BUF_ITEM_COUNT)

#define NOTIFICATION_FLAG_BUFFER_A_READY (1UL << 0)
#define NOTIFICATION_FLAG_BUFFER_B_READY (1UL << 1)

static TaskHandle_t consumerThreadHandle;

void DMAz_StreamX_IRQHandler(void)
{
	uint32_t notificationFlags = 0UL;
	
	if (DMA_IsActiveFlag_TCy(DMAz)) {
		DMA_ClearFlag_TCy(DMAz);

		// We know here that the second half of the
		// DMA transfer has been complete
		
		notificationFlags |= NOTIFICATION_FLAG_BUFFER_B_READY;
	}
	if (DMA_IsActiveFlag_HTy(DMAz)) {
		DMA_ClearFlag_HTy(DMAz);

		// We know here that the first half of the 
		// DMA transfer has been complete

		notificationFlags |= NOTIFICATION_FLAG_BUFFER_A_READY;
	}
	
	BaseType_t wasHigherPriorityTaskWoken = pdFALSE;
	xTaskNotifyFromISR(consumerThreadHandle, notificationFlags, eSetBits, &wasHigherPriorityTaskWoken);
	portYIELD_FROM_ISR(wasHigherPriorityTaskWoken);
}

void consumer_thread()
{
	uint32_t notificationValue;

	while (1) {
		// access buf1 or buf2,
		// depending on buf_flag, choose access to the relevant buffer
		//
		// Just that we should not Read the buffer to which DMA is writing
		// We will need to sleep, in case the buffer is not yet ready
		//
		// The idea is that the consumption rate is higher
		// than the producer rate. Which is easy with additional sleep
		// for the consumer.
		
		xTaskNotifyWait(0, ULONG_MAX, &notificationValue, portMAX_DELAY);
		if (notificationValue & NOTIFICATION_FLAG_BUFFER_A_READY) {
			consumeBuffer(BUFFER_A);
		}
		if (notificationValue & NOTIFICATION_FLAG_BUFFER_B_READY) {
			consumeBuffer(BUFFER_B);
		}
	}
}

As others suggested you would still want to add code to verify you are meeting your real-time deadlines. If you get behind, you might end up processing buffer ‘A’ while the DMA is filling buffer ‘A’, which of course is no good.

1 Like

Hi Gaurav,

Yeah mostly that’s what the idea is. I agree that there exists a race in handling the flags. But to get the idea across, the minimalist approach is the best, so I believe. :slight_smile:

I have added in a SCB_InvalidateDCache_by_Addr() for the Cache to be synced in the DMA handler.

The data rate was not much, a timer firing up an ADC, the DMA it and read it out, was the test that I tried. The timer fires at about 1s rate, the DMA pumps out 10 ADC sequences (16bits x 12x10), so it takes about 10s for DMA HT DMARQ and another 10s for the TC DMARQ. This was all in the interest of testing the thought. Each DMARQ pumps about 240 bytes, which can be read of in the thread/task easily.

Given a larger picture, I would probably like to use a ringbuffer there instead. But that’s what appears to be there in stream buffer too …

I took a look at stream buffer :

Please accept my apologies; But readability is quite a bit bad, after looking at different things, I felt like I was playing “tetris” with text scattered here and there …
At my side, please have a look at how it looks like (attached screenshots) …

It is so painful to the eyes, too much scrolling up, down, left, right to even read.
For someone who is reading the code, it is too much of a pain. I kind of feel it’s a bit of a punishment. :wink:

It is a bit difficult for me to believe that no one else sees this as a problem …

This same problem exists for me regarding HAL/Cube and that’s why I steer completely away from it. Some days, I’ve had splitting headaches, looking into it…

Maybe it is only me;
For quite a while, I have been forced to do strict Codingstyle, patches to be accepted in Linux kernel mainline, but given the initial gripes about it, I found that Code readability was quite important as it helped me to move fast enough, whether reading or fixing a bug. Actually, that helped me to focus on the problem that I was supposed to look at, rather than trying to make out what is written.

Hi Jeff,

Thanks for taking the time, much appreciated.

The reason why I looked into HT/TC was the block operation,
a nice thing if handled properly.

I am reading into xTaskNotifyWait().
It appears to be interesting, small and concise.

You can read the stream buffer documentation here: https://www.freertos.org/RTOS-stream-message-buffers.html

And the corresponding API documentation: https://www.freertos.org/RTOS-stream-buffer-API.html

Thanks.

Gaurav,

Thanks for the links.

I would like to understand what the functions themselves are doing,
so that I get a better grasp of what’s going on, which led me to reading the code.

Hi Manu,

If you adjust the tab size in your editor, the code becomes much easier to read. In FreeRTOS code, tab alignment is every 4 columns. From your screen shots, it appears your editor is currently set for 8.

Hi Jeff,

I tried the xTaskNotifyWait() option, but could not get it working.
Maybe I did something really stupid, but could not spot anything obvious to my eyes.

This is what I tried, any thoughts ?

static TaskHandle_t h_tmr_task;

#define FLAG_BUFA_RDY		(1UL << 0)
#define FLAG_BUFB_RDY		(1UL << 1)
#define FLAG_TIM2_UPD		(1UL << 2)

void TMR_Thread(void *arg)
{
	uint32_t notifier;

	printf(" (%d) %s: Start Timer\r\n", __LINE__, __FUNCTION__);
	LL_TIM_EnableCounter(TIM2);				/* enable timer counter */

	while (1) {
		xTaskNotifyWait(0, ULONG_MAX, &notifier, portMAX_DELAY);
		if (notifier & FLAG_TIM2_UPD)
			printf("(%d) %s: TIM2 Update\r\n", __LINE__, __FUNCTION__);
	}
}

void TIM2_IRQHandler(void)
{
	uint32_t notifier = 0UL;	

	if (LL_TIM_IsActiveFlag_UPDATE(TIM2)) {
		LL_TIM_ClearFlag_UPDATE(TIM2);

		LL_GPIO_TogglePin(GPIOB, LL_GPIO_PIN_14);	/* Toggle RED LED on IRQ */
		notifier |= FLAG_TIM2_UPD;
	}
	BaseType_t task_state = pdFALSE;
	xTaskNotifyFromISR(h_tmr_task, notifier, eSetBits, &task_state);
	portYIELD_FROM_ISR(task_state);
}

/*
 * looping indefinitely here:
 * 3589:                 while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )
 * WTF! xTaskNotifyFromISR() blocks the ISR indefinitely, no notifications either!
 */

/*
 * Unrolling
 *
 * #define xTaskNotifyFromISR(xTaskToNotify,
 *			      ulValue,
 *			      eAction,
 *			      pxHigherPriorityTaskWoken)
 *
 * xTaskGenericNotifyFromISR((xTaskToNotify),
 *			     (ulValue),
 *			     (eAction),
 *			     NULL,
 *			     (pxHigherPriorityTaskWoken))
 */
 
int main(void)
{
	SystemClock_Config();					/* 480Mhz, Systick@1mS */
	led_init();
	config_usart();						/* 115200, 8N1 */
	
	printf(" -------------------------\r\n");
	printf("  -* STM32H7x TMR Test *- \r\n");
	printf(" -------------------------\r\n");
	tim2_init();						/* Updates, reloads, IRQ at ~1s */

	xTaskCreate(TMR_Thread,					/* Task function */
		    "TMR",					/* Task name */
		    configMINIMAL_STACK_SIZE * 2,		/* Stack size */
		    NULL,					/* optional arg */
		    tskIDLE_PRIORITY,				/* priority */
		    &h_tmr_task);				/* Task Handle */

	vTaskStartScheduler();					/* start scheduler */

	while (1) {}						/* scheduler has taken over */
}

Looks good to me – nothing stands out.

You seeing activity on Port B pin 14?

PortB.Pin14, I have a RED LED.

The RED LED is completely lit. I was expecting it to toggle @1s rate.
(TIM2 update rate). Did try the debugger eventually.

xTaskNotifyWait() goes into an indefinite loop.

I’ve included within the code, around where it looping.
Around Line# 3589

within xTaskGenericNotifyFromISR()

Sorry - I skipped right over that comment in your code because it seemed so unrelated. I don’t think that code is anywhere in xTaskGenericNotifyFromIsr(). It is however part of the idle task, in function prvCheckTasksWaitingTermination(). Can you step through xTaskGenericNotifyFromIsr() to see where things go so wrong?

Yes. Sorry!, It goes into the scheduler, during the idle task,
yes ~3589 Thread Creation,
Execution did not reach the first print statement in the thread function yet.
(TMR2 ISR hasnt fired off yet, either)

3376 taskYIELD()
3589 …

Single stepping seems like ages between those 2 lines,
Previously I slept off on the keyboard,
single stepping had made that note …

3589 while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )
{

3374 if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) 1 )
{
taskYIELD();
}

Skipped through…

It does loop through in the thread:
after Starting TIM2
within
xTaskNotifyWait()

Single stepping TIM2 doesn’t fire …
What’s to be done ?

It goes into xNotifyTaskWait()

–>taskENTER_CRITICAL(),
–>prvAddCurrentTaskToDelayedList()
–>uxListRemove()
–>vListInsert()
–>portYIELD_WITHIN_API()
–>taskEXIT_CRITICAL()

and it loops a zillion times.
The point to be noted is that, while single stepping, TIM2 has not fired even once.
When freely run, it fires once and gets frozen there in the ISR.

Dont know where else to look into. :frowning:

Seems something very basic is going wrong.

Maybe best at this point to step back to a basic application with a single task that calls vTaskDelay(1000) in a loop. Also be sure your configuration has the basic safeguards in place like stack checking (configCHECK_FOR_STACK_OVERFLOW), assertions (configASSERT), heap checks (configUSE_MALLOC_FAILED_HOOK), and that you’re using a recent version FreeRTOS that validates your interrupt priorities and NVIC config.