许可优化
许可优化
产品
产品
解决方案
解决方案
服务支持
服务支持
关于
关于
软件库
当前位置:服务支持 >  软件文章 >  GD32开发笔记6 OLED加ug82基于SSD1306操作记录

GD32开发笔记6 OLED加ug82基于SSD1306操作记录

阅读数 4
点赞 0
article_banner

1. 功能概述

利用 DAC + DMA + 定时器触发,循环输出自定义波形(支持正弦波和方波),并在每次 DMA 传输完成后更新波形缓冲区,实现波形的实时调整。

2. 关键变量

代码块


#define DAC_BUFFER_LENGTH 250
uint32_t DAC_Buffer_Rx[DAC_BUFFER_LENGTH];       // 原始波形数据
uint32_t DAC_Buffer_Rx_DMA[DAC_BUFFER_LENGTH];   // 供DMA传输的缓冲区(防止DMA传输中数据被意外修改)
uint8_t DAC_Tx_Flag = 0;                         // 标志位:通知主循环波形已更新

      复制成功
     
     
     
     

3. 波形生成函数

正弦波 Generate_sinwave

  • 参数 shape_angle:正弦波的初始相位(弧度制,代码未使用角度制,留意参数类型为 float)。
  • 原理:将 2π2π 等分成 250 份,计算每个点的值并用公式归一化到 [0,255][0,255] 范围(适配 8 位 DAC 精度)。 text
  • 代码块
Value = 255 * (sin(angle) + 1) / 2

      复制成功
     
     
     
     
  • 写入 DAC_Buffer_Rx 数组供后续 DMA 发送。

方波 Generate_FangWave

  • 参数 max、min:方波高低电平对应的 DAC 值。
  • 参数 zhankongbi:占空比(0~1),前 zhankongbi * 250 个点输出 min,其余输出 max。
  • 同样写入 DAC_Buffer_Rx。

4. DAC_DMA_Init 初始化流程

  1. 调用波形生成函数(本例生成方波,高电平 100,低电平 10,占空比 50%)。
  2. 启动定时器 htim6(触发源),每产生一次更新事件就会触发一次 DAC 转换,DMA 负责将缓冲区数据逐点传至 DAC 寄存器。
  3. 调用 HAL_DAC_Start_DMA 启动 DAC 通道1 的 DMA 模式: 缓冲区:DAC_Buffer_Rx_DMA 长度:DAC_BUFFER_LENGTH 对齐:8 位右对齐(DAC_ALIGN_8B_R)
  4. 关闭 DMA 半传输中断 (DMA_IT_HT),只保留传输完成中断 (DMA_IT_TC),以便在传输完一轮 250 个点后才处理回调。

5. DMA 传输完成回调 HAL_DAC_ConvCpltCallbackCh1

当 DAC 触发 DMA 将 250 个点全部转换完毕后,进入该回调。

  1. 暂时停止 DAC DMA:HAL_DAC_Stop_DMA,保证在更新缓冲区时不会与 DMA 发生冲突。
  2. 拷贝新波形数据:将用户可能已更新过的 DAC_Buffer_Rx 复制到 DAC_Buffer_Rx_DMA。
  3. 置标志位 DAC_Tx_Flag = 1,通知主循环准备下一轮波形数据。
  4. 重新启动 DMA,并再次关闭半传输中断。
  5. 由于定时器 htim6 一直在运行,新的一轮 250 点会立即紧接上一次传输继续被触发,实现波形连续输出且允许在每轮间更新数据。

6. 主循环处理 DAC_Proc

  • 检查 DAC_Tx_Flag,若为 1 则清零,并在此处添加下一轮波形的更新逻辑(代码中为注释掉的 memset,实际可按需调用新的 Generate_sinwave 或修改方波参数等)。

设计意图:把耗时计算放在主循环中,避免在中断回调中执行复杂运算;利用双缓冲思想避免 DMA 与 CPU 的数据争抢。


二、ADC 部分(ADC_App.c)

1. 功能概述

利用 ADC + DMA + 定时器触发,循环采集双通道(或序列)ADC 数据,并在传输完成后通过串口打印偶通道的数值(即保留每个采样序列的第 0、2、4… 个数据)。

2. 关键变量


#define ADC_RX_BUFFER_LENGTH 500           // DMA 传输的总数据个数(所有通道之和)
uint32_t ADC_Rx_Buffer[ADC_RX_BUFFER_LENGTH/2];   // 提取后的偶通道数据
uint32_t ADC_Rx_Buffer_DMA[ADC_RX_BUFFER_LENGTH]; // DMA 直接填充的原始数据
uint8_t ADC_Rx_Flag = 0;

      复制成功
     
     
     
     

3. ADC_DMA_Init 初始化

  1. 启动定时器 htim3 作为 ADC 触发源,定时产生采样事件。
  2. 调用 HAL_ADC_Start_DMA 启动 ADC1 的 DMA 循环采集,缓冲区 ADC_Rx_Buffer_DMA 长 500。
  3. 关闭 DMA 半传输中断,只在全部传输完成时进回调。

4. DMA 传输完成回调 HAL_ADC_ConvCpltCallback

  1. 停止定时器 htim3,暂停触发,避免在数据处理时产生新的 DMA 请求。
  2. 停止 ADC DMA。
  3. 数据提取:遍历 ADC_Rx_Buffer_DMA,将偶数下标(0,2,4…)的元素拷贝到 ADC_Rx_Buffer —— 因为 DMA 会按通道顺序交替存储(假设为两个通道或规则序列,通道数据交错排列)。
  4. 置标志位 ADC_Rx_Flag = 1,通知主循环有新数据待处理。
  5. 重新启动定时器和 ADC DMA,并再次关闭半传输中断,进入新一轮采集。

5. 主循环处理 ADC_DMA_Proc

  1. 检查 ADC_Rx_Flag,有效时将 250 个提取出的数据通过 Serial_Printf 经 UART1 逐行打印。
  2. 清零 ADC_Rx_Buffer(使用 memset)。
  3. 清零标志位。

设计要点:通过定时器停止—处理—重启的策略,保证数据处理时 ADC 暂停,处理完成后再恢复采样,避免数据丢失或冲突。


总代码:

代码块


#include "ADC_App.h"

#define ADC_RX_BUFFER_LENGTH 500
uint32_t ADC_Rx_Buffer[ADC_RX_BUFFER_LENGTH/2];
uint32_t ADC_Rx_Buffer_DMA[ADC_RX_BUFFER_LENGTH];
uint8_t ADC_Rx_Flag=0;

void ADC_DMA_Init(void){
	HAL_TIM_Base_Start(&htim3);
	HAL_ADC_Start_DMA(&hadc1,ADC_Rx_Buffer_DMA,ADC_RX_BUFFER_LENGTH);
	__HAL_DMA_DISABLE_IT(&hdma_adc1,DMA_IT_HT);
}



void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc){
	HAL_TIM_Base_Stop(&htim3);
	HAL_ADC_Stop_DMA(&hadc1);
	for(uint16_t i=0;i<ADC_RX_BUFFER_LENGTH/2;i++){
		ADC_Rx_Buffer[i]=ADC_Rx_Buffer_DMA[2*i];
	}
	ADC_Rx_Flag=1;
	HAL_TIM_Base_Start(&htim3);
	HAL_ADC_Start_DMA(&hadc1,ADC_Rx_Buffer_DMA,ADC_RX_BUFFER_LENGTH);
	__HAL_DMA_DISABLE_IT(&hdma_adc1,DMA_IT_HT);
}

void ADC_DMA_Proc(void){
	if(!ADC_Rx_Flag) return;
	ADC_Rx_Flag=0;
	for(uint16_t i=0;i<ADC_RX_BUFFER_LENGTH/2;i++){
		Serial_Printf(&huart1,"%lu\r\n",ADC_Rx_Buffer[i]);
	}
	memset(ADC_Rx_Buffer,0,ADC_RX_BUFFER_LENGTH/2);
}

#include "DAC_APP.h"
#include "math.h"

#define DAC_BUFFER_LENGTH 250
uint32_t DAC_Buffer_Rx[DAC_BUFFER_LENGTH];
uint32_t DAC_Buffer_Rx_DMA[DAC_BUFFER_LENGTH];
uint8_t DAC_Tx_Flag=0;

void Generate_sinwave(float shape_angle){
	float add_angle=2*3.1415926/(float)DAC_BUFFER_LENGTH;
	for(uint16_t i=1;i<=DAC_BUFFER_LENGTH;i++){
		DAC_Buffer_Rx[i-1]=255.0f*(sinf(shape_angle+i*add_angle)+1.0f)/2.0f;
	}
}

void Generate_FangWave(float max,float min,float zhankongbi){
	for(uint16_t i=1;i<=DAC_BUFFER_LENGTH;i++){
		if(i<zhankongbi*DAC_BUFFER_LENGTH){
			DAC_Buffer_Rx[i-1]=min;
		}else{
			DAC_Buffer_Rx[i-1]=max;
		}
	}
}

void DAC_DMA_Init(void){
//	Generate_sinwave(30);
	Generate_FangWave(10,100,0.5);
	HAL_TIM_Base_Start(&htim6);
	HAL_DAC_Start_DMA(&hdac,DAC_CHANNEL_1,DAC_Buffer_Rx_DMA,DAC_BUFFER_LENGTH,DAC_ALIGN_8B_R);
	__HAL_DMA_DISABLE_IT(&hdma_dac1,DMA_IT_HT);
}

void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac){
	if(hdac->Instance==DAC1){
		HAL_DAC_Stop_DMA(hdac,DAC_CHANNEL_1);
		for(uint16_t i=0;i<DAC_BUFFER_LENGTH;i++){
			DAC_Buffer_Rx_DMA[i]=DAC_Buffer_Rx[i];
		}
		DAC_Tx_Flag=1;
		HAL_DAC_Start_DMA(hdac,DAC_CHANNEL_1,DAC_Buffer_Rx_DMA,DAC_BUFFER_LENGTH,DAC_ALIGN_8B_R);
	  __HAL_DMA_DISABLE_IT(&hdma_dac1,DMA_IT_HT);
	}
}

void DAC_Proc(void){
	if(!DAC_Tx_Flag) return;
	DAC_Tx_Flag=0;
//	memset(DAC_Buffer_Rx,2400,DAC_BUFFER_LENGTH);
}


      复制成功
     
     
     
     
  1. SSD1306芯片 显示类型:单色(常见颜色:白色、蓝色或黄蓝双色) 分辨率:最大支持128×64像素(常见于0.96英寸屏幕),另有128×32像素(常见于0.91英寸屏幕) 通信方式:主要支持I2C,部分模块提供SPI接口 工作电压:3.3V–5V 控制方式:通过发送一系列命令控制显示
  2. I2C通信协议 I2C是一种同步半双工串行通信协议,使用SCL(时钟线)和SDA(数据线)两根总线。 总线结构:开漏输出。原因:开漏输出配合外部上拉电阻可实现“线与”逻辑,使多个设备能安全共享同一总线,支持多主机仲裁与时钟同步,同时便于匹配不同电平标准。 主从架构:一个主设备,多个从设备(也可以多主多从) 设备寻址:通过ID号去找到不同的芯片 当使用I2C协议时,SSD1306作为从设备,拥有一个固定的设备地址(ID号,通常是0x78或0x7A,可能通过模块上的跳线选择)
  3. SSD1306的控制流程 1.初始化序列:上电后,需要向SSD1306发送一系列特定的命令来配置其工作状态,通常包括: 设置显示时钟分频系数和振荡器频率 命令 0xD5,常用参数 0x80(默认值,分频比 = 1,振荡器频率 ≈ 最大频率的 90%)。 设置 MUX 复用比(决定扫描行数,64 行时使用) 0xA8, 0x3F 设置显示偏移 0xD3, 0x00 设置起始行 0x40 | 0x00 → 实际发送 0x40 配置电荷泵(产生 OLED 所需高电压) 0x8D, 0x14 配置内存地址模式(水平 / 垂直 / 页地址模式) 0x20, 0x00(水平) / 0x01(垂直) / 0x02(页) 设置 SEG/COM 引脚硬件配置 0xDA, 0x12 设置对比度 0x81, 0xCF 设置预充电周期 0xD9, 0xF1 设置 VCOMH 电压 0xDB, 0x40 设置全部显示来源 正常显示 GDDRAM 内容:0xA4 强制全部点亮(忽略 GDDRAM):0xA5 设置正常 / 反相显示 正常显示:0xA6 反相显示:0xA7 开启显示 0xAF 2.设置地址指针:在写入像素数据之前,需要发送命令设置GDDRAM的目标页地址和列地址(例如使用页面地址模式时发送0xB0|Page_num,0x00|(col&0x0F),0x10|(col>>4) 3.写入数据:发送写数据指令(控制字节0x40)和随后的像素数据字节。数据会根据设置的地址模式自动写入GDDRAM,且地址指针会自动递增
  4. I2C相关API 1.I2C_HandleTypeDef 句柄:存储I2C的配置信息、运行状态、错误代码等等 2.HAL_StatusTypeDef HAL_I2C_Init配置I2C初始化结构体 3.HAL_I2C_Master_Transmit 以Master形式通过I2C发送数据到指定的从设备地址
  5. 移植u8g2图形库 虽然基础的 SSD1306 驱动可以满足基本的文本和点线显示需求,但如果你想在 OLED 上绘制更复杂的图形、使用丰富的字体或者更方便地布局界面,强大的 u8g2 图形库 是一个绝佳的选择。 u8g2 是一个适用于嵌入式系统的单色显示屏图形库,支持多种控制器(包括 SSD1306)和通信接口。它提供了丰富的绘图 API(点、线、矩形、圆形、文本等)和大量的字体支持。相比简单的字符驱动,u8g2 使得创建图形化界面和显示复杂信息变得更加容易。 接下来,我们将根据参考文档,演示如何将 u8g2 移植到我们的 STM32 项目中(基于硬件 I2C)。移植的核心在于提供平台相关的底层接口函数,让 u8g2 库能够控制 GPIO(如果需要)并与显示屏进行通信。 移植步骤: 1.解压文件,找到 `csrc` 文件夹。这个文件夹包含了 u8g2 库的核心代码和支持的各种显示驱动。为了显著减小最终编译后的代码体积(嵌入式系统资源有限),我们需要进行裁剪。根据你的 OLED 型号删除 `csrc` 目录中不需要的显示驱动文件 (`u8x8_d_*.c`)。参考文档以 0.91 寸 OLED (通常是 SSD1306 控制器, 128x32 分辨率) 为例,建议只保留与 SSD1306 相关的驱动文件,例如 `u8x8_d_ssd1306_128x32_univision.c` (文件名可能因 u8g2 版本略有不同,查找包含 `ssd1306` 和对应分辨率的文件),删除其他所有 `u8x8_d_*.c` 文件。 修改 `u8g2_d_setup.c` 文件。此文件包含了针对不同显示屏、接口和缓冲模式的初始化设置函数 (`u8g2_Setup_...`)。同样为了精简,只保留与你选择的驱动和接口匹配的 setup 函数。参考文档示例针对 SSD1306、I2C 接口、128x32 分辨率,保留了 `u8g2_Setup_ssd1306_i2c_128x32_univision_f` ("f" 代表 full buffer 模式)。删除文件中其他的 `u8g2_Setup_...` 函数定义。与驱动文件类似,`setup` 文件也包含了对应各种硬件组合的初始化代码。每个 `Setup` 函数都包含了特定的配置序列和对底层驱动、内存管理函数的调用。只保留与我们硬件 (SSD1306)、接口 (I2C)、分辨率 (128x32) 以及选择的缓冲模式 (Full Buffer 'f') 相匹配的那个 Setup 函数,可以进一步减小代码体积。 2.u8g2 通过回调函数与底层硬件交互(GPIO 控制和通信)。我们需要为 STM32 HAL 库实现这些回调函数。u8g2 将硬件交互分为两类:GPIO 及延时操作、字节传输操作(如 I2C, SPI)。我们需要根据选择的接口(这里是硬件 I2C)编写对应的回调函数。 原因: u8g2 库本身是平台无关的,它不知道你使用的是 STM32、ESP32 还是其他 MCU,也不知道你使用的是 HAL 库、LL 库还是寄存器操作。为了让 u8g2 能在你的特定平台上工作,你需要提供一组符合 u8g2 接口规范的回调函数,这些函数内部调用你平台(STM32 HAL)的 API 来完成实际的硬件操作(如 I2C 发送、延时等)。这是一种典型的硬件抽象层 (HAL) 或适配层 (Adapter) 的设计模式,实现了库与具体硬件的解耦。
#include "u8g2.h"
#include "main.h" // 确保包含了 HAL 库和 I2C 定义
#include "i2c.h"  // 确保包含 I2C 句柄定义 (例如 hi2c1)

// u8g2 的 GPIO 和延时回调函数
uint8_t u8g2_gpio_and_delay_stm32(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
  switch(msg)
  {
    case U8X8_MSG_GPIO_AND_DELAY_INIT:
      // 初始化 GPIO (如果需要,例如 SPI 的 CS, DC, RST 引脚)
      // 对于硬件 I2C,这里通常不需要做什么
      break;
    case U8X8_MSG_DELAY_MILLI:
      // 原因: u8g2 内部某些操作需要毫秒级的延时等待。
      // 提供毫秒级延时,直接调用 HAL 库函数。
      HAL_Delay(arg_int);
      break;
    case U8X8_MSG_DELAY_10MICRO:
      // 原因: 某些通信协议或显示时序可能需要微秒级延时。
      // 提供 10 微秒延时。HAL_Delay(1) 精度不够(通常是毫秒级)。
      // 需要更精确的延时,可以使用 CPU NOP 指令或 DWT 定时器。
      // 以下简单循环仅为示例,**需要根据你的 CPU 时钟频率精确调整循环次数**。
      for (volatile uint32_t i = 0; i < 150; ++i) {} // 示例循环,计数需调整
      break;
    case U8X8_MSG_DELAY_100NANO:
      // 原因: 更精密的时序控制,通常在高速接口或特定操作中需要。
      // 提供 100 纳秒延时。非常短,通常用 NOP 指令实现。
      // **同样需要根据 CPU 时钟频率调整 NOP 数量**。
       __NOP(); __NOP(); __NOP(); // 示例 NOP
      break;
    case U8X8_MSG_GPIO_I2C_CLOCK: // [[fallthrough]] // Fallthrough 注释表示有意为之
    case U8X8_MSG_GPIO_I2C_DATA:
      // 控制 SCL/SDA 引脚电平。这些仅在**软件模拟 I2C** 时需要实现。
      // 使用硬件 I2C 时,这些消息可以忽略,由 HAL 库处理。
      break;
     // --- 以下是 GPIO 相关的消息,主要用于按键输入或 SPI 控制 --- 
     // 如果你的 u8g2 应用需要读取按键或控制 SPI 引脚 (CS, DC, Reset),
     // 你需要在这里根据 msg 类型读取/设置对应的 GPIO 引脚状态。
     // 对于仅使用硬件 I2C 显示的场景,可以像下面这样简单返回不支持。
     case U8X8_MSG_GPIO_CS:
        // SPI 片选控制
        break;
      case U8X8_MSG_GPIO_DC:
        // SPI 数据/命令线控制
        break;
      case U8X8_MSG_GPIO_RESET:
        // 显示屏复位引脚控制
        break;
    case U8X8_MSG_GPIO_MENU_SELECT:
      u8x8_SetGPIOResult(u8x8, /* 读取选择键 GPIO 状态 */ 0);
      break;
    default:
      u8x8_SetGPIOResult(u8x8, 1); // 不支持的消息
      break;
  }
  return 1;
}

// u8g2 的硬件 I2C 通信回调函数
uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
  static uint8_t buffer[32]; // u8g2 每次传输最大 32 字节
  static uint8_t buf_idx;
  uint8_t *data;

  switch(msg)
  {
    case U8X8_MSG_BYTE_SEND:
      // 原因: u8g2 通常不会一次性发送大量数据,而是分块发送。
      // 这个消息用于将一小块数据 (arg_int 字节) 从 u8g2 内部传递到我们的回调函数。
      // 我们需要将这些数据暂存到本地 buffer 中,等待 START/END_TRANSFER 信号。
      data = (uint8_t *)arg_ptr;
      while( arg_int > 0 )
      {
        buffer[buf_idx++] = *data;
        data++;
        arg_int--;
      }
      break;
    case U8X8_MSG_BYTE_INIT:
      // 原因: 提供一个机会进行 I2C 外设的初始化。
      // 初始化 I2C (通常在 main 函数中已完成)
      // 由于我们在 main 函数中已经调用了 MX_I2C1_Init(),这里通常可以留空。
      break;
    case U8X8_MSG_BYTE_SET_DC:
      // 原因: 这个消息用于 SPI 通信中控制 Data/Command 选择引脚。
      // 设置数据/命令线 (I2C 不需要)
      // I2C 通过特定的控制字节 (0x00 或 0x40) 区分命令和数据,因此该消息对于 I2C 无意义。
      break;
    case U8X8_MSG_BYTE_START_TRANSFER:
      // 原因: 标记一个 I2C 传输序列的开始。
      buf_idx = 0;
      // 我们在这里重置本地缓冲区的索引,准备接收新的数据块。
      break;
    case U8X8_MSG_BYTE_END_TRANSFER:
      // 原因: 标记一个 I2C 传输序列的结束。
      // 此时,本地 buffer 中已经暂存了所有需要发送的数据块。
      // 这是执行实际 I2C 发送操作的最佳时机。
      // 发送缓冲区中的数据
      // 注意: u8x8_GetI2CAddress(u8x8) 返回的是 7 位地址 * 2 = 8 位地址
      if (HAL_I2C_Master_Transmit(&hi2c1, u8x8_GetI2CAddress(u8x8), buffer, buf_idx, 100) != HAL_OK)
      {
        return 0; // 发送失败
      }
      break;
    default:
      return 0;
  }
  return 1;
}

      复制成功
     
     
     
     

3.注册u8g2示例

在HeaderFiles.h声明 u8g2 结构体: 在全局范围(例如 `oled_app.c` 或 `main.c`)声明一个 `u8g2_t` 类型的结构体变量。如果需要在多个文件访问,使用 `extern`。

调用初始化函数: 在 `main.c` 的 `main` 函数中,替换掉之前 `OLED_Init()` 的调用,改为使用 u8g2 的初始化序列。这通常包括三个步骤:`setup`, `init display`, `set power save`。

原因: 初始化 u8g2 是一个标准流程,必须在使用任何绘图 API 之前完成:

`u8g2_Setup_...()`: 这是配置的核心。它将 `u8g2_t` 结构体与具体的硬件驱动、通信方式(通过回调函数指针)和缓冲模式关联起来。必须选择与硬件和回调函数完全匹配的 Setup 函数,否则无法正确工作。

`u8g2_InitDisplay()`: 这个函数会通过之前设置的回调函数,向 OLED 控制器 (SSD1306) 发送一系列初始化命令(类似于第 2.4 节描述的序列),配置其内部寄存器,使其准备好接收显示数据。

`u8g2_SetPowerSave(0)`: SSD1306 初始化后可能处于省电模式(屏幕关闭)。调用这个函数并传入参数 0 会发送命令 (0xAF) 来唤醒屏幕,使其正常显示。

代码块


#include "u8g2.h"
#include "mydefine.h" // 假设 extern u8g2_t u8g2; 在这里,或者直接包含定义了 u8g2 的头文件

// 声明回调函数原型 (如果定义在其他 .c 文件)
uint8_t u8g2_gpio_and_delay_stm32(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);


int main(void)
{
  // ... HAL_Init(), SystemClock_Config(), MX_GPIO_Init() ...
  MX_I2C1_Init(); // 确保 I2C 初始化在前

  // u8g2 初始化
  // 1. Setup: 这是最关键的一步,它配置了 u8g2 实例。
  //    - 选择与硬件匹配的 setup 函数 (SSD1306, I2C, 128x32, Full Buffer)。
  //    - &u8g2: 指向要配置的 u8g2 结构体实例的指针。
  //    - U8G2_R0: 旋转设置。U8G2_R0=0°, U8G2_R1=90°, U8G2_R2=180°, U8G2_R3=270°。
  //    - u8x8_byte_hw_i2c: 指向你的硬件 I2C 字节传输回调函数的指针。
  //    - u8g2_gpio_and_delay_stm32: 指向你的 GPIO 和延时回调函数的指针。
  u8g2_Setup_ssd1306_i2c_128x32_univision_f(&u8g2, U8G2_R0, u8x8_byte_hw_i2c, u8g2_gpio_and_delay_stm32);
  //    - &u8g2: u8g2 结构体指针
  //    - U8G2_R0: 旋转设置 (0度)
  // 2. Init Display: 发送初始化序列到 OLED
  u8g2_InitDisplay(&u8g2);

  // 3. Set Power Save: 唤醒屏幕。
  //    - 参数 0 表示关闭省电模式 (屏幕亮起)。
  //    - 参数 1 表示进入省电模式 (屏幕熄灭)。
  u8g2_SetPowerSave(&u8g2, 0);

  // (可选) 4. Clear Display: 清空屏幕物理显存。
  //    - 这个函数会立即发送清屏命令到设备。
  //    - 注意与 `u8g2_ClearBuffer` 的区别,后者仅清空内存中的缓冲区。
  // u8g2_ClearDisplay(&u8g2);

  // ... 其他初始化 ...

  while (1)
  {
    // ... 主循环 ...
  }
}

      复制成功
     
     
     
     


免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删

相关文章
技术文档
QR Code
微信扫一扫,欢迎咨询~
customer

online

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 board-phone 155-2731-8020
close1
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

姓名不为空

姓名不为空
手机不正确

手机不正确

手机不正确
公司不为空

公司不为空

公司不为空