基于立创·地文星CW32F030C8T6开发板的数字电压电流表设计与实现

基于立创·地文星CW32F030C8T6开发板的数字电压电流表设计与实现 基于立创·地文星CW32F030C8T6开发板的数字电压电流表设计与实现最近有不少朋友在嘉立创训练营里做项目看到大家用「立创·地文星」开发板玩出了不少花样。我也用这块板子做了一个便携式的数字电压电流表功能虽然比不上专业万用表但自己动手从画板子到写代码把东西做出来的过程成就感满满。这个项目麻雀虽小五脏俱全涵盖了电源设计、信号采样、MCU编程和自动量程切换非常适合想从点灯进阶到实际小产品开发的嵌入式初学者。今天我就把自己做这个项目的全过程包括硬件设计里踩过的坑、软件环境的搭建都详细梳理一遍希望能帮你少走些弯路。1. 项目整体规划与硬件设计思路我的目标是做一个能拿在手里、用一套表笔就能测直流电压、电流和电阻的小工具。核心就是利用CW32F030C8T6这块MCU内部的ADC模数转换器来读取外部模拟信号然后通过计算转换成我们看得懂的数字显示。整个硬件系统可以分成几个关键部分给整个系统供电的电源电路、为ADC提供精准参考的基准电压源、测量外部电压的分压采样电路、测量电流的运放放大电路以及测量电阻的分压法电路。下面咱们就一块一块来看。1.1 电源输入与防反接保护电源是系统的基石必须稳定可靠。我参考了范例项目的设计使用了一颗线性稳压器LDO来将外部输入的较高电压比如9V电池或24V适配器降为MCU和运放需要的3.3V。提示这里选择LDO而不是开关电源DCDC主要是考虑到LDO输出噪声小对后级敏感的模拟电路比如ADC基准源干扰更小虽然效率低一些但对于这种小功率设备完全够用。我在电源输入端加了一个小改动——防反接保护。听训练营的李工讲过可以用一个反向并联的二极管来实现。具体就是在电源正极串联一个二极管防止反接时电流倒灌同时再反向并联一个二极管用于在电源意外反接时形成泄放通路保护前级电路。虽然增加了少许压降但能有效防止因电池或电源接反而烧毁板子对于经常插拔的表笔接口来说很实用。LED指示灯我用的是1.5kΩ的限流电阻。原设计用10kΩ是为了省电且不刺眼但我用的是贴片LED亮度本来就不高用10k可能就看不到了所以换成了1.5k。1.2 精准的电压基准源ADC要把模拟电压转换成数字值需要一个非常稳定的“标尺”这就是电压基准源。MCU内部虽然有参考电压但精度和温漂通常不如专用的基准芯片。我选用了一颗输出2.5V的电压基准芯片。这个2.5V基准有两个用途作为ADC的参考电压让ADC的测量结果更准确。为电阻测量电路供电用一个精准的电压去激励待测电阻测量结果会更可靠。在画这部分电路时我有点纠结。理论上基准电压源的输出布线要尽量简洁减少干扰最好能单独走线回到芯片的GND。但受限于板子空间和布线难度我最终只是保证了电气连接的通畅。从实际测试来看效果也还行如果大家对精度有极致追求可以在后续版本中优化这部分布局。1.3 电压采样与自动量程切换电压测量原理很简单就是利用电阻分压把待测的高电压按比例降低到ADC能安全测量的范围比如0-3.3V。但为了能测量较宽范围的电压比如0-30V就需要不同的分压比例也就是不同的量程。我设计了两档量程大量程分压比大用于测量较高的电压。小量程分压比小用于精确测量较低的电压。如何实现自动切换呢我用到了MOSFET金属-氧化物半导体场效应晶体管作为电子开关。通过MCU控制MOSFET的导通与关断来切换接入分压电路的下拉电阻阻值从而改变量程。这里我踩了一个大坑最初画板子时我把NMOS管放在了分压电路的上端高侧并且把它的漏极D和源极S接反了。结果电路完全无法工作。原因在于对于NMOS管要让它完全导通栅极G的电压必须比源极S的电压高出一个门槛值Vgs(th)。如果我把它用作高侧开关当它导通时源极S的电压会接近输入的高电压比如30V。那么为了维持导通栅极G就需要一个高于30V Vgs(th)的电压这通常需要额外的电荷泵电路来产生非常麻烦。正确的做法是将NMOS用作低侧开关。如下图所示让它控制接地端的电阻。这样它的源极S始终接地0V我们只需要给栅极G一个3.3V的逻辑高电平就能轻松使其导通控制简单可靠。正确设计NMOS作为下管控制下端电阻的通断在软件逻辑上我让MCU的ADC先默认使用大量程进行测量。如果测得的电压值低于某个预设的阈值比如1V说明输入电压较低使用大量程会导致分辨率不足。此时MCU就控制MOSFET切换到小量程电阻再进行一次更精确的测量从而实现“自动量程切换”。1.4 电流采样电路学习参考电流测量通常是通过测量一个已知阻值的采样电阻两端的电压然后根据欧姆定律I V / R计算得出。我设计了一个基于运放的放大电路用来放大采样电阻上的微小压降。这个电路结构比普通的同相放大器多了两个电阻它有一个好处理论上可以作为“高侧采样”即采样电阻可以放在电源正极路径上不影响负载的地电位。但是在我的设计中这个电路最终是作为“低侧采样”来用的。因为我要实现电压、电流、电阻测量共用同一套表笔黑表笔始终接地所以采样电阻只能放在负载和地之间。注意这个用通用运放搭建的电流采样电路实际效果并不理想仅供学习原理。主要问题是当采样电阻上的压降非常微小时比如uV级别通用运放自身的输入失调电压、温漂等误差会被放大导致测量结果不准。在实际产品中测量微小电流应该选用专门的“电流感应放大器”芯片。为了减小误差我实际焊接时使用了阻值稍大100mΩ的采样电阻并配合10倍放大。这比使用10mΩ电阻、放大100倍的方案误差要小一些。1.5 电阻测量电路测量电阻我用了最简单的分压法。原理是将一个已知阻值的精密电阻R_ref和待测电阻R_x串联然后用一个已知的精准电压我用了前面提到的2.5V基准源给它们供电。这样ADC测量的是R_x上的分压。根据分压公式V_adc 2.5V * (R_x / (R_ref R_x))我们就能反推出R_x R_ref * (V_adc / (2.5V - V_adc))。在这个电路中我用了PMOS管作为开关。为什么不用NMOS呢因为分压点ADC测量点的电压不是0V地。如果用NMOS做开关当它需要导通时其源极电压可能很高又会出现之前提到的“需要高栅极电压”的问题。而PMOS管作为高侧开关则很合适用低电平0V就能控制其导通。提示根据我的代码设置ADC使用1.5V内部参考电压。这意味着ADC能测量的最大电压是1.5V。通过计算可以得出这套分压电路能测量的最大电阻值约为150kΩ。如果需要测量更大阻值如兆欧级需要调整参考电阻或采用其他测量方法如恒流源法。2. 软件开发环境搭建macOS篇很多朋友用Windows下的Keil或IAR但我平时用macOS比较多所以折腾了一套基于CMake ARM-GCC PyOCD的开发环境。这套环境跨平台而且更贴近开源和命令行我觉得对理解编译链很有帮助。2.1 安装必要的工具首先需要安装三个核心工具ARM-GCC 编译器负责把C代码编译成CW32芯片能执行的机器码。可以去ARM官网下载或者通过macOS的包管理器Homebrew安装brew install arm-gcc-binCMake一个跨平台的构建系统生成器。它根据CMakeLists.txt配置文件来生成适合你当前平台的构建脚本比如Makefile。brew install cmakePyOCD一个开源的ARM Cortex-M调试器软件。它可以通过DAP-Link、ST-Link等调试器与芯片通信实现程序烧录和调试。pip install pyocd2.2 项目结构与CMake配置我的项目代码结构大致如下voltage_current_meter/ ├── CMakeLists.txt # CMake主配置文件 ├── src/ │ ├── main.c # 主程序 │ ├── adc.c # ADC驱动代码 │ ├── gpio.c # GPIO控制代码 │ └── ... ├── inc/ # 头文件目录 ├── cw32_f030c8t6.ld # 链接脚本定义内存布局 └── CW32F030_StdPeriph_Lib/ # 芯源官方标准外设库最关键的CMakeLists.txt文件内容节选如下它定义了如何编译这个项目# 指定CMake最低版本 cmake_minimum_required(VERSION 3.10) # 定义项目名称和编程语言 project(voltage_current_meter C ASM) # 设置编译目标为单片机不使用标准C库的系统调用 set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR ARM) # 指定交叉编译工具链前缀 set(CMAKE_C_COMPILER arm-none-eabi-gcc) set(CMAKE_ASM_COMPILER arm-none-eabi-gcc) set(CMAKE_CXX_COMPILER arm-none-eabi-g) # 添加头文件搜索路径 include_directories( inc CW32F030_StdPeriph_Lib/inc ) # 添加源文件 file(GLOB_RECURSE SOURCES src/*.c CW32F030_StdPeriph_Lib/src/*.c ) # 添加启动文件汇编 set(STARTUP_ASM_FILE CW32F030_StdPeriph_Lib/startup/startup_cw32f030.s) # 定义编译选项优化等级、调试信息、硬件浮点等 add_compile_options( -mcpucortex-m0plus -mthumb -mfloat-abisoft -Og -g -fdata-sections -ffunction-sections ) # 定义链接选项指定链接脚本、优化内存使用 add_link_options( -T${CMAKE_SOURCE_DIR}/cw32_f030c8t6.ld -mcpucortex-m0plus -mthumb -specsnano.specs -specsnosys.specs -Wl,--gc-sections -Wl,-Map${PROJECT_NAME}.map ) # 创建可执行目标 add_executable(${PROJECT_NAME} ${SOURCES} ${STARTUP_ASM_FILE}) # 设置生成Hex和Bin格式文件用于烧录 set(HEX_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.hex) set(BIN_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.bin) add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_OBJCOPY} -O ihex $TARGET_FILE:${PROJECT_NAME} ${HEX_FILE} COMMAND ${CMAKE_OBJCOPY} -O binary $TARGET_FILE:${PROJECT_NAME} ${BIN_FILE} COMMENT Generating HEX and BIN files )2.3 编译与烧录配置好之后在项目根目录打开终端依次执行以下命令生成构建系统让CMake根据你的配置生成Makefile。mkdir build cd build cmake ..编译项目make如果一切顺利你会在build目录下看到生成的voltage_current_meter.bin和voltage_current_meter.hex文件。连接开发板并烧录将立创·地文星开发板通过USB线连接到电脑并确保PyOCD能识别到调试探头。pyocd list # 列出连接的调试器确认找到设备烧录程序使用PyOCD将编译好的二进制文件烧录到芯片的Flash中。pyocd flash -t cw32f030c8t6 ../voltage_current_meter.hex烧录成功后按下开发板的复位键程序就开始运行了3. 核心软件逻辑与代码解析软件部分主要围绕ADC采样、量程判断和数值计算展开。这里我挑几个关键点讲讲。3.1 ADC初始化与采样首先需要初始化CW32的ADC模块。关键步骤是选择时钟源、设置采样周期、对齐方式并校准ADC以减小误差。// ADC初始化示例代码 (简化版) void ADC_Init(void) { ADC_InitTypeDef ADC_InitStruct; // 使能ADC时钟 RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_ADC, ENABLE); // ADC基本配置 ADC_InitStruct.ADC_ClkDiv ADC_ClkDiv_16; // ADC时钟分频 ADC_InitStruct.ADC_SampleTime ADC_SampleTime_10Cycles; // 采样时间 ADC_InitStruct.ADC_Align ADC_Align_Right; // 数据右对齐 ADC_Init(ADC_InitStruct); // 执行ADC校准 ADC_Calibration(); // 使能ADC ADC_Enable(); }进行单次电压采样的函数可能像这样uint16_t ADC_ReadChannel(ADC_Channel_TypeDef channel) { ADC_Channel_Config(channel); // 配置要采样的通道 ADC_SoftwareStartConv(); // 软件触发转换 while(ADC_GetFlagStatus(ADC_FLAG_EOC) RESET); // 等待转换完成 return ADC_GetConversionValue(); // 读取转换结果 }3.2 自动量程切换逻辑这是本项目软件的核心。以电压测量为例流程如下初始化上电后默认控制MOSFET接通大量程的分压电阻网络。第一次采样ADC读取当前电压值V_raw。判断将V_raw转换成实际电压值V_actual。如果V_actual小于设定的阈值例如1.0V说明输入电压较低当前大量程的分辨率不够因为大部分ADC量程没用到。切换控制GPIO输出关闭大量程的MOSFET打开小量程的MOSFET。第二次采样等待电路稳定后可能需要延时几毫秒再次进行ADC采样这次得到的就是高精度的测量值。计算与显示根据当前使用的量程分压比将ADC值换算为实际电压并显示在屏幕上。// 电压测量函数伪代码 float MeasureVoltage(void) { float voltage; uint16_t adc_value; // 步骤1 2: 默认使用大量程并采样 SetVoltageRange(LARGE_RANGE); Delay_ms(5); // 等待量程切换稳定 adc_value ADC_ReadChannel(VOLTAGE_ADC_CH); // 步骤3: 判断是否需要切换量程 voltage ConvertADCToVoltage(adc_value, LARGE_RANGE); if (voltage VOLTAGE_RANGE_THRESHOLD) { // 步骤4: 切换到小量程 SetVoltageRange(SMALL_RANGE); Delay_ms(5); // 步骤5: 重新采样 adc_value ADC_ReadChannel(VOLTAGE_ADC_CH); // 步骤6: 按小量程计算 voltage ConvertADCToVoltage(adc_value, SMALL_RANGE); } // 如果电压高于阈值则直接使用大量程的计算结果 return voltage; }电流和电阻的测量逻辑类似都需要根据初步采样结果判断是否在最佳量程并进行切换。3.3 数据处理与显示ADC采回来的是原始数字需要经过换算和滤波。换算根据当前量程的分压比或放大倍数、ADC的参考电压将ADC值转换为实际的电压/电流/电阻值。公式是基础但务必准确。滤波为了读数稳定通常会对连续采样的一组数据做处理比如取平均值、中位值或者进行一阶低通滤波。简单的移动平均滤波就能极大改善显示跳变的问题。#define SAMPLE_SIZE 10 uint16_t adc_buffer[SAMPLE_SIZE]; uint32_t adc_sum 0; // 连续采样10次并求和 for(int i0; iSAMPLE_SIZE; i) { adc_buffer[i] ADC_ReadChannel(xxx_CH); adc_sum adc_buffer[i]; Delay_ms(1); } // 计算平均值 uint16_t adc_average adc_sum / SAMPLE_SIZE; // 再进行换算...最后将处理好的数值通过SPI或I2C发送到OLED屏幕显示一个简易的数字表头就完成了。4. 调试心得与常见问题MOSFET接反或不工作这是我最大的坑。务必确认MOSFET的类型N沟道还是P沟道和使用位置高侧开关还是低侧开关。NMOS做低侧开关最简单。用万用表二极管档测量一下D-S极之间的体二极管方向可以帮助判断引脚。量程切换后读数不稳MOSFET开关切换后电路中的寄生电容充放电需要时间。在切换量程后增加一个几十毫秒的延时再进行ADC采样读数就会稳定下来。电流测量误差大如前面所说通用运放做微小信号放大误差显著。如果对电流测量精度有要求强烈建议使用专用的电流检测放大器芯片它们集成了精密运放和匹配电阻性能好很多。ADC读数跳动除了软件滤波硬件上也要注意。模拟电源要干净ADC参考电压引脚要接高质量的退耦电容如10uF钽电容0.1uF陶瓷电容采样信号线尽量短远离数字信号线。电阻测量范围小受限于ADC的参考电压和量程。如果想测更大电阻要么提高激励电压注意不能超过ADC量程要么减小参考电阻R_ref要么采用需要更多MCU资源的比例法或恒流源法。这个项目从画原理图、设计PCB到焊接调试、编写代码几乎涵盖了嵌入式开发的全流程。虽然现在它只是一个功能简单的原型但其中涉及的电源管理、模拟信号调理、MCU外设使用和自动控制逻辑都是非常实用的技能。希望我的这些分享能给你带来一些启发。接下来我打算优化一下电流测量部分再做个漂亮点的外壳让它真正变成一个能日常使用的小工具。