
基于CW32F030C8的简易电压电流表[训练营2024.7]
简介
本工程项目源于芯源半导体联合立创开发板举办的嘉立创EDA电压电流表训练营活动,为电压电流表训练营项目,配合CW32F030C8T6最小系统板使用的测量U、I的扩展板。
简介:本工程项目源于芯源半导体联合立创开发板举办的嘉立创EDA电压电流表训练营活动,为电压电流表训练营项目,配合CW32F030C8T6最小系统板使用的测量U、I的扩展板。开源协议
:GPL 3.0
描述
本项目的io分配仅适用于芯源半导体的CW32F030C8T6最小系统板,具体分配情况请看原理图
最终效果图与成品图
训练营课程资料下载区
1.立创开发板LOGO 见附件
2.芯源半导体LOGO 见附件
3.文档资料 CW32数字电压电流表训练营项目教程文档 | 立创开发板技术文档中心 (lckfb.com)
4.B站视频资料 手把手教你做电压电流表:1.1硬件-供电电路-LDO单路设计分析_哔哩哔哩_bilibili
4.作者自己的笔记 语雀文档地址 (添加了一些自己的,从硬件到软件一步步做的)
5.官方工程 立创·地文星CW32数字电压电流表扩展板
..
原理讲解(详见文档)
1.供电电路 SE8550K2
LDO(低压差线性稳压器)选型
本项目使用LDO作为电源,考虑到实际的电压表头产品多在24V或36V供电的工业场景中应用,本项目选择了最高输入电压高达40V的SE8550K2作为电源。本项目没有使用DCDC降压电路来应对大压差的主要原因为避免设计过程中引入DCDC的纹波干扰,次要原因为降低项目成本。
2.MCU控制 CW32 M0
不要盲目的选型
在对本项目进行MCU(微控制器单元)选型时,需要综合考虑多个方面以确保选择的MCU能够满足项目需求。
- 明确自己的项目需求:清晰地了解项目需要多少计算能力,包括时钟速度、处理器核心的类型、是否需要浮点运算单元等。
- 明确项目所需的I/O端口和重要外设,如ADC外设。由于本项目为开发板项目,主要目的为调试学习,在硬件上,对I/O数量不做严格限制:即不考虑此带来的成本等问题。
记得对应好口
3.电压采样 电阻之比电压之比
本项目设计分压电阻为220K+10K,因此分压比例为22:1(ADC_IN11)
分压电阻选型
- 设计测量电压的最大值,出于安全考虑,本项目为30V(实际最大可显示99.9V或100V);
- ADC参考电压,本项目中为1.5V,该参考电压可以通过程序进行配置;
- 功耗,为了降低采样电路的功耗,通常根据经验值将低侧电阻(R7)选择为10K;
随后便可以通过以上参数计算出分压电阻的高侧电阻:
- 计算所需的分压比例:即ADC参考电压:设计输入电压,通过已知参数可以计算出1.5V/30V=0.05
- 计算高侧电阻:即低侧电阻/分压比例,通过已知参数可以计算出10K/0.05=200K
- 选择标准电阻:选择一颗略高于计算值的电阻,计算值为200K,通常我们选择E24系列电阻,因此本项目中选择大于200K且最接近的220K。
如果在实际使用中,需要测量的电压低于2/3的模块设计电压,即66V,则可以根据实际情况更换分压电阻并修改程序从而提升测量的精度,下面将进行案例说明:
- 假设被测电压不高于24V,其他参数不变
- 通过计算可以得到1.5V/24V=0.0625,10K/0.0625=160K,160K为标准E24电阻可以直接选用,或适当留出冗余量选择更高阻值的180K
如果在实际使用中,需要测量的电压若高于模块99V的设计电压,可以选择更换分压电阻或通过修改基准电压来实现更大量程的电压测量范围,下面将进行案例说明:
- 假设被测电压为160V,选择提升电压基准的方案扩大量程
- 已知选用电阻的分压比例为0.0145,通过公式反推,我们可以计算出
160V*0.0145=2.32V
,因此我们可以选择2.5V的电压基准来实现量程的提升(扩大量程将会降低精度)
考虑到被测电源可能存在波动,在电路设计时,在低侧分压电阻上并联了10nF的滤波电容提高测量稳定性。
换挡
在本项目中,额外增加了一组电压采样电路,因此,我们可以探讨一下换挡对于提高测量精度的意义。万用表想要测的更准确,往往设置了多个档位。通过对不同档位的调整,获得被测点位在相应量程下的最佳的测量精度。
本项目实现此功能需要实现软硬件结合。当我们首先使用前文所讲的ADC_IN11通道测量30V以内电压时。若所测得电压在0~3V以内,则使用ADC_IN9通道测量。此时,由于分压比减小,测量精度大大提高。
实现换挡的思路有很多种,开发板的设计给大家提供了更多设计的可能。
4.电流采样 电流流小电阻分压
本项目采用低侧电流采样电路进行电流检测,采样电路的低侧与开发板表头接口共地
学习时,请不要焊接R0!!!
设计分析
本项目设计的采样电流为3A,选择的采样电阻(R0)为100mΩ
采样选型主要需要参考以下几个方面:
- 预设计测量电流的最大值,本项目中为3A
- 检流电阻带来的压差,一般不建议超过0.5V
- 检流电阻的功耗,应当根据该参数选择合适的封装,本项目考虑到大电流时的功耗(温度)问题,选择了1W封装的金属绕线电阻
- 检流电阻上电压的放大倍数:本项目中没有使用运放搭建放大电路,因此倍率为1
随后便可以通过以上参数计算出检流的阻值选择:
- 由于本项目没有使用放大电路,因此需要选择更大的采样电阻获得更高的被测电压以便于进行测量
- 考虑到更大的电阻会带来更大的压差、更高的功耗,因此也不能无限制的选择更大的电阻
- 本项目选用了1W封装的电阻,对应的温升功率为1W
综合以上数据,本项目选择了100mΩ的检流电阻,根据公式可以计算出3A*100mΩ=300mV,900mW
如需应对不同的使用环境,尤其是电流较大的场景,可以将R0电阻更换为康铜丝或者分流器,可以更具实际使用场景,选择替代。出于安全和学习用途考虑,本项目对超出3A量程不做过多探讨,但原理一致。
5.数码管 共阴极数码管 (直接单片机端口,移位寄存器见温湿度仪工程)
本项目采用了数码管作为显示单元。
在本项目中使用了两颗0.28寸的三位共阴数码管作为显示器件,相较于显示屏,数码管在复杂环境中拥有更好的识别度,可以根据实际使用环境的需求,改为更小的限流电阻实现更高的数码管亮度;在另一方面,数码管拥有较好的机械性能,不会像显示屏一样容易被外力损坏。在工业等有稳定可靠性应用中,多被采用。从开发版学习的角度来看,更易有目的的学习电子测量原理相关开发。
在本项目中,经过实际测试,数码管的限流电阻(R1~R6)被配置为300Ω,对应的亮度无论是红色还是蓝色数码管,均具有较好的识别度,且亮度柔和不刺眼。
严格来讲,限流电阻应该加在段上,加在位上,会影响显示效果。我们实际设计加在位上,省几个电阻,但对显示影响并不突出。所以还是加在位上,图个方便。
数码管的驱动原理
数码管的驱动原理主要涉及到通过控制数码管的各个灯段的开关状态来显示数字、字母或符号。以下是详细的驱动原理说明:
- 数码管的基本构成:
- 数码管通常由七段或八段LED(本项目为8段)组成,每个段代表数码管的一部分,可以显示数字0-9、字母A-F等字符。
- 数码管有共阴极和共阳极两种类型,它们的区别在于LED的公共端COM(即连接所有LED的一端)是连接到电源的负极还是正极。
- 驱动方式:
- 段选:通过控制数码管的各个灯段的开关状态来显示所需的数字或字符。每个灯段对应一个控制信号,当控制信号开启时,该段会显示点亮,反之则灭掉。(a、b、c、d、e、f、g、dp)
- 位选:通过控制数码管的位线来选择需要显示的数码管。位线控制是将需要显示的数码管的位线设置为高电平,其他数码管的位线设置为低电平。通过不断地切换位线的状态,可以实现多个数码管之间的显示切换。
- 驱动电路:
- 数码管驱动电路可以通过硬件电路实现,如使用数字信号处理器(DSP)、微控制器(MCU)或移位寄存器等集成电路来生成适合LED的控制信号。
- 这些控制信号可以是脉冲宽度调制(PWM)信号、串行数据信号等形式。通过控制这些信号的频率、宽度和幅度,可以实现数码管的亮暗控制,从而显示出所需的数字或字母。
- 软件控制:
- 除了硬件驱动电路,还可以通过软件控制来实现数码管的驱动。通过编程生成适合数码管的控制信号,可以实现更加灵活和复杂的显示效果,如数字的滚动显示、交替显示等。
- 共阴极与共阳极数码管的驱动:
- 对于共阴极数码管,共阴极引脚连接到电源的负极,控制引脚连接到控制芯片的输出引脚。当需要显示某个数字时,控制芯片会输出相应的编码信号到控制引脚,使得对应的LED段点亮。
- 对于共阳极数码管,工作原理与共阴极数码管相似,只是共阳极引脚连接到电源的正极,控制引脚连接到控制芯片的输出引脚。
- 编码显示:
- 为了使数码管显示出相应的数字或字符,必须使段数据口输出相应的字形编码。例如,要显示数字“0”,共阳极数码管的字型编码为11000000B(即C0H),而共阴极数码管的字型编码为00111111B(即3FH),具体编码以实际数码管为准。
- 动态显示与静态显示:
- 数码管可以采用静态显示或动态显示方式。静态显示时,每个数码管的8个字段分别与一个8位I/O口地址相连,I/O口只要有段码输出,相应字符即显示出来并保持不变。动态显示则是一位一位地轮流点亮各位数码管,通过快速切换实现人眼视觉上的同时显示。
总结来说,数码管的驱动原理是通过控制数码管的各个灯段的开关状态来显示数字、字母或符号,并通过段选和位选的方式实现多个数码管之间的显示切换。同时,可以通过硬件电路或软件控制来实现数码管的驱动,并根据需要选择共阴极或共阳极数码管进行驱动。
本项目实际采用动态扫描显示驱动数码管。
推算一下数码管所需电流
本项目实际采用动态扫描显示驱动数码管,因而在同一时刻,最多仅有8个段的数码管(或理解为LED)被点亮,或者说有某一位被点亮。根据设计,所需驱动电流即为IO口高电平电压3.3V÷300Ω≈11mA。
此时应注意选型的MCU是否有足够的拉电流/灌电流的能力。
由数据手册分析可知,CW32可以。(有些芯片是不行的)
6.工作指示灯 电源指示灯和工作指示灯
7.按键电路 普通按键
8.基准电压 TL431
本项目额外增加了一个TL431电路用来提供一个2.5V的基准电压,可用于给芯片一个用于校准AD的外部电压基准,从产品设计角度来讲,由于CW32本身的ADC性能优势,可以不需要此电路。在开发板上设计此电路,用于学习相关应用原理。
TL431算是一个比较“老”的器件了,很经典,应用很广泛,现在在很多电子产品中仍然有其身影。
可能很多新手初次接触此器件,我们简单的讲讲此产品的原理,方便大家更好的应用TL431。
TI从名称上,将其定义为:精密可编程基准,我们在参考文献的第一页上,可以重点关注几个特性。
精密:精密,说明其输出电压非常准。我使用的为±0.5%精度的TL431,在室温下,板上实测2.495V。相较于常见的稳压二极管,精度天差地别。在应用电路图中,TL431内部以一个稳压管的符号做示意。
可调输出电压:可调输出电压在Vref到36V之间,我们在项目中使用输出Vref电压。Vref电压约为2.5V。所以我们在描述中用2.5V,实际是约等于的。
灌电流能力:也就是输出电压的引脚可以提供多少电流,这与在应用电路中的电阻(R13)的阻值有很大关系。不能低于1mA。如果没有灌电流的需求,则不要将电流设计过大,造成不必要的功耗影响。
..
PCB设计(详见文档)
1.官方文档写的很详细
..
软件
1.官方文档写的很详细
main.c
#include "main.h"
#include "Seg_Dis.h"
#include "BTIM1.h"
#include "ADC.h"
#include "flash.h"
extern uint16_t Volt_Buffer[ADC_SAMPLE_SIZE];
extern uint16_t Curr_Buffer[ADC_SAMPLE_SIZE];
extern uint8_t Seg_Reg[6];
uint16_t V_Buffer,I_Buffer;
unsigned int spp_start=0;
uint32_t ble_time=0;
unsigned int timecount=0;
//5V与15V 校准
unsigned int X05=0;
unsigned int X15=0;
unsigned int Y15=15;
unsigned int Y05=5;
float K; //斜率
//0.5A与1.5A 校准
unsigned int IX05=0;
unsigned int IX15=0;
unsigned int IY15=150;
unsigned int IY05=50;
float KI; //斜率
//定义模式
unsigned char Mode=0;
//mode0 :电压电流常规显示模式
//mode1 :电压5V校准
//mode2 :电压15V校准
//mode3 :电流0.5A校准
//mode4 :电流1.5A校准
unsigned char BrushFlag=0;
void RCC1_Configuration(void)
{
FLASH_SetLatency(FLASH_Latency_2); // 设置主频为48MHZ需要注意,Flah的访问周期需要更改为FLASH_Latency_2。
RCC_HSI_Enable(RCC_HSIOSC_DIV1); // 设置频率为48M
RCC_SYSCLKSRC_Config(RCC_SYSCLKSRC_HSI); //选择SYSCLK时钟源 48MHz
RCC_HCLKPRS_Config(RCC_HCLK_DIV1); //配置SYSTICK到HCLK分频系数 48MHz
RCC_PCLKPRS_Config(RCC_PCLK_DIV8); //配置HCLK 到 PCLK的分频系数 6MHz
InitTick(48000000); // SYSTICK 的工作频率为48MHz,每ms中断一次
}
void RCC_Configuration(void)
{
/* 0. HSI使能并校准 */
RCC_HSI_Enable(RCC_HSIOSC_DIV6);
/* 1. 设置HCLK和PCLK的分频系数 */
RCC_HCLKPRS_Config(RCC_HCLK_DIV1);
RCC_PCLKPRS_Config(RCC_PCLK_DIV1);
/* 2. 使能PLL,通过PLL倍频到64MHz */
RCC_PLL_Enable(RCC_PLLSOURCE_HSI, 8000000, 6); // HSI 默认输出频率8MHz
// RCC_PLL_OUT(); //PC13脚输出PLL时钟
///< 当使用的时钟源HCLK大于24M,小于等于48MHz:设置FLASH 读等待周期为2 cycle
///< 当使用的时钟源HCLK大于48MHz:设置FLASH 读等待周期为3 cycle
__RCC_FLASH_CLK_ENABLE();
FLASH_SetLatency(FLASH_Latency_3);
/* 3. 时钟切换到PLL */
RCC_SysClk_Switch(RCC_SYSCLKSRC_PLL);
RCC_SystemCoreClockUpdate(48000000);
RCC_PCLKPRS_Config(RCC_PCLK_DIV8); //配置HCLK 到 PCLK的分频系数 6MHz
}
void KEYGPIO_Init(void)
{
__RCC_GPIOB_CLK_ENABLE();//打开GPIOB的时钟
__RCC_GPIOC_CLK_ENABLE();//打开GPIOC的时钟
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pins = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14; //
GPIO_InitStruct.Mode = GPIO_MODE_INPUT_PULLUP;
GPIO_InitStruct.IT = GPIO_IT_NONE;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_Init(CW_GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pins = GPIO_PIN_13; //
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init(CW_GPIOC, &GPIO_InitStruct);
}
extern void DisplaySETV(uint32_t value);
void DisplayBuff(void)
{
if(Mode==0)
{
Display(V_Buffer);
if(I_Buffer>400)I_Buffer=400;
DisplayI(I_Buffer);
}
else if(Mode==1) //S.05.
{
Seg_Reg[0] =5+10;
Seg_Reg[1] =0;
Seg_Reg[2]=5+10;
DisplaySETV(V_Buffer);
}
else if(Mode==2) //S.15.
{
Seg_Reg[0] =5+10;
Seg_Reg[1] =1;
Seg_Reg[2]=5+10;
DisplaySETV(V_Buffer);
}
else if(Mode==3) //A.0.5
{
Seg_Reg[0] =20;
Seg_Reg[1] =0+10;
Seg_Reg[2]=5;
DisplayI(I_Buffer);
}
else if(Mode==4) //A.1.5
{
Seg_Reg[0] =20;
Seg_Reg[1] =1+10;
Seg_Reg[2]=5;
DisplayI(I_Buffer);
}
}
void ComputeK(void)
{
K=(Y15-Y05);
K=K/(X15-X05);
KI=(IY15-IY05);
KI=KI/(IX15-IX05);
}
void save_calibration(void)
{
uint16_t da[5];
da[0]=0xaa;
da[1]=X05;
da[2]=X15;
da[3]=IX05;
da[4]=IX15;
flash_erase();
flash_write(0,da,5);
}
/**
* @brief
*
*/
void read_vol_cur_calibration(void)
{
uint16_t da[5];
flash_read(0,da, 5);
if(da[0]!=0xaa)//还没校准过时,计算理论值,并存储
{
X15=15.0/23/1.5*4096;
X05=5.0/23/1.5*4096;
IX05=0.5*0.1/1.5*4096;
IX15=1.5*0.1/1.5*4096;
save_calibration();
}
else
{
X05=da[1];
X15=da[2];
IX05=da[3];
IX15=da[4];
}
}
void Volt_Cal(void);
int main()
{
RCC_Configuration(); //系统时钟64M
KEYGPIO_Init();
GPIO_WritePin(CW_GPIOC,GPIO_PIN_13,GPIO_Pin_RESET);
Seg_Init();
Btim1_Init();
ADC_init();
read_vol_cur_calibration();
ComputeK();
while(1)
{
if(BrushFlag==1)
{
DisplayBuff();
BrushFlag=0;
}
if(timecount>= 300) //300ms改变一次数码管显示值//
{
timecount=0;
Volt_Cal();
BrushFlag=1;
}
}
}
uint32_t Mean_Value_Filter(uint16_t *value, uint32_t size) //均值滤波
{
uint32_t sum = 0;
uint16_t max = 0;
uint16_t min = 0xffff;
int i;
for(i = 0; i < size; i++)
{
sum += value[i];
if(value[i] > max)
{
max = value[i];
}
if(value[i] < min)
{
min = value[i];
}
}
sum -= max + min;
sum = sum / (size - 2);
//if(sum>1)sum+=4; 后期校准
return sum;
}
void Volt_Cal(void)
{
float t,KT1;
V_Buffer = Mean_Value_Filter(Volt_Buffer,ADC_SAMPLE_SIZE);//使用均值滤波
I_Buffer = Mean_Value_Filter(Curr_Buffer,ADC_SAMPLE_SIZE); //使用均值滤波
if(V_Buffer>=X05)
{
t=V_Buffer-X05;
V_Buffer=(K*t+Y05)*1000;}
else
{
KT1=5000;
KT1=KT1/X05;
V_Buffer=KT1*V_Buffer;
}
// 四舍五入
if(V_Buffer % 10 >= 5)
{
V_Buffer = V_Buffer / 10 + 1; //10mV为单位
}
else
{
V_Buffer = V_Buffer / 10;
}
if(I_Buffer>=IX05)
{
t=I_Buffer-IX05;
I_Buffer=(KI*t+IY05)*10;
}
else
{
KT1=500;
KT1=KT1/IX05;
I_Buffer=KT1*I_Buffer;
}
if(I_Buffer % 10 >= 5)
{
I_Buffer = I_Buffer / 10 + 1;
}
else
{
I_Buffer = I_Buffer / 10;
}
//I_Buffer=I_Buffer * ADC_REF_VALUE >> 12;
/**
mv =I_Buffer * ADC_REF_VALUE >> 12,
R = 100mr,
10ma = mv/R/10=mv/0.1/10 = mv
*/
}
void BTIM1_IRQHandler(void)
{
static uint32_t keytime=0,keytime2=0,keytime3=0,ledcount=0;
/* USER CODE BEGIN */
if (BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))
{
BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);
Get_ADC_Value();
ledcount++; //LED闪
if(ledcount>=1000)
{PC13_TOG();ledcount=0;}
timecount++;
Dis_Refresh();//数码管扫描显示
if(GPIO_ReadPin(CW_GPIOB,GPIO_PIN_12)==GPIO_Pin_RESET)
{
keytime++;
if(keytime>=100 )
{
keytime=0; //切换模式
Mode++;
if(Mode>=5)Mode=0;
BrushFlag=1; //更新数码管
}
}
else keytime=0;
if(GPIO_ReadPin(CW_GPIOB,GPIO_PIN_13)==GPIO_Pin_RESET&&Mode!=0)
{
keytime2++;
if(keytime2>=100 )
{
keytime2=0; //切换模式
if(Mode==1)
{
X05=Mean_Value_Filter(Volt_Buffer,ADC_SAMPLE_SIZE);
save_calibration();ComputeK();Volt_Cal();BrushFlag=1;Mode=0;
}
if(Mode==2)
{
X15=Mean_Value_Filter(Volt_Buffer,ADC_SAMPLE_SIZE);
save_calibration();ComputeK();Volt_Cal();BrushFlag=1;Mode=0;
}
if(Mode==3)
{
IX05=Mean_Value_Filter(Curr_Buffer,ADC_SAMPLE_SIZE);
save_calibration();ComputeK();Volt_Cal();BrushFlag=1;Mode=0;
}
if(Mode==4)
{
IX15=Mean_Value_Filter(Curr_Buffer,ADC_SAMPLE_SIZE);
save_calibration();ComputeK();Volt_Cal();BrushFlag=1;Mode=0;
}
}
}
else keytime2=0;
if(GPIO_ReadPin(CW_GPIOB,GPIO_PIN_13)==GPIO_Pin_RESET)
{
keytime3++;
if(keytime3>=100 )
{
keytime3=0; //切换模式
Mode=0;
BrushFlag=1; //更新数码管
}
}
else keytime3=0;
}
/* USER CODE END */
}

..
遇到的问题
1. 从官网下载的源码,经过调编译环境V5.06,以及确认激活、版本正常、代码无修改、路径正常后仍显示报错cmsis_version.h找不到(如下图),则有可能是CMSIS固件太久了,更新一下即可,固件见附件 ARM.CMSIS.5.9.0.pack
2. 香蕉头插座要量好尺寸,防止插不进去,我的第一版就是差点尺寸,建议调大0.2CM直径,如V1.1
3. 操作流程看我的V1.1的丝印
..
买买买
1.立创商城(合理利用16-15就够用)
2.淘宝【淘宝】https://m.tb.cn/h.gOiMK9fGGU9G6cX?tk=yMCK3VkxsKW MF6563 「5只 4MM灯笼头香蕉插头插座接线柱 公母对插电源测试端子母座插头」
点击链接直接打开 或者 淘宝搜索直接打开
3.【淘宝】https://m.tb.cn/h.gOiosFkG74O7PQ9?tk=RDeJ3VkDlEE CZ0002 「大毅合金电阻2512/2W/3W1% 0.033R-0.2R/RLP25FEGR033-R200」
点击链接直接打开 或者 淘宝搜索直接打开
4.【淘宝】https://m.tb.cn/h.gOiL1LAF1qzPgGD?tk=Alt93VkDOB3 HU9196 「~dc直流电源插头插座公母接头转接头母座公头5.5-2.1/2.5mm3.5圆孔(~@ ~)」
点击链接直接打开 或者 淘宝搜索直接打开
..
最后
感谢立创EDA
感谢芯源半导体
感谢立创开发板
感谢立创商城
感谢立创开发板-开发菌、芯源半导体-李工、立创开发板-李国怿
下期再见
设计图

BOM


评论