แนะนำการใช้งานบอร์ด ESP32-C3 Super-Mini#


ESP32-C3 Super-Mini#

บทความนี้กล่าวถึง การใช้งานบอร์ดไมโครคอนโทรลเลอร์ ESP32-C3 Super-Mini ของบริษัท QSZNTEC / Maker Go ในเบื้องต้น

ข้อมูลเกี่ยวกับบอร์ด

  • ชิปตัวประมวลผล: Espressif ESP32-C3FH4 SoC
    • Single-Core RISC-V 32-bit CPU, 160MHz
    • IEEE 802.11 b/g/n 2.4GHz WiFi / BLE 5.0 / Bluetooth Mesh
  • มีหน่วยความจำแบบแฟลชรวมไว้ในชิปเดียวกัน: Embedded SPI Flash (4MB)
  • เชื่อมต่อด้วยคอนเนกเตอร์ Type-C USB
  • ใช้ตัวสร้างความถี่ Crystal Oscillator 40MHz
  • ไม่มีชิป USB-to-Serial Bridge ดังนั้นต้องใช้งานแบบ USB-CDC สำหรับการรับส่งข้อมูลกับคอมพิวเตอร์ของผู้ใช้
  • ใช้ไอซีควบคุมแรงดันคงที่: ME611C33 LDO Voltage Regulator (3.3V)
  • ใช้สายอากาศแบบ Ceramic Chip Antenna (SMD)
  • มีปุ่มกด BOOT (10k pullup, GPIO-9)
  • มีปุ่มกด RESET (10k pullup, CHIP_EN)
  • มี Onboard LED (GPIO-8, Active-low)
  • 13x I/O Pins
  • 6x ADC Input Pins (A0 .. A5)
  • Power-On LED: VSYS
  • Power-On LED: 3.3V
  • ถ้ามีการบัดกรีด้วยขา Pin Headers ก็สามารถเสียบขาลงบนเบรดบอร์ดได้ (Breadboard-Friendly)

รูป: บอร์ด ESP32-C3 Super-Mini มุมมองด้านหน้าและด้านหลัง

รูป: บอร์ด ESP32-C3 Super-Mini รุ่นเก่า (ซ้ายมือ) และรุ่นปรับปรุง (ขวามือ)

รูป: แผนผังแสดงตำแหน่งขาของบอร์ด ESP32-C3 Super-Mini (มุมมองจากด้านหลังของบอร์ด)

รูป: ผังวงจร

 


Arduino Sketch Demo 1#

โค้ดตัวอย่างนี้สาธิตการสร้างทาสก์ (Task) โดยใช้ FreeRTOS ซึ่งรองรับการใช้งานโดยอัตโนมัติ (ถ้าใช้ Espressif ESP-IDF / Arduino Core for ESP32) ในตัวอย่างนี้เป็นการสร้างทาสก์ เพื่อทำให้ LED ที่ขา GPIO-8 กระพริบด้วยอัตราคงที่

const int ledPin = 8; // use GPIO-8

// Task function to blink the LED.
void led_blink(void *pvParameters) {
  (void)pvParameters; // Task parameters not used
  int state = 0;
  pinMode( ledPin, OUTPUT );
  while (1) {
    digitalWrite( ledPin, state ^=1 ); // Toggle the LED
    vTaskDelay( 500 / portTICK_PERIOD_MS ); 
  }
}

void setup() {
  Serial.begin( 115200 );
  delay( 2000 );
  Serial.println( "ESP32C3 FreeRTOS Demo..." );

  // Create and run a FreeRTOS task.
  xTaskCreate( 
    led_blink,       // Task function
    "ledBlinkTask",  // Task name
    512,             // Stack size (words)
    NULL,            // Task parameter
    1,               // Task priority
    NULL             // Task handle
  );
}

void loop() {
}

 


Arduino Sketch Demo 2#

โค้ดตัวอย่างนี้สาธิตการสร้างทาสก์จำนวน 2 ทาสก์ โดยให้ทาสก์แรกอ่านค่าจากขาอินพุตแบบแอนะล็อก ตามช่วงเวลาที่กำหนดไว้ เลือกใช้ขา GPIO-2 ในตัวอย่างนี้ ค่าที่ได้จากการแปลงด้วยวงจร ADC ภายในชิป จะเป็นตัวเลขขนาด 12 บิต จากนั้นก็ส่งข้อมูลตัวเลขที่ได้ไปยังอีกทาสก์หนึ่ง โดยใช้วิธีการสื่อสารข้อมูลด้วยแถวคอย (Queue) เมื่อทาสก์ที่สองได้รับข้อมูล ก็จะนำค่าดังกล่าวไปใช้ในการอัปเดตค่า Duty Cycle ของสัญญาณ PWM เพื่อปรับความสว่างของ LED

const int AIN_PIN = 2;  // GPIO-2
const int LED_PIN = 8;  // GPIO-8

// FreeRTOS task handles
TaskHandle_t tasks[2];

// Queue handle for passing values between tasks.
QueueHandle_t queue = NULL;

// Task function to read analog value
void analog_input_read(void *pvParameters) {
  (void)pvParameters;
  pinMode( AIN_PIN, ANALOG );
  analogSetPinAttenuation( AIN_PIN, ADC_11db );
  analogReadResolution( 12 ); // 12-bit ADC resolution

  while (1) {
    // Read the analog input pin.
    uint32_t value = analogRead(AIN_PIN);
    // Send the analog value to the queue.
    xQueueSend(queue, &value, portMAX_DELAY);
    // Delay for 50 ticks (1 tick = 1 msec).
    vTaskDelay( pdMS_TO_TICKS(50) );
  }
}

// Task function to process the value
void led_pwm_update(void *pvParameters) {
  (void)pvParameters;
  int value;
  while (1) {
    // Wait until the queue is not empty.
    if (xQueueReceive(queue, &value, portMAX_DELAY)) {
      // Update the PWM duty cycle (8-bit) for LED dimming.
      Serial.printf( "AIN: %lu\n", value );
      analogWrite( LED_PIN, 255 - (value >> 4) );
    }
  }
}

void setup() {
  Serial.begin( 115200 );
  while(!Serial);
  delay( 2000 );
  Serial.println( "ESP32C3 FreeRTOS Demo..." );

  // Create a FreeRTOS queue used to pass an integer value.
  queue = xQueueCreate( 1, sizeof(uint32_t) );

  // Create two FreeRTOS tasks
  xTaskCreate(
      analog_input_read,     // Task function
      "AnalogReadTask",      // Task name
      2048,                  // Stack size (words)
      NULL,                  // Task input parameter
      1,                     // Task priority
      &tasks[0]              // Task handle
  );
  xTaskCreate(
      led_pwm_update,        // Task function
      "ledPwmTask",          // Task name
      4096,                  // Stack size (words)
      NULL,                  // Task input parameter
      2,                     // Task priority
      &tasks[1]              // Task handle
  );
}

void loop() {
}

รูป: ตัวอย่างการต่อวงจรทดลองบนเบรดบอร์ด โดยใช้ตัวต้านทานปรับค่าได้สร้างสัญญาณอินพุต-แอนะล็อก

 


Arduino Sketch Demo 3#

ตัวอย่างโค้ดถัดไป สาธิตการตรวจสอบการกดปุ่มที่ขา GPIO-9 ซึ่งเป็นปุ่มกดบนบอร์ดที่ทำงานแบบ Active-Low เมื่อมีการกดปุ่มแล้วเกิดเหตุการณ์ขอบขาลง จะทำให้เกิดอินเทอร์รัพท์ และมีการเรียกใช้ฟังก์ชัน ISR (Interrupt Service Routine) ที่เกี่ยวข้อง

ฟังก์ชัน ISR จะใช้เซมาฟอร์แบบไบนารี (Binary Semaphore) สื่อสารกับอีกทาสก์หนึ่งที่คอยอยู่ เมื่อทาสก์ดังกล่าวเข้าถึงเซมาฟอร์ได้ ก็จะสลับสถานะลอจิก LED ที่ขา GPIO-8 หนึ่งครั้ง และวนกลับไปรอเซมาฟอร์อีกรอบ

const int BTN_PIN = 9;
const int LED_PIN = 8;

// FreeRTOS task handle
TaskHandle_t buttonTask;

// FreeRTOS binary semaphore
SemaphoreHandle_t semaphore;

// Function to handle button press
void buttonAction( void *pvParameters ) {
  (void)pvParameters;
  bool state = 0;
  uint32_t clicks = 0;
  pinMode( LED_PIN, OUTPUT );
  digitalWrite( LED_PIN, HIGH ); 
  while (1) {
    if (xSemaphoreTake(semaphore, portMAX_DELAY) == pdTRUE) {
      digitalWrite( LED_PIN, state ^= 1 ); // Toggle the LED. 
      Serial.printf("Button pressed! (#%lu)\n", ++clicks );
    }
  }
}

void setup() {
  Serial.begin( 115200 );
  while(!Serial);
  delay( 2000 );
  Serial.println( "ESP32C3 FreeRTOS Demo..." );

  pinMode( BTN_PIN, INPUT_PULLUP );
  // Create a FreeRTOS binary semaphore.
  semaphore = xSemaphoreCreateBinary();

  // Attach an external interrupt to the button pin.
  attachInterrupt( digitalPinToInterrupt(BTN_PIN), [] {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR( semaphore, &xHigherPriorityTaskWoken );
    if (xHigherPriorityTaskWoken == pdTRUE) {
       portYIELD_FROM_ISR();
    }
  }, FALLING );

  // Create the button task.
  xTaskCreate(
      buttonAction,       // Task function
      "ButtonTask",       // Task name
      2048,               // Stack size (words)
      NULL,               // Task input parameter
      1,                  // Task priority
      &buttonTask         // Task handle
  );
}

void loop() {
}

รูป: ตัวอย่างข้อความเอาต์พุต หลังจากมีการกดปุ่มบนบอร์ดหลายครั้ง

 

หากจะเปลี่ยนจากการใช้เซมาฟอร์แบบไบนารี ไปใช้วิธีการแจ้งเตือนเหตุการณ์ระหว่างทาสก์ (Task Notification) ก็มีตัวอย่างการเขียนโค้ดดังต่อไปนี้

const int BTN_PIN = 9;
const int LED_PIN = 8;

// FreeRTOS task handle
TaskHandle_t buttonTask;

// Function to handle button press
void buttonAction(void *pvParameters) {
  (void)pvParameters;
  bool state = 0;
  uint32_t clicks = 0;
  pinMode( LED_PIN, OUTPUT );
  digitalWrite( LED_PIN, HIGH );
  while (1) {
    // Wait for task notification.
    ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
    digitalWrite( LED_PIN, state ^= 1 ); // Toggle the LED.
    Serial.printf( "Button pressed! (#%lu)\n", ++clicks );
  }
}

void setup() {
  Serial.begin( 115200 );
  while(!Serial);
  delay( 2000 );
  Serial.println( "ESP32C3 FreeRTOS Demo..." );

  pinMode( BTN_PIN, INPUT_PULLUP );

  // Attach an external interrupt to the button pin.
  attachInterrupt( digitalPinToInterrupt(BTN_PIN), [] {
    // Send a task notification to the button task.
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    vTaskNotifyGiveFromISR( buttonTask, &xHigherPriorityTaskWoken );
    if (xHigherPriorityTaskWoken == pdTRUE) {
       portYIELD_FROM_ISR();
    }
  }, FALLING);

  // Create the button task.
  xTaskCreate(
      buttonAction,   // Task function
      "ButtonTask",   // Task name
      2048,           // Stack size (words)
      NULL,           // Task input parameter
      1,              // Task priority
      &buttonTask     // Task handle
  );
}

void loop() {
}

 


Arduino Sketch Demo 4#

โค้ดตัวอย่างถัดไป สาธิตการเปลี่ยนสีของ WS2812 RGB LED เมื่อมีการกดปุ่มในแต่ละครั้ง ตามลำดับของค่าสีที่ได้มีการกำหนดไว้ในอาร์เรย์ ในการเขียนโค้ดเพื่อใช้งานโมดูล WS2812 / NeoPixel ก็มีไลบรารี Adafruit_NeoPixel ของบริษัท Adafruit ให้ใช้งานได้

เมื่อมีการกดปุ่มแต่ละครั้ง จะเกิดอินเทอร์รัพท์ และฟังก์ชัน ISR ที่เกี่ยวข้องจะสื่อสารไปยังทาสก์ ด้วยวิธี Task Notification ของ FreeRTOS และทาสก์ดังกล่าวที่รออยู่ ได้รับการแจ้งเหตุการณ์จาก ISR ก็จะทำงานได้แล้วเปลี่ยนสีของ RGB LED แล้วรอการสื่อสารจาก ISR ในครั้งถัดไป

#include <Adafruit_NeoPixel.h>

const int BTN_PIN = 9;
const int WS2812_PIN = 7;

const uint32_t COLORS [] = {
   0x00001f, //  Blue
   0x001f00, // Green
   0x1f0000, // Red
   0x0000000 // OFF
};
const uint32_t NUM_COLORS = sizeof(COLORS)/sizeof(uint32_t);

// Create a NeoPixel object for a single WS2812 RGB LED.
Adafruit_NeoPixel neopixel(1, WS2812_PIN, NEO_GRB + NEO_KHZ800);

// FreeRTOS task handle
TaskHandle_t buttonTask;

// Function to handle button press
void buttonAction(void *pvParameters) {
  (void)pvParameters;
  uint32_t clicks = 0;
  uint32_t index = (NUM_COLORS-1);

  neopixel.begin();
  neopixel.setPixelColor(0, 0x1f1f1f );
  neopixel.show();

  while (1) {
    // Wait for task notification.
    ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
    Serial.printf( "Button pressed! (#%lu)\n", ++clicks );
    // Change the WS2812 color.
    index = (index+1) % NUM_COLORS;
    neopixel.setPixelColor(0, COLORS[ index ] );
    neopixel.show();
  }
}

void setup() {
  Serial.begin( 115200 );
  while(!Serial);
  delay( 2000 );
  Serial.println( "ESP32C3 FreeRTOS Demo..." );

  pinMode( BTN_PIN, INPUT_PULLUP );

  // Attach an external interrupt to the button pin.
  attachInterrupt( digitalPinToInterrupt(BTN_PIN), [] {
    // Send a task notification to the button task.
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    vTaskNotifyGiveFromISR( buttonTask, &xHigherPriorityTaskWoken );
    if (xHigherPriorityTaskWoken == pdTRUE) {
       portYIELD_FROM_ISR();
    }
  }, FALLING);

  // Create the button task.
  xTaskCreate(
      buttonAction,   // Task function
      "ButtonTask",   // Task name
      2048,           // Stack size (words)
      NULL,           // Task input parameter
      1,              // Task priority
      &buttonTask     // Task handle
  );
}

void loop() {
}

รูป: ตัวอย่างการต่อวงจรทดลองบนเบรดบอร์ด โดยใช้โมดูล WS2812 RGB LED (Single-Pixel)

 


Arduino Sketch Demo 5#

ตัวอย่างถัดไปสาธิตการรับและส่งต่อข้อมูลระหว่าง USB-CDC กับ Hardware Serial (เลือกใช้ขา GPIP-20 สำหรับ RX และขา GPIO-21 สำหรับ TX) มีการทำงานออกเป็นสองส่วน คือ ฟังก์ชัน task1 และ task2

  • task1: เมื่อมีข้อมูลได้ถูกส่งจากคอมพิวเตอร์ของผู้ใช้ทางช่องทาง USB-CDC ก็จะมีการอ่านข้อมูลดังกล่าว แล้วส่งต่อออกไปทางขา TX ของ Hardware Serial
  • task2: ถ้ามีข้อมูลถูกส่งเข้ามาทาง Hardware Serial ที่ขา RX ก็จะอ่านและส่งต่อไปยัง USB-CDC
#include <HardwareSerial.h>

#define RX_PIN   (20)
#define TX_PIN   (21)
#define BUF_SIZE (64)

// Create a HardwareSerial object using UART0
HardwareSerial MySerial(0);
uint8_t tx_data[ BUF_SIZE+1 ];
uint8_t rx_data[ BUF_SIZE+1 ];

void task1() {
  uint32_t len;
  if ( (len = Serial.available()) > 0 ) {
    if ( len > BUF_SIZE ) { len = BUF_SIZE; }
    // Read incoming bytes from Serial.
    len = Serial.readBytes( tx_data, len );
    if ( len > 0 ) {
      // Send the received bytes to MySerial.
      MySerial.write( tx_data, len );
    }
  }
}

void task2() {
  uint32_t len;
  if ( (len = MySerial.available()) > 0 ) {
    if ( len > BUF_SIZE ) { len = BUF_SIZE; }
    // Read incoming bytes from MySerial.
    len = MySerial.readBytes( tx_data, len );
    if ( len > 0 ) {
      // Send the received bytes to Serial.
      Serial.write( tx_data, len );
    }
  }
}

void setup() {
  Serial.begin( 115200 );
  Serial.setTimeout(10);
  MySerial.begin( 115200, SERIAL_8N1, RX_PIN, TX_PIN );
  MySerial.setDebugOutput(false);
  while( MySerial.available() ) { (void)MySerial.read(); }
  MySerial.setTimeout(10);
}

void loop() {
  task1();
  task2();
}

ถ้าจะลองเขียนให้อยู่ในรูปแบบของทาสก์สำหรับ FreeRTOS ก็มีตัวอย่างดังนี้

#include <HardwareSerial.h>

#define RX_PIN   (20)
#define TX_PIN   (21)
#define BUF_SIZE (64)

HardwareSerial MySerial(0);
uint8_t tx_data[ BUF_SIZE+1 ];
uint8_t rx_data[ BUF_SIZE+1 ];

void task1(void *pvParameters) {
  (void)pvParameters; // Task parameters not used.
  uint32_t len;
  while (1) {    
    if ( (len = Serial.available()) > 0 ) {
      if ( len > BUF_SIZE ) { len = BUF_SIZE; }
      len = Serial.readBytes( tx_data, len );
      if ( len > 0 ) {
        MySerial.write( tx_data, len );
      }
    }
    vTaskDelay(pdMS_TO_TICKS(10));
  }
}

void task2(void *pvParameters) {
  (void)pvParameters; // Task parameters not used.
  uint32_t len;
  while (1) {
    if ( (len = MySerial.available()) > 0 ) {
      if ( len > BUF_SIZE ) { len = BUF_SIZE; }
      len = MySerial.readBytes( tx_data, len );
      if ( len > 0 ) {
        Serial.write( tx_data, len );
      }
    }
    vTaskDelay(pdMS_TO_TICKS(10));
  }
}

void setup() {
  Serial.begin( 115200 );
  Serial.setTimeout(10);
  MySerial.begin( 115200, SERIAL_8N1, RX_PIN, TX_PIN );
  MySerial.setDebugOutput(false);
  while( MySerial.available() ) { (void)MySerial.read(); }
  MySerial.setTimeout(10);

  xTaskCreate( task1, "Task1", 1024, NULL, 1, NULL );
  xTaskCreate( task2, "Task2", 1024, NULL, 1, NULL );
}

void loop() {
}

 


กล่าวสรุป#

บทความนี้ได้นำเสนอการใช้งานบอร์ด ESP32-C3 Super-Mini ซึ่งถือว่าเป็นบอร์ดไมโครคอนโทรลเลอร์อีกตัวอย่างหนึ่งที่ใช้ชิป ESP32-C3 SoC และบอร์ดมีขนาดเล็ก ขนาดใกล้เคียงกับบอร์ด Seeed Studio XIAO ESP32-C3 นอกจากนั้นแล้วยังได้สาธิตการเขียนโค้ดด้วย Arduino + FreeRTOS เป็นตัวอย่าง

 


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

Created: 2023-09-17 | Last Updated: 2023-09-30