در قسمت پانزدهم از آموزش آردوینو به بررسی گرد کردن اعداد ممیزی، چگونگی استفاده از توابع مثلثاتی و تولید اعداد تصادفی، پرداختیم. در این قسمت قصد داریم درباره تنظیم و خواندن بیتها و همچنین، شیفتِ بیتها صحبت کنیم. در پایان مطلب نیز درباره استخراج بایتهای High و Low بهصورت int یا long و تشکیل int یا long از High و Low Byte توضیحاتی ارائه می دهیم.
تنظیم و خواندن بیتها
اگر شما بخواهید یک بیت خاص را در یک متغیر عددی بخوانید یا تنظیم کنید، میتوانید از توابع زیر استفاده کنید:
bitSet(x, bitPosition)
تابع bitSet مقدار 1 را در جایگاه bitPosition مربوط به متغیر x مینویسد
bitClear(x, bitPosition)
تابع bitClear مقدار 0 را در جایگاه bitPosition مربوط به متغیر x مینویسد
bitRead(x, bitPosition)
مقدار (0 یا 1) بیت را در bitPosition موجود متغیر x برمی گرداند.
bitWrite(x, bitPosition, value)
مقدار داده شده (0 یا 1) بیت را در bitPosition موجود متغیر x تنظیم می کند.
bit(bitPosition)
مقدار bit position موجود را برمیگرداند: بیت(0) 1، بیت(1) 2، بیت(2) 4 و… است.
در اینجا یک اسکچ وجود دارد که توابع برای تغییر بیتهای یک متغیر 8 بیتی به نام flags استفاده می کنند. همچنین، از هر یک از هشت بیت به عنوان یک فلگ مستقل استفاده می کند که می تواند روشن و خاموش شود:
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 | // bitFunctions // demonstrates using the bit functions byte flags = 0; // these examples set, clear, or read bits in a variable called flags // bitSet example void setFlag(int flagNumber) { bitSet(flags, flagNumber); } // bitClear example void clearFlag(int flagNumber) { bitClear(flags, flagNumber); } // bitPosition example int getFlag(int flagNumber) { return bitRead(flags, flagNumber); } void showFlags(void); void setup() { Serial.begin(9600); } void loop() { flags = 0; // clear all flags showFlags(); setFlag(2); // set some flags setFlag(5); showFlags(); clearFlag(2); showFlags(); delay(10000); // wait a very long time } // reports flags that are set void showFlags() { for(int flag=0; flag < 8; flag++) { if (getFlag(flag) == true) Serial.print("* bit set for flag "); else Serial.print("bit clear for flag "); Serial.println(flag); } Serial.println(); } |
این کد هر 10 ثانیه موارد زیر را پرینت می کند:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | bit clear for flag 0 bit clear for flag 1 bit clear for flag 2 bit clear for flag 3 bit clear for flag 4 bit clear for flag 5 bit clear for flag 6 bit clear for flag 7 bit clear for flag 0 bit clear for flag 1 * bit set for flag 2 bit clear for flag 3 bit clear for flag 4 * bit set for flag 5 bit clear for flag 6 bit clear for flag 7 bit clear for flag 0 bit clear for flag 1 bit clear for flag 2 bit clear for flag 3 bit clear for flag 4 * bit set for flag 5 bit clear for flag 6 bit clear for flag 7 |
خواندن و ست (تنظیم) کردن بیتها، امری رایج است و بسیاری از کتابخانههای آردوینو از این قابلیت استفاده میکنند. مهم ترین کاربردهای عملیات بیت (bit operation) عبارت است از: ذخیره و بازیابی مقادیر باینری (روشن/خاموش (on/off)، درست/نادرست (true/false)، 1/0، high/low و … .
✅نکته مهم
حالت هشت سوئیچ را میتوان بهجای نیاز به هشت بایت یا اعداد صحیح در یک مقدار 8 بیتی بسته بندی کرد. مثال در راه حل این دستور العمل نشان می دهد که چگونه می توان هشت مقدار را به صورت جداگانه در یک بایت تنظیم یا پاک کرد
وقتی که میخواهیم هشت وضعیت را ذخیره کنیم، میتوانیم از یک بایت 8 بیتی استفاده کنیم. این 8 بیت میتوانند به صورت جداگانه تنظیم یا پاک شوند. این روش معمولاً در برنامهنویسی میکروکنترلرها و دستگاههای الکترونیکی استفاده میشود.
در برنامهنویسی، عبارت “فلگ” به مقادیری اشاره دارد که وضعیت یک جنبه خاص از برنامه را ذخیره میکنند. در این اسکچ، بیت فلگها با استفاده از تابع bitRead خوانده میشوند و با استفاده از توابع bitSet یا bitClear تنظیم یا پاک میشوند.
این توابع دو پارامتر دارند: مورد اول، مقدار خواندن یا نوشتن است (در مثال گفته شده، فلگها) و مورد دوم، موقعیت بیت (bit position) است که نشان میدهد خواندن یا نوشتن کجا باید انجام شود. موقعیت بیت 0 کم اهمیت ترین (راست ترین) بیت است. موقعیت 1 دومین موقعیت از سمت راست است و به همین ترتیب ادامه دارد. بنابراین:
1 2 | bitRead(2, 1); // returns 1 : 2 is binary 10 and bit in position 1 is 1 bitRead(4, 1); // returns 0 : 4 is binary 100 and bit in position 1 is 0 |
همچنین، تابعی به نام bit وجود دارد که مقدار هر موقعیت بیت (bit position) را برمیگرداند:
1 2 3 4 5 | bit(0) is equal to 1; bit(1) is equal to 2; bit(2) is equal to 4; ... bit(7) is equal to 128 |
شیفت بیتها
برای انجام عملیات شیفت بیتی که بیتها را در یک بایت (byte)، اینتیجر (int) یا لانگ (long) به چپ یا راست منتقل میکند، میتوانید از عملگر << برای شیفت به چپ و از عملگر >> برای شیفت به راست استفاده کنید. این عملگرها بیتهای یک مقدار را به تعداد مشخصی به سمت چپ یا راست منتقل میکنند.
این فرگمنت متغیر x را برابر با ۶ قرار میدهد. سپس بیتها را یک جایگاه به چپ شیفت میدهد و مقدار جدید (۱۲) را پرینت میکند. بعد از آن، این مقدار، دو جایگاه به راست شیفت داده میشود (و در این مثال برابر با ۳ میشود):
1 2 3 4 5 | int x = 6; x = x << 1; // 6 shifted left once is 12 Serial.println(x); x = x >> 2; // 12 shifted right twice is 3 Serial.println(x); |
نحوه عملکرد عملیات شیفت بیتی به این صورت است: وقتی عدد 6 (که در دودویی 0110 است) یک جایگاه به چپ شیفت داده میشود مقدار آن ۱۲ میشود، زیرا مقدار دودویی به 1100 تبدیل میشود که در مبنای دهدهی برابر با ۱۲ است. وقتی 1100 دو جایگاه به راست شیفت داده میشود، مقدار آن 0011 میشود که در مبنای دهدهی برابر با ۳ است.
جابهجایی یک عدد به سمت چپ به تعداد nبرابر است با مکان، معادل با ضربکردن آن عدد در 2 به توان n است. به همین ترتیب، جابهجایی یک عدد به سمت راست به تعداد n برابر است با مکان، معادل با تقسیمکردن آن عدد بر 2 به توان n. خلاصه این موارد به شرح زیر میباشد:
جابهجایی به سمت چپ (شیفت چپ):
جابهجایی به سمت راست (شیفت راست):
برای مثال، به عبارات زیر توجه کنید:
1 2 3 4 5 6 | x << 1 is the same as x * 2. x << 2 is the same as x * 4. x << 3 is the same as x * 8. x >> 1 is the same as x / 2. x >> 2 is the same as x / 4. x >> 3 is the same as x / 8. |
چیپ کنترلر آردوینو میتواند عملیات شیفت بیتی را به طور مؤثرتری نسبت به ضرب و تقسیم انجام دهد. همچنین، ممکن است شما با کدی روبهرو شوید که از عملیات شیفت بیتی (bit shift) برای ضرب و تقسیم استفاده میکند:
1 | int c = (a << 1) + (b >> 2); //add (a times 2) plus ( b divided by 4) |
شاید در نگاه اول، عبارت (a << 1) + (b >> 2) شبیه به (a * 2) + (b / 4) نباشد، اما هر دو عبارت کار یکسانی انجام میدهند. کامپایلر آردوینو به اندازهای هوشمند است که بتواند تشخیص دهد ضرب یک عدد صحیح در یک ثابت که توانی از دو است، معادل با یک عملیات شیفت می باشد و همان کدی را ایجاد میکند که نسخهای از عملیات شیفت استفاده میکند. بهطورکلی، خواندن سورس کدی که دارای عملگرهای ریاضی (Arithmetic Operators) میباشد، برای ما آسانتر است؛ بنابراین وقتی هدف ضرب و تقسیم است، این سورس کد، بهتر است.
در آردوینو، توابع مختلفی برای کار با بیتها و بایتها وجود دارد که به شما این امکان را میدهند که عملیاتهای پایهای را روی بیتها انجام دهید. در اینجا توضیح مختصری در مورد هر یک از این توابع ارائه شده است:
- lowByte(): کوچکترین بایت (سمت راستترین) یک متغیر را استخراج میکند.
- highByte(): بزرگترین بایت (سمت چپترین) در یک کلمه یا دومین بایت کوچک از یک نوع داده بزرگتر را استخراج میکند.
- bitRead(): مقدار بیت موردنظر در متغیر موردنظر را میخواند.
- bitWrite(): برای 1 کردن یا 0 کردن مقدار بیت مورد نظر استفاده می شود.
- bitSet(): برای 1 کردن مقدار بیت مورد نظر استفاده می شود.
- bitClear(): بیت موردنظر را پاک میکند. بهعبارتدیگر، آن بیت را صفر میکند.
- bit(): مقدار بیت مشخص شده را محاسبه میکند یا بهعبارتدیگر، مقدار بیت موردنظر ما را برمیگرداند.
استخراج بایتهای High و Low بهصورت int یا long
اگر شما بخواهید بزرگترین بایت (چپترین) یا کوچکترین بایت (راستترین) “یک” در عدد صحیح را استخراج کنید، (بهعنوانمثال، هنگام ارسال مقادیر صحیح بهعنوان بایت در یک سریال یا خط ارتباطی دیگر) میتوانید از توابع highByte(i) و lowByte(i) استفاده نمایید؛ به این صورت که از تابعlowByte(i) برای دریافت کوچکترین بایت “یک” در عدد صحیح و از تابع highByte(i) برای دریافت بزرگترین بایت “یک” در عدد صحیح میتوانید استفاده کنید.
اسکچ زیر یک مقدار صحیح را به کوچکترین بایت و بزرگترین بایت تبدیل میکند:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* * ByteOperators sketch */ int intValue = 258; // 258 in hexadecimal notation is 0x102 void setup() { Serial.begin(9600); } void loop() { int loWord,hiWord; byte loByte, hiByte; hiByte = highByte(intValue); loByte = lowByte(intValue); Serial.println(intValue, DEC); Serial.println(intValue, HEX); Serial.println(loByte, DEC); Serial.println(hiByte, DEC); delay(10000); // wait a very long time } |
در این اسکچ intValue به عنوان نمونه آورده شده است که مقدار یک عدد صحیح را به همراه کوچکترین و بزرگترین بایتهای آن (با توضیحات) پرینت میکند:
1 2 3 4 | 258 the integer value to be converted 102 the value in hexadecimal notation 2 the low byte 1 the high byte |
برای استخراج مقادیر بایت از یک متغیر نوع long در آردوینو، ابتدا باید مقدار ۳۲ بیتی long را به دو کلمه (word) ۱۶ بیتی تقسیم کنید و سپس آنها را به بایتها تبدیل کنید، همانطور که در کد قبلی نشان داده شد. در زمان نوشتن این مطلب، کتابخانه استاندارد آردوینو تابعی برای انجام این عملیات روی یک متغیر long نداشت، اما شما میتوانید کد زیر را به اسکچ خود اضافه کنید تا بتوانید این کار را انجام دهید:
1 2 | #define highWord(w) ((w) >> 16) #define lowWord(w) ((w) & 0xffff) |
این عبارات ماکرو (macro) هستند: highWord یک عملیات شیفت 16 بیتی برای تولید یک مقدار 16 بیتی انجام می دهد و lowWord هم 16 بیت پایین را با استفاده از عملگر بیتی And ماسک می کند.
✅نکته
این کد مقدار 16909060، 32 بیتی (هگزادسیمال 0x1020304) را به مقادیر بیش تر (High) و کمتر (Low) تشکیل دهنده 16 بیتی خود تبدیل می کند:
1 2 3 4 5 | long longValue = 16909060; int loWord = lowWord(longValue); int hiWord = highWord(longValue); Serial.println(loWord, DEC); Serial.println(hiWord, DEC); |
این کد مقادیر عددی زیر را پرینت میکند:
1 2 | 772 772 is 0x0304 in hexadecimal 258 258 is 0x0102 in hexadecimal |
توجه داشته باشید که 772 در اعشار، 0x0304 در هگزادسیمال است که کلمه مرتبه پایین (low-order word) (16 بیتی) از longValue 0x1020304 می باشد.
تشکیل int یا long از High و Low Byte
در ادامه درباره این توضیح خواهیم داد که شما چگونه میتوانید یک مقدار صحیح 16 بیتی (int) یا 32 بیتی (long) از بایت های جداگانه ایجاد کنید؛ به عنوان مثال، هنگام دریافت اعداد صحیح به عنوان بایت های جداگانه از طریق یک پیوند ارتباطی سریال.
از تابع word(h,l) برای تبدیل دو بایت به یک عدد صحیح آردوینو استفاده کنید. در اینجا کدی آورده شده است که بایتهای high و low منفرد را به یک عدد صحیح تبدیل میکند:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /* * Forming an int or long with byte operations sketch */ int intValue = 0x102; // 258 void setup() { Serial.begin(9600); } void loop() { int aWord; byte loByte, hiByte; hiByte = highByte(intValue); loByte = lowByte(intValue); Serial.println(intValue, DEC); Serial.println(loByte, DEC); Serial.println(hiByte, DEC); aWord = word(hiByte, loByte); // convert the bytes back into a word Serial.println(aWord, DEC); delay(10000); // wait a very long time |
word بایت های high و low را در یک مقدار 16 بیتی جمع می کند. خروجی مقدار صحیح، بایت low، بایت high و بایت هایی است که به یک مقدار صحیح تبدیل می شوند:
1 2 3 4 | 258 2 1 258 |
آردوینو تابعی برای تبدیل یک مقدار long 32 بیتی به دو کلمه (word) 16 بیتی ندارد، اما شما میتوانید با افزودن کد زیر به بالای اسکچ خود، قابلیت ()makeLong را اضافه کنید:
1 | #define makeLong(hi, low) ((hi) << 16 | (low)) |
کد زیر کامند (دستوری) را تعریف میکند که مقدار high را 16 بیت به سمت چپ منتقل میکند و مقدار low را به ان اضافه می کند:
1 2 3 | #define makeLong(hi, low) (((long) hi) << 16 | (low)) #define highWord(w) ((w) >> 16) #define lowWord(w) ((w) & 0xffff) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // declare a value to test long longValue = 0x1020304; // in decimal: 16909060 // in binary : 00000001 00000010 00000011 00000100 void setup() { Serial.begin(9600); } void loop() { int loWord, hiWord; Serial.println(longValue, DEC); // this prints 16909060 loWord = lowWord(longValue); // convert long to two words hiWord = highWord(longValue); Serial.println(loWord,DEC); // print the value 772 Serial.println(hiWord,DEC); // print the value 258 longValue = makeLong(hiWord, loWord); // convert the words back to a long Serial.println(longValue,DEC); // this again prints 16909060 delay(10000); // wait a very long time } |
خروجی عبارت است از:
1 2 3 4 | 16909060 772 258 16909060 |
✅نکته