ดิว.นินจา

ดิว.นินจา

Monday, January 1, 2018

การใช้อินเตอร์รัพท์บน ESP32 อ่านและนับค่าจากเอนโคเดอร์

ในงานควบคุมการเคลื่อนที่โดยใช้มอเตอร์ เช่นเครื่องซีเอ็นซีและหุ่นยนต์ จะนิยมใช้เอนโคเดอร์แบบส่วนเพิ่ม (incremental encoder) ในการอ่านค่าตำแหน่งของเพลา มีข้อดีคือมีจำนวนสายสัญญาณน้อยและสามารถสะสมค่าได้ แต่จำเป็นต้องมีภาครับที่สามารถแปลงสัญญาณและนับจำนวนพัลซ์ที่สัมพันธ์กับค่าตำแหน่ง ในไมโครคอนโทรลเลอร์ที่ออกแบบมาสำหรับงานควบคุมมอเตอร์ เช่น PIC ตระกูล MC จาก บ.ไมโครชิพ จะมีโมดูล QEI (Quadrature Encoder Interface) ที่เป็นฮาร์ดแวร์เฉพาะสำหรับงานดังกล่าว และมีวงจรเสริมเช่นตัวกรองดิจิทัล ทำให้สามารถอ่านค่าได้อย่างแม่นยำและรวดเร็ว อย่างไรก็ตาม สำหรับงานที่ไม่ต้องการสมรรถนะสูงมาก เช่นการทดลองในชั้นเรียน เราสามารถใช้ขาอินพุตอินเตอร์รัพท์ภายนอกช่วยในการอ่านและนับค่าจากเอนโคเดอร์แบบส่วนเพิ่มได้ ในบทความนี้จะกล่าวถึงการพัฒนาส่วนเชื่อมต่อเอนโคเดอร์บน ESP32

รูปที่ 1 แสดงโครงสร้างทางกลและไฟฟ้าของส่วนกำเนิดสัญญาณจากจานเอนโคเดอร์แบบส่วนเพิ่ม อธิบายหลักการโดยสังเขปได้คือ เมื่อมอเตอร์หมุน จะทำให้จานที่มีช่องแสงหมุนตัดแสงที่ส่งจาก LED ตัวส่งไปยังภาครับ ทำให้เกิดพัลส์ 2 ช่อง คือ A และ B ที่ต่างเฟสกัน 90 องศา เรียกว่า quadrature signals โดยเราสามารถทราบทั้งตำแหน่งและทิศทางได้จากสัญญาณ A และ B นี้ ตัวอย่างเช่นเมื่อ A นำหน้า B แสดงว่ามอเตอร์กำลังหมุนตามเข็มนาฬิกา หรือ B นำหน้า A เมื่อหมุนทวนเข็ม เป็นต้น

รูปที่ 1 ส่วนประกอบของเอนโคเดอร์แบบส่วนเพิ่ม

รูปที่ 2 แสดงตัวอย่างสัญญาณที่ได้จากจานเอนโคเดอร์ สำหรับสัญญาณ Z เรียกว่าตัวชี้ (index pulse) โดยจะสร้าง 1 พัลส์จากการหมุน 1 รอบ ในบทความนี้จะไม่ใช้งานสัญญาณ Z

รูปที่ 2 สัญญาณที่กำเนิดจากจานเอนโคเดอร์แบบส่วนเพิ่ม

ในเอนโคเดอร์ที่ใช้ในงานอุตสาหกรรม นอกจากจะส่งสัญญาณเช่นในรูปที่ 2 แล้วยังมักจะส่งคู่สัญญาณที่ต่างเฟส 180 องศามาพร้อมกันด้วย โดยทางภาครับสามารถใช้วงจรขยายผลต่างเพื่อขจัดสัญญาณโหมดร่วม (common mode) ที่เป็นสัญญาณรบกวนออกไปได้

ฮาร์ดแวร์ที่ใช้

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

เนื่องจากไม่ต้องการวงจรเพิ่มเติมที่ซับซ้อน จึงใช้แผ่นวงจรพิมพ์อเนกประสงค์เพื่อต่อบอร์ด LOLIN32 กับอุปกรณ์ภายนอก (รูปที่ 3)

รูปที่ 3 ด้านบนของบอร์ดเชื่อมต่อสัญญาณ

โดยใช้สายบัดกรีเชื่อมต่อขาสัญญาณดังแสดงในรูปที่ 4 บอร์ดนี้สร้างเผื่อไว้ในอนาคตให้รับสัญญาณจากเอนโคเดอร์ได้ 2 ตัว แต่ในบทความนี้จะทดสอบเพียงตัวเดียว โดยใช้ขา 34, 35 ต่อกับสัญญาณเอนโคเดอร์ A, B ตามลำดับ ที่เหลือเป็นคอนเนกเตอร์สำหรับ OLED และสร้างเผื่อสำหรับ PWM ขับมอเตอร์ (ยังไม่ใช้ในการทดลองนี้)

รูปที่ 4 ด้านล่างของบอร์ดเชื่อมต่อสัญญาณ

เมื่อประกบบอร์ด LOLIN32 เข้ากับบอร์ดที่สร้างขึ้นนี้จะเป็นดังรูปที่ 5 ความไม่สมมาตรของคอนเนกเตอร์ บนบอร์ด LOLIN32 ทำให้ไม่ต้องกังวลว่าจะเสียบผิดด้าน

รูปที่ 5 การใช้งานบอร์ด LOLIN32 กับบอร์ดเชื่อมต่อสัญญาณ

สำหรับการทดสอบในบทความนี้จะเน้นเรื่องการรับสัญญาณจากเอนโคเดอร์และนับค่าได้อย่างถูกต้อง การใช้เอนโคเดอร์ที่ติดกับมอเตอร์จะทำให้ตรวจสอบได้ยาก ดังนั้นจึงเลือกใช้อุปกรณ์ handwheel ที่ใช้ในเครื่องซีเอ็นซ๊ โดยเมื่อหมุน 1 คลิก จะกำเนิดสัญญาณ A และ B 1 ไซเคิล รูปที่ 6 แสดงการใช้งาน handwheel กับ LOLIN32 ผ่านบอร์ดเชื่อมต่อสัญญาณ

รูปที่ 6 การใช้ handwheel เป็นตัวกำเนิดสัญญาณ quadrature signal

สัญญาณที่ได้จาก handwheel นี้จะเป็นสัญญาณ quadrature signals เหมือนกับเอนโคเดอร์ที่ติดตั้งกับมอเตอร์ รูปที่ 7 แสดงเฟสของสัญญาณ A และ B เทียบกับทิศทางการหมุน คือเมื่อหมุน handwheel ตามเข็มนาฬิกา เฟสของสัญญาณ A จะนำหน้า B 90 องศา หากหมุนทวนเข็ม B จะนำหน้า A 90 องศา หากผู้อ่านทดลองกับเอนโคเดอร์ที่กำเนิดสัญญาณสลับกันจากนี้ (คือ A นำหน้า B = หมุนทวนเข็ม) โปรแกรมในบทความนี้ก็ยังสามารถใช้งานได้เพียงแต่ทิศทางในการนับจะกลับกันเท่านั้น

รูปที่ 7 เฟสของสัญญาณ quadrature เทียบกับทิศทางการหมุน

อินเตอร์รัพท์ภายนอกบน ESP32

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

การใช้งานก็เพียงแต่ใช้ฟังก์ชัน attachInterrupt() เพื่อกำหนดขา GPIO ฟังก์ชันที่ต้องการเรียกทำงาน และเหตุการณ์ที่ทำให้เกิดอินเทอร์รัพท์ (ตัวเลือกคือ LOW, HIGH, CHANGE, RISING, FALLING) ตัวอย่างเช่น


attachInterrupt(34, funcA, RISING);

จะอินเทอร์รัพท์เมื่อลอจิกที่ขา GPIO34 เปลี่ยนจาก LOW เป็น HIGH และโปรแกรมจะข้ามไปทำงานที่ฟังก์ชัน funcA()

การนับค่าแบบพื้นฐาน

วิธีการที่ง่ายที่สุดคือเพิ่มหรือลดค่าของตัวนับ 1 หน่วยสำหรับทุก 1 ไซเคิลของ A หรือ B โดยทิศทางการนับขึ้นกับเฟสของสัญญาณ A, B โปรแกรมที่ใช้คือ enc1_int.ino ซึ่งรวมอยู่ใน ZIP file ท้ายบทความนี้

เริ่มจากการกำหนดขาที่ใช้สำหรับสัญญาณ A, B


const int ENC1A = 34;
const int ENC1B = 35;

ในฟังก์ชัน setup() ตั้งค่าให้ทั้งสองขานี้เป็นอินพุต


pinMode(ENC1A, INPUT);
pinMode(ENC1B, INPUT);

และกำเนิดอินเตอร์รัพท์ทุกขอบขาขึ้นของสัญญาณ A


attachInterrupt(ENC1A, fn1a, RISING);

ดังนั้นทุกขอบขาขึ้นของ A ตัวประมวลผลจะข้ามไปทำงานตามฟังก์ชันอินเตอร์รัพท์ fn1a() ซึ่งเขียนไว้ดังนี้


void fn1a(void)  
{
  if(digitalRead(ENC1B)) encval--;
  else encval++;
}

โดยจะตรวจสอบระดับสัญญาณ B ว่าเป็น LOW หรือ HIGH ถ้าเป็น LOW จะเพิ่มค่าตัวแปร encval หรือหากเป็น HIGH จะลดค่า encval สอดคล้องกับเฟสของสัญญาณตามรูปที่ 7 ดังนั้นตำแหน่งของการหมุนจะสะสมอยู่ในตัวแปร encval

โปรแกรม enc1_int.ino จะมีฟังก์ชันรับคำสั่ง cmdInt() ที่มีเพียงคำสั่งเดียว คือ setenc สำหรับเขียนค่าลงในตัวแปร encval ตัวอย่างเช่น setenc=0 จะเคลียร์ค่าใน encval เป็นศูนย์

ค่าของ encval จะถูกส่งออกทางพอร์ตอนุกรมเมื่อมีการเปลี่ยนแปลง คือมีการหมุน handwheel ทำให้สามารถอ่านได้โดยใช้ Serial Monitor บน Arduino IDE ผลจากการรันโปรแกรมและทดลองหมุน handwheel ตามเข็มและทวนเข็มเป็นดังในรูปที่ 8 โดยเริ่มจาก setenc=0 และหมุนหน้าปัทม์ที่ละคลิกตามเข็มและทวนเข็ม จะเป็นว่าค่าเปลี่ยนไป 1 หน่วยทุก 1 คลิก โดยทิศทางการนับขึ้นกับว่าหมุนตามหรือทวนเข็ม

รูปที่ 8 ผลจากการรัน enc1_int.ino และหมุน handwheel ทีละคลิก

การนับค่าแบบความละเอียด 4 เท่า

ในกรณีที่ต้องการความละเอียดในการนับค่าสูงสุด เราสามารถตรวจสอบทุกๆ การเปลี่ยนแปลงของสัญญาณ A และ B ซึ่งจะเกิดขึ้น 4 ครั้งใน 1 ไซเคิลของสัญญาณ ดังนั้นความละเอียดในการนับจะเพิ่มขึ้น 4 เท่า

โครงสร้างหลักของโปรแกรม enc1x4_int.ino จะคล้ายกับ enc1_int.ino เพียงแต่มีการกำหนดตัวแปรแบบ BOOL ที่มีค่าเป็น 0 หรือ 1 เพื่อเก็บค่าสถานะของสัญญาณ A, B


// status of ENC1A and ENC1B pins
bool fn1a_enc1a, fn1a_enc1b, fn1b_enc1a, fn1b_enc1b;

และตรวจสอบอินเตอร์รัพท์ทุกการเปลี่ยนแปลงของขาสัญญาณ A และ B


  attachInterrupt(ENC1A, fn1a, CHANGE);
  attachInterrupt(ENC1B, fn1b, CHANGE);  

โดยอินเตอร์รัพท์ ฟังก์ชัน เขียนได้ดังนี้


// interrupt routine when channel A changes
void fn1a(void)  
{
  fn1a_enc1a = digitalRead(ENC1A);
  fn1a_enc1b = digitalRead(ENC1B);
  if (fn1a_enc1a ^ fn1a_enc1b) encval++;
  else encval--;
}
// interrupt routine when channel B changes
void fn1b(void)  
{
  fn1b_enc1a = digitalRead(ENC1A);
  fn1b_enc1b = digitalRead(ENC1B);
  if (fn1b_enc1a ^ fn1b_enc1b) encval--;
  else encval++;  
}

ตัวดำเนินการ ^ คือ EXCLUSIVE OR ที่จะมีค่าจริงต่อเมื่อสัญญาณ A และ B แตกต่างกัน ขอทิ้งให้ผู้อ่านเป็นอาหารสมองในการตรวจสอบว่าเงื่อนไขในฟังก์ชันเหล่านี้สอดคล้องกับลักษณะของสัญญาณในรูปที่ 7

เมื่อทดสอบโปรแกรม enc1x4_int.ino กับ handwheel จะได้ผลใน Serial Monitor ดังแสดงในรูปที่ 9 จะเห็นว่าค่าเปลี่ยน 4 หน่วยในแต่ละคลิกที่หมุน กล่าวคือความละเอียดที่ได้เป็น 4 เท่าตามต้องการ

รูปที่ 9 ผลจากการรัน enc1x4_int.ino และหมุน handwheel ทีละคลิก

ต่อไปเป็นการทดสอบความถูกต้องแม่นยำในการอ่านและเก็บสะสมค่านับ เริ่มโดยหมุนหน้าปัทม์ของ handwheel ให้เลข 0 ตรงกับขีดดังในรูปที่ 10

รูปที่ 10 ตั้งค่าตำแหน่งศูนย์บน handwheel

พิมพ์คำสั่ง setenc=0 เพื่อรีเซ็ตค่านับเป็นศูนย์ ดังนั้นตำแหน่งนี้ของ handwheel จะให้ค่า encval = 0 หลังจากนั้นหมุนหน้าปัทม์ไปมาด้วยความเร็วสักพักหนึ่ง ก่อนจะหมุนกลับมาที่ตำแหน่งเดิม พบว่าตัวนับยังอ่านค่าได้เป็นศูนย์ แสดงว่าการอ่านและเก็บสะสมค่าไม่มีข้อผิดพลาด รูปที่ 11 แสดงตัวอย่างจากการทดลองนี้

รูปที่ 11 แสดงการสะสมค่านับที่ยังคงตำแหน่ง 0 ได้อย่างถูกต้อง

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

มาถึงจุดนี้เราได้แสดงว่าการอ่านและนับค่าจากเอนโคเดอร์โดยใช้อินเตอร์รัพท์สามารถทำงานได้ตามต้องการ ผู้อ่านสามารถเพิ่มเติมฟังก์ชันที่ต้องการได้ตามความประสงค์ ตัวอย่างเช่นหากต้องการแสดงค่าบน OLED 128 x 64 สามารถทำได้ดังแสดงในรูปที่ 12 โดยโปรแกรม enc1x4_int_oled.ino ที่แสดงค่าตำแหน่งจากตัวแปร encval และทิศทางการหมุน

รูปที่ 12 การแสดงค่านับและทิศทางการหมุนบน OLED

สรุป

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

รวมโปรแกรมที่ใช้ในบทความนี้ esp32_encint.zip

No comments:

Post a Comment

แนะนำหนังสือ “ตัวควบคุมป้อนกลับบนอินเทอร์เน็ตโดย ESP8266”

ปัจจุบันเมื่อกล่าวถึงอุปกรณ์ IoT (Internet of Things) คงมีน้อยคนที่จะไม่รู้จัก ในยุคที่การเข้าถึงอินเทอร์เน็ตเป็นกิจวัตรประจำวันของมนุษย์เ...