1#!/usr/bin/python2.5 2# 3# Copyright 2013 Olivier Gillet. 4# 5# Author: Olivier Gillet (ol.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 31import numpy 32import optparse 33import zlib 34 35from stm_audio_bootloader import audio_stream_writer 36 37 38class QpskEncoder(object): 39 40 def __init__( 41 self, 42 sample_rate=48000, 43 carrier_frequency=2000, 44 bit_rate=8000, 45 packet_size=256): 46 period = sample_rate / carrier_frequency 47 symbol_time = sample_rate / (bit_rate / 2) 48 49 assert (symbol_time % period == 0) or (period % symbol_time == 0) 50 assert (sample_rate % carrier_frequency) == 0 51 assert (sample_rate % bit_rate) == 0 52 53 self._sr = sample_rate 54 self._br = bit_rate 55 self._carrier_frequency = carrier_frequency 56 self._sample_index = 0 57 self._packet_size = packet_size 58 59 @staticmethod 60 def _upsample(x, factor): 61 return numpy.tile(x.reshape(len(x), 1), (1, factor)).ravel() 62 63 def _encode_qpsk(self, symbol_stream): 64 ratio = self._sr / self._br * 2 65 symbol_stream = numpy.array(symbol_stream) 66 bitstream_even = 2 * self._upsample(symbol_stream % 2, ratio) - 1 67 bitstream_odd = 2 * self._upsample(symbol_stream / 2, ratio) - 1 68 return bitstream_even / numpy.sqrt(2.0), bitstream_odd / numpy.sqrt(2.0) 69 70 def _modulate(self, q_mod, i_mod): 71 num_samples = len(q_mod) 72 t = (numpy.arange(0.0, num_samples) + self._sample_index) / self._sr 73 self._sample_index += num_samples 74 phase = 2 * numpy.pi * self._carrier_frequency * t 75 return (q_mod * numpy.sin(phase) + i_mod * numpy.cos(phase)) 76 77 def _encode(self, symbol_stream): 78 return self._modulate(*self._encode_qpsk(symbol_stream)) 79 80 def _code_blank(self, duration): 81 num_zeros = int(duration * self._br / 8) * 4 82 symbol_stream = numpy.zeros((num_zeros, 1)).ravel().astype(int) 83 return self._encode(symbol_stream) 84 85 def _code_packet(self, data): 86 assert len(data) <= self._packet_size 87 if len(data) != self._packet_size: 88 data = data + '\x00' * (self._packet_size - len(data)) 89 90 crc = zlib.crc32(data) & 0xffffffff 91 92 data = map(ord, data) 93 # 16x 0 for the PLL ; 8x 21 for the edge detector ; 8x 3030 for syncing 94 preamble = [0] * 8 + [0x99] * 4 + [0xcc] * 4 95 crc_bytes = [crc >> 24, (crc >> 16) & 0xff, (crc >> 8) & 0xff, crc & 0xff] 96 bytes = preamble + data + crc_bytes 97 98 symbol_stream = [] 99 for byte in bytes: 100 symbol_stream.append((byte >> 6) & 0x3) 101 symbol_stream.append((byte >> 4) & 0x3) 102 symbol_stream.append((byte >> 2) & 0x3) 103 symbol_stream.append((byte >> 0) & 0x3) 104 105 return self._encode(symbol_stream) 106 107 def code_intro(self): 108 yield numpy.zeros((1.0 * self._sr, 1)).ravel() 109 yield self._code_blank(1.0) 110 111 def code_outro(self, duration=1.0): 112 yield self._code_blank(duration) 113 114 def code(self, data, page_size=1024, blank_duration=0.06): 115 if len(data) % page_size != 0: 116 tail = page_size - (len(data) % page_size) 117 data += '\xff' * tail 118 119 offset = 0 120 remaining_bytes = len(data) 121 num_packets_written = 0 122 while remaining_bytes: 123 size = min(remaining_bytes, self._packet_size) 124 yield self._code_packet(data[offset:offset+size]) 125 num_packets_written += 1 126 if num_packets_written == page_size / self._packet_size: 127 yield self._code_blank(blank_duration) 128 num_packets_written = 0 129 remaining_bytes -= size 130 offset += size 131 132STM32F4_SECTOR_BASE_ADDRESS = [ 133 0x08000000, 134 0x08004000, 135 0x08008000, 136 0x0800C000, 137 0x08010000, 138 0x08020000, 139 0x08040000, 140 0x08060000, 141 0x08080000, 142 0x080A0000, 143 0x080C0000, 144 0x080E0000 145] 146 147STM32F1_PAGE_SIZE = 1024 148STM32F4_BLOCK_SIZE = 16384 149STM32F4_APPLICATION_START = 0x08008000 150 151def main(): 152 parser = optparse.OptionParser() 153 parser.add_option( 154 '-s', 155 '--sample_rate', 156 dest='sample_rate', 157 type='int', 158 default=48000, 159 help='Sample rate in Hz') 160 parser.add_option( 161 '-c', 162 '--carrier_frequency', 163 dest='carrier_frequency', 164 type='int', 165 default=6000, 166 help='Carrier frequency in Hz') 167 parser.add_option( 168 '-b', 169 '--baud_rate', 170 dest='baud_rate', 171 type='int', 172 default=12000, 173 help='Baudrate in bps') 174 parser.add_option( 175 '-p', 176 '--packet_size', 177 dest='packet_size', 178 type='int', 179 default=256, 180 help='Packet size in bytes') 181 parser.add_option( 182 '-o', 183 '--output_file', 184 dest='output_file', 185 default=None, 186 help='Write output file to FILE', 187 metavar='FILE') 188 parser.add_option( 189 '-t', 190 '--target', 191 dest='target', 192 default='stm32f1', 193 help='Set page size and erase time for TARGET', 194 metavar='TARGET') 195 196 197 options, args = parser.parse_args() 198 data = file(args[0], 'rb').read() 199 if len(args) != 1: 200 logging.fatal('Specify one, and only one firmware .bin file!') 201 sys.exit(1) 202 203 if options.target not in ['stm32f1', 'stm32f4']: 204 logging.fatal('Unknown target: %s' % options.target) 205 sys.exit(2) 206 207 output_file = options.output_file 208 if not output_file: 209 if '.bin' in args[0]: 210 output_file = args[0].replace('.bin', '.wav') 211 else: 212 output_file = args[0] + '.wav' 213 214 encoder = QpskEncoder( 215 options.sample_rate, 216 options.carrier_frequency, 217 options.baud_rate, 218 options.packet_size) 219 writer = audio_stream_writer.AudioStreamWriter( 220 output_file, 221 options.sample_rate, 222 16, 223 1) 224 225 # INTRO 226 for block in encoder.code_intro(): 227 writer.append(block) 228 229 blank_duration = 1.0 230 if options.target == 'stm32f1': 231 for block in encoder.code(data, STM32F1_PAGE_SIZE, 0.06): 232 if len(block): 233 writer.append(block) 234 elif options.target == 'stm32f4': 235 for x in xrange(0, len(data), STM32F4_BLOCK_SIZE): 236 address = STM32F4_APPLICATION_START + x 237 block = data[x:x+STM32F4_BLOCK_SIZE] 238 pause = 2.5 if address in STM32F4_SECTOR_BASE_ADDRESS else 0.2 239 for block in encoder.code(block, STM32F4_BLOCK_SIZE, pause): 240 if len(block): 241 writer.append(block) 242 blank_duration = 5.0 243 for block in encoder.code_outro(blank_duration): 244 writer.append(block) 245 246 247if __name__ == '__main__': 248 main() 249