Port Modbus Slave Stack On ATSAME54

We are referring modbus server stack implementation for SAM3X and We want to port it on SAME54P20A. First we have downloaded the example project for SAME54P20A which is using Usart_Asynchrnous module from Atmel START. Then create new top level directory for project files. Following files needs modification while porting, they are present in sub directory named ‘port’ which will contain all port specific files,

  • portserial.c
  • porttimer.c
  • portevent.c
  • portother.c
  • port.h

Atmel start project is having makefile in sub-directory ‘gcc’. Add these port specific files paths in makefile. Then add modbus stack folder to top level directory which is having all the modbus specific functions. For compilation we hav to add these all file paths in makefile which is in gcc directory. We have to first check the ‘port.h’ file whether it is suitable for our platform. If not ,then we have to define platform specific functions for serial interface,timer driver.

portserial.c: In this file there are functions for initializing USART, enabling and disabling USART module, enabling and disabling the receiver and transmitter. For enabling and disabling transmitter and receiver, we need to use corresponding low level API’s of ATSAME54P20A from ‘hri’ folder. The reference code for ATSAm3S:

Changes we have to do for ATSAME54:

In reference program for SAM3X they are using vUSARTHandler() interrupt handler in which they are checking for Rx or Tx interrupt invoked according to that respective xMBPortSerialGetByte(), xMBPortSerialPutByte() functions are called.

For SAME54P20A, there is an API- ‘usart_async_register_callback()’ so instead of ‘vUSARTHandler’, we have to create two different interrupt service routines one is for Rx interrupt and another for Tx interrupt.

portimer.c: The modbus protocol stack requires timer for detection of end of the frame. There are API’s available in SAME54P20A timer driver for initializing ,enabling and disabling the timer. For SAM3X they are using timer interrupt function TCX_IRQHANDLER() which is calling pxMBPortCBTimerExpired() function.

For ATSAME54P20A, there is timer task structure. So instead of using timer_handler we have to initialize timer structure which is having callback function in which we will call ‘pxMBPortCBTimerExpired()’ function.

If you are using FreeRTOS in your project then you need to modify ‘portevent.c’, We are not using rtos in our project so will not discuss on updating portevent functions. In next section we will update on it.

How To Use Two Timers In Same Project

If you are using SAM series controller you probably have came across this question, How do i implement two timer which generate two interrupt at different time in same project? or How we can use two timer in same project in SAME54 controller? Well SAM series controller are ARM-based MCUs and MPUs from ATMEL now Microchip.

We are using ATSAME54P20A ie SAME54 explained pro board to evaluate the functionality of it. So created new project using start.atmel.com with TC0 and TC1 along with WDT and interrputs. The project tree looks as below,

This project works well and TC0 timer interrupt is working but TC1 timer interrupt is not working. Following is code for initialization and using timer.

	TIMER_1_task1.interval = 50;
TIMER_1_task1.cb       = TIMER_1_task1_cb;
TIMER_1_task1.mode     = TIMER_TASK_REPEAT;
timer_add_task(&TIMER_1, &TIMER_1_task1);
TIMER_0_task1.interval = 10;
TIMER_0_task1.cb       = TIMER_0_task1_cb;
TIMER_0_task1.mode     = TIMER_TASK_REPEAT;
timer_add_task(&TIMER_0, &TIMER_0_task1);

Put breakpoint in ‘TIMER_1_task1_cb’ function and it never hits breakpoint, but it hits breakpoint in ‘TIMER_0_task1_cb’ function. When we check out status flag of timer1, as soon as it initializes TC0 using ‘TIMER_0_init()’ from driver_init.c it sets ‘STOP’ and ‘SLAVE’ bit.

Bit 1 – SLAVE Slave Status Flag
This bit is only available in 32-bit mode on the slave TC (i.e., TC1 and/or TC3). The bit is set when the associated master TC (TC0 and TC2, respectively) is set to run in 32-bit mode.
Bit 0 – STOP Stop Status Flag
This bit is set when the TC is disabled, on a Stop command, or on an overflow/underflow condition when the One-Shot bit in the Control B Set register (CTRLBSET.ONESHOT) is ‘1’.

Problem is while using start.atmel.com configuration there is no option to set timer in 8 bit or 16 bit or 32 bit mode. By default when we create project it sets in 32-bit mode.

Solution to this issue is set both timers in either 16-bit or 8 bit mode. The settings can be found in “Config\hpl_tc_config.h” file. The change should look like as below

#ifndef CONF_TC0_MODE
#ifndef CONF_TC1_MODE

With this change both timers are working.

How To Write RTOS Safe ISR For ATSAME

In our application it is required to use FreeRTOS API functions from an interrupt service routine(ISR). Good thing about FreeRTOS is it has provided interrupt safe API, meaning one can use FreeRTOS API functions from ISR, as they have provided two versions of some API. Functions intended for use from ISRs have “FromISR” appended to their name eg. xQueueSendFromISR

In our application we were using xQueueSendFromISR() from IRQ10 ie ‘EIC_10_IRQn’, to send message to other task, code snippet is as below

void sendMsgToIoTaskFromISR(int eventId, int data)
Task_EventMsg_t evtMsg;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;   	// wake up the higher priority task
evtMsg.eventId = eventId;
evtMsg.iData = data;    
if( xQueueSendFromISR( gIo.ioMgrQHandle, ( void * ) &evtMsg, &xHigherPriorityTaskWoken ) != pdPASS )
/* If the task with handle xTaskToNotify was blocked waiting for the notification
then sending the notification will have removed the task from the Blocked
state.  If the task left the Blocked state, and if the priority of the task
is higher than the current Running state task (the task that this interrupt
interrupted), then lHigherPriorityTaskWoken will have been set to pdTRUE
internally within vTaskNotifyGiveFromISR().  Passing pdTRUE into the
portEND_SWITCHING_ISR() macro will result in a context switch being pended to
ensure this interrupt returns directly to the unblocked, higher priority,
task.  Passing pdFALSE into portEND_SWITCHING_ISR() has no effect. */	
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );

With this it is calling assert using function “portASSERT_IF_INTERRUPT_PRIORITY_INVALID();” when we did some digging it is failing because of the condition ‘ucCurrentPriority >= ucMaxSysCallPriority’ . The reason to fail this condition is ‘ ucCurrentPriority’ is ‘0’ and ucMaxSysCallPriority is 128 (0x80). After reading FreeRTOS documentation on how ISR priorities are set. It is understood that ISR priorities are set to ‘0’ by controller and RTOS expects the priority to be ‘128’ or above, as recommended below:

 Remember that ARM Cortex-M cores use numerically low priority numbers to represent HIGH priority interrupts. This can seem counter-intuitive and is easy to forget! If you wish to assign an interrupt a low priority do NOT assign it a priority of 0 (or other low numeric value) as this will result in the interrupt actually having the highest priority in the system – and therefore potentially make your system crash if this priority is above configMAX_SYSCALL_INTERRUPT_PRIORITY. Also, do not leave interrupt priorities unassigned, as by default they will have a priority of 0 and therefore the highest priority possible.

That is suggestion from FreeRTOS, but how to change priority of interrupts, To solve this issue we have done workaround, probably it might help someone. In ‘hpl_eic.c’ file from “hpl\eic” folder, in function “_ext_irq_init” change the priority of IRQ10 to 128 or more that that. Simply add below line

NVIC_SetPriority(EIC_10_IRQn, NVIC_SIGNAL_PRI_80);

Your new code should look like

NVIC_SetPriority(EIC_10_IRQn, NVIC_SIGNAL_PRI_80);

Compile and test this new code and you will see interrupts are working in your RTOS.

This trick of setting interrupt priority to 0x80 works well for other interrupts as well, like timer interrupt, External interrupts etc. Just make sure that in init function for that I/O you are calling NVIC_SetPriority() function… Happy RTOS safe ISRing..

Further Reading [PDF]Mastering the FreeRTOS™ Real Time Kernel

How To Use WDT On Free RTOS Running ATSAMEx

After doing some google and spending almost 4 hours on choosing right place to start WDT, we have decided to share our learning here. Well we are using ATSAME54P19/20 controller from Microchip formally Atmel. Watchdog timer – WDT works well without Free RTOS (10.0) but when WDT is enabled along with RTOS it simply does not work well.

In big while(1) embedded applications with no RTOS you can always initialize WDT after peripheral initialization and feed/touch WDT in while/main loop. In case of RTOS there are multiple tasks/threads and to be specific in case for Free RTOS when you try to enable WDT right before vTaskStartScheduler(); call it simply does not work.

Tried with following solutions – like configure WDT for 20 sec reset time. And enable WDT just before scheduler starts. WDT is not waiting till expiry it is immediately resetting. Then tried enabling WDT after scheduler has started but no luck.

So tried with the RTOS Hook functions . Every RTOS provides hook functions for user application. There are few hook functions in Free RTOS as well. First tried with Idle Hook function by setting configUSE_IDLE_HOOK to 1 within FreeRTOSConfig.h, but it seems this vApplicationIdleHook() function gets called only when there is nothing to be done by other tasks. In our application we have 4 tasks, along with timers enabled in it. This is reason the vApplicationIdleHook() function was not getting executed.

There is other hook function ie tick hook function vApplicationTickHook(), but that is getting called at every tick. Enabling WDT at every tick is not right place. So we need some hook function which will be called once after the RTOS scheduler is initialized. And vApplicationDaemonTaskStartupHook() is the right function. Initialize this hook by setting configUSE_DAEMON_TASK_STARTUP_HOOK to 1 in FreeRTOSConfig.h , put your WDT initialization and enable code here. From any other task or timer task you can feed WDT (wdt_feed(&WDT_0);).

void vApplicationDaemonTaskStartupHook(void)
#ifdef WDT_IN_USE 
uint32_t clk_rate;
uint16_t timeout_period;
clk_rate       = 1000;
timeout_period = 4096;
wdt_set_timeout_period(&WDT_0, clk_rate, timeout_period);

Happy WDT testing with RTOS on ATMEL controllers 🙂