kai迪皮 发表于 2025-6-30 19:55

APM32F402 & HC-SR04超声测距应用场景:加个小屏幕

本帖最后由 kai迪皮 于 2025-6-30 20:06 编辑

# 1. 背景:我们已经能测得很准,但还要更“便携”

在先前我们进行了

1. (https://bbs.21ic.com/icview-3465660-1-1.html)
2. (https://bbs.21ic.com/icview-3466026-1-1.html)
3. (https://bbs.21ic.com/icview-3466234-1-1.html)

可是在前面的实验里,我们通过 USB 连接到电脑,看串口打印的结果——这没什么不好,可是当我们想把这个小东西拿到现场去测试时,就会发现,拖着 USB 线、插着电脑太不方便。难道要当一个“抱着笔记本奔跑的测距侠”吗?这个操作可不优雅,也不利于后期的现场使用。

因此,从需求层面看:如果能在 APB402 + HC-SR04 + DHT11 的这个测距系统上加块小屏,让它在脱离电脑的情况下就能即时显示,那就太棒了!这样一来,我们只要给整套方案接个电源(例如手机充电宝),就能轻便地拿在手里满世界量距离、量温湿度,不再依赖上位机软件。

所以,“加个小屏幕”的念头便油然而生。而这里,我选择了常见的 0.96 英寸 OLED(SSD1306 驱动),它在嵌入式应用里极为常见,扮相简洁,显示效果清晰,且功耗低、引脚少。下面,让我们一起看看如何在 APM32F402 平台上和这块小屏“愉快共舞”吧!

# 2. 选择 OLED0.96:小巧玲珑,效果出众

## 2.1 为何选 OLED0.96

OLED 屏可以说是嵌入式开发中一朵“秀气的小花”——它身材不大、功耗不高,对 MCU 的资源占用也不算太苛刻。对于不需要复杂彩色 GUI 的场景(比如只想简单显示数字或字符),黑白OLED 0.96 英寸屏幕就已足够美观、易读。

此外,SSD1306 这款驱动芯片有现成的开源库和移植教程,不论是采用 I2C 通信还是 SPI 通信,都能快速上手。只要稍加修改,就能在 APM32、STM32、甚至其他品牌的 MCU 上跑个欢。正所谓“万金油式驱动”,开发者再熟悉不过。

我之前就[利用APM32F4 DAL库驱动OLED(SSD1306)](https://bbs.21ic.com/icview-3406306-1-1.html)介绍过这个OLED在APM32F4上的驱动,但我们之前选择的是DAL驱动库,在APM32F402平台上我想选择极海的标准库,那从DAL库到标准库,我们看看要经历什么吧。

## 2.2 I2C 初始化(移植到 APM32F4 标准库)

我们这里使用 PB8 / PB9 这两脚来做 I2C1 的 SCL 和 SDA( **PB6/PB7 在APM32F402EVB板上用作 LED** ),只需要通过 GPIO\_ConfigPinRemap 等函数来把引脚重映射即可。下面是示例初始化函数:

```c
void I2C1_Config(void)
    {
      GPIO_Config_T gpioConfigStruct;
      I2C_Config_T i2cConfigStruct;

      /* Enable I2C related Clock */
      RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOB | RCM_APB2_PERIPH_AFIO);
      RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_I2C1);

      GPIO_ConfigPinRemap(GPIO_REMAP_I2C1); // SCL—PB8, SDA—PB9

      /* Free I2C_SCL and I2C_SDA */
      gpioConfigStruct.mode = GPIO_MODE_AF_OD;
      gpioConfigStruct.speed = GPIO_SPEED_50MHz;
      gpioConfigStruct.pin = GPIO_PIN_8;
      GPIO_Config(GPIOB, &gpioConfigStruct);

      gpioConfigStruct.mode = GPIO_MODE_AF_OD;
      gpioConfigStruct.speed = GPIO_SPEED_50MHz;
      gpioConfigStruct.pin = GPIO_PIN_9;
      GPIO_Config(GPIOB, &gpioConfigStruct);

      /* Config I2C1 */
      I2C_Reset(I2C1);
      i2cConfigStruct.mode = I2C_MODE_I2C;
      i2cConfigStruct.dutyCycle = I2C_DUTYCYCLE_2;
      i2cConfigStruct.ackAddress = I2C_ACK_ADDRESS_7BIT;
      i2cConfigStruct.ownAddress1 = 0XA0;
      i2cConfigStruct.ack = I2C_ACK_ENABLE;
      i2cConfigStruct.clockSpeed = 400000; // fast mode 400k
      I2C_Config(I2C1, &i2cConfigStruct);

      /* Enable I2Cx */
      I2C_Enable(I2C1);
    }
```

初始化思路相当简单,无非是:

**1. 时钟打开;
2. GPIO 重映射并配置成开漏输出;
3. I2C 外设设定模式、地址、时钟;
4. 开启 I2C 外设。**

## 2.3 ssd1306 驱动迁移到 APM32F402 标准库

接下来,就该说到驱动文件了。我们在把DAL库中 使用 I2C 写SSD1306寄存器操作改成APM32F402 标准库即可。下面这段示例演示了如何发送指令和数据:

```c
void OLED_I2C_SendByte(uint8_t addr, uint8_t Byte)
    {
      I2C_EnableGenerateStart(I2C1);
      while (!I2C_ReadStatusFlag(I2C1, I2C_FLAG_START));
      I2C_Tx7BitAddress(I2C1, SSD1306_I2C_ADDR, I2C_DIRECTION_TX);
      while (!I2C_ReadStatusFlag(I2C1, I2C_FLAG_ADDR));
      (void)I2C1->STS2;

      I2C_TxData(I2C1, addr);
      while (!I2C_ReadStatusFlag(I2C1, I2C_FLAG_BTC));
      (void)I2C1->STS2;

      I2C_TxData(I2C1, Byte);
      while (!I2C_ReadStatusFlag(I2C1, I2C_FLAG_BTC));
      I2C_EnableGenerateStop(I2C1);
      I2C_ClearIntFlag(I2C1, I2C_INT_FLAG_STOP);
    }
```

然后替换核心的**ssd1306_WriteCommand、ssd1306_WriteData**函数:

```c
// Send a byte to the command register
    void ssd1306_WriteCommand(uint8_t byte) {
      OLED_I2C_SendByte(0x00, byte);
    }

    // Send data
    void ssd1306_WriteData(uint8_t* buffer, size_t buff_size) {
      while (buff_size--) {
      OLED_I2C_SendByte(0x40, *buffer);
      buffer++;
    }}
```

还有就是在 ssd1306_conf.h 中定义一些宏:

1. 添加APM32F402xx_STD、SSD1306_USE_I2C_STD,这些宏声明我们在使用标准库
2. 替换DAL库的延时函数等,我这里直接使用极海官方的板载延时驱动

```c
// Choose a microcontroller family
#define APM32F402xx_STD


// Choose a bus
#define SSD1306_USE_I2C
#define SSD1306_USE_I2C_STD

#define DAL_LOGI(tag, fmt)printf("[%s] " fmt, tag)

#define DAL_DelayBOARD_Delay_Ms
#define DAL_GetTick BOARD_ReadTick
```

# 3. 显示设置:来点“花式摆盘”

有了驱动,咱们就可以在屏幕上随心所欲地显示各种信息。我写了个函数 ssd1306\_DisPlay(...),把当前距离、温度、湿度值,转成字符串并绘制到 OLED 上。示例使用 Font\_11x18 这种较大的字体,让数字更清晰:

```c
void ssd1306_DisPlay(float Dist, int8_t temp, int8_t humi)
    {
      // Use a buffer to store strings
      char buff;

      // 1) 在第一行显示 "Dist:xxx m"
      ssd1306_SetCursor(2, 0);
      snprintf(buff, sizeof(buff), "Dist:%.2f m", Dist/1000);
      ssd1306_WriteString(buff, Font_11x18, White);

      // 2) 在第二行显示 "Temp: xx °C"
      ssd1306_SetCursor(2, 18);// 18 是行高(根据字体大小调节)
      snprintf(buff, sizeof(buff), "Temp:%d*C", temp);
      ssd1306_WriteString(buff, Font_11x18, White);

      // 3) 在第三行显示 "Humi: xx %"
      ssd1306_SetCursor(2, 36);// 18*2 = 36
      snprintf(buff, sizeof(buff), "Humi:%d   %%", humi);
      ssd1306_WriteString(buff, Font_11x18, White);

      // 4) 调用更新函数,将显示缓冲区的内容刷新到屏幕
      ssd1306_UpdateScreen();
    }
```

这里说明一下“°C”这个符号在字库里可能没有,所以我这里示例用 “*C” 或者你可以改成“°C”,然后保证字体库包含度符号即可。

屏幕显示效果:

!(data/attachment/forum/202506/30/195225cnufnirhfrrrdhv2.png "PixPin_2025-06-30_19-44-57.png")

+罗马仕充电宝效果:

!(data/attachment/forum/202506/30/200518lg8xu55zuuriwzju.gif "PixPin_2025-06-30_20-05-00.gif")

# 4. 总结:从测量到可视化,只差一块屏

前面我们利用 APM32F402 强大的定时器功能来驱动 HC-SR04,借助滤波算法稳定测量数据,并考虑温湿度补偿应对环境因素,以确保测量精度,如今借助SSD1306显示,让它足以手持外出测量。

这里是代码哦:[!(/source/plugin/zhanmishu_markdown/template/editor/images/upload.svg) 附件:APM32F402_403_SDK_V1.0.1_HC_SR04_DHT11__OLED.zip](forum.html?mod=attachment&aid=2419710 "attachment")

至此,我们已从“测量”跨越到“可视化”,实现了一个真正脱离电脑、独立运行的超声测距仪表雏形。现在,不妨带上这块小屏,走出实验室,尽情测量吧!

kai迪皮 发表于 2025-6-30 20:05

#申请原创# @21小跑堂

转瞬回声 发表于 2025-6-30 20:38

话说,你们的OLED为什么都是I2C接口的,而我的是SPI接口的啊

kai迪皮 发表于 2025-7-1 09:10

转瞬回声 发表于 2025-6-30 20:38
话说,你们的OLED为什么都是I2C接口的,而我的是SPI接口的啊

不同规格的,SPI接口其实非常不错的比I2C接口快许多

shanyuxiang 发表于 2025-7-1 13:38

转瞬回声 发表于 2025-6-30 20:38
话说,你们的OLED为什么都是I2C接口的,而我的是SPI接口的啊

OLED的驱动芯片一般是既支持IIC也支持SPI,通过修改模块上的几个电阻可以进行切换。
页: [1]
查看完整版本: APM32F402 & HC-SR04超声测距应用场景:加个小屏幕