Keil MDK + STM32F4 + FreeRTOS

<rawat.s>
4 min readFeb 23, 2021

--

​บทความนี้แนะนำขั้นตอนการใช้งานซอฟต์แวร์ ARM Keil MDK (MDK-Lite) สำหรับบอร์ดไมโครคอนโทรลเลอร์ STM32F4 และการใช้งานระบบปฏิบัติการเวลาจริง FreeRTOS ในเบื้องต้น​

แนะนำให้ศึกษาขั้นตอนการใช้งาน MDK จากบทความตอนที่ 1 ให้เข้าใจก่อน

การสร้างโปรเจกต์และโค้ดตัวอย่าง


ขั้นตอนในการใช้งาน มีดังนี้

  • เปิดใช้งานโปรแกรม 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 ในโปรเจกต์
รูป: ตัวอย่างการเลือก Target Device เป็น STM32F407VGTx
รูป: ตัวอย่างการเลือก RTE Components

ข้อสังเกต: ในตัวอย่างนี้ เราจะใช้เฉพาะ FreeRTOS และไม่ได้ใช้ API ตามรูปแบบของ CMSIS RTOS (Version 1) หรือ CMSIS RTOS2 (Version 2)

รูป: ตัวอย่างการเลือก RTE Components (ต่อ)

ในไฟล์ 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)

รูป: การตั้งค่าใน Logic Analyzer เพื่อดูการเปลี่ยนแปลงค่าบิตในรีจิสเตอร์ GIOD->BSRR
รูป: ตัวอย่างรูปคลื่นสัญญาณที่ได้จากการทำงานของ Simulator

ข้อสังเกต:

  • ถ้าใช้งาน FreeRTOS แล้ว SysTick Timer ชองซีพียู ARM จะถูกใช้โดย FreeRTOS และ คำสั่งอย่างเช่น HAL_Delay() จะไม่สามารถใช้ได้
  • การสร้างโปรเจกต์ใน uVision IDE สำหรับ STM32 ยังมีวิธีอื่นอีก เช่น การสร้างไฟล์ต่าง ๆ สำหรับโปรเจกต์ โดยใช้ซอฟต์แวร์ที่มีชื่อว่า STM32CubeMX ซึ่งจะต้องมีการติดตั้งซอฟต์แวร์ดังกล่าวในเครื่องของผู้ใช้ก่อน

กล่าวสรุป

​ ​

เราได้เห็นขั้นตอนการสร้างโปรเจกต์และทดลองใช้งานโค้ดตัวอย่าง โดยได้นำไปใช้กับบอร์ดไมโครคอนโทรลเลอร์จริง และใช้วิธีจำลองการทำงานด้วย Simulator

--

--

<rawat.s>
<rawat.s>

Written by <rawat.s>

I'm Thai and working in Bangkok/Thailand.

No responses yet