در این آموزش میخواهیم با هم یاد بگیریم که چگونه با استفاده از بوردهای آردوینو یک فرستنده RC بسازیم. علت اینکه به سراغ چنین پروژهای آمدهایم این است که این کنترلر رادیویی میتواند جایگزین خوبی برای کنترلرهای وایرلس باشد و تقریبا در هر پروژهای کاربرد داشته باشد.
مقدمه
امروزه و با پیشرفت تکنولوژی، تقریبا میتوان هر پروژهی آردوینویی را با استفاده از تجهیزاتی مختصر و تنظیماتی مخصوص، به راحتی به صورت وایرلس کنترل کرد. فرستندهی RC که امروز با هم میسازیم نیز میتواند به راحتی جایگزین فرستندههای RC کارخانهای شود که در اسباببازیها، ماشینها، هواپیماهای تک سرنشین و … استفاده میشوند. برای کنترل این ابزارها کافیست سیگنالهای مناسب را از آنها دریافت کنیم. در ویدئوی فوق با ذکر چند مثال از جمله کنترل کردن یک ماشین رباتیکی که در آن از بورد آردوینو استفاده شده است، کنترل موتور DC براشلس و کنترل تعداد سروو موتور، توضیح داده میشود که این کنترل کردن به چه شکل انجام میشود.
ارتباط رادیویی در این کنترل بر پایهی ماژول گیرندهی NRF24L01 انجام میشود. این ماژول با استفاده از یک آنتن تقویت کنندهی سیگنال، میتواند در فضاهای باز تا برد حدود ۷۰۰ متری را پوشش دهد. دارای ۱۴ کانال است که ۶ کانال آن ورودیهای آنالوگ و ۸ کانال ورودیهای دیجیتال هستند.
داری دو عدد پتانسیومتر، دو عدد تاگل سوییچ، دو عدد ورودی اهرمی، شش عدد دکمه و یک واحد اندازهگیری داخلی است که این واحد مجهز به شتاب سنج و ژیروسکوپ است و به این ترتیب میتواند اهدافی که در حوالی کنترلر پرسه میزنند را نیز کنترل کند.
مدار مورد نیاز برای ساخت فرستندهی RC با استفاده از آردوینو
برای شروع پروژه، ابتدا به سراغ مدار آن برویم. قلب این کنترل یک بورد Arduino Pro Mini است که با استفاده از دو عدد باتری LiPo که ولتاژ حدود ۷.۴ ولت تولید میکنند، تغذیه میشود. این باتریها را میتوانیم مستقیما به پینهای RAW از آردوینو متصل کنیم. این پینها دارای رگولاتورهایی هستند که ولتاژ را به سطح ۵ ولت میبرند. (البته دقت داشته باشد که بورد Arduino Pro Mini دارای دو ورژن مختلف است که یکی همین موردی است که ما اینجا استفاده میکنیم و در ولتاژ ۵ ولت کار میکند، و نوع دیگر دارای ولتاژ کاری ۳.۳ ولت است)
از سوی دیگر، ماژول NRF24L01 نیاز به یک ولتاژ ۳.۳ ولتی دارد که پیشنهاد میشود که از یک منبع جداگانه و اختصاصی تامین شود. بنابراین باید یک ولتاژ رگولاتور جداگانه به باتریها متصل کنیم که ۷.۴ ولت را دریافت کرده و ولتاژ ۳.۳ ولت را برای ماژول بسازد. ضمنا برای افزایش پایداری این ولتاژ از یک خازن دیکوپلینگ هم در کنار ماژول NRF24L01 استفاده میکنیم. پایداری ولتاژ به پایداری انتقال ارتباط نیز کمک خواهد کرد. این ماژول از طریق پروتکل SPI با بورد آردوینو ارتباط برقرار میکند. (شتاب سنج و ژیروسکوپ از پروتکل I2C برای ارتباط با آردوینو استفاده میکند)
یک بار دیگر لیست تجهیزات مورد نیاز برای انجام پروژه را با هم مرور میکنیم.
- ماژول گیرنده NRF24L01 + PA + LNA
- پتانسیومتر
- سروو موتور
- تاگل سوییچ
- بازوی اهرمی به عنوان ورودی (جوی استیک)
- Arduino Pro Mini
- رگولاتور ولتاژ (مدل HT7333 3.3v یا AMS1117 3.3v )
قبل از تهیه قطعات بنظرم مقاله فروشگاه های اینترنتی قطعات الکترونیک را بخونید.
طراحی PCB
در این پروژه از تمام پینهای آنالوگ و دیجیتال بورد آردوینو استفاده خواهیم کرد. بنابراین اگر بخواهیم سیمها را معمولی وصل کنیم و قسمتهای مختلف را به هم متصل کنیم، در نهایت یک مدار بسیار شلوغ و تا حدودی گیج کننده خواهیم داشت. میل خودتان است که با این وضعیت راحت هستید یا خیر. اگر دوست دارید نتیجهی نهایی مرتبتر باشد این قسمت را همراه با ما انجام دهید. در غیر این صورت میتوانید به بخش بعدی بروید. ما میخواهیم با استفاده از نرمافزار آنلاین طراحی مداری که استفاده از آن نیز رایگان است؛ یعنی EasyEDA، یک PCB اختصاصی برای این پروژه طراحی کنیم.
نکتهای که ما در اینجا مد نظر میگیریم این است که بورد نهایی ما ابعاد متناسبی داشته باشد و بتوان آن را به راحتی و با دو دست نگه داشت. طوری که تمام کنترلرها و دکمهها در محدودهی انگشتان دستها باشند. به عبارت دقیقتر؛ میخواهیم کنترلر نهایی خوش دست باشد. با این هدف، گوشههای برد را گرد میکنیم و سوراخهایی ۳ میلیمتری ایجاد میکنیم که بعدا بتوانیم به کمک آنها بورد را بر روی چیز دیگری فیکس کنیم. پینهایی که برای پروگرم کردن بورد آردوینو نیاز داریم را در قسمت بالایی در نظر میگیریم تا هر زمان خواستیم آن را پروگرم کنیم پینها به راحتی در دسترس باشند. پینهای Rx و Tx آردوینو را نیز برای اتصال به جوی استیکها در نظر میگیریم. البته اتصال این دو پین باید در هنگام پروگرم کردن آردوینو قطع شود بنابراین آنها را به نحوی طراحی میکنیم که بتوان به کمک جامپر اتصالشان را قطع و وصل کرد.
نکتهی مهمی که در همین ابتدا باید یادآور شویم این است که اگر نسخهی بورد Arduino Pro Mini مورد استفاده ی شما با بورد ما متفاوت است؛ تغییرات متناسب را در طراحی PCB لحاظ کنید.
در تصویر زیر سه نسخهی مختلف از طراحی PCB را بر اساس ورژنهای مختلف بورد و رگولاتور ولتاژ میتوانیم ببینیم و مقایسه کنیم.
در این لینک میتوانید به فایل پروژهی این طراحی PCB دسترسی داشته باشید. اگر بر روی لینک کلیک کنید، PCBهای متناسب با نسخههای مختلف بورد باز خواهند شد که هر کدام را که متناسب با بورد خودتان است میتوانید انتخاب و استفاده کنید. فایلهای Gerber این طراحیها که برای ساخت PCB آماده هستند را در ادامه برایتان قرار دادهایم.
- Arduino based RC Transmitter PCB Gerber file V1
- Arduino based RC Transmitter PCB Gerber file V2
- Arduino based RC Transmitter PCB Gerber file V3
فایلهای فوق را میتوانید دانلود کنید و سپس به راحتی در Gerber viewer بکشید و باز کنید. معمولا در این مرحله مشکلی نخواهیم داشت و پس از باز شدن فایل میتوانیم تنظیمات بیشتر را برای PCB دلخواهمان انجام دهیم. مثلا تغییر رنگ PCB و … .
نکته خیلی مهم: متاسفانه معمولا در ایران فایل استاندارد Gerber قبول نمیکنند قبل از ارسال فایل به شرکت باهاشون صحبت کنید.
همانطور که در تصویر زیر میبینید، همه چیز همانطور شده است که خودمان تنظیم کرده بودیم.
سوار کردن قطعات روی PCB
در این مرحله میتوانیم قطعات مدار مورد نظر برای پروژه را بر روی PCB قرار دهیم. ما در اینجا با لحیم کردن پین هدرهای بورد Arduino Pro Mini شروع میکنیم. برای آنکه در هنگام لحیم کردن در جای خود فیکس باشند و تکان نخورند میتوانید از یک برد بورد کمک بگیرید.
برخی بوردهای Pro Mini در سمت دیگرشان نیز پینهایی دارند که بسته به شرکت تولید کننده بورد، محل این پینها میتواند با هم متفاوت باشد.
مثلا مدلی که ما در اینجا داریم در هر سمت دارای ۵ پین است (پین GND این ردیف پینها را بی خیال میشویم چون در PCB، از محل مربوط به قرارگرفتن آنها trace عبور دادهایم) پس از لحیم کردن بورد آردوینو و فیکس کردن پایههای آن، نوبت به ماژولهای ژیروسکوپ و شتابسنج میرسد که دقیقا در بغل آردوینو قرار میگیرند.
سپس رگولاتور ولتاژ ۳.۳ ولتی را با یک خازن در کنار آن لحیم میکنیم. خازن دیگری را نیز در کنار ماژول NRF24L01 قرار میدهیم. این ماژول هم سه مدل مختلف دارد که از هر کدام از آنها میتوانیم برای این پروژه استفاده کنیم.
در ادامه به سراغ پینهای پروگرم کردن آردوینو، Rx و Tx و پینهای تامین تغذیه و … میرویم.
پس از آنها نوبت به پتانسیومترها میرسد که چون پایههای آنها نسبتا کوتاه است از پین هدر کمک میگیریم.
تاگل سوییچها و جوی استیکها را نیز هر کدام در جای خود قرار داده و لحیم میکنیم.
و در نهایت به کلیدهای فشاری میرسیم. متاسفانه طول پایههای این کلیدها نیز کوتاه است و برای لحیم کردنشان باید از پین هدر کمک بگیریم.
بسیار خب. کار سر هم کردن PCB و سوار کردن قطعات در این لحظه تمام است و میتوانیم به سراغ مرحلهی بعد یعنی ایجاد یک پوشش یا محفظه برای آن برویم. ما در اینجا از محفظهی شفاف استفاده میکنیم چون ترجیح میدهیم که PCB و جزییات آن مشخص باشند.
پیشنهاد ما استفاده از پلاستیکهای ضخیم و فشردهای مانند اکریلیک است که به عنوان مثال قطر ۴ میلیمتری آنها برای کار ما مناسب خواهد بود. البته در ابتدا که این اکریلیکها را تهیه کنید یک لایه محافظ بر روی آنها کشیده شده است که رنگ آبی دارد؛ اما اگر آن لایه را بردارید، خود پلاستیک کاملا شفاف و بی رنگ است. ایدهی ما برای محفظهای که میخواهیم محافظ بوردمان باشد به این شکل است که دو لایه از این پلاستیک را دقیقا به شکل خود PCB برش دهیم و در بالا و پایین آن قرار دهیم که تا حدودی از PCB در برابر آسیبهای احتمالی محافظت کنند.
بسیار خب پس PCB را روی اکریلیک قرار داده و شکل دور آن را میکشیم. سپس با استفاده از اره شکل کشیده شده را برش داده و جدا میکنیم.
پس از جدا کردن در صورت نیاز گوشهها و لبهها را با سوهان صاف میکنیم تا تیز و برنده نباشند.
پس از آن محلهایی که برای عبور قطعات (مثلا جوی استیکها یا کلیدها) نیاز داریم را علامت میزنیم تا برش دهیم. این محلها را با استفاده از دریل با قطر مناسب ایجاد کرده و سوراخها را ایجاد میکنیم.
فقط به قطر سوراخها حتما دقت داشته باشید چون با هم متفاوت هستند. مثلا برای پتانسیومترها یا تاگل سوییچها باید از دریل ۶ میلیمتری و برای جوی استیکها باید از دریل ۲۵ میلیمتری استفاده کنیم. پس از ایجاد سوراخها مجددا با کمک سوهان ناهمواریها را برطرف میکنیم.
قبل از اینکه کاوری که ساختهایم روی بورد فیکس کنیم، لازم است این نکته را هم یادآوری کنیم که پین هدر مربوط به منبع تغذیه را برعکس لحیم کردهایم تا از پشت بورد که محل قرارگیری باتریهاست در دسترس باشند.
در این مرحله کاور آبی رنگ روی پلاستیک را جدا میکنیم و نتیجه بسیار خوب است؛ چون در حین مراحل کاری که تا اینجا داشتهایم محافظ باعث شده است که پلاستیک کاملا شفاف و تمیز بماند.
با استفاده از پیچ و مهرههای مناسب، کاور را روی بورد فیکس میکنیم و پس از آن پتانسیومترها را نیز روی بورد نصب میکنیم. علت اینکه تا این لحظه آنها را فیکس نکردهایم این است که دقیقا نمیدانستیم که باید در چه ارتفاعی قرار بگیرند که پس از نصب محافظ این ارتفاع مشخص میشود.
در سمت دیگر، نگه دارندهی باتری را نیز با پیچ و مهره فیکس میکنیم و در نهایت محافظ قسمت پشتی را نیز به همین روش قسمت رویی فیکس میکنیم.
ریزه کاریهای نهایی مانند نصب آنتن ماژول NRF24l01، نصب جوی استیکها و … را نیز انجام میدهیم. به تدریج فرستندهی RC ما در حال آماده شدن میباشد.
آخرین کاری که باید انجام دهیم پروگرم کردن بورد آردوینو است. برای پروگرم کردن بورد Pro Mini به یک اینترفیس USB به Serial UART نیاز داریم. این اینترفیس از طریق همان درگاهی که برای پروگرم کردن روی بورد در نظر گرفته بودیم (در قسمت رویی بورد) به آردوینو متصل میشود.
در لپتاپ، Arduino IDE را باز میکنیم و بورد Arduino Pro یا Pro Mini را انتخاب میکنیم. تنظیمات پورتها را نیز انجام میدهیم و نوع پروگرم کردن را بر روی حالت USBasp قرار میدهیم.
حالا همه چیز آماده است و فقط باید کد را بر روی بورد آپلود کنیم.
کدهای لازم برای 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); }
در ادامه به سراغ کد مربوط به گیرنده میرویم.
کد بخش گیرنده
در ابتدا اجازه دهید در مورد اینکه دادهها چطور باید دریافت شوند فکر کنیم. در اینجا ما یک شماتیک ساده از بورد آردوینو و گیرندهی NRF24L01 داریم. هر بورد آردوینویی را میتوانیم در این شماتیک جایگذاری کنیم.
در ادامه یک کد ساده برای دریافت دادهها داریم که دادهها را پس از دریافت بر روی مانیتور سریال نمایش میدهد. این نمایش دادن کمک میکند که از عملکرد صحیح دریافت و ارسال اطمینان حاصل کنیم.
در اینجا هم مانند بخش فرستنده، کتابخانهی 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
کد گیرنده (ماشین رباتیکی)
به همان صورتی که توضیح دادیم کد بخش گیرنده میتواند نوشته شود، عمل میکنیم. یعنی کتابخانههای لازم را اضافه کرده و structure و radio communication را تعریف میکنیم . سپس در یک تابع حلقه (loop) مدام ورودی را چک میکنیم و در صورت نیاز به انجام کاری بخصوص از دستورات if استفاده میکنیم.
در این مثال خاص به عنوان نمونه ما شرط را این گونه تعریف میکنیم که اگر مقدار متغیر مربوط به جوی استیک یک شد، کنترلر شروع به درایو کردن ماشین کند.
کنترل ربات حشرهای با استفاده از RC Transmitter
مثال دیگری که میتوانیم بزنیم، ربات حشرهای است.
کد گیرنده (ربات حشرهای)
مانند مثالهای قبلی، این ربات را نیز میتوانیم با استفاده از RC Transmitter کنترل کنیم. تنها کاری که باید انجام دهیم این است که ورودی دریافت شده از کنترلر را بخوانیم و بر اساس آن تصمیم بگیریم که ربات چه کاری انجام دهد؛ مثلا به جلو، چپ یا راست برود و یا گاز بگیرد و … .
ESC و Servo control با استفاده از RC Transmitter
به عنوان آخرین مثال هم نگاهی به RC Transmitter که ساختهایم بیندازیم که در کنترل دیوایسهای RC تجاری چگونه عمل میکند.
معمولا در این گونه ابزارها باید موتور DC براشلس (بدون جاروبک) و یا سروو موتور درون آنها را کنترل کنیم. بنابراین دادهای که از طرف Transmitter دریافت میشود، با استفاده از کتابخانهی Arduino Servo library و تبدیل ورودی به درجهی چرخش موتور (از صفر تا ۱۸۰) منجر به کنترل سروو موتور خواهد شد و برای کنترل موتور براشلس نیز با استفاده از ESC و همان کتابخانهی سروو موتور، سیگنالهای PWM با فرکانس ۵۰ مگاهرتز میسازیم و آن را کنترل میکنیم. با تغییر دادن duty cycle سیگنال PWM از ۱۰۰۰ میکرو ثانیه به ۲۰۰۰ میکرو ثانیه، میتوانیم RPM (دور موتور) را از صفر تا حداکثر مقدار آن افزایش داده و تغییر دهیم.
نکتهای که باید در اینجا ذکر کنیم این است که اگر از گیرندههای استاندارد RC استفاده کنیم، نمیتوانیم آن را همزمان با ماژول NRF24L01 با فرکانس ۲.۴ گیگاهرتز ترکیب کنیم و لازم است اصلاحاتی را در طراحی و تنظیمات انجام دهیم. به این ترتیب خواهیم توانست سیگنال PWM یا PPM متناسب با آن دیوایس را ساخته و آن را کنترل کنیم.
/* 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; }
بسیار خب، این جلسه هم به اتمام رسید. امیدواریم که مطالب آن برای شما مفید بوده باشند. در صورتی که هر سوال یا نظری دربارهی این پروژه داشتید در بخش نظرات پایین صفحه با ما و سایر دوستانتان در میان بگذارید.
- منبع: ترجمه از سایت howtomechatronics.com
اگر آموزش ساخت فرستندهی RC با آردوینو براتون مفید واقع شده ما را نیز دعا کنید و اگر خواستین میتوانید از محتوای رایگان آموزشی حمایت مالی کنید. همچنین نظرات، پیشنهادات و درخواستهای خود را در کامنتها ⇓ بنویسید.
اگر این نوشته برایتان مفید بود لطفا کامنت بنویسید.
سلام
من این پرژه رو کامل مو به مو انجام دادم ولی هیچ کدوم از خروجی ها خروجی ندارن و از سلامت بودن وسایلم اطمینان دارم اگر می شود راه نمایی کنید.
باید کتابخانه های که فراخوان شده رو نصب کرد اینا بصورت پیشفرض نصب نشدن
سلام . پروژه رو میشه با آردوینو نانو پروگرام کرد ؟
سلام
اون قطعه بالاتر از جوی استیک که دوتا پین داره RXI ,TXO چیه؟؟
ممنون اگه پاسخ بدین
کلید باتن هایی هستند که داخل جواستیک بکار رفته
سلام.از این دستگاه.میشه برای دسته بازی کامپیوتر استفاده کرد؟
سلام وقتتون بخیر
بجای arduino pro mini از چه مدل دیگه ای میشه استفاده کرد؟
لطفا جواب بدید
با تشکر
سلام منم هر کاری بگید انجام دادم ولی کار نکرد
گفتم شاید ماژول ها خراب هستند رفتم دوتای دیگه خریدم
بازم کار نکرد گفتم شاید برد اردینو خرابه رفتم دوتا برد نانو و دوتا پرو مینی خریدم بازم کار نکرد هرچی کد بود توی سایت روشون امتحان کردم جواب نداد
بارها بارها سیم کشی ها رو از اول کشیدم
کدها آپلود میشدن تو سریال مانیتور هم داده ها نشون داده میشه ولی کار نمیکنه یه بار کار کرد با یه فرمان کل سروها حرکت میکردند
فقط مونده روی یه کامپیوتر دیگه امتحان کنم دیگه حوصله ای هم نمونده برام واقعا خستم کرده