How to read/write external data flash, blocking or non-blocking?

I’m new in RTOS and want to use it in my project, and hope someone can give me some advice about how to design the firmware driver.

we have a nor flash that used to read/write user data.
When we want to write data(one page, 256Bytes) to flash, the process is

  • Read the specified sector(4K Bytes) into buffer
  • Erase the sector
  • Modify the buffer and write it back to the sector

If we use a blocking function, it will be something like:

uint8_t sector_buf[4096];
void DataFlash_Write(uint32_t addr, uint8_t *data, uint16_t dataLen)
{
	while(DataFlash_IsBusy());	// Wait Data Flash ready to operate
	Nor_Flash_Read_Sector(sector_buf,addr);	// Read(backup) the sector into buffer
	while(DataFlash_IsBusy());
	Nor_Flash_Erase_Sector(addr);	// Erase this sector
	while(DataFlash_IsBusy());
	//-- Modify sector_buf
	while(DataFlash_IsBusy());
	Nor_Flash_Write_Sector(sector_buf,addr);	// Write Modified data back to the sector
}

If we use non-block function(FreeRTOS Task), it will be a large switch case(State Machine),
and then we can use xQueueSend(…) to read/write data

void DataFlash_Task(void)
{
    switch (dataFlashData.state)
    {
        case DATAFLASH_STATE_INIT:
        {
			// Do some init
            dataFlashData.state = DATAFLASH_STATE_WAIT_CMD;
        }
        case DATAFLASH_STATE_WAIT_CMD:
        {
			// Receive cmd from other
			xQueueReceive(dataFlashEvtQ, &data_flash_task_data.eventInfo, portMAX_DELAY);
			if (data_flash_task_data.eventInfo.type== cmd1)
			{
				dataFlashData.state = DATAFLASH_STATE_CMD1;
			} else if if (data_flash_task_data.eventInfo.type== cmd2)
			{
				dataFlashData.state = DATAFLASH_STATE_CMD2;
			}
			...// Other cmd cases
        }
		case DATAFLASH_STATE_READ_SECTOR:
		case DATAFLASH_STATE_ERASE_SECTOR:
		case DATAFLASH_STATE_WRITE_SECTOR:
		case DATAFLASH_STATE_READY:
		... // Other states
	}
}

We have many data types that need to save so we split falsh address into several range to operate,
it means the state machine will be very large and complicated.

For some real case, we will need to do something like:
(e.g. read file bock many times from usb drive and write into flash)

uint8_t sector_buf[4096];
void WriteDatatoFlash_type1(void) 
{
	uint8_t writeBuf[256];
	for(int i=0;i<blockNum;i++)
	{
		while(DataFlash_IsBusy());
		DataFlash_ReadSector(addr,sector_buf);
		while(DataFlash_IsBusy());
		DataFlash_EraseSector(addr);
		while(DataFlash_IsBusy());
		DataFlash_WritePage(i,readBuf);
	}
}

we have many read/write types so it will be to complicated if we use a state machine to control all process.
And because the state machine is async, so we need to use callback to notify one read/write is complete,
when we have many read/write in a loop, it also too complicated to implement the caller function…

I think one benefit we get from FreeRTOS is,
it can do context switching for us so we don’t need to worry about it takes too mach time and affect other tasks?

I use qspi nor flash as example to descript the question, but there are other perpherals act like this, we must wait and check status, do some additional operations(like erase) and finally we can read/write data to it.

I really want to know is, how to write the driver function?
should I need to use a task, receive msg from other task and perform operations?
or I can just implenent a blocking read/write function and let freeRTOS do the context for me?

May someone give me some advice about it?

Hello ntustzeus,

welcome to the forum!

To my best knowledge, there is no asynchronous I/O architecture on FreeRTOS on the application level, that is, you can not use a read() function with the option to, say, register a completion callback. Then again, there is no I/O system to begin with, so you are more or less free to design your own I/O architecture.

I’ve been writing these kinds of drivers for about 20 years now. It’s not a process that can be sketched out in a few words. On the receive side, you will probably need a two step process; the ISR signals some kind of intermediate processor task that knows about pending I/O requests and can then in its context execute a completion callback. There are subtle race condtions to deal with. Asynchronous transmits are a little bit more complicated due to the issue of serialization.

You can contact me in private if you are interested in more info or help (which nevertheless is beyond the scope of free support).

Thanks for the tips.

In fact, I’m not quite sure how to descript my question

When I start trying to use FreeRTOS, I read guides/e-books from offical site.
The offical guides tell me how to:
Create task/timer,
Send/receive queue/notify to communicate between tasks
Use mutex, semaphore to protect shared resource
and some other features provided by FreeRTOS

These concepts are not new to me, but I just don’t know what is a good design in this(embedded) field.

It may like some design patterns that can tell me: [it] is a better way to solve the problem in this case.

I don’t have this image in my mind, so I may ask some ridiculous question like:
(If I want to control perpheral like LCD, DataFlash,…)

  • Which design is better?

    • Use xQueueReceive() in LCD_Tasks/DataFlash_Tasks and excute xQueueSend() from other Task
    • Use LCD_ShowMsg(“Some string I want”) or DataFlash_Write(addr, data) directly
  • For a task

    • Should we always use xQueueReceive()/vTaskDelay()/vTaskSuspend() in a task to prevent it takes to much time?
      (As I’m worry about if I use DataFlash_Write(…) and must wait erase/busy satae before really write something, is it bad?)
    • What kind of job is worth to create a task? Is there any principle about it? Maybe something like:
      • Need to communicate with other task?
      • Need to protect some shared resources?
        (Many example/demo use task to control LED, I think it doesn’t worth to consume a big heap for this purpose)

As a beginner, I need some tips about how to controll perpherals in a real world (in FreeRTOS style), is there any article / disscussion / book about it?

Partitioning an application into multiple tasks is not restricted to embedded systems. It applies to bigger machine applications (Linux, Windows,…), too.
I think a good approach is, to find functionally encapsulated work items of your application. These could be candidates to run as separate tasks.
E.g. controlling a LCD display could be such a dedicated job maybe getting messages/information from multiple other tasks (with the need to enqueue them) to display appropriately.
As you might know there a some more mechanisms than queues provided by FreeRTOS. Each mechanism has its uses cases where it fits best.
Hint: Better avoid using vTaskSuspend which is intended for somewhat special use cases and is not so easy to get right in practice.

1 Like

Here is how I usually implement Async IO with FreeRTOS. The IO function first acquires a mutex to gain exclusive use of the resource. Then it retrieves the current’s tasks id and stores in a variable. The I/O operation is then initiated using a facility on the MCU that supports asynchronous operations, such as interrupt or DMA driven IO. The function then uses TaskNotifyTake to sleep and allow other tasks to run.

The ISR for the IO will then use TaskNotifyGive to wake the sleeping task once it has completed the operation, and the task can then unlock the mutex and continue doing whatever else.

There’s actually a simple example of this here: FreeRTOS task notifications, fast Real Time Operating System (RTOS) event mechanism

Kusuelk, one thing I will point out is that what you are describing is really SYNCRONOUS I/O, at least as far as that task is concerned, as THAT task blocks for the I/O to be completed.

Now, I find that in most cases, with a RTOS syncronous I/O for a task tends to work out fine, as presumably we have already split into different tasks things that we want to try to overlap.

Typically you use real Async I/O where you make a call to start an I/O operation and the task then continues and gets some form of notification later when it is done in cases where you don’t want to pay the cost for real multi-tasking and are using Async to get some of the advantages at a cheaper cost.

You may be thinking of being different than the other extreme of exclusive I/O when the task starts up the I/O operation and NOTHING else happens while the I/O is going on as the task uses busy-waits for the I/O.