├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── audio_stream_writer.py ├── fsk ├── __init__.py ├── demodulator.h ├── encoder.py ├── packet_decoder.cc └── packet_decoder.h └── qpsk ├── __init__.py ├── demodulator.cc ├── demodulator.h ├── encoder.py ├── packet_decoder.cc └── packet_decoder.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Precompiled python modules 2 | *.pyc 3 | 4 | # LaTeX compiled file 5 | *.pdf 6 | *.log 7 | *.aux 8 | 9 | # OS X crap 10 | .DS_Stor? 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Except when noted otherwise, all code is copyright Emilie Gillet and is 2 | released under the MIT License with the following notice: 3 | 4 | Copyright 2013 Emilie Gillet. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FSK/QPSK Bootloaders for STM32F. 2 | See LICENSE for licensing information. -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichenettes/stm-audio-bootloader/b376bd644871edc06f9ee75fb95be516a10ab394/__init__.py -------------------------------------------------------------------------------- /audio_stream_writer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.5 2 | # 3 | # Copyright 2013 Emilie Gillet. 4 | # 5 | # Author: Emilie Gillet (emilie.o.gillet@gmail.com) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | # See http://creativecommons.org/licenses/MIT/ for more information. 26 | # 27 | # ----------------------------------------------------------------------------- 28 | # 29 | # Streaming .wav file writer. 30 | 31 | import copy 32 | import logging 33 | import numpy 34 | import struct 35 | 36 | _DATA_CHUNK_HEADER_SIZE = 8 37 | _FMT_CHUNK_DATA_SIZE = 16 38 | _FMT_CHUNK_HEADER_SIZE = 8 39 | _RIFF_FORMAT_DESCRIPTOR_SIZE = 4 40 | 41 | _UNSIGNED_CHAR_TO_FLOAT_SCALE = 128.0 42 | _FLOAT_TO_UNSIGNED_CHAR_SCALE = 127.0 43 | 44 | 45 | class AudioStreamWriter(object): 46 | 47 | def __init__(self, file_name, sample_rate, bitdepth=16, num_channels=1): 48 | self._sr = sample_rate 49 | self._file_name = file_name 50 | self._sample_rate = sample_rate 51 | self._bitdepth = bitdepth 52 | self._num_channels = num_channels 53 | self._f = file(file_name, 'wb') 54 | self._num_bytes = 0 55 | self._write_header(False) 56 | 57 | @staticmethod 58 | def quantize(signal, bitdepth): 59 | """Convert an array of float to an array of integers. 60 | 61 | Args: 62 | signal: numpy array. source signal. 63 | bitdepth: int. size of the integer in bits. 64 | 65 | Returns: 66 | array of integers. 67 | """ 68 | norm = numpy.abs(signal).max() 69 | 70 | # Normalization or clipping. 71 | scaled_signal = copy.copy(signal) 72 | if norm > 1.0: 73 | logging.warning('Some samples will be clipped.') 74 | # Clip samples above 1 and below -1. 75 | scaled_signal[scaled_signal < -1] = -1 76 | scaled_signal[scaled_signal > 1] = 1 77 | 78 | if bitdepth == 8: 79 | scaled_signal = (scaled_signal + 1.0) * _FLOAT_TO_UNSIGNED_CHAR_SCALE 80 | scaled_signal = numpy.array(scaled_signal, dtype=numpy.uint8) 81 | else: 82 | scale = (1 << (bitdepth - 1)) - 1 83 | # pylint: disable-msg=C6407 84 | scaled_signal = scaled_signal * scale 85 | scaled_signal = numpy.array(scaled_signal, dtype='i%d' % (bitdepth / 8)) 86 | 87 | return scaled_signal 88 | 89 | def _write_header(self, restore_position): 90 | f = self._f 91 | 92 | total_size = _RIFF_FORMAT_DESCRIPTOR_SIZE 93 | total_size += _FMT_CHUNK_HEADER_SIZE + _FMT_CHUNK_DATA_SIZE 94 | total_size += _DATA_CHUNK_HEADER_SIZE + self._num_bytes 95 | 96 | current_position = f.tell() 97 | f.seek(0) 98 | f.write('RIFF') 99 | f.write(struct.pack(' 36 | 37 | namespace stm_audio_bootloader { 38 | 39 | class Demodulator { 40 | public: 41 | Demodulator() { } 42 | ~Demodulator() { } 43 | 44 | void Init(uint32_t pause, uint32_t one, uint32_t zero) { 45 | pause_one_threshold_ = (pause + one) >> 1; 46 | one_zero_threshold_ = (one + zero) >> 1; 47 | } 48 | 49 | void Sync() { 50 | symbols_.Init(); 51 | previous_sample_ = false; 52 | duration_ = 0; 53 | swallow_ = 4; 54 | } 55 | 56 | void PushSample(bool sample) { 57 | if (previous_sample_ == sample) { 58 | ++duration_; 59 | } else { 60 | previous_sample_ = sample; 61 | uint8_t symbol = 0; 62 | 63 | if (duration_ >= pause_one_threshold_) { 64 | symbol = 2; 65 | } else if (duration_ >= one_zero_threshold_) { 66 | symbol = 1; 67 | } else { 68 | symbol = 0; 69 | } 70 | 71 | if (swallow_) { 72 | symbol = 2; 73 | --swallow_; 74 | } 75 | 76 | symbols_.Overwrite(symbol); 77 | duration_ = 0; 78 | } 79 | } 80 | 81 | inline size_t available() const { 82 | return symbols_.readable(); 83 | } 84 | 85 | inline uint8_t NextSymbol() { 86 | return symbols_.ImmediateRead(); 87 | } 88 | 89 | private: 90 | stmlib::RingBuffer symbols_; 91 | 92 | uint32_t pause_one_threshold_; 93 | uint32_t one_zero_threshold_; 94 | 95 | bool previous_sample_; 96 | uint32_t duration_; 97 | 98 | uint8_t swallow_; 99 | 100 | DISALLOW_COPY_AND_ASSIGN(Demodulator); 101 | }; 102 | 103 | } // namespace stm_audio_bootloader 104 | 105 | #endif // STM_AUDIO_BOOTLOADER_FSK_DEMODULATOR_H_ 106 | -------------------------------------------------------------------------------- /fsk/encoder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.5 2 | # 3 | # Copyright 2013 Emilie Gillet. 4 | # 5 | # Author: Emilie Gillet (emilie.o.gillet@gmail.com) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | # See http://creativecommons.org/licenses/MIT/ for more information. 26 | # 27 | # ----------------------------------------------------------------------------- 28 | # 29 | # Fsk encoder for converting firmware .bin files into . 30 | 31 | import logging 32 | import numpy 33 | import optparse 34 | import zlib 35 | 36 | from stm_audio_bootloader import audio_stream_writer 37 | 38 | 39 | class FskEncoder(object): 40 | 41 | SQUARE = 0 42 | SINE = 1 43 | SINE_NO_DC = 2 44 | COSINE = 3 45 | 46 | def __init__( 47 | self, 48 | sample_rate=48000, 49 | pause_period=32, 50 | one_period=8, 51 | zero_period=4, 52 | packet_size=256, 53 | scheme=SQUARE): 54 | self._sr = sample_rate 55 | self._pause_period = pause_period 56 | self._one_period = one_period 57 | self._zero_period = zero_period 58 | self._packet_size = packet_size 59 | self._state = 1 60 | self._scheme = scheme 61 | 62 | def _encode(self, symbol_stream): 63 | symbol_stream = numpy.array(symbol_stream) 64 | counts = [numpy.sum(symbol_stream == symbol) for symbol in range(3)] 65 | durations = [self._zero_period, self._one_period, self._pause_period] 66 | 67 | if self._scheme == self.SQUARE: 68 | signals = [1 for d in durations] 69 | elif self._scheme == self.SINE: 70 | signals = [numpy.sin(numpy.arange(d) * numpy.pi / d) for d in durations] 71 | elif self._scheme == self.SINE_NO_DC: 72 | signals = [numpy.sin(numpy.arange(d) * numpy.pi / d) * max(float(durations[0]) / d, 0.5) for d in durations] 73 | else: 74 | signals = [numpy.cos(numpy.arange(d) * numpy.pi / d) for d in durations] 75 | 76 | total_length = numpy.dot(durations, counts) 77 | signal = numpy.zeros((total_length, )) 78 | state = self._state 79 | index = 0 80 | for symbol in symbol_stream: 81 | d = durations[symbol] 82 | signal[index:index + d] = state * signals[symbol] 83 | state = -state 84 | index += d 85 | assert index == signal.shape[0] 86 | self._state = state 87 | return signal 88 | 89 | def _code_blank(self, duration): 90 | num_symbols = int(duration * self._sr / self._pause_period) + 1 91 | return self._encode([2] * num_symbols) 92 | 93 | def _code_packet(self, data): 94 | assert len(data) <= self._packet_size 95 | if len(data) != self._packet_size: 96 | data = data + '\x00' * (self._packet_size - len(data)) 97 | 98 | crc = zlib.crc32(data) & 0xffffffff 99 | 100 | data = map(ord, data) 101 | crc_bytes = [crc >> 24, (crc >> 16) & 0xff, (crc >> 8) & 0xff, crc & 0xff] 102 | bytes = [0x55] * 4 + data + crc_bytes 103 | 104 | symbol_stream = [] 105 | for byte in bytes: 106 | mask = 0x80 107 | for _ in range(0, 8): 108 | symbol_stream.append(1 if (byte & mask) else 0) 109 | mask >>= 1 110 | 111 | return self._encode(symbol_stream) 112 | 113 | def code_intro(self): 114 | yield numpy.zeros((1 * self._sr, 1)).ravel() 115 | yield self._code_blank(1.0) 116 | 117 | def code_outro(self, duration=1.0): 118 | yield self._code_blank(duration) 119 | 120 | def code(self, data, page_size=1024, blank_duration=0.06): 121 | if len(data) % page_size != 0: 122 | tail = page_size - (len(data) % page_size) 123 | data += '\xff' * tail 124 | 125 | offset = 0 126 | remaining_bytes = len(data) 127 | num_packets_written = 0 128 | while remaining_bytes: 129 | size = min(remaining_bytes, self._packet_size) 130 | yield self._code_packet(data[offset:offset+size]) 131 | num_packets_written += 1 132 | if num_packets_written == page_size / self._packet_size: 133 | yield self._code_blank(blank_duration) 134 | num_packets_written = 0 135 | remaining_bytes -= size 136 | offset += size 137 | 138 | 139 | STM32F4_SECTOR_BASE_ADDRESS = [ 140 | 0x08000000, 141 | 0x08004000, 142 | 0x08008000, 143 | 0x0800C000, 144 | 0x08010000, 145 | 0x08020000, 146 | 0x08040000, 147 | 0x08060000, 148 | 0x08080000, 149 | 0x080A0000, 150 | 0x080C0000, 151 | 0x080E0000 152 | ] 153 | 154 | PAGE_SIZE = { 'stm32f1': 1024, 'stm32f3': 2048 } 155 | PAUSE = { 'stm32f1': 0.06, 'stm32f3': 0.15 } 156 | STM32F4_BLOCK_SIZE = 16384 157 | STM32F4_APPLICATION_START = 0x08008000 158 | 159 | def main(): 160 | parser = optparse.OptionParser() 161 | parser.add_option( 162 | '-s', 163 | '--sample_rate', 164 | dest='sample_rate', 165 | type='int', 166 | default=48000, 167 | help='Sample rate in Hz') 168 | parser.add_option( 169 | '-b', 170 | '--pause_period', 171 | dest='pause_period', 172 | type='int', 173 | default=64, 174 | help='Period (in samples) of a blank symbol') 175 | parser.add_option( 176 | '-n', 177 | '--one_period', 178 | dest='one_period', 179 | type='int', 180 | default=16, 181 | help='Period (in samples) of a one symbol') 182 | parser.add_option( 183 | '-z', 184 | '--zero_period', 185 | dest='zero_period', 186 | type='int', 187 | default=8, 188 | help='Period (in samples) of a zero symbol') 189 | parser.add_option( 190 | '-p', 191 | '--packet_size', 192 | dest='packet_size', 193 | type='int', 194 | default=256, 195 | help='Packet size in bytes') 196 | parser.add_option( 197 | '-g', 198 | '--page_size', 199 | dest='page_size', 200 | type='int', 201 | default=1024, 202 | help='Flash page size') 203 | parser.add_option( 204 | '-o', 205 | '--output_file', 206 | dest='output_file', 207 | default=None, 208 | help='Write output file to FILE', 209 | metavar='FILE') 210 | parser.add_option( 211 | '-t', 212 | '--target', 213 | dest='target', 214 | default='stm32f1', 215 | help='Set page size and erase time for TARGET', 216 | metavar='TARGET') 217 | 218 | options, args = parser.parse_args() 219 | data = file(args[0], 'rb').read() 220 | if len(args) != 1: 221 | logging.fatal('Specify one, and only one firmware .bin file!') 222 | sys.exit(1) 223 | 224 | if options.target not in ['stm32f1', 'stm32f3', 'stm32f4']: 225 | logging.fatal('Unknown target: %s' % options.target) 226 | sys.exit(2) 227 | 228 | output_file = options.output_file 229 | if not output_file: 230 | if '.bin' in args[0]: 231 | output_file = args[0].replace('.bin', '.wav') 232 | else: 233 | output_file = args[0] + '.wav' 234 | 235 | encoder = FskEncoder( 236 | options.sample_rate, 237 | options.pause_period, 238 | options.one_period, 239 | options.zero_period, 240 | options.packet_size) 241 | writer = audio_stream_writer.AudioStreamWriter( 242 | output_file, 243 | options.sample_rate, 244 | 16, 245 | 1) 246 | 247 | # INTRO 248 | for block in encoder.code_intro(): 249 | writer.append(block) 250 | 251 | blank_duration = 1.0 252 | if options.target in PAGE_SIZE.keys(): 253 | for block in encoder.code(data, PAGE_SIZE[options.target], PAUSE[options.target]): 254 | if len(block): 255 | writer.append(block) 256 | elif options.target == 'stm32f4': 257 | for x in xrange(0, len(data), STM32F4_BLOCK_SIZE): 258 | address = STM32F4_APPLICATION_START + x 259 | block = data[x:x+STM32F4_BLOCK_SIZE] 260 | pause = 2.5 if address in STM32F4_SECTOR_BASE_ADDRESS else 0.2 261 | for block in encoder.code(block, STM32F4_BLOCK_SIZE, pause): 262 | if len(block): 263 | writer.append(block) 264 | blank_duration = 5.0 265 | for block in encoder.code_outro(blank_duration): 266 | writer.append(block) 267 | 268 | if __name__ == '__main__': 269 | main() 270 | -------------------------------------------------------------------------------- /fsk/packet_decoder.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Emilie Gillet. 2 | // 3 | // Author: Emilie Gillet (emilie.o.gillet@gmail.com) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | // 23 | // See http://creativecommons.org/licenses/MIT/ for more information. 24 | 25 | #include "stm_audio_bootloader/fsk/packet_decoder.h" 26 | 27 | #include 28 | 29 | #include "stmlib/utils/crc32.h" 30 | 31 | namespace stm_audio_bootloader { 32 | 33 | void PacketDecoder::ParseSyncHeader(uint8_t symbol) { 34 | if (!((1 << symbol) & expected_symbols_)) { 35 | state_ = PACKET_DECODER_STATE_ERROR_SYNC; 36 | return; 37 | } 38 | 39 | switch (symbol) { 40 | case 2: 41 | ++sync_blank_size_; 42 | if (sync_blank_size_ >= kMaxSyncDuration && packet_count_) { 43 | state_ = PACKET_DECODER_STATE_END_OF_TRANSMISSION; 44 | return; 45 | } 46 | expected_symbols_ = (1 << 0) | (1 << 1) | (1 << 2); 47 | preamble_remaining_size_ = kPreambleSize; 48 | break; 49 | 50 | case 1: 51 | expected_symbols_ = (1 << 0); 52 | --preamble_remaining_size_; 53 | break; 54 | 55 | case 0: 56 | expected_symbols_ = (1 << 1); 57 | --preamble_remaining_size_; 58 | break; 59 | } 60 | 61 | if (preamble_remaining_size_ == 0) { 62 | state_ = PACKET_DECODER_STATE_DECODING_PACKET; 63 | packet_size_ = 0; 64 | packet_[packet_size_] = 0; 65 | symbol_count_ = 0; 66 | } 67 | } 68 | 69 | PacketDecoderState PacketDecoder::ProcessSymbol(uint8_t symbol) { 70 | switch (state_) { 71 | case PACKET_DECODER_STATE_SYNCING: 72 | ParseSyncHeader(symbol); 73 | break; 74 | 75 | case PACKET_DECODER_STATE_DECODING_PACKET: 76 | if (symbol == 2) { 77 | state_ = PACKET_DECODER_STATE_ERROR_SYNC; 78 | } else { 79 | packet_[packet_size_] |= symbol; 80 | ++symbol_count_; 81 | if (symbol_count_ == 8) { 82 | symbol_count_ = 0; 83 | ++packet_size_; 84 | if (packet_size_ == kPacketSize + 4) { 85 | uint32_t crc = crc32(0, packet_, kPacketSize); 86 | uint32_t expected_crc = \ 87 | (static_cast(packet_[kPacketSize + 0]) << 24) | \ 88 | (static_cast(packet_[kPacketSize + 1]) << 16) | \ 89 | (static_cast(packet_[kPacketSize + 2]) << 8) | \ 90 | (static_cast(packet_[kPacketSize + 3]) << 0); 91 | state_ = crc == expected_crc \ 92 | ? PACKET_DECODER_STATE_OK 93 | : PACKET_DECODER_STATE_ERROR_CRC; 94 | ++packet_count_; 95 | } else { 96 | packet_[packet_size_] = 0; 97 | } 98 | } else { 99 | packet_[packet_size_] <<= 1; 100 | } 101 | } 102 | break; 103 | 104 | case PACKET_DECODER_STATE_OK: 105 | case PACKET_DECODER_STATE_ERROR_SYNC: 106 | case PACKET_DECODER_STATE_ERROR_CRC: 107 | case PACKET_DECODER_STATE_END_OF_TRANSMISSION: 108 | break; 109 | } 110 | return state_; 111 | } 112 | 113 | } // namespace stm_audio_bootloader 114 | -------------------------------------------------------------------------------- /fsk/packet_decoder.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Emilie Gillet. 2 | // 3 | // Author: Emilie Gillet (emilie.o.gillet@gmail.com) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | // 23 | // See http://creativecommons.org/licenses/MIT/ for more information. 24 | // 25 | // ----------------------------------------------------------------------------- 26 | // 27 | // FSK demodulator for firmware updater (through gate inputs) 28 | 29 | #ifndef STM_AUDIO_BOOTLOADER_FSK_PACKET_DECODER_H_ 30 | #define STM_AUDIO_BOOTLOADER_FSK_PACKET_DECODER_H_ 31 | 32 | #include "stmlib/stmlib.h" 33 | #include "stmlib/utils/ring_buffer.h" 34 | 35 | namespace stm_audio_bootloader { 36 | 37 | enum PacketDecoderState { 38 | PACKET_DECODER_STATE_SYNCING, 39 | PACKET_DECODER_STATE_DECODING_PACKET, 40 | PACKET_DECODER_STATE_OK, 41 | PACKET_DECODER_STATE_ERROR_SYNC, 42 | PACKET_DECODER_STATE_ERROR_CRC, 43 | PACKET_DECODER_STATE_END_OF_TRANSMISSION 44 | }; 45 | 46 | const uint16_t kMaxSyncDuration = 500; // Symbols 47 | const uint16_t kPreambleSize = 32; 48 | const uint16_t kPacketSize = 256; 49 | 50 | class PacketDecoder { 51 | public: 52 | PacketDecoder() { } 53 | ~PacketDecoder() { } 54 | 55 | void Init() { 56 | packet_count_ = 0; 57 | } 58 | 59 | void Reset() { 60 | state_ = PACKET_DECODER_STATE_SYNCING; 61 | sync_blank_size_ = 0; 62 | expected_symbols_ = 0xff; 63 | preamble_remaining_size_ = kPreambleSize; 64 | } 65 | 66 | const uint8_t* packet_data() const { return packet_; } 67 | 68 | PacketDecoderState ProcessSymbol(uint8_t symbol); 69 | 70 | private: 71 | void ParseSyncHeader(uint8_t symbol); 72 | 73 | PacketDecoderState state_; 74 | 75 | uint8_t expected_symbols_; 76 | uint8_t preamble_remaining_size_; 77 | uint16_t sync_blank_size_; 78 | 79 | uint8_t symbol_count_; 80 | uint8_t packet_[kPacketSize + 4]; 81 | uint16_t packet_size_; 82 | uint16_t packet_count_; 83 | 84 | DISALLOW_COPY_AND_ASSIGN(PacketDecoder); 85 | }; 86 | 87 | } // namespace stm_audio_bootloader 88 | 89 | #endif // STM_AUDIO_BOOTLOADER_FSK_PACKET_DECODER_H_ 90 | -------------------------------------------------------------------------------- /qpsk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichenettes/stm-audio-bootloader/b376bd644871edc06f9ee75fb95be516a10ab394/qpsk/__init__.py -------------------------------------------------------------------------------- /qpsk/demodulator.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Emilie Gillet. 2 | // 3 | // Author: Emilie Gillet (emilie.o.gillet@gmail.com) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | // 23 | // See http://creativecommons.org/licenses/MIT/ for more information. 24 | 25 | #include "stm_audio_bootloader/qpsk/demodulator.h" 26 | 27 | #include 28 | 29 | #define abs(x) (x < 0 ? -x : x) 30 | 31 | namespace stm_audio_bootloader { 32 | 33 | const uint16_t kObservationWindow = 4096; 34 | const uint32_t kPowerThreshold = 30; 35 | const uint16_t kNumSkippedZeroSymbols = 32; 36 | const uint16_t kNumLockedTemplateSymbols = 4; 37 | 38 | int16_t lut_sine[] = { 39 | 0, 50, 100, 150, 200, 250, 300, 350, 399, 40 | 448, 497, 546, 594, 642, 689, 737, 783, 829, 41 | 875, 920, 965, 1009, 1052, 1095, 1137, 1179, 1219, 42 | 1259, 1299, 1337, 1375, 1412, 1448, 1483, 1517, 1550, 43 | 1583, 1614, 1644, 1674, 1702, 1730, 1756, 1781, 1806, 44 | 1829, 1851, 1872, 1892, 1910, 1928, 1944, 1959, 1973, 45 | 1986, 1998, 2008, 2017, 2025, 2032, 2038, 2042, 2045, 46 | 2047, 2048, 2047, 2045, 2042, 2038, 2032, 2025, 2017, 47 | 2008, 1998, 1986, 1973, 1959, 1944, 1928, 1910, 1892, 48 | 1872, 1851, 1829, 1806, 1781, 1756, 1730, 1702, 1674, 49 | 1644, 1614, 1583, 1550, 1517, 1483, 1448, 1412, 1375, 50 | 1337, 1299, 1259, 1219, 1179, 1137, 1095, 1052, 1009, 51 | 965, 920, 875, 829, 783, 737, 689, 642, 594, 52 | 546, 497, 448, 399, 350, 300, 250, 200, 150, 53 | 100, 50, 0, -50, -100, -150, -200, -250, -300, 54 | -350, -399, -448, -497, -546, -594, -642, -689, -737, 55 | -783, -829, -875, -920, -965, -1009, -1052, -1095, -1137, 56 | -1179, -1219, -1259, -1299, -1337, -1375, -1412, -1448, -1483, 57 | -1517, -1550, -1583, -1614, -1644, -1674, -1702, -1730, -1756, 58 | -1781, -1806, -1829, -1851, -1872, -1892, -1910, -1928, -1944, 59 | -1959, -1973, -1986, -1998, -2008, -2017, -2025, -2032, -2038, 60 | -2042, -2045, -2047, -2048, -2047, -2045, -2042, -2038, -2032, 61 | -2025, -2017, -2008, -1998, -1986, -1973, -1959, -1944, -1928, 62 | -1910, -1892, -1872, -1851, -1829, -1806, -1781, -1756, -1730, 63 | -1702, -1674, -1644, -1614, -1583, -1550, -1517, -1483, -1448, 64 | -1412, -1375, -1337, -1299, -1259, -1219, -1179, -1137, -1095, 65 | -1052, -1009, -965, -920, -875, -829, -783, -737, -689, 66 | -642, -594, -546, -497, -448, -399, -350, -300, -250, 67 | -200, -150, -100, -50, 0 68 | }; 69 | 70 | const int16_t filter_coefficients_3_7[] = { 71 | -20, 40, -55, 64, 255, 64, -55, 40 72 | }; 73 | 74 | const int16_t filter_coefficients_4_7[] = { 75 | -21, -51, 123, 255, 123, -51, -21 76 | }; 77 | 78 | const int16_t filter_coefficients_6_7[] = { 79 | -70, 25, 180, 255, 180, 25, -70 80 | }; 81 | 82 | const int16_t filter_coefficients_8_7[] = { 83 | -35, 85, 205, 255, 205, 85, -35 84 | }; 85 | 86 | const int16_t filter_coefficients_12_7[] = { 87 | 45, 149, 227, 255, 227, 149, 45 88 | }; 89 | 90 | const int16_t filter_coefficients_16_7[] = { 91 | 0, 98, 180, 236, 255, 236, 180, 98 92 | }; 93 | 94 | const int16_t* const cutoff_table[] = { 95 | NULL, 96 | NULL, 97 | NULL, 98 | filter_coefficients_3_7, 99 | filter_coefficients_4_7, 100 | NULL, 101 | filter_coefficients_6_7, 102 | NULL, 103 | filter_coefficients_8_7, 104 | NULL, 105 | NULL, 106 | NULL, 107 | filter_coefficients_12_7, 108 | NULL, 109 | NULL, 110 | NULL, 111 | filter_coefficients_16_7 112 | }; 113 | 114 | void Demodulator::Init( 115 | uint32_t phase_increment, 116 | uint16_t cutoff_reciprocal, 117 | uint16_t symbol_duration) { 118 | for (uint16_t i = 0; i < kFilterSize; ++i) { 119 | filter_coefficients_[i] = cutoff_table[cutoff_reciprocal][i]; 120 | } 121 | symbol_duration_ = symbol_duration; 122 | phase_increment_ = phase_increment; 123 | dc_offset_ = 2048 << 8; 124 | 125 | samples_.Init(); 126 | symbols_.Init(); 127 | 128 | Reset(); 129 | } 130 | 131 | void Demodulator::Reset() { 132 | memset(q_taps_, 0, sizeof(q_taps_)); 133 | memset(i_taps_, 0, sizeof(i_taps_)); 134 | memset(q_history_, 0, sizeof(q_history_)); 135 | memset(i_history_, 0, sizeof(i_history_)); 136 | phase_ = 0; 137 | phase_error_ = 0; 138 | decision_counter_ = 0; 139 | history_ptr_ = 0; 140 | decision_counter_ = symbol_duration_; 141 | } 142 | 143 | int16_t Demodulator::DecideSymbol(bool adjust_timing) { 144 | int32_t q_acc = 0; 145 | int32_t i_acc = 0; 146 | 147 | // Summation sets for decision 148 | // 149 | // Present ---------------------------------------> Past 150 | // 151 | // ========================================= Early 152 | // ========================================= On time 153 | // ========================================= Late 154 | // ===== Head 155 | // ===== Head 2 156 | // ===== Tail 157 | // ===== Tail 2 158 | // 159 | uint16_t now = history_ptr_ + kHistorySize; 160 | for (uint16_t i = 1; i < symbol_duration_ - 1; ++i) { 161 | uint16_t ptr = (now - i) & kHistoryMask; 162 | q_acc += q_history_[ptr]; 163 | i_acc += i_history_[ptr]; 164 | } 165 | 166 | if (adjust_timing) { 167 | uint16_t head_ptr = history_ptr_; 168 | uint16_t head_2_ptr = (now - 1) & kHistoryMask; 169 | uint16_t tail_ptr = (now - symbol_duration_ + 2) & kHistoryMask; 170 | uint16_t tail_2_ptr = (now - symbol_duration_ + 1) & kHistoryMask; 171 | 172 | int32_t q_acc_late = q_history_[head_ptr] + q_acc - q_history_[tail_ptr]; 173 | int32_t i_acc_late = i_history_[head_ptr] + i_acc - q_history_[tail_ptr]; 174 | int32_t q_acc_early = q_acc + q_history_[tail_2_ptr] - q_history_[head_2_ptr]; 175 | int32_t i_acc_early = i_acc + i_history_[tail_2_ptr] - i_history_[head_2_ptr]; 176 | 177 | uint32_t late_strength = abs(q_acc_late) + abs(i_acc_late); 178 | uint32_t on_time_strength = abs(q_acc) + abs(i_acc); 179 | uint32_t early_strength = abs(q_acc_early) + abs(i_acc_early); 180 | 181 | if (late_strength > ((5 * on_time_strength) >> 2)) { 182 | q_acc = q_acc_late; 183 | i_acc = i_acc_late; 184 | ++decision_counter_; 185 | } else if (early_strength > ((5 * on_time_strength) >> 2)) { 186 | q_acc = q_acc_early; 187 | i_acc = i_acc_early; 188 | --decision_counter_; 189 | } 190 | } 191 | 192 | return (i_acc < 0 ? 0 : 1) + (q_acc < 0 ? 0 : 2); 193 | } 194 | 195 | void Demodulator::EstimateDcComponent() { 196 | while (samples_.readable()) { 197 | int32_t sample = samples_.ImmediateRead() - (dc_offset_ >> 8); 198 | dc_offset_ += sample; 199 | ++skipped_samples_; 200 | if (skipped_samples_ >= kObservationWindow) { 201 | skipped_samples_ = 0; 202 | power_ = 0; 203 | state_ = DEMODULATOR_STATE_ESTIMATE_POWER; 204 | break; 205 | } 206 | } 207 | } 208 | 209 | void Demodulator::EstimatePower() { 210 | while (samples_.readable()) { 211 | int32_t sample = samples_.ImmediateRead() - (dc_offset_ >> 8); 212 | dc_offset_ += sample; 213 | 214 | power_ += sample * sample >> 4; 215 | 216 | ++skipped_samples_; 217 | if (skipped_samples_ >= kObservationWindow) { 218 | uint32_t detected_power = power_ / kObservationWindow; 219 | skipped_samples_ = 0; 220 | power_ = 0; 221 | if (detected_power >= kPowerThreshold) { 222 | state_ = DEMODULATOR_STATE_CARRIER_SYNC; 223 | break; 224 | } 225 | } 226 | } 227 | } 228 | 229 | void Demodulator::Demodulate() { 230 | while (samples_.readable()) { 231 | // Remove DC offset. 232 | int32_t sample = samples_.ImmediateRead() - (dc_offset_ >> 8); 233 | dc_offset_ += sample; 234 | phase_ += phase_increment_; 235 | 236 | // Demodulate. 237 | int32_t i_osc = lut_sine[((phase_ >> 24) + 64) & 0xff]; 238 | int32_t q_osc = lut_sine[phase_ >> 24]; 239 | i_taps_[0] = (sample * -i_osc) >> 8; 240 | q_taps_[0] = (sample * q_osc) >> 8; 241 | 242 | int32_t i = 0; 243 | int32_t q = 0; 244 | 245 | // Carrier rejection filter. 246 | for (uint16_t j = 0; j < kFilterSize; ++j) { 247 | i += i_taps_[j] * filter_coefficients_[j]; 248 | q += q_taps_[j] * filter_coefficients_[j]; 249 | } 250 | for (uint16_t j = kFilterSize - 1; j > 0; --j) { 251 | i_taps_[j] = i_taps_[j - 1]; 252 | q_taps_[j] = q_taps_[j - 1]; 253 | } 254 | 255 | // PLL to lock onto the carrier. 256 | int16_t i_sign = i < 0 ? -1 : 1; 257 | int16_t q_sign = q < 0 ? -1 : 1; 258 | int32_t phase_error = i_sign * q - q_sign * i; 259 | if (state_ == DEMODULATOR_STATE_CARRIER_SYNC) { 260 | phase_error = i - q; // Lock to (-1 -1) during sync phase. 261 | } 262 | phase_error_ = (3 * phase_error_ + phase_error) >> 2; 263 | phase_ += phase_error_ << 3; 264 | phase_increment_ += (phase_error_ >> 1); 265 | 266 | // Store in history buffer (for detection). 267 | q_history_[history_ptr_] = q >> 8; 268 | i_history_[history_ptr_] = i >> 8; 269 | history_ptr_ = (history_ptr_ + 1) & kHistoryMask; 270 | 271 | --decision_counter_; 272 | if (!decision_counter_) { 273 | decision_counter_ = symbol_duration_; 274 | // In carrier sync mode, we just let the PLL stabilize until we 275 | // consistently decode a string of 0s. 276 | switch (state_) { 277 | case DEMODULATOR_STATE_CARRIER_SYNC: 278 | if (DecideSymbol(false) == 0) { 279 | ++skipped_symbols_; 280 | if (skipped_symbols_ == kNumSkippedZeroSymbols) { 281 | SyncDecision(); 282 | } 283 | } 284 | break; 285 | 286 | case DEMODULATOR_STATE_DECISION_SYNC: 287 | symbols_.Overwrite(4); 288 | break; 289 | 290 | case DEMODULATOR_STATE_OK: 291 | symbols_.Overwrite(DecideSymbol(true)); 292 | break; 293 | 294 | default: 295 | break; 296 | } 297 | } 298 | 299 | if (state_ == DEMODULATOR_STATE_DECISION_SYNC) { 300 | // Match the demodulated Q/I channels with the shape of the alignment 301 | // sequence. 302 | int32_t correlation = 0; 303 | uint16_t ptr = history_ptr_ + kHistorySize; 304 | for (uint16_t k = 0; k < 2 * symbol_duration_; ++k) { 305 | int32_t expected_q = k < symbol_duration_ ? -1 : 1; 306 | int32_t expected_i = -expected_q; 307 | correlation += q_history_[(ptr - k) & kHistoryMask] * expected_q; 308 | correlation += i_history_[(ptr - k) & kHistoryMask] * expected_i; 309 | } 310 | if (correlation > correlation_maximum_) { 311 | correlation_maximum_ = correlation; 312 | } 313 | if (correlation < 0) { 314 | // Reset the peak detector at each valley in the detection function, 315 | // so that we can detect several consecutive peaks. 316 | correlation_maximum_ = 0; 317 | } 318 | 319 | // Detect a local maximum in the output of the correlator. 320 | ptr = history_ptr_ + kHistorySize - (symbol_duration_ >> 1); 321 | if (correlation_history_[0] > correlation && 322 | correlation_history_[0] > correlation_history_[1] && 323 | correlation_history_[0] == correlation_maximum_ && 324 | q_history_[ptr & kHistoryMask] < 0 && 325 | i_history_[ptr & kHistoryMask] > 0) { 326 | ++skipped_symbols_; 327 | if (skipped_symbols_ == kNumLockedTemplateSymbols) { 328 | state_ = DEMODULATOR_STATE_OK; 329 | } 330 | decision_counter_ = symbol_duration_ - 1; 331 | } 332 | 333 | correlation_history_[1] = correlation_history_[0]; 334 | correlation_history_[0] = correlation; 335 | } 336 | } 337 | } 338 | 339 | } // namespace stm_audio_bootloader 340 | -------------------------------------------------------------------------------- /qpsk/demodulator.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Emilie Gillet. 2 | // 3 | // Author: Emilie Gillet (emilie.o.gillet@gmail.com) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | // 23 | // See http://creativecommons.org/licenses/MIT/ for more information. 24 | // 25 | // ----------------------------------------------------------------------------- 26 | // 27 | // QPSK demodulator for firmware updater. 28 | 29 | #ifndef STM_AUDIO_BOOTLOADER_DEMODULATOR_H_ 30 | #define STM_AUDIO_BOOTLOADER_DEMODULATOR_H_ 31 | 32 | #include "stmlib/stmlib.h" 33 | #include "stmlib/utils/ring_buffer.h" 34 | 35 | namespace stm_audio_bootloader { 36 | 37 | enum DemodulatorState { 38 | DEMODULATOR_STATE_ESTIMATE_DC_COMPONENT, 39 | DEMODULATOR_STATE_ESTIMATE_POWER, 40 | DEMODULATOR_STATE_CARRIER_SYNC, 41 | DEMODULATOR_STATE_DECISION_SYNC, 42 | DEMODULATOR_STATE_OVERFLOW, 43 | DEMODULATOR_STATE_OK 44 | }; 45 | 46 | const uint16_t kFilterSize = 7; 47 | const uint16_t kHistorySize = 256; 48 | // Works because kHistorySize is a power of 2! 49 | const uint16_t kHistoryMask = kHistorySize - 1; 50 | 51 | class Demodulator { 52 | public: 53 | Demodulator() { } 54 | ~Demodulator() { } 55 | 56 | void Init( 57 | uint32_t phase_increment, 58 | uint16_t cutoff_reciprocal, 59 | uint16_t symbol_duration); 60 | 61 | inline void PushSample(int16_t sample) { 62 | if (!samples_.writable()) { 63 | state_ = DEMODULATOR_STATE_OVERFLOW; 64 | } 65 | samples_.Overwrite(sample); 66 | } 67 | 68 | inline void SyncCarrier(bool discover) { 69 | skipped_samples_ = skipped_symbols_ = 0; 70 | samples_.Flush(); 71 | symbols_.Flush(); 72 | state_ = discover 73 | ? DEMODULATOR_STATE_ESTIMATE_DC_COMPONENT 74 | : DEMODULATOR_STATE_CARRIER_SYNC; 75 | phase_ = phase_error_ = 0; 76 | } 77 | 78 | inline void SyncDecision() { 79 | symbols_.Flush(); 80 | state_ = DEMODULATOR_STATE_DECISION_SYNC; 81 | correlation_history_[0] = correlation_history_[1] = -1024; 82 | correlation_maximum_ = 0; 83 | skipped_symbols_ = 0; 84 | } 85 | 86 | inline size_t available() const { 87 | return symbols_.readable(); 88 | } 89 | 90 | DemodulatorState state() const { return state_; } 91 | 92 | inline void ProcessAtLeast(size_t size) { 93 | if (samples_.readable() >= size) { 94 | while (samples_.readable()) { 95 | if (state_ == DEMODULATOR_STATE_ESTIMATE_DC_COMPONENT) { 96 | EstimateDcComponent(); 97 | } else if (state_ == DEMODULATOR_STATE_ESTIMATE_POWER) { 98 | EstimatePower(); 99 | } else { 100 | Demodulate(); 101 | } 102 | } 103 | } 104 | } 105 | 106 | inline uint8_t NextSymbol() { 107 | return symbols_.ImmediateRead(); 108 | } 109 | 110 | inline int32_t q() const { return q_history_[history_ptr_]; } 111 | inline int32_t i() const { return i_history_[history_ptr_]; } 112 | 113 | private: 114 | void Reset(); 115 | void EstimateDcComponent(); 116 | void EstimatePower(); 117 | void Demodulate(); 118 | int16_t DecideSymbol(bool adjust_timing); 119 | 120 | DemodulatorState state_; 121 | 122 | int32_t dc_offset_; 123 | uint32_t skipped_samples_; 124 | uint32_t power_; 125 | 126 | // Local oscillator. 127 | uint32_t phase_; 128 | uint32_t phase_increment_; 129 | int32_t phase_error_; 130 | 131 | // Demodulator filter. 132 | int32_t filter_coefficients_[kFilterSize]; 133 | int32_t q_taps_[kFilterSize]; 134 | int32_t i_taps_[kFilterSize]; 135 | 136 | // History of demodulated signal for matched filter and decision. 137 | int16_t q_history_[kHistorySize]; 138 | int16_t i_history_[kHistorySize]; 139 | uint16_t history_ptr_; 140 | 141 | // Correlator between the demodulated signal and the synchronization 142 | // template. 143 | int32_t correlation_history_[2]; 144 | int32_t correlation_maximum_; 145 | 146 | // Decision timing. 147 | uint16_t symbol_duration_; 148 | uint16_t decision_counter_; 149 | uint16_t skipped_symbols_; 150 | 151 | stmlib::RingBuffer samples_; 152 | stmlib::RingBuffer symbols_; 153 | 154 | DISALLOW_COPY_AND_ASSIGN(Demodulator); 155 | }; 156 | 157 | } // namespace stm_audio_bootloader 158 | 159 | #endif // STM_AUDIO_BOOTLOADER_DEMODULATOR_H_ 160 | -------------------------------------------------------------------------------- /qpsk/encoder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.5 2 | # 3 | # Copyright 2013 Emilie Gillet. 4 | # 5 | # Author: Emilie Gillet (emilie.o.gillet@gmail.com) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | # See http://creativecommons.org/licenses/MIT/ for more information. 26 | # 27 | # ----------------------------------------------------------------------------- 28 | # 29 | # Qpsk encoder for converting firmware .bin files into . 30 | 31 | import logging 32 | import numpy 33 | import optparse 34 | import zlib 35 | 36 | from stm_audio_bootloader import audio_stream_writer 37 | 38 | 39 | class Scrambler(object): 40 | 41 | def __init__(self, seed=0): 42 | self._state = seed 43 | 44 | def scramble(self, data): 45 | data = map(ord, data) 46 | for index, byte in enumerate(data): 47 | data[index] = data[index] ^ (self._state >> 24) 48 | self._state = (self._state * 1664525L + 1013904223L) & 0xffffffff 49 | return ''.join(map(chr, data)) 50 | 51 | 52 | 53 | class QpskEncoder(object): 54 | 55 | def __init__( 56 | self, 57 | sample_rate=48000, 58 | carrier_frequency=2000, 59 | bit_rate=8000, 60 | packet_size=256, 61 | scramble=False): 62 | period = sample_rate / carrier_frequency 63 | symbol_time = sample_rate / (bit_rate / 2) 64 | 65 | assert (symbol_time % period == 0) or (period % symbol_time == 0) 66 | assert (sample_rate % carrier_frequency) == 0 67 | assert (sample_rate % bit_rate) == 0 68 | 69 | self._sr = sample_rate 70 | self._br = bit_rate 71 | self._carrier_frequency = carrier_frequency 72 | self._sample_index = 0 73 | self._packet_size = packet_size 74 | self._scrambler = Scrambler(0) if scramble else None 75 | 76 | @staticmethod 77 | def _upsample(x, factor): 78 | return numpy.tile(x.reshape(len(x), 1), (1, factor)).ravel() 79 | 80 | def _encode_qpsk(self, symbol_stream): 81 | ratio = self._sr / self._br * 2 82 | symbol_stream = numpy.array(symbol_stream) 83 | bitstream_even = 2 * self._upsample(symbol_stream % 2, ratio) - 1 84 | bitstream_odd = 2 * self._upsample(symbol_stream / 2, ratio) - 1 85 | return bitstream_even / numpy.sqrt(2.0), bitstream_odd / numpy.sqrt(2.0) 86 | 87 | def _modulate(self, q_mod, i_mod): 88 | num_samples = len(q_mod) 89 | t = (numpy.arange(0.0, num_samples) + self._sample_index) / self._sr 90 | self._sample_index += num_samples 91 | phase = 2 * numpy.pi * self._carrier_frequency * t 92 | return (q_mod * numpy.sin(phase) + i_mod * numpy.cos(phase)) 93 | 94 | def _encode(self, symbol_stream): 95 | return self._modulate(*self._encode_qpsk(symbol_stream)) 96 | 97 | def _code_blank(self, duration): 98 | num_zeros = int(duration * self._br / 8) * 4 99 | symbol_stream = numpy.zeros((num_zeros, 1)).ravel().astype(int) 100 | return self._encode(symbol_stream) 101 | 102 | def _code_packet(self, data): 103 | assert len(data) <= self._packet_size 104 | if len(data) != self._packet_size: 105 | data = data + '\xff' * (self._packet_size - len(data)) 106 | 107 | if self._scrambler: 108 | data = self._scrambler.scramble(data) 109 | 110 | crc = zlib.crc32(data) & 0xffffffff 111 | 112 | data = map(ord, data) 113 | # 16x 0 for the PLL ; 8x 21 for the edge detector ; 8x 3030 for syncing 114 | preamble = [0] * 8 + [0x99] * 4 + [0xcc] * 4 115 | crc_bytes = [crc >> 24, (crc >> 16) & 0xff, (crc >> 8) & 0xff, crc & 0xff] 116 | bytes = preamble + data + crc_bytes 117 | 118 | symbol_stream = [] 119 | for byte in bytes: 120 | symbol_stream.append((byte >> 6) & 0x3) 121 | symbol_stream.append((byte >> 4) & 0x3) 122 | symbol_stream.append((byte >> 2) & 0x3) 123 | symbol_stream.append((byte >> 0) & 0x3) 124 | 125 | return self._encode(symbol_stream) 126 | 127 | def code_intro(self): 128 | yield numpy.zeros((1 * self._sr, 1)).ravel() 129 | yield self._code_blank(1.0) 130 | 131 | def code_outro(self, duration=1.0): 132 | yield self._code_blank(duration) 133 | 134 | def code(self, data, page_size=1024, blank_duration=0.06): 135 | if len(data) % page_size != 0: 136 | tail = page_size - (len(data) % page_size) 137 | data += '\xff' * tail 138 | 139 | offset = 0 140 | remaining_bytes = len(data) 141 | num_packets_written = 0 142 | while remaining_bytes: 143 | size = min(remaining_bytes, self._packet_size) 144 | yield self._code_packet(data[offset:offset+size]) 145 | num_packets_written += 1 146 | if num_packets_written == page_size / self._packet_size: 147 | yield self._code_blank(blank_duration) 148 | num_packets_written = 0 149 | remaining_bytes -= size 150 | offset += size 151 | 152 | STM32F4_SECTOR_BASE_ADDRESS = [ 153 | 0x08000000, 154 | 0x08004000, 155 | 0x08008000, 156 | 0x0800C000, 157 | 0x08010000, 158 | 0x08020000, 159 | 0x08040000, 160 | 0x08060000, 161 | 0x08080000, 162 | 0x080A0000, 163 | 0x080C0000, 164 | 0x080E0000 165 | ] 166 | 167 | STM32H7_SECTOR_BASE_ADDRESS = [ 168 | 0x08000000, 169 | 0x08020000, 170 | 0x08040000, 171 | 0x08060000, 172 | 0x08080000, 173 | 0x080a0000, 174 | 0x080c0000, 175 | 0x080e0000, 176 | 0x08100000, 177 | 0x08120000, 178 | 0x08140000, 179 | 0x08160000, 180 | 0x08180000, 181 | 0x081a0000, 182 | 0x081c0000, 183 | 0x081e0000, 184 | ] 185 | 186 | PAGE_SIZE = { 'stm32f1': 1024, 'stm32f3': 2048 } 187 | PAUSE = { 'stm32f1': 0.06, 'stm32f3': 0.15 } 188 | 189 | STM32F4_BLOCK_SIZE = 16384 190 | STM32F4_APPLICATION_START = 0x08008000 191 | 192 | STM32H7_BLOCK_SIZE = 0x10000 193 | STM32H7_APPLICATION_START = 0x08020000 194 | 195 | def main(): 196 | parser = optparse.OptionParser() 197 | parser.add_option( 198 | '-k', 199 | '--scramble', 200 | dest='scramble', 201 | action='store_true', 202 | default=False, 203 | help='Randomize data stream') 204 | parser.add_option( 205 | '-s', 206 | '--sample_rate', 207 | dest='sample_rate', 208 | type='int', 209 | default=48000, 210 | help='Sample rate in Hz') 211 | parser.add_option( 212 | '-c', 213 | '--carrier_frequency', 214 | dest='carrier_frequency', 215 | type='int', 216 | default=6000, 217 | help='Carrier frequency in Hz') 218 | parser.add_option( 219 | '-b', 220 | '--baud_rate', 221 | dest='baud_rate', 222 | type='int', 223 | default=12000, 224 | help='Baudrate in bps') 225 | parser.add_option( 226 | '-p', 227 | '--packet_size', 228 | dest='packet_size', 229 | type='int', 230 | default=256, 231 | help='Packet size in bytes') 232 | parser.add_option( 233 | '-o', 234 | '--output_file', 235 | dest='output_file', 236 | default=None, 237 | help='Write output file to FILE', 238 | metavar='FILE') 239 | parser.add_option( 240 | '-t', 241 | '--target', 242 | dest='target', 243 | default='stm32f1', 244 | help='Set page size and erase time for TARGET', 245 | metavar='TARGET') 246 | 247 | 248 | options, args = parser.parse_args() 249 | data = file(args[0], 'rb').read() 250 | 251 | if len(args) != 1: 252 | logging.fatal('Specify one, and only one firmware .bin file!') 253 | sys.exit(1) 254 | 255 | if options.target not in ['stm32f1', 'stm32f3', 'stm32f4', 'stm32h7']: 256 | logging.fatal('Unknown target: %s' % options.target) 257 | sys.exit(2) 258 | 259 | output_file = options.output_file 260 | if not output_file: 261 | if '.bin' in args[0]: 262 | output_file = args[0].replace('.bin', '.wav') 263 | else: 264 | output_file = args[0] + '.wav' 265 | 266 | encoder = QpskEncoder( 267 | options.sample_rate, 268 | options.carrier_frequency, 269 | options.baud_rate, 270 | options.packet_size, 271 | options.scramble) 272 | writer = audio_stream_writer.AudioStreamWriter( 273 | output_file, 274 | options.sample_rate, 275 | 16, 276 | 1) 277 | 278 | # INTRO 279 | for block in encoder.code_intro(): 280 | writer.append(block) 281 | 282 | blank_duration = 1.0 283 | if options.target in PAGE_SIZE.keys(): 284 | for block in encoder.code(data, PAGE_SIZE[options.target], PAUSE[options.target]): 285 | if len(block): 286 | writer.append(block) 287 | elif options.target == 'stm32f4' or options.target == 'stm32h7': 288 | if options.target == 'stm32f4': 289 | block_size = STM32F4_BLOCK_SIZE 290 | start_address = STM32F4_APPLICATION_START 291 | sector_base = STM32F4_SECTOR_BASE_ADDRESS 292 | erase_pause = 3.5 293 | else: 294 | block_size = STM32H7_BLOCK_SIZE 295 | start_address = STM32H7_APPLICATION_START 296 | sector_base = STM32H7_SECTOR_BASE_ADDRESS 297 | erase_pause = 3.0 298 | for x in xrange(0, len(data), block_size): 299 | address = start_address + x 300 | block = data[x:x+block_size] 301 | pause = erase_pause if address in sector_base else 0.2 302 | for block in encoder.code(block, block_size, pause): 303 | if len(block): 304 | writer.append(block) 305 | blank_duration = 5.0 306 | for block in encoder.code_outro(blank_duration): 307 | writer.append(block) 308 | 309 | 310 | if __name__ == '__main__': 311 | main() 312 | -------------------------------------------------------------------------------- /qpsk/packet_decoder.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Emilie Gillet. 2 | // 3 | // Author: Emilie Gillet (emilie.o.gillet@gmail.com) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | // 23 | // See http://creativecommons.org/licenses/MIT/ for more information. 24 | 25 | #include "stm_audio_bootloader/qpsk/packet_decoder.h" 26 | 27 | #include "stmlib/utils/crc32.h" 28 | 29 | namespace stm_audio_bootloader { 30 | 31 | void PacketDecoder::Init(uint16_t max_sync_duration, bool scramble) { 32 | Reset(); 33 | packet_count_ = 0; 34 | max_sync_duration_ = max_sync_duration; 35 | scramble_ = scramble; 36 | scrambler_state_ = 0; 37 | } 38 | 39 | void PacketDecoder::ParseSyncHeader(uint8_t symbol) { 40 | if (!((1 << symbol) & expected_symbols_)) { 41 | state_ = PACKET_DECODER_STATE_ERROR_SYNC; 42 | return; 43 | } 44 | 45 | switch (symbol) { 46 | case 4: 47 | ++sync_blank_size_; 48 | if (sync_blank_size_ >= max_sync_duration_ && packet_count_) { 49 | state_ = PACKET_DECODER_STATE_END_OF_TRANSMISSION; 50 | return; 51 | } 52 | expected_symbols_ = (1 << 1) | (1 << 2) | (1 << 4); 53 | preamble_remaining_size_ = kPreambleSize; 54 | break; 55 | 56 | case 3: 57 | expected_symbols_ = (1 << 0); 58 | --preamble_remaining_size_; 59 | break; 60 | 61 | case 2: 62 | expected_symbols_ = (1 << 1); 63 | break; 64 | 65 | case 1: 66 | expected_symbols_ = (1 << 2) | (1 << 3); 67 | break; 68 | 69 | case 0: 70 | expected_symbols_ = (1 << 3); 71 | --preamble_remaining_size_; 72 | break; 73 | } 74 | 75 | if (preamble_remaining_size_ == 0) { 76 | state_ = PACKET_DECODER_STATE_DECODING_PACKET; 77 | packet_size_ = 0; 78 | packet_[packet_size_] = 0; 79 | symbol_count_ = 0; 80 | } 81 | } 82 | 83 | PacketDecoderState PacketDecoder::ProcessSymbol(uint8_t symbol) { 84 | switch (state_) { 85 | case PACKET_DECODER_STATE_SYNCING: 86 | ParseSyncHeader(symbol); 87 | break; 88 | 89 | case PACKET_DECODER_STATE_DECODING_PACKET: 90 | packet_[packet_size_] |= symbol; 91 | ++symbol_count_; 92 | if (symbol_count_ == kSymbolMask + 1) { 93 | symbol_count_ = 0; 94 | ++packet_size_; 95 | if (packet_size_ == kPacketSize + 4) { 96 | uint32_t crc = crc32(0, packet_, kPacketSize); 97 | uint32_t expected_crc = \ 98 | (static_cast(packet_[kPacketSize + 0]) << 24) | \ 99 | (static_cast(packet_[kPacketSize + 1]) << 16) | \ 100 | (static_cast(packet_[kPacketSize + 2]) << 8) | \ 101 | (static_cast(packet_[kPacketSize + 3]) << 0); 102 | state_ = crc == expected_crc \ 103 | ? PACKET_DECODER_STATE_OK 104 | : PACKET_DECODER_STATE_ERROR_CRC; 105 | 106 | if (scramble_ && state_ == PACKET_DECODER_STATE_OK) { 107 | for (uint16_t i = 0; i < kPacketSize; ++i) { 108 | packet_[i] ^= scrambler_state_ >> 24; 109 | scrambler_state_ = scrambler_state_ * 1664525L + 1013904223L; 110 | } 111 | } 112 | ++packet_count_; 113 | } else { 114 | packet_[packet_size_] = 0; 115 | } 116 | } else { 117 | packet_[packet_size_] <<= kSymbolShift; 118 | } 119 | break; 120 | 121 | case PACKET_DECODER_STATE_OK: 122 | case PACKET_DECODER_STATE_ERROR_SYNC: 123 | case PACKET_DECODER_STATE_ERROR_CRC: 124 | case PACKET_DECODER_STATE_END_OF_TRANSMISSION: 125 | break; 126 | } 127 | return state_; 128 | } 129 | 130 | } // namespace stm_audio_bootloader 131 | -------------------------------------------------------------------------------- /qpsk/packet_decoder.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Emilie Gillet. 2 | // 3 | // Author: Emilie Gillet (emilie.o.gillet@gmail.com) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | // 23 | // See http://creativecommons.org/licenses/MIT/ for more information. 24 | // 25 | // ----------------------------------------------------------------------------- 26 | // 27 | // QPSK decoder for firmware updater. 28 | 29 | #ifndef STM_AUDIO_BOOTLOADER_QPSK_PACKET_DECODER_H_ 30 | #define STM_AUDIO_BOOTLOADER_QPSK_PACKET_DECODER_H_ 31 | 32 | #include "stmlib/stmlib.h" 33 | #include "stmlib/utils/ring_buffer.h" 34 | 35 | namespace stm_audio_bootloader { 36 | 37 | enum PacketDecoderState { 38 | PACKET_DECODER_STATE_SYNCING, 39 | PACKET_DECODER_STATE_DECODING_PACKET, 40 | PACKET_DECODER_STATE_OK, 41 | PACKET_DECODER_STATE_ERROR_SYNC, 42 | PACKET_DECODER_STATE_ERROR_CRC, 43 | PACKET_DECODER_STATE_END_OF_TRANSMISSION 44 | }; 45 | 46 | const uint16_t kPacketSize = 256; 47 | const uint16_t kPreambleSize = 16; 48 | const uint8_t kSymbolMask = 0x3; 49 | const uint8_t kSymbolShift = 2; 50 | 51 | class PacketDecoder { 52 | public: 53 | PacketDecoder() { } 54 | ~PacketDecoder() { } 55 | 56 | void Init() { 57 | Init(1000, false); 58 | } 59 | void Init(uint16_t max_sync_duration) { 60 | Init(max_sync_duration, false); 61 | } 62 | 63 | void Init(uint16_t max_sync_duration, bool scramble); 64 | 65 | void Reset() { 66 | state_ = PACKET_DECODER_STATE_SYNCING; 67 | expected_symbols_ = (1 << 4); 68 | sync_blank_size_ = 0; 69 | } 70 | 71 | PacketDecoderState ProcessSymbol(uint8_t symbol); 72 | const uint8_t* packet_data() const { return packet_; } 73 | 74 | private: 75 | void ParseSyncHeader(uint8_t symbol); 76 | 77 | PacketDecoderState state_; 78 | uint8_t packet_[kPacketSize + 4]; 79 | uint16_t packet_size_; 80 | uint16_t symbol_count_; 81 | uint16_t packet_count_; 82 | 83 | uint8_t expected_symbols_; 84 | uint8_t preamble_remaining_size_; 85 | 86 | uint16_t sync_blank_size_; 87 | uint16_t max_sync_duration_; 88 | 89 | bool scramble_; 90 | uint32_t scrambler_state_; 91 | 92 | DISALLOW_COPY_AND_ASSIGN(PacketDecoder); 93 | }; 94 | 95 | } // namespace stm_audio_bootloader 96 | 97 | #endif // STM_AUDIO_BOOTLOADER_QPSK_PACKET_DECODER_H_ 98 | --------------------------------------------------------------------------------