การเขียนโปรแกรม Arduino สำหรับบอร์ด STM32 Nucleo#

Keywords: Arduino IDE, STM32duino, STM32 Nucleo, Nucleo-L432KC


STM32Duino สำหรับ Arduino IDE#

บทความนี้กล่าวถึง การทดลองใช้งาน STM32duino ซึ่งเป็น Arduino Core สำหรับไมโครคอนโทรลเลอร์ตระกูล STM32 ที่นำมาติดตั้งและใช้งานได้ร่วมกับซอฟต์แวร์ Arduino IDE

การเลือกใช้เครื่องมือเหล่านี้ อาจช่วยให้ผู้ที่สนใจเริ่มต้นใช้งานบอร์ดไมโครคอนโทรลเลอร์ STM32 ได้ง่ายขึ้น (โดยเฉพาะอย่างยิ่งผู้ที่คุ้นเคยกับการเขียนโปรแกรม Arduino มาก่อน) และยังสามารถใช้ได้กับบอร์ด ** Nucleo-32 / Nucleo-64 / Nucleo-144 / Discovery หลาย ๆ บอร์ดของบริษัท STMicroelectronics และบอร์ดของบริษัทอื่นที่ใช้ชิป STM32**

อย่างไรก็ตาม ถ้าต้องการใช้งานบอร์ดหรือชิป MCU ของ STMicroelectronics ในเชิงลึก แนะนำให้ใช้ภาษา C/C++ ร่วมกับ STM32Cube HAL (Hardware Abstraction Layer) หรือจะฝึกเขียนโค้ดโดยใช้ RTOS อย่างเช่น Mbed OS หรือ Zephyr RTOS ก็ได้

รูป: ซอฟต์แวร์ที่เกี่ยวข้องกับการเขียนโปรแกรมไมโครคอนโทรลเลอร์ STM32 (Source: STMicroelectronics)

 


การติดตั้ง STM32Duino สำหรับ Arduino IDE#

ขั้นตอนการติดตั้ง STM32Duino

  • เปิดใช้งาน Arduino IDE แล้วไปยังเมนู "File > Preferences"
  • เพิ่มรายการ URL ของไฟล์ .json ต่อไปนี้ ซึ่งจะใช้สำหรับ STM32 Core และอยู่ใน Github package_stmicroelectronics_index.json
  • ไปยังเมนู "Tools > Board: ... > Boards Manager ..."
  • ในหน้าต่าง Boards Manager จะมีรายการ STM32 MCU based Boards ให้เลือก Install
  • ติดตั้ง STM32 Cores ซึ่งจะทำการดาวน์โหลดไฟล์ต่าง ๆ ที่เกี่ยวข้องจากอินเทอร์เน็ต

แนะนำให้ศึกษาข้อมูลเพิ่มเติมจากไฟล์ README.md ใน Github ของ STM32duino

รูป: เนื้อหาบางส่วนในไฟล์ README.md

 

รูป: การเพิ่มรายการไฟล์ .json เพื่อติดตั้ง Arduino Boards Manager สำหรับ STM32duino

 

รูป: การเลือกเวอร์ชันของ STM32duino เพื่อติดตั้งใช้งาน (เช่น 2.2.0 เป็นเวอร์ชันล่าสุดที่ได้ทดลองใช้งาน)

 

รูป: ขั้นตอนการดาวน์โหลดไฟล์ที่เกี่ยวข้องจากอินเทอร์เน็ตโดยอัตโนมัติเพื่อติดตั้ง STM32duio

ตัวอย่างโค้ดและการใช้งานไลบรารีของ STM32duino ได้จาก

ตัวอย่างไลบรารีสำหรับ STM32Duino เช่น

  • STM32FreeRTOS สำหรับผู้ที่ต้องการใช้งาน FreeRTOS
  • STM32Ethernet สำหรับเขียนโค้ดเพื่อส่งข้อมูลในเครือข่ายด้วยโพรโทคอล TCP/IP Stack (ใช้ไลบรารี LwIP เป็นพื้นฐานในการทำงาน) หากใช้บอร์ด NUCLEO ที่รองรับการใช้งาน Ethernet เช่น Nucleo-F429ZI และไลบรารีใช้งานได้เหมือน Arduino Ethernet API
  • STM32RTC สำหรับการใช้งานวงจร RTC (Real-Time Clock) ที่เป็นวงจรอยู่ภายในชิป STM32
  • STM32SD สำหรับการเขียนอ่านข้อมูลลงใน MicroSD โดยใช้ไลบรารี FatFS (FAT-based File System) เป็นพื้นฐานในการทำงาน

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

บอร์ด Nucleo L432KC เป็นบอร์ดไมโครคอนโทรลเลอร์ที่ใช้ชิป STM32L432KC (32-bit ARM Cortex-M4F) เริ่มมีการจำหน่ายในปีค.ศ. 2016 (Initial release)

** ข้อมูลเชิงเทคนิค**

  • Package: UFQFPN32
  • CPU: ARM 32-bit Cortex-M4 CPU with FPU
  • CPU frequency: 80MHz (max.)
  • VDD: from 1.65V to 3.6V (3.3V typ.)
  • On-chip memory
    • 256 KB Flash
    • 64 KB SRAM
  • Peripherals
    • GPIO (20) with external interrupt capability
    • General-Purpose Timers (4)
    • SPI/I2S (2)
    • I2C (2)
    • USART (2)
    • 12-bit ADC with 10 channels (5 Msps)
    • 12-bit DAC with 2 channels
    • RTC
    • CAN Controller
    • USB 2.0 full-speed (crystal less)
    • Random Generator (TRNG for HW entropy)
  • Onboard ST-LINK/V2-1 debugger/programmer
  • Board voltage supply: VUSB (5V), VIN (7 - 12V)
  • Arm Mbed Enabled compliant

รูป: บอร์ด Nucleo L432KC และ Pinmap ซึ่งเป็นแผนผังแสดงตำแหน่งของขาต่าง ๆ

รูป: Nucleo L432KC - Arduino Pins

รูป: ผังวงจรของบอร์ด Nucleo L432KC (ไม่รวมส่วนที่เป็นวงจร ST-Link/v2)

อ้างอิงจาก: https://os.mbed.com/platforms/ST-Nucleo-L432KC/

บอร์ดในกลุ่มที่เรียกว่า STM32 Nucleo-32 ใช้ชิปที่มี 32 ขา ตัวถังของไอซีแบบ LQFP32 หรือ UFQFPN32 มีการจัดวางขาบนบอร์ดเหมือน Arduino Nano และมีให้เลือกใช้งานอยู่หลายบอร์ด การออกแบบบอร์ดในกลุ่มนี้อ้างอิงตาม MB1180 Reference Board เช่น

  • NUCLEO-F031K6
  • NUCLEO-F042K6
  • NUCLEO-F301K8
  • NUCLEO-F303K8
  • NUCLEO-L011K4
  • NUCLEOL031K6
  • NUCLEO-L412KB
  • NUCLEO-L432KC

รูป: ตัวเลือกสำหรับบอร์ด NUCLEO ของบริษัท STMicroelectronics

บอร์ด Nucleo-32 ได้รวมวงจร ST-LINK/V2 In-Circuit Debugger / Programmer มาให้แล้ว (วงจรอยู่ด้านล่างของแผ่น PCB ส่วนด้านบนเป็นวงจรของ STM32L432KC)

เมื่อเสียบสาย MicroUSB เชื่อมต่อบอร์ดกับคอมพิวเตอร์ของผู้ใช้ บอร์ดจะได้รับการจ่ายแรงดันไฟเลี้ยง (VUSB=+5V และใช้กระแสไฟฟ้าไม่เกิน 300 mA) สังเกตที่ตัวไดโอดเปล่งแสง (LED) ซึ่งมีตัวหนังสือเขียนกำกับไว้ LD2 จะสว่างขึ้น และในคอมพิวเตอร์ของผู้ใช้ จะมองเห็นอุปกรณ์ 3 กรณี ดังนี้

  • ST-Link Debugger ใช้สำหรับการอัปโหลดไฟล์ .bin หรือดีบักการทำงานของโปรแกรมโดยใช้ฮาร์ดแวร์ วงจร ST-Link เชื่อมต่อกับขา SWCLK / SWDIO (SWD interface) ของชิป STM32 (Target Device) บนบอร์ด
  • Virtual COM port ใช้สำหรับสื่อสารข้อมูลผ่านพอร์ตอนุกรมที่ขา PA2 / PA15 (VCP_TX / VCP_RX) ของชิป STM32 ผ่านทางวงจร ST-Link ซึ่งทำหน้าที่เป็นวงจร USB-to-Serial
  • Mass storage มองเห็นเป็นชื่อไดรฟ์ เช่น NUCLEO_L432KC ใช้สำหรับการอัปโหลดไฟล์ .bin ไปชิปไมโครคอนโทรลเลอร์ โดยการลากไฟล์มาวาง (Drag & Drop)

ข้อสังเกต:

  • ขา VIN บนบอร์ด สามารถใช้สำหรับการป้อนแรงดันไฟเลี้ยงได้ และรับแรงดันได้ในช่วง 7V ถึง 12V และมีวงจรแปลงแรงดันลง (เป็นแบบ Linear Voltage Regulator) ให้มีแรงดันคงที่ +5V (regulated)
  • ถ้าจ่ายไฟ +5V เข้าที่ขา 5V pin บนบอร์ด วงจร ST-Link จะไม่ทำงาน เพราะวงจรส่วนนี้ จะต้องใช้แรงดันไฟเลี้ยงจาก VUSB หรือขา VIN เท่านั้น
  • บอร์ด Nucleo L432KC มีวงจรคริสตัลสำหรับสร้างความถี่ 32.768 kHz (LSE)
  • ขา PA9 / D1 และ PA10 / D0 ตรงกับขา USART1_TX และ USART1_RX ตามลำดับ
  • ขา PA2 และ PA15 ตรงกับขา VCP_TX และ VCP_RX ตามลำดับ
  • ที่ตำแหน่งขา D2 กับขา GND บนบอร์ด Nucleo-32 ได้มีการใส่ Jumper เอาไว้ ถ้าจะใช้งานบอร์ด ให้ถอด Jumper ดังกล่าวออกก่อน

โค้ดตัวอย่างที่ 1 สาธิตการทำให้ LED บนบอร์ด ซึ่งเป็นวงจรเอาต์พุตที่ต่ออยู่ที่ขา D13 (Arduino Pin) หรือขา PB_3 (MCU Pin) เกิดการกระพริบ โดยกำหนดสถานะลอจิกเป็นเอาต์พุตสลับ LOW (0) และ HIGH (1) โดยเว้นระยะประมาณ 100 มิลลิวินาที

#define LED_PIN   PB3  // PB_3 (MCU pin) or D13 (Arduino pin)
#define DELAY_MS  100

void setup() {
  pinMode( LED_PIN, OUTPUT ); // configure LED_PIN as output
}

void loop() {
  static int state = 0;           // used to keep the LED state
  digitalWrite( LED_PIN, state ); // update the output
  state ^= 1;                     // toggle the LED state
  delay( DELAY_MS );              // delay (in msec)
}

 

ก่อนทำขั้นตอน Verify & Upload เพื่อคอมไพล์และอัปโหลด Arduino Sketch ให้ตรวจสอบการตั้งค่าใช้งานดังนี้

  • เลือกบอร์ด Nucleo-32 / Nucleo L432KC
  • เลือก U(S)ART Support: Enabled (generic Serial)
  • เลือก USB Support (if available): None
  • เลือก STM32CubeProgrammer (SWD) หรือเลือกวิธี Mass Storage (แนะนำวิธีนี้) สำหรับ Upload Method
  • เลือกพอร์ต Serial / COM port ที่ตรงกับบอร์ดในขณะที่เชื่อมต่อใช้งานผ่านทางพอร์ต USB

ข้อสังเกต: โปรแกรมต่าง ๆ ที่เกี่ยวข้องกับ STM32duino จะถูกติดตั้งไว้ในไดเรกทอรีสำหรับผู้ใช้ เช่น สำหรับ Windows ดังนี้

C:\Users\%username%\AppData\Local\Arduino15\packages\STMicroelectronics

ภายในไดเรกทอรีดังกล่าว หากดูในไดเรกทอรีย่อย .\tools\STM32Tools\2.1.1 (เวอร์ชันล่าสุดที่ได้ทดลองใช้งาน) จะมีไฟล์ stm32CubeProg.sh ที่ใช้สำหรับการอัปโหลดไฟล์ .bin ไปยังบอร์ด STM32

ในการใช้งาน STM32CubeProgrammer (SWD) ภายในการทำงานของ Arduino IDE จะต้องมีการติดตั้ง ซอฟต์แวร์ STM32CubeProgrammer ก่อน และเพิ่มรายการ PATH ในระบบให้เรียกใช้ได้แบบ Command Line

 

รูป: ตรวจสอบการตั้งค่าสำหรับ Arduino Sketch เพื่อใช้งานกับบอร์ด Nucleo L432KC

 

รูป: ตัวอย่างข้อความเอาต์พุตเมื่อทำขั้นตอน Upload ได้สำเร็จแล้ว

โค้ดตัวอย่างต่อไปนี้ แสดงข้อมูลเกี่ยวกับระบบ เช่น ความถี่ในการทำงานของซีพียู ความจุของหน่วยความจำ Flash และตัวเลข CPU ID (32-bit) และ Unique Device ID (96-bit) เป็นต้น

void sysinfo() {
  pinMode( LED_BUILTIN, OUTPUT ); 

  Serial.printf( "CPU frequency: %lu MHz\n", SystemCoreClock/1000000UL );
  Serial.printf( "Flash size: %u KB\n", *(uint16_t *)FLASHSIZE_BASE );
  Serial.printf( "Unique device ID (96-bit): 0x%08X%08X%08X\n", 
                 *(uint32_t *)(UID_BASE),
                 *(uint32_t *)(UID_BASE+4),
                 *(uint32_t *)(UID_BASE+8) );
  Serial.printf( "CPUID (32-bit): 0x%08X\n", 
                 *(uint32_t *)(SCB_BASE) );
  Serial.printf( "LED_BUILTIN: %d (%d)\n\n", LED_BUILTIN, PB3 );
}

void setup() {
  pinMode( LED_BUILTIN, OUTPUT ); // configure LED_PIN as output
  Serial.begin( 115200 );
  // 02020000 = v2.2.0
  Serial.printf( "STM32_CORE_VERSION: %08x", STM32_CORE_VERSION );
}

void loop() {
  static int state = 0;
  sysinfo();
  digitalWrite( LED_BUILTIN, state ^= 1 );
  delay(1000);
}

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

 


▷ ** ตัวอย่างโค้ด 2: Press the button to toggle the LED (Interrupt-based Method)**#

โค้ดตัวอย่างที่ 2 สาธิตการใช้คำสั่ง attachInterrupt() ของ Arduino API สำหรับเปิดใช้งานอินเทอร์รัพท์ภายนอก (External Interrupt) ร่วมกับขาอินพุตที่ต่อกับวงจรปุ่มกดภายนอก ซึ่งเป็นวงจรปุ่มกดแบบ Active-Low ที่ขา D12 หรือ PB_4

เมื่อมีการกดปุ่มแต่ละครั้ง จะทำให้เกิดอินเทอร์รัพท์หนึ่งครั้ง และจะมีการทำคำสั่งต่าง ๆ ในฟังก์ชัน irq_handler() ซึ่งส่งผลให้มีการเปลี่ยนสถานะสำหรับเอาต์พุต LED ของบอร์ด

#define LED_PIN      PB3   // PB_3 or D13 pin 
#define BTN_PIN      PB4   // PB_4 or D12 pin 

#define COUNTDOWN_MAX  (16)

volatile bool state_changed = false;
char sbuf[64];  // string buffer (char array)

void irq_handler( ) { // triggered on the falling edge
  detachInterrupt( BTN_PIN );  // disable the interrupt
  state_changed = true;        // set 'state_changed' flag
}

void setup() {
  // configure the button pin as input
  pinMode( BTN_PIN, INPUT_PULLUP ); // enable internal pull-up
  // configure the LED pin as output
  pinMode( LED_PIN, OUTPUT ); 
  // update the LED output
  digitalWrite( LED_PIN, LOW ); 
  // specify the baudrate and start the serial
  Serial.begin( 115200 );
  // send a string to the serial
  Serial.println( "Arduino-STM32 Programming - Nucleo L432KC..." );
  // enable the external interrupt on the push button
  attachInterrupt( BTN_PIN, irq_handler, FALLING );
}

void loop() {
  // if an button-change event is detected
  if ( state_changed ) {
     uint16_t bits; // used to keep the input sample
     // sample the button input pin
     for ( int i=0; i < 16; i++ ) {
        bits = (bits << 1) | digitalRead( BTN_PIN );
        delay(2);
     }
     if (bits == 0) { // if the input pin is low and stable
        // toggle and update the LED output
        int state = !digitalRead(LED_PIN);
        digitalWrite( LED_PIN, state ); 
        sprintf( sbuf, "LED state: %d @%lu", state, millis() );
        Serial.println( sbuf );
        Serial.flush();
     }
     uint32_t countdown = COUNTDOWN_MAX;
     while ( countdown ) {
        // decrement if the input button is high, 
        // otherwise reset to initial value
        if ( digitalRead(BTN_PIN) ) {
          countdown--;
        } else {
          countdown = COUNTDOWN_MAX;
        }
        delay(2);
     }
     state_changed = false; // clear flag
     // re-enable the external interrupt on the push button
     attachInterrupt( BTN_PIN, irq_handler, FALLING ); 
   }
   else
     delay(10);
}

 


ตัวอย่างโค้ด 3: PWM-based LED Dimming#

โค้ดตัวอย่างที่ 3 สาธิตการปรับความสว่างของ LED เมื่อต่อกับขา D13 หรือ PB_3 โดยใช้เทคนิคที่เรียกว่า PWM (Pulse-Width Modulation) และสามารถทำได้โดยเรียกใช้คำสั่ง analogWrite()

#define PWM_LED_PIN   PB3  // PB_3 or D13 pin 
#define DELAY_MS      5

uint8_t value = 0; // used to store the PWM duty cycle between 0..255
int pm = 1; // 1 or -1

void setup() {
  pinMode( PWM_LED_PIN, OUTPUT );
  analogWrite( PWM_LED_PIN, value );
}

void loop() {
  if ( value == 255 && pm == 1 ) {
    pm = -1; // decrement
  } else if ( value == 0 && pm == -1 ) {
    pm = 1;  // increment
  }
  value += pm;
  analogWrite( PWM_LED_PIN, value ); 
  delay( DELAY_MS );
}

 


ตัวอย่างโค้ด 4: Timer-based LED Toggle#

โค้ดตัวอย่างที่ 4 สาธิตการใช้ไลบรารี HardwareTimer ของ STM32Duino ซึ่งสามารถเลือกใช้กับวงจรตัวนับภายในได้ เช่น TIM1 หรือ TIM2 เป็นต้น เพื่อทำหน้าที่เป็นตัวนับตามจังหวะของสัญญาณ Clock เช่น 10 Hz และมีการสร้างฟังก์ชันสำหรับ Callback ที่จะถูกเรียกทุก ๆ 0.1 วินาที และนำมาใช้ในการสลับสถานะของ LED บนบอร์ดทดลอง

#define LED_PIN   PB3    // PB_5 or D13 pin 
#define USE_TIM   TIM2

HardwareTimer *timer = NULL;

void timer_callback( void ) {
   static int state = 0;
   state ^= 1;                     // toggle the state
   digitalWrite( LED_PIN, state ); // update the LED output
}

void setup() {
   pinMode( LED_PIN, OUTPUT );
   timer = new HardwareTimer( USE_TIM );
   timer->setOverflow( 10, HERTZ_FORMAT ); // timer frequency = 10 Hz
   timer->attachInterrupt( timer_callback );
   timer->resume();
}

void loop() {}

 


▷ ** ตัวอย่างโค้ด 5: BH1750 Light Sensor Reading (I2C)**#

โค้ดตัวอย่างที่ 5 สาธิตการอ่านข้อมูลจากโมดูล BH1750 ซึ่งเป็นเซ็นเซอร์วัดความเข้มแสง โดยเชื่อมต่อกับบัส I2C ที่ขา PB_6 / I2C1_SCL และ PB_7 / I2C1_SDA และใช้แรงดันไฟเลี้ยง +3.3V และ GND ป้อนให้โมดูลเซ็นเซอร์

ในกรณีนี้ STM32 ทำหน้าที่เป็น I2C Master และในการเขียนโปรแกรมเราสามารถใช้ไลบรารี Wire ของ STM32duino ได้ (ดูตัวอย่างการใช้งานไลบรารีนี้ได้จาก "STM32duino Wire Examples")

ตัวอย่างนี้กำหนดค่าให้ BH1750 ทำงานในโหมดที่เรียกว่า Continuously H-Resolution Mode โดยจะวัดค่าความเข้มแสงต่อเนื่องไปเรื่อย ๆ และจะมีการวัดและอัปเดทค่าทุก ๆ 120 มิลลิวินาที

#include <Wire.h> // requires the Wire library

#define SCL_PIN      PB6  // PB_6 or D5 pin
#define SDA_PIN      PB7  // PB_7 or D4 pin

#define BH1750_I2CADDR             0x23 // 0x23 or 0x5c
#define BH1750_CONT_HIGH_RES_MODE  0x10 // Continuously H-Resolution Mode

bool bh1750_init() {
   Wire.beginTransmission( BH1750_I2CADDR );
   Wire.write( (uint8_t) BH1750_CONT_HIGH_RES_MODE );
   uint8_t error = Wire.endTransmission();
   delay(120);
   if ( error != 0 ) {
      Serial.println( "No ACK from sensor !!!" );
      return false;
   }
   return true;
}

boolean bh1750_read( uint16_t *lux_value ) {
   uint16_t level;
   boolean ok = true;
   // Start transmission to the sensor (I2C slave)
   Wire.beginTransmission( BH1750_I2CADDR ); 
   // Read two bytes from sensor (MSB and LSB bytes)
   Wire.requestFrom( BH1750_I2CADDR, 2 ); 
   if ( Wire.available() == 2 ) {
     level = Wire.read();
     level <<= 8;
     level |= Wire.read();
   } else {
     ok = false;
   }
   Wire.endTransmission();
   // Divide the 16-bit raw value by 1.2 to get the value in Lux 
   *lux_value = ok ? (10UL * level)/12 : 0; 
   return ok;
}

void setup() {
   Serial.begin( 115200 );
   Wire.setSDA( SDA_PIN );
   Wire.setSCL( SCL_PIN );
   Wire.begin(); 
   Wire.setClock( 400000 ); // 400kHz speed 
   delay(10);
   while( !bh1750_init() ) {
     Serial.println( "BH1750 initialization failed!" );
     delay(1000);
   }
   Serial.println( "\n\nBH1750 initialization OK!\n" );
}

void loop() {
   uint16_t lux;
   if ( bh1750_read( &lux ) ) { // if sensor reading is OK
     Serial.printf( "Light: %5d Lux\n", lux );
   } else {
     Serial.printf( "Light: ----- Lux\n" );
   }
   delay(500); 
}

รูป: การต่อวงจรทดลองร่วมกับโมดูล BH1750

 


ตัวอย่างโค้ด 6: SHT3x-DIS Sensor Reading (I2C)#

โค้ดตัวอย่างที่ 6 สาธิตการอ่านข้อมูลจากโมดูลเซนเซอร์วัดอุณหภูมิและความชื้นสัมพัทธ์ SHT3x-DIS โดยเชื่อมต่อกับบัส I2C ที่ขา PB_8 / I2C1_SCL และ PB_9 / I2C1_SDA และใช้แรงดันไฟเลี้ยง +3.3V

ในกรณีนี้ STM32 ทำหน้าที่เป็น I2C Master และในการเขียนโปรแกรมเราสามารถใช้ไลบรารี Wire ของ STM32duino ได้

#include <Wire.h>

#define SCL_PIN      PB6   // PB_6 or D5 pin
#define SDA_PIN      PB7   // PB_7 or D4 pin

#define SHT3x_I2C_ADDR         0x44    // SHT3x-DIS (I2C)
// single shot mode, no clock stretching
#define SHT3x_ACCURACY_HIGH    0x2C06
#define SHT3x_ACCURACY_MEDIUM  0x2C0D
#define SHT3x_ACCURACY_LOW     0x2C10
#define SHT3x_SOFT_RESET       0x30A2

TwoWire i2c( SDA_PIN, SCL_PIN );

char sbuf[64];
uint32_t ts;
float temp, humid;
uint8_t data[6]; 

// CRC-8, Polynomial: 0x31 = x^8 + x^5 + x^4 + 1, Initial value = 0xff
uint8_t CRC8( uint8_t data[], uint8_t len ) {
  uint8_t crc = 0xFF;
  for ( uint8_t i=0; i < len; i++ ) {
    crc ^= data[i];
    for ( uint8_t j=0; j < 8; j++ ) {
      crc = crc & 0x80 ? (crc << 1) ^ 0x31 : crc << 1;
    }
  }
  return crc;
}

void sht3x_init() {
  // Start I2C Transmission
  i2c.beginTransmission( SHT3x_I2C_ADDR );
  // Send soft reset command
  i2c.write( (SHT3x_SOFT_RESET >> 8) & 0xff );
  i2c.write( SHT3x_SOFT_RESET & 0xff ); 
  i2c.endTransmission();  // Stop I2C transmission
  delay(10);
  i2c.beginTransmission( SHT3x_I2C_ADDR );
  // Send soft reset command
  i2c.write( (SHT3x_ACCURACY_HIGH >> 8) & 0xff );
  i2c.write( SHT3x_ACCURACY_HIGH & 0xff ); 
  i2c.endTransmission();  // Stop I2C transmission
}

bool sht3x_read_sensor() {
  memset( data, 0x00, 6 );
  data[3] = data[5] = 0xff;
  // Start I2C Transmission
  i2c.beginTransmission( SHT3x_I2C_ADDR );
  // Send measurement command
  i2c.write( (SHT3x_ACCURACY_HIGH >> 8) & 0xff );
  i2c.write( SHT3x_ACCURACY_HIGH & 0xff ); 
  i2c.endTransmission();  // Stop I2C transmission
  delay(20); // note: measurement time is about 15 msec
  // Request 6 bytes of data
  i2c.requestFrom( SHT3x_I2C_ADDR, 6 ); // request 6 bytes
  if ( i2c.available() == 6 ) {
    data[0] = i2c.read(); // temperature Celsius (MSB: high byte)
    data[1] = i2c.read(); // temperature Celsius (LSB: low byte)
    data[2] = i2c.read(); // temperature Celsius CRC
    data[3] = i2c.read(); // relative humidty (%RH) (MSB: high byte)
    data[4] = i2c.read(); // relative humidty (%RH) (LSB: low byte)
    data[5] = i2c.read(); // relative humidty CRC
  } else {
    Serial.println("SHT3x sensor reading error\n");
    return false;
  }
  // check CRC for both values
  if ( CRC8(data,2)==data[2] && CRC8(data+3,2)==data[5] ) {
    uint16_t value;
    value = (data[0] << 8) + data[1];
    temp  = ((value * 175.0) / 65535) - 45; // temperature in Celsius.
    value = (data[3] << 8) + data[4];
    humid = ((value * 100.0) / 65535); // relative humidity
    return true;
  } 
  Serial.println("Checksum error\n");
  return false;  
}

void setup() {
  Serial.begin( 115200 );  // set serial baudrate to 115200
  i2c.begin( );            // start the I2C bus
  i2c.setClock( 400000 );  // set I2C clock frequency to 400kHz
  sht3x_init();            // initalize the SHT3x-DIS
  ts = millis();
}

void loop() {
  if ( millis() - ts >= 1000 ) { // read sensor every 1000 msec
    String str;
    ts += 1000;
    if ( sht3x_read_sensor() ) {
      str = "";
      dtostrf( humid, 3, 1, sbuf );
      str += "Humidity: ";
      str += sbuf;
      str += " %RH, ";
      dtostrf( temp, 3, 1, sbuf );
      str += "Temperature: ";
      str += sbuf;
      str += " deg.C";
      Serial.println( str.c_str() );
    } 
    else {
      Serial.println( "Sensor reading failed..." );
    }
  }
}

รูป: การต่อวงจรทดลองร่วมกับโมดูล SHT3x

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

 


▷ ** ตัวอย่างโค้ด 7: Serial Bridge**#

โค้ดตัวอย่างที่ 7 สาธิตการใช้ฮาร์ดแวร์ภายในที่เป็น USART จำนวน 2 ชุด พร้อมกัน (Serial และ Serial1) โดยทำหน้าที่เป็น Serial Bridge คือ ถ้าได้รับข้อมูลจากขา RX ของ Serial ก็จะส่งออกทางขา TX ของ Serial1 หรือถ้ารับข้อมูลจากขา RX ของ Serial1 ก็จะส่งออกทางขา TX ของ Serial ในการทดลองนี้ จะต้องใช้โมดูล USB-to-Serial (3.3V logic level) มาต่อกับขา RX / TX ของ Serial1


ข้อสังเกต: Serial ตรงกับ USART2 ซึ่งเป็นพอร์ตอนุกรมที่ต่อผ่าน ST-Link/V2 บนบอร์ดทดลอง และ Serial1 (ซึ่งสร้างจากคลาส HardwareSerial) ตรงกับ USART1 ซึ่งจะใช้ต่อเข้ากับโมดูล USB-to-Serial

// USART2: USART2_TX = PA2 / VCP_TX and USART2_RX = PA15 / VCP_RX 
// USART1: USART1_TX = PA9 / D1 and USART1_RX = PA10 / D0

HardwareSerial Serial1( PA10, PA9 );

void setup() {
  Serial.begin( 115200 );   // Serial = Serial2 for Nucleo L432KC Board
  Serial1.begin( 115200 );  // PA_9/D1=TX, PA_10/D0=RX (USART1)
}

void loop() {
  while ( Serial.available() > 0 ) {
     // read one char from Serial Rx and write it to Serial1 Tx
     char ch = Serial.read(); 
     Serial1.write( ch );
  }
  while ( Serial1.available() > 0 ) {
     // read one char from Serial1 Rx and write it to Serial Tx
     char ch = Serial1.read();
     Serial.write( ch );
  }
}

 


▷ ** ตัวอย่างโค้ด 8: การอ่านตัวเลขสุ่มจากวงจร RNG**#

ตัวอย่างนี้สาธิตการเขียนโค้ดด้วยวิธีเข้าถึงรีจิสเตอร์ของวงจรภายในชิป STM32L4 ที่เรียกว่า Hardware-RNG (Hardware Random Number Generator) เพื่อเปิดใช้งานวงจรดังกล่าวและอ่านค่าตัวเลขสุ่มขนาด 32 บิต ดังนั้นจึงเป็นตัวอย่างการเขียนโค้ดในรูปแบบที่เรียกว่า "Bare-Metal"

#include "stm32l4xx.h"

void RNG_Config( void ) {
  // enable peripheral clock for RNG
  RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN;
  // reset the RNG peripheral
  RCC->AHB2RSTR |=  RCC_AHB2RSTR_RNGRST;
  RCC->AHB2RSTR &= ~RCC_AHB2RSTR_RNGRST;
  // enable RNG
  RNG->CR |= RNG_CR_RNGEN;
}

boolean RNG_Read( uint32_t *value ) {
  uint32_t timeout = 255;
  // busy-wait until the RNG is ready (with timeout)
  while ( !(RNG->SR & RNG_SR_DRDY) ) { 
    if (timeout == 0) break;
    timeout--;
  }
  uint32_t is_error = (RNG->SR & (RNG_SR_SECS|RNG_SR_CECS));
  if ( (timeout>0) && (is_error==0) ) {
    *value = (uint32_t)RNG->DR; // read the data register of RNG
    return true; // success
  }
  *value = 0xffffffff;
  return false; // error
}

void setup() {  
  pinMode( LED_BUILTIN, OUTPUT ); 
  Serial.begin( 115200 );  // the same as Serial when using USB CDC
  Serial.printf( "\n\nSTM32L432KC RNG Demo...\n" );
  RNG_Config();
}

void loop() {
  // read four 32-bit random values from RNG
  for ( int i=0; i < 4; i++ ) {
    uint32_t value;
    if ( RNG_Read( &value ) ) {
       Serial.printf( "%08X ", value  );
    } else {
       Serial.print( "-------- " );
    }
  }
  Serial.print( "\n" );
  // toggle the LED
  digitalWrite( LED_BUILTIN, !digitalRead( LED_BUILTIN ) );
  delay(100);
}

 

รูป: ตัวอย่างข้อความเอาต์พุต

 

แต่หากจะลองเขียนโค้ด โดยใช้ฟังก์ชันของ STM32L4-HAL (Hardware Abstraction Layer) เพื่อใช้งาน Hardware RNG ก็มีตัวอย่างดังนี้ (ดูตัวอย่าง RNG_MultiRNG ที่ใช้ STM32-HAL API สำหรับ NUCLEO-L432KC ของ STM32CubeL4)

#include "stm32l4xx.h"
#include "stm32l4xx_hal.h"
#include "stm32l4xx_hal_rng.h"

RNG_HandleTypeDef hrng; // the Hardware RNG instance

void RNG_Config( void ) {
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;

  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RNG;
  // Choose the MSI as the clock source for the RNG peripheral
  PeriphClkInitStruct.RngClockSelection = RCC_RNGCLKSOURCE_MSI;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) {
     Serial.println(" RNG clock configuration error !!!\n" );
  }

  // Enable the RNG peripheral clock 
  __HAL_RCC_RNG_CLK_ENABLE();

  // Activate the RNG peripheral 
  hrng.Instance = RNG;
  hrng.State = HAL_RNG_STATE_RESET;
  hrng.Lock  = HAL_UNLOCKED;
#ifdef RNG_CR_CED
  hrng.Init.ClockErrorDetection = RNG_CED_ENABLE;
#endif

  if ( HAL_RNG_Init(&hrng) != HAL_OK ) {
    Serial.println( "HAL_RNG_Init() failed !!!" );
    Serial.printf( "RNG state: %lu\n", HAL_RNG_GetError(&hrng) );
  }
}

boolean RNG_Read( uint32_t *value ) {
  *value = 0xffffffff;
  if ( HAL_RNG_GenerateRandomNumber( &hrng, value )==HAL_OK ) {
     return true;
  }
  return false;
}

void setup() {  
  pinMode( LED_BUILTIN, OUTPUT ); 
  Serial.begin( 115200 );  // the same as Serial when using USB CDC
  Serial.printf( "\n\nSTM32L432KC Hardware RNG Demo...\n" );
  RNG_Config();
}

void loop() {
  // read four 32-bit random values from RNG
  for ( int i=0; i < 4; i++ ) {
    uint32_t value;
    if ( RNG_Read( &value ) ) {
       Serial.printf( "%08X ", value  );
    } else {
       Serial.print( "-------- " );
    }
  }
  Serial.print( "\n" );
  // toggle the LED
  digitalWrite( LED_BUILTIN, !digitalRead( LED_BUILTIN ) );
  delay(500);
}

หากจะคอมไพล์โค้ดตัวอย่างนี้ใน Arduino IDE ให้สร้างไฟล์ชื่อ build_opt.h เพิ่มในไดเรกทอรีของ Arduino Sketch และใส่ข้อความดังนี้

-DHAL_RNG_MODULE_ENABLED
-DRNG_CR_CED

 


▷ ** ตัวอย่างโค้ด 9: การใช้ไลบรารี Adafruit DHT เพื่ออ่านค่าจากโมดูล DHT22 **#

ตัวอย่างนี้สาธิตการใช้เขียนโค้ดโดยใช้ฟังก์ชันจากไลบรารีสำหรับ Arduino เพื่ออ่านค่าอุณหภูมิและความชื้นสัมพัทธ์จากโมดูล DHT22

ไปที่เมนูคำสั่ง "Tools > Include Library > Manage Libraries ..." และเมื่อเปิดหน้าต่าง Library Manager ให้ค้นหาชื่อไลบรารี "DHT sensor library" ของ Adafruit แล้วเลือกเวอร์ชันล่าสุดและกดปุ่ม Install เพื่อติดตั้งไลบรารี

รูป: ขั้นตอนการติดตั้งไลบรารี Adafruit DHT sensor library

 

#include <DHT.h>  // Adafruit Unified Sensor (tested with version 1.4.3)

#define DHTPIN    PB5      // use D11 pin on NUCEO-L432KC
#define DHTTYPE   DHT22

DHT dht(DHTPIN, DHTTYPE);  // Initilize object dht for class DHT 
                           // with DHT pin with STM32 and DHT type as DHT22
void setup() {
  Serial.begin( 115200 );
  Serial.flush();
  Serial.println( "STM32duino - DHT22 Demo..." );
  dht.begin();    // initialize the DHT22 sensor module                     
  delay(2000);    // wait 2 seconds for it to stabilize
}

char sbuf[32];
void loop() {
  float h = dht.readHumidity();     // read humidity value
  float t = dht.readTemperature() + 0.5;  // read temperature value

  // T: -40 to +125 degrees Celsius
  // H: 0 to 100%
  dtostrf( t, 1, 1, sbuf ); // convert float to string
  Serial.printf( "T=%s deg.C, ", sbuf );
  dtostrf( h, 1, 1, sbuf ); // convert float to string
  Serial.printf( "H=%s %%\n", sbuf );
  delay( 2000 );
}

รูป: การต่อวงจรทดลองเพื่อใช้งานโมดูล DHT22

รูป: ตัวอย่างข้อความเอาต์พุต

 


▷ ** ตัวอย่างโค้ด 10: การใช้งาน Virtual USB**#

ชิปไมโครคอนโทรลเลอร์ STM32L432KC รองรับการใช้งาน Native USB และสามารถนำมาใช้งานเป็นอุปกรณ์ประเภท USB Device / FullSpeed ได้ แต่บอร์ด NUCLEO-L432KC ไม่ได้ต่อขาสัญญาณ USB D+ และ USB D- กับคอนเนกเตอร์ MicroUSB สำหรับขาสัญญาณดังกล่าว ดังนั้นหากจะใช้งานในกรณีนี้ จะต้องใช้โมดูลคอนเนกเตอร์ 5-pin MicroUSB Breakout นำมาต่อวงจรเพิ่ม ดังนี้

5-pin MicroUSB  -- NUCLEO-L432KC
1) VUSB (5V)    -- ไม่ต้องต่อสายไฟ
2) D-           -- ต่อกับขา D10 / PA_11 ของบอร์ด NUCLEO-L432KC
3) D+           -- ต่อกับขา D2  / PA_12 ของบอร์ด NUCLEO-L432KC 
                -- และต่อตัวต้านทาน 1.5k Pull-up ไปยัง +3.3V (สำหรับ FullSpeed)
4) ID (sense)   -- ไม่ต้องต่อสายไฟ (floating)
5) GND          -- ต่อกับขา GND ของบอร์ด NUCLEO-L432KC

ข้อสังเกต: บอร์ด NUCLEO-L432KC ได้รับแรงดันไฟเลี้ยง +5V จากพอร์ต MicroUSB สำหรับวงจร onboard ST-Link/v2 ดังนั้น เมื่อต้องการใช้ขา USB D+/D- เพิ่ม ไม่จำเป็นต้องต่อแรงดันไฟเลี้ยง VUSB จากพอร์ต MicroUSB ที่ได้นำมาต่อเพิ่ม

รูป: ตัวอย่างการต่อวงจรบนเบรดบอร์ด

 

ตัวอย่างการเขียนโค้ดเพื่อส่งข้อมูลออกทาง SerialUSB (USB Serial) และ Serial2 (Hardware Serial)

void setup() {  
  pinMode( LED_BUILTIN, OUTPUT ); 
  Serial2.begin( 115200 ); // VCP -> onboard ST-Link
  SerialUSB.begin( 115200 );  // the same as Serial when using USB CDC
  while (!SerialUSB) {} // wait until the virtual COM port is open
  SerialUSB.printf( "STM32L432KC - SerialUSB or Serial\n" );
  SerialUSB.printf( "STM32L432KC - Serial2\n" );
}

void loop() {
  // toggle the LED
  int state = !digitalRead( LED_BUILTIN);
  digitalWrite( LED_BUILTIN, state );
  Serial.printf(  "LED: %d (Serial)\n", state );
  Serial2.printf( "LED: %d (Serial2)\n", state );
  delay(500);
}

 

ก่อนทำขั้นตอนคอมไพล์โค้ด ให้เปลี่ยนการตั้งค่าดังนี้

รูป: การตั้งค่าใน Arduino IDE เพื่อเปิดใช้งาน USB Support > USB CDC / Serial

 

เมื่ออัปโหลด Arduino Sketch แล้วเสียบสาย USB ครบทั้งสองเส้น จะมองเห็นรายการอุปกรณ์ USB Serial Devices ดังนี้

รูป: ตัวอย่างรายการของพอร์ตอนุกรมที่มองเห็นในเครื่องของผู้ใช้ (Windows) เช่น COM4 ตรงกับอุปกรณ์ ST-Link/v2 ของบอร์ด NUCLEO-L432KC และ COM6 ตรงกับอุปกรณ์ USB Serial Device ของ NUCLEO-L432KC

รูป: ตัวอย่างข้อความเอาต์พุตที่ถูกส่งออกมาทาง Serial หรือ SerialUSB (USB Serial Device)

รูป: ตัวอย่างข้อความเอาต์พุตที่ถูกส่งออกมาทาง Serial2 (VCP ST-Link/v2)

 


กล่าวสรุป#

บทความนี้ได้นำเสนอตัวอย่างการเขียนโค้ดโดยใช้ซอฟต์แวร์ Arduino IDE และได้มีการติดตั้ง STM32duino - Arduino Boards Manager เพื่อนำมาใช้งานกับบอร์ดไมโครคอนโทรลเลอร์ ST NUCLEO อย่างเช่น บอร์ด NUCLEO-L432KC ได้เห็นตัวอย่างการใช้คำสั่ง Arduino API และมีบางตัวอย่างได้เปรียบเทียบการเขียนโค้ดแบบ Bare-Metal การเขียนโค้ดโดยใช้ STM32-HAL API รวมถึงการติดตั้งและใช้งานไลบรารีสำหรับ Arduino

 


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

Created: 2022-01-01 | Last Updated: 2022-03-06