今天和大家分享一下,使用硬件IIC驱动OLED屏幕的开发过程。
一:OLED屏幕知识简单分享
OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display, OELD)。因为具备轻薄、省电等特性,因此从 2003 年开始,这种显示设备在 MP3 播放器上得到了广泛应用,而对于同属数码类产品的 DC 与手机,此前只是在一些展会上展示过采用 OLED 屏幕的工程样品。自 2007 年后,寿命得 到很大提高,具备了许多 LCD 不可比拟的优势。
GND:电源地 VCC:2.2V~5.5V SCL(D0):CLK 时钟 (高电平 2.2V~5.5V) SDA(D1):MOSI 数据(高电平 2.2V~5.5V) RST:复位(高电平 2.2V~5.5V) D/C:数据/命令(高电平 2.2V~5.5V) 兼容 3.3V 和 5V 控制芯片的 I/O 电平(无需任何设置,直接兼容)。
这是本次活动使用的OLED屏幕,虽然背面标识的时SSD1315,但是和驱动SSD1306的方式大体相同,驱动的时候可以参考SSD1306的驱动方式进行驱动;
二:IIC协议的知识分享:
IIC通讯协议的了解
IIC(Inter-Integrated Circuit)是一种串行通信协议,主控制器和从器件间的主从通信。它使用两根线:SDA(数据线)和SCL(时钟线)。SDA(SerialData Line):用于传输数据。SCL(Serial Clock Line):用于同步数据传输的时钟信号。
注意:1.1C不同于485和CAN可长距离通讯,IIC只能短距离,常用干IC与IC通讯。例如存储芯片:24C02;传感器模块:ST30,气压模块MPL MPU6050,温度模块ATH20等等,有很多的模块,当时我们熟悉了IIC 的通讯方式,可以外扩很多模块,适用于电子爱好者和DIY;
2.IIC电路设计时,建议在SDA和SCL两根线外加上拉电阻,以保证时序通讯的稳定性。不过在本次的活动电路板上面并没有外接上拉电阻,也不知道是不是模块上面焊接好了。
2.1 IIC类型
IIC 总线在传送数据过程中共有三种类型信号,分别是:
1.开始信号:SCL 为高电平时,SDA由高电平向低电平跳变,开始传送数据。
2.结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
3.应答信号:从机 在接收到 8bit 数据后,向发送数据的主机发出特定的低电平脉冲,表示已收到数据。主机向从机发出一个信号后等待从机发出一个应答信号,主机接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
2.2 IIC的总时序图
起始条件是SCL保持高电平,SDA由高电平变为低电平后,延时(最少4us,这个和线长有关系),SCL变为低电平。只有当SCL线上的时钟信号为低时,数据线上的“高”或“低”状态才可以改变。输出到SDA线上的每个字节必须是8位,数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。当传输完SDA马上拉低,接着输出低电平信号主机进而产生停止信号,结束传送过程。
2.3 IIC的 发送时序
1主机首先产生起开始信号,然后紧跟着发送一个从机地址,这个地址共有7位,紧接着的第8位是数据方向位(R/W),0表示主机发送数据(写),1表示主机接收数据(读)。
2.主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比较,若相同,则认为自已正在被主机寻址,根据R/T位将自己确定为发送器和接收器。这时候主机等待从机的应答信号,当主机收到应答信号时,发送要访问从机的那个地址,继续等待从机的应答信号,当主机收到应答信号时,发送N个字节的数据,继续等待从机的N次应答信号,主机产生停止信号,结束传送过程。
2.4 IIC的读时序
主机首先产生开始信号然后紧跟着发送一个从机地址,注意此时该地址的第8位为0,表明是向从机写命令。
2.这时候主机等待从机的应答信号(ACK)当主机收到应答信号时,发送要访问的地址,继续等待从机的应答信号,当主机收到应答信号后,主机要改变通信模式(主机将由发送变为接收,从机将由接收变为发送)所以主机重新发送一个开始start信号,然后紧跟着发送一个从机地址,注意此时该地址的第8位为1,表明将主机设置成接收模式开始读取数据,这时候主机等待从机的应答信号,当主机收到应答信号时,就可以接收1个字节的数据,当接收完成后,主机发送非应答信号,表示不在接收数据主机进而产生停止信号,结束传送过程。
三:字库的生成:
这里只需要软件生成就可以,方法如下:
字库如下所示:注意字库的驱动方式需要和代码相配合,否则会导致乱码!!!!!!!!!!!!!!!!!
/***************************16*16的点阵字体取模方式:共阴——列行式——逆向输出*********/ const unsigned char F16x16[] = { // 电(0) 子(1) 产(2) 品(3) 世(4) 界(5) 0x00,0x00,0xF8,0x88,0x88,0x88,0x88,0xFF,0x88,0x88,0x88,0x88,0xF8,0x00,0x00,0x00, 0x00,0x00,0x1F,0x08,0x08,0x08,0x08,0x7F,0x88,0x88,0x88,0x88,0x9F,0x80,0xF0,0x00,/*"电",0*/ /* (16 X 16 , 宋体 )*/ 0x80,0x82,0x82,0x82,0x82,0x82,0x82,0xE2,0xA2,0x92,0x8A,0x86,0x82,0x80,0x80,0x00, 0x00,0x00,0x00,0x00,0x00,0x40,0x80,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"子",1*/ /* (16 X 16 , 宋体 )*/ 0x00,0x04,0x84,0x84,0x94,0xE4,0x85,0x86,0x84,0xC4,0xB4,0x84,0x84,0x84,0x80,0x00, 0x80,0x60,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"产",2*/ /* (16 X 16 , 宋体 )*/ 0x00,0x00,0x00,0x7E,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x7E,0x00,0x00,0x00,0x00, 0x00,0xFE,0x42,0x42,0x42,0xFE,0x00,0x00,0x00,0xFE,0x42,0x42,0x42,0xFE,0x00,0x00,/*"品",3*/ /* (16 X 16 , 宋体 )*/ 0x20,0x20,0x20,0xFE,0x20,0x20,0xFF,0x20,0x20,0x20,0xFF,0x20,0x20,0x20,0x20,0x00, 0x00,0x00,0x00,0x7F,0x40,0x40,0x47,0x44,0x44,0x44,0x47,0x40,0x40,0x40,0x00,0x00,/*"世",4*/ /* (16 X 16 , 宋体 )*/ 0x00,0x00,0x00,0xFE,0x92,0x92,0x92,0xFE,0x92,0x92,0x92,0xFE,0x00,0x00,0x00,0x00, 0x08,0x08,0x04,0x84,0x62,0x1E,0x01,0x00,0x01,0xFE,0x02,0x04,0x04,0x08,0x08,0x00,/*"界",5*/ /* (16 X 16 , 宋体 )*/ };
在生成字库的时候,需要根据自己的代码设置 宽度和高度,还有就是设置项里面,我的开发经验时只要是将屏幕点亮基本可以说明IIC的读写指令是没有问题的,只是自己操作的屏幕的指令有问题,大家在开发的时候可以注意下,可以先烧录一下其他人的代码,确定硬件是好的,再去对软件进行调试,达到事半功倍的效果。这里感谢一下我再调试IIC时候,帮助我的大佬。
四:硬件连接和cube配置过程:
4.1 这里我使用硬件的IIC,对OLED进行驱动;
PB8 -----------------SCL
PB9 -----------------SDA
3V3 -----------------VCC
GND------------------GND
这里我是用IIC1的复用功能,并没有使用 PB6和PB7 引脚;
4.2 cube r软件配置如下:
引脚选择,这里我们直接点击PB8引脚,设置成IIC功能就可以。
IIC 参数配置:
这里IIC最大的通讯速度可以设置成100K,注意需要设置成7位的模式,其他参数就直接默认就可以。这里要注意需要打开 硬件IIC的中断;
当我们开启的外设功能较多时,需要看一下各个外设的优先级,
不过在本次活动中,我们开启的活动并不是很多,所以这里随便设置一下也是可以的。
注意:本工程在04帖子上面的工程上进行修改,添加,由于OLED只是作为显示,所以优先级不建议设置的太高就行,只要能正常显示字样就可以。
五:软件代码编写:
5.1 硬件IIC的初始化:
I2C_HandleTypeDef hi2c1; /* I2C1 init function */ void MX_I2C1_Init(void) { /* USER CODE BEGIN I2C1_Init 0 */ /* USER CODE END I2C1_Init 0 */ /* USER CODE BEGIN I2C1_Init 1 */ /* USER CODE END I2C1_Init 1 */ hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN I2C1_Init 2 */ /* USER CODE END I2C1_Init 2 */ }
5.2 :IIC的驱动OLED 代码如下:
static void I2C_WriteByte(uint8_t addr, uint8_t value) { unsigned char dta[2] = {0}; dta[0] = addr; dta[1] = value; OLED_send_byteS(dta, 2); } void WriteCmd(unsigned char I2C_Command)//写命令 { I2C_WriteByte(0x00, I2C_Command); } void WriteDat(unsigned char I2C_Data)//写数据 { I2C_WriteByte(0x40, I2C_Data); }
OLED初始化部分:
void OLED_ON(void) { WriteCmd(0X8D); //设置电荷泵 WriteCmd(0X14); //开启电荷泵 WriteCmd(0XAF); //OLED唤醒 } //关闭OLED显示 void OLED_OFF(void) { WriteCmd(0x8D);//电荷泵使能 WriteCmd(0x10);//关闭电荷泵 WriteCmd(0xAE);//关闭屏幕 } void OLED_Init(void) { HAL_Delay(100); WriteCmd(0xAE); //display off WriteCmd(0x20); //Set Memory Addressing Mode WriteCmd(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid WriteCmd(0xb0); //Set Page Start Address for Page Addressing Mode,0-7 WriteCmd(0xc8); //Set COM Output Scan Direction WriteCmd(0x00); //---set low column address WriteCmd(0x10); //---set high column address WriteCmd(0x40); //--set start line address WriteCmd(0x81); //--set contrast control register WriteCmd(0xff); //亮度调节 0x00~0xff WriteCmd(0xa1); //--set segment re-map 0 to 127 WriteCmd(0xa6); //--set normal display WriteCmd(0xa8); //--set multiplex ratio(1 to 64) WriteCmd(0x3F); // WriteCmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content WriteCmd(0xd3); //-set display offset WriteCmd(0x00); //-not offset WriteCmd(0xd5); //--set display clock divide ratio/oscillator frequency WriteCmd(0xf0); //--set divide ratio WriteCmd(0xd9); //--set pre-charge period WriteCmd(0x22); // WriteCmd(0xda); //--set com pins hardware configuration WriteCmd(0x12); WriteCmd(0xdb); //--set vcomh WriteCmd(0x20); //0x20,0.77xVcc WriteCmd(0x8d); //--set DC-DC enable WriteCmd(0x14); // WriteCmd(0xaf); //--turn on oled panel OLED_Fill(0xff);//全屏填充 OLED_ON(); }
5.3 部分驱动 字符串、汉字和数字代码如下:
显示汉字:
void OLED_ShowCN(unsigned char x, unsigned char y, unsigned char N) { unsigned char wm=0; unsigned int adder=32*N; OLED_SetPos(x , y); for(wm = 0;wm < 16;wm++) { WriteDat(F16x16[adder]); adder += 1; } OLED_SetPos(x,y + 1); for(wm = 0;wm < 16;wm++) { WriteDat(F16x16[adder]); adder += 1; } }
显示字符串:
void OLED_ShowStr(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize) { unsigned char c = 0,i = 0,j = 0; switch(TextSize) { case 1: { while(ch[j] != '\0') { c = ch[j] - 32; if(x > 126) { x = 0; y++; } OLED_SetPos(x,y); for(i=0;i<6;i++) WriteDat(F6x8[c][i]); x += 6; j++; } }break; case 2: { while(ch[j] != '\0') { c = ch[j] - 32; if(x > 120) { x = 0; y++; } OLED_SetPos(x,y); for(i=0;i<8;i++) WriteDat(F8X16[c*16+i]); OLED_SetPos(x,y+1); for(i=0;i<8;i++) WriteDat(F8X16[c*16+i+8]); x += 8; j++; } }break; } }
显示数字如下:
//显示2个数字 //x,y :起点坐标 //len :数字的位数 //size:字体大小 void OLED_ShowNum(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t size1) { uint8_t t,temp; for(t=0;t<len;t++) { temp=(num/OLED_Pow(10,len-t-1))%10; if(temp==0) { OLED_ShowChar(x+(size1/2)*t,y,'0',size1); } else { OLED_ShowChar(x+(size1/2)*t,y,temp+'0',size1); } } }
主程序代码如下:
在用户代码区添加需要显示的字符串:
/* USER CODE BEGIN PFP */ const uint8_t hello_str[] = "Hello !EEPW?& DigiKey! Hello STM32F103RB!\r\n"; const uint8_t Output_str[] = "Hello !Let us do it ! autor by clever !\r\n"; const uint8_t test_str[] = "Hello world"; const uint8_t test_str1[] = "by: clever013"; /* USER CODE END PFP */
OLED_Init(); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ OLED_ColorTurn(0);//0������ʾ��1 ��ɫ��ʾ OLED_DisplayTurn(0);//0������ʾ 1 ��Ļ��ת��ʾ OLED_Refresh(); OLED_Clear(); OLED_ShowCN(16, 0, 0); OLED_ShowCN(32, 0, 1); OLED_ShowCN(48, 0, 2); OLED_ShowCN(64, 0, 3); OLED_ShowCN(80, 0, 4); OLED_ShowCN(96, 0, 5); // sprintf(buffer,"%4.1fML",(float)123/4096*3.3*2); OLED_ShowStr(20, 2, (uint8_t *)test_str, 2); OLED_ShowStr(20, 4, (uint8_t *)test_str1, 2); // OLED_ShowStr(63,3,"HelloWorld:",16); OLED_ShowNum(64,6,123456,6,16);
实物测试图片如下:
至此:使用STM32F103RB 硬件IIC 驱动OLED屏幕 工程,调试完毕;;;1.设置好I0信息,并点亮屏幕显示HelloWorld的任务完成;
由于自己代码写的比较乱,稍后整理一下发到电子产品世界的论坛上面;;;