
开源协议
:GPL 3.0
描述
一、简介
系统以 CW32F030单片机作为系统核心处理器,可实现对电压电流的同时采集。可由外部电源DC供电,也可由被测电源供电,测量电源的同时给电压电流表供电。内置不同量程档位,且可以拿其他标准化器件进行手动校准,将校准的参数存入Flash中,确保掉电不丢失。
二、功能及实物图
电压能实现30V以内的测量,电流能实现3A以内的测量
本工程分为6个工作模式
模式0:实现压流同采,数码管上排显示电压,数码管下排显示电流,且可以切换电压测量的量程,30V档位,12V档位,3V档位之间切换,量程越小测量越精准。
模式1:高精度电流采集模式,可以显示到小数点后四位
模式2:5V标定设置,将电压表接入,ADC采集电压与电压表可能会有误差,拧动滑动变阻器,使电压表测量值为5V,将此时的读取出来的ADC值做保存处理并且与5V对应起来
模式3:对15V电压做校准
模式4:对0.5A电流做校准
模式5:对1A电流做校准
实物图展示
三、核心电路PCB设计说明
1、供电电路
本项目使用LDO作为电源,考虑到实际的电压表头产品多在24V或36V供电的工业场景中应用,本项目选择了最高输入电压高达40V的SE8550K2作为电源。本项目没有使用DCDC降压电路来应对大压差的主要原因为避免设计过程中引入DCDC的纹波干扰,次要原因为降低项目成本。
2、电压采样电路
电压采样电路ADC读取选取1.5V为基准电压,由于MCU引脚无法测量这么大的电压,因此采用分压电路,读取完成之后根据分压电路的原理进行计算,可算出被测电压的大小,拿30v档位举例,具体计算如下:
3、电流采样电路
电流测量本质还是测电压,将测出的电压经过运算算出电流,量程是测0-3A的电流,最大电流为3A,经过P=IIR,算出功率为900mw,因此在次电阻选型时要注意。
4、电压电流的标定与校准
当插入跳线帽时,加入滑动变阻器,扭动滑动变阻器可改变电压电流大小,用标准化器件做对比可以进行数值标定校准。
5、核心板-CW32
测量电压电流,都是采集的模拟量,我们可以通过使用MCU进行模拟量的采集,然后转为数字量,也就是ADC模数转化,将数字量进行运算可以使用显示。
6、PCB注意事项
如图中蓝线走向,由于点解电容与陶瓷电容要进行一个滤波的作用,在进行PCB连线时,不能直接就近连接,需要按顺序依次经过电容的一侧,最后进入SE8550,做到滤波的作用
四、重要程序
此程序采用FreeRTOS操作系统进行编程
1、变量定义及注释
/*******
Mode=0:压流同采
Mode=1:高精度电流采集
Mode=2:5V标定调整模式
Mode=3:15V标定调整模式
Mode=4:0.5A标定调整模式
Mode=5:1.5A标定调整模式
*******/
/********
K1:切换模式加
K2:在模式0下可换电压档位 30V 12V 3V档位
K3:在进入电压电流标定模式时候(Mode=2,3,4,5)
K4:切换模式减
*********/
TaskHandle_t Task1_Handle=NULL;
TaskHandle_t Task2_Handle=NULL;
TaskHandle_t Task3_Handle=NULL;
uint8_t Seg_Table[22] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f,
0xbf, 0x86, 0xdb, 0xcf, 0xe6, 0xed, 0xfd, 0x87, 0xff, 0xef,
0x77,0xBE}; //A U.
uint8_t Seg_Table_Buff[6]={9,8,7,6,5,4};
float V=0,I=0; //真实电压
uint32_t V_d,I_d; //降小数值转化为整数值
uint32_t V_ADC0,I_ADC; //ADC读取出来的数字值
uint8_t Flag_Key1 = 0,Flag_Key2 = 0,Flag_Key3 = 0,Flag_Key4 = 0,Mode=0;
uint8_t SMG_Display_Flag=0,KeyNum=0;
//5V与15V 校准 X轴为ADC读取数字量 Y轴为真实电压值
/*由于Y轴值用5V与15V作标定,此程序的x轴值,全部由30V档位测量得出的ADC值,其他档位不适用*/
/*想要每个量程做标定,只需选取两个量程之内的点,进行公式运算做适配即可*/
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 IY15=0.15;
float IY05=0.05;
float KI; //斜率
ADC_InitTypeDef ADC_InitStructure;
ADC_SingleChTypeDef ADC_SingleChStructure;
ADC_SerialChTypeDef ADC_SerialChStructure;
2、函数与线程的作用
3、标定理论值计算
/*
理论值计算过程,对应原理图30V挡位计算 设ADC读取电压值为VIN0 AD为读取的数字量 输入电压值以Y15=15V标准计算
AD VIN0 15 VIN0 15*4096
—— = —— —— = —— 算出AD=------- AD值也就是此时15V输入电压计算出对应的数字量X15,对应X轴
4096 1.5 220+10 10 1.5*23
*/
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/1.5*4096;
IX15=1.5/1.5*4096;
save_calibration();
}
else
{
X05=da[1];
X15=da[2];
IX05=da[3];
IX15=da[4];
}
}
4、模式的切换及标定值存储
void KEY(void *param)
{
while(1)
{
KeyNum=KEY_Scan();
/*切换模式
Mode=0:压流同采
Mode=1:高精度电流采集
Mode=2:5V标定调整模式
Mode=3:15V标定调整模式
Mode=4:0.5A标定调整模式
Mode=5:1.5A标定调整模式
*/
if(KeyNum==1)
{
Mode++;
if(Mode>5)
{
Mode=0;
}
if(Mode==2||Mode==3) //为电压标定模式时,自动切换为30V档位
{
ADC_SerialChStructure.ADC_Sqr0Chmux = ADC_SqrCh0;
ADC_SerialChContinuousModeCfg(&ADC_SerialChStructure);
}
}
else if(KeyNum==2&&Mode==0) //切换电压采集挡位
{
SMG_Display_Flag++;
if(SMG_Display_Flag>2)
{
SMG_Display_Flag=0;
}
if(SMG_Display_Flag==0)
{
ADC_SerialChStructure.ADC_Sqr0Chmux = ADC_SqrCh0;
ADC_SerialChContinuousModeCfg(&ADC_SerialChStructure);
}
else if(SMG_Display_Flag==1)
{
ADC_SerialChStructure.ADC_Sqr0Chmux = ADC_SqrCh1;
ADC_SerialChContinuousModeCfg(&ADC_SerialChStructure);
}
else if(SMG_Display_Flag==2)
{
ADC_SerialChStructure.ADC_Sqr0Chmux = ADC_SqrCh2;
ADC_SerialChContinuousModeCfg(&ADC_SerialChStructure);
}
}
else if(KeyNum==3)
{
if(Mode==2)
{
X05=Get_Adc_Average(10,ADC_GET_V);
save_calibration();
ComputeK();
}
else if(Mode==3)
{
X15=Get_Adc_Average(10,ADC_GET_V);
save_calibration();
ComputeK();
}
else if(Mode==4)
{
IX05=Get_Adc_Average(10,ADC_GET_I);
save_calibration();
ComputeK();
}
else if(Mode==5)
{
IX15=Get_Adc_Average(10,ADC_GET_I);
save_calibration();
ComputeK();
}
}
else if(KeyNum==4)
{
Mode--;
if(Mode>10)
{
Mode=5;
}
if(Mode==2||Mode==3) //为电压标定模式时,自动切换为30V档位
{
ADC_SerialChStructure.ADC_Sqr0Chmux = ADC_SqrCh0;
ADC_SerialChContinuousModeCfg(&ADC_SerialChStructure);
}
}
vTaskDelay(1);
}
}
5、ADC读取及转化
void ADCIN(void *param)
{
float KT1;
__RCC_GPIOA_CLK_ENABLE(); // 使能GPIO时钟
__RCC_ADC_CLK_ENABLE(); //使能ADC时钟
PA00_ANALOG_ENABLE();
PA01_ANALOG_ENABLE();
PA02_ANALOG_ENABLE();
ADC_InitStructure.ADC_OpMode = ADC_SingleChOneMode; //单通道单次转换模式
ADC_InitStructure.ADC_ClkDiv = ADC_Clk_Div128; // 时钟频率 = PCLK / 4 = 64MHz / 4 = 16MHz
ADC_InitStructure.ADC_SampleTime = ADC_SampTime10Clk; //10个ADC时钟周期
ADC_InitStructure.ADC_VrefSel = ADC_Vref_BGR1p5; //VDDA参考电压
ADC_InitStructure.ADC_InBufEn = ADC_BufDisable; //关闭跟随器
ADC_InitStructure.ADC_TsEn = ADC_TsDisable; //关闭内置温度传感器
ADC_InitStructure.ADC_DMAEn = ADC_DmaDisable; //不触发DMA
ADC_InitStructure.ADC_Align = ADC_AlignRight; //ADC转换结果右对齐
ADC_InitStructure.ADC_AccEn = ADC_AccDisable; //转换结果累加不使能
ADC_SerialChStructure.ADC_Sqr0Chmux = ADC_SqrCh0; //配置ADC序列,PB01是ADC的第9通道
ADC_SerialChStructure.ADC_Sqr1Chmux = ADC_SqrCh4;
ADC_SerialChStructure.ADC_SqrEns = ADC_SqrEns01;
ADC_SerialChStructure.ADC_InitStruct = ADC_InitStructure; //ADC初始化
ADC_SerialChContinuousModeCfg(&ADC_SerialChStructure);
ADC_ClearITPendingAll();
ADC_Enable();
ADC_SoftwareStartConvCmd(ENABLE); //启动ADC转换
while(1)
{
/*压流同采*/
if(Mode==0)
{
if(SMG_Display_Flag==0) //30V档位
{
V_ADC0=Get_Adc_Average(10,ADC_GET_V);
printf("Flag0:%d\r\n",V_ADC0);
// V=(V_ADC0*1.5)/4096*(220+10)/10;
if(V_ADC0>=X05)
{
V=(V_ADC0-X05)*K+Y05; //y = k×(Xad– X1)+ y1 测量的ADC值是X,求出Y真实电压
}
else
{
KT1=5;
KT1=KT1/X05;
V=KT1*V_ADC0;
}
}
else if(SMG_Display_Flag==1) //12V档位
{
V_ADC0=Get_Adc_Average(10,ADC_GET_V);
printf("Flag1:%d\r\n",V_ADC0);
V=(V_ADC0*1.5)/4096*(71.5+10)/10;
}
else if(SMG_Display_Flag==2) //3V档位
{
V_ADC0=Get_Adc_Average(10,ADC_GET_V);
printf("Flag2:%d\r\n",V_ADC0);
V=(V_ADC0*1.5)/4096*(10+10)/10;
}
I_ADC=Get_Adc_Average(10,ADC_GET_I);
printf("电流:%d\r\n",I_ADC);
// I=1.5/4096*I_ADC*10;
if(I_ADC>=IX05)
{
I=(I_ADC-IX05)*KI+IY05; //y = k×(Xad– X1)+ y1 测量的ADC值是X,求出Y真实电压
}
else
{
KT1=0.05;
KT1=KT1/IX05;
I=KT1*I_ADC;
}
I*=10;
printf("V:%f\r\n",V);
if(V<10)
{
V_d=V*100;
Seg_Table_Buff[0]=V_d/100+10;
Seg_Table_Buff[1]=V_d/10%10;
Seg_Table_Buff[2]=V_d%10;
}
else if(V>10)
{
V_d=V*10;
Seg_Table_Buff[0]=V_d/100;
Seg_Table_Buff[1]=V_d/10%10+10;
Seg_Table_Buff[2]=V_d%10;
}
I_d=I*100;
Seg_Table_Buff[3]=I_d/100+10;
Seg_Table_Buff[4]=I_d/10%10;
Seg_Table_Buff[5]=I_d%10;
}
/*高精度5位测电流*/
else if(Mode==1)
{
I_ADC=Get_Adc_Average(10,ADC_GET_I);
printf("电流:%d\r\n",I_ADC);
// I=1.5/4096*I_ADC*10;
if(I_ADC>=IX05)
{
I=(I_ADC-IX05)*KI+IY05; //y = k×(Xad– X1)+ y1 测量的ADC值是X,求出Y真实电压
}
else
{
KT1=0.05;
KT1=KT1/IX05;
I=KT1*I_ADC;
}
I*=10;
I_d=I*10000;
Seg_Table_Buff[0]=20;
Seg_Table_Buff[1]=I_d/10000+10;
Seg_Table_Buff[2]=I_d/1000%10;
Seg_Table_Buff[3]=I_d/100%10;
Seg_Table_Buff[4]=I_d/10%10;
Seg_Table_Buff[5]=I_d%10;
}
/**5V标定模式**/
else if(Mode==2)
{
V_ADC0=Get_Adc_Average(10,ADC_GET_V);
if(V_ADC0>=X05)
{
V=(V_ADC0-X05)*K+Y05; //y = k×(Xad– X1)+ y1 测量的ADC值是X,求出Y真实电压
}
else
{
KT1=5;
KT1=KT1/X05;
V=KT1*V_ADC0;
}
Seg_Table_Buff[0]=21;
Seg_Table_Buff[1]=5+10;
Seg_Table_Buff[2]=0;
if(V<10)
{
V_d=V*100;
Seg_Table_Buff[3]=V_d/100+10;
Seg_Table_Buff[4]=V_d/10%10;
Seg_Table_Buff[5]=V_d%10;
}
else if(V>10)
{
V_d=V*10;
Seg_Table_Buff[3]=V_d/100;
Seg_Table_Buff[4]=V_d/10%10+10;
Seg_Table_Buff[5]=V_d%10;
}
}
else if(Mode==3)
{
V_ADC0=Get_Adc_Average(10,ADC_GET_V);
if(V_ADC0>=X05)
{
V=(V_ADC0-X05)*K+Y05; //y = k×(Xad– X1)+ y1 测量的ADC值是X,求出Y真实电压
}
else
{
KT1=5;
KT1=KT1/X05;
V=KT1*V_ADC0;
}
Seg_Table_Buff[0]=21;
Seg_Table_Buff[1]=1;
Seg_Table_Buff[2]=5+10;
if(V<10)
{
V_d=V*100;
Seg_Table_Buff[3]=V_d/100+10;
Seg_Table_Buff[4]=V_d/10%10;
Seg_Table_Buff[5]=V_d%10;
}
else if(V>10)
{
V_d=V*10;
Seg_Table_Buff[3]=V_d/100;
Seg_Table_Buff[4]=V_d/10%10+10;
Seg_Table_Buff[5]=V_d%10;
}
}
else if(Mode==4)
{
I_ADC=Get_Adc_Average(10,ADC_GET_I);
if(I_ADC>=IX05)
{
I=(I_ADC-IX05)*KI+IY05; //y = k×(Xad– X1)+ y1 测量的ADC值是X,求出Y真实电压
}
else
{
KT1=0.05;
KT1=KT1/IX05;
I=KT1*I_ADC;
}
I*=10;
I_d=I*100;
Seg_Table_Buff[0]=20;
Seg_Table_Buff[1]=0+10;
Seg_Table_Buff[2]=5;
Seg_Table_Buff[3]=I_d/100+10;
Seg_Table_Buff[4]=I_d/10%10;
Seg_Table_Buff[5]=I_d%10;
}
else if(Mode==5)
{
I_ADC=Get_Adc_Average(10,ADC_GET_I);
if(I_ADC>=IX05)
{
I=(I_ADC-IX05)*KI+IY05; //y = k×(Xad– X1)+ y1 测量的ADC值是X,求出Y真实电压
}
else
{
KT1=0.05;
KT1=KT1/IX05;
I=KT1*I_ADC;
}
I*=10;
I_d=I*100;
Seg_Table_Buff[0]=20;
Seg_Table_Buff[1]=1+10;
Seg_Table_Buff[2]=5;
Seg_Table_Buff[3]=I_d/100+10;
Seg_Table_Buff[4]=I_d/10%10;
Seg_Table_Buff[5]=I_d%10;
}
vTaskDelay(300);
}
}
五、实物验证
1、采用30V档位对电池进行测量,且用万用表做辅助
可以看到万用表量出来是9.38V,而未进行标定的工程测出来是9.44V,误差较大
2、采用12V档位测电池
可以看到与万用表两位精度丝毫不差,量程越小,测量电压越精准
3、使用3V量程
超过限制之后显示最大量程
4、电流检测
由于身边没有稳定电流源,读取拓展板内部产生的电流进行读取,用万用表做验证。采用高精度电流模式,可以看到万用表测出来电压是0.23V,由于器件使用100mΏ电阻,所以此时算出来电流是2.3A,数码管显示电流为2.2692A,在没有进行标定时,误差极小。
设计图

BOM


评论