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部分代碼(該部分我們不再這里列出來了,請大家參考光盤里面的代碼)。 在知道了取模方式之后,我們就可以根據取模的方式來編寫顯示字符的代碼了,這里我們針對以上取模方式的顯示字符代碼如下: 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模塊的使用,在調試代碼的時候,又多了一種顯示信息的途徑,在以后的程序編寫中,大家可以好好利用。 |