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模块的使用,在调试代码的时候,又多了一种显示信息的途径,在以后的程序编写中,大家可以好好利用。 |