液位传感器模块
原理 · 硬件 · 程序 三位一体演示

基于 STM32F103C8T6 + 0.96寸 OLED + 电阻式液位传感器, 通过 ADC 采样实现水位的实时采集与显示。 传感器裸露铜线浸入水中时电阻变化,经分压后产生可测量的模拟电压信号, MCU 以 12 位精度将电压转换为 0~100% 液位百分比并实时刷新 OLED 屏幕。

主控 STM32F103C8T6 屏幕 0.96" OLED (I²C) 采样 ADC1_CH0 (PA0) 分辨率 12-bit (0~4095) 更新 1 Hz 输出 0 ~ 100 % 采样周期 55.5 ADC Clocks 电压范围 0 ~ 3.3 V
100 80 60 40 0 S Live Simulation
OVERVIEW

项目概览

本演示项目使用电阻式液位传感器采集水位信号,MCU 通过 ADC 通道读取模拟电压,并在 OLED 屏幕上实时显示液位百分比。

1 传感器信号采集

电阻式传感器输出随水位变化的模拟电压,经分压后送入 ADC1_IN0 (PA0)。

  • 12 位分辨率 (0 ~ 4095)
  • 采样周期 239.5 个 ADC 时钟
  • ADC 时钟 9 MHz

2 数据换算与限幅

将原始 ADC 数值换算为 0 ~ 100 的液位百分比,并做限幅保护。

  • 换算公式:ADC/4095 × 100 × 4
  • 上限 100 截断
  • 1 秒刷新一次

3 OLED 实时显示

通过软件 I²C (PB14/PB15) 驱动 0.96 寸 OLED,居中显示当前液位。

  • 从机地址 0x78
  • 显示格式 level:XX
  • 8×16 ASCII 字符
PRINCIPLE & FEATURE

传感器原理与功能

本模块板载电阻式液位传感器,基于电导变化检测水位,适用于连续液位监测场景。

电阻式水位传感器 · 工作原理

100% 90% 80% 70% 60% 50% 40% 30% 20% 10%

传感器由 10 根平行裸露导线 等距排布在 PCB 上(每根代表 10% 液位刻度)。 当水位上升浸没导线时,水(含有微量离子)的导电性 将原本不相连的导线两两接通,形成分压电路。浸没的导线越多,输出电压越高。

分压原理
Vout = Vref × (N / 10)
N = 浸没导线数,N ∈ {0, 1, ..., 10}
当前:N=5 → Vout = 50%
  • 工作电压DC 3.3 ~ 5 V
  • 输出形式模拟电压 0 ~ Vref
  • 检测精度10% 步进(10 段)
  • 特点结构简单 / 成本低 / 实时性好
  • 局限性接触液体 / 易腐蚀 / 不可用于污浊或导电性差液体
HARDWARE

原理图 & PCB 封装展示

本模块板载两组 3Pin 接口:J1 接电阻式水位传感器(模拟量),J2 接非接触式液位传感器(数字量)。

原理图

i 接口说明

  • J1 · 电阻式GND / VCC / I/O
  • J2 · 非接触式VCC / SIGNAL / GND
  • 主控STM32F103C8T6
  • 屏幕0.96" OLED (I²C)

! 注意:原理图与代码引脚不一致

原理图中 I/O 接 PA4、SIGNAL 接 PA6; 但示例程序实际使用的是 PA0 (ADC1_IN0)。 实际接线需将传感器信号线接到 PA0,或修改源码匹配原理图引脚。

PCB封装

ii PCB 引脚分布

J1(电阻式)

  • 1I/O
  • 2VCC
  • 3GND

J2(非接触式)

  • 1GND
  • 2SIG
  • 3VCC
CODE ANALYSIS

程序源码分析

本节分析核心源码:main.c 主循环、adc.c ADC 驱动、gpio.c 引脚初始化。代码由 STM32CubeMX + Keil 工具链生成,使用 HAL 库。

/* main.c - 主循环 */
#include "main.h"
#include "adc.h"
#include "gpio.h"
#include "./HAL/OLED/OLED.h"

uint32_t Time_Now;
uint8_t  level;

int main(void)
{
  HAL_Init();
  SystemClock_Config();          // HSE 8M × PLL9 = 72 MHz
  MX_GPIO_Init();
  MX_ADC1_Init();
  OLED_Init();

  while (1)
  {
    if (HAL_GetTick() - Time_Now >= 1000)   // 每 1 s 刷新一次
    {
      HAL_ADC_Start(&hadc1);
      if (HAL_ADC_PollForConversion(&hadc1, 999) == HAL_OK)
        level = HAL_ADC_GetValue(&hadc1)/4095.0*100*4;
      HAL_ADC_Stop(&hadc1);

      if (level >= 100) level = 100;   // 限幅

      OLED_Clear();
      OLED_CenterPrintf(24, "level:%d", level);
      OLED_Update();

      Time_Now = HAL_GetTick();
    }
  }
}
/* adc.c - ADC1 通道 0 初始化 (PA0) */
ADC_HandleTypeDef hadc1;

void MX_ADC1_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};

  hadc1.Instance                   = ADC1;
  hadc1.Init.ScanConvMode          = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode    = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv      = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign             = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion       = 1;
  HAL_ADC_Init(&hadc1);

  sConfig.Channel      = ADC_CHANNEL_0;      // PA0
  sConfig.Rank         = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
  HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}

void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if (adcHandle->Instance == ADC1) {
    __HAL_RCC_ADC1_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    GPIO_InitStruct.Pin   = GPIO_PIN_0;       // PA0
    GPIO_InitStruct.Mode  = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  }
}
/* gpio.c - GPIO 时钟使能 */
void MX_GPIO_Init(void)
{
  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();   // LED 等外设
  __HAL_RCC_GPIOA_CLK_ENABLE();   // PA0 / OLED SCL
}

/* 注:PB14/PB15 (I2C for OLED) 的初始化在 OLED.c 中的 OLED_GPIO_Init() 完成 */
/*     SCL = PB15, SDA = PB14, 开漏输出 */

A 系统时钟 72 MHz

外部晶振 HSE 8 MHz,通过 PLL ×9 升频到 72 MHz 主频。 ADC 时钟来自 PCLK2 (72 MHz) 经 8 分频,得到 9 MHz。

B ADC 通道选择

程序使用 ADC_CHANNEL_0,对应 PA0 引脚; 采样时间选最长档 239.5 cycles,以获得更稳定的读数。

C 换算公式

原始 ADC 范围 0~4095,公式为 ADC/4095 × 100 × 4。 ×4 是该传感器的校准系数,因分压电阻设计使最大有效输出约为满量程的 1/4。

D 非阻塞时序

用 HAL_GetTick() 差值判断 1 秒周期,while(1) 不会因等待 ADC 而阻塞。 每次循环 Start → Poll → GetValue → Stop,单次轮询模式。

E OLED 软件 I²C

OLED 的 SCL/SDA 使用 PB15/PB14,通过 GPIO_MODE_OUTPUT_OD 模拟 I²C 总线。 从机地址 0x78。

! 注意

原理图标注 I/O 接 PA4、SIGNAL 接 PA6,但本程序绑定 PA0。实际接线需按程序连 PA0,或修改 ADC_CHANNEL。

关键换算公式 · Key Formula
level = ADC ÷ 4095 × 100 × 4
4095 为 12-bit ADC 满量程,×4 是该模块分压电阻的标定系数,结果再被限幅到 0~100。
FLOW CHART

主程序流程图

下图展示从 MCU 上电到 OLED 初始化再到定时循环采集的完整流程,包含循环回路。

系统上电 / Reset HAL_Init() 系统初始化 SystemClock_Config() HSE 8M × PLL9 = 72MHz MX_GPIO_Init() 使能 GPIOA / GPIOD MX_ADC1_Init() ADC1_CH0 (PA0) · 239.5 cycles OLED_Init() PB14/PB15 · I²C 0x78 while(1) 1s 到? HAL_GetTick() 否 是 HAL_ADC_Start + PollForConversion level = ADC/4095×100×4 换算液位百分比 if(level≥100) level=100 OLED_Clear() 清屏 OLED_Printf(24,"level:%d") 居中显示液位 OLED_Update() 刷新显示 Time_Now = HAL_GetTick() ↺ 循环回路
LIVE DEMO

液位实时模拟演示

拖动滑块或点击「自动模拟」,右侧 OLED 屏幕会按主程序逻辑显示 level:XX,严格遵循源码换算公式。OLED 显示与实物一致:黑色背景、白色像素点阵。

0.96" OLED · 模拟显示 128×64 · SSD1306
ADC 原始1638
换算后160
限幅100
公式追溯
① 原始 ADC 值:1638
② 归一化:1638 / 4095 = 0.400
③ 换算百分比:0.400 × 100 = 40.0
④ 校准 ×4:40.0 × 4 = 160
⑤ 限幅到 100:100