การเขียนโปรแกรมไมโครคอนโทรลเลอร์ STM32F4 โดยใช้ Arduino - STM32duino และ FreeRTOS#

Keywords: STM32F4, BlackPill Boards, Arduino, STM32duino, FreeRTOS


บอร์ดไมโครคอนโทรลเลอร์ STM32F4#

ไมโครคอนโทรลเลอร์ STM32F4 Series ของบริษัท STMicroelectronics มีตัวประมวลผลหรือซีพียูคอร์ (CPU Core) ขนาด 32 บิต ที่ทำงานได้ตามสถาปัตยกรรมชุดคำสั่ง Arm Cortex-M4 บอร์ด เช่น WeAct Studio BlackPill เป็นบอร์ดไมโครคอนโทรลเลอร์ที่มีชิป STM32F401 หรือ STM32F411 เป็นตัวเลือกหนึ่งที่น่าสนใจ เนื่องจากมีราคาถูก

ในการเขียนโปรแกรมไมโครคอนโทรลเลอร์ STM32F4 โดยทั่วไป ผู้ใช้สามารถเขียนโปรแกรมด้วยภาษา C/C++ และเลือกใช้ไลบรารีของบริษัท STMicroelectronics เช่น STM32F4 HAL (Hardware Abstraction Layer) ซึ่งมีรายละเอียดตามเอกสาร UM1725 User manual รวมถึงมีซอฟต์แวร์ประเภท IDE ให้เลือกใช้ได้ เช่น STM32CubeIDE

อย่างไรก็ตาม ในปัจจุบันการเขียนโปรแกรมสำหรับ STM32 สามารถทำได้ง่ายขึ้น โดยใช้ Arduino API เป็นพื้นฐานในการเขียนโค้ด โดยจะต้องมีการติดตั้งซอฟต์แวร์ Arduino Core สำหรับชิป STM32 ที่มีชื่อว่า STM32duino

แนะนำให้ศึกษาบทความที่เกี่ยวข้อง:

 


การเริ่มต้นใช้งาน Arduino IDE#

ถัดไปเป็นตัวอย่างการใช้งานซอฟต์แวร์ Arduino IDE v2 สำหรับการเขียนโปรแกรมบอร์ดไมโครคอนโทรลเลอร์ และเลือกใช้บอร์ด STM32F411CEU6 BlackPill ผู้ใช้จะต้องติดตั้ง Arduino Core for STM32 โดยไปที่เมนู "Files > Preferences > Additional Boards Manager URLs" เพิ่มรายการ URL ของไฟล์ .json ต่อไปนี้

https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json

รูป: เพิ่มรายการ URL ของไฟล์ .json สำหรับ STM32duino

จากนั้นไปยังเมนู "Tools > Board: ... > Boards Manager ในหน้าต่าง Boards Manager ให้ค้นหารายการสำหรับ STM32 แล้วคลิกเลือกและกดปุ่ม Install เพื่อทำการติดตั้ง STM32 Cores ซึ่งจะทำการดาวน์โหลดไฟล์ต่าง ๆ ที่เกี่ยวข้องจากอินเทอร์เน็ต

เวอร์ชันที่ได้ทดลองใช้คือ STM32duino v2.3.0

  • xpack-arm-none-eabi-gcc@10.3.1-2.3
  • STMicroelectronics:STM32Tools@2.1.1
  • STMicroelectronics:CMSIS@5.7.0

รูป: การติดตั้ง STM32duino - Arduino Core for STM32

ถัดไปให้สร้าง Arudino Sketch สำหรับการเขียนโค้ด โดยเลือกจากรายการบอร์ดให้ตรงกับ "Generic STM32F4 series > BlackPill F411CE"

ข้อสังเกต: ถ้าเลือกใช้บอร์ด BlackPill อีกรุ่นหนึ่งคือ STM32F401CC หรือ STM32F401CDU6 ก็ให้เลือก "BlackPill F401CC"

ตัวอย่างโค้ดสาธิต: LED Blink

#define LED_PIN PC13 // onboard LED

void setup() {
   pinMode( LED_PIN, OUTPUT ); 
}

void loop() {
  static uint32_t state = 0;
  // Toggle the LED state and update the LED output pin.
  digitalWrite( LED_PIN, state ^= 1 );
  // Wait for some delay before re-entering the loop function.
  delay(100);
}

รูป: เลือกบอร์ดไมโครคอนโทรลเลอร์ที่ต้องการใช้งาน

เนื่องจากบอร์ด STM32F411CE BlackPill ไม่มีชิป USB-to-Serial Bridge แต่สามารถใช้พอร์ต USB-C เชื่อมต่อกับคอมพิวเตอร์ผู้ใช้ และจะต้องเปิดใช้งาน USB-CDC ดังนั้นในการโค้ด Arduino Sketch และใช้คำสั่ง Serial หรือ SerialUSB โดยใช้วงจร USB ของชิป STM32F411CE แนะนำให้เลือกรูปแบบการตั้งค่าดังนี้ "USB support (if available): enabled" และสำหรับตัวเลือก U(S)ART support ให้ตั้งค่าเป็น "CDC (generic 'Serial' supersede U(S)ART)"

รูป: การตั้งค่า USB Support สำหรับ USB-CDC

การอัปโหลดไฟล์เฟิร์มแวร์ (.bin) ที่ได้จากการคอมไพล์โค้ดของ Arduino Sketch มีให้เลือกได้หลายวิธี เช่น

  • การใช้ SWD Programmer: วิธีนี้ต้องมีอุปกรณ์ที่เรียกว่า ST-Link/v2 หรือ ST-Link/v3 และใช้ร่วมกับคำสั่งของซอฟต์แวร์ (ฟรี) STM32CubeProgrammer ของบริษัท STMicroelectronics
  • การใช้ DFU Bootloader: ชิป STM32F4 ได้มีการติดตั้งโปรแกรม DFU Bootloader ไว้ภายในชิป ในหน่วยความจำแบบ ROM มาจากโรงงานแล้ว และจะใช้สำหรับการอัปโหลดไฟล์เฟิร์มแวร์ผ่านทางพอร์ต USB ในโหมด DFU และจะต้องใช้ร่วมกับซอฟต์แวร์ STM32CubeProgrammer เช่นกัน

ดังนั้นสำหรับสองวิธีนี้ จะต้องมีการดาวน์โหลดและติดตั้งซอฟต์แวร์ STM32CubeProgrammer ของบริษัท STMicroelectronics ในเครื่องคอมพิวเตอร์ของผู้ใช้ก่อน

รูป: เลือกวิธีการเพื่อใช้สำหรับการอัปโหลดไฟล์เฟิร์มแวร์: STM32CubeProgrammer (DFU)

รูป: ตัวอย่างการใช้งาน STM32CubeProgrammer เพื่อลองเชื่อมต่อกับบอร์ด STM32F411CE BlackPill ผ่านทางพอร์ต USB ในโหมด DFU

ก่อนการอัปโหลด Arduino Sketch ผ่านทางพอร์ต USB ให้ทำขั้นตอนดังนี้

  1. เชื่อมต่อขา A10 (PA10/USB_FS_ID) ด้วยสาย Jumper Wire กับตัวต้านทาน 10k แบบ Pullup ไปยัง 3.3V
  2. เสียบสาย USB-C เชื่อมต่อบอร์ดไมโครคอนโทรลเลอร์กับคอมพิวเตอร์
  3. กดปุ่ม BOOT0 กดค้างไว้ กดปุ่ม RESET เพื่อรีเซตการทำงานของบอร์ด BlackPill ให้เข้าสู่ DFU Bootloader Mode แล้วจึงปล่อยปุ่ม RESET และ BOOT0 ตามลำดับ
  4. อัปโหลด Arduino Sketch หลังจากทำขั้นตอน Verify แล้ว ไปยังบอร์ด

 

รูป: การอัปโหลดด้วยวิธี STM32CubeProgrammer (DFU)

 


โค้ดตัวอย่างนี้สาธิตการส่งข้อความผ่านทางพอร์ต USB ที่เกี่ยวข้องกับการทำคำสั่งของ Serial

#define LED_PIN PC13

void setup() {
  pinMode( LED_PIN, OUTPUT ); 
  Serial.begin( 115200 );
  while ( !Serial );
  Serial.println( "STM32duino SerialUSB demo..." );
}

void loop() {
  static uint32_t state = 0;
  // Toggle the LED state and update the LED output pin.
  digitalWrite( LED_PIN, state ^= 1 );
  // Send the current state of the LED.
  while (!Serial.availableForWrite()) { delay(1); }
  Serial.printf( "state: %d\n", state );
  // Wait for some delay before re-entering the loop function.
  delay( 500 );
}

 


Analog Input + PWM Output#

โค้ดตัวอย่างนี้สาธิตการอ่านค่าจากขาอินพุตแบบแอนะล็อก โดยใช้คำสั่ง analogRead() ซึ่งจะมีการใช้งานวงจร ADC (Analog-to-Digital Converter) ที่อยู่ภายในชิป STM32F4 และนำค่าที่อ่านได้ไปใช้สำหรับการปรับค่า Duty Cycle ของสัญญาณ PWM (Pulse Width Modulation) ที่ถูกสร้างโดยคำสั่ง analogWrite()

#define LED_PIN PC13
#define AIN_PIN PA0
#define PWM_PIN PA5

// LDR (high-side, 3.3V) + resistor (low-side, GND)
// low light intensity -> lower PWM duty cycle, lower analog input voltage

//#define ADC_RESOLUTION  (10)
//#define PWM_RESOLUTION  (8)
//#define PWM_FREQ        (1000)

#define ADC_RESOLUTION (12)
#define PWM_RESOLUTION (10)
#define PWM_FREQ (10000)

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(115200);
  while (!Serial);
  Serial.println("STM32duino PWM output demo...");
  // Set ADC resolution (default: 10 bits)
  analogReadResolution(ADC_RESOLUTION);
  // Set PWM resolution (default: 8 bits)
  analogWriteResolution(PWM_RESOLUTION);
  // Set PWM frequency  (default: 1kHz)
  analogWriteFrequency(PWM_FREQ);
  // Write PWM value (zero)
  analogWrite(PWM_PIN, 0);
}

void loop() {
  // Read analog value.
  int adc_value = analogRead(AIN_PIN);
  // Write PWM value.
  int pwm_value = 0;
  int diff = (ADC_RESOLUTION - PWM_RESOLUTION);
  if (diff >= 0) {
    pwm_value = adc_value >> diff;
  } else {
    pwm_value = adc_value << (-diff);
  }
  analogWrite(PWM_PIN, pwm_value);
  // Send a text to the Serial output.
  Serial.printf("ADC: %lu, PWM: %lu\n", adc_value, pwm_value);
  // Wait for some delay before re-entering the loop.
  delay(200);
}

รูป: ตัวอย่างข้อความเอาต์พุตที่ได้รับจากบอร์ดและแสดงผลใน Arduino Serial Monitor

รูป: สัญญาณเอาต์พุต PWM (ความถี่ 1kHz)

รูป: สัญญาณเอาต์พุต PWM (เมื่อมีการตั้งค่าความถี่ให้เป็น 10kHz)

 


STM32-FreeRTOS: Multitasking#

ตัวอย่างโค้ดถัดไปสาธิตการเขียนโค้ดโดยใช้ไลบรารี STM32FreeRTOS เพื่อการเขียนโค้ดโดยใช้ FreeRTOS ซึ่งเป็นระบบปฏิบัติการเวลาจริง (RTOS) แบบ Open Source ผู้ใช้จะต้องมีการติดตั้งไลบรารี STM32FreeRTOS ก่อนทำขั้นตอนคอมไพล์โค้ดตัวอย่าง

ในโค้ดตัวอย่างมีการสร้าง "ทาสก์" (Tasks) โดยใช้คำสั่ง xTaskCreate(...) ของ FreeRTOS API ได้แก่ ทาสก์ชื่อ "T0" และ "T1" ซึ่งงานได้อิสระจากกัน ทาสก์ทั้งสองทำหน้าที่สลับสถานะของลอจิกที่ขาเอาต์พุต PC13 และ PA5 ตามลำดับ และทำให้เกิดอัตราการกระพริบของ LED ที่แตกต่างกัน (สลับสถานะลอจิกทุก ๆ 100 และ 10 มิลลิวินาที ตามลำดับของทาสก์)

// Arduino library: https://github.com/stm32duino/STM32FreeRTOS
#include <Arduino.h>
#include <STM32FreeRTOS.h>              // use FreeRTOS v10.3.1 ported to STM32duino
#include <STM32FreeRTOSConfig_extra.h>  // use custom STM32-FreeRTOS configuration file

#define LED1_PIN PC13  // onboard LED
#define LED2_PIN PA5

void task0(void *pvParameters);
void task1(void *pvParameters);

#define TASK_STACK_SIZE (configMINIMAL_STACK_SIZE + 200)

void setup() {
  Serial.begin(115200);
  while (!Serial);
  Serial.println("STM32F411CE BlackPill - STM32Duino FreeRTOS demo...");
  Serial.printf("SystemCoreClock: %lu MHz\n", (uint32_t)(SystemCoreClock / 1e6));
  Serial.printf("configTICK_RATE_HZ: %lu\n", configTICK_RATE_HZ);
  Serial.printf("configMAX_PRIORITIES: %lu\n", configMAX_PRIORITIES);
  Serial.printf("configPRIO_BITS: %lu\n", configPRIO_BITS);
  Serial.printf("configLIBRARY_LOWEST_INTERRUPT_PRIORITY: %lu\n",
                configLIBRARY_LOWEST_INTERRUPT_PRIORITY);
  Serial.printf("configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY: %lu\n",
                configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY);
  Serial.printf("configKERNEL_INTERRUPT_PRIORITY: %lu\n",
                configKERNEL_INTERRUPT_PRIORITY);
  Serial.printf("configTIMER_TASK_PRIORITY: %lu\n", configTIMER_TASK_PRIORITY);
  Serial.printf("configMINIMAL_STACK_SIZE: %lu\n", configMINIMAL_STACK_SIZE);
  Serial.printf("configISR_STACK_SIZE_WORDS: %lu\n", configISR_STACK_SIZE_WORDS);
  Serial.printf("configTOTAL_HEAP_SIZE: %lu\n", configTOTAL_HEAP_SIZE);
  delay(100);

  // Create Task 0
  xTaskCreate(task0, "T0",
              TASK_STACK_SIZE,
              NULL, tskIDLE_PRIORITY + 1, NULL);
  // Create Task 1
  xTaskCreate(task1, "T1",
              TASK_STACK_SIZE,
              NULL, tskIDLE_PRIORITY + 1, NULL);
  // Start the FreeRTOS task scheduler
  vTaskStartScheduler();
  while (1) {}
}

void loop() {
}

// task entry function
void task0(void *pvParameters) {
  (void)pvParameters;
  int state = 0;
  Serial.println("Task0 started...");
  pinMode(LED1_PIN, OUTPUT);
  while (1) {
    digitalWrite(LED1_PIN, state ^= 1);
    vTaskDelay(pdMS_TO_TICKS(100) /*msec*/);
  }
}

// task entry function
void task1(void *pvParameters) {
  (void)pvParameters;
  Serial.println("Task1 started...");
  int state = 1;
  pinMode(LED2_PIN, OUTPUT);
  while (1) {
    digitalWrite(LED2_PIN, state ^= 1);
    vTaskDelay(10 /*ticks*/);  // 1 Tick = 1msec
  }
}

นอกจากการสร้างไฟล์สำหรับ Arduino Sketch (.ino) ตามโค้ดตัวอย่างข้างล่าง จะต้องมีการสร้างไฟล์อีกหนึ่งไฟล์ (เพิ่ม Tab) แล้วตั้งชื่อไฟล์ STM32FreeRTOSConfig_extra.h

รูป: ตัวอย่างการสร้างไฟล์ STM32FreeRTOSConfig_extra.h

เพิ่มโค้ดดังต่อไปนี้ เพื่อเลือกรูปแบบการใช้งานหน่วยความจำประเภท Heap สำหรับ FreeRTOS ถ้าดูโค้ดในไฟล์ heap.c จะหมายถึงการใช้โค้ดจากไฟล์ heap_3.c

#define configMEMMANG_HEAP_NB 3

ข้อสังเกต:

  • เมื่อดูโค้ดในไฟล์ FreeRTOSConfig.h จะเห็นได้ว่า ถ้ามีการสร้างไฟล์ STM32FreeRTOSConfig.h เพิ่มไว้ในโปรเจกต์โดยผู้ใช้ ก็จะใช้ไฟล์นี้สำหรับการตั้งค่าการใช้งาน FreeRTOS
  • หรืออีกตัวเลือกหนึ่งคือ ถ้ามีการสร้างไฟล์ STM32FreeRTOSConfig_extra.h ไว้ในโปรเจกต์โดยผู้ใช้ ก็จะนำเข้าไฟล์นี้ก่อนไฟล์ FreeRTOSConfig_Default.h
  • เมื่อทำคำสั่งมาถึง vTaskStartScheduler() จะเป็นการเริ่มต้นการทำงานของตัวจัดการลำดับการทำงานของทาสก์ (Task Scheduler) โดย FreeRTOS Kernel และจะไม่จบการทำงานของคำสั่งนี้ (เป็น Blocking Call) ดังนั้นคำสั่งที่อยู่ถัดไปในโค้ด ของฟังก์ชัน setup() จะไม่ทำงาน (ในกรณีที่การทำงานเป็นปรกติ)

 

เมื่ออัปโหลดโค้ดไปยังบอร์ด BlackPill ได้แล้ว จะต้องมีการเปิดพอร์ต Serial เพื่อรับข้อความจากบอร์ด และจะเห็นว่า LED บนบอร์ดที่ต่อกับขา PC13 มีการกระพริบ และแนะนำให้วัดสัญญาณที่ขาเอาต์พุตด้วยเครื่องออสซิลโลสโคป

รูป: ตัวอย่างข้อความเอาต์พุตที่ได้รับมาจากบอร์ด STM32F411CE BlackPill

รูป: การวัดสัญญาณเอาต์พุตด้วยออสซิลโลสโคปและตัวอย่างคลื่นสัญญาณที่ขา PA5 และขา PC13

 


Analog Input + PWM Output: Multitasking#

โค้ดตัวอย่างนี้สาธิตการสร้างทาสก์ (Tasks) โดยแบ่งเป็นสองทาสก์ที่ทำงานอิสระจากกัน ทาสก์แรกทำให้ LED บนบอร์ดกระพริบด้วยอัตราคงที่ ทาสก์ที่สองอ่านค่าจากขาอินพุตแบบแอนะล็อก โดยใช้คำสั่ง analogRead() ซึ่งจะมีการใช้งานวงจร ADC (Analog-to-Digital Converter) ที่อยู่ภายในชิป STM32F4 และนำค่าที่อ่านได้ไปใช้สำหรับการปรับค่า Duty Cycle ของสัญญาณ PWM (Pulse Width Modulation) ที่ถูกสร้างโดยคำสั่ง analogWrite()

// Arduino library: https://github.com/stm32duino/STM32FreeRTOS
#include <Arduino.h>
#include <STM32FreeRTOS.h> // use FreeRTOS v10.3.1 ported to STM32duino
#include <STM32FreeRTOSConfig_extra.h>  // use custom STM32-FreeRTOS configuration file

#define LED_PIN PC13
#define AIN_PIN PA0
#define PWM_PIN PA5

#define ADC_RESOLUTION (12)
#define PWM_RESOLUTION (10)
#define PWM_FREQ (10000)

void task0(void *pvParameters);
void task1(void *pvParameters);

void setup() {
  Serial.begin(115200);
  while(!Serial);

  // Create Task 0
  xTaskCreate(task0, "T0",
              configMINIMAL_STACK_SIZE + 400,
              NULL, tskIDLE_PRIORITY + 1, NULL);
  // Create Task 1
  xTaskCreate(task1, "T1",
              configMINIMAL_STACK_SIZE + 400,
              NULL, tskIDLE_PRIORITY + 1, NULL);
  // Start the FreeRTOS task scheduler
  vTaskStartScheduler();
  while (1) {}
}

void loop() {
}

#define MS_TO_TICKS(x)  ((x) * configTICK_RATE_HZ/1000L)

// task entry function
void task0(void *pvParameters) {
  (void)pvParameters;
  int state = 0;
  pinMode(LED_PIN, OUTPUT);
  while (1) {
    digitalWrite(LED_PIN, state ^= 1);
    vTaskDelay( MS_TO_TICKS(100) );
  }
}

// task entry function
void task1(void *pvParameters) {
  (void)pvParameters;
  // Set ADC resolution (default: 10 bits)
  analogReadResolution(ADC_RESOLUTION);
  // Set PWM resolution (default: 8 bits)
  analogWriteResolution(PWM_RESOLUTION);
  // Set PWM frequency  (default: 1kHz)
  analogWriteFrequency(PWM_FREQ);
  // Write PWM value (zero)
  analogWrite(PWM_PIN, 0);

  while (1) {
    // Read analog value.
    int adc_value = analogRead(AIN_PIN);
    // Write PWM value.
    int pwm_value = 0;
    int diff = (ADC_RESOLUTION - PWM_RESOLUTION);
    if (diff >= 0 ) {
      pwm_value = adc_value >> diff;
    } else {
      pwm_value = adc_value << (-diff);
    }
    analogWrite(PWM_PIN, pwm_value);
    vTaskDelay( MS_TO_TICKS(100) );
  }
}

 


FreeRTOS + External Interrupt#

ตัวอย่างถัดไปสาธิตการเขียนโค้ด Arduino Sketch ที่มีการสร้างทาสก์ หรือแบ่งการทำงานของโปรแกรมเป็นสองทาสก์ โดยให้ทาสก์ "T1" สร้างสัญญาณดิจิทัล ในลักษณะเป็นพัลส์ High ความกว้างประมาณ 50 ไมโครวินาที ที่ขา PA5 ซึ่งถูกกำหนดให้เป็นขาเอาต์พุตแบบดิจิทัล ในการต่อวงจรทดลองจริง จะต้องมีการเชื่อมต่อขา PA5 ด้วยสายไฟไปยังขา PA6 ที่ถูกกำหนดให้เป็นขาอินพุตแบบดิจิทัล

ในตัวอย่างนี้มีการเปิดใช้งานอินเทอร์รัพท์ภายนอก (External Interrupt) โดยใช้คำสั่ง attachInterrupt(...) ของ Arduino API เมื่อขา PA6 มีการเปลี่ยนแปลงค่าลอจิก จะเกิดเหตุการณ์อินเทอร์รัพท์ และมีการเรียกใช้ฟังก์ชันให้ทำงานโดยอัตโนมัติ ฟังก์ชันสำหรับเหตุการณ์ดังกล่าวเรียกว่า ISR (Interrupt Service Routine)

ตัวอย่างนี้สาธิตการใช้งานสิ่งที่เรียกว่า "เซมาฟอร์แบบไบนารี" (Binary Semaphore) ที่มีการสร้างขึ้นมาใช้งานโดยใช้คำสั่ง xSemaphoreCreateBinary() ของ FreeRTOS API และนำมาใช้สำหรับการสื่อสารระหว่างฟังก์ชัน ISR และทาสก์ เมื่อถูกสร้างขึ้นมา เซมาฟอร์แบบไบนารีถูกกำหนดให้มีค่าเป็น 0

ทาสก์ "T0" รอการสื่อสารจากฟังก์ชัน pulse_isr() ซึ่งเป็น ISR ในโค้ดตัวอย่าง และรอจนกว่าจะสามารถเข้าใช้งานเซมาฟอร์แบบไบนารีได้ กล่าวคือ ทาสก์นี้จะรอจนกว่าค่าของเซมาฟอร์แบบไบนารี เปลี่ยนจากค่า 0 เป็น 1 ซึ่งจะเกิดขึ้นเมื่อฟังก์ชัน ISR ทำงานในแต่ละครั้ง จากการทำคำสั่ง xSemaphoreGiveFromISR(...)

เมื่อทาสก์ "T0" เข้าใช้งานเซมาฟอร์แบบไบนารีได้แล้ว ก็จะลดค่าของเซมาฟอร์ให้เป็น 0 ซึ่งเกิดจากการทำคำสั่ง xSemaphoreTake() และมีการเปลี่ยนสถานะลอจิกที่ขา PC13 หนึ่งครั้ง ดังนั้นในตัวอย่างนี้ ถ้าสถานะลอจิกของขา PA5 / PA6 เปลี่ยน สถานะลอจิกที่ขา PC13 ก็จะเปลี่ยนตาม

// Arduino library: https://github.com/stm32duino/STM32FreeRTOS
#include <Arduino.h>
#include <STM32FreeRTOSConfig_extra.h>
#include <STM32FreeRTOS.h>

#include <semphr.h>

#define LED_PIN PC13
#define PULSE_IN_PIN PA6
#define PULSE_OUT_PIN PA5

SemaphoreHandle_t sem = NULL;

void task0(void *pvParameters);
void task1(void *pvParameters);

void pulse_isr() {
  static BaseType_t xHigherPriorityTaskWoken;
  xHigherPriorityTaskWoken = pdFALSE;
  xSemaphoreGiveFromISR(sem, &xHigherPriorityTaskWoken);
  if (xHigherPriorityTaskWoken == pdTRUE) {
    portYIELD_FROM_ISR(&xHigherPriorityTaskWoken);
  }
}

void setup() {
  Serial.begin(115200);
  while (!Serial);
  Serial.println( "STM32F4 FreeRTOS demo... ");
  pinMode(LED_PIN, OUTPUT); 
  pinMode(PULSE_OUT_PIN, OUTPUT);
  pinMode(PULSE_IN_PIN, INPUT_PULLUP);
  digitalWrite(PULSE_OUT_PIN, LOW);
  attachInterrupt(digitalPinToInterrupt(PULSE_IN_PIN), pulse_isr, CHANGE);

  // Create a binary semaphore
  sem = xSemaphoreCreateBinary();

  // Create Task 0
  xTaskCreate(task0, "T0",
              configMINIMAL_STACK_SIZE + 400,
              NULL, tskIDLE_PRIORITY + 2, NULL);
  // Create Task 1
  xTaskCreate(task1, "T1",
              configMINIMAL_STACK_SIZE + 400,
              NULL, tskIDLE_PRIORITY + 1, NULL);

  vTaskStartScheduler();
  while(1){}
}

void loop() {
}

// task entry function
void task0(void *pvParameters) {
  int state = 0;
  while (1) {
    if (xSemaphoreTake(sem, portMAX_DELAY) == pdTRUE) {
      digitalWrite(LED_PIN, state ^= 1);
    }
  }
}

// task entry function
void task1(void *pvParameters) {
  vTaskDelay(10);
  digitalWrite(PULSE_OUT_PIN, HIGH);
  delayMicroseconds(50);
  digitalWrite(PULSE_OUT_PIN, LOW);
  while (1) {
    vTaskDelay(portMAX_DELAY);
  }
}

รูป: การวัดสัญญาณเอาต์พุตด้วยออสซิลโลสโคปและตัวอย่างคลื่นสัญญาณที่ขา GPIO ที่เกี่ยวข้อง

การเปลี่ยนแปลงสถานะลอจิกที่ขา PA5 / PA6 (สัญญาณช่อง 1) จะทำให้เกิดการเปลี่ยนแปลงที่ขา PC13 (สัญญาณช่อง 2) ตามมา มีทั้งสองกรณีคือ ขอบขาขึ้นและขอบขาลงตามลำดับ

หากวัดระยะเวลาจากขอบขาขึ้นของสัญญาณช่องที่ 1 ไปยังตำแหน่งของขอบขาขึ้นของสัญญาณช่องที่ 2 ที่ตามมา ก็จะได้ระยะเวลาในการตอบสนองต่อเหตุการณ์อินเทอร์รัพท์ภายนอก รวมถึงระยะเวลาเปลี่ยนสถานะการทำงานของทาสก์ (Task Context Switching) และระยะเวลาในการสื่อสารกันระหว่าง ISR ไปยังอีกทาสก์หนึ่งที่รอเข้าใช้เซมาฟอร์แบบไบนารี ในรูปตัวอย่างจะเห็นได้ว่า มีระยะเวลาน้อยกว่า 10 ไมโครวินาที

 


กล่าวสรุป#

บทความนี้แนะนำวิธีการเขียนโค้ด Arduino โดยใช้ซอฟต์แวร์ Arduino IDE v2 และใช้งาน STM32-FreeRTOS สำหรับบอร์ด STM32F411CE BlackPill ในเบื้องต้น เมื่อสามารถใช้งานซอฟต์แวร์และไลบรารีที่เกี่ยวข้องได้แล้ว ก็สามารถนำไปใช้ในการเรียนรู้การเขียนโค้ดด้วย FreeRTOS เพิ่มเติมได้ต่อไป

การเขียนโค้ดด้วย STM32duino เพื่อเรียนรู้การใช้งาน FreeRTOS แทนที่จะใช้ STM32 HAL และ STM32CubeIDE มีข้อดี คือ ผู้เรียนสามารถโฟกัสที่การใช้คำสั่งของ FreeRTOS API และใช้ Arduino API ที่ง่ายกว่า แทนที่จะเริ่มต้นเรียนรู้การใช้งาน STM32 HAL แต่ถ้ามีความเข้าใจ FreeRTOS ในระดับหนึ่งแล้ว จะเปลี่ยนไปใช้ STM32 HAL แทน Arduino API ก็เป็นตัวเลือกที่น่าสนใจ ในลำดับถัดไปสำหรับรูปแบบการเขียนโค้ด

 


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

Created: 2022-12-01 | Last Updated: 2022-12-07