【CanMV K210】传感器实验 霍尔传感器磁场方向与强度检测

在智能硬件实验中,传感器的价值并不只是"读到一个数值",更重要的是把真实环境的变化转化成程序可以判断的状态。霍尔传感器就是一个典型例子。当磁铁靠近或远离传感器时,模拟输出电压会发生变化,程序通过 ADC 采集这个变化,就能判断是否存在磁场、磁场偏移方向以及磁场强弱程度。

本实验使用 CanMV K210 开发板通过 I2C 总线连接 PCF8591 模数转换模块,再读取模拟霍尔传感器输出的 ADC 数据。程序启动后会先进行无磁场基准校准,正式运行时不断采集 ADC 平均值,并与基准值比较。偏移量较小时认为没有明显磁场,偏移量达到一定范围后判断为弱磁场或强磁场,同时根据偏移正负区分磁场方向 A 与方向 B。

学习目标 说明
理解模拟霍尔传感器 认识磁铁靠近、远离和方向变化对模拟输出电压的影响
掌握 PCF8591 ADC 读取 使用 PCF8591 将霍尔传感器输出的模拟电压转换成数字值
理解基准校准 通过无磁场状态下的平均值建立后续判断参考
学会偏移量判断 根据当前 ADC 值与基准值的差值判断磁场方向和强弱
建立传感器调试思路 通过预热、丢弃初始值、多次采样平均和状态变化打印提高稳定性

这个实验的重点不是简单读取一个 ADC 数字,而是建立"采集数据、建立基准、计算偏移、判断状态"的完整流程。后续很多模拟传感器实验,例如光照强度检测、声音强度检测、距离模拟量检测、旋钮输入检测,都可以沿用类似的处理方法。

文章目录

理论基础

模拟霍尔传感器用于检测磁场变化。磁铁靠近传感器时,传感器输出端的电压会发生变化;磁铁远离时,输出电压又会回到接近无磁场的状态。由于输出是连续变化的模拟电压,CanMV K210 不能直接通过普通数字 GPIO 精确读取,所以需要借助 PCF8591 这类 ADC 模数转换模块。

PCF8591 位于 CanMV K210 和模拟霍尔传感器之间。霍尔传感器负责把磁场变化转换成电压变化,PCF8591 负责把电压变化转换成数字值,CanMV K210 再通过 I2C 总线读取这些数字值。程序拿到 ADC 数值后,不直接把它当成最终结果,而是先和无磁场基准值比较,计算偏移量,再根据偏移量判断是否有磁场、磁场强弱以及偏移方向。

本实验中的"方向 A"和"方向 B"是程序根据 ADC 偏移正负定义的两个状态。偏移为正时,说明当前 ADC 值高于无磁场基准值;偏移为负时,说明当前 ADC 值低于无磁场基准值。由于不同霍尔模块、磁铁极性和安装方向会影响输出变化方向,所以这里不直接写成南极或北极,而是使用方向 A、方向 B 这种更适合实验调试的命名。
#mermaid-svg-V6A52Dvf3ZkMtIwy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-V6A52Dvf3ZkMtIwy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-V6A52Dvf3ZkMtIwy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-V6A52Dvf3ZkMtIwy .error-icon{fill:#552222;}#mermaid-svg-V6A52Dvf3ZkMtIwy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-V6A52Dvf3ZkMtIwy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-V6A52Dvf3ZkMtIwy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-V6A52Dvf3ZkMtIwy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-V6A52Dvf3ZkMtIwy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-V6A52Dvf3ZkMtIwy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-V6A52Dvf3ZkMtIwy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-V6A52Dvf3ZkMtIwy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-V6A52Dvf3ZkMtIwy .marker.cross{stroke:#333333;}#mermaid-svg-V6A52Dvf3ZkMtIwy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-V6A52Dvf3ZkMtIwy p{margin:0;}#mermaid-svg-V6A52Dvf3ZkMtIwy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-V6A52Dvf3ZkMtIwy .cluster-label text{fill:#333;}#mermaid-svg-V6A52Dvf3ZkMtIwy .cluster-label span{color:#333;}#mermaid-svg-V6A52Dvf3ZkMtIwy .cluster-label span p{background-color:transparent;}#mermaid-svg-V6A52Dvf3ZkMtIwy .label text,#mermaid-svg-V6A52Dvf3ZkMtIwy span{fill:#333;color:#333;}#mermaid-svg-V6A52Dvf3ZkMtIwy .node rect,#mermaid-svg-V6A52Dvf3ZkMtIwy .node circle,#mermaid-svg-V6A52Dvf3ZkMtIwy .node ellipse,#mermaid-svg-V6A52Dvf3ZkMtIwy .node polygon,#mermaid-svg-V6A52Dvf3ZkMtIwy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-V6A52Dvf3ZkMtIwy .rough-node .label text,#mermaid-svg-V6A52Dvf3ZkMtIwy .node .label text,#mermaid-svg-V6A52Dvf3ZkMtIwy .image-shape .label,#mermaid-svg-V6A52Dvf3ZkMtIwy .icon-shape .label{text-anchor:middle;}#mermaid-svg-V6A52Dvf3ZkMtIwy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-V6A52Dvf3ZkMtIwy .rough-node .label,#mermaid-svg-V6A52Dvf3ZkMtIwy .node .label,#mermaid-svg-V6A52Dvf3ZkMtIwy .image-shape .label,#mermaid-svg-V6A52Dvf3ZkMtIwy .icon-shape .label{text-align:center;}#mermaid-svg-V6A52Dvf3ZkMtIwy .node.clickable{cursor:pointer;}#mermaid-svg-V6A52Dvf3ZkMtIwy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-V6A52Dvf3ZkMtIwy .arrowheadPath{fill:#333333;}#mermaid-svg-V6A52Dvf3ZkMtIwy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-V6A52Dvf3ZkMtIwy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-V6A52Dvf3ZkMtIwy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V6A52Dvf3ZkMtIwy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-V6A52Dvf3ZkMtIwy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V6A52Dvf3ZkMtIwy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-V6A52Dvf3ZkMtIwy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-V6A52Dvf3ZkMtIwy .cluster text{fill:#333;}#mermaid-svg-V6A52Dvf3ZkMtIwy .cluster span{color:#333;}#mermaid-svg-V6A52Dvf3ZkMtIwy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-V6A52Dvf3ZkMtIwy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-V6A52Dvf3ZkMtIwy rect.text{fill:none;stroke-width:0;}#mermaid-svg-V6A52Dvf3ZkMtIwy .icon-shape,#mermaid-svg-V6A52Dvf3ZkMtIwy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V6A52Dvf3ZkMtIwy .icon-shape p,#mermaid-svg-V6A52Dvf3ZkMtIwy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-V6A52Dvf3ZkMtIwy .icon-shape .label rect,#mermaid-svg-V6A52Dvf3ZkMtIwy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V6A52Dvf3ZkMtIwy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-V6A52Dvf3ZkMtIwy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-V6A52Dvf3ZkMtIwy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-V6A52Dvf3ZkMtIwy .sensor>*{fill:#E8F3FF!important;stroke:#5B9BFF!important;stroke-width:2px!important;color:#1F3B6D!important;}#mermaid-svg-V6A52Dvf3ZkMtIwy .sensor span{fill:#E8F3FF!important;stroke:#5B9BFF!important;stroke-width:2px!important;color:#1F3B6D!important;}#mermaid-svg-V6A52Dvf3ZkMtIwy .sensor tspan{fill:#1F3B6D!important;}#mermaid-svg-V6A52Dvf3ZkMtIwy .adc>*{fill:#EAFBF1!important;stroke:#42B983!important;stroke-width:2px!important;color:#1F5C3A!important;}#mermaid-svg-V6A52Dvf3ZkMtIwy .adc span{fill:#EAFBF1!important;stroke:#42B983!important;stroke-width:2px!important;color:#1F5C3A!important;}#mermaid-svg-V6A52Dvf3ZkMtIwy .adc tspan{fill:#1F5C3A!important;}#mermaid-svg-V6A52Dvf3ZkMtIwy .bus>*{fill:#FFF4E8!important;stroke:#F4A261!important;stroke-width:2px!important;color:#7A4A00!important;}#mermaid-svg-V6A52Dvf3ZkMtIwy .bus span{fill:#FFF4E8!important;stroke:#F4A261!important;stroke-width:2px!important;color:#7A4A00!important;}#mermaid-svg-V6A52Dvf3ZkMtIwy .bus tspan{fill:#7A4A00!important;}#mermaid-svg-V6A52Dvf3ZkMtIwy .code>*{fill:#F4EEFF!important;stroke:#8E6AD8!important;stroke-width:2px!important;color:#3D2B68!important;}#mermaid-svg-V6A52Dvf3ZkMtIwy .code span{fill:#F4EEFF!important;stroke:#8E6AD8!important;stroke-width:2px!important;color:#3D2B68!important;}#mermaid-svg-V6A52Dvf3ZkMtIwy .code tspan{fill:#3D2B68!important;}#mermaid-svg-V6A52Dvf3ZkMtIwy .power>*{fill:#FCEEF4!important;stroke:#E76F51!important;stroke-width:2px!important;color:#7A2740!important;}#mermaid-svg-V6A52Dvf3ZkMtIwy .power span{fill:#FCEEF4!important;stroke:#E76F51!important;stroke-width:2px!important;color:#7A2740!important;}#mermaid-svg-V6A52Dvf3ZkMtIwy .power tspan{fill:#7A2740!important;} 磁铁靠近或远离

磁场发生变化
模拟霍尔传感器

输出模拟电压
PCF8591 AIN0

采集模拟输入
ADC 转换

模拟电压转数字值
I2C 通信

SCL / SDA
CanMV K210

物理引脚 6 / 7
Python 程序

读取 ADC 平均值
基准比较

offset = value - baseline
状态判断

无磁场 / 弱磁场 / 强磁场 / 方向A / 方向B
模块供电

VCC
公共地线

GND

这张流程图展示的是电路和数据之间的完整关系。磁场变化并不会直接变成程序状态,而是先影响霍尔传感器的模拟电压,再由 PCF8591 转成 ADC 数值,最后由程序完成基准比较和阈值判断。这个过程比普通数字开关传感器更复杂,但也能获得更细的变化趋势。

程序中还加入了预热、丢弃初始值和多次采样平均。这样做的原因是模拟量读取容易受到模块上电、I2C 通信、传感器波动和输入噪声影响。直接读取一次 ADC 值很容易出现瞬间偏差,而多次采样取平均可以让结果更平稳。无磁场基准校准则可以适应不同模块的初始输出差异,避免把某个固定 ADC 值硬写死。

硬件设施

本实验真正使用到的核心硬件包括 CanMV K210 开发板、PCF8591 ADC 模数转换模块和模拟霍尔传感器。CanMV K210 本身无法直接从普通 GPIO 读取模拟电压,因此需要借助 PCF8591 把霍尔传感器的模拟信号转换成数字量。软件侧主要使用 machine.I2C 创建软件 I2C 总线,使用 pcf8591 驱动模块读取 ADC 通道数据,使用 time.sleep_ms() 控制采样间隔和初始化等待时间。

接线关系可以先通过下面这张图建立整体印象。CanMV K210 的物理引脚 6 和物理引脚 7 连接到 PCF8591 的 SCL、SDA,模拟霍尔传感器的输出端接入 PCF8591 的 AIN0 通道,模块之间还需要正确连接供电和 GND。

硬件 / 软件 作用 说明
CanMV K210 开发板 实验运行平台 执行 MicroPython 程序,并通过 I2C 总线读取外部 ADC 数据
模拟霍尔传感器 磁场检测模块 磁铁靠近时输出模拟电压变化,电压变化会反映到 ADC 数值上
PCF8591 模数转换模块 ADC 采集模块 将模拟霍尔传感器输出的模拟电压转换为数字量
物理引脚 6 I2C SCL 作为软件 I2C 时钟线
物理引脚 7 I2C SDA 作为软件 I2C 数据线
machine.I2C I2C 通信模块 用于创建软件 I2C 总线,连接 PCF8591 模块
fpioa_manager.fm 引脚功能映射辅助模块 在软件 I2C 初始化时绑定 GPIOHS 功能
pcf8591 ADC 驱动模块 封装 PCF8591 的读取逻辑,通过 read() 获取指定通道 ADC 值
time 延时控制模块 控制模块预热、采样间隔、循环检测节奏

实验中用到的核心模块如下。PCF8591 负责模拟量转换,模拟霍尔传感器负责感知磁场变化,CanMV K210 负责读取 ADC 数据并完成状态判断。调试时不要只看模块是否插上,还需要确认 SCL、SDA、AIN0、VCC 和 GND 是否都接对。

开发板物理引脚 / 接口 通信功能 代码变量 对应硬件 说明
6 I2C SCL I2C_SCL_PIN PCF8591 SCL 作为软件 I2C 时钟线
7 I2C SDA I2C_SDA_PIN PCF8591 SDA 作为软件 I2C 数据线
GPIOHS1 I2C SCL 映射功能 gscl=fm.fpioa.GPIOHS1 软件 I2C 时钟辅助映射 用于配合软件 I2C 引脚功能绑定
GPIOHS2 I2C SDA 映射功能 gsda=fm.fpioa.GPIOHS2 软件 I2C 数据辅助映射 用于配合软件 I2C 引脚功能绑定
PCF8591 AIN0 ADC 输入通道 ADC_CHANNEL = 0 模拟霍尔传感器输出端 霍尔模拟信号接入 PCF8591 的第 0 通道
VCC 模块供电 代码未直接体现 PCF8591 与霍尔传感器供电端 保证 ADC 模块和传感器正常工作
GND 公共地线 代码未直接体现 CanMV、PCF8591 与传感器 GND 保证 I2C 通信和 ADC 采样具有统一参考电平

完成接线后的整体效果如下。程序运行时,保持磁铁远离传感器完成基准校准;校准完成后,再移动磁铁观察串口中的 ADC 原始值、基准值、偏移量、当前状态和磁场强度。

实验现象 正常表现 异常提示
程序启动 串口提示正在校准无磁场基准值 启动阶段应让磁铁远离传感器
无磁场状态 ADC 偏移量较小,显示未检测到磁铁 状态频繁跳动时需要增大阈值或增加采样次数
磁铁靠近 ADC 值相对基准出现明显偏移 数值不变化时检查霍尔输出是否接入 AIN0
磁铁翻转方向 偏移量正负可能发生变化 方向 A / B 与安装方向和磁铁极性有关
磁铁更近 偏移量增大,可能从弱磁场变成强磁场 强弱分界不合适时调整 STRONG_MAGNET_THRESHOLD
程序需要运行两次才正常 首次控制字未写入或首次 ADC 数据不稳定 检查 pcf8591.py_last_ctl 是否初始化为 None

软件代码

本实验代码围绕"稳定读取模拟霍尔传感器数据"展开。程序不是简单读取一次 ADC,而是加入了 I2C 频率配置、模块预热、丢弃初始数据、多次采样取平均、无磁场基准校准、偏移量计算和状态变化打印等处理。这些设计能减少初始读数不稳定、瞬间波动和环境基准差异带来的影响。

软件环境 作用 检查重点
CanMV IDE 编辑、运行和调试 K210 程序 能识别开发板串口,并能正常运行基础测试程序
CanMV 固件 提供 machine.I2Cfpioa_manager 等模块 固件环境需要支持当前 I2C 与 FPIOA 写法
USB 串口驱动 让电脑识别开发板串口 串口工具中能看到对应端口
pcf8591.py PCF8591 驱动文件 需要与主程序放在同一运行目录下
串口终端 查看霍尔传感器检测结果 能看到 ADC 原始值、基准、偏移和磁场状态

这类程序如果出现"第一次运行不正常,第二次运行才正常",重点检查 PCF8591 驱动文件。常见原因是驱动类初始化时把 _last_ctl 直接设置成默认控制字,导致第一次读取 AIN0 时 _write_control() 判断控制字没有变化,从而跳过真正的 I2C 写入。修正方式是把 _last_ctl 初始化为 None,确保第一次读取一定会写入控制字。

驱动文件保存为 pcf8591.py

python 复制代码
#********************************************
# ----湖南创乐博智能科技有限公司----
#  文件名:pcf8591.py
#  版本:V2.1
#  说明:PCF8591 模数转换传感器驱动文件
#********************************************

AOUTFLG = 0b01000000
AINPRG0 = 0b00000000
AINPRG1 = 0b00010000
AINPRG2 = 0b00100000
AINPRG3 = 0b00110000
AUTOINC = 0b00000100
ACHNNL0 = 0b00000000
ACHNNL1 = 0b00000001
ACHNNL2 = 0b00000010
ACHNNL3 = 0b00000011


class PCF8591:
    def __init__(self, i2c, addr=0x48, enable_out=True, in_program=AINPRG0):
        self.i2c = i2c
        self.addr = addr
        self._aout = self.set_out(enable_out)
        self._ainprg = self.set_program(in_program)

        # 不能初始化为 self._make_control()
        # 否则第一次 read(0) 时可能因为控制字相同而跳过 I2C 写入
        self._last_ctl = None

    def _make_control(self, auto_increment=False, channel=ACHNNL0):
        return 0 | self._aout | self._ainprg | (AUTOINC if auto_increment else 0) | channel

    def _write_control(self, control):
        if self._last_ctl is None or control != self._last_ctl:
            self.i2c.writeto(self.addr, bytes([control]))

            # PCF8591 切换通道后第一次读取可能是旧数据,这里做一次丢弃读取
            self.i2c.readfrom(self.addr, 1)

            self._last_ctl = control

    def _read_raw(self):
        return self.i2c.readfrom(self.addr, 4)

    def set_out(self, enable_out):
        self._aout = AOUTFLG if enable_out else 0
        return self._aout

    def set_program(self, in_program):
        self._ainprg = in_program
        return self._ainprg

    def read(self, channel=-1):
        if channel == -1:
            self.set_out(True)
            self._write_control(self._make_control(auto_increment=True))
            return self._read_raw()
        else:
            self._write_control(self._make_control(channel=channel))
            return int(self._read_raw()[0])

    def write(self, value):
        self.set_out(True)
        control = self._make_control()
        self._last_ctl = control
        self.i2c.writeto(self.addr, bytes([control, value]))

主程序保存为 main.py 或直接在 CanMV IDE 中运行:

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ----湖南创乐博智能科技有限公司----
# 文件名:16_analog_hall_switch_optimized.py
# 版本:V2.2
# 说明:模拟霍尔传感器实验优化版
#####################################################

from machine import I2C
from fpioa_manager import fm
import pcf8591
import time


# =========================
# 硬件配置区
# =========================

I2C_SCL_PIN = 6
I2C_SDA_PIN = 7

ADC_CHANNEL = 0

I2C_FREQ = 100000              # 100k 比 400k 更稳定
WARMUP_DELAY_MS = 500          # 初始化后等待模块稳定

DISCARD_COUNT = 8              # 正式读取前丢弃前几次 ADC 数据
SAMPLE_COUNT = 10              # 每次取平均的采样次数
CALIBRATION_COUNT = 30         # 无磁场校准采样次数

NO_MAGNET_THRESHOLD = 8        # 偏移小于该值,认为没有明显磁场
STRONG_MAGNET_THRESHOLD = 35   # 偏移大于该值,认为磁场较强

LOOP_DELAY_MS = 200


# =========================
# 初始化
# =========================

def setup():
    """初始化 I2C 和 PCF8591 模数转换模块"""
    global hall_adc

    i2c = I2C(
        I2C.I2C_SOFT,
        freq=I2C_FREQ,
        scl=I2C_SCL_PIN,
        sda=I2C_SDA_PIN,
        gscl=fm.fpioa.GPIOHS1,
        gsda=fm.fpioa.GPIOHS2
    )

    time.sleep_ms(WARMUP_DELAY_MS)

    hall_adc = pcf8591.PCF8591(i2c)

    # 丢弃前几次读数,避免第一次 ADC 数据不稳定
    discard_adc_data()

    print("模拟霍尔传感器初始化完成")


def discard_adc_data():
    """丢弃前几次 ADC 读取结果"""
    for _ in range(DISCARD_COUNT):
        hall_adc.read(ADC_CHANNEL)
        time.sleep_ms(50)


# =========================
# ADC 数据读取
# =========================

def read_adc_average():
    """多次读取 ADC 后取平均值,减少瞬间波动"""
    total = 0

    for _ in range(SAMPLE_COUNT):
        total += hall_adc.read(ADC_CHANNEL)
        time.sleep_ms(5)

    return total // SAMPLE_COUNT


def calibrate_baseline():
    """
    校准无磁场基准值

    启动程序后,保持磁铁远离霍尔传感器。
    程序会采集多次 ADC 数据,并计算平均值作为无磁场基准。
    """
    print("正在校准无磁场基准值,请保持磁铁远离传感器...")

    discard_adc_data()

    total = 0

    for _ in range(CALIBRATION_COUNT):
        total += read_adc_average()
        time.sleep_ms(20)

    baseline = total // CALIBRATION_COUNT

    print("校准完成,无磁场基准值:", baseline)
    return baseline


# =========================
# 状态判断
# =========================

def get_magnet_level(offset_abs):
    """根据 ADC 偏移量判断磁场强度"""
    if offset_abs < NO_MAGNET_THRESHOLD:
        return "无明显磁场"

    if offset_abs < STRONG_MAGNET_THRESHOLD:
        return "弱磁场"

    return "强磁场"


def get_magnet_state(value, baseline):
    """
    根据 ADC 当前值和基准值判断磁场状态

    返回:
    state  : 状态编号
    label  : 状态名称
    offset : ADC 偏移量
    level  : 磁场强度等级
    """
    offset = value - baseline
    offset_abs = abs(offset)

    level = get_magnet_level(offset_abs)

    if offset_abs < NO_MAGNET_THRESHOLD:
        return 0, "未检测到磁铁", offset, level

    if offset > 0:
        return 1, "检测到磁场方向 A", offset, level

    return -1, "检测到磁场方向 B", offset, level


def print_status(value, baseline, offset, label, level):
    """打印完整磁场检测结果"""
    print("")
    print("================================")
    print("ADC 原始值:", value)
    print("无磁场基准:", baseline)
    print("ADC 偏移量:", offset)
    print("当前状态:", label)
    print("磁场强度:", level)
    print("================================")
    print("")


# =========================
# 主循环
# =========================

def loop():
    """循环检测霍尔传感器状态"""
    baseline = calibrate_baseline()
    last_state = None

    while True:
        value = read_adc_average()
        state, label, offset, level = get_magnet_state(value, baseline)

        # 状态发生变化时,打印完整信息
        if state != last_state:
            print_status(value, baseline, offset, label, level)
            last_state = state
        else:
            # 状态未变化时,只打印简洁数据
            print("ADC:", value, "Offset:", offset, "State:", label)

        time.sleep_ms(LOOP_DELAY_MS)


# =========================
# 程序入口
# =========================

if __name__ == '__main__':
    try:
        setup()
        loop()
    except KeyboardInterrupt:
        print("程序已停止")

这段程序可以分成硬件配置、初始化、ADC 读取、基准校准、磁场状态判断、结果打印和循环检测几个部分。硬件配置区把 I2C 引脚、ADC 通道、采样次数、阈值和循环间隔集中定义,后续调整实验参数时不需要进入函数内部修改逻辑。I2C_FREQ = 100000 让软件 I2C 使用 100kHz 频率,相比更高频率更偏向稳定读取,适合传感器入门实验。

setup() 是整个程序的硬件初始化入口。这里创建软件 I2C 对象,并通过 scl=6sda=7 指定通信引脚,再把这个 I2C 对象传入 pcf8591.PCF8591(i2c) 创建 ADC 读取对象。初始化后加入 WARMUP_DELAY_MS 等待时间,是为了让 PCF8591 和 I2C 总线状态稳定。discard_adc_data() 会丢弃前几次 ADC 读数,避免程序启动瞬间的异常值影响后面的基准校准。

函数名 功能 对应现象
setup() 初始化软件 I2C 和 PCF8591 ADC 对象 程序启动后完成传感器读取准备
discard_adc_data() 丢弃启动阶段的前几次 ADC 数据 减少首次读数不稳定带来的干扰
read_adc_average() 多次读取 ADC 并取平均值 串口输出的 ADC 数值更平稳
calibrate_baseline() 采集无磁场状态下的平均值 得到后续判断使用的无磁场基准
get_magnet_level() 根据偏移幅度判断磁场强度 输出无明显磁场、弱磁场或强磁场
get_magnet_state() 根据当前值和基准值判断磁场状态 区分未检测到磁铁、方向 A、方向 B
print_status() 打印完整检测结果 状态变化时输出 ADC、基准、偏移和强度
loop() 持续读取传感器并判断状态 磁铁靠近、远离或翻转方向时,串口持续显示变化

主循环中的 last_state 用来记录上一次磁场状态。状态发生变化时,程序会打印完整信息,包括 ADC 原始值、无磁场基准、ADC 偏移量、当前状态和磁场强度。状态没有变化时,只打印简洁数据,避免串口信息过于冗长。这种写法适合传感器调试,既能观察连续变化,又能在状态切换时看到完整判断依据。

扩展应用

霍尔传感器实验常见问题大多来自 I2C 通信、ADC 通道、基准校准和阈值设置。排查时可以先确认 PCF8591 是否能正常读取,再观察无磁场状态下的 ADC 是否稳定,随后再调整阈值和采样参数。

问题现象 可能原因 处理思路
程序启动后报 I2C 或设备读取错误 SCL / SDA 接线错误、PCF8591 供电异常、I2C 总线未稳定 核对物理引脚 6 和 7 是否分别连接到 SCL、SDA,检查模块供电和 GND 是否可靠
程序第一次运行异常,第二次才正常 驱动首次没有写入控制字,或 PCF8591 首次 ADC 读数不稳定 检查 pcf8591.py_last_ctl 是否为 None,并保留预热和丢弃初始读数逻辑
ADC 数值一直不变 霍尔传感器输出未接入 AIN0、ADC 通道选择错误、传感器供电异常 确认霍尔模拟输出接入 PCF8591 的第 0 通道,必要时修改 ADC_CHANNEL
一启动就判断有磁场 校准时磁铁距离传感器太近,基准值被磁场影响 校准阶段保持磁铁远离传感器,重新运行程序建立新的基准值
状态频繁在有磁场和无磁场之间跳动 阈值过小、采样波动较大、环境干扰明显 适当增大 NO_MAGNET_THRESHOLD,或增加 SAMPLE_COUNT 提高平均采样稳定性
弱磁场和强磁场区分不明显 STRONG_MAGNET_THRESHOLD 不适合当前传感器输出范围 观察串口中的 Offset 数值,再根据实际偏移范围调整强磁场阈值
磁场方向 A / B 与预期相反 霍尔模块安装方向、磁铁极性或 ADC 偏移方向不同 以串口偏移量正负为准理解程序状态,必要时交换标签文字
无磁场状态 ADC 波动较大 模拟输入连线不稳、供电波动、传感器输出噪声较大 缩短连线,检查供电,适当增加 SAMPLE_COUNT 或提高 NO_MAGNET_THRESHOLD

模拟霍尔传感器实验的价值在于把磁场变化转化成可计算的数据。相比只输出开关状态的数字传感器,模拟霍尔传感器可以提供更细的变化趋势,程序不仅能判断有没有磁铁,还能根据 ADC 偏移量估计强弱,并用偏移正负区分不同方向。这种模式很适合用来讲解传感器采集、基准校准、阈值判断和状态机设计。

应用场景 实现思路 可扩展能力
磁铁靠近检测 通过 ADC 偏移量判断磁铁是否进入检测范围 可用于门磁、位置检测、磁吸结构识别等入门实验
磁场方向判断 根据当前 ADC 值与基准值的正负偏移区分方向 可扩展为简单的极性检测或方向变化提示
设备状态触发 当磁场状态从无磁场变为有磁场时触发程序逻辑 后续可扩展 LED、蜂鸣器或屏幕显示反馈
传感器数据滤波教学 使用多次采样平均减少瞬间波动 可继续引入滑动平均、中位数滤波等数据处理方法
阈值判断实验 通过不同阈值区分无磁场、弱磁场和强磁场 可训练传感器标定和参数调试思维
硬件调试反馈 串口输出 ADC、基准值和偏移量,观察真实硬件变化 可用于定位接线、供电、通道选择和环境干扰问题
自动化控制入口 磁场状态作为开关条件,驱动后续控制逻辑 电机、继电器、网络上传属于后续课程可扩展方向

从工程角度看,当前代码已经具备传感器项目中常见的基本结构。配置参数集中管理,初始化和采样逻辑分离,校准函数单独封装,状态判断函数返回结构化结果,主循环只负责调度和输出。这种写法比简单读取 ADC 更适合后续扩展,后面接入 LED 状态提示、LCD 数值显示、蜂鸣器报警或数据上传时,只需要在状态变化位置增加对应动作,不需要重写底层采集逻辑。

总结

本实验通过 CanMV K210 开发板、PCF8591 模数转换模块和模拟霍尔传感器,完成了磁场状态检测的完整流程。代码涉及软件 I2C 通信、ADC 通道读取、模块预热、无效数据丢弃、多次采样平均、无磁场基准校准、偏移量计算、阈值判断和状态变化输出。实验现象不再停留在"读取一个 ADC 数字",而是进一步把数字转换成未检测到磁铁、磁场方向 A、磁场方向 B、弱磁场和强磁场等可理解的状态。

这类实验适合作为传感器采集课程的进阶案例。串口中的变量和判断条件,对应的是磁铁靠近、远离和方向变化;代码中的平均采样和基准校准,对应的是硬件世界里无法完全避免的波动和误差。后续课程可以在这个基础上继续扩展 LED 指示、蜂鸣器报警、LCD 显示、传感器数据记录、AI 摄像头识别联动和 Web 远程监控等内容。理解了"采集数据、建立基准、计算偏移、判断状态"这条主线,更多模拟传感器实验都可以沿用同样的编程思路。

相关推荐
yubo05091 小时前
计算机视觉第九课:颜色 + 形状 联合识别
人工智能·计算机视觉
czzxxxxxx1 小时前
知识IP卡在变现第一步:创客匠人用一套陪跑系统回答“谁来陪你落地”
大数据·人工智能
jiayong231 小时前
ZeroClaw 使用方式与启动指南
人工智能·ai·智能体·zeroclaw
有来有去95271 小时前
【模型评测】SWE-bench Verified数据集-1-配置评测任务
人工智能·深度学习·语言模型
Lsland..1 小时前
AI Agent到底是什么
java·人工智能·llm
Akamai中国1 小时前
针对 Akamai Cloud 上的 NVIDIA RTX Pro 6000 Blackwell 进行基准测试
人工智能·云计算·gpu算力·云服务
code 小楊1 小时前
AI Agent 进阶范式 Plan-and-Execute 深度详解:原理、架构、实战与工程落地
人工智能·架构
ai产品老杨1 小时前
解耦视频流利器:如何利用 GB28181 与 RTSP 协议统一收敛多厂商设备?一套支持 Docker 部署与源码交付的边缘计算 AI 视频中台深度解析
人工智能·docker·边缘计算
Lsland..1 小时前
MCP协议AI时代的HTTP
人工智能·网络协议·http