X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=src%2Fdrivers%2Fao_aprs.c;h=8a1b6a4df2763e1e74ec9f61a082dc012f0da082;hb=a21dbb05fad2625d17bc2302faa96dc295e6ed7c;hp=1a074ba516fed78c691d3fc01d629cc60e8cf754;hpb=51ef826372f466f44901c4c609ed6a987d30fda4;p=fw%2Faltos diff --git a/src/drivers/ao_aprs.c b/src/drivers/ao_aprs.c index 1a074ba5..8a1b6a4d 100644 --- a/src/drivers/ao_aprs.c +++ b/src/drivers/ao_aprs.c @@ -145,11 +145,6 @@ #include -typedef int bool_t; -typedef int32_t int32; -#define false 0 -#define true 1 - // Public methods, constants, and data structures for each class. static void timeInit(void); @@ -258,12 +253,31 @@ typedef enum /// AX.25 compliant packet header that contains destination, station call sign, and path. /// 0x76 for SSID-11, 0x78 for SSID-12 -static const uint8_t TNC_AX25_HEADER[] = { - 'A' << 1, 'P' << 1, 'A' << 1, 'M' << 1, ' ' << 1, ' ' << 1, 0x60, \ - 'K' << 1, 'D' << 1, '7' << 1, 'S' << 1, 'Q' << 1, 'G' << 1, 0x78, \ - 'W' << 1, 'I' << 1, 'D' << 1, 'E' << 1, '2' << 1, ' ' << 1, 0x65, \ +static uint8_t TNC_AX25_HEADER[] = { + 'A' << 1, 'P' << 1, 'A' << 1, 'M' << 1, ' ' << 1, ' ' << 1, 0x60, + 'N' << 1, '0' << 1, 'C' << 1, 'A' << 1, 'L' << 1, 'L' << 1, 0x78, + 'W' << 1, 'I' << 1, 'D' << 1, 'E' << 1, '2' << 1, ' ' << 1, 0x65, 0x03, 0xf0 }; +#define TNC_CALLSIGN_OFF 7 +#define TNC_CALLSIGN_LEN 6 + +static void +tncSetCallsign(void) +{ +#ifndef AO_APRS_TEST + uint8_t i; + + for (i = 0; i < TNC_CALLSIGN_LEN; i++) { + if (!ao_config.callsign[i]) + break; + TNC_AX25_HEADER[TNC_CALLSIGN_OFF + i] = ao_config.callsign[i] << 1; + } + for (; i < TNC_CALLSIGN_LEN; i++) + TNC_AX25_HEADER[TNC_CALLSIGN_OFF + i] = ' ' << 1; +#endif +} + /// The next bit to transmit. static uint8_t tncTxBit; @@ -285,9 +299,6 @@ static uint8_t tncLength; /// A copy of the last 5 bits we've transmitted to determine if we need to bit stuff on the next bit. static uint8_t tncBitStuff; -/// Pointer to TNC buffer as we save each byte during message preparation. -static uint8_t *tncBufferPnt; - /// Buffer to hold the message portion of the AX.25 packet as we prepare it. static uint8_t tncBuffer[TNC_BUFFER_SIZE]; @@ -468,54 +479,262 @@ static void tnc1200TimerTick() } // END switch } +static void tncCompressInt(uint8_t *dest, int32_t value, int len) { + int i; + for (i = len - 1; i >= 0; i--) { + dest[i] = value % 91 + 33; + value /= 91; + } +} + +static int ao_num_sats(void) +{ + int i; + int n = 0; + + for (i = 0; i < ao_gps_tracking_data.channels; i++) { + if (ao_gps_tracking_data.sats[i].svid) + n++; + } + return n; +} + +static char ao_gps_locked(void) +{ + if (ao_gps_data.flags & AO_GPS_VALID) + return 'L'; + else + return 'U'; +} + +static int tncComment(uint8_t *buf) +{ +#if HAS_ADC + struct ao_data packet; + + ao_arch_critical(ao_data_get(&packet);); + + int16_t battery = ao_battery_decivolt(packet.adc.v_batt); +#ifdef AO_SENSE_DROGUE + int16_t apogee = ao_ignite_decivolt(AO_SENSE_DROGUE(&packet)); +#endif +#ifdef AO_SENSE_MAIN + int16_t main = ao_ignite_decivolt(AO_SENSE_MAIN(&packet)); +#endif + + return sprintf((char *) buf, + "%c%d B%d.%d" +#ifdef AO_SENSE_DROGUE + " A%d.%d" +#endif +#ifdef AO_SENSE_MAIN + " M%d.%d" +#endif + , ao_gps_locked(), + ao_num_sats(), + battery/10, + battery % 10 +#ifdef AO_SENSE_DROGUE + , apogee/10, + apogee%10 +#endif +#ifdef AO_SENSE_MAIN + , main/10, + main%10 +#endif + ); +#else + return sprintf((char *) buf, + "%c%d", + ao_gps_locked(), + ao_num_sats()); +#endif +} + +/* + * APRS use a log encoding of altitude with a base of 1.002, such that + * + * feet = 1.002 ** encoded_altitude + * + * meters = (1.002 ** encoded_altitude) * 0.3048 + * + * log2(meters) = log2(1.002 ** encoded_altitude) + log2(0.3048) + * + * log2(meters) = encoded_altitude * log2(1.002) + log2(0.3048) + * + * encoded_altitude = (log2(meters) - log2(0.3048)) / log2(1.002) + * + * encoded_altitude = (log2(meters) + log2(1/0.3048)) * (1/log2(1.002)) + * + * We need 9 bits of mantissa to hold 1/log2(1.002) (~ 347), which leaves us + * 23 bits of fraction. That turns out to be *just* enough to avoid any + * errors in the result (cool, huh?). + */ + +#define fixed23_int(x) ((uint32_t) ((x) << 23)) +#define fixed23_one fixed23_int(1) +#define fixed23_two fixed23_int(2) +#define fixed23_half (fixed23_one >> 1) +#define fixed23_floor(x) ((x) >> 23) +#define fixed23_real(x) ((uint32_t) ((x) * fixed23_one + 0.5)) + +static inline uint64_t +fixed23_mul(uint32_t x, uint32_t y) +{ + return ((uint64_t) x * y + fixed23_half) >> 23; +} + +/* + * Use 30 fraction bits for the altitude. We need two bits at the + * top as we need to handle x, where 0 <= x < 4. We don't + * need 30 bits, but it's actually easier this way as we normalize + * the incoming value to 1 <= x < 2, and having the integer portion + * way up high means we don't have to deal with shifting in both + * directions to cover from 0 to 2**30-1. + */ + +#define fixed30_int(x) ((uint32_t) ((x) << 30)) +#define fixed30_one fixed30_int(1) +#define fixed30_half (fixed30_one >> 1) +#define fixed30_two fixed30_int(2) + +static inline uint32_t +fixed30_mul(uint32_t x, uint32_t y) +{ + return ((uint64_t) x * y + fixed30_half) >> 30; +} + +/* + * Fixed point log2. Takes integer argument, returns + * fixed point result with 23 bits of fraction + */ + +static uint32_t +ao_fixed_log2(uint32_t x) +{ + uint32_t result; + uint32_t frac = fixed23_one; + + /* Bounds check for sanity */ + if (x <= 0) + return 0; + + if (x >= fixed30_one) + return 0xffffffff; + + /* + * Normalize and compute integer log portion + * + * This makes 1 <= x < 2, and computes result to be + * the integer portion of the log2 of x + */ + + for (result = fixed23_int(30); x < fixed30_one; result -= fixed23_one, x <<= 1) + ; + + /* + * Given x, find y and n such that: + * + * x = y * 2**n 1 <= y < 2 + * + * That means: + * + * lb(x) = n + lb(y) + * + * Now, repeatedly square y to find find z and m such that: + * + * z = y ** (2**m) 2 <= z < 4 + * + * This is possible because 1 <= y < 2 + * + * lb(y) = lb(z) / 2**m + * + * (1 + lb(z/2)) + * = ------------- + * 2**m + * + * = 2**-m + 2**-m * lb(z/2) + * + * Note that if 2 <= z < 4, then 1 <= (z/2) < 2, so we can + * iterate to find lb(z/2) + * + * In this implementation, we don't care about the 'm' value, + * instead we only care about 2**-m, which we store in 'frac' + */ + + while (frac != 0 && x != fixed30_one) { + /* Repeatedly square x until 2 <= x < 4 */ + while (x < fixed30_two) { + x = fixed30_mul(x, x); + + /* Divide the fractional result bit by 2 */ + frac >>= 1; + } + + /* Add in this result bit */ + result |= frac; + + /* Make 1 <= x < 2 again and iterate */ + x >>= 1; + } + return result; +} + +#define APRS_LOG_CONVERT fixed23_real(1.714065192056127) +#define APRS_LOG_BASE fixed23_real(346.920048461100941) + +static int +ao_aprs_encode_altitude(int meters) +{ + return fixed23_floor(fixed23_mul(ao_fixed_log2(meters) + APRS_LOG_CONVERT, APRS_LOG_BASE) + fixed23_half); +} + /** * Generate the plain text position packet. */ -static void tncPositionPacket(void) +static int tncPositionPacket(void) { - int32_t latitude = 45.4694766 * 10000000; - int32_t longitude = -122.7376250 * 10000000; - uint32_t altitude = 10000; - uint16_t lat_deg; - uint16_t lon_deg; - uint16_t lat_min; - uint16_t lat_frac; - uint16_t lon_min; - uint16_t lon_frac; - int c; - - char lat_sign = 'N', lon_sign = 'E'; - - if (latitude < 0) { - lat_sign = 'S'; - latitude = -latitude; + static int32_t latitude; + static int32_t longitude; + static int32_t altitude; + int32_t lat, lon, alt; + uint8_t *buf; + + if (ao_gps_data.flags & AO_GPS_VALID) { + latitude = ao_gps_data.latitude; + longitude = ao_gps_data.longitude; + altitude = ao_gps_data.altitude; + if (altitude < 0) + altitude = 0; } - if (longitude < 0) { - lon_sign = 'W'; - longitude = -longitude; - } + buf = tncBuffer; + *buf++ = '!'; + + /* Symbol table ID */ + *buf++ = '/'; + + lat = ((uint64_t) 380926 * (900000000 - latitude)) / 10000000; + lon = ((uint64_t) 190463 * (1800000000 + longitude)) / 10000000; - lat_deg = latitude / 10000000; - latitude -= lat_deg * 10000000; - latitude *= 60; - lat_min = latitude / 10000000; - latitude -= lat_min * 10000000; - lat_frac = (latitude + 50000) / 100000; - - lon_deg = longitude / 10000000; - longitude -= lon_deg * 10000000; - longitude *= 60; - lon_min = longitude / 10000000; - longitude -= lon_min * 10000000; - lon_frac = (longitude + 50000) / 100000; - - c = sprintf ((char *) tncBufferPnt, "=%02u%02u.%02u%c\\%03u%02u.%02u%cO /A=%06u\015", - lat_deg, lat_min, lat_frac, lat_sign, - lon_deg, lon_min, lon_frac, lon_sign, - altitude * 100 / 3048); - tncBufferPnt += c; - tncLength += c; + alt = ao_aprs_encode_altitude(altitude); + + tncCompressInt(buf, lat, 4); + buf += 4; + tncCompressInt(buf, lon, 4); + buf += 4; + + /* Symbol code */ + *buf++ = '\''; + + tncCompressInt(buf, alt, 2); + buf += 2; + + *buf++ = 33 + ((1 << 5) | (2 << 3)); + + buf += tncComment(buf); + + return buf - tncBuffer; } static int16_t @@ -552,25 +771,17 @@ void ao_aprs_send(void) timeInit(); tncInit(); + tncSetCallsign(); - // Set a pointer to our TNC output buffer. - tncBufferPnt = tncBuffer; - - // Set the message length counter. - tncLength = 0; - - tncPositionPacket(); + tncLength = tncPositionPacket(); // Calculate the CRC for the header and message. crc = sysCRC16(TNC_AX25_HEADER, sizeof(TNC_AX25_HEADER), 0xffff); crc = sysCRC16(tncBuffer, tncLength, crc ^ 0xffff); // Save the CRC in the message. - *tncBufferPnt++ = crc & 0xff; - *tncBufferPnt = (crc >> 8) & 0xff; - - // Update the length to include the CRC bytes. - tncLength += 2; + tncBuffer[tncLength++] = crc & 0xff; + tncBuffer[tncLength++] = (crc >> 8) & 0xff; // Prepare the variables that are used in the real-time clock interrupt. tncBitCount = 0; @@ -579,7 +790,7 @@ void ao_aprs_send(void) tncIndex = 0; tncMode = TNC_TX_SYNC; - ao_radio_send_lots(tncFill); + ao_radio_send_aprs(tncFill); } /** @} */