WebUSB / Headphone connection to Espruino

Posted on
Page
of 2
/ 2
Next
  • Well it seems like it wouldn't be a day of the week if I wasn't requesting some crazy feature for Espruino, but I'm wondering @Gordon if you would consider adding support for WebUSB? Arduino implementation:

    https://github.com/webusb/arduino

  • There's been some talk about it, but it's quite a lot of work. My reasoning for not attempting it is:

    • It's Chrome only afaik, there's been very little uptake as far as I can see and very little work on it recently by the Chrome team, so Google may just kill it
    • It requires me to re-work Espruino's USB support, as far as I can tell this would stop all existing serial-port based methods of communicating with Espruino from working and make me totally reliant on WebUSB or native apps
    • In order to update firmware over WebUSB, the bootloader would need updating, which is non-trivial and definitely couldn't be done via WebUSB
    • In order to use WebUSB you'd need a computer and software that had access via serial to update the firmware - in which case why not use that?

    So for me, it's not looking good. Urish has made a web bluetooth polyfill for Chrome on windows which uses a plugin API (https://github.com/urish/web-bluetooth-p­olyfill). IMO I'd be much better off making my own plugin for each major platform that handled serial, since the plugin API seems to work on a few different browsers. It'd stop me getting in this stupid situation where I'm dependent on one browser maker.

  • Bummer didn't realise it would be so difficult I assumed it was basically just setting https://wicg.github.io/webusb/#webusb-pl­atform-capability-descriptor and away we go.

    My use case was not to use it for flashing code to the board but rather being able to connect to the Espruino though a web-ui in Chrome and be able to update files on the SD card. This would let me have someone configure the Espruino WiFi with their WiFi credentials via a web-app and then send this info into the Espruino.

  • Sadly I don't think it's that easy - if the operating system has recognised the device as a serial port then it'll 'claim' it and create a serial port device with it.

    Once that's happened, I don't think Chrome will be able to get access to it, even if it thinks it's a Web USB device... So you can have one, or the other. I could create a separate set of USB endpoints just for chrome, but it starts to get really scary at that point.

    I could be wrong about that, but that's the distinct impression I got when I looked into it.

    If I created a bunch of plugins for different platforms and a single library you could use to get serial access, that might even work better for your use case though?

  • Unfortunately I don't think I could get away with people installing plugins, it was something that would have to work with absolutely zero effort.

    I have a final idea involving the web audio api and the ADC's on the Espruino but its such a bad idea...

  • It's a really annoying situation. Electric Imp devices used to have a light sensor, and would listen to a certain pattern of flashes on the screen of whatever device was trying to communicate with them.

    That could work out pretty well for small amounts of data, and could then be used straight from any device, without setup?

  • Well, whaddaya know, you've already done what I was gonna do:

    http://www.espruino.com/Headphone

    this will work fine :D

  • Yep - the only gotcha is that some phones seem to have the audio wired backwards, but it's not a big deal. Just do it once one way, and again the other... Or if you're doing two-way comms you can just check and see which option works.

  • @Gordon just realised I can't really use audio-to-serial directly as my WiFi pins for USART are taken up with something else.

    I see there is code for going from audio-to-serial https://espruino.github.io/EspruinoOrion­/serial_to_audio.js, do you have anything for using the ADC pins and analogRead to get data out of an audio signal?

  • You should be able to use normal digital pins and pinMode(pin,"input_pullup") - then you can use setWatch. Rather than trying to use the RS232 serial protocol, you could actually just send a series of pulses - a long pulse for 1 and a short for 0... That way you don't have to worry about the polarity of the input signal.

    If you did want to use software serial there's some code for it here though: https://github.com/espruino/Espruino/iss­ues/549#issuecomment-175083164

  • @Gordon hmm, I've tried setting the pinMode but I'm not getting any response out of setWatch. I've used various tone generators but nothing ever seems to trigger.

    setWatch(function(e) { console.log(e.time-e.lastTime); }, A5, { repeat:true, edge:'falling' });
    

    I'm relatively sure my circuit is right, can you think of anything I might be missing?


    1 Attachment

    • IMG_20170801_153709066.jpg
  • Maybe you could check the voltage of RX (A5) relative to GND (in fact you might just be able to use analogRead) - it should be somewhere around 1.6v (or 0.5 on analogRead).

    It might also be worth giving a single-direction one a go first? It might be your phone/PC that's confused about the type of device that's plugged in - and using a standard 3 wire headphone jack would remove that possibility I guess.

  • I got the guys in the shop to look at it with an oscilloscope, there is definitely voltage on the line, with about 1.5v swing.

    analogRead without pinMode set (i.e after a reboot):

    setInterval(function () { console.log(analogRead(A5)); }, 1000);
    =1
    0.39795529106
    0.28686961165
    0.27002365148
    0.42871747920
    0.01000991836
    0.13598840314
    0.16748302433
    0
    0
    0
    0


    1 Attachment

    • IMG_20170801_163221336.jpg
  • The voltage swing is good, but as far as I can tell from that trace (I'm not 100% sure where 0v is on the scope) the 'resting' voltage is 0v, so it's oscillating between -0.7 and 0.7v...

    That sort of implies that the pullup on the pin isn't working for some reason? Ideally it should sit at around 1.6v, so would go between 0.9v and 2.3v depending on the signal.

  • Well I guess when I measured it on the scope it wasn't connected to the Espruino so the internal voltage pullup wouldn't have been in effect.

    I'm guessing on A5 I would need to call pinMode to set "input_pullup"? Like I said these tests were after a reboot so whatever the default is would be set.

  • Ahh, yes. You'd need it connected to the Espruino, and with the pullup. That's the important time to measure the voltage :)

  • I'll try and do some more tests tomorrow

  • Ok so here our resting voltage is 0v, and with the pullup set, we're getting 1v - 2.5v.

    Espruino says:

    0.00585946440
    0.00585946440
    0.00585946440
    0.00585946440
    0.00585946440
    0.00585946440
    0.00585946440
    0.00585946440
    0.00585946440
    0.00585946440
    0.00585946440
    0.00585946440
    0.00585946440
    0.00585946440
    0.00585946440

    when using analogRead(A5)

    I've gotta be doing something stupid here...


    1 Attachment

    • IMG_20170802_084304577.jpg
  • If I plug A5 into Espruino 3.3v output, I get the following:

    analogRead(A5);
    =0.99928282597

    If I then set the pinmode...

    pinMode(A5, 'input_pullup');
    =undefined
    analogRead(A5);
    =0.00585946440

  • Ok if I use digitalRead I get sensible values. And now setWatch is magically working...

  • Ahhh the secret is a square wave not a sine wave.

  • Thinking about it, analogRead may be a bit of a bad start... I seem to recall that moving to reading analog values removes the pullup on STM32 devices.

    I've just looked up the IO pin characteristics again, and I get the attached. It looks like you're just on the edge of the acceptable input voltage for a 0 reading at 1v. Please could you try using a lower value resistor for the pull-down? You've got 47k at the moment, but something like a 33k or 39k resistor might just get the voltage down enough that a '0' was registered.

    It's odd because it had worked for me, but there's some variance in all the components and I guess this time it was just too close to work... If it works I'll update the resistor values in the docs.

    When you get that done, just try something simple like:

    pinMode(A5, 'input_pullup');
    setWatch(function(e) { console.log(e); }, A5, { repeat:true, edge:'both' });
    

    1 Attachment

    • Screenshot at 2017-08-02 10-35-16.png
  • @Gordon I'm getting fairly reliable readings now.

    I guess my next question is how do I generate sounds of varying length. Out of my depth here I think.

    I thought that this would generate 0.5seconds of...something, and then 0.5 seconds of silence, but I just get a click

    AudioContext = window.AudioContext || window.webkitAudioContext;
    audioContext = new AudioContext();
    
    gainNode = audioContext.createGain();
    gainNode.gain.value = 1;
    
    var buffer = audioContext.createBuffer(1, 44100, 44100);
    var b = buffer.getChannelData(0);
    
    for (var i = 0; i < 22050; i++) {
      b[i] = 1.0;
    }
    
    for (var i = 22050; i < 44100; i++) {
      b[i] = -1.0;
    }
    
    source = audioContext.createBufferSource();
    source.buffer = buffer;
    source.connect(gainNode);
    source.start();
    
    gainNode.connect(audioContext.destinatio­n);
    
  • I think the click comes from the transition from 1 to -1 (and maybe at the beginning and end too), but basically that's all you need to do - just outputting -1 usually with bursts of 1 of varying lengths.

    Doing something like b[i] = Math.sin(i); should at least give you something audible to test with though?

  • So after a bit of fiddling, I've come up with the following that seems to work. I'm sure this has been done before and in a better and more reliable way, but this is what I have.

    PC Sound Generator:

    AudioContext = window.AudioContext || window.webkitAudioContext;
    audioContext = new AudioContext();
    
    gainNode = audioContext.createGain();
    gainNode.gain.value = 1;
    
    var buffer = audioContext.createBuffer(1, 44100, 44100);
    var b = buffer.getChannelData(0);
    
    function fillAt(arr, val, at, len) {
      arr.fill(val, at, at+len);
    }
    
    function charToBinary(char) {
      var bin = char.charCodeAt(0).toString(2);
      return new Array(9 - bin.length).join('0') + bin;
    }
    
    b.fill(-1.0);
    
    function writeBinary(offset, binary) {
      if (!binary) {
        binary = '00000000';
      }
      
      var data = binary.split('');
      // Bit preamble
      fillAt(b, 1.0, offset+4, 2);
      fillAt(b, 1.0, offset+300, 2);
      // Data
      data[0] === '1' && fillAt(b, 1.0, offset+304, 2);
      data[1] === '1' && fillAt(b, 1.0, offset+308, 2);
      data[2] === '1' && fillAt(b, 1.0, offset+312, 2);
      data[3] === '1' && fillAt(b, 1.0, offset+316, 2);
      data[4] === '1' && fillAt(b, 1.0, offset+320, 2);
      data[5] === '1' && fillAt(b, 1.0, offset+324, 2);
      data[6] === '1' && fillAt(b, 1.0, offset+328, 2);
      data[7] === '1' && fillAt(b, 1.0, offset+332, 2);
      // Bit postamble
      fillAt(b, 1.0, offset+336, 2);
      fillAt(b, 1.0, offset+632, 2);
      
      return offset+632;
    }
    
    function writeString(str) {
      var chars = str.split('');
      var lastOffset = 0;
      
      for(var i = 0; i < chars.length; i++) {
        var char = chars[i];
        var bin = charToBinary(char);
        lastOffset = writeBinary(lastOffset, bin);
      }
    }
    
    // Stream start
    fillAt(b, 1.0, 0, 2);
    
    writeString('abcdefghijklmnopqrstuvwxyz'­);
    
    
    source = audioContext.createBufferSource();
    source.buffer = buffer;
    source.connect(gainNode);
    gainNode.connect(audioContext.destinatio­n);
    source.start();
    
    

    Espruino Receiver:

    let readBits = false;
    let bits = [];
    
    function handlePulse(e) {
      const diff = e.time - e.lastTime;
    
      //console.log(diff);
      
      if (diff > 1.0 || isNaN(diff)) {
        if (readBits) {
          //console.log('stop reading bits');
          readBits = false;
        }
        return;
      }
    
      const zeroes = (diff.toFixed(4) * 10000).toFixed(1);
    
      if (zeroes > 50) {
        if (readBits) {
          //console.log('stop reading bits');
          readBits = false;
          bits.pop();
          
          //console.log(bits);
          var str = bits.join('');
          var num = parseInt(str, 2);
          var chr = String.fromCharCode(num);
          console.log(chr);
          bits = [];
        } else {
          //console.log('start reading bits');
          readBits = true;
        }
        
        return;
      }
    
      if (readBits) {
        //console.log(diff);
        //console.log(zeroes);
    
        for(var i = 0; i < zeroes; i++) {
          bits.push(0);
        }
    
        bits.push(1);
      }
    }
    
    function onInit() {
      pinMode(A5, 'input_pullup');
      setWatch(handlePulse, A5, { repeat: true, edge: 'rising' });
    }
    

    So now my question @Gordon, is how do I reverse this? i.e generate sound on Espruino. I'm guessing I want to just be doing digitalWrite to the pin I have wired to my headphone socket, can I reuse any of the code I have or should I start again?

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

WebUSB / Headphone connection to Espruino

Posted by Avatar for dave_irvine @dave_irvine

Actions