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