ساخت فرستنده‌ی RC با آردوینو

در این آموزش می‌خواهیم با هم یاد بگیریم که چگونه با استفاده از بوردهای آردوینو یک فرستنده RC بسازیم. علت اینکه به سراغ چنین پروژه‌ای آمده‌ایم این است که این کنترلر رادیویی می‌تواند جایگزین خوبی برای کنترلرهای وایرلس باشد و تقریبا در هر پروژه‌ای کاربرد داشته باشد.

مقدمه

امروزه و با پیشرفت تکنولوژی، تقریبا می‌توان هر پروژه‌ی آردوینویی را با استفاده از تجهیزاتی مختصر و تنظیماتی مخصوص، به راحتی به صورت وایرلس کنترل کرد. فرستنده‌ی RC که امروز با هم می‌سازیم نیز می‌تواند به راحتی جایگزین فرستنده‌های RC کارخانه‌ای شود که در اسباب‌بازی‌ها، ماشین‌ها، هواپیما‌های تک سرنشین و … استفاده می‌شوند. برای کنترل این ابزارها کافیست سیگنال‌های مناسب را از آنها دریافت کنیم. در ویدئوی فوق با ذکر چند مثال از جمله کنترل کردن یک ماشین رباتیکی که در آن از بورد آردوینو استفاده شده است، کنترل موتور DC براشلس و کنترل تعداد سروو موتور، توضیح داده‌ می‌شود که این کنترل کردن به چه شکل انجام می‌شود.

ساخت فرستنده‌ی RC با استفاده از آردوینو

ارتباط رادیویی در این کنترل بر پایه‌ی ماژول گیرنده‌ی NRF24L01 انجام می‌شود. این ماژول با استفاده از یک آنتن تقویت کننده‌ی سیگنال، می‌تواند در فضاهای باز تا برد حدود ۷۰۰ متری را پوشش دهد. دارای ۱۴ کانال است که ۶ کانال آن ورودی‌های آنالوگ و ۸ کانال ورودی‌های دیجیتال هستند.

ساخت فرستنده‌ی RC با استفاده از آردوینو

داری دو عدد پتانسیومتر، دو عدد تاگل سوییچ، دو عدد ورودی اهرمی، شش عدد دکمه و یک واحد اندازه‌گیری داخلی است که این واحد مجهز به شتاب سنج و ژیروسکوپ است و به این ترتیب می‌تواند اهدافی که در حوالی کنترلر پرسه می‌زنند را نیز کنترل کند.

مدار مورد نیاز برای ساخت فرستنده‌ی RC با استفاده از آردوینو

برای شروع پروژه، ابتدا به سراغ مدار آن برویم. قلب این کنترل یک بورد Arduino Pro Mini است که با استفاده از دو عدد باتری LiPo که ولتاژ حدود ۷.۴ ولت تولید می‌کنند، تغذیه می‌شود. این باتری‌ها را می‌توانیم مستقیما به پین‌های RAW از آردوینو متصل کنیم. این پین‌ها دارای رگولاتورهایی هستند که ولتاژ را به سطح ۵ ولت می‌برند. (البته دقت داشته باشد که بورد Arduino Pro Mini دارای دو ورژن مختلف است که یکی همین موردی است که ما اینجا استفاده می‌کنیم و در ولتاژ ۵ ولت کار می‌کند، و نوع دیگر دارای ولتاژ کاری ۳.۳ ولت است)

ساخت فرستنده‌ی RC با استفاده از آردوینو

از سوی دیگر، ماژول NRF24L01 نیاز به یک ولتاژ ۳.۳ ولتی دارد که پیشنهاد می‌شود که از یک منبع جداگانه و اختصاصی تامین شود. بنابراین باید یک ولتاژ رگولاتور جداگانه به باتری‌ها متصل کنیم که ۷.۴ ولت را دریافت کرده و ولتاژ ۳.۳ ولت را برای ماژول بسازد. ضمنا برای افزایش پایداری این ولتاژ از یک خازن دیکوپلینگ هم در کنار ماژول NRF24L01 استفاده می‌کنیم. پایداری ولتاژ به پایداری انتقال ارتباط نیز کمک خواهد کرد. این ماژول از طریق پروتکل SPI با بورد آردوینو ارتباط برقرار می‌کند. (شتاب سنج و ژیروسکوپ از پروتکل I2C برای ارتباط با آردوینو استفاده می‌کند)

یک بار دیگر لیست تجهیزات مورد نیاز برای انجام پروژه را با هم مرور می‌کنیم.

  • ماژول گیرنده NRF24L01 + PA + LNA
  • پتانسیومتر
  • سروو موتور
  • تاگل سوییچ
  • بازوی اهرمی ‌به عنوان ورودی (جوی استیک)
  • Arduino Pro Mini
  • رگولاتور ولتاژ (مدل HT7333 3.3v یا AMS1117 3.3v )

قبل از  تهیه قطعات بنظرم مقاله فروشگاه‌ های اینترنتی قطعات الکترونیک را بخونید.

طراحی PCB

در این پروژه از تمام پین‌های آنالوگ و دیجیتال بورد آردوینو استفاده خواهیم کرد. بنابراین اگر بخواهیم سیم‌ها را معمولی وصل کنیم و قسمت‌های مختلف را به هم متصل کنیم، در نهایت یک مدار بسیار شلوغ و تا حدودی گیج کننده خواهیم داشت. میل خودتان است که با این وضعیت راحت هستید یا خیر. اگر دوست دارید نتیجه‌ی نهایی مرتب‌تر باشد این قسمت را همراه با ما انجام دهید. در غیر این صورت می‌توانید به بخش بعدی بروید. ما می‌خواهیم با استفاده از نرم‌افزار آنلاین طراحی مداری که استفاده از آن نیز رایگان است؛ یعنی EasyEDA، یک PCB اختصاصی برای این پروژه طراحی کنیم.

ساخت فرستنده‌ی RC با استفاده از آردوینو

نکته‌ای که ما در اینجا مد نظر می‌گیریم این است که بورد نهایی ما ابعاد متناسبی داشته باشد و بتوان آن را به راحتی و با دو دست نگه داشت. طوری که تمام کنترلرها و دکمه‌ها در محدوده‌ی انگشتان دست‌ها باشند. به عبارت دقیق‌تر؛ می‌خواهیم کنترلر نهایی خوش دست باشد. با این هدف، گوشه‌های برد را گرد می‌کنیم و سوراخ‌هایی ۳ میلی‌متری ایجاد می‌کنیم که بعدا بتوانیم به کمک آنها بورد را بر روی چیز دیگری فیکس کنیم. پین‌هایی که برای پروگرم کردن بورد آردوینو نیاز داریم را در قسمت بالایی در نظر می‌گیریم تا هر زمان خواستیم آن را پروگرم کنیم پین‌ها به راحتی در دسترس باشند. پین‌های Rx و Tx آردوینو را نیز برای اتصال به جوی استیک‌ها در نظر می‌گیریم. البته اتصال این دو پین باید در هنگام پروگرم کردن آردوینو قطع شود بنابراین آنها را به نحوی طراحی می‌کنیم که بتوان به کمک جامپر اتصالشان را قطع و وصل کرد.

نکته‌ی مهمی‌ که در همین ابتدا باید یادآور شویم این است که اگر نسخه‌ی بورد Arduino Pro Mini مورد استفاده ی شما با بورد ما متفاوت است؛ تغییرات متناسب را در طراحی PCB لحاظ کنید.

در تصویر زیر سه نسخه‌ی مختلف از طراحی PCB را بر اساس ورژن‌های مختلف بورد و رگولاتور ولتاژ می‌توانیم ببینیم و مقایسه کنیم.

ساخت فرستنده‌ی RC با استفاده از آردوینو

در این لینک می‌توانید به فایل پروژه‌ی این طراحی PCB دسترسی داشته باشید. اگر بر روی لینک کلیک کنید، PCB‌های متناسب با نسخه‌های مختلف بورد باز خواهند شد که هر کدام را که متناسب با بورد خودتان است می‌توانید انتخاب و استفاده کنید. فایل‌های Gerber این طراحی‌ها که برای ساخت PCB آماده هستند را در ادامه برایتان قرار داده‌ایم.

فایل‌های فوق را می‌توانید دانلود کنید و سپس به راحتی در Gerber viewer بکشید و باز کنید. معمولا در این مرحله مشکلی نخواهیم داشت و پس از باز شدن فایل می‌توانیم تنظیمات بیشتر را برای PCB دلخواهمان انجام دهیم. مثلا تغییر رنگ PCB و … . 

نکته خیلی مهم: متاسفانه معمولا در ایران فایل استاندارد Gerber قبول نمی‌کنند قبل از ارسال فایل به شرکت باهاشون صحبت کنید.

همان‌طور که در تصویر زیر می‌بینید، همه چیز همان‌طور شده است که خودمان تنظیم کرده بودیم.

ساخت فرستنده‌ی RC با استفاده از آردوینو

سوار کردن قطعات روی PCB

در این مرحله می‌توانیم قطعات مدار مورد نظر برای پروژه را بر روی PCB قرار دهیم. ما در اینجا با لحیم کردن پین هدرهای بورد Arduino Pro Mini شروع می‌کنیم. برای آنکه در هنگام لحیم کردن در جای خود فیکس باشند و تکان نخورند می‌توانید از یک برد بورد کمک بگیرید.

ساخت فرستنده‌ی RC با استفاده از آردوینو

برخی بوردهای Pro Mini در سمت دیگرشان نیز پین‌هایی دارند که بسته به شرکت تولید کننده بورد، محل این پین‌ها می‌تواند با هم متفاوت باشد.

ساخت فرستنده‌ی RC با استفاده از آردوینو

مثلا مدلی که ما در اینجا داریم در هر سمت دارای ۵ پین است (پین GND این ردیف پین‌ها را بی خیال می‌شویم چون در PCB، از محل مربوط به قرارگرفتن آنها trace عبور داده‌ایم) پس از لحیم کردن بورد آردوینو و فیکس کردن پایه‌های آن، نوبت به ماژول‌های ژیروسکوپ و شتاب‌سنج می‌رسد که دقیقا در بغل آردوینو قرار می‌گیرند.

مطلب پیشنهادی:  با آردوینو یک وب‌سرور بسازید

ساخت فرستنده‌ی RC با استفاده از آردوینو

سپس رگولاتور ولتاژ ۳.۳ ولتی را با یک خازن در کنار آن لحیم می‌کنیم. خازن دیگری را نیز در کنار ماژول NRF24L01 قرار می‌دهیم. این ماژول هم سه مدل مختلف دارد که از هر کدام از آنها می‌توانیم برای این پروژه استفاده کنیم.

ساخت فرستنده‌ی RC با استفاده از آردوینو

در ادامه به سراغ پین‌های پروگرم کردن آردوینو، Rx و Tx و پین‌های تامین تغذیه و … می‌رویم.

پس از آنها نوبت به پتانسیومترها می‌رسد که چون پایه‌های آنها نسبتا کوتاه است از پین هدر کمک می‌گیریم.

ساخت فرستنده‌ی RC با استفاده از آردوینو

تاگل سوییچ‌ها و جوی استیک‌ها را نیز هر کدام در جای خود قرار داده و لحیم می‌کنیم.

ساخت فرستنده‌ی RC با استفاده از آردوینو

و در نهایت به کلیدهای فشاری می‌رسیم. متاسفانه طول پایه‌های این کلیدها نیز کوتاه است و برای لحیم کردنشان باید از پین هدر کمک بگیریم.

بسیار خب. کار سر هم کردن PCB و سوار کردن قطعات در این لحظه تمام است و می‌توانیم به سراغ مرحله‌ی بعد یعنی ایجاد یک پوشش یا محفظه برای آن برویم. ما در اینجا از محفظه‌ی شفاف استفاده می‌کنیم چون ترجیح می‌دهیم که PCB و جزییات آن مشخص باشند.

ساخت فرستنده‌ی RC با استفاده از آردوینو

پیشنهاد ما استفاده از پلاستیک‌های ضخیم و فشرده‌ای مانند اکریلیک است که به عنوان مثال قطر ۴ میلی‌متری آنها برای کار ما مناسب خواهد بود. البته در ابتدا که این اکریلیک‌ها را تهیه کنید یک لایه محافظ بر روی آنها کشیده شده است که رنگ آبی دارد؛ اما اگر آن لایه را بردارید، خود پلاستیک کاملا شفاف و بی رنگ است. ایده‌ی ما برای محفظه‌ای که می‌خواهیم محافظ بوردمان باشد به این شکل است که دو لایه از این پلاستیک را دقیقا به شکل خود PCB برش دهیم و در بالا و پایین آن قرار دهیم که تا حدودی از PCB در برابر آسیب‌های احتمالی محافظت کنند.

ساخت فرستنده‌ی RC با استفاده از آردوینو

بسیار خب پس PCB را روی اکریلیک قرار داده و شکل دور آن را می‌کشیم. سپس با استفاده از اره شکل کشیده شده را برش داده و جدا می‌کنیم.

ساخت فرستنده‌ی RC با استفاده از آردوینو

پس از جدا کردن در صورت نیاز گوشه‌ها و لبه‌ها را با سوهان صاف می‌کنیم تا تیز و برنده نباشند.

ساخت فرستنده‌ی RC با استفاده از آردوینو

پس از آن محل‌هایی که برای عبور قطعات (مثلا جوی استیک‌ها یا کلیدها) نیاز داریم را علامت می‌زنیم تا برش دهیم. این محل‌ها را با استفاده از دریل با قطر مناسب ایجاد کرده و سوراخ‌ها را ایجاد می‌کنیم.

ساخت فرستنده‌ی RC با استفاده از آردوینو

فقط به قطر سوراخ‌ها حتما دقت داشته باشید چون با هم متفاوت هستند. مثلا برای پتانسیومترها یا تاگل سوییچ‌ها باید از دریل ۶ میلی‌متری و برای جوی استیک‌ها باید از دریل ۲۵ میلی‌متری استفاده کنیم. پس از ایجاد سوراخ‌ها مجددا با کمک سوهان ناهمواری‌ها را برطرف می‌کنیم.

ساخت فرستنده‌ی RC با استفاده از آردوینو

قبل از اینکه کاوری که ساخته‌ایم روی بورد فیکس کنیم، لازم است این نکته را هم یادآوری کنیم که پین هدر مربوط به منبع تغذیه را برعکس لحیم کرده‌ایم تا از پشت بورد که محل قرارگیری باتری‌هاست در دسترس باشند.

ساخت فرستنده‌ی RC با استفاده از آردوینو

در این مرحله کاور آبی رنگ روی پلاستیک را جدا می‌کنیم و نتیجه بسیار خوب است؛ چون در حین مراحل کاری که تا اینجا داشته‌ایم محافظ باعث شده است که پلاستیک کاملا شفاف و تمیز بماند.

ساخت فرستنده‌ی RC با استفاده از آردوینو

با استفاده از پیچ و مهره‌های مناسب، کاور را روی بورد فیکس می‌کنیم و پس از آن پتانسیومترها را نیز روی بورد نصب می‌کنیم. علت اینکه تا این لحظه آنها را فیکس نکرده‌ایم این است که دقیقا نمی‌دانستیم که باید در چه ارتفاعی قرار بگیرند که پس از نصب محافظ این ارتفاع مشخص می‌شود.

ساخت فرستنده‌ی RC با استفاده از آردوینو

در سمت دیگر، نگه دارنده‌ی باتری را نیز با پیچ و مهره فیکس می‌کنیم و در نهایت محافظ قسمت پشتی را نیز به همین روش قسمت رویی فیکس می‌کنیم.

ساخت فرستنده‌ی RC با استفاده از آردوینو

ریزه‌ کاری‌های نهایی مانند نصب آنتن ماژول NRF24l01، نصب جوی استیک‌ها و … را نیز انجام می‌دهیم. به تدریج فرستنده‌ی RC ما در حال  آماده شدن می‌باشد.

ساخت فرستنده‌ی RC با استفاده از آردوینو

آخرین کاری که باید انجام دهیم پروگرم کردن بورد آردوینو است. برای پروگرم کردن بورد Pro Mini به یک اینترفیس USB به Serial UART نیاز داریم. این اینترفیس از طریق همان درگاهی که برای پروگرم کردن روی بورد در نظر گرفته بودیم (در قسمت رویی بورد) به آردوینو متصل می‌شود.

ساخت فرستنده‌ی RC با استفاده از آردوینو

در لپ‌تاپ، Arduino IDE را باز می‌کنیم و بورد Arduino Pro یا Pro Mini را انتخاب می‌کنیم. تنظیمات پورت‌ها را نیز انجام می‌دهیم و نوع پروگرم کردن را بر روی حالت USBasp قرار می‌دهیم.

ساخت فرستنده‌ی RC با استفاده از آردوینو

حالا همه چیز آماده است و فقط باید کد را بر روی بورد آپلود کنیم.

کد‌های لازم برای RC Transmitter مبتنی بر Arduino

در این بخش در مورد کد‌های مورد نیاز توضیح می‌دهیم. برای ارتباطات وایرلسی که نیاز داریم ابتدا باید کتابخانه‌های SPI و RF24 را اضافه کنیم. برای ارتباط با ماژول شتاب‌سنج نیز کتابخانه I2C را نیز اضافه می‌کنیم. مرحله‌ی بعدی تعریف ورودی‌ها و متغیرهایی است که در کد استفاده می‌کنیم.

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Wire.h>


// Define the digital inputs
#define jB1 1 // Joystick button 1
#define jB2 0 // Joystick button 2
#define t1 7 // Toggle switch 1
#define t2 4 // Toggle switch 1
#define b1 8 // Button 1
#define b2 9 // Button 2
#define b3 2 // Button 3
#define b4 3 // Button 4

const int MPU = 0x68; // MPU6050 I2C address
float AccX, AccY, AccZ;
float GyroX, GyroY, GyroZ;
float accAngleX, accAngleY, gyroAngleX, gyroAngleY;
float angleX, angleY;
float AccErrorX, AccErrorY, GyroErrorX, GyroErrorY;
float elapsedTime, currentTime, previousTime;
int c = 0;


RF24 radio(5, 6); // nRF24L01 (CE, CSN)
const byte address[6] = "00001"; // Address

برای ورودی ۱۴ بیتی کنترلر یک structure تعریف می‌کنیم. ماکسیسم ظرفیت بافر ماژول NRF24L01 و نیز حداکثر حجم داده‌ای که این ماژول می‌تواند مبادله کند، ۳۲ بایت است پس حجم structure را نیز همین مقدار تعریف می‌کنیم.

/ Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
byte j1PotX;
byte j1PotY;
byte j1Button;
byte j2PotX;
byte j2PotY;
byte j2Button;
byte pot1;
byte pot2;
byte tSwitch1;
byte tSwitch2;
byte button1;
byte button2;
byte button3;
byte button4;
};

Data_Package data; //Create a variable with the above structure

در بخش setup، ماژول MPU6050 را initialize می‌کنیم و مقدار خطای IMU را نیز می‌توان محاسبه کرد. مقدار این خطا در ادامه در محاسبه‌ی زاویه‌ی درست ژیروسکوپ مورد استفاده قرار می‌گیرد.

void initialize_MPU6050() {
Wire.begin(); // Initialize comunication
Wire.beginTransmission(MPU); // Start communication with MPU6050 // MPU=0x68
Wire.write(0x6B); // Talk to the register 6B
Wire.write(0x00); // Make reset - place a 0 into the 6B register
Wire.endTransmission(true); //end the transmission
// Configure Accelerometer
Wire.beginTransmission(MPU);
Wire.write(0x1C); //Talk to the ACCEL_CONFIG register
Wire.write(0x10); //Set the register bits as 00010000 (+/- 8g full scale range)
Wire.endTransmission(true);
// Configure Gyro
Wire.beginTransmission(MPU);
Wire.write(0x1B); // Talk to the GYRO_CONFIG register (1B hex)
Wire.write(0x10); // Set the register bits as 00010000 (1000dps full scale)
Wire.endTransmission(true);
}

در قسمت بعدی باید radio communication را مقداردهی اولیه کنیم، مقاومت پول آپ آردوینو برای تمام پین‌ها را فعال کنیم و هر پینی که به مقدار پیش‌فرض نیاز دارد را مقداردهی کنیم.

// Define the radio communication
radio.begin();
radio.openWritingPipe(address);
radio.setAutoAck(false);
radio.setDataRate(RF24_250KBPS);
radio.setPALevel(RF24_PA_LOW);

// Activate the Arduino internal pull-up resistors
pinMode(jB1, INPUT_PULLUP);
pinMode(jB2, INPUT_PULLUP);
pinMode(t1, INPUT_PULLUP);
pinMode(t2, INPUT_PULLUP);
pinMode(b1, INPUT_PULLUP);
pinMode(b2, INPUT_PULLUP);
pinMode(b3, INPUT_PULLUP);
pinMode(b4, INPUT_PULLUP);

در بخش تابع حلقه، ورودی‌های آنالوگ را که مقادیر از صفر تا ۱۰۲۳ دارند، می‌خوانیم و آنها را به اعدادی در بازه‌ی صفر تا ۲۵۵ (بایت) نگاشت می‌کنیم. علت این کار این است که در structure که تعریف کرده‌ایم داده‌ها به صورت بایت تعریف شده‌اند.

هر کدام از این ورودی‌ها در یک متغیر جداگانه از structure ذخیره خواهند شد.

// Read all analog inputs and map them to one Byte value
data.j1PotX = map(analogRead(A1), 0, 1023, 0, 255); // Convert the analog read value from 0 to 1023 into a BYTE value from 0 to 255
data.j1PotY = map(analogRead(A0), 0, 1023, 0, 255);
data.j2PotX = map(analogRead(A2), 0, 1023, 0, 255);
data.j2PotY = map(analogRead(A3), 0, 1023, 0, 255);
data.pot1 = map(analogRead(A7), 0, 1023, 0, 255);
data.pot2 = map(analogRead(A6), 0, 1023, 0, 255);

نکته: توجه داریم که چون برای پین‌های دیجیتال از مقاومت پول آپ استفاده کرده‌ایم، زمانی که کلیدها را فشار می‌دهیم، این ورودی‌ها مقدار صفر را خواهند خواند.

// Read all digital inputs
data.j1Button = digitalRead(jB1);
data.j2Button = digitalRead(jB2);
data.tSwitch2 = digitalRead(t2);
data.button1 = digitalRead(b1);
data.button2 = digitalRead(b2);
data.button3 = digitalRead(b3);
data.button4 = digitalRead(b4);

با استفاده از تابع ()radio.write مقادیر را از کانال‌های ۱۴ گانه دریافت کرده و به گیرنده ارسال می‌کنیم.

// Send the whole data from the structure to the receiver
radio.write(&data, sizeof(Data_Package));

زمانی که تاگل سوییچ شماره‌ی یک روشن می‌شود، داده‌های دریافت شده از ژیروسکوپ و شتاب‌سنج برای کنترل استفاده می‌شوند.

if (digitalRead(t1) == 0) {
read_IMU(); // Use MPU6050 instead of Joystick 1 for controling left, right, forward and backward movements
}

به جای X و Y جوی استیک‌ها، مقادیری که از IMU دریافت کرده‌ایم را وارد می‌کنیم. که البته مقادیر دریافت شده را باید از فرمت زاویه‌هایی در بازه‌ی ۹۰- تا ۹۰ درجه، به بایت‌هایی در بازه‌ی صفر تا ۲۵۵ تبدیل کنیم.

// Map the angle values from -90deg to +90 deg into values from 0 to 255, like the values we are getting from the Joystick
data.j1PotX = map(angleX, -90, +90, 255, 0);
data.j1PotY = map(angleY, -90, +90, 0, 255);

بسیار خب، این کد بخش فرستنده بود که در ادامه کامل آن را نیز می‌توانید ببینید.

/*
DIY Arduino based RC Transmitter
by Dejan Nedelkovski, www.HowToMechatronics.com
Library: TMRh20/RF24, https://github.com/tmrh20/RF24/
Tr: Melec.ir */ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> #include <Wire.h> // Define the digital inputs #define jB1 1 // Joystick button 1 #define jB2 0 // Joystick button 2 #define t1 7 // Toggle switch 1 #define t2 4 // Toggle switch 1 #define b1 8 // Button 1 #define b2 9 // Button 2 #define b3 2 // Button 3 #define b4 3 // Button 4 const int MPU = 0x68; // MPU6050 I2C address float AccX, AccY, AccZ; float GyroX, GyroY, GyroZ; float accAngleX, accAngleY, gyroAngleX, gyroAngleY; float angleX, angleY; float AccErrorX, AccErrorY, GyroErrorX, GyroErrorY; float elapsedTime, currentTime, previousTime; int c = 0; RF24 radio(5, 6); // nRF24L01 (CE, CSN) const byte address[6] = "00001"; // Address // Max size of this struct is 32 bytes - NRF24L01 buffer limit struct Data_Package { byte j1PotX; byte j1PotY; byte j1Button; byte j2PotX; byte j2PotY; byte j2Button; byte pot1; byte pot2; byte tSwitch1; byte tSwitch2; byte button1; byte button2; byte button3; byte button4; }; Data_Package data; //Create a variable with the above structure void setup() { Serial.begin(9600); // Initialize interface to the MPU6050 initialize_MPU6050(); // Call this function if you need to get the IMU error values for your module //calculate_IMU_error(); // Define the radio communication radio.begin(); radio.openWritingPipe(address); radio.setAutoAck(false); radio.setDataRate(RF24_250KBPS); radio.setPALevel(RF24_PA_LOW); // Activate the Arduino internal pull-up resistors pinMode(jB1, INPUT_PULLUP); pinMode(jB2, INPUT_PULLUP); pinMode(t1, INPUT_PULLUP); pinMode(t2, INPUT_PULLUP); pinMode(b1, INPUT_PULLUP); pinMode(b2, INPUT_PULLUP); pinMode(b3, INPUT_PULLUP); pinMode(b4, INPUT_PULLUP); // Set initial default values data.j1PotX = 127; // Values from 0 to 255. When Joystick is in resting position, the value is in the middle, or 127. We actually map the pot value from 0 to 1023 to 0 to 255 because that's one BYTE value data.j1PotY = 127; data.j2PotX = 127; data.j2PotY = 127; data.j1Button = 1; data.j2Button = 1; data.pot1 = 1; data.pot2 = 1; data.tSwitch1 = 1; data.tSwitch2 = 1; data.button1 = 1; data.button2 = 1; data.button3 = 1; data.button4 = 1; } void loop() { // Read all analog inputs and map them to one Byte value data.j1PotX = map(analogRead(A1), 0, 1023, 0, 255); // Convert the analog read value from 0 to 1023 into a BYTE value from 0 to 255 data.j1PotY = map(analogRead(A0), 0, 1023, 0, 255); data.j2PotX = map(analogRead(A2), 0, 1023, 0, 255); data.j2PotY = map(analogRead(A3), 0, 1023, 0, 255); data.pot1 = map(analogRead(A7), 0, 1023, 0, 255); data.pot2 = map(analogRead(A6), 0, 1023, 0, 255); // Read all digital inputs data.j1Button = digitalRead(jB1); data.j2Button = digitalRead(jB2); data.tSwitch2 = digitalRead(t2); data.button1 = digitalRead(b1); data.button2 = digitalRead(b2); data.button3 = digitalRead(b3); data.button4 = digitalRead(b4); // If toggle switch 1 is switched on if (digitalRead(t1) == 0) { read_IMU(); // Use MPU6050 instead of Joystick 1 for controling left, right, forward and backward movements } // Send the whole data from the structure to the receiver radio.write(&data, sizeof(Data_Package)); } void initialize_MPU6050() { Wire.begin(); // Initialize comunication Wire.beginTransmission(MPU); // Start communication with MPU6050 // MPU=0x68 Wire.write(0x6B); // Talk to the register 6B Wire.write(0x00); // Make reset - place a 0 into the 6B register Wire.endTransmission(true); //end the transmission // Configure Accelerometer Wire.beginTransmission(MPU); Wire.write(0x1C); //Talk to the ACCEL_CONFIG register Wire.write(0x10); //Set the register bits as 00010000 (+/- 8g full scale range) Wire.endTransmission(true); // Configure Gyro Wire.beginTransmission(MPU); Wire.write(0x1B); // Talk to the GYRO_CONFIG register (1B hex) Wire.write(0x10); // Set the register bits as 00010000 (1000dps full scale) Wire.endTransmission(true); } void calculate_IMU_error() { // We can call this funtion in the setup section to calculate the accelerometer and gury data error. From here we will get the error values used in the above equations printed on the Serial Monitor. // Note that we should place the IMU flat in order to get the proper values, so that we then can the correct values // Read accelerometer values 200 times while (c < 200) { Wire.beginTransmission(MPU); Wire.write(0x3B); Wire.endTransmission(false); Wire.requestFrom(MPU, 6, true); AccX = (Wire.read() << 8 | Wire.read()) / 4096.0 ; AccY = (Wire.read() << 8 | Wire.read()) / 4096.0 ; AccZ = (Wire.read() << 8 | Wire.read()) / 4096.0 ; // Sum all readings AccErrorX = AccErrorX + ((atan((AccY) / sqrt(pow((AccX), 2) + pow((AccZ), 2))) * 180 / PI)); AccErrorY = AccErrorY + ((atan(-1 * (AccX) / sqrt(pow((AccY), 2) + pow((AccZ), 2))) * 180 / PI)); c++; } //Divide the sum by 200 to get the error value AccErrorX = AccErrorX / 200; AccErrorY = AccErrorY / 200; c = 0; // Read gyro values 200 times while (c < 200) { Wire.beginTransmission(MPU); Wire.write(0x43); Wire.endTransmission(false); Wire.requestFrom(MPU, 4, true); GyroX = Wire.read() << 8 | Wire.read(); GyroY = Wire.read() << 8 | Wire.read(); // Sum all readings GyroErrorX = GyroErrorX + (GyroX / 32.8); GyroErrorY = GyroErrorY + (GyroY / 32.8); c++; } //Divide the sum by 200 to get the error value GyroErrorX = GyroErrorX / 200; GyroErrorY = GyroErrorY / 200; // Print the error values on the Serial Monitor Serial.print("AccErrorX: "); Serial.println(AccErrorX); Serial.print("AccErrorY: "); Serial.println(AccErrorY); Serial.print("GyroErrorX: "); Serial.println(GyroErrorX); Serial.print("GyroErrorY: "); Serial.println(GyroErrorY); } void read_IMU() { // === Read acceleromter data === // Wire.beginTransmission(MPU); Wire.write(0x3B); // Start with register 0x3B (ACCEL_XOUT_H) Wire.endTransmission(false); Wire.requestFrom(MPU, 6, true); // Read 6 registers total, each axis value is stored in 2 registers //For a range of +-8g, we need to divide the raw values by 4096, according to the datasheet AccX = (Wire.read() << 8 | Wire.read()) / 4096.0; // X-axis value AccY = (Wire.read() << 8 | Wire.read()) / 4096.0; // Y-axis value AccZ = (Wire.read() << 8 | Wire.read()) / 4096.0; // Z-axis value // Calculating angle values using accAngleX = (atan(AccY / sqrt(pow(AccX, 2) + pow(AccZ, 2))) * 180 / PI) + 1.15; // AccErrorX ~(-1.15) See the calculate_IMU_error()custom function for more details accAngleY = (atan(-1 * AccX / sqrt(pow(AccY, 2) + pow(AccZ, 2))) * 180 / PI) - 0.52; // AccErrorX ~(0.5) // === Read gyro data === // previousTime = currentTime; // Previous time is stored before the actual time read currentTime = millis(); // Current time actual time read elapsedTime = (currentTime - previousTime) / 1000; // Divide by 1000 to get seconds Wire.beginTransmission(MPU); Wire.write(0x43); // Gyro data first register address 0x43 Wire.endTransmission(false); Wire.requestFrom(MPU, 4, true); // Read 4 registers total, each axis value is stored in 2 registers GyroX = (Wire.read() << 8 | Wire.read()) / 32.8; // For a 1000dps range we have to divide first the raw value by 32.8, according to the datasheet GyroY = (Wire.read() << 8 | Wire.read()) / 32.8; GyroX = GyroX + 1.85; //// GyroErrorX ~(-1.85) GyroY = GyroY - 0.15; // GyroErrorY ~(0.15) // Currently the raw values are in degrees per seconds, deg/s, so we need to multiply by sendonds (s) to get the angle in degrees gyroAngleX = GyroX * elapsedTime; gyroAngleY = GyroY * elapsedTime; // Complementary filter - combine acceleromter and gyro angle values angleX = 0.98 * (angleX + gyroAngleX) + 0.02 * accAngleX; angleY = 0.98 * (angleY + gyroAngleY) + 0.02 * accAngleY; // Map the angle values from -90deg to +90 deg into values from 0 to 255, like the values we are getting from the Joystick data.j1PotX = map(angleX, -90, +90, 255, 0); data.j1PotY = map(angleY, -90, +90, 0, 255); }

در ادامه به سراغ کد مربوط به گیرنده می‌رویم.

مطلب پیشنهادی:  پروژه اتصال سنسور DHT-22 به Arduino

کد بخش گیرنده

در ابتدا اجازه دهید در مورد اینکه داده‌ها چطور باید دریافت شوند فکر کنیم. در اینجا ما یک شماتیک ساده از بورد آردوینو و گیرنده‌ی NRF24L01 داریم. هر بورد آردوینویی را می‌توانیم در این شماتیک جای‌گذاری کنیم.

ساخت فرستنده‌ی RC با استفاده از آردوینو

در ادامه یک کد ساده برای دریافت داده‌ها داریم که داده‌ها را پس از دریافت بر روی مانیتور سریال نمایش می‌دهد. این نمایش دادن کمک می‌کند که از عملکرد صحیح دریافت و ارسال اطمینان حاصل کنیم.

در اینجا هم مانند بخش فرستنده، کتابخانه‌ی RF24 را اضافه می‌کنیم، متغیرها و پین‌ها را تعریف می‌کنیم، structure تعریف می‌کنیم و … .

در بخش setup، تنظیمات بخش radio communication را مانند بخش فرستنده قرار می‌دهیم. ماژول را هم به عنوان گیرنده تعریف می‌کنیم و از تابع ()radio.startListening استفاده می‌کنیم.

/*
DIY Arduino based RC Transmitter Project
== Receiver Code ==

by Dejan Nedelkovski, www.HowToMechatronics.com
Library: TMRh20/RF24, https://github.com/tmrh20/RF24/
*/
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 radio(10, 9); // nRF24L01 (CE, CSN)
const byte address[6] = "00001";

unsigned long lastReceiveTime = 0;
unsigned long currentTime = 0;

// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
byte j1PotX;
byte j1PotY;
byte j1Button;
byte j2PotX;
byte j2PotY;
byte j2Button;
byte pot1;
byte pot2;
byte tSwitch1;
byte tSwitch2;
byte button1;
byte button2;
byte button3;
byte button4;
};

Data_Package data; //Create a variable with the above structure

void setup() {
Serial.begin(9600);
radio.begin();
radio.openReadingPipe(0, address);
radio.setAutoAck(false);
radio.setDataRate(RF24_250KBPS);
radio.setPALevel(RF24_PA_LOW);
radio.startListening(); // Set the module as receiver
resetData();
}
void loop() {
// Check whether there is data to be received
if (radio.available()) {
radio.read(&data, sizeof(Data_Package)); // Read the whole data and store it into the 'data' structure
lastReceiveTime = millis(); // At this moment we have received the data
}
// Check whether we keep receving data, or we have a connection between the two modules
currentTime = millis();
if ( currentTime - lastReceiveTime > 1000 ) { // If current time is more then 1 second since we have recived the last data, that means we have lost connection
resetData(); // If connection is lost, reset the data. It prevents unwanted behavior, for example if a drone has a throttle up and we lose connection, it can keep flying unless we reset the values
}
// Print the data in the Serial Monitor
Serial.print("j1PotX: ");
Serial.print(data.j1PotX);
Serial.print("; j1PotY: ");
Serial.print(data.j1PotY);
Serial.print("; button1: ");
Serial.print(data.button1);
Serial.print("; j2PotX: ");
Serial.println(data.j2PotX); 
}

void resetData() {
// Reset the values when there is no radio connection - Set initial default values
data.j1PotX = 127;
data.j1PotY = 127;
data.j2PotX = 127;
data.j2PotY = 127;
data.j1Button = 1;
data.j2Button = 1;
data.pot1 = 1;
data.pot2 = 1;
data.tSwitch1 = 1;
data.tSwitch2 = 1;
data.button1 = 1;
data.button2 = 1;
data.button3 = 1;
data.button4 = 1;
}

در بخش تابع حلقه، با استفاده از تابع ()available هربار چک می‌کنیم که آیا داده‌ی جدیدی رسیده است یا خیر. اگر رسیده باشد، داده را می‌خوانیم و در یکی از متغیرهای structure ذخیره می‌کنیم. همزمان آن را بر روی serial monitor نیز نمایش می‌دهیم که صحت عملکرد مشخص شود.

مطلب پیشنهادی:  تولید تن صدا با آردوینو

با استفاده از یک تابع ()millis و یک جمله ی if، چک می‌کنیم که اگر رسیدن داده بیش از یک ثانیه طول کشید، متغیرها را به مقدار پیش‌فرض‌ آنها ریست می‌کنیم. دلیل انجام چنین کاری این است که از بروز رفتارهای ناخواسته در سیستم جلوگیری کنیم.

این روش دریافت داده را می‎توانیم برای پروژه‌های مختلف اجرا کنیم. مثلا برای پروژه‌ی کنترل ماشین رباتیکی که در ویدئوی اول جلسه دیدیم.

کنترل وایرلس ماشین رباتیکی با استفاده از RC transmitter

ساخت فرستنده‌ی RC با استفاده از آردوینو

کد گیرنده (ماشین رباتیکی)

به همان صورتی که توضیح دادیم کد بخش گیرنده می‌تواند نوشته شود، عمل می‌کنیم. یعنی کتابخانه‌های لازم را اضافه کرده و structure و radio communication را تعریف می‌کنیم . سپس در یک تابع حلقه (loop) مدام ورودی را چک می‌کنیم و در صورت نیاز به انجام کاری بخصوص از دستورات if استفاده می‌کنیم.

در این مثال خاص به عنوان نمونه ما شرط را این‌ گونه تعریف می‌کنیم که اگر مقدار متغیر مربوط به جوی استیک یک شد، کنترلر شروع به درایو کردن ماشین کند.

کنترل ربات حشره‌‌ای با استفاده از RC Transmitter

مثال دیگری که می‌توانیم بزنیم، ربات حشره‌ای است.

ساخت فرستنده‌ی RC با استفاده از آردوینو

کد گیرنده (ربات حشره‌‌ای)

مانند مثال‌های قبلی، این ربات را نیز می‌توانیم با استفاده از RC Transmitter کنترل کنیم. تنها کاری که باید انجام دهیم این است که ورودی دریافت شده از کنترلر را بخوانیم و بر اساس آن تصمیم بگیریم که ربات چه کاری انجام دهد؛ مثلا به جلو، چپ یا راست برود و یا گاز بگیرد و … .

ESC و Servo control با استفاده از RC Transmitter

به عنوان آخرین مثال هم نگاهی به RC Transmitter که ساخته‌ایم بیندازیم که در کنترل دیوایس‌های RC تجاری چگونه عمل می‌کند.

ساخت فرستنده‌ی RC با استفاده از آردوینو

معمولا در این گونه ابزارها باید موتور DC براشلس (بدون جاروبک) و یا سروو موتور درون آنها را کنترل کنیم. بنابراین داده‌ای که از طرف Transmitter دریافت می‌شود، با استفاده از کتابخانه‌ی Arduino Servo library و تبدیل ورودی به درجه‌ی چرخش موتور (از صفر تا ۱۸۰) منجر به کنترل سروو موتور خواهد شد و برای کنترل موتور براشلس نیز با استفاده از ESC و همان کتابخانه‌ی سروو موتور، سیگنال‌های PWM با فرکانس ۵۰ مگاهرتز می‌سازیم و آن را کنترل می‌کنیم. با تغییر دادن duty cycle سیگنال PWM از ۱۰۰۰ میکرو ثانیه به ۲۰۰۰ میکرو ثانیه، می‌توانیم RPM (دور موتور) را از صفر تا حداکثر مقدار آن افزایش داده و تغییر دهیم.

نکته‌ای که باید در اینجا ذکر کنیم این است که اگر از گیرنده‌های استاندارد RC استفاده کنیم، نمی‌توانیم آن را همزمان با ماژول NRF24L01 با فرکانس ۲.۴ گیگاهرتز ترکیب کنیم و لازم است اصلاحاتی را در طراحی و تنظیمات انجام دهیم. به این ترتیب خواهیم توانست سیگنال PWM یا PPM  متناسب با آن دیوایس را ساخته و آن را کنترل کنیم.

ساخت فرستنده‌ی RC با استفاده از آردوینو

/*
DIY Arduino based RC Transmitter Project
== Receiver Code - ESC and Servo Control ==

by Dejan Nedelkovski, www.HowToMechatronics.com
Library: TMRh20/RF24, https://github.com/tmrh20/RF24/
*/
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>

RF24 radio(10, 9); // nRF24L01 (CE, CSN)
const byte address[6] = "00001";

unsigned long lastReceiveTime = 0;
unsigned long currentTime = 0;

Servo esc; // create servo object to control the ESC
Servo servo1;
Servo servo2;
int escValue, servo1Value, servo2Value;

// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
byte j1PotX;
byte j1PotY;
byte j1Button;
byte j2PotX;
byte j2PotY;
byte j2Button;
byte pot1;
byte pot2;
byte tSwitch1;
byte tSwitch2;
byte button1;
byte button2;
byte button3;
byte button4;
};

Data_Package data; //Create a variable with the above structure

void setup() {
Serial.begin(9600);
radio.begin();
radio.openReadingPipe(0, address);
radio.setAutoAck(false);
radio.setDataRate(RF24_250KBPS);
radio.setPALevel(RF24_PA_LOW);
radio.startListening(); // Set the module as receiver
resetData();
esc.attach(9);
servo1.attach(3);
servo2.attach(4);
}
void loop() {
// Check whether we keep receving data, or we have a connection between the two modules
currentTime = millis();
if ( currentTime - lastReceiveTime > 1000 ) { // If current time is more then 1 second since we have recived the last data, that means we have lost connection
resetData(); // If connection is lost, reset the data. It prevents unwanted behavior, for example if a drone jas a throttle up, if we lose connection it can keep flying away if we dont reset the function
}
// Check whether there is data to be received
if (radio.available()) {
radio.read(&data, sizeof(Data_Package)); // Read the whole data and store it into the 'data' structure
lastReceiveTime = millis(); // At this moment we have received the data
}
// Controlling servos
servo1Value = map(data.j2PotX, 0, 255, 0, 180);
servo2Value = map(data.j2PotY, 0, 255, 0, 180);
servo1.write(servo1Value);
servo2.write(servo2Value);
// Controlling brushless motor with ESC
escValue = map(data.pot1, 0, 255, 1000, 2000); // Map the receiving value form 0 to 255 to 0 1000 to 2000, values used for controlling ESCs
esc.writeMicroseconds(escValue); // Send the PWM control singal to the ESC
}

void resetData() {
// Reset the values when there is no radio connection - Set initial default values
data.j1PotX = 127;
data.j1PotY = 127;
data.j2PotX = 127;
data.j2PotY = 127;
data.j1Button = 1;
data.j2Button = 1;
data.pot1 = 1;
data.pot2 = 1;
data.tSwitch1 = 1;
data.tSwitch2 = 1;
data.button1 = 1;
data.button2 = 1;
data.button3 = 1;
data.button4 = 1;
}

بسیار خب، این جلسه هم به اتمام رسید. امیدواریم که مطالب آن برای شما مفید بوده باشند. در صورتی که هر سوال یا نظری درباره‌ی این پروژه داشتید در بخش نظرات پایین صفحه با ما و سایر دوستانتان در میان بگذارید.

اگر آموزش ساخت فرستنده‌ی RC با آردوینو براتون مفید واقع شده ما را نیز دعا کنید و اگر خواستین می‌توانید از محتوا‌ی رایگان آموزشی حمایت مالی کنید. همچنین نظرات، پیشنهادات و درخواست‌های خود را در کامنت‌ها ⇓ بنویسید.

همچنین لطفا اپلیکیشن اندویدی ما را هم نصب کنید.

دانلود اپلیکیشن میکرو دیزاینر الکترونیک
اگر این نوشته‌ برایتان مفید بود ما را نیز دعا کنید و کامنت بنویسید.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *