FreeRTOS Porting Guide(2/4)

2015. 10. 8. 15:162018년 이전 관심사/Cortex

반응형

FreeRTOS

Download FreeRTOS

FreeRTOS사이트에 접속하면, FreeRTOS에 관한 정보와 문서, 소스 코드를 다운 받을 수 있다.

Folder Structure

FreeRTOS를 다운로드 받으면, 크게 Free-RTOS-Plus 폴더와 FreeRTOS 폴더를 확인 할 수 있다.
자세한 내용은 http://www.freertos.org/a00017.html를 참고 하기 바란다.

20150922_193631

먼저 FreeRTOS 폴더를 확인해 보면 Demo 폴더와 Source 폴더로 구성되어 있으며, Demo 폴더에는 FreeRTOS 커널 기반의 예제 데모 프로젝트가 존재 한다.

20150922_193652

Source 폴더에는 FreeRTOS Kenel 구현 파일들이 존재하며, Portable 폴더에는 프로세스별, IDE별 FreeRTOS Port를 정의한 파일이 존재 한다.

20150922_193732

본 장에서는 Cortex M0계열의 W7500 프로세서를 이용하여 FreeRTOS를 구동하므로, portable/RVDS/ARM_CM0 폴더에 있는 port.c와 portmacro.h를 사용하려고 한다.
추가로 FreeRTOS의 Memory Management를 위해 portable/MemMang 폴더에 있는 파일들을 프로젝트에 추가 해야 한다. Memory Management에 대한 자세한 설명은 http://www.freertos.org/a00111.html를 참고 하기 바란다.

WIZwiki-W7500ECO FreeRTOS Porting

본 장에서는 WIZwiki-W7500ECO Board에 FreeRTOS를 Porting 하는 방법에 대해 설명한다. 본 장에서 구동하는 예제는 WIZwiki-W7500ECO Board의 LED1을 20ms 마다 Blink 하는 프로그램이다.

Step 1 : Download W7500 Library and Example

https://github.com/Wiznet/W7500에 접속한 후, W7500 Library Code를 다운로드 한다.

20150922_201106

Step 2 : Create Keil Project for W7500

W7500-master\W7500x_Library_Examples\Projects\Peripheral_Examples 에 프로젝트를 위한 폴더를 생성한 후, 적당한 프로젝트 파일 하나는 복사한다. 나는 OS\FreeRTOS 폴더를 생성 한 후, GPIO\Blink_LED 안에 있는 파일들을 OS\FreeRTOS 폴더에 복사해서 사용하였다. 원래는 새로운 프로젝트를 만들어서 사용하는 것이 정석이나, WIZnet에서 이미 만들어 놓은 프로젝트의 설정 값들을 그대로 사용하기 위해 위와 같은 방법으로 프로젝트를 생성 하였다.

OS\FreeRTOS\MDK 폴더의 W7500x_GPIO_Blink_LED.uvproj를 열면 아래 그림과 같이 W7500의 GPIO를 On/Off 하는 예제를 확인 할 수 있다.

Include Path 설정법

본 예제는 GPIO\Blink_LED와 폴더 Depth가 동일하기 때문에 Keil 프로젝트의 설정 변경 없이 그대로 사용할 수 있다. 하지만 폴더 Depth를 기존 프로젝트들과 다르게 만들었을 경우, 컴파일이 안되는 문제가 발생 할 수 있다. 이런 경우, 아래 방법을 이용하여 Include Path를 설정하면 문제를 해결 할 수 있다.

[Project]-[Options for Target] 선택 후, [C/C++]탭의 Include Paths에 아래 폴더들의 경로를 지정한다.

  • \Libraries\CMSIS\Device\WIZnet\W7500\Include
  • \Libraries\W7500x_stdPeriph_Driver\inc
  • \Libraries\CMSIS\Include
  • ..\

20150922_202318

20150922_202539

Step 3 : Porting FreeRTOS

Step1에서 다운로드 받은 FreeRTOS 파일을 Step 2의 OS\FreeRTOS\ 폴더에 아래 그림과 같이 복사한다.
FreeRTOS 폴더에는 아래 그림에 있는 내용만 남겨 놓고 다른 폴더들은 다 삭제 하기 바란다.

20150922_203751

위 과정을 완료 후, Keil 프로젝트에 FreeRTOS 그룹을 만들고 아래 파일들은 추가 한다.

  • OS\FreeRTOS\FreeRTOS\croutine.c
  • OS\FreeRTOS\FreeRTOS\list.c
  • OS\FreeRTOS\FreeRTOS\queue.c
  • OS\FreeRTOS\FreeRTOS\tasks.c
  • OS\FreeRTOS\FreeRTOS\timers.c
  • OS\FreeRTOS\FreeRTOS\portable\MemMang\heap_2.c
  • OS\FreeRTOS\FreeRTOS\portable\RVDS\ARM_CM0\port.c

20150922_204021

Create FreeRTOSConfig.h

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/*-----------------------------------------------------------
 * Application specific definitions.
 *
 * These definitions should be adjusted for your particular hardware and
 * application requirements.
 *
 * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
 * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
 *
 * See http://www.freertos.org/a00110.html.
 *----------------------------------------------------------*/

/* Prevent C code being included by the IAR assembler. */
#ifndef __IASMARM__
    #include <stdint.h>
    extern uint32_t SystemCoreClock;
#endif

#define configUSE_PREEMPTION            1
#define configUSE_IDLE_HOOK                0
#define configUSE_TICK_HOOK                0
#define configCPU_CLOCK_HZ                ( SystemCoreClock )
#define configTICK_RATE_HZ                ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES            ( 5 )
#define configMINIMAL_STACK_SIZE        ( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE            ( ( size_t ) ( 10 * 1024 ) )
#define configMAX_TASK_NAME_LEN            ( 16 )

#define configUSE_TRACE_FACILITY    0
#define configUSE_16_BIT_TICKS        0
#define configIDLE_SHOULD_YIELD        1
#define configUSE_MUTEXES                1

/* Co-routine definitions. */
#define configUSE_CO_ROUTINES             0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )

/* Software timer definitions. */
#define configUSE_TIMERS                1
#define configTIMER_TASK_PRIORITY        ( 2 )
#define configTIMER_QUEUE_LENGTH        5
#define configTIMER_TASK_STACK_DEPTH    ( 80 )

/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet        1
#define INCLUDE_uxTaskPriorityGet        1
#define INCLUDE_vTaskDelete                1
#define INCLUDE_vTaskCleanUpResources    1
#define INCLUDE_vTaskSuspend            1
#define INCLUDE_vTaskDelayUntil            1
#define INCLUDE_vTaskDelay                1
#define INCLUDE_eTaskGetState            1

/* Normal assert() semantics without relying on the provision of an assert.h
header file. */
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }

/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names - or at least those used in the unmodified vector table. */
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler

/* Bump up the priority of recmuCONTROLLING_TASK_PRIORITY to prevent false
positive errors being reported considering the priority of other tasks in the
system. */
#define recmuCONTROLLING_TASK_PRIORITY ( configMAX_PRIORITIES - 1 )

#endif
/* Kernel includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

#include "W7500x_gpio.h"


/* Priorities at which the tasks are created. */
#define mainQUEUE_RECEIVE_TASK_PRIORITY    ( tskIDLE_PRIORITY + 2 )
#define mainQUEUE_SEND_TASK_PRIORITY        ( tskIDLE_PRIORITY + 1 )

/* The rate at which data is sent to the queue.  The 200ms value is converted
to ticks using the portTICK_PERIOD_MS constant. */
#define mainQUEUE_SEND_FREQUENCY_MS            ( 200 / portTICK_PERIOD_MS )

/* The number of items the queue can hold.  This is 1 as the receive task
will remove items as they are added, meaning the send task should always find
the queue empty. */
#define mainQUEUE_LENGTH                    ( 1 )

/* Values passed to the two tasks just to check the task parameter
functionality. */
#define mainQUEUE_SEND_PARAMETER            ( 0x1111UL )
#define mainQUEUE_RECEIVE_PARAMETER            ( 0x22UL )

/* The number of the LED that is toggled. */
#define mainLED_TO_TOGGLE                    ( 0 )

/*
 * The tasks as described in the comments at the top of this file.
 */
static void prvQueueReceiveTask( void *pvParameters );
static void prvQueueSendTask( void *pvParameters );

/*
 * Called by main() to create the simply blinky style application if
 * mainCREATE_SIMPLE_BLINKY_DEMO_ONLY is set to 1.
 */
void main_blinky( void );

void vParTestToggleLED( unsigned long ulLED );

/*
 * The hardware only has a single LED.  Simply toggle it.
 */
extern void vMainToggleLED( void );

/* The queue used by both tasks. */
static QueueHandle_t xQueue = NULL;


void main_blinky( void )
{
    /* Create the queue. */
    xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( unsigned long ) );

    if( xQueue != NULL )
    {
        /* Start the two tasks as described in the comments at the top of this
        file. */
        xTaskCreate( prvQueueReceiveTask,                    /* The function that implements the task. */
                    "Rx",                                     /* The text name assigned to the task - for debug only as it is not used by the kernel. */
                    configMINIMAL_STACK_SIZE,                 /* The size of the stack to allocate to the task. */
                    ( void * ) mainQUEUE_RECEIVE_PARAMETER, /* The parameter passed to the task - just to check the functionality. */
                    mainQUEUE_RECEIVE_TASK_PRIORITY,         /* The priority assigned to the task. */
                    NULL );                                    /* The task handle is not required, so NULL is passed. */

        xTaskCreate( prvQueueSendTask, "TX", configMINIMAL_STACK_SIZE, ( void * ) mainQUEUE_SEND_PARAMETER, mainQUEUE_SEND_TASK_PRIORITY, NULL );

        /* Start the tasks and timer running. */
        vTaskStartScheduler();
    }

    /* If all is well, the scheduler will now be running, and the following
    line will never be reached.  If the following line does execute, then
    there was insufficient FreeRTOS heap memory available for the idle and/or
    timer tasks    to be created.  See the memory management section on the
    FreeRTOS web site for more details. */
    for( ;; );
}

static void prvQueueSendTask( void *pvParameters )
{
    TickType_t xNextWakeTime;
    const unsigned long ulValueToSend = 100UL;

    /* Check the task parameter is as expected. */
    configASSERT( ( ( unsigned long ) pvParameters ) == mainQUEUE_SEND_PARAMETER );

    /* Initialise xNextWakeTime - this only needs to be done once. */
    xNextWakeTime = xTaskGetTickCount();

    for( ;; )
    {
        /* Place this task in the blocked state until it is time to run again.
        The block time is specified in ticks, the constant used converts ticks
        to ms.  While in the Blocked state this task will not consume any CPU
        time. */
        vTaskDelayUntil( &xNextWakeTime, mainQUEUE_SEND_FREQUENCY_MS );

        /* Send to the queue - causing the queue receive task to unblock and
        toggle the LED.  0 is used as the block time so the sending operation
        will not block - it shouldn't need to block as the queue should always
        be empty at this point in the code. */
        xQueueSend( xQueue, &ulValueToSend, 0U );
    }
}

static void prvQueueReceiveTask( void *pvParameters )
{
    unsigned long ulReceivedValue;

    /* Check the task parameter is as expected. */
    configASSERT( ( ( unsigned long ) pvParameters ) == mainQUEUE_RECEIVE_PARAMETER );

    for( ;; )
    {
        /* Wait until something arrives in the queue - this task will block
        indefinitely provided INCLUDE_vTaskSuspend is set to 1 in
        FreeRTOSConfig.h. */
        xQueueReceive( xQueue, &ulReceivedValue, portMAX_DELAY );

        /*  To get here something must have been received from the queue, but
        is it the expected value?  If it is, toggle the LED. */
        if( ulReceivedValue == 100UL )
        {
            vParTestToggleLED( mainLED_TO_TOGGLE );
            ulReceivedValue = 0U;
        }
    }
}

void vParTestToggleLED( unsigned long ulLED )
{
    if( GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == Bit_SET )
        GPIO_ResetBits(GPIOA, GPIO_Pin_1);
    else
        GPIO_SetBits(GPIOA, GPIO_Pin_1);
}

Modified main.c

/**
  ******************************************************************************
  * @file    Uart/Printf/main.c 
  * @author  IOP Team
  * @version V1.0.0
  * @date    01-May-2015
  * @brief   Main program body
  ******************************************************************************
  * @attention
  *
  * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
  * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
  * TIME. AS A RESULT, WIZnet SHALL NOT BE HELD LIABLE FOR ANY
  * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
  * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
  * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
  *
  * <h2><center>&copy; COPYRIGHT 2015 WIZnet Co.,Ltd.</center></h2>
  ******************************************************************************
  */ 

/* Includes ------------------------------------------------------------------*/
#include <stdio.h>
#include "W7500x_uart.h"
#include "W7500x_gpio.h"

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "queue.h"

extern void main_blinky( void );
/*
 * Initialise the LED ports, and create a timer that periodically toggles an LED
 * just to provide a visual indication that the program is running.
 */


int main()
{
    GPIO_InitTypeDef GPIO_InitDef;
    SystemInit();
    SystemCoreClockUpdate();

    GPIO_InitDef.GPIO_Pin = ( GPIO_Pin_1 | GPIO_Pin_2) ; // Set to Pin_1 (LED1) & Pin_2 (LED2)
    GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT; // Set to Mode Output
    GPIO_Init(GPIOA, &GPIO_InitDef);
    PAD_AFConfig(PAD_PA,(GPIO_Pin_1|GPIO_Pin_2), PAD_AF1); // PAD Config - LED used 2nd Function    

    main_blinky();

    for(;;);
}


void vApplicationMallocFailedHook( void )
{
    /* vApplicationMallocFailedHook() will only be called if
    configUSE_MALLOC_FAILED_HOOK is set to 1 in FreeRTOSConfig.h.  It is a hook
    function that will get called if a call to pvPortMalloc() fails.
    pvPortMalloc() is called internally by the kernel whenever a task, queue,
    timer or semaphore is created.  It is also called by various parts of the
    demo application.  If heap_1.c or heap_2.c are used, then the size of the
    heap available to pvPortMalloc() is defined by configTOTAL_HEAP_SIZE in
    FreeRTOSConfig.h, and the xPortGetFreeHeapSize() API function can be used
    to query the size of free heap space that remains (although it does not
    provide information on how the remaining heap might be fragmented). */
    taskDISABLE_INTERRUPTS();
    for( ;; );
}
/*-----------------------------------------------------------*/

void vApplicationIdleHook( void )
{
    /* vApplicationIdleHook() will only be called if configUSE_IDLE_HOOK is set
    to 1 in FreeRTOSConfig.h.  It will be called on each iteration of the idle
    task.  It is essential that code added to this hook function never attempts
    to block in any way (for example, call xQueueReceive() with a block time
    specified, or call vTaskDelay()).  If the application makes use of the
    vTaskDelete() API function (as this demo application does) then it is also
    important that vApplicationIdleHook() is permitted to return to its calling
    function, because it is the responsibility of the idle task to clean up
    memory allocated by the kernel to any task that has since been deleted. */
}
/*-----------------------------------------------------------*/

void vApplicationStackOverflowHook( TaskHandle_t pxTask, char *pcTaskName )
{
    ( void ) pcTaskName;
    ( void ) pxTask;

    /* Run time stack overflow checking is performed if
    configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2.  This hook
    function is called if a stack overflow is detected. */
    taskDISABLE_INTERRUPTS();
    for( ;; );
}
/*-----------------------------------------------------------*/

void vApplicationTickHook( void )
{
    /* This function will be called by each tick interrupt if
    configUSE_TICK_HOOK is set to 1 in FreeRTOSConfig.h.  User code can be
    added here, but the tick hook is called from an interrupt context, so
    code must not attempt to block, and only the interrupt safe FreeRTOS API
    functions can be used (those that end in FromISR()).  The code in this
    tick hook implementation is for demonstration only - it has no real
    purpose.  It just gives a semaphore every 50ms.  The semaphore unblocks a
    task that then toggles an LED.  Additionally, the call to
    vQueueSetAccessQueueSetFromISR() is part of the "standard demo tasks"
    functionality. */

    /* The semaphore and associated task are not created when the simple blinky
    demo is used. */

}
/*-----------------------------------------------------------*/

Fixed Error

Error: L6200E: Symbol PendSV_Handler multiply defined (by port.o and w7500x_it.o).
Error: L6200E: Symbol SysTick_Handler multiply defined (by port.o and w7500x_it.o).

위 Error는 PendSV_Handler와 SysTick_Handler 함수가 중복되어 있어서 발생한 Error이다. 이를 해결하기 위해서는 W7500x_it.c를 아래와 같이 수정하면 된다.

__weak void PendSV_Handler(void)
{}
__weak void SysTick_Handler(void)
{}

Step 4 : Execute Example

https://youtu.be/aXhDnW_doLs

반응형