Adaptívna predikcia merania hmotnosti
Aj váha sa môže naučiť presne vážiť
Pre meranie hmotnosti vo vonkajšom prostredí môže byť obtiažne zabezpečiť presné meranie hmotnosti ktoré by bolo nezávislé na zmenách okolitej teploty. V prípade použitia štyroch tenzometrov je situácia ešte zložitejšia ako pri jednom tenzometri. Existujú tenzometre ktoré majú dva tenzometrické snímače a dokážu kompenzovať vplyv teploty do určitej miery vzájomnou kompenzáciou tenzometrických snímačov. Na princípe tepelnej rozťažnosti nosnej časti tenzometra. Konštrukčne jednoduchšie tenzometre majú jeden snímač a sú prednostne určené do prostrednia kde sa okolitá teplota nemení. Pre meranie hmotnosti úľov vo vonkajšom prostredí preto stojíme pred otázkou ako tento problém vyriešiť. Nemôžeme si dovoliť nulovať váhu pred každým meraním ako je to vo všetkých ostatných aplikáciách kde sa zisťuje okamžitá hmotnosť a nie prírastok alebo úbytok hmotnosti po nejakom čase. Úľ je na váhe nepretržite, váženie nefunguje na princípe napríklad automobilových váh, kde nie je pred vážením automobilu na váhe nič, tá sa vynuluje a hmotnosť automobilu odváži presne nezávisle na okamžitej okolitej teplote. Lebo nepresnosti vo vážení vplyvom teploty sú lineárne, je to posun hmotnosti a to sa dá korigovať jednoduchým posunutím nuly. Pri meraní hmostnosti úľa ak by sme aj znulovali váhu pre meraním a odčítali hmotnosť tak o 2 hodiny keď by sa okolitá teplota zmenila nemôžeme zas znulovať váhu, lebo nevieme o koľko tú hmotnosť posunúť, lebo je na váhe úľ.
V tomto článku sa pozrieme bližšie ako vyriešiť vplyv teploty na meranie hmotnosti učením sa na základe predchádzajúcich meraných hodnôt teploty a hmotnosti. Pri štyroch tenzometroch zapojených v plnom mostíku môže byť vplyv teploty na váženie dosť výrazný. Lebo sa poruší rovnovážny stav mostíka narušením jedného tenzometra ktorý bude mať rozdielnu teplotu oproti ostatným. Na včelnici svieti slnko z jednej strany a ešte počas dňa z východu a potom zo západu. Tým sa tenzometre nahrievajú nerovnomerne aj počas dňa kedy sa zdá že je celý deň stabilná teplota. Ak je polooblačno tak sa aj tento rovnomerný slnečný svit narušuje a teplota tenzometrov môže značne kolísať. Predné tenzometre budú mať inú teplotu ako zadné. V období keď je slnko za mrakmi môže byť teplota okolo úľa rovnomerná a všetky 4 tenzometre môžu mať rovnakú teplotu.
Tepelné snímače ktoré merajú vonkajšiu teplotu alebo teplotu tenzometrov nedokážu odmerať presne teplotu nosnej časti tenzometra v mieste kde je tenzometrický senzor a tým nie je takto nameraná teplota synchrónna s teplotou pod tenzometrickým snímačom. Čo má za následok nemožnosť kompenzovať vplyv vonkajšej teploty s vážením v rovnakom čase.
Aby sme dokázali kompenzovať všetky 4 tenzometre potrebovali by sme merať teplotu každého tenzometra. Alebo v mieste každého tenzometra. Ak by boli tenzometre nezávislé nespojené do mostíka dali by sa kompenzovať každý zvlášť. Pri zapojení tenzometrov do plného mostíka by mala každá zmena teploty tenzometra vlyv na všetky tenzometre a tým by sa robila korekcia obtiažnejšie.
V našich úľových váhach meriame teplotu dvoch tenzometrov zo štyroch zapojených v uhlopriečke. Tým dosiahneme obraz teploty zo štyroch strán úľa. Lebo pri slnečnom počasí je predná strana úľa vždy vyhriata viac ako zadná.
Naše váhy používajú 4 tenzometre aj v zapojení plného mostíka FILIP-PG a FILIP-WG ale aj 4 tenzometre ktoré sú samostatné a už čiastočne hardvérovo tepelne kompenzované zapojené paralelne FILIP-SL a FILIP-SLG. Dva snímače teploty tenzometrov majú všetky typy.
Meranie teploty
Meranie teploty tenzometrov uskutočňujeme NTC termistormi 10kOhm/25 °C. Termistory sú zapojené v prúdovej slučke s konštantným prúdom 55uA.
O meranie sa stará hardvérový modul mikrokontroléra CTMU. Inicializácia CTMU modulu. Nastavenie prúdovej slučky na 55uA.
CTMUCONH = 0b00100000;
CTMUCONL = 0b10010000;
//CTMUICON - CTMU Current Control Register
API_RecordRead(&FR, DREG_ADC_PIC18F);
CTMU_TEMPERATURE_CurrentCalibReg = FR.idata[ 4 ];
CTMUICON = 3; //55uA, Nominal - No Adjustment
CTMUICON |= CTMU_TEMPERATURE_CurrentCalibReg;
//CTMUICON = 2; //5,5uA, Nominal - No Adjustment
CTMUEN = 0; //disable the CTMU
EDG1STAT = 0; //disable current
Pri zmene okolitej teploty sa mení hodnota termistora a tým na ňom vznikne iný úbytok napätia nelineárne úmerný teplote. Toto napätie sa meria ďalším hardvérovám modulom mikrokontroléra 12 bitovým analógovo digitálnym prevodníkom.
SelectChannel = (BYTE)(_Port - ADC_PIC18F_PORT); ADCON2 = 0b10111100; ADCON0 &= 0x03; ADCON0 |= (SelectChannel<<2); #if defined(CTMU_ON) if(_State & ADC_PIC18F_CTMU_CONVERSION){ ctmu_active = 1; CTMUICON = CtmuTemperature[channel_active - 1].Current; CTMUICON |= CTMU_TEMPERATURE_CurrentCalibReg; CTMUEN = 1; Delay10us(15); IDISSEN = 1; //drain charge on the circuit Delay10us(15); IDISSEN = 0; //end drain of circuit EDG1STAT = 1; //Begin charging the circuit Delay10us(15); } else Delay10us(15); #else Delay10us(15); #endif GODONE = TRUE; AppFlag2.bits.adc_corversion = TRUE; conversion = TRUE; return ADC_PIC18F_STATE_START;
Odmerané napätie sa použije ako vstup pre výpočet teploty tenzomerov polynómom tretieho rádu ktorý prepočíta nelineárnu charakteristiku závislosti napätia na termistore a teploty.
short int _GetTemperatureNTC( WORD port, double Resistance, double Division, BYTE Index, BYTE Type ) { double fRx; double fTemperatureActual; double fLN, f; short int temp; if(Index == 0){
switch(port){ case ADC_PIC18F_TEMP2_PORT: f = 2.048 * (Division - (double)AdcPic18f.OffsetCTMU[0])/4096.0; fRx = f/CTMU_TEMPERATURE_ConstantCurrent[0]; break; case ADC_PIC18F_TEMP3_PORT: f = 2.048 * (Division - (double)AdcPic18f.OffsetCTMU[1])/4096.0; fRx = f/CTMU_TEMPERATURE_ConstantCurrent[0]; break; }
} else{
switch(port){ case ADC_PIC18F_TEMP2_PORT: f = 2.048 * (Division - (double)(AdcPic18f.OffsetCTMU[0]/10))/4096.0; fRx = f/CTMU_TEMPERATURE_ConstantCurrent[1]; break; case ADC_PIC18F_TEMP3_PORT: f = 2.048 * (Division - (double)(AdcPic18f.OffsetCTMU[1]/10))/4096.0; fRx = f/CTMU_TEMPERATURE_ConstantCurrent[1]; break; }
} if(Type == NTCLE100E3103){
fLN = log( (fRx/Resistance) );
f = CONSTANT_A1_10K_VISHAY;
f += (CONSTANT_B1_10K_VISHAY * fLN);
f += (CONSTANT_C1_10K_VISHAY * _RaiseToApower( fLN, 2 ));
f += (CONSTANT_D1_10K_VISHAY * _RaiseToApower( fLN, 3 ));
}
else return 0;
fTemperatureActual = (100.0 / f) - (double)VALUE_0_CELSIUS_IS_KELVIN * 100.0;
temp = (short int)fTemperatureActual;
return( temp );
}
Výhoda takéhoto merania teploty je v cene, kde termistor stojí 25 centov. Prúdová slučka je analógová so zanedbateľným prúdom ako aj analógové meranie tenzometrov a takéto zapojenie nesposobuje žiadne rušenie merania hmotnosti. Ďalšia výhoda je vo veľmi nízkej spotrebe. Počas merania tečie termistormi elektrický prúd iba 55uA.
Teplota jedného tenzometra je v CtmuTemperature[1].Value a teplota druhého tenzometra je v CtmuTemperature[2].Value. Vo formáte datového typu long s presnosťou na stotinu °C zapísaná v stotinách °C. Napríklad 20°C je zapísané ako 2000.
case ADC_PIC18F_TEMP2_PORT:
if((state = ADC_PIC18F_ReadData( ADC_PIC18F_TEMP2_PORT, &data, ADC_PIC18F_GET_FREE )) == ADC_PIC18F_STATE_FREE){ ADC_PIC18F_ReadData( ADC_PIC18F_TEMP2_PORT, &data, ADC_PIC18F_CTMU_CONVERSION ); return 2; } else if(state == ADC_PIC18F_STATE_CONVERSION) return 2; else if(state == ADC_PIC18F_STATE_NEXT) return 3; else if(state == ADC_PIC18F_STATE_OK){ CtmuTemperature[1].Segments = data; if(data >= 3900L && CtmuTemperature[1].Current == 3){ CtmuTemperature[1].Current = 2; return 2; } else if(data < 370L && CtmuTemperature[1].Current == 2){ CtmuTemperature[1].Current = 3; return 2; } else if(data >= 4095L && CtmuTemperature[1].Current == 2){ CtmuTemperature[1].Error = 1; if(AppFlag2.bits.reset_sms){ strcpy(Packet.bTemp, "\r\nTemperature T Error"); UART_PutData(PRINT_TEXT, Packet.bTemp, strlen(Packet.bTemp)); if( Bluetooth_FLAG_M66.Semaphore.Control.enable_bluetooth && Bluetooth_FLAG_M66.Semaphore.Control.BluetoothAttach) BLUETOOTH_PutData(BLUETOOTH_SPP_PORT, Packet.bTemp, strlen(Packet.bTemp)); } break; } else CtmuTemperature[1].Error = 0; if(CtmuTemperature[1].Current == 3) (*_Temp) = (long)_GetTemperatureNTC(ADC_PIC18F_TEMP2_PORT, 10000.0, (double)data, 0, NTCLE100E3103);
else (*_Temp) = (long)_GetTemperatureNTC(ADC_PIC18F_TEMP2_PORT, 10000.0, (double)data, 1, NTCLE100E3103); lvTemp.Val = (*_Temp);
lvTemp.Val = lvTemp.Val - CtmuTemperature[1].CalibrationValue; i = CtmuTemperature[1].IndexBuffer; CtmuTemperature[1].Buffer[i] = lvTemp.Val; if(AppFlag2.bits.reset_sms){ sprintf(Packet.bTemp, "\r\nCtmuTemperature[1].Buffer=%ld", CtmuTemperature[1].Buffer[i]); UART_PutData(PRINT_TEXT, Packet.bTemp, strlen(Packet.bTemp)); } CtmuTemperature[1].IndexBuffer++; if(CtmuTemperature[1].IndexBuffer == 2){ CtmuTemperature[1].IndexBuffer = 0; CTMU_Flag.bits.temp2 = TRUE; } CtmuTemperature[1].Value = 0; for(i=0; i<2; i++){ CtmuTemperature[1].Value = CtmuTemperature[1].Value + CtmuTemperature[1].Buffer[i]; } CtmuTemperature[1].Value = CtmuTemperature[1].Value / 2L; lvTemp.Val = CtmuTemperature[1].Value; lvTemp.Val -= (long)(((double)CtmuTemperature[1].CalibrationTemp - (double)CtmuTemperature[0].Value) * 0.03); CtmuTemperature[1].Value = lvTemp.Val; if(AppFlag2.bits.reset_sms){ if(AppFlag.bits.wake_up_input){ sprintf(Packet.bTemp, "\r\nTemperature T %ld", CtmuTemperature[1].Value); UART_PutData(PRINT_TEXT, Packet.bTemp, strlen(Packet.bTemp)); if( Bluetooth_FLAG_M66.Semaphore.Control.enable_bluetooth && Bluetooth_FLAG_M66.Semaphore.Control.BluetoothAttach) BLUETOOTH_PutData(BLUETOOTH_SPP_PORT, Packet.bTemp, strlen(Packet.bTemp)); } } } break; case ADC_PIC18F_TEMP3_PORT: if((state = ADC_PIC18F_ReadData( ADC_PIC18F_TEMP3_PORT, &data, ADC_PIC18F_GET_FREE )) == ADC_PIC18F_STATE_FREE){ ADC_PIC18F_ReadData( ADC_PIC18F_TEMP3_PORT, &data, ADC_PIC18F_CTMU_CONVERSION ); return 2; } else if(state == ADC_PIC18F_STATE_CONVERSION) return 2; else if(state == ADC_PIC18F_STATE_NEXT) return 3; else if(state == ADC_PIC18F_STATE_OK){ CtmuTemperature[2].Segments = data; if(data >= 3900L && CtmuTemperature[2].Current == 3){ CtmuTemperature[2].Current = 2; return 2; } else if(data < 370L && CtmuTemperature[2].Current == 2){ CtmuTemperature[2].Current = 3; return 2; } else if(data >= 4095L && CtmuTemperature[2].Current == 2){ CtmuTemperature[2].Error = 1; AppFlag2.bits.adc_ok = TRUE; if(AppFlag2.bits.reset_sms){ strcpy(Packet.bTemp, "\r\nTemperature T2 Error"); UART_PutData(PRINT_TEXT, Packet.bTemp, strlen(Packet.bTemp)); if( Bluetooth_FLAG_M66.Semaphore.Control.enable_bluetooth && Bluetooth_FLAG_M66.Semaphore.Control.BluetoothAttach) BLUETOOTH_PutData(BLUETOOTH_SPP_PORT, Packet.bTemp, strlen(Packet.bTemp)); } break; } else CtmuTemperature[2].Error = 0; if(CtmuTemperature[2].Current == 3) (*_Temp) = (long)_GetTemperatureNTC(ADC_PIC18F_TEMP3_PORT, 10000.0, (double)data, 0, NTCLE100E3103);
else (*_Temp) = (long)_GetTemperatureNTC(ADC_PIC18F_TEMP3_PORT, 10000.0, (double)data, 1, NTCLE100E3103); lvTemp.Val = (*_Temp);
lvTemp.Val = lvTemp.Val - CtmuTemperature[2].CalibrationValue; i = CtmuTemperature[2].IndexBuffer; CtmuTemperature[2].Buffer[i] = lvTemp.Val; if(AppFlag2.bits.reset_sms){ sprintf(Packet.bTemp, "\r\nCtmuTemperature[2].Buffer=%ld", CtmuTemperature[2].Buffer[i]); UART_PutData(PRINT_TEXT, Packet.bTemp, strlen(Packet.bTemp)); } CtmuTemperature[2].IndexBuffer++; if(CtmuTemperature[2].IndexBuffer == 2){ CtmuTemperature[2].IndexBuffer = 0; CTMU_Flag.bits.temp3 = TRUE; AppFlag2.bits.adc_ok = TRUE; } CtmuTemperature[2].Value = 0; for(i=0; i<2; i++){ CtmuTemperature[2].Value = CtmuTemperature[2].Value + CtmuTemperature[2].Buffer[i]; } CtmuTemperature[2].Value = CtmuTemperature[2].Value / 2L; lvTemp.Val = CtmuTemperature[2].Value; lvTemp.Val -= (long)(((double)CtmuTemperature[2].CalibrationTemp - (double)CtmuTemperature[0].Value) * 0.03); CtmuTemperature[2].Value = lvTemp.Val; if(AppFlag2.bits.reset_sms){ if(AppFlag.bits.wake_up_input){ sprintf(Packet.bTemp, "\r\nTemperature T2 %ld", CtmuTemperature[2].Value); UART_PutData(PRINT_TEXT, Packet.bTemp, strlen(Packet.bTemp)); if( Bluetooth_FLAG_M66.Semaphore.Control.enable_bluetooth && Bluetooth_FLAG_M66.Semaphore.Control.BluetoothAttach) BLUETOOTH_PutData(BLUETOOTH_SPP_PORT, Packet.bTemp, strlen(Packet.bTemp)); } } } break;
Meranie hmotnosti
Tenzometre sú napájané jednosmerným napätím 3,0V. Výstup z tenzometrov je napätie 0 až 10mV lineárne úmerné hmotnosti. Toto napätie sa zosiluje zosilňovačom so ziskom 128 v diferencálnom zapojení operačného zosilňovača. Následne sa digitalizuje 24 bitovým analógovo digitálnym prevodníkom a výsledok sa zapíše do registra. Na tento účel slúži zosilňovač a prevodník v jenom obvod HX711.
Mikrokontrolér pri čítaní tenzometrov zapne napájacie napätie 3,0V na tenzometre a po zapísaní odmeranej hodnoty z tenzometrov prečíta register HX711 a získa tým hodnotu počtu dielikov úmernú hmotnosti úľa. Hodnotu číta po jednoduchom sériovom rozhraní sposobom CLK, DATA. Namerané dieliky sú uložené v HW711_Registers.Data.
if(_Port == HX711_PORT_1 && !HX711_Registers.ErrorA){ HX711_PowerUp(HX711_PORT_1); DelayMs(1); data=0; count = 0; while(HX711_DT_A_IN){ if(count > 3000){ HX711_Registers.ErrorA = 1; HX711_PowerDown(HX711_PORT_1); (*_Data) = 0; return FALSE; } DelayMs(1); count++; } for (i=0;i<24;i++){ HX711_SCK_A_OUT=1; data = data << 1; HX711_SCK_A_OUT=0; if(HX711_DT_A_IN) data++; } HX711_SCK_A_OUT=1; HX711_Registers.Data = data; (*_Data) = HX711_Registers.Data; HX711_SCK_A_OUT=0; HX711_PowerDown(HX711_PORT_1); } else if(_Port == HX711_PORT_1 && HX711_Registers.ErrorA){ HX711_Registers.ErrorA = FALSE; return FALSE; }
Na výpočet hmotnosti je potrebé použiť kalibračné konštanty, prípadný posun hmotnosti nastavený tarou a korekciu vplyvu teploty na meranie hmotnosti ktorú práve riešime.
if(HX711_ReadData(HX711_PORT_1, &Data)){ if(Data & 0x800000) flag = 1;
else flag = 0; Data = (DWORD)((long)(Data&0xff7fffff) + Weight[0].CalibZero); if(flag && !(Data & 0x800000)) Data = 0; else if(!flag && (Data & 0x800000)) Data = 0; Data = Data&0xff7fffff; if(CtmuTemperature[2].Error && CtmuTemperature[1].Error) lWeight.Val = Weight[0].CalibTemp;
else if(!CtmuTemperature[2].Error && CtmuTemperature[1].Error) lWeight.Val = CtmuTemperature[2].Value; else if(CtmuTemperature[2].Error && !CtmuTemperature[1].Error) lWeight.Val = CtmuTemperature[1].Value; else lWeight.Val = (CtmuTemperature[1].Value + CtmuTemperature[2].Value)/2; lTempWeight2 = (long)round((double)(((float)(Weight[0].CalibTemp - lWeight.Val))/100.0 * Weight[0].TempConst)); lWeight.Val = (long) (((float) Data * 1000.0) / (double)Weight[0].CalibValue) - Weight[0].Tare + lTempWeight2 ; //1g
if(lTempWeight < (lWeight.Val - 50L) || lTempWeight > (lWeight.Val + 50L)){ lTempWeight = lWeight.Val; count++; if(count >= 5){ if(WEIGHT_NumberActive > 1) cyclic = 1; Weight[0].Error = TRUE; Weight[0].HwError++; if(Weight[0].HwError > 51) Weight[0].HwError = 51; goto NEXT2; } goto NEXT1; } Weight[0].Value = (lWeight.Val + lTempWeight)/2; if(!Weight[0].HistAccept){
Weight[0].HistValue = Weight[0].Value; Weight[0].HistAccept = 1; } Weight[0].Error = FALSE; Weight[0].HwError = 0; measure |= 0x01;
if(WEIGHT_NumberActive > 1) cyclic = 1; } } else{ if(WEIGHT_NumberActive > 1) cyclic = 1;
}
}
Vo Weight[0].Value dostaneme hmotnosť úľa v gramoch. Weight[0].TempConst je konštanta korekcie hmotnosti na jeden stupeň celzia. Pri tejto adaptívnej metóde zisťovania tejto hodnoty, bude daná hodnota premenlivá dynamická a nie konštantná. Pri každom jednom vážení sa bude dynamicky počítať z predchádzajúcich nameraných vzoriek hmotnosti a teploty. Teraz si ukážene ako a prečo ju počítať.
Výpočet konštanty pre tepelnú korekciu
Dynamickú korekciu vypočítame podľa tejto funkcie. Výsledok bude už skorigovaná hmotnosť úľa Weight[_number].Value.
BOOL WEIGHT_CorrectionFilter(BYTE _number) { long lTempWeight, lTempWeight2; LONG_VAL lWeight; WEIGHT_CorrectionAddData(_number); WEIGHT_CorrectionOffset(_number); WEIGHT_CorrectionNormal(_number); WEIGHT_CorrectionMoveOptimal(_number); WEIGHT_CorrectionSynchronization(_number); WEIGHT_CorrectionFindValue(_number); if(CtmuTemperature[2].Error && CtmuTemperature[1].Error) lWeight.Val = Weight[_number].CalibTemp; else if(!CtmuTemperature[2].Error && CtmuTemperature[1].Error) lWeight.Val = CtmuTemperature[2].Value; else if(CtmuTemperature[2].Error && !CtmuTemperature[1].Error) lWeight.Val = CtmuTemperature[1].Value; else lWeight.Val = (CtmuTemperature[1].Value + CtmuTemperature[2].Value)/2; lTempWeight2 = (long)round((double)(((float)(Weight[_number].CalibTemp - lWeight.Val))/100.0 * Weight[_number].TempConst)); lTempWeight = Weight[_number].Value; Weight[_number].Value = lTempWeight + lTempWeight2; return ERR_OK; }
WeightCorrection[number].WeightValue[index] a WeightCorrection[number].Temp1[index] a WeightCorrection[number].Temp2[index] sú hodmoty hmotnosti a teploty ktoré musíme uchovávať z predchádzajúcich meraní. Merania musia prebiehať každú hodinu počas posledných 72 hodín. Takže potrebujeme uchovávať 72 hodnôt hmotnosti pre každú vážiacu plošinu a 2x72 hodnôt teploty tenzometrov.
Podrobnejší popis tejto funkcie doplním neskôr.