前一段時間在*寶上買了一款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)
2017-12-20 13:33 上傳
點擊文件名下載附件
STM8_VIMeter工程文件
|