• STM32使用硬件IIC读取SHTC3温湿度传感器 显示在OLED屏上


    STM32使用硬件I2C读取SHTC3温湿度传感器的数据并显示在0.96寸OLED屏上。

    我用的是STM32F103C8T6,程序用的是ST标准库写的。

    实现效果图


    I2C协议简介

    I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备(那些电平转化芯片),现在被广泛地使用在系统内多个集成电路(IC)间的通讯。

    I2C只有一跟数据总线 SDA(Serial Data Line),串行数据总线,只能一位一位的发送数据,属于串行通信,采用半双工通信

    半双工通信:可以实现双向的通信,但不能在两个方向上同时进行,必须轮流交替进行,其实也可以理解成一种可以切换方向的单工通信,同一时刻必须只能一个方向传输,只需一根数据线.
    对于I2C通讯协议把它分为物理层和协议层物理层规定通讯系统中具有机械、电子功能部分的特性(硬件部分),确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准(软件层面)。

    I2C物理层

    I2C 通讯设备之间的常用连接方式

    (1) 它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。

    (2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线SDA(Serial Data Line ),一条串行时钟线SCL(Serial Data Line )。数据线即用来表示数据,时钟线用于数据收发同步

    (3) 总线通过上拉电阻接到电源。当 I2C 设备空闲时会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平

    I2C通信时单片机GPIO口必须设置为开漏输出,否则可能会造成短路。

    关于更多STM32的I2C相关信息和使用方法可以看这篇文章:https://url.zeruns.tech/JC0Ah

    我这里就不详细讲解了。

    SHTC3温湿度传感器

    SHTC3数据手册下载地址:https://url.zeruns.tech/WpLDy

    浏览数据手册可以得到一个大概信息,SHTC3是一个可以检测温度和湿度的传感器,
    温度范围:-40℃~125℃
    湿度范围:0%~100%
    工作电压:1.6v~3.6v
    通讯方式:i2c
    时钟频率:三种模式分别是 0 ~ 100kHz 0 ~ 400kHz 0 ~ 1000kHz

    找到如下几个关键信息

    温湿度设备地址和读写命令

    在实际的使用过程中,SHTC3的设备地址需要与读写数据/命令方向位组成一个字节同时发送,字节的最低位为读写数据/命令方向位,高7位是SHTC3的设备地址。

    如果要通过I2C写数据或命令给SHTC3,在I2C起始信号之后,需要发送“1110 0000”,即0xE0给SHTC3,除了通过高7位“1110 000”的设备地址寻址还通过最低位“0”通知SHTC3接下来是写数据或命令操作。

    如果要通过I2C读取SHTC3中的数据,在I2C起始信号之后,需要发送“1110 0001”,即0xE1给SHTC3,除了通过高7位“1110 000”的设备地址寻址还通过最低位“1”通知SHTC3接下来是读取数据的操作。
    简单来说就是,0xE0表示写数据,0xE1表示读数据。不过使用STM32硬件I2C时只需要输入0xE0就行,最低位标准库会处理的。

    读取温湿度数据

    =

    可知,不同的命令,除了获取的数据顺序不一样,还有一个Clock Stretching Enable 和 Disable的区别。

    Clock Stretching是时钟拉伸的意思。如果使用Clock Stretching Enable命令的话,那么发送完测量命令之后,在SHTC3测量温度湿度数据的过程中,SHTC3会拉低I2C的时钟线SCL,通过这样来禁止主机发送命令给SHTC3,只有当SHTC3完成温度湿度数据测量时,SHTC3才会释放时钟线SCL。

    如果使用Clock Stretching Disable命令的话,在SHTC3测量数据的过程中,SHTC3并不会拉低I2C的时钟线SCL,只是如果主机在SHTC3测量数据的过程中发送命令或数据的时候,SHTC3是不会响应主机的,主机可以通过SHTC3是否有响应信号来判断SHTC3是否完成数据的测量。

    从数据手册可知,一个测量周期包概括四个步骤:

    1. 发送唤醒命令。
    2. 发送测量命令
    3. 读取测量完成之后的数据。
    4. 发送休眠命令。

    以上唤醒命令和休眠命令在数据手册中查询。

    总结如下:

    1. 唤醒SHTC3:先发送写入指令(0xE0),再发送唤醒指令高位(0x35),再发送唤醒指令低位(0x17)。
    2. 等待唤醒:数据手册上写的最大唤醒时间是240us,等待的时间大于这个就行了。
    3. 发送采集指令:先发送写入指令(0xE0),再发送采集指令的高位和低位。采集指令有多个,根据需要自行选择。
    4. 接收数据:发送读取指令(0xE1),连续接收6个字节数据。如果采集的指令是先存温度,那么这6个字节的第1-2个字节就是温度数值,第3个字节是温度校验。第4-5个字节是湿度数值,第6个字节是湿度校验。如果采集的指令是先存湿度,则前3个字节和后3个字节相反。
    5. 进入睡眠:发送写入指令,再发送睡眠指令进入睡眠。

    数据的计算

    由shtc3数据手册可知

    例如:采集到的湿度数值是0x6501,换算成十进制是25857。
    则:湿度 = 100 * 25857 / 65536 = 39.45 (单位:%)
    采集到的温度数值是0x6600,换算成十进制是26112。
    则:温度 = -45 + 175 * 26112 / 65536 = 24.72 (单位:℃)

    需要用的元件

    STM32最小系统板:https://s.click.taobao.com/bqMwZRu
    SHTC3模块:https://s.click.taobao.com/WxACJRu
    OLED模块:https://s.click.taobao.com/aNlvZRu
    杜邦线:https://s.click.taobao.com/xAkAJRu
    面包板:https://s.click.taobao.com/ShJAJRu
    ST-LINK V2:https://s.click.taobao.com/C8ftZRu

    程序

    这里就放出main.c、shtc3.c和oled.c这三个主要的代码,其他的请下载下面链接的压缩包。

    完整工程文件:https://url.zeruns.tech/EXCvo

    SHTC3和OLED模块的SCL接PB6,SDA接PB7。

    使用VSCode代替Keil实现STM32和51单片机的开发:https://blog.zeruns.tech/archives/690.html

    main.c

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "IWDG.h"
    #include "SHTC3.h"
    
    uint16_t numlen(uint16_t num);
    
    int main(void)
    {
    	IWDG_Configuration();	//初始化看门狗
    	OLED_Init();			//初始化OLED屏
    	SHTC3_I2C_Init();		//初始化SHTC3
    	
    	OLED_ShowString(1, 1, "T:");
    	OLED_ShowString(2, 1, "H:");
    	OLED_ShowString(4, 1, "err_count:");
    
    	uint32_t a=0;
    	uint16_t err_count=0;
    	
    	while (1)
    	{
    		a++;
    		OLED_ShowNum(3, 1, a, 9);
    		if(a==999999999)a=0;
    
    		float Temp,Hum;			//声明变量存放温湿度数据
    
    		if(ReadSHTC3(&Hum,&Temp))	//读取温湿度数据
    		{
    			if(Temp>=0)
    			{
    				char String[10];
    				sprintf(String, "%.2fC", Temp);	//格式化字符串输出到字符串变量
    				OLED_ShowString(1, 3, String);	//显示温度
    				/*
    				OLED_ShowNum(1,3, (uint8_t)Temp, numlen((uint8_t)Temp));//显示温度整数部分
    				OLED_ShowChar(1, 3+numlen((uint8_t)Temp), '.');			//显示小数点
    				OLED_ShowNum(1,3+numlen((uint8_t)Temp)+1, (uint8_t)(Temp*100)%100, 2);	//显示温度小数部分
    				OLED_ShowChar(1, 3+numlen((uint8_t)Temp)+1+2, 'C');		//显示符号
    				*/
    
    				sprintf(String, "%.2f%%", Hum);	//格式化字符串输出到字符串变量
    				OLED_ShowString(2, 3, String);	//显示湿度
    				/*
    				OLED_ShowNum(2,3, (uint8_t)Hum, numlen((uint8_t)Hum));	//显示湿度整数部分
    				OLED_ShowChar(2, 3+numlen((uint8_t)Hum), '.');			//显示小数点
    				OLED_ShowNum(2,3+numlen((uint8_t)Hum)+1, (uint8_t)(Hum*100)%100, 2);	//显示湿度小数部分
    				OLED_ShowChar(2, 3+numlen((uint8_t)Hum)+1+2, '%');			//显示符号
    				*/
    			}else
    			{
    				char String[10];
    				sprintf(String, "-%.2fC", Temp);//格式化字符串输出到字符串变量
    				OLED_ShowString(1, 3, String);	//显示温度
    				/*
    				OLED_ShowChar(1, 3, '-');			//显示负号
    				OLED_ShowNum(1,3+1, (uint8_t)Temp, numlen((uint8_t)Temp));	//显示温度整数部分
    				OLED_ShowChar(1, 3+1+numlen((uint8_t)Temp), '.');			//显示小数点
    				OLED_ShowNum(1,3+1+numlen((uint8_t)Temp)+1, (uint8_t)(Temp*100)%100, 2);	//显示温度小数部分
    				OLED_ShowChar(1, 3+1+numlen((uint8_t)Temp)+1+2, 'C');		//显示符号
    				*/
    
    				sprintf(String, "%.2f%%", Hum);	//格式化字符串输出到字符串变量
    				OLED_ShowString(2, 3, String);	//显示湿度
    				/*
    				OLED_ShowNum(2,3, (uint8_t)Hum, numlen((uint8_t)Hum));	//显示湿度整数部分
    				OLED_ShowChar(2, 3+numlen((uint8_t)Hum), '.');			//显示小数点
    				OLED_ShowNum(2,3+numlen((uint8_t)Hum)+1, (uint8_t)(Hum*100)%100, 2);	//显示湿度小数部分
    				OLED_ShowChar(2, 3+numlen((uint8_t)Hum)+1+2, '%');			//显示符号
    				*/
    			}
    		}
    		else
    		{
    			err_count++;
    			OLED_ShowNum(4,11, err_count, numlen(err_count));	//显示错误次数计数
    		}
    	/*
    	https://blog.zeruns.tech
    	*/
    
    		Delay_ms(100);	//延时100毫秒
    
    		IWDG_FeedDog();	//喂狗(看门狗,超过1秒没有执行喂狗则自动复位)
    	}
    }
    
    /**
      * @brief  计算整数长度
      * @param  num 要计算长度的整数
      * @retval 长度值
      */
    uint16_t numlen(uint16_t num)
    {
        uint16_t len = 0;        // 初始长度为0
        for(; num > 0; ++len)    // 判断num是否大于0,否则长度+1
            num /= 10;	         // 使用除法进行运算,直到num小于1
        return len;              // 返回长度的值
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101

    SHTC3.c

    #include "stm32f10x.h"
    #include "Delay.h"
    
    /*SHTC3地址*/
    #define SHTC3_ADDRESS 0xE0
    
    /*设置使用哪一个I2C*/
    #define I2Cx I2C1
    
    /*
    https://blog.zeruns.tech
    */
    
    /**
      * @brief  CRC校验,CRC多项式为:x^8+x^5+x^4+1,即0x31
      * @param  DAT 要校验的数据
      * @retval 校验码
      */
    uint8_t SHTC3_CRC_CAL(uint16_t DAT)
    {
    	uint8_t i,t,temp;
    	uint8_t CRC_BYTE;
    
    	CRC_BYTE = 0xFF;
    	temp = (DAT>>8) & 0xFF;
    
    	for(t = 0; t < 2; t++)
    	{
    		CRC_BYTE ^= temp;
    		for(i = 0;i < 8;i ++)
    		{
    			if(CRC_BYTE & 0x80)
    			{
    				CRC_BYTE <<= 1;
    				CRC_BYTE ^= 0x31;
    			}
    			else
    			{
    				CRC_BYTE <<= 1;
    			}
    		}
    
    		if(t == 0)
    		{
    			temp = DAT & 0xFF;
    		}
    	}
    
    	return CRC_BYTE;
    }
    
    /*发送起始信号*/
    void SHTC3_I2C_START(){
        while( I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));//等待总线空闲
    	I2C_GenerateSTART(I2Cx, ENABLE);//发送起始信号
    	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);//检测EV5事件
    }
    
    /*发送停止信号*/
    void SHTC3_I2C_STOP(){
        I2C_GenerateSTOP(I2Cx, ENABLE);//发送停止信号
    }
    
    /**
      * @brief  发送两个字节数据
      * @param  MSB 高8位
      * @param  LSB 低8位
      * @retval 无
      */
    void SHTC3_WriteByte(uint8_t MSB,uint8_t LSB)
    {
    	SHTC3_I2C_START();  //发送起始信号
    	
    	I2C_Send7bitAddress(I2Cx, SHTC3_ADDRESS, I2C_Direction_Transmitter);    //发送设备写地址
    	while(I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);  //检测EV6事件
    
        I2C_SendData(I2Cx, MSB);//发送高8位数据
    	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//检测EV8事件
       
    	I2C_SendData(I2Cx, LSB);//发送低8位数据
    	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//检测EV8事件
    	
    	I2C_GenerateSTOP(I2Cx, ENABLE);//发送停止信号
    	
    }
    
    /**
      * @brief  读取数据
      * @retval 读取到的字节数据
      */
    uint8_t SHTC3_ReadData()
    {
        while (!I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_BYTE_RECEIVED));//检测EV7事件
    	return I2C_ReceiveData(I2Cx);//读取数据并返回
    }
    
    /*软件复位SHTC3*/
    void SHTC3_SoftReset(void)                    
    {
        SHTC3_WriteByte(0x80,0x5D);    //重置SHTC3
    }
    
    /*引脚初始化*/
    void SHTC3_I2C_Init(void)
    {
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);	//使能I2C1时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能GPIOB时钟
     
    	/*STM32F103芯片的硬件I2C1: PB6 -- SCL; PB7 -- SDA */
    	GPIO_InitTypeDef  GPIO_InitStructure;               //定义结构体配置GPIO
    	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;		//设置输出模式为开漏输出,需接上拉电阻
    	GPIO_Init(GPIOB, &GPIO_InitStructure);              //初始化GPIO
    	
    	I2C_DeInit(I2Cx);	//将外设I2C寄存器重设为缺省值
    	I2C_InitTypeDef  I2C_InitStructure;                 //定义结构体配置I2C
    	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;			//工作模式
    	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	//时钟占空比,Tlow/Thigh = 2
    	I2C_InitStructure.I2C_OwnAddress1 = 0x30;	//主机的I2C地址,用不到则随便写,无影响
    	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	//使能应答位
    	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//设置地址长度7位
    	I2C_InitStructure.I2C_ClockSpeed = 400000;	//I2C传输速度,400K,根据自己所用芯片手册查看支持的速度。	
    	I2C_Init(I2Cx, &I2C_InitStructure);         //初始化I2C
    
    	I2C_Cmd(I2Cx, ENABLE);  //启用I2C
    
    	SHTC3_WriteByte(0X35,0X17);//唤醒SHTC3
    	
    	Delay_us(200);
    }
    
    /**
      * @brief  读取SHTC3数据
      * @param  *Hum 湿度
      * @param  *Temp 温度
      * @retval 1 - 读取成功;0 - 读取失败
      */
    uint8_t ReadSHTC3(float *Hum,float *Temp)
    {
        uint16_t HumData,TempData,HumCRC,TempCRC;//声明变量存放读取的数据
        
        /*SHTC3_WriteByte(0X35,0X17);//唤醒SHTC3
    	
    	Delay_us(300);*/
        
        SHTC3_WriteByte(0X5C,0X24);//发送指令,先读取湿度数据,时钟拉伸(测量数据期间拉低SCL时钟线,占用总线)
    	
        SHTC3_I2C_START();//发送起始信号
        
    	I2C_Send7bitAddress(I2Cx,SHTC3_ADDRESS,I2C_Direction_Receiver);//发送设备读地址
    	
    	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED )==ERROR);//检测EV6事件
    	
        HumData = SHTC3_ReadData(); //读取湿度高8位数据
        HumData=HumData<<8;         //左移8位
    	HumData |= SHTC3_ReadData();//读取湿度低8位数据
        HumCRC = SHTC3_ReadData();  //读取湿度CRC校验数据
    
        TempData = SHTC3_ReadData();//读取温度高8位数据
        TempData=TempData<<8;       //左移8位
    	TempData |= SHTC3_ReadData();//读取温度低8位数据
        TempCRC = SHTC3_ReadData(); //读取温度CRC校验数据
    
        SHTC3_I2C_STOP();   //发送停止信号
    		
    	//SHTC3_WriteByte(0XB0,0X98);//发送休眠指令
    
        if( SHTC3_CRC_CAL(HumData)==HumCRC && SHTC3_CRC_CAL(TempData)==TempCRC ){   //对接收到数据进行CRC校验
           *Hum = (float)HumData*100/65536;        //将接收的16位二进制数据转换为10进制湿度数据
           *Temp = (float)TempData*175/65536-45;   //将接收的16位二进制数据转换为10进制温度数据
           return 1;
        }
        else{
            return 0;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177

    OLED.c

    #include "stm32f10x.h"
    #include "OLED_Font.h"
    
    /*OLED屏地址*/
    #define OLED_ADDRESS 0x78
    
    /*设置哪一个使用I2C*/
    #define I2Cx I2C1
    
    /*
    https://blog.zeruns.tech
    */
    
    /*引脚初始化*/
    void OLED_I2C_Init(void)
    {
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);	//使能I2C1时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能GPIOB时钟
     
    	/*STM32F103芯片的硬件I2C: PB6 -- SCL; PB7 -- SDA */
    	GPIO_InitTypeDef  GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;		//设置输出模式为开漏输出,需接上拉电阻
    	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	
    	I2C_DeInit(I2Cx);	//将外设I2C寄存器重设为缺省值
    	I2C_InitTypeDef  I2C_InitStructure;
    	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;			//工作模式
    	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	//时钟占空比,Tlow/Thigh = 2
    	I2C_InitStructure.I2C_OwnAddress1 = 0x30;	//主机的I2C地址,用不到则随便写,无影响
    	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	//使能应答位
    	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//设置地址长度7位
    	I2C_InitStructure.I2C_ClockSpeed = 400000;	//I2C传输速度,400K,根据自己所用芯片手册查看支持的速度。	
    	I2C_Init(I2Cx, &I2C_InitStructure);
    
    	I2C_Cmd(I2Cx, ENABLE);
    }
    
    void I2C_WriteByte(uint8_t addr,uint8_t data)
    {
    	
    	while( I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
    	
    	//发送起始信号
    	I2C_GenerateSTART(I2Cx, ENABLE);
    	//检测EV5事件
    	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
    	//发送设备写地址
    	I2C_Send7bitAddress(I2Cx, OLED_ADDRESS, I2C_Direction_Transmitter);
    	//检测EV6事件
    	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
    	
    	//发送要操作设备内部的地址
    	I2C_SendData(I2Cx, addr);
    	//检测EV8_2事件
    	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    	
    	I2C_SendData(I2Cx, data);//发送数据
    	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    
    	//发送停止信号
    	I2C_GenerateSTOP(I2Cx, ENABLE);
    }
     
    /**
      * @brief  OLED写命令
      * @param  Command 要写入的命令
      * @retval 无
      */
    void OLED_WriteCommand(unsigned char Command)//写命令
    {
    	I2C_WriteByte(0x00, Command);
    }
     
    /**
      * @brief  OLED写数据
      * @param  Data 要写入的数据
      * @retval 无
    */
    void OLED_WriteData(unsigned char Data)//写数据
    {
    	I2C_WriteByte(0x40, Data);
    }
    
    /**
      * @brief  OLED设置光标位置
      * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
      * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
      * @retval 无
      */
    void OLED_SetCursor(uint8_t Y, uint8_t X)
    {
    	OLED_WriteCommand(0xB0 | Y);					//设置Y位置
    	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置低4位
    	OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置高4位
    }
    
    /**
      * @brief  OLED清屏
      * @param  无
      * @retval 无
      */
    void OLED_Clear(void)
    {  
    	uint8_t i, j;
    	for (j = 0; j < 8; j++)
    	{
    		OLED_SetCursor(j, 0);
    		for(i = 0; i < 128; i++)
    		{
    			OLED_WriteData(0x00);
    		}
    	}
    }
    
    /**
      * @brief  OLED部分清屏
      * @param  Line 行位置,范围:1~4
      * @param  start 列开始位置,范围:1~16
      * @param  end 列开始位置,范围:1~16
      * @retval 无
      */
    void OLED_Clear_Part(uint8_t Line, uint8_t start, uint8_t end)
    {  
    	uint8_t i,Column;
    	for(Column = start; Column <= end; Column++)
    	{
    		OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//设置光标位置在上半部分
    		for (i = 0; i < 8; i++)
    		{
    			OLED_WriteData(0x00);			//显示上半部分内容
    		}
    		OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//设置光标位置在下半部分
    		for (i = 0; i < 8; i++)
    		{
    			OLED_WriteData(0x00);		//显示下半部分内容
    		}
    	}
    }
    
    /**
      * @brief  OLED显示一个字符
      * @param  Line 行位置,范围:1~4
      * @param  Column 列位置,范围:1~16
      * @param  Char 要显示的一个字符,范围:ASCII可见字符
      * @retval 无
      */
    void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
    {      	
    	uint8_t i;
    	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//设置光标位置在上半部分
    	for (i = 0; i < 8; i++)
    	{
    		OLED_WriteData(OLED_F8x16[Char - ' '][i]);			//显示上半部分内容
    	}
    	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//设置光标位置在下半部分
    	for (i = 0; i < 8; i++)
    	{
    		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);		//显示下半部分内容
    	}
    }
    
    /**
      * @brief  OLED显示字符串
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  String 要显示的字符串,范围:ASCII可见字符
      * @retval 无
      */
    void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
    {
    	uint8_t i;
    	for (i = 0; String[i] != '\0'; i++)
    	{
    		OLED_ShowChar(Line, Column + i, String[i]);
    	}
    }
    
    /**
      * @brief  OLED次方函数
      * @retval 返回值等于X的Y次方
      */
    uint32_t OLED_Pow(uint32_t X, uint32_t Y)
    {
    	uint32_t Result = 1;
    	while (Y--)
    	{
    		Result *= X;
    	}
    	return Result;
    }
    
    /**
      * @brief  OLED显示数字(十进制,正数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~4294967295
      * @param  Length 要显示数字的长度,范围:1~10
      * @retval 无
      */
    void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
    	uint8_t i;
    	for (i = 0; i < Length; i++)							
    	{
    		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
    	}
    }
    
    /**
      * @brief  OLED显示数字(十进制,带符号数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:-2147483648~2147483647
      * @param  Length 要显示数字的长度,范围:1~10
      * @retval 无
      */
    void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
    {
    	uint8_t i;
    	uint32_t Number1;
    	if (Number >= 0)
    	{
    		OLED_ShowChar(Line, Column, '+');
    		Number1 = Number;
    	}
    	else
    	{
    		OLED_ShowChar(Line, Column, '-');
    		Number1 = -Number;
    	}
    	for (i = 0; i < Length; i++)							
    	{
    		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
    	}
    }
    
    /**
      * @brief  OLED显示数字(十六进制,正数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~0xFFFFFFFF
      * @param  Length 要显示数字的长度,范围:1~8
      * @retval 无
      */
    void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
    	uint8_t i, SingleNumber;
    	for (i = 0; i < Length; i++)							
    	{
    		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
    		if (SingleNumber < 10)
    		{
    			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
    		}
    		else
    		{
    			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
    		}
    	}
    }
    
    /**
      * @brief  OLED显示数字(二进制,正数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
      * @param  Length 要显示数字的长度,范围:1~16
      * @retval 无
      */
    void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
    	uint8_t i;
    	for (i = 0; i < Length; i++)							
    	{
    		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
    	}
    }
    
    /**
      * @brief  OLED初始化
      * @param  无
      * @retval 无
      */
    void OLED_Init(void)
    {
    	uint32_t i, j;
    	
    	for (i = 0; i < 1000; i++)			//上电延时
    	{
    		for (j = 0; j < 1000; j++);
    	}
    	
    	OLED_I2C_Init();			//端口初始化
    	
    	OLED_WriteCommand(0xAE);	//关闭显示
    	
    	OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率
    	OLED_WriteCommand(0x80);
    	
    	OLED_WriteCommand(0xA8);	//设置多路复用率
    	OLED_WriteCommand(0x3F);
    	
    	OLED_WriteCommand(0xD3);	//设置显示偏移
    	OLED_WriteCommand(0x00);
    	
    	OLED_WriteCommand(0x40);	//设置显示开始行
    	
    	OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置
    	
    	OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置
    
    	OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置
    	OLED_WriteCommand(0x12);
    	
    	OLED_WriteCommand(0x81);	//设置对比度控制
    	OLED_WriteCommand(0xCF);
    
    	OLED_WriteCommand(0xD9);	//设置预充电周期
    	OLED_WriteCommand(0xF1);
    
    	OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别
    	OLED_WriteCommand(0x30);
    
    	OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭
    
    	OLED_WriteCommand(0xA6);	//设置正常/倒转显示
    
    	OLED_WriteCommand(0x8D);	//设置充电泵
    	OLED_WriteCommand(0x14);
    
    	OLED_WriteCommand(0xAF);	//开启显示
    		
    	OLED_Clear();				//OLED清屏
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336

    部分内容参考以下两篇文章:

    https://blog.csdn.net/mj475002864/article/details/114027993

    https://blog.csdn.net/k666499436/article/details/124686559

    推荐阅读

  • 相关阅读:
    机器学习入门--门控循环单元(GRU)原理与实践
    Django 模型相关
    近期部分航院部分消息(主要是长长见识摘录的)
    综合练习
    docker+nginx 安装部署修改资源目录配置文件和容器端口信息
    Java IP归属地查询(离线方式+在线方式,内附查询IP方法)
    清除linux内存buff/cache
    C语言:如何给全局变量起一个别名?
    FPGA结构分析——IDDR,网口储备点1
    Day726.Java平台模块系统 -Java8后最重要新特性
  • 原文地址:https://blog.csdn.net/u012513463/article/details/127688867