บทความนี้นำเสนอขั้นตอนการใช้งานซอฟต์แวร์ ARM Keil MDK (MDK-Lite) และเขียนโค้ดโดยใช้ระบบปฏิบัติการเวลาจริง (RTOS) ตามรูปแบบของ CMSIS-RTOS2 โดยใช้ ARM-Keil RTX5 และ FreeRTOS เป็น Real-Time Kernel ตามลำดับ
คำแนะนำ: บทความนี้นำเสนอต่อจากบทความก่อนหน้า
ขั้นตอนการใช้งาน CMSIS-RTOS2 โดยใช้ RTX5
เริ่มต้นด้วยการสร้างโปรเจกต์ใหม่ ตั้งชื่อโปรเจกต์และสร้างไดเรกทอรีใหม่
เลือกไมโครคอนโทรลเลอร์เป็นเป้าหมาย ในกรณีนี้คือ STM32F407VGTx โดยใช้บอร์ด STM32F4 Discovery
จากนั้นจะต้องเลือกคอมโพเนนต์สำหรับ Run-Time Environment (RTE) ตามรูปดังนี้
เมื่อคลิกเลือกคอมโพเนนต์ตามตัวอย่างแล้ว ช่องทุกเลือกจะต้องเป็นสีเขียวอ่อน จากนั้นกดปุ่ม OK
ข้อสังเกต: ถ้าต้องการใช้งาน Peripherals ของ STM32F4 นอกเหนือจาก GPIO ก็สามารถคลิกเพิ่มได้ เช่น UART/USART
จากนั้นเพิ่มไฟล์ main.c
ในโปรเจกต์ แล้วใส่โค้ดตัวอย่างต่อไปนี้
โค้ดนี้สาธิตการสร้างทาสก์ (Task) แต่สำหรับ CMSIS-RTOS2 เราจะเรียกว่า เธรด (Thread) จำนวน 2 ชุด และกำหนดหน้าที่ของเธรดเพื่อสลับสถานะลอจิกของขา GPIO ที่ตรงกับ LED บนบอร์ด (พอร์ต GPIOD ขา 14 และ 15 ตามลำดับ) โดยเว้นช่วงระยะเวลาในการสลับสถานะลอจิก
เมื่อจะเริ่มต้นใช้งาน RTOS จะต้องเรียกคำสั่ง osKernelInitialize()
ของ CMSIS-RTOS2 ถัดไปเป็นการสร้างเธรด โดยใช้คำสั่ง osThreadNew()
และถ้าจะเริ่มต้นการทำงาน RTOS Kernel จะต้องทำคำสั่ง osKernelStart()
คำแนะนำ: การใช้งานคำสั่งต่าง ๆ ของ CMSIS-RTOS2 API สามารถศึกษาได้จาก https://arm-software.github.io/CMSIS_5/RTOS2/html/rtos_api2.html
#include "stm32f4xx_hal.h"
#include "cmsis_os2.h"// function prototypes
void LEDs_Config( void );
void led1_blink( void * ); // task entry function: task1
void led2_blink( void * ); // task entry function: task2// global variables
static osThreadId_t task1_handle;
static osThreadId_t task2_handle;int main (void) {
HAL_Init();
SystemCoreClockUpdate();
LEDs_Config(); // Initialize LEDs osKernelInitialize(); // Initialize CMSIS-RTOS
const osThreadAttr_t task1_attr = {
.name = "Task1",
.priority = (osPriority_t) osPriorityNormal,
.stack_size = 256
};
const osThreadAttr_t task2_attr = {
.name = "Task2",
.priority = (osPriority_t) osPriorityNormal,
.stack_size = 256
}; task1_handle = osThreadNew( led1_blink, NULL, &task1_attr );
task2_handle = osThreadNew( led2_blink, NULL, &task2_attr );
osKernelStart(); //Start thread execution
while(1) {}
}void LEDs_Config(void) {
GPIO_InitTypeDef GPIO_InitStruct;
// turn on clock for GPIO port D
__HAL_RCC_GPIOD_CLK_ENABLE();
// Configure GPIO port D (Pin 12..15) as output pins
GPIO_InitStruct.Pin = (GPIO_PIN_12 | GPIO_PIN_13
| GPIO_PIN_14 | GPIO_PIN_15);
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init( GPIOD, &GPIO_InitStruct );
// drive these output pins to low
HAL_GPIO_WritePin( GPIOD, GPIO_PIN_12 | GPIO_PIN_13
| GPIO_PIN_14 | GPIO_PIN_15, GPIO_PIN_RESET );
}void led1_blink( void *arg ){
while (1) {
HAL_GPIO_TogglePin( GPIOD, GPIO_PIN_14 );
osDelay(200);
}
}void led2_blink( void *arg ){
osDelay(100);
while (1) {
HAL_GPIO_TogglePin( GPIOD, GPIO_PIN_15 );
osDelay(200);
}
}
ในตัวอย่างนี้ เธรดทั้งสองมีระดับความสำคัญเท่ากัน ระดับความสำคัญหรือ Priority Levels สำหรับ CMSIS-RTOS2 มีดังนี้
- osPriorityIdle (1, lowest priority)
- osPriorityLow (8)
- osPriorityBelowNormal (16)
- osPriorityNormal (24, default priority)
- osPriorityAboveNormal (32)
- osPriorityHigh (40)
- osPriorityRealTime (48)
- osPriorityISR (56, highest priority)
.
การกำหนดค่าต่าง ๆ ที่เกี่ยวข้องกับการทำงานของ RTX5 สามารถดูได้ในไฟล์ RTX_Config.h
.
ลองดูตัวอย่างถัดไป เป็นการสร้างและใช้งาน Binary Semaphore (เซมาฟอร์แบบไบนารี) ดังนี้ โดยมีคำสั่งที่เกี่ยวข้อง เช่น
osSemaphoreNew()
osSemaphoreAcquire()
osSemaphoreRelease()
#include "stm32f4xx_hal.h"
#include "cmsis_os2.h"// function prototypes
void LEDs_Config( void );
void led1_blink( void * ); // task entry function: task1
void led2_blink( void * ); // task entry function: task2// global variables
static osThreadId_t task1_handle;
static osThreadId_t task2_handle;
static osSemaphoreId_t sem_id;int main (void) {
HAL_Init();
SystemCoreClockUpdate();
osKernelInitialize(); // Initialize CMSIS-RTOS LEDs_Config(); // Initialize LEDs
sem_id = osSemaphoreNew( 1, 1, NULL ); // Create a semaphore const osThreadAttr_t task1_attr = {
.name = "Task1",
.priority = (osPriority_t) osPriorityNormal,
.stack_size = 256
};
const osThreadAttr_t task2_attr = {
.name = "Task2",
.priority = (osPriority_t) osPriorityNormal,
.stack_size = 256
};
task1_handle = osThreadNew( led1_blink, NULL, &task1_attr );
task2_handle = osThreadNew( led2_blink, NULL, &task2_attr );
osKernelStart(); //Start thread execution
while(1) {}
}void LEDs_Config(void) {
GPIO_InitTypeDef GPIO_InitStruct;
// turn on clock for GPIO port D
__HAL_RCC_GPIOD_CLK_ENABLE();
// Configure GPIO port D (Pin 12..15) as output pins
GPIO_InitStruct.Pin = (GPIO_PIN_12 | GPIO_PIN_13
| GPIO_PIN_14 | GPIO_PIN_15);
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init( GPIOD, &GPIO_InitStruct );
// drive these output pins to low
HAL_GPIO_WritePin( GPIOD, GPIO_PIN_12 | GPIO_PIN_13
| GPIO_PIN_14 | GPIO_PIN_15, GPIO_PIN_RESET );
}void led1_blink( void *arg ){
while (1) {
osSemaphoreAcquire( sem_id, osWaitForever );
HAL_GPIO_TogglePin( GPIOD, GPIO_PIN_14 );
osDelay(100);
osSemaphoreRelease( sem_id );
osDelay(100);
}
}void led2_blink( void *arg ){
osDelay(100);
while (1) {
osSemaphoreAcquire( sem_id, osWaitForever );
HAL_GPIO_TogglePin( GPIOD, GPIO_PIN_15 );
osDelay(100);
osSemaphoreRelease( sem_id );
osDelay(100);
}
}
ขั้นตอนการใช้งาน CMSIS-RTOS2 โดยใช้ FreeRTOS
เริ่มต้นด้วยการสร้างโปรเจกต์ใหม่ ตั้งชื่อโปรเจกต์และสร้างไดเรกทอรีใหม่ และเลือกไมโครคอนโทรลเลอร์เป็นเป้าหมาย ในกรณีนี้คือ STM32F407VGTx โดยใช้บอร์ด STM32F4 Discovery
จากนั้นจะต้องเลือกคอมโพเนนต์สำหรับ Run-Time Environment (RTE) ตามรูปดังนี้
เมื่อสร้างโปรเจกต์ใหม่แล้ว ถัดไปเป็นการเพิ่มไฟล์ main.c
คราวนี้ เราจะลองเลือกใช้ “User Code Template” และใช้ STM32Cube Framework:Classic โดยใช้ชื่อ main สำหรับ STM32Cube HAL
จากนั้นจะได้ไฟล์ main.c
เพิ่มเข้ามาในโปรเจกต์ ภายในมีโค้ดตามโครงสร้างในรูปต่อไปนี้
ให้นำโค้ดจากตัวอย่างก่อนหน้านี้ มาเพิ่มลงในโค้ด main.c
เช่น การเปิดใช้งาน GPIO สำหรับ LEDs และการสร้างเซมาฟอร์และเธรด เป็นต้น
จากนั้นให้ทำขั้นตอน Build Target และทดสอบการทำงานของโค้ด โดยใช้ In-Circuit Debugger (ผ่านทางอุปกรณ์ ST-Link Debugger) หรือใช้ Software Simulator ก็ได้
ข้อสังเกต: HSE (External High-Speed Oscillator) สำหรับบอร์ด STM32F4 Discovery ใช้ความถี่เท่ากับ 8 MHz (ไม่ใช่ 25 MHz) ดังนั้นจึงจะต้องแก้ไขค่า HSE_VALUE
จาก 25000000 ให้เป็น 8000000 ในไฟล์ system_stm32f4xx.c
และในไฟล์ stm32f4xx_hal_conf.h
และการคำนวณความถี่ใหม่จะเป็นดังนี้
f_CPU = ((HSE_VALUE/PLL_M)*PLL_N)/PLL_P = ((8MHz/8)*336)/2 = 168 MHz
การใช้งาน USART1 เพื่อส่งข้อความ
ถ้าจะลองใช้ USART เช่น USART1 และเลือกใช้ขา PB6 และ PB7 สำหรับ TX และ RX ตามลำดับ และตั้งค่า Baudrate = 115200 ก็สามารถเพิ่มโค้ดลงในไฟล์ main.c
โดยเขียนให้อยู่ในรูปของฟังก์ชันดังนี้
void USART1_Config(void){
GPIO_InitTypeDef GPIO_InitStruct;
__GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
__HAL_RCC_USART1_CLK_ENABLE();
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK) {
Error_Handler();
}
}
ในฟังก์ชัน main()
จะต้องมีการเรียกฟังก์ชัน USART1_Config()
ก่อนใช้งาน USART1
สำหรับการใช้งาน USART1 เราจะใช้เป็นการส่งข้อความออกไป จากเธรดทั้งสอง ดังนี้
void led1_blink( void *arg ){
char sbuf[32];
uint32_t cnt = 0;
while (1) {
osSemaphoreAcquire( sem_id, osWaitForever );
HAL_GPIO_TogglePin( GPIOD, GPIO_PIN_14 );
sprintf( sbuf, "Task1, count: %06d\r\n", ++cnt );
HAL_UART_Transmit(&huart1, (uint8_t*)sbuf, strlen(sbuf), 100);
osDelay(100);
osSemaphoreRelease( sem_id );
osDelay(100);
}
}void led2_blink( void *arg ){
char sbuf[32];
uint32_t cnt = 0;
osDelay(100);
while (1) {
osSemaphoreAcquire( sem_id, osWaitForever );
HAL_GPIO_TogglePin( GPIOD, GPIO_PIN_15 );
sprintf( sbuf, "Task2, count: %06d\r\n", ++cnt );
HAL_UART_Transmit(&huart1, (uint8_t*)sbuf, strlen(sbuf), 100);
osDelay(100);
osSemaphoreRelease( sem_id );
osDelay(100);
}
}
หรือถ้าต้องการการส่งข้อความที่ระบุความถี่ของระบบ (SystemCoreClock) ก็ใช้คำสั่งดังนี้ ซึ่งจะได้เท่ากับ 168000000 (168 MHz)
char sbuf[40];
sprintf( sbuf, "Clock freq.: %lu\r\n", SystemCoreClock );
HAL_UART_Transmit(&huart1, (uint8_t*)sbuf, strlen(sbuf), 100);
กล่าวสรุป
เราได้เห็นขั้นตอนการสร้างโปรเจกต์และโค้ดตัวอย่างที่สาธิตการใช้งาน CMSIS-RTOS2 โดยใช้ Real-Time Kernel สองกรณีคือ Keil RTX5 และ FreeRTOS