stm32采集显示温湿度&通过IIC实现OLED屏显示

stm32采集显示温湿度&通过IIC实现OLED屏显示

  • 一、任务要求
  • 二、基于I2C协议通过AHT20及串口采集输出温湿度数据
  • (一)“软件I2C”和“硬件I2C”
  • (二)采集输出温湿度数据
  • 1.项目创建及修改
  • 2.线路连接及烧录
  • 3.实现现象
  • 三、使用IIC接口实现OLED显示(U8g2)
  • (一)U8g2移植
  • (二)显示学号和姓名
  • 1.代码修改
  • 2.线路连接及烧录
  • 3.实现现象
  • (三)温湿度
  • 1.代码修改
  • 2.线路连接
  • 3.实现现象
  • (四)滑动字符
  • 1.代码修改
  • 2.实现现象
  • 四、总结
  • 一、任务要求

    1.学习I2C总线通信协议,使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。具体任务:
    1)解释什么是“软件I2C”和“硬件I2C”?
    2)阅读AHT20数据手册,编程实现:每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机。

    2.理解OLED屏显和汉字点阵编码原理,使用STM32F103的SPI或IIC接口实现以下功能:

    1. 显示自己的学号和姓名;
    2. 显示AHT20的温度和湿度;
    3. 上下或左右的滑动显示长字符,比如“Hello,欢迎来到重庆交通大学物联网205实训室!”或者一段歌词或诗词(使用硬件刷屏模式)。

    二、基于I2C协议通过AHT20及串口采集输出温湿度数据

    (一)“软件I2C”和“硬件I2C”

    1、什么是12C协议 :
    I2C总线是一种双向的同步串行总线,它支持设备之间的短距离通信,经常用于处理器和一些外围设备之间的接口通信。I2C总线的标准通信速率是100Kbps,快速模式是400Kbps,高速模式支持3.4Mbps。I2C总线支持多设备的通信,而且各个设备之间的SCL和SDA线都是线与关系。I2C总线上扩展的器件的数量主要由电容负载来决定,其负载能力为400pF。I2C总线具有极低的电流消耗。

    2、I2C的两种方式——硬件I2C和软件I2C:
    硬件I2C:
    一块硬件电路,硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,硬件(固件)I2C是直接调用内部寄存器进行配置直接利用 STM32 芯片中的硬件 I2C 外设。

    硬件I2C的使用:
    只要配置好对应的寄存器,外设就会产生标准串口协议的时序。在初始化好 I2C 外设后,只需要把某寄存器位置 1,此时外设就会控制对应的 SCL 及 SDA 线自动产生 I2C 起始信号,不需要内核直接控制引脚的电平。

    软件I2C:
    软件IIC 软件IIC通信指的是用单片机的两个I/O端口模拟出来的IIC,用软件控制管脚状态以模拟I2C通信波形,软件模拟寄存器的工作方式。直接使用 CPU 内核按照 I2C 协议的要求控制 GPIO 输出高低电平,从而模拟I2C。

    软件I2C的使用:
    需要在控制产生 I2C 的起始信号时,控制作为 SCL 线的 GPIO 引脚输出高电平,然后控制作为 SDA 线的 GPIO 引脚在此期间完成由高电平至低电平的切换,最后再控制SCL 线切换为低电平,这样就输出了一个标准的 I2C 起始信号。

    两者的差别:
    硬件 I2C 直接使用外设来控制引脚,可以减轻 CPU 的负担。不过使用硬件I2C 时必须使用某些固定的引脚作为 SCL 和 SDA,软件模拟 I2C 则可以使用任意 GPIO 引脚,相对比较灵活。对于硬件I2C用法比较复杂,软件I2C的流程更清楚一些。如果要详细了解I2C的协议,使用软件I2C可能更好的理解这个过程。在使用I2C过程,硬件I2C可能通信更加快,更加稳定。

    (二)采集输出温湿度数据

    1.项目创建及修改

  • stm32cubemx
  • 配置RCC:

    配置SYS:

    配置USART1:

    配置I2C1(接温湿度传感器模块):

    配置时钟树:


  • keil
  • 首先创建一个组,命名为AHT20

    在里面添加文件aht.c和aht.h
    aht.c

    #include "aht20.h"
    
    #define AHT20_ADDRESS 0x70 // 从机地址
    //AHT20 的驱动程序
     void AHT20_Init () //AHT20初始化函数  记住要在"aht20.h"中声明
    {
      uint8_t readBuffer;//用于接收状态信息
      HAL_Delay(40);
      HAL_I2C_Master_Receive(&hi2c1, AHT20_ADDRESS, &readBuffer, 1, HAL_MAX_DELAY);//I2C读取函数,读数据函数 readBuffer此时获得了一个字节的状态字。
      if((readBuffer & 0x08) == 0x00) //判断第三位是否为0 发送0xBE命令初始化
      {
    	  uint8_t sendBuffer [3] = {0xBE , 0x08 , 0x00};
    	  HAL_I2C_Master_Transmit(&hi2c1, AHT20_ADDRESS, sendBuffer, 3, HAL_MAX_DELAY);//I2C发送函数
      }
    
    }
    
     void AHT20_Read(float *Temperature , float *Humidity) //AHT20读取温度湿度函数  记住要在"aht20.h"中声明
    {
    	uint8_t sendBuffer [3] = {0xAC , 0x33 , 0x00};
    	uint8_t readBuffer [6];
    	HAL_I2C_Master_Transmit(&hi2c1, AHT20_ADDRESS, sendBuffer, 3, HAL_MAX_DELAY);
    	HAL_Delay(75);
    	HAL_I2C_Master_Receive(&hi2c1, AHT20_ADDRESS, readBuffer, 6, HAL_MAX_DELAY);
    	if((readBuffer[0] & 0x80) == 0x00)
    	{
    		uint32_t date = 0;//接收温湿度需要2个半字节 所以要32
    		date = ((uint32_t )readBuffer[3] >> 4) + ((uint32_t )readBuffer[2] << 4) + ((uint32_t )readBuffer[1] << 12);//对数据进行移位拼接.
    		*Humidity = date * 100.0f / (1 << 20);//(1 << 20) 意为2的20次方. 乘100.0可以表示为百分数
    
    		date = (((uint32_t )readBuffer[3] & 0x0F)<< 16) + ((uint32_t )readBuffer[4] << 8) + (uint32_t )readBuffer[5];//& 0x0F: 将这个无符号整数与十六进制数0x0F进行按位与操作。0x0F的二进制表示为00001111,这个操作会保留readBuffer[3]的低四位,即将高四位清零。
    		*Temperature = date * 200.0f / (1 << 20) - 50;
    	}
    }
    

    aht.h

    /*
     * aht20.h
     *
     *  Created on: Apr 25, 2024
     *      Author: lenovo
     */
    
    #ifndef INC_AHT20_H_
    #define INC_AHT20_H_
    
    #include "i2c.h"
     void AHT20_Init (void);
    
     void AHT20_Read(float *Temperature , float *Humidity);
    #endif /* INC_AHT20_H_ */
    
    

    找到文件的位置创建AHT20文件夹,然后将创建的两个文件剪切到里面,再在keil中添加读取文件的路径,将原本两个文件删除,重新添加文件,找到文件所在位置重新添加进去

    main.c

    /* USER CODE BEGIN Header */
    /**
      ******************************************************************************
      * @file           : main.c
      * @brief          : Main program body
      ******************************************************************************
      * @attention
      *
      * Copyright (c) 2024 STMicroelectronics.
      * All rights reserved.
      *
      * This software is licensed under terms that can be found in the LICENSE file
      * in the root directory of this software component.
      * If no LICENSE file comes with this software, it is provided AS-IS.
      *
      ******************************************************************************
      */
    /* USER CODE END Header */
    /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    #include "i2c.h"
    #include "usart.h"
    #include "gpio.h"
    
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    #include "i2c.h"
    #include <stdio.h>
    #include "string.h"
    #include "aht20.h"
    /* USER CODE END Includes */
    
    /* Private typedef -----------------------------------------------------------*/
    /* USER CODE BEGIN PTD */
    
    /* USER CODE END PTD */
    
    /* Private define ------------------------------------------------------------*/
    /* USER CODE BEGIN PD */
    
    /* USER CODE END PD */
    
    /* Private macro -------------------------------------------------------------*/
    /* USER CODE BEGIN PM */
    
    /* USER CODE END PM */
    
    /* Private variables ---------------------------------------------------------*/
    
    /* USER CODE BEGIN PV */
    
    /* USER CODE END PV */
    
    /* Private function prototypes -----------------------------------------------*/
    void SystemClock_Config(void);
    /* USER CODE BEGIN PFP */
    
    /* USER CODE END PFP */
    
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
    int main(void)
    {
      /* USER CODE BEGIN 1 */
    
      /* USER CODE END 1 */
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_I2C1_Init();
      MX_USART1_UART_Init();
      /* USER CODE BEGIN 2 */
      AHT20_Init ();//初始化AHT20
    
      float temperature , humidity ;
      char message [50];
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
    	  AHT20_Read( &temperature , &humidity); //读取AHT20
    	  sprintf(message ,"温度: %f℃ , 湿度: %f.\r\n",temperature , humidity);//拼接
    	  HAL_UART_Transmit(&huart1, (uint8_t*)message,strlen(message) , HAL_MAX_DELAY);//串口发???函??
    	  HAL_Delay(1000);
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    
    /**
      * @brief System Clock Configuration
      * @retval None
      */
    void SystemClock_Config(void)
    {
      RCC_OscInitTypeDef RCC_OscInitStruct = {0};
      RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
      /** Initializes the RCC Oscillators according to the specified parameters
      * in the RCC_OscInitTypeDef structure.
      */
      RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
      RCC_OscInitStruct.HSIState = RCC_HSI_ON;
      RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
      if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
      {
        Error_Handler();
      }
    
      /** Initializes the CPU, AHB and APB buses clocks
      */
      RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
      RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
      if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
      {
        Error_Handler();
      }
    }
    
    /* USER CODE BEGIN 4 */
    
    /* USER CODE END 4 */
    
    /**
      * @brief  This function is executed in case of error occurrence.
      * @retval None
      */
    void Error_Handler(void)
    {
      /* USER CODE BEGIN Error_Handler_Debug */
      /* User can add his own implementation to report the HAL error return state */
      __disable_irq();
      while (1)
      {
      }
      /* USER CODE END Error_Handler_Debug */
    }
    
    #ifdef  USE_FULL_ASSERT
    /**
      * @brief  Reports the name of the source file and the source line number
      *         where the assert_param error has occurred.
      * @param  file: pointer to the source file name
      * @param  line: assert_param error line source number
      * @retval None
      */
    void assert_failed(uint8_t *file, uint32_t line)
    {
      /* USER CODE BEGIN 6 */
      /* User can add his own implementation to report the file name and line number,
         ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
      /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */
    

    2.线路连接及烧录

    官方代码使用的是PB14,PB15引脚需要修改对应的引脚才可以正常使用
    1.AHT20接线图:

    2.整体连接

    3.实现现象

    打开串口助手,打开串口接收数据

    温湿度

    三、使用IIC接口实现OLED显示(U8g2)

    (一)U8g2移植

    可以不参考OLED厂家给的代码例子,用目前最为主流的0.96寸OLED的GUI开源图形库图形库u8g2 来处理OLED,方法更通用。

    1.U8g2库开源网址:https://github.com/olikraus/u8g2
    先在github中下载好U8g2库,解压备用。

    2.stm32cubeMX

  • 配置RCC
  • 配置SYS
  • 配置I2C2
  • 配置TIM1
  • 配置时钟树
  • 配置工程

  • 3.精简U8g2库
    由于我们是下载了U8g2库中的所有文件,但是其中我们只需要很小一部分的文件,为了减小整个工程的代码体积,在移植U8g2时,可以删除一些无用的文件。

  • 精简U8g2库中的csrc文件
    源码中也包含了各个驱动对应的文件,这些驱动文件通常是u8x8_d_xxx.c,xxx包括驱动的型号和屏幕分辨率。
    ssd1306驱动芯片的OLED,使用u8x8_d_ssd1306_128x64_noname.c这个文件,其它的屏幕驱动和分辨率的文件可以删掉(注意不要删错了,删掉的文件名字大概都长u8x8_d_xxx.c,xxx这样)。
  • 精简u8g2_d_setup.c文件
    由于我只使用IIC接口,只会用到该c文件中的一个函数u8g2_Setup_ssd1306_i2c_128x64_noname_f**,所以其他多余的函数就可以删掉了(注意注意,千万不要删错了!!!中间有一个i2c,末尾是f!!!!)

  • 精简u8g2_d_memory.c
    由于用到的u8g2_Setup_ssd1306_i2c_128x64_noname_f函数中,只调用了u8g2_d_memory.c中的u8g2_m_16_8_f这个函数,所以只留下这个函数,其它的函数一定要删掉或注释掉,否则编译时很可能会提示内存不足!!
  • 4.keil移植
    打开刚刚cube创建好的项目,在左侧Keil工程目录添加自己精简后U8g2库文件中的csrc文件(如果最开始已经添加,就删掉重新添加一遍),然后再添加U8g2的头文件搜寻目录(U8g2里面都是csrc文件里面的文件,读者朋友可以根据自己的需要删减),如下:


    5.编写移植函数
    新建一个HARDWARE组,在里面创建添加如下四个文件

  • stm32_u8g2.h
  • #ifndef __STM32_U8G2_H
    #define __STM32_U8G2_H
     
    /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    #include "u8g2.h"
    /* USER CODE BEGIN Includes */
     
    /* USER CODE END Includes */
     
     
     
    /* USER CODE BEGIN Private defines */
     
    /* USER CODE END Private defines */
    #define u8         unsigned char  // ?unsigned char ????
    #define MAX_LEN    128  //
    #define OLED_ADDRESS  0x78 // oled
    #define OLED_CMD   0x00  // 
    #define OLED_DATA  0x40  // 
     
    /* USER CODE BEGIN Prototypes */
    uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
    uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
    void u8g2Init(u8g2_t *u8g2);
    void draw(u8g2_t *u8g2);
    void testDrawPixelToFillScreen(u8g2_t *u8g2);
     
    #endif
    
  • stm32_u8g2.c
  • #include "stm32_u8g2.h"
    #include "tim.h"
    #include "i2c.h"
     
     
    uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
    {
        /* u8g2/u8x8 will never send more than 32 bytes between START_TRANSFER and END_TRANSFER */
        static uint8_t buffer[128];
        static uint8_t buf_idx;
        uint8_t *data;
     
        switch (msg)
        {
        case U8X8_MSG_BYTE_INIT:
        {
            /* add your custom code to init i2c subsystem */
            MX_I2C2_Init(); //I2C初始化
        }
        break;
     
        case U8X8_MSG_BYTE_START_TRANSFER:
        {
            buf_idx = 0;
        }
        break;
     
        case U8X8_MSG_BYTE_SEND:
        {
            data = (uint8_t *)arg_ptr;
     
            while (arg_int > 0)
            {
                buffer[buf_idx++] = *data;
                data++;
                arg_int--;
            }
        }
        break;
     
        case U8X8_MSG_BYTE_END_TRANSFER:
        {
            if (HAL_I2C_Master_Transmit(&hi2c2, OLED_ADDRESS, buffer, buf_idx, 1000) != HAL_OK)
                return 0;
        }
        break;
     
        case U8X8_MSG_BYTE_SET_DC:
            break;
     
        default:
            return 0;
        }
     
        return 1;
    }
     
     
     
    uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
    {
        switch (msg)
        {
        case U8X8_MSG_DELAY_100NANO: // delay arg_int * 100 nano seconds
            __NOP();
            break;
        case U8X8_MSG_DELAY_10MICRO: // delay arg_int * 10 micro seconds
            for (uint16_t n = 0; n < 320; n++)
            {
                __NOP();
            }
            break;
        case U8X8_MSG_DELAY_MILLI: // delay arg_int * 1 milli second
            HAL_Delay(1);
            break;
        case U8X8_MSG_DELAY_I2C: // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz
            Tims_delay_us(5);
            break;                    // arg_int=1: delay by 5us, arg_int = 4: delay by 1.25us
        case U8X8_MSG_GPIO_I2C_CLOCK: // arg_int=0: Output low at I2C clock pin
            break;                    // arg_int=1: Input dir with pullup high for I2C clock pin
        case U8X8_MSG_GPIO_I2C_DATA:  // arg_int=0: Output low at I2C data pin
            break;                    // arg_int=1: Input dir with pullup high for I2C data pin
        case U8X8_MSG_GPIO_MENU_SELECT:
            u8x8_SetGPIOResult(u8x8, /* get menu select pin state */ 0);
            break;
        case U8X8_MSG_GPIO_MENU_NEXT:
            u8x8_SetGPIOResult(u8x8, /* get menu next pin state */ 0);
            break;
        case U8X8_MSG_GPIO_MENU_PREV:
            u8x8_SetGPIOResult(u8x8, /* get menu prev pin state */ 0);
            break;
        case U8X8_MSG_GPIO_MENU_HOME:
            u8x8_SetGPIOResult(u8x8, /* get menu home pin state */ 0);
            break;
        default:
            u8x8_SetGPIOResult(u8x8, 1); // default return value
            break;
        }
        return 1;
    }
     
    //U8g2的初始化,需要调用下面这个u8g2_Setup_ssd1306_128x64_noname_f函数,该函数的4个参数含义:
    //u8g2:传入的U8g2结构体
    //U8G2_R0:默认使用U8G2_R0即可(用于配置屏幕是否要旋转)
    //u8x8_byte_sw_i2c:使用软件IIC驱动,该函数由U8g2源码提供
    //u8x8_gpio_and_delay:就是上面我们写的配置函数
     
    void u8g2Init(u8g2_t *u8g2)
    {
    	u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_hw_i2c, u8x8_gpio_and_delay); // 初始化u8g2 结构体
    	u8g2_InitDisplay(u8g2);                                                                       // 
    	u8g2_SetPowerSave(u8g2, 0);                                                                   // 
    	u8g2_ClearBuffer(u8g2);
    }
     
     
    void draw(u8g2_t *u8g2)
    {
    	u8g2_ClearBuffer(u8g2); 
    	
        u8g2_SetFontMode(u8g2, 1); /*字体模式选择*/
        u8g2_SetFontDirection(u8g2, 0); /*字体方向选择*/
        u8g2_SetFont(u8g2, u8g2_font_inb24_mf); /*字库选择*/
        u8g2_DrawStr(u8g2, 0, 20, "U");
        
        u8g2_SetFontDirection(u8g2, 1);
        u8g2_SetFont(u8g2, u8g2_font_inb30_mn);
        u8g2_DrawStr(u8g2, 21,8,"8");
            
        u8g2_SetFontDirection(u8g2, 0);
        u8g2_SetFont(u8g2, u8g2_font_inb24_mf);
        u8g2_DrawStr(u8g2, 51,30,"g");
        u8g2_DrawStr(u8g2, 67,30,"\xb2");
        
        u8g2_DrawHLine(u8g2, 2, 35, 47);
        u8g2_DrawHLine(u8g2, 3, 36, 47);
        u8g2_DrawVLine(u8g2, 45, 32, 12);
        u8g2_DrawVLine(u8g2, 46, 33, 12);
      
        u8g2_SetFont(u8g2, u8g2_font_4x6_tr);
        u8g2_DrawStr(u8g2, 1,54,"github.com/olikraus/u8g2");
    		
    	u8g2_SendBuffer(u8g2);
    	HAL_Delay(1000);
    }
     
    //画点填充
    void testDrawPixelToFillScreen(u8g2_t *u8g2)
    {
      int t = 1000;
    	u8g2_ClearBuffer(u8g2);
     
      for (int j = 0; j < 64; j++)
      {
        for (int i = 0; i < 128; i++)
        {
          u8g2_DrawPixel(u8g2,i, j);
        }
      }
      HAL_Delay(1000);
    }
    
  • test.h
  • #ifndef __TEST_H
    #define __TEST_H
     
    #include "main.h"
    #include "u8g2.h"
     
    void testDrawProcess(u8g2_t *u8g2);
    void testShowFont(u8g2_t *u8g2);
    void testDrawFrame(u8g2_t *u8g2);
    void testDrawRBox(u8g2_t *u8g2);
    void testDrawCircle(u8g2_t *u8g2);
    void testDrawFilledEllipse(u8g2_t *u8g2);
    void testDrawMulti(u8g2_t *u8g2);
    void testDrawXBM(u8g2_t *u8g2);
     
    void u8g2DrawTest(u8g2_t *u8g2);
     
    #endif
    
  • test.c
  • #include "test.h"
     
    //---------------U8g2测试函数
     
    #define SEND_BUFFER_DISPLAY_MS(u8g2, ms)\
      do {\
        u8g2_SendBuffer(u8g2); \
        HAL_Delay(ms);\
      }while(0);
     
     
    //进度条显示
    void testDrawProcess(u8g2_t *u8g2)
    {
    	for(int i=10;i<=80;i=i+2)
    	{
    		u8g2_ClearBuffer(u8g2); 
    			
    		char buff[20];
    		sprintf(buff,"%d%%",(int)(i/80.0*100));
    		
    		u8g2_SetFont(u8g2,u8g2_font_ncenB12_tf);
    		u8g2_DrawStr(u8g2,16,32,"STM32 U8g2");//字符显示
    		
    		u8g2_SetFont(u8g2,u8g2_font_ncenB08_tf);
    		u8g2_DrawStr(u8g2,100,49,buff);//当前进度显示
    		
    		u8g2_DrawRBox(u8g2,16,40,i,10,4);//圆角填充框矩形框
    		u8g2_DrawRFrame(u8g2,16,40,80,10,4);//圆角矩形
    		
    		u8g2_SendBuffer(u8g2);
    	}
    	HAL_Delay(500);
    }
     
     
    //字体测试 数字英文可选用 u8g2_font_ncenB..(粗) 系列字体
    //u8g2_font_unifont_t_symbols/u8g2_font_unifont_h_symbols(细 圆润)
    void testShowFont(u8g2_t *u8g2)
    {
    	int t = 1000;
    	char testStr[14] = "STM32F103C8T6";
    	
    	u8g2_ClearBuffer(u8g2);
    	
    	u8g2_SetFont(u8g2,u8g2_font_u8glib_4_tf);
    	u8g2_DrawStr(u8g2,0,5,testStr);
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    	
    	u8g2_SetFont(u8g2,u8g2_font_ncenB08_tf);
    	u8g2_DrawStr(u8g2,0,30,testStr);
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    	
        u8g2_SetFont(u8g2,u8g2_font_ncenB10_tr);
    	u8g2_DrawStr(u8g2,0,60,testStr);
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    }
     
    //画空心矩形
    void testDrawFrame(u8g2_t *u8g2)
    {
    	int t = 1000;
    	int x = 16;
    	int y = 32;
    	int w = 50;
    	int h = 20;
    	u8g2_ClearBuffer(u8g2);
    	u8g2_DrawStr(u8g2,0, 15, "DrawFrame");
     
    	u8g2_DrawFrame(u8g2, x, y, w, h);
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    	u8g2_DrawFrame(u8g2, x+w+5, y-10, w-20, h+20);
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    }
     
    //画实心圆角矩形
    void testDrawRBox(u8g2_t *u8g2)
    {
    	int t = 1000;
    	int x = 16;
    	int y = 32;
    	int w = 50;
    	int h = 20;
    	int r = 3;
    	u8g2_ClearBuffer(u8g2);
    	u8g2_DrawStr(u8g2,0, 15, "DrawRBox");
     
    	u8g2_DrawRBox(u8g2, x, y, w, h, r);
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    	u8g2_DrawRBox(u8g2, x+w+5, y-10, w-20, h+20, r);
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    }
     
    //画空心圆
    void testDrawCircle(u8g2_t *u8g2)
    {
    	int t = 600;
    	int stx = 0;  //画图起始x
    	int sty = 16; //画图起始y
    	int with = 16;//一个图块的间隔
    	int r = 15;   //圆的半径
    	u8g2_ClearBuffer(u8g2);
    	u8g2_DrawStr(u8g2, 0, 15, "DrawCircle");
     
    	u8g2_DrawCircle(u8g2, stx, sty - 1 + with, r, U8G2_DRAW_UPPER_RIGHT); //右上
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    	u8g2_DrawCircle(u8g2, stx + with, sty, r, U8G2_DRAW_LOWER_RIGHT); //右下
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    	u8g2_DrawCircle(u8g2, stx - 1 + with * 3, sty - 1 + with, r, U8G2_DRAW_UPPER_LEFT); //左上
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    	u8g2_DrawCircle(u8g2, stx - 1 + with * 4, sty, r, U8G2_DRAW_LOWER_LEFT); //左下
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    	u8g2_DrawCircle(u8g2, stx - 1 + with * 2, sty - 1 + with * 2, r, U8G2_DRAW_ALL);//整个圆
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    	
        u8g2_DrawCircle(u8g2, 32*3, 32, 31, U8G2_DRAW_ALL);//右侧整个圆
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    }
     
    //画实心椭圆
    void testDrawFilledEllipse(u8g2_t *u8g2)
    {
    	int t = 800;
    	int with = 16;//一个图块的间隔
    	int rx = 27;  //椭圆x方向的半径
    	int ry = 22;  //椭圆y方向的半径
    	u8g2_ClearBuffer(u8g2);
    	u8g2_DrawStr(u8g2,0, 14, "DrawFilledEllipse");
     
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    	u8g2_DrawFilledEllipse(u8g2, 0, with, rx, ry, U8G2_DRAW_LOWER_RIGHT);//右下
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    	u8g2_DrawFilledEllipse(u8g2, with * 4 - 1, with, rx, ry, U8G2_DRAW_LOWER_LEFT); //左下
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    	u8g2_DrawFilledEllipse(u8g2, 0, with * 4 - 1, rx, ry, U8G2_DRAW_UPPER_RIGHT); //右上
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    	u8g2_DrawFilledEllipse(u8g2, with * 4 - 1, with * 4 - 1, rx, ry, U8G2_DRAW_UPPER_LEFT); //左上
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    	u8g2_DrawFilledEllipse(u8g2, with * 6, with * 2.5, rx, ry, U8G2_DRAW_ALL);//整个椭圆
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    }
     
    //环形测试
    void testDrawMulti(u8g2_t *u8g2)
    {
    	u8g2_ClearBuffer(u8g2);
    	for (int j = 0; j < 64; j+=16)
    	{
    		for (int i = 0; i < 128; i+=16)
    		{
    			u8g2_DrawPixel(u8g2, i, j);
    			u8g2_SendBuffer(u8g2);
    		}
    	}
      
    	//实心矩形逐渐变大
        u8g2_ClearBuffer(u8g2);
    	for(int i=30; i>0; i-=2)
    	{
    		u8g2_DrawBox(u8g2,i*2,i,128-i*4,64-2*i);
    		u8g2_SendBuffer(u8g2);
    	}
    	//空心矩形逐渐变小
    	u8g2_ClearBuffer(u8g2);
    	for(int i=0; i<32; i+=2)
    	{
    		u8g2_DrawFrame(u8g2,i*2,i,128-i*4,64-2*i);
    		u8g2_SendBuffer(u8g2);
    	}
    	
    	//实心圆角矩形逐渐变大
    	u8g2_ClearBuffer(u8g2);
    	for(int i=30; i>0; i-=2)
    	{
    		u8g2_DrawRBox(u8g2,i*2,i,128-i*4,64-2*i,10-i/3);
    		u8g2_SendBuffer(u8g2);
    	}
        //空心圆角矩形逐渐变小
    	u8g2_ClearBuffer(u8g2);
    	for(int i=0; i<32; i+=2)
    	{
    		u8g2_DrawRFrame(u8g2,i*2,i,128-i*4,64-2*i,10-i/3);
    		u8g2_SendBuffer(u8g2);
    	}
    	
    	//实心圆逐渐变大
    	u8g2_ClearBuffer(u8g2);
    	for(int i=2; i<64; i+=3)
    	{
    		u8g2_DrawDisc(u8g2,64,32,i, U8G2_DRAW_ALL);
    		u8g2_SendBuffer(u8g2);
    	}
    	//空心圆逐渐变小
    	u8g2_ClearBuffer(u8g2);
    	for(int i=64; i>0; i-=3)
    	{
    		u8g2_DrawCircle(u8g2,64,32,i, U8G2_DRAW_ALL);
    		u8g2_SendBuffer(u8g2);
    	}
    	
    	//实心椭圆逐渐变大
        u8g2_ClearBuffer(u8g2);
    	for(int i=2; i<32; i+=3)
    	{
    		u8g2_DrawFilledEllipse(u8g2,64,32, i*2, i, U8G2_DRAW_ALL);
    		u8g2_SendBuffer(u8g2);
    	}
        //空心椭圆逐渐变小
        u8g2_ClearBuffer(u8g2);
    	for(int i=32; i>0; i-=3)
    	{
    		u8g2_DrawEllipse(u8g2,64,32, i*2, i, U8G2_DRAW_ALL);
    		u8g2_SendBuffer(u8g2);
    	}
    }
     
     
    // width: 128, height: 48
    const unsigned char bilibili[] U8X8_PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xe0, 0x03, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0xf0, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0xff, 0xff, 0x01, 0xfc, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x03, 0xfc, 0x00, 0x00, 0x3c, 0xc0, 0x0f, 0x00, 0x80, 0x03, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0x07, 0xfc, 0x00, 0x00, 0x3c, 0xc0, 0x0f, 0x00, 0xc0, 0x07, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xfc, 0x00, 0x00, 0x3c, 0x80, 0x0f, 0x00, 0xc0, 0x07, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0x80, 0x0f, 0xf8, 0x00, 0x00, 0x3c, 0x80, 0x0f, 0x00, 0x80, 0x07, 0x00, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x78, 0x80, 0x0f, 0x00, 0x80, 0x07, 0x00, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x78, 0x80, 0x0f, 0x00, 0x80, 0x07, 0x00, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x80, 0x79, 0x80, 0x0f, 0x00, 0x98, 0x07, 0x00, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0xe0, 0x79, 0x9f, 0x0f, 0x00, 0xbe, 0xe7, 0x01, 0xc0, 0x07, 0x10, 0x40, 0x00, 0x1f, 0xf8, 0x00, 0xe0, 0x7b, 0x1f, 0x0f, 0x00, 0xbe, 0xe7, 0x01, 0xc0, 0x87, 0x1f, 0xe0, 0x0f, 0x1f, 0xf8, 0x00, 0xe0, 0x7b, 0x1e, 0x0f, 0x00, 0x3e, 0xe7, 0x01, 0xc0, 0xe7, 0x3f, 0xe0, 0x3f, 0x1f, 0xf0, 0x00, 0xe0, 0x7b, 0x1e, 0x0f, 0x00, 0x3e, 0xe7, 0x01, 0xc0, 0xe7, 0x3f, 0xe0, 0x3f, 0x1f, 0xf0, 0x00, 0x60, 0x71, 0x1e, 0x0f, 0x00, 0x34, 0xe7, 0x01, 0xc0, 0xe7, 0x07, 0x00, 0x3f, 0x1f, 0xf0, 0x00, 0x00, 0x70, 0x00, 0x1f, 0x00, 0x00, 0x07, 0x00, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0xc0, 0x73, 0x1e, 0x1f, 0x00, 0x3c, 0xc7, 0x01, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0xc0, 0x73, 0x1e, 0x1f, 0x00, 0x7c, 0xe7, 0x01, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0xc0, 0x73, 0x1e, 0x1f, 0x00, 0x7c, 0xef, 0x01, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x01, 0xc0, 0x77, 0x1e, 0x1e, 0x00, 0x7c, 0xef, 0x01, 0xc0, 0x07, 0x00, 0x03, 0x00, 0x1f, 0xf0, 0xff, 0xc1, 0xf7, 0x1e, 0xfe, 0x1f, 0x78, 0xef, 0x01, 0xc0, 0x07, 0x70, 0x37, 0x00, 0x1f, 0xe0, 0xff, 0x87, 0xf7, 0x1e, 0xfe, 0xff, 0x78, 0xee, 0x01, 0xc0, 0x07, 0xe0, 0x3f, 0x00, 0x1f, 0xe0, 0xff, 0x9f, 0xf7, 0x1e, 0xfe, 0xff, 0x79, 0xce, 0x01, 0xc0, 0x07, 0xc0, 0x18, 0x00, 0x1f, 0xe0, 0xff, 0xbf, 0xe7, 0x1e, 0xfe, 0xff, 0x7b, 0xce, 0x01, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x1f, 0xe0, 0xc7, 0xbf, 0xe7, 0x1e, 0xfe, 0xf8, 0x77, 0xce, 0x01, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x1f, 0xe0, 0x0f, 0x3f, 0xe7, 0x1c, 0xfe, 0xf0, 0x77, 0xce, 0x03, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x1f, 0xe0, 0xcf, 0x3f, 0xe7, 0x1c, 0xfe, 0xf8, 0xf3, 0xce, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xe0, 0xef, 0x1f, 0xe7, 0x1c, 0xfe, 0xfe, 0xf1, 0xce, 0x03, 0x80, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xe0, 0xff, 0x0f, 0xcf, 0x1c, 0xfc, 0xff, 0xf0, 0xc0, 0x03, 0x00, 0xff, 0xff, 0xff, 0xff, 0x07, 0xe0, 0xff, 0x03, 0x06, 0x1c, 0xfc, 0x7f, 0x60, 0xc0, 0x01, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x03, 0xe0, 0xff, 0x00, 0x00, 0x00, 0xfc, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0xff, 0xff, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    // width: 128, height: 48
    const unsigned char three_support[] U8X8_PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0xfc, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0x00, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x03, 0x00, 0x00, 0x80, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xc0, 0x3f, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00, 0x00, 0x80, 0x0f, 0xf0, 0x01, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00, 0x00, 0xc0, 0xfd, 0xff, 0x00, 0x00, 0xc0, 0x7f, 0xfe, 0x01, 0x00, 0x00, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xe0, 0xfd, 0xff, 0x01, 0x00, 0xc0, 0x1f, 0xf8, 0x03, 0x00, 0x00, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xe0, 0xfd, 0xff, 0x01, 0x00, 0xc0, 0x0f, 0xf0, 0x03, 0x00, 0x00, 0xfe, 0xff, 0x07, 0x00, 0x00, 0xe0, 0xfd, 0xff, 0x01, 0x00, 0xc0, 0x67, 0xe6, 0x03, 0x00, 0x00, 0xfc, 0xff, 0x03, 0x00, 0x00, 0xe0, 0xfd, 0xff, 0x01, 0x00, 0xc0, 0x67, 0xe6, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x01, 0x00, 0x00, 0xe0, 0xfd, 0xff, 0x00, 0x00, 0xc0, 0x67, 0xe6, 0x03, 0x00, 0x00, 0xf0, 0x7f, 0x00, 0x00, 0x00, 0xe0, 0xfd, 0xff, 0x00, 0x00, 0xc0, 0x67, 0xee, 0x03, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, 0x00, 0xe0, 0xfd, 0xff, 0x00, 0x00, 0x80, 0x7f, 0xfe, 0x01, 0x00, 0x00, 0xe0, 0xff, 0x00, 0x00, 0x00, 0xe0, 0xfd, 0xff, 0x00, 0x00, 0x80, 0x7f, 0xfe, 0x01, 0x00, 0x00, 0xf0, 0xff, 0x00, 0x00, 0x00, 0xe0, 0xfd, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x00, 0x00, 0x00, 0xe0, 0xfd, 0x7f, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x00, 0xf8, 0xf9, 0x01, 0x00, 0x00, 0xe0, 0xfd, 0x7f, 0x00, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0x00, 0xf8, 0xf0, 0x00, 0x00, 0x00, 0xe0, 0xfd, 0x1f, 0x00, 0x00, 0x00, 0xf8, 0x1f, 0x00, 0x00, 0x00, 0x30, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
     
    void testDrawXBM(u8g2_t *u8g2)
    {
    	int t = 1000;
    	u8g2_ClearBuffer(u8g2);
    	u8g2_DrawStr(u8g2,0, 14, "DrawXBM");
     
    	u8g2_DrawXBM(u8g2,0, 16, 128, 48, bilibili);
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
     
    	u8g2_ClearBuffer(u8g2);
    	u8g2_DrawStr(u8g2,0, 14, "bilibili");
    	u8g2_DrawXBM(u8g2,0, 16, 128, 48, three_support);
    	SEND_BUFFER_DISPLAY_MS(u8g2,t);
    }
     
    void u8g2DrawTest(u8g2_t *u8g2)
    {
    	testDrawProcess(u8g2);
    	testDrawMulti(u8g2);
    	//testDrawFrame(u8g2);
    	//testDrawRBox(u8g2);
    	//testDrawCircle(u8g2);
    	//testDrawFilledEllipse(u8g2);
    	testShowFont(u8g2);
    	testDrawXBM(u8g2);
     
    }
    

    6.测试现象

    U8g2示例

    (二)显示学号和姓名

    1.代码修改

    不需要更改stm32cubemx的设置,而且同样要用到U8g2,所以可以直接用刚刚U8g2移植成功后的keil工程通过修改main.c完成,当然如果你不想所有工程的代码都在一个main.c中,你也可以复制几个工程,在几个工程里面分别完成各个功能。

    1.中文取模
    要显示中文,我们需要把想要显示的字取模然后放进main.c中,所以先打开取模软件PCtoLCD2002

  • 选择模式:字符模式
  • 如图设置:
  • 生成字模:
  • 2.代码修改
    在main.中添加自己生成的字模,其中,字模放在主函数int main之前,main函数更改为如下:

    	u8g2_SetFont(&u8g2,u8g2_font_ncenB12_tf);//设置字体格式
      	u8g2_DrawXBMP(&u8g2,16,0,16,16,deng);//(参数顺序依次是,结构体、x、y、字宽、字高、储存要显示的字点阵的数组)
      	u8g2_DrawXBMP(&u8g2,32,0,16,16,huai);
      	u8g2_DrawXBMP(&u8g2,48,0,16,16,jin);
    	u8g2_SetFont(&u8g2,u8g2_font_ncenB10_tf);
    	u8g2_DrawStr(&u8g2,16,50,"632207030115");
    	u8g2_SendBuffer(&u8g2);
    

    最后修改完成的main.c如下

    /* USER CODE BEGIN Header */
    /**
      ******************************************************************************
      * @file           : main.c
      * @brief          : Main program body
      ******************************************************************************
      * @attention
      *
      * Copyright (c) 2024 STMicroelectronics.
      * All rights reserved.
      *
      * This software is licensed under terms that can be found in the LICENSE file
      * in the root directory of this software component.
      * If no LICENSE file comes with this software, it is provided AS-IS.
      *
      ******************************************************************************
      */
    /* USER CODE END Header */
    /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    #include "i2c.h"
    #include "tim.h"
    #include "gpio.h"
    #include "u8g2.h"
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    
    /* USER CODE END Includes */
    
    /* Private typedef -----------------------------------------------------------*/
    /* USER CODE BEGIN PTD */
    
    /* USER CODE END PTD */
    
    /* Private define ------------------------------------------------------------*/
    /* USER CODE BEGIN PD */
    
    /* USER CODE END PD */
    
    /* Private macro -------------------------------------------------------------*/
    /* USER CODE BEGIN PM */
    
    /* USER CODE END PM */
    
    /* Private variables ---------------------------------------------------------*/
    
    /* USER CODE BEGIN PV */
    
    /* USER CODE END PV */
    
    /* Private function prototypes -----------------------------------------------*/
    void SystemClock_Config(void);
    /* USER CODE BEGIN PFP */
    
    /* USER CODE END PFP */
    
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
    	
    	
    /*邓*/
    static const unsigned char  deng[] ={0x00,0x00,0x7E,0x3E,0x40,0x22,0x40,0x12,0x42,0x12,0x24,0x0A,0x28,0x12,0x10,0x12,0x10,0x22,0x28,0x22,0x28,0x22,0x44,0x16,0x42,0x0A,0x01,0x02,0x00,0x02,0x00,0x02};
    /*怀*/
    static const unsigned char huai[] ={0x08,0x00,0x08,0x00,0xE8,0x7F,0x08,0x04,0x18,0x04,0x2A,0x02,0x0A,0x02,0x0A,0x0B,0x89,0x12,0x48,0x22,0x28,0x42,0x08,0x02,0x08,0x02,0x08,0x02,0x08,0x02,0x08,0x02};
    /*瑾*/
    static const unsigned char jin[] ={0x00,0x11,0x00,0x11,0xDF,0x7F,0x04,0x11,0x04,0x1F,0x04,0x04,0x84,0x3F,0x9F,0x24,0x84,0x3F,0x04,0x04,0xC4,0x7F,0x1C,0x04,0x87,0x3F,0x02,0x04,0xC0,0x7F,0x00,0x00};
    	
    
    	
    int main(void)
    {
      /* USER CODE BEGIN 1 */
     
      /* USER CODE END 1 */
     
      /* MCU Configuration--------------------------------------------------------*/
     
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
     
      /* USER CODE BEGIN Init */
     
      /* USER CODE END Init */
     
      /* Configure the system clock */
      SystemClock_Config();
     
      /* USER CODE BEGIN SysInit */
     
      /* USER CODE END SysInit */
     
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_I2C2_Init();
      MX_TIM1_Init();
      /* USER CODE BEGIN 2 */
      u8g2_t u8g2;
      u8g2Init(&u8g2);	
      /* USER CODE END 2 */
     
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
     
        /* USER CODE BEGIN 3 */
    	u8g2_SetFont(&u8g2,u8g2_font_ncenB12_tf);//设置字体格式
      	u8g2_DrawXBMP(&u8g2,16,0,16,16,deng);//(参数顺序依次是,结构体、x、y、字宽、字高、储存要显示的字点阵的数组)
     	u8g2_DrawXBMP(&u8g2,32,0,16,16,huai);
      	u8g2_DrawXBMP(&u8g2,48,0,16,16,jin);
    	u8g2_SetFont(&u8g2,u8g2_font_ncenB10_tf);
    	u8g2_DrawStr(&u8g2,16,50,"632207030115");
    	u8g2_SendBuffer(&u8g2);
    
    		
     
      }
      /* USER CODE END 3 */
    }
    
    /**
      * @brief System Clock Configuration
      * @retval None
      */
    void SystemClock_Config(void)
    {
      RCC_OscInitTypeDef RCC_OscInitStruct = {0};
      RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
      /** Initializes the RCC Oscillators according to the specified parameters
      * in the RCC_OscInitTypeDef structure.
      */
      RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
      RCC_OscInitStruct.HSEState = RCC_HSE_ON;
      RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
      RCC_OscInitStruct.HSIState = RCC_HSI_ON;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
      RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
      RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
      if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
      {
        Error_Handler();
      }
    
      /** Initializes the CPU, AHB and APB buses clocks
      */
      RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
      RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
      if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
      {
        Error_Handler();
      }
    }
    
    /* USER CODE BEGIN 4 */
    
    /* USER CODE END 4 */
    
    /**
      * @brief  This function is executed in case of error occurrence.
      * @retval None
      */
    void Error_Handler(void)
    {
      /* USER CODE BEGIN Error_Handler_Debug */
      /* User can add his own implementation to report the HAL error return state */
      __disable_irq();
      while (1)
      {
      }
      /* USER CODE END Error_Handler_Debug */
    }
    
    #ifdef  USE_FULL_ASSERT
    /**
      * @brief  Reports the name of the source file and the source line number
      *         where the assert_param error has occurred.
      * @param  file: pointer to the source file name
      * @param  line: assert_param error line source number
      * @retval None
      */
    void assert_failed(uint8_t *file, uint32_t line)
    {
      /* USER CODE BEGIN 6 */
      /* User can add his own implementation to report the file name and line number,
         ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
      /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */
    
    
    

    2.线路连接及烧录

    1.OLED为4引脚的,线路连接上
    GND—>GND
    VCC—>3.3V
    SCL—>B10
    SDA—>B11

    2.烧录过程和串口烧录的过程相同,可以参照之前的博客,使用FlyMcu软件进行烧录。hex文件为编译完成后的hex文件。注意,烧录程序时,需要更改boot,烧录完成后需要复原。

    3.实现现象

    (三)温湿度

    1.代码修改

    main.c

    /* USER CODE BEGIN Header */
    /**
      ******************************************************************************
      * @file           : main.c
      * @brief          : Main program body
      ******************************************************************************
      * @attention
      *
      * Copyright (c) 2024 STMicroelectronics.
      * All rights reserved.
      *
      * This software is licensed under terms that can be found in the LICENSE file
      * in the root directory of this software component.
      * If no LICENSE file comes with this software, it is provided AS-IS.
      *
      ******************************************************************************
      */
    /* USER CODE END Header */
    /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    #include "i2c.h"
    #include "tim.h"
    #include "gpio.h"
    #include "u8g2.h"
    #include "OLED_Front.h"
    #include "aht20.h"
    #include <stdio.h>
    #include "string.h"
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    
    /* USER CODE END Includes */
    
    /* Private typedef -----------------------------------------------------------*/
    /* USER CODE BEGIN PTD */
    
    /* USER CODE END PTD */
    
    /* Private define ------------------------------------------------------------*/
    /* USER CODE BEGIN PD */
    
    /* USER CODE END PD */
    
    /* Private macro -------------------------------------------------------------*/
    /* USER CODE BEGIN PM */
    
    /* USER CODE END PM */
    
    /* Private variables ---------------------------------------------------------*/
    
    /* USER CODE BEGIN PV */
    
    /* USER CODE END PV */
    
    /* Private function prototypes -----------------------------------------------*/
    void SystemClock_Config(void);
    /* USER CODE BEGIN PFP */
    
    /* USER CODE END PFP */
    
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    
    
    
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
    int main(void)
    {
    
      /* USER CODE BEGIN 1 */
    
      /* USER CODE END 1 */
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_I2C2_Init();
      MX_TIM1_Init();
      MX_I2C1_Init();
      /* USER CODE BEGIN 2 */
    
      u8g2_t u8g2;     
      u8g2Init(&u8g2);      //初始化
    	
        
        
      //uint16_t x=0;
      /* USER CODE END 2 */
        float temperature=1 , humidity ;
        char t[5];
        char h[5];
    
      //uint16_t x=0;
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
    
        //显示采集到的温度和湿度
        AHT20_Read( &temperature , &humidity); //读取AHT20
    	  sprintf(t ," %f",temperature);//拼接
        sprintf(h ," %f",humidity);//拼接
        u8g2_SetFont(&u8g2,u8g2_font_ncenB12_tf);//设置字体格式
        //(参数顺序依次是,结构体、x、y、字宽、字高、储存要显示的字点阵的数组)
        u8g2_DrawXBMP(&u8g2,16,0,16,16,wen);
        u8g2_DrawXBMP(&u8g2,32,0,16,16,du);
    		u8g2_DrawStr(&u8g2,48,16,":");
        u8g2_SetFont(&u8g2,u8g2_font_ncenB10_tf);
        u8g2_DrawXBMP(&u8g2,16,32,16,16,shi);
        u8g2_DrawXBMP(&u8g2,32,32,16,16,du);
    		u8g2_DrawStr(&u8g2,48,48,":");
        u8g2_SetFont(&u8g2,u8g2_font_ncenB10_tf);
        u8g2_DrawUTF8(&u8g2,80,16,t);
    		u8g2_DrawUTF8(&u8g2,80,48,h);
        u8g2_SendBuffer(&u8g2);
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    
    /**
      * @brief System Clock Configuration
      * @retval None
      */
    void SystemClock_Config(void)
    {
      RCC_OscInitTypeDef RCC_OscInitStruct = {0};
      RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
      /** Initializes the RCC Oscillators according to the specified parameters
      * in the RCC_OscInitTypeDef structure.
      */
      RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
      RCC_OscInitStruct.HSEState = RCC_HSE_ON;
      RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
      RCC_OscInitStruct.HSIState = RCC_HSI_ON;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
      RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
      RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
      if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
      {
        Error_Handler();
      }
    
      /** Initializes the CPU, AHB and APB buses clocks
      */
      RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
      RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
      if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
      {
        Error_Handler();
      }
    }
    
    /* USER CODE BEGIN 4 */
    
    /* USER CODE END 4 */
    
    /**
      * @brief  This function is executed in case of error occurrence.
      * @retval None
      */
    void Error_Handler(void)
    {
      /* USER CODE BEGIN Error_Handler_Debug */
      /* User can add his own implementation to report the HAL error return state */
      __disable_irq();
      while (1)
      {
      }
      /* USER CODE END Error_Handler_Debug */
    }
    
    #ifdef  USE_FULL_ASSERT
    /**
      * @brief  Reports the name of the source file and the source line number
      *         where the assert_param error has occurred.
      * @param  file: pointer to the source file name
      * @param  line: assert_param error line source number
      * @retval None
      */
    void assert_failed(uint8_t *file, uint32_t line)
    {
      /* USER CODE BEGIN 6 */
      /* User can add his own implementation to report the file name and line number,
         ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
      /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */
    
    

    2.线路连接

    采用st-link烧录
    AHT20接线图:

    整体连接图:

    3.实现现象

    温湿度oled

    (四)滑动字符

    1.代码修改

    和显示学号姓名一样,不需要更改stm32cubemx的设置,而且同样要用到U8g2,所以可以直接用刚刚U8g2移植成功后的keil工程通过修改main.c完成,当然如果你不想所有工程的代码都在一个main.c中,你也可以复制几个工程,在几个工程里面分别完成各个功能。

  • 取字模
    设置与刚才显示学号姓名相同,只需要更改想显示的字
  • =

  • 代码修改
    将main.c中的while循环整体(while前面也有一句,别忘记了)修改为:
  • unsigned int x=16;
      while (1)
      {
    if(x<=128)
    		{
    			x++;//向右滑动,可以提高每次x的增加量来控制滑动速度
    		}
    		else if(x>128)//置零
    		{
    			x=0;
    		}
    				u8g2_SetFont(&u8g2,u8g2_font_ncenB12_tf);//设置字体格式
    				u8g2_DrawXBMP(&u8g2,x,0,16,16,fang);
    				u8g2_DrawXBMP(&u8g2,x+16,0,16,16,fu);
    				u8g2_DrawXBMP(&u8g2,x+32,0,16,16,jie);
    				u8g2_DrawXBMP(&u8g2,x+48,0,16,16,feng);
    				u8g2_DrawXBMP(&u8g2,x+64,0,16,16,sheng);
    				u8g2_DrawXBMP(&u8g2,x+80,0,16,16,gen);
    				u8g2_DrawXBMP(&u8g2,x+96,0,16,16,wo);
    				u8g2_DrawXBMP(&u8g2,x+112,0,16,16,hua);
    				u8g2_SendBuffer(&u8g2);
    }
    

    2.实现现象

    四、总结

    详细介绍了 I2C 协议中软件 I2C 和硬件 I2C 的区别。软件 I2C 通过单片机的 I/O 端口模拟,灵活性高;硬件 I2C 使用芯片上的 I2C 外设,通信更快更稳定,但引脚固定。
    通过 stm32cubemx 配置相关引脚和外设,在 keil 中编写 AHT20 驱动程序,实现了每隔 2 秒采集温湿度数据并通过串口发送到上位机。
    采用 U8g2 图形库进行 OLED 显示。首先进行库的移植,包括在 stm32cubeMX 中配置相关引脚和时钟,精简 U8g2 库文件,然后在 keil 中添加库文件和头文件路径,并编写移植函数。实现了显示学号姓名、温湿度以及滑动字符等功能。显示中文时需先进行取模操作,再将字模添加到 main.c 中。
    通过本次实验,对 I2C 协议、温湿度传感器和 OLED 显示有了更深入的理解和实践经验。在实验过程中,遇到了如引脚配置错误、库文件移植问题等,但通过查阅资料和不断调试都得以解决。这让我明白在嵌入式开发中,仔细阅读芯片手册和库文档的重要性,同时也锻炼了自己的问题解决能力和耐心。此外,实验也让我体会到嵌入式系统的复杂性和多样性,为今后的学习和实践打下了坚实的基础。

    作者:HuaijinD

    物联沃分享整理
    物联沃-IOTWORD物联网 » stm32采集显示温湿度&通过IIC实现OLED屏显示

    发表回复