前一段时间在*宝上买了一款0-80V 0-6A可调电源,下图左边的。
此款电源测量电压的精度很不错,但是电流非常不准。250mA以下显示0,0.5A显示0.3A,1A时显示0.83A。3A是准的,3A以上又偏大。
没办法打开,拆机,发现用的表头在淘宝上有卖,测量电流不准的原因是电流分流电阻并不是表头配套的。
没有办法,只有自己重新编程才能解决电流测量不准的问题。首先对照实物绘制原理图,如下。
原来这款表头的电流量程是50A的,需配50A的分流电阻,测量电流才能达到该表头原有的设计精度。可是此电源却没有用50A的分流电阻,而是用约2毫欧的分流电阻。这样0-6A在分流电阻上的电压为 0-12mV。
对照原理图测量电流部分可以得出:运放部分的放大倍数为 23(188.2/8.2)- 35(288.2/8.2),输出电压为 0-420mV。这个电压太小,因为STM8的ADC输入电压可以到3300mV,所以我将放大电阻R6有原来的180k换成了1M,这样放大倍数就变成 123-135,运放的输出电压为 0-1620mV。
实际上,放大倍数在 3300/12=275时最理想(R6换成2M左右),可以充分利用ADC的转换精度。但我手头只有1M贴片,就没有再折腾了。
然后就是编程了,STM8是第一次,一直在用STM32。就在网上找了些例子,几番折腾终于能够编译和下载了。
电压测量校准比较简单,只调整电位器就能达到不错的精度。不过我还是用软件作了多区间线形校正。
10V以下精度 <1%,10V以上精度 <0.5%
电流校正没有分区间,精度基本做到 <1%。
下面是自己编程的工程文件,可以直接修改编译。
STM8S单片机源程序(IAR环境)如下:
- #include "math.h"
- //#include "stdbool.h"
- #include "iostm8s003f3.h"
- #include "stm8s.h" //包含用到的外设的头文件
- #define u8 unsigned char
- #define uchar unsigned char
- #define uint unsigned int
- #define sCnt 104
- uint ms_count, tmpin, ADCBuff[6], ADValueV[sCnt], ADValueI[sCnt], ADCData, mA;
- u8 pDataV=0, pDataI=0, vDotPS, cDotPS, VGain = 35, ampGain = 129, vFlag=0, iFlag=0;
- unsigned long mV; //uint的话,mV最大只能到 65535mV,本电源80V,故用long
- float voltRAW, curtRAW, Rs = 0.00218;
- float ADVal_Av[2]={0}, Val_Av[2]={0};
- float preCoef[2]={0.005}; //ADC 转换滤波噪声用的系数
- bool preTrend[2]={TRUE}; //ADC 转换结果的变化趋势 true:变大,false:变小
- float ADvalue[2]={0.0}; //ADC 前次的转换结果滤波后的值
- float newData[2]={0.0}; //ADC 本次次的转换结果
- /*******************************************************************************
- **函数名称:void GPIO_Init0()
- **功能描述:GPIO->功能引脚初始化
- **入口参数:无
- **输出:无
- *******************************************************************************/
- void GPIO_Init0()
- {
- PA_ODR |=0x02; //PA1置高,I个位选通
- PA_ODR &=0xf3; //PA2,3置零,g,f
- PA_DDR |=0x0e; //设置端口PA1,2,3的输入输出方向寄存器为输出方向
- PA_CR1 |=0x0e; //PA1,2,3为推挽输出
- PA_CR2 |=0x0e; //PA1,2,3是输出速度最快为10MHz
-
- PB_ODR |=0x30; //PB4,5置高,I十位和百位选通
- PB_DDR |=0x30; //设置端口PB4,5的输入输出方向寄存器为输出方向
- PB_CR1 |=0x30; //PB4,5为推挽输出
- PB_CR2 |=0x30; //PB4,5是输出速度最快为10MHz
-
- PC_ODR &=0x07; //置零PC3,4,5,6,7, c,f,h,d,e
- PC_DDR |=0xf8; //设置端口PC->3,4,5,6,7的输入输出方向寄存器为输出方向
- PC_CR1 |=0xf8; //PC->3,4,5,6,7为推挽输出
- PC_CR2 |=0xf8; //PC->3,4,5,6,7是输出速度最快为10MHz
-
- PD_ODR &=0xfd; //置零PD1, a
- PD_ODR |=0x70; //PD4,5,6置高 V百位 个位 和十位选通
- PD_DDR |=0x72; //设置端口PD->1,4,5,6的输入输出方向寄存器为输出方向
- PD_CR1 |=0x72; //PD->1,4,5,6为推挽输出
- PD_CR2 |=0x72; //PD->1,4,5,6是输出速度最快为10MHz
- }
- /*******************************************************************************
- **函数名称:void SMG_Display(int num)
- **功能描述:SMG显示函数
- **入口参数:int num
- **输出:无
- *******************************************************************************/
- void SMG1_Display(unsigned char num)
- {
- PA_ODR |=0x02; //PA1置高,I个位选通
- PB_ODR |=0x30; //PB4,5置高,I十位和百位选通
- PD_ODR |=0x70; //PD4,5,6置高 V百位 个位 和十位选通
- switch(num)
- {
- case 0: //a b c d e f
- PA_ODR |=0x08; //PA3置高,b
- PA_ODR &=0xfb; //PA2置零,g
- PC_ODR &=0xdf; //置零PC5,h
- PC_ODR |=0xd8; //PC3,4,6,7置高 c,f,d,e
- PD_ODR |=0x02; //PD1置高 a
- break;
- case 1: //b c
- PA_ODR |=0x08; //PA3置高,b
- PA_ODR &=0xfb; //PA2置零,g
- PC_ODR &=0x0f; //置零PC4,5,6,7,f,h,d,e
- PC_ODR |=0x08; //PC3置高 c
- PD_ODR &=0xfd; //PD1置零 a
- break;
- case 2: // a b d e g
- PA_ODR |=0x0c; //PA2,3置高,g,b
- PC_ODR &=0xc7; //置零PC3,4,5 c,f,h
- PC_ODR |=0xc0; //PC6,7置高,d e
- PD_ODR |=0x02; //PD1置高 a
- break;
- case 3: //a b c d g
- PA_ODR |=0x0c; //PA2,3置高,g,b
- PC_ODR &=0x4f; //置零PC4,5,7,f,h,e
- PC_ODR |=0x48; //PC3,6置高 c,d
- PD_ODR |=0x02; //PD1置高 a
- break;
- case 4: //b c f g
- PA_ODR |=0x0c; //PA2,3置高,g,b
- PC_ODR &=0x1f; //置零PC5,6,7,h,d,e
- PC_ODR |=0x18; //PC3,4置高 c,f
- PD_ODR &=0xfd; //PD1置零 a
- break;
- case 5: //a c d f g
- PA_ODR |=0x04; //PA2置高,g
- PA_ODR &=0xf7; //PA3置零,b
- PC_ODR &=0x5f; //置零PC5,7 h,e
- PC_ODR |=0x58; //PC3,4,6置高 c,f,d
- PD_ODR |=0x02; //PD1置高 a
- break;
- case 6: //a c d e f g
- PA_ODR |=0x04; //PA2置高,g
- PA_ODR &=0xf7; //PA3置零,b
- PC_ODR &=0xdf; //置零PC5,h
- PC_ODR |=0xd8; //PC3,4,6,7置高 c,f,d,e
- PD_ODR |=0x02; //PD1置高 a
- break;
- case 7: //a b c
- PA_ODR |=0x08; //PA3置高,b
- PA_ODR &=0xfb; //PA2置零,g
- PC_ODR &=0x0f; //置零PC4,5,6,7,f,h,d,e
- PC_ODR |=0x08; //PC3置高 c
- PD_ODR |=0x02; //PD1置高 a
- break;
- case 8: //a b c d e f g
- PA_ODR |=0x0c; //PA2,3置高,g,b
- PC_ODR &=0xdf; //置零PC5,h
- PC_ODR |=0xd8; //PC3,4,6,7置高 c,f,d,e
- PD_ODR |=0x02; //PD1置高 a
- break;
- case 9: //a b c d f g
- PA_ODR |=0x0c; //PA2,3置高,g,b
- PC_ODR &=0x5f; //置零PC5,7 h,e
- PC_ODR |=0x58; //PC3,4,6置高 c,f,d
- PD_ODR |=0x02; //PD1置高 a
- break;
- }
- }
- /*******************************************************************************
- **函数名称:void ADC_Init()
- **功能描述:初始化ADC
- **入口参数:无
- **输出:无
- *******************************************************************************/
- void ADC_Init()
- {
- //电流
- PD_DDR_bit.DDR2 = 0; //设置PD->2 为输入
- PD_CR1_bit.C12 = 0; //设置为浮空输入
- PD_CR2_bit.C22 = 0; //设置中断禁止
-
- //电压
- PD_DDR_bit.DDR3 = 0; //设置PD->3 为输入
- PD_CR1_bit.C13 = 0; //设置为浮空输入
- PD_CR2_bit.C23 = 0; //设置中断禁止
-
- ADC_CR1_bit.SPSEL = 7; //fmaster / 18 = 16MHZ / 18 = 888888HZ
- ADC_CR2_bit.ALIGN = 1; //RIGHT ALIGN
-
- ADC_CR1_bit.ADON = 1; //初次上电唤醒ADC
- }
- /*******************************************************************************
- **函数名称:uint Get_Adc(unsigned char ac)
- **功能描述:读取ADC完成一次模数转换结果
- **入口参数:ac ->ADC通道号
- **输出:ADC转换结果
- *******************************************************************************/
- uint Get_Adc(u8 ac) //获取adc值
- {
- uint Value;
- ADC_CSR_bit.CH = ac; //选择ADC通道 AIN3,4
- ADC_CR1 |= 0x01; //开始转换
- while((0x80&ADC_CSR)==0); //等待转换完成
- ADC_CSR &= ~0x80; //清除转换标志
- Value = (uint)ADC_DRL; //取低8位
- Value |= (uint)ADC_DRH<<8; //低8位和高两位合并
- return(Value); //返回adc值
- }
- float* Ad_Av(u8 ac) //每次只采样一个数据,采完sCnt个数据就更新结果,否则就显示上一次的结果
- {
- u8 num, pMaxV=0, pMaxV2=0, pMinV=0, pMinV2=0, pMaxI=0, pMaxI2=0, pMinI=0, pMinI2=0;
- if(ac==4){ //电压
- ADValueV[pDataV] = Get_Adc(ac);
- Val_Av[0] += ADValueV[pDataV++];
- }else{ //电流
- ADValueI[pDataI] = Get_Adc(ac);
- Val_Av[1] += ADValueI[pDataI++];
- }
- if(pDataV>=sCnt){ //电压数据采样完成,去除最大最小求平均
- pDataV=0; vFlag=1;
- for(num=0;num<sCnt;num++) {
- if(ADValueV[num] < ADValueV[pMinV]) pMinV = num;
- if(ADValueV[num] > ADValueV[pMaxV]) pMaxV = num;
- }
- for(num=0;num<sCnt;num++) { //去掉次大次小
- if(num != pMinV && ADValueV[num] < ADValueV[pMinV2]) pMinV2 = num;
- if(num != pMaxV && ADValueV[num] > ADValueV[pMaxV2]) pMaxV2 = num;
- }
- ADVal_Av[0] = (Val_Av[0] - ADValueV[pMinV] - ADValueV[pMinV2] - ADValueV[pMaxV] - ADValueV[pMaxV2])/(sCnt-4);
- //ADVal_Av[0] = (Val_Av[0] - ADValueV[pMinV] - ADValueV[pMaxV])/(sCnt-2); //只去掉最大最小的话,用这句
- Val_Av[0]=0;
- }
- if(pDataI>=sCnt){ //电流数据采样完成,去除最大最小求平均
- pDataI=0; iFlag=1;
- for(num=0;num<sCnt;num++) {
- if(ADValueI[num] < ADValueI[pMinI]) pMinI = num;
- if(ADValueI[num] > ADValueI[pMaxI]) pMaxI = num;
- }
- for(num=0;num<sCnt;num++) { //如果还要去掉次大次小的话,就去掉注释
- if(num != pMinI && ADValueI[num] < ADValueI[pMinI2]) pMinI2 = num;
- if(num != pMaxI && ADValueI[num] > ADValueI[pMaxI2]) pMaxI2 = num;
- }
- ADVal_Av[1] = (Val_Av[1] - ADValueI[pMinI] - ADValueI[pMinI2] - ADValueI[pMaxI] - ADValueI[pMaxI2])/(sCnt-4);
- //ADVal_Av[1] = (Val_Av[1] - ADValueI[pMinI] - ADValueI[pMaxI])/(sCnt-2); //只去掉最大最小的话,用这句
- Val_Av[1]=0;
- }
- return(ADVal_Av);
- }
- /***** 噪声滤波 *******************
- baseCoef:滤波系数,越小滤波越强;
- i:通道,可供多个测量对象滤波
- preCoef:上次滤波后计算的系数值
- preTrend:上次滤波的数据变化方向
- preData:上次滤波后的结果
- newData:这次新采样的数据
- ***********************************************************************/
- void Noise_Filter(float baseCoef, u8 i, float *preCoef, bool *preTrend, float *preData, float *newData){
- bool newTrend;
- if (newData[i] == preData[i]) return;
- if (newData[i] > preData[i]) newTrend = TRUE;
- else newTrend = FALSE;
- if (newTrend == preTrend[i]) preCoef[i] = preCoef[i] * 2;
- else preCoef[i] = baseCoef;
- preTrend[i] = newTrend;
- if(preCoef[i] < baseCoef) preCoef[i] = baseCoef;
- else if(preCoef[i] > 0.995) preCoef[i] = 0.995;
- preData[i] = preData[i] + (newData[i] - preData[i])* preCoef[i];
- }
- /***** 电压数据处理 ***************************************
- tmpin:采集的电压数据;
- 输出:电压值,单位:V
- *********************************************************/
- float ProcessVoltage(float tmpin)
- {
- float Temp;
- Temp = tmpin * 3.3 * VGain / 1023; //单位:V
- //下面是校正,应根据实际情况重新计算。其实不校正精度也不错
- if(Temp<=0.5) Temp = 1.0 * Temp;
- else if(Temp>0.5 && Temp<2.5) Temp = 0.98 * Temp + 0.28;
- else if(Temp<6.5) Temp = 1.005 * Temp + 0.235;
- else if(Temp<11.5) Temp = 1.00325 * Temp + 0.22925;
- else if(Temp<36.5) Temp = 0.990417 * Temp + 0.385;
- else Temp = 0.988889 * Temp + 0.482222;
- if(Temp<0) Temp=0;
- return (Temp);
-
- }
- /***** 电流数据处理 ***************************************
- tmpin:采集的电流数据;
- 输出:电流值,单位:A
- *********************************************************/
- float ProcessCurrent(float tmpin)
- {
- float Temp;
- Temp = tmpin * 3.3 / 1023 / ampGain / Rs; //单位:A
- //下面是校正,应根据实际情况重新计算。本例中是将运放的反馈电阻 R6(180k)
- //改成 1M后,放大系数ampGain由29变为129。
- //取样电阻 Rs约为 2mR。
- Temp = 1.385042 * Temp - 1.698751;
- if(Temp<0) Temp=0;
- return (Temp);
-
- }
- /**** 本例中未使用 ******************************************************
- **函数名称:void Timer4Init()
- **功能描述:定时器4参数初始化
- **入口参数:无
- **输出:无
- *******************************************************************************/
- void Timer4Init()
- {
- TIM4_IER_bit.UIE = 0; //禁止中断
- TIM4_EGR_bit.UG = 0;
- TIM4_PSCR_bit.PSC = 7; // 设置TIM4的时钟分频系数为 128 即定时器时钟 = 16000000 /128 = 125KHZ
-
- TIM4_ARR = 125; // 设定TIM4产生1毫秒的计数值
- TIM4_CNTR = 0x00; // 清除TIM4计数寄存器数值
-
- TIM4_SR_bit.UIF = 0; //清除中断标志
- TIM4_SR_bit.TIF = 0;
- TIM4_CR1_bit.CEN = 1; //使能定时器4计数
- TIM4_CR1_bit.ARPE = 1; //使能预装载
- TIM4_IER_bit.UIE = 1; // 使能更新中断
- }
- /**** 本例中未使用 *******************************************************
- **函数名称:void TIM2_Init()
- **功能描述:定时器2参数初始化
- **入口参数:无
- **输出:无
- *******************************************************************************/
- void TIM2_Init()
- {
- TIM2_PSCR = 0x00; //定时器2预分频数为 1 分频,即定时器时钟 = 系统时钟 = 16MHz
- TIM2_ARRH = 0x3E; //设置1毫秒时间自动重载 16000 = 0x3e80
- TIM2_ARRL = 0x80; //
- TIM2_CNTRH = 0x00; //清除计数寄存器
- TIM2_CNTRL = 0x00; //
- TIM2_SR1 = 0x00; //清除所有的中断标志
-
- }
- /**** 本例中未使用 ******************************************************
- **函数名称:void TIM2_DelayMs(unsigned int ms)
- **功能描述:定时器2参进行精确延时,最小为1毫秒,最大65535
- **入口参数:unsigned int ms 1=< ms <= 65535
- **输出:无
- *******************************************************************************/
- void TIM2_DelayMs(unsigned int ms)
- {
-
- TIM2_CR1 = 0x81; //启动定时器2开始计数
- while(ms--)
- {
-
- while( !(TIM2_SR1 & 0x01)); //等待计数是否达到1毫秒
- TIM2_SR1 &= ~(0x01); //计数完成1毫秒,清除相应的标志
- }
- TIM2_CR1 = 0x00; //延时全部结束,关闭定时器2
- }
- /*******************************************************************************
- **函数名称:void delay(unsigned int ms)
- **功能描述:大概延时
- **入口参数:unsigned int ms 输入大概延时数值
- **输出:无
- *******************************************************************************/
- void delay(unsigned int ms)
- {
- unsigned int x , y;
- for(x = ms; x > 0; x--) /* 通过一定周期循环进行延时*/
- for(y = 1000 ; y > 0 ; y--);
- }
- /*******************************************************************************
- **函数名称:void V_Show()
- **功能描述:在数码管上显示电压值
- **入口参数:ms:每个数码管延时。显示数据存储在 ADCBuff[2] - ADCBuff[0]
- **输出:无
- *******************************************************************************/
- void V_Show(unsigned int ms)
- {
- SMG1_Display(ADCBuff[2]); //显示V百位
- if(vDotPS==2) PC_ODR |=0x20; //PC5置高,h点
- PD_ODR &=0xef; //PD4置低 V百位选通
- delay(ms);
- SMG1_Display(ADCBuff[1]); //显示V十位
- if(vDotPS==1) PC_ODR |=0x20; //PC5置高,h点
- PD_ODR &=0xbf; //PD6置低 V十位选通
- delay(ms);
- SMG1_Display(ADCBuff[0]); //显示V个位
- PD_ODR &=0xdf; //PD5置低 V个位选通
- delay(ms);
- }
- /*******************************************************************************
- **函数名称:void I_Show()
- **功能描述:在数码管上显示电流值
- **入口参数:ms:每个数码管延时。显示数据存储在 ADCBuff[5] - ADCBuff[3]
- **输出:无
- *******************************************************************************/
- void I_Show(unsigned int ms)
- {
- SMG1_Display(ADCBuff[5]); //显示A百位
- if(cDotPS==5) PC_ODR |=0x20; //PC5置高,h点
- PB_ODR &=0xdf; //PB5置低,I百位选通
- delay(ms);
- SMG1_Display(ADCBuff[4]); //显示A十位
- if(cDotPS==4) PC_ODR |=0x20; //PC5置高,h点
- PB_ODR &=0xe0; //PB4置低,I十位选通
- delay(ms);
- SMG1_Display(ADCBuff[3]); //显示A个位
- PA_ODR &=0xfd; //PA1置低,I个位选通
- delay(ms);
- }
- /* 主函数 */
- int main(void)
- {
- //asm("sim"); //关闭系统总中断
- CLK_CKDIVR = 0x00; //CPUDIV = 1 HSIDIV = 1 内部时钟 = 16Mhz
- GPIO_Init0();
- ADC_Init(); //调用ADC初始化函数
- //TIM2_Init();
- //Timer4Init(); //初始化定时器4
- //asm("rim"); //打开系统总中断
- while(1)
- {
- //if(adcflag == 1) //1秒定时时间到对使能ADC进行采样
- //{
- //asm("sim"); //关系统总中断
- voltRAW = Ad_Av(4)[0]; //获取电压数据
- if(vFlag==1){ //电压数据采集完成才进行以下计算,否则跳过,将显示上次数据
- vFlag=0;
- newData[0] = ProcessVoltage(voltRAW);
- Noise_Filter(0.5, 0, preCoef, preTrend, ADvalue, newData);
- //ADvalue[0] = newData[0]; //不要上面的滤波时使用
- mV = (unsigned long)(ADvalue[0]*1000);
- if(mV > 9999){
- vDotPS=1;
- ADCBuff[0] = mV / 100 % 10; //0.1V
- ……………………
- …………限于本文篇幅 余下代码请从51黑下载附件…………
复制代码
所有资料51hei提供下载:
STM8S_VIMeter.zip
(1.16 MB, 下载次数: 803)
|