در این مطلب قصد دارم در مورد راه اندازی سروو موتور(Servo Motor) با ماژول ESP8266 صحبت کنم. قرار نیست در مورد سروو موتور و ساختارش صحبت کنم، با یک سرچ ساده در گوگل کلی مطلب در مورد این موتور و انواع آن پیدا خواهید کرد.
تنها مطلبی که اینجا به درد ما می خورد این است که سروو موتور (Servo Motor) با پالس کار میکند. دقیق تر بخوام بگم کافیست به ورودیه سیگنال سروو، پالس مشخصی را اعمال کنیم تا سروو در موقعیت مورد نظر قرار گیرد.
من از بورد Node MCU و سروو موتور مدل MG995 استفاده می کنم. برای شروع اول دنبال مشخصات سروو موتور خود میگردیم. و سعی میکنیم زاویه گردش و پهنای پالس آن را پیدا کنیم.
مشخصات سروو
اولین چیزی که برای ما مهم است نحوهی نصب سروو میباشد.
همانطور که می بینید کابل سیگنال را باید به پینی وصل کنیم که قرار است PWM را برای ما ایجاد کند.
حال باید مشخصات دیگر سرووی خود را پیدا کنیم.
در اینجا به ما میگه برای کنترل دوتا نکته را باید رعایت کنیم.
اول فرکانس PWM ما باید 50 هرتز باشد. اگر یک ثانیه را بر 50 تقسیم کنیم عدد 20 میلی بدست میآید. یعنی هر سیکل کامل ما باید 20 میلی ثانیه طول بکشد. حالا اینکه این کجا بدرد ما میخورد را خواهید دید.
دومین مورد که گفته شده این است که، مقدار زمان یک بودن سیگنال PWM ما مشخص کنندهی محلی است که محور سروو باید در آن قرار گیرد. اگر مقدار یک بودن سیگنال 0.5 میلی ثانیه باشد محور سروو در صفر درجه قرار خواهد گرفت. اگر مقدار 1.5 ثانیه باشد، مشخص کنندهی 90 درجه است. و برای 180 درجه باید مقدار یک بودن سیگنال، 2.5 میلی ثانیه باشد.
توجه داشته باشید کل سیگنال ما نباید از 20 میلی ثانیه بیشتر طول بکشد. پس وقتی مقدار یک بودن سیگنال 2.5 میلی ثانیه است، مقدار صفر بودن سیگنال PWM باید 17.5 میلی ثانیه باشد.
نکته بعدی این که این مقدار ها برای هر مدل از سروو فرق دارد. برای همین پیشنهاد میکنم حتما دیتا شیت سرووی خود را بخوانید.
در این تصویر دقیقتر سیگنال را نشان می دهد.
راه اندازی سروو با کتابخانه
از بهترین روش ها برای راه اندازی سروو استفاده از کتابخانهی سروو میباشد.
#include <Servo.h> Servo myservo; // create servo object to control a servo // twelve servo objects can be created on most boards void setup() { myservo.attach(2); // attaches the servo on GIO2 to the servo object } void loop() { int pos; for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees // in steps of 1 degree myservo.write(pos); // tell servo to go to position in variable 'pos' delay(15); // waits 15ms for the servo to reach the position } for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees myservo.write(pos); // tell servo to go to position in variable 'pos' delay(15); // waits 15ms for the servo to reach the position } }
همانطور که می بینید اول کتابخانهی سروو را فراخوانی می کنیم. و بعد یک نمونه یا شیء از کتابخانه می سازیم. و پین مورد نظر را به آن متصل میکنیم. و تمام سروو شروع میکنه به حرکت.
ولی مشکلی که اینجا پیش میاید این است که سرووی من فقط 90 درجه گردش میکند. اینجا همانجایی است که گفتم باید دیتاشیت سرووی خود را بخوانید. برای درست کردن این مورد هنگام اتصال پین باید دو پارامتر دیگر را هم مشخص کنیم. کمترین و بیشترین مقدار یک بودن سیگنال.
void setup() { myservo.attach(2,500,2500); // attaches the servo on GIO2 to the servo object }
پس کد ما به این صورت میشود. توجه داشته باشید که مقدار باید به میکرو ثانیه وارد شود. پس کار تمام شد و سرووی من شروع کرد به گردش 180 درجهای.
ولی خیلی زود باز به مشکلی خوردم. در پروژه ای، وقتی از 10 درجه به 50 درجه میخواهیم حرکت کنیم همه چیز درست کار می کند. ولی وقتی مثلا از 20 درجه به 25 درجه حرکت می دهیم، سروو دچار مشکل میشود و صدای عجیبی می دهد. پس مشخص شد که وقتی در بازه های کوچک می خواهیم حرکتش بدهیم درست کار نمیکند. برای همین بهتر است خودمان سروو را راه اندازی کنیم تا بعد از فرستادن یک ثانیه سیگنال، سیگنال را قطع کنیم.
راهاندازی سروو بدون کتابخانه
برای این کار کافیست یک سیگنال PWM تولید کنیم با مشخصات مورد نیاز.
uint32_t pulse = 0; //us void movePulse(int x) { for (int pulseCounter = 0; pulseCounter <= 50; pulseCounter++) { digitalWrite(2, HIGH); delayMicroseconds(x); digitalWrite(2, LOW); delayMicroseconds(20000 - x); // between pulses } } void setup() { pinMode(2, OUTPUT); digitalWrite(2, LOW); } void loop() { if (Serial.available() > 0) { pulse = Serial.parseInt(); movePulse(pulse); } }
برای ایجاد کد PWM از برنامهی بالا استفاده می کنیم. یک تابع نوشتیم که با استفاده از Delay برای ما پالس را ایجاد می کند. و این سیکل را به مدت یک ثانیه ایجاد می کند.
خیلی هم عالی، ولی ما به عنوان یک برنامه نویس حرفهای یاد گرفتیم که در برنامه خود نباید ار توابع Delay و delayMicroseconds استفاده کنیم. برای اینکه برنامهی مارا متوقف می کند. و این برای برنامه و میکروی ما امتیاز منفی حساب میشه. پس باید از روش دیگری استفاده کنیم.
راهاندازی سروو با استفاده از وقفه
ESP8266 ها دو تایمر کانتر دارند (البته در مدلهای جدید) که تایمر صفر برای وایفای و پروتکل TCP استفاده می شود. که اصلا توصیه نمی شود از آن استفاده کنیم مگر اینگه نخواهید از وایفای استفاده کنیم. که در این صورت دیگه نیازی نیست از ESP استفاده کنیم. پس میریم سراغ تایمر یک این میکرو.
استفاده از تایمر به این صورت است که در یک بازهی زمانی ای که مشخص می کنیم اجرای برنامه را متوقف می کند. و میرود تابعی را که ISR می نامند را اجرا می کند و دوباره به اجزای برنامه از همانجایی که متوقف شده است بر می گردد. توجه داشته باشید که این تابع نباید زیاد طولانی باشد و نباید از دستورات delay و Serial در آن استفاده شود.
همچنین اگر متغییری را در آن استفاده می کنید باید با Volatile مشخص کنید که این متغیر مستقیم از رم خوانده شود. برای مطالعهی بیشتر وقفه ها این مطلب را بخوانید.
//timer dividers // enum TIM_DIV_ENUM { // TIM_DIV1 = 0, //80MHz (80 ticks/us - 104857.588 us max) // TIM_DIV16 = 1, //5MHz (5 ticks/us - 1677721.4 us max) // TIM_DIV256 = 3 //312.5Khz (1 tick = 3.2us - 26843542.4 us max) // }; // //timer int_types // #define TIM_EDGE 0 // #define TIM_LEVEL 1 // //timer reload values // #define TIM_SINGLE 0 //on interrupt routine you need to write a new value to start the timer again // #define TIM_LOOP 1 //on interrupt the counter will start with the same value again // #define timer1_read() (T1V) // #define timer1_enabled() ((T1C & (1 << TCTE)) != 0) // #define timer1_interrupted() ((T1C & (1 << TCIS)) != 0) // typedef void(*timercallback)(void); // void timer1_isr_init(void); // void timer1_enable(uint8_t divider, uint8_t int_type, uint8_t reload); // void timer1_disable(void); // void timer1_attachInterrupt(timercallback userFunc); // void timer1_detachInterrupt(void); // void timer1_write(uint32_t ticks); //maximum ticks 8388607 volatile uint32_t degree = 0; //0 - 180 volatile uint32_t pulse_width = 20000;//us volatile uint32_t counter = 0; //us #define SERVO 2 //======================================================================= void ICACHE_RAM_ATTR onTimerISR(){ if(digitalRead(SERVO)){ digitalWrite(SERVO,LOW); //Toggle SERVO Pin timer1_write((pulse_width - map(degree ,0,180,500,2500)) * 5);//1s }else{ digitalWrite(SERVO,HIGH); //Toggle SERVO Pin timer1_write(map(degree ,0,180,500,2500) * 5);//12ms } counter += 1; // } if(counter > 100) timer1_disable(); } //======================================================================= // Setup //======================================================================= void setup() { Serial.begin(115200); Serial.println(""); pinMode(SERVO,OUTPUT); digitalWrite(SERVO,LOW); timer1_attachInterrupt(onTimerISR); } //======================================================================= // MAIN LOOP //======================================================================= void loop() { if (Serial.available() > 0) { // read the incoming byte: counter = 0; degree = Serial.parseInt(); timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE); timer1_write(20000 * 5); } } //=======================================================================
در اول برنامه یک سری از دستورات و پارامترها را که برای استفاده از تایمر یک لازم است قرار دادم که کامنت کردم. مثل لبهی بالا رونده یا پایین رونده و یا اسکیل تایمر.
نکته بعدی این است که تابع ISR باید با کلمه کلیدی ICACHE_RAM_ATTR همراه شود تا این تابع در رم ایجاد شود.
همانطور که میبینید تمام مقدارها در 5 ضرب شدهاند این برای آن است که وقتی اسکیل را روی 16 قرار میدهیم هر 5 تیکی که اتفاق می افتد، یک میکرو ثانیه سپری شده است و برای خوانا بودن به این صورت نوشته شده است.
تغیییر دیگری که ایجاد کرده ایم ورودی را به صورت درجه به برنامه می دهیم.
امیدوارم این مطلب مفید واقع بشه.
دیدگاه ها :