แนะนำเรื่องการใช้ I2C สำหรับ Arduino, ESP32 และ Micro Python

I²C – Wikipedie

I2C — Arduino-ESP32 2.0.6 documentation

ความสัมพันธ์

I2C เป็นโปรโตคอลการสื่อสารที่ถูกออกแบบมาเพื่อใช้ในการเชื่อมต่ออุปกรณ์อิเล็กทรอนิกส์ในระบบแบบแปลงข้อมูลแบบมาสเตอร์-สแลฟ (Master-Slave) ซึ่งหมายความว่ามีอุปกรณ์หลัก (Master) และอุปกรณ์ย่อย (Slave) ที่เชื่อมต่อกันผ่านสาย SDA และ SCL.

  • Master (ผู้ควบคุมหลัก): อุปกรณ์หลักใช้ในการส่งคำสั่งและข้อมูลไปยังอุปกรณ์ย่อย (Slave) โดยอาศัย I2C address ของแต่ละอุปกรณ์. Master กำหนดความเร็วในการสื่อสาร (บางครั้งเรียกว่า Baud rate) และควบคุมการสื่อสารโดยรวมในเครือข่าย I2C.
  • Slave (ผู้ควบคุมย่อย): อุปกรณ์ย่อยรอรับคำสั่งและข้อมูลจาก Master โดยอ้างอิงถึง I2C address ของตัวเอง. Slave จะตอบสนองกับคำสั่งของ Master และส่งข้อมูลกลับหา Master ตามที่ได้รับคำสั่ง.

ความจำเป็น :

การใช้งาน I2C มีความจำเป็นในหลายสถานการณ์ เช่น:

  • การสื่อสารกับอุปกรณ์นิ่ง (Peripherals): I2C มักถูกใช้ในการสื่อสารกับอุปกรณ์เซ็นเซอร์และโมดูลต่าง ๆ ที่อยู่ในระบบภายนอก เช่น อุปกรณ์เก็บข้อมูลแบบ EEPROM, อุปกรณ์เรนเดอร์แสดงผล OLED, หรือเซ็นเซอร์อุณหภูมิ.
  • การเชื่อมต่อหลายอุปกรณ์: I2C ช่วยให้คุณสามารถเชื่อมต่อหลายอุปกรณ์ไปยังไมโครคอนโทรลเลอร์เดียว โดยใช้แค่สองสายเท่านั้น นี่เป็นประโยชน์ในโปรเจกต์ที่มีหลายอุปกรณ์ที่ต้องการการสื่อสาร.
  • ประหยัดพื้นที่: I2C เป็นโปรโตคอลที่ใช้เส้นสายน้อยกว่า การสื่อสารแบบอื่น ๆ เช่น SPI (Serial Peripheral Interface) หรือ UART (Universal Asynchronous Receiver-Transmitter) ซึ่งทำให้เหมาะสำหรับโปรเจกต์ที่มีพื้นที่จำกัด.
  • ความสะดวกและเร็ว: I2C มีความสะดวกในการใช้งานและมีความเร็วในการสื่อสารที่เพียงพอสำหรับส่วนใหญ่ของโปรเจกต์.
  • การติดตั้งและการใช้งาน: มีไลบรารีที่มีอยู่สำหรับ I2C บนหลายไมโครคอนโทรลเลอร์ที่ทำให้ง่ายต่อการติดตั้งและการใช้งาน.

ดังนั้น I2C เป็นโปรโตคอลที่มีประโยชน์มากในการเชื่อมต่อและควบคุมอุปกรณ์อิเล็กทรอนิกส์ในโปรเจกต์ที่ต้องการการสื่อสารระหว่างไมโครคอนโทรลเลอร์และอุปกรณ์อื่น ๆ ในระบบของคุณโดยมีความสะดวกและประหยัดพื้นที่อีกด้วย.



การใช้งาน I2C (Inter-Integrated Circuit) เป็นหนึ่งในวิธีที่ดีในการเชื่อมต่ออุปกรณ์อิเล็กทรอนิกส์ต่าง ๆ กับไมโครคอนโทรลเลอร์เช่น Arduino, ESP32 และไมโครไพทอล เนื้อหาในบทความนี้จะช่วยคุณเริ่มต้นในการใช้งาน I2C เบื้องต้นสำหรับเครื่องมือเหล่านี้

1. I2C เบื้องต้น

I2C เป็นมาตรฐานสื่อสารที่ออกแบบมาเพื่อใช้ในการเชื่อมต่ออุปกรณ์อิเล็กทรอนิกส์ในระบบแบบแปลงข้อมูลแบบมาสเตอร์-สแลฟ. มันใช้สายสองเส้นสำหรับการสื่อสาร - SDA (Serial Data Line) และ SCL (Serial Clock Line). นี่คือขั้นตอนพื้นฐานในการใช้งาน I2C:

  • ให้ตรวจสอบความถูกต้องของการต่อสาย SDA และ SCL ระหว่างอุปกรณ์ที่คุณต้องการเชื่อมต่อและไมโครคอนโทรลเลอร์ของคุณ (Arduino, ESP32, หรือไมโครไพทอล).
  • ต้องมีข้อมูลที่แน่นอนเกี่ยวกับที่อยู่ I2C ของอุปกรณ์ (หรือที่เรียกว่า I2C address) ซึ่งต้องระบุในโค้ดของคุณ.
  • คุณต้องเขียนโค้ดในไมโครคอนโทรลเลอร์เพื่อส่งข้อมูลไปยังอุปกรณ์ I2C หรือรับข้อมูลจากอุปกรณ์ I2C โดยใช้ไลบรารีหรือไลบรารีที่มีอยู่สำหรับแต่ละไมโครคอนโทรลเลอร์.

2. การใช้ I2C บน Arduino

การต่อสาย

  • ใน Arduino, สาย SDA และ SCL ประกอบด้วยขา A4 และ A5 ตามลำดับ.

การเขียนโค้ด

  • คุณสามารถใช้ Wire Library ใน Arduino ในการเขียนโค้ดสำหรับ I2C.

#include <Wire.h>

 void setup() {

  Wire.begin(); // เริ่มต้นการใช้งาน I2C

  // สามารถเพิ่มการตั้งค่าอื่น ๆ ตามต้องการ

}

 void loop() {

  // โค้ดเชื่อมต่อกับอุปกรณ์ I2C และสื่อสารข้อมูลได้ที่นี่

}

 

3. การใช้ I2C บน ESP32

อ้างอิง : https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/api/i2c.html

การต่อสาย

  • ใน ESP32, สาย SDA และ SCL ประกอบด้วยขา GPIO 21 และ GPIO 22 ตามลำดับ.

การเขียนโค้ด

  • คุณสามารถใช้ไลบรารี Wire.h ใน ESP32 เช่นเดียวกับ Arduino.

 

#include <Wire.h>

 void setup() {

  Wire.begin(); // เริ่มต้นการใช้งาน I2C

  // สามารถเพิ่มการตั้งค่าอื่น ๆ ตามต้องการ

}

 

void loop() {

  // โค้ดเชื่อมต่อกับอุปกรณ์ I2C และสื่อสารข้อมูลได้ที่นี่

}

 

4. การใช้ I2C บน Micro Python

การต่อสาย

  • การต่อสาย I2C บนไมโครไพทอลจะขึ้นอยู่กับบอร์ดคอนโทรลเลอร์ที่คุณใช้. คุณต้องทำการต่อสาย SDA และ SCL ไปยังขาที่เหมาะสมของบอร์ด.

การเขียนโค้ด

  • คุณต้องใช้ไลบรารี I2C ที่เหมาะสำหรับไมโครไพทอล ที่อาจต่างกันไปตามบอร์ดและไมโครคอนโทรลเลอร์ที่คุณใช้.

นี่คือการเริ่มต้นเบื้องต้นในการใช้งาน I2C สำหรับ Arduino, ESP32, และไมโครไพทอล โปรดทราบว่าการใช้งาน I2C อาจต้องปรับแต่งตามอุปกรณ์และโครงการของคุณ. ตรวจสอบเอกสารและคู่มือที่เกี่ยวข้องของผู้ผลิตไมโครคอนโทรลเลอร์และอุปกรณ์ I2C เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับการใช้งาน I2C ในบทบาทที่คุณต้องการใช้งาน.




Code : 

#include <Arduino.h>
#include <Wire.h>

// กำหนดที่อยู่ I2C สำหรับอุปกรณ์สามตัว
#define SLAVE_ADDRESS_1 8
#define SLAVE_ADDRESS_2 9
#define SLAVE_ADDRESS_3 10

void setup() {
Wire.begin(); // กำหนดค่าเริ่มต้นให้การสื่อสารผ่าน I2C
Serial.begin(9600); // กำหนดค่าเริ่มต้นให้การสื่อสารผ่านช่องทางอีกช่องหนึ่งสำหรับการดีบัค
}

void loop() {
// จัดการการสื่อสารกับแต่ละอุปกรณ์แยกกัน
handleDevice1(SLAVE_ADDRESS_1);
handleDevice2(SLAVE_ADDRESS_2);
handleDevice3(SLAVE_ADDRESS_3);

// โค้ดเพิ่มเติมสำหรับงานที่ซ้ำกัน
}

void handleDevice1(int address) {
// สื่อสารกับอุปกรณ์ 1 (ที่อยู่อาณาเขต: SLAVE_ADDRESS_1)
Wire.beginTransmission(address); // เริ่มการสื่อสารกับอุปกรณ์
// ส่งคำสั่งหรือข้อมูลที่เฉพาะกับอุปกรณ์ 1
Wire.write("คำสั่งสำหรับอุปกรณ์ 1");
Wire.endTransmission(); // สิ้นสุดการสื่อสาร
}

void handleDevice2(int address) {
// สื่อสารกับอุปกรณ์ 2 (ที่อยู่อาณาเขต: SLAVE_ADDRESS_2)
Wire.beginTransmission(address); // เริ่มการสื่อสารกับอุปกรณ์
// ส่งคำสั่งหรือข้อมูลที่เฉพาะกับอุปกรณ์ 2
Wire.write("คำสั่งสำหรับอุปกรณ์ 2");
Wire.endTransmission(); // สิ้นสุดการสื่อสาร
}

void handleDevice3(int address) {
// สื่อสารกับอุปกรณ์ 3 (ที่อยู่อาณาเขต: SLAVE_ADDRESS_3)
Wire.beginTransmission(address); // เริ่มการสื่อสารกับอุปกรณ์
// ส่งคำสั่งหรือข้อมูลที่เฉพาะกับอุปกรณ์ 3
Wire.write("คำสั่งสำหรับอุปกรณ์ 3");
Wire.endTransmission(); // สิ้นสุดการสื่อสาร
}

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

การขัดจังหวะสามารถแบ่งออกได้เป็น 2 ประเภทหลักๆ คือ

  • การขัดจังหวะจากอุปกรณ์ภายนอก (External Interrupt) เป็นการขัดจังหวะที่เกิดจากอุปกรณ์ภายนอก เช่น การกดปุ่มบนแป้นพิมพ์ การมาถึงของข้อมูลจากอุปกรณ์ภายนอก เป็นต้น
  • การขัดจังหวะจากซอฟต์แวร์ (Software Interrupt) เป็นการขัดจังหวะที่เกิดจากซอฟต์แวร์ เช่น การเรียกใช้ฟังก์ชัน interrupt ในโปรแกรม

หลักการทำงานของการขัดจังหวะ มีดังนี้

  1. อุปกรณ์ภายนอกหรือซอฟต์แวร์จะส่งสัญญาณขัดจังหวะไปยังไมโครโปรเซสเซอร์
  2. ไมโครโปรเซสเซอร์จะหยุดการทำงานที่ดำเนินอยู่ในปัจจุบันชั่วคราว
  3. ไมโครโปรเซสเซอร์จะบันทึกค่าสถานะการทำงานปัจจุบันลงในหน่วยความจำ
  4. ไมโครโปรเซสเซอร์จะเปลี่ยนเส้นทางการประมวลผลไปยังโปรแกรมย่อยการขัดจังหวะ
  5. โปรแกรมย่อยการขัดจังหวะจะจัดการกับเหตุการณ์สำคัญที่เกิดขึ้น
  6. โปรแกรมย่อยการขัดจังหวะจะส่งสัญญาณกลับไปยังไมโครโปรเซสเซอร์
  7. ไมโครโปรเซสเซอร์จะกลับมาทำงานที่ดำเนินอยู่เดิม

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

นอกจากนี้ การขัดจังหวะยังช่วยให้สามารถจัดการกับเหตุการณ์สำคัญต่างๆ ได้อย่างทันท่วงที เช่น การกดปุ่มบนแป้นพิมพ์ การมาถึงของข้อมูลจากอุปกรณ์ภายนอก เป็นต้น

ไมโครคอนโทรลเลอร์ ESP32 พัฒนาโดย Espressif Systems มีระบบขัดจังหวะที่ยืดหยุ่นมาก IRAM_ATTR เป็นคำหลักที่ใช้ระบุว่าควรวางฟังก์ชันเฉพาะไว้ใน RAM ภายในของ ESP32 เพื่อการดำเนินการที่รวดเร็วยิ่งขึ้น โดยเฉพาะอย่างยิ่งที่สำคัญอย่างยิ่งสำหรับ Interrupt Service Routines (ISR) เพื่อให้แน่ใจว่าฟังก์ชันดังกล่าวสามารถทำงานได้อย่างรวดเร็วและไม่ถูกขัดจังหวะโดยการทำงานของหน่วยความจำแฟลช .

จำนวน "ชุดการขัดจังหวะ" ที่สามารถรองรับได้:

  • จำนวนการขัดจังหวะของฮาร์ดแวร์: ESP32 มีพิน GPIO จำนวนหนึ่ง ซึ่งส่วนใหญ่สามารถกำหนดค่าให้เป็นแหล่งสัญญาณขัดจังหวะภายนอกได้ ตามทฤษฎีแล้ว หมายความว่า GPIO เกือบทุกพินสามารถมีรูทีนขัดจังหวะของตัวเองได้ อย่างไรก็ตาม จำนวนที่แน่นอนอาจน้อยกว่าเล็กน้อย เนื่องจากพินบางตัวอาจมีฟังก์ชันพิเศษหรืออาจไม่ปรากฏบนทุกโมดูล
  • การขัดจังหวะที่ใช้ร่วมกัน: เป็นไปได้ที่พิน GPIO หลายอันจะแชร์รูทีนขัดจังหวะเดียว ซึ่งทำได้โดยการตรวจสอบว่าพินใดที่ทำให้เกิดการขัดจังหวะภายใน ISR
  • การขัดจังหวะของซอฟต์แวร์และการแจ้งเตือนงาน: นอกเหนือจากการขัดจังหวะด้วยฮาร์ดแวร์แล้ว ESP32 ยังรองรับการขัดจังหวะที่เรียกใช้ซอฟต์แวร์และการแจ้งเตือนงานอีกด้วย สิ่งเหล่านี้ใช้ภายในสภาพแวดล้อม FreeRTOS ที่กรอบงาน ESP-IDF ใช้ โดยทั่วไปแล้วจะไม่ถูกระบุว่าเป็น "การขัดจังหวะ" ในความหมายของไมโครคอนโทรลเลอร์แบบดั้งเดิม แต่มีจุดประสงค์ที่คล้ายกัน
  • ข้อจำกัดของ RAM ภายใน (IRAM): แม้ว่าคุณจะสามารถกำหนด ISR ได้จำนวนมาก แต่โปรดจำไว้ว่า IRAM ของ ESP32 นั้นมีจำกัด หากคุณวางฟังก์ชันมากเกินไปหรือฟังก์ชันขนาดใหญ่ใน IRAM โดยใช้ IRAM_ATTR พื้นที่ว่างของคุณอาจหมด คุณต้องจัดการพื้นที่นี้อย่างรอบคอบเพื่อให้แน่ใจว่า ISR ที่สำคัญและฟังก์ชันที่สำคัญอื่นๆ สามารถเหมาะสมได้

ดังนั้น แม้ว่าจะไม่มี "จำนวนชุดของการขัดจังหวะ" ที่เข้มงวดซึ่ง ESP32 สามารถรองรับได้ แต่จำนวนดังกล่าวก็ถูกกำหนดโดยพิน GPIO ที่มีอยู่ซึ่งสามารถตั้งค่าเป็นแหล่งการขัดจังหวะ กลไกการขัดจังหวะระดับซอฟต์แวร์อื่นๆ และความพร้อมใช้งานของ IRAM ช่องว่าง.



อ้างอิงถึงการใช้IRAM_ATTR คำหลักสำหรับรูทีนการบริการขัดจังหวะ (ISR) บน ESP32 โดยทั่วไปจะทำเพื่อให้แน่ใจว่า ISR ถูกจัดเก็บไว้ใน RAM ภายใน (IRAM) ของ ESP32 เพื่อให้สามารถดำเนินการได้อย่างรวดเร็วและไม่ได้รับผลกระทบจากการทำงานของแฟลชที่อาจเกิดขึ้น

อธิบายให้ชัดเจนกว่านี้:

เมื่อเกิดการขัดจังหวะระบบจะต้องตอบสนองทันที อย่างไรก็ตาม ใน ESP32 หากโค้ดของคุณดำเนินการจากหน่วยความจำแฟลช อาจเกิดข้อขัดแย้งได้หากการดำเนินการอื่น เช่น การเขียนหรืออ่านแฟลชเกิดขึ้นพร้อมกัน เมื่อ ISR ไม่ได้อยู่ใน IRAM และกำลังดำเนินการแฟลชเมื่อเกิดการขัดจังหวะ การตอบสนองของการขัดจังหวะอาจล่าช้า ซึ่งอาจนำไปสู่การพลาดการขัดจังหวะหรือปัญหาอื่นๆ

เพื่อป้องกันสิ่งนี้ ควรวางโค้ดสำคัญเช่น ISR ไว้ใน IRAM เพื่อให้แน่ใจว่าการตอบสนองของการขัดจังหวะจะเกิดขึ้นทันที และไม่ต้องรอการทำงานของแฟลชใดๆ

นี่เป็นตัวอย่างง่ายๆ ของวิธีที่คุณอาจใช้IRAM_ATTR ด้วย ISR:

 

void IRAM_ATTR handleInterrupt() {

    // Your ISR code here

}

   โดยการวางIRAM_ATTR ก่อนคำจำกัดความฟังก์ชัน ISR คุณกำลังสั่งให้ ESP-IDF (กรอบการพัฒนาอย่างเป็นทางการสำหรับ ESP32) เพื่อวางจัดการขัดจังหวะ ทำงานใน RAM ภายใน ช่วยให้มั่นใจถึงการดำเนินการที่รวดเร็วเมื่อเกิดการขัดจังหวะ

อย่างไรก็ตาม โปรดทราบว่า IRAM มีขนาดจำกัด ดังนั้นให้ใช้IRAM_ATTR อย่างรอบคอบและเฉพาะสำหรับฟังก์ชันที่ความเร็วในการดำเนินการเป็นสิ่งสำคัญ เช่น ISR หากคุณใช้งานมากเกินไป พื้นที่ IRAM ของคุณจะหมด ซึ่งจะส่งผลให้เกิดข้อผิดพลาดในการคอมไพล์

 

ตัวอย่าง 5 buttons

// Define 5 buttons
struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};

Button buttons[] = {
{18, 0, false},
{19, 0, false},
{20, 0, false},
{21, 0, false},
{22, 0, false}
};

// ISR for all buttons
void IRAM_ATTR handleInterrupt() {
for (int i = 0; i < 5; i++) {
if (digitalRead(buttons[i].PIN) == LOW) { // If button is pressed
buttons[i].numberKeyPresses++;
buttons[i].pressed = true;
}
}
}

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

// Attach interrupt for all buttons
for (int i = 0; i < 5; i++) {
pinMode(buttons[i].PIN, INPUT_PULLUP);
attachInterrupt(buttons[i].PIN, handleInterrupt, FALLING);
}
}

void loop() {
for (int i = 0; i < 5; i++) {
if (buttons[i].pressed) {
Serial.printf("Button %d has been pressed %u times\n", i + 1, buttons[i].numberKeyPresses);
buttons[i].pressed = false;
}
}
}


อธิบาย Code นี้:

  • อาร์เรย์ปุ่ม ใช้สำหรับเก็บข้อมูลทั้ง 5 ปุ่ม
  • รูทีนบริการขัดจังหวะครั้งเดียวจัดการขัดจังหวะ ใช้สำหรับปุ่มทั้งหมด ภายใน ISR นี้ จะมีการตรวจสอบแต่ละปุ่มเพื่อดูว่าปุ่มใดถูกกด
  • ในระหว่างติดตั้งแต่ละปุ่มจะถูกเตรียมใช้งานเป็นอินพุตพร้อมกับตัวต้านทานแบบดึงขึ้นภายใน และเชื่อมโยงกับรูทีนบริการขัดจังหวะ
  • Loop ฟังก์ชั่นตรวจสอบสถานะของปุ่มแต่ละปุ่ม และหากกด จะแสดงผลจำนวนครั้งที่กด

ตรวจสอบให้แน่ใจว่า Pin ที่คุณใช้ (18, 19, 20, 21, 22 ในตัวอย่างนี้) พร้อมใช้งานสำหรับจุดประสงค์นี้บนบอร์ด/โมดูล ESP32 เฉพาะของคุณ ถ้าไม่คุณสามารถเปลี่ยนเป็นหมุดอื่นที่เหมาะสมได้

 REF .

https://lastminuteengineers.com/handling-esp32-gpio-interrupts-tutorial/

 

การควบคุม RC servo ด้วย PID Control เป็นเรื่องที่น่าสนใจและเป็นประโยชน์สำหรับผู้ที่สนใจในการพัฒนาโปรเจ็คทางด้าน อิเล็กทรอนิกส์ และ โรบอทิกส์ สำหรับผู้เริ่มต้น มีข้อแนะนำดังนี้:

  1. เริ่มจากพื้นฐาน:

    • ความหมายของ RC servo และ ทำไมต้องใช้ PID Control
    • ศึกษาการทำงานของ RC servo รวมถึงการเชื่อมต่อไฟฟ้า
  2. ทฤษฎี PID Control:

    • ความหมายของ P, I, D และ วิธีการทำงานของแต่ละส่วน
    • การปรับ PID constants และวิธีการดูแล้วว่า constants ใดทำให้การควบคุมดีขึ้น
  3. การเขียนโค้ด:

    • ตัวอย่างโค้ดที่ง่ายสำหรับควบคุม RC servo ด้วย PID
    • อธิบายการทำงานของโค้ดในแต่ละส่วน
  4. ปฏิบัติการ:

    • การเชื่อมต่อ RC servo กับ ESP32 และอุปกรณ์อื่น ๆ
    • วิธีการปรับปรุงและประสิทธิภาพในการควบคุม
  5. การทดสอบและการปรับแต่ง:

    • วิธีการทดสอบระบบ เช่น การใช้ฟังก์ชันแตกต่าง ๆ และการปรับค่าต่าง ๆ
    • สาเหตุที่ PID ไม่ทำงานดีในบางเวลา และวิธีการแก้ไข
  6. เคสตัวอย่างและโปรเจคที่เกี่ยวข้อง:

    • ตัวอย่างโปรเจคที่สามารถประยุกต์ใช้ PID Control กับ RC servo
    • การพัฒนาและประยุกต์ใช้ในความเป็นจริง

       

Single RC Servo Control with PID 

//ESP32 CPU with Arduino IDE

#include <ESP32_Servo.h>
#include <PID_v1.h>

Servo throttleServo;
const int throttlePin = 14; // The digital pin where the servo is connected
const int sensorPin = 4; // The digital pin where the sensor is connected

const double setPointRPM = 6800.0; // Store setpoint RPM as double

double measuredRPM = 0;
volatile int pulseCount = 0;
unsigned long lastPulseTime = 0;

// PID parameters
double kp = 1.0; // Proportional constant
double ki = 0.1; // Integral constant
double kd = 0.1; // Derivative constant

double throttleOutput; // Declare throttleOutput here

double actualSetPointRPM = setPointRPM; // Create a double variable for setpoint RPM

PID pid(&measuredRPM, &throttleOutput, &actualSetPointRPM, kp, ki, kd, DIRECT);

const int minServoValue = 0; // Minimum servo value
const int maxServoValue = 180; // Maximum servo value

void setup() {
Serial.begin(115200);
throttleServo.attach(throttlePin);
pinMode(sensorPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(sensorPin), handleInterrupt, FALLING);

// Set the output limits for the PID controller
pid.SetOutputLimits(minServoValue, maxServoValue);

// Set the mode of the PID controller to AUTOMATIC
pid.SetMode(AUTOMATIC);
}

void loop() {
unsigned long currentTime = millis();
if (currentTime - lastPulseTime >= 1000) {
measuredRPM = pulseCount * (60000.0 / (currentTime - lastPulseTime));
lastPulseTime = currentTime;
pulseCount = 0;

pid.Compute(); // Compute the PID output

throttleServo.write(throttleOutput);

Serial.print("Measured RPM: ");
Serial.print(measuredRPM);
Serial.print("\tThrottle Output: ");
Serial.println(throttleOutput);
printPIDValues(kp, ki, kd); // Print PID gains for debugging
}
}

void handleInterrupt() {
pulseCount++;
}


void printPIDValues(double Kp, double Ki, double Kd) {
Serial.print("P: "); Serial.print(Kp);
Serial.print("\tI: "); Serial.print(Ki);
Serial.print("\tD: "); Serial.println(Kd);
}

 

 

 

Multi RC Servo Control with PID 

 

//ESP32 CPU with Arduino IDE

#include <ESP32_Servo.h>
#include <PID_v1.h>

struct ServoControl {
Servo servo;
int servoPin;
int sensorPin;
double setPointRPM;
double kp;
double ki;
double kd;

// Runtime variables
PID* pid;
double measuredRPM = 0;
volatile int pulseCount = 0;
unsigned long lastPulseTime = 0;
double throttleOutput;

double input;
double output;
double setpoint;

ServoControl(int sPin, int sensor, double sp, double p, double i, double d)
: servoPin(sPin), sensorPin(sensor), setPointRPM(sp), kp(p), ki(i), kd(d) {}
};

ServoControl controls[] = {
ServoControl(14, 4, 6800.0, 1.0, 0.1, 0.1),
ServoControl(15, 5, 6800.0, 1.2, 0.05, 0.2),
ServoControl(16, 17, 6800.0, 1.2, 0.05, 0.2)
};

void IRAM_ATTR handleInterrupt0() { controls[0].pulseCount++; }
void IRAM_ATTR handleInterrupt1() { controls[1].pulseCount++; }
void IRAM_ATTR handleInterrupt2() { controls[2].pulseCount++; }

typedef void (*ISR)(void);
ISR interruptHandlers[] = {
handleInterrupt0,
handleInterrupt1,
handleInterrupt2 // Fixed to correct interrupt handler
};

void initServo(ServoControl &control, int index) {
control.servo.attach(control.servoPin);
pinMode(control.sensorPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(control.sensorPin), interruptHandlers[index], FALLING);

control.input = control.measuredRPM;
control.output = control.throttleOutput;
control.setpoint = control.setPointRPM;

control.pid = new PID(&control.input, &control.output, &control.setpoint, control.kp, control.ki, control.kd, DIRECT);
control.pid->SetOutputLimits(0, 180);
control.pid->SetMode(AUTOMATIC);
}

void setup() {
Serial.begin(115200);
for (int i = 0; i < sizeof(controls)/sizeof(controls[0]); i++) {
initServo(controls[i], i);
}
}

void adjustPIDViaSerial() {
if (Serial.available()) {
String input = Serial.readStringUntil('\n');
int servoID;
char parameter;
double value;
sscanf(input.c_str(), "%d,%c,%lf", &servoID, &parameter, &value);

if(servoID >= 0 && servoID < sizeof(controls)/sizeof(controls[0])) {
switch (parameter) {
case 'P': controls[servoID].kp = value; break;
case 'I': controls[servoID].ki = value; break;
case 'D': controls[servoID].kd = value; break;
}

controls[servoID].pid->SetTunings(controls[servoID].kp, controls[servoID].ki, controls[servoID].kd);
Serial.println("PID values updated!");
} else {
Serial.println("Invalid servo ID!");
}
}
}

void loop() {
adjustPIDViaSerial();

for (int i = 0; i < sizeof(controls)/sizeof(controls[0]); i++) {
ServoControl& control = controls[i];
unsigned long currentTime = millis();
if (currentTime - control.lastPulseTime >= 1000) {
control.measuredRPM = control.pulseCount * (60000.0 / (currentTime - control.lastPulseTime));
control.lastPulseTime = currentTime;
control.pulseCount = 0;

control.input = control.measuredRPM;
control.pid->Compute();

control.servo.write(control.output);

Serial.print("Servo #");
Serial.print(i);
Serial.print(" - Measured RPM: ");
Serial.print(control.measuredRPM);
Serial.print("\tThrottle Output: ");
Serial.println(control.output);
}
}
}

 

ตัวควบคุม PID คืออะไร?

คำว่า PID ย่อมาจากอนุพันธ์อินทิกรัลตามสัดส่วน และ เป็นอุปกรณ์ประเภทหนึ่งที่ใช้ในการควบคุมตัวแปร กระบวนการต่างๆ เช่น ความดัน การไหล อุณหภูมิ และ ความเร็วในงานอุตสาหกรรม ในคอนโทรลเลอร์นี้ อุปกรณ์ป้อนกลับแบบลูปควบคุมใช้ เพื่อควบคุมตัวแปรกระบวนการทั้งหมด การควบคุมประเภทนี้ใช้เพื่อขับเคลื่อนระบบไปในทิศทางของตำแหน่งเป้าหมาย หรือ ระดับอื่น มีเกือบทุกที่สำหรับการควบคุมอุณหภูมิ และ ใช้ในกระบวนการทางวิทยาศาสตร์ ระบบอัตโนมัติ และ สารเคมี ในคอนโทรลเลอร์นี้ ฟีดแบ็คเป็นแบบลูปปิด (Close Loop) จะใช้เพื่อรักษาเอาท์พุตจริง

 

ระบบควบคุม แนวคิดพื้นฐานเบื้องหลังคอนโทรลเลอร์ PID คือ การอ่านเซ็นเซอร์ จากนั้นจึงคำนวณเอาต์พุต Actuator ที่ต้องการโดยคำนวณการตอบสนองตามสัดส่วน ปริพันธ์ และอนุพันธ์ แล้วรวมส่วนประกอบทั้งสามนี้เพื่อคำนวณเอาต์พุต ก่อนที่เราจะเริ่มต้นกำหนดพารามิเตอร์ของตัวควบคุม PID เราจะดูว่าระบบวงปิดคืออะไรและคำศัพท์บางคำที่เกี่ยวข้องกัน

กระบวนการ ออกแบบการควบคุม เริ่มต้นด้วยการกำหนดข้อกำหนดด้านประสิทธิภาพ ประสิทธิภาพของระบบควบคุมมักจะวัดโดยการใช้ฟังก์ชันขั้นตอนเป็นตัวแปรคำสั่ง set point จากนั้นจึงวัดการตอบสนองของตัวแปรกระบวนการ โดยทั่วไป การตอบสนองจะถูกหาปริมาณโดยการวัดคุณลักษณะของรูปคลื่นที่กำหนดไว้ เวลาที่เพิ่มขึ้นคือระยะเวลาที่ระบบใช้ในการเปลี่ยนจาก 10% เป็น 90% ของค่าสถานะคงตัวหรือค่าสุดท้าย เปอร์เซ็นต์โอเวอร์ชูตคือจำนวนที่ตัวแปรกระบวนการโอเวอร์ชูตค่าสุดท้าย ซึ่งแสดงเป็นเปอร์เซ็นต์ของค่าสุดท้าย เวลาในการชำระเป็นเวลาที่ตัวแปรกระบวนการต้องชำระภายในเปอร์เซ็นต์ที่กำหนด (โดยทั่วไปคือ 5%) ของค่าสุดท้าย ข้อผิดพลาด Steady-State คือความแตกต่างสุดท้ายระหว่างตัวแปรกระบวนการและจุดตั้งค่า

แหล่งความรู้ PID

https://www.teachmemicro.com/arduino-pid-control-tutorial/