• Thr 2019.08.01

    Seeking technique suggestions here. Individually, manual toggle of lines allows correct result from one pass of setWatch().

    Continuation of threads:

    Espruino Wifi as USB Master, Pico as USB slave - is that possible?
    Does pinMode 'input' state place pin in tri-state mode

    Goal: After CS Chip Select goes low, on rising edge of SCK clock line, read state of MOSI



    Setup: Espruino MDBT42Q as master sending software SPI to Espruino WiFi as slave

    Data sent inside a setInterval() creating a CS pulse Low of ~200msec surounding 4 x 8bits of ~300usec clocked at 3usec:

    Limited code for brevity

    spi.setup( { mosi:D27, miso:D26, sck:D25, baud:100000 } );
      ...
      digitalWrite(PIN_CS,0);
    
      var val = spi.send([0xC0,0xF4,3,4]);
    
      digitalWrite(PIN_CS,1);
    

    Verified: Using logic analyzer and protocol decoder am able to see and verify CS, SCK, MOSI, MISO and data as placed on MOSI

    Individually, the setWatch() definitions execute as expected.

    // WiFi
    const MOSI = B5;  // pin 17
    const MISO = B4;  // pin 16
    const SCK  = B3;  // pin 15
    const CS   = B1;  // pin  7
    
    
    setWatch(function(e) { 
      // watchdog to prevent runaway
      if( countWatch >= 10 ) clearWatch();
      countWatch++;
      console.log(e.time-e.lastTime);
      }, B3, { repeat:true, edge:'falling' }
    );
    
    
    
    read:  0.00000286102
    calc:  SPI 100000 1usec pulse 2usec  500KHz
    
    

    The idea is to only allow the second setWatch() to run after tripped by the setWatch() monitoring CS

    var state;
    
    //Start first: setw()
    
    //Chip Select detect
    function setcs() {
      setWatch(function(e) { 
        console.log(e.time-e.lastTime);
    
        // new attempt to signal 2nd setWatch()
        state = 0;
    
    //ABANDONED as second setWatch() always returns 0's
    //    var state = digitalRead( CS );
    //    if( !state )
    //      fetchBit();
    
        }, B1, { repeat:true, edge:'falling' }
    //    }, B1, { repeat:true, edge:'rising' }
      );
    }
    
    
    
    
    // Monitor clock line
    function setw() {
      setWatch(function(e) {
        // watchdog to prevent runaway
        if( countWatch >= 10 ) clearWatch();
        countWatch++;
    //  console.log(e.time-e.lastTime);
    
    //    var state = digitalRead( CS );
        if( !state )
          fetchBit();
    
    //    }, B3, { repeat:true, edge:'falling' }
        }, B3, { repeat:true, edge:'rising' }
      );
    }
    

    When I manually set the state of MOSI using

      digitalWrite(MOSI,0);
      digitalWrite(MOSI,1);
    
    
    
    function fetchBit() {
      var val = 0x00;
      val = digitalRead( MOSI );  // B5
      aryByteCmd[ elemBits++ ] = val;
        ...
    
    

    function fetchBit() reads and stores manually, correctly.

    Suspicion: A timing issue, starting technique or a different means to trigger the second setWatch() is needed.

  • Aside: You can pass in the CS pin to the send command like var val = spi.send([0xC0,0xF4,3,4], PIN_CS);

    Also, the setWatch docs say you can ask for the state of another pin, so you don't have to read it (might matter, as you don't have to spend the time to read it):

    // Advanced: If specified, the given pin will be read whenever the
    watch is called // and the state will be included as a 'data' field
    in the callback data : pin

    I don't completely understand the snippets, for example, you don't call setw from B1's setWatch, but that call should be there, right?

    Why not just set up one setWatch once on the SCK pin, and ignore if the CS is not low? Ok, depends on how many other slaves are there, and how much CPU the watch uses...

    So I guess you could

    • use 1 setwatch on the SCK pin, pass the MOSI in as data
    • in the watch, check the state of CS, if not 0, just exit
    • process the data as you wish :)
  • Putting a watch on a (fast) clocked pin (such as SPI SCK) is the recipe for Espruino's interrupt / event queue / FIFO overflow...

  • After 10 clock cycles you would remove all watchers with clearWatch(); if you would have called setw(); to set a watch.

  • Fri 2019.08.20

    'I don't completely understand the snippets'

    It was extremely late as I posted the above, and not proud of it's brevity. Did so, as had I waited till morning for a more complete post, most that might respond would already be heading for bed in their timezone.

    'After 10 clock cycles you would remove all watchers'

    Yes that is it's intention as my note there indicates. There is no way to Ctrl+C out of this, forcing a WebIDE reboot at these speeds.

    'You can pass in the CS pin to the send command like '

    Thanks for that reminder. In this proof on concept phase, just trying to see if I can detect at least a few bits correctly.

    'how many other slaves are there, and how much CPU'

    Right now just proof of concept with two micros.

    'Why not just set up one setWatch once on the SCK pin'

    There are a bazillion pulses. I'm sure overflow would occur then. Also, since the master controls the clock, how does the slave know when to record which bit for which byte? I was relying on the rising edge detection of setWatch()

    'So I guess you could' (with the three steps that follow)

    The two functions are effectively doing just that. It is difficult to comprehend what the actual code execution is going on, when in single step mode it appears to work, but at full speed, the wrong data is returned. Maybe the SCK pulse width is just too narrow for setWatch()?


    'recipe for Espruino's interrupt / event queue overflow'

    As there are only 32, I felt that starting after CS goes low, might not be an issue. But as the responses are all zero during this sampling, it is quite possible that, that is in fact what is going on. Any idea on how to check for IRQ issues?



    I'll continue to try slowing down the SPI bus, but don't know how slow below the default is reliable?

  • Any idea on how to check for IRQ issues?

    Get the current time, call the function, get the current time and calculate the duration. If it's slower than your clock there's a problem for sure.

  • The Javascript getTime() function seems to be ~100 times slower than the pulse sampling e.lastTime using setWatch(), which seems to be able to send the correct time duration.

    I was really after is there a way to check the IRQ register for flag detail.

  • You're almost certainly hitting timing issues because Espruino doesn't execute JS code in an interrupt.

    However there's a solution to this. Espruino's setWatch provides a data pin argument, which samples the data pin in the interrupt and writes the result into the queue along with the event:

    https://www.espruino.com/Reference#l__gl­obal_setWatch

    setWatch(function(e) {
      print(e.data);
    },CLK_PIN,{edge:"rising", data:MOSI_PIN, repeat: true});
    

    And in this particular case I wouldn't do a setWatch in a setWatch either - just leave them both running:

    var isActive = false;
    setWatch(function(e) {
      isActive = !e.state;
    },CS_PIN,{edge:"both", repeat: true});
    setWatch(function(e) {
      if (isActive) print(e.data);
    },CLK_PIN,{edge:"rising", data:MOSI_PIN, repeat: true});
    

    Because they're both running, events will be put in the queue in the correct order. Even though they are not processed immediately everything will still work great.

    HOWEVER this will only work for small packets of SPI data. Anything with >100 clock cycles will fill up the input FIFO before Espruino is able to process it.

  • Fri 2019.08.02

    Thank you @Gordon for the explanation, as I'm sure that will help others also. I knew I was on the right thought/concept, just didn't have the know-how for coding the solution. Your snippet is concise and self explanatory. Will work on it/study this weekend.

    Any idea of the fastest pulse, shortest duration that might be acceptable for setWatch() to that 100 event limit? These are tid-bits that IMHO should be added to the docs.

  • Sun 2019.08.04

    'HOWEVER this will only work for small packets of SPI data'

    Only have a need for four bytes, around 32 edges, so shouldn't be an issue.


    @Gordon, would it be possible to provide the option to allow slower than the SPI 100Kbit default rate? How difficult/time frame would that be to implement with your already over limit demands? fingers crossed


    The snippet in #8 works as outlined, but, and a big butt at that, ;-) not at the default SPI speeds. setWatch() not responsive enough.

    From:

    http://www.espruino.com/SPI
    "Pretty much all SPI devices are guaranteed to support 100kBits/sec transfer speed, so that is the default in Espruino. However you can specify higher speeds with bitrate in SPI.setup if your device supports it, eg:"

    and

    "Note: baud rate is ignored for software SPI - it will always send as fast as possible."



    Made several attempts to slow SPI by dividing 100000 by 10's down as far as 5000, but the result is the same. The slowest clock pulse is around 3usec 333KHz or the same as the default for both hardware and software SPI.

    I ended up writing a Javascript pin toggler a slow SPI emulator that clocks out 1000 times slower, so that I could verify added code to the snippet you provided. Data is solid and the slave is able to absorb at that slow speed. The fastest that Javascript would eventually run is limited by how fast setInterval can be called, and that appears to be around 1.8msec so two pulses at 2msec 0.004 is 250Hz. So using Javascript, that is limited to around 1000 times not fast enough to approach the current SPI default setting implementation.

    The source I have cobbled together runs at 2 pulses 0.01 10msec or 100Hz

    When clocked out of the MDBT42Q using the only available software SPI at the default, which is the slowest SPI will clock data, pulses are counted but the bits are not as they should be. I'm going to play with the clock mode setting tomorrow, but even inverting the bits doesn't come close the the data sent.

    baud:100000
    60usec for two bytes   15KHz
    SCK   3usec    333KHz
    

    Pin wiring

          MDBT         Pulse View          WiFi
     Pin Label  Name   Color  PV     Name     Label   Pin
      1   D25   SCK     Blu   D6   SPI3.SCK    B3     15
      2   D26   MISO    Yel   D4   SPI3.MISO   B4     16
      3   D27   MOSI    Grn   D5   SPI3.MOSI   B5     17
      4   D28   CS      Brn   D1   SS          B1      7
    

    Found the send cmd bool jsspiSend() but don't know where to find object spi_sender

    https://github.com/espruino/Espruino/blo­b/master/src/jsspi.c#L210

    This proof of concept does indeed show the WiFi running as a slave, at 1000 times slower but only as true SPI appears to be too fast for setWatch() to monitor.

    Will mess with clock mode and attempting to speed up the Javascript.

    Cleaning up both code modules to upload shortly (late Monday). . . .

  • Ok, so this is with MDBT42Q as the master? I don't believe the hardware on the MDBT42Q is capable of going below 125kHz, at least according to the datasheet.

    You could try poke32(0x40003524, 0x01000000) which should be 62.5kHz (and if that works, try lower numbers in the second argument).

    On the STM32 the hardware should be capable of going lower though so if you used that as a master you'd be sorted.

    I'm afraid that right now software SPI doesn't implement a programmable baud rate - it's so rare that you wouldn't push SPI as fast as you can that it just wasn't worth it.

    You could try:

    var MOSI=D27;
    var SCK=D25;
    var CS=D28;
    
    function send(data) {
      var p=SCK.set.bind(SCK);
      var n=SCK.reset.bind(SCK);
      var w=MOSI.write.bind(MOSI);
      CS.reset();
      data.forEach(function(d) {
    w(d&128)p()n()
    w(d&64)p()n()
    w(d&32)p()n()
    w(d&16)p()n()
    w(d&8)p()n()
    w(d&4)p()n()
    w(d&2)p()n()
    w(d&1)p()n()
      });
      CS.set();
    }
    
    send([0,1,2]);
    

    Which should implement software SPI in JS in a reasonably fast way - I just tried it and I'm getting around 2kHz

    ... but as I'd mentioned in other threads, if you want to do device->device comms, UART is so much easier and less trouble - and it uses half as many pins.

  • Mon 2019.08.05

    'UART is so much easier and less trouble'

    Project goal to push EspruinoWiFi for end users. Current board layout already has SPI for a separate slave. The other micro, short on available pins, will act as a slave initially, will need to act as a master periodically. The above proof of concept works, but at much too slow a speed. Goal to stay between 100000-800000 bits/sec.


    setWatch() on WiFi as slave isn't picking up bits accurately at 100000, but does around 5000+



    Thank you for promptly responding @Gordon. You have validated some additional questions I had.

    'On the STM32 the hardware should be capable of going lower '

    My initial testing is showing this isn't the case. I'll re-check that, but the reason to use my initial observation to use the MDBT42Q as the master.

    'You could try poke32(0x40003524, 0x01000000)'

    New learning there, but will look into. The real issue isn't necessarily the need to change the clock speed, but to understand why setWatch() becomes unreliable at top speed.

    'software SPI doesn't implement a programmable baud rate'

    No need to panic, really not needed. Just needed a method to be able to get at the setWatch() issue. Can only go so fast using Javascript, and at lowest default SPI speed too fast for setWatch() to handle.

    One thought I had using software SPI, is whether the clock width is remaining stable. There is a bit of jitter using the logic analyzer, and wondered if that might be causing setWatch() to misbehave. The data stays in tact with the protocol analyzer, so I didn't originally think so. Don't have a fast enough scope to cross check.

    Working on the WiFi slave code cleanup right now. Only one line inside the snippet you provided, so I don't believe setWatch() execution is the cause.

  • Goal to stay between 100000-800000 bits/sec.

    You're totally out of luck then - setWatch is not going to work. Maybe you'll get it working but it'll take ~32ms to process your 32 bits of data in JS - so I don't understand why you want a really high bitrate.

    Can you try this code:

    // out
    var spi = new SPI();
    spi.setup({mosi:D29,sck:D28});
    // in
    pinMode(D31, "input");
    var d=1;
    setWatch(function(e) {
      d=(d<<1)|e.data;
      if (d&256) {
        print("Got "+(d&255));
        d=1;
      }
    },D30,{edge:"rising", data:D31, repeat: true});
    
    // Test
    spi.write([1,2,3,4]);
    

    Just tested this on an nRF52 (all running on the same device) and it's fine, even at the 'normal' speed.

  • If you want to use SPI I would suggest extending the Espruino SPI class to support slave mode. The Espruino Wifi chip itself supports SPI slave mode, the source code library for this chip includes SPI slave mode already, so it seems feasible.

  • Extend SPI

    That would be the ideal, yes. I'm not sure Robin's up for that though since it requires a lot of hacking around in C.

    Ok, I just tried this:

    Espruino WiFi

    // in
    pinMode(B5, "input");
    var d=1;
    setWatch(function(e) {
      d=(d<<1)|e.data;
      if (d&256) {
        print("Got "+(d&255));
        d=1;
      }
    },B3,{edge:"rising", data:B5, repeat: true});
    

    MDBT42

    var spi = SPI1;
    spi.setup({mosi:D14,sck:D15});
    
    spi.write([1,2,3,4]);
    

    Get them grounded, connect B3->D15 and B5->D14 and it works perfectly.

    So I'm not quite sure what's going on with your code.

  • Mon 2019.08.05

    'Get them grounded,'

    Yeah, and I have a soldered bread board to boot. More high freq caps needed?

    I know it's getting late for you guys, thanks for the samples for me to work on as I have the rest of the day.

    Here is a possible area of contention I'll look at:

    Fr #8 L6 if (isActive) print(e.data);

    When I use typeof( e.data )    'boolean' is returned.

    Fr #15 L5 d=(d<<1)|e.data;

    Bit shifting is being performed. (Isn't the left shift done first, then the 'Or', then the assignment is performed?)

    As @AkosLukacs pointed out in #2 'and the state will be included as a 'data' field in the callback'
    As 'boolean' is returned, I've made the assumption that the 'field' was just a boolean. So how is it possible to 'Or' a boolean with a data byte in that line?


    'so I don't understand why you want a really high bitrate'

    Not absolutely needed. Have seen Arduino projects running that high though. (at least coded that way)   Me thinks others blindly start coding without checking the data sheets, then like wildfire, everyone provides a quick knock off with all the original source, also without doing their homework. Then we all wonder why code examples are as they are.

    'extending the Espruino SPI class to support slave mode'

    A nice thought, but not much call for it and I'm sure it would be at the bottom of Gordon's overloaded list right now.

    'I'm not sure Robin's up for that'

    Although I've been poking around the source to attempt to get answers, I am absolutely sure I don't possess the necessary skills, and wouldn't within at minimum, a years time, even investing full-time, but the day job gets in the way of that. Also running short on years left and that item just isn't on my bucket list.

  • This code works if there's not much data transfer. But it fails as soon as the bus becomes busy, the event queue will simply overflow (getting events at the bus clock frequency). And you skipped the CS detection in this code completely.

    To make it work you must add a logical AND gate with CS and MOSI as inputs, and MOSI as output to the electrical circuit. (Javascript is not fast enough to enable this code (or a setWatch) only after CS was detected).
    Or change the Master code and wiring: Instead of using one MOSI and multiple CS change to multiple MOSI lines (one for each slave) without CS.

  • 'And you skipped the CS detection in this code completely'

    I don't believe that is the case. The setWatch() is performing that task. Gordon's example #8 on which the last is based is fine. I'm with you Gordon.

    'But it fails as soon as the bus becomes busy'

    See second logic analyzer image in #1 above. One second between bus usage, and only the sixteen clock pulses there during valid data.

  • Could try using the exact code I posted, with no modifications other than the pin names? It might help to ensure we're on the same page.

    Then the CS line could be added later.

    how is it possible to 'Or' a boolean with a data byte in that line?

    Try it - boolean values get converted to 0/1.

    The code itself is a hack to try and get a little faster decoding of binary data - since at the end of the day this is what you wanted. I started off with print(e.data) and added the extra code when that worked.

  • This code works if there's not much data transfer. But it fails as soon as the bus becomes busy, the event queue will simply overflow (getting events at the bus clock frequency). And you skipped the CS detection in this code completely.

    Yes, there's no CS. That'd have to be added as as you note the only way to do it would use up processing power even when CS wasn't asserted.

    And I agree this will fail when there is more than a little bit of traffic on the bus. This is more to humour @Robin and prove that setWatch actually does work ok.

    ... because right now since there's no support for hardware SPI or I2C slave, using Serial would be the sane option for transferring data between two Espruino devices.

  • Quick update as I realize it is late by you, . . . with more testing to do,

    'This is more to humour Robin'

    Okay humored! (sic US sp)


    L2 spi.setup({mosi:D14,sck:D15});

    Took a bit to get up and running, determined PulseView software requires MISO to be included also, for the protocol decoder to work.

    With using just the code from #15

    First thing I noticed is the active state on the pins is not the same. Clock phase. As pinMode() isn't used, I'm guessing looking at default and setup will uncover that.

    Added a setInterval() on send side MDBT42Q so I didn't have to keep pulsing that side, and kept the bus quiet for at least a full second.

    Without the CS detect, the WiFi slave side does decode as in #15

    MDBT sends out 8usec clock ~100KHz


    I don't see any major differences, except for the CS state inclusion.

    Speculation here: Sanity Check

    Commented out strings, outside of functions are not included in the upload? (yes?)

    Inside the CS setWatch() during development, I placed console.log() statements to see what was going on. Had a hunch that the update to the WebIDE console window was getting bogged down, as it took a while to Ctrl+C out. I commented out ~three debug statements, and the console updated fine.

    HUNCH: Even though the console now appeared to be updating without noticeable delay, that in reality, the commented out lines were causing a delay in the setWatch() read perhaps?

    Off for coding CS inclusion and more testing . . . .

  • Commented out strings, outside of functions are not included in the upload? (yes?)

    "it depends" :)

    Without minification, all comments and whitespace is included!
    By default the web ide does NOT minify your "main" code, but does minify the included modules. But check the minification in the settings.

  • console.log() takes a lot of time. If you call it once every 8 bit it's somewhat ok, but if you call it for every bit it'll slow down things significantly. But the read data should be still valid in my understanding.
    For time critical functions it's better to store data you want to print in some variable or array and print it at a later point.

  • 'store data you want to print in some variable or array and print it at a later point'

    That is in fact what I am doing, an array and indexer, adding only one line of code to Gordon's #8. On CD low set array index to zero, inc when clocking in bits with second setWatch(). I print to the console on/after raised CS detection.


    And now the really bizarre . . . . One can not make this stuff up!!

    Using Gordon's #15 and adding a setInterval() as explained in #21

    I sent a bit stream at a one second interval that has an easy to detect bit inversions in the stream:

    const CMD = 0xD6;    // 1101 0110
    const DAT = 0x37;    // 0011 0111
    
        spi.write([CMD,DAT]);
    

    Endless stream of these repeating pairs: (as expected)

    Got 214
    Got 55
    

    Walked away for a half hour to run an errand.

    Upon return, without any intervention by me, the WebIDE is now reporting instead:

    Endless stream of these repeating pairs: (incorrect)

    Got 190
    Got 177
    

    Quickly checked PulseView and it is reporting 0xD6 0x37 (as it should)

    Rechecked with new runs two more times!! Master is sending correctly as PulseView reveals this to be true.

    The slave is suspect as the data is now not being decoded correctly, and mirrors what I observed over the weekend with different #8 code snippet and setWatch.

    What is being sent and originally decoded
    
    11010110    0xD6   214
    00110111    0x37    55
    
    What now is being detected
    
    10111110    0xBE   190
    10110001    0xB1   177
    

    It is this inconsistency that is driving me nuts and making coding this a chore. A one second pause to 200usec of data duty cycle shouldn't have an effect.

    Could setWatch() have an internal counter that eventually rolls over in the middle of parsing bits. Say at count 65530 the seventh bit inclusion resets and then starts at zero rather than continuing for the seventh and eighth bits?


    Speculation anyone???

  • Speculation:
    You lost a bit in outer space.
    Fix:
    Make d a global variable.
    Add another setWatch() for the CS and set d=1; within this code.

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

Proper technique needed to nest a setWatch() within a separate setWatch()

Posted by Avatar for Robin @Robin

Actions