最近用nRF51822寫了個天馬4線SPI的1.77寸LCD彩屏驅動,效果如下:
屏幕的規格資料為:http://pan.baidu.com/s/1gdfkr5L
屏幕的驅動資料為:http://pan.baidu.com/s/1dD3AUFB
工程結構為:
main.c是main函數所在,程序入口
core文件夾中是nrf51822的啟動文件,不必看
lib中:
nrf_delay.c是一個簡單的延時函數(while循環延時,不精准)
font.c存放一種字體,供屏幕顯示用(屏幕每種字體都會對應一種字體資源,字體是一個個01點陣)
代碼詳解:
main.c中只看main函數即可~(上面相關函數是main函數中被注釋掉部分調用實現一個稍微復雜的動畫用的)
1 int main(void) 2 { 3 unsigned int x,y,cnt; 4 int dis; 5 double my_time; 6 7 myDis.cen_x=COL/2; 8 myDis.cen_y=ROW-50; 9 myDis.dir=0; 10 myDis.pre_dir=0; 11 myDis.len=20; 12 myDis.dir_change=1; 13 14 15 GPIO_Init(); 16 LCD_Init(); 17 DispColor(RED); 18 19 x=y=cnt=0; 20 dis=1; 21 my_time=0; 22 23 24 // while(1<2) 25 // { 26 // cnt++; 27 // myDis.pre_dir=myDis.dir; 28 // myDis.dir=cnt%4; 29 // DispInt(cnt,COL/2-FONT_W*2,ROW,BLUE,RED); 30 // DispStr("X-CASE",x,y,BLACK,RED); 31 // drawDIS(1); 32 33 // x+=dis*1; 34 // y+=dis*2; 35 // if(x>=COL-FONT_W*6) 36 // { 37 // dis=-1; 38 // } 39 // if(x<=0) 40 // { 41 // dis=1; 42 // } 43 // nrf_delay_ms(100); 44 // } 45 while(1<2) 46 { 47 my_time+=0.1; 48 //DrawLine(x,x+30,y,y+30,RED); 49 x=60*cos(my_time)+my_time; 50 y=60*sin(my_time)+80; 51 52 //nrf_delay_ms(10); 53 if(my_time<300)DrawLine(x,x+2,y,y+2,BLUE); 54 else if(my_time<600)DrawLine(x,x+2,y,y+2,GREEN); 55 else if(my_time<900)DrawLine(x,x+2,y,y+2,BLACK); 56 else { 57 DispColor(RED); 58 my_time=0; 59 } 60 } 61 }
nrf_delay.c中是一個簡單延時函數:
1 #include "compiler_abstraction.h" 2 #include "nrf.h" 3 #include "nrf_delay.h" 4 5 /*lint --e{438} "Variable not used" */ 6 void nrf_delay_ms(uint32_t volatile number_of_ms) 7 { 8 while(number_of_ms != 0) 9 { 10 number_of_ms--; 11 nrf_delay_us(999); 12 } 13 }
下面重點看nrf_lcd部分:
先看.H文件:
1 #ifndef _NRF_LCD_H 2 #define _NRF_LCD_H 3 4 5 #include "pca10001.h" 6 #include "nrf_gpio.h" 7 #include "nrf_delay.h" 8 #include "font.h" 9 /* 10 引腳高低電平宏定義 11 */ 12 #define CS_SET nrf_gpio_pin_set(CS) 13 #define CS_CLEAR nrf_gpio_pin_clear(CS) 14 #define RS_SET nrf_gpio_pin_set(RS) 15 #define RS_CLEAR nrf_gpio_pin_clear(RS) 16 #define RET_SET nrf_gpio_pin_set(RET) 17 #define RET_CLEAR nrf_gpio_pin_clear(RET) 18 #define SCL_SET nrf_gpio_pin_set(SCL) 19 #define SCL_CLEAR nrf_gpio_pin_clear(SCL) 20 #define SDA_SET nrf_gpio_pin_set(SDA) 21 #define SDA_CLEAR nrf_gpio_pin_clear(SDA) 22 /* 23 宏定義等待函數 24 */ 25 #define DELAY_MS(n) nrf_delay_ms(n) 26 27 28 29 //------------------------------------------------------ 30 #define PIC_WIDTH 160 //預備向LCD顯示區域填充的圖片的大小 31 #define PIC_HEIGHT 160 32 33 #define ROW 160 //顯示的行、列數 34 #define COL 128 35 36 37 #define BLUE 0xF800 //定義顏色常量 38 #define GREEN 0x07E0 39 #define RED 0x001F 40 #define WHITE 0xFFFF 41 #define BLACK 0x0000 42 #define GRAY 0xEF5D //0x2410 43 #define GRAY75 0x39E7 44 #define GRAY50 0x7BEF 45 #define GRAY25 0xADB5 46 47 48 void GPIO_Init(void); 49 void LCD_Init(void); 50 void DispColor(unsigned int color); 51 void DispInt(unsigned int i, unsigned int Xstart, unsigned int Ystart, unsigned int TextColor, unsigned int BackColor); 52 void DispStr(unsigned char *str, unsigned int Xstart, unsigned int Ystart, unsigned int TextColor, unsigned int BackColor); 53 void DrawLine(unsigned int Xstart, unsigned int Xend, unsigned int Ystart, unsigned int Yend, unsigned int color); 54 55 #endif
1、其中引腳高低電平宏定義是將控制LCD的5條線的引腳的高低電平用宏定義,方便移植
2、將nrf_delay中的毫秒延時函數也用宏定義為DELAY_MS也是方便移植
3、這里PIC_WIDTH和PIC_HEIGHT沒用~是我的大的工程中用的~表示一個圖片的大小,不必看
4、ROW和COL表示屏幕的長和寬
5、37~45行是常用顏色定義
6、48~53是對外的函數,要想用屏幕首先要調用GPIO初始化函數,然后調用LCD初始化函數將LCD屏幕顯示屬性的一些命令傳送給屏幕,最后可以調用dispcolor將整個屏幕顯示一種顏色,調用dispInt顯示一個int的數值在屏幕上,dispstr將一個字符串顯示在屏幕上(不要覺得這兩個函數不起眼,其實要有字庫支持,不像你平時在高級的操作系統上調用print那么簡單),這里drawline函數不僅可以繪制直線,還能繪制矩形,直線的寬度變寬就變成矩形了~呵呵
在.c中則是上面函數的具體實現:
首先看GPIO初始化函數:
1 void GPIO_Init() 2 { 3 nrf_gpio_cfg_output(CS); 4 nrf_gpio_cfg_output(RS); 5 nrf_gpio_cfg_output(RET); 6 nrf_gpio_cfg_output(SCL); 7 nrf_gpio_cfg_output(SDA); 8 }
1、該函數是將我們對屏幕控制的5條線設置成輸出模式,這個和nRF51822系統相關,要根據nRF限制進行設置,51單片機則不用設置引腳模式,stm則需要設置引腳模式
2、這里的CS\RS等引腳是在pca10001.h中定義的,也就是指定用nRF51的哪些引腳,這里可見用了第10、11、13、14、15五個引腳
接着看模擬的SPI發送一個字節的函數:
1 void SendDataSPI(unsigned char dat) 2 { 3 unsigned char i; 4 for(i = 0; i < 8; i++) 5 { 6 if( (dat & 0x80) != 0 ) SDA_SET; 7 else SDA_CLEAR; 8 9 dat <<= 1; 10 11 SCL_CLEAR; 12 SCL_SET; 13 } 14 }
1、這個是根據屏幕驅動協議ILI9163文檔中要求而寫的通信底層協議
2、函數中將dat的8位從高到底發送出去,11、12行SCL變化一次將數據發送出去一位
3、接下來會基於這個函數實現向LCD屏幕的寫數據和寫命令函數,然后又基於寫數據和寫命令函數封裝成繪制圖形的函數
5.1、dispint是用一個4位的形式顯示一個整數,不足的補零
5.2、dispstr是顯示一個字符串
5.3、drawline不僅能繪制直線還能繪制矩形
4.1、putpixel是繪制一個像素點,和底層writeonedot不同就在於先調用blockwrite刷新了該點的區域
4.2、dispcolor是將整個屏用一種顏色刷屏
4.3、disonechar是顯示一個字符
3.1、blockwrite相當於擦除,每次要對大范圍的區域進行刷新前都要調用這個函數進行“擦除”
可見更高層的函數都調用了這個函數,因為無論是顯示字符還是繪制區域都要先“擦除”
3.2、lcd_init是屏幕初始化函數
2.1、寫命令和寫數據函數是基於SPI發送一字節函數寫的
2.2、writeonedot函數是填充一像素點數據,上面繪制直線和繪制字符都用到了它
1、SPI發送一字節的底層通信函數
從下到上依次為:數據傳輸實現層、基礎數據段傳輸封裝層、高級數據段傳輸封裝層、基礎應用層、高級應用層
- 高級數據段傳輸封裝層
1 void WriteComm(unsigned int i) 2 { 3 CS_CLEAR; 4 RS_CLEAR; 5 SendDataSPI(i); 6 CS_SET; 7 } 8 void WriteData(unsigned int i) 9 { 10 CS_CLEAR; 11 RS_SET; 12 SendDataSPI(i); 13 CS_SET; 14 } 15 void WriteOneDot(unsigned int color) 16 { 17 CS_CLEAR; 18 RS_SET; 19 20 SendDataSPI(color >> 8); 21 SendDataSPI(color); 22 23 CS_SET; 24 }
- 高級數據段傳輸封裝層
1 /* 2 LCD初始化函數 3 */ 4 void LCD_Init(void) 5 { 6 DELAY_MS(100); 7 RET_SET; 8 DELAY_MS(100); 9 RET_CLEAR; 10 DELAY_MS(100); 11 RET_SET; 12 DELAY_MS(100); 13 14 //-------------Start Initial Sequence--------// 15 WriteComm(0x11); //Exit Sleep 16 DELAY_MS(10);//20 17 WriteComm(0x26); //Set Default Gamma 18 WriteData(0x04); 19 20 WriteComm(0xB1);//Set Frame Rate 21 WriteData(0x0B); 22 WriteData(0x14); 23 24 WriteComm(0xC0); //Set VRH1[4:0] & VC[2:0] for VCI1 & GVDD 25 WriteData(0x0C); 26 WriteData(0x05); 27 28 WriteComm(0xC1); //Set BT[2:0] for AVDD & VCL & VGH & VGL 29 WriteData(0x02); 30 31 WriteComm(0xC5); //Set VMH[6:0] & VML[6:0] for VOMH & VCOML 32 WriteData(0x3F);//44 33 WriteData(0x48); 34 35 WriteComm(0xC7);// Set VMF 36 WriteData(0xC2); 37 38 WriteComm(0x2A); //Set Column Address 39 WriteData(0x00); 40 WriteData(0x00); 41 WriteData(0x00); 42 WriteData(0x7F); 43 WriteComm(0x2B); //Set Page Address 44 WriteData(0x00); 45 WriteData(0x00); 46 WriteData(0x00); 47 WriteData(0x9F); 48 49 WriteComm(0x3A); //Set Color Format 50 WriteData(0x55); 51 WriteComm(0x36); 52 WriteData(0xC8); 53 54 WriteComm(0xF2); //Enable Gamma bit 55 WriteData(0x01); 56 WriteComm(0xE0); 57 WriteData(0x3F);//p1 58 WriteData(0x25);//p2 59 WriteData(0x21);//p3 60 WriteData(0x24);//p4 61 WriteData(0x1D);//p5 62 WriteData(0x0D);//p6 63 WriteData(0x4C);//p7 64 WriteData(0xB8);//p8 65 WriteData(0x38);//p9 66 WriteData(0x17);//p10 67 WriteData(0x0F);//p11 68 WriteData(0x08);//p12 69 WriteData(0x04);//p13 70 WriteData(0x02);//p14 71 WriteData(0x00);//p15 72 WriteComm(0xE1); 73 WriteData(0x00);//p1 74 WriteData(0x1A);//p2 75 WriteData(0x1E);//p3 76 WriteData(0x0B);//p4 77 WriteData(0x12);//p5 78 WriteData(0x12);//p6 79 WriteData(0x33);//p7 80 WriteData(0x47);//p8 81 WriteData(0x47);//p9 82 WriteData(0x08);//p10 83 WriteData(0x20);//p11 84 WriteData(0x27);//p12 85 WriteData(0x3C);//p13 86 WriteData(0x3D);//p14 87 WriteData(0x3F);//p15 88 89 90 WriteComm(0x36); //MX, MY, RGB mode 91 WriteData(0xC0);//c8豎屏 68橫屏 92 93 WriteComm(0x29); // Display On 94 95 WriteComm(0x2C); 96 } 97 98 /* 99 LCD塊寫(大量數據修改,相當於擦除) 100 */ 101 void BlockWrite(unsigned int Xstart, unsigned int Xend, unsigned int Ystart, unsigned int Yend) 102 { 103 //ILI9163C 104 WriteComm(0x2A); 105 WriteData(Xstart >> 8); 106 WriteData(Xstart); 107 WriteData(Xend >> 8); 108 WriteData(Xend); 109 110 WriteComm(0x2B); 111 WriteData(Ystart >> 8); 112 WriteData(Ystart); 113 WriteData(Yend >> 8); 114 WriteData(Yend); 115 116 WriteComm(0x2c); 117 }
注:在LCD初始化函數和blockwrite函數中經常會看到writecomm或者部分writedata參數有些是很奇怪的數據,如0x2A,0x2B...其實這些是屏幕驅動芯片所規定的一些寄存器的地址和這些寄存器的配置數值。這里屏幕初始化就是按照屏幕驅動芯片規格來配置的~而塊寫這個功能的實現也是要嚴格按照屏幕驅動說明的!!!反正,玩硬件少不了和各種文檔打交道!!!
- 基礎應用層
1 /* 2 繪制一個像素點 3 */ 4 void PutPixel(unsigned int x, unsigned int y, unsigned int color) 5 { 6 BlockWrite(x, x, y, y); 7 CS_CLEAR; 8 RS_SET; 9 SendDataSPI(color >> 8); 10 SendDataSPI(color); 11 CS_SET; 12 } 13 /* 14 LCD顯示顏色(顏色已在.h文件中定義) 15 */ 16 void DispColor(unsigned int color) 17 { 18 unsigned int i, j; 19 BlockWrite(0, COL - 1, 0, ROW - 1); 20 21 CS_CLEAR; 22 RS_SET; 23 for(i = 0; i < ROW; i++) 24 { 25 for(j = 0; j < COL; j++) 26 { 27 SendDataSPI(color >> 8); 28 SendDataSPI(color); 29 } 30 } 31 CS_SET; 32 } 33 void DispOneChar(unsigned char ord, unsigned int Xstart, unsigned int Ystart, unsigned int TextColor, unsigned int BackColor) // ord:0~95 34 { 35 unsigned char i, j; 36 unsigned char *p; 37 unsigned char dat; 38 unsigned int index; 39 40 BlockWrite(Xstart, Xstart + (FONT_W - 1), Ystart, Ystart + (FONT_H - 1)); 41 42 index = ord; 43 44 if(index > 95) //95:ASCII CHAR NUM 45 index = 95; 46 47 index = index * ((FONT_W / 8) * FONT_H); 48 49 p = ascii; 50 p = p + index; 51 52 for(i = 0; i < (FONT_W / 8 * FONT_H); i++) 53 { 54 dat = *p++; 55 for(j = 0; j < 8; j++) 56 { 57 if((dat << j) & 0x80) 58 { 59 WriteOneDot(TextColor); 60 } 61 else 62 { 63 WriteOneDot(BackColor); 64 } 65 } 66 }
67 }
注:1、可見基礎應用層都在繪制前先調用了塊寫,並且下面准備用多大區域就用塊寫寫對應多大區域
注:2、繪制一像素點的升級版是填充整個屏幕,不同是不必for循環調用繪制一像素來實現刷屏,而是直接先塊寫然后刷屏
注:3、顯示一個字符需要調用字庫,字庫格式如下:
- 高級應用層
1 void DispInt(unsigned int i, unsigned int Xstart, unsigned int Ystart, unsigned int TextColor, unsigned int BackColor) 2 { 3 if(Xstart > ((COL - 1) - FONT_W * 4)) 4 { 5 Xstart = (COL - 1) - FONT_W * 4; 6 } 7 if(Ystart > ((ROW - 1) - FONT_H)) 8 { 9 Ystart = (Ystart - 1) - FONT_H; 10 } 11 12 DispOneChar((i >> 12) % 16, Xstart, Ystart, TextColor, BackColor); //ID value 13 DispOneChar((i >> 8) % 16, Xstart + FONT_W, Ystart, TextColor, BackColor); 14 DispOneChar((i >> 4) % 16, Xstart + FONT_W * 2, Ystart, TextColor, BackColor); 15 DispOneChar(i % 16, Xstart + FONT_W * 3, Ystart, TextColor, BackColor); 16 17 BlockWrite(0, COL - 1, 0, ROW - 1); 18 } 19 void DispStr(unsigned char *str, unsigned int Xstart, unsigned int Ystart, unsigned int TextColor, unsigned int BackColor) 20 { 21 while(!(*str == '\0')) 22 { 23 DispOneChar(ToOrd(*str++), Xstart, Ystart, TextColor, BackColor); 24 25 if(Xstart > ((COL - 1) - FONT_W)) 26 { 27 Xstart = 0; 28 Ystart = Ystart + FONT_H; 29 } 30 else 31 { 32 Xstart = Xstart + FONT_W; 33 } 34 35 if(Ystart > ((ROW - 1) - FONT_H)) 36 { 37 Ystart = 0; 38 } 39 } 40 BlockWrite(0, COL - 1, 0, ROW - 1); 41 } 42 /* 43 繪制一片區域(名字為線,其實可以刷一個面) 44 */ 45 void DrawLine(unsigned int Xstart, unsigned int Xend, unsigned int Ystart, unsigned int Yend, unsigned int color) 46 { 47 unsigned int i, j; 48 49 BlockWrite(Xstart, Xend, Ystart, Yend); 50 51 for(i = Ystart; i < Yend + 1; i++) 52 { 53 for(j = Xstart; j < Xend + 1; j++) 54 { 55 WriteOneDot(color); 56 } 57 } 58 }
注:1、無論是寫一個字符還是寫字符串或是整數,最麻煩的不過是計算所需要的像素區域的值位於字庫的哪里,所以里面多了很多計算~
注:2、繪制直線和區域填充類似,只是區域填充直接調用底層SPI數據傳輸函數,這里調用了writeonedot函數。我覺得也可以直接調用底層,也許會加快繪制速度!
小結
上面一個小小的驅動函數組織不算完美,我只是用廠家給的demo移植到nRF上,所以函數調用有點亂~
用nRF51822四線SPI驅動1.77寸(128X160像素,每個像素需要16位數據)刷屏的速度人是可以感知的!
因此,如果想利用它來做復雜的動畫就有點難度了~
而我目前正遇到這個難題~攻克中!!!
該nRF51822晶振是16M的,刷一張圖時間大概0.8s左右,接下來我將用stm32,72M的試試~
上述工程代碼:http://pan.baidu.com/s/1bnHmi55
@beautifulzzzz
2015-11-25 持續更新中~