در قسمت هفتم از آموزش آردوینو به انواع دادههای آردوینو در برنامه نویسی آردوینو، پرداختیم. در این قسمت قصد داریم به آموزش استفاده از اعداد ممیز شناور و آرایهها بپردازیم.
شیوه استفاده از اعداد ممیز شناور (Floating-Point Numbers)
به طور کلی، اعداد ممیز شناور برای مقادیر اعشاری استفاده میشوند. (این اعداد روشی برای نمایش مقادیر کسری هستند).
کد زیر نحوه نوشتن مقادیر ممیز شناور و مشکلاتی را که ممکن است در هنگام مقایسه مقادیر ممیز شناور با آن مواجه شوید را نشان میدهد:
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 | /* * Floating-point example * This sketch initialized a float value to 1.1 * It repeatedly reduces the value by 0.1 until the value is 0 */ float value = 1.1; void setup() { Serial.begin(9600); } void loop() { value = value - 0.1; // reduce value by 0.1 each time through the loop if( value == 0) { Serial.println("The value is exactly zero"); } else if(almostEqual(value, 0)) { Serial.print("The value "); Serial.print(value,7); // print to 7 decimal places Serial.println(" is almost equal to zero, restarting countdown"); value = 1.1; } else { Serial.println(value); } delay(250); } // returns true if the difference between a and b is small bool almostEqual(float a, float b) { const float DELTA = .00001; // max difference to be almost equal if (a == 0) return fabs(b) <= DELTA; if (b == 0) return fabs(a) <= DELTA; return fabs((a - b) / max(fabs(a), fabs(b))) <= DELTA; } |
در محاسبات اعداد اعشاری، ممکن است که مقادیر دارای کمی خطا باشند. این خطا به دلیل این است که اعداد اعشاری محدودهی بزرگی را پوشش میدهند؛ بنابراین مقدار آنها تقریبی است. به این خاطر، برای تست مقادیر، باید بررسی کنید که آیا مقادیر مربوطه در محدودهای از تلرانس قرار دارند یا خیر.
خروجی سریال مانیتور این اسکچ به شرح زیر است:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 1.00 0.90 0.80 0.70 0.60 0.50 0.40 0.30 0.20 0.10 The value -0.0000001 is almost equal to zero, restarting countdown 1.00 0.90 |
خروجی از 1.00 شروع میشود و به تعداد معکوس ادامه مییابد.
ممکن است انتظار داشته باشید که پس از مقدار 0.1، کد “The value is exactly zero” چاپ شود و سپس 0.1 از آن کم شود؛ اما مقدار (value) هرگز برابر با صفر (zero) نیست: if (value == 0).
تابع almostEqual بررسی میکند که متغیر value در محدودهای از مقدار موردنظر قرار دارد یا نه و اگر قرار داشته باشد، مقدار true را برمیگرداند. محدوده قابلقبول با ثابت DELTA تعیین میشود؛ شما میتوانید این مقدار را به اندازههای کوچکتر یا بزرگتر تغییر دهید. تابع fabs(مخفف floating-point absolute value) قدرمطلق یک متغیر اعشاری را برمیگرداند و از آن برای بررسی تفاوت بین پارامترهای داده شده استفاده میشود.
قبل از اینکه تابع almostEqual تفاوت بین a و b را با DELTA مقایسه کند، این تفاوت را با بیشینهی مقدار a یا b مقایسه میکند. این کار برای در نظر گرفتن این موضوع ضروری است که دقت اعداد اعشاری با توجه به بزرگی آنها، متغیر است. در واقع، از آنجایی که این کد مقداری را با صفر مقایسه میکند، این عبارت لازم نیست؛ زیرا منطق (logic) در دو کد قبلی وقتی که a یا b برابر با صفر است، اجرا میشود.
جدول 1 نشان میدهد که در orderهای مختلفی از بزرگی برای هر دو مقدار شروع (Start) و مقایسه (Comparison) چه اتفاقی میافتد. Equal At مقداری را نشان میدهد که توسط مقدار شروع به دست میآید وقتی که آنها به عنوان مساوی در نظر گرفته میشوند. Unscaled Difference تفاوت بین a و b را نشان میدهد وقتی که تابع almostEqual بگوید که مقادیر این دو تقریباً مساوی است.
همانطور که در جدول 1 مشاهده می کنید، زمانی که شما به مقدار 100 برسید، مقدار بدون مقیاس (unscaled) DELTA تا میزان 0.00001 افزایش می یابد.
ممیز شناور، اعداد را به صورت تقریبی نشان میدهد؛ زیرا فقط از 32 بیت برای نشان دادن مقادیر در یک محدوده بزرگ استفاده میکند. از این 32 بیت، 8 بیت مربوط به ضریب اعشاری و 24 بیت مربوط به علامت و مقدار است.
نکته✅
در برد آردوینو Uno، دادههای float و double یکسان هستند؛ اما داده double در بردهای 32 بیتی و بسیاری از پلتفرمهای دیگر، دقت بالاتری دارند.
به طور کلی، اگر شما کدی در یک پلتفرم دیگر وارد میکنید که از دادههای float و double استفاده میکند، حتماً باید بررسی کنید که برای درخواست شما دقت کافی وجود دارد یا خیر.
شیوه استفاده از آرایهها
در ادامه نحوه ایجاد آرایه و شیوه کار کردن با آنها را بررسی خواهیم کرد. به طور کلی، آرایهها ممکن است یک لیست ساده باشند یا میتوانند دو یا چند بعد داشته باشند. در ادامه یاد خواهید گرفت که چگونه اندازه آرایه را تعیین کنید و همچنین، چگونه به عناصر موجود در آرایه دسترسی پیدا کنید.
اسکچ زیر، دو آرایه ایجاد میکند: آرایهای از اعداد صحیح برای پینهای متصل به سوئیچها و آرایهای از پینهای متصل به LEDها که در شکل 1 نشان داده شده است:
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 | /* array sketch an array of switches controls an array of LEDs see Chapter 5 for more on using switches see Chapter 7 for information on LEDs */ int inputPins[] = {2, 3, 4, 5}; // create an array of pins for switch inputs int ledPins[] = {10, 11, 12, 13}; // create array of output pins for LEDs void setup() { for (int index = 0; index < 4; index++) { pinMode(ledPins[index], OUTPUT); // declare LED as output pinMode(inputPins[index], INPUT_PULLUP); // declare as input } } void loop() { for (int index = 0; index < 4; index++) { int val = digitalRead(inputPins[index]); // read input value if (val == LOW) // check if the switch is pressed { digitalWrite(ledPins[index], HIGH); // LED on if switch is pressed } else { digitalWrite(ledPins[index], LOW); // turn LED off } } } |
اگر با حالت ورودی (INPUT) در آردوینو آشنا هستید، ممکن است عادت داشته باشید که کلید را با یک مقاومت pull-up به ورودی متصل کنید، با استفاده از حالت INPUT_PULLUP، نیازی به این مقاومت در مدار شما نیست؛ زیرا در این حالت از مقاومتهای pull-up داخلی آردوینو استفاده میشود و نیازی به مقاومت خارجی نیست.
آرایه مجموعهای از مقادیر متوالی از یک نوع دادهای(متغیر) هستند. هر مقدار، یک عنصر (element) نامیده میشود. همچنین، تعداد عناصر را اندازه (size) آرایه مینامند.
آرایهها در زبان برنامهنویسی آردوینو یک روش معمول برای ذخیرهسازی گروهی از پینها هستند. در اینجا پینها به سوئیچها و LEDها متصل میشوند. بخشهای مهم این مثال شامل تعریف آرایه و دسترسی به عناصر آرایه هستند.
کد زیر یک آرایه از اعداد صحیح با چهار عنصر تعریف (یا ایجاد) میکند و هر عنصر را مقداردهی اولیه میکند. عنصر اول برابر با 2، عنصر دوم برابر با 3 و به همین ترتیب است:
1 | int inputPins[] = {2,3,4,5}; |
اگر مقادیر را هنگام تعریف یک آرایه مقداردهی اولیه نکنید (به عنوان مثال، وقتی که مقادیر تنها در زمان اجرای اسکچ در دسترس خواهند بود)، باید هر عنصر را به صورت جداگانه تنظیم کنید. شما میتوانید آرایه را به شکل زیر تعریف کنید:
1 | int inputPins[4]; |
اگر آرایهای را خارج از یک تابع تعریف کنید، این آرایه دارای چهار عنصر و مقدار اولیه هر عنصر، صفر در نظر گرفته میشود و اگر آرایه را داخل یک تابع مانند setup() یا loop() تعریف کنید، مقدار عناصر به صورت تصادفی (رندوم) انتخاب میشود.
عدد داخل کروشه ([ ]) اندازهی آرایه و تعداد عناصر را نشان میدهد. آرایه ذکر شده در مثال قبلی، دارای اندازهی چهار است و میتواند حداکثر چهار مقدار صحیح داشته باشد. اگر تعریف آرایه شامل مقداردهی اولیه باشد (همانند مثال اول)، میتوان اندازه آرایه را حذف کرد، زیرا کامپایلر با شمارش تعداد مقداردهیکنندهها، اندازه آرایه را تعیین میکند.
نکته✅
در واقع، زبان برنامهنویسی آردوینو به نحوی طراحی شده است که از زبانهای C و C++ پیروی کند. در این زبانها، آرایههایی که به صورت سراسری (خارج از توابع) تعریف میشوند، اگر مقداردهی اولیه نشوند، مقدار عناصر آنها، صفر میشوند.
عناصر آرایههایی که در تابع مقداردهی اولیه نمیشوند، تعریف نشده میمانند و معمولاً مقدار متغیرهای تعریف نشده مانند int i; صفر میشود؛ اما این موضوع قطعی نیست؛ بنابراین در کل، بهتر است که قبل از هر چیزی، متغیرها را مقداردهی اولیه کنید. این کار باعث جلوگیری از ایجاد مشکلات در آینده میشود و به بهبود خوانایی کد نیز کمک میکند.
اولین عنصر آرایه [0] arrayname است:
1 2 | int firstElement = inputPins[0]; // this is the first element inputPins[0] = 2; // set the value of the first element to 2 |
آخرین عنصر یک واحد کمتر از اندازه آرایه است؛ بنابراین برای یک آرایهی چهارعنصری، آخرین عنصر، عنصر 3 است:
1 | int lastElement = inputPins[3]; // this is the last element |
همانطور که اشاره شد، آخرین عنصر آرایهای با اندازه چهار، عنصر 3 یعنی [3] array و اولین عنصر آن [0] array است. به طور کلی، چهار عنصر این آرایه عبارت اند از:
1 | inputPins[0],inputPins[1],inputPins[2],inputPins[3] |
در اسکچ قبلی، چهارعنصر با استفاده از یک حلقه (لوپ) for قابلدسترسی هستند:
1 2 3 4 5 | for (int index = 0; index < 4; index++) { pinMode(ledPins[index], OUTPUT); // declare LED as output pinMode(inputPins[index], INPUT_PULLUP); // declare as input } |
این حلقه دارای متغیر شاخصی (index) است که مقادیر آن به این صورت است که از ۰ شروع میشود و تا ۳ ادامه پیدا میکند. دسترسی به یک عنصر که فراتر از اندازه واقعی آرایه باشد، اشتباه رایجی است. این یک باگ است که میتواند علائم مختلفی داشته باشد و باید مراقب باشیم تا از آن جلوگیری کنیم. یکی از راههای کنترل حلقههای خود، تعیین اندازه یک آرایه با استفاده از یک ثابت به صورت زیر است:
1 2 3 4 5 6 7 8 9 | const int PIN_COUNT = 4; // define a constant for the number of elements int inputPins[PIN_COUNT] = {2,3,4,5}; int ledPins[PIN_COUNT] = {10, 11, 12, 13}; /* ... */ for(int index = 0; index < PIN_COUNT; index++) { pinMode(ledPins[index], OUTPUT); pinMode(inputPins[index], INPUT_PULLUP); } |
نکته✅
در زبانهای برنامهنویسی C و C++، اگر سعی کنید دادهها را خارج از اندازه آرایه ذخیره کنید یا بخوانید، کامپایلر خطایی گزارش نمیدهد. البته این احتمال هم وجود دارد که اسکچ شما کرش کند؛ بنابراین، شما باید در استفاده از عناصری که در محدودههای مشخصی قرار دارند، دقت کنید.
استفاده از یک ثابت برای تعیین اندازه یک آرایه و ارجاع به عناصر آن در کد، کمک میکند تا کد شما در محدوده آرایه باقی بماند.
در کد آردوینو، یکی از کاربردهای آرایهها، نگهداری استرینگهای کاراکتری متنی است. این استرینگها با نام “استرینگهای کاراکتری” (character strings) یا بهاختصار “استرینگها” (strings) شناخته میشوند. یک استرینگ کاراکتری شامل یک یا چند کاراکتر است که کاراکتر null (مقدار 0) بهعنوان پایان استرینگ به این کاراکترها اضافه میشود.
نکته✅
در قسمت بعدی آموزش آردوینو درباره روشهای استفاده از استرینگها توضیح خواهیم داد. پس در ادامه حتماً همراه سیسوگ باشید.