前言

参考文章/博主

I2C总线介绍-举例AT24C02

从IIC实测波形入手,搞懂IIC通信

I2C时序讲解

仲裁机制

遵循3个机制:

  1. “线与”机制。多主机时,总线具有“线与”的逻辑功能,即只要有一个节点发送低电平时,总线上就表现为低电平
  2. SDA回读机制。总线被启动后,多个主机在每发送一个数据位时都要对自己的输出电平进行检测,只要检测的电平与自己发出的电平一致,就会继续占用总线
  3. 低电平优先机制。由于线与的存在,当多主机发送时,谁先发送低电平谁就会掌握对总线的控制权
  • 【1】

参考:I2C总线仲裁原理

I2C总线上可能在某一时刻有两个主控设备要同时向总线发送数据,这种情况叫做总线竞争。

I2C总线具有多主控能力,可以对发生在SDA线上的总线竞争进行仲裁,其仲裁原则是这样的: 假

主控器1 要发送的数据 DATA1为“101 ……”主控器2 要发送的数据 DATA2为“1001 ……” 总线被启动后两个主控器在每发送一个数据位时都要对自己的输出电平进行检测,只要检测的电平与自己发出的电平一致,他们就会继续占用总线。在这种情况下总线还是得不到仲裁。当 主控器1 发送第 3 位数据 “1” 时( 主控器2发送“0” ),由于 “线与” 的结果 SDA 上的电平为 “0” ,这样当 主控器1 检测自己的输出电平时,就会测到一个与自身不相符的 “0” 电平。这时 主控器1 只好放弃对总线的控制权;因此 主控器2 就成为总线的唯一主宰者。不难看出:

  1. 对于整个仲裁过程 主控器1主控器2 都不会丢失数据;
  2. 各个主控器没有对总线实施控制的优先级别,他们遵循 “低电平优先” 的原则,即谁先发送低电平谁就会掌握对总线的控制权
  • 【2】

参考:I2C总线的仲裁机制

在多主的通信系统中。总线上有多个节点,它们都有自己的寻址地址,可以作为从节点被别的节点访问,同时它们都可以作为主节点向其他的节点发送控制字节和传送数据。但是如果有两个或两个以上的节点都向总线上发送启动信号并开始传送数据,这样就形成了冲突。要解决这种冲突,就要进行仲裁的判决,这就是I2C总线上的仲裁

I2C总线上的仲裁分两部分: SCL线的同步和SDA线的仲裁

  1. SCL线的同步(时钟同步)

SCL 同步是由于总线具有 线“与” 的逻辑功能,即只要有一个节点发送低电平时,总线上就表现为低电平。当所有的节点都发送高电平时,总线才能表现为高电平。正是由于 线“与” 逻辑功能的原理,当多个节点同时发送时钟信号时,在总线上表现的是统一的时钟信号。这就是 SCL 的同步原理

  1. SDA仲裁

SDA 线的仲裁也是建立在总线具有 线“与” 逻辑功能的原理上的。节点在发送 1 位数据后,比较总线上所呈现的数据与自己发送的是否一致。是,继续发送;否则,退出竞争。 SDA 线的仲裁可以保证 I2C 总线系统在多个主节点同时企图控制总线时通信正常进行并且数据不丢失。总线系统通过仲裁只允许 一个 主节点可以继续占据总线

仲裁过程

DATA1DATA2 分别是主节点向总线所发送的数据信号, SDA 为总线上所呈现的数据信号, SCL 是总线上所呈现的时钟信号。当 主节点1、2 同时发送起始信号时,两个主节点都发送了高电平信号。这时总线上呈现的信号为高电平,两个主节点都检测到总线上的信号与自己发送的信号相同,继续发送数据。第 2 个时钟周期, 2 个主节点都发送低电平信号,在总线上呈现的信号为低电平,仍继续发送数据。在第 3 个时钟周期, 主节点1 发送高电平信号,而 主节点2 发送低电平信号。根据总线的 线“与” 的逻辑功能,总线上的信号为低电平,这时 主节点1 检测到总线上的数据和自己所发送的数据不一样,就断开数据的输出级,转为从机接收状态。这样 主节点2 就赢得了总线,而且数据没有丢失,即总线的数据与 主节点2 所发送的数据一样,而 主节点1 在转为从节点后继续接收数据,同样也没有丢掉 SDA 线上的数据。因此在仲裁过程中数据没有丢失

总结SDA仲裁和SCL时钟同步处理过程没有先后关系,而是同时进行的

基础知识

IC 总线最多可以挂多少个设备由 IIC 地址决定, 8 位地址 0x00 不用,那就是 127 个地址,所以理论上可以挂 127 个从器件,但是, IIC 协议没有规定总线上 device 最大数目,但是规定了总线电容不能超过 400pF 。管脚都是有输入电容的, PCB 上也会有寄生电容,所以会有一个限制。实际设计中经验值大概是不超过 8 个器件总线

I2CI^2C由数据线 SDA 和时钟线 SCL 构成的串行总线,可发送和接收数据,两条线必须通过 上拉电路连接至正电源 ,数据传输只能 在总线不忙时启动,所以先发送一个设备地址,选中这个设备,设备地址 最后一位 代表了是写还是读。选中设备后,再发送寄存器地址,代表选中某个寄存器,再开始传输数据

IC总线在传送数据过程中共有三种类型信号: 开始信号结束信号应答信号

起始信号是必需的,结束信号和应答信号,都可以不要

I2C协议规定设备地址可以是 7 位或 10 位,实际中7位的地址应用比较广泛,紧跟设备地址的一个数据位用来表示数据传输方向,第 8 位或第 11

位设备地址 = 7位从机地址+ 读/写地址

再给地址添加一个方向位位用来表示接下来数据传输的方向, 0 表示主设备向从设备(write)写数据, 1 表示主设备向从设备(read)读数据

I2CI^2C 通信分为 低速模式 100kbit/s 、快速模式 400kbit/s 和高速模式3.4Mbit/s 。因为所有的 I2CI^2C 器件都支持低速,但却未必支持另外两种速度,所以作为通用的I2CI^2C程序我们选择 100k 这个速率来实现,也就是说实际程序产生的时序必须 小于等于100k 的时序参数,也就是 高低电平的保持时间需要大于等于4.7us(一般我们取5us)

发送数据是 先高再低

IIC信号在数据传输过程中,当 SCL=1 高电平时,数据线 SDA 必须保持稳定状态,不允许有电平跳变,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。 SCL=1 时 数据线 SDA 的任何电平变换会看做是总线的起始信号或者停止信号

空闲状态:当总线上的 SDASCL 两条信号线同时处于高电平,便是 空闲 状态,当我们不传输数据时, SDASCL 被上拉电阻拉高,即进入 空闲 状态

IIC的两个引脚一般设置为 开漏输出

上拉电阻的作用:确保总线空闲时为高电平

SCL为高电平期间SDA 表示的数据有效,此时 SDA 的电平要稳定, SDA 为高电平时表示数据 “1”,为低电平时表示数据 “0”

SCL为低电平期间SDA 的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备

若主从机在传输数据期间,需要完成其它功能(例如一个中断),可以主动拉低 SCL ,使I2C进入等待状态,直到处理结束再释放 SCL,数据传输会继续

关于IIC总线上的毛刺?

毛刺出现的原因,就在于IIC时钟传输的第九位:

主机发送了八个字节以后,需要放弃IIC控制权,将控制权交给从机,需要从机响应应答位;

主机放弃控制权的过程:

  1. 释放SDA
  2. 由于SDA存在上拉电阻,此时SDA被拉高
  3. 这是从机响应了第九个时钟,开始获取SDA控制,将SDA拉低

以上只是一瞬间,表现为SDA会出现毛刺,且是周期性的

以上,就是IIC会周期性出现毛刺的原因,当然,这个毛刺是可以解决的,使用 模拟IIC 的办法

模拟IIC详解

模拟IIC的话优点是通用,移植方便,硬件IIC的话效率高,但是不通用

模拟IIC只需要2个IO口即可

初始化状态

SCL 和SDA都保持高电平

cpp
//IIC总线 PB6:SCL  PB7:SDA
void IIC_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitStructure.GPIO_Mode =GPIO_Mode_Out_OD; //配置为开漏输出
	GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6|GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7);	// 初始状态都为高电平
}

开始/停止信号

起始信号 :SCL 线为高电平期间,SDA 线由 高电平向低电平的变化

停止信号 :SCL线为高电平期间,SDA 线由 低电平向高电平的变化

起始和终止信号都是由主机发出的,在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线

cpp
//产生IIC开始信号
void IIC_Start(void)
{
	IIC_SCL=1; 
	IIC_SDA=1; 
	delay_us(5); 
	IIC_SDA=0; 
	delay_us(5); 
	IIC_SCL=0; 
}

在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态

cpp
void IIC_Stop(void)
{
	IIC_SCL=0;
	IIC_SDA=0;
	delay_us(2);
	IIC_SCL=1;
	delay_us(5);
	IIC_SDA=1;
	delay_us(5);
}

发送应答/非应答

应答信号:应答出现在每一次接收数据的 IC 完成 8 个数据位传输后紧跟着的时钟周期,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据(当接收方接收该字节成功,便会输出一个 ACK 应答信号)。 CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号, CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障( 低电平0表示应答,1表示非应答)

PS

当主机为接收方时,收到最后一个字节后,主机可以不发送 ACK ,直接发送停止信号来结束传输

当从机为接收方时,没有发送 ACK,则表示从机可能在忙其它事、或者不匹配地址信号和不支持多主机发送,主机可以发送停止信号,再次发送起始信号启动新的传输

应答信号有应答(ACK)和不应答(NACK)两种。按方向又可以分成 单片机对设备芯片应答(或不应答)设备芯片对单片机应答(或不应答)两种情况(但是设备芯片对单片机的应答信号是芯片本身主动发送的,不需要我们编写代码)

cpp
//单片机对设备芯片的应答ACK信号:可以理解为单片机向从设备发送一位值为0的数据
void IIC_ACK(void)
{
	IIC_SCL=0;
	IIC_SDA=0; //在SCL为0时准备好数据
	delay_us(5);
	IIC_SCL=1; //SCL的上升沿发送一位数据
	delay_us(5);//在SCL高电平期间保持不变
	IIC_SCL=0;
    delay_us(2);
    IIC_SDA=1;	// 应答完一定要释放SDA总线
    delay_us(2);
}
 
//单片机对设备芯片的非应答NADCK信号:可以理解为单片机向从设备发送一位值为1的数据
void IIC_NACK(void)
{
	IIC_SCL=0;
	IIC_SDA=1;  //在SCL为0时准备好数据
	delay_us(2);
	IIC_SCL=1;  //SCL的上升沿发送一位数据
	delay_us(5); //在SCL高电平期间保持不变
	IIC_SCL=0;
    delay_us(5);
}

等待应答

先让SDA=1,再判断在一定时间内SDA是否变为0,从而识别出外设有没有发送应答信号

cpp
//单片机等待接收设备芯片的应答:相当于从设备向单片机发送一位数据
u8 IIC_WaitACK(void)
{
    uint8_t ucErrTimer = 0;
// 此处是为了让单片机处于高阻态,以便于单片机能读取SDA线上的电平。但是会发现不设置成高阻态程序也能正常运行,原因是即使
// IIC_SDA=0时单片机不能读取到外部电平,此时单片机被MOS管拉低,读到的值会总是为0,单片机会误认为是从机发送的应答信号,因此程序也会正常运行。    
	IIC_SCL=0;  
	IIC_SDA=1;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2); //以上是为了提供给从机一个上升沿时钟信号,从机就会主动发送一位数据
    while (READ_SDA)	// 判断从机发送的是不应答还是应答信号
    {
        ucErrTimer++;
        if (ucErrTimer > 250)
        {
            IIC_Stop(); 
			return 1;
        }
    }
	IIC_SCL=0;
	return 0; //如果从机发送过来的是应答信号就继续传输数据
}

发送一个字节

IIC协议要求数据传输是以字节为单位的(一次传输8位)。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(8位数据位加1位应答位称为一帧)

一开始为什么把IIC_SCL=0; ?

因为主要IIC_SCL上升沿时准备好数据就是稳定有效数据

cpp
//发送一个字节数据:相当于把8位数据一位一位发送过去
//此函数被用来发送设备芯片地址、数据在设备芯片中的存储地址、以及要发送的8位数据
void IIC_SendByte(u8 txd)
{
    u8 i;
    IIC_SCL = 0;
    for(i = 0; i < 8; i++) //先传送高位,再传送低位
    {
        // 判断最高位
        if(txd & 0x80)
        {
            IIC_SDA = 1;
        }
        else
        {
            IIC_SDA = 0;
        }
        // 将最高位丢弃,次高位变成最高位
        txd <<= 1;
        delay_us(2);
        IIC_SCL = 1;	// 为了SDA线上的数据必须保持稳定
        delay_us(2);
        IIC_SCL = 0;	// 为了上升沿时准备好数据就是稳定有效数据
        delay_us(2);
    }
}

读取一个字节

cpp
//读取一个字节数据:从设备会在主设备每发送一个时钟上升沿时向主设备发送一位数据
u8 IIC_ReadByte(void)
{
	u8 i;
	u8 receive=0;
	for(i=0;i<8;i++)
	{
		IIC_SCL=0;	// 先拉低SCL,延时后拉高就是为了读取有效的数据
		delay_us(2);
		IIC_SCL=1;
		receive<<=1;
		if(READ_SDA)
        {
            receive++;
        }
	}		
	return receive;
}

硬件IIC详解

硬件IIC

一般硬件IIC的话,SDASCL 都配置成复用开漏输出,输出信号源于I2C外设

复用功能模式中,输出使能,输出速度可配置,可工作在开漏模式, 但是输出信号源于其它外设(来自I2C外设),输出数据寄存器 GPIOx_ODR 无效;输入可用,可以通过输入数据寄存器可获取 I/O 实际状态,但一般直接用外设的寄存器来获取该数据信号

基于STM32F407讲解

STM32F407VET6有 3 个硬件IIC

  • I2C外设功能框图

I2C1 是挂载在 APB1 总线下的,所以时钟是 42MHz

  • 数据控制逻辑

当向外发送数据的时候,数据移位寄存器以 “数据寄存器” 为数据源,把数据一位一位地通过 SDA 信号线发送出去

当从外部接收数据的时候,数据移位寄存器把 SDA 信号线采样到的数据一位一位地存储到 “数据寄存器”

编程示例1

介绍:基于STM32F103ZET6,采用模拟IIC,数字温湿度传感器采用 SHT3x(特点:典型精度2%RH(湿度)和0.3°C(温度);

非常快速启动和测量时间小;8针DFN封装)

  • 硬件连接

STM32IO 外设
PG11 SCL
PG12 SDA
  • 芯片手册

SHT30-DIS 手册里这里很重要,这个地址取决于硬件电路设计,上图中ADDR接地的,所以这个传感器器件地址是 0x44

通信部分:

所有 SHT3x-DIS 命令和数据都映射到一个 16-位地址空间。 此外,数据和命令是受 CRC 校验和保护

单次测量读取温湿度时序;Repeatability(重复性)越大采集速度越慢

周期性测量读取温湿度时序

复位命令

加热命令

寄存器状态查询

读取的数值计算

  • MX配置

  • 程序编写

初始化.c

Myinit.c
cpp
#include "AllHead.h"

void vHardware_Init(void)
{
    // 打开定时器6
    Timer6.Timer6_Start_IT();
    // 周期性测量获取SHT30的温度
    SHT30.Measure_Period_Mode();
}
I2C.h
cpp
#ifndef __I2C_H
#define __I2C_H

#include "AllHead.h"

//宏定义
//定义枚举类型
typedef enum
{
	ACK	 = GPIO_PIN_RESET,
	NACK = GPIO_PIN_SET,
}ACK_Value_t;

//定义结构体类型
typedef struct
{
	void (*Init)(void);  //I2C初始化
	void (*Start)(void); //I2C起始信号
	void (*Stop)(void);  //I2C停止信号
	ACK_Value_t (*Write_Byte)(uint8_t);      //I2C写字节
	uint8_t     (*Read_Byte) (ACK_Value_t);  //I2C读字节
}I2C_Soft_t;


extern I2C_Soft_t  I2C_Soft;

#endif
I2C.c
cpp
#include "AllHead.h"

//置位与清零SCL管脚
#define	SET_SCL	HAL_GPIO_WritePin(SHT30_SCL_GPIO_Port,SHT30_SCL_Pin,GPIO_PIN_SET) 
#define	CLR_SCL	HAL_GPIO_WritePin(SHT30_SCL_GPIO_Port,SHT30_SCL_Pin,GPIO_PIN_RESET)
//置位与清零SDA管脚
#define	SET_SDA	HAL_GPIO_WritePin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin,GPIO_PIN_SET)
#define	CLR_SDA	HAL_GPIO_WritePin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin,GPIO_PIN_RESET)
//读SDA管脚状态
#define READ_SDA	HAL_GPIO_ReadPin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin)

void Init(void);  //I2C初始化
void Start(void); //I2C起始信号
void Stop(void);  //I2C停止信号
ACK_Value_t Write_Byte(uint8_t);      //I2C写字节
uint8_t Read_Byte (ACK_Value_t);  //I2C读字节
static void I2C_Delay_us(uint8_t);


I2C_Soft_t I2C_Soft = 
{
	Init,
	Start,
	Stop,
	Write_Byte,
	Read_Byte
};

/*
	* @name   Init
	* @brief  I2C初始化
	* @param  None
	* @retval None
*/
static void Init(void)
{
    SET_SCL;
    SET_SDA;
}

/*
	* @name   Start
	* @brief  I2C起始信号
	* @param  None
	* @retval None
*/
static void Start(void)
{
    //SCL为高电平,SDA的下降沿为I2C起始信号
    SET_SDA;
    SET_SCL;
    I2C_Delay_us(1);

    CLR_SDA;
    I2C_Delay_us(10);

    CLR_SCL;
    I2C_Delay_us(1);
}

/*
	* @name   Stop
	* @brief  I2C停止信号
	* @param  None
	* @retval None
*/
static void Stop(void)
{
    //SCL为高电平,SDA的上升沿为I2C停止信号
    CLR_SDA;
    SET_SCL;
    I2C_Delay_us(1);

    I2C_Delay_us(10);
    SET_SDA;
}

/*
	* @name   Write_Byte
	* @brief  I2C写字节
	* @param  WR_Byte -> 待写入数据
	* @retval ACK_Value_t -> 从机应答值
*/
static ACK_Value_t Write_Byte(uint8_t WR_Byte)
{
    uint8_t i;
    // 存储读取的SDA电平状态
    ACK_Value_t  ACK_Rspond;

    //SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
    //数据按8位传输,高位在前,利用for循环逐个接收
    for(i = 0; i < 8; i++)
    {
        //SCL清零,主机SDA准备数据
        CLR_SCL;
        I2C_Delay_us(1);
        if(WR_Byte & 0x80)
        {
            SET_SDA;
        }
        else
        {
            CLR_SDA;
        }
        I2C_Delay_us(1);
        //SCL置高,传输数据
        SET_SCL;
        I2C_Delay_us(10);

        //准备发送下一比特位
        WR_Byte <<= 1;
    }

    CLR_SCL;
    //释放SDA,等待从机应答
    SET_SDA;
    I2C_Delay_us(1);

    SET_SCL;
    I2C_Delay_us(10);

    ACK_Rspond = (ACK_Value_t)READ_SDA;

    CLR_SCL;
    I2C_Delay_us(1);

    //返回从机的应答信号
    return ACK_Rspond;
}

/*
	* @name   Read_Byte
	* @brief  I2C读字节
	* @param  ACK_Value -> 主机回应值
	* @retval 从机返回值
*/
static uint8_t Read_Byte(ACK_Value_t ACK_Value)
{
    uint8_t RD_Byte = 0, i;

    ////接收数据
    //SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
    //数据按8位传输,高位在前,利用for循环逐个接收
    for(i = 0; i < 8; i++)
    {
        //准备接收下一比特位
        RD_Byte <<= 1;

        //SCL清零,从机SDA准备数据
        CLR_SCL;
        I2C_Delay_us(10);

        //SCL置高,获取数据
        SET_SCL;
        I2C_Delay_us(10);

        RD_Byte |= READ_SDA;
    }


    //SCL清零,主机准备应答信号
    CLR_SCL;
    I2C_Delay_us(1);

    //主机发送应答信号
    if(ACK_Value == ACK)
    {
        CLR_SDA;
    }
    else
    {
        SET_SDA;
    }
    I2C_Delay_us(1);


    SET_SCL;
    I2C_Delay_us(10);

    //Note:
    //释放SDA数据线
    //SCL先清零,再释放SDA,防止连续传输数据时,从机错将SDA释放信号当成NACk信号
    CLR_SCL;
    SET_SDA;
    I2C_Delay_us(1);

    //返回数据
    return RD_Byte;
}

/*
	* @name   I2C_Delay
	* @brief  I2C延时
	* @param  None
	* @retval None
*/
static void I2C_Delay_us(uint8_t us)
{
    uint8_t i = 0;

    //通过示波器测量进行校准
    while(us--)
    {
        for(i = 0; i < 7; i++);
    }
}
Timer6.h
cpp
#ifndef __Timer6_H
#define __Timer6_H

#include "AllHead.h"

//定义枚举类型
typedef enum
{
	TIMER6_10mS  	= (uint16_t)2,
	TIMER6_50mS  	= (uint16_t)10,
	TIMER6_100mS	= (uint16_t)20,
	TIMER6_200mS	= (uint16_t)40,
	TIMER6_500mS	= (uint16_t)100,
	TIMER6_1S     = (uint16_t)200,
	TIMER6_2S     = (uint16_t)400,
	TIMER6_3S     = (uint16_t)600,
	TIMER6_5S     = (uint16_t)1000,
	TIMER6_10S    = (uint16_t)2000,
	TIMER6_3min   = (uint16_t)36000,
}TIMER6_Value_t;

//定义结构体类型
typedef struct
{
	uint16_t volatile usDelay_Timer;    //延时定时器
	uint16_t volatile SHT30_Measure_Timeout;	// 温湿度延时时间
	
	void (*Timer6_Start_IT)(void);      //定时器6以中断模式启动
} Timer6_t;

extern Timer6_t  Timer6;

#endif
Timer6.c
cpp
#include "AllHead.h"
    
static void Timer6_Start_IT(void);  //定时器6以中断模式启动
	
Timer6_t  Timer6 = 
{
	0,
	0,
	
	Timer6_Start_IT     
};

/*
	* @name   Timer6_Start_IT
	* @brief  定时器6以中断模式启动
	* @param  None
	* @retval None      
*/
static void Timer6_Start_IT(void)
{
	HAL_TIM_Base_Start_IT(&htim6); //启动定时器6
}

手册里有写 "0"表示写位

SHT30.h
cpp
#ifndef __SHT30_H
#define __SHT30_H

#include "AllHead.h"

//宏定义
#define SHT30_ADDR  (uint8_t)(0x44 << 1) //传感器地址因为最后一位是读写位所以需要左移一位
//#define SHT30_ADDR  (unsigned char)(0x45 << 1) //传感器地址

// 读和写
#define	Write_CMD   0xFE
#define	Read_CMD    0x01

//定义枚举类型

//定义结构体类型

typedef struct
{
    float   fTemperature;  //温度 -40至125℃    精度0.1℃
    uint8_t ucHumidity;    //湿度 0%RH至100%RH  精度1%RH

    void (*Measure_Period_Mode)(void);  //周期测量模式
} SHT30_t;

extern SHT30_t  SHT30;
#endif

周期性启动时序对应:

然后开始读,判断返回值从机的应答信号,直到回应ACK才退出循环或者超时

在通过公式计算温湿度值时,因为 2^16^ - 1是65535,175/65535 = 0.002670328…,结果除不尽,但浮点型float的精度为6~7位有效数字,直接计算的话相当于把小数点后7位之后的数据省略掉了,会造成计算出来的温湿度精度有些损失,所以有了这样的方法:先让公式的数值都*100,这样的话计算出来的温湿度值也会 *100,但这样计算的小数位就会多两位,精度较为准确,在赋给最后结果变量前再 *0.01,将结果变回正常值即可

SHT30.c
cpp
#include "AllHead.h"


void Measure_Period_Mode(void);  //周期测量模式
static uint8_t CRC_8(uint8_t *, uint8_t);

SHT30_t SHT30 =
{
    0.0,
    0,
    Measure_Period_Mode
};


/*
	* @name   Measure_Period_Mode
	* @brief  周期测量模式
	* @param  None
	* @retval None
*/
static void Measure_Period_Mode(void)
{
    uint16_t  Measure_Timeout = 0;

    uint8_t   temp_array[6] = {0};
    uint16_t  temp_uint     = 0;
    float     temp_float    = 0;

    //启动周期性测量
    I2C_Soft.Start();
    // 写操作
    I2C_Soft.Write_Byte(SHT30_ADDR & Write_CMD);
    I2C_Soft.Write_Byte(0x27); //High repeat , mps = 10
    I2C_Soft.Write_Byte(0x37);

    Timer6.SHT30_Measure_Timeout = 0;
    //发送接收数据命令
    do
    {
        if(Timer6.SHT30_Measure_Timeout >= TIMER6_2S) //2s内没获取到数据,退出等待
            break;

        I2C_Soft.Start();
        I2C_Soft.Write_Byte(SHT30_ADDR & Write_CMD);
        //0xE000是向SHT30取数据的指令,主机发送该指令后开始读取SHT30的温湿度数据
        I2C_Soft.Write_Byte(0xE0);
        I2C_Soft.Write_Byte(0x00);
		//重新发送起始信号,往SHT30发送地址加读取数据指令
        I2C_Soft.Start();
    }
    while(I2C_Soft.Write_Byte(SHT30_ADDR | Read_CMD) == NACK);

    //开始接收测量数据,并计算
    if(Timer6.SHT30_Measure_Timeout < TIMER6_2S)
    {
        temp_array[0] = I2C_Soft.Read_Byte(ACK);
        temp_array[1] = I2C_Soft.Read_Byte(ACK);
        temp_array[2] = I2C_Soft.Read_Byte(ACK);
        temp_array[3] = I2C_Soft.Read_Byte(ACK);
        temp_array[4] = I2C_Soft.Read_Byte(ACK);
        temp_array[5] = I2C_Soft.Read_Byte(NACK);
        I2C_Soft.Stop();

        //////计算温度,精度0.1
        if(CRC_8(temp_array, 2) == temp_array[2]) //CRC-8 校验
        {
            //取出16位的温度值
            temp_uint         = temp_array[0] * 256 + temp_array[1];
            //根据手册公式计算,为了精度,计算数值先*100
            temp_float        = ((float)temp_uint) * 0.267032 - 4500;
            //再除以100,得到正常温度值
            SHT30.fTemperature = temp_float * 0.01;
        }

        //////计算湿度,精度1%RH
        if(CRC_8(&temp_array[3], 2) == temp_array[5]) //CRC-8 校验
        {
            //取出16位的湿度值
            temp_uint      = temp_array[3] * 256 + temp_array[4];
            //根据手册公式计算
            temp_float     = ((float)temp_uint) * 0.152590;
            temp_float     = temp_float * 0.01;
            //除以100,得到正常湿度值
            SHT30.ucHumidity = (unsigned char)temp_float;
        }
    }
}

/*
	* @name   CRC_8
	* @brief  CRC-8校验
	* @param  Crc_ptr -> 校验数据首地址
						LEN     -> 校验数据长度
	* @retval CRC_Value -> 校验值
*/
static uint8_t CRC_8(uint8_t *Crc_ptr, uint8_t LEN)
{
    uint8_t CRC_Value = 0xFF;
    uint8_t i = 0, j = 0;

    for(i = 0; i < LEN; i++)
    {
        CRC_Value ^= *(Crc_ptr + i);
        for(j = 0; j < 8; j++)
        {
            if(CRC_Value & 0x80)
                CRC_Value = (CRC_Value << 1) ^ 0x31;
            else
                CRC_Value = (CRC_Value << 1);
        }
    }
    return CRC_Value;
}

编程示例2

介绍:基于MSP430F149,采用模拟IIC,OLED模块(0.96寸)采用 SSD1306 驱动芯片,像素是 128x64

注意这个的显示字串符有问题故不要参考显示部分

  • 硬件连接

  • 驱动手册阅读

一个控制字节主要由 Co 和 D/C# 位组成,后面跟着 六个“0”;如果 Co 位设置为 逻辑“0”,则以下信息的传输将包含
只有 数据字节;D/C# 位决定下一个数据字节是作为命令还是作为数据。 如果 D/C# 位是设置为 逻辑“0”,它将以下数据字节定义为 命令。 如果 D/C# 位设置为 逻辑“1”,它定义了后面的数据字节作为 将存储在 GDDRAM 中的数据。GDDRAM 列地址指针会自动加一数据写入。

写数据时,发送0x78从机地址,Co置0,D/C# 置1,发送0x40

要是写多个控制命令的话,Co置1,D/C# 置0,然后就可以写入命令,写完如果想写数据则变成0x40

有3种不同的内存寻址方式:页面寻址方式、水平寻址方式和垂直寻址方式

  • 命令

见手册 COMMAND TABLE

  • 取模,汉字的话设置为 16x16,ASCII码的话也是

95个ASCII码(第一个是空格)

cpp
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
  • 程序编写
cpp
/*
* @function     : Sys_Init
* @param        : None
* @retval       : None
* @brief        : 系统初始化
*/
static void vSys_Init(void)
{
  WatchDog.vWatchDog_Init();    // 看门狗初始化
  Hardware_Init.vCLK_Init();    // 时钟初始化
  Hardware_Init.vGPIO_Init();   // GPIO初始化
  TimerA.vTimerA_Init();
  Hardware_Init.vIE_Init();     // 中断初始化
  USART1.vUSART1_Init();        // 串口1初始化
  Pwm.vPWM_Init();      // PWM初始化
  ADC.vADC_Init();      // ADC初始化
  OLED.vOLED_Init();                //OLED初始化
  OLED.vOLED_Clear();               //OLED清屏
  
  //OLED屏幕初始显示
  OLED.vOLED_Show_CHN(0,8,"太");
  OLED.vOLED_Show_CHN(0,32,"阳");
  OLED.vOLED_Show_CHN(0,56,"能");
  OLED.vOLED_Show_CHN(0,80,"路");
  OLED.vOLED_Show_CHN(0,104,"灯");
  
  OLED.vOLED_Show_String(3,24,"VIN:",ASCII_SIZE_16);
  OLED.vOLED_Show_String(5,24,"BAT:",ASCII_SIZE_16);  
  
  USART1.vUSART1_SendString("系统初始化完成\r\n");
  printf("PI = %.1f\r\n",3.14);
}

/*
* @function     : vRun
* @param        : None
* @retval       : None
* @brief        : 系统运行
*/
static void vRun(void)
{
  uint16_t Temp_uint  = 0;
  
  //采集电池与太阳能板电压
  ADC.vADC_Get_BAT_Voltage();
  ADC.vADC_Get_VIN_Voltage();
   
  //OLED显示太阳能板电压
  Temp_uint = (uint16_t)(ADC.fVIN_VOltage * 10);
  OLED.vOLED_Show_Char(3,64,Temp_uint / 100 + '0',ASCII_SIZE_16);
  OLED.vOLED_Show_Char(3,72,Temp_uint % 100 / 10 + '0',ASCII_SIZE_16);
  OLED.vOLED_Show_Char(3,80,'.',ASCII_SIZE_16);
  OLED.vOLED_Show_Char(3,88,Temp_uint % 10 + '0',ASCII_SIZE_16);
  OLED.vOLED_Show_Char(3,96,'V',ASCII_SIZE_16);
  
  //OLED显示电池电压
  Temp_uint = (uint16_t)(ADC.fBAT_Voltage * 10);
  OLED.vOLED_Show_Char(5,72,Temp_uint / 10 + '0',ASCII_SIZE_16);
  OLED.vOLED_Show_Char(5,80,'.',ASCII_SIZE_16);
  OLED.vOLED_Show_Char(5,88,Temp_uint % 10 + '0',ASCII_SIZE_16);
  OLED.vOLED_Show_Char(5,96,'V',ASCII_SIZE_16);
  
  //间隔500ms采集一次
  Public.vDelay_ms(500); 
}
oled.h
cpp
#ifndef __OLED_H
#define __OLED_H
#include <main.h>

// OLED的IIC地址(SA0 = 0) --- 0111 1000
#define OLED_ADDR       0x78
// OLED参数(宽度,高度,页数量)
#define OLED_WIDTH      128
#define OLED_HEIGHT     64
#define OLED_PAGE_MAX   8
// IIC相关 --- SCL
#define SET_SCL P5OUT |= BIT6   // SCL输出高电平
#define CLR_SCL P5OUT &= (~BIT6)        // SCL输出低电平
// IIC相关 --- SDA
#define SDA_IN          P5DIR &= (~BIT5)        // SDA设为输入
#define READ_SDA        ((P5IN >> 5) & BIT0)    // 读取SDA输入
#define SDA_OUT         P5DIR |= BIT5   // SDA设为输出
#define SET_SDA         P5OUT |= BIT5   // SDA输出高电平
#define CLR_SDA         P5OUT &= (~BIT5)        // SDA输出低电平
  

typedef enum
{
  ASCII_SIZE_8 = 8,
  ASCII_SIZE_16 = 16,
  ASCII_SIZE_24 = 24,
}ASCII_Size_t;

typedef struct
{
  void (*vOLED_Init)(void);     // OLED初始化
  void (*vOLED_Clear)(void);    // 清屏
  void (*vOLED_Show_Char)(uint8_t, uint8_t, uint8_t, ASCII_Size_t);        // OLED显示字符
  void (*vOLED_Show_String)(uint8_t, uint8_t, const char*, ASCII_Size_t);       // OLED显示字符串
  void (*vOLED_Show_CHN)(uint8_t, uint8_t, const char*);        // OLED显示汉字
}OLED_t;

extern OLED_t OLED;

#endif
oled.c
cpp
/***************************************************************************
 * File          : oled.c
 * Author        : Luckys.
 * Date          : 2023-06-08
 * description   : OLED
****************************************************************************/


#include <main.h>
#include "oled_font.h"  // 哪里需要调用再调用

/*====================================static function declaration area BEGIN====================================*/
static void vOLED_Init(void);     // OLED初始化
static void vOLED_Clear(void);    // 清屏
static void vOLED_Show_Char(uint8_t, uint8_t, uint8_t, ASCII_Size_t);        // OLED显示字符
static void vOLED_Show_String(uint8_t, uint8_t, const char*, ASCII_Size_t);       // OLED显示字符串
static void vOLED_Show_CHN(uint8_t, uint8_t, const char*);        // OLED显示汉字
static void vOLED_Set_Pos(uint8_t, uint8_t);   // 设置位置

static void vIIC_Start(void);    // IIC启动
static void vIIC_Stop(void);    // IIC停止
static uint8_t ucIIC_Write_Byte(uint8_t);    // IIC写入字节
static void vOLED_Write_CMD(uint8_t);    // OLED写命令
static void vOLED_Write_Data(uint8_t);    // OLED写数据
/*====================================static function declaration area   END====================================*/


OLED_t OLED = 
{
  vOLED_Init,
  vOLED_Clear,
  vOLED_Show_Char,
  vOLED_Show_String,
  vOLED_Show_CHN,
};


/*
* @function     : vOLED_Init
* @param        : None
* @retval       : None
* @brief        : OLED初始化
*/
static void vOLED_Init(void)
{
  	Public.vDelay_ms(100);        // 上电延时
    vOLED_Write_CMD(0xAE); // OLED休眠
    vOLED_Write_CMD(0x00); // 设置低列地址
    vOLED_Write_CMD(0x10); // 设置高列地址
    vOLED_Write_CMD(0x40); // 设置起始地址线
    vOLED_Write_CMD(0xB0); // set page address
    vOLED_Write_CMD(0x81); // 设置对比度
    vOLED_Write_CMD(0xFF); //--128
    vOLED_Write_CMD(0xA1); // 0xa0左右反置 0xa1正常
    vOLED_Write_CMD(0xA6); // normal / reverse
    vOLED_Write_CMD(0xA8); // 设置多路复用(1 to 64)
    vOLED_Write_CMD(0x3F); // 1/32 duty
    vOLED_Write_CMD(0xC8); // Com scan direction
    vOLED_Write_CMD(0xD3); // 设置显示的偏移映射内存计数器
    vOLED_Write_CMD(0x00); //

    vOLED_Write_CMD(0xD5); // 设置显示时钟分频比/振荡器频率
    vOLED_Write_CMD(0x80); // 设置分频比例,时钟设置为100帧/秒

    vOLED_Write_CMD(0xD8); // set area color mode off
    vOLED_Write_CMD(0x05); //

    vOLED_Write_CMD(0xD9); // 预充电时间
    vOLED_Write_CMD(0xF1); // 预充电为15个脉冲,释放为1个脉冲

    vOLED_Write_CMD(0xDA); // 引脚设置硬件配置
    vOLED_Write_CMD(0x12); //

    vOLED_Write_CMD(0xDB); // 设置VCOM电平
    vOLED_Write_CMD(0x30); //
    // 唤醒
    vOLED_Write_CMD(0x8D); // 设置电荷泵
    vOLED_Write_CMD(0x14); // 开启电荷泵

    vOLED_Write_CMD(0xAF); // OLED唤醒(AE是OLED休眠)
}

/*
* @function     : vOLED_Clear
* @param        : None
* @retval       : None
* @brief        : 清屏
*/
static void vOLED_Clear(void)
{
  uint8_t Page,Seg;
  
  for (Page = 0; Page < 8; Page++)
  {
    vOLED_Write_CMD(0xB0 + Page);       // 一共8页(行)
    vOLED_Write_CMD(0x00);      // 低
    vOLED_Write_CMD(0x01);      // 高
    
    for (Seg = 0; Seg < 128; Seg++)
    {
      vOLED_Write_Data(0x00);
    }
  }
}

/*
* @function     : vOLED_Show_Char
* @param        : Page -> 页位置 Seg -> 段位置
* @retval       : None
* @brief        : OLED显示字符
*/
static void vOLED_Set_Pos(uint8_t Page, uint8_t Seg)
{
  vOLED_Write_CMD(0xB0 + Page);
  vOLED_Write_CMD(Seg & 0x0F);  // 低4位
  vOLED_Write_CMD((Seg & 0xF0 >> 4) | 0x10);  // 高4位
}

/*
* @function     : vOLED_Show_Char
* @param        : Page -> 页位置 Seg -> 段位置 ch -> 要显示的字符 ch_size -> 字体大小
* @retval       : None
* @brief        : OLED显示字符
*/
static void vOLED_Show_Char(uint8_t Page, uint8_t Seg, uint8_t ch, ASCII_Size_t ch_size)
{
  uint8_t ucIndex,i;
  
  // ASCII 字符集数组索引,需要减去偏移量(' ' -> 空格对应的码值)
  ucIndex = ch - ' ';
  // 判断大小
  if (ASCII_SIZE_16 == ch_size)
  {
    // 设置字符上半部分
    vOLED_Set_Pos(Page,Seg);    
    // 写入字符上半部分数据
    for (i = 0; i < 8; i++)
    {
      vOLED_Write_Data(ucASCII_16x8[ucIndex][i]);
    }
    // 设置字符下半部分
    vOLED_Set_Pos(Page + 1,Seg);
    // 写入字符下半部分数据
    for (i = 0; i < 8; i++)
    {
      vOLED_Write_Data(ucASCII_16x8[ucIndex][i + 8]);
    }
  }
}

/*
* @function     : vOLED_Show_String
* @param        : Page -> 页位置 Seg -> 段位置 p_Str -> 要显示的字符串 ch_size -> 字体大小
* @retval       : None
* @brief        : OLED显示字符串
*/
static void vOLED_Show_String(uint8_t Page, uint8_t Seg, const char* p_Str, ASCII_Size_t ch_size)
{
  // 字符尺寸高度为 1-8时占1Page  9-16时占2Page 17-24时占3Page ....以此类推
  // 那怎么算出对应的Page是1,2,3.... 那直接加7除以8即可
  uint8_t ch_Pages = (ch_size + 7) / 8; // 字符占用的页数
  
  while(*p_Str != '\0')
  {
    // 自动换行
    if ((Seg + ch_size / 2) > OLED_WIDTH)       // 如果 位置+字符宽度(16/2=8) 大于 屏幕宽度
    {
      Seg = 0;
      Page += ch_Pages;
      if (Page >= OLED_PAGE_MAX)
      {
        Page = 0;
      }
    }
    // 自动换页
    if ((Page + ch_Pages) > OLED_PAGE_MAX)
    {
      Seg = 0;
      Page = 0;
    }
    // 显示字符
    vOLED_Show_Char(Page,Seg,*p_Str,ch_size);
    // 更新字符
    p_Str++;
    // 更新显示位置
    Seg += ch_size / 2;
  }
}

/*
* @function     : vOLED_Show_CHN
* @param        : Page -> 页位置 Seg -> 段位置 p_Str -> 要显示的字符串
* @retval       : None
* @brief        : OLED显示汉字
*/
static void vOLED_Show_CHN(uint8_t Page, uint8_t Seg, const char* p_Str)
{
  uint16_t usCHN_Number;  // 字库中汉字数量
  uint16_t usIndex;     // 字库中的汉字索引
  uint8_t i;
  
  // 统计汉字的位置
  usCHN_Number = sizeof(CHN_16x16) / sizeof(CHN_16x16_t);
  // 查找汉字的位置
  for (usIndex = 0; usIndex < usCHN_Number; usIndex++)
  {
    if ((CHN_16x16[usIndex].Index[0] == *p_Str) && (CHN_16x16[usIndex].Index[1] == *(p_Str + 1)))       // 因为一个汉字占两个字节
    {
      // 设置字符上半部分起始位置
      vOLED_Set_Pos(Page,Seg);   
      // 写入上半部分数据
      for (i = 0; i < 16; i++)
      {
        vOLED_Write_Data(CHN_16x16[usIndex].CHN_code[i]);
      }
      // 设置字符下半部分起始位置
      vOLED_Set_Pos(Page + 1,Seg);
      // 写入下半部分数据
      for (i = 0; i < 16; i++)
      {
        vOLED_Write_Data(CHN_16x16[usIndex].CHN_code[i + 16]);
      }
      break;    // 找到指针,退出循环
    }
  }
}

/*
* @function     : vIIC_Start
* @param        : None
* @retval       : None
* @brief        : IIC启动
*/
static void vIIC_Start(void)
{
  //SCL为高电平,SDA的下降沿为I2C起始信号
  SET_SDA;
  SET_SCL;
  _NOP();
  CLR_SDA;
  _NOP();
  CLR_SCL;
}

/*
* @function     : vIIC_Stop
* @param        : None
* @retval       : None
* @brief        : IIC停止
*/
static void vIIC_Stop(void)
{
  //SCL为高电平,SDA的上升沿为I2C停止信号
  CLR_SDA;
  SET_SCL;
  _NOP();
  SET_SDA;
}

/*
* @function     : ucIIC_Write_Byte
* @param        : WR_byte -> 待写入字节
* @retval       : 应答
* @brief        : IIC写入字节
*/
static uint8_t ucIIC_Write_Byte(uint8_t WR_byte)
{
    uint8_t i;
    // 存储读取的SDA电平状态
    uint8_t  ACK_Rspond;

    //SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
    //数据按8位传输,高位在前,利用for循环逐个接收
    for(i = 0; i < 8; i++)
    {
        //SCL清零,主机SDA准备数据
        CLR_SCL;
        if((WR_byte & BIT7) == BIT7)
        {
            SET_SDA;
        }
        else
        {
            CLR_SDA;
        }
        _NOP();
        //SCL置高,传输数据
        SET_SCL;
        _NOP();

        //准备发送下一比特位
        WR_byte <<= 1;
    }

    CLR_SCL;
    //SDA设为输入 释放SDA,等待从机应答
    SET_SDA;
    SDA_IN; 
    _NOP();

    SET_SCL;
    _NOP();

    ACK_Rspond = READ_SDA;      // 接收应答
    // SDA设为输出
    SDA_OUT; 
    CLR_SCL;
    _NOP();

    //返回从机的应答信号
    return ACK_Rspond;
}

/*
* @function     : vOLED_Write_CMD
* @param        : CMD -> 待写入命令
* @retval       : None
* @brief        : OLED写命令
*/
static void vOLED_Write_CMD(uint8_t CMD)
{
  vIIC_Start();
  ucIIC_Write_Byte(OLED_ADDR & (~BIT0));        // R/W#=0
  ucIIC_Write_Byte(0x00);       // Co=0,D/C#=0
  ucIIC_Write_Byte(CMD);
  vIIC_Stop();
}

/*
* @function     : vOLED_Write_Data
* @param        : Data -> 待写入数据
* @retval       : None
* @brief        : OLED写数据
*/
static void vOLED_Write_Data(uint8_t Data)
{
  vIIC_Start();
  ucIIC_Write_Byte(OLED_ADDR & (~BIT0));        // R/W#=0
  ucIIC_Write_Byte(0x40);       // Co=0,D/C#=1
  ucIIC_Write_Byte(Data);
  vIIC_Stop();
}


oled_font.h
cpp
#ifndef __OLED_FONT_H
#define __OLED_FONT_H
#include <main.h>



// 汉字
typedef struct
{
  uint8_t Index[2];
  uint8_t CHN_code[32];
}CHN_16x16_t;

const CHN_16x16_t CHN_16x16[] = 
{
  {{"太"},{0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xFF,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x80,0x80,0x40,0x20,0x10,0x0C,0x13,0x60,0x03,0x0C,0x10,0x20,0x40,0x80,0x80,0x00}},
  {{"阳"},{0x00,0xFE,0x02,0x22,0xDA,0x06,0x00,0xFC,0x04,0x04,0x04,0x04,0x04,0xFC,0x00,0x00,0x00,0xFF,0x08,0x10,0x08,0x07,0x00,0xFF,0x41,0x41,0x41,0x41,0x41,0xFF,0x00,0x00}},
  {{"能"},{0x08,0xCC,0x4A,0x49,0x48,0x4A,0xCC,0x18,0x00,0x7F,0x88,0x88,0x84,0x82,0xE0,0x00,0x00,0xFF,0x12,0x12,0x52,0x92,0x7F,0x00,0x00,0x7E,0x88,0x88,0x84,0x82,0xE0,0x00}},
  {{"路"},{0x00,0x3E,0x22,0xE2,0x22,0x3E,0x00,0x10,0x88,0x57,0x24,0x54,0x8C,0x00,0x00,0x00,0x40,0x7E,0x40,0x3F,0x22,0x22,0x00,0x01,0xFE,0x42,0x42,0x42,0xFE,0x01,0x01,0x00}},
  {{"灯"},{0x80,0x70,0x00,0xFF,0x20,0x10,0x04,0x04,0x04,0x04,0xFC,0x04,0x04,0x04,0x04,0x00,0x80,0x60,0x18,0x07,0x08,0x30,0x00,0x00,0x40,0x80,0x7F,0x00,0x00,0x00,0x00,0x00}},  
};

// ASCII码
const uint8_t ucASCII_16x8[95][16] = 
{
  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/
  {0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x00},/*"!",1*/
  {0x00,0x10,0x0C,0x02,0x10,0x0C,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*""",2*/
  {0x00,0x40,0xC0,0x78,0x40,0xC0,0x78,0x00,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x00},/*"#",3*/
  {0x00,0x70,0x88,0x88,0xFC,0x08,0x30,0x00,0x00,0x18,0x20,0x20,0xFF,0x21,0x1E,0x00},/*"$",4*/
  {0xF0,0x08,0xF0,0x80,0x60,0x18,0x00,0x00,0x00,0x31,0x0C,0x03,0x1E,0x21,0x1E,0x00},/*"%",5*/
  {0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x2C,0x19,0x27,0x21,0x10},/*"&",6*/
  {0x00,0x12,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",7*/
  {0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00},/*"(",8*/
  {0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00},/*")",9*/
  {0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00},/*"*",10*/
  {0x00,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x0F,0x01,0x01,0x01},/*"+",11*/
  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0x70,0x00,0x00,0x00,0x00,0x00},/*",",12*/
  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x00},/*"-",13*/
  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00},/*".",14*/
  {0x00,0x00,0x00,0x00,0xC0,0x38,0x04,0x00,0x00,0x60,0x18,0x07,0x00,0x00,0x00,0x00},/*"/",15*/
  {0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00},/*"0",16*/
  {0x00,0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00},/*"1",17*/
  {0x00,0x70,0x08,0x08,0x08,0x08,0xF0,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00},/*"2",18*/
  {0x00,0x30,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x18,0x20,0x21,0x21,0x22,0x1C,0x00},/*"3",19*/
  {0x00,0x00,0x80,0x40,0x30,0xF8,0x00,0x00,0x00,0x06,0x05,0x24,0x24,0x3F,0x24,0x24},/*"4",20*/
  {0x00,0xF8,0x88,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x20,0x20,0x20,0x11,0x0E,0x00},/*"5",21*/
  {0x00,0xE0,0x10,0x88,0x88,0x90,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x20,0x1F,0x00},/*"6",22*/
  {0x00,0x18,0x08,0x08,0x88,0x68,0x18,0x00,0x00,0x00,0x00,0x3E,0x01,0x00,0x00,0x00},/*"7",23*/
  {0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00},/*"8",24*/
  {0x00,0xF0,0x08,0x08,0x08,0x10,0xE0,0x00,0x00,0x01,0x12,0x22,0x22,0x11,0x0F,0x00},/*"9",25*/
  {0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00},/*":",26*/
  {0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0x00},/*";",27*/
  {0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00},/*"<",28*/
  {0x00,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x00,0x02,0x02,0x02,0x02,0x02,0x02,0x00},/*"=",29*/
  {0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00},/*">",30*/
  {0x00,0x70,0x48,0x08,0x08,0x88,0x70,0x00,0x00,0x00,0x00,0x30,0x37,0x00,0x00,0x00},/*"?",31*/
  {0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x28,0x2F,0x28,0x17,0x00},/*"@",32*/
  {0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20},/*"A",33*/
  {0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00},/*"B",34*/
  {0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00},/*"C",35*/
  {0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00},/*"D",36*/
  {0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00},/*"E",37*/
  {0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00},/*"F",38*/
  {0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00},/*"G",39*/
  {0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20},/*"H",40*/
  {0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},/*"I",41*/
  {0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00},/*"J",42*/
  {0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00},/*"K",43*/
  {0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00},/*"L",44*/
  {0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x01,0x3E,0x01,0x3F,0x20,0x00},/*"M",45*/
  {0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00},/*"N",46*/
  {0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00},/*"O",47*/
  {0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00},/*"P",48*/
  {0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x28,0x28,0x30,0x50,0x4F,0x00},/*"Q",49*/
  {0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20},/*"R",50*/
  {0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00},/*"S",51*/
  {0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00},/*"T",52*/
  {0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00},/*"U",53*/
  {0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00},/*"V",54*/
  {0x08,0xF8,0x00,0xF8,0x00,0xF8,0x08,0x00,0x00,0x03,0x3E,0x01,0x3E,0x03,0x00,0x00},/*"W",55*/
  {0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20},/*"X",56*/
  {0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00},/*"Y",57*/
  {0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00},/*"Z",58*/
  {0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00},/*"[",59*/
  {0x00,0x04,0x38,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00},/*"\",60*/
  {0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00},/*"]",61*/
  {0x00,0x00,0x04,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"^",62*/
  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},/*"_",63*/
  {0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"`",64*/
  {0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x00,0x19,0x24,0x24,0x12,0x3F,0x20,0x00},/*"a",65*/
  {0x10,0xF0,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00},/*"b",66*/
  {0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00},/*"c",67*/
  {0x00,0x00,0x80,0x80,0x80,0x90,0xF0,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20},/*"d",68*/
  {0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x24,0x24,0x24,0x24,0x17,0x00},/*"e",69*/
  {0x00,0x80,0x80,0xE0,0x90,0x90,0x20,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},/*"f",70*/
  {0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00},/*"g",71*/
  {0x10,0xF0,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20},/*"h",72*/
  {0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},/*"i",73*/
  {0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00},/*"j",74*/
  {0x10,0xF0,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x06,0x29,0x30,0x20,0x00},/*"k",75*/
  {0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},/*"l",76*/
  {0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F},/*"m",77*/
  {0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20},/*"n",78*/
  {0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00},/*"o",79*/
  {0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0x91,0x20,0x20,0x11,0x0E,0x00},/*"p",80*/
  {0x00,0x00,0x00,0x80,0x80,0x00,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0x91,0xFF,0x80},/*"q",81*/
  {0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00},/*"r",82*/
  {0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00},/*"s",83*/
  {0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x10,0x00},/*"t",84*/
  {0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20},/*"u",85*/
  {0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x03,0x0C,0x30,0x0C,0x03,0x00,0x00},/*"v",86*/
  {0x80,0x80,0x00,0x80,0x80,0x00,0x80,0x80,0x01,0x0E,0x30,0x0C,0x07,0x38,0x06,0x01},/*"w",87*/
  {0x00,0x80,0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x20,0x31,0x0E,0x2E,0x31,0x20,0x00},/*"x",88*/
  {0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x81,0x86,0x78,0x18,0x06,0x01,0x00},/*"y",89*/
  {0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00},/*"z",90*/
  {0x00,0x00,0x00,0x00,0x00,0xFC,0x02,0x02,0x00,0x00,0x00,0x00,0x01,0x3E,0x40,0x40},/*"{",91*/
  {0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00},/*"|",92*/
  {0x02,0x02,0xFC,0x00,0x00,0x00,0x00,0x00,0x40,0x40,0x3E,0x01,0x00,0x00,0x00,0x00},/*"}",93*/
  {0x00,0x02,0x01,0x02,0x02,0x04,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} /*"~",94*/  
};

#endif

编程示例3

介绍:基于CW32030C8T6,采用模拟IIC,OLED模块(0.96寸)采用 SSD1306 驱动芯片,像素是 128x64,跟上面差不多但是这个的显示正常

  • 硬件连接

IIC初始化部分跟上面一致,需要初始化引脚为开漏输出,这里就不写出来了

us延时的话自己调试,差不多就行了,不能延时太长否则会刷新得很慢

汉字取模:

oled_096.h
cpp
#ifndef __OLED_096_H
#define __OLED_096_H
#include "main.h"

// OLED的IIC地址(SA0 = 0) --- 0111 1000
#define OLED096_ADDR       0x78
// OLED参数(宽度,高度,页数量)
#define OLED096_WIDTH      128
#define OLED096_HEIGHT     64

typedef enum
{
    ASCII_SIZE_16 = 16,
} ASCII_Size_t;

// 最大只能显示4行
typedef enum
{
    OLED096_Line1 = 0,  
    OLED096_Line2 = 2,
    OLED096_Line3 = 4,
    OLED096_Line4 = 6,
} OLED091_Line_t;

typedef struct
{
  void (*OLED096_Init)(void);     // OLED初始化
  void (*OLED096_Clear)(void);    // 清屏
  void (*OLED096_Show_String)(uint8_t, OLED091_Line_t, const char*, ASCII_Size_t);       // OLED显示字符串
  void (*OLED096_Show_CHN)(uint8_t, OLED091_Line_t, const char*);        // OLED显示单个汉字
}OLED096_t;

extern OLED096_t OLED096;


#endif
oled_096.c
cpp
/***************************************************************************
 * File: oled_096.c
 * Author: Luckys.
 * Date: 2023/06/13
 * description: 0.96寸OLED
 -----------------------------------
接线:
    VCC -----> 3.3V
    GND -----> GND
    SCL -----> PB6
    SDA -----> PB7
 -----------------------------------
****************************************************************************/
#include "main.h"
#include "oled_font.h"

/*====================================static function declaration area BEGIN====================================*/

static void OLED096_Init(void);     // OLED初始化
static void OLED096_Clear(void);    // 清屏
static void OLED096_Show_String(uint8_t, OLED091_Line_t, const char*, ASCII_Size_t);       // OLED显示字符串
static void OLED096_Show_CHN(uint8_t, OLED091_Line_t, const char*);        // OLED显示单个汉字

static void OLED096_Set_Pos(uint8_t, uint8_t); // OLED设置坐标
static void OLED096_Write_CMD(uint8_t);    // OLED写命令
static void OLED096_Write_Data(uint8_t);    // OLED写数据

/*====================================static function declaration area   END====================================*/
OLED096_t OLED096 =
{
    OLED096_Init,
    OLED096_Clear,
    OLED096_Show_String,
    OLED096_Show_CHN,
};

/*
* @function: OLED096_Write_CMD
* @param: CMD -> 待写入命令
* @retval: None
* @brief: OLED写命令
*/
static void OLED096_Write_CMD(uint8_t CMD)
{
    I2C_Soft.I2C_Start();
    I2C_Soft.I2C_Write_Byte(OLED096_ADDR);        // R/W#=0
    I2C_Soft.I2C_Write_Byte(0x00);       // Co=0,D/C#=0
    I2C_Soft.I2C_Write_Byte(CMD);
    I2C_Soft.I2C_Stop();    
}

/*
* @function: OLED096_Write_Data
* @param: Data -> 待写入数据
* @retval: None
* @brief: OLED写数据
*/
static void OLED096_Write_Data(uint8_t Data)
{
    I2C_Soft.I2C_Start();
    I2C_Soft.I2C_Write_Byte(OLED096_ADDR);        // R/W#=0
    I2C_Soft.I2C_Write_Byte(0x40);       // Co=0,D/C#=0
    I2C_Soft.I2C_Write_Byte(Data);
    I2C_Soft.I2C_Stop();    
}

/*
* @function: OLED096_Init
* @param: None
* @retval: None
* @brief: OLED初始化
*/
static void OLED096_Init(void)
{
    Public.System_MS_Delay(200); // 上电延时
    OLED096_Write_CMD(0xAE); // OLED休眠
    OLED096_Write_CMD(0x00); // 设置低列地址
    OLED096_Write_CMD(0x10); // 设置高列地址
    OLED096_Write_CMD(0x40); // 设置起始地址线
    OLED096_Write_CMD(0xB0); // set page address
    OLED096_Write_CMD(0x81); // 设置对比度
    OLED096_Write_CMD(0xFF); //--128
    OLED096_Write_CMD(0xA1); // 0xa0左右反置 0xa1正常
    OLED096_Write_CMD(0xA6); // normal / reverse
    OLED096_Write_CMD(0xA8); // 设置多路复用(1 to 64)
    OLED096_Write_CMD(0x3F); // 1/32 duty
    OLED096_Write_CMD(0xC8); // Com scan direction
    OLED096_Write_CMD(0xD3); // 设置显示的偏移映射内存计数器
    OLED096_Write_CMD(0x00); //

    OLED096_Write_CMD(0xD5); // 设置显示时钟分频比/振荡器频率
    OLED096_Write_CMD(0x80); // 设置分频比例,时钟设置为100帧/秒

    OLED096_Write_CMD(0xD8); // set area color mode off
    OLED096_Write_CMD(0x05); //

    OLED096_Write_CMD(0xD9); // 预充电时间
    OLED096_Write_CMD(0xF1); // 预充电为15个脉冲,释放为1个脉冲

    OLED096_Write_CMD(0xDA); // 引脚设置硬件配置
    OLED096_Write_CMD(0x12); //

    OLED096_Write_CMD(0xDB); // 设置VCOM电平
    OLED096_Write_CMD(0x30); //
    // 唤醒
    OLED096_Write_CMD(0x8D); // 设置电荷泵
    OLED096_Write_CMD(0x14); // 开启电荷泵

    OLED096_Write_CMD(0xAF); // OLED唤醒(AE是OLED休眠)

    OLED096.OLED096_Clear();    // 清屏
    OLED096.OLED096_Show_CHN(8,OLED096_Line1,"温");
    OLED096.OLED096_Show_CHN(32,OLED096_Line1,"湿");
    OLED096.OLED096_Show_CHN(56,OLED096_Line1,"度");
    OLED096.OLED096_Show_CHN(80,OLED096_Line1,"采");
    OLED096.OLED096_Show_CHN(104,OLED096_Line1,"集");   
    OLED096.OLED096_Show_String(0,OLED096_Line2,"ABC1234567890123",ASCII_SIZE_16);   
    OLED096.OLED096_Show_String(0,OLED096_Line3,"ABC123",ASCII_SIZE_16);   
    OLED096.OLED096_Show_String(0,OLED096_Line4,"ABC123",ASCII_SIZE_16);   
}

/*
* @function: OLED096_Clear
* @param: None
* @retval: None
* @brief: OLED清屏
*/
static void OLED096_Clear(void)
{
    uint8_t Page, Seg;

    for (Page = 0; Page < 8; Page++)
    {
        OLED096_Write_CMD(0xB0 + Page); // 一共8页(行)
        OLED096_Write_CMD(0x00);        // 低
        OLED096_Write_CMD(0x01);        // 高

        for (Seg = 0; Seg < 128; Seg++)
        {
            OLED096_Write_Data(0x00);
        }
    }
}

/*
* @function     : OLED096_Set_Pos
* @param        : Page -> 行 Seg -> 列
* @retval       : None
* @brief        : OLED设置坐标
*/
static void OLED096_Set_Pos(uint8_t Page, uint8_t Seg)
{
    OLED096_Write_CMD(0xB0 + Seg);
    OLED096_Write_CMD(((Page & 0xF0) >> 4) | 0x10); // 高4位
    OLED096_Write_CMD((Page & 0x0F));               // 低4位
}

/*
* @function: OLED096_Show_String
* @param: x -> 列 y -> 行 p_Str -> 要显示的字符串 ch_size -> 字体大小
* @retval: None
* @brief: OLED显示字符串
*/
static void OLED096_Show_String(uint8_t x, OLED091_Line_t y, const char *p_Str, ASCII_Size_t ch_size)
{
    uint8_t i = 0;
    uint8_t c = 0;

    if (ch_size == 16)
    {
        while (p_Str[i] != '\0')
        {
            c = p_Str[i++] - ' ';
            OLED096_Set_Pos(x, y);
            for (uint8_t j = 0; j < 8; j++)
                OLED096_Write_Data(ucASCII_16x8[c * 16 + j]);
            OLED096_Set_Pos(x, y + 1);
            for (uint8_t j = 0; j < 8; j++)
                OLED096_Write_Data(ucASCII_16x8[c * 16 + j + 8]);

            x += 8;
            if (x > 120)
            {
                x = 0;
                y += 2;
            }
        }
    }
}

/*
* @function: OLED096_Show_CHN
* @param: x -> 列 y -> 行  p_Str -> 单个汉字字符串
* @retval: None
* @brief: // OLED显示单个汉字
*/
static void OLED096_Show_CHN(uint8_t x, OLED091_Line_t y, const char *p_Str)
{
    uint16_t usCHN_Number; // 字库中汉字数量
    uint16_t usIndex;      // 字库中的汉字索引
    uint8_t i;

    // 统计汉字的位置
    usCHN_Number = sizeof(CHN_16x16) / sizeof(Oled_Font16x16_t);
    // 查找汉字的位置
    for (usIndex = 0; usIndex < usCHN_Number; usIndex++)
    {
        if ((CHN_16x16[usIndex].Index[0] == *p_Str) && (CHN_16x16[usIndex].Index[1] == *(p_Str + 1))) // 因为一个汉字占两个字节
        {
            OLED096_Set_Pos(x, y);
            for (i = 0; i < 16; i++)
            {
                OLED096_Write_Data(CHN_16x16[usIndex].CHN_code[i]);
            }

            OLED096_Set_Pos(x, y + 1);
            for (i = 0; i < 16; i++)
            {
                OLED096_Write_Data(CHN_16x16[usIndex].CHN_code[i + 16]);
            }
            break;
        }
    }
}
oled_font.c
cpp
#ifndef __OLED_FONT_H
#define __OLED_FONT_H
#include "main.h"

/*===========================================0.96OLED===========================================*/

// ASCII码 16x16 只能显示一行16个
const uint8_t ucASCII_16x8[] =
{
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0
    0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x30, 0x00, 0x00, 0x00, //! 1
    0x00, 0x10, 0x0C, 0x06, 0x10, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //" 2
    0x40, 0xC0, 0x78, 0x40, 0xC0, 0x78, 0x40, 0x00, 0x04, 0x3F, 0x04, 0x04, 0x3F, 0x04, 0x04, 0x00, //# 3
    0x00, 0x70, 0x88, 0xFC, 0x08, 0x30, 0x00, 0x00, 0x00, 0x18, 0x20, 0xFF, 0x21, 0x1E, 0x00, 0x00, //$ 4
    0xF0, 0x08, 0xF0, 0x00, 0xE0, 0x18, 0x00, 0x00, 0x00, 0x21, 0x1C, 0x03, 0x1E, 0x21, 0x1E, 0x00, //% 5
    0x00, 0xF0, 0x08, 0x88, 0x70, 0x00, 0x00, 0x00, 0x1E, 0x21, 0x23, 0x24, 0x19, 0x27, 0x21, 0x10, //& 6
    0x10, 0x16, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //' 7
    0x00, 0x00, 0x00, 0xE0, 0x18, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x07, 0x18, 0x20, 0x40, 0x00, //( 8
    0x00, 0x02, 0x04, 0x18, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x20, 0x18, 0x07, 0x00, 0x00, 0x00, //) 9
    0x40, 0x40, 0x80, 0xF0, 0x80, 0x40, 0x40, 0x00, 0x02, 0x02, 0x01, 0x0F, 0x01, 0x02, 0x02, 0x00, //* 10
    0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x1F, 0x01, 0x01, 0x01, 0x00, //+ 11
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xB0, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, //, 12
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, //- 13
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, //. 14
    0x00, 0x00, 0x00, 0x00, 0x80, 0x60, 0x18, 0x04, 0x00, 0x60, 0x18, 0x06, 0x01, 0x00, 0x00, 0x00, /// 15
    0x00, 0xE0, 0x10, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x00, 0x0F, 0x10, 0x20, 0x20, 0x10, 0x0F, 0x00, //0 16
    0x00, 0x10, 0x10, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00, //1 17
    0x00, 0x70, 0x08, 0x08, 0x08, 0x88, 0x70, 0x00, 0x00, 0x30, 0x28, 0x24, 0x22, 0x21, 0x30, 0x00, //2 18
    0x00, 0x30, 0x08, 0x88, 0x88, 0x48, 0x30, 0x00, 0x00, 0x18, 0x20, 0x20, 0x20, 0x11, 0x0E, 0x00, //3 19
    0x00, 0x00, 0xC0, 0x20, 0x10, 0xF8, 0x00, 0x00, 0x00, 0x07, 0x04, 0x24, 0x24, 0x3F, 0x24, 0x00, //4 20
    0x00, 0xF8, 0x08, 0x88, 0x88, 0x08, 0x08, 0x00, 0x00, 0x19, 0x21, 0x20, 0x20, 0x11, 0x0E, 0x00, //5 21
    0x00, 0xE0, 0x10, 0x88, 0x88, 0x18, 0x00, 0x00, 0x00, 0x0F, 0x11, 0x20, 0x20, 0x11, 0x0E, 0x00, //6 22
    0x00, 0x38, 0x08, 0x08, 0xC8, 0x38, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, //7 23
    0x00, 0x70, 0x88, 0x08, 0x08, 0x88, 0x70, 0x00, 0x00, 0x1C, 0x22, 0x21, 0x21, 0x22, 0x1C, 0x00, //8 24
    0x00, 0xE0, 0x10, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x00, 0x00, 0x31, 0x22, 0x22, 0x11, 0x0F, 0x00, //9 25
    0x00, 0x00, 0x00, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, //: 26
    0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x60, 0x00, 0x00, 0x00, 0x00, //; 27
    0x00, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, //< 28
    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, //= 29
    0x00, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, //> 30
    0x00, 0x70, 0x48, 0x08, 0x08, 0x08, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x30, 0x36, 0x01, 0x00, 0x00, //? 31
    0xC0, 0x30, 0xC8, 0x28, 0xE8, 0x10, 0xE0, 0x00, 0x07, 0x18, 0x27, 0x24, 0x23, 0x14, 0x0B, 0x00, //@ 32
    0x00, 0x00, 0xC0, 0x38, 0xE0, 0x00, 0x00, 0x00, 0x20, 0x3C, 0x23, 0x02, 0x02, 0x27, 0x38, 0x20, //A 33
    0x08, 0xF8, 0x88, 0x88, 0x88, 0x70, 0x00, 0x00, 0x20, 0x3F, 0x20, 0x20, 0x20, 0x11, 0x0E, 0x00, //B 34
    0xC0, 0x30, 0x08, 0x08, 0x08, 0x08, 0x38, 0x00, 0x07, 0x18, 0x20, 0x20, 0x20, 0x10, 0x08, 0x00, //C 35
    0x08, 0xF8, 0x08, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x20, 0x3F, 0x20, 0x20, 0x20, 0x10, 0x0F, 0x00, //D 36
    0x08, 0xF8, 0x88, 0x88, 0xE8, 0x08, 0x10, 0x00, 0x20, 0x3F, 0x20, 0x20, 0x23, 0x20, 0x18, 0x00, //E 37
    0x08, 0xF8, 0x88, 0x88, 0xE8, 0x08, 0x10, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x03, 0x00, 0x00, 0x00, //F 38
    0xC0, 0x30, 0x08, 0x08, 0x08, 0x38, 0x00, 0x00, 0x07, 0x18, 0x20, 0x20, 0x22, 0x1E, 0x02, 0x00, //G 39
    0x08, 0xF8, 0x08, 0x00, 0x00, 0x08, 0xF8, 0x08, 0x20, 0x3F, 0x21, 0x01, 0x01, 0x21, 0x3F, 0x20, //H 40
    0x00, 0x08, 0x08, 0xF8, 0x08, 0x08, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00, //I 41
    0x00, 0x00, 0x08, 0x08, 0xF8, 0x08, 0x08, 0x00, 0xC0, 0x80, 0x80, 0x80, 0x7F, 0x00, 0x00, 0x00, //J 42
    0x08, 0xF8, 0x88, 0xC0, 0x28, 0x18, 0x08, 0x00, 0x20, 0x3F, 0x20, 0x01, 0x26, 0x38, 0x20, 0x00, //K 43
    0x08, 0xF8, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x3F, 0x20, 0x20, 0x20, 0x20, 0x30, 0x00, //L 44
    0x08, 0xF8, 0xF8, 0x00, 0xF8, 0xF8, 0x08, 0x00, 0x20, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 0x20, 0x00, //M 45
    0x08, 0xF8, 0x30, 0xC0, 0x00, 0x08, 0xF8, 0x08, 0x20, 0x3F, 0x20, 0x00, 0x07, 0x18, 0x3F, 0x00, //N 46
    0xE0, 0x10, 0x08, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x0F, 0x10, 0x20, 0x20, 0x20, 0x10, 0x0F, 0x00, //O 47
    0x08, 0xF8, 0x08, 0x08, 0x08, 0x08, 0xF0, 0x00, 0x20, 0x3F, 0x21, 0x01, 0x01, 0x01, 0x00, 0x00, //P 48
    0xE0, 0x10, 0x08, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x0F, 0x18, 0x24, 0x24, 0x38, 0x50, 0x4F, 0x00, //Q 49
    0x08, 0xF8, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x03, 0x0C, 0x30, 0x20, //R 50
    0x00, 0x70, 0x88, 0x08, 0x08, 0x08, 0x38, 0x00, 0x00, 0x38, 0x20, 0x21, 0x21, 0x22, 0x1C, 0x00, //S 51
    0x18, 0x08, 0x08, 0xF8, 0x08, 0x08, 0x18, 0x00, 0x00, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x00, 0x00, //T 52
    0x08, 0xF8, 0x08, 0x00, 0x00, 0x08, 0xF8, 0x08, 0x00, 0x1F, 0x20, 0x20, 0x20, 0x20, 0x1F, 0x00, //U 53
    0x08, 0x78, 0x88, 0x00, 0x00, 0xC8, 0x38, 0x08, 0x00, 0x00, 0x07, 0x38, 0x0E, 0x01, 0x00, 0x00, //V 54
    0xF8, 0x08, 0x00, 0xF8, 0x00, 0x08, 0xF8, 0x00, 0x03, 0x3C, 0x07, 0x00, 0x07, 0x3C, 0x03, 0x00, //W 55
    0x08, 0x18, 0x68, 0x80, 0x80, 0x68, 0x18, 0x08, 0x20, 0x30, 0x2C, 0x03, 0x03, 0x2C, 0x30, 0x20, //X 56
    0x08, 0x38, 0xC8, 0x00, 0xC8, 0x38, 0x08, 0x00, 0x00, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x00, 0x00, //Y 57
    0x10, 0x08, 0x08, 0x08, 0xC8, 0x38, 0x08, 0x00, 0x20, 0x38, 0x26, 0x21, 0x20, 0x20, 0x18, 0x00, //Z 58
    0x00, 0x00, 0x00, 0xFE, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x40, 0x40, 0x40, 0x00, //[ 59
    0x00, 0x0C, 0x30, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x38, 0xC0, 0x00, //\ 60
    0x00, 0x02, 0x02, 0x02, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x7F, 0x00, 0x00, 0x00, //] 61
    0x00, 0x00, 0x04, 0x02, 0x02, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //^ 62
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, //_ 63
    0x00, 0x02, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //` 64
    0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x19, 0x24, 0x22, 0x22, 0x22, 0x3F, 0x20, //a 65
    0x08, 0xF8, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x11, 0x20, 0x20, 0x11, 0x0E, 0x00, //b 66
    0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x0E, 0x11, 0x20, 0x20, 0x20, 0x11, 0x00, //c 67
    0x00, 0x00, 0x00, 0x80, 0x80, 0x88, 0xF8, 0x00, 0x00, 0x0E, 0x11, 0x20, 0x20, 0x10, 0x3F, 0x20, //d 68
    0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x1F, 0x22, 0x22, 0x22, 0x22, 0x13, 0x00, //e 69
    0x00, 0x80, 0x80, 0xF0, 0x88, 0x88, 0x88, 0x18, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00, //f 70
    0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x6B, 0x94, 0x94, 0x94, 0x93, 0x60, 0x00, //g 71
    0x08, 0xF8, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x3F, 0x21, 0x00, 0x00, 0x20, 0x3F, 0x20, //h 72
    0x00, 0x80, 0x98, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00, //i 73
    0x00, 0x00, 0x00, 0x80, 0x98, 0x98, 0x00, 0x00, 0x00, 0xC0, 0x80, 0x80, 0x80, 0x7F, 0x00, 0x00, //j 74
    0x08, 0xF8, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x20, 0x3F, 0x24, 0x02, 0x2D, 0x30, 0x20, 0x00, //k 75
    0x00, 0x08, 0x08, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00, //l 76
    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x3F, 0x20, 0x00, 0x3F, //m 77
    0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x3F, 0x21, 0x00, 0x00, 0x20, 0x3F, 0x20, //n 78
    0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x1F, 0x20, 0x20, 0x20, 0x20, 0x1F, 0x00, //o 79
    0x80, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xA1, 0x20, 0x20, 0x11, 0x0E, 0x00, //p 80
    0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x0E, 0x11, 0x20, 0x20, 0xA0, 0xFF, 0x80, //q 81
    0x80, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x20, 0x20, 0x3F, 0x21, 0x20, 0x00, 0x01, 0x00, //r 82
    0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x33, 0x24, 0x24, 0x24, 0x24, 0x19, 0x00, //s 83
    0x00, 0x80, 0x80, 0xE0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x20, 0x20, 0x00, 0x00, //t 84
    0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x1F, 0x20, 0x20, 0x20, 0x10, 0x3F, 0x20, //u 85
    0x80, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x01, 0x0E, 0x30, 0x08, 0x06, 0x01, 0x00, //v 86
    0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, 0x0F, 0x30, 0x0C, 0x03, 0x0C, 0x30, 0x0F, 0x00, //w 87
    0x00, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x31, 0x2E, 0x0E, 0x31, 0x20, 0x00, //x 88
    0x80, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x81, 0x8E, 0x70, 0x18, 0x06, 0x01, 0x00, //y 89
    0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x21, 0x30, 0x2C, 0x22, 0x21, 0x30, 0x00, //z 90
    0x00, 0x00, 0x00, 0x00, 0x80, 0x7C, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x40, 0x40, //{ 91
    0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, //| 92
    0x00, 0x02, 0x02, 0x7C, 0x80, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x3F, 0x00, 0x00, 0x00, 0x00, //} 93
    0x00, 0x06, 0x01, 0x01, 0x02, 0x02, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //~ 94
};

typedef struct
{
    uint8_t Index[2];	
    uint8_t CHN_code[32];
}Oled_Font16x16_t;

const Oled_Font16x16_t CHN_16x16[] =
{
    {{"温"}, {0x10, 0x60, 0x02, 0x8C, 0x00, 0x00, 0xFE, 0x92, 0x92, 0x92, 0x92, 0x92, 0xFE, 0x00, 0x00, 0x00, 0x04, 0x04, 0x7E, 0x01, 0x40, 0x7E, 0x42, 0x42, 0x7E, 0x42, 0x7E, 0x42, 0x42, 0x7E, 0x40, 0x00}},
    {{"湿"}, {0x10, 0x60, 0x02, 0x8C, 0x00, 0xFE, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0xFE, 0x00, 0x00, 0x00, 0x04, 0x04, 0x7E, 0x01, 0x44, 0x48, 0x50, 0x7F, 0x40, 0x40, 0x7F, 0x50, 0x48, 0x44, 0x40, 0x00}},
    {{"度"}, {0x00, 0x00, 0xFC, 0x24, 0x24, 0x24, 0xFC, 0x25, 0x26, 0x24, 0xFC, 0x24, 0x24, 0x24, 0x04, 0x00, 0x40, 0x30, 0x8F, 0x80, 0x84, 0x4C, 0x55, 0x25, 0x25, 0x25, 0x55, 0x4C, 0x80, 0x80, 0x80, 0x00}},
    {{"采"}, {0x00, 0x00, 0x04, 0x14, 0x64, 0x04, 0x0C, 0xB4, 0x02, 0x02, 0x42, 0x33, 0x02, 0x00, 0x00, 0x00, 0x40, 0x41, 0x21, 0x11, 0x09, 0x05, 0x03, 0xFF, 0x03, 0x05, 0x09, 0x11, 0x21, 0x41, 0x40, 0x00}},
    {{"集"}, {0x20, 0x10, 0x08, 0xFC, 0x57, 0x54, 0x54, 0x55, 0xFE, 0x54, 0x54, 0x54, 0x54, 0x04, 0x00, 0x00, 0x44, 0x44, 0x24, 0x27, 0x15, 0x0D, 0x05, 0xFF, 0x05, 0x0D, 0x15, 0x25, 0x25, 0x45, 0x44, 0x00}},
    {{"℃"}, {0x06, 0x09, 0x09, 0xE6, 0xF8, 0x0C, 0x04, 0x02, 0x02, 0x02, 0x02, 0x02, 0x04, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1F, 0x30, 0x20, 0x40, 0x40, 0x40, 0x40, 0x40, 0x20, 0x10, 0x00, 0x00}},
};

#endif

编程示例4

参考:HAL库硬件IIC驱动OLED

介绍:基于STM32F407VET6,采用硬件IIC,OLED模块(0.96寸)采用 SSD1306 驱动芯片,像素是 128x64,OLED相关函数参考示例3即可,主要看硬件IIC相关的函数

  • MX配置

标准模式和快速模式都测试了正常

  • 程序

程序也简单,只需要改两个函数即可,改成下面

cpp
// OLED的IIC地址(SA0 = 0) --- 0111 1000
#define OLED_ADDR       0x78

/*
* @function: OLED_Write_CMD
* @param: CMD -> 待写入命令
* @retval: None
* @brief: OLED写命令
*/
static void OLED_Write_CMD(uint8_t CMD)
{
    // 参数1--I2C句柄  参数2--I2C设备地址  参数3--要写入的内存地址  参数4--内存地址大小  参数5--要写入的数据指针  参数6--要写入的数据长度 参数7--超时时间
	HAL_I2C_Mem_Write(&hi2c1, OLED_ADDR, 0x00, I2C_MEMADD_SIZE_8BIT, &CMD, 1, 0x100);
}

/*
* @function: OLED_Write_Data
* @param: Data -> 待写入数据
* @retval: None
* @brief: OLED写数据
*/
static void OLED_Write_Data(uint8_t Data)
{
	HAL_I2C_Mem_Write(&hi2c1, OLED_ADDR, 0x40, I2C_MEMADD_SIZE_8BIT, &Data, 1, 0x100);
}
  • 实验现象