Đặng Hồng Luật -CN-ĐTVT 03 – K55 Bách Khoa Hà Nội
1. Giới thiệu tổng quan về LCD Nokia 5110
LCD Nokia 5110
thuộc dòng sản phầm Graphic LCD 84x48.
LCD Nokia 5110 là màn hình LCD Graphic đơn giản với nhiều ứng dụng
khác nhau. Nguồn gốc màn hinh này thực chất là từ các thế hệ điện thoại di động
trước đây. Nó đã được gắn trên một bô PCB dễ dàng hàn gắn với các board khác.
LCD Nokia 5110 sử
dụng vi điều khiển PCD8544, được sử dụng cùng với Nokia 3310 LCD trước đây.
PCD8544 là dạng low power CMOS controller/driver, được thiết kế vơi chế độ hiển
thị màn hình graphic là 84 cột và 48 hàng. Tất cả các chưc năng cơ bản đã được
tích hợp sẵn trên chip, từ đó cho ta hiệu quả về một thiết bị ngoại vi chiếm ít
nguồn tiêu thụ. PCD8544 giao tiếp với vi điều khiển của chúng ta qua một loạt
các chân bus đã được cung cấp sẵn.
2. Giao tiếp với LCD Nokia 5110
2.1. Cấu tạo – Sơ đồ nguyên lý của LCD Nokia 5110
Sơ đồ nguyên lý của LCD Nokia 5110
Qua sơ đồ trên,
ta thấy được LCD Nokia 5110 được điều khiển toàn bộ bởi 5 cổng đầu vào, và 3 cổng
nguồn, dưới dạng giao tiếp SPI, gồm:
-
RST – Tín hiệu Reset, giúp khởi động, reset màn
hình
-
CE – Tín hiệu Chip Select, giúp đưa ra tín hiệu
chọn chip hoạt động ở mức tích cực thấp
-
DC – Tín hiệu Data/Command giúp ta chọn chế độ
đưa lệnh vào PCD8544 là lệnh điều khiển, hay dữ liệu hiển thị
-
DIN – Dữ liệu đầu vào có thể là lệnh điều khiển
hoặc dữ liệu hiển thị màn hình
-
CLK – Xung Clock điều khiển
-
VCC – Nguồn nuôi
-
LIGHT – Đất cho đèn LED Backlight
-
GND – Đất cho nguồn nuôi
2.2. Lập trình giao tiếp với LCD Nokia 5110
2.2.1. Thư viện và các lệnh định nghĩa cơ bản
Như thường lệ, bước
đầu của một chương trình điều khiển, luôn là bước nhập thư viện, cũng như cung
cấp các định nghĩa cơ bản cho toàn bộ chương trình sau này sử dụng.
Đầu tiên, ta gọi
thư viện của chip MSP430G2553 đầu tiên, do ta đang sử dụng IC này để làm vi điều
khiển hiện tại:
#include <msp430g2553.h> //Call header for
MSP430G2553
Thư viện được gọi
dưới lệnh include và thư viện gọi ra ở dạng file header.
Tiếp theo sau đó,
ta định nghĩa lại một vài toán tử chúng ta sẽ thường xuyên sử dụng trong suốt
chương trình sau này, điều này sẽ khiến cho chương trình của chúng ta về sau
trông sẽ dễ hiểu hơn với ngôn ngữ thường ngày chúng ta quen thuộc, và quan trọng
nhất là dễ dàng đọc hiểu nhanh chóng trong quá trình sửa chữa sau này, tránh
tình trạng nhầm lẫn ký tự gây khó khăn sau này:
#define set |= //Set 1 bit to 1
#define clr &=~ //Set all bits to 0
#define rev
^= //Reverse
all bits
Toán hạng “set’ sử
dụng phép OR để nâng 1 bit duy nhất lên trạng thái “1”.
Toán hạng “clr” sử
dụng toán hạng AND với NOT để biến tất cả các bit hiện tại về “0”.
Toán hạng “rev” sử
dụng toán hạng XOR để đảo tất cả các bit hiện tại.
Tiếp sau đó, ta định
nghĩa các cổng nối bus tín hiệu từ LCD tới vi điều khiển của chúng ta sẽ nối với
các cổng nào trên vi điều khiển:
#define RST BIT0 //P1.0
#define CE BIT1 //P1.1
#define DC BIT2 //P1.2
#define DIN BIT3 //P1.3
#define CLK
BIT4 //P1.4
Qua việc định
nghĩa như vậy, ta đã set các bit hiện tại RST cho tới CLK lần lượt vào các Port
1.0 cho tới 1.4 trên vi điều khiển. Việc định nghĩa các cổng như vậy sẽ giúp ta
tốn ít thời gian cho việc lập trình sau này hơn, do sau này ta sẽ chỉ việc gọi
tên các cổng chúng ta quan tâm tới trên LCD, sau đó chương trình định nghĩa sẽ
tự tham chiếu lại địa chỉ các port giúp chúng ta.
Tiếp sau đó, do
Port 1 được sử dụng như là Port giao tiếp với LCD, nên ta định nghĩa tiếp:
#define LCD_OUT P1OUT //Output of LCD is marked to values of P1
#define
LCD_DIR P1DIR //Direction
of LCD is direction of P1
Do cổng ra tới
LCD cũng chính là Port 1 ở vi điều khiển, nên ta cho chúng tham chiếu tương ứng
với nhau.
Cuối cùng, ta
tham chiếu định nghĩa nốt một biến nữa là biến phân biệt DIN dạng Command hay
Data:
#define LCD_CMD 0 //When DC=0 bits comes to command
#define
LCD_DATA 1 //When
DC=1 bits comes to data
Điều này sẽ giúp
chúng ta đỡ băn khoăn và nhầm lẫn hơn sau này do chỉ việc phải xem với giá trị
nào thì LCD sẽ lấy tín hiệu là Command/Data, và trong tương lai, ta chỉ cần viết
lệnh do chúng ta định nghĩa từ trước là được. Điều này sẽ giúp chúng ta tiết kiệm
được rất nhiều thời gian, đồng bộ định nghĩa chương trình và tránh nhầm lẫn
tương lai.
2.2.2. Định nghĩa các biến, tham số sử dụng trong chương trình
Tiếp theo sau đó,
là phần định nghĩa các biến số, tham số sẽ được sử dụng trong chương trình
tương lai của chúng ta.
Đầu tiên, dĩ
nhiên là chúng ta sẽ cần một bảng chữ cái để giúp hiển thị các thông điệp lên
màn hình. Ở đây, ta sử dụng bảng mã ASCII:
static const char ASCII[][5] =
{
{0x00,
0x00, 0x00, 0x00, 0x00} // 20
,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 !
,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 "
,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 #
,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $
,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 %
,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 &
,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 '
…
,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x
,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y
,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z
,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b {
,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c |
,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d }
,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e
,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f
};
Việc nhập bảng chữ
cái này sẽ giúp chúng ta có thể hiển thị các chữ cái và ký tự thường sử dụng
sau này lên màn hình thông qua các hàm chức năng tương lai sẽ sử dụng.
Tiếp theo sau đó là một vài hình ảnh giúp tăng tính sinh động cho màn
hình sau này chúng ta sử dụng. Do màn hình chúng ta đang sử dụng có kích cỡ
48x84, nên các ảnh chúng ta sử dụng cũng sẽ phải ở định dạng như vậy, hoặc nếu
không chúng ta sẽ phải chuyển đổi về dạng như vậy để sử dụng.
Ảnh dưới định dạng lớn, sẽ được chuyển dần về dạng bé hơn và đưa tới
dạng bitmap, từ đó, ta sẽ convert được ảnh sang dạng bit như sau:
const unsigned char Terran_bm [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
…
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
};
Các bức ảnh còn lại
cũng được làm với cách làm tương tự và nhập vào dưới dạng các biến để sử dụng
sau này.
2.2.3. Các hàm chức năng
Trước khi vào
chương trình chính, ta sẽ khai báo các chương trình con được sử dụng tương lai
trong chương trình chính của chúng ta.
void Delay(volatile long time);
void ShiftOut(unsigned char txdata);
void LCDWrite(char dc,unsigned char data);
void GotoXY(int x,int y);
void LCDClr();
void LCDChar(char character);
void LCDString(char *characters);
void LCDInit(unsigned char
contrast,unsigned char
coefficent,unsigned char
bias);
Các chương trình
chức năng con chúng ta cần gồm có:
-
Delay – Giúp chương trình giữ trạng thái đó
trong một số lượng clock nhất định chúng ta đưa vào
-
ShiftOut – Giúp đưa dữ liệu từ vi điều khiển tới
LCD
-
LCDWrite – Giúp đưa dữ liệu tới LCD, có thể là dạng
Command, hay Data
-
GotoXY – Giúp đưa con trỏ tới tọa độ X,Y nào đó
trên màn hình LCD
-
LCDClr – Giúp xóa trắng màn hình
-
LCDChar – Giúp đưa một ký tự lên màn hình
-
LCDString – Giúp đưa một chuỗi ký tự lên màn
hình
-
LCDInit – Đưa các giá trị khởi tạo LCD
2.2.3.1. Delay
Chương trình con
này giúp giữ trạng thái LCD trong một khoảng thời gian tương ứng với số xung
clock nào đó.
void Delay(volatile long time)
{
while(time>0)
time--;
}
Hàm chức năng này
hoạt động theo nguyên tắc là nhận một giá trị biến “time” ở đầu vào. Biến này sẽ
được đưa vào vào lặp while để xét điều kiện, nếu số lượng vẫn lớn hơn 0 thì biến
while sẽ tiếp tục xét tiếp liên tục. Sau mỗi lần xét như vậy, biến time sẽ bị
giảm giá trị đi 1. Do đó, chương trình sẽ giữ trạng thái LCD trong vòng “time”
lần xung clock.
2.2.3.2. ShiftOut
Hàm chức năng này
có nhiệm vụ đưa dữ liệu ra vi điều khiển tới LCD qua port từng bit một. Sử dụng
phương pháp dịch bit, đúng như tên gọi của hàm:
void ShiftOut(unsigned char txdata)
{
unsigned int i;
LCD_OUT
clr CLK;
LCD_OUT
set DIN;
for(i=8;i>0;i--)
{
if((txdata & BIT7)
== BIT7)
LCD_OUT
set DIN;
else
LCD_OUT
clr DIN;
LCD_OUT
set CLK;
LCD_OUT
clr CLK;
txdata<<=1;
}
}
Hàm nhận dữ liệu
đầu vào dạng 2 bit hexa, tương ứng, ta sẽ được dạng 8 bit binary.
Nguyên tắc làm việc
của hàm chức năng này là:
-
Đầu tiên, ta hạ bit CLK xuống 0 và nâng mức DIN
lên 1 để bắt đầu cho phép quá trình nạp dữ liệu.
-
Sau đó, sử dụng một hàm for, chạy 8 lần, tương ứng
8 bit được truyền. 8 bit này sẽ được AND với BIT7 (0x80 = 1000.0000), tức là chỉ
lấy bit MSB để xét, nếu như bit này có giá trị 1, thì sau khi AND sẽ cho ra kết
quả là 1, tương ứng, đầu ra LCD_OUT sẽ được set lên mức 1. Tương tự như vậy, ta
có truòng hợp ngược lại.
-
Sau khi xét xong một bit, còn 7 bit như vậy nữa,
để tiếp tục xét, ta sử dụng toán tử dịch trái bit đi 1, do đó, bit tiếp theo giờ
sẽ là MSB, cứ như vậy cho tới hết 8 bit cần xét.
2.2.3.3. LCDWrite
Hàm chức năng này
có nhiệm vụ giúp viết dữ liệu vào LCD, dữ liệu đó vừa có thể là Command, vừa có
thể là Data, tùy theo biến số ta đưa vào quy định dạng dữ liệu gửi đi.
void LCDWrite(char dc,unsigned char data)
{
if(dc)
LCD_OUT
set DC;
else
LCD_OUT
clr DC;
LCD_OUT
clr CE;
ShiftOut(data);
LCD_OUT
set CE;
}
Hàm chức năng này có
nguyên tắc hoạt động khá đơn giản:
-
Đầu
tiên hàm sẽ xét biến dc đưa vào, nếu dc là 1 (ta lúc đầu đã tham chiếu là
LCD_DATA) thì dữ liệu ra sẽ là Data, và đầu ra LCD sẽ set bit DC lên mức 1.
Tương tự như vậy ta có trường hợp ngược lại
-
Sau
đã xác định được dạng dữ liệu, hàm đưa tín hiệu hạ mức CE xuống 0 để chọn chip
hoạt động, và bắt đầu gọi hàm truyền dữ liệu ShiftOut với giá trị được đưa vào
là dữ liệu cần truyền.
-
Sau
hàm ShiftOut đã thực hiện xong nhiệm vụ, ta lại set tín hiệu CE lên mức 1, bỏ
chọn chip.
2.2.3.4. GotoXY
Hàm chức năng này
có tác dụng đưa con trỏ ghi dữ liệu ra màn hình tới vị trí cần ghi:
void GotoXY(int x,int y)
{
LCDWrite(LCD_CMD,0x80 | x);
LCDWrite(LCD_CMD,0x40 | y);
}
Như được thấy ở
trong hàm, ta sử dụng 2 câu lệnh Command gửi tới LCD. Tra trong bảng Command
tương ứng, ta được đó là 2 lệnh set giá trị địa chỉ cho RAM, tương ứng với địa
chỉ dữ liệu sẽ được hiển thị lên màn hình, ở đây ta hiểu như đó sẽ là con trỏ
ghi dữ liệu của ta lên màn hình. Theo như bảng, ta sẽ có được 5 dòng và 84 cột
địa chỉ để ghi dữ liệu.
2.2.3.5. LCDClr
Hàm chức năng này
giúp chúng ta xóa sạch dữ liệu trong RAM của LCD, tương đương với việc xóa màn
hình LCD.
void LCDClr()
{
unsigned int i;
for(i=0;i<504;i++)
{
LCDWrite(LCD_DATA,0x00);
}
GotoXY(0,0);
}
Nguyên tắc xóa
khá đơn giản, ta chỉ việc sử dụng vòng lặp for chạy 504 lần ( (84x4)/8 = 504 ),
mỗi lần chạy như vậy, sẽ đưa ra một lệnh nhập dữ liệu Data, nhưng dữ liệu đưa
vào là trắng trơn, nên tất cả giá trị trong thanh RAM sẽ về 0 hết sau 504 lần lặp.
Sau khi đã hoàn
thành việc xóa RAM, ta đưa lại con trỏ về vị trí khởi điểm là (0,0) với lệnh
GotoXY đã viết.
2.2.3.6. LCDChar
Hàm chức năng
LCDChar có nhiệm vụ đưa ra màn hình một ký tự (character). Biến đưa vào sẽ dưới
dạng biến địa chỉ, và thông qua địa chỉ bảng mã ASCII chúng ta sử dụng, sẽ cho
ra được ký tự trên màn hình.
void LCDChar(char character)
{
unsigned int i;
LCDWrite(LCD_DATA,0x00);
for(i=0;i<5;i++)
LCDWrite(LCD_DATA,ASCII[character
- 0x20][i]);
LCDWrite(LCD_DATA,0x00);
}
Nguyên tắc thực
hiện lệnh:
-
Đầu tiên gọi một lệnh ghi dữ liệu ở cột đầu tiên là dữ liệu trắng,
tương ứng với khoảng trống cần thiết để tách biệt với ký tự nếu có phía trước.
-
Sau đó, ta sử dụng vòng lặp for để ghi tiếp 5 cột
phía sau cột trắng đó để biểu thị ký tự của chúng ta. Do bảng mã chúng ta có địa
chỉ bắt đầu từ 0x20, nên chúng ta chỉ việc lấy biến địa chỉ đưa vào trừ đi 0x20
là ta sẽ có số thứ tự trong mảng ASCII. Tương tự, có biến i là số cột hiển thị,
i sẽ có giá trị chạy từ 0 tới 4. Cần để ý thêm là mảng ASCII của chúng ta là mảng
ASCII [ ] [5] – tức là một ma trận có nhiều hàng, mỗi hàng có 5 cột, và địa chỉ
của ký tự đầu tiên bắt đầu từ 0x20. Như hình bên, ta sẽ thấy rằng RAM của
MSP430 có địa chỉ suất phát từ 0x20 (0200h) nên ta sẽ lấy được đại chỉ biến đầu
tiên bắt đầu từ đó.
-
Cuối cùng, ta cũng thêm một lệnh đưa dữ liệu trắng
vào cột cuối cùng trong 7 cột hiển thị chữ để giúp tách biệt chữ trước với chữ
sau trong quá trình hiển thị nội dung nếu có trên màn hình LCD.
2.2.3.7. LCDString
Hàm LCDString có
chức năng hiển thị một xâu ký tự (string) ra màn hình:
void LCDString(char *characters)
{
while(*characters)
LCDChar(*characters++);
}
Nguyên tắc của việc
nhập một xâu ký tự dựa trên việc tạo một vòng lặp liên tiếp để hiển thị từng ký
tự một cho tới khi hoàn thiện được chuỗi ký tự mong muốn của chúng ta.
Biến đưa vào ở
đây sẽ là một con trỏ (pointer) và con trỏ này sau mỗi lần đưa ra ký tự sẽ được
tăng giá trị lên 1, cho tới khi con trỏ đạt một giá trị rỗng ( =0 ) thì vòng lặp
sẽ dừng lại, tươnng ứng với việc kết thúc chuỗi ký tự.
2.2.3.8. LCDInit
Hàm LCDInit có chức
năng khởi tạo LCD, các biến được đưa vào là các tham số để điều chỉnh các giá
trị contrast, coefficient, bias.
void LCDInit(unsigned char contrast,unsigned char coefficient,unsigned char bias)
{
LCD_DIR
set RST + CE + DC + CLK + DIN
LCD_OUT
clr RST;
LCD_OUT
set RST;
LCDWrite(LCD_CMD,0x21);
LCDWrite(LCD_CMD,0x80 +
contrast);
LCDWrite(LCD_CMD,0x04 +
coefficient);
LCDWrite(LCD_CMD,0x10 +
bias);
LCDWrite(LCD_CMD,0x20);
LCDWrite(LCD_CMD,0x0C);
}
Bước đầu tiên
trong việc thiết lập LCD, như thường lệ là phải xét các bit hoạt động điều kiện
cho LCD.
Đầu tiên, ta set
các bit RST, CE, DC, CLK, DIN tương ứng với các port thành đầu ra bằng cách
thay đổi các bit tương ứng ở LCD_DIR, tương ứng với port 1 của MSP430G2553.
Sau đó, ta hạ mức
RST rồi lại nâng lên mức 1 để tạo tín hiệu reset cho LCD.
Tiếp theo, ta đưa
ra các lệnh thiết lập cho LCD dưới dạng Command:
-
0x21 – Chọn chế độ active cho chip, chế độ địa
chỉ hàng ngang và tập lệnh mở rộng (để chỉnh sửa các tham số hệ thống)
-
0x80 + contrast – Chỉnh sửa độ tương phản cho
màn hình
-
0x04 + coefficient – Chỉnh sửa thông số nhiệt
cho LCD
-
0x10 + bias – Chỉnh sửa thông số bias cho LCD
-
0x20 – Tương tự như lệnh 0x21, nhưng giờ ta bỏ
chế độ tập lệnh mở rộng sau khi đã chỉnh sửa các tham số hệ thống xong.
-
0x0C – Chọn chế độ hiển thị các bit 1 là màu đen
và bit 0 là màu trắng trên màn hình LCD, tức là chữ đen và nền trắng. Ngược lại
thì ta sẽ có lệnh 0x0D
2.2.4. Chương trình chính
Cuối cùng, chương
trình chính của chúng ta chỉ còn bao gồm những bước cuối là:
-
Dừng Watchdog Timer và thiết lập xung Clock
-
Khởi tạo màn hình LCD
-
Thực hiện các lệnh dữ liệu ra màn hình
void main(void)
{
WDTCTL
= WDTPW + WDTHOLD;
BCSCTL1
= 0xFF;
DCOCTL
= 0x0F;
LCDInit(60,0,4);
while(1)
{
LCDClr();
GotoXY(21,0);
LCDString("Luatdh");
GotoXY(24,1);
LCDString("Hanoi");
GotoXY(7,2);
LCDString("University");
GotoXY(35,3);
LCDString("of");
GotoXY(7,4);
LCDString("Sience
and");
GotoXY(7,5);
LCDString("Technology");
Delay(700000);
LCDClr();
unsigned int i;
for(i=0;i<504;i++)
LCDWrite(LCD_DATA,logo_footer_blizzard[i]);
Delay(700000);
LCDWrite(LCD_CMD,0x0D);
Delay(700000);
LCDWrite(LCD_CMD,0x0C);
Delay(700000);
LCDWrite(LCD_CMD,0x0D);
Delay(700000);
LCDWrite(LCD_CMD,0x0C);
Delay(700000);
LCDClr();
for(i=0;i<504;i++)
LCDWrite(LCD_DATA,Terran_bm[i]);
Delay(700000);
LCDWrite(LCD_CMD,0x0D);
Delay(700000);
LCDWrite(LCD_CMD,0x0C);
Delay(700000);
LCDWrite(LCD_CMD,0x0D);
Delay(700000);
LCDWrite(LCD_CMD,0x0C);
Delay(700000);
LCDClr();
for(i=0;i<504;i++)
LCDWrite(LCD_DATA,Zerg_bm[i]);
Delay(700000);
LCDWrite(LCD_CMD,0x0D);
Delay(700000);
LCDWrite(LCD_CMD,0x0C);
Delay(700000);
LCDWrite(LCD_CMD,0x0D);
Delay(700000);
LCDWrite(LCD_CMD,0x0C);
Delay(700000);
LCDClr();
for(i=0;i<504;i++)
LCDWrite(LCD_DATA,Protoss_bm[i]);
Delay(700000);
LCDWrite(LCD_CMD,0x0D);
Delay(700000);
LCDWrite(LCD_CMD,0x0C);
Delay(700000);
LCDWrite(LCD_CMD,0x0D);
Delay(700000);
LCDWrite(LCD_CMD,0x0C);
Delay(700000);
}
}
Các bạn có thể download toàn bộ project mẫu ở đây : MSP430vsLCDNokia5110
thanks anh
ReplyDelete