การสร้างโปรเจกต์และโค้ดตัวอย่าง
ขั้นตอนในการใช้งาน มีดังนี้
- เปิดใช้งานโปรแกรม Keil uVision 5 IDE (สำหรับระบบปฏิบัติการ Windows เท่านั้น)
- สร้างโปรเจกต์ใหม่ ตั้งชื่อโปรเจกต์ และเลือกหรือสร้างไดเรกทอรีใหม่สำหรับโปรเจกต์
- เลือก Target Device เป็นไมโครคอนโทรลเลอร์ตระกูล STM32F4 เช่น STM32F407VGTx สำหรับบอร์ด STM32F Discovery ของบริษัท STMicroelectronics
- ในหน้าต่าง “Manage Run-Time Environment” (RTE) ผู้ใช้จะต้องเลือก RTE Components ต่าง ๆ ที่จะใช้งาน เช่น CMSIS CORE, Device Startup, STM32Cube HAL และ RTOS (FreeRTOS) เป็นต้น และให้สังเกตว่า ในการเลือกคอมโพเนนต์ต่าง ๆ สำหรับ RTE อาจมีความเชื่อมโยงกันหรือความจำเป็นที่จะต้องใช้คอมโพเนนต์อื่นร่วมด้วย (Component Dependency) ถ้าเลือกได้ถูกต้อง ในช่องเล็ก ๆ สำหรับ Sel (Selected) จะเป็นสีเขียวอ่อน
- สร้างไฟล์
main.c
แล้วเพิ่มลงในโปรเจกต์ - แก้ไขไฟล์
FreeRTOSConfig.h
ในโปรเจกต์
ข้อสังเกต: ในตัวอย่างนี้ เราจะใช้เฉพาะ FreeRTOS และไม่ได้ใช้ API ตามรูปแบบของ CMSIS RTOS (Version 1) หรือ CMSIS RTOS2 (Version 2)
ในไฟล์ FreeRTOSConfig.h
เราสามารถตั้งค่าการใช้งาน FreeRTOS ได้ และจะเห็นว่า มีการประกาศใช้ C Macros ที่มีชื่อขึ้นต้นด้วย config
เช่น
configTICK_RATE_HZ
หมายถึง อัตราหรือจังหวะการนับของ SystemTick Timer ที่ใช้สำหรับการทำงานของ FreeRTOS ในกรณีนี้คือ 1000 Hz (1 kHz)configTOTAL_HEAP_SIZE
หมายถึง ขนาดของ Heap Memory ใน SRAM ที่อนุญาตให้ใช้ได้configMINIMAL_STACK_SIZE
หมายถึง ขนาดที่เล็กที่สุดของ Stack (หน่วยนับเป็น 32-bit Words ไม่ใช่ไบต์) ที่จะใช้กับ FreeRTOS Task
ให้แก้ไขตามรูปตัวอย่างต่อไปนี้ (ให้สังเกตบรรทัดที่มีการใช้สี Highlight)
.
ในไฟล์ main.c
(ให้สร้างไฟล์แล้วเพิ่มลงในโปรเจกต์) ให้ลองใช้โค้ดตัวอย่างต่อไปนี้ และเป็นการสาธิตการสร้าง FreeRTOS Task จำนวน 2 ทาสก์ (“Task1” และ “Task2”)
ทาสก์ทั้งสอง มีระดับความสำคัญเท่ากัน และมีหน้าที่คอยทำให้ LED ที่เกี่ยวข้องเปลี่ยนสถานะเป็น High และ Low ตามลำดับ โดยเว้นระยะเวลาตามที่กำหนดไว้
ทาสก์ทั้งสอง ไม่ได้ทำงานอิสระจากกัน เนื่องจากว่า จะต้องมีการเข้าใช้ Binary Semaphore
บอร์ด STM32F4 Discovery มีขาของพอร์ต GPIOD ที่ตรงกับ LED บนบอร์ดจำนวน 4 ดวง ได้แก่ขาหมายเลย 12, 13, 14, 15 และเรากำหนดให้ Task1 ใช้ขา GPIOD 13 และ Task2 ใช้ขา GPIOD 15 ตามลำดับ
#include <stm32f4xx_hal.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"// functions prototypes
void LEDs_Config(void);
void vTask1( void *pvParameters ); // task entry function for Task1
void vTask2( void *pvParameters ); // task entry function for Task2// extern functions
extern void SystemInit(void);
extern void SystemCoreClockUpdate(void);// global variable
SemaphoreHandle_t xSemaphore = NULL;int main( void ) { SystemInit();
HAL_Init();
SystemCoreClockUpdate();
// Configure GPIO pins for onboard LEDs
LEDs_Config();
// Create Task 1
xTaskCreate( vTask1, "Task1",
configMINIMAL_STACK_SIZE,
(void*)NULL, tskIDLE_PRIORITY+1, NULL );
// Create Task 2
xTaskCreate( vTask2, "Task2",
configMINIMAL_STACK_SIZE+1, (void*)NULL,
tskIDLE_PRIORITY+1, NULL );
// Create a binary semaphore
xSemaphore = xSemaphoreCreateBinary();
xSemaphoreGive( xSemaphore );
// Start the scheduler
vTaskStartScheduler();
while(1){} // loop forever
}void vTask1( void *pvParameters ) {
while (1) {
xSemaphoreTake(xSemaphore,(TickType_t) portMAX_DELAY);
HAL_GPIO_TogglePin( GPIOD, GPIO_PIN_13 );
vTaskDelay( 100 / portTICK_PERIOD_MS );
xSemaphoreGive(xSemaphore);
vTaskDelay( 100 / portTICK_PERIOD_MS );
}
}void vTask2( void *pvParameters ) {
while (1) {
xSemaphoreTake(xSemaphore,(TickType_t) portMAX_DELAY);
HAL_GPIO_TogglePin( GPIOD, GPIO_PIN_15 );
vTaskDelay( 100 / portTICK_PERIOD_MS );
xSemaphoreGive(xSemaphore);
vTaskDelay( 100 / portTICK_PERIOD_MS );
}
}void LEDs_Config(void) {
GPIO_InitTypeDef GPIO_InitStruct;
// turn on peripheral clock for GPIO port D
__HAL_RCC_GPIOD_CLK_ENABLE(); HAL_GPIO_WritePin( GPIOD, GPIO_PIN_12 | GPIO_PIN_13
| GPIO_PIN_14 | GPIO_PIN_15, GPIO_PIN_RESET ); // Configure GPIO port D (Pin 12..14) 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 );
}
ในโค้ดตัวอย่างที่เกี่ยวข้องกับฟังก์ชันของทาสก์ อาจเขียนใหม่ได้ดังนี้
void vTask1( void *pvParameters ) {
while (1) {
xSemaphoreTake(xSemaphore,(TickType_t) portMAX_DELAY);
HAL_GPIO_WritePin( GPIOD, GPIO_PIN_13, GPIO_PIN_SET );
vTaskDelay( 100 / portTICK_PERIOD_MS );
HAL_GPIO_WritePin( GPIOD, GPIO_PIN_13, GPIO_PIN_RESET );
xSemaphoreGive(xSemaphore);
vTaskDelay( 100 / portTICK_PERIOD_MS );
}
}void vTask2( void *pvParameters ) {
while (1) {
xSemaphoreTake(xSemaphore,(TickType_t) portMAX_DELAY);
HAL_GPIO_WritePin( GPIOD, GPIO_PIN_15, GPIO_PIN_SET );
vTaskDelay( 100 / portTICK_PERIOD_MS );
HAL_GPIO_WritePin( GPIOD, GPIO_PIN_15, GPIO_PIN_RESET );
xSemaphoreGive(xSemaphore);
vTaskDelay( 100 / portTICK_PERIOD_MS );
}
}
ถ้าสังเกต จะเห็นได้ว่า มีการเปลี่ยนจากการใช้คำสั่ง HAL_GPIO_TogglePin()
มาเป็น HAL_GPIO_WritePin()
การทดสอบการทำงานของโค้ดตัวอย่าง
เมื่อใส่โค้ดตัวอย่างลงในไฟล์ main.c
ถัดไป ให้ทำขั้นตอน Build Target เพื่อคอมไพล์โค้ด
การสาธิตการทำงานของโค้ดตัวอย่าง แบ่งได้เป็นสองกรณีคือ
- การใช้บอร์ดไมโครคอนโทรลเลอร์ (In-Circuit Debugging)
- การใช้ซอฟต์แวร์จำลองการทำงาน (Simulator-based Debugging)
วิธีที่สองเหมาะสำหรับผู้ใช้ที่ยังไม่มีบอร์ดไมโครคอนโทรลเลอร์สำหรับใช้งาน และต้องการจะลองรันโค้ดตัวอย่าง
ถ้าจะทำขั้นตอน In-Circuit Debugging จะต้องไปตั้งค่าการใช้งานในส่วนของ Debug ก่อน โดยเลือกอุปกรณ์เป็น ST-Link Debugger
เมื่อเข้าสู่ Debug Session ก็สามารถกำหนดหรือเพิ่มตำแหน่งของ Breakpoints ในโค้ดของเราได้ เพื่อให้ทำคำสั่งในฮาร์ดแวร์แล้วมาหยุดชั่วคราวในตำแหน่งดังกล่าว และเราสามารถดูค่าของรีจิสเตอร์ภายในไมโครคอนโทรลเลอร์ได้ เช่น รีจิสเตอร์ที่เกี่ยวข้องกับการทำงานของพอร์ต GPIOD ตามรูปตัวอย่าง
ถ้าไม่มีฮาร์ดแวร์ หรือจะเปลี่ยนมาใช้ Simulator ให้เปลี่ยนจาก ST-Link Debugger ไปใช้ Software Simulator และให้ตั้งค่าความถี่ 16 MHz
เพิ่มไฟล์ debug.ini
(Initialization File) ไว้ในโปรเจกต์ แล้วใส่ข้อความหนึ่งบรรทัดต่อไปนี้ เพื่ออนุญาตให้ Simulator เขียนอ่านข้อมูลในบริเวณดังกล่าวของ System Memory Map ได้ ( เช่น ส่วนที่เกี่ยวข้องกับ Peripherals ต่าง ๆ )
MAP 0x40000000, 0x400FFFFF READ WRITE
ถ้าต้องการเฝ้าดูการเปลี่ยนแปลงค่าของตัวแปร หรือ รีจิสเตอร์ตัวใดตัวหนึ่ง ในขณะจำลองการทำงาน ก็สามารถทำได้ เช่น ให้ลองเพิ่มข้อความต่อไปนี้ลงใน Watch 1 Window เพื่อดูค่าของรีจิสเตอร์ BSRR (แอดเดรสตรงกับ 0x40020C18) ของพอร์ต GPIOD
(*((unsigned long *)0x40020C18))
จากนั้นก็เริ่มต้น Debug Session ตามปรกติ สามารถกำหนดตำแหน่งของ Breakpoint ได้เช่นกัน
เราสามารถใช้ค่าจาก Watch 1 Windows เพิ่มลงใน Logic Analyzer แบ่งเป็นสอง 2 กรณี ตามตัวอย่าง (ดูการเซตบิตและเคลียร์บิตที่ขา GPIOD หมายเลข 13 และ 15)
ข้อสังเกต:
- ถ้าใช้งาน FreeRTOS แล้ว SysTick Timer ชองซีพียู ARM จะถูกใช้โดย FreeRTOS และ คำสั่งอย่างเช่น
HAL_Delay()
จะไม่สามารถใช้ได้ - การสร้างโปรเจกต์ใน uVision IDE สำหรับ STM32 ยังมีวิธีอื่นอีก เช่น การสร้างไฟล์ต่าง ๆ สำหรับโปรเจกต์ โดยใช้ซอฟต์แวร์ที่มีชื่อว่า STM32CubeMX ซึ่งจะต้องมีการติดตั้งซอฟต์แวร์ดังกล่าวในเครื่องของผู้ใช้ก่อน
กล่าวสรุป
เราได้เห็นขั้นตอนการสร้างโปรเจกต์และทดลองใช้งานโค้ดตัวอย่าง โดยได้นำไปใช้กับบอร์ดไมโครคอนโทรลเลอร์จริง และใช้วิธีจำลองการทำงานด้วย Simulator