提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
本文将实现:
- 学习I2C总线通信协议,使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。具体任务:
1)解释什么是“软件I2C”和“硬件I2C”? (阅读野火配套教材的第23章“I2C–读写EEPROM”原理章节)
2)阅读AHT20数据手册,编程实现:每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机(win10)。
- 理解OLED屏显和汉字点阵编码原理,使用STM32F103的SPI或IIC接口实现以下功能:
-
显示自己的学号和姓名;
-
显示AHT20的温度和湿度;
-
上下或左右的滑动显示长字符,比如“Hello,欢迎来到重庆交通大学物联网205实训室!”或者一段歌词或诗词(使用硬件刷屏模式)。
提示:以下是本篇文章正文内容,下面案例可供参考
一、I2C通信基础
1. I2C协议简介
-
I2C总线特点和应用场景
I2C(集成电路总线),由Philips公司(2006年迁移到NXP)在1980年代初开发的一种简单、双线双向的同步串行总线,它利用一根时钟线和一根数据线在连接总线的两个器件之间进行信息的传递,为设备之间数据交换提供了一种简单高效的方法。每个连接到总线上的器件都有唯一的地址,任何器件既可以作为主机也可以作为从机,但同一时刻只允许有一个主机
-
通信时序和协议规范
数据信号以8位的序列传输。所以在特殊的开始条件发生后,就会出现第一个8位序列,它指示了数据被发送到哪个从设备的地址。每个8位序列之后都会跟随一个称为确认的位。
在大多数情况下,第一个确认位之后会跟着另一个寻址序列,但这次是针对从设备的内部寄存器。在寻址序列之后是数据序列,直到数据完全传输完毕,并以一个特殊的停止条件结束。
开始条件发生在数据线在时钟线仍然高电平的时候变低。之后,时钟开始,并且在每个时钟脉冲期间传输每一位数据。设备寻址序列从最重要的位开始,以最不重要的位结束,实际上是由7位组成的,因为第8位用于指示主设备是向从设备写入(逻辑低)还是从中读取(逻辑高)。
下一个确认位由从设备用来指示它是否成功接收了前一个位序列。所以这次主设备将SDA线的控制权交给从设备,如果从设备成功接收了前一个序列,它将把SDA线拉低到所谓的确认状态。
如果从设备没有把SDA线拉低,这种状态被称为不确认,意味着它没有成功接收前一个序列,这可能由多种原因造成。例如,从设备可能正忙,可能不理解接收到的数据,或者不能再接收任何数据等等。 在这种情况下,主设备决定如何继续操作。
接下来是内部寄存器的寻址。内部寄存器是从设备内存中包含各种信息或数据的位置。
在寻址之后,数据传输序列开始,要么来自主设备,要么来自从设备,这取决于在读/写位选择的模式。
在数据完全发送之后,传输将以停止条件结束,当SDA线在SCL线高电平时从低变高。这就是I2C通信协议的工作原理。
2. 软件I2C vs 硬件I2C
2.1 软件I2C的实现原理和特点
软件I2C是通过GPIO口模拟I2C总线时序来实现通信的方式。其基本原理是:
-
端口配置
- 将GPIO配置为开漏输出模式
- 通过软件控制SDA和SCL引脚的电平变化
- 需要上拉电阻(典型值4.7kΩ)实现空闲状态下的高电平
-
时序实现
- 通过软件延时控制时钟频率
- 手动模拟起始信号、停止信号
- 按位发送/接收数据
- 需要模拟应答信号的检测与发送
-
特点
- 实现灵活,可以使用任意GPIO口
- 时序可控性强,便于调试
- 占用CPU资源较多,通信效率较低
- 代码量较大,需要自行处理所有细节
2.2 硬件I2C的工作机制和优势
硬件I2C是利用STM32内置的I2C外设进行通信的方式:
-
硬件结构
- 专用的I2C通信外设
- 内置移位寄存器
- 硬件时序控制电路
- 内置波特率发生器
-
工作机制
- 通过寄存器配置通信参数
- 硬件自动产生起始、停止信号
- 自动控制时钟同步
- 硬件实现数据收发和应答处理
- 支持中断和DMA传输
-
优势
- 使用简单,代码量小
- 通信可靠性高
- CPU负载低
- 支持更高的通信速率
- 具备多种错误检测机制
2.3 两种方式的对比分析
对比项目 | 软件I2C | 硬件I2C |
---|---|---|
实现难度 | 较难,需要自行实现所有时序 | 较简单,主要是寄存器配置 |
资源占用 | 仅占用GPIO,资源消耗少 | 需要使用专用外设资源 |
CPU负载 | 高,需要CPU持续参与 | 低,可使用中断或DMA |
通信速率 | 较低,通常不超过100KHz | 最高可达400KHz(快速模式) |
可靠性 | 受软件延时精度影响 | 可靠性高,有硬件错误检测 |
灵活性 | 高,可使用任意GPIO | 只能使用指定的硬件I2C引脚 |
调试难度 | 容易定位问题,时序可控 | 出现问题较难排查 |
应用场景 | 简单应用,引脚受限情况 | 对可靠性要求高的正式产品 |
2.4 选择建议
在实际应用中,建议优先使用硬件I2C,只有在特殊情况下才考虑软件模拟方案。对于本项目中的AHT20温湿度传感器,由于其通信速率要求不高,两种方案都可以采用,但考虑到代码简洁性和可靠性,推荐使用硬件I2C方式。
二、AHT20温湿度传感器应用
1. AHT20硬件介绍
AHT20是一款数字式温湿度传感器,具有高精度、快速响应和低功耗的特点。能够准确测量环境的温度和湿度,并通过I2C接口与主控设备进行通信。要实现AHT20温湿度传感器的数据采集,首先需要阅读AHT20的数据手册,了解其通信协议和指令。然后,可以使用STM32F103的硬件I2C或软件I2C来实现与AHT20的通信。在采集到温湿度数据后,可以通过编程控制OLED显示相应的温湿度值。同时,使用STM32F103的串口功能,将采集到的温湿度数据发送到上位机的“串口助手”软件。具体实现步骤如下:
1)初始化STM32F103的硬件I2C或软件I2C,并配置相应的引脚。
2)根据AHT20的数据手册,编写相应的指令,实现对AHT20的初始化和温湿度数据采集。
3)初始化OLED显示屏,并编写相应的显示程序,将采集到的温湿度数据显示到OLED上。
4)初始化STM32F103的串口功能,并编写相应的发送程序,将采集到的温湿度数据通过串口发送到上位机的“串口助手”软件。
5)编写主循环程序,每隔2秒钟调用一次温湿度数据采集和显示发送程序。
2. 温湿度采集程序实现
- 配置CubeMx
1)首先点击:
2)然后选择我们要用的芯片型号(STM32F103C8T6),双击即可打开工程。
3)配置调试接口
4)配置时钟模式,具体如下
使HSE配置为内部晶振/陶瓷振荡器
5)配置时钟树
所以我们选择HSE ,然后PLL进行倍频:
6)配置USART1
7)生成代码
2. keil相关操作
首先将AHT官方给的.c和.h放在工程目录下
aht20.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;
}
}
aht20.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_ */
注意要在魔术棒里面配置路径
修改主程序:
/* 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 */
3.实现效果
温湿度视频
三、OLED显示实现
1. OLED显示原理
OLED即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示,OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术,LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示,OLED 效果要来得好一些,以目前的技术,OLED 的尺寸还难以大型化,但是分辨率确可以做到很高。
点阵概念
我们用之前的方法一个IO口只能控制一个led,如果需要用更少的IO口控制更多的led怎么办?于是,就有了点阵。
例如:8X8点阵共由64个发光二极管组成,且每个发光二极管是放置在行线和列线的交叉点上,当对应的某一行置1电平,某一列置0电平,则相应的二极管就亮;如要将第一个点点亮,则1脚接高电平a脚接低电平,则第一个点就亮了。
借助取模软件,即可将我们所需要的文字或字母,以点阵的形式呈现出来。
我们知道英文字母数量比较少,我们只要用一个字节(8位)就足以表达。但是汉字非常多。要怎么表达呢?
前人采用的一个方法就是把ASCII码的高128位作为汉字的内码,低128位仍然作为英文字母的内码,然后用两个字节来表示一个汉字。通过这个内码,我们可以获取汉字的字模信息。然后再根据这些字模的信息,把相应的汉字显示出来。
汉字编码
区位码
点阵字库其实就是按照汉字内码的顺序,把汉字的字模信息存起来。16×16的点阵字库有94区,每个区有94个汉字的字模。这样总的有94×94个汉字。
在国标 GD2312—80 中规定,所有的国标汉字及符号分配在一个 94 行、94 列的方阵中,方阵的每一行称为一个“区”,编号为 01 区到 94 区,每一列称为一个“位”,编号为01 位到 94 位,方阵中的每一个汉字和符号所在的区号和位号组合在一起形成的四个阿拉伯数字就是它们的“区位码”。
区位码的前两位是它的区号,后两位是它的位号。用区位码就可以唯一地确定一个汉字或符号。
机内码
汉字的机内码是指在计算机中表示一个汉字的编码。机内码与区位码稍有区别。
汉字区位码的区码和位码的取值均在 1-94 之间,如直接用区位码作为机内码,就会与基本 ASCII 码混淆。为了避免机内码与基本 ASCII 码的冲突,需要避开基本 ASCII 码中的控制码(00H~1FH),还需与基本 ASCII 码中的字符相区别。
因此:先在区码和位码分别加上 20H,在此基础上再加 80H。
(此处“H”表示前两位数字为十六进制数)
经过这些处理,用机内码表示一个汉字需要占两个字节,分别称为高位字节和低位字节,这两位字节的机内码按如下规则表示:
高位字节 = 区码 + 20H + 80H(或区码 + A0H)
低位字节 = 位码 + 20H + 80H(或位码 + AOH)
由于汉字的区码与位码的取值范围的十六进制数均为 01H-5EH(即十进制的 01-94),所以汉字的高位字节与低位字节的取值范围则为 A1H-FEH(即十进制的 161-254)。
点阵字库存储
在汉字的点阵字库中,每个字节的每个位都代表一个汉字的一个点,每个汉字都是由一个矩形的点阵组成,0 代表没有,1 代表有点,将 0 和 1 分别用不同颜色画出,就形成了一个汉字。
字库根据字节所表示点的不同有分为 横向矩阵 和 纵向矩阵,目前多数的字库都是横向矩阵的存储方式(用得最多的应该是早期 UCDOS 字库),纵向矩阵一般是因为有某些液晶是采用纵向扫描显示法,为了提高显示速度,于是便把字库矩阵做成纵向。
之后所描述的都是指横向矩阵字库。
16*16点阵字库
对于 1616 的矩阵来说,它所需要的位数共是 1616=256 个位,每个字为 8 位,因此,每个汉字都需要用 256/8=32 个字节来表示。
每两个字节代表一行的 16 个点,共需要 16 行,显示汉字时,只需一次性读取 32 个字节,并将每两个字节为一行打印出来,即可形成一个汉字。
1414与1212点阵字库
对于 1414 和 1212 的字库,理论上计算,它们所需要的点阵分别为(1414/8)=25, (1212/8)=18 个字节,但是,如果按这种方式来存储,那么取点阵和显示时, 由于它们每一行都不是 8 的整位数 ,因此,就会涉到点阵的计算处理问题,会增加程序的复杂度,降低程序的效率。
为解决这个问题,有些点阵字库会将 1414 和 1212 的字库按 1614和 1612 来存储,即,每行还是按两个字节来存储,但是 1414 的字库,每两个字节的最后两位是没有使用,1212 的字节,每两字节的最后 4 位是没有使用,这个根据不同的字库会有不同的处理方式,所以在使用字库时要注意这个问题。
2. 基础显示功能
在上述基础上配置IIC
在Keil里面:
首先添加u2g8的相关官方代码
网站上搜索字模提取器 V2.2即可下载文件包:
提取器环境配置
选择C51即可生成点阵
在test.c里面插入
void TEST_MainPage(void)
{
// GUI_ShowString(28,0,"abc",16,1);//英文姓名
GUI_ShowCHinese(28,20,16,"彭旺仔",1);//中文姓名
GUI_ShowString(4,48,"632007030***",16,1);//数字详细
delay_ms(1500);
delay_ms(1500);
}
3. 完成效果
4. 动态显示效果
动态显示:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2022 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "tim.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stm32_u8g2.h"
#include "test.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
*/
/*当*/
static const unsigned char dang[] ={
0x80,0x00,0x84,0x10,0x88,0x10,0x90,0x08,0x90,0x04,0x80,0x00,0xFE,0x1F,0x00,0x10,0x00,0x10,0x00,0x10,0xFC,0x1F,0x00,0x10,0x00,0x10,0x00,0x10,0xFE,0x1F,0x00,0x10};
/*时*/
static const unsigned char shi[] ={
0x00,0x10,0x00,0x10,0x3E,0x10,0x22,0x10,0xA2,0x7F,0x22,0x10,0x22,0x10,0x3E,0x10,0x22,0x11,0x22,0x12,0x22,0x12,0x22,0x10,0x3E,0x10,0x22,0x10,0x00,0x14,0x00,0x08};
/*明*/
static const unsigned char ming[] ={
0x00,0x00,0x00,0x3F,0x3E,0x21,0x22,0x21,0x22,0x21,0x22,0x3F,0x3E,0x21,0x22,0x21,0x22,0x21,0x22,0x3F,0x3E,0x21,0x22,0x21,0x80,0x20,0x80,0x20,0x40,0x28,0x20,0x10};
/*月*/
static const unsigned char yue[]={
0x00,0x00,0xF8,0x1F,0x08,0x10,0x08,0x10,0x08,0x10,0xF8,0x1F,0x08,0x10,0x08,0x10,0x08,0x10,0xF8,0x1F,0x08,0x10,0x08,0x10,0x04,0x10,0x04,0x10,0x02,0x14,0x01,0x08};
/*在*/
static const unsigned char zai[]={
0x40,0x00,0x40,0x00,0x20,0x00,0xFF,0x7F,0x10,0x00,0x10,0x02,0x08,0x02,0x0C,0x02,0xEA,0x3F,0x09,0x02,0x08,0x02,0x08,0x02,0x08,0x02,0x08,0x02,0xF8,0x7F,0x08,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 */
unsigned int x=16;
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
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,dang);
u8g2_DrawXBMP(&u8g2,x+16,0,16,16,shi);
u8g2_DrawXBMP(&u8g2,x+32,0,16,16,ming);
u8g2_DrawXBMP(&u8g2,x+48,0,16,16,yue);
u8g2_DrawXBMP(&u8g2,x+64,0,16,16,zai);
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 */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
5.完成效果
oled滑动显示
五、OLED加AHT20温湿度传感器
将两个工程合并,得到效果:
六、附录
- 完整程序源码
Github链接: https://github.com/sunyuan1111/AHT20 - 参考资料清单
https://blog.csdn.net/qq_35057766/article/details/135706589?fromshare=blogdetail&sharetype=blogdetail&sharerId=135706589&sharerefer=PC&sharesource=2401_82919733&sharefrom=from_link
总结
技术实现要点
- I2C通信模块
- 深入学习了I2C通信协议的基本原理和时序特点
- 掌握了STM32F103硬件I2C的配置方法
- 成功实现AHT20传感器的温湿度数据采集
- OLED显示功能
- 理解了点阵显示原理和汉字编码存储方式
- 实现了基础的字符显示和动态滚动效果
- 完成了温湿度数据的实时显示
项目收获
- 技术层面
- 掌握了嵌入式系统中I2C通信的实际应用
- 提升了对显示驱动程序的开发能力
- 加深了对STM32外设配置的理解
- 开发经验
- 培养了查阅数据手册和技术文档的能力
- 积累了调试和问题解决的经验
- 提高了代码编写和工程管理能力
后续改进方向
- 功能优化
- 添加更多显示效果和交互功能
- 优化显示刷新率和动画流畅度
- 增加数据存储和分析功能
- 性能提升
- 优化中断处理机制
- 改进电源管理策略
- 提高系统稳定性
通过本次实验,不仅完成了预期目标,也为今后的嵌入式开发积累了宝贵经验。我将继续深入学习,不断提升技术水平。