Mediciones de conductividad del agua / ESP8266 - obteniendo resultados extraños

2

Espero que este sea el foro correcto y gracias de antemano por leer esto.

Resumen: Estoy trabajando en mediciones de EC (conductividad eléctrica) usando un capacitor y los pines de un microprocesador ESP8266 para crear una corriente CA, y usarla para medir la conductividad de una solución iónica. Lo que realmente estoy midiendo es el tiempo de descarga del condensador y, a partir de eso, se mide la resistencia eléctrica.

Documento de investigación en el que se basa este proyecto: enlace

Ahora, cuando se miden líquidos con bajo contenido de EC, con largos tiempos de descarga (que son de 15 a 20 microsegundos), el tiempo de descarga aumenta desproporcionadamente. El mismo efecto lo veo en tiempos de descarga cortos (en la tonalidad de < 1 microsegundo) pero menos fuertes, al menos dentro del rango que estoy midiendo.

Cada sonda EC requiere tres puertos GPIO. Un ESP8266 maneja tres sondas EC. El código fuente del boceto utilizado en la parte inferior, vea los comentarios en la fuente para obtener una explicación de cómo funciona.

Creo firmemente que el método de medición como tal es correcto: las mediciones son altamente reproducibles, y el uso de una CA de alta frecuencia es la forma correcta de medir un líquido iónico. Lo más probable es que el problema esté oculto dentro del ESP8266: espero descubrir qué causa este error y cómo corregirlo.

Laprimeraobservaciónclaraesquelospuertosnosontodosiguales.Porsupuesto,hayalgunosquetienenresistenciasdepull-upopull-down,yGPIO2estáconectadoalLEDinterno,perohaymásdiferenciassistémicasentrelospuertos.Obtengodiferentestiemposdedescargaparadiferentesgruposdepuertos,peroesostiempossonconsistentesentremisdosprototipos,porloqueesunacosadeESP,nodecomponentesexternos.

EstegráficomuestralaCEmedidafrentealtiempodedescargarecíproca,queesunamedidadirectadelaconductividad(Cfija,porloquetserelacionaconRenuncircuitoRC,ylaconductividades1/R).Haydostablas,126y127,cadaunacontressondas.Mismosoftwareyamp;esquemático.

Haytresgruposdedoslíneasqueestánprácticamenteunaencimadelaotra.EstomuestraladiferenciaenelcomportamientodelospuertosGPIO.Loscondensadoresutilizadostienenunatoleranciadel5%.

ECdebeserlinealalaraízcuadradadelaconcentración;Lassolucionesdiluidascomolasqueutilizopuedenaproximarsebienalhacerlalinealalaconcentraciónensímisma.Estoesloquehice.Luego,conlosresultadosdelaregresión,calculéelerrordemedicióndecadapunto(calculeelvaloresperadoutilizandolapendiente/intersección,compareconelvalormedido).Esteeselresultadoenerrores:

Nuevamente,tresgruposdedoslíneas:loserroressonaltamenteconsistentesentrelosdostableros.Estomehacecreerquehayalgúntipodeerrorsistémico,quedeberíatenerseencuenta,pero¿cuálpodríaseresteerror?

Códigofuente:solopartesrelevantes(estonoseejecutarácomoestá).

//Pinconnections.#defineWIFILED2//IndicatorLED-onwhenWiFiisconnected.//EC1,capPos:GPIO12//EC1,capNeg:GPIO14//EC1,ECPin:GPIO16//EC2,capPos:GPIO0//EC2,capNeg:GPIO4//EC2,ECPin:GPIO13//EC3,capPos:GPIO5//EC3,capNeg:GPIO1//EC3,ECPin:GPIO15//TX=1,RX=3.#ifSERIAL//don'ttrytousethethirdprobeasitmessesuptheSerialinterface.intcapPos[]={12,0};//CapPos/C+forECprobes.intcapNeg[]={16,4};//CapNeg/C-forECprobes.intECpin[]={14,13};//ECpin/ECforECprobes.#defineNPROBES2#elseintcapPos[]={12,0,5};//CapPos/C+forECprobes.intcapNeg[]={16,4,1};//CapNeg/C-forECprobes.intECpin[]={14,13,15};//ECpin/ECforECprobes.#defineNPROBES3#endifintinterruptPin;//storeswhichpinisusedtoconnecttheECinterrupt.//ECprobedata#defineCYCLETIME12.5//thetimeittakesinnanosecondstocompleteoneCPUcycle(12.5nsona80MHzprocessor)unsignedlongstartCycle;unsignedlongendCycle;floatEC[3]={-1,-1,-1};//thearrayinwhichtostoretheECreadings.//GPIOregistersforECprobing-thisisESP8266specific.#defineGPIO_SET_OUTPUT_HIGHPERIPHS_GPIO_BASEADDR+4#defineGPIO_SET_OUTPUT_LOWPERIPHS_GPIO_BASEADDR+8#defineGPIO_SET_OUTPUTPERIPHS_GPIO_BASEADDR+16#defineGPIO_SET_INPUTPERIPHS_GPIO_BASEADDR+20/***capacitorbasedTDSmeasurement*pinCapPos-----330ohmresistor----|------------|*||*capECprobeor*|resistor(forsimulation)*pinCapNeg---------------------------||*|*pinECpin------180ohmresistor-----------------|**So,what'sgoingonhere?*EC-electicconductivity-isthereciprocaloftheresistanceoftheliquid.*Sowehavetomeasuretheresistance,butthiscannotbedonedirectlyasrunning*aDCcurrentthroughanionicliquidjustdoesn'twork,aswegetelectrolysisand*themigrationofionstotherespectiveelectrodes.**SothisroutingisusingthepinsoftheNodeMCUandacapacitortoproducea*highfrequencyACcurrent(1kHzormore-basedonthepulselength,butthe*pulsescomeatintervals).Alternatingthedirectionofthecurrentinthese*shortpulsespreventstheproblemsmentionedabove.**Thentogettheresistanceitisnotpossibletomeasurethevoltageoverthe*ECprobe(thenormalwayofmeasuringelectricalresistance)asthisdropswith*thecapacitordischarging.Insteadwemeasurethetimeittakesforthecapto*dischargeenoughforthevoltageonthepintodropsomuch,thattheinput*flipsfromHightoLowstate.Thistimetakenisadirectmeasureofthe*resistanceencountered(thecapandtheECprobeformanRCcircuit)inthe*system,andthat'swhatweneedtoknow.**Nowtheworkingofthistechnique.*Stage1:chargethecapfullthroughpinCapPos.*Stage2:letthecapdrainthroughtheECprobe,measurethetimeittakesfrom*flippingthepinsuntilCapPosdropsLOW.*Stage3:chargethecapfullwithoppositecharge.*Stage4:letthecapdrainthroughtheECprobe,forthesameperiodoftimeas*wasmeasuredinStage2(ascompensation).*Capisasmallcapacitor,inthissystemweuse47nFbutwithotherprobesa*largerorsmallervaluecanberequired(theoriginalresearchthisisbased*uponuseda3.3nFcap).The330RresistoristheretoprotectpinCapPosand*CapNegfrombeingoverloadedwhenthecapischargedup,the180Rresistor*protectsECpinfromtoohighcurrentscausedbyveryhighECorshortingthe*probe.**Pinssettoinputareassumedtohaveinfiniteimpedance,leakingisnottakeninto*account.ThespecsofNodeMCUgivesome66MOhmforimpedance,severalordersof*magnitudeabovethetypical1-100kOhmresistanceencounteredbytheECprobe.**Thisfunctionusesdelay()invariousforms,noyield()orsoasit'smeantto*bearealtimemeasurement.Yield()ingtothetaskschedulerisabadidea.*Withthemeasurementtakingonlyjustover0.1secondsthisshouldnotbean*issue.**Originalresearchthisisbasedupon:*https://hal.inria.fr/file/index/docid/635652/filename/TDS_Logger_RJP2011.pdf**/voidgetEC(){intsamples=100;//numberofECsamplestotakeandaverage.unsignedlongstartTime;//thetimestamp(inmicroseconds)themeasurementstarts.unsignedlongendTime;//thetimestamp(inmicroseconds)themeasurementisfinished.unsignedintdischargeTime;//thetimeittookforthecapacitortodischarge.unsignedintchargeDelay=100;//Thetime(inmicroseconds)giventothecaptofullycharge/discharge-atleast5xRC.unsignedinttimeout=2;//dischargetimeoutinmilliseconds-ifnottriggeredwithinthistime,theECprobeisprobablynotthere.delay(1);for(intj=0;j<NPROBES;j++){interruptPin=capPos[j];intpinECpin=ECpin[j];intpincapPos=capPos[j];intpincapNeg=capNeg[j];Average<unsignedint>discharge(samples);//Thesamplingresults.if(LOGGING)writeLog("Probing EC probe " + String(j+1) + " on capPos pin " + String (pincapPos) + ", capNeg pin " + String (pincapNeg) + ", ECpin " + String (pinECpin) + ".");
    for (int i=0; i<samples; i++) { // take <samples> measurements of the EC.

      // Stage 1: fully charge capacitor for positive cycle.
      // CapPos output high, CapNeg output low, ECpin input.
      pinMode (pinECpin, INPUT);
      pinMode (pincapPos,OUTPUT);
      pinMode (pincapNeg, OUTPUT);
      WRITE_PERI_REG (GPIO_SET_OUTPUT_HIGH, (1<<pincapPos));
      WRITE_PERI_REG (GPIO_SET_OUTPUT_LOW, (1<<pincapNeg));
      delayMicroseconds(chargeDelay); // allow the cap to charge fully.
      yield();

      // Stage 2: positive side discharge; measure time it takes.
      // CapPos input, CapNeg output low, ECpin output low.
      endCycle = 0;
      startTime = millis();
      pinMode (pincapPos,INPUT);
      attachInterrupt(digitalPinToInterrupt(interruptPin), capDischarged, FALLING);
      WRITE_PERI_REG (GPIO_SET_OUTPUT_LOW, (1<<pinECpin));
      pinMode (pinECpin, OUTPUT); 

      // Use cycle counts and an interrupt to get a much more precise time measurement, especially for high-EC situations.
      startCycle = ESP.getCycleCount();
      while (endCycle == 0) {
        if (millis() > (startTime + timeout)) break;
        yield();
      }
      detachInterrupt(digitalPinToInterrupt(pincapPos));
      if (endCycle == 0) dischargeTime = 0;
      else {

        // Handle potential overflow of micros() just as we measure, this happens about every 54 seconds
        // on a 80-MHz board.
        if (endCycle < startCycle) dischargeTime = (4294967295 - startCycle + endCycle) * CYCLETIME;
        else dischargeTime = (endCycle - startCycle) * CYCLETIME;
        discharge.push(dischargeTime);
        if (LOGGING) writeLog ("sampled dischargeTime: " + String(dischargeTime));
      }

      yield();

      // Stage 3: fully charge capacitor for negative cycle. CapPos output low, CapNeg output high, ECpin input.
      WRITE_PERI_REG (GPIO_SET_OUTPUT_HIGH, (1<<pincapNeg));
      WRITE_PERI_REG (GPIO_SET_OUTPUT_LOW, (1<<pincapPos));
      pinMode (pinECpin, INPUT); 
      pinMode (pincapPos,OUTPUT);
      pinMode (pincapNeg, OUTPUT);
      delayMicroseconds(chargeDelay);
      yield();

      // Stage 4: negative side charge; don't measure as we just want to balance it the directions.
      // CapPos input, CapNeg high, ECpin high.
      WRITE_PERI_REG (GPIO_SET_OUTPUT_HIGH, (1<<pinECpin));
      pinMode (pincapPos,INPUT);
      pinMode (pinECpin, OUTPUT); 
      delayMicroseconds(dischargeTime/1000);
      yield();
    }

    // Stop any charge from flowing while we're not measuring by setting all ports to OUTPUT, LOW.
    // This will of course also completely discharge the capacitor.
    pinMode (pincapPos, OUTPUT);
    digitalWrite (pincapPos, LOW);
    pinMode (pincapNeg, OUTPUT);
    digitalWrite (pincapNeg, LOW);
    pinMode (pinECpin, OUTPUT);
    digitalWrite (pinECpin, LOW);
    yield();
    float dischargeAverage = discharge.mean();
    if(LOGGING)  writeLog("Discharge time probe " + String(j) + ": " + String(dischargeAverage) + " ns.");

    /**
     * Calculate EC from the discharge time.
     * 
     * Discharge time is directly related to R x C. 
     * Here we have a discharge time, as we have a fixed capacitor this discharge time is linearly? related
     * to the resistance we try to measure.
     * Now we don't care about the actual resistance value - what we care about is the EC and TDS values. The 
     * EC is the reciprocal of the resistance, the TDS is a function of EC. The capacitor is fixed, so the actual
     * value is irrelevant(!) for the calculation as this is represented in the calibration factor.
     * 
     * As ion activity changes drastically with the temperature of the liquid, we have to correct for that. The 
     * temperature correction is a simple "linear correction", typical value for this ALPHA factor is 2%/degC.
     * 
     * Source and more information:
     * https://www.analyticexpert.com/2011/03/temperature-compensation-algorithms-for-conductivity/
     * 
     * Port D8 of NodeMCU leaks charge, this has to be accounted for using the INF time factor. This port should be
     * avoided later.
     */
    #ifdef USE_NTC

      // Calculate corrected time.
      if (dischargeAverage > 0) {
        float t = dischargeAverage - ZERO[j];
        if (INF[j] > 0) t /= 1-dischargeAverage/INF[j];
        EC[j] = ECSLOPE[j] / (dischargeAverage * (1 + ALPHA * (watertemp - 25))) + ECOFFSET[j];
      }
      else {
        EC[j] = -1;
      }
    #else
      EC[j] = dischargeAverage;
    #endif
  }
}

// Upon interrupt: register the cycle count of when the cap has discharged.
void capDischarged() {
  endCycle = ESP.getCycleCount();
  detachInterrupt(digitalPinToInterrupt(interruptPin));
}
    
pregunta Wouter

1 respuesta

1

Mi conjetura es que \ $ Rds_ {on} \ $ para los mosfets internos no coinciden, como es el caso de la mayoría de los GPIO, tienen amplias tolerancias en el circuito de GPIO y ya que el ESP8266 no tiene ninguna documentación o prueba (Es un producto chino y el 95% de ellos tiene documentación y pruebas deficientes).

Supongo que usas el GPIO para un interruptor lateral bajo, si lo haces, compraría algunos mosfets y los usaría como un interruptor lateral bajo en lugar de los puertos GPIO para que sepas cuál es tu \ $ Rds_ { en} \ $ va a ser.

De cualquier manera, sé que la conductividad de un líquido va a ser de 1 Ohm a mili-Ohm, y si se pone un mosfet en serie con el que tiene 10s de mili-ohmios de resistencia (y es casi seguro que varía de puerto a puerto). Dado que se trata de una incógnita, ¿por qué no comprar algunas mosfets baratas y convertirlas en una fuente de error conocida?

Prueba esto:
Haga que Vin vaya a su gpio, obtenga algunos mosfets con el \ $ Rds_ {on} \ $ que necesita y asegúrese de que el voltaje del GPIO (probablemente 3.3) los active. Tu carga sería la conexión al agua. Es posible que necesite una doble etapa con su 12V para obtener mosfets con un \ $ Rds_ {on} \ $ lo suficientemente bajo para su aplicación.

También observaría las tolerancias en sus resistencias, 330Ω al 1% sería 3.3Ω. Entonces, si está conectando 330Ω en su software, la resistencia real podría estar en cualquier lugar entre 333.3Ω y 326.7Ω. Lo mismo con los condensadores, ya que algunas cerámicas tienen tolerancias del 10%.

    
respondido por el laptop2d

Lea otras preguntas en las etiquetas