基于立创PY32F002A单片机的电池内阻测试仪:从硬件设计到GNU ARM汇编编程全解析
基于立创PY32F002A单片机的电池内阻测试仪从硬件设计到GNU ARM汇编编程全解析最近在立创商城淘到了几片PY32F002A这颗超低成本的ARM Cortex-M0单片机想着不能让它吃灰就动手做了一个高精度的电池内阻测试仪。这玩意儿不仅能测电池内阻还能测电容的ESR等效串联电阻和电流采样电阻毫欧档分辨率能达到0.01毫欧对于分析电池健康状态或者挑选低ESR电容来说是个非常实用的工具。很多朋友对如何用低成本MCU做精密测量感兴趣但又觉得模拟电路和底层编程门槛太高。别担心今天我就把自己从画电路板到写汇编代码的整个过程掰开揉碎了讲给你听。咱们不玩虚的就从最基础的原理开始一步步带你做出这个能测0.1毫欧电阻的小工具。1. 硬件设计信号链是如何搭建的整个仪器的核心思路很简单给电池注入一个微小的、已知频率的交流电流信号然后测量电池两端产生的交流电压。根据欧姆定律电压除以电流就等于电阻。难点在于电池内阻通常只有几毫欧甚至零点几毫欧而电池本身的直流电压有好几伏我们要从这巨大的直流电压背景中精准地提取出那微小的交流电压信号。1.1 信号发生与恒流源首先我们需要一个纯净的交流信号源。我选择了1kHz的正弦波这个频率不高不低既能避开市电50Hz的干扰又不会因为频率太高而引入太多分布参数的影响。第一步从PWM到正弦波PY32F002A的定时器可以输出PWM波。我让单片机在PB3引脚输出一个1kHz的PWM但这个PWM是方波含有大量高频谐波不能直接用。解决办法是用一个简单的RC低通滤波器电阻R和电容C组成把方波“平滑”成近似正弦波。原理就像用筛子筛沙子高频成分被滤掉了剩下的就是基础的1kHz正弦波。第二步信号抬升与缓冲单片机输出的信号是0到3.3VVCC摆动的。为了后续电路处理方便我们需要把这个信号的中心点抬升到电源电压的一半也就是1.65V。这样信号就在1.65V上下波动有正有负相对于1.65V这个“地”来说。 具体做法是经过RC滤波后的正弦波先通过一个隔直电容C15去掉直流分量变成一个以0V为中心的正弦波。然后通过两个电阻分压在运放U2.1的同相输入端提供一个VCC/21.65V的偏置电压。U2.1接成电压跟随器它的作用是“缓冲”输入阻抗极高几乎不从前级电路吸取电流输出阻抗极低可以驱动后级电路而不被影响。这样我们就得到了一个以1.65V为基准的、纯净的1kHz正弦波。第三步恒流源驱动这是关键一步我们要把电压信号转换成恒定的电流信号注入到电池里。这里用到了运放U2.2构成的经典恒流源电路。原理运放会努力使其同相输入端和反相输入端-的电压相等。我们把U2.1输出的正弦波电压Vin接到U2.2的同相端。那么反相端的电压也会等于Vin。电流计算反相端的电压Vin施加在采样电阻R_sense三个并联的100欧电阻等效约33.3欧上。根据欧姆定律流过R_sense的电流就是I Vin / R_sense。恒流输出这个电流同样会流过连接在运放输出端和反相端之间的被测电池通过H3、H5端口。因为运放的“虚短”特性只要Vin和R_sense不变这个电流I就是恒定的不随电池内阻变化而改变这就是“恒流源”。保护输出端并联的TVS管和四个10uF电容是为了保护运放免受电池接入时的冲击并确保1kHz交流信号能顺利耦合进去。注意这里的恒流值是Vin的峰值 / 33.3欧。Vin是1.65V基准上的正弦波假设其幅度为A伏那么注入电池的交流电流幅度就是A/33.3安培。我设计的目标测量电流是20mA左右所以需要合理设置前级PWM的占空比和RC滤波参数来控制A的大小。1.2 电压采样与仪表放大器用四线开尔文测试法能消除测试线电阻的影响。H3、H5注入电流H4、H6则专门用来测量电池两端的电压。电压采样点H4、H6上感应到的信号是电池内阻Rx上产生的压降Vx I * Rx叠加在电池本身的直流电压上。这个直流电压可能高达4.2V锂电池。隔直与抬升首先用C43、C44两个100nF电容隔掉危险的直流高压只让1kHz的交流小信号Vx通过。同样地信号通过电容后中心点又回到了0V需要再次用电阻网络将其偏置到1.65V以便后续运放处理。这里用了四个三极管接成二极管形式来提供偏置相比普通二极管其反向漏电流更小对微弱信号的影响更小。仪表放大器Vx信号非常微弱可能只有几十到几百微伏需要放大。我用了三个通用运放如LMV358搭建了一个经典的仪表放大器。优点输入阻抗极高不会从被测电池吸取显著电流共模抑制比CMRR高能有效抑制电池直流电压这个“共模信号”只放大我们需要的交流差模信号Vx。放大倍数图中电阻配置的放大倍数约为21倍。放大后的信号就足够单片机ADC去采集了。1.3 量程切换与单片机接口为了覆盖毫欧和欧姆两个量程我使用了模拟开关U5如CD4053。量程切换原理毫欧档信号直接进入仪表放大器放大21倍后送入单片机ADC。欧姆档信号先经过电阻分压衰减比如10倍再进入仪表放大器放大21倍。这样对于同一个电压信号欧姆档下ADC读到的数值相当于毫欧档的1/10。好处两个档位信号路径的相位延迟相移几乎完全相同。这意味着我们只需要校准一个档位的相位另一个档位自然就是准的大大简化了软件校准。单片机连接PB3输出1kHz PWM作为信号源。PA0, PA1连接在一起作为ADC输入引脚采集放大后的电压信号。为什么用两个通道可能是为了同步采样或其他诊断目的在代码中我们看到ADC配置了通道0和1。PB0, PA7, PA4SPI接口用于驱动数码管显示。PA2, PA3按键输入用于切换量程、校准等。H8(SWDIO), H9(SWCLK)SWD调试下载接口必备。2. 软件核心用汇编实现精密测量逻辑软件全部用GNU ARM汇编编写追求极致的效率和可控性。整个测量逻辑是一个精妙的协作过程。2.1 系统初始化与信号生成首先需要让整个系统“动”起来。1. 生成1kHz正弦波 使用定时器1TIM1的PWM模式通过查表法输出正弦波。代码中配置了ARR自动重装载值和CCR捕获比较值来设定频率和占空比波形。cos_sin_biao_1k这个表里就存储了一个正弦周期的数据定时器会循环读取并输出再经过硬件RC滤波就得到了模拟正弦波。tim1chushiha: ldr r0, 0x40012c00 TIM1 控制寄存器基地址 movs r1, #0 str r1, [r0, #0x28] PSC (预分频器)设为0不分频 ldr r1, 47999 str r1, [r0, #0x2c] ARR (自动重装载值)决定PWM频率 ldr r1, 0x6800 str r1, [r0, #0x18] CCMR1 (通道模式寄存器)配置PWM模式 ldr r1, 0x10 str r1, [r0, #0x20] CCER (捕获比较使能寄存器)使能输出 ldr r1, 0x8000 str r1, [r0, #0x44] BDTR (刹车和死区寄存器)主要使能主输出 ldr r1, 0x81 str r1, [r0] CR1 (控制寄存器1)启动定时器2. 触发ADC采样 另一个定时器3TIM3被配置为100kHz的触发频率。它每隔10微秒1/100kHz产生一个触发信号去启动一次ADC转换。tim3chushihua: ldr r3, 0x40000400 TIM3 基地址 ldr r2, 0 str r2, [r3, #0x28] PSC 0 ldr r2, 959 str r2, [r3, #0x2c] ARR 959配合系统时钟产生100kHz更新率 movs r2, #0x20 str r2, [r3, #0x04] 配置TRGO主模式输出为更新事件触发 str r1, [r3] 启动TIM3 (CR1寄存器已在之前设置)3. ADC与DMA配置 这是数据采集的核心。ADC被设置为由TIM3触发、连续扫描模式同时采样PA0和PA1两个通道。最关键的是配合DMA直接存储器访问。__adc_dma: ldr r0, 0x40020000 DMA1 基地址 ldr r1, 0x40012440 外设地址ADC数据寄存器(ADC_DR) str r1, [r0, #0x10] 写入DMA_CPAR外设地址寄存器 ldr r1, 0x20000160 储存器地址内存中存放ADC数据的数组 str r1, [r0, #0x14] 写入DMA_CMAR存储器地址寄存器 ldr r1, 1000 传输数量1000个数据 str r1, [r0, #0x0c] 写入DMA_CNDTR数据数量寄存器 ldr r1, 0x35a1 传输模式配置 str r1, [r0, #0x08] 写入DMA_CCR配置寄存器使能循环模式循环模式DMA传输完1000个数据后会自动从头开始覆盖旧数据。这样我们就能始终在内存中保有最新的1000个采样点。双通道虽然原理上只需要一个电压信号但代码中配置了双通道采样。这可能用于监控参考信号或实现差分输入增强抗干扰能力。4. SysTick定时器 配置为每10毫秒中断一次。这个中断是整个测量节奏的“心跳”每隔10ms我们就对DMA缓冲区里最新的1000个点代表10个完整的1kHz波形周期进行一次计算。2.2 数字信号处理从采样值到电阻值每10msSysTick中断服务程序被调用开始处理数据。第一步离散傅里叶变换DFT我们的目标是从ADC采样值中提取出1kHz信号的幅度和相位。虽然用了100kHz采样但数据里可能包含噪声。DFT相当于一个非常精准的“滤波器”只取出1kHz这个特定频率成分的信息。 代码中的__dft子程序就是计算这1000个点与1kHz标准正弦波、余弦波的相关性最终得到实部R和虚部I。这代表了被测信号在1kHz频率上的“投影”。_systickzhongduan: push {r0-r4,lr} __suan_dft: bl __dft 调用DFT计算返回实部在R0虚部在R1 ldr r2, shangbi_rr ldr r3, shangbi_ii str r0, [r2] 存储原始实部 str r1, [r3] 存储原始虚部第二步滑动平均滤波直接计算出的实部虚部可能仍有波动。为了读数更稳定我对其进行了200点的滑动平均滤波__lv_bo_qi子程序。这相当于一个低通滤波器让显示值不会跳得太厉害。第三步计算幅度并处理相位计算幅度滤波后的实部(R)和虚部(I)通过__ji_suan_fu_du子程序计算幅度Magnitude sqrt(R^2 I^2)。这个幅度值与电池内阻Rx成正比。相位旋转这是消除测试线电感影响的关键技巧。测试夹和导线会引入微小的电感导致测量到的电压信号相对电流信号有一个相位偏移不再是纯电阻。校准先用测试夹夹住一个已知的、无感的精密电阻比如1欧姆的金属膜电阻。计算相位通过atan2(I, R)函数计算出此时测量信号的相位角θ。这个θ就是系统固有的相位误差。旋转归零在后续测量任何未知元件时都将测量得到的复数(R, I)旋转一个角度(-θ)。旋转公式如下代码中已实现R_corrected R * cosθ I * sinθ I_corrected I * cosθ - R * sinθ只取实部旋转后理想纯电阻的虚部I_corrected应为0。我们只使用实部R_corrected来计算电阻。这样测量结果就只对纯电阻敏感不受测试线电感的影响了。第四步量程切换根据当前档位liangcheng变量控制模拟开关的引脚代码中地址0x50000400疑似为GPIO端口基地址切换信号通路。ldr r0, liangcheng ldr r2, 0x50000400 ldr r3, [r0] cmp r3, #1 beq __haoou_dang 如果量程为1跳转到毫欧档 __ou_dang: 欧姆档 movs r3, #1 lsls r3, r3, #16 操作某个GPIO引脚可能是PB0 str r3, [r2, #0x18] 控制模拟开关切换到衰减路径 b __systick_fanhui __haoou_dang: 毫欧档 movs r3, #1 str r3, [r2, #0x18] 控制模拟开关切换到直通路径2.3 校准让测量结果可信任何精密测量仪器都离不开校准。这个内阻仪提供了三个校准菜单P-00 短路清零将测试夹直接短路此时理想读数应为0。但由于运放失调、PCB漏电等因素可能有一个很小的底数。在这个菜单下调整将这个底数归零。P-01 相位校准夹住一个1欧姆的无感标准电阻调整相位角参数使测量结果的虚部为零或相位角显示为0°。这一步就是上面提到的消除测试线电感影响的关键操作。P-02 标准电阻校准夹住一个已知阻值的标准电阻例如10毫欧或1欧姆调整幅度增益系数使仪器显示值与标准电阻的标称值一致。校准操作流程按住“切换档位”键的同时再按另一个键进入校准模式。数码管显示“P-00”按“加”键可在P-00、P-01、P-02间切换。按另一个键进入当前显示的校准项。在校准项内用两个按键调整数值。调整好后同时按两个键保存到单片机的Flash中并退出到测量状态。提示校准顺序建议为先做“相位校准”P-01再做“标准电阻校准”P-02最后做“短路清零”P-00。对于毫欧档如果找不到很小的标准电阻可以用一个稍大的如10毫欧校准后再通过“短路清零”来微调系统零位也能获得很好的效果。3. 实战效果与总结按照上面的设计打板、焊接、编写程序后我进行了一系列测试。从实物照片可以看到用四线开尔文夹测量0.1毫欧、0.2毫欧、0.3毫欧、0.5毫欧和1毫欧的电阻显示都非常稳定和准确证明了整个设计是成功的。这个项目带给我们的启示低成本MCU也能干精密活PY32F002A虽然便宜但具备16位ADC、DMA、定时器等必要外设配合合理的模拟前端设计和数字信号处理算法完全能满足毫欧级测量需求。模拟电路是基础恒流源、仪表放大器、隔直、偏置这些模拟电路设计是信号质量的保证。数字算法再厉害也救不了一个糟糕的模拟前端。数字处理是灵魂DFT算法精准提取特征频率相位旋转消除系统误差滑动滤波提高显示稳定性这些软件算法将硬件的潜力发挥到了极致。校准至关重要没有校准的测量仪器只是玩具。通过简单的三点校准可以补偿掉元器件公差、PCB布局、测试线等带来的几乎所有系统误差。希望这篇详细的解析能帮你打通从模拟电路到嵌入式编程的任督二脉。自己动手做一遍你会对精密测量有完全不一样的理解。