17.3 軟體設計 軟體設計我們依舊在之前的工程上面增加,首先在HARDWARE檔案夾下建立一個OLED的檔案夾。然後打開USER檔案夾下的工程,建立一個oled.c的檔案和oled.h的頭檔案,儲存在OLED檔案夾下,并将OLED檔案夾加入頭檔案包含路徑。 oled.c的代碼,由于比較長,這裡我們就不貼出來了,僅介紹幾個比較重要的函數。首先是OLED_Init函數,該函數的結構比較簡單,開始是對IO口的初始化,這裡我們用了宏定義OLED_MODE來決定要設定的IO口,其他就是一些初始化序列了,我們按照廠家提供的資料來做就可以。最後要說明一點的是,因為OLED是無背光的,在初始化之後,我們把顯存都清空了,是以我們在螢幕上是看不到任何内容的,跟沒通電一個樣,不要以為這就是初始化失敗,要寫入資料子產品才會顯示的。OLED_Init函數代碼如下: //初始化SSD1306 void OLED_Init(void) { RCC->APB2ENR|=1<<4; //使能PORTC時鐘 RCC->APB2ENR|=1<<5; //使能PORTD時鐘 RCC->APB2ENR|=1<<8; //使能PORTG時鐘 GPIOD->CRL&=0XF0FF0FFF;//PD3,6推挽輸出 GPIOD->CRL|=0X03003000; GPIOD->ODR|=1<<3; GPIOD->ODR|=1<<6; #if OLED_MODE==1 GPIOC->CRL=0X33333333; //PC0~7 OUT GPIOC->ODR|=0X00FF; GPIOG->CRH&=0X000FFFFF;//PG13,14,15 OUT GPIOG->CRH|=0X33300000; GPIOG->ODR|=7<<13; #else GPIOC->CRL&=0XFFFFFF00; //PC0,1 OUT GPIOC->CRL|=0X00000033; GPIOC->ODR|=3<<0; GPIOG->CRH&=0X0FFFFFFF;//RST GPIOG->CRH|=0X30000000; GPIOG->ODR|=1<<15; #endif OLED_RST=0; //複位 delay_ms(100); OLED_RST=1; OLED_WR_Byte(0xAE,OLED_CMD);//關閉顯示 OLED_WR_Byte(0xD5,OLED_CMD);//設定時鐘分頻因子,震蕩頻率 OLED_WR_Byte(80,OLED_CMD); //[3:0],分頻因子;[7:4],震蕩頻率 OLED_WR_Byte(0xA8,OLED_CMD);//設定驅動路數 OLED_WR_Byte(0X3F,OLED_CMD);//預設0X3F(1/64) OLED_WR_Byte(0xD3,OLED_CMD);//設定顯示偏移 OLED_WR_Byte(0X00,OLED_CMD);//預設為0 OLED_WR_Byte(0x40,OLED_CMD);//設定顯示開始行 [5:0],行數. OLED_WR_Byte(0x8D,OLED_CMD);//電荷泵設定 OLED_WR_Byte(0x14,OLED_CMD);//bit2,開啟/關閉 OLED_WR_Byte(0x20,OLED_CMD);//設定記憶體位址模式 OLED_WR_Byte(0x02,OLED_CMD); //[1:0],00,列位址模式;01,行位址模式;10,頁位址模式;預設10; OLED_WR_Byte(0xA1,OLED_CMD);//段重定義設定,bit0:0,0->0;1,0->127; OLED_WR_Byte(0xC0,OLED_CMD); //設定COM掃描方向;bit3:0,普通模式;1,重定義模式 COM[N-1]->COM0;N:驅動路數 OLED_WR_Byte(0xDA,OLED_CMD);//設定COM硬體引腳配置 OLED_WR_Byte(0x12,OLED_CMD);//[5:4]配置 OLED_WR_Byte(0x81,OLED_CMD);//對比度設定 OLED_WR_Byte(0xEF,OLED_CMD);//1~255;預設0X7F (亮度設定,越大越亮) OLED_WR_Byte(0xD9,OLED_CMD);//設定預充電周期 OLED_WR_Byte(0xf1,OLED_CMD);//[3:0],PHASE 1;[7:4],PHASE 2; OLED_WR_Byte(0xDB,OLED_CMD);//設定VCOMH 電壓倍率 OLED_WR_Byte(0x30,OLED_CMD);//[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc; OLED_WR_Byte(0xA4,OLED_CMD);//全局顯示開啟;bit0:1,開啟;0,關閉;(白屏/黑屏) OLED_WR_Byte(0xA6,OLED_CMD);//設定顯示方式;bit0:1,反相顯示;0,正常顯示 OLED_WR_Byte(0xAF,OLED_CMD);//開啟顯示 OLED_Clear(); } 接着,要介紹的是OLED_Refresh_Gram函數。我們在STM32内部定義了一個塊GRAM:u8 OLED_GRAM[128][8];此部分GRAM對應OLED子產品上的GRAM。在操作的時候,我們隻要修改STM32内部的GRAM就可以了,然後通過OLED_Refresh_Gram函數把GRAM一次重新整理到OLED 的GRAM上。該函數代碼如下: //更新顯存到LCD void OLED_Refresh_Gram(void) { u8 i,n; for(i=0;i<8;i++) { OLED_WR_Byte (0xb0+i,OLED_CMD); //設定頁位址(0~7) OLED_WR_Byte (0x00,OLED_CMD); //設定顯示位置—列低位址 OLED_WR_Byte (0x10,OLED_CMD); //設定顯示位置—列高位址 for(n=0;n<128;n++)OLED_WR_Byte(OLED_GRAM[n],OLED_DATA); } } OLED_Refresh_Gram函數先設定頁位址,然後寫入列位址(也就是縱坐标),然後從0開始寫入128個位元組,寫滿該頁,最後循環把8頁的内容都寫入,就實作了整個從STM32顯存到OLED顯存的拷貝。 OLED_Refresh_Gram函數還用到了一個外部函數,也就是我們接着要介紹的函數:OLED_WR_Byte,該函數直接和硬體相關,函數代碼如下: #if OLED_MODE==1 //向SSD1306寫入一個位元組。 //dat:要寫入的資料/指令 //cmd:資料/指令标志 0,表示指令;1,表示資料; void OLED_WR_Byte(u8 dat,u8 cmd) { DATAOUT(dat); OLED_RS=cmd; OLED_CS=0; OLED_WR=0; OLED_WR=1; OLED_CS=1; OLED_RS=1; } #else //向SSD1306寫入一個位元組。 //dat:要寫入的資料/指令 //cmd:資料/指令标志 0,表示指令;1,表示資料; void OLED_WR_Byte(u8 dat,u8 cmd) { u8 i; OLED_RS=cmd; //寫指令 OLED_CS=0; for(i=0;i<8;i++) { OLED_SCLK=0; if(dat&0x80)OLED_SDIN=1; else OLED_SDIN=0; OLED_SCLK=1; dat<<=1; } OLED_CS=1; OLED_RS=1; } #endif 這裡有2個一樣的函數,通過宏定義OLED_MODE來決定使用哪一個。如果OLED_MODE=1,就定義為并口模式,選擇第一個函數,而如果為0,則為4順序列槽模式,選擇第二個函數。這兩個函數輸入參數均為2個:dat和cmd,dat為要寫入的資料,cmd則表明該資料是指令還是資料。這兩個函數的時序操作就是根據上面我們對8080接口以及4線SPI接口的時序來編寫的。 OLED_GRAM[128][8]中的128代表列數(x坐标),而8代表的是頁,每頁又包含8行,總共64行(y坐标)。從高到低對應行數從小到大。比如,我們要在x=100,y=29這個點寫入1,則可以用這個句子實作: OLED_GRAM[100][4]|=1<<2; 一個通用的在點(x,y)置1表達式為: OLED_GRAM[x][7-y/8]|=1<<(7-y%8); 其中x的範圍為:0~127;y的範圍為:0~63。 是以,我們可以得出下一個将要介紹的函數:畫點函數,void OLED_DrawPoint(u8 x,u8 y,u8 t);函數代碼如下: void OLED_DrawPoint(u8 x,u8 y,u8 t) { u8 pos,bx,temp=0; if(x>127||y>63)return;//超出範圍了. pos=7-y/8; bx=y%8; temp=1<<(7-bx); if(t)OLED_GRAM[x][pos]|=temp; else OLED_GRAM[x][pos]&=~temp; } 該函數有3個參數,前兩個是坐标,第三個t為要寫入1還是0。該函數實作了我們在OLED子產品上任意位置畫點的功能。 在介紹完畫點函數之後,我們介紹一下顯示字元函數,OLED_ShowChar,在介紹之前,我們來介紹一下字元(ASCII字元集)是怎麼顯示在OLED子產品上去的。要顯示字元,我們先要有字元的點陣資料,ASCII常用的字元集總共有95個,從空格符開始,分别為: !"#$%&\'()*+,-0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~. 我們先要得到這個字元集的點陣資料,這裡我們介紹一個款很好的字元提取軟體:PCtoLCD2002完美版。該軟體可以提供各種字元,包括漢字(字型和大小都可以自己設定)陣提取,且取模方式可以設定好幾種,常用的取模方式,該軟體都支援。該軟體還支援圖形模式,也就是使用者可以自己定義圖檔的大小,然後畫圖,根據所畫的圖形再生成點陣資料,這功能在制作圖示或圖檔的時候很有用。 該軟體的界面如圖17.3.1所示: 圖17.3.1 PCtoLCD2002軟體界面 然後我們選擇設定,在設定裡面設定取模方式如圖17.3.2所示: 圖17.3.2 設定取模方式 上圖設定的取模方式,在右上角的取模說明裡面有,即:從第一列開始向下每取8個點作為一個位元組,如果最後不足8個點就補滿8位。取模順序是從高到低,即第一個點作為最高位。如*-------取為10000000。其實就是按如圖17.3.3所示的這種方式: 圖17.3.3 取模方式圖解 從上到下,從左到右,高位在前。我們按這樣的取模方式,然後把ASCII字元集按12*6大小和16*0大小取模出來(對應漢字大小為12*12和16*16,字元的隻有漢字的一半大!),儲存在oledfont.h裡面,每個12*6的字元占用12個位元組,每個16*8的字元占用16個位元組。具體見oledfont.h部分代碼(該部分我們不再這裡列出來了,請大家參考CD光牒裡面的代碼)。 在知道了取模方式之後,我們就可以根據取模的方式來編寫顯示字元的代碼了,這裡我們針對以上取模方式的顯示字元代碼如下: void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode) { u8 temp,t,t1; u8 y0=y; chr=chr-\' \';//得到偏移後的值 for(t=0;t<size;t++) { if(size==12)temp=oled_asc2_1206[chr][t]; //調用1206字型 else temp=oled_asc2_1608[chr][t]; //調用1608字型 for(t1=0;t1<8;t1++) { if(temp&0x80)OLED_DrawPoint(x,y,mode); else OLED_DrawPoint(x,y,!mode); temp<<=1; y++; if((y-y0)==size) { y=y0; x++; break; } } } } 該函數為字元以及字元串顯示的核心部分,函數中chr=chr-\' \';這句是要得到在字元點陣資料裡面的實際位址,因為我們的取模是從空格鍵開始的,例如oled_asc2_1206[0][0],代表的是空格符開始的點陣碼。在接下來的代碼,我們也是按照從上到小,從左到右的取模方式來編寫的,先得到最高位,然後判斷是寫1還是0,畫點;接着讀第二位,如此循環,直到一個字元的點陣全部取完為止。這其中涉及到列位址和行位址的自增,根據取模方式來了解,就不難了。 oled.c的内容就為大家介紹到這裡,将oled.c儲存,然後加入到HARDWARE組下。接下來我們在oled.h中輸入如下代碼: #ifndef __OLED_H #define __OLED_H #include "sys.h" #include "stdlib.h" //OLED模式設定 //0:4線串行模式 //1:并行8080模式 #define OLED_MODE 1 //---------------------------OLED端口定義-------------------------- #define OLED_CS PDout(6) #define OLED_RST PGout(15) #define OLED_RS PDout(3) #define OLED_WR PGout(14) #define OLED_RD PGout(13) //PC0~7,作為資料線 #define DATAOUT(x) GPIOC->ODR=(GPIOC->ODR&0xff00)|(x&0x00FF); //輸出 //使用4線串行接口時使用 #define OLED_SCLK PCout(0) #define OLED_SDIN PCout(1) #define OLED_CMD 0 //寫指令 #define OLED_DATA 1 //寫資料 //OLED控制用函數 void OLED_WR_Byte(u8 dat,u8 cmd); void OLED_Display_On(void); void OLED_Display_Off(void); void OLED_Refresh_Gram(void); void OLED_Init(void); void OLED_Clear(void); void OLED_DrawPoint(u8 x,u8 y,u8 t); void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot); void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode); void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size); void OLED_ShowString(u8 x,u8 y,const u8 *p); #endif 該部分比較簡單,OLED_MODE的定義也在這個檔案裡面,我們必須根據自己OLED子產品BS0~2的設定(目前代碼僅支援8080和4線SPI)來确定OLED_MODE的值。 儲存好oled.h之後,我們就可以在主程式裡面編寫我們的應用層代碼了,該部分代碼如下: int main(void) { u8 t; Stm32_Clock_Init(9); //系統時鐘設定 uart_init(72,9600); //序列槽初始化為9600 delay_init(72); //延時初始化 LED_Init(); //初始化與LED連接配接的硬體接口 OLED_Init(); //初始化液晶 OLED_ShowString(0,0, "0.96\' OLED TEST"); OLED_ShowString(0,16,"ATOM@ALIENTEK"); OLED_ShowString(0,32,"2010/06/3"); OLED_ShowString(0,48,"ASCII:"); OLED_ShowString(63,48,"CODE:"); OLED_Refresh_Gram(); t=\' \'; while(1) { OLED_ShowChar(48,48,t,16,1);//顯示ASCII字元 OLED_Refresh_Gram(); t++; if(t>\'~\')t=\' \'; OLED_ShowNum(103,48,t,3,16);//顯示ASCII字元的碼值 delay_ms(300); LED0=!LED0; } } 該部分代碼用于在OLED上顯示一些字元,然後從空格鍵開始不停的循環顯示ASCII字元集,并顯示該字元的ASCII值。注意在test.c檔案裡面包含oled.h頭檔案,同時把oled.c檔案加入到HARDWARE組下,然後我們編譯此工程,直到編譯成功為止。 17.4 下載下傳驗證 将代碼下載下傳到戰艦STM32後,可以看到DS0不停的閃爍,提示程式已經在運作了。同時可以看到OLED子產品顯示如圖17.4.1所示: 圖17.4.1 OLED顯示效果 最後一行不停的顯示ASCII字元以及其碼值。通過這一章的學習,我們學會了ALIENTEK OLED子產品的使用,在調試代碼的時候,又多了一種顯示資訊的途徑,在以後的程式編寫中,大家可以好好利用。 |