در قسمت بیستم از آموزش آردوینو به بررسی دریافت دادههای باینری از آردوینو در کامپیوتر، ارسال مقادیر باینری از Processing به آردوینو و همچنین، ارسال مقادیر چندین پین آردوینو، پرداختیم. در این قسمت قصد داریم درباره ثبت اطلاعات آردوینو در یک فایل کامپیوتر و همچنین، ارسال داده به بیش از یک دستگاه سریال صحبت کنیم.
ثبت اطلاعات آردوینو در یک فایل کامپیوتر
اگر میخواهید بدانید که چگونه باید یک فایل ایجاد کنید و اطلاعات دریافتی از طریق پورت سریال آردوینو را در آن ذخیره کنید، این مطلب را از دست ندهید. بهعنوانمثال، اگر میخواهید مقادیر پینهای دیجیتال و آنالوگ را در فواصل زمانی منظم در یک فایل لاگ ذخیره کنید.
ارسال اطلاعات از آردوینو به کامپیوتر در قسمتهای قبلی توضیح داده شد. اسکچ پروسسینگ (Processing) که وظیفه ثبت دادهها در فایل را بر عهده دارد، بر اساس اسکچی است که در همان قسمت توضیح داده شد.
اسکچ Processing یک فایل ایجاد میکند (نام فایل از تاریخ و زمان فعلی گرفته میشود) در همان دایرکتوری که اسکچ Processing قرار دارد. پیامهای دریافتی از آردوینو به این فایل اضافه میشوند. با فشاردادن هر کلید، فایل ذخیره شده و برنامه بسته میشود.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | /* * ReceiveMultipleFieldsBinaryToFile_P * * portIndex must be set to the port connected to the Arduino * based on ReceiveMultipleFieldsBinary, this version saves data to file * Press any key to stop logging and save file */ import processing.serial.*; import java.util.*; import java.text.*; PrintWriter output; DateFormat fnameFormat = new SimpleDateFormat("yyMMdd_HHmm"); DateFormat timeFormat = new SimpleDateFormat("hh:mm:ss"); String fileName; Serial myPort; // Create object from Serial class short portIndex = 0; // select the com port, 0 is the first port char HEADER = 'H'; void setup() { size(200, 200); // Open whatever serial port is connected to Arduino. String portName = Serial.list()[portIndex]; println((Object[]) Serial.list()); println(" Connecting to -> " + portName); myPort = new Serial(this, portName, 9600); Date now = new Date(); fileName = fnameFormat.format(now); output = createWriter(fileName + ".txt"); // save the file in the sketch folder } void draw() { int val; if ( myPort.available() >= 15) // wait for the entire message to arrive { if( myPort.read() == HEADER) // is this the header { String timeString = timeFormat.format(new Date()); println("Message received at " + timeString); output.println(timeString); // get the integer containing the bit values val = readArduinoInt(); // print the value of each bit for (int pin=2, bit=1; pin <= 13; pin++){ print("digital pin " + pin + " = " ); output.print("digital pin " + pin + " = " ); int isSet = (val & bit); if (isSet == 0){ println("0"); output.println("0"); } else { println("1"); output.println("1"); } bit = bit * 2; // shift the bit } // print the six analog values for (int i=0; i < 6; i ++){ val = readArduinoInt(); println("analog port " + i + "=" + val); output.println("analog port " + i + "=" + val); } println("----"); output.println("----"); } } } void keyPressed() { output.flush(); // Writes the remaining data to the file output.close(); // Finishes the file exit(); // Stops the program } // return the integer value from bytes received on the serial port // (in low,high order) int readArduinoInt() { int val; // Data received from the serial port val = myPort.read(); // read the least significant byte val = myPort.read() * 256 + val; // add the most significant byte return val; } |
فراموش نکنید که باید portIndex را به پورت سریالی که به آردوینو متصل است، تنظیم کنید. اگر مقدار اشتباهی برای portIndex انتخاب کردید، خروجی اولیه اسکچ Processing را بررسی کنید، جایی که لیست پورتهای سریال موجود را چاپ میکند و پورت صحیح را انتخاب کنید.
نام پیشفرض فایل لاگ با استفاده از تابع DateFormat در Processing ایجاد میشود.
1 | DateFormat fnameFormat= new SimpleDateFormat("yyMMdd_HHmm"); |
برای ایجاد نام کامل فایل، از کدی استفاده میشود که مسیر ذخیرهسازی فایل و پسوند آن را به نام فایل اضافه میکند.
1 | output = createWriter(fileName + ".txt"); |
برای ایجاد فایل و خروج از اسکچ، میتوانید درحالیکه پنجره اصلی اسکچ پروسسینگ (Processing) فعال است، هر کلیدی را فشار دهید. اما کلید Escape را فشار ندهید؛ زیرا این کار باعث میشود اسکچ بدون ذخیرهسازی فایل به طور کامل بسته شود. فایل در همان دایرکتوری که اسکچ پروسسینگ در آن ذخیره شده است، ایجاد خواهد شد (اسکچ باید حداقل یکبار ذخیره شده باشد تا اطمینان حاصل شود که دایرکتوری وجود دارد). برای پیداکردن این دایرکتوری، از منوی Sketch گزینه Show Sketch Folder را انتخاب کنید.
createWriter تابع پروسسینگ (Processing) است که فایل را باز میکند؛ این تابع یک آبجکت (واحدی از قابلیتهای زمان اجرا (runtime)) به نام output ایجاد میکند که وظیفه مدیریت خروجی واقعی فایل را برعهده دارد. شما میتوانید محتویات فایل را با استفاده از قابلیتهای استاندارد مدیریت رشته در پروسسینگ بهصورت دلخواه تغییر دهید. برای مثال، نسخه زیر از تابع draw یک فایل جداشده با کاما تولید میکند که میتواند توسط یک صفحهگسترده (spreadsheet) یا دیتابیس (database) خوانده شود. باقی اسکچ پروسسینگ نیز میتواند همانند قبل باشد، اگرچه ممکن است بخواهید پسوند را از .txt به .csv تغییر دهید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | void draw() { int val; if (myPort.available() >= 15) // wait for the entire message to arrive { if (myPort.read() == HEADER) // is this the header { String timeString = timeFormat.format(new Date()); output.print(timeString); val = readArduinoInt(); // print the value of each bit for (int pin=2, bit=1; pin <= 13; pin++){ int isSet = (val & bit); if (isSet == 0){ output.print(",0"); } else { output.print(",1"); } bit = bit * 2; // shift the bit } // output the six analog values delimited by a comma for (int i=0; i < 6; i ++){ val = readArduinoInt(); output.print("," + val); } output.println(); } } } |
برای کسب اطلاعات بیشتر در مورد تابع createWriter، به صفحه زیر مراجعه کنید.
https://processing.org/reference/createWriter_.html
همچنین، پروسسینگ (Processing) شامل آبجکت Table برای ایجاد، دستکاری و ذخیرهسازی فایلهای CSV است.
ارسال داده به بیش از یک دستگاه سریال
بهعنوانمثال، اگر شما بخواهید دادهها را به یک دستگاه سریال مثل serial LCDارسال کنید، اما از پورت سریال به USB داخلی برای ارتباط با کامپیوتر خود استفاده میکنید.
روی بردی که بیش از یک پورت سریال دارد، این موضوع مشکلی نخواهد داشت. ابتدا شما باید برد خود را طبق شکل 1 به دستگاه سریال متصل کنید. سپس میتوانید دو پورت سریال را مقداردهی اولیه کنید و از Serial برای اتصال به کامپیوتر خود استفاده کنید:
1 2 3 4 5 | void setup() { // initialize two serial ports on a board that supports this Serial.begin(9600); // primary serial port Serial1.begin(9600); // Some boards have even more serial ports } |
✅نکته
در یک برد آردوینو مبتنی بر ATmega328، مانند آردوینو Uno که تنها یک پورت سریال سختافزاری دارد، اگر نیاز به استفاده از پورت سریال دیگری داشته باشید، باید یک پورت سریال مجازی ایجاد کنید. برای این کار میتوانید از کتابخانهی SoftwareSerial استفاده کنید.
دو پین دیجیتال آزاد را انتخاب کنید، یکی برای ارسال و دیگری برای دریافت و دستگاه سریال خود را به آنها متصل کنید طوری که پین ارسال (TX) به پین دریافت (RX) دستگاه مقابل متصل شود و برعکس. برای سناریوهایی که فقط در حال ارسال داده هستید، مانند زمانی که کاراکترها را روی یک نمایشگر LCD سریال نمایش میدهید، فقط کافیست پین ارسال (TX) را به پین دریافت (RX) دستگاه متصل کنید، همانطور که در شکل 2 نشان داده شده است که در آن پین 3 به عنوان پین ارسال انتخاب شده است.
در اسکچ خود، یک آبجکت از نوع SoftwareSerial ایجاد کنید و پینهایی را که بهعنوان پورت سریال انتخاب کردهاید به آن اختصاص دهید. در این مثال، ما یک آبجکت به نام serial_lcd ایجاد میکنیم و به آن دستور میدهیم که از پینهای 2 و 3 استفاده کند. حتی اگر قصد دریافت هیچ دادهای از این اتصال سریال را نداشته باشیم، همچنان باید یک پین برای دریافت مشخص کنیم. بنابراین، هنگامی که از پورت SoftwareSerial استفاده میکنید، نباید از پین 2 برای کار دیگری استفاده کنید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /* * SoftwareSerialOutput sketch * Output data to a software serial port */ #include <SoftwareSerial.h> const int rxpin = 2; // pin used to receive (not used in this version) const int txpin = 3; // pin used to send to LCD SoftwareSerial serial_lcd(rxpin, txpin); // new serial port on pins 2 and 3 void setup() { Serial.begin(9600); // 9600 baud for the built-in serial port serial_lcd.begin(9600); //initialize the software serial port also for 9600 } int number = 0; void loop() { serial_lcd.print("Number: "); // send text to the LCD serial_lcd.println(number); // print the number on the LCD Serial.print("Number: "); Serial.println(number); // print the number on the PC console delay(500); // delay half second between numbers number++; // to the next number } |
برای استفاده از اسکچ با یک پورت سریال سختافزاری داخلی، پینها را همانطور که در شکل 1 نشان داده شده است، متصل کنید، سپس این خطوط را از کد حذف کنید:
1 2 3 4 | #include <SoftwareSerial.h> const int rxpin = 2; const int txpin = 3; SoftwareSerial serial_lcd(rxpin, txpin); |
در نهایت، این خط را بهجای خطوط حذف شده اضافه کنید:
1 | #define serial_gps Serial1 |
(در صورت استفاده از یک پورت دیگر، بهجای Serial1 آن را تغییر دهید).
✅نکته
این اسکچ فرض میکند که یک نمایشگر LCD سریال به پین 3 همانطور که در شکل 2 نشان داده شده، متصل شده است و یک کنسول سریال به پورت داخلی متصل شده است. حلقه (loop) به طور مکرر همان پیام را روی هر دو نمایشگر نشان میدهد:
1 2 3 | Number: 0 Number: 1 ... |
میکروکنترلر Arduino حداقل یک پورت سریال سختافزاری داخلی دارد. در Arduino Uno، این پورت به اتصال سریال USB متصل است و همچنین، به پینهای 0 (دریافت) و 1 (ارسال) متصل شده است که به شما این امکان را میدهد که دستگاهی مانند نمایشگر سریال LCD را به Arduino متصل کنید. کاراکترهایی که از طریق آبجکتSerial ارسال میکنید روی LCD نمایش داده می شوند.
✅نکته
علاوه بر اتصال سریال USB داخلی (onboard )، برخی بردها از یک یا چند اتصال سریال مستقیم پشتیبانی میکنند. روی این بردها، پینهای 0 و 1 معمولاً به آبجکتSerial1 متصل هستند که به شما این امکان را میدهد تا همزمان با اینکه با دستگاه متصل به پینهای 0 و 1 داده مبادله میکنید، اتصال سریال USB به کامپیوتر خود را حفظ کنید. برخی بردها از پورتهای سریال اضافی روی مجموعهای دیگر از پینها پشتیبانی میکنند. تمام پینهایی که از ورودی و خروجی سریال پشتیبانی میکنند، علاوه بر اینکه پینهای دیجیتال عمومی هستند، توسط (UART) receiver-transmitter universal asynchronous که درون چیپ تعبیه شده است، پشتیبانی میشوند. این سختافزار وظیفه تولید دنبالهای از پالسهای زمانی دقیق و همچنین، تفسیر استریم (stream ) مشابهی که در بازگشت دریافت میکند را بر عهده دارد.
اگرچه بردهای مبتنی بر ARM SAMD (بردهای M0 وM4 ) از دو پورت سریال سختافزاری پشتیبانی میکنند و برد Mega چهار پورت از این نوع دارد، برد Arduino Uno و بیشتر بردهای مشابه مبتنی بر ATmega328 تنها یک پورت سریال دارند. در بردهای آردوینو Uno و بردهای مشابه آن، برای پروژههایی که به اتصال به دو یا چند دستگاه سریال نیاز دارند، باید از یک کتابخانه نرمافزاری استفاده کنید که پورتهای اضافی را شبیهسازی کند. یک کتابخانه “سریال نرمافزاری” به طور مؤثر یک جفت دلخواه از پینهای دیجیتال ورودی/خروجی را به یک پورت سریال جدید تبدیل میکند.
برای ساخت پورت سریال نرمافزاری خود، یک جفت پین را انتخاب میکنید که بهعنوان خطوط ارسال و دریافت پورت عمل خواهند کرد، به همان روشی که یک پورت سریال سختافزاری از پینهای اختصاصیافته خود استفاده میکند. در شکل 2، پینهای 3 و 2 نشان داده شدهاند، اما میتوان از هر پین دیجیتال موجود استفاده کرد، با برخی استثنائات برای بردهای خاص. بهتر است از پینهای 0 و 1 استفاده نکنید، زیرا این پینها قبلاً توسط پورت داخلی (built-in) هدایت میشدند.
سینتکس نوشتن داده به پورت نرمافزاری مشابه سینتکس نوشتن به پورت سختافزاری است. در برنامه نمونه، دادهها به هر دو پورت سخت افزاری و مجازی با استفاده از توابع print() و println() ارسال میشود:
1 2 3 4 | serial_lcd.print("Number: "); // send text to the LCD serial_lcd.println(number); // print the number on the LCD Serial.print("Number: "); Serial.println(number); // print the number on the PC console |
✅نکته
نیک گامون یک نسخهی ویژه از SoftwareSerial که فقط برای ارسال داده طراحی شده است، نگهداری میکند که به شما این امکان را میدهد تا زمانی که نیازی به دریافت داده ندارید، از تخصیص پین برای دریافت دادهها خودداری کنید.