## NUC140 規格 MCU: NUC140VE3CN NUC140 / NUC240為 Cortex™-M0 32 位元微控制器系列。 ## 建立 FreeRTOS 專案 1. 專案建立: 下載芯片對應到的 BSP (Board Support Package), BSP 為一系列程式碼的集合,用於硬體與作業系統之間的溝通,下載好 BSP 後根據 Software Packs 建立 Keil 專案 ![](https://hackmd.io/_uploads/rJdZEr-F2.png) 2. 下載 RTOS 與引入 RTOS 到專案中: 下載 [RTOS 原始碼](https://github.com/FreeRTOS/FreeRTOS) ```shell $ git clone "https://github.com/FreeRTOS/FreeRTOS" ``` 以下為 FreeRTOS 專案結構 - 載入內核檔案,包含基礎資料結構,task 等等: `FreeRTOS\Source` - 處理器相關的硬體介面抽象層: `FreeRTOS\Source\portable\GCC\ARM_CM0` - 記憶體分配以及管理單元,在目錄底下可以看到有許多檔案,這邊使用 `heap_2.c`: `FreeRTOS\Source\portable\MemMang` - 定義內核各個介面的標頭檔: `FreeRTOS\Source\include` 於專案中設定引入的標頭檔路徑 ![](https://hackmd.io/_uploads/r1QXtH-Kn.png) 接著進入到專案管理中,將 FreeRTOS 中檔案引入到專案中 <img src="https://hackmd.io/_uploads/HyK9trWth.png" width=350> <img src="https://hackmd.io/_uploads/SyNecHZK2.png" width=350> - STARTUP: 放置芯片對應的設定檔案,如 `system_NUC100series.c` 等等 - USER: 放置內核各個介面的標頭檔,如 `FreeRTOS.h` - CMSIS: 由於 NUC 140 為 Arm M0 系列,因此我們需要使用 `core_cm0.c` 等檔案 - FWLB: - DOC: 說明文件 - FreeRTOS/src: 放置內核檔案,如 `task.c` 等等 - FreeRTOS/port: 放置記憶體管理,如 `heap_2.c` 以及處理器相關的硬體介面抽象層,如 `port.c` - CODE: 執行的主要程式碼,如 `main.c` - NUC140/src: 放置芯片廠商提供的程式碼,如 `SYS_init.c` 用於初始化芯片的程式碼,或是 `LCD.c` 定義與 LCD 互動的 API - NUC140/lib: 放置芯片周圍設備的驅動程式,如 `gpio.c`, `uart.c` 3. 設定 `FreeRTOSConfig.h` ```c /* FreeRTOS V7.4.0 - Copyright (C) 2013 Real Time Engineers Ltd. FEATURES AND PORTS ARE ADDED TO FREERTOS ALL THE TIME. PLEASE VISIT http://www.FreeRTOS.org TO ENSURE YOU ARE USING THE LATEST VERSION. */ #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. *----------------------------------------------------------*/ /* Ensure stdint is only used by the compiler, and not the assembler. */ #ifdef __ICCARM__ #include <stdint.h> extern uint32_t SystemCoreClock; #endif #ifdef __ARMCC_VERSION #include "NUC100Series.h" #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 ( 10 ) #define configMINIMAL_STACK_SIZE ( ( unsigned short ) 50 ) #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 11 * 1024 ) ) #define configMAX_TASK_NAME_LEN ( 10 ) #define configUSE_TRACE_FACILITY 1 #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 1 #define configUSE_MUTEXES 1 #define configQUEUE_REGISTRY_SIZE 8 #define configCHECK_FOR_STACK_OVERFLOW 0 #define configUSE_RECURSIVE_MUTEXES 1 #define configUSE_MALLOC_FAILED_HOOK 0 #define configUSE_APPLICATION_TASK_TAG 0 #define configUSE_COUNTING_SEMAPHORES 1 #define configGENERATE_RUN_TIME_STATS 0 #define configUSE_QUEUE_SETS 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 10 #define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 ) /* 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 /* Cortex-M specific definitions. */ #ifdef __NVIC_PRIO_BITS /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */ #define configPRIO_BITS __NVIC_PRIO_BITS #else #define configPRIO_BITS 4 /* 15 priority levels */ #endif /* The lowest interrupt priority that can be used in a call to a "set priority" function. */ #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xf /* The highest interrupt priority that can be used by any interrupt service routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER PRIORITY THAN THIS! (higher priorities are lower numeric values. */ #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 /* Interrupt priorities used by the kernel port layer itself. These are generic to all Cortex-M ports, and do not rely on any particular library functions. */ #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) /* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!! See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) /* 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. */ #define vPortSVCHandler SVC_Handler #define xPortPendSVHandler PendSV_Handler #define xPortSysTickHandler SysTick_Handler #endif /* FREERTOS_CONFIG_H */ ``` ## 程式碼範例 以下程式碼將示範使用 FreeRTOS 提供的 API 與 NUC140 周圍設備進行互動,以下程式編譯便完成燒入後,將會在 LCD 上看到文字訊息,並且看到 Task 的切換,每過了 1 秒鐘,將會切換 Task,並且下方的 LED 燈將會指示目前執行的 Task。在 LCD 的第三行,將會看到一個 Timer 進行計時。 ```c #include <stdio.h> #include "NUC100Series.h" #include "MCU_init.h" #include "SYS_init.h" #include "LCD.h" #include "FreeRTOS.h" #include "task.h" #include "timers.h" #include "semphr.h" #define THREAD_TEST_STACK_SIZE configMINIMAL_STACK_SIZE #define THREAD_TEST_PRIORITY ( tskIDLE_PRIORITY + 1 ) #define TASK_SWITCH_TIME 1000 void Thread_Task1( void *pvParameters ); void Thread_Task2( void *pvParameters ); void Thread_Task3( void *pvParameters ); void Thread_Task4( void *pvParameters ); TaskHandle_t HTask1; TaskHandle_t HTask2; TaskHandle_t HTask3; TaskHandle_t HTask4; SemaphoreHandle_t xSemaphore; TimerHandle_t Timers; void vStartThreadTasks( void ) { xSemaphore = xSemaphoreCreateCounting( 4, 0 ); xTaskCreate( Thread_Task1, (const char * ) "Th_Task1", THREAD_TEST_STACK_SIZE, NULL, THREAD_TEST_PRIORITY, &HTask1); xTaskCreate( Thread_Task2, (const char * ) "Th_Task2", THREAD_TEST_STACK_SIZE, NULL, THREAD_TEST_PRIORITY, &HTask2); xTaskCreate( Thread_Task3, (const char * ) "Th_Task3", THREAD_TEST_STACK_SIZE, NULL, THREAD_TEST_PRIORITY, &HTask3); xTaskCreate( Thread_Task4, (const char * ) "Th_Task4", THREAD_TEST_STACK_SIZE, NULL, THREAD_TEST_PRIORITY, &HTask4); } void Thread_Task1( void *pvParameters ) { portTickType xLastTime1; xLastTime1 = xTaskGetTickCount(); for( ;; ) { vTaskDelayUntil( &xLastTime1, TASK_SWITCH_TIME); print_Line(1, "Task 1"); PC12 = 0; PC13 = 1; PC14 = 1; PC15 = 1; vTaskDelay((4 * TASK_SWITCH_TIME)); } } void Thread_Task2( void *pvParameters ) { portTickType xLastTime2; xLastTime2 = xTaskGetTickCount(); for( ;; ) { vTaskDelayUntil( &xLastTime2, (2 * TASK_SWITCH_TIME)); print_Line(1, "Task 2"); PC12 = 1; PC13 = 0; PC14 = 1; PC15 = 1; vTaskDelay((4 * TASK_SWITCH_TIME)); } } void Thread_Task3( void *pvParameters ) { portTickType xLastTime3; xLastTime3 = xTaskGetTickCount(); for( ;; ) { vTaskDelayUntil( &xLastTime3, (3 * TASK_SWITCH_TIME) ); print_Line(1, "Task 3"); PC12 = 1; PC13 = 1; PC14 = 0; PC15 = 1; vTaskDelay((4 * TASK_SWITCH_TIME)); } } void Thread_Task4( void *pvParameters ) { portTickType xLastTime4; xLastTime4 = xTaskGetTickCount(); for( ;; ) { vTaskDelayUntil( &xLastTime4, (4 * TASK_SWITCH_TIME)); print_Line(1, "Task 4"); PC12 = 1; PC13 = 1; PC14 = 1; PC15 = 0; vTaskDelay((4 * TASK_SWITCH_TIME)); } } void vTimerCallback(TimerHandle_t xTimer) { const uint32_t ulMaxExpiryCountBeforeStopping = UINT32_MAX; uint32_t ulCount; configASSERT( xTimer ); ulCount = ( uint32_t ) pvTimerGetTimerID( xTimer ); ulCount++; char buf[15] = ""; sprintf(buf, "%s= %02d:%02d", "Timer", ulCount/60, ulCount%60); print_Line(2, buf); if( ulCount >= ulMaxExpiryCountBeforeStopping ) { print_Line(2, "Reach to limit"); xTimerStop( xTimer, 0 ); } else { vTimerSetTimerID( xTimer, ( void * ) ulCount ); } } int main(void) { SYS_Init(); init_LCD(); clear_LCD(); print_Line(0, "RTOS TEST"); GPIO_SetMode(PC, BIT12, GPIO_MODE_OUTPUT); GPIO_SetMode(PC, BIT13, GPIO_MODE_OUTPUT); GPIO_SetMode(PC, BIT14, GPIO_MODE_OUTPUT); GPIO_SetMode(PC, BIT15, GPIO_MODE_OUTPUT); Timers = xTimerCreate("Timer", 1000, pdTRUE, (void *) NULL, vTimerCallback); if(Timers == NULL) { } else { if(xTimerStart(Timers, 0) != pdPASS) { } } vStartThreadTasks(); vTaskStartScheduler(); } ``` 我們拆解上面程式碼 首先是標頭檔引用的部分,我們引入 `FreeRTOS.h` 和 NUC140 提供周圍設備的 API 接口,如 `LCD.h`,方便我們接下來使用 FreeRTOS 與設備進行互動 ```c #include <stdio.h> #include "NUC100Series.h" #include "MCU_init.h" #include "SYS_init.h" #include "LCD.h" #include "FreeRTOS.h" #include "task.h" #include "timers.h" #include "semphr.h" ``` 接著是 main 的部分,在 main 中我們需要初始化芯片以及周圍設備,並且啟動某一些 pin 讓我們接下來能夠使用 ```c int main(void) { SYS_Init(); init_LCD(); clear_LCD(); GPIO_SetMode(PC, BIT12, GPIO_MODE_OUTPUT); GPIO_SetMode(PC, BIT13, GPIO_MODE_OUTPUT); GPIO_SetMode(PC, BIT14, GPIO_MODE_OUTPUT); GPIO_SetMode(PC, BIT15, GPIO_MODE_OUTPUT); } ``` 我們在上面啟用了 LCD 這個設備,而下面我們用了 `print_Line` 這個 LCD 提供的 API,讓我們輸出內容到 LCD 上面。 接著看到 FreeRTOS 與硬體的互動 ```c void Thread_Task1( void *pvParameters ) { portTickType xLastTime1; xLastTime1 = xTaskGetTickCount(); for( ;; ) { vTaskDelayUntil( &xLastTime1, TASK_SWITCH_TIME); print_Line(1, "Task 1"); PC12 = 0; PC13 = 1; PC14 = 1; PC15 = 1; vTaskDelay((4 * TASK_SWITCH_TIME)); } } ``` 我們在 Task 中存取 `PC12` 等腳位,這邊表示啟用 LED,LED 之所以能夠啟用是因為我們在前面的 main 中將 GPIO pin 12 設為輸出模式,因此上面的操作有效。 以下為時序圖 ![](https://hackmd.io/_uploads/ByKGtxnY3.png)