• 正文
  • 相关推荐
申请入驻 产业图谱

【CW32无线抄表项目】模拟电压(VC)比较器

1小时前
28
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

一VC介绍

特性 ADC (模数转换) VC (电压比较器)
反应速度 慢(需要采样、转换、计算) 极快(纳秒级响应,几乎瞬间)
功耗 高(ADC 模块很费电) 极低(微安级,适合电池供电)
省心程度 需要 CPU 不停地去“问”结果 自动触发(电压一变立马发中断,CPU 可以睡觉)

纯硬件电路(电压比较器 VC)来代替软件算法(ADC 采样),从而解放 CPU。

框图详情

整体结构

VC1 和 VC2 是两套几乎对称的比较器通道,每路都有 正输入 INP负输入 INN、比较器核心、极性控制、窗口模式、数字滤波和中断逻辑。

比较结果最终既可以作为内部状态/标志使用,也可以形成 VC1_OUT / VC2_OUT 输出信号,并触发 VC1中断 / VC2中断

输入部分怎么理解

INPINN 前面都有一个多路选择器,说明比较器输入源不是固定的,而是可以选不同通道。

外部通道可以选 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 不一定完全独立,也可以组合成一个“窗口比较器”。

窗口模式的意义

正常模式下,VC1VC2 各比各的。

窗口模式下,通常一个比较器作为“下限阈值”,另一个作为“上限阈值”:

低于下限

落在窗口内

高于上限

这样就很适合做“区间检测”,比如电池电压是否在正常范围内,而不只是“高于某个点”。

图中两个比较器中间交叉和逻辑门的部分,就是在做这个窗口逻辑组合。

数字滤波和状态输出

数字滤波 模块说明比较结果不是直接裸输出,而是可以经过稳定化处理。

VCx_CR1.FLTCLK:滤波时钟。

VCx_CR1.FLTTIME:滤波时间/滤波长度。

这类设计通常用于抑制噪声、毛刺、慢速抖动。

滤波后的结果会反映到:

VCx_SR.FLTV:滤波后的状态标志

VCx_OUT:最终输出信号

中断部分

触发条件选择 由 VCx_CR1[7:5] 控制,通常就是配置:

上升沿触发

下降沿触发

双沿触发

或某种电平/状态触发

VCx_CR0.IE:中断使能。

条件满足并且中断使能后,输出 VCx中断

这张图对应的典型用途

电压阈值检测:比如某路电压是否超过设定值

欠压/过压检测:配合内部参考和分压器

窗口检测:判断电压是否落在某个安全区间

零点检测/波形边沿检测

传感器阈值比较:温度、模拟量越界判断

读图时最关键的信号链

输入选择:INP/INN 选谁

比较器配置:ENHYS

输出逻辑:POL

是否启用窗口:WINDOW

是否做数字滤波:FLTCLKFLTTIME

是否开中断:IE 和触发条件选择

配置流程

第一步,确定使用 VC1 还是 VC2,以及是“单路比较”还是“窗口比较”

第二步,配置比较器输入源:谁接 INP,谁接 INN

第三步,如果要用内部参考/分压阈值,先配置分压器

第四步,配置比较器本体参数:使能前先设 HYSPOLWINDOW

第五步,配置数字滤波:FLTCLKFLTTIME

第六步,配置中断触发条件和中断使能

第七步,最后打开比较器 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

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

以开放、共享、互助为理念,致力于构建武汉芯源半导体CW32系列MCU生态社区。无论是嵌入式MCU小自还是想要攻破技术难题的工程师,亦或是需求解决方案的产品经理都可在CW32生态社区汲取营养、共同成长。