هدف ما در اینجا این است که شما با خواندن این مقاله بتوانید ماژول روتاری انکودر را با استفاده از آردوینو راه اندازی کنید. پس تا انتهای مقاله همراه ما باشید.
روتاری انکودر یا انکودر چرخشی نوعی حسگر موقعیت است که موقعیت زاویهای محور خود را به سیگنال خروجی تبدیل میکند و مشخص میکند این محور چه مقدار و در چه جهتی چرخیده است. انکودرها، در کاربردهای بسیاری مانند رباتیک، دستگاه CNC و چاپگرها بهکار برده میشوند. دلیل آن هم استحکام و قابلیت کنترل دیجیتال آنها است.
به طور کلی، دو نوع روتاری انکودر وجود دارد: مطلق و افزایشی. انکودر مطلق موقعیت دقیق محور را بر حسب زاویه به ما میدهد، در حالی که انکودر افزایشی میزان جابه جایی محور را نسبت به مبدا نشان میدهد. ما در این مقاله به انکودر نوع افزایشی میپردازیم.
روتاری انکودر چه تفاوتی با پتاسیومتر دارد؟
روتاری انکودر نمونه دیجیتالی و مدرن پتانسیومتر است و نسبت به آن قابلیت بیشتری دارد. انکودرها میتوانند به طور کامل و نامحدود بچرخند، در حالیکه پتانسیومترها تنها میتوانند 3/4 محیط دایره چرخانده شوند.
اگر بخواهید موقعیت دقیق محور را تشخیص دهید، بهترین گزینه، پتاسیومتر است. درحالیکه اگر بخواهید مقدار جابهجایی نسبت به یک موقعیت را به جای موقعیت دقیق چرخش بدانید، باید از روتاری انکودر استفاده کنید.
نحوه کار روتاری انکودر
اما انکودر چرخشی چگونه کار میکند؟ در اینجا باهم نحوه کار آن را بررسی میکنیم.
همانطور که در شکل زیر میبینید داخل یک انکودر یک دیسک شیاردار وجود دارد که به پایه زمین مشترک C و دو پایه A و B متصل شده است.
زمانیکه شما محور را بچرخانید، نقاط A و B، طبق ترتیب خاصی و با توجه به جهت چرخش محور، در تماس با پایه C زمین مشترک قرار میگیرند. پایه A و B پس از تماس با زمین مشترک، سیگنالهایی با اختلاف فاز °90 تولید میکنند. زیرا موقعیت این دو پایه به گونهای در نظر گرفته شده است که یک پایه قبل از دیگری در تماس با زمین مشترک قرار میگیرد. به این دو سیگنال خروجی تربیعی گفته میشود.
اگر محور را ساعتگرد بچرخانید، ابتدا پایه A و سپس پایه B وصل میشود. اما اگر محور را پادساعتگرد بچرخانید، ابتدا پایه B و پس از آن پایه A متصل خواهد شد.
با بررسی اتصال و عدم اتصال هر کدام از پایهها به زمین مشترک و تغییرات سیگنالها، میتوانیم جهت چرخش محور را مشخص کنیم. یعنی با تحت نظر داشتن حالت B، زمانیکه حالت A در حال تغییر است، میتوانید این کار را انجام دهید.
وقتی A تغییر حالت میدهد:
- اگر B != A باشد، محور ساعتگرد چرخانده شده است.
- اگر B = A باشد، محور پادساعتگرد چرخانده شده است.
پایههای ماژول روتاری انکودر
حالا بیایید، پایههای روتاری انکودر را با هم بررسی کنیم. پایههای روتاری انکودر به شکل زیر است:
GND به زمین وصل میشود.
VCC ولتاژ تغذیه مثبت است و معمولا 3.3V یا 5V میباشد.
SW خروجی سوییچ فشاری active low است. با فشار دادن محور، ولتاژ این پایه LOW میشود.
CLK (Output A) پالس خروجی اولیه برای تعیین میزان چرخش است. هنگام چرخش محور در هر جهت و عبور دیسک از مکان A، خروجی CLK یک چرخه HIGH و سپس LOW شدن را طی میکند.
DT (Output B) مانند خروجی CLK است، اما فاز آن نسبت به CLK، نود درجه عقبتر است. از این خروجی میتوان برای تعیین جهت چرخش محور استفاده کرد.
سیمکشی – راه اندازی ماژول روتاری انکودر با آردوینو
الان زمان آن است که راه اندازی ماژول روتاری انکودر را با استفاده از آردوینو شروع کنیم. انجام این کار بسیار ساده است، پس اصلا نگران نباشید. قطعاتی که در اینجا نیاز دارید، عبارت اند از:
پس از اینکه قطعات مورد نظر را تهیه کردید، در قدم اول، پایه V+ روی ماژول را به 5V آردوینو و پایه GND را به زمین وصل کنید. سپس، پایههای CLK و DT را به ترتیب به پایه دیجیتال 2# و 3# وصل کنید و در آخر پایه SW را به پایه دیجیتال 4# متصل نمایید. میتوانید طبق تصویر زیر، سیمکشی را انجام دهید:
کد آردوینو- خواندن روتاری انکودر
در بخش قبل باهم اتصالات را انجام دادیم. اما اکنون با نوشتن کد میتوانید با انکودر کارهای بسیاری انجام دهید. دستوراتی که در ادامه میبینید، به شما این امکان را میدهد تا از سکون یا چرخش محور، جهت چرخش، فشرده شدن یا فشرده نشدن محور مطللع شوید. این دستورات را بر روی آردوینو آپلود کنید تا در ادامه بیشتر در مورد آن صحبت کنیم.
// Rotary Encoder Inputs
#define CLK 2
#define DT 3
#define SW 4
int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";
unsigned long lastButtonPress = 0;
void setup() {
// Set encoder pins as inputs
pinMode(CLK,INPUT);
pinMode(DT,INPUT);
pinMode(SW, INPUT_PULLUP);
// Setup Serial Monitor
Serial.begin(9600);
// Read the initial state of CLK
lastStateCLK = digitalRead(CLK);
}
void loop() {
// Read the current state of CLK
currentStateCLK = digitalRead(CLK);
// If last and current state of CLK are different, then pulse occurred
// React to only 1 state change to avoid double count
if (currentStateCLK != lastStateCLK && currentStateCLK == 1){
// If the DT state is different than the CLK state then
// the encoder is rotating CCW so decrement
if (digitalRead(DT) != currentStateCLK) {
counter --;
currentDir ="CCW";
} else {
// Encoder is rotating CW so increment
counter ++;
currentDir ="CW";
}
Serial.print("Direction: ");
Serial.print(currentDir);
Serial.print(" | Counter: ");
Serial.println(counter);
}
// Remember last CLK state
lastStateCLK = currentStateCLK;
// Read the button state
int btnState = digitalRead(SW);
//If we detect LOW signal, button is pressed
if (btnState == LOW) {
//if 50ms have passed since last LOW pulse, it means that the
//button has been pressed, released and pressed again
if (millis() - lastButtonPress > 50) {
Serial.println("Button pressed!");
}
// Remember last button press event
lastButtonPress = millis();
}
// Put in a slight delay to help debounce the reading
delay(1);
}
پس از اینکه دستورات بالا را بر روی آردوینو آپلود کردید، اگر همه چیز به خوبی انجام شده باشد، باید خروجی زیر را در سریال مانیتور مشاهده کنید:
نکته:
اگر چرخشی که در سریال مانیتور مشاهده میکنید، برخلاف انتظار شما بود، خطوط CLK و DT را باهم جابهجا کنید.
توضیحات کد:
این دستورات با معرفی پایههای آردوینو که به ترتیب به پایههای SW, DT ,CLK انکودر متصل شده است، شروع میشود.
#define CLK 2
#define DT 3
#define SW 4
در ادامه دستورات، چند متغیر از نوع عدد صحیح تعریف میشود. متغیر counter
تعداد پالس ایجاد شده را که با هر بار چرخاندن محور ایجاد میشود، شمارش میکند.
متغیرهای currentStateCLK
و lastStateCLK
نیز، وضعیت خروجی CLK را نگه میدارند و برای تعیین میزان چرخش مورد استفاده قرار میگیرند. از متغیر رشتهای currentDir
هم برای چاپ کردن جهت چرخش بر روی سریال مانیتور و از متغیر lastButtonPress
نیز برای دیبانس و نویزگیری سوییچ استفاده میشود.
int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";
unsigned long lastButtonPress = 0;
در بخش Setup، ابتدا اتصالات انکودر را به عنوان ورودی تعریف میکنیم. سپس، مقاومت Pullup ورودی را روی پایه SW فعال میکنیم و تنظیمات مربوط به سریال مانیتور را انجام میدهیم. در آخر هم مقدار فعلی پایه CLK را میخوانیم و آن را در متغیر lastStateCLK
ذخیره مینماییم.
pinMode(CLK,INPUT);
pinMode(DT,INPUT);
pinMode(SW, INPUT_PULLUP);
Serial.begin(9600);
lastStateCLK = digitalRead(CLK);
در بخش loop، وضعیت CLK را مجددا چک کرده و با متغیر lastStateCLK
مقایسه میکنیم. اگر مقادیر متفاوت بود، یعنی محور چرخیده و یک پالس رخ داده است. علاوه بر این، برای اینکه هر پالس دوبار شمارش نشود، مقدار متغیر currentStateCLK
را نیز بررسی میکنیم.
برای اینکه هر پالس دو بار شمارش نشود، تنها باید لبههای بالا رونده پالس یا لبههای پایین رونده پالس شمارش شوند. در اینجا تنها لبههای بالا رونده شمارش میشوند. به همین دلیل عبارت currentStateCLK == 1
در عبارت شرطی آورده شده است.
currentStateCLK = digitalRead(CLK);
if (currentStateCLK != lastStateCLK && currentStateCLK == 1){
در داخل عبارت if، جهت چرخش را مشخص میکنیم. برای این کار، پایه DT روی ماژول انکودر را میخوانیم و آن را با حالت فعلی پایه CLK مقایسه میکنیم.
اگر این دو مقدار متفاوت باشند، یعنی محور ساعتگرد چرخانده شده است. در این حالت، باید یک واحد به شمارنده اضافه کنیم و مقدار CW را به متغیر currentDir
بدهیم. اما اگر این دو مقدار یکسان باشند، محور پادساعتگرد چرخانده شده است. در این حالت، یک واحد از شمارنده کم میکنیم و مقدار CCW را به متغیر currentDir
میدهیم.
if (digitalRead(DT) != currentStateCLK) {
counter --;
currentDir ="CCW";
} else {
counter ++;
currentDir ="CW";
}
در نهایت خروجی را بر روی سریال مانیتور چاپ میکنیم:
Serial.print("Direction: ");
Serial.print(currentDir);
Serial.print(" | Counter: ");
Serial.println(counter);
خارج از عبارت if ، متغیر lastStateCLK
را نیز با حالت فعلی CLK بهروزرسانی میکنیم.
lastStateCLK = currentStateCLK;
دستورات زیر در صورتیکه سوییچ فشرده شود، در سریال مانیتور عبارت Button pressed را چاپ میکند. اما از آنجاییکه تغییر حالت سوییچ همراه با نویز است، با استفاده از دستور ()millis
که زمان شروع برنامه فعلی را به میلی ثانیه برمیگرداند، دیبانسینگ و نویزگیری کلید انجام میشود. به این صورت که اگر کلید بیش از ms50 در وضعیت LOW باقی ماند، پیغام !Button pressed بر روی سریال مانیتور چاپ میشود.
int btnState = digitalRead(SW);
if (btnState == LOW) {
if (millis() - lastButtonPress > 50) {
Serial.println("Button pressed!");
}
lastButtonPress = millis();
}
سپس، همه دستورات بالا مجددا انجام میشود.
کد آردوینو- راه اندازی ماژول روتاری انکودر با وقفه آردوینو
برای کار با روتاری انکودر باید دائما تغییرات سیگنالهای CLK و DT را بررسی کنیم و برای اینکه بفهمیم این تغییرات چه زمانی رخ میدهد، باید بهطور پیوسته از آنها نمونهبرداری کنیم (مانند آنچه در دستورات قبلی انجام دادیم). البته این روش مناسب نیست. زیرا:
- با این روش، دائما مجبوریم بررسی کنیم که آیا تغییری رخ داده است یا نه. به همین دلیل آردوینو دائما مشغول خواهد بود.
- از لحظه وقوع تا لحظه چک کردن نوعی تاخیر وجود خواهد داشت. اگر لازم باشد که بلافاصله واکنش نشان دهیم، با این تاخیر مواجه خواهیم شد.
- این احتمال وجود دارد که برخی از تغییرات سیگنال، در صورتیکه مدت زمان تغییر کوتاه باشد، آشکارسازی نشود.
با توجه به این دلایل، بهترین روش جایگزین برای نمونهبرداری که بهصورت گسترده مورد استفاده قرار میگیرد، استفاده از وقفه است. با استفاده از وقفه، نیازی نیست که دائما از یک رویداد خاص نمونهبرداری کنید. این کار باعث میشود آردوینو برای انجام کارهای دیگر آزاد باشد و هیچ رویدادی را نیز از دست ندهد.
سیمکشی
در بخش قبل یاد گرفتید که بهترین روش به جای نمونهبرداری، استفاده از وقفه است. اما در اینجا، میخواهیم نحوه سیمکشی برای این روش را به شما نشان دهیم.
از آنجاییکه اکثر آردوینوها (مانند Arduino UNO) تنها دارای دو وقفه خارجی هستند، شما فقط میتوانید تغییرات سیگنالهای CLK و DT را بررسی کنید. به همین دلیل، باید اتصالات پایه SW را که در سیمکشی قبلی انجام دادید، حذف کنید.
با حذف اتصالات این پایه، نتیجه کار شما به شکل زیر خواهد شد:
برخی بردها (مانند برد Arduino mega 2560) وقفههای خارجی بیشتری دارند. اگر شما یکی از آنها را دارید و میخواهید از آن استفاده کنید، میتوانید اتصالات پایه SW را نگه دارید و دستورات بخش بعدی را که شامل کدهای کلید فشاری است، بهکار ببرید.
کد آردوینو
دستورات زیر، کاربرد وقفه را هنگام خواندن روتاری انکودر نشان میدهد.
// Rotary Encoder Inputs
#define CLK 2
#define DT 3
int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";
void setup() {
// Set encoder pins as inputs
pinMode(CLK,INPUT);
pinMode(DT,INPUT);
// Setup Serial Monitor
Serial.begin(9600);
// Read the initial state of CLK
lastStateCLK = digitalRead(CLK);
// Call updateEncoder() when any high/low changed seen
// on interrupt 0 (pin 2), or interrupt 1 (pin 3)
attachInterrupt(0, updateEncoder, CHANGE);
attachInterrupt(1, updateEncoder, CHANGE);
}
void loop() {
//Do some useful stuff here
}
void updateEncoder(){
// Read the current state of CLK
currentStateCLK = digitalRead(CLK);
// If last and current state of CLK are different, then pulse occurred
// React to only 1 state change to avoid double count
if (currentStateCLK != lastStateCLK && currentStateCLK == 1){
// If the DT state is different than the CLK state then
// the encoder is rotating CCW so decrement
if (digitalRead(DT) != currentStateCLK) {
counter --;
currentDir ="CCW";
} else {
// Encoder is rotating CW so increment
counter ++;
currentDir ="CW";
}
Serial.print("Direction: ");
Serial.print(currentDir);
Serial.print(" | Counter: ");
Serial.println(counter);
}
// Remember last CLK state
lastStateCLK = currentStateCLK;
}
به این نکته حتما دقت داشته باشید که حلقه اصلی در این برنامه خالی نگه داشته شود تا آردوینو مشغول انجام هیچ کاری نباشد.
در این دستورات، پایه دیجیتال 2 (وقفه شماره 0) به CLK و پایه دیجیتال 3 (وقفه شماره 1) به DT متصل شده است و وضعیت آنها توسط آردوینو بررسی میشود. هر تغییر ولتاژ از HIGH بهLOW و از LOW بهHIGH به این معنا است که محور چرخانده شده است. هر زمان که این اتفاق رخ داد، تابع updateEncoder
(که اغلب روتین سرویس وقفه یا به اختصار ISR نامیده میشود) فراخوانی خواهد شد. با فراخوانی تابع، دستورات آن اجرا میشود و سپس برنامه به دستورات قبلی باز میگردد.
همه مواردی که گفته شد، توسط تابع ()attachInterrupt
انجام میشود. این تابع دارای سه پارامتر است.
در پارامتر اول، ابتدا باید شماره پایه دیجیتالی را که برای وقفه استفاده میشود (در آردوینو UNO پایه دیجیتال 2 و 3 برای وقفه اختصاص داده شده است)، بنویسیم. علاوه بر این، در پارامتر اول میتوان به جای شماره پایه، شماره وقفه را قرار داد. همانطور که قبلا گفته شد پایه دیجیتال 2 مربوط به وقفه 0 و پایه دیجیتال 3 مربوط به وقفه 1 است.
پارامتر دوم مربوط به ISR یا روتین سرویس وقفه است که باید پس از رخ دادن وقفه اجرا شود. پارامتر سوم نیز مشخص میکند که چه زمانی وقفه باید ایجاد شود. برای این پارامتر، change قرار داده شده است و باعث میشود هر زمان که مقدار پایه تغییر کند (هر تغییر ولتاژ از HIGH به LOW و هر تغییر LOW به HIGH)، وقفه ایجاد شود.
attachInterrupt(0, updateEncoder, CHANGE);
attachInterrupt(1, updateEncoder, CHANGE);
کنترل سروو موتور با روتاری انکودر
در اینجا پروژه دیگری برای شما در نظر گرفتیم و آن کنترل موقعیت یک سروو موتور با استفاده از روتاری انکودر است. این پروژه میتواند در موقعیتهای بسیاری مورد استفاده قرار بگیرد. به عنوان مثال، زمانیکه میخواهید بازوی ربات خود را حرکت دهید، میتوانید از این پروژه استفاده کنید، زیرا به شما این امکان را میدهد تا وضعیت بازو را به دقت کنترل کنید.
اگر قبلا با سروو موتور کار نکردید، میتوانید ابتدا آموزش زیر را مطالعه کنید و پس از آن این پروژه را انجام دهید.
آشنایی با سروو موتور و نحوه راه اندازی آن با آردوینو
سیمکشی
همانطور که در تصویر زیر میبینید، علاوه بر قطعاتی که در پروژههای قبلی استفاده کردید، به یک سروو موتور هم نیاز خواهید داشت.
برای سیمکشی، سیم قرمز سروو موتور را به تغذیه 5V خارجی، سیم مشکی یا قهوهای را به زمین و سیم زرد یا نارنجی را به پایه 9 که PWM است، وصل نمایید. البته شما میتوانید از 5V خروجی آردوینو نیز استفاده کنید، اما به یاد داشته باشید که سروو موتور دارای نویز الکتریکی است و بر روی آردوینو اثرات نامطلوبی میگذارد. بنابراین به شما توصیه میکنیم حتما از یک منبع تغذیه خارجی استفاده کنید.
کد آردوینو
پس اتصال سروو موتور به آردوینو، باید دستورات آن را بارگذاری کنید. دستورات زیر امکان کنترل دقیق سروو موتور را با استفاده از روتاری انکودر به شما میدهد. با چرخش محور روتاری انکودر و به ازای هر پالس، موقعیت بازوی سروو یک درجه تغییر میکند.
// Include the Servo Library
#include <Servo.h>
// Rotary Encoder Inputs
#define CLK 2
#define DT 3
Servo servo;
int counter = 0;
int currentStateCLK;
int lastStateCLK;
void setup() {
// Set encoder pins as inputs
pinMode(CLK,INPUT);
pinMode(DT,INPUT);
// Setup Serial Monitor
Serial.begin(9600);
// Attach servo on pin 9 to the servo object
servo.attach(9);
servo.write(counter);
// Read the initial state of CLK
lastStateCLK = digitalRead(CLK);
}
void loop() {
// Read the current state of CLK
currentStateCLK = digitalRead(CLK);
// If last and current state of CLK are different, then pulse occurred
// React to only 1 state change to avoid double count
if (currentStateCLK != lastStateCLK && currentStateCLK == 1){
// If the DT state is different than the CLK state then
// the encoder is rotating CCW so decrement
if (digitalRead(DT) != currentStateCLK) {
counter --;
if (counter<0)
counter=0;
} else {
// Encoder is rotating CW so increment
counter ++;
if (counter>179)
counter=179;
}
// Move the servo
servo.write(counter);
Serial.print("Position: ");
Serial.println(counter);
}
// Remember last CLK state
lastStateCLK = currentStateCLK;
}
این دستورات، شباهت زیادی به دستورات قبلی دارد. اما در برخی موارد هم تفاوتهایی با یکدیگر دارند.
در ابتدا این دستورات، کتابخانه داخلی سروو موتور را فراخوانی میکنیم. سپس یک شئ برای معرفی سروو موتور ایجاد مینماییم.
#include <Servo.h>
Servo servo;
در بخش Setup، شئ سروو را به پایه 9 متصل میکنیم. (پایه کنترلی که سرو موتور به آن متصل شده است.)
servo.attach(9);
در بخش loop ، پارامتر counter را بین 0 تا 179 محدود میکنیم، زیرا سروو موتور انتخاب شده 180 درجه است. در واقع از آنجاییکه سروو نمیتواند بیش از این مقدار بچرخد، اگر تعداد پالسها بیش از 179 شود، عدد 179 را برای تعداد پالسهای بیش از 179 جایگزین میکنیم. اما در صورتیکه counter منفی شود، زوایای منفی با صفر جایگزین میشوند، زیرا حداقل زاویهای که سروو موتور میتواند چرخش کند، صفر درجه است.
if (digitalRead(DT) != currentStateCLK) {
counter --;
if (counter<0)
counter=0;
} else {
counter ++;
if (counter>179)
counter=179;
}
در آخر هم، پس از مشخص شدن میزان زاویه چرخش توسط روتاری انکودر (پارامتر counter)، باید به سروو دستور حرکت بدهیم. برای این کار از دستور ()servo.write استفاده میکنیم. در واقع این دستور، توسط پارامتر داخل پرانتز، سروو را در موقعیت مشخص شده قرار میدهد که در اینجا counter است.
servo.write(counter);
مروری بر آنچه گفته شد
در این مقاله برای راه اندازی ماژول روتاری انکودر یا همان انکودر چرخشی با آردوینو، ابتدا شما را با این ماژول و نحوه عمکلرد آن آشنا کردیم. اما همانطور که دیدید، برای اینکه بتوانید با انکودر کار کنید، حتما باید تغییرات سیگنالها را دائما بررسی کنید و این روش چندان مناسب نیست. اما جایگزین عالی برای این روش، استفاد از وقفهها است. به همین ترتیب، به اتفاق هم با استفاده از وقفهها این ماژول را راه اندازی کردیم. در آخر نیز نحوه کنترل دقیق سروو موتور با ماژول روتاری انکودر را به عنوان یک پروژه جالب و البته کاربردی انجام دادیم.
هدف ما این است که این وبلاگ بتواند محیطی کاربردی و مفیدی را برای شما عزیزان فراهم کند. بنابراین اگر نظر، پیشنهاد یا سوالی داشتید، حتما با ما در میان بگذارید.