轉者注:AIHHLI 咚冬兄寫得比較生動,值得一讀;對其他的點陣屏的研究有很大的參考價值。 基于ST7920控制的12864液晶用于字符顯示很方便的,但網友說用它顯示圖形并不合適,原因就是它繪圖時先要關閉顯示,繪完后又要打開,速度會較慢。我沒有用過別的液晶,手中只有這一款,擺弄了幾天,掌握了一點東西,寫出來共享。
首先,我們知道,圖形都是由像素點組成的,繪圖的基礎其實就是畫點。只要我們能點亮液晶的任意一個像素點,那么繪圖就不是什么難事了。萬丈高樓平地起嘛,先要做的,當然是要打好基礎。 ST7920提供了用于繪圖的GDRAM(graph display RAM)。共 64×32 個字節的空間(由擴充指令設定繪圖 RAM 地址),最多可以控制 256×64點陣的二維繪圖緩沖空間。在它的Datasheet給出了GDRAM的坐標地址對照表: 用坐標表示,就是這樣: 它的橫坐標每一個地址都是16 位的。共16個地址,256位。
很明顯,它能控制256*64像素的液晶屏,而我們的只是128*64像素液晶屏,顯然只用到它的一部分。
我剛開始以為它對應屏幕的繪圖RAM是這樣分布的(如紅色部分): 結果栽了大根頭,后來終于弄明白,原來它對應屏幕的GDRAM是這樣分布的: 只要我們清楚了它的GDRAM和屏幕上像素點的映射(對應)關系,點亮對應的像素點就容易多了。要點亮某一個像素點,就是將這個像素點在GDRAM中對應的位置1,這個相信沒人會不知道吧?
我們先討論一下思路,再一步步寫代碼。我覺得,思路要比代碼重要的多,只要你的思路通了,正確了,那么寫出代碼肯定會很容易。
首先,給你x,y的坐標,要你點亮一個點,要怎么做呢?從上面的圖我們知道,它是分為兩個半屏的,首先,我們要確定這個點是在上半屏還是下半屏,然后確定它是在那一行(縱坐標Y),再確定它是在哪一個字節的哪一個位(也就是確定它在那一列,即橫坐標X)。這些都確定后我們就定位到某一個具體的位上了,只就將這個位置1,就OK了。
下面我們邊寫代碼邊討論。
因為這里僅僅是討論如何在12864上打點的,而不是給12864寫一個驅動,所以對于基本的數據讀寫函數,我們不做討論,這里假設已經有了如下基本函數: ?
1
2
3
| void lcd_write_cmd(unsigned char); //lcd 命令寫
void lcd_write_data(unsigned char); //lcd 數據寫
unsigend char lcd_read_data(void); //lcd 數據讀
|
好了,就這些了。
為了方便,我們定義如下宏: ?
1
2
3
4
| #define BASIC_SET 0x00 //基本指令集,后面的數字查數據手冊,下同。
#define EXTEND_SET 0x00 //擴展指令集
#define DRAW_ON 0x00 //繪圖顯示開
#define DRAW_OFF 0x00 //繪圖顯示關
|
我們現在開始寫點亮某一個點的函數: ?
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
| void lcd_set_dot(unsigned char x, unsigned char y)
{
unsigned char x_byet, x_bit; //在橫坐標的哪一個字節,哪一個位
unsigned char y_byte, y_bit;
x_byte = x / 16; //算出它在哪一個字節(地址)
//注意一個地址是16位的
x_bit = x % 16; //算出它在哪一個位
y_byte = y /32; //y是沒在哪個字節這個說法
//這里只是確定它在上半屏還是下半屏
//0:上半屏 1:下半屏
y_bit = y % 32; //y_bit確定它是在第幾行
lcd_write_cmd(EXTEND_SET); //擴展指令集
lcd_write_cmd(DRAW_OFF); //繪圖顯示關閉
lcd_write_cmd(0x80 + y_bit); //先寫垂直地址
//具體參照數據手冊
lcd_write_cmd(0x80 + x_byte + 8 * y_byte); //水平坐標
//下半屏的水平坐標起始地址為0x88
//(+8*y_byte)就是用來確定在上半屏還是下半屏
if (x_bit < 8) //如果x_bit位數小于8
{
lcd_write_data(0x01 << (7 - x_bit)); //寫高字節。因為坐標是從左向右的
//而GDRAM高位在左,底位在右
lcd_write_data(0x00); //低字節全部填0
}
else
{
lcd_write_data(0x00); //高字節全部填0
lcd_write_data(0x01 << (15 - x_bit));
}
lcd_write_cmd(DRAW_ON); //打開繪圖顯示
lcd_write_cmd(BASIC_SET); //回到基本指令集,畢竟ST7920是以字符為主的
return ;
}
|
基本畫點函數算是完成了,但是我們如果使用這個函數,就會發現問題。你且用它沿橫坐標畫幾個連續的點試試,肯定不是你想要的結果。
出現問題的原因是因為我們畫點時對其余的位全部填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
| void lcd_set_dot(unsigned char x, unsigned char y)
{
unsigned char x_byet, x_bit; //在橫坐標的哪一個字節,哪一個位
unsigned char y_byte, y_bit;
unsigned char tmph, tmpl; //定義兩個臨時變量,用于存放讀出來的數據
x &= 0x7F;
y &= 0x3F;
x_byte = x / 16; //算出它在哪一個字節(地址)
//注意一個地址是16位的
x_bit = x&0x0F; //算出它在哪一個位
y_byte = y /32; //y是沒在哪個字節這個說法
//這里只是確定它在上半屏還是下半屏
//0:上半屏 1:下半屏
y_bit = y&0x3F; //y_bit確定它是在第幾行
lcd_write_cmd(EXTEND_SET); //擴展指令集
lcd_write_cmd(DRAW_OFF); //繪圖顯示關閉
lcd_write_cmd(0x80 + y_bit); //先寫垂直地址(最高位必須為1)
//具體參照數據手冊
lcd_write_cmd(0x80 + x_byte + 8 * y_byte); //水平坐標
//下半屏的水平坐標起始地址為0x88
//(+8*y_byte)就是用來確定
//在上半屏還是下半屏
lcd_read_data(); //先空讀一次
tmph = lcd_read_data(); //讀高位
tmpl = lcd_read_data();
lcd_write_cmd(0x80 + y_bit); //讀操作會改變AC,所以重新設置一次
lcd_write_cmd(0x80 + x_byte + 8 * y_byte);
if (x_bit < 8) //如果x_bit位數小于8
{
lcd_write_data(tmph | (0x01 << (7 - x_bit))); //寫高字節。因為坐標是從左向右的
//而GDRAM高位在左,底位在右
lcd_write_data(tmpl); //原數據送回
}
else
{
lcd_write_data(tmph); //原數據送回
lcd_write_data(tmpl | (0x01 << (15 - x_bit)));
}
lcd_write_cmd(DRAW_ON); //打開繪圖顯示
lcd_write_cmd(BASIC_SET); //回到基本指令集,畢竟ST7920是以字符為主的
return ;
}
|
畫點函數到此就完成了,剩下的事情就是對函數的優化了。例如對入口參數的檢查,對乘除法的優化等等。
|