diff --git a/libraries/SondeLib/RS41.cpp b/libraries/SondeLib/RS41.cpp index a71c9ac..dfc506e 100644 --- a/libraries/SondeLib/RS41.cpp +++ b/libraries/SondeLib/RS41.cpp @@ -17,6 +17,128 @@ static byte data[800]; static int dpos = 0; +static byte subframe[51*16]; // 816 subframe bytes +static bool subframeReceived[51] = { false }; // do we have data for row +static bool subframeComplete = false; // is the subframe complete +static bool validExternalTemperature = false; // have received all the calibration frames for the external temperature +static bool validHumidity = false; // have received all the calibration frames for the humidity +static bool validRAExternalTemperature = false; // have received all the calibration frames for the external temperature +static bool validRAHumidity = false; // have received all the calibration frames for the humidity + +// whole 51 row frame as C structure +// taken from https://github.com/einergehtnochrein/ra-firmware +union subframeBuffer { + byte rawData[51*16]; + struct __attribute__((__packed__)) { + uint16_t crc16; /* CRC16 CCITT Checksum over range 0x002...0x31F */ + uint16_t frequency; /* 0x002: TX is on 400 MHz + (frequency / 64) * 10 kHz */ + uint8_t startupTxPower; /* 0x004: TX power level at startup (1...7) */ + uint8_t reserved005; + uint8_t reserved006; + uint16_t reserved007; /* 0x007: ?? (some bitfield) [0],[1],[2],[3]. Init value = 0xE */ + uint16_t reserved009; /* 0x009: ? */ + uint8_t reserved00B; + uint8_t reserved00C; + uint8_t serial[8]; /* 0x00D: Sonde ID, 8 char, not terminated */ + uint16_t firmwareVersion; /* 0x015: 10000*major + 100*minor + patch*/ + uint16_t reserved017; + uint16_t minHeight4Flight; /* 0x019: Height (meter above ground) where flight mode begins */ + uint8_t lowBatteryThreshold100mV; /* 0x01B: (Default=18) Shutdown if battery voltage below this + threshold for some time (10s ?) + */ + uint8_t nfcDetectorThreshold; /* 0x01C: NFC detector threshold [25mV] (Default: 0x05 = 125mV) */ + uint8_t reserved01D; /* 0x01D: ?? (Init value = 0xB4) */ + uint8_t reserved01E; /* 0x01E: ?? (Init value = 0x3C) */ + uint16_t reserved01F; + int8_t refTemperatureThreshold; /* 0x021: Reference temperature threshold [°C] */ + uint8_t reserved022; + uint16_t reserved023; + uint16_t reserved025; + int16_t flightKillFrames; /* 0x027: Number of frames in flight until kill (-1 = disabled) */ + uint16_t reserved029; /* 0x029: ? (Init value = 0) */ + uint8_t burstKill; /* 0x02B: Burst kill (0=disabled, 1=enabled) */ + uint8_t reserved02C; + uint8_t reserved02D; + uint16_t reserved02E; + uint16_t reserved030; + uint8_t reserved032; + uint16_t reserved033; + uint16_t reserved035; + uint16_t reserved037; + uint16_t reserved039; /* 0x039: */ + uint8_t reserved03B; /* 0x03B: */ + uint8_t reserved03C; /* 0x03C: */ + float refResistorLow; /* 0x03D: Reference resistor low (750 Ohms) */ + float refResistorHigh; /* 0x041: Reference resistor high (1100 Ohms) */ + float refCapLow; /* 0x045: Reference capacitance low (0) */ + float refCapHigh; /* 0x049: Reference capacitance high (47 pF) */ + float taylorT[3]; /* 0x04D: Tayor coefficients for main temperature calculation */ + float calT; /* 0x059: Calibration factor for main sensor */ + float polyT[6]; /* 0x05D: */ + float calibU[2]; /* 0x075: Calibration coefficients for humidity sensor */ + + float matrixU[7][6]; /* 0x07D: Matrix for humidity sensor RH calculation */ + float taylorTU[3]; /* 0x125: Coefficients for U sensor temperature calculation */ + float calTU; /* 0x131: Calibration factor for U temperature sensor */ + float polyTrh[6]; /* 0x135: */ + + uint8_t reserved14D; /* 0x14D: */ + uint32_t reserved14E; /* 0x14E: */ + + float f152; + uint8_t u156; + float f157; /* 0x157: ?? (Initialized by same value as calibU) */ + uint8_t reserved15B[0x160-0x15B]; + float f160[35]; + uint8_t startIWDG; /* 0x1EC: If ==0 or ==2: Watchdog IWDG will not be started */ + uint8_t parameterSetupDone; /* 0x1ED: Set (!=0) if parameter setup was done */ + uint8_t reserved1EE; + uint8_t reserved1EF; + float f1F0[8]; + float pressureLaunchSite[2]; /* 0x210: Pressure [hPa] at launch site */ + struct { + char variant[10]; /* 0x218: Sonde variant (e.g. "RS41-SG") */ + uint8_t mainboard[10]; /* 0x222: Name of mainboard (e.g. "RSM412") */ + } names; + struct { + uint8_t mainboard[9]; /* 0x22C: Serial number of mainboard (e.g. "L1123553") */ + uint8_t text235[12]; /* 0x235: "0000000000" */ + uint16_t reserved241; /* 0x241: */ + uint8_t pressureSensor[8]; /* 0x243: Serial number of pressure sensor (e.g. "N1310487") */ + uint16_t reserved24B; /* 0x24B: */ + } serials; + uint16_t reserved24D; /* 0x24D: */ + uint8_t reserved24F; + uint8_t reserved250; + uint16_t reserved251; /* 0x251: (Init value = 0x21A = 538) */ + uint8_t xdataUartBaud; /* 0x253: 1=9k6, 2=19k2, 3=38k4, 4=57k6, 5=115k2 */ + uint8_t reserved254; + float cpuTempSensorVoltageAt25deg; /* 0x255: CPU temperature sensor voltage at 25°C */ + uint8_t reserved259; + uint8_t reserved25A[0x25E -0x25A]; + float matrixP[18]; /* 0x25E: Coefficients for pressure sensor polynomial */ + float f2A6[17]; + uint8_t reserved2EA[0x2FA-0x2EA]; + uint16_t halfword2FA[9]; + float reserved30C; + float reserved310; /* 0x310: */ + uint8_t reserved314; /* 0x314: */ + uint8_t reserved315; /* 0x315: */ + int16_t burstKillFrames; /* 0x316: Number of active frames after burst kill */ + uint8_t reserved318[0x320-0x318]; + + /* This is fragment 50. It only uses 14 valid bytes! */ + int16_t killCountdown; /* 0x320: Counts frames remaining until kill (-1 = inactive) */ + uint8_t reserved322[6]; + int8_t intTemperatureCpu; /* 0x328: Temperature [°C] of CPU */ + int8_t intTemperatureRadio; /* 0x329: Temperature [°C] of radio chip */ + int8_t reserved32A; /* 0x32A: */ + uint8_t reserved32B; /* 0x32B: */ + uint8_t reserved32C; /* 0x32C: ? (the sum of two slow 8-bit counters) */ + uint8_t reserved32D; /* 0x32D: ? (the sum of two slow 8-bit counters) */ + } value; + } calibration; + static uint16_t CRCTAB[256]; #define X2C_DIVR(a, b) ((b) != 0.0f ? (a)/(b) : (a)) @@ -251,6 +373,11 @@ static int32_t getint32(const byte frame[], uint32_t frame_len, return (int32_t)n; } /* end getint32() */ +static uint32_t getint24(const byte frame[], uint32_t frame_len, uint32_t p) { // 24bit unsigned int + uint32_t val24 = 0; + val24 = frame[p] | (frame[p+1]<<8) | (frame[p+2]<<16); + return val24; +} static uint32_t getcard16(const byte frame[], uint32_t frame_len, uint32_t p) @@ -379,7 +506,124 @@ static void posrs41(const byte b[], uint32_t b_len, uint32_t p) sonde.si()->validPos = 0x7f; } /* end posrs41() */ +void ProcessSubframe( byte subframeBytes[16], int subframeNumber ) { + // the total subframe consists of 51 rows, each row 16 bytes + // based on https://github.com/bazjo/RS41_Decoding/tree/master/RS41-SGP#Subframe + memcpy( calibration.rawData+16*subframeNumber, subframeBytes, 16); + subframeReceived[subframeNumber] = true; // mark this row of the total subframe as complete + + #if 0 + Serial.printf("subframe number: 0x%02X\n", subframeNumber ); + Serial.print("subframe values: "); + for ( int i = 0; i < 16; i++ ) { + Serial.printf( "%02X ", subframeBytes[i] ); + } + Serial.println(); + + Serial.println("Full subframe"); + for ( int j = 0; j<51; j++ ) { + Serial.printf("%03X ", j*16); + for ( int i = 0; i < 16; i++ ) { + Serial.printf( "%02X ", subframe[j*16+i] ); + } + Serial.println(); + } + Serial.println(); + #endif +} + +/* Find the water vapor saturation pressure for a given temperature. + * Uses the Hyland and Wexler equation with coefficients for T < 0°C. + */ +// taken from https://github.com/einergehtnochrein/ra-firmware +static float _RS41_waterVaporSaturationPressure (float Tcelsius) +{ + /* Convert to Kelvin */ + float T = Tcelsius + 273.15f; + + /* Apply some correction magic */ + T = 0 + - 0.4931358f + + (1.0f + 4.61e-3f) * T + - 1.3746454e-5f * T * T + + 1.2743214e-8f * T * T * T + ; + + /* Plug into H+W equation */ + float p = expf(-5800.2206f / T + + 1.3914993f + + 6.5459673f * logf(T) + - 4.8640239e-2f * T + + 4.1764768e-5f * T * T + - 1.4452093e-8f * T * T * T + ); + + /* Scale result to hPa */ + return p / 100.0f; +} + +// taken from https://github.com/einergehtnochrein/ra-firmware +float GetRATemp( uint32_t measuredCurrent, uint32_t refMin, uint32_t refMax, float calT, float taylorT[3], float polyT[6] ) { + /* Reference values for temperature are two known resistors. + * From that we can derive the resistance of the sensor. + */ + float current = float(measuredCurrent - refMin) / float(refMax - refMin); + float res = calibration.value.refResistorLow + + (calibration.value.refResistorHigh - calibration.value.refResistorLow) * current; + float x = res * calT; + + float Tuncal = 0 + + taylorT[0] + + taylorT[1] * x + + taylorT[2] * x * x; + + /* Apply calibration polynomial */ + float temperature = + Tuncal + polyT[0] + + polyT[1] * Tuncal + + polyT[2] * Tuncal * Tuncal + + polyT[3] * Tuncal * Tuncal * Tuncal + + polyT[4] * Tuncal * Tuncal * Tuncal * Tuncal + + polyT[5] * Tuncal * Tuncal * Tuncal * Tuncal * Tuncal; + + return temperature; +} + +// taken from https://github.com/einergehtnochrein/ra-firmware +float GetRAHumidity( uint32_t humCurrent, uint32_t humMin, uint32_t humMax, float sensorTemp, float externalTemp ) { + + float current = float( humCurrent - humMin) / float( humMax - humMin ); + /* Compute absolute capacitance from the known references */ + float C = calibration.value.refCapLow + + (calibration.value.refCapHigh - calibration.value.refCapLow) * current; + /* Apply calibration */ + float Cp = ( C / calibration.value.calibU[0] - 1.0f) * calibration.value.calibU[1]; + Serial.printf("Cp = %f\n", Cp ); + + int j, k; + float sum = 0; + float xj = 1.0f; + for (j = 0; j < 7; j++) { + float yk = 1.0f; + for (k = 0; k < 6; k++) { + sum += xj * yk * calibration.value.matrixU[j][k]; + yk *= ( sensorTemp - 20.0f) / 180.0f; + } + xj *= Cp; + } + + /* Since there is always a small difference between the temperature readings for + * the atmospheric (main) tempoerature sensor and the temperature sensor inside + * the humidity sensor device, transform the humidity value to the atmospheric conditions + * with its different water vapor saturation pressure. + */ + float RH = sum + * _RS41_waterVaporSaturationPressure(sensorTemp) + / _RS41_waterVaporSaturationPressure(externalTemp); + + return RH; +} // returns: 0: ok, -1: rs or crc error int RS41::decode41(byte *data, int maxlen) @@ -457,6 +701,13 @@ int RS41::decode41(byte *data, int maxlen) sonde.si()->countKT = cntdown; sonde.si()->crefKT = fnr; } + + // process this subframe + int subframeOffset = 24; // 24 = 0x18, start of subframe data + byte receivedBytes[16]; + memcpy( receivedBytes, data+p+subframeOffset, 16); + ProcessSubframe( receivedBytes, calnr ); + } // TODO: some more data break; @@ -476,6 +727,64 @@ int RS41::decode41(byte *data, int maxlen) case '{': // pos posrs41(data+p, len, 0); break; + case 'z': // 0x7a is character z - 7A-MEAS temperature and humidity frame + { + uint32_t tempMeasMain = getint24(data, 560, p+0); + uint32_t tempMeasRef1 = getint24(data, 560, p+3); + uint32_t tempMeasRef2 = getint24(data, 560, p+6); + uint32_t humidityMain = getint24(data, 560, p+9); + uint32_t humidityRef1 = getint24(data, 560, p+12); + uint32_t humidityRef2 = getint24(data, 560, p+15); + uint32_t tempHumiMain = getint24(data, 560, p+18); + uint32_t tempHumiRef1 = getint24(data, 560, p+21); + uint32_t tempHumiRef2 = getint24(data, 560, p+24); + uint32_t pressureMain = getint24(data, 560, p+27); + uint32_t pressureRef1 = getint24(data, 560, p+30); + uint32_t pressureRef2 = getint24(data, 560, p+33); + #if 0 + Serial.printf( "External temp: tempMeasMain = %ld, tempMeasRef1 = %ld, tempMeasRef2 = %ld\n", tempMeasMain, tempMeasRef1, tempMeasRef2 ); + Serial.printf( "Rel Humidity: humidityMain = %ld, humidityRef1 = %ld, humidityRef2 = %ld\n", humidityMain, humidityRef1, humidityRef2 ); + Serial.printf( "Humid sensor: tempHumiMain = %ld, tempHumiRef1 = %ld, tempHumiRef2 = %ld\n", tempHumiMain, tempHumiRef1, tempHumiRef2 ); + Serial.printf( "Pressure sens: pressureMain = %ld, pressureRef1 = %ld, pressureRef2 = %ld\n", pressureMain, pressureRef1, pressureRef2 ); + #endif + + // for a valid temperature, require rLow rHigh (frames 3 4) TaylorT (frames 4 5), polyT (frames 5 6 7), + validExternalTemperature = subframeReceived[3] && subframeReceived[4] && subframeReceived[5] + && subframeReceived[6] && subframeReceived[7]; + + // for a valid RA humidity, valid temperature, calibU (frame 7) matrixU (frame 7-12), taylorTU (frame 12 13), + // calTU (frame 13) polyTrh (frame 13 14) + validHumidity = validExternalTemperature && subframeReceived[0x08] && subframeReceived[0x09] && subframeReceived[0x0A] + && subframeReceived[0x0B] && subframeReceived[0x0C] && subframeReceived[0x0D] && subframeReceived[0x0E] + && subframeReceived[0x0F] && subframeReceived[0x10] && subframeReceived[0x11] && subframeReceived[0x12]; + + if ( validExternalTemperature ) { + if ( tempMeasMain > tempMeasRef1 && tempMeasMain < tempMeasRef2 ) { + sonde.si()->temperature = GetRATemp( tempMeasMain, tempMeasRef1, tempMeasRef2, + calibration.value.calT, calibration.value.taylorT, calibration.value.polyT ); + Serial.printf("tempRA = %f\n", sonde.si()->temperature ); + } + else { + Serial.println( "External temperature data measurement mismatch"); + } + } + + if ( validHumidity ) { + if ( tempHumiMain > tempHumiRef1 && tempHumiMain < tempHumiRef2 ) { + sonde.si()->tempRHSensor = GetRATemp( tempHumiMain, tempHumiRef1, tempHumiRef2, + calibration.value.calTU, calibration.value.taylorTU, calibration.value.polyTrh ); + sonde.si()->relativeHumidity = GetRAHumidity( humidityMain, humidityRef1, humidityRef2, sonde.si()->tempRHSensor, sonde.si()->temperature ); + Serial.printf("Relative humidity = %f\n", sonde.si()->relativeHumidity ); + } + else { + Serial.println( "Sensor temperature data measurement mismatch" ); + } + } + } + break; + + + default: break; }} diff --git a/libraries/SondeLib/Sonde.h b/libraries/SondeLib/Sonde.h index 189df28..eb555f4 100644 --- a/libraries/SondeLib/Sonde.h +++ b/libraries/SondeLib/Sonde.h @@ -99,6 +99,9 @@ typedef struct st_sondeinfo { // shut down timers, currently only for RS41; -1=disabled int16_t launchKT, burstKT, countKT; uint16_t crefKT; // frame number in which countKT was last sent + float temperature = -300.0; // platinum resistor temperature + float tempRHSensor = -300.0; // temperature of relative humidity sensor + float relativeHumidity = -1.0; // relative humidity } SondeInfo; // rxStat: 3=undef[empty] 1=timeout[.] 2=errro[E] 0=ok[|] 4=no valid position[°]