对于单片机来说,有一个良好的人机交互界面是很重要的。那么我们常用的单片机显示设备有什么呢?OLED屏是一个不错选择。
OLED能显示我们的相对应的信息,使得我们的电子设计顿时高大上许多。
OLED是啥呢?OLED跟LED差不多,简单点说,就是一个个小小的LED组合起来,控制每一个小小的LED灯像素的亮灭来进行显示,这样就可以显示任意字符了。
对于OLED来说,单位面积内像素点的个数直接决定了我们显示屏的分辨率。像我在某宝购买的OLED显示屏,是1.3寸内有128*64个像素点。
显然我们是不可能搞这么多的GPIO口来控制这个OLED的,那么我们该如何控制呢?
前人早就为我们想好的解决措施,人家造了专门控制这种OLED的芯片方便我们使用,我们只需要按照芯片所需要的通信协议跟芯片通信就可以操作这个OLED了。
那么我们应该用什么通信协议呢?这个芯片支持IIC和SPI两种通信方式,这里我们主要使用讲解IIC。
I2C 总线是一种串行数据总线,只有二根信号线,一根是双向的数据线SDA,另一根是时钟线SCL,两条线可以挂多个设备。 IIC设备(绝大多数)里有个固化的地址,只有在两条线上传输的值等于IIC设备的固化地址时,其才会作出响应。通常我们为了方便把IIC设备分为主设备和从设备,基本上谁控制时钟线(即控制SCL的电平高低变换)谁就是主设备。
我们操作OLED显示屏就是属于最简单的一种IIC应用。我们只需要向OLED发送数据,而不需要接收OLED发送的数据。
IIC的具体通信协议我就不详细说了,主要分为起始信号,应答信号,结束信号,同时向设备发送数据或者接收数据。
对于我们的MSP432中,其实是自带了IIC的库,但是为了我们方便理解(才不是我老是发送不了数据呢),我们使用软件模拟IIC。
那什么又是软件模拟IIC呢?就是拿GPIO口当做SCL和SDA,自己来控制传输线的高低电平!
我们万能的某宝卖家写了利用51和STM32操作OLED例程,我们只需要对其修改下就可以运用到我们的MSP432中。
下面我列出所有核心函数:
//起始信号 void I2C_Start(void) { GPIO_write(CONFIG_GPIO_SDA,1); GPIO_write(CONFIG_GPIO_SCL,1); GPIO_write(CONFIG_GPIO_SDA,0); GPIO_write(CONFIG_GPIO_SCL,0); } //结束信号 void I2C_Stop(void) { GPIO_write(CONFIG_GPIO_SDA,0); GPIO_write(CONFIG_GPIO_SCL,1); GPIO_write(CONFIG_GPIO_SDA,0); } //等待信号响应 void I2C_WaitAck(void) //测数据信号的电平 { GPIO_write(CONFIG_GPIO_SDA,1); GPIO_write(CONFIG_GPIO_SCL,1); GPIO_write(CONFIG_GPIO_SCL,0); } //写入一个字节 void Send_Byte(uint8_t dat) { uint8_t i; for(i=0;i<8;i++) { GPIO_write(CONFIG_GPIO_SCL,0);//将时钟信号设置为低电平 if(dat&0x80)//将dat的8位从最高位依次写入 { GPIO_write(CONFIG_GPIO_SDA,1); } else { GPIO_write(CONFIG_GPIO_SDA,0); } GPIO_write(CONFIG_GPIO_SCL,1); GPIO_write(CONFIG_GPIO_SCL,0); dat<<=1; } } //发送一个字节 //向SSD1306写入一个字节。 //mode:数据/命令标志 0,表示命令;1,表示数据; void OLED_WR_Byte(uint8_t dat,uint8_t mode) { I2C_Start(); Send_Byte(0x78); I2C_WaitAck(); if(mode){Send_Byte(0x40);} else{Send_Byte(0x00);} I2C_WaitAck(); Send_Byte(dat); I2C_WaitAck(); I2C_Stop(); } //坐标设置 void OLED_Set_Pos(uint8_t x, uint8_t y) { OLED_WR_Byte(0xb0+y,OLED_CMD); OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD); OLED_WR_Byte((x&0x0f),OLED_CMD); } //开启OLED显示 void OLED_Display_On(void) { OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令 OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON } //关闭OLED显示 void OLED_Display_Off(void) { OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令 OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF } //清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!! void OLED_Clear(void) { uint8_t 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(0,OLED_DATA); } //更新显示 } //在指定位置显示一个字符,包括部分字符 //x:0~127 //y:0~63 //sizey:选择字体 6x8 8x16 void OLED_ShowChar(uint8_t x,uint8_t y,uint8_t chr,uint8_t sizey) { uint8_t c=0,sizex=sizey/2; uint16_t i=0,size1; if(sizey==8)size1=6; else size1=(sizey/8+((sizey%8)?1:0))*(sizey/2); c=chr-\' \';//得到偏移后的值 OLED_Set_Pos(x,y); for(i=0;i<size1;i++) { if(i%sizex==0&&sizey!=8) OLED_Set_Pos(x,y++); if(sizey==8) OLED_WR_Byte(asc2_0806[c][i],OLED_DATA);//6X8字号 else if(sizey==16) OLED_WR_Byte(asc2_1608[c][i],OLED_DATA);//8x16字号 // else if(sizey==xx) OLED_WR_Byte(asc2_xxxx[c][i],OLED_DATA);//用户添加字号 else return; } } //m^n函数 uint32_t oled_pow(uint8_t m,uint8_t n) { uint32_t result=1; while(n--)result*=m; return result; } //显示数字 //x,y :起点坐标 //num:要显示的数字 //len :数字的位数 //sizey:字体大小 void OLED_ShowNum(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t sizey) { uint8_t t,temp,m=0; uint8_t enshow=0; if(sizey==8)m=2; for(t=0;t<len;t++) { temp=(num/oled_pow(10,len-t-1))%10; if(enshow==0&&t<(len-1)) { if(temp==0) { OLED_ShowChar(x+(sizey/2+m)*t,y,\' \',sizey); continue; }else enshow=1; } OLED_ShowChar(x+(sizey/2+m)*t,y,temp+\'0\',sizey); } } //显示一个字符号串 void OLED_ShowString(uint8_t x,uint8_t y,uint8_t *chr,uint8_t sizey) { uint8_t j=0; while (chr[j]!=\'\0\') { OLED_ShowChar(x,y,chr[j++],sizey); if(sizey==8)x+=6; else x+=sizey/2; } } //显示汉字 void OLED_ShowChinese(uint8_t x,uint8_t y,uint8_t no,uint8_t sizey) { uint16_t i,size1=(sizey/8+((sizey%8)?1:0))*sizey; for(i=0;i<size1;i++) { if(i%sizey==0) OLED_Set_Pos(x,y++); if(sizey==16) OLED_WR_Byte(Hzk[no][i],OLED_DATA);//16x16字号 // else if(sizey==xx) OLED_WR_Byte(xxx[c][i],OLED_DATA);//用户添加字号 else return; } } //显示图片 //x,y显示坐标 //sizex,sizey,图片长宽 //BMP:要显示的图片 void OLED_DrawBMP(uint8_t x,uint8_t y,uint8_t sizex, uint8_t sizey,uint8_t BMP[]) { uint16_t j=0; uint8_t i,m; sizey=sizey/8+((sizey%8)?1:0); for(i=0;i<sizey;i++) { OLED_Set_Pos(x,i+y); for(m=0;m<sizex;m++) { OLED_WR_Byte(BMP[j++],OLED_DATA); } } } //初始化SSD1306 void OLED_Init(void) { sleep(1); OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel OLED_WR_Byte(0x00,OLED_CMD);//---set low column address OLED_WR_Byte(0x10,OLED_CMD);//---set high column address OLED_WR_Byte(0x40,OLED_CMD);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F) OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register OLED_WR_Byte(0xCF,OLED_CMD); // Set SEG Output Current Brightness OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常 OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常 OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64) OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F) OLED_WR_Byte(0x00,OLED_CMD);//-not offset OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration OLED_WR_Byte(0x12,OLED_CMD); OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02) OLED_WR_Byte(0x02,OLED_CMD);// OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5) OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7) OLED_Clear(); OLED_WR_Byte(0xAF,OLED_CMD); /*display ON*/ }
程序有点长,大家直接复制就可以使用。
那我们该如何调用呢?非常简单。比如我们显示某种字符,我们这样作为按键的回调函数,当我们按下按键就可以显示某种字符。
void gpioButtonFxn1(uint_least8_t index) { /* Clear the GPIO interrupt and toggle an LED */ GPIO_toggle(CONFIG_GPIO_LED_1); OLED_Clear(); OLED_ShowString(10,4,"2020/05/28",8); OLED_ShowString(10,6,"DerekChen",8); sleep(1); }
哈!非常简单是不是。那么我们要显示中文呢?中文有点复杂,你需要先生成中文的字符点阵数据,然后就可以照样显示了!