กลับไปหน้ารวมไฟล์
rpm-meter-with-hall-effect-square-encoder-sensors-a3ef1b.md

โปรเจกต์นี้คือการใช้อุปกรณ์ตรวจจับสนามแม่เหล็ก (Hall effect sensors) มาหาทิศทางการหมุนและความเร็วรอบ (RPM) ของมอเตอร์กระแสตรง (DC motor) ... และที่นี่เราจัดหนัก วัดพร้อมกันทีละ 2 ตัวเลยจ้า!

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

บนเพลาของมอเตอร์แต่ละตัว พี่ได้ทำตัวเข้ารหัส (encoder) แบบชั่วคราวขึ้นมาโดยใช้แม่เหล็กนีโอไดเมียม

สำหรับการควบคุมทิศทางการหมุนของมอเตอร์แต่ละตัว พี่ใช้ Op-Amp (LM324N) ร่วมกับจอยสติ๊กและไดรเวอร์ (L293N และ L298N)

ส่วนการแปลงสัญญาณอนาล็อกจากเซนเซอร์แต่ละตัวให้เป็นดิจิตอล พี่ใช้ Op-Amp ทำงานในโหมดคอมพาเรเตอร์

และที่ขาดไม่ได้เลย พี่ใช้ชิป Schmitt Trigger Inverter (SN74LS14N) เพื่อกำจัดสัญญาณรบกวนชั่วขณะ (transient effect) จากเซนเซอร์แต่ละตัว ป้องกันการอ่านค่าผิดพลาด ทำให้ได้ค่าความเร็วรอบและทิศทางการหมุนของมอเตอร์แต่ละตัวที่แม่นยำพอสมควร

ไมโครคอนโทรลเลอร์ที่ใช้คือ ATmega32u4 (Beatle BadUSB) ซึ่งทำงานเหมือน Arduino Leonardo แต่โค้ดนี้สามารถนำไปใช้กับ Arduino รุ่นอื่นๆ ส่วนใหญ่ได้

จอแสดงผลเป็น ST7735 ขนาด 128x128 พิกเซล ใช้บัส SPI ใช้ง่ายมาก

ลงลึกกันหน่อยดีกว่า (Technical Deep-Dive)

โปรเจกต์ "Mag-Tach" นี้คือคลาสเรียนขั้นสูงเรื่อง การตรวจจับแม่เหล็กแบบละเอียดยิบ และ ความสมบูรณ์ของสัญญาณ ในขณะที่มาตรวัด RPM ทั่วไปใช้เซนเซอร์อินฟราเรดแบบจับพัลส์เดียว โปรเจกต์นี้ใช้สถาปัตยกรรม Quadrature Hall Effect แบบสองช่องสัญญาณ โดยการจับการเปลี่ยนแปลงของฟลักซ์แม่เหล็กจากแม่เหล็กนีโอไดเมียมผ่านเซนเซอร์เชิงเส้น และปรับแต่งสัญญาณด้วย Schmitt trigger ทำให้ Mag-Tach สามารถดึงข้อมูลได้ทั้งขนาด (RPM) และทิศทาง (+/-) ของมอเตอร์ DC สองตัวที่ทำงานพร้อมกันได้ ให้ข้อมูลเทเลเมทรีคุณภาพสูงสำหรับระบบหุ่นยนต์หรือระบบขับเคลื่อน

  • การวิเคราะห์การเข้ารหัสแบบ Quadrature:
    • การเลื่อนเฟส 90 องศา: เซนเซอร์ Hall effect (KY-035) สองตัวจะถูกวางห่างกันเป็นระยะเชิงมุม 90 องศาเทียบกับขั้วแม่เหล็ก ดังที่เห็นในภาพแรก สิ่งนี้สร้างคลื่นสี่เหลี่ยมสองช่อง (ช่อง A และ B) ที่เลื่อนเฟสกัน
    • ตรรกะหาทิศทางเวกเตอร์: โดยการตรวจสอบว่าช่องสัญญาณไหนเกิดขอบขาลง (FALLING edge) ก่อน ATmega32U4 จะสามารถระบุทิศทางการหมุนได้ ถ้า A นำหน้า B มอเตอร์จะหมุนตามเข็ม (CW, +) ถ้า B นำหน้า A มอเตอร์จะหมุนทวนเข็ม (CCW, -)
  • การปรับแต่งสัญญาณและกำจัดสัญญาณรบกวน:
    • Schmitt Trigger (SN74LS14N): เซนเซอร์ Hall effect มักจะสร้าง "การกระเพื่อม" ชั่วคราว (jitter) ที่ขอบเขตของสนามแม่เหล็ก โปรเจกต์นี้ใช้ Schmitt trigger ที่มี ฮิสเทอรีซิส ในการแปลงสัญญาณอนาล็อกให้เป็นดิจิตอล สิ่งนี้ทำให้พินอินเตอร์รัปต์เห็นเฉพาะคลื่นสี่เหลี่ยมที่ "สะอาด" เท่านั้น กำจัดสัญญาณเท็จที่เกิดจากสัญญาณรบกวนแม่เหล็ก
    • การเปรียบเทียบด้วย Op-Amp: LM324N ที่เห็นในภาพที่สอง ทำหน้าที่เป็นคอมพาเรเตอร์ความเร็วสูง แปลงแรงดันอนาล็อกเชิงเส้นจากเซนเซอร์ฮอลล์ให้เป็นการเปลี่ยนแปลงระดับลอจิกที่ Schmitt trigger ต้องการ
  • การหาความถี่ด้วย Timer1:
    • การวัดแบบใช้คาบเวลา: แทนที่จะนับพัลส์ต่อวินาที (ซึ่งไม่แม่นยำที่ RPM ต่ำ) Mag-Tach วัด เวลาระหว่างพัลส์ โดยใช้ไลบรารี TimerOne ด้วยการคำนวณส่วนกลับของคาบเวลา ระบบจะได้การอัปเดตค่า RPM ที่เกือบจะทันที แม้ในช่วงสตาร์ตช้าหรือการชะลอตัวอย่างรวดเร็ว

วิศวกรรมและการนำไปใช้

  • สถาปัตยกรรมแบบ Interrupt-Driven:

    • การวัดความเร็วรอบสูงๆ ต้องใช้ตรรกะแบบไม่บล็อก CPU นะน้อง โปรเจคนี้ใช้ attachInterrupt บนขา 2 กับ 3 เพื่อจัดการพัลส์จากมอเตอร์ A/B วิธีนี้ทำให้ลูปหลักของ CPU ไปโฟกัสที่การวาดกราฟฟิคบนหน้าจอ ST7735 TFT ได้สบายๆ ส่วนเรื่องจับเวลาที่แม่นยำระดับมิลลิวินาทีปล่อยให้ฮาร์ดแวร์จัดการในแบ็กกราวด์ไปเลย
  • การวิเคราะห์ข้อมูลด้วยภาพ (ST7735):

    • หน้าจอสีขนาด 128x128 ถูกใช้เพื่อสร้าง กราฟแท่งสดๆ เหมือนในรูปสุดท้ายเลย โค้ดจะแมปค่า rpm ไปเป็นแกน Y บนหน้าจอ ทำให้เห็นภาพการทำงานของมอเตอร์แบบอนาล็อกแบบเรียลไทม์ไปพร้อมๆ กับตัวเลข RPM แบบดิจิตอล ดูแล้วเข้าใจง่ายดี
  • การป้องกันข้อมูลแบบอะตอมมิก:

    • เพื่อป้องกัน "Race Conditions" เวลาที่ลูปหลักกำลังอ่านค่าตัวแปรหลายไบต์ แต่ Interrupt กำลังอัปเดตค่ามันอยู่ โค้ดเลยใช้บล็อก ATOMIC() ตัวนี้จะล็อคไม่ให้ Interrupt มารบกวนชั่วคราวตอนคำนวณค่า RPM พอเสร็จแล้วค่อยปล่อย รับรองข้อมูลไม่เพี้ยนแน่นอน

สรุป

Mag-Tach นี่ถือเป็นก้าวสำคัญในวงการ Propulsion Telemetry เลยนะเว้ย การที่เราเข้าใจการผสมผสาน Quadrature Phase Logic, Hysteresis-based Debouncing และ Atomic Data Management อย่างลงตัว จะทำให้เราสร้างเอนโคเดอร์ระดับอุตสาหกรรมสำหรับหุ่นยนต์ที่งานต้องเป๊ะๆ ได้เลย


Vector Precision: การไขปริศนาการหมุนผ่านแม่เหล็กแบบ Quadrature

ในวิดีโอต่อไปนี้จะเห็นการทำงานและการทดสอบโค้ด:

/*
RPM Meter Direction
Use Square Encoder with Hall effect sensors
By DrakerDG (c)
https://www.youtube.com/user/DrakerDG
*/
#include <SPI.h>
#include <TFT_ST7735.h>
#include <SimplyAtomic.h>
#include <TimerOne.h>
#define	 GREEN 0x07E0
#define YELLOW 0x07FF
#define DC A0 //9
#define RS A1 //10
#define CS A2 //11
// Sensor pins
const byte PinX[4] = {2, 3, 10, 11};
// Limit of microseconds
const long uSeg = 100000;
// Left position RPM labels
const byte x1 = 75;
// Pulse timer counters
volatile unsigned long pwc[2];
// PWM periods
volatile unsigned long pwm[2];
// RPM values
unsigned long rpm[2];
// States of the second sensors
volatile bool Sen[2];
// Count variable printing millis
unsigned long prT = 0;
TFT_ST7735 tft = TFT_ST7735(CS, DC, RS);
void CountSA(void);
void CountSB(void);
void RPMc(void);
void Draw_Table(void);
void Print_Data(void);
void setup(){
 Serial.begin(9600);
 tft.begin();
 tft.setRotation(1);
 tft.clearScreen();
 tft.setTextWrap(true);
 tft.setTextColor(YELLOW, BLACK);
 tft.setCursor(0, 0);
 Draw_Table();
 for (byte i=0; i<4; i++){
   pinMode(PinX[i], INPUT);
   }
 for (byte i=0; i<2; i++){
   pwc[i] = uSeg;
   pwm[i] = uSeg;
   rpm[i] = 0;
 }
 // Interrupt to count period time
 Timer1.initialize(100);
 Timer1.attachInterrupt(RPMc);
 // Interrupt of Sensor 1 of Motor A
 attachInterrupt(digitalPinToInterrupt(PinX[0]), CountSA, FALLING);
 // Interrupt of Sensor 1 of Motor B
 attachInterrupt(digitalPinToInterrupt(PinX[1]), CountSB, FALLING);
}
void loop(){
 Print_Data();
}
void CountSA(){
 // 2nd sensor value
 Sen[0] = 1 & (PINB >> 7); 
 pwm[0] = pwc[0]; // Save the period
 pwc[0] = 0; // Reset the timer
}
void CountSB(){
 // 2nd sensor value
 Sen[1] = 1 & (PINB >> 6);
 pwm[1] = pwc[1]; // Save the period
 pwc[1] = 0; // Reset the timer
}
void RPMc(){
 for (byte i=0; i<2; i++){
   // Increase the time counter
   pwc[i]++;
   if (pwc[i] > (uSeg)){
     // Limit the timer & period
     pwc[i] = uSeg;
     pwm[i] = uSeg;
   }
 }  
}
void Draw_Table(){
 // Code to draw the table on screen
 tft.drawFastVLine(22, 0, 128, WHITE);
 for ( int i=0; i<11; i+=1 ){
   tft.drawFastHLine( 20, 5+i*12, 4, WHITE);
   if (!(i&1)){
     tft.setCursor( 0, i*12 + 2);
     tft.print((10.0-i)*0.5, 1);
     }
   }
 tft.drawFastHLine( 20, 125, 128, WHITE);
 tft.setTextSize(1);
 tft.setCursor(x1, 10);
 tft.print("Motor A");
 tft.setCursor(x1+30, 45);
 tft.print("RPM");
 tft.setCursor(x1, 70);
 tft.print("Motor B");
 tft.setCursor(x1+30, 105);
 tft.print("RPM");
 tft.setTextSize(2);
}
void Print_Data(){
 unsigned long nwT = millis();
 // Calculations and prints every 10ms
if ((nwT - prT) > 10){
   prT = nwT;
   char sRPM[10];
   for (byte i=0; i<2; i++){
     //RPM
     tft.setCursor(x1, 25+60*i);
     // Protects math calculation
     ATOMIC()
     {
       // Detect Rotation Decrease
       if (pwc[i]>(pwm[i]*2)){
         pwm[i] *= 2;
         pwm[i] = constrain(pwm[i], 0, uSeg);
         pwc[i] = pwc[i]*2;
       }
       /* detects or not the
       rotation of the motors */
       if (pwm[i] < uSeg) rpm[i] = 6*uSeg/pwm[i]; // Detects rotation
       else if ((rpm[i] > 0)&&(pwm[i] == uSeg)) rpm[i] = int(rpm[i]/2); // No rotatiom
       // Limits the value of RPMs
       rpm[i] = constrain(rpm[i], 0, 9999);
     }
     dtostrf(rpm[i], 4, 0, sRPM);
     // Print the RPMs
     tft.print(sRPM);
     int valX = rpm[i]*120/500;
     // Prints the RPM bars
     tft.fillRect(30+15*i, 0, 10, 125 - valX, BLACK);
     tft.fillRect(30+15*i, 125 - valX, 10, valX, GREEN);
     tft.setCursor(x1-15, 25+60*i);
     /* Determines the direction
     of rotation of the motors.
     Prints + if it turns CW and
     - if it turns CCW */
     if ((rpm[i] == 0)&&(pwm[i] == uSeg)) tft.print(" ");
     else if (Sen[i]) tft.print("-");
     else tft.print("+");
     }
   }
}
วิดีโอทดสอบการทำงาน ดูให้เคลียร์นะน้อง
เซ็นเซอร์ฮอลล์เอฟเฟกต์สองตัววางในตำแหน่งควอดราจอร์ (Quadrature) ดูดีๆ วางผิดทิศงานเข้า
โอแพมสี่ช่อง (LM324N) ใช้แปลงสัญญาณอนาล็อกจากเซ็นเซอร์ให้เป็นดิจิตอล ตัวช่วยสำคัญ
โอแพมสี่ช่อง (LM324N) อีกตัว คราวนี้ไว้ควบคุมการหมุนของมอเตอร์ ห้ามช็อตนะตัวนี้
ไดรเวอร์ควบคุมมอเตอร์สองตัว (L293N) ตัวหลักของงานนี้เลย สู้งานนะน้อง
หน้าจอแสดงผลทิศทางการหมุน (+/-) และค่า RPM ของมอเตอร์ DC สองตัว จัดไปวัยรุ่น!

ข้อมูล Frontmatter ดั้งเดิม

apps:
  - "1x Arduino IDE"
author: "drakerdg"
category: ""
components:
  - "4x KY-035"
  - "1x ATmega32U4"
  - "1x DSO138"
  - "1x Dual H-Bridge motor drivers L293D"
  - "1x ST7735"
  - "1x SN74LS14N"
  - "4x Neodymium magnets"
  - "2x General Purpose Quad Op-Amp"
description: "ใช้เซนเซอร์ KY-035 แบบ Quadrature งานง่ายแต่หล่อ วัดได้ทั้งทิศทางการหมุนและค่า RPM ของมอเตอร์แบบจัดเต็ม!"
difficulty: "Intermediate"
documentationLinks: []
downloadableFiles:
  - "https://github.com/DrakerDG/RPM_Meter_Direction_V3"
  - "https://github.com/DrakerDG/RPM_Meter_Direction_V3"
encryptedPayload: "U2FsdGVkX1/iR3hJ/9dWDESayDLQcsSHVPjtaUyqXkYUHNhz1kcIakwe533cgMCghkv2MdF3F4QCaXsW2rHcra14/FD/c14/rjWpuQ4WaLw="
heroImage: "https://cdn.jsdelivr.net/gh/bigboxthailand/arduino-assets@main/images/projects/rpm-meter-with-hall-effect-square-encoder-sensors-a3ef1b_cover.gif"
lang: "en"
likes: 3
passwordHash: "e2427876bc9462f5ffed5723cfbaedc7e83504d799a8a8c857be232216ecfcb9"
price: 699
seoDescription: "Measure RPM and rotation direction using KY-035 Hall Effect Square Encoder Sensors for your Arduino projects."
tags:
  - "toys"
  - "monitoring"
  - "robots"
title: "วัดรอบมอเตอร์ตึงๆ ด้วย Hall Effect Sensor แบบ Square Encoder"
tools: []
videoLinks:
  - "https://www.youtube.com/embed/lOM5GylRBGY"
views: 5783