FreeRTOS on AVR with external RAM

AVR microcontrollers aren’t best choice to run FreeRTOS scheduler due to low RAM. Atmega128 has only 4K of RAM memory, so this limits FreeRTOS functionality to very basic. Anyway this can be solved by adding extra RAM connected to external memory interface. We have already implemented external memory block of 8K previously so now we can muck around.

Lets continue with our previous code having several simple tasks (button state reading, LCD output and LED flash), and add more to it. We are going to set up external RAM for storing heaps. This will allow to store large data buffers without worrying of heap and stack overlap.

So first of all we take care of linker options. In AVRStudio5 project properties AVR/GNU C Linker-> Miscellaneous enter linker option:

-Wl,--defsym=__heap_start=0x801100,--defsym=__heap_end=0x8030ff

This will point linker to use memory area from 0x801100 to 0x8030ff (whole external RAM) for heap only.

Second step is to set up microcontroller to use external memory. To make things clean and modular we are going to create separate driver source files xmem.c and xmem.h. And write simple XMEM_init() function:

void vXMEMInit(void)
{
	MCUCR |= (1<<SRE);   /* External memory interface enable */
    XMCRA = 0;
    XMCRB |= (1<<XMM1)|(1<<XMM0);//PC7..PC5 released pins
}

In the beginning of main routine we simply call this function to initialize external RAM before using it.

Writing USART drivers

We are going to need an USART functionality for debugging and displaying information in convenient way. So first of all we need drivers that could be used in tasks. Probably most convenient way using USART is to send messages through queues. This way any task could add communicate to USART by using messaging service instead accessing peripheral directly. So we are going to implement two queues – one for TX and another for RX channels.

//receive and transmit queues

xQueueHandlexRxedChars=NULL;

xQueueHandlexCharsForTx=NULL;

Then during USART initialization we create the queues.

xRxedChars=xQueueCreate(uxQueueLength,(signedchar)sizeof(signedchar));

xCharsForTx=xQueueCreate(uxQueueLength,(signedchar)sizeof(signedchar));

queue length is given when USART is initialized (30 in our example). Now when queues are ready initially they can be used to communicate with USART. Messages to queues are put and read through two custom functions that make life easier:

portBASE_TYPE xUSART0PutChar(unsigned char cOutChar)
{
//Return false if after the block time there is no room on the Tx queue.
	if( xQueueSend( xCharsForTx, &cOutChar, xBlockTime ) != pdPASS )
	{
		return pdFAIL;
	}
	//enable usart UDRE interrupt to transmit
	prvUDRIE0InterruptOn();
	return pdPASS;
}
portBASE_TYPE xUSART0GetChar(unsigned char *pcRxedChar)
{
/* Get the next character from the buffer.  Return false if no characters
	are available, or arrive before xBlockTime expires. */
	if( xQueueReceive( xRxedChars, pcRxedChar, xBlockTime ) )
	{
		return pdTRUE;
	}
	else
	{
		return pdFALSE;
	}
}

These functions give additional safety when there is no chars in receiver queue and when transmit queue is full. As you may noticed there is a private function prvUDRIE0InterruptOn() called in xUSART0PutChar(). This simply enables USART data ready interrupt once there is at least one character inside transmit queue.

USART transmit and receive is performed through interrupt routines.

ISR( USART0_RX_vect )
{
signed char cChar;
signed portBASE_TYPE xHigherPriorityTaskWoken;

	cChar = UDR0;

	xQueueSendFromISR( xRxedChars, &cChar, &xHigherPriorityTaskWoken );

}
ISR( USART0_UDRE_vect )
{
signed char cChar, cTaskWoken;

	if( xQueueReceiveFromISR( xCharsForTx, &cChar, &cTaskWoken ) == pdTRUE )
	{
		/* Send the next character queued for Tx. */
		UDR0 = cChar;
	}
	else
	{
		/* Queue empty, nothing to send. */
		prvUDRIE0InterruptOff();
	}
	
}

This is robust and efficient way of communicating. As mentioned above – transmitter ISR is enabled only when there is data to transmit in queue. Receiver ISR is only called when any data is present in RX buffer.

Putting single characters isn’t convenient way of sending messages. So there are two additional functions that allow sending whole string of text to queue

portBASE_TYPExUSART0SendData(constunsignedchar*data)


when string is stored in RAM and

portBASE_TYPExUSART0SendDataP(constunsignedchar*data)


when message is sent from Flash. In order to save precious RAM it is recommended to store static text messages in Flash memory like:

staticconstuint8_tbutton[]PROGMEM="Button ON\r\n";


This is pretty basic implementation of USART that just works for this demo program. It is worth noticing that there could be good idea of implementing different message queues for separate message types like errors, actual data. This way there is less chance that different messages gets mixed inside queue. We used single queue for transmitter as long as it worked fine.

In order to test USART receiver there is a small code added to LCD task which simply tests if there is a character received and then displays it on LCD screen:

if (xUSART0GetChar(&rxchar)!=pdFALSE)
{
	LCDGotoXY(14,0);
	LCDsendChar(rxchar);
}

Creating USART task

As we added new resource to our list obviously we can create another demo task that utilizes sending USART messages.

void vUSART0TxTask( void *pvParameters )
{
static const uint8_t button[] PROGMEM="Button ON\r\n";
static const uint8_t rtos[] PROGMEM="Button OFF\r\n";
portTickType xLastWakeTime;
const portTickType xFrequency = 2000;
vUSART0Init(30);
xLastWakeTime=xTaskGetTickCount();
	for( ;; )
	{
if(xButtonSemaphore!=NULL)
{
	if (xSemaphoreTake(xButtonSemaphore, (portTickType)10)==pdTRUE)
	{
		xUSART0SendDataP(button);
		//don't give back semaphore as it is one way trigger
	}else{
		xUSART0SendDataP(rtos);
	}
}
		vTaskDelayUntil(&xLastWakeTime,xFrequency);		
	}
}

this task simply takes semaphores from button task and displays button status every 2s in terminal window.

XMEM testing task

As we have added external memory to our device we are going to test its functionality. First of all we allocate 256 bytes of heap memory with malloc() function. Then write some dummy data to it and then test if data written is correct. This ensures that data is physically written to external RAM. Testing status messages are displayed in terminal window.

void vXMEMTestTask( void *pvParameters )
{
static const uint8_t xmemok[] PROGMEM="XMEM OK\r\n";
static const uint8_t xmemfail[] PROGMEM="XMEM FAIL!\r\n";
static const uint8_t heapfull[] PROGMEM="Heap Full\r\n";
static const uint8_t heaprdfail[] PROGMEM="Heap Test Fail\r\n";
static const uint8_t heaprdok[] PROGMEM="Heap Test OK\r\n";
portSHORT *xmem;
portSHORT xdata;
unsigned portSHORT index, testflag=0;
portTickType xLastWakeTime;
const portTickType xFrequency = 10000;
	xmem = malloc(BUFFER_SIZE);
xLastWakeTime=xTaskGetTickCount();
	if (xmem!=NULL)
	{
		xUSART0SendDataP(xmemok);
	}
	else 
	{
		xUSART0SendDataP(xmemfail);
	}
	for (;;)
	{
		xdata=1;
		for(index = 0; index < BUFFER_SIZE; index++)
			{
    			xmem[index] = xdata++;
			}
		xUSART0SendDataP(heapfull);
		//read heap and test
		xdata=1;
		for(index = 0; index < BUFFER_SIZE; index++)
		{
			if (xmem[index] != xdata++)
			{
				testflag=1;
				break;
			}
		}
		if (!testflag)
		{
			xUSART0SendDataP(heaprdok);
		}
		else
		{
			//reset flag
			testflag=0;
			xUSART0SendDataP(heaprdfail);
		}
		vTaskDelayUntil(&xLastWakeTime,xFrequency);	
	}
}

Heap memory test is run every 10s.

Running the system

So far we have added two more tasks to our scheduler:

xTaskCreate( vUSART0TxTask, ( signed char * ) "USART", configMINIMAL_STACK_SIZE, NULL, mainUSART_TASK_PRIORITY, NULL );	
xTaskCreate( vXMEMTestTask, ( signed char * ) "XMEM", configMINIMAL_STACK_SIZE, NULL, mainXMEM_TASK_PRIORITY, NULL );

Including idle task we already have 6 tasks running. This is a terminal window view where you can see buttn status and heap memory test results.

You can download AVRStudio5 project files here (M128RTOS_external_heap.zip).

2 Comments:

  1. manish.electronicbazaar@gmail.com

    Awesome! Everything I desired summarised in a very short way.

  2. Pingback: FreeRTOS on STM32 - Do It Easy With ScienceProg

Leave a Reply

Your email address will not be published. Required fields are marked *