八、IIC通信和操作四角OLED显示屏
一、IIC协议介绍
(1)协议介绍
IIC(又称I2C,Inter-Integrated Circuit),即集成电路总线,是一种两线式串行总线,由PHILIPS公司开发,用于连接微控制器及其外围设备。多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。I2C总线由数据线SDA和时钟线SCL构成通信线路,既可用于发送数据,也可接收数据,是一种半双工通信协议。总线上的主设备与从设备之间以字节(8位)为单位进行双向的数据传输。
(2)传输模式
- 标准模式:100K bit/s
- 快速模式:400K bit/s
- 高速模式:3.4M bit/s
(3)连接线路
(4)时序分析
- 空闲状态:IIC总线的SDA和SCL线同时处于高电平时。
- 起始信号S:SCL和SDA都处于高电平状态,此时SDA线从高电平跃变到低电平产生起始信号。
- 终止信号P:当SCL处于高电平,SDA处于低电平,SDA线从低电平跃变到高电平,产生终止信号。
- 应答信号:8位一字节数据传送完毕后,第九位为应答信号,0表示接收到应答,1表示无应答。
(5)代码编写模拟IIC,使用位带和SysTick滴答定时器延时函数
1.起始信号
1 void IIC_Start(void) 2 { 3 sda_out(); 4 SCL=1; 5 SDA=1; 6 IIC_Delay_Us();//拉高SCL和SDA,维持5us 7 SDA=0; 8 IIC_Delay_Us();//拉低SDA,维持5us 9 SCL=0; 10 IIC_Delay_Us();//拉低SCL,维持5us,等待数据发送或接收 11 }
2.终止信号
1 void IIC_Stop(void) 2 { 3 sda_out(); 4 SCL=1; 5 SDA=0; 6 IIC_Delay_Us();//SCL高电平,SDA低电平,维持5us 7 SDA=1; 8 IIC_Delay_Us();//SDA电平拉高,维持5us 9 }
3.主机接收标志位
1 u8 IIC_Check_ACK(void) 2 { 3 u8 ack; 4 sda_in(); 5 SCL=0; 6 IIC_Delay_Us(); 7 SCL=1; 8 Delay_Us(2);//拉高后延时2us等待信号稳定 9 if(SDA_STATE){//无应答 10 ack=NACK; 11 }else{//有应答 12 ack=ACK; 13 } 14 Delay_Us(2); 15 SCL=0; 16 return ack; 17 }
4.主机发送标志位
1 void IIC_ACK(u8 ack) 2 { 3 sda_out(); 4 SCL=0; 5 IIC_Delay_Us();//发送数据,SCL拉低,SDA信号变换 6 if(ack==ACK){ 7 SDA=0;//变换SDA电平 8 IIC_Delay_Us(); 9 }else{ 10 SDA=1;//变换SDA电平 11 IIC_Delay_Us(); 12 } 13 SCL=1; 14 IIC_Delay_Us();//拉高SCL将SDA数据发送出去 15 SCL=0; 16 }
5.主机发送一字节数据给从机
1 void IIC_Send_Byte(u8 byte_data) 2 { 3 u8 i; 4 sda_out(); 5 for(i=0;i<8;i++){ 6 SCL=0; 7 IIC_Delay_Us();//拉低SCL等待SDA发送一位数据 8 if(byte_data&0x80)//最高位为1 9 SDA=1; 10 else 11 SDA=0; 12 IIC_Delay_Us(); 13 14 SCL=1; 15 IIC_Delay_Us();//拉高SCL将SDA的一位数据发送出去 16 17 byte_data<<=1; 18 } 19 SCL=0; 20 IIC_Delay_Us();//拉低SCL等待SDA发送一位数据 21 }
6.主机接收从机发送过来的一字节数据
1 u8 IIC_Read_Byte(void) 2 { 3 sda_in(); 4 u8 temp,i; 5 for(i=0;i<8;i++){ 6 SCL=0; 7 IIC_Delay_Us();//拉低SCL等待接收数据 8 SCL=1; 9 Delay_Us(2); 10 temp<<=1; 11 if(SDA_STATE) 12 temp|=0x01; 13 Delay_Us(2); 14 } 15 SCL=0; 16 IIC_Delay_Us();//拉低SCL 17 return temp; 18 }
7.其他函数以及头文件
1 void IIC_Init(void) 2 { 3 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); 4 5 GPIO_InitTypeDef GPIO_InitStruct; 6 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14|GPIO_Pin_15; 7 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出 8 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz; 9 GPIO_Init(GPIOB,&GPIO_InitStruct); 10 11 Delay_Init(); 12 } 13 14 void IIC_Delay_Us(void) 15 { 16 Delay_Us(5); 17 } 18 19 static void sda_out(void) 20 { 21 GPIO_InitTypeDef GPIO_InitStruct; 22 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15; 23 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出 24 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz; 25 GPIO_Init(GPIOB,&GPIO_InitStruct); 26 } 27 28 static void sda_in(void) 29 { 30 GPIO_InitTypeDef GPIO_InitStruct; 31 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15; 32 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;//上拉输入 33 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz; 34 GPIO_Init(GPIOB,&GPIO_InitStruct); 35 } 36 37 //头文件 38 #ifndef __MY_IIC_H__ 39 #define __MY_IIC_H__ 40 #include "stm32f10x.h" 41 #include "delay.h" 42 #include "sys.h" 43 44 #define SCL PBout(14) 45 #define SDA PBout(15) 46 47 #define SDA_STATE PBin(15) 48 49 #define ACK 0 //应答信号 50 #define NACK 1 //不应答信号 51 52 //IIC初始化端口 53 void IIC_Init(void); 54 //IIC延时5us 55 void IIC_Delay_Us(void); 56 57 static void sda_out(void); 58 static void sda_in(void); 59 60 //IIC开始信号 61 void IIC_Start(void); 62 //结束信号 63 void IIC_Stop(void); 64 //主机应答从机 65 void IIC_ACK(u8 ack); 66 //从机应答主机 67 u8 IIC_Check_ACK(void); 68 //发送一个字节数据 69 void IIC_Send_Byte(u8 byte_data); 70 //接收一字节数据 71 u8 IIC_Read_Byte(void); 72 73 #endif
二、OLED相关信息
- 显示分辨率128*64 点阵
- IIC从地址0x78
发送完从机地址后发送一字节的控制字节,告诉IIC设备接下来是指令字节还是数据字节。Co=0,接下来的只包含数据字节。D/C=0,接下来写入的是命令字节。D/C=1,接下来写入的是数据字节。
写命令和写数据的程序如下:
1.写命令
1 static void Write_Cmd(u8 cmd) 2 { 3 IIC_Start(); 4 IIC_Send_Byte(0x78); 5 IIC_Check_ACK();//可能有误 6 IIC_Send_Byte(0x00); 7 IIC_Check_ACK(); 8 IIC_Send_Byte(cmd); 9 IIC_Check_ACK(); 10 IIC_Stop(); 11 }
2.写数据
1 static void Write_Data(u8 data) 2 { 3 IIC_Start(); 4 IIC_Send_Byte(0x78); 5 IIC_Check_ACK(); 6 IIC_Send_Byte(0x40); 7 IIC_Check_ACK(); 8 IIC_Send_Byte(data); 9 IIC_Check_ACK(); 10 IIC_Stop(); 11 }
- 显存GDDRAM大小为128*64
- 每8COM组成一个Page,共有8Page,即64行
- 每一个COM中有128个Segment,即128列
- 通过设置COM与Page的映射关系,可以改变OLED的行刷新方向
- 通过设置SEG与Column的映射关系,可以改变OLED的列刷新方向
- 在一个Page中,数据刷新总是从低SEG刷新到高SEG,在一个SEG中,数据的低位在上,高位在下,即在一个SEG中数据总是从低COM刷新到高COM
3.命令解释Command
3.1 设置对比度
命令 | 数据 |
0x81 |
对比度(0~255) 0x00~0xFF 默认值为0x7F(127) |
先发送命令字节0x81,然后设置对比度数据,实际是设置OLED的驱动电流,对比度设置越大,驱动电流越大,显示亮度就越高。
3.2 OLED显示跟随GDDRAM
命令 | 数据 |
0xA4:OLED显示跟随GDDRAM 0xA5:OLED显示固定,不跟随GDDRAM 默认值为0xA4 |
无 |
3.3 设置反色
命令 | 数据 |
0xA6:正常显示,即GDDRAM中1表示显示,0表示不显示 0xA7:反转显示,即GDDRAM中0表示显示,1表示不显示 默认值0xA6 |
无 |
3.4 开启/关闭显示
命令 | 数据 |
0xAE:关闭显示,进入睡眠模式 0xAF:开启显示 默认值0xAE |
无 |
3.5 设置显示列地址
命令 | 数据 | 说明 |
0x00~0x0F:设置列地址的低四位,默认0x00 0x10~0x1F:设置列地址的高四位,默认0x10 |
无 |
此设置仅在页寻址模式下有效 两个设置共同决定显示列的起始地址 |
3.6 设置寻址模式
命令 | 数据 | 说明 |
0x20 |
0x00:行寻址模式 0x01:列寻址模式 0x02:页寻址模式 默认0x02 |
先发送0x20,然后发送寻址模式选择字节 |
页寻址模式
水平/行寻址模式
垂直/列寻址模式
3.7 设置列地址
命令 | 数据 | 说明 |
0x21 |
0~127,默认为0 0~127,默认为127 |
先发送0x21,然后发送两字节的数据用于设置列的起始地址和结束地址 仅在行/列寻址模式下有效 |
3.8 设置页地址
命令 | 数据 | 说明 |
0x22 |
0~7:默认0 0~7:默认7 |
先发送0x22,然后发送两字节的数据用于设置页的起始地址和结束地址 仅在行/列寻址模式下有效 |
3.9 设置页的起始地址
命令 | 说明 |
0xB0~00xB7 默认0xB0 |
用于设置页寻址模式下的页起始地址 |
3.10 设置显示开始行
命令 | 数据 |
0x40~0x7F对应0-63行 默认0x40 |
无 |
3.11 设置列对于Segment的映射
命令 | 说明 |
0xA0:Column0映射到Seg0 0xA1:Column127映射到Seg0 默认为0xA0 |
从左刷新数据还是从右刷新数据 |
3.12 设置通道数
命令 | 数据 |
0xA8 |
0x10~0x3F 对应15+1~63+1 默认为63 |
3.13 设置COM Driver的扫描方向
命令 | 说明 |
0xC0:从COM0扫描到COMN 0xC8:从COMN扫描到COM0 默认值0xC0 |
也就是从上往下扫描还是从下往上扫描 |
3.14 设置COM的偏移值,一般设置为0,保证屏幕的完整显示
命令 | 数据 |
0xD3 |
0~63 默认0 |
3.15 设置时钟分频比和时钟频率
命令 | 数据 |
0xDA |
[3:0]:设置时钟分频比 [7:4]:设置时钟频率 0x12:128*64OLED 0x02:128*32OLED |
3.16 预充电时间,保持0x22默认值即可
4.代码设置OLED初始化
1 void Oled_Init(void) 2 { 3 IIC_Init(); 4 Delay_Ms(200); 5 6 Write_Cmd(0xA8);//设置分辨率 7 Write_Cmd(0x3F);//0x3f : 128*64 0x1f 128*32 8 9 Write_Cmd(0xDA);//设置COM硬件引脚配置,适应分辨率 10 Write_Cmd(0x12);//0x12 : 0.96->128*64 0x02 : 0.91->128*32 11 12 Write_Cmd(0xD3);//设置显示偏移 13 Write_Cmd(0x00);//默认无偏移 14 Write_Cmd(0x40);//设置显示开始0-63 15 Write_Cmd(0xA0);//段SEGMENT重映射对于IIC四角OLED要设置为0xA1 16 Write_Cmd(0x81);//对比度设置 17 Write_Cmd(0x7F);//亮度设置0x00-0xFF,数值越大亮度越大 18 Write_Cmd(0xA4);//输出遵循RAM内容,0x05输出忽略RAM内容 19 Write_Cmd(0xA6);//显示方式正常显示,0xA7反向显示,逆码,0点亮还是1点亮 20 Write_Cmd(0xD5);//设置显示时钟分频/振荡器频率 21 Write_Cmd(0xF0);//设置分辨率值 22 Write_Cmd(0x8D);//充电泵设置 23 Write_Cmd(0x14);//允许在显示开启的时候使用,0x10:不允许在开启前使用 24 Write_Cmd(0xAE);//显示关闭,0xAF显示开启 25 Write_Cmd(0x20);//设置内存地址模式 水平/垂直/页寻址(默认) 26 Write_Cmd(0x02);//水平0x00 垂直0x01 页寻址0x02 27 Write_Cmd(0xC0);//设置COM扫描方式0xC0上下反置左到右 0xC8正常右到左 28 Write_Cmd(0xB0);//为页寻址模式设置开启地址0-7 29 Write_Cmd(0x00);//设置低列地址 30 Write_Cmd(0x10);//设置高列地址 31 Write_Cmd(0x40);//设置显示开始行 0-63 32 Write_Cmd(0xD9);//设置预充电时期 33 Write_Cmd(0x22);//充电时间 34 Write_Cmd(0xDB);//设置取消选择级别 35 Write_Cmd(0x20);//默认0x20 0.77xvcc 36 Write_Cmd(0xAF);//显示开启 37 38 Oled_Off(); 39 Oled_Clear(); 40 Oled_On(); 41 }
5.开关显示屏代码
1 void Oled_On(void) 2 { 3 Write_Cmd(0x8D);//操作充电泵 4 Write_Cmd(0x14);//打开 5 Write_Cmd(0xAF); 6 } 7 8 void Oled_Off(void) 9 { 10 Write_Cmd(0x8D); 11 Write_Cmd(0x10);//关闭 12 Write_Cmd(0xAE); 13 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用