در قسمت قبل از آموزش برنامه نویسی آردوینو با قالببندی متن، کنترل مکاننما و اسکرولکردن نوشتهها روی LCD آشنا شدیم. در قسمت قبلی قالب بندی، کنترل و اسکرول متن در LCD قابلیتهای پیشرفتهتری از نمایشگرهای متنی را بررسی کردیم.
در این قسمت یاد میگیریم چگونه نمادهایی مانند درجه و پی را از حافظه داخلی LCD نمایش دهیم، کاراکترهای کاملاً سفارشی بسازیم، چند کاراکتر را برای ساخت اعداد بزرگ کنار هم قرار دهیم و حتی از پیکسلهای داخل یک کاراکتر برای رسم نمودار میلهای استفاده کنیم.
نمایشگرهای متنی مبتنی بر HD44780 فقط به حروف و اعداد معمولی محدود نیستند. در حافظه کاراکتری این نمایشگرها مجموعهای از نمادها قرار دارد که میتوان با استفاده از کد مربوط به هر کاراکتر آنها را روی صفحه نمایش داد. برای مثال میتوان نمادهایی مانند درجه °، پی π، سنت، ریشه دوم و امگا را نمایش داد. برای این کار ابتدا باید کد کاراکتر موردنظر را از جدول Character Pattern در دیتاشیت کنترلر LCD پیدا کنیم.

| 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 83 84 85 | /* * LiquidCrystal Library - Special Chars */ #include <LiquidCrystal.h> const int numRows = 2; const int numCols = 16; const byte degreeSymbol = B11011111; const byte piSymbol = B11110111; const byte centsSymbol = B11101100; const byte sqrtSymbol = B11101000; const byte omegaSymbol = B11110100; byte charCode = 32; int col; int row; LiquidCrystal lcd(12, 11, 5, 4, 3, 2); void setup() { lcd.begin(numRows, numCols); showSymbol(degreeSymbol, "degrees"); showSymbol(piSymbol, "pi"); showSymbol(centsSymbol, "cents"); showSymbol(sqrtSymbol, "sqrt"); showSymbol(omegaSymbol, "ohms"); lcd.clear(); } void loop() { lcd.write(charCode); calculatePosition(); if(charCode == 255) { delay(2000); lcd.clear(); row = col = 0; charCode = 32; } charCode = charCode + 1; } void calculatePosition() { col = col + 1; if(col == numCols) { col = 0; row = row + 1; if(row == numRows) { row = 0; delay(2000); lcd.clear(); } lcd.setCursor(col, row); } } void showSymbol(byte symbol, char *description) { lcd.clear(); lcd.write(symbol); lcd.print(' '); lcd.print(description); delay(3000); } |
دیتاشیت کنترلر LCD جدولی از الگوهای کاراکتری موجود در حافظه دارد. برای نمایش یک نماد باید محل آن را در جدول پیدا کنیم و بیتهای مربوط به سطر و ستون آن را با یکدیگر ترکیب کنیم.
کتاب برای توضیح این موضوع نماد درجه را مثال میزند. کد باینری این نماد برابر است با:
| 1 | B11011111 |
همین مقدار را میتوان بهصورت هگزادسیمال یا دهدهی نیز بیان کرد:
| 1 2 | 0xDF 223 |
بنابراین سه مقدار بالا به یک کاراکتر اشاره میکنند. در برنامه، نسخه باینری استفاده شده است.
برای نمایش مستقیم یک کد کاراکتر از lcd.write() استفاده میکنیم. برای مثال:
| 1 | lcd.write(degreeSymbol); |
تابع showSymbol() ابتدا نماد را نمایش میدهد و سپس توضیح مربوط به آن را کنار نماد چاپ میکند.
| 1 2 3 4 5 6 7 8 | void showSymbol(byte symbol, char *description) { lcd.clear(); lcd.write(symbol); lcd.print(' '); lcd.print(description); delay(3000); } |
اگر نمادی که به آن نیاز داریم در حافظه داخلی LCD وجود نداشته باشد، میتوانیم کاراکتر اختصاصی خودمان را طراحی کنیم.
هر کاراکتر متنی LCD روی یک شبکه 5×8 پیکسل ساخته میشود. برای تعریف یک کاراکتر سفارشی باید یک آرایه شامل هشت بایت بسازیم؛ هر بایت یکی از سطرهای کاراکتر را مشخص میکند.
در مقادیر باینری، عدد 1 به معنی پیکسل روشن و عدد 0 به معنی پیکسل خاموش است. در مثال کتاب دو چهره، یکی خندان و دیگری ناراحت، ساخته شدهاند.

| 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 | /* * custom_char sketch * creates an animated face using custom characters */ #include <LiquidCrystal.h> LiquidCrystal lcd(12, 11, 5, 4, 3, 2); byte happy[8] = { B00000, B10001, B00000, B00000, B10001, B01110, B00000, B00000 }; byte saddy[8] = { B00000, B10001, B00000, B00000, B01110, B10001, B00000, B00000 }; void setup() { lcd.createChar(0, happy); lcd.createChar(1, saddy); lcd.begin(16, 2); } void loop() { for(int i=0; i<2; i++) { lcd.setCursor(0,0); lcd.write(i); delay(500); } } |
آرایه happy از هشت سطر تشکیل شده است:
| 1 2 3 4 5 6 7 8 9 10 11 | byte happy[8] = { B00000, B10001, B00000, B00000, B10001, B01110, B00000, B00000 }; |
پنج بیت سمت راست هر مقدار مشخص میکنند کدام پیکسلهای آن سطر روشن باشند. با کنار هم قرار دادن هشت سطر، شکل کامل کاراکتر ساخته میشود.
برای ایجاد کاراکتر از تابع lcd.createChar() استفاده میشود:
| 1 2 | lcd.createChar(0, happy); lcd.createChar(1, saddy); |
در این مثال چهره خندان در موقعیت صفر و چهره ناراحت در موقعیت یک ذخیره میشوند. سپس میتوان کاراکتر را با lcd.write() نمایش داد:
| 1 | lcd.write(0); |
lcd.write(0) و lcd.write('0') تفاوت وجود دارد. در مثال کتاب، عبارت اول کاراکتر سفارشی شماره صفر را نمایش میدهد؛ اما عبارت دوم کاراکتر معمولی عدد صفر را چاپ میکند.حلقه برنامه بهطور پیوسته بین دو کاراکتر ساختهشده جابهجا میشود:
| 1 2 3 4 5 6 | for(int i=0; i<2; i++) { lcd.setCursor(0,0); lcd.write(i); delay(500); } |
در نتیجه چهره روی صفحه هر نیم ثانیه بین حالت خندان و ناراحت تغییر میکند و یک انیمیشن بسیار ساده ایجاد میشود.
اندازه کاراکترهای LCD ثابت است، اما میتوان با ترکیب چند کاراکتر سفارشی یک نماد یا عدد بزرگتر ساخت.
در بخش 11.7 کتاب، پنج کاراکتر سفارشی ساخته میشوند و سپس این قطعات مانند بلوکهای سازنده کنار هم قرار میگیرند تا اعداد با ارتفاع دو سطر LCD نمایش داده شوند.

| 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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | /* * customChars * This sketch displays double-height digits * the bigDigit arrays were inspired by Arduino forum member dcb */ #include <LiquidCrystal.h> LiquidCrystal lcd(12, 11, 5, 4, 3, 2); byte glyphs[5][8] = { { B11111,B11111,B00000,B00000,B00000,B00000,B00000,B00000 }, { B00000,B00000,B00000,B00000,B00000,B00000,B11111,B11111 }, { B11111,B11111,B00000,B00000,B00000,B00000,B11111,B11111 }, { B11111,B11111,B11111,B11111,B11111,B11111,B11111,B11111 }, { B00000,B00000,B00000,B00000,B00000,B01110,B01110,B01110 } }; const int digitWidth = 3; const char bigDigitsTop[10][digitWidth] = { 3,0,3, 0,3,32, 2,2,3, 0,2,3, 3,1,3, 3,2,2, 3,2,2, 0,0,3, 3,2,3, 3,2,3 }; const char bigDigitsBot[10][digitWidth] = { 3,1,3, 1,3,1, 3,1,1, 1,1,3, 32,32,3, 1,1,3, 3,1,3, 32,32,3, 3,1,3, 1,1,3 }; char buffer[12]; void setup() { lcd.begin(20,4); for(int i=0; i<5; i++) { lcd.createChar(i, glyphs[i]); } for(int digit=9; digit>=0; digit--) { showDigit(digit, 0); delay(1000); } lcd.clear(); } void loop() { int number = millis() / 1000; showNumber(number, 0); delay(1000); Serial.begin(9600); } void showDigit(int digit, int position) { lcd.setCursor(position * (digitWidth + 1), 0); for(int i=0; i<digitWidth; i++) { lcd.write(bigDigitsTop[digit][i]); } lcd.setCursor(position * (digitWidth + 1), 1); for(int i=0; i<digitWidth; i++) { lcd.write(bigDigitsBot[digit][i]); } } void showNumber(int value, int position) { int index; String valStr = String(value); for(index=0; index<5; index++) { char c = valStr.charAt(index); if(c == 0) { return; } c = c - 48; showDigit(c, position + index); } } |
آرایه glyphs یک آرایه دوبعدی است:
| 1 | byte glyphs[5][8] |
عدد 5 نشاندهنده تعداد Glyphهای مورد استفاده و عدد 8 نشاندهنده هشت سطر پیکسلی هر Glyph است.
این پنج قطعه پایه، قسمتهای مختلف اعداد بزرگ را تشکیل میدهند؛ برای مثال یک بلوک کاملاً پر، بخش بالایی، بخش پایینی و ترکیبهایی از آنها.
دو آرایه bigDigitsTop و bigDigitsBot مشخص میکنند برای ساخت هر عدد، در ردیف بالا و ردیف پایین LCD کدام Glyphها نمایش داده شوند.
هر عدد بزرگ از شش کاراکتر تشکیل میشود؛ سه کاراکتر بخش بالایی عدد و سه کاراکتر بخش پایینی را میسازند.
تابع showDigit() یک رقم را در موقعیت مشخص روی LCD نمایش میدهد. ابتدا سه Glyph مربوط به قسمت بالایی و سپس سه Glyph مربوط به قسمت پایینی نوشته میشوند.
| 1 2 3 4 5 6 7 8 9 10 11 12 | void showDigit(int digit, int position) { lcd.setCursor(position * (digitWidth + 1), 0); for(int i=0; i<digitWidth; i++) lcd.write(bigDigitsTop[digit][i]); lcd.setCursor(position * (digitWidth + 1), 1); for(int i=0; i<digitWidth; i++) lcd.write(bigDigitsBot[digit][i]); } |
تابع showNumber() عدد را ابتدا به رشته تبدیل میکند و سپس هر رقم را جداگانه به showDigit() میفرستد.
| 1 | String valStr = String(value); |
در کد کتاب تا پنج رقم بررسی میشود و هر کاراکتر ASCII با کمکردن 48 به مقدار عددی تبدیل میشود:
| 1 | c = c - 48; |
تا اینجا چند کاراکتر را با یکدیگر ترکیب کردیم تا شکلهای بزرگتر بسازیم. در بخش 11.8 دقیقاً مسیر برعکس میشود: از پیکسلهای داخل یک کاراکتر استفاده میکنیم تا جزئیات کوچکتری نسبت به اندازه یک کاراکتر ایجاد کنیم.
کاربرد نمونه این روش، ساخت نمودار میلهای روی LCD است.
کتاب هشت کاراکتر سفارشی میسازد که هرکدام یک سطر پیکسل بیشتر از کاراکتر قبلی دارند. در نتیجه میتوان هشت سطح مختلف ارتفاع را نمایش داد.

| 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 | /* * customCharPixels */ #include <LiquidCrystal.h> LiquidCrystal lcd(12, 11, 5, 4, 3, 2); const int numRows = 2; const int numCols = 16; byte glyphs[8][8] = { {B00000,B00000,B00000,B00000,B00000,B00000,B00000,B11111}, {B00000,B00000,B00000,B00000,B00000,B00000,B11111,B11111}, {B00000,B00000,B00000,B00000,B00000,B11111,B11111,B11111}, {B00000,B00000,B00000,B00000,B11111,B11111,B11111,B11111}, {B00000,B00000,B00000,B11111,B11111,B11111,B11111,B11111}, {B00000,B00000,B11111,B11111,B11111,B11111,B11111,B11111}, {B00000,B11111,B11111,B11111,B11111,B11111,B11111,B11111}, {B11111,B11111,B11111,B11111,B11111,B11111,B11111,B11111} }; void setup() { lcd.begin(numCols, numRows); for(int i=0; i<8; i++) { lcd.createChar(i, glyphs[i]); } lcd.clear(); } void loop() { for(byte i=0; i<8; i++) { lcd.write(i); } delay(2000); lcd.clear(); } |
اولین Glyph فقط پایینترین سطر را روشن میکند:
| 1 2 3 4 5 6 7 8 | {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B11111} |
در Glyph بعدی دو سطر پایینی روشن هستند و این روند ادامه پیدا میکند تا در آخرین Glyph هر هشت سطر روشن شوند.
در نتیجه میتوان مقادیر مختلف را با ارتفاعهای متفاوت روی LCD نمایش داد.
کتاب نمونهای ارائه میکند که مقدار ورودی آنالوگ A0 را خوانده و آن را به یکی از هشت سطح موجود تبدیل میکند:
| 1 2 3 4 5 6 7 | int value = analogRead(A0); byte glyph = map(value, 0, 1023, 0, 8); lcd.write(glyph); delay(100); |
تابع map() محدوده ورودی آنالوگ را به محدوده مورد استفاده برای Glyphهای نمودار تبدیل میکند.
برای رسیدن به رزولوشن بیشتر میتوان دو سطر LCD را روی هم قرار داد. تابع زیر در کتاب برای نمایش مقادیر روی ارتفاع دو کاراکتر استفاده شده است:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | void doubleHeightBars(int value, int column) { char upperGlyph; char lowerGlyph; if(value < 8) { upperGlyph = ' '; lowerGlyph = value; } else { upperGlyph = value - 8; lowerGlyph = 7; } lcd.setCursor(column, 0); lcd.write(upperGlyph); lcd.setCursor(column, 1); lcd.write(lowerGlyph); } |
اگر مقدار کمتر از 8 باشد فقط نیمه پایین نمودار استفاده میشود. برای مقادیر بالاتر، کاراکتر پایین کاملاً پر شده و ادامه ارتفاع در سطر بالایی LCD نمایش داده میشود.
نمونه زیر ورودی آنالوگ را به محدوده 0 تا 16 تبدیل میکند و برای هر ستون یک میله با ارتفاع مناسب نمایش میدهد:
| 1 2 3 4 5 6 7 8 9 10 | for(int i=0; i<16; i++) { int value = analogRead(A0); value = map(value, 0, 1023, 0, 16); doubleHeightBars(value, i); delay(1000); } |
با این روش دو سطر LCD در مجموع رزولوشن عمودی بیشتری برای نمودار فراهم میکنند.
در پایان بخش 11.8 کتاب اشاره میشود که همین ایده را میتوان برای نمودارهای افقی نیز استفاده کرد. برای این کار میتوان پنج کاراکتر ساخت که هر کاراکتر یک ستون پیکسل بیشتر از قبلی داشته باشد و با منطق مشابه، مقدار موردنظر را به عرض مناسب تبدیل کرد.
جمع بندی
در قسمت 61 آموزش آردوینو با قابلیتهای پیشرفتهتر LCDهای متنی آشنا شدیم. ابتدا نمادهای از پیش تعریفشده را با lcd.write() نمایش دادیم و سپس با lcd.createChar() کاراکترهای کاملاً سفارشی ساختیم. بعد دیدیم که چگونه چند Glyph کوچک میتوانند کنار هم قرار بگیرند و اعداد بزرگ با ارتفاع دو سطر ایجاد کنند. در نهایت نیز از همان قابلیت کاراکتر سفارشی برای ساخت نمودارهای میلهای با چند سطح ارتفاع استفاده کردیم.
در قسمت بعدی آموزش وارد مبحث نمایشگرهای گرافیکی میشویم و بررسی میکنیم برای پروژههای Arduino چگونه یک نمایشگر گرافیکی مناسب انتخاب کنیم.
سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.