Successfully locking on first frame

This commit is contained in:
Daniele Verducci (Slimpenguin) 2022-06-04 09:11:59 +02:00
parent 861dafb169
commit e6df77cf59
2 changed files with 64 additions and 57 deletions

101
decode.py
View File

@ -1,9 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" @package docstring """ @package docstring
Manchester encoder Manchester decoder
Encodes a file using the manchester encoding and outputs it as audio file Decodes a file encoded with the manchester code
See https://www.youtube.com/watch?v=8BhjXqw9MqI&list=PLowKtXNTBypH19whXTVoG3oKSuOcw_XeW&index=3 See https://www.youtube.com/watch?v=8BhjXqw9MqI&list=PLowKtXNTBypH19whXTVoG3oKSuOcw_XeW&index=3
and following videos by awesome Ben Eater and following videos by awesome Ben Eater
Very slow and inefficient implementation, meant only to be clear and didactic Very slow and inefficient implementation, meant only to be clear and didactic
@ -32,7 +32,7 @@ NAME = 'manchester-decoder'
VERSION = '0.1' VERSION = '0.1'
DESCRIPTION = 'Decodes a file using the manchester encoding' DESCRIPTION = 'Decodes a file using the manchester encoding'
FRAME_DELIMITER = 129 # (1, 0, 0, 0, 0, 0, 0, 1) FRAME_DELIMITER = 126 # (01111110)
PREAMBLE_DURATION = 128 PREAMBLE_DURATION = 128
ZERO_POINT = 0 # The 0 value: values less than this are considered 0, more than this 1 ZERO_POINT = 0 # The 0 value: values less than this are considered 0, more than this 1
@ -49,7 +49,7 @@ class Main:
try: try:
self.syncWithClock() self.syncWithClock()
self._log.info("Sync: clock duration is {}".format(self.clockDuration)) self._log.info("Sync: clock duration is {}".format(self.clockDuration))
#self.waitForStart() self.waitForStart()
#self._log.info("Found start of data") #self._log.info("Found start of data")
except ValueError as e: except ValueError as e:
self._log.error("Ran out of input data before completing initialization!") self._log.error("Ran out of input data before completing initialization!")
@ -60,37 +60,34 @@ class Main:
def syncWithClock(self): def syncWithClock(self):
# Uses the preamble to obtain the clock duration # Uses the preamble to obtain the clock duration
i = 0 analyzedCycles = 0
while True: while True:
(duration, value) = self.readRawBit() (cycles, raising) = self.goToNextZeroCrossing(True)
analyzedCycles = analyzedCycles + cycles
print(duration, value) if analyzedCycles > PREAMBLE_DURATION * self.clockDuration / 4:
# At this point we should have an idea of the clock duration, move on
if i < PREAMBLE_DURATION/4: return
# First cycles are used to extimate clock duration
self.clockDuration = self.clockDuration + duration / 2
i = i + 1
# We are now synced with clock. Add a 25% "slack" to make following samples at
# 1/4 and 3/4 to clock cycle: remember that every cycle has 2 values: 01 if the
# encoded bit was 1 and 10 if it was 0
for x in range(0, int(self.clockDuration / 4)):
self.readRawBit()
def waitForStart(self): def waitForStart(self):
# After the clock has been extimated, continue reading and wait for first delimiter # After the clock has been extimated, continue reading and wait for first delimiter
lastByte = 0 lastByte = 0
while True: while True:
value = decodeBit() value = self.decodeBit()
# Shift the byte to left and add the read bit in the least significant position # Shift the byte to left
lastByte = lastByte << 1 lastByte = lastByte << 1
# Truncate the length to 8 bits
lastByte = lastByte & 255 # 0 11111111
# Add the read bit in the least significant position
if value: if value:
lastByte = lastByte + 1 lastByte = lastByte + 1
if lastByte == FRAME_DELIMITER: if lastByte == FRAME_DELIMITER:
# Found frame delimiter! We are in sync! YAY! # Found frame delimiter! We are in sync! YAY!
self._log.info("Found first frame delimiter")
return return
#def decodeFile(): #def decodeFile():
# From the bit after the FRAME_DELIMITER on, there is the real data. Decode and write to file # From the bit after the FRAME_DELIMITER on, there is the real data. Decode and write to file
@ -98,33 +95,25 @@ class Main:
#except ValueError as e: #except ValueError as e:
# Completed reading file # Completed reading file
def readRawBit(self):
# Reads a raw bit. Returns the duration of that bit and the value.
duration = 0
prev = None
while True:
frame = self.audioSource.readframes(1)
if not frame:
raise ValueError('No more data to read')
v = int(struct.unpack('<h', frame)[0]) > ZERO_POINT
if prev is None:
prev = v
else:
# Detect zero crossing
if prev < ZERO_POINT and v > ZERO_POINT:
break
if prev > ZERO_POINT and v < ZERO_POINT:
break
prev = v
duration = duration + 1
return (duration, prev > ZERO_POINT)
def decodeBit(self): def decodeBit(self):
# Decodes a bit. Searches for the phase invertion at 75% to 125% of the clock cycle
bitDuration = 0
while True:
(duration, raising) = self.goToNextZeroCrossing(False)
bitDuration = bitDuration + duration
if bitDuration < self.clockDuration * 0.75:
# Ignore: half-cycle crossing due to two equal digits one near the other
continue
if bitDuration > self.clockDuration * 1.25:
# Lost tracking!
raise Exception("Lost tracking! No phase inversion found between {} and {} samples from the last one".format(self.clockDuration * 0.75, self.clockDuration * 1.25))
# This is our phase inversion signal
return raising
# Reads and decodes 2 raw bits into 1 decoded bit. 01 (raising) = 1, 10 (falling) = 0 # Reads and decodes 2 raw bits into 1 decoded bit. 01 (raising) = 1, 10 (falling) = 0
# Works only once clock is synced # Works only once clock is synced
# The bits are read at 1/4 and 3/4 of clock cycle # The bits are read at 1/4 and 3/4 of clock cycle
@ -147,14 +136,32 @@ class Main:
# Lost sync!!! # Lost sync!!!
raise Exception() raise Exception()
def goToNextZeroCrossing(self, adjustClockDuration):
# Find the next zero crossing and returns:
# (cycles since last inversion, True if is raising, False if is falling)
cyclesSinceLastInversion = 0
prev = None
while True:
frame = self.audioSource.readframes(1)
if not frame:
raise ValueError('No more data to read')
v = int(struct.unpack('<h', frame)[0]) > ZERO_POINT
if prev == None:
prev = v
if v != prev:
# Zero-point crossing!
if adjustClockDuration:
self.clockDuration = (self.clockDuration + cyclesSinceLastInversion) / 2
return (cyclesSinceLastInversion, v)
cyclesSinceLastInversion = cyclesSinceLastInversion + 1
if __name__ == '__main__': if __name__ == '__main__':
import argparse import argparse

View File

@ -32,7 +32,7 @@ NAME = 'manchester-encoder'
VERSION = '0.1' VERSION = '0.1'
DESCRIPTION = 'Encodes a file using the manchester encoding and outputs it as audio file' DESCRIPTION = 'Encodes a file using the manchester encoding and outputs it as audio file'
FRAME_DELIMITER = 129 # (1, 0, 0, 0, 0, 0, 0, 1) FRAME_DELIMITER = 126 # (01111110)
PREAMBLE_DURATION = 128 PREAMBLE_DURATION = 128
AUDIO_VOLUME = 16384 # 0 to 32767 AUDIO_VOLUME = 16384 # 0 to 32767
AUDIO_BITRATE = 44100 AUDIO_BITRATE = 44100
@ -69,23 +69,23 @@ class Main:
if not byte: if not byte:
# Finished reading file # Finished reading file
# Terminate with delimiter and exit # Terminate with delimiter and exit
self.encodeByte(FRAME_DELIMITER) self.encodeByte(FRAME_DELIMITER, encode_frame_delimiter=False)
break break
byte = byte[0] byte = byte[0]
# Every 64 bytes, outputs a frame delimiter: 01111110 # Every 64 bytes, outputs a frame delimiter: 01111110
# This is used by receiver to syncronize to the start of a byte # This is used by receiver to syncronize to the start of a byte
if position % 64 == 0: if position % 64 == 0:
self.encodeByte(FRAME_DELIMITER) self.encodeByte(FRAME_DELIMITER, encode_frame_delimiter=False)
position = position + 1 position = position + 1
# Encode byte # Encode byte
self.encodeByte(byte) self.encodeByte(byte)
self.audioSink.close() self.audioSink.close()
self._log.info('Completed') self._log.info('Completed')
def encodeByte(self, byte): def encodeByte(self, byte, encode_frame_delimiter=True):
# Encodes a byte with the Mancester Encoding # Encodes a byte with the Mancester Encoding
# Note that the byte is read from the most important to the least important bit # Note that the byte is read from the most important to the least important bit
# Es: 10000010 is not 130, but 65 # Es: 10000010 is not 130, but 65
@ -98,13 +98,13 @@ class Main:
# Write bit # Write bit
self.encodeBit(lastBit) self.encodeBit(lastBit)
# If we have 5 consecutive "1", add a 0 after, to avoid being interpreted as a frame delimiter # If we have 5 consecutive "1", add a 0 after, to avoid being interpreted as a frame delimiter (01111110))
if lastBit: if lastBit:
consecutiveOnes = consecutiveOnes + 1 consecutiveOnes = consecutiveOnes + 1
else: else:
consecutiveOnes = 0 consecutiveOnes = 0
if consecutiveOnes == 5: if consecutiveOnes == 5 and encode_frame_delimiter:
self.encodeBit(0) self.encodeBit(0)
consecutiveOnes = 0 consecutiveOnes = 0
@ -116,7 +116,7 @@ class Main:
def encodeBit(self, bit): def encodeBit(self, bit):
# Encodes a single bit in a pair of bits to be written on the media. # Encodes a single bit in a pair of bits to be written on the media.
# The "1" is encoded as a transition from 0 to 1 (01) while the "0" is endoded as a # The "1" is encoded as a transition from 0 to 1 (01) while the "0" is endoded as a
# transition from 1 to 0 (10) # transition from 1 to 0 (10)
if bit: if bit:
self.out(0) self.out(0)
@ -132,7 +132,7 @@ class Main:
value = AUDIO_VOLUME value = AUDIO_VOLUME
else: else:
value = -AUDIO_VOLUME value = -AUDIO_VOLUME
# The duration of the signal is calculated based on the user-specified clock speed # The duration of the signal is calculated based on the user-specified clock speed
duration = int(AUDIO_BITRATE / self.clock) duration = int(AUDIO_BITRATE / self.clock)
for x in range(duration): for x in range(duration):