在智能硬件实验中,传感器的价值并不只是"读到一个数值",更重要的是把真实环境的变化转化成程序可以判断的状态。霍尔传感器就是一个典型例子。当磁铁靠近或远离传感器时,模拟输出电压会发生变化,程序通过 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.I2C、fpioa_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=6、sda=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 远程监控等内容。理解了"采集数据、建立基准、计算偏移、判断状态"这条主线,更多模拟传感器实验都可以沿用同样的编程思路。