一VC介绍
| 特性 | ADC (模数转换) | VC (电压比较器) |
| 反应速度 | 慢(需要采样、转换、计算) | 极快(纳秒级响应,几乎瞬间) |
| 功耗 | 高(ADC 模块很费电) | 极低(微安级,适合电池供电) |
| 省心程度 | 需要 CPU 不停地去“问”结果 | 自动触发(电压一变立马发中断,CPU 可以睡觉) |
用纯硬件电路(电压比较器 VC)来代替软件算法(ADC 采样),从而解放 CPU。
框图详情
整体结构
VC1 和 VC2 是两套几乎对称的比较器通道,每路都有 正输入 INP、负输入 INN、比较器核心、极性控制、窗口模式、数字滤波和中断逻辑。
比较结果最终既可以作为内部状态/标志使用,也可以形成 VC1_OUT / VC2_OUT 输出信号,并触发 VC1中断 / VC2中断。
输入部分怎么理解
INP、INN 前面都有一个多路选择器,说明比较器输入源不是固定的,而是可以选不同通道。
外部通道可以选 VCx_CH0 ~ VCx_CH7 之类的模拟输入。
内部参考源也能接进来,图里明确给了:
1.2V基准电压
ADC参考电压
中间还有一个 电阻分压器,输入可来自 VDDA 或 ADC参考电压,输出为 VCx_DIV.DIV,这说明芯片内部还能先做一次分压,再送去比较器当阈值参考。
比较器核心
中间三角形带 +/- 的就是模拟比较器本体。
VCx_CR0.EN:使能比较器。
VCx_CR0.HYS:迟滞控制。这个很重要,输入电压靠近阈值时容易抖动,开迟滞可以减少误翻转。
输出整形
VCx_CR0.POL:输出极性翻转。也就是你可以选择“正向输出”还是“反向输出”。
后面还有 VCx_CR0.WINDOW:这表示 VC1 和 VC2 不一定完全独立,也可以组合成一个“窗口比较器”。
窗口模式的意义
正常模式下,VC1、VC2 各比各的。
窗口模式下,通常一个比较器作为“下限阈值”,另一个作为“上限阈值”:
低于下限
落在窗口内
高于上限
这样就很适合做“区间检测”,比如电池电压是否在正常范围内,而不只是“高于某个点”。
图中两个比较器中间交叉和逻辑门的部分,就是在做这个窗口逻辑组合。
数字滤波和状态输出
数字滤波 模块说明比较结果不是直接裸输出,而是可以经过稳定化处理。
VCx_CR1.FLTCLK:滤波时钟。
VCx_CR1.FLTTIME:滤波时间/滤波长度。
这类设计通常用于抑制噪声、毛刺、慢速抖动。
滤波后的结果会反映到:
VCx_SR.FLTV:滤波后的状态标志
VCx_OUT:最终输出信号
中断部分
触发条件选择 由 VCx_CR1[7:5] 控制,通常就是配置:
上升沿触发
下降沿触发
双沿触发
或某种电平/状态触发
VCx_CR0.IE:中断使能。
条件满足并且中断使能后,输出 VCx中断。
这张图对应的典型用途
电压阈值检测:比如某路电压是否超过设定值
欠压/过压检测:配合内部参考和分压器
窗口检测:判断电压是否落在某个安全区间
零点检测/波形边沿检测
传感器阈值比较:温度、模拟量越界判断
读图时最关键的信号链
输入选择:INP/INN 选谁
比较器配置:EN、HYS
输出逻辑:POL
是否启用窗口:WINDOW
是否做数字滤波:FLTCLK、FLTTIME
是否开中断:IE 和触发条件选择
配置流程
第一步,确定使用 VC1 还是 VC2,以及是“单路比较”还是“窗口比较”
第二步,配置比较器输入源:谁接 INP,谁接 INN
第三步,如果要用内部参考/分压阈值,先配置分压器
第四步,配置比较器本体参数:使能前先设 HYS、POL、WINDOW
第五步,配置数字滤波:FLTCLK、FLTTIME
第六步,配置中断触发条件和中断使能
第七步,最后打开比较器 EN,再读取输出标志或等待中断
按寄存器拆开看
VCx_CR0.INP
选择比较器正端输入 VCx_INP
可选外部通道,如 VCx_CH0 ~ VCx_CH7
VCx_CR0.INN
温度传感器
1.2V基准电压
ADC参考电压
分压器输出
选择比较器负端输入 VCx_INN
除外部通道外,从图上看还能选内部源:
VCx_DIV.VIN
选择分压器输入源
图里给出的候选是 VDDA 或 ADC参考电压
VCx_DIV.DIV
设置分压比
作用是把内部电压先缩放,再作为比较阈值送入比较器
VCx_CR0.HYS
设置迟滞
输入靠近阈值有噪声时建议打开,能减少抖动误翻转
VCx_CR0.POL
设置输出极性
正常输出还是反向输出,由这个位控制
VCx_CR0.WINDOW
设置是否进入窗口比较模式
单独使用某一路时通常关闭
VC1 + VC2 组成上下限窗口检测时打开相关配置
VCx_CR1.FLTCLK
选择数字滤波时钟
VCx_CR1.FLTTIME
选择数字滤波时间/长度
值越大,一般抗毛刺越强,但响应更慢
VCx_CR1[7:5]
选择中断触发条件
从框图看,是“触发条件选择”,通常会是上升沿/下降沿/双沿一类
VCx_CR0.IE
使能该路比较器中断输出
VCx_CR0.EN
最后一步使能比较器
VCx_SR.FLTV
读取滤波后的比较结果状态
VCx_OUT
比较器最终输出信号
推荐的实际配置顺序
1) 先关闭 VCx_CR0.EN
2) 配置 VCx_CR0.INP,选择正端输入
3) 配置 VCx_CR0.INN,选择负端输入
4) 如果负端或正端要用内部阈值,先配 VCx_DIV.VIN 和 VCx_DIV.DIV
5) 配置 VCx_CR0.HYS
6) 配置 VCx_CR0.POL
7) 如果要做窗口比较,再配置 VCx_CR0.WINDOW
8) 配置 VCx_CR1.FLTCLK 和 VCx_CR1.FLTTIME
9) 如果要中断,配置 VCx_CR1[7:5] 和 VCx_CR0.IE
10) 清状态标志(如果手册定义有清标志位)
11) 打开 VCx_CR0.EN
12) 通过 VCx_SR.FLTV 轮询,或者等待 VCx 中断
翻成“使用场景”会更好理解
普通比较模式
目标:判断某个输入电压是否高于阈值
配法:
INP 接被测信号
INN 接 1.2V基准 或分压后的内部参考
配 HYS
可选 POL
WINDOW=0
需要稳定输出就开滤波
需要事件响应就开中断
窗口比较模式
目标:判断输入值是否位于某个区间内
配法:
一路比较下限
一路比较上限
两路共享同一被测量,或按手册要求分别接入
打开窗口逻辑 WINDOW
输出就不再只是单纯“大于/小于”,而是参与区间判断
典型思路:
VC1:输入与“下限阈值”比较
VC2:输入与“上限阈值”比较
两路通过窗口逻辑组合后,判断:
低于下限
落在区间内
高于上限
一个更实用的寄存器思维导图
输入从哪来:INP / INN
阈值怎么来:内部基准 / 分压器 / 外部脚
输出要不要翻转:POL
抖动要不要抑制:HYS + FLTCLK + FLTTIME
是不是要做区间检测:WINDOW
要不要中断:CR1[7:5] + IE
最后开机:EN
几个配置经验
阈值附近信号有噪声时,优先开 HYS,再决定是否加数字滤波
想快速响应边沿,用较弱滤波;想避免误触发,用较强滤波
如果只做软件轮询,重点看 VCx_SR.FLTV
如果做中断检测边沿,重点配 VCx_CR1[7:5] 和 VCx_CR0.IE
如果输入源切换频繁,建议先关 EN 再改输入选择
二霍尔模块介绍
AH812D
特性:
高带带宽 (120kHz):反应极快,不管是水表慢转还是电机快转都能抓到。
静态 2.5V 输出:这是最重要的信息。意思是当周围没有磁场时,它默认吐出 2.5V 电压。
耐热抗压:能在 -40°C 到 150°C 工作,工业级水准。
应用场景:除了咱们做的水表计圈,它还能测电流、控电机。
1脚 (VCC):电源正极。
2脚 (GND):电源负极(地)。
3脚 (VOUT):信号输出。它会把磁场的大小变成“变化的电压”从这里传给单片机的 PB00(也就是咱们连 VC 的地方)。
$$C_{BYPASS$$ (0.1uF):这是“电源滤镜”,必须靠近传感器的电源脚,防止电源杂波干扰测量。
$$C_$$ (0.5nF):这是“输出滤镜”,能让输出的电压信号更平滑,配合咱们之前代码里的数字滤波,效果翻倍。
工作电压 (4.5V - 5.5V):典型值是 5V。
静态输出 (2.5V):再次强调,没磁场时就是 2.5V。
伏笔:这就是为什么你之前测到 2.5V 的原因,也是为什么咱们要把 VC 阈值设在 2.67V 的物理依据。
中心点:横轴(输出电压)在 2.5V 时,纵轴(磁场强度)是 0。
线性关系:
磁铁的南极 (S) 靠近,电压往 4.5V 爬。
磁铁的北极 (N) 靠近,电压往 0.5V 掉。
型号区别:A、B、C、D 四条线斜率不同,代表灵敏度不同。斜率越陡(如 AH812-A),磁铁稍微动一点,电压跳得越厉害。
手册虽然推荐 0.5nF,但在低速计数的场景下(如水表、转速计),我们可以换成 470nF (104电容)。这样做相当于给信号加了一个‘减震器’,能有效防止因为磁铁手抖导致的误触发计数。
好处一:强力硬件“去噪”
霍尔传感器的输出信号比较微弱,容易受到电机或电源的杂波干扰。
0.5nF:只能滤掉极高频的电磁波。
470nF:形成了一个更强的低通滤波器。它能把绝大部分的高频毛刺直接掐死在硬件阶段,让传给单片机 PB00 的电压信号像丝绸一样顺滑。
好处二:自带硬件“防抖”
之前晃动磁铁时数值猛跳,很大一部分原因是电压在阈值附近抖动。
当用了 470nF 后,这个电容像一个“储能水槽”,它会让电压的变化变得缓慢而圆滑。
即使磁铁在边缘轻微抖动,电压也不会立刻跟着剧烈跳变。这种硬件级的延迟配合咱们代码里的数字滤波,能极大地解决“晃一下跳 145 圈”的问题。
唯一的代价:
牺牲了响应速度:用大电容会让传感器的反应变慢。如果用来检测每秒转几万转的电机,470nF 会让波形失真;但水表叶轮每秒钟可能才转几圈,这种微秒级的延迟完全可以忽略不计。
实物
钕磁铁
钕磁铁(Neodymium magnet),全称钕铁硼磁铁(NdFeB),在电子爱好者圈子里有个响亮的称号——“磁王”。
它是目前人类能制造出的磁性最强的永久磁铁。别看它通常只有一颗纽扣甚至一颗米粒那么大,它能吸起自身重量 600 倍以上的物体。同等体积下,它的磁力最强;同等磁力下,它的体积最小。
在智能水表计圈这个具体的场景下,选用钕磁铁主要有三个“非它不可”的理由:
穿透力强(体积小): 水表的叶轮封装在充满水的密封腔内,而我们的传感器 PB00 是装在干燥的电路板上的。磁力必须穿过加厚的塑料外壳才能被感应到。普通磁铁如果想穿透这么厚的壳,体积会变得巨大,根本塞不进叶轮;而钕磁铁只需要一小块,就能释放出足够的磁通量。
极高的稳定性(寿命长): 水表一装就是几年甚至十年。普通磁铁容易随着时间流逝或温度变化而退磁(磁力变弱),导致计圈越来越不准。钕磁铁具有极高的矫顽力,只要不遇到极高温(通常高于 80℃),它的磁性能保持很多年不衰减。
线性响应更清晰: 配合选用的 AH812 线性霍尔传感器,钕磁铁能提供非常稳定的磁场梯度。这意味着当它靠近时,产生的电压变化非常干脆、线性度好,不会给单片机的 VC 模块带来模糊的“灰色地带”。
建立“磁力桥梁”
当叶轮旋转时,嵌在上面的钕磁铁会随之转动。磁铁周围存在着看不见的磁感线。当磁铁靠近 AH812 时,这些磁感线会垂直穿过传感器的芯片表面。
激发“霍尔效应”
AH812 内部有一个半导体薄片。平时电流平稳流过,输出 2.5V。当钕磁铁的磁场扫过时,磁力会把薄片里的电子推向一边。根据磁场强弱,电子偏转的程度不同,输出端的电压就会随之起伏(比如从 2.5V 爬升到 3.2V)。
触发“逻辑闸门”
这就是咱们之前代码干的事了:
钕磁铁远在天边:传感器输出 2.5V —> VC 比较器发现 2.5V < 2.67V 没动静。
钕磁铁近在眼前:传感器输出 3.2V —>VC 比较器发现 3.2V > 2.67V 触发中断,计数器 WaterPulseCount 加 1。
三程序详情
第一步:确定使用模式 & 关使能
流程图节点:单路比较模式 -> 选 VC2 -> 关闭比较器使能
对应代码:我们选择了 VC2,因为你的霍尔传感器物理引脚 PB00 就是焊在 VC2 的通道 3 上。
第二步:是否使用内部分压阈值? (造一把尺子)
流程图节点:是 -> 配置 VCx_DIV.VIN (选源) -> 配置 VCx_DIV.DIV (设分压比)
对应代码:
VC_DivStruct.VC_DivVref = VC_DivVref_VDDA; // 原料:选 3.3V 供电VC_DivStruct.VC_DivValue = 51; // 比例:切成 63 份,取 51 份
第三步:配置输入源 (牵线搭桥)
流程图节点:配置 VCx_CR0.INP (正端) / INN (负端)
对应代码:
VC_InitStruct.VC_InputP = VC_InputP_Ch3; // 正端:接外面的水表 PB00VC_InitStruct.VC_InputN = VC_InputN_DivOut; // 负端:接内部的 2.67V 标尺
第四步:配置比较器本体 (定规矩)
流程图节点:配置 HYS (迟滞) / POL (极性) / WINDOW (窗口)
对应代码:
VC_InitStruct.VC_Hys = VC_Hys_20mV; // 迟滞:20mV 缓冲带VC_InitStruct.VC_Polarity = VC_Polarity_Low; // 极性:正常输出VC_InitStruct.VC_Window = VC_Window_Disable; // 窗口:不搞花里胡哨的双重比较
第五步:是否启用数字滤波? (戴上降噪耳机)
流程图节点:是 -> 配置 FLTCLK (时钟) -> 配置 FLTTIME (时间)
对应代码:
VC_InitStruct.VC_FilterEn = VC_Filter_Enable;VC_InitStruct.VC_FilterTime = VC_FltTime_63Clk; // 听大约 0.4 毫秒
第六步:是否启用中断? & 清标志
流程图节点:是 -> 选触发条件 -> 使能中断 -> 清除标志位
对应代码:
VC2_ITConfig(VC_IT_FALL | VC_IT_RISE, ENABLE); // 靠近和离开都喊我VC2_EnableIrq(VC_INT_PRIORITY); // 打开法官办公室的门VC2_ClearIrq(); // 把以前的旧案子档案全撕了
第七步:使能比较器 & 等待
流程图节点:VCx_CR0.EN = 1 -> 等待中断
对应代码:
VC2_EnableChannel(); // 法官,醒醒,开工了!VC_EnableNvic(ADC_IRQn, VC_INT_PRIORITY); // 打开老板办公室的对讲机接收端
vc.h
#ifndef __VC_H#define __VC_H#include "cw32f030.h"#include "cw32f030_vc.h"#include "cw32f030_gpio.h"#include "cw32f030_rcc.h"// 1. 使用 extern 声明全局变量,告诉 main.c 这些变量的存在// 注意:这里绝对不能写 "= 0",只声明,不赋值!extern volatile boolean_t gFlagIrq;extern volatile uint32_t WaterPulseCount;// 2. 声明初始化函数void WaterMeter_VC_Init(void);#endif /* __VC_H */
vc.c
#include "vc.h"// 1. 在这里真正地定义并初始化变量(它们的主人是 vc.c)volatile boolean_t gFlagIrq = FALSE;volatile uint32_t WaterPulseCount = 0;/*** @brief 水表计圈 VC 模块完整初始化* 包含 GPIO、分压器、比较器、滤波器以及中断的配置*/void WaterMeter_VC_Init(void){VC_InitTypeDef VC_InitStruct;VC_DivTypeDef VC_DivStruct;VC_BlankTypeDef VC_BlankStruct;VC_OutTypeDef VC_OutStruct;// 1. 开启时钟__RCC_GPIOB_CLK_ENABLE();__RCC_VC_CLK_ENABLE();// 2. 将 PB00 设置为模拟输入模式 (VC2_CH3)PB00_ANALOG_ENABLE();// 3. 配置内部分压器产生 2.67V 阈值 (3.3V * 51 / 63)VC_DivStruct.VC_DivVref = VC_DivVref_VDDA;VC_DivStruct.VC_DivEn = VC_Div_Enable;VC_DivStruct.VC_DivValue = 51;VC1VC2_DIVInit(&VC_DivStruct);// 4. 配置比较器通道VC_InitStruct.VC_InputP = VC_InputP_Ch3;VC_InitStruct.VC_InputN = VC_InputN_DivOut;VC_InitStruct.VC_Hys = VC_Hys_20mV;VC_InitStruct.VC_Resp = VC_Resp_High;// 开启硬件滤波 (63个时钟周期)VC_InitStruct.VC_FilterEn = VC_Filter_Enable;VC_InitStruct.VC_FilterClk = VC_FltClk_RC150K;VC_InitStruct.VC_FilterTime = VC_FltTime_63Clk;VC_InitStruct.VC_Window = VC_Window_Disable;VC_InitStruct.VC_Polarity = VC_Polarity_Low;VC2_ChannelInit(&VC_InitStruct);// 5. 初始化空白窗口和输出连接VC1VC2_BlankInit(&VC_BlankStruct);VC2_BlankCfg(&VC_BlankStruct);VC1VC2_OutInit(&VC_OutStruct);VC2_OutputCfg(&VC_OutStruct);// 6. 开启中断VC2_ITConfig(VC_IT_FALL | VC_IT_RISE, ENABLE);VC2_EnableIrq(VC_INT_PRIORITY);VC2_ClearIrq();VC2_EnableChannel();// 7. 开启 NVIC 中断通道VC_EnableNvic(ADC_IRQn, VC_INT_PRIORITY);}
main.c
#include "main.h"#include "cw32f030_gpio.h"#include "cw32f030_rcc.h"#include "init.h"#include "buffer.h"#include "fun.h"#include "radio.h"#include "delay.h"#include "flashhoufun.h"#include "cw32_eval_spi_flash.h"#include "dma.h"#include "vc.h"// 全局中断标志 (fun.c 也要用)volatile uint8_t g_bIrqTriggered = 0;void System_Init_Config(void);//int32_t main(void)//{// // 1. 硬件初始化// System_Init_Config();//// // 2. 射频初始化// if (rf_init() != OK)// {// while(1); // 失败报警// }// rf_set_default_para();// // 3. 初始状态设置 (编译时决定)// #ifdef SLAVE_MODE// // [从机] 上电必须开启接收,否则听不到第一句// rf_enter_single_timeout_rx(15000);// #endif//// // [主机] 不需要预先接收,它会主动发送// while (1)// {// // === 1. 优先处理中断 (公共逻辑) ===// if (g_bIrqTriggered)// {// g_bIrqTriggered = 0;// rf_irq_process(); // SPI 读取状态// }// // === 2. 业务逻辑 (编译时二选一) ===// #ifdef MASTER_MODE// OnMaster();// #endif// #ifdef SLAVE_MODE// OnSlave();// #endif// }////}//int32_t main(void)//{// System_Init_Config();//// SPI_FLASH_Init();//// flash_fun();// while (1)// {// }//}//int32_t main(void)//{// System_Init_Config(); // 初始化时钟和串口// SPI_FLASH_Init(); // 初始化 SPI 硬件// SPI2_DMA_Init(); // 初始化 DMA 配置// printf("开始 DMA 验证...rn");// // 第一步:写入 kunkun// W25Q_DMA_Write_Kunkun(0x0000);//////// // 第二步:读取回来// W25Q_DMA_Read_Back(0x0000);// // 第三步:验证// if (strcmp((char*)CW_DMA_RxBuf1, "kunkun") == 0) {// printf("验证通过!收到了:%srn", CW_DMA_RxBuf1);// } else {// printf("验证失败,收到了垃圾数据。rn");// }// while(1);//}int main(void){// 1. 初始化基础外设//LED_Init();// 2. 调用封装好的 VC 模块初始化WaterMeter_VC_Init();// 3. 开启内核全局中断总闸 (所有配置就绪后,最后开总闸)__enable_irq();// 4. 主循环while (1){// 直接使用 vc.h 里 extern 声明过来的标志位if(gFlagIrq){PB09_TOG();gFlagIrq = FALSE;// 这里可以加一句串口打印,用来观察 WaterPulseCount// printf("当前水表圈数: %drn", WaterPulseCount);}}}void System_Init_Config(void){RCC_Configuration();GPIO_Configuration();SPI_Configuration();EXTI_Configuration();ADC_Configuration();}
实物展示
效果演示
如图所示传感器和磁铁在至于同一平面时,只有从下往上计数会加1,从上往下则不会。
磁极的“盲区”与极性 (Magnet Orientation)
这一串磁铁,磁极通常是在圆面的两头(轴向充磁)。
磁场角度:AH812 这种线性霍尔传感器最喜欢垂直穿过它身体的磁力线。
当你由下往上划时,磁铁底部的磁场刚好以最佳角度“切”过了传感器。而当你换个方向划,磁场的角度发生了细微偏转,导致垂直分量变小,电压就达不到 2.67V 了。
扫码加入QQ群3群| 610403240
28