การเขียนโปรแกรมด้วย FreeRTOS Kernel สำหรับบอร์ด Raspberry Pi Pico#


การติดตั้งและใช้งานซอฟต์แวร์เพื่อใช้งาน FreeRTOS#

บทความนี้นำเสนอขั้นตอนการใช้งาน FreeRTOS สำหรับบอร์ด Raspberry Pi Pico ดังนั้นจะต้องมีการติดตั้งซอฟต์แวร์ที่เกี่ยวข้องก่อน แนะนำให้ศึกษาจากบทความ "การติดตั้งและใช้งาน Pico SDK for RP2040 และ VS Code บน WSL 2 / Ubuntu"

ขั้นตอนการดำเนินการ#

  1. เปิดใช้งาน Ubuntu ที่ทำงานด้วย WSL สำหรับ Windows
  2. ติดตั้ง Pico C/C++ SDK ในไดเรกทอรี $HOME/pico
  3. ทำคำสั่งเพื่อดาวน์โหลดซอร์สโค้ดของ FreeRTOS-Kernel มายังคอมพิวเตอร์ของผู้ใช้ โดยเก็บไว้ในไดเรกทอรี $HOME/pico/FreeRTOS-Kernel (ทดลองใช้เวอร์ชัน FreeRTOS V10.4.6)
  4. สร้างไดเรกทอรีสำหรับโปรเจกต์ตัวอย่าง (ตั้งชื่อเป็น pico_freertos_demo) ให้อยู่ในไดเรกทอรี $HOME/pico

    • สร้างไฟล์ CMakeLists.txt สำหรับใช้กับคำสั่ง cmake (CMake Build Tools)
    • สร้างไฟล์ main.c สำหรับเขียนโค้ดตัวอย่างเพื่อสาธิตการใช้งาน FreeRTOS ในเบื้องต้น
  5. สร้างไดเรกทอรีใหม่ที่มีชื่อว่า freertos ให้อยู่ภายใต้ pico_freertos_demo

    • สร้าง Symbolic Link (เหมือน Shortcut) เพื่อให้ชี้ไปยังไดเรกทอรี FreeRTOS-Kernel
    • สร้างไฟล์ CMakeLists.txt เพื่อใช้กับซอร์สโค้ดของ FreeRTOS
    • สร้างไฟล์ FreeRTOSConfig.h เพื่อสาธิตการกำหนดค่าต่าง ๆ ในการใช้งาน FreeRTOS เป็นตัวอย่าง

โครงสร้างของไดเรกทอรีสำหรับโปรเจกต์ตัวอย่าง pico_freertos_demo จะมีลักษณะภายในดังนี้ เมื่อทำทุกขั้นตอนครบแล้ว

$ tree -L 2 $HOME/pico/pico_freertos_demo
.
├── CMakeLists.txt
├── freertos
│   ├── CMakeLists.txt
│   ├── FreeRTOS-Kernel -> ../../FreeRTOS-Kernel
│   └── FreeRTOSConfig.h
└── main.c

2 directories, 4 files

 


การดาวน์โหลด FreeRTOS-Kernel#

เริ่มต้นด้วยการทำคำสั่งใน Linux Terminal สำหรับ WSL - Ubuntu ดังนี้

# change the working directory to '$HOME/pico'
$ cd $HOME/pico

# clone the master branch of the FreeRTOS-Kernel from github
$ git clone https://github.com/FreeRTOS/FreeRTOS-Kernel.git

เมื่อได้ดาวน์โหลด FreeRTOS-Kernel มาแล้ว ลองทำคำสั่ง tree เพื่อแสดงรายการภายในไดเรกทอรี $HOME/pico/FreeRTOS-Kernel/ ตามตัวอย่างดังนี้

$ tree -L 1 $HOME/pico/FreeRTOS-Kernel/

├── CMakeLists.txt
├── GitHub-FreeRTOS-Kernel-Home.url
├── History.txt
├── LICENSE.md
├── Quick_Start_Guide.url
├── README.md
├── croutine.c
├── event_groups.c
├── include
├── list.c
├── portable
├── queue.c
├── stream_buffer.c
├── tasks.c
└── timers.c

2 directories, 13 files

 


การสร้างโปรเจกต์ใหม่#

ถัดไปเป็นการสร้างไดเรกทอรีสำหรับโปรเจกต์ใหม่ โดยตั้งชื่อให้เป็น pico_freertos_demo และสร้างไฟล์ใหม่ ในไดเรกทอรีของโปรเจกต์ ได้แก่ไฟล์ CMakeLists.txt และ main.c และสร้างไดเรกทอรี freertos

ผู้ใช้ WSL - Ubuntu สามารถใช้โปรแกรม อย่างเช่น notepad.exe ในการสร้างและแก้ไขไฟล์ดังกล่าวได้

# create a new directory for the project named 'pico_freertos_demo'
$ mkdir -p pico_freertos_demo && cd pico_freertos_demo/

File: $HOME/pico/pico_freertos_demo/CMakeLists.txt

#######################################################
cmake_minimum_required(VERSION 3.13)
# initialize the SDK based on PICO_SDK_PATH
# note: this must happen before project()
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)

# Set name of project (as 'pico_freertos_demo') and C/C++ standards
project(pico_freertos_demo C CXX ASM)
set(CMAKE_C_STANDARD   11)
set(CMAKE_CXX_STANDARD 17)

pico_sdk_init()

add_subdirectory(freertos)

# add the main.c file to the project
add_executable(${PROJECT_NAME}
    main.c
)

# Add the pico_stdlib library
target_link_libraries(${PROJECT_NAME}
    pico_stdlib
    freertos
)

# Enable USB-serial output and disable UART for printf() output
pico_enable_stdio_usb(${PROJECT_NAME}  1)
pico_enable_stdio_uart(${PROJECT_NAME} 0)

# create map/bin/hex/uf2 file in addition to ELF (.elf).
pico_add_extra_outputs(${PROJECT_NAME})
#######################################################

File: $HOME/pico/pico_freertos_demo/main.c

#include <stdio.h>
#include "pico/stdlib.h"
#include <FreeRTOS.h>
#include <task.h>

#define LED_PIN   (PICO_DEFAULT_LED_PIN)

void led_task() {
    gpio_init( LED_PIN );
    gpio_set_dir( LED_PIN, GPIO_OUT );
    while (1) {
        gpio_put( LED_PIN, 1);
        vTaskDelay( 500  /*ticks*/ );
        gpio_put(LED_PIN, 0);
        vTaskDelay( 500  /*ticks*/ );
    }
}

int main() {
    // initialize the stdio for printf()
    stdio_init_all();
    // create an LED blinking task
    xTaskCreate( led_task, "LED_Task", 128, NULL, 1, NULL );
    // start the FreeRTOS task scheduler
    vTaskStartScheduler();
    while(1){};
}

ขั้นตอนถัดไปเป็นการสร้างไดเรกทอรีที่มีชื่อว่า freertos ภายใต้ไดเรกทอรีของโปรเจกต์ pico_freertos_demo

# Under the ./freertos subdirectory:
# 1) make a softlink to the 'FreeRTOS-Kernel' directory
# 2) create and edit the 'CMakeLists.txt' file 
# 3) create and edit the 'FreeRTOSConfig.h' file

# create a subdirectory named 'freertos' for the FreeRTOS
$ mkdir -p ./freertos && cd ./freertos

ทำคำสั่งเพื่อสร้าง Soft (Symbolic) Link ให้ชี้ไปยังไดเรกทอรีของ FreeRTOS-Kernel

$ ln -s ../../FreeRTOS-Kernel ./FreeRTOS-Kernel

จากนั้นให้สร้างไฟล์ CMakeLists.txt และ FreeRTOSConfig.h ภายในไดเรกทอรี freertos ตามตัวอย่างดังนี้

File: $HOME/pico/pico_freertos_demo/freertos/CMakeLists.txt

#######################################################
set(FREERTOS_KERNEL_SRC FreeRTOS-Kernel)

add_library(freertos STATIC
    ${FREERTOS_KERNEL_SRC}/portable/GCC/ARM_CM0/port.c
    ${FREERTOS_KERNEL_SRC}/portable/MemMang/heap_3.c
    ${FREERTOS_KERNEL_SRC}/event_groups.c
    ${FREERTOS_KERNEL_SRC}/list.c
    ${FREERTOS_KERNEL_SRC}/queue.c
    ${FREERTOS_KERNEL_SRC}/stream_buffer.c
    ${FREERTOS_KERNEL_SRC}/timers.c
    ${FREERTOS_KERNEL_SRC}/tasks.c
)

target_include_directories(freertos PUBLIC
    .
    ${FREERTOS_KERNEL_SRC}/include
    ${FREERTOS_KERNEL_SRC}/portable/GCC/ARM_CM0
)
#######################################################

File: $HOME/pico/pico_freertos_demo/freertos/FreeRTOSConfig.h

// see: https://www.freertos.org/a00110.html

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/* Definitions that map the FreeRTOS port interrupt handlers
   to Pico SDK ISR handlers */
#define vPortSVCHandler         isr_svcall
#define xPortPendSVHandler      isr_pendsv
#define xPortSysTickHandler     isr_systick

#define configUSE_PREEMPTION                     1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION  0
#define configUSE_TICKLESS_IDLE                  0
#define configCPU_CLOCK_HZ                       (133000000)
#define configTICK_RATE_HZ                       (1000)
#define configMAX_PRIORITIES                     (5)
#define configMINIMAL_STACK_SIZE                 (128)
#define configMAX_TASK_NAME_LEN                  (16)
#define configUSE_16_BIT_TICKS                   0
#define configIDLE_SHOULD_YIELD                  1
#define configUSE_TASK_NOTIFICATIONS             1
#define configTASK_NOTIFICATION_ARRAY_ENTRIES    3
#define configUSE_MUTEXES                        1
#define configUSE_RECURSIVE_MUTEXES              1
#define configUSE_COUNTING_SEMAPHORES            1
#define configQUEUE_REGISTRY_SIZE                8
#define configUSE_QUEUE_SETS                     1
#define configUSE_TIME_SLICING                   1
#define configUSE_NEWLIB_REENTRANT               0
#define configENABLE_BACKWARD_COMPATIBILITY      0
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS  5
#define configSTACK_DEPTH_TYPE                   uint16_t
#define configMESSAGE_BUFFER_LENGTH_TYPE         size_t

/* Memory allocation related definitions. */
#define configSUPPORT_STATIC_ALLOCATION          0
#define configSUPPORT_DYNAMIC_ALLOCATION         1
#define configAPPLICATION_ALLOCATED_HEAP         1

/* Hook function related definitions. */
#define configUSE_IDLE_HOOK                      0
#define configUSE_TICK_HOOK                      0
#define configCHECK_FOR_STACK_OVERFLOW           0
#define configUSE_MALLOC_FAILED_HOOK             0
#define configUSE_DAEMON_TASK_STARTUP_HOOK       0

/* Run time and task stats gathering related definitions. */
#define configGENERATE_RUN_TIME_STATS            0
#define configUSE_TRACE_FACILITY                 1
#define configUSE_STATS_FORMATTING_FUNCTIONS     1

/* Co-routine related definitions. */
#define configUSE_CO_ROUTINES                    0
#define configMAX_CO_ROUTINE_PRIORITIES          1

/* Software timer related definitions. */
#define configUSE_TIMERS                         1
#define configTIMER_TASK_PRIORITY                (3)
#define configTIMER_QUEUE_LENGTH                 (8)
#define configTIMER_TASK_STACK_DEPTH             (configMINIMAL_STACK_SIZE)

/* Define to trap errors during development. */
#define configASSERT( x )

/* Optional functions */
#define INCLUDE_vTaskPrioritySet                 1
#define INCLUDE_uxTaskPriorityGet                1
#define INCLUDE_vTaskDelete                      1
#define INCLUDE_vTaskSuspend                     1
#define INCLUDE_xResumeFromISR                   1
#define INCLUDE_vTaskDelayUntil                  1
#define INCLUDE_vTaskDelay                       1
#define INCLUDE_xTaskGetSchedulerState           1
#define INCLUDE_xTaskGetCurrentTaskHandle        1
#define INCLUDE_uxTaskGetStackHighWaterMark      1
#define INCLUDE_xTaskGetIdleTaskHandle           1
#define INCLUDE_eTaskGetState                    1
#define INCLUDE_xEventGroupSetBitFromISR         1
#define INCLUDE_xTimerPendFunctionCall           1
#define INCLUDE_xTaskAbortDelay                  1
#define INCLUDE_xTaskGetHandle                   1
#define INCLUDE_xTaskResumeFromISR               1

#endif /* FREERTOS_CONFIG_H */

ข้อสังเกต: รายละเอียดเกี่ยวกับการกำหนดค่าต่าง ๆ สำหรับ FreeRTOS Customization ในไฟล์ FreeRTOSConfig.h สามารถศึกษาเพิ่มเติมได้จาก https://www.freertos.org/a00110.html

 


การเปิดโปรเจกต์ใช้งานใน VS Code IDE#

เมื่อสร้างโปรเจกต์ได้แล้ว ให้เปิดโปรเจกต์ใน VS Code IDE โดยทำคำสั่ง code . ใน Linux Terminal ในไดเรกทอรี $HOME/pico/pico_freertos_demo

รูป: เลือก Kit (Compiler / Toolchains) ใน VS Code IDE เมื่อเปิดไดเรกทอรีของโปรเจกต์

รูป: ทำขั้นตอน Build

ในไดเรกทอรี $HOME/pico/pico_freertos_demo/build มีไฟล์เอาต์พุต .uf2 สำหรับนำไปใส่ลงในไดรฟ์ของบอร์ด Pico (เมื่ออยู่ในโหมด BOOTSEL)

$ powershell.exe copy ./build/pico_freertos_demo.uf2 "D:"

รูป: ทำคำสั่งเพื่อนำไฟล์ pico_freertos_demo.uf2 ไปโปรแกรมลงในหน่วยความจำภายในของบอร์ด Pico

รูป: ตัวอย่างบอร์ดไมโครคอนโทรลเลอร์ที่ได้นำมาทดลอง

 


ตัวอย่างการเขียนโค้ดแบบมัลติทาสก์ด้วย FreeRTOS#

ตัวอย่างโค้ดต่อไปนี้ สาธิตการใช้คำสั่ง xTaskCreate(...) ของ FreeRTOS เพื่อสร้างทาสก์ได้แก่

  • ทาสก์ LED_Task ที่ทำให้ LED กระพริบด้วยอัตราคงที่ และ
  • ทาสก์ Serial_Task ที่มีตัวแปรเป็นตัวนับขึ้นครั้งละหนึ่ง แล้วส่งข้อความออกทาง Standard Output ผ่านทาง Serial โดยใช้คำสั่ง printf() ทุก ๆ 1 วินาที
#include <stdio.h>
#include "pico/stdlib.h"
#include <FreeRTOS.h>
#include <task.h>

#define LED_PIN   (PICO_DEFAULT_LED_PIN) // P25 pin

void led_task() {
    gpio_init( LED_PIN );
    gpio_set_dir( LED_PIN, GPIO_OUT );
    while (1) {
        gpio_put(LED_PIN, 1);
        vTaskDelay( pdMS_TO_TICKS(100) );
        gpio_put(LED_PIN, 0);
        vTaskDelay( pdMS_TO_TICKS(100) );
    }
}

void serial_task() {
    uint32_t counter = 0;
    while (1) {
        printf( "Counter: %lu\n", ++counter );
        vTaskDelay( pdMS_TO_TICKS(1000) );
    }
}

int main() {
    // initialize the stdio for printf()
    stdio_init_all();
    // create an LED blinking task 
    xTaskCreate(led_task, "LED_Task", 128, NULL, 1, NULL);
    // create a serial-printf task
    xTaskCreate(serial_task, "Serial_Task", 256, NULL, 1, NULL);
    // start the FreeRTOS task scheduler
    vTaskStartScheduler();
    while(1){};
}

 


กล่าวสรุป#

บทความนี้ได้นำเสนอขั้นตอนการสร้างโปรเจกต์และสร้างไฟล์ต่าง ๆ ที่เกี่ยวข้องกับการเขียนโค้ด C/C++ แบบมัลติทาสก์ (Multi-Tasking) โดยใช้ FreeRTOS สำหรับ Raspberry Pi Pico (RP2040) และนำไปใช้งานร่วมกับซอฟต์แวร์ VS Code IDE

 


This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Created: 2022-02-22 | Last Updated: 2022-02-22