/* M10 and M20 decoder functions */ #include "M10M20.h" #include "SX1278FSK.h" #include "rsc.h" #include "Sonde.h" #include #define M10M20_DEBUG 1 #if M10M20_DEBUG #define M10M20_DBG(x) x #else #define M10M20_DBG(x) #endif static byte data1[512]; static byte *dataptr=data1; static uint8_t rxbitc; static uint16_t rxbyte; static int rxp=0; static int haveNewFrame = 0; //static int lastFrame = 0; static int headerDetected = 0; decoderSetupCfg m10m20SetupCfg = { .bitrate = 9600, //// Disable auto-AFC, auto-AGC, RX Trigger by preamble .rx_cfg = 0x00, .sync_cfg = 0x70, .sync_len = 1, .sync_data = (const uint8_t *)"\x66\x66", // Preamble detection off (+ size 1 byte, maximum tolerance; should not matter for "off"?) .preamble_cfg = 0x00 | 0x00 | 0x1F, }; int M10M20::setup(float frequency, int /*type*/) { M10M20_DBG(Serial.println("Setup sx1278 for M10/M20 sonde"));; if(sx1278.ON()!=0) { M10M20_DBG(Serial.println("Setting SX1278 power on FAILED")); return 1; } // setFSK: switches to FSK standby mode if(sx1278.setFSK()!=0) { M10M20_DBG(Serial.println("Setting FSK mode FAILED")); return 1; } Serial.print("M10/M20: setting RX frequency to "); Serial.println(frequency); int res = sx1278.setFrequency(frequency); // Test: maybe fix issue after spectrum display? sx1278.writeRegister(REG_PLL_HOP, 0); if(sx1278.setAFCBandwidth(sonde.config.m10m20.agcbw)!=0) { M10M20_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.m10m20.agcbw)); return 1; } if(sx1278.setRxBandwidth(sonde.config.m10m20.rxbw)!=0) { M10M20_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.m10m20.rxbw)); return 1; } /// TODO: Maybe do this conditionally? -- maybe skip if afc if agcbw set to 0 or -1? //// Step 1: Tentative AFC mode sx1278.clearIRQFlags(); // preamble detector + AFC + AGC on // wait for preamble interrupt within 2sec sx1278.setBitrate(4800); // DetectorOn=1, Preamble detector size 01, preamble tol 0x0A (10) sx1278.setPreambleDetect(0x80 | 0x20 | 0x0A); // Manual start RX, Enable Auto-AFC, Auto-AGC, RX Trigger (AGC+AFC)by preamble sx1278.setRxConf(0x20 | 0x10 | 0x08 | 0x06); // Packet config 1: fixed len, no mancecer, no crc, no address filter // Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0) if(sx1278.setPacketConfig(0x08, 0x40)!=0) { M10M20_DBG(Serial.println("Setting Packet config FAILED")); return 1; } // enable RX sx1278.setPayloadLength(0); sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE); unsigned long t0 = millis(); M10M20_DBG(Serial.printf("M10M20::setup() AFC preamble search start at %ld\n",t0)); while( millis() - t0 < 1000 ) { uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS1); if(value & 2) { int32_t afc = sx1278.getAFC(); int16_t rssi = sx1278.getRSSI(); Serial.printf("M10M20::setup: preamble: AFC is %d, RSSI is %.1f\n", afc, rssi/2.0); sonde.sondeList[rxtask.currentSonde].rssi = rssi; sonde.sondeList[rxtask.currentSonde].afc = afc; break; } yield(); } if( millis() - t0 >= 1000) { Serial.println("Preamble scan for AFC: TIMEOUT\n"); return 1; // no preamble, so we may fail fast.... } //// Step 2: Real reception if( DecoderBase::setup(m10m20SetupCfg, sonde.config.m10m20.agcbw, sonde.config.m10m20.rxbw)!=0 ) { return 1; } #if 0 // Now all done in DecoderBase::setup // FSK standby mode, seems like otherweise baudrate cannot be changed? sx1278.setFSK(); if(sx1278.setBitrate(9600)!=0) { M10M20_DBG(Serial.println("Setting bitrate 9600bit/s FAILED")); return 1; } M10M20_DBG(Serial.printf("Exact bitrate is %f\n", sx1278.getBitrate())); // Probably not necessary, as this was set before if(sx1278.setAFCBandwidth(sonde.config.m10m20.agcbw)!=0) { M10M20_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.m10m20.agcbw)); return 1; } if(sx1278.setRxBandwidth(sonde.config.m10m20.rxbw)!=0) { M10M20_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.m10m20.rxbw)); return 1; } ///// Enable auto-AFC, auto-AGC, RX Trigger by preamble //if(sx1278.setRxConf(0x1E)!=0) { // Disable auto-AFC, auto-AGC, RX Trigger by preamble if(sx1278.setRxConf(0x00)!=0) { M10M20_DBG(Serial.println("Setting RX Config FAILED")); return 1; } // version 1, working with continuous RX const char *SYNC="\x66\x66"; if(sx1278.setSyncConf(0x70, 1, (const uint8_t *)SYNC)!=0) { M10M20_DBG(Serial.println("Setting SYNC Config FAILED")); return 1; } // Preamble detection off (+ size 1 byte, maximum tolerance; should not matter for "off"?) if(sx1278.setPreambleDetect(0x00 | 0x00 | 0x1F)!=0) { M10M20_DBG(Serial.println("Setting PreambleDetect FAILED")); return 1; } #endif // Packet config 1: fixed len, no mancecer, no crc, no address filter // Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0) if(sx1278.setPacketConfig(0x08, 0x40)!=0) { M10M20_DBG(Serial.println("Setting Packet config FAILED")); return 1; } // enable RX sx1278.setPayloadLength(0); // infinite for now... sx1278.setRxConf(0x20); uint16_t afc = sx1278.getRawAFC(); sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE); delay(50); sx1278.setRawAFC(afc); delay(50); Serial.printf("after RX_MODE: AFC is %d\n", sx1278.getAFC()); #if M10M20_DEBUG M10M20_DBG(Serial.println("Setting SX1278 config for M10 finished\n"); Serial.println()); #endif return res; } M10M20::M10M20() { } #define M10_FRAMELEN 101 #define M10_CRCPOS 99 #define M20_FRAMELEN 88 #define M20_CRCPOSB 22 void M10M20::printRaw(uint8_t *data, int len) { char buf[3]; int i; for(i=0; i> 1) | ((b & 1) << 7); b ^= (b >> 2) & 0xFF; // A1 t6 = ( c & 1) ^ ((c >> 2) & 1) ^ ((c >> 4) & 1); t7 = ((c >> 1) & 1) ^ ((c >> 3) & 1) ^ ((c >> 5) & 1); t = (c & 0x3F) | (t6 << 6) | (t7 << 7); // A2 s = (c >> 7) & 0xFF; s ^= (s >> 2) & 0xFF; c0 = b ^ t ^ s; return ((c1 << 8) | c0) & 0xFFFF; } static uint16_t crc_M10M20(int len, uint8_t *msg) { uint16_t cs = 0; for (int i = 0; i < len; i++) { cs = update_checkM10M20(cs, msg[i]); } return cs; } static bool checkM10M20crc(int crcpos, uint8_t *msg) { uint16_t cs, cs1; cs = crc_M10M20(crcpos, msg); cs1 = (msg[crcpos] << 8) | msg[crcpos+1]; return (cs1 == cs); } typedef uint32_t SET256[8]; static SET256 sondeudp_VARSET = {0x03BBBBF0UL,0x80600000UL,0x06A001A0UL, 0x0000001CUL,0x00000000UL,0x00000000UL,0x00000000UL, 0x00000000UL}; // VARSET=SET256{4..9,11..13,15..17,19..21,23..25,53..54,63,69,71,72,85,87,89,90,98..100}; static SET256 sondeudp_VARSETM20 = {0xF3E27F54UL,0x0000000FUL,0x00000030UL, 0x00000000UL, 0x00444C39UL, 0x53445A00UL, 0x00000000UL, 0x00000000UL}; // VARSET=SET256{2,4,6,8..10,11..14,17,21..25,28..35,68,69}; (* known as variable *) static uint8_t fixcnt[M10_FRAMELEN]; static uint8_t fixbytes[M10_FRAMELEN]; static int32_t getint32(uint8_t *data) { return (int32_t)( data[3]|(data[2]<<8)|(data[1]<<16)|(data[0]<<24) ); } static int32_t getint24(uint8_t *data) { return (int32_t)(data[2]|(data[1]<<8)|(data[0]<<16) ); } static int32_t getint24_r(uint8_t *data) { return (int32_t)(data[0]|(data[1]<<8)|(data[2]<<16) ); } static int16_t getint16(uint8_t *data) { return (int16_t)(data[1]|((uint16_t)data[0]<<8)); } static int16_t getint16_r(uint8_t *data) { return (int16_t)(((uint16_t)data[1]<<8) |data[0]); } static char dez(uint8_t nr) { nr = nr%10; return '0'+nr; } static char hex(uint8_t nr) { nr = nr&0x0f; if(nr<10) return '0'+nr; else return 'A'+nr-10; } const static float DEGMUL = 1.0/0xB60B60; #define VMUL 0.005 #define VMUL_M20 0.01 #ifndef PI #define PI (3.1415926535897932384626433832795) #endif #define RAD (PI/180) // ret: 1=frame ok; 2=frame with errors; 0=ignored frame (m10dop-alternativ) int M10M20::decodeframeM10(uint8_t *data) { int repairstep = 16; int repl = 0; bool crcok; // error correction, inspired by oe5dxl's sondeudp do { crcok = checkM10M20crc(M10_CRCPOS, data); if(crcok || repairstep==0) break; repl = 0; for(int i=0; i=repairstep) ) { repl++; data[i] = fixbytes[i]; } } repairstep >>= 1; } while(true); if(crcok) { for(int i=0; id); // Set type info to M10 memcpy(si->typestr, "M10 ", 5); si->subtype = 1; // subtype 1: M10 // Its a M10 // getid... char ids[12]; ids[0] = 'M'; ids[1] = 'E'; ids[2] = hex(data[95]/16); ids[3] = hex(data[95]); ids[4] = hex(data[93]); uint32_t id = data[96] + data[97]*256; ids[5] = hex(id/4096); ids[6] = hex(id/256); ids[7] = hex(id/16); ids[8] = hex(id); ids[9] = 0; strncpy(si->id, ids, 10); ids[0] = hex(data[95]/16); ids[1] = dez((data[95]&0x0f)/10); ids[2] = dez((data[95]&0x0f)); ids[3] = '-'; ids[4] = dez(data[93]); ids[5] = '-'; ids[6] = dez(id>>13); id &= 0x1fff; ids[7] = dez(id/1000); ids[8] = dez((id/100)%10); ids[9] = dez((id/10)%10); ids[10] = dez(id%10); ids[11] = 0; strncpy(si->ser, ids, 12); si->validID = true; Serial.printf("ID is %s [%02x %02x %d]\n", ids, data[95], data[93], id); // ID printed on sonde is ...-.-abbbb, with a=id>>13, bbbb=id&0x1fff in decimal // position data si->lat = getint32(data+14) * DEGMUL; si->lon = getint32(data+18) * DEGMUL; si->alt = getint32(data+22) * 0.001; float ve = getint16(data+4)*VMUL; float vn = getint16(data+6)*VMUL; si->vs = getint16(data+8) * VMUL; si->hs = sqrt(ve*ve+vn*vn); si->sats = data[30]; float dir = atan2(ve, vn)*(1.0/RAD); if(dir<0) dir+=360; si->dir = dir; si->validPos = 0x3f; // m10 temp float T = NAN; { const float p0 = 1.07303516e-03, p1 = 2.41296733e-04, p2 = 2.26744154e-06, p3 = 6.52855181e-08; const float Rs[3] = { 12.1e3 , 36.5e3 , 475.0e3 }; const float Rp[3] = { 1e20 , 330.0e3 , 2000.0e3 }; uint8_t sct = data[62]; float rt = getint16_r(data+63) & (0xFFF); if(rt!=0 && sct<3) { rt = (4095-rt)/rt - (Rs[sct]/Rp[sct]); if(rt>0) { rt = Rs[sct] / rt; if(rt>0) { rt = log(rt); rt = 1/( p0 + p1*rt + p2*rt*rt + p3*rt*rt*rt ) - 273.15; if(rt>-99 && rt<50) { T = rt; } } } } si->temperature = T; } // m10 battery uint16_t batADC = (uint16_t)getint16_r(data+0x45); si->batteryVoltage = 2.709 * batADC * 2.5/1023.0; // m10 humidity { float cRHc55 = ((float)(uint32_t)getint24_r(data+0x35)) / (uint32_t)getint24_r(data+0x32); float TH = -273.15; const float huRs = 22.1e3; const float p0 = 4.42606809e-03, p1 = -6.58184309e-04, p2 = 8.95735557e-05, p3 = -2.84347503e-06; float R = huRs / ( (4095.0/getint16_r(data+0x59)) - 1 ); if(R>0) TH += 1/( p0 + p1*log(R) + p2*log(R)*log(R) + p3*log(R)*log(R)*log(R) ); //float Tc = T; float rh = (cRHc55-0.8955)/0.002; const float T0=0.0, T1=-30.0; //float T = Tc; if(T100.0) rh=100.0; si->relativeHumidity = rh; } Serial.printf("hum: %.2f batt: %.2f\n", si->relativeHumidity, si->batteryVoltage); uint32_t gpstime = getint32(data+10); uint16_t gpsweek = getint16(data+32); // UTC is GPSTIME - 18s (24*60*60-18 = 86382) // one week = 7*24*60*60 = 604800 seconds // unix epoch starts jan 1st 1970 0:00 // gps time starts jan 6, 1980 0:00. thats 315964800 epoch seconds. // subtracting 86400 yields 315878400UL si->time = (gpstime/1000) + 86382 + gpsweek*604800 + 315878400UL; // consistent with autorx, vframe is based on GPS time without the -18 seconds adjustment // for the GPS time / UTC time difference (included in 86382 above) si->vframe = si->time - 315964800 + 18; si->validTime = true; } else { Serial.printf("data is %02x %02x %02x\n", data[0], data[1], data[2]); return 0; } return 1; } static uint32_t rxdata; static bool rxsearching=true; static bool isM20=false; // search for // //101001100110011010011010011001100110100110101010100110101001 // //1010011001100110100110100110 0110.0110 1001.1010 1010.1001 1010.1001 => 0x669AA9A9 void M10M20::processM10data(uint8_t dt) { for(int i=0; i<8; i++) { uint8_t d = (dt&0x80)?1:0; dt <<= 1; rxdata = (rxdata<<1) | d; if( (rxbitc&1)==0 ) { // "bit1" rxbyte = (rxbyte<<1) | d; } else { // "bit2" ==> 01 or 10 => 1, otherweise => 0 rxbyte = rxbyte ^ d; } // if(rxsearching) { if( rxdata == 0xcccca64c || rxdata == 0x333359b3 ) { rxsearching = false; rxbitc = 0; rxp = 0; //isM20 = false; headerDetected = 1; #if 1 int rssi=sx1278.getRSSI(); int fei=sx1278.getFEI(); int afc=sx1278.getAFC(); Serial.print("SYNC!!! Test: RSSI="); Serial.print(rssi); Serial.print(" FEI="); Serial.print(fei); Serial.print(" AFC="); Serial.println(afc); sonde.si()->rssi = rssi; sonde.si()->afc = afc; #endif } } else { rxbitc = (rxbitc+1)%16; // 16; if(rxbitc == 0) { // got 8 data bit //Serial.printf("%03x ",rxbyte); dataptr[rxp++] = rxbyte&0xff; // (rxbyte>>1)&0xff; // detect type of sonde: // 64 9F 20 => M10 // 64 49 0x => M10 (?) -- not used here // 45 20 7x => M20 if(rxp==2) { // Update: change type only if valid type information in received data if(dataptr[0]==0x45 && dataptr[1]==0x20) { isM20 = true; } else if(/*dataptr[0]==0x64 &&*/ dataptr[1]==0x9F) { isM20 = false; } } if(isM20) { if(rxp>=M20_FRAMELEN) { rxsearching = true; haveNewFrame = decodeframeM20(dataptr); } } else { if(rxp>=M10_FRAMELEN) { rxsearching = true; haveNewFrame = decodeframeM10(dataptr); } } } } } } int M10M20::receive() { unsigned long t0 = millis(); Serial.printf("M10M20::receive() start at %ld\n",t0); while( millis() - t0 < 1100 ) { uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2); if ( bitRead(value, 7) ) { Serial.println("FIFO full"); } if ( bitRead(value, 4) ) { Serial.println("FIFO overflow"); } if ( bitRead(value, 2) == 1 ) { Serial.println("FIFO: ready()"); sx1278.clearIRQFlags(); } if(bitRead(value, 6) == 0) { // while FIFO not empty byte data = sx1278.readRegister(REG_FIFO); //Serial.printf("%02x:",data); processM10data(data); value = sx1278.readRegister(REG_IRQ_FLAGS2); } else { if(headerDetected) { t0 = millis(); // restart timer... don't time out if header detected... headerDetected = 0; } if(haveNewFrame) { Serial.printf("M10M20::receive(): new frame complete after %ldms\n", millis()-t0); printRaw(dataptr, M10_FRAMELEN); int retval = haveNewFrame==1 ? RX_OK: RX_ERROR; haveNewFrame = 0; return retval; } delay(2); } } int32_t afc = sx1278.getAFC(); int16_t rssi = sx1278.getRSSI(); Serial.printf("receive: AFC is %d, RSSI is %.1f\n", afc, rssi/2.0); Serial.printf("M10M20::receive() timed out\n"); return RX_TIMEOUT; // TODO RX_OK; } #define M10MAXLEN (240) int M10M20::waitRXcomplete() { return 0; } // ret: 1=frame ok; 2=frame with errors; 0=ignored frame (m20dop-alternativ) int M10M20::decodeframeM20(uint8_t *data) { int repairstep = 16; int frl; int repl = 0; bool crcok = false; bool crcbok = false; //SondeInfo *si = sonde.si(); SondeData *si = &(sonde.si()->d); // error correction, inspired by oe5dxl's sondeudp // check first block uint8_t s[200]; s[0] = 0x16; for(int i=1; i<=M20_CRCPOSB-1; i++) { s[i] = data[i+1]; } crcbok = (crc_M10M20(M20_CRCPOSB-1, s) == ((data[M20_CRCPOSB] << 8) | data[M20_CRCPOSB+1])); frl = data[0] + 1; // frame len? (0x45+1 => 70) if(frl>M20_FRAMELEN) { frl = M20_FRAMELEN; } do { crcok = checkM10M20crc(frl-2, data); if(crcok || repairstep == 0) break; repl = 0; for(int i=crcbok?M20_CRCPOSB+2:0; i=repairstep) ) { repl++; data[i] = fixbytes[i]; } } repairstep >>= 1; } while(true); if(crcbok) { int oklen = crcok ? frl-2 : 21; for(int i=0; itypestr, "M20 ", 5); si->subtype = 2; // subtype 2: M20 // getid... // TODO: Adjust ID calculation and serial number reconstruction char ids[11]={'M','E','0','0','0','0','0','0','0','0','0'}; ids[0] = 'M'; ids[1] = 'E'; uint32_t id = data[18]; // getint16(data+18); ids[2] = hex(id/16); ids[3] = hex(id); // id = getint16_r(data+19)/4; ids[4] = (char)((id/10000)%10+48); ids[5] = (char)((id/1000)%10+48); ids[6] = (char)((id/100)%10+48); ids[7] = (char)((id/10)%10+48); ids[8] = (char)(id%10+48); ids[9] = 0; strncpy(si->id, ids, 10); // Serial: AAB-C-DDEEE char *ser = si->ser; uint8_t tmp = data[18] & 0x7F; ser[0] = (tmp/12) + '0'; ser[1] = ((tmp%12 + 1) / 10 ) + '0'; ser[2] = ((tmp%12 + 1) % 10 ) + '0'; ser[3] = '-'; ser[4] = ((data[19]&0x03)<<1) + (data[18]/128) + 1 + '0'; ser[5] = '-'; ser[6] = ids[4]; ser[7] = ids[5]; ser[8] = ids[6]; ser[9] = ids[7]; ser[10] = ids[8]; ser[11] = 0; // TODO if(crcok) { si->validID = true; //Serial.printf("ID is %s [%02x %02x %d]\n", ids, data[95], data[93], id); // ID printed on sonde is ...-.-abbbb, with a=id>>13, bbbb=id&0x1fff in decimal // position data // 0x1C 4 byte si->lat = getint32(data+28) * 1e-6; //0x20 4 byte si->lon = getint32(data+32) * 1e-6; //0x08 3 byte si->alt = getint24(data+8) * VMUL_M20; //0x0B 2 byte //VMUL_M20 specific float ve = getint16(data+11)*VMUL_M20; //0x0D 2 byte float vn = getint16(data+13)*VMUL_M20; //0x18 2 byte si->vs = getint16(data+24) * VMUL_M20; si->hs = sqrt(ve*ve+vn*vn); float dir = atan2(ve, vn)*(1.0/RAD); if(dir<0) dir+=360; si->dir = dir; si->validPos = 0x3f; //0x0F 3 byte uint32_t tow = getint24(data+15); uint16_t week = getint16(data+26); si->time = (tow+week*604800+315964800)-18; si->vframe =si->time - 315964800; si->validTime = true; } return crcok?1:2; } M10M20 m10m20 = M10M20();