STM32 HAL库0.96寸OLED显示液晶屏

本文介绍了使用STM32 HAL库通过I2C协议驱动0.96寸OLED显示屏的方法。首先概述了OLED的基本特性和应用,然后详细讲解了汉字点阵生成的方法,并提供了完整的代码示例,包括初始化、清屏、字符串显示和自定义汉字显示函数。这些代码实现了在STM32F103ZET6开发板上显示特定内容的功能,如英文句子和中文字符"慢慢变好"。

目录

[二、0.96 寸 OLED](#二、0.96 寸 OLED)

三、生成汉字点阵

三、代码示例

1.初始化

2清屏函数

3.字符串显示函数

4.自定义汉字字模显示函数

5.完整示例代码,附件是源码

五、运行结果

六、总结


零、屏幕显示的本质

屏幕显示的核心其实非常简单,归根结底就是"点灯"。每一个分辨率单位对应一个像素点,也就是一盏可以单独控制的灯,屏幕放大后就是一个一个的小方块"灯"。
在屏幕上显示内容时,实际上就是在指定坐标(x, y)上点亮或熄灭相应的像素点。
屏幕显示=坐标(x,y)+内容

对于分辨率为128x64的显示屏来说,意味着它有64行、每行128个像素点,总共包含8192个独立可控制的灯(即像素)。要控制这个128x64的显示屏,本质上就是要对这8192个像素进行精确的点亮和熄灭操作。

在实际应用中,我们并不会一个个地去控制这些像素点,因为这样的效率太低了。我们会按照字节(8位)来操作显存,每个字节控制8个连续的像素点。

一、开发环境

硬件:正点原子精英 V2 STM32F103开发板

单片机:STM32F103ZET6

Keil版本:5.32

STM32CubeMX版本:6.9.2

STM32Cube MCU Packges版本:STM32F1xx_DFP.2.4.1

串口:USART1(PA9,PA10)

I2C2:PB10(SCL),PB11(SDA)

屏幕:0.96 寸 OLED 显示液晶屏模128 * 64 SSD1306,屏幕的SCL、SDA接到STM32的PB10(SCL),PB11(SDA)

二、0.96 寸 OLED

0.96 寸 OLED 显示液晶屏模块是一种体积小巧但功能强大的显示设备,在众多小型电子项目中广泛应用。它具有 128 * 64 的分辨率,采用 SSD1306 驱动芯片,支持 SPI 和 I2C 两种通信接口.

三、生成汉字点阵

显示汉字需要自行生成字库调用,运行"PCtoLCD2002"软件,点击菜单"选项"进行设置,如下图所示

比如显示"慢慢变好".输入"慢慢变好".点击生产字模,把字模复制到chinese_font.c

四、代码示例

底层驱动已经实现好,只是调用函数.

1.初始化
cpp 复制代码
************** 7. 初始化函数 **************/
/*
 *  函数名:OLED_Init
 *  功能描述:初始化OLED
 *  输入参数:无
 *  输出参数:无
 *  返回值:无
 */
void OLED_Init(void)
{   
    /*
     * 前提: 已经初始化的I2C通道
     * 本工程里已经: 
     *    使用MX_I2C1_Init初始化I2C通道
     *    使用HAL_I2C_MspInit初始化I2C引脚
     */
    
    OLED_SetMemAddrMode(PAGE_ADDR_MODE);    // 0. 设置地址模式
    OLED_SetMuxRatio(0x3F);                 // 1. 设置多路复用率
    OLED_SetDispOffset(0x00);               // 2. 设置显示的偏移值
    OLED_SetDispStartLine(0x00);            // 3. 设置起始行
    OLED_SEG_REMAP();                       // 4. 行翻转
    OLED_SCAN_REMAP();                      // 5. 正常扫描
    OLED_SetComConfig(COM_PIN_SEQ, COM_NOREMAP);          // 6. COM 引脚设置
    OLED_SetContrastValue(0x7F);            // 7. 设置对比度
    ENTIRE_DISP_OFF();                      // 8. 全屏点亮/熄灭
    DISP_NORMAL();                          // 9. 显示模式
    OLED_SetDCLK_Freq(0x00, 0x08);          // 10. 设置分频系数和频率增值
    OLED_SetChargePump(PUMP_ENABLE);        // 11. 使能电荷碰撞
    
    OLED_SetComConfig(COM_PIN_ALT, COM_NOREMAP);
    
    DISP_ON();
}
2.清屏函数

清屏函数的核心思路是通过循环遍历屏幕的每一页,将每一页的所有像素点都置为 0,从而实现清屏的效果。buf 数组用于存储要写入屏幕的数据,所有元素初始化为 0。OLED_SetPosition 函数用于设置当前操作的页和列位置。OLED_WriteNBytes 函数用于将指定数量的数据写入屏幕。

通过这种方式,可以确保整个屏幕的显示内容被清空,显示为全黑。

cpp 复制代码
/*
 *  函数名:OLED_Clear
 *  功能描述:清屏函数,用于将0.96寸OLED显示液晶屏的显示内容清空,使屏幕显示为全黑。
 *  输入参数:无
 *  输出参数:无
 *  返回值:无
 *  实现原理:
 *      该OLED屏幕采用页寻址模式,整个屏幕被划分为8页,每页有128列。
 *      清屏的过程就是将每一页的128个像素点都置为0(即不发光)。
 *      首先创建一个长度为128的缓冲区,所有元素初始化为0,代表一列的像素数据都为0。
 *      然后通过循环遍历每一页,设置当前操作的页和列位置,将缓冲区的数据写入该页,从而清空该页的显示内容。
*/
void OLED_Clear(void)
{
    // 定义一个循环变量i,用于遍历屏幕的每一页
    uint8_t i = 0;
    // 定义一个长度为128的缓冲区buf,用于存储要写入屏幕的数据
    // 所有元素初始化为0,代表一列的像素数据都为0,即不发光
    uint8_t buf[128] = {0};
    
    // 循环遍历屏幕的8页
    for(i = 0; i < 8; i++)
    {
        // 调用OLED_SetPosition函数,设置当前操作的页和列位置
        // i表示当前页号(0 - 7),0表示列号从第0列开始
        OLED_SetPosition(i, 0);
        // 调用OLED_WriteNBytes函数,将缓冲区buf中的128个字节数据写入当前页
        // 由于buf中的数据都为0,所以写入后该页的所有像素点都不发光,实现了清屏效果
        OLED_WriteNBytes(&buf[0], 128);
    }
}
3.字符串显示函数

此函数的主要逻辑是遍历传入的字符串,使用 OLED_PutChar 函数逐个显示字符。每显示一个字符,x 坐标右移一位。当 x 坐标超出范围(大于 15)时,x 坐标重置为 0,y 坐标下移 2 页。最后返回成功显示的字符数量.

cpp 复制代码
/*
 *  函数名:OLED_PrintString
 *  功能描述:在0.96寸OLED显示液晶屏上显示一个字符串。该函数会将传入的字符串按字符逐个显示在指定的起始坐标位置。
 *  输入参数:
 *            x --> x坐标(0~15),表示字符串起始显示位置的列坐标,取值范围为0到15,每一个单位对应屏幕上一定的列位置。
 *            y --> y坐标(0~7),表示字符串起始显示位置的页坐标,取值范围为0到7,每一页对应屏幕垂直方向上的一部分区域。
 *            str --> 显示的字符串,是一个以'\0'结尾的字符数组。
 *  输出参数:无
 *  返回值:打印了多少个字符,即成功显示在屏幕上的字符数量。
 */
int OLED_PrintString(uint8_t x, uint8_t y, const char *str)
{   
    // 定义一个整型变量i,用于记录当前处理的字符在字符串中的索引位置,同时也用于统计打印的字符数量
    int i = 0;

    // 进入循环,只要字符串还未结束(即当前字符不为字符串结束符'\0'),就继续处理
    while (str[i])
    {
        // 调用OLED_PutChar函数,将当前字符显示在指定的x、y坐标位置
        OLED_PutChar(x, y, str[i]);

        // 显示完一个字符后,将x坐标加1,以便在下一列显示下一个字符
        x++;

        // 判断x坐标是否超出了最大允许值(即是否超过了15)
        if(x > 15)
        {
            // 如果x坐标超出范围,将x坐标重置为0,以便从下一行的起始位置开始显示
            x  = 0;
            // 同时将y坐标增加2,因为通常每显示一行字符后,需要移动到下一页继续显示
            y += 2;
        }
                
        // 索引位置i加1,指向下一个字符
        i++;
    }

    // 循环结束后,返回打印的字符数量
    return i;
}
4.自定义汉字字模显示函数

此函数 OLED_PrintChinese1 用于在 OLED 屏幕指定位置显示预定义的中文字符。它通过外部数组 g_chinese_fonts1 存储中文字符的字模数据,按顺序将每个中文字符的上半部分和下半部分数据依次写入 OLED 屏幕,并更新列位置以显示下一个字符。在写入前会检查输入坐标是否有效,无效则不进行显示操作

cpp 复制代码
/**
 * @brief 在OLED屏幕上显示预定义的中文字符。
 * 
 * 该函数用于在OLED屏幕的指定位置显示一系列预定义的中文字符。中文字符的字模数据存储在外部数组g_chinese_fonts1中。
 * 
 * @param x 中文字符起始显示位置的列索引(范围0 - 15),每个单位对应8列。
 * @param y 中文字符起始显示位置的页索引(范围0 - 7),表示垂直位置。
 * 
 * @return 无
 */
void OLED_PrintChinese1(uint8_t x, uint8_t y)
{   
    // 声明外部数组g_chinese_fonts1,该数组存储了中文字符的字模数据。
    // 数组的每个元素代表一个中文字符,每个中文字符由32字节的数据组成(上半部分16字节,下半部分16字节)。
    extern uint8_t g_chinese_fonts1[4][32];

    // 保存起始页位置,用于后续操作。
    uint8_t page = y;
    // 计算起始列位置,由于每个中文字符宽度为16列,而x是按8列单位计数的,所以乘以8。
    uint8_t col  = x * 8;

    // 检查输入的坐标是否超出OLED屏幕的有效范围。
    // 如果y超过7(页索引范围为0 - 7)或者x超过15(列索引范围为0 - 15),则直接返回,不进行显示操作。
    if (y > 7 || x > 15)
        return;

    // 循环变量,用于遍历中文字符数组。
    int i;
    // 遍历g_chinese_fonts1数组中的每个中文字符。
    // sizeof(g_chinese_fonts1)返回整个数组的字节数,sizeof(g_chinese_fonts1[0])返回一个中文字符数据的字节数,
    // 两者相除得到数组中中文字符的数量。
    for (i = 0; i < sizeof(g_chinese_fonts1) / sizeof(g_chinese_fonts1[0]); i++)
    {
        // 设置OLED屏幕的当前显示位置为当前页和当前列。
        // 这是为了准备写入当前中文字符的上半部分数据。
        OLED_SetPosition(page, col);

        // 向OLED屏幕发送当前中文字符的上半部分数据(前16字节)。
        // &g_chinese_fonts1[i][0]是当前中文字符上半部分数据的起始地址,16表示要发送的字节数。
        OLED_WriteNBytes((uint8_t*)&g_chinese_fonts1[i][0], 16);

        // 设置OLED屏幕的当前显示位置为下一页(page + 1)和当前列。
        // 这是为了准备写入当前中文字符的下半部分数据。
        OLED_SetPosition(page + 1, col);

        // 向OLED屏幕发送当前中文字符的下半部分数据(后16字节)。
        // &g_chinese_fonts1[i][16]是当前中文字符下半部分数据的起始地址,16表示要发送的字节数。
        OLED_WriteNBytes((uint8_t*)&g_chinese_fonts1[i][16], 16);

        // 更新列位置,为显示下一个中文字符做准备。
        // 每个中文字符宽度为16列,所以将列位置增加16。
        col += 16;
    }
}
5.完整示例代码,附件是源码
cpp 复制代码
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 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 <string.h>
#include "driver_oled.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
extern UART_HandleTypeDef huart1;
extern I2C_HandleTypeDef hi2c2;
/* 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 */
//char *str= "I2C FUNCTIONS\r\n";
//char c;

/* 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_USART1_UART_Init();
  MX_I2C2_Init();
  /* USER CODE BEGIN 2 */

  OLED_Init();
	OLED_Clear();
	OLED_PrintString(0, 0, "I love Aissa");
	OLED_PrintChinese1(3, 3);
	OLED_PrintChinese2(3, 6);
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* 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_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 */

五、运行结果

屏幕显示如下内容

六、总结

本文介绍了使用STM32 HAL库通过I2C协议驱动0.96寸OLED显示屏的方法,仅供参考,有任何问题,欢迎在评论区留言讨论!

相关推荐
智商偏低4 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen5 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
森焱森7 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白7 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D8 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术10 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt11 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘11 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang11 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c
几个几个n14 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件