To generate a FreeRTOS solution which is fully integrated into our components, we need to determine the current state of application development, make some preliminary considerations and then develop implementation examples.
Current State of Application Development on NetX 90
All netX 90 application examples are based on different components. A typical project contains the following components:
Preliminary Considerations on Porting
The following list describes the considerations made:
Component | Objectives |
---|---|
FreeRTOS | Use the latest FreeRTOS V10.4.3 Modifications shall be documented so that we can easily upgrade to the next version |
CMSIS | The FreeRTOS port shall use the vendor-specific definitions |
NetX peripheral driver | The netX 90 peripheral drivers shall be configured using the FreeRTOS Mutex implementation FreeRTOS is required to trigger the timer driver's systick handler to archive compatibility between the two components |
Newlib C | A threadsafe implementation possibility shall be provided |
CifX API | The cifX API shall be configured using the FreeRTOS Mutex implementation |
Porting Implementation
FreeRTOS
The current version of FreeRTOS can be downloaded from www.freertos.org. For porting, we need the folder <FreeRTOS/Source> and from the portable folder we need to use one of the following port/ portmacro files:
portable | FPU | MPU |
---|---|---|
ARM_CM3 | ||
ARM_CM3_MPU | ||
ARM_CM4FP | ||
ARM_CM4_MPU |
CMSIS
The vendor-specific definitions, which are required for FreeRTOS, are the following:
#define configCPU_CLOCK_HZ ( SystemCoreClock ) #define configPRIO_BITS __NVIC_PRIO_BITS /* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS standard names. */ #define xPortPendSVHandler PendSV_Handler #define vPortSVCHandler SVC_Handler #define xPortSysTickHandler SysTick_Handler
NetX Peripheral Driver
For the dynamic peripheral driver implementation, the following definitions need to be made in the netx_drv_user_conf.h:
/*! * \brief Define that has to be set if an RTOS is used. */ #define RTOS_USED /*! * \brief RTOS_USED relevant define that ignores the compiler error that is * generated to let the engineer know, that those functions has to be implemented * by him. */ #define RTOS_ERROR_IGNORE /*! * \brief RTOS_USED relevant type used for the element to be locked */ #define DRV_LOCK_T SemaphoreHandle_t /*! * \brief RTOS_USED relevant initializer type of the type to be locked as rvalue is in default the mutex initializer type. */ #define DRV_LOCK_INITIALIZER_TYPE SemaphoreHandle_t /*! * \brief RTOS_USED relevant initializer value of the type to be locked as rvalue is in default the mutex initializer value. */ #define DRV_LOCK_INITIALIZER_VALUE NULL /*! * \brief RTOS_USED relevant initializer of the type to be locked as rvalue. */ #define DRV_LOCK_INITIALIZER xSemaphoreCreateMutex() /*! * \brief RTOS_USED relevant function executing the lock. * Shall return DRV_LOCKED in case the mutex is not free or * might be implemented with priority inheritance or similiar functionality. */ #define DRV_LOCK(__HANDLE__) if(xSemaphoreTake((__HANDLE__)->tLock,0)!=pdTRUE ){return DRV_LOCKED;} /*! * \brief RTOS_USED relevant function releasing the lock. */ #define DRV_UNLOCK(__HANDLE__) xSemaphoreGive((__HANDLE__)->tLock)
Static implementation: tbd.
Newlib C
For a threadsafe newlib C implementation, we need to define:
#define configUSE_NEWLIB_REENTRANT 1
and provide reentrant implementations sbrk. Broadly used is this independent implementation: newlib and FreeRTOS
CifX API
The cifX Toolkit configuration can use newlib and FreeRTOS functions/ objects. The changes are:
/* * * cifX Toolkit API is working with millisecond increments and thus requires a timer resolution of at least 1kHz. * * This implementation excepts the kernel tick to be set appropriately, otherwise * custom implementation is needed facilitating a hardware clock or similar. * * If FreeRTOS' configuration configTICK_RATE_HZ is set to 1000, FreeRTOS ticks may be used. * In case this value exceeds 1000Hz, limitation of macro pdMS_TO_TICKS() need * to be considered and the calculation to convert timer ticks to ms needs updating. * * vTaskDelay( requires INCLUDE_vTaskDelay to be set. * * xTaskGetTickCount() * As the toolkit operates on 32bit timeout values, 16bit timer resolutions * should be avoided in the first place. * In case configUSE_16_BIT_TICKS is set, casting and overflow handling needs to added * to guarantee expected handling. * */ /*****************************************************************************/ /*! Get Millisecond counter value (used for timeout handling) * \return Counter value with a resolution of 1ms */ /*****************************************************************************/ uint32_t OS_GetMilliSecCounter(void) { #if ((1000 == configTICK_RATE_HZ) && !configUSE_16_BIT_TICKS) TickType_t tTicks = xTaskGetTickCount(); return (uint32_t)tTicks; #else #error OS_Sleep(): Custom timer needed #endif /* configTICK_RATE_HZ, !configUSE_16_BIT_TICKS */ } /*****************************************************************************/ /*! Sleep for the given time * \param ulSleepTimeMs Time in ms to sleep (0 will sleep for 50us) */ /*****************************************************************************/ void OS_Sleep(uint32_t ulSleepTimeMs) { #if ((1000 == configTICK_RATE_HZ) && !configUSE_16_BIT_TICKS) vTaskDelay(pdMS_TO_TICKS(ulSleepTimeMs)); #else #error OS_Sleep(): Custom timer needed #endif /* configTICK_RATE_HZ, !configUSE_16_BIT_TICKS */ } /*****************************************************************************/ /*****************************************************************************/ /*!< Interrupt handling */ /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ /*! DSR task evaluating DPM changes. * Function should be instantiated as a high priority task * (all cifX API calls should be lower that its priority). * \param pvParameters User parameter (unused) */ /*****************************************************************************/ void CIFX_DSRHandler(void* pvParameters) { while(s_ptIrqDevInst) { (void)ulTaskNotifyTake(pdTRUE, /* Clear the notification value on exit */ portMAX_DELAY);/* Block indefinitely */ if(s_fDSRActive) { cifXTKitDSRHandler(s_ptIrqDevInst); } else break; } vTaskDelete(s_xDsrTask); s_xDsrTask = NULL; } /*****************************************************************************/ /*! IRQ handler for DPM interrupt. * Fixed location in NVIC (idpm_com_host_IRQn). */ /*****************************************************************************/ void IDPM_IRQHandler(void) { /* Prohibit any call if toolkit is not fully configured */ if(!s_fOSInitDone || !s_ptIrqDevInst || !s_xDsrTask) { /* Spurious IRQ, disable if detected */ NVIC_DisableIRQ(idpm_com_host_IRQn); } else { int iResult = 0; iResult = cifXTKitISRHandler(s_ptIrqDevInst, 1); /* Ignore register block as we are netX90 APP */ /* "The DSR is expected to be interruptible and will process the interrupt events in non-interrupt mode." -> Signal to DSR thread */ if(CIFX_TKIT_IRQ_DSR_REQUESTED == iResult) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* Unblock the handling task so the task can perform any processing necessitated by the interrupt. s_xDsrTask is the task's handle, which was obtained when the task was created. */ vTaskNotifyGiveFromISR(s_xDsrTask, &xHigherPriorityTaskWoken); /* Force a context switch if xHigherPriorityTaskWoken is now set to pdTRUE. */ portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } } /*****************************************************************************/ /*! Enable interrupts on the given device. * Achieved by creating an DSR service task which is called by ISR handler * (the latter is installed in NVIC). * \param pvOSDependent Pointer to internal device structure */ /*****************************************************************************/ void OS_EnableInterrupts(void* pvOSDependent) { s_fDSRActive = true; (void)xTaskCreate(CIFX_DSRHandler, "CIFXDSR", configMINIMAL_STACK_SIZE, /* Depending on the application usage, more Stack may be needed */ (void*) NULL, CIFX_INTERRUPT_DSR_TASK_PRIORITY, &s_xDsrTask); /* Store context locally of ISR/DSR */ s_ptIrqDevInst = (DEVICEINSTANCE*)pvOSDependent; /* Only used the shared interrupt */ NVIC_SetPriority(idpm_com_host_IRQn, NETX90_IDPM_IRQ_PRIORITY); NVIC_EnableIRQ(idpm_com_host_IRQn); } /*****************************************************************************/ /*! Disable interrupts on the given device * \param pvOSDependent Pointer to internal device structure */ /*****************************************************************************/ void OS_DisableInterrupts(void* pvOSDependent) { UNREFERENCED_PARAMETER(pvOSDependent); /* Disable physical interrupt of DPM */ NVIC_DisableIRQ(idpm_com_host_IRQn); NVIC_SetPriority(idpm_com_host_IRQn, 0); /* Reset priority to default */ /* Trigger deinit of DSR task */ s_fDSRActive = false; (void)xTaskNotifyGive(s_xDsrTask); taskYIELD(); /* Task handle is deleted in DSR epilogue */ s_ptIrqDevInst = NULL; } /*****************************************************************************/ /*****************************************************************************/ /*!< Mutex functions (channel access) */ /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ /*! Create mutex * \return Handle to new created mutex */ /*****************************************************************************/ void* OS_CreateMutex(void) { return (void*) xSemaphoreCreateMutex(); } /*****************************************************************************/ /*! Try to acquire mutex with timeout * \param pvMutex Handle to mutex * \param ulTimeout Timeout in ms to wait for mutex * \return !=0 if mutex was acquired */ /*****************************************************************************/ int OS_WaitMutex(void* pvMutex, uint32_t ulTimeout) { BaseType_t tRes; #if ((1000 == configTICK_RATE_HZ) && !configUSE_16_BIT_TICKS) tRes = xSemaphoreTake((SemaphoreHandle_t)pvMutex, (TickType_t)pdMS_TO_TICKS(ulTimeout)); #else #error OS_WaitMutex(): Custom timer needed #endif /* configTICK_RATE_HZ, !configUSE_16_BIT_TICKS */ /* Map return value for cifX Toolkit */ if(pdPASS == tRes) return 1; else return 0; } /*****************************************************************************/ /*! Release previously acquired mutex * \param pvMutex Handle to mutex */ /*****************************************************************************/ void OS_ReleaseMutex(void* pvMutex) { (void)xSemaphoreGive((SemaphoreHandle_t)pvMutex); } /*****************************************************************************/ /*! Delete mutex * \param pvMutex Handle to mutex */ /*****************************************************************************/ void OS_DeleteMutex(void* pvMutex) { vSemaphoreDelete((SemaphoreHandle_t)pvMutex); } /*****************************************************************************/ /*****************************************************************************/ /*!< Lock functions */ /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ /*! Create Lock (Usually same as mutex, but does not support timed waiting) * \return Handle to created lock */ /*****************************************************************************/ void* OS_CreateLock(void) { /* Ideally, any task which calls cifX API functions requiring a lock (ultimately changes to HSK cells) * should be running in task mode. * While changing the implementation of OS_*Lock() functions to work in interrupt mode could be done, * it should be seen as application specific. * Special care needs to be taken to be fully IRQ safe (beware of e.g. configMAX_SYSCALL_INTERRUPT_PRIORITY * and other FreeRTOS/IRQ setting!). */ /* For critical sections, no initialisation is needed, so return a valid placeholder. */ return (void*)1; } /*****************************************************************************/ /*! Acquire a lock * \param pvLock Handle to lock */ /*****************************************************************************/ void OS_EnterLock(void* pvLock) { /* Taken from FreeRTOS 10.2. documentation (taskENTER_CRITICAL()): * "Preemptive context switches only occur inside an interrupt, so will not occur when interrupts are disabled. * Therefore, the task that called taskENTER_CRITICAL() is guaranteed to remain in the Running state until * the critical section is exited, unless the task explicitly attempts to block or yield * (which it should not do from inside a critical section)." */ taskENTER_CRITICAL(); } /*****************************************************************************/ /*! Release a lock * \param pvLock Handle to lock */ /*****************************************************************************/ void OS_LeaveLock(void* pvLock) { taskEXIT_CRITICAL(); } /*****************************************************************************/ /*! Delete a lock * \param pvLock Handle to lock */ /*****************************************************************************/ void OS_DeleteLock(void* pvLock) { UNREFERENCED_PARAMETER(pvLock); } /*****************************************************************************/ /*****************************************************************************/ /*!< Event functions (signalled from DSR) */ /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ /*! Create event * \return Handle to created event */ /*****************************************************************************/ void* OS_CreateEvent(void) { SemaphoreHandle_t tSem = xSemaphoreCreateBinary(); return (void*)tSem; } /*****************************************************************************/ /*! Signal event * \param pvEvent Handle to event */ /*****************************************************************************/ void OS_SetEvent(void* pvEvent) { (void)xSemaphoreGive((SemaphoreHandle_t)pvEvent); } /*****************************************************************************/ /*! Reset event * \param pvEvent Handle to event */ /*****************************************************************************/ void OS_ResetEvent(void* pvEvent) { /* no equivalent available and not used by toolkit (currently) */ UNREFERENCED_PARAMETER(pvEvent); } /*****************************************************************************/ /*! Delete event * \param pvEvent Handle to event */ /*****************************************************************************/ void OS_DeleteEvent(void* pvEvent) { vSemaphoreDelete((SemaphoreHandle_t)pvEvent); } /*****************************************************************************/ /*! Wait for event * \param pvEvent Handle to event * \param ulTimeout Timeout in ms to wait for event * \return CIFX_EVENT_SIGNALLED if event was set, CIFX_EVENT_TIMEOUT otherwise */ /*****************************************************************************/ uint32_t OS_WaitEvent(void* pvEvent, uint32_t ulTimeout) { BaseType_t tRes; #if ((1000 == configTICK_RATE_HZ) && !configUSE_16_BIT_TICKS) tRes = xSemaphoreTake((SemaphoreHandle_t)pvEvent, (TickType_t)pdMS_TO_TICKS(ulTimeout)); #else #error OS_WaitEvent(): Custom timer needed #endif /* configTICK_RATE_HZ, !configUSE_16_BIT_TICKS */ /* Map return value for cifX Toolkit */ if(pdPASS == tRes) return CIFX_EVENT_SIGNALLED; else return CIFX_EVENT_TIMEOUT; }