Wednesday, 7 August 2013

Data into smartphone and tablets via audio

There's not been much activity on the blog recently, as we've been spending most evenings for the last few weeks working on a data-over-audio approach to communicate with smartphones, tablets and iPads/iPhones without the need for expensive Apple-approved hardware.
It's proven more difficult that we first anticipated: not because what we were trying to do was inherently wrong, but because of the different microphone/hardware jacks on all the different devices.

Let's rewind back to the start and try to explain what the idea was:

Firstly, we wanted to generate a tone on a microcontroller, which could be decoded on any device, preferably using the Flash/AIR development kit. Flash allows us to  "parse" incoming audio data, by presenting the captured audio as a byte array.

Our first attempt was to use the simplest of audio-to-date, similar to NZR.
We simply held the output line high for a number of microseconds to represent a one bit, and pulled it low to represent a zero.


Because microphone input levels are usually in the range 0 +/- 1v, we put a simple voltage divider on our PIC output pin, to bring the voltage down from 5v to about 1v.
We included a potentiometer so that there was a basic form of volume control on the output going to the device. For testing, we directed all audio to the microphone jack on the development PC.

This crude, but simple set up worked, in a fashion. We loaded Audacity onto the laptop and simply displayed the captured audio data on-screen. Everything looked fine; until there was a long period of 0xFF data (all ones)

The input signal stayed high for a few microseconds, then faded away to zero.
After some digging around on the internet, we found that most devices include an inline, blocking capacitor on the input mic, to protect the device from over-voltage.

So while our simple design worked for signals that were switching quickly, we couldn't guarantee that it would work for certain data values. And having a system that only works for a particular range of values is a bit feeble, so we had to think again.

A bit more digging around and we uncovered the idea of "manchester encoding". This is the encoding method used by most industry standard equipment. Simply put, a bit value of one is actually transmitted as 01, and a bit value of zero is 10.

This approach ensures that the input signal is always changing and that the voltage never stays high for more than just a few microseconds.


The data before and after each bit may change the overall wavform of the data being sent, but for each bit, there will be a low-to-high transition for a bit value of 1 and a high to low transition for a bit value of zero. So far, so good. We threw together some simple firmware for testing this approach.

Since we're going to be capturing data at 44.1khz, we worked out that each sample length would be 1/44100 = 0.00002267573 seconds (i.e. roughly 0.023ms or 23uS). Because the figures don't divide exactly, over time we're expecting to get some "framing errors" - i.e. one sample may be slightly too long, or slightly too short once a number of samples have been played.
To get around this problem, instead of one sample being either high or low, we're going to use multiples of 8 samples. This means that the data is being sent much more slowly, but does allow for a degree of error correction when decoding the incoming data.

Define CONF_WORD = 0x3f82
Define CONF_WORD_2 = 0x3fbc
Define CLOCK_FREQUENCY = 20

Dim i As Byte
Dim j As Byte
Dim k As Byte
Dim tmp As Byte
Dim bytestosend(5) As Byte
Dim bytepointer As Byte
Dim bitpointer As Word
Dim delayus As Byte
Dim pin_direction As Byte

Symbol audio_pin_out = PORTB.7

AllDigital
Config PORTB = Output
Config PORTB.5 = Input
Config PORTB.4 = Input
OPTION_REG.7 = 0 'turn on portb pullups

init:
      i = 0
      k = 0
      bytepointer = 0
      bitpointer = 0

      bytestosend(0) = 0 '00000000
      bytestosend(1) = 11 '00001011
      bytestosend(2) = 04 '00000100
      bytestosend(3) = 19 '00010011
      bytestosend(4) = 75 '01001011

      delayus = 125 '46=22khz, 23=44.1khz etc., 125=8khz
      pin_direction = 0

loop:
      If PORTB.5 = 0 Then
            WaitMs 10
            If PORTB.5 = 0 Then
                  Gosub senddata
                  While PORTB.5 = 0
                        'do nothing
                  Wend
            Endif
      Endif

Goto loop
End

senddata:
      'using manchester encoding
      'start with the line high
      High audio_pin_out
      WaitUs delayus

      For i = 0 To 5
            tmp = bytestosend(i)
            For j = 0 To 7
                  k = tmp And 1

                  'for some devices, the signals are inverted
                  If PORTB.4 = 1 Then k = k Xor 1

                  If k = 1 Then
                        'manchesterOne
                        Low audio_pin_out
                        WaitUs delayus
                        High audio_pin_out
                        WaitUs delayus
                        pin_direction = 1
                  Else
                        'manchesterZero
                        High audio_pin_out
                        WaitUs delayus
                        Low audio_pin_out
                        WaitUs delayus
                        pin_direction = 0
                  Endif
                  tmp = ShiftRight(tmp, 1)
            Next j
      Next i


      'return the output pin high
      High audio_pin_out

Return

With the firmware written up and the PIC programmed, all the remained was to capture the audio in Flash and decode it into meaningful data..........