I2C
目录
2.6.2.从从机0x20读一个字节(假设读到的是0x64)
[5.6.Size = 1](#5.6.Size = 1)
[5.7.Size = 2](#5.7.Size = 2)
[5.8.Size > 2](#5.8.Size > 2)
[6.1.硬I2C VS 软I2C](#6.1.硬I2C VS 软I2C)
一、基本电路结构
1.1.串口的缺点
一个串口只能连接一个串口设备
只能实现一对一的通信

而IIC通过一个通信接口就可以连接很多外设
1.2.I2C总线的电路结构

由时钟线和数据线组成,一般由单片机作为IIC总线主机
其他被连接设备为从机,这些设备都有SDA和SCL引脚
所有设备的SDA引脚连接在数据线上
所有设备的SCL引脚连接在时钟线上
时钟线与数据线都需接上拉电阻,并且都为开漏输出(逻辑线与)
每一个从机都有一个7位的从机地址
主机先发送从机地址与从机建立通信,然后与从机进行数据交互
1.3.数据线和时钟线
SDA:数据线,用来传输数据
数据信号:

**SCL:**时钟线,用来传输时钟
时钟信号:

注:
时钟信号总是由主机发送给从机(单向)
数据信号可以由主机发送给从机,也可以由从机发送给主机(双向)

1.4.逻辑线与
1.4.1.逻辑与
与运算:&
Y = X1 & X2 & X3 ... &Xn
只要 X1~Xn里任意一个等于0,结果为0
当X1~Xn全部都等于1,结果为1
1.4.2.逻辑线与
将所有设备的SCL引脚设置为输出开漏模式
向所有设备的SCL引脚写1,内部的N-MOS管断开
此时SCL引脚处于高阻抗,相当于引脚从电路断开
如果不接上拉电阻,时钟线就会处于悬空状态,导致电平不稳定
上拉电阻可以将悬空的时钟线拉到稳定的高电平,表示1

将所有设备的SCL引脚设置为输出开漏模式
向某个设备的SCL引脚写0,其余SCL引脚都写1
写1的引脚为高阻抗,写0的引脚输出低电压(相当于接地)
时钟线通过写0的SCL引脚接地输出低电平,表示0

1.5.主机如何发送时钟信号
发送低电平:

Y = 0 & 1 & 1 ... & 1 = 0(低电平)

发送高电平:

Y = 1 & 1 & 1 ... & 1 = 1 (高电平)

1.6.主机如何发送数据
发送0:

Y = 0 & 1 & 1 ... & 1 = 0(低电平)
发送1:

Y = 1 & 1 & 1 ... & 1 = 1 (高电平)
1.7.从机如何发送数据
发送0:

Y = 1 & 0 & 1 ... & 1 = 0(低电平)
发送1:

Y = 1 & 1 & 1 ... & 1 = 1 (高电平)
1.8.为何上拉?为何开漏?
向设备引脚写1,引脚处于高阻态,相当于开路,总线能够与上拉电阻相连,产生高电平
向设备引脚写0,引脚处于低电压,相当于接地,总线能够通过引脚端口电路接地,产生低电平
如果是推挽模式,一个设备写0为低电平,一个设备写1为高电平,直接造成短路
如果是下拉电阻,只能将总线拉到低电平,无法为总线提供高电平
二、通信协议
2.1.I2C通信的基本流程
- 主机向总线发送发送起始位
- 主机向总线发送7位从机地址+1位读写位
- 主机和从机进行数据传输
- 主机向总线发送停止位
2.2.I2C的数据帧格式

**注:**串口的数据帧每次只能传输8~9bit,I2C的数据帧每次可以传输多个字节
2.3.起始位和停止位

2.3.1.起始位
- SCL = H
- SDA ↓

2.3.2.停止位
- SCL = H
- SDA ↑

2.4.寻址

应答(Ack):
向主机SDA引脚写1,引脚处于高阻态,相当于开路,总线能够与上拉电阻相连,产生高电平
但在这过程中,若向从机SDA引脚写0,引脚处于低电压,相当于接地,总线能够通过引脚端口电
路接地,产生低电平,此时主机检测到低电压,接收到从机的应答信号
Y = 1 & 0 & 1 ... & 1 = 0(低电平)
注:
- 串口是低位先行(LSB First),发送字节时从最低位开始
- I2C是高位先行(MSB First),发送字节时从最高位开始
- 0写1读
2.5.传输数据

**注:**发送方释放SDA,接收方拉低SDA表示应答
2.6.例子
2.6.1.向从机0x78写两个字节(0x5a,0x33)
寻址0x78(写):
0x78二进制:0111 1000
左移1位: 1111 0000
低位或0为写: 1111 000(7位地址)0(读写位)

写数据0x5a:
0x5a二进制:0101 1010

写数据0x33:
0x33二进制:0011 0011

2.6.2.从从机0x20读一个字节(假设读到的是0x64)
寻址0x20(读):
0x20二进制:0010 0000
左移1位: 0100 0000
低位或1为读: 0100 000(7位地址)1(读写位)

读数据0x64:
0x64二进制:0101 0100

I2C停止条件解读:
主机发送Nack:
- **时机:**读取最后一个数据字节后,进入第九个SCL时钟周期
- **电平:**主机作为接收方,保持SDA高电平,不进行拉低操作
- **作用:**向从机表明这时最后一个字节,无需再发送数据
准备停止条件:
- **时机:**Nack周期结束后,SCL线被主机拉低后
- **电平:**在SCL为低电平时,主机将SDA从高电平拉到低电平
- **作用:**为后续生成停止条件作准备
生成停止条件:
- **时机:**主机拉高SCL线,使其处于高电平状态
- **电平:**在SCL为高电平时,主机将SDA从低电平拉到高电平
- **作用:**标志着通信结束,总线都被释放,处于空闲状态
三、I2C模块的使用方法
3.1.I2C模块简介

I2C模块是单片机上的片上外设,给单片机提供I2C通信接口
3.2.IO引脚的初始化
3.2.1.引脚分布




**复用:**CPU借助I2C模块来控制IO引脚
**开漏:**实现逻辑线与
3.2.2.编码部分
cpp
//#1:初始化GPIO
/*开启AFIO模块的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
/*使能I2C1的重映射*/
GPIO_PinRemapConfig(GPIO_Remap_I2C1,ENABLE);
/*开启GPIOB模块的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
/*GPIO结构的前置声明*/
GPIO_InitTypeDef GPIO_InitStruct;
/*选择PB8和PB9引脚*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
/*设置复用输出开漏模式*/
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
/*最大输出速度尾2MHz*/
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
/*初始化PB8和PB9引脚*/
GPIO_Init(GPIOB,&GPIO_InitStruct);
3.3.连接电路


3.4.I2C的速度模式

**波特率:**每秒钟传输的位数
|--------|-----|-----------------|----------|
| 标准模式 | Sm | Standard Mode | ≤100kbps |
| 快速模式 | Fm | Fast Mode | ≤400kbps |
| 快速增强模式 | Fm+ | Fast Mode Plus | ≤1Mbps |
| 高速模式 | HSm | High Speed Mode | ≤3.4Mbps |
| 超快模式 | UFm | Ultra Fast Mode | ≤5Mbps |
**注:**STM32只支持标准模式和快速模式
3.5.时钟信号的占空比
快速模式下可以设置时钟信号的占空比
**时钟信号:**SCL传输的信号,控制通信的速率
**注:**多数通信协议使用高电平采样,低电平更新的工作方式

**占空比:**在一个周期中高电压占整个周期的比例

**注:**一般情况下使用2:1的占空比
3.6.I2C模块的初始化
3.6.1.编程接口
cpp
void I2C_Init(I2CTypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
解析:
- 参数1:I2C名称 I2C1,I2C2
- 参数2:初始化的参数结构体地址
作用: 初始化I2C,配置I2C的各种参数
- I2C的波特率
- I2C的模式
- I2C的占空比(快速模式)
- 与从机模式有关的参数
**补充:**I2C_InitTypeDef结构(I2C参数菜单)
cpp
typedef struct I2C_InitTypeDef
{
uint32_t I2C_ClockSpeed;
uint16_t I2C_Mode;
uint16_t I2C_DutyCycle;
uint16_t I2C_Ack;
uint16_t I2C_OwnAddress1;
uint16_t I2C_AcknowledgedAddress;
};
分析:
**1.I2C_ClockSpeed:**波特率
- Sm:<=100
- Fm:<=400
**2.I2C_Mode:**模式
- I2C_Mode_I2C:标准I2C模式
- I2C_Mode_SMBusDevice:系统管理总线设备模式
- I2C_Mode_SMBusHost:系统管理总线主机模式
**3.I2C_DutyCycle:**快速模式下时钟信号的占空比
- I2C_DutyCycle_16_9:占空比为16:9
- I2C_DutyCycle_2:占空比为2:1
**4.I2C_Ack:**与从机模式有关
**5.I2C_OwnAddress1:**与从机模式有关
**6.I2C_AcknowledgedAddress:**用于选择10位从机地址模式
3.6.2.编码部分
cpp
//#2:初始化I2C1模块
/*开启I2C1模块的时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
/*施加复位信号*/
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);
/*释放复位信号*/
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);
/*I2C结构的前置声明*/
I2C_InitTypeDef I2C_InitStruct;
/*波特率为400k*/
I2C_InitStruct.I2C_ClockSpeed = 400000;
/*模式为标准I2C*/
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
/*占空比为2:1*/
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
/*初始化I2C1*/
I2C_Init(I2C1,&I2C_InitStruct);
/*闭合I2C1总开关*/
I2C_Cmd(I2C1,ENABLE);

注:
- I2C时钟用的是APB1,而非APB2
- I2C需要施加和释放复位信号
- 最后要像串口一样闭合总开关
四、写数据
4.1.I2C模块的内部结构框图

**SDA:**I2C数据引脚
**SCL:**I2C时钟引脚
发送数据:
将发送数据写入发送数据寄存器,I2C模块将数据移到移位寄存器,逐个bit位通过SDA发送
接收数据:
SDA接收从机发送的波形,I2C模块将波形解析,逐个bit位移到移位寄存器,放入接收数据寄存器
**SR1(Status Register1):**状态寄存器1,存放I2C模块运行状态的标志位
**SR2(Status Register2):**状态寄存器2,存放I2C模块运行状态的标志位
**SDA控制电路:**控制SDA引脚向外发送波形,解析SDA接收的波形
**SDA控制寄存器:**控制I2C模块行为
**SCL控制电路:**控制SCL引脚向外发送时钟信号
SCL配置寄存器:配置时钟信号的速度模式,占空比和波特率
4.2.数据发送过程简介

- 主机发送起始位
- 主机发送7位地址+读写位
- 等待从机Ack
- 主机逐个字节发送数据
- 每次发送后等待从机Ack
- 主机发送停止位
4.2.1.发送字节函数
cpp
int My_I2C_SendByte(I2C_TypeDef* I2Cx, uint8_t Addr,uint8_t* pData, uint16_t Size);
解析:
- 参数1:I2C接口的名称
- 参数2:从机地址,靠左
- 参数3:要发送的数据
- 参数4:要发送的数据的数量(单位:字节)
- 返回值:0 (发送成功)1(寻址失败)2 (发送的数据被拒绝,从机未发送Ack)
**作用:**通过I2C向从机发送若干个字节
4.3.等待总线空闲
总线空闲时,SDA和SCL引脚都为高电平
如果不是高电平时,就需要等待总线空闲

SR2状态寄存器中BUSY(总线忙标志位)
- 0(RESET):总线空闲
- 1(SET):总线忙
代码编写:
cpp
//#1:等待总线空闲
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_BUSY) == SET);
**注:**RESET为0,SET为1
4.4.发送起始位
向SDA控制寄存器的START位写1
等待起始位发送完成

SDA配置寄存器中的START(发送起始位)
- ENABLE:写1
- DISABLE:写0
SR1状态寄存器中的SB(起始位发送完成标志位)
- 0(RESET):起始位未发送
- 1(SET):起始位发送完成
代码编写:
cpp
//#2:发送起始位
I2C_GenerateSTART(I2Cx,ENABLE);
//#3:等待起始位发送完成
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_SB) == RESET);
4.5.发送地址
清零AF标志位,发送7位地址+读写位
等待ADDR从0变成1,变为1后寻址成功,跳出循环
如果AF从0变成1,说明未收到Ack,发送停止位,寻址失败
清零ADDR(先读取SR1,再读取SR2,就可以自动清零)

SR1状态寄存器中的AF(Acknowledge Failure 应答失败标志位)
- 0(RESET):收到Ack
- 1(SET):未收到Ack
SR1状态寄存器中的ADDR(寻址标志位)
- 0(RESET):寻址失败
- 1(SET):寻址成功
代码编写:
cpp
//#3.发送地址
/*清除AF标志位*/
I2C_ClearFlag(I2Cx,I2C_FLAG_AF);
/*发送7位地址+读写位*/
I2C_SendData(I2Cx,Addr & 0xfe);
while(1)
{
/*判断寻址是否成功*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_ADDR) == SET)
{
break;
}
/*如果未收到Ack*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET)
{
/*发送停止位*/
I2C_GenerateSTOP(I2Cx,ENABLE);
/*寻址失败*/
return -1;
}
}
//#4:清除ADDR标志位
/*读取SR1*/
I2C_ReadRegister(I2Cx,I2C_Register_SR1);
/*读取SR2*/
I2C_ReadRegister(I2Cx,I2C_Register_SR2);
**注:**低位与0表示写

4.6.发送数据
循环发送数据
通过AF标志位,判断上一次发送的数据是否被拒收,如果拒收则发送停止位
通过TxE标志位,判断发送数据寄存器中是否有值,防止造成数据丢失,将数据发出
等待数据发送完成
通过AF标志位,判断上一次发送的数据是否被拒收,如果拒收则发送停止位
通过BTF标志位,判断移位数据寄存器和发送数据寄存器是否都为空

SR1状态寄存器中的BTF(数据发送完成标志位)
- 0(RESET):数据发送未完成
- 1(SET):数据发送完成
代码编写:
cpp
//#5:发送数据
/*循环发送数据*/
for(uint16_t i = 0;i < Size;i++)
{
while(1)
{
/*如果上一次未收到Ack*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET)
{
/*发送停止位*/
I2C_GenerateSTOP(I2Cx,ENABLE);
/*数据拒收*/
return -2;
}
/*如果发送数据寄存器中有数据*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_TXE) == SET)
{
/*跳出循环*/
break;
}
}
/*数据发出*/
I2C_SendData(I2Cx,pData[i]);
}
/*等待数据发送完成*/
while(1)
{
/*如果上一次未收到Ac*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET)
{
/*发送停止位*/
I2C_GenerateSTOP(I2Cx,ENABLE);
/*数据被拒收*/
return -2;
}
/*如果数据发送完成*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_BTF) == SET)
{
/*跳出循环*/
break;
}
}
4.7.发送停止位
发送停止位

SDA配置寄存器中的STOP(发送停止位)
- ENABLE:写1
- DISABLE:写0
代码编写:
cpp
//#5:发送停止位
/*发送停止位*/
I2C_GenerateSTOP(I2Cx,ENABLE);
/*发送成功*/
return 0;
4.8.启动OLED

OLED地址为0x78
cpp
uint8_t commands[] = {
0x00,//命令流
0x8d,0x14,//使能电荷泵
0xaf,//打开屏幕开关
0xa5//让屏幕全亮
};
4.9.总代码
cpp
#include "stm32f10x.h"
void My_I2C_Init(void);
int My_I2C_SendByte(I2C_TypeDef* I2Cx, uint8_t Addr,uint8_t* pData, uint16_t Size);
int main(void)
{
My_I2C_Init();
uint8_t commands[] = {0x00,0x8d,0x14,0xaf,0xa5};
My_I2C_SendByte(I2C1,0x78,commands,5);
while(1)
{
}
}
void My_I2C_Init(void)
{
//#1:初始化GPIO
/*开启AFIO模块的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
/*使能I2C1的重映射*/
GPIO_PinRemapConfig(GPIO_Remap_I2C1,ENABLE);
/*开启GPIOB模块的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
/*GPIO结构的前置声明*/
GPIO_InitTypeDef GPIO_InitStruct;
/*选择PB8和PB9引脚*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
/*设置复用输出开漏模式*/
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
/*最大输出速度尾2MHz*/
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
/*初始化PB8和PB9引脚*/
GPIO_Init(GPIOB,&GPIO_InitStruct);
//#2:初始化I2C1模块
/*开启I2C1模块的时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
/*施加复位信号*/
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);
/*释放复位信号*/
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);
/*I2C结构的前置声明*/
I2C_InitTypeDef I2C_InitStruct;
/*波特率为400k*/
I2C_InitStruct.I2C_ClockSpeed = 400000;
/*模式为标准I2C*/
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
/*占空比为2:1*/
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
/*初始化I2C1*/
I2C_Init(I2C1,&I2C_InitStruct);
/*闭合I2C1总开关*/
I2C_Cmd(I2C1,ENABLE);
}
int My_I2C_SendByte(I2C_TypeDef* I2Cx, uint8_t Addr,uint8_t* pData, uint16_t Size)
{
//#1:等待总线空闲
/*等待总线空闲*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_BUSY) == SET);
//#2:发送起始位
/*发送起始位*/
I2C_GenerateSTART(I2Cx,ENABLE);
/*等待起始位发送完成*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_SB) == RESET);
//#3.发送地址
/*清除AF标志位*/
I2C_ClearFlag(I2Cx,I2C_FLAG_AF);
/*发送7位地址+读写位*/
I2C_SendData(I2Cx,Addr & 0xfe);
while(1)
{
/*判断寻址是否成功*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_ADDR) == SET)
{
break;
}
/*如果未收到Ack*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET)
{
/*发送停止位*/
I2C_GenerateSTOP(I2Cx,ENABLE);
/*寻址失败*/
return -1;
}
}
//#4:清除ADDR标志位
/*读取SR1*/
I2C_ReadRegister(I2Cx,I2C_Register_SR1);
/*读取SR2*/
I2C_ReadRegister(I2Cx,I2C_Register_SR2);
//#5:发送数据
/*循环发送数据*/
for(uint16_t i = 0;i < Size;i++)
{
while(1)
{
/*如果上一次未收到Ack*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET)
{
/*发送停止位*/
I2C_GenerateSTOP(I2Cx,ENABLE);
/*数据拒收*/
return -2;
}
/*如果发送数据寄存器中有数据*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_TXE) == SET)
{
/*跳出循环*/
break;
}
}
/*数据发出*/
I2C_SendData(I2Cx,pData[i]);
}
/*等待数据发送完成*/
while(1)
{
/*如果上一次未收到Ac*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET)
{
/*发送停止位*/
I2C_GenerateSTOP(I2Cx,ENABLE);
/*数据被拒收*/
return -2;
}
/*如果数据发送完成*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_BTF) == SET)
{
/*跳出循环*/
break;
}
}
//#5:发送停止位
/*发送停止位*/
I2C_GenerateSTOP(I2Cx,ENABLE);
/*发送成功*/
return 0;
}
五、读数据
5.1.接收字节函数
cpp
int My_I2C_ReceiveBytes(I2C_TypeDef* I2Cx,uint8_t Addr,uint8_t* pBuffer,uint16_t Size);
解析:
- 参数1:I2C接口名称
- 参数2:从机地址,靠左
- 参数3:接收缓冲区
- 参数4:要发送的数据的数量(单位:字节)
- 返回值:0(读取成功)-1(寻址失败)
**作用:**通过I2C从从机读取若干个字节
5.2.数据读取的流程

- 主机发送起始位
- 主机发送7位地址+读写位
- 等待从机Ack
- 主机逐个字节接收数据
- 每次接收后等待主机Ack
- 最后一个字节主机Nak
- 主机发送停止位
5.3.发送起始位和地址
5.3.1.发送起始位
向SDA控制寄存器的START位写1
等待起始位发送完成

代码编写:
cpp
//#1:发送起始位
/*发送起始位*/
I2C_GenerateSTART(I2Cx,ENABLE);
/*等待起始位发送完成*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_SB) == RESET);
5.3.2.发送地址+读写位
清零AF标志位,发送7位地址+读写位
等待ADDR从0变成1,变为1后寻址成功,跳出循环
如果AF从0变成1,说明未收到Ack,发送停止位,寻址失败
清零ADDR(先读取SR1,再读取SR2,就可以自动清零)

代码编写:
cpp
//#2:发送地址
/*清除AF标志位*/
I2C_ClearFlag(I2Cx,I2C_FLAG_AF);
/*发送7位地址+读写位*/
I2C_SendData(I2Cx,Addr | 0x01);
while(1)
{
/*判断寻址是否成功*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_ADDR) == SET)
{
break;
}
/*如果未收到Ack*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET)
{
/*发送停止位*/
I2C_GenerateSTOP(I2Cx,ENABLE);
/*寻址失败*/
return -1;
}
}
**注:**低位或1表示读

5.4.如何发送ACK和NAK

SDA配置寄存器中的ACK(发送应答位)
- 发送NAK:写0
- 发送ACK:写1
**注:**只作用于正在被接收的字节
5.5.如何发送停止位

向SDA配置寄存器中的STOP位写1
**注:**作用于当前字节接收完成之后
5.6.Size = 1


主机发送起始位,7位地址+读写位,从机Ack
清除ADDR标志位,开始接收第一个字节数据
在第一个字节接收完成前写主机NAK和停止位
等待第一个字节数据存储接收数据寄存器(RxNE标志位为1时收到数据),读取数据

代码编写:
cpp
//#3:接收数据
if(Size == 1)
{
/*清除ADDR标志位*/
/*读取SR1*/
I2C_ReadRegister(I2Cx,I2C_Register_SR1);
/*读取SR2*/
I2C_ReadRegister(I2Cx,I2C_Register_SR2);
/*ACK = 0,STOP = 1*/
I2C_AcknowledgeConfig(I2Cx,DISABLE);
I2C_GenerateSTOP(I2Cx,ENABLE);
/*等待RxNE为1*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
/*读取数据*/
pBuffer[0] = I2C_ReceiveData(I2Cx);
}
5.7.Size = 2


主机发送起始位,7位地址+读写位,从机Ack
清除ADDR标志位,开始接收第一个字节数据
在第一个字节接收完成前写从机Ack
等待第一个字节数据存储接收数据寄存器(RxNE标志位为1时收到数据),读取数据
在第二个字节接收完成前写主机NAK和停止位
等待第二个字节数据存储接收数据寄存器(RxNE标志位为1时收到数据),读取数据

代码编写:
cpp
else if(Size == 2)
{
/*清除ADDR标志位*/
/*读取SR1*/
I2C_ReadRegister(I2Cx,I2C_Register_SR1);
/*读取SR2*/
I2C_ReadRegister(I2Cx,I2C_Register_SR2);
/*ACK = 1*/
I2C_AcknowledgeConfig(I2Cx,ENABLE);
/*等待RxNE为1*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
/*读取第一个字节*/
pBuffer[0] = I2C_ReceiveData(I2Cx);
/*ACK = 0,STOP = 1*/
I2C_AcknowledgeConfig(I2Cx,DISABLE);
I2C_GenerateSTOP(I2Cx,ENABLE);
/*等待RxNE为1*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
/*读取第二个字节*/
pBuffer[1] = I2C_ReceiveData(I2Cx);
}
5.8.Size > 2


主机发送起始位,7位地址+读写位,从机Ack
清除ADDR标志位,开始接收第一个字节数据
在第一个字节接收完成前写从机Ack
等待第一个字节数据存储接收数据寄存器(RxNE标志位为1时收到数据),读取数据
......(重复N - 1次)
在第N个字节接收完成前写主机NAK和停止位
等待第N个字节数据存储接收数据寄存器(RxNE标志位为1时收到数据),读取数据

代码编写:
cpp
else
{
/*清除ADDR标志位*/
/*读取SR1*/
I2C_ReadRegister(I2Cx,I2C_Register_SR1);
/*读取SR2*/
I2C_ReadRegister(I2Cx,I2C_Register_SR2);
/*ACK = 1*/
I2C_AcknowledgeConfig(I2Cx,ENABLE);
/*读取第N个字节前的数据*/
for(uint16_t i = 0;i < Size - 1;i++)
{
/*等待RxNE为1*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
/*读取数据*/
pBuffer[i] = I2C_ReceiveData(I2Cx);
}
/*读取第N个字节的数据*/
/*ACK = 0,STOP = 1*/
I2C_AcknowledgeConfig(I2Cx,DISABLE);
I2C_GenerateSTOP(I2Cx,ENABLE);
/*等待RxNE为1*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
/*读取第N个字节*/
pBuffer[Size - 1] = I2C_ReceiveData(I2Cx);
}
/*接收成功*/
return 0
5.9.读取OLED

从OLED中读一个字节,会读出8个bit位数据
其中只有D6位有有效数据,表示屏幕的开关
- D6 = 0:表示屏幕开启
- D6 = 1:表示屏幕关闭

代码编写:
cpp
uint8_t rcvd;
My_I2C_ReceiveBytes(I2C1,0x78,&rcvd,1);
/*检测到屏幕亮*/
if((rcvd & (0x01<<6)) == 0)
{
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);//亮灯
}
else/*检测到屏幕灭*/
{
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);//灭灯
}
解析:
将读取到的值存放在rcvd变量,将rcvd的第六位与1,判断是0还是1
- 如果是0:Led亮(屏幕开启)
- 如果是1:Led灭(屏幕关闭)
总代码:
cpp
#include "stm32f10x.h"
void My_I2C_Init(void);
int My_I2C_SendByte(I2C_TypeDef* I2Cx, uint8_t Addr,uint8_t* pData, uint16_t Size);
int My_I2C_ReceiveBytes(I2C_TypeDef* I2Cx,uint8_t Addr,uint8_t* pBuffer,uint16_t Size);
void My_OnBoardLED_Init(void);
int main(void)
{
My_I2C_Init();
uint8_t commands[] = {0x00,0x8d,0x14,0xaf,0xa5};
My_I2C_SendByte(I2C1,0x78,commands,5);
My_OnBoardLED_Init();
uint8_t rcvd;
My_I2C_ReceiveBytes(I2C1,0x78,&rcvd,1);
/*检测到屏幕亮*/
if((rcvd & (0x01<<6)) == 0)
{
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);//亮灯
}
else/*检测到屏幕灭*/
{
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);//灭灯
}
while(1)
{
}
}
void My_I2C_Init(void)
{
//#1:初始化GPIO
/*开启AFIO模块的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
/*使能I2C1的重映射*/
GPIO_PinRemapConfig(GPIO_Remap_I2C1,ENABLE);
/*开启GPIOB模块的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
/*GPIO结构的前置声明*/
GPIO_InitTypeDef GPIO_InitStruct;
/*选择PB8和PB9引脚*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
/*设置复用输出开漏模式*/
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
/*最大输出速度尾2MHz*/
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
/*初始化PB8和PB9引脚*/
GPIO_Init(GPIOB,&GPIO_InitStruct);
//#2:初始化I2C1模块
/*开启I2C1模块的时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
/*施加复位信号*/
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);
/*释放复位信号*/
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);
/*I2C结构的前置声明*/
I2C_InitTypeDef I2C_InitStruct;
/*波特率为400k*/
I2C_InitStruct.I2C_ClockSpeed = 400000;
/*模式为标准I2C*/
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
/*占空比为2:1*/
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
/*初始化I2C1*/
I2C_Init(I2C1,&I2C_InitStruct);
/*闭合I2C1总开关*/
I2C_Cmd(I2C1,ENABLE);
}
int My_I2C_SendByte(I2C_TypeDef* I2Cx, uint8_t Addr,uint8_t* pData, uint16_t Size)
{
//#1:等待总线空闲
/*等待总线空闲*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_BUSY) == SET);
//#2:发送起始位
/*发送起始位*/
I2C_GenerateSTART(I2Cx,ENABLE);
/*等待起始位发送完成*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_SB) == RESET);
//#3:发送地址
/*清除AF标志位*/
I2C_ClearFlag(I2Cx,I2C_FLAG_AF);
/*发送7位地址+读写位*/
I2C_SendData(I2Cx,Addr & 0xfe);
while(1)
{
/*判断寻址是否成功*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_ADDR) == SET)
{
break;
}
/*如果未收到Ack*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET)
{
/*发送停止位*/
I2C_GenerateSTOP(I2Cx,ENABLE);
/*寻址失败*/
return -1;
}
}
//#4:清除ADDR标志位
/*读取SR1*/
I2C_ReadRegister(I2Cx,I2C_Register_SR1);
/*读取SR2*/
I2C_ReadRegister(I2Cx,I2C_Register_SR2);
//#5:发送数据
/*循环发送数据*/
for(uint16_t i = 0;i < Size;i++)
{
while(1)
{
/*如果上一次未收到Ack*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET)
{
/*发送停止位*/
I2C_GenerateSTOP(I2Cx,ENABLE);
/*数据拒收*/
return -2;
}
/*如果发送数据寄存器中有数据*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_TXE) == SET)
{
/*跳出循环*/
break;
}
}
/*数据发出*/
I2C_SendData(I2Cx,pData[i]);
}
/*等待数据发送完成*/
while(1)
{
/*如果上一次未收到Ac*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET)
{
/*发送停止位*/
I2C_GenerateSTOP(I2Cx,ENABLE);
/*数据被拒收*/
return -2;
}
/*如果数据发送完成*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_BTF) == SET)
{
/*跳出循环*/
break;
}
}
//#5:发送停止位
/*发送停止位*/
I2C_GenerateSTOP(I2Cx,ENABLE);
/*发送成功*/
return 0;
}
int My_I2C_ReceiveBytes(I2C_TypeDef* I2Cx,uint8_t Addr,uint8_t* pBuffer,uint16_t Size)
{
//#1:发送起始位
/*发送起始位*/
I2C_GenerateSTART(I2Cx,ENABLE);
/*等待起始位发送完成*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_SB) == RESET);
//#2:发送地址
/*清除AF标志位*/
I2C_ClearFlag(I2Cx,I2C_FLAG_AF);
/*发送7位地址+读写位*/
I2C_SendData(I2Cx,Addr | 0x01);
while(1)
{
/*判断寻址是否成功*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_ADDR) == SET)
{
break;
}
/*如果未收到Ack*/
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET)
{
/*发送停止位*/
I2C_GenerateSTOP(I2Cx,ENABLE);
/*寻址失败*/
return -1;
}
}
//#3:接收数据
if(Size == 1)
{
/*清除ADDR标志位*/
/*读取SR1*/
I2C_ReadRegister(I2Cx,I2C_Register_SR1);
/*读取SR2*/
I2C_ReadRegister(I2Cx,I2C_Register_SR2);
/*ACK = 0,STOP = 1*/
I2C_AcknowledgeConfig(I2Cx,DISABLE);
I2C_GenerateSTOP(I2Cx,ENABLE);
/*等待RxNE为1*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
/*读取数据*/
pBuffer[0] = I2C_ReceiveData(I2Cx);
}
else if(Size == 2)
{
/*清除ADDR标志位*/
/*读取SR1*/
I2C_ReadRegister(I2Cx,I2C_Register_SR1);
/*读取SR2*/
I2C_ReadRegister(I2Cx,I2C_Register_SR2);
/*ACK = 1*/
I2C_AcknowledgeConfig(I2Cx,ENABLE);
/*等待RxNE为1*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
/*读取第一个字节*/
pBuffer[0] = I2C_ReceiveData(I2Cx);
/*ACK = 0,STOP = 1*/
I2C_AcknowledgeConfig(I2Cx,DISABLE);
I2C_GenerateSTOP(I2Cx,ENABLE);
/*等待RxNE为1*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
/*读取第二个字节*/
pBuffer[1] = I2C_ReceiveData(I2Cx);
}
else
{
/*清除ADDR标志位*/
/*读取SR1*/
I2C_ReadRegister(I2Cx,I2C_Register_SR1);
/*读取SR2*/
I2C_ReadRegister(I2Cx,I2C_Register_SR2);
/*ACK = 1*/
I2C_AcknowledgeConfig(I2Cx,ENABLE);
/*读取第N个字节前的数据*/
for(uint16_t i = 0;i < Size - 1;i++)
{
/*等待RxNE为1*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
/*读取数据*/
pBuffer[i] = I2C_ReceiveData(I2Cx);
}
/*读取第N个字节的数据*/
/*ACK = 0,STOP = 1*/
I2C_AcknowledgeConfig(I2Cx,DISABLE);
I2C_GenerateSTOP(I2Cx,ENABLE);
/*等待RxNE为1*/
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
/*读取第N个字节*/
pBuffer[Size - 1] = I2C_ReceiveData(I2Cx);
}
/*接收成功*/
return 0;
}
void My_OnBoardLED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOC,&GPIO_InitStruct);
}
六、软I2C
6.1.硬I2C VS 软I2C
**硬件I2C(硬件模拟):**靠单片机内部的专用电路自动产生波形
优点:
- 解放CPU:硬件电路自动处理时序,CPU可以处理其他任务
- 速度快且稳定:晶振驱动硬件电路,时序精准,适合高速传输
- 代码简洁:不需要写复杂的延时和操作代码,只需配置好寄存器
缺点:
- 引脚固定,移植性差
- 从机没有Ack,主机会卡死
- 需在外部接上拉电阻
**软件I2C(软件模拟):**靠CPU执行代码控制GPIO引脚高低电平画出波形
优点:
- 引脚随意:如果I2C的引脚被占用,可以用任意GPIO引脚
- 兼容性好:不同传感器的I2C时序有差异,软I2C可以进行修改
- 调试方便:可以清楚看见每个电平变化
缺点:
- CPU占用高
- 时序精度一般
6.2.IO引脚初始化


软I2C的GPIO引脚由CPU直接控制,同时为了实现硬I2C的逻辑线与
需要用开漏模式,所以设置为通用输出开漏,且初始电压高(空闲)
代码编写:
cpp
void My_SI2C_Init(void)
{
/*开启GPIOA的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*声明GPIO结构变量*/
GPIO_InitTypeDef GPIO_InitStruct;
/*选择PA0和PA1引脚*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
/*通用输出开漏模式*/
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
/*最大输出速率为2MHz*/
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
/*初始化PA0和PA1引脚*/
GPIO_Init(GPIOA,&GPIO_InitStruct);
/*将引脚设置位高电平*/
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_SET);
}
6.3.IO读写和延迟函数
6.3.1.编程接口1
cpp
void scl_write(uint8_t level);
**作用:**向SCL写0或写1
- 写0:输出低电平
- 写1:输出高电平
代码编写:
cpp
void scl_write(uint8_t level)
{
/*向PA0引脚写0*/
if(level == 0)
{
/*输出低电平,总线为低电平*/
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
}
else/*向PA0引脚写1*/
{
/*输出高阻态 总线为高电平*/
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
}
}
6.3.2.编程接口2
cpp
void sda_write(uint8_t level);
**作用:**向SDA写0或写1
- 写0:输出低电平
- 写1:输出高电平
代码编写:
cpp
void sda_write(uint8_t level)
{
/*向PA1引脚写0*/
if(level == 0)
{
/*输出低电平,总线为低电平*/
GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_RESET);
}
else/*向PA1引脚写1*/
{
/*输出高阻态 总线为高电平*/
GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_SET);
}
}
6.3.3.编程接口3
cpp
uint8_t sda_read(void);
**作用:**读取SDA的值
- 返回值为0:低电平
- 返回值为1:高电平
代码编写:
cpp
uint8_t sda_read(void)
{
/*如果读到PA1为高电平*/
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1) == Bit_SET)
{
/*返回1*/
return 1;
}
else/*如果读到PA1为低电平*/
{
/*返回0*/
return 0;
}
}
6.3.4.编程接口4
cpp
void delay_us(uint32_t us)
{
uint32_t n = us * 8;
for(uint32_t i = 0;i < n;i++);
}
**作用:**实现微秒级延迟
**注:**每执行一次for循环需要八分之一微秒
6.4.发送起始位和停止位
**起始位:**当SCL为高电平时,SDA产生下降沿

代码编写:
cpp
void SendStart(void)
{
sda_write(0);
delay_us(1);
}
**停止位:**当SCL为高电平时,SDA产生上升沿

注:
由于停止位前的SDA电平高低不确定,此时若SDA为高电平就无法在停止位产生上升沿
所以需提前拉低SDA,但为了避免时钟线误认为是数据采样,还需要在这之前拉低SCL
所以整个流程为:
- 拉低SCL,拉低SDA,延迟1us维持电平稳定
- 拉高SCL,准备接收SDA,延迟1us维持电平稳定
- 拉高SDA,发送停止位,延迟1us维持电平稳定
代码编写:
cpp
void SendStop(void)
{
scl_write(0);
sda_write(0);
delay_us(1);
scl_write(1);
delay_us(1);
sda_write(1);
delay_us(1);
}
6.5.发送一个字节

发送一个bit位:

拉低SCL,让SDA准备数据0或者1
延迟1us后,再拉高SCL接收数据
最后再延迟1us,维持电平稳定
代码编写:
cpp
scl_write(0);
sda_write(x);
delay_us(1);
scl_write(1);
delay_us(1);
发送字节函数:
cpp
uint8_t sendByte(uint8_t byte);
解析:
- 参数1:要发送的字节
- 返回值:接收方回复,0(ACK)1(NAK)
代码编写:
cpp
uint8_t sendByte(uint8_t Byte)
{
for(int8_t i = 7;i >= 0;i--)
{
scl_write(0);
if((Byte & (0x01 << i)) != 0)
{
sda_write(1);
}
else
{
sda_write(0);
}
delay_us(1);
scl_write(1);
delay_us(1);
}
//读取ACK和NAK
scl_write(0);
sda_write(1);
delay_us(1);
scl_write(1);
delay_us(1);
return sda_read();
}
解析:
计算x的值是0还是1:

从Byte数据的高位开始逐位与1
如果与1后的结果是0,说明该bit位数据为0,输入低电平
如果与1后的结果不是0,说明该bit为数据为1,输入高电平
检测ACK或NAK:

拉低SCL,拉高SDA,释放SDA总线,延迟1us稳定电平
如果是接收方ACK,接收方的硬件电路自动拉低SDA引脚电平
如果是接收方NAK,接收方则不作调整,保持SDA高电平
此时再拉高SCL,延迟1us,读取低电平0(ACK),读取高电平1(NAK)
6.6.接收一个字节
接收1个bit位:

拉低SCL,拉高SDA,释放SDA总线,延迟1us稳定电平
如果发送方发送高电平,发送方的硬件电路自动拉低SDA引脚电平
如果发送方发送低电平,发送方则不作调整,保持SDA高电平
此时再拉高SCL,延迟1us,读取低电平0,读取高电平1
代码编写:
cpp
scl_write(0);
sda_write(1);
delay_us(1);
scl_write(1);
delay_us(1);
bit = sda_read();
接收字节函数:
cpp
uint8_t receiveByte(uint8_t Ack);
解析:
- 参数1:Ack为0时回复Nak,Ack为1时回复Ack
- 返回值:读到字节的数据
代码编写:
cpp
uint8_t receiveByte(uint8_t Ack)
{
uint8_t byte = 0;
for(int8_t i = 7;i >= 0;i--)
{
scl_write(0);
sda_write(1);
delay_us(1);
scl_write(1);
delay_us(1);
if(sda_read() != 0)
{
byte |= 0x01 << i;
}
}
//回复ACK或NAK
scl_write(0);
sda_write(!Ack);
delay_us(1);
scl_write(1);
delay_us(1);
return byte;
}
解析:
读出byte数据:
从byte的高位开始逐位或1
- 如果读取到高电平就或1
- 如果读到低电平就跳过
回复ACK或NAK:

拉低SCL,控制主机回复应答信号
传参为1代表ACK,传参为0代表NAK
但实际上:
向sda_write()传1时,为高电平代表NAK
向sda_write()传0时,为低电平代表ACK
要对参数ACK取反,才能保证逻辑正确
准备好SDA数据后,延迟1us稳定电平
此时再拉高SCL,延迟1us,读取低电平ACK,读取高电平NAK
注:
与主机回复应答信号不同的是
从机回复ACK或NAK是通过释放SDA
让从机的硬件电路控制选择ACK或NAK
而主机回复应答信号需要CPU主动控制(函数传参)
6.7.综合
软I2C写
cpp
int My_SI2C_SendBytes(uint8_t Addr,uint8_t* pData,uint16_t Size);
解析:
- 参数1:从机地址,靠左
- 参数2:要发送的数据
- 参数3:要发送的数据的数量(单位:字节)
代码编写:
cpp
int My_SI2C_SendBytes(uint8_t Addr,uint8_t* pData,uint16_t Size)
{
/*发送起始位*/
SendStart();
/*发送地址+读写位*/
/*如果没有接收到ACK*/
if(sendByte(Addr & 0xfe) != 0)
{
/*发送停止位*/
SendStop();
/*寻址失败*/
return -1;
}
for(uint32_t i = 0;i < Size;i++)
{
/*如果接收方回复NAK*/
if(sendByte(pData[i]) != 0)
{
/*发送停止位*/
SendStop();
/*数据被拒收*/
return -2;
}
}
/*发送停止位*/
SendStop();
/*数据发送成功*/
return 0;
}

软I2C读
cpp
int My_SI2C_ReceiveBytes(uint8_t Addr,uint8_t* pBuffer,uint16_t Size);
解析:
- 参数1:从机地址,靠左
- 参数2:接收缓冲区
- 参数3:要接收的数据的数量(单位:字节)
代码编写:
cpp
int My_SI2C_ReceiveBytes(uint8_t Addr,uint8_t* pBuffer,uint16_t Size)
{
/*发送起始位*/
SendStart();
/*发送地址+读写位*/
/*如果没有接收到ACK*/
if(sendByte(Addr | 0x01) != 0)
{
/*发送停止位*/
SendStop();
/*寻址失败*/
return -1;
}
/*接收数据*/
for(uint32_t i = 0;i < Size - 1;i++)
{
pBuffer[i] = receiveByte(1);
}
pBuffer[Size - 1] = receiveByte(0);
/*发送停止位*/
SendStop();
/*数据接收成功*/
return 0;
}

6.8.测试
总代码编写:
cpp
#include "stm32f10x.h"
void My_SI2C_Init(void);
void scl_write(uint8_t level);
void sda_write(uint8_t level);
uint8_t sda_read(void);
void delay_us(uint32_t us);
void SendStart(void);
void SendStop(void);
uint8_t sendByte(uint8_t Byte);
uint8_t receiveByte(uint8_t Ack);
int My_SI2C_SendBytes(uint8_t Addr,uint8_t* pData,uint16_t Size);
int My_SI2C_ReceiveBytes(uint8_t Addr,uint8_t* pBuffer,uint16_t Size);
int main(void)
{
My_SI2C_Init();
uint8_t commands[] = {
0x00,//命令流
0x8d,0x14,//使能电荷泵
0xaf,//打开屏幕开关
0xa5,//让屏幕全亮
};
My_SI2C_SendBytes(0x78,commands,5);
while(1)
{
}
}
void My_SI2C_Init(void)
{
/*开启GPIOA的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*声明GPIO结构变量*/
GPIO_InitTypeDef GPIO_InitStruct;
/*选择PA0和PA1引脚*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
/*通用输出开漏模式*/
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
/*最大输出速率为2MHz*/
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
/*初始化PA0和PA1引脚*/
GPIO_Init(GPIOA,&GPIO_InitStruct);
/*将引脚设置位高电平*/
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_SET);
}
void scl_write(uint8_t level)
{
/*向PA0引脚写0*/
if(level == 0)
{
/*输出低电平,总线为低电平*/
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
}
else/*向PA0引脚写1*/
{
/*输出高阻态 总线为高电平*/
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
}
}
void sda_write(uint8_t level)
{
/*向PA1引脚写0*/
if(level == 0)
{
/*输出低电平,总线为低电平*/
GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_RESET);
}
else/*向PA1引脚写1*/
{
/*输出高阻态 总线为高电平*/
GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_SET);
}
}
uint8_t sda_read(void)
{
/*如果读到PA1为高电平*/
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1) == Bit_SET)
{
/*返回1*/
return 1;
}
else/*如果读到PA1为低电平*/
{
/*返回0*/
return 0;
}
}
void delay_us(uint32_t us)
{
uint32_t n = us * 8;
for(uint32_t i = 0;i < n;i++);
}
void SendStart(void)
{
sda_write(0);
delay_us(1);
}
void SendStop(void)
{
scl_write(0);
sda_write(0);
delay_us(1);
scl_write(1);
delay_us(1);
sda_write(1);
delay_us(1);
}
uint8_t sendByte(uint8_t Byte)
{
for(int8_t i = 7;i >= 0;i--)
{
scl_write(0);
if((Byte & (0x01 << i)) != 0)
{
sda_write(1);
}
else
{
sda_write(0);
}
delay_us(1);
scl_write(1);
delay_us(1);
}
//读取ACK和NAK
scl_write(0);
sda_write(1);
delay_us(1);
scl_write(1);
delay_us(1);
return sda_read();
}
uint8_t receiveByte(uint8_t Ack)
{
uint8_t byte = 0;
for(int8_t i = 7;i >= 0;i--)
{
scl_write(0);
sda_write(1);
delay_us(1);
scl_write(1);
delay_us(1);
if(sda_read() != 0)
{
byte |= 0x01 << i;
}
}
//回复ACK或NAK
scl_write(0);
sda_write(!Ack);
delay_us(1);
scl_write(1);
delay_us(1);
return byte;
}
int My_SI2C_SendBytes(uint8_t Addr,uint8_t* pData,uint16_t Size)
{
/*发送起始位*/
SendStart();
/*发送地址+读写位*/
/*如果没有接收到ACK*/
if(sendByte(Addr & 0xfe) != 0)
{
/*发送停止位*/
SendStop();
/*寻址失败*/
return -1;
}
/*发送数据*/
for(uint32_t i = 0;i < Size;i++)
{
/*如果接收方回复NAK*/
if(sendByte(pData[i]) != 0)
{
/*发送停止位*/
SendStop();
/*数据被拒收*/
return -2;
}
}
/*发送停止位*/
SendStop();
/*数据发送成功*/
return 0;
}
int My_SI2C_ReceiveBytes(uint8_t Addr,uint8_t* pBuffer,uint16_t Size)
{
/*发送起始位*/
SendStart();
/*发送地址+读写位*/
/*如果没有接收到ACK*/
if(sendByte(Addr | 0x01) != 0)
{
/*发送停止位*/
SendStop();
/*寻址失败*/
return -1;
}
/*接收数据*/
for(uint32_t i = 0;i < Size - 1;i++)
{
pBuffer[i] = receiveByte(1);
}
pBuffer[Size - 1] = receiveByte(0);
/*发送停止位*/
SendStop();
/*数据接收成功*/
return 0;
}
七、封装常用功能
7.1.测试硬件I2C

7.1.1.初始化硬件I2C代码
cpp
#include "stm32f10x.h"
void My_I2C1_Init(void);
int main(void)
{
My_I2C1_Init();
while(1)
{
}
}
void My_I2C1_Init(void)
{
//#1:对PB6和PB7进行初始化
/*开启GPIOB的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
/*声明GPIO结构变量*/
GPIO_InitTypeDef GPIO_InitStruct;
/*选择PB6和PB7引脚*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
/*复用输出开漏模式*/
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
/*最大输出速率为2MHz*/
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
/*初始化PB6和PB7引脚*/
GPIO_Init(GPIOB,&GPIO_InitStruct);
//#2:对I2C1进行初始化
/*开启I2C1的时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
/*复位I2C1*/
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);
/*声明I2C结构变量*/
I2C_InitTypeDef I2C_InitStruct;
/*波特率为400k*/
I2C_InitStruct.I2C_ClockSpeed = 400000;
/*标准I2C模式*/
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
/*占空比为2:1*/
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
/*初始化I2C1*/
I2C_Init(I2C1,&I2C_InitStruct);
/*闭合总开关*/
I2C_Cmd(I2C1,ENABLE);
}
7.1.2.硬件I2C收发数据函数
cpp
int My_I2C_SendBytes(...)//发送字节
int My_I2C_ReceiveBytes(...)//接收字节
7.1.3.发送数据
cpp
#include "stm32f10x.h"
#include "i2c.h"
void My_I2C1_Init(void);
int main(void)
{
My_I2C1_Init();
uint8_t commands[] = {0x00,0x8d,0x14,0xaf,0xa5};
My_I2C_SendBytes(I2C1,0x78,commands,5);
while(1)
{
}
}
7.1.4.接收数据
初始化板载LED
cpp
void My_OnBoardLED_Init(void)
{
/*开启GPIOC的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
/*声明GPIO结构变量*/
GPIO_InitTypeDef GPIO_InitStruct;
/*选择PC13引脚*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
/*通用输出开漏模式*/
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
/*最大输出速率为2MHz*/
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
/*初始化PC13引脚*/
GPIO_Init(GPIOC,&GPIO_InitStruct);
/*默认熄灭*/
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}
接收数据
cpp
#include "stm32f10x.h"
#include "i2c.h"
void My_I2C1_Init(void);
void My_OnBoardLED_Init(void);
int main(void)
{
My_I2C1_Init();
My_OnBoardLED_Init();
uint8_t commands[] = {0x00,0x8d,0x14,0xaf,0xa5};
My_I2C_SendBytes(I2C1,0x78,commands,5);
uint8_t rcvd;
My_I2C_ReceiveBytes(I2C1,0x78,&rcvd,1);
if((rcvd & (0x00 << 6)) == 0)
{
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
}
else
{
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}
while(1)
{
}
}
7.2.测试软件I2C
7.2.1.软件I2C收发数据函数
cpp
void My_SI2C_Init(...);//初始化软I2C
int My_SI2C_SendBytes(...);//发送字节
int My_SI2C_ReceiveBytes(...);//接收字节
7.2.2.收发数据代码
cpp
#include "stm32f10x.h"
#include "si2c.h"
/*声明软I2C结构变量*/
SI2C_TypeDef si2c;
void My_OnBoardLED_Init(void);
int main(void)
{
/*配置SCL和SDA引脚*/
si2c.SCL_GPIOx = GPIOB;
si2c.SCL_GPIO_Pin = GPIO_Pin_6;
si2c.SDA_GPIOx = GPIOB;
si2c.SDA_GPIO_Pin = GPIO_Pin_7;
/*初始化软I2C*/
My_SI2C_Init(&si2c);
My_OnBoardLED_Init();
uint8_t commands[] = {0x00,0x8d,0x14,0xaf,0xa5};
My_SI2C_SendBytes(&si2c,0x78,commands,5);
uint8_t rcvd;
My_SI2C_ReceiveBytes(&si2c,0x78,&rcvd,1);
if((rcvd & (0x00 << 6)) == 0)
{
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
}
else
{
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}
while(1)
{
}
}
八、OLED显示器
8.1.OLED显示器的基本原理

通过I2C总线,对OLED中的内存进行读写
内存中的每一个bit位都代表屏幕上的一个像素
- 写1点亮
- 写0熄灭
向内存写入数据,调整内存内容,显示对应图像
8.2.屏幕初始化

8.2.1.初始化软I2C
cpp
#include "stm32f10x.h"
#include "si2c.h"//引用软I2C的头文件
/*声明软I2C结构变量*/
SI2C_TypeDef si2c;
/*声明软I2C初始化函数*/
void My_SoftwareI2C_Init(void)
int main(void)
{
/*初始化软I2C*/
My_SoftwareI2C_Init();
while(1)
{
}
}
void My_SoftwareI2C_Init(void)
{
/*配置SCL和SDA引脚*/
/*指定SCL的位置*/
si2c.SCL_GPIOx = GPIOB;
si2c.SCL_GPIO_Pin = GPIO_Pin_6;
/*指定SDA的位置*/
si2c.SDA_GPIOx = GPIOB;
si2c.SDA_GPIO_Pin = GPIO_Pin_7;
/*初始化软I2C*/
My_SI2C_Init(&si2c);
}
8.2.2.屏幕初始化代码的编写
cpp
int OLED_Init(OLED_TypeDef* OLED,OLED_InitTypeDef* OLED_InitStruct);
解析:
- 参数1:所使用的OLED的名称
- 参数2:OLED的初始化参数
**作用:**对OLED显示器进行初始化
**补充:**OLED_InitTypeDef结构(OLED参数菜单)
cpp
struct OLED_InitTypeDed
{
int(*i2c_write_cb)(uint8_t addr,const uint8_t* pdata,uint16_t size);
}
分析:
**i2c_write_cb:**I2C写数据回调函数
cpp
#include "stm32f10x.h"
#include "si2c.h"//引用软I2C的头文件
#include "oled.h"//引用OLED的头文件
/*声明软I2C结构变量*/
SI2C_TypeDef si2c;
/*声明OLED结构变量*/
OLED_TypeDef oled;
/*声明软I2C初始化函数*/
void My_SoftwareI2C_Init(void);
/*声明软I2C写字节函数*/
int i2c_write_bytes(uint8_t addr,const uint8_t* pdata,uint16_t size);
/*声明OLED初始化函数*/
void My_OLEDScreen_Init(void);
int main(void)
{
/*初始化软I2C*/
My_SoftwareI2C_Init();
/*初始化OLED*/
My_OLEDScreen_Init();
while(1)
{
}
}
void My_SoftwareI2C_Init(void)
{
/*配置SCL和SDA引脚*/
/*指定SCL的位置*/
si2c.SCL_GPIOx = GPIOB;
si2c.SCL_GPIO_Pin = GPIO_Pin_6;
/*指定SDA的位置*/
si2c.SDA_GPIOx = GPIOB;
si2c.SDA_GPIO_Pin = GPIO_Pin_7;
/*初始化软I2C*/
My_SI2C_Init(&si2c);
}
int i2c_write_bytes(uint8_t addr,const uint8_t* pdata,uint16_t size)
{
return My_SI2C_SendBytes(&si2c,addr,pdata,size);
}
void My_OLEDScreen_Init(void)
{
/*声明OLED初始化结构变量*/
OLED_InitTypeDef OLED_InitStruct;
/*将软I2C写字节函数赋给结构中的变量*/
OLED_InitStruct.i2c_write_cb = i2c_write_bytes;
/*初始化OLED*/
OLED_Init(&oled,&OLED_InitStruct);
}
解析:
**封装底层驱动:**将软件I2C的发送函数My_SI2C_SendBytes封装成通用接口i2c_write_byte
**注册回调函数:**将i2c_write_byte赋值给OLED初始化结构的i2c_write_cb成员,关联驱动与屏幕
**统一调用入口:**在OLED_Init内部通过回调函数i2c_write_cb发起I2C通信,将参数传给底层发送
8.3.基本概念和操作
8.3.1.画笔和画刷

cpp
/*设置画笔*/
void OLED_SetPen(OLED_TypeDef *OLED, uint8_t Pen_Color, uint8_t Width);
/*设置画刷*/
void OLED_SetBrush(OLED_TypeDef *OLED, uint8_t Brush_Color);
8.3.2.屏幕坐标系

8.3.3.光标

cpp
/*设置光标位置*/
void OLED_SetCursor(OLED_TypeDef *OLED, int16_t X, int16_t Y);
/*设置光标的X坐标*/
void OLED_SetCursorX(OLED_TypeDef *OLED, int16_t X);
/*设置光标的Y坐标*/
void OLED_SetCursorY(OLED_TypeDef *OLED, int16_t Y);
/*移动光标*/
void OLED_MoveCursor(OLED_TypeDef *OLED, int16_t dX, int16_t dY);
/*沿X轴方向移动光标*/
void OLED_MoveCursorX(OLED_TypeDef *OLED, int16_t dX);
/*沿Y轴方向移动光标*/
void OLED_MoveCursorY(OLED_TypeDef *OLED, int16_t dY);
/*获取光标当前位置*/
void OLED_GetCursor(OLED_TypeDef *OLED, int16_t *pXOut, int16_t *pYOut);
/*获取光标X坐标*/
int16_t OLED_GetCursorX(OLED_TypeDef *OLED);
/*获取光标Y坐标*/
int16_t OLED_GetCursorY(OLED_TypeDef *OLED);
8.4.文字相关的操作
8.4.1.打印字符串
cpp
int OLED_DrawString(OLED_TypeDef* OLED,const char* Str);
解析:
- 参数1:所使用的OLED的名称
- 参数2:要显示的字符串
示例:
cpp
int main(void)
{
/*初始化软I2C*/
My_SoftwareI2C_Init();
/*初始化OLED*/
My_OLEDScreen_Init();
//#1:打印Hello World
/*白色画笔*/
OLED_SetPen(&oled,PEN_COLOR_WHITE,1);
/*透明画刷*/
OLED_SetBrush(&oled,PEN_COLOR_TRANSPARENT);
/*设置光标*/
OLED_SetCursor(&oled,24,50);
/*打印字符串*/
OLED_DrawString(&oled,"Hello world");
/*将数据发送OLED*/
OLED_SendBuffer(&oled);
while(1)
{
}
}
8.4.2.设置字体
示例:
cpp
#include "stm32f10x.h"
#include "si2c.h"//引用软I2C的头文件
#include "oled.h"//引用OLED的头文件
#include "kt16.h"//引用字体头文件
/*声明软I2C结构变量*/
SI2C_TypeDef si2c;
/*声明OLED结构变量*/
OLED_TypeDef oled;
/*声明软I2C初始化函数*/
void My_SoftwareI2C_Init(void);
/*声明软I2C写字节函数*/
int i2c_write_bytes(uint8_t addr,const uint8_t* pdata,uint16_t size);
/*声明OLED初始化函数*/
void My_OLEDScreen_Init(void);
int main(void)
{
/*初始化软I2C*/
My_SoftwareI2C_Init();
/*初始化OLED*/
My_OLEDScreen_Init();
//#1:打印Hello World
/*白色画笔*/
OLED_SetPen(&oled,PEN_COLOR_WHITE,1);
/*透明画刷*/
OLED_SetBrush(&oled,PEN_COLOR_TRANSPARENT);
/*设置光标*/
OLED_SetCursor(&oled,24,50);
/*打印字符串*/
OLED_DrawString(&oled,"Hello world");
//#2:打印你好世界
/*设置字体*/
OLED_SetFont(&oled,&kt16);
/*计算横坐标*/
int16_t x = (OLED_GetScreenWidth(&oled) - OLED_GetStrWidth(&oled,"你好世界")) / 2;
/*设置光标*/
OLED_SetCursor(&oled,x,28);
/*打印字符串*/
OLED_DrawString(&oled,"你好世界");
/*将数据发送OLED*/
OLED_SendBuffer(&oled);
while(1)
{
}
}
8.4.3.格式化打印字符串
cpp
#include "stm32f10x.h"
#include "si2c.h"//引用软I2C的头文件
#include "oled.h"//引用OLED的头文件
#include "kt16.h"//引用字体头文件
/*声明软I2C结构变量*/
SI2C_TypeDef si2c;
/*声明OLED结构变量*/
OLED_TypeDef oled;
/*声明软I2C初始化函数*/
void My_SoftwareI2C_Init(void);
/*声明软I2C写字节函数*/
int i2c_write_bytes(uint8_t addr,const uint8_t* pdata,uint16_t size);
/*声明OLED初始化函数*/
void My_OLEDScreen_Init(void);
int main(void)
{
/*初始化软I2C*/
My_SoftwareI2C_Init();
/*初始化OLED*/
My_OLEDScreen_Init();
//#1:打印Hello World
/*白色画笔*/
OLED_SetPen(&oled,PEN_COLOR_WHITE,1);
/*透明画刷*/
OLED_SetBrush(&oled,PEN_COLOR_TRANSPARENT);
/*设置光标*/
OLED_SetCursor(&oled,24,50);
/*打印字符串*/
OLED_DrawString(&oled,"Hello world");
//#2:打印你好世界
/*设置字体*/
OLED_SetFont(&oled,&kt16);
/*计算横坐标*/
int16_t x = (OLED_GetScreenWidth(&oled) - OLED_GetStrWidth(&oled,"你好世界")) / 2;
/*设置光标*/
OLED_SetCursor(&oled,x,28);
/*打印字符串*/
OLED_DrawString(&oled,"你好世界");
//#3:格式化打印字符串
/*设置默认字体*/
OLED_SetFont(&oled,&default_font);
/*设置光标*/
OLED_SetCursor(&oled,58,64);
/*格式化字符串*/
OLED_Printf(&oled,"%04d/%02d/%02d",2026,1,29);
/*将数据发送OLED*/
OLED_SendBuffer(&oled);
while(1)
{
}
}
8.4.4.设置文本区域

cpp
/*设置剪切区域*/
void OLED_StartClipRegion(OLED_TypeDef *OLED, int16_t X, int16_t Y, uint16_t Width, uint16_t Height);
/*停止剪切区域*/
void OLED_StopClipRegion(OLED_TypeDef *OLED);
cpp
#include "stm32f10x.h"
#include "si2c.h"//引用软I2C的头文件
#include "oled.h"//引用OLED的头文件
/*声明软I2C结构变量*/
SI2C_TypeDef si2c;
/*声明OLED结构变量*/
OLED_TypeDef oled;
/*声明软I2C初始化函数*/
void My_SoftwareI2C_Init(void);
/*声明软I2C写字节函数*/
int i2c_write_bytes(uint8_t addr,const uint8_t* pdata,uint16_t size);
/*声明OLED初始化函数*/
void My_OLEDScreen_Init(void);
int main(void)
{
/*初始化软I2C*/
My_SoftwareI2C_Init();
/*初始化OLED*/
My_OLEDScreen_Init();
//#4:设置文本区域
/*设置剪切区域*/
OLED_StartTextRegion(&oled,0,0,128,64);
OLED_DrawString(&oled,"We are in the fantasy.\r\n");
OLED_DrawString(&oled,"Beautiful light is always filled there.\r\n");
OLED_DrawString(&oled,"The life has been filled...\r\n");
/*将数据发送OLED*/
OLED_SendBuffer(&oled);
while(1)
{
}
}
8.5.绘图相关的操作
8.5.1.画点
cpp
/*画点*/
void OLED_DrawDot(OLED_TypeDef *OLED);
cpp
#include "stm32f10x.h"
#include "si2c.h"//引用软I2C的头文件
#include "oled.h"//引用OLED的头文件
/*声明软I2C结构变量*/
SI2C_TypeDef si2c;
/*声明OLED结构变量*/
OLED_TypeDef oled;
/*声明软I2C初始化函数*/
void My_SoftwareI2C_Init(void);
/*声明软I2C写字节函数*/
int i2c_write_bytes(uint8_t addr,const uint8_t* pdata,uint16_t size);
/*声明OLED初始化函数*/
void My_OLEDScreen_Init(void);
int main(void)
{
/*初始化软I2C*/
My_SoftwareI2C_Init();
/*初始化OLED*/
My_OLEDScreen_Init();
//#5:画点
/*白色画笔*/
OLED_SetPen(&oled,PEN_COLOR_WHITE,3);
/*设置光标*/
OLED_SetCursor(&oled,29,32);
/*画点*/
OLED_DrawDot(&oled);
/*画剩下的7个点*/
for(uint32_t i = 1;i < 8;i++)
{
/*光标左移10像素*/
OLED_MoveCursorX(&oled,10);
/*画点*/
OLED_DrawDot(&oled);
}
/*将数据发送OLED*/
OLED_SendBuffer(&oled);
while(1)
{
}
}
8.5.2.画线
cpp
/*从光标的当前位置到(x,y)画线*/
void OLED_DrawLine(OLED_TypeDef *OLED, int16_t X, int16_t Y);
/*从光标的当前位置到(x,y)画线,并把光标移动到(x,y)*/
void OLED_LineTo(OLED_TypeDef *OLED, int16_t X, int16_t Y);
cpp
#include "stm32f10x.h"
#include "si2c.h"//引用软I2C的头文件
#include "oled.h"//引用OLED的头文件
/*声明软I2C结构变量*/
SI2C_TypeDef si2c;
/*声明OLED结构变量*/
OLED_TypeDef oled;
/*声明软I2C初始化函数*/
void My_SoftwareI2C_Init(void);
/*声明软I2C写字节函数*/
int i2c_write_bytes(uint8_t addr,const uint8_t* pdata,uint16_t size);
/*声明OLED初始化函数*/
void My_OLEDScreen_Init(void);
int main(void)
{
/*初始化软I2C*/
My_SoftwareI2C_Init();
/*初始化OLED*/
My_OLEDScreen_Init();
//#6:画线
/*设置光标*/
OLED_SetCursor(&oled,0,0);
/*画线*/
OLED_DrawLine(&oled,128,64);
/*设置光标*/
OLED_SetCursor(&oled,0,64);
/*画线*/
OLED_DrawLine(&oled,128,0);
/*设置光标*/
OLED_SetCursor(&oled,84,22);
/*画线*/
OLED_LineTo(&oled,44,22);
OLED_LineTo(&oled,44,42);
OLED_LineTo(&oled,84,42);
/*将数据发送OLED*/
OLED_SendBuffer(&oled);
while(1)
{
}
}
8.5.3.画矩形和画圆
cpp
/*画圆*/
void OLED_DrawCircle(OLED_TypeDef *OLED, uint16_t Radius);
/*画矩形*/
void OLED_DrawRect(OLED_TypeDef *OLED, uint16_t Width, uint16_t Height);
cpp
#include "stm32f10x.h"
#include "si2c.h"//引用软I2C的头文件
#include "oled.h"//引用OLED的头文件
/*声明软I2C结构变量*/
SI2C_TypeDef si2c;
/*声明OLED结构变量*/
OLED_TypeDef oled;
/*声明软I2C初始化函数*/
void My_SoftwareI2C_Init(void);
/*声明软I2C写字节函数*/
int i2c_write_bytes(uint8_t addr,const uint8_t* pdata,uint16_t size);
/*声明OLED初始化函数*/
void My_OLEDScreen_Init(void);
int main(void)
{
/*初始化软I2C*/
My_SoftwareI2C_Init();
/*初始化OLED*/
My_OLEDScreen_Init();
//#7:画矩形和圆
/*设置光标*/
OLED_SetCursor(&oled,20,20);
/*白色画笔*/
OLED_SetPen(&oled,PEN_COLOR_WHITE,1);
/*透明画刷*/
OLED_SetBrush(&oled,BRUSH_TRANSPARENT);
/*画矩形*/
OLED_DrawRect(&oled,40,20);
/*设置光标*/
OLED_SetCursor(&oled,65,30);
/*画圆形*/
OLED_DrawCircle(&oled,5);
/*设置光标*/
OLED_SetCursor(&oled,70,20);
/*透明画笔*/
OLED_SetPen(&oled,PEN_COLOR_TRANSPARENT,1);
/*透明画刷*/
OLED_SetBrush(&oled,BRUSH_WHITE);
/*画矩形*/
OLED_DrawRect(&oled,40,20);
/*将数据发送OLED*/
OLED_SendBuffer(&oled);
while(1)
{
}
}
8.5.4.绘制位图
cpp
/*画位图*/
void OLED_DrawBitmap(OLED_TypeDef *OLED, uint16_t Width, uint16_t Height, const uint8_t *pBitmap);
图片转换网址:https://javl.github.io/image2cpp/
图像参数设置
满屏为:128 * 64
缩放模式为:stretch to fill canvas
输出格式为:Plain bytes
生成代码
cpp
#include "stm32f10x.h"
#include "si2c.h"//引用软I2C的头文件
#include "oled.h"//引用OLED的头文件
/*声明软I2C结构变量*/
SI2C_TypeDef si2c;
/*声明OLED结构变量*/
OLED_TypeDef oled;
/*声明位图数据变量*/
uint8_t bitmap[] = {};
/*声明软I2C初始化函数*/
void My_SoftwareI2C_Init(void);
/*声明软I2C写字节函数*/
int i2c_write_bytes(uint8_t addr,const uint8_t* pdata,uint16_t size);
/*声明OLED初始化函数*/
void My_OLEDScreen_Init(void);
int main(void)
{
/*初始化软I2C*/
My_SoftwareI2C_Init();
/*初始化OLED*/
My_OLEDScreen_Init();
//#8:绘制位图
/*设置光标*/
OLED_SetCursor(&oled,0,0);
/*绘图*/
OLED_DrawBitmap(&oled,128,64,bitmap);
/*将数据发送OLED*/
OLED_SendBuffer(&oled);
while(1)
{
}
}
绘制动图:
cpp
#include "stm32f10x.h"
#include "si2c.h"//引用软I2C的头文件
#include "oled.h"//引用OLED的头文件
#include "delay.h"
/*声明软I2C结构变量*/
SI2C_TypeDef si2c;
/*声明OLED结构变量*/
OLED_TypeDef oled;
/*声明位图数据变量*/
uint8_t bitmap[] = {};
const uint8_t bitmapFlip[] = {};
/*声明软I2C初始化函数*/
void My_SoftwareI2C_Init(void);
/*声明软I2C写字节函数*/
int i2c_write_bytes(uint8_t addr,const uint8_t* pdata,uint16_t size);
/*声明OLED初始化函数*/
void My_OLEDScreen_Init(void);
int main(void)
{
/*初始化软I2C*/
My_SoftwareI2C_Init();
/*初始化OLED*/
My_OLEDScreen_Init();
//#8:绘制动图
while(1)
{
/*清屏*/
OLED_Clear(&oled)
/*设置光标*/
OLED_SetCursor(&oled,0,0);
/*绘图*/
OLED_DrawBitmap(&oled,128,64,bitmap);
/*将数据发送OLED*/
OLED_SendBuffer(&oled);
Delay(500);
/*清屏*/
OLED_Clear(&oled)
/*设置光标*/
OLED_SetCursor(&oled,0,0);
/*绘图*/
OLED_DrawBitmap(&oled,128,64,bitmap);
/*将数据发送OLED*/
OLED_SendBuffer(&oled);
Delay(500);
}
}
