From e79e98fbd88d9b094008f3e49297839a44dd4546 Mon Sep 17 00:00:00 2001 From: Manolis Surligas Date: Fri, 29 Apr 2016 21:43:09 +0300 Subject: [PATCH] AX.25 support at the UPSAT FSK encoder/decoder --- grc/satnogs_upsat_fsk_frame_encoder.xml | 26 ++- include/satnogs/ax25.h | 118 ++++++++++++- include/satnogs/upsat_fsk_frame_encoder.h | 20 ++- lib/upsat_fsk_frame_acquisition_impl.cc | 45 +++++ lib/upsat_fsk_frame_acquisition_impl.h | 4 + lib/upsat_fsk_frame_encoder_impl.cc | 195 +++++++++++++++++++--- lib/upsat_fsk_frame_encoder_impl.h | 14 ++ 7 files changed, 386 insertions(+), 36 deletions(-) diff --git a/grc/satnogs_upsat_fsk_frame_encoder.xml b/grc/satnogs_upsat_fsk_frame_encoder.xml index 9d5369c..1fb7b5d 100644 --- a/grc/satnogs_upsat_fsk_frame_encoder.xml +++ b/grc/satnogs_upsat_fsk_frame_encoder.xml @@ -4,7 +4,7 @@ satnogs_upsat_fsk_frame_encoder satnogs import satnogs - satnogs.upsat_fsk_frame_encoder($preamble, $sync_word, $append_crc, $whitening, $manchester, $msb_first, $ax_25, $settling_samples) + satnogs.upsat_fsk_frame_encoder($preamble, $sync_word, $append_crc, $whitening, $manchester, $msb_first, $ax_25, $src_addr, $src_ssid, $dest_addr, $dest_ssid, $settling_samples) Frame Preamble @@ -88,6 +88,30 @@ + + Destination Callsign + dest_addr + string + + + + Destination SSID + dest_ssid + int + + + + Source Callsign + src_addr + string + + + + Source SSID + src_ssid + int + + Number of zero settling samples settling_samples diff --git a/include/satnogs/ax25.h b/include/satnogs/ax25.h index e0c3f3a..19f3b1e 100644 --- a/include/satnogs/ax25.h +++ b/include/satnogs/ax25.h @@ -22,6 +22,8 @@ #define INCLUDE_SATNOGS_AX25_H_ #include +#include +#include namespace gr { @@ -44,7 +46,8 @@ namespace gr { AX25_I_FRAME, //!< AX25_I_FRAME Information frame AX25_S_FRAME, //!< AX25_S_FRAME Supervisory frame - AX25_U_FRAME //!< AX25_U_FRAME Unnumbered frame + AX25_U_FRAME, //!< AX25_U_FRAME Unnumbered frame + AX25_UI_FRAME /**!< AX25_UI_FRAME Unnumbered information frame */ } ax25_frame_type_t; typedef enum @@ -53,6 +56,12 @@ namespace gr AX25_ENC_OK } ax25_encode_status_t; + typedef enum + { + AX25_DEC_FAIL, + AX25_DEC_OK + } ax25_decode_status_t; + typedef struct { uint8_t address[AX25_MAX_ADDR_LEN]; @@ -60,7 +69,8 @@ namespace gr uint16_t ctrl; size_t ctrl_len; uint8_t pid; - uint8_t info[AX25_MAX_FRAME_LEN]; + uint8_t *info; + size_t info_len; ax25_frame_type_t type; } ax25_frame_t; @@ -75,7 +85,7 @@ namespace gr { uint16_t fcs = 0xFFFF; while (len--) { - fcs = (fcs >> 8) ^ crc16_ccitt_table_reverse[(fcs ^ *buffer++) & 0XFF]; + fcs = (fcs >> 8) ^ crc16_ccitt_table_reverse[(fcs ^ *buffer++) & 0xFF]; } return fcs ^ 0xFFFF; } @@ -146,7 +156,7 @@ namespace gr if( ctrl_len == AX25_MIN_CTRL_LEN || ctrl_len == AX25_MAX_CTRL_LEN){ memcpy(out + i, &ctrl, ctrl_len); - i += addr_len; + i += ctrl_len; } else{ return 0; @@ -157,7 +167,7 @@ namespace gr * FIXME: For now, only the "No layer 3 is implemented" information is * inserted */ - if(type == AX25_I_FRAME){ + if (type == AX25_I_FRAME || type == AX25_UI_FRAME) { out[i++] = 0xF0; } memcpy(out + i, info, info_len); @@ -315,6 +325,104 @@ namespace gr return AX25_ENC_OK; } + static inline ax25_decode_status_t + ax25_decode (uint8_t *out, size_t *out_len, + const uint8_t *ax25_frame, size_t len) + { + size_t i; + size_t frame_start = UINT_MAX; + size_t frame_stop = UINT_MAX; + uint8_t res; + size_t cont_1 = 0; + size_t received_bytes = 0; + size_t bit_cnt = 0; + uint8_t decoded_byte = 0x0; + uint16_t fcs; + uint16_t recv_fcs; + + + /* Start searching for the SYNC flag */ + for(i = 0; i < len - sizeof(AX25_SYNC_FLAG_MAP_BIN); i++) { + res = (AX25_SYNC_FLAG_MAP_BIN[0] ^ ax25_frame[i]) | + (AX25_SYNC_FLAG_MAP_BIN[1] ^ ax25_frame[i + 1]) | + (AX25_SYNC_FLAG_MAP_BIN[2] ^ ax25_frame[i + 2]) | + (AX25_SYNC_FLAG_MAP_BIN[3] ^ ax25_frame[i + 3]) | + (AX25_SYNC_FLAG_MAP_BIN[4] ^ ax25_frame[i + 4]) | + (AX25_SYNC_FLAG_MAP_BIN[5] ^ ax25_frame[i + 5]) | + (AX25_SYNC_FLAG_MAP_BIN[6] ^ ax25_frame[i + 6]) | + (AX25_SYNC_FLAG_MAP_BIN[7] ^ ax25_frame[i + 7]); + /* Found it! */ + if(res == 0){ + frame_start = i; + break; + } + } + + /* We failed to find the SYNC flag */ + if(frame_start == UINT_MAX){ + return AX25_DEC_FAIL; + } + + for(i = frame_start + sizeof(AX25_SYNC_FLAG_MAP_BIN); + i < len - sizeof(AX25_SYNC_FLAG_MAP_BIN); i++) { + /* Check if we reached the frame end */ + res = (AX25_SYNC_FLAG_MAP_BIN[0] ^ ax25_frame[i]) | + (AX25_SYNC_FLAG_MAP_BIN[1] ^ ax25_frame[i + 1]) | + (AX25_SYNC_FLAG_MAP_BIN[2] ^ ax25_frame[i + 2]) | + (AX25_SYNC_FLAG_MAP_BIN[3] ^ ax25_frame[i + 3]) | + (AX25_SYNC_FLAG_MAP_BIN[4] ^ ax25_frame[i + 4]) | + (AX25_SYNC_FLAG_MAP_BIN[5] ^ ax25_frame[i + 5]) | + (AX25_SYNC_FLAG_MAP_BIN[6] ^ ax25_frame[i + 6]) | + (AX25_SYNC_FLAG_MAP_BIN[7] ^ ax25_frame[i + 7]); + /* Found it! */ + if(res == 0){ + frame_stop = i; + break; + } + + if (ax25_frame[i]) { + cont_1++; + decoded_byte |= 1 << bit_cnt; + bit_cnt++; + } + else { + /* If 5 consecutive 1's drop the extra zero*/ + if (cont_1 >= 5) { + cont_1 = 0; + } + else{ + bit_cnt++; + cont_1 = 0; + } + } + + /* Fill the fully constructed byte */ + if(bit_cnt == 8){ + out[received_bytes++] = decoded_byte; + bit_cnt = 0; + decoded_byte = 0x0; + } + } + + if(frame_stop == UINT_MAX || received_bytes < AX25_MIN_ADDR_LEN){ + return AX25_DEC_FAIL; + } + + /* Now check the CRC */ + fcs = ax25_fcs (out, received_bytes - sizeof(uint16_t)); + recv_fcs = (((uint16_t) out[received_bytes - 2]) << 8) + | out[received_bytes - 1]; + + if(fcs != recv_fcs) { + LOG_WARN("AX.25 CRC-16 failed"); + return AX25_DEC_FAIL; + } + + *out_len = received_bytes - sizeof(uint16_t); + return AX25_DEC_OK; + + } + } // namespace satnogs } // namespace gr diff --git a/include/satnogs/upsat_fsk_frame_encoder.h b/include/satnogs/upsat_fsk_frame_encoder.h index 560359c..83327c4 100644 --- a/include/satnogs/upsat_fsk_frame_encoder.h +++ b/include/satnogs/upsat_fsk_frame_encoder.h @@ -63,7 +63,10 @@ namespace gr * the Manchester algorithm of the CC1120 chip. False otherwise. * * @param msb_first if set to true, the the treansmission starts from the - * MSB of each byte. + * MSB of each byte. In case the AX.25 encapuslation is selected, this + * parameter is NOT taken into consideration for the AX.25 part, + * as the AX.25 dictates that the LS bit should be sent first. + * * @param ax25_format if set to true the frame payload will be encoded * using AX.25 encapsulation. * @@ -78,11 +81,16 @@ namespace gr static sptr make (const std::vector& preamble, const std::vector& sync_word, - bool append_crc = true, - bool whitening = false, bool manchester = false, - bool msb_first = true, - bool ax25_format = false, - size_t settling_samples = 64); + bool append_crc, + bool whitening, + bool manchester, + bool msb_first, + bool ax25_format, + const std::string& ax25_dest_addr, + uint8_t ax25_dest_ssid, + const std::string& ax25_src_addr, + uint8_t ax25_src_ssid, + size_t settling_samples); }; } // namespace satnogs diff --git a/lib/upsat_fsk_frame_acquisition_impl.cc b/lib/upsat_fsk_frame_acquisition_impl.cc index 229dc3a..29f84a1 100644 --- a/lib/upsat_fsk_frame_acquisition_impl.cc +++ b/lib/upsat_fsk_frame_acquisition_impl.cc @@ -25,6 +25,7 @@ #include #include "upsat_fsk_frame_acquisition_impl.h" #include +#include #include #include @@ -97,6 +98,8 @@ namespace gr } d_pdu = new uint8_t[UPSAT_MAX_FRAME_LEN]; + d_ax25_tmp_buf = new uint8_t[2 * UPSAT_MAX_FRAME_LEN * 8]; + d_ax25_buf = new uint8_t[2 * UPSAT_MAX_FRAME_LEN]; } /* @@ -105,6 +108,8 @@ namespace gr upsat_fsk_frame_acquisition_impl::~upsat_fsk_frame_acquisition_impl () { delete[] d_pdu; + delete[] d_ax25_tmp_buf; + delete[] d_ax25_buf; } inline void @@ -173,6 +178,23 @@ namespace gr d_decoded_bits = 0; } + inline void + upsat_fsk_frame_acquisition_impl::unpack_ax25_bytes (size_t len_bytes) + { + size_t i; + uint8_t *in = d_pdu + 1; + for(i = 0; i < len_bytes; i++){ + d_ax25_tmp_buf[8*i] = (in[i] >> 7) & 0x1; + d_ax25_tmp_buf[8*i + 1] = (in[i] >> 6) & 0x1; + d_ax25_tmp_buf[8*i + 2] = (in[i] >> 5) & 0x1; + d_ax25_tmp_buf[8*i + 3] = (in[i] >> 4) & 0x1; + d_ax25_tmp_buf[8*i + 4] = (in[i] >> 3) & 0x1; + d_ax25_tmp_buf[8*i + 5] = (in[i] >> 2) & 0x1; + d_ax25_tmp_buf[8*i + 6] = (in[i] >> 1) & 0x1; + d_ax25_tmp_buf[8*i + 7] = in[i] & 0x1; + } + } + int upsat_fsk_frame_acquisition_impl::work ( int noutput_items, gr_vector_const_void_star &input_items, @@ -181,6 +203,8 @@ namespace gr int i; uint16_t crc_received; uint16_t crc_calc; + size_t ax25_frame_len = 0; + ax25_decode_status_t status; const float *in = (const float *) input_items[0]; for (i = 0; i < noutput_items; i++) { slice_and_shift (in[i]); @@ -273,9 +297,30 @@ namespace gr d_decoded_bytes++; if (d_decoded_bytes == d_frame_len) { + if(d_is_ax25) { + unpack_ax25_bytes(d_frame_len - 1); + status = ax25_decode(d_ax25_buf, &ax25_frame_len, + d_ax25_tmp_buf, (d_frame_len - 1)*8); + if(status == AX25_DEC_OK){ + message_port_pub (pmt::mp ("pdu"), + pmt::make_blob(d_ax25_buf, ax25_frame_len)); + } + else{ + LOG_WARN("AX.25 decoding failed."); + } + + /* + * We are done here. Whitening and FSK CRC is not supported + * when transmitting/receiving AX.25 frames + */ + reset_state (); + break; + } + if(d_whitening){ d_descrambler.descramble(d_pdu+1, d_pdu+1, d_frame_len - 1); } + if(!d_check_crc){ message_port_pub ( pmt::mp ("pdu"), diff --git a/lib/upsat_fsk_frame_acquisition_impl.h b/lib/upsat_fsk_frame_acquisition_impl.h index def126c..58da2fe 100644 --- a/lib/upsat_fsk_frame_acquisition_impl.h +++ b/lib/upsat_fsk_frame_acquisition_impl.h @@ -63,6 +63,8 @@ namespace gr size_t d_frame_len; whitening d_descrambler; uint8_t *d_pdu; + uint8_t *d_ax25_tmp_buf; + uint8_t *d_ax25_buf; inline void slice_and_shift (float in); @@ -79,6 +81,8 @@ namespace gr have_frame_len (); inline void have_payload (); + inline void + unpack_ax25_bytes(size_t len_bytes); public: upsat_fsk_frame_acquisition_impl (const std::vector &preamble, diff --git a/lib/upsat_fsk_frame_encoder_impl.cc b/lib/upsat_fsk_frame_encoder_impl.cc index d03a748..d9608b2 100644 --- a/lib/upsat_fsk_frame_encoder_impl.cc +++ b/lib/upsat_fsk_frame_encoder_impl.cc @@ -37,9 +37,12 @@ namespace gr upsat_fsk_frame_encoder::make (const std::vector& preamble, const std::vector& sync_word, bool append_crc, bool whitening, - bool manchester, - bool msb_first, + bool manchester, bool msb_first, bool ax25_format, + const std::string& ax25_dest_addr, + uint8_t ax25_dest_ssid, + const std::string& ax25_src_addr, + uint8_t ax25_src_ssid, size_t settling_samples) { return gnuradio::get_initial_sptr ( @@ -47,6 +50,8 @@ namespace gr append_crc, whitening, manchester, msb_first, ax25_format, + ax25_src_addr, ax25_src_ssid, + ax25_dest_addr, ax25_dest_ssid, settling_samples)); } @@ -59,6 +64,10 @@ namespace gr bool append_crc, bool whitening, bool manchester, bool msb_first, bool ax25_format, + const std::string& ax25_dest_addr, + uint8_t ax25_dest_ssid, + const std::string& ax25_src_addr, + uint8_t ax25_src_ssid, size_t settling_samples) : gr::sync_block ("upsat_fsk_frame_encoder", gr::io_signature::make (0, 0, 0), @@ -91,6 +100,18 @@ namespace gr * User def. User def. 1B 1-255 B 2 B */ d_pdu = new uint8_t[d_max_pdu_len]; + + + if(d_is_ax25){ + d_ax25_addr = new uint8_t[AX25_MAX_ADDR_LEN]; + /* One useful bit per byte for the AX.25 buffer */ + d_ax25_pdu = new uint8_t[2 * d_max_pdu_len * 8]; + d_ax25_tmp_buf = new uint8_t[d_max_pdu_len * 2]; + d_ax25_addr_len = ax25_create_addr_field (d_ax25_addr, ax25_dest_addr, + ax25_dest_ssid, ax25_src_addr, + ax25_src_ssid); + } + d_pdu_encoded = new float[d_max_pdu_len*8 + d_settling_samples]; /* Copy the preamble at the start of the pdu */ @@ -105,6 +126,11 @@ namespace gr { delete [] d_pdu; delete [] d_pdu_encoded; + if(d_is_ax25) { + delete [] d_ax25_addr; + delete [] d_ax25_pdu; + delete [] d_ax25_tmp_buf; + } } inline void @@ -163,25 +189,23 @@ namespace gr add_item_tag (0, item, eob_key, value, srcid); } - int - upsat_fsk_frame_encoder_impl::work (int noutput_items, - gr_vector_const_void_star &input_items, - gr_vector_void_star &output_items) + inline int + upsat_fsk_frame_encoder_impl::raw_frame_handling (float* out, + int noutput_items) { uint16_t crc; size_t min_available; pmt::pmt_t pdu; - float *out = (float *) output_items[0]; /* * If the whole previous frame has been successfully sent, block waiting * for a new one */ - if(d_encoded == 0){ - pdu = delete_head_blocking(pmt::mp("pdu")); + if (d_encoded == 0) { + pdu = delete_head_blocking (pmt::mp ("pdu")); d_pdu_len = pmt::blob_length (pdu); - if(d_pdu_len > UPSAT_MAX_FRAME_LEN){ + if (d_pdu_len > UPSAT_MAX_FRAME_LEN) { LOG_ERROR("PDU is greater than the supported. Dropping the PDU"); return 0; } @@ -198,21 +222,21 @@ namespace gr pmt::blob_data (pdu), d_pdu_len); /* If it is necessary calculate and append the CRC */ - if(d_append_crc) { - crc = crc16_ccitt(d_pdu + d_preamble_len + d_sync_word_len, - d_pdu_len + 1); + if (d_append_crc) { + crc = crc16_ccitt (d_pdu + d_preamble_len + d_sync_word_len, + d_pdu_len + 1); /* CRC must be transmitted MSB first */ - crc = htons(crc); - memcpy(d_pdu + d_preamble_len + d_sync_word_len + 1 + d_pdu_len, - &crc, sizeof(uint16_t)); + crc = htons (crc); + memcpy (d_pdu + d_preamble_len + d_sync_word_len + 1 + d_pdu_len, + &crc, sizeof(uint16_t)); d_pdu_len += sizeof(uint16_t); } /* * Whitening is performed on all bytes except preamble and SYNC fields */ - if(d_whitening){ - d_scrambler.reset(); + if (d_whitening) { + d_scrambler.reset (); d_scrambler.scramble (d_pdu + d_preamble_len + d_sync_word_len, d_pdu + d_preamble_len + d_sync_word_len, d_pdu_len + 1); @@ -235,28 +259,151 @@ namespace gr d_settling_samples * sizeof(float)); /* The new frame now has a bigger size of course*/ - d_pdu_len += d_settling_samples/8; + d_pdu_len += d_settling_samples / 8; /* Start of burst */ - add_sob(nitems_written(0)); + add_sob (nitems_written (0)); } noutput_items = std::max (0, noutput_items); min_available = std::min ((size_t) noutput_items, (d_pdu_len - d_encoded) * 8); - memcpy(out, d_pdu_encoded + d_encoded*8, min_available * sizeof(float)); - d_encoded += min_available/8; + memcpy (out, d_pdu_encoded + d_encoded * 8, + min_available * sizeof(float)); + d_encoded += min_available / 8; /* End of the frame reached */ - if(d_encoded == d_pdu_len){ - add_eob(nitems_written(0) + min_available - 1); + if (d_encoded == d_pdu_len) { + add_eob (nitems_written (0) + min_available - 1); d_encoded = 0; } return min_available; } + inline int + upsat_fsk_frame_encoder_impl::ax25_frame_handling (float* out, + int noutput_items) + { + uint16_t crc; + size_t min_available; + pmt::pmt_t pdu; + size_t len; + size_t encoded_len; + ax25_encode_status_t status; + size_t extra_bits; + size_t i; + + /* + * If the whole previous frame has been successfully sent, block waiting + * for a new one + */ + if (d_encoded == 0) { + /* Reset the buffer */ + memset(d_ax25_pdu, 0, 2 * d_max_pdu_len * 8); + pdu = delete_head_blocking (pmt::mp ("pdu")); + d_pdu_len = pmt::blob_length (pdu); + + if (d_pdu_len > UPSAT_MAX_FRAME_LEN) { + LOG_ERROR("PDU is greater than the supported. Dropping the PDU"); + return 0; + } + + len = ax25_prepare_frame(d_ax25_tmp_buf, (uint8_t *) pmt::blob_data (pdu), + d_pdu_len, AX25_UI_FRAME, d_ax25_addr, + d_ax25_addr_len, 0x03, 1); + status = ax25_bit_stuffing(d_ax25_pdu, &encoded_len, + d_ax25_tmp_buf, len); + if(status != AX25_ENC_OK) { + LOG_WARN("Failed to properly encode into AX.25 frame"); + return 0; + } + + /* Due to bit stuffing the resulting bit number may not be + * a multiple of 8. Thus we append proper number of zeros + * after the SYNC flag. So neither the AX.25 or the FSK + * framing are affected + */ + extra_bits = 8 - (encoded_len % 8); + encoded_len += extra_bits; + + /* + * Set the frame length at the corresponding field. + * NOTE: The frame length is calculated taking consideration only + * the address field (if exists) and the payload. Length and CRC fields + * are NOT included. + */ + d_pdu[d_preamble_len + d_sync_word_len] = (uint8_t) encoded_len; + + /* If it is necessary calculate and append the CRC */ + if (d_append_crc) { + LOG_WARN("AX.25 has its own CRC-16 field. Skipping..."); + } + + /* + * Whitening can not be applied in the AX.25 because it will alter + * the SYNC flag of the stack + */ + if (d_whitening) { + LOG_WARN("AX.25 and whitening are not compatible." + " No whitening will be performed"); + } + + d_pdu_len = d_preamble_len + d_sync_word_len + 1 + encoded_len/8; + + /* NRZ encoding the FSK preamble with the standard method */ + map_msb_first (d_pdu_encoded, (d_preamble_len + d_sync_word_len + 1) * 8); + + /* NRZ encode the AX.25 part of the FSK frame */ + for(i = 0; i < encoded_len; i++){ + d_pdu_encoded[i + (d_preamble_len + d_sync_word_len + 1)*8] + = (d_ax25_pdu[i] * 2.0f) - 1.0f; + } + + /* Reset the settling trailing samples */ + memset (d_pdu_encoded + d_pdu_len * 8, 0, + d_settling_samples * sizeof(float)); + + /* The new frame now has a bigger size of course*/ + d_pdu_len += d_settling_samples / 8; + + /* Start of burst */ + add_sob (nitems_written (0)); + } + + noutput_items = std::max (0, noutput_items); + min_available = std::min ((size_t) noutput_items, + (d_pdu_len - d_encoded) * 8); + + memcpy (out, d_pdu_encoded + d_encoded * 8, + min_available * sizeof(float)); + d_encoded += min_available / 8; + + /* End of the frame reached */ + if (d_encoded == d_pdu_len) { + add_eob (nitems_written (0) + min_available - 1); + d_encoded = 0; + } + + return min_available; + } + + int + upsat_fsk_frame_encoder_impl::work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) + { + float *out = (float *) output_items[0]; + + if(d_is_ax25) { + return ax25_frame_handling(out, noutput_items); + } + else{ + return raw_frame_handling(out, noutput_items); + } + } + } /* namespace satnogs */ } /* namespace gr */ diff --git a/lib/upsat_fsk_frame_encoder_impl.h b/lib/upsat_fsk_frame_encoder_impl.h index 34f3edb..25cb8fa 100644 --- a/lib/upsat_fsk_frame_encoder_impl.h +++ b/lib/upsat_fsk_frame_encoder_impl.h @@ -23,6 +23,7 @@ #include #include +#include namespace gr { @@ -47,6 +48,10 @@ namespace gr size_t d_pdu_len; whitening d_scrambler; uint8_t *d_pdu; + uint8_t *d_ax25_pdu; + uint8_t *d_ax25_tmp_buf; + uint8_t *d_ax25_addr; + size_t d_ax25_addr_len; float *d_pdu_encoded; inline void @@ -58,6 +63,11 @@ namespace gr inline void add_eob (uint64_t item); + inline int + raw_frame_handling(float *out, int noutput_items); + inline int + ax25_frame_handling(float *out, int noutput_items); + public: upsat_fsk_frame_encoder_impl (const std::vector& preamble, const std::vector& sync_word, @@ -65,6 +75,10 @@ namespace gr bool manchester, bool msb_first, bool ax25_format, + const std::string& ax25_dest_addr, + uint8_t ax25_dest_ssid, + const std::string& ax25_src_addr, + uint8_t ax25_src_ssid, size_t settling_samples); ~upsat_fsk_frame_encoder_impl ();