• ( See updated version of module)

    Was looking for some serially connected RAM that has to briefly hold on to some processing data (not like EEPROM / SDCard) and has to be very fast. Came across this thing and it has great specs (datasheet attached). Ordered - 2 pieces - and got package yesterday. 'Tin-glued' it to Espruino board, added some more machined socket pins for connecting. With hardware 'complete', I started coding...

    Reading the device ID got pretty quickly going... even though not standard. Everything after that became just rubbish... and still is. Nevertheless, I publish it here...

    // FRAMSPI_inline.js (c) muet.com
    
    // F RAM SPI Module
    
    var framspiModule = { connect: function(spi,cs,hd,wp) {
      pinMode(cs,"output"); cs.set();
      if (hd) { pinMode(hd,"output"); hd.set(); }
      if (wp) { pinMode(wp,"output"); wp.set(); }
    
      var fram = 
    { id: function() {
        var d = spi.send(
          [0x9F,0x01,0x01,0x01,0x01,0x01,0x01,0x01­,0x01,0x01]
         ,cs); // chip select, write 0x9F, send 10*0x00 not working
        return d;
      }
    , stat: function() { 
        var d = spi.send(
          [0x05,0x00]
         ,cs);
        return d;
      }
    , wen: function() {
        spi.write(
          [0x06,0x00]
         ,cs);
      }
    , wdi: function() {
        spi.write(
          [0x04,0x00]
         ,cs);  
      }
    , read: function(a,n){
        cs.reset();
        spi.write([0x03,0x00,0x00]);
        var d = new Uint8Array(n);
        while (n>0) { n--; d[n] = spi.send(0x00); } 
        cs.set();
        return d;
      }
    , write: function(a,d){
        cs.reset();
        spi.write([0x02,a>>8,a]);
        spi.write(d);
        spi.write([0xFF]);
        cs.set();
      }
    };
      return fram;
    }
    };
    
    SPI2.setup({sck:B13, miso:B14, mosi:B15, baud: 1000000});
    
    var fram = framspiModule.connect(SPI2,B12);
    
    console.log(fram.id());
    
    /*
    fram.write(  0,"FRAM@000");
    fram.write(  8,"FRAM@008");
    fram.write( 16,"FRAM@016");
    fram.write(128,"FRAM@128");
    fram.write(256,"FRAM@256");
    fram.write(512,"FRAM@512");
    
    console.log("@000:",fram.read(  0,  8));
    console.log("@008:",fram.read(  8,  8));
    console.log("@016:",fram.read( 16,  8));
    console.log("@128:",fram.read(128,  8));
    console.log("@256:",fram.read(256,  8));
    console.log("@512:",fram.read(512,  8));
    */
    

    The challenge is in SPI. Having done some work with the ILI9341 SPI LCD controller, I though: piece of cake... wrong. The FRAM's SPI is behaving weird: bytes get stuck in the output buffer and are read the next time. Also randomly dummy bytes are inserted for no obvious reason. Elaborating and trying to track some of the signals lead to the death of one chip... an so I had hope that the second would fair better. So far I can say: work different... actually more annoying: the broken chip could handle ID-read followed by a status-read. The new chip inserts dummy bytes so that the first status read has just those, as you can see in the output below (chip ID shows on upload, fram.stat() is called with a command n console):

     1v70 Copyright 2014 G.Williams
    >echo(0);
    [ 255, 127, 127, 127, 127, 127, 127, 194, 34, 0 ]
    =undefined
    >fram.stat();
    =[ 255, 255 ]
    >fram.stat()
    =[ 255, 0 ]
    > 
    

    Tried to add other dummy byte, but this makes it even worse. Somehow, the chip does not follow its own spec which says that when chip select (CS) goes high, the SPI transaction is considered complete, even though send may not have completed yet.

    It seems, that only continuously sending / receiving works to some degree: everything packed in a Uint8Array - including the command and parameters - and SPI.send() it a once. Using a mix and mach of SPI.write() and SPI.send() fails.

    Looking for answers I came across hints that the SPI behavior is a bit unique... something about having to keep the clock going...

    I have not given up yet, but if things do not smoothen out soon, I will look for a different solution.... :(


    5 Attachments

  • It's strange about breaking one of the chips - are you sure it's wired correctly? IMO you shouldn't be able to break anything just by sending badly formed SPI data.

    I don't know if it's helpful, but if you don't need to read any values then when you use SPI.write you can just give it multiple arguments and it will send them one after the other.

  • Braking of the chip was when I touched unintendedly VBat/5V with HOLD and WP - hold for interruption of a communication and write protection. Some stuff still worked: SPI still responded, but the ID was messed... obviously some 'inner' / memory oriented part got 'flushed'. Luckily nothing else 'departed'.

    The incident happened when I measured input and behavior on the lines for wiring verification.

    Since I successfully execute read ID, status, AND write status commands, I can assume that wiring is ok. It has to do with the code - write or send related buffering and the clock.

    Below is the list of commands. the bolded ones work.

    Commands (opcode binary - hex - symbol - Description)

    1. 0000 0110b - 06h - WREN - Set write enable latch
    2. 0000 0100b - 04h - WRDI - Reset write enable latch
    3. 0000 0101b - 05h - RDSR - Read Status Register
    4. 0000 0001b - 01h - WRSR - Write Status Register
    5. 0000 0011b - 03h - READ - Read memory data
    6. 0000 1011b - 0Bh - FSTRD - Fast read memory data
    7. 0000 0010b - 02h - WRITE - Write memory data
    8. 1011 1001b - 09h - SLEEP - Enter sleep mode
    9. 1001 1111b - 9Fh - RDID - Read device ID

    WREN and WRDI change the register which I can verify with RDSR.

    This status register is though in the control section of the chip and not the memory section. Where the ID is located, I do not know... it could be a PROM part of the chip. So I wonder if I ever experience access to the memory part.

    A more elaborate command sequence with output below:

     1v70 Copyright 2014 G.Williams
    >echo(0);
    [ 255, 127, 127, 127, 127, 127, 127, 194, 34, 0 ]
    =undefined (code loaded, above fram.stat() is part of the loading)
    >fram.stat();
    =[ 255, 255 ]
    >fram.stat();
    =[ 255, 0 ]
    >fram.wen();
    =undefined
    >fram.stat();
    =[ 255, 255 ]
    >fram.stat();
    =[ 255, 2 ]
    >fram.stat();
    =[ 255, 2 ]
    >fram.wdi();
    =undefined
    >fram.stat();
    =[ 255, 255 ]
    >fram.stat();
    =[ 255, 0 ]
    >fram.stat();
    =[ 255, 0 ]
    >fram.id();
    =[ 255, 0, 255, 127, 127, 127, 127, 127, 127, 194 ]
    >fram.stat();
    =[ 34, 0 ]
    >fram.id();
    =[ 255, 0, 255, 127, 127, 127, 127, 127, 127, 194 ]
    >fram.stat();
    =[ 34, 0 ]
    >fram.stat();
    =[ 255, 0 ]
    >fram.stat();
    =[ 255, 0 ]
    >fram.id();
    =[ 255, 0, 255, 127, 127, 127, 127, 127, 127, 194 ]
    >fram.stat();
    =[ 34, 0 ]
    > 
    

    It looks like as if bytes are still hanging somewhere in the SPI output buffer and are then delivered prepend to the next output. Have to figure out how to suck up and throw away thse trailing things... it looks like it happens only when the command changes: a repeated status read - stat() has not trailing/leading fillers. Using another command than stat() after *stat()8, such as id(), I get the filler again. I can consume them with a stat() call that expects two bytes from the chip (interestingly ignoring the command). After consuming them - obviously no matter with what command, just byte count correct - I'm back in sync again.

    While trying to describe my observation, I guess I noticed this pattern: on changing command sequence, some fill bytes show up. This would also explain some other observations I made and tell me that I had access to the memory: when reading from the memory, I got 0's and 255's back... the 255's were fillers, where as the 0's were actually memory data (chip comes w/ 0's initialized).

    I give it another try with module code changed to take this command repeat/change pattern into account.

    ...it's obviously not that simple... sucking up left over bytes has also an impact on what's delivered next...

    It must have something t do with when spi clock stops and when chip select goes high. The whole thing needs more discovery on the SPI part.

  • Some chips have a 'write protect' to stop accidental writed - for instance even the STM32 in Espruino requires a series of values to be written in the correct order to 'unlock' it. Could that be the problem?

  • I considered that (see commands 0x06 and 0x04). It breaks already when just using the write enable and write disable in the status register to control that. The 'write protect' in the status register is set to 0 = 'write protect' to prevent setup skirmishes on control pins to do harm.

  • Hurray! ...got it working!

    See updated version of module.

    Key to the solution is: Commands CANNOT be chained..., they all have to be concluded / committed individually by the chip select to go high!

    With that I just have increased my #memory (for very fast data) by 66%.

    // FRAMSPI_inline.js (c) muet.com
    
    // F RAM SPI Module
    
    var framspiModule = { connect: function(spi,cs,hd,wp) {
      pinMode(cs,"output"); cs.set();
      if (hd) { pinMode(hd,"output"); hd.set(); }
      if (wp) { pinMode(wp,"output"); wp.set(); }
    
      var fram = 
    { id: function() {
        cs.reset(); spi.send(0x9F);
        return spi.send(Uint8Array(9),cs);
      }
    , stat: function() {
        cs.reset(); spi.send(0x05);
        return spi.send(Uint8Array(1),cs);
      }
    , wen: function() { spi.send(0x06,cs); }
    , wdi: function() { spi.send(0x04,cs); }
    , read: function(a,n){
        cs.reset(); spi.send([0x03,a>>8,a]);
        return spi.send(new Uint8Array(n),cs);
     }
    , write: function(a,d){
        spi.send(0x06,cs); cs.reset();
        spi.send([0x02,a>>8,a]);
        spi.send(d); cs.set();
        return d.length;
      }
    };
      return fram;
    }
    };
    
    SPI2.setup({sck:B13, miso:B14, mosi:B15, baud: 18000000});
    
    var fram = framspiModule.connect(SPI2,A2);
    
    console.log(fram.id());
    
    function writes() {
    fram.write(  0,"FRAM@000");
    fram.write(  8,"FRAM@008");
    fram.write( 16,"FRAM@016");
    fram.write(128,"FRAM@128");
    fram.write(256,"FRAM@256");
    fram.write(512,"FRAM@512");
    }
    
    function reads() {
    console.log("@000:",fram.read(  0,  8));
    console.log("@008:",fram.read(  8,  8));
    console.log("@016:",fram.read( 16,  8));
    console.log("@128:",fram.read(128,  8));
    console.log("@256:",fram.read(256,  8));
    console.log("@512:",fram.read(512,  8));
    }
    
    ArrayBufferView.prototype.asString = function() {
     var s = "";
     this.forEach(function(d) { s+=String.fromCharCode(d); });  
     return s;
    };
    
    function hurray() {
      fram.write( 16,"Hurray! ...it works!");
      console.log(fram.read(16,20));
      console.log(fram.read(16,20).asString())­;
    }
      
    writes();
    reads();
    hurray();
    

    This is the output (see hurray() function):

     1v70 Copyright 2014 G.Williams
    >echo(0);
    new Uint8Array([127, 127, 127, 127, 127, 127, 194, 34, 0])
    @000: new Uint8Array([70, 82, 65, 77, 64, 48, 48, 48])
    @008: new Uint8Array([70, 82, 65, 77, 64, 48, 48, 56])
    @016: new Uint8Array([70, 82, 65, 77, 64, 48, 49, 54])
    @128: new Uint8Array([70, 82, 65, 77, 64, 49, 50, 56])
    @256: new Uint8Array([70, 82, 65, 77, 64, 50, 53, 54])
    @512: new Uint8Array([70, 82, 65, 77, 64, 53, 49, 50])
    new Uint8Array([72, 117, 114, 114, 97, 121, 33, 32, 46, 46, 46, 105, 116, 32, 119, 111, 114, 107, 115, 33])
    Hurray! ...it works!
    =undefined
    > 
    

    I set the SPI baud to 18MHz... does it really do it that fast? I could not find quickly what the limit is for the chip on the Espruino board running at 72Mhz.

    Initially, I could not even reliably read the device id and the status register. The first break through came when putting command, command parameter(s), and necessary clocking into a single array and make it an spi.send(), even though one would expect that a spi.write() would be the thing for commands and command parameter(s), because nothing should be returned for those. With known number of 0xFFs to the response one can live. For the id and status read this was sending:

    • [0x9F,0x00,0x00,0x00,0x00,0x00,0x00,0x00­,0x00,0x00]
    • [0x05,0x00]

    When using SPI.write() for the commands and parameters, the next SPI.send is returning first a 0xFF for each command and parameter byte, before the data, and leave stuff 'unread'... and subsequently 'read' by the next SPI.send()... just weird... there was no reasonable pattern to discover to discard reliably this unwanted, delayed read stuff... and adding more to the send to 'read' all made it worse: it prepended even more to the next 'read'. To not return unwanted information, I split sending of command and parameters from the clock ticking for receiving. The splitting requires the 'manual' handling of chip select outside of the SPI.send(), but can be kept inside latter for the last SPI.send().

    , id: function() {
        cs.reset(); spi.send(0x9F);
        return spi.send(Uint8Array(9),cs);
      }
    , stat: function() {
        cs.reset(); spi.send(0x05);
        return spi.send(Uint8Array(1),cs);
      }
    

    Next step was writing to the status register with the lesson learned.

    The write and read was a bit more complicated... This was the first working write:

    , wr0: function() {
        spi.send(0x06,cs);
        spi.send(  
          [0x02
          ,0x00,0x00
          ,0x40,0x30,0x031,0x032
          ]
         ,cs);
        spi.send(0x04,cs);
      }
    

    I first did a manual un-set of the write protection, and then do the write, and that made the read work. This told me that I cannot chain commands, like un-set write protection, then write, and finally set write protection again.

    , rd1: function(){
        var d = spi.send(
          [0x03
          ,0x00,0x00
          ,0x00,0x00,0x00,0x00
          ]
         ,cs);
        return d;
      }
    , rd2: function(a){
        var d = spi.send(
          [0x03
          ,a>>8,a
          ,0x00,0x00,0x00,0x00
          ]
         ,cs);
        return d;
      }
    

    From then on it was figuring out how to do it with the least statements, which unveiled another nifty corner: reset chip select and include the set of it in the send worked for all places except for the write. For the write I had to just do a simple send and then explicitly set chip select high.

    Trying to convert a Uint8Array to a string became a bit a detour... Espruino JS does not look at Uint8Array as an array that can be used in a Function.call(null,args) as args parameter as regular JS does (see http://forum.espruino.com/conversations/­258049). Resorting to beloved iteration and string concatenation in JS as Uint8Array asString() prototype extension gets it done.

    There are a few things left to look into. I was not able to use A5..A7 with A2 as chip select for the above writing sequence. First writes made it, but after a while it garbled. Could it be that B13..B15 are configured differently for SPI? ...like pulling up/down things? Also, actual speed has to be found out. But more important for now: the protocol.

    With 'hurray' going, it is now the time to think about a 'good' protocol for writing and reading of the FRAM in applications: a lean, concise protocol - not to fat and not to frugal . Questions for the protocol are: File System? ...may be not, since the amount of memory is small and filesystem overhead may be significant... read/write of data types? Strings, for example ( fram.substr(120,40 ) for 40 bytes starting with address 120,.... or 0 ending/delimited strings, or String objects with length at the beginning? ...reads and writes for every object type... or just limit to String with JSON for other things... Memory management with ribs... or even object-space w/ r/w of 'String objects' w/ 'simple' garbage collection.

    Object store w/ simple garbage collection: no reference for mark, just check for active / inactive indicator in the object list. MSBit is not used for address and can therefore be used in an object list held in a heap. Heap residing in one side of the memory and data in the opposite one could be a quite luxury solution - function-wise - and not too fat for the implementation. The protocol would be a simple CRU(D), with create returning a handle (address into object list/heap) for future access, update with automatically managing updates with a different size, and delete with setting the deletede / inactive indicator.

    Any protocol suggestions are welcome!

  • Great news it's working.

    18MHz... does it really do it that fast?

    You'd have to check the datasheet. It's possible. You won't get a continuous stream of bits at that though!

    SPI.write() + unread bytes

    I've just checked into this - it'll be fixed for 1v72. Looks like the same issue caused CS to be raised too early, which could have caused problems too.

    Could it be that B13..B15 are configured differently for SPI

    They're connected to the SD card as well - could that be doing it?

  • FRAM is ready for continuously delivering up to 40MHz SPI clock due to its technology... as explained in FRAM's data sheet (see Functional Overview in first post). There is no buffer involved... it is really like reading and writing RAM... There exist also FRAMs with 8bit parallel interface, that are then 8 times faster regarding throughput. I just do not know where the ferroelectric technology is heading for and what the caveats are.

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

256-Kbit (32 K × 8) Serial (SPI) F-RAM - Ferroelecric RAM - SPI challenges

Posted by Avatar for allObjects @allObjects

Actions