【CW32无线抄表项目】模拟电压(VC)比较器
一、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.EN2)配置VCx_CR0.INP,选择正端输入3)配置VCx_CR0.INN,选择负端输入4)如果负端或正端要用内部阈值,先配VCx_DIV.VIN和VCx_DIV.DIV5)配置VCx_CR0.HYS6)配置VCx_CR0.POL7)如果要做窗口比较,再配置VCx_CR0.WINDOW8)配置VCx_CR1.FLTCLK和VCx_CR1.FLTTIME9)如果要中断,配置VCx_CR1[7:5]和VCx_CR0.IE10)清状态标志(如果手册定义有清标志位)11)打开VCx_CR0.EN12)通过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; // 正端:接外面的水表 PB00 VC_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 验证...\r\n"); // // 第一步:写入 kunkun // W25Q_DMA_Write_Kunkun(0x0000); // // // // // 第二步:读取回来 // W25Q_DMA_Read_Back(0x0000); // // 第三步:验证 // if (strcmp((char*)CW_DMA_RxBuf1, "kunkun") == 0) { // printf("验证通过!收到了:%s\r\n", CW_DMA_RxBuf1); // } else { // printf("验证失败,收到了垃圾数据。\r\n"); // } // 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("当前水表圈数: %d\r\n", WaterPulseCount); } } } void System_Init_Config(void) { RCC_Configuration(); GPIO_Configuration(); SPI_Configuration(); EXTI_Configuration(); ADC_Configuration(); }四、实物展示
效果演示
如图所示传感器和磁铁在至于同一平面时,只有从下往上计数会加1,从上往下则不会。
磁极的“盲区”与极性 (Magnet Orientation)
这一串磁铁,磁极通常是在圆面的两头(轴向充磁)。
- 磁场角度:AH812 这种线性霍尔传感器最喜欢垂直穿过它身体的磁力线。
- 当你由下往上划时,磁铁底部的磁场刚好以最佳角度“切”过了传感器。而当你换个方向划,磁场的角度发生了细微偏转,导致垂直分量变小,电压就达不到 2.67V 了。
