افزایش دقت با سنسور دما در آردوینو
پشتیبانی از اعداد ممیز شناور(floating-point) یک پیشرفت محسوب می شود اما به طور عمده دقت خروجی برنامه ی ما را افزایش می دهد. ما با استفاده از برخی ترفندهای ریاضی عدد صحیح می توانستیم به یک اثر مشابه دست پیدا کنیم. اما اکنون ما حتی می خواهیم یک چیز پیشرفته تر را اضافه کنیم که با استفاده از نرم افزار نمی توانیم آن را شبیه سازی کنم: یک سنسور دما.
وقتی به شما می گوییم که صوت در هوا با سرعت 343 متر بر ثانیه حرکت می کند، حرف خیلی دقیقی نزده ایم زیرا سرعت صوت ثابت(constant) نیست و به دمای هوا وابسته است. اگر ما دما را به حساب نیاوریم، این خطا(error ) می تواند تا 12 درصد قابل توجه باشد. ما با استفاده از فرمول ساده ی زیر، سرعت واقعی صوت یعنی C را به دست می آوریم:
$$C = 331.5 + (0.6 * t)$$
برای استفاده از این فرمول، ما تنها باید دمای فعلی را به سلسیوس در آن قرار دهیم.
ما قصد داریم از سنسور دمای محیط TMP36 ساخت شرکت Analog Devices استفاده کنیم. زیرا قیمت آن ارزان است و استفاده از آن آسان است. برای دانلود فایل اطلاعات این قطعه اینجا کلیک کنید. برای متصل کردن قطعه ی TMP36 به آردوینو، اتصال به زمین(ground) و برق(power) آردوینو را به پین های متناظر قطعه ی TMP36 متصل کنید. سپس پین سیگنال سنسور را به پین A0 متصل کنید(که پین آنالوگ شماره 0 است).
همان طور که ممکن است از روی نام سازنده حدس زده باشید، قطعه ی TMP36 یک دستگاه آنالوگ است: این قطعه بسته به دمای فعلی، ولتاژ پین سیگنال خود را تغییر می دهد. هرچه که دما بیشتر شود، ولتاژ مورد نظر نیز بیشتر می شود. این برای ما یک فرصت عالی است که یاد بگیریم چگونه از پین های IO آنالوگ آردوینو استفاده کنیم. بنابراین اجازه دهید یک سری کد را مشاهده کنیم که از این سنسور استفاده می کنند:
const unsigned int TEMP_SENSOR_PIN = A0;
const float SUPPLY_VOLTAGE = 5.0;
const unsigned int BAUD_RATE = 9600;
void setup() {
Serial.begin(BAUD_RATE);
}
void loop() {
const float tempC = get_temperature();
const float tempF = (tempC * 9.0 / 5.0) + 32.0;
Serial.print(tempC);
Serial.print(" C, ");
Serial.print(tempF);
Serial.println(" F");
delay(1000);
}
const float get_temperature() {
const int sensor_voltage = analogRead(TEMP_SENSOR_PIN);
const float voltage = sensor_voltage * SUPPLY_VOLTAGE / 1024;
return (voltage * 1000 - 500) / 10;
}
این کد در آدرس زیر قرار دارد:
InputDevices/Temperature/SensorTest/SensorTest.ino
در دو خط اول، ما چند ثابت(constant) برای پین آنالوگی که سنسور به آن متصل شده است و برای تغذیه ولتاژ آردوینو را تعریف می کنیم. سپس متد setup را تعریف کرده ایم و پس از آن متد loop قرار دارد که هر ثانیه یکبار دمای فعلی را در خروجی نشان می دهد. تمام منطق( logic) سنسور در داخل متد get_temperature قرار گرفته است. این متد، دما را به درجه ی سلسیوس برمی گرداند و ما آن را به مقادیر فارنهایت نیز تبدیل می کنیم.
اگر به یاد داشته باشید، برای سنسور PING ما تنها به یک پین دیجیتال نیاز داشتیم که می توانست HIGH یا LOW باشد. اما پین های آنالوگ متفاوت هستند و ولتاژی از 0 ولت تا ولتاژ منبع تغذیه فعلی (که معمولا 5 ولت است) را ارائه می دهند.
ما می توانیم با استفاده از متد analogRead، که یک مقدار بین 0 تا 1023 را برمی گرداند، پین های آنالوگ آردوینو را بخوانیم. علت اینکه متد analogRead یک مقدار بین 0 تا 1023 را برمی گرداند، این است که پین های آنالوگ دارای 10 بیت رزولوشن(resolution ) هستند(\( 1024 = 2 ^{10}\)). ما از این در خط 20 استفاده می کنیم تا ولتاژ فعلی تامین شده توسط قطعه ی TMP36 را قرائت کنیم.
چگونه داده های سنسور را رمز گذاری کنیم؟
رمز گذاری داده های سنسور مشکلی است که اغلب باید در پروژه های آردوینو حل شود؛ زیرا معمولا تمام داده های خوبی که ما جمع آوری می کنیم باید توسط برنامه هایی که روی کامپیوترهای عادی اجرا می شوند، تفسیر شوند. وقتی که ما یک فرمت داده(data format) را تعریف می کنیم، باید چند چیز را در نظر بگیریم. از جمله اینکه ، این فرمت نباید حافظه ارزشمند آردوینو را هدر دهد. در مورد ما، می توانیم از XML برای رمزگذاری داده های سنسور استفاده کنیم:
<sensor-data>
<temperature>30.05</temperature>
<distance>51.19</distance>
</sensor-data>
بدیهی است که این انتخاب خوبی نیست، زیرا اکنون ما داریم بخش هایی از حافظه را برای ایجاد ساختار فرمت فایل هدر می دهیم. علاوه بر این، برنامه ی دریافت کننده باید از یک تجزیه کننده ی XML برای تفسیر داده ها استفاده کند. اما ما نباید افراط کنیم. به همین علت ما باید از فرمت های باینری، تنها اگر ضروری هستند یا اینکه برنامه ی دریافت کننده ی ما داده های باینری را مورد استفاده قرار می دهد، استفاده کنیم. در مجموع، استفاده از فرمت های داده ای که در آن مقادیر با یک کاراکتر از یکدیگر جدا می شوند مانند فرمت CSV اغلب بهترین انتخاب هستند.
تنها یک مشکل باقی مانده است: ما باید مقدار برگردانده شده توسط متد analogRead را به یک مقدار ولتاژ حقیقی تبدیل کنیم، بنابراین ما ولتاژ تغذیه ی آردوینو را بدانیم. این ولتاژ معمولا 5 ولت است اما مدل هایی از آردوینو مثل Arduino Pro وجود دارند که از 3.3 ولت استفاده می کنند. پس ما باید یک ثابت به نام SUPPLY_VOLTAGE را بر این اساس تعریف کنیم. ما می توانیم خروجی پین آنالوگ مذکور را بر 1024 تقسیم کنیم و آن را در ولتاژ تغذیه(SUPPLY_VOLTAGE) ضرب کنیم. ما این کار را در خط 21 انجام داده ایم.
اکنون ما باید ولتاژی که این سنسور به ما می دهد را به درجه ی سلسیوس تبدیل کنیم. در فایل مشخصات این سنسور، فرمول زیر را مشاهده می کنیم:
T = ((sensor output in mV) - 500) / 10
ما باید 500 میلی ولت را از خروجی سنسور(sensor output) کم کنیم؛ زیرا سنسور همواره یک ولتاژ مثبت را برمی گرداند. به این طریق، ما می توانیم دماهای منفی را نیز ارائه دهیم. رزولوشن سنسور 10 میلی ولت است، بنابراین ما باید سمت راست فرمول بالا را بر 10 تقسیم کنیم.
مقدار ولتاژ 750 میلی ولت به دمای زیر تعلق خواهد داشت:
(750 - 500) / 10 = 25°C
می توانید این موضوع را در کدهای خط 22 مشاهده کنید. اکنون برنامه را کامپایل کنید و آن را در آردوینو آپلود کنید و در سریال مانیتور(serial monitor) چیزی شبیه به زیر را خواهید دید:
20.80 C, 69.44 F
20.80 C, 69.44 F
20.31 C, 68.56 F
20.80 C, 69.44 F
20.80 C, 69.44 F
همان طور که مشاهده می کنید، سنسور برای آماده شدن کمی به زمان نیاز دارد اما نتایج آن خیلی زود پایدار می شوند.به هر حال، ما همواره نیاز داریم تا یک وقفه ی(delay) کوتاه بین دو فراخوانی متد analogRead قرار دهیم، زیرا سیستم آنالوگ داخلی آردوینو بین دو قرائت، به کمی زمان نیاز دارد(0.0001 ثانیه در برد Uno). بنابراین ما از یک وقفه ی یک ثانیه ای برای اینکه خروجی(output) ساده تر قرائت شود استفاده می کنیم. یک علت دیگرش این است که نمی خواهیم دما به سرعت تغییر کند. در غیر این صورت، یک وقفه ی یک میلی ثانیه ای کافی است.
اکنون ما دو مدار جداگانه داریم: یکی برای محاسبه ی فاصله و یکی برای محاسبه ی دما. در تصویر 17 ما این دو مدار را با یکدیگر ترکیب کرده ایم که می توانید مشاهده کنید. در این مدار، سنسورهای TMP36 و PING با یکدیگر کار می کنند. در تصویر 18 تصویر واقعی این مدار نشان داده شده است.
با استفاده از برنامه ی زیر، مدار مذکور فعال خواهد شد:
const unsigned int TEMP_SENSOR_PIN = A0;
const float SUPPLY_VOLTAGE = 5.0;
const unsigned int PING_SENSOR_IO_PIN = 7;
const float SENSOR_GAP = 0.2;
const unsigned int BAUD_RATE = 9600;
float current_temperature = 0.0;
unsigned long last_measurement = millis();
void setup() {
Serial.begin(BAUD_RATE);
}
void loop() {
unsigned long current_millis = millis();
if (abs(current_millis - last_measurement) >= 1000) {
current_temperature = get_temperature();
last_measurement = current_millis;
}
Serial.print(scaled_value(current_temperature));
Serial.print(",");
const unsigned long duration = measure_distance();
Serial.println(scaled_value(microseconds_to_cm(duration)));
}
long scaled_value(const float value) {
float round_offset = value < 0 ? -0.5 : 0.5;
return (long)(value * 100 + round_offset);
}
const float get_temperature() {
const int sensor_voltage = analogRead(TEMP_SENSOR_PIN);
const float voltage = sensor_voltage * SUPPLY_VOLTAGE / 1024;
return (voltage * 1000 - 500) / 10;
}
const float microseconds_per_cm() {
return 1 / ((331.5 + (0.6 * current_temperature)) / 10000);
}
const float sensor_offset() {
return SENSOR_GAP * microseconds_per_cm() * 2;
}
const float microseconds_to_cm(const unsigned long microseconds) {
const float net_distance = max(0, microseconds - sensor_offset());
return net_distance / microseconds_per_cm() / 2;
}
const unsigned long measure_distance() {
pinMode(PING_SENSOR_IO_PIN, OUTPUT);
digitalWrite(PING_SENSOR_IO_PIN, LOW);
delayMicroseconds(2);
digitalWrite(PING_SENSOR_IO_PIN, HIGH);
delayMicroseconds(5);
digitalWrite(PING_SENSOR_IO_PIN, LOW);
pinMode(PING_SENSOR_IO_PIN, INPUT);
return pulseIn(PING_SENSOR_IO_PIN, HIGH);
}
این کد در آدرس زیر قرار دارد:
InputDevices/Ultrasonic/PreciseSensor/PreciseSensor.ino
(تصویر 17: سنسورهای TMP36 و PING با یکدیگر کار می کنند)
(تصویر 18: مدار اصلی)
کدهای بالا، تریکبی از برنامه های سنسورهای PING و TMP36 است. و تعداد کمی از کدها تغییر داده شده اند. که عبارتند از:
1. تابع microseconds_per_cm جایگزین ثابت MICROSECONDS_PER_CM شده است؛ این تابع مشخص می کند که صوت بسته به دمای فعلی، به طور داینامیک یک سانتی متر را در چند میکروثانیه طی می کند.
2. به دلیل اینکه دمای فعلی اغلب معمولا تغییر نمی کند یا به سرعت تغییر نمی کند، ما آن را به صورت آنی اندازه گیری نمی کنیم بلکه فقط هر یک ثانیه یک بار آن را اندازه گیری می کنیم. ما در خط 7، از متد millis برای به دست آوردن تعداد میلی ثانیه هایی که از زمان استارت شدن آردوینو گذشته است، استفاده می کنیم. در خط های 15 تا 19 ما بررسی می کنیم که آیا از وقتی که آخرین اندازه گیری صورت گرفته است، یک ثانیه گذشته است یا نه. اگر یک ثانیه گذشته باشد، ما دوباره دمای فعلی را اندازه گیری می کنیم.
3.ما دیگر داده های سنسور را در پورت سریال(serial port) تبدیل به اعداد ممیز شناور(floating-point) نمی کنیم، بلکه به جای آن از اعداد صحیح مقیاس پذیر(scaled integer values) استفاده می کنیم. این کار به وسیله ی تابع scaled_value انجام می شود ؛ که یک مقدار شناور(float ) را به دو رقم اعشار گرد می کند و با ضرب آن در 100 آن را به یک مقدار long تبدیل می کند. و در سمت دریافت(receiving side)، ما باید آن را دوباره بر 100 تقسیم کنیم.
اگر برنامه را در آردوینو آپلود کنید و دست خود را در جلوی سنسور قرار دهید، در خروجی، چیزی مثل زیر را مشاهده خواهید کرد:
2129,1016
2129,1027
2129,1071
2129,1063
2129,1063
2129,1063
خروجی بالا یک سری مقادیری است که با کاما از یکدیگر جدا شده اند؛ که مقدار اول نشان دهنده ی دمای فعلی به درجه ی سلسیوس است و مقدار دوم نشان دهنده ی فاصله تا نزدیک ترین شیء است که به واحد سانتی متر می باشد. هر دوی این مقادیر باید بر 100 تقسیم شوند تا داده ی حقیقی سنسور به دست آید.
اکنون پروژه ی کوچک ما از دو سنسور برخوردار است. یکی از سنسورها به یک پین دیجیتال متصل می شوند و دیگری از یک پین آنالوگ استفاده می کند. در بخش بعدی، می آموزید که چگونه داده های سنسور را به یک رایانه برگردانید و از آن برای ایجاد برنامه هایی مبتنی بر حالت فعلی جهان واقعی استفاده کنید.
ص92