DS24B33/DS2431 eeprom

Posted on
Page
of 2
/ 2
Next
  • Hi, I know @DrAzzy was going to look at this, but the bits arrived and I couldn't resist.

    Note: you need a 0.2-2.2k pullup resistor - so pretty hefty (not like the ~10k you can get away with on the DS18B20).

    var ow = new OneWire(B15);
    var code = ow.search()[0];
    
    function write(addr, data) {
      while (data.length>0) {
        var l = data.length;
        if (l>8) l=8; // 32 works with DS2433, but need 8 for DS2431
        ow.reset();
        ow.select(code);
        ow.write(0x0F); // write spad
        ow.write(addr&0xFF);
        ow.write(addr>>16);
        for (var i=0;i<l;i++) 
          ow.write(data.charCodeAt(i));
    
        ow.reset();
        ow.select(code);
        ow.write(0xAA); // read spad
        var a=[ow.read(),ow.read(),ow.read()];
    
        ow.reset();
        ow.select(code);
        ow.write(0x55); // copy spad to eeprom
        ow.write(a[0]);
        ow.write(a[1]);
        ow.write(a[2],true/*pull up for write*/);
        // TODO: maybe we should delay here?
        
        // get ready for next iteration, if there is one
        data=data.substr(l);
        addr+=l;
      }
    }
    
    
    function read(addr, cnt) {
      ow.reset();
      ow.select(code);
      ow.write(0xF0); // read
      ow.write(addr&0xFF);
      ow.write(addr>>16);
      var res = "";
      while (cnt--) res += String.fromCharCode(ow.read());
      return res;
    }
    
    write(0,"Hello World")
    read(0,16)
    // ="Hello World\x00\x00\x00\x00\x00"
    

    I'll stick it in a module soon.

  • Cool, thanks - that gives me an idea how to talk to it. OW is so weird.

    I intend to write a module for the DS2431/33/28EC20 (the 20kbit one) this weekend, since I'm already in the process of overhauling the AT24 and AT25 modules for general streamlining and to support a wider range of parts. I can get sizes up to 2mbit supported pretty easy on AT24 (bringing it to 32-2048 kbit, with a second module for the smaller sizes being easy), and I think I can get support for all sizes on AT25 (no lower bound). Adding support for FRAM should be trivial.

    I'm honestly inclined to get rid of separate documentation for the AT24,AT25,DSxx, and instead replace it with one big "eeprom.md" which would describe the common interface provided by all the eeprom modules, list and link to said modules, and list considerations specific to each type of EEPROM. What do you think?

  • Well, I'd implement a separate module for OneWire vs I2C devices at least (and even AT24/25 if you find the protocol is different). As we're after saving as much RAM as possible we don't want to be including code that's never used.

    I'd have a very basic .md file for each module, with keywords, a link to EEPROM.md, wiring, any gotchas, a link to the datasheet and the part on farnell or something. But give them all the EEPROM keyword, then write the all-encompassing EEPROM.md and include:

    Devices
    ------
    
    * APPEND_KEYWORD: EEPROM
    

    That will then automatically link in all the pages for the EEPROMs.

    By the way, DSxx is probably a bad name for the module given the DS18B20 :) Maybe DS2xxx?

    Let me know if I can help at all... Can we try and simplify the API a bit and stick with reading Strings or Arrays (or Uint8Array via E.toArrayBuffer())? It seems there's quite a lot of duplication in AT24 at the moment because of the need to get arrays or strings back. If it causes trouble then I could add a library function to make the conversions easier?

  • Sounds good, I'll do that.
    To be clear, I was always planning to have a different module for each interface, I was just pondering combining the documentation.

    I expect there will be 4 modules:

    OneWire (name TBD)
    I2C (AT24) (like we have now, only expanded up to 2mbit)
    SPI (AT25) (like we have now, only expanded to highest sizes available, and possibly down to the tiny sizes as well)
    I2C (name TBD?) for 16k and under AT24 series.

    I think the logic to support FRAM will be trivial and tiny - if it's not, I'll split it out into other modules. I ordered a couple of FRAM chips - I realized I actually have a use case where not caring about write cycles makes my life much easier.

  • Great, thanks! I thought that might be the case - just wanted to check :)

    So the lower-size AT24s use a significantly different protocol?

    It's going to be really handy - I did something recently on a Teensy and I'd forgotten just how handy the EEPROM was.

  • where not caring about write cycles makes my life much easier.

    Exactly what made me go for FRAM vs EEPROM... ;-) - recalling the challenges when building hardware where EPROMs needed those even for reading (...with then 'fast' micros: 4MHz Z80).

    What are the thoughts regarding the module name and the setup. I could imagine something more on the lines of external memory or just memory - MEMORY, **XMEMORY **, which includes the memory management logic, and it is connected by passing a connected driver for the specific memory attachment.

    The memory attachment could be something like OWMsomething, SPIMsomething, I2CMsomething, or even P(arallel)(4..8)Msomething. The something could then be generic or the whole driver name is device specific.

    Regarding the API for the MEMORY or XMEMORY, @Gordon's suggestion works for me - or the memory manager API as suggested in response to @Gordon's post - because it is the bare necessity. For performing operations though - such as in graphics where I have the need - the memory manager may be a bit far away from the actual memory access code. What I experienced in writing the the suggested memory management code is the need for 1 byte, 2 bytes, and n byte reads and writes, where 1 byte and 2 bytes operation are more for the int type data and n bytes are string type data. With write() and read() begin the string type - with or without count for the read, I would like to have also write1(), write2(), read1(), and read2() (or alike). Those would then accept any data and take the LSByte(s) from *numeric*s (*int*s) and first Byte(s) from any other reasonably suitable data type, and would return int. Furthermore, with smart arranged data in the memory, a first read with giving address could be followed by subsequent read that does not need passing the address again: read() with no argument. Furthermore, the API with writing without passing size(limit) and reading with size for bare metal operations looks to me as inconsistent. A write without limit is quite dangerous... I agree that matter on fact/nature of being on a micro everything is dangerous, that was the reason that my memory manager goes with the write() without size/limit for a dynamically managed memory space with garbage collection. I adds more complexity, sure, but it is a more robust/safe place. The passed address is therefore not the actual data area address but the address of the pointer to the data area. Adding an optional size argument in the bare metal write would solve the 'issue'. Even with the bare metal operations hidden behind/in a memory manager, the shining through of some of the operations - such as the read/write of 1 or 2 bytes and related data typing is very useful for application code.

    In the memory manager I used the notion of datatype. Currently I'm only supporting (assuming) string data, because that's what I needed (and took the extra app work to convert any other data types first into and afterwards from strings). The most other data type is key-value pair - with key a string, and value what ever will be supported. Beyond the string and int data type though, the bare metal should - and obviously does already - not care about.

  • I would like to have also write1(), write2(), read1(), and read2() (or alike)

    This is exactly what I hope to avoid - it is basically all duplicated code, when a single read and write function that take an array will handle everything.

    If you want these functions, maybe add another module that implements them on top of the basic read/write functions.

    the API with writing without passing size(limit) and reading with size for bare metal operations looks to me as inconsistent

    It's not really - just saving space. If you're doing write(array) why should you have to write write(string, string.length)? It's not the same as C where you're dealing with just a pointer - the length of the array/string is an integral part of it.

  • See the point to avoid... and also the point that a wrapping layer can take care of the read/write 1, 2, or n.

    Regarding a robust/safe API: with the paradigm all power to the app, just taking the passed in data and write works. I'd like to prefer though having the support of an (optional) safe guard, which makes above paradigm and the bare metal version even more applicable... ...without any memory manager. Even though there is just a pointer passed, the pointer does not point to bare date (data bytes). The pointer points to an object which includes meta data... such as the length of the array/string... that's the reason that I actually never much liked C in an (business) application context, where tracking byte counts should not really be part of the problem to be solved.

    Saving space is a noble thing. Making the app waste it before every write to be safe not so much. Please no offense taken here.

    Are you considering of implementing the module natively? ...then saving space has a totally different ranking... ;)

  • I think we're probably misunderstanding each other...

    These modules will be (and are) written as JavaScript, so when I say write(data) I don't mean write(char *data) (which is obviously bad news) I mean write(new String()) - which contains the length of the string alongside the actual data.

  • Yeah - read2(adr) makes no sense to me, when it could be read(adr,2) - only then you can read as many as you want.

    Currently my modules have these functions:
    read(address,bytes)
    readc(bytes) -Continues read from where it last read from.
    reads(address,bytes) -works like read(), but reads 64 bytes at a time and converts to string.
    writes(address,data) -writing a string is faster than an array, as we have to convert the array to a string.
    writeb(address,data)
    writel(address,data) -write long block of data (more than 1 page), supplied as string.

    My thinking for the overhaul is:

    Remove readc(), and instead allow read(,bytes) to have same functionality (should be easy).

    Combine writeb() and writes() and detect the type of the data, and react appropriately. I think this will also shrink code size.

    That leaves reads() and writel()

    reads() is necessary to read long strings, because you need to read those piecewise and convert to string, and combine those, since you don't have the memory to read it in all at once as a simple array. I'm still thinking about what the right way to handle this is.

    writel() handles long writes (ie, ones longer than a page). I think I can probably make writel smart enough to determine when it needs to do this.

    I'm also toying with the idea of a couple of helper functions that you could add if you need, like easy ones for converting to signed/unsigned 16 bit numbers and stuff, whether in the modules, or in a second module (that would add methods to the first, like the font modules do).

    Re: small AT24's: Ones with 12-19 bits of address space are addressed using 2 bytes of data and up to 3 bits of the device address. Ones with 11 or less bits of address space are addressed using 1 byte of data and up to 3 bits of the device address. I hadn't given much thought to how to do this before, but there may be an easy way of doing it that I hadn't thought of before.

  • Thoughts on how much sanity checking to provide? Catching people who call the functions with bad arguments makes things more user friendly, but it also makes the module bigger.

    Edit: AT24 is down to read() write() and writel()... You know how sometimes you look back at your old work and you're like, "man, was I that dumb back then?" Definitely did some things the hard way.

  • Could we have commented errorchecking ?
    If this could be switched on/off in WebIDE, hmmm.
    Ok, sometimes I should sleep a night before posting.

  • You mean have the errors be turned on/off in the IDE?

    I cannot describe how much I would like to have some sort of preprocessor in the IDE that would let you do #define and #ifdef , and have that handled by the webide before it sends the code. Then you could just #define debug, and send the code and get error checking. But that's an IDE feature that doesn't exist (yet), so it's outside the scope of this topic.

    I've got AT24 converted to just read(add,bytes,asStr) and write(add,data), and cut 500 bytes from pre-minified code size, will try it out later tonight and see if it works correctly. If it does, converting AT25 should be a cinch.

  • I like the discussion... sort of... ?-)

    We all come from different requirement angles. Requirements we came across while trying a thing the best we know and understand - biased or not biased by past experience or immediacy of need. The angles are from usage, implementation options, feasibility, cost, and visionary point of views... Therefore, I don't want to get carried away (anymore) and look at at all the options. So far, I can do with the bare metal API what I want to achieve (in, for example, the memory manager), since it accepts arrays as well. To have some minimal support for int data type was just an idea. I do not have much knowledge what a function definition costs, and therefore - even if an additional layer or incrementally addable extensions may cost more with little extensions and for sure alway more in cycles, overall it may come in more beneficial. Extending incrementally - like Smalltalk could do add methods to given classes - can be done with additional require(s) - especially with the option of the 1v72+ of returning plain objects versus the 1v7 export functions only pattern, a great extension request @Gordon added instantly. I do not know the cost, but for sure he would have objected if too costly. My time with Espruino changed my thinking about resourcefulness, even though I was already always challenging requirements with frugalness.

    On a related matter - but not related to passing optionally a length - I have the following question:

    Regarding space, what is the price of a variable definition var v = and its meta data for hosting any data type (I mean instance of class) vs. var v = {k:"", v:f} or var v = {k:i, v:f}. with i and f defined (upfront as global) variables referencing an int and function, respectively? (I know that variable name lengths matter, but for the calculation example I need only to know the minimum). - May be I can reliably find out myself with process.memory()... Can I?

    I was thinking to uses a new data type which is a reference and retrieves the value from (external) memory on access. I did not wanted to make it an additional data type of the language yet... but was kind of thinking of it... I'm sure you are getting where I'm heading to or for.

  • mmm, yeah - I see your point on 16 bit integers, since they're such a common use case, there's a need to have code to do that readily available. Thinking about where to put it.

    What we're mostly concerned about, I think, is the memory footprint of the modules, and helping users stay out of jail, as you say - particularly since one of the use-cases for an eeprom is storing code that you're out of RAM for.

    I think process.memory() works for seeing how much memory something takes up. It wouldn't really have much point if it didn't. There's also E.getSizeOf(), which i think now returns accurate data (at one point modules confused it).

  • The helper functions to encode and decode 32-bit signed ints are easy (i picked 32-bit because that's what espruino uses for ints). My vote is that if you want that, you do:

    rom.write(0,rom.nToS(1231864));
    rom.aToN(rom.read(0,4));

    Those two functions cost 16 jsvars (hand minified cause I hit the cap) combined - this probably is okay?

    New AT24 is working now. Next is AT25.

  • @allObjects E.getSizeOf(xxx) is what I'd suggest - just have a play around and see how much space things take.

    @DrAzzy sounds great about cutting it down... getting rid of writel is great - ideally things using write shouldn't really have to worry about the page issues.

    With debug code, I just tested this: If you add var D=X; up the top of the module then when it is minified, the closure compiler is smart enough to pull out the unreferenced code:

      var D=false;
      exports.foo = function() {
        if (D) console.log("Debug");
         console.log("World");
      }
    

    With nToS/etc - they could be in a separate module. For easy access of ints/etc you can also use ArrayBufferView too (if the source data is an arraybuffer): (new Uint16Array(arr.buffer,0,1))[0].

    Wrt I2C - if I2C.readFrom returned a Uint8Array, would it break anything? It'd be faster and more memory efficient for sure.

  • That's good to know about the minifier!

    Yeah. The pages were ugly. No reason for users to have to deal with that. writel is gone, and you no longer need to align writes with pages - the only thing you need to know about pages is the size of them when you call connect().

    Ya - and if they go into a new module, we could afford to have more of them, like for 16 bit numbers and stuff.

    Re: I2C returning Uint8Array - I had asked for a way to make I2C return data in a more memory-efficient way, and you had concerns because some of the array methods didn't work on typed arrays. I would support making I2C.readFrom() return Uint8Array, and if they need an array, just convert it to a simple array? (I assume there's a simple way to do this - I don't recall ever wanting to do that)

  • because some of the array methods didn't work on typed arrays

    Thanks - sorry, I get quite forgetful :) I think ES5 doesn't support a lot of the nice stuff you can do to Arrays (map/etc) but ES6 does. At some point I gave in and copied it from ES6 - and I guess it may have come up before I did that, when Uint8Array wasn't quite as useful?

  • Yeah, I think that was it. I can't find the changelog entry for that to date it though.

  • Got my OW eeproms... Gonna take a stab at a module tonight.

    it was an itsy-bitsy teeny-weeny dee-ess-two-four-be-thirtythree...

  • 
      /*
    This module interfaces with OneWire EEPROMs like the DS24B33, DS2431, and DS28EC20.  
    
    Usage: 
    
    Setup onewire, then call:
    
    var eeprom=require("DSmem").connect(onewire,­ pagesize, capacity, nopartial, device)
    
    onewire is the OneWire bus object. 
    
    pagesize is the page size for page writes, in bytes. 
    
    capacity is the eeprom capacity, in kbits. 
    
    nopartial is true for devices that cannot start a write except on a page boundary, otherwise undefined or false. 
    
    device is the code identifying the device, or it's number in the search. Optional - if undefined, the first device will be assumed. 
    
    eeprom.read(address,bytes,asStr)
    Read the specified number of bytes. If asStr is true, it will return the value as a string. 
    
    eeprom.write(address,data)
    Write the specified data starting at the specified address. Writes that cross page boundaries are handled transparently.  
    
    Additionally, this includes the helper function, which converts arrays of bytes to strings:
    eeprom.aToS(array);
    
    */
    
    exports.connect = function(ow, pgsz, cap,n,device) {
    		return new OWmem(ow, pgsz, cap,n,device);
    };
    
    function OWmem(ow, pgsz, cap,n, device) {
    	this.ow = ow;
    	this.np=n;
    	this.code=(device===undefined)?ow.search­()[0]:(typeof device=="string"?device:ow.search[device­]);
    	this.pgsz=pgsz
    	this.cap=cap<<7;
    }
    OWmem.prototype.aToS= function(a) {
    	var s = "";
    	for (var i in a)
    		s+=String.fromCharCode(a[i]);
    	return s;
    };
    OWmem.prototype.s=function(cmd) {
    	this.ow.reset();
    	this.ow.select(this.code)
    	for (var i=0;i<cmd.length;i++) {
    		this.ow.write(cmd[i],i==3);
    	}
    }
    
    OWmem.prototype.write= function (addr, data) {
    	if(addr+data.length > this.cap) throw "Write exceeds size"; //CRITICAL to test for this, as writing to the addresses right after the normal address space can set the EEPROM read-only (forever)!
    	if(typeof data=="object"){data=this.aToS(data);}
    	if(this.np&&(addr%this.pgsz)){ //if it's a device that doesn't support writes not starting at page boundary, and that's what we're doing 
    		data=this.read(addr-(addr%this.pgsz),add­r%this.pgsz,1)+data; //read the data on the page before the target address;
    		addr-=addr%this.pgsz; //and set the address to the start of the page; 
    	}
    	while (data.length>0) {
    		var l = data.length;
    		if (l>(this.pgsz-(addr%this.pgsz))) {l=this.pgsz-(addr%this.pgsz); }// Writes must align on page boundaries, so if we don't start at one, account for that. 
    		this.s([0x0F,addr&0xFF,addr>>8]); //write to spad with address
    		for (var i=0;i<l;i++) {
    			this.ow.write(data.charCodeAt(i)); //write out the data.
    		}
    		this.s([0xAA]); //read spad.
    		//room for improvement: Manufacturer recommends reading back the scratch pad completely, and verifying it aginast the data, before comitting it. 
    		this.s([0x55,this.ow.read(),this.ow.read­(),this.ow.read()]); 
    		var et=getTime()+0.07; while (getTime() < et) {"";} //delay(7) - it says it needs 5, but let's not rush it.
    		if (this.ow.read()==170) { //check for the indication of successful write - if not, we'll try again per datasheet. 
    			// get ready for next iteration, if there is one
    			data=data.substr(l);
    			addr+=l;
    		}
    	}
    }
    OWmem.prototype.read = function (addr, cnt, asStr) {
    	this.s([0xF0,addr&0xFF,addr>>8]);//selec­t address
    	var res = new Uint8Array(cnt);
    	for (i=0;i<cnt;i++){
    		res[i]=this.ow.read();
    	}
    	return (asStr?this.aToS(res):res);
    }
    
    

    Thoughts on naming? writing more efficient JS?

    Tested with DS24B33 and DS28EC20, and should work on DS2431, if i followed the datasheet correctly.

  • Looks great... and takes care of the page stuff transparently.

    I know the usual way is to export only the connect(). The connect hides the new. With 1v72 you could return the OWmem (function) as class (see How to create a module that is a 'Class' - aka a function usable with new - or: How to make require() return a function and not an object).

    Exposing the class itself allows to add/modify the prototype by additional, OWmem extending requires and adding method. A similar approach is already used when extending the Graphics class dynamically with specific fonts. For example, require("Font8x16").add(Graphics);, adds the .setFont8x16() method to the Graphics class.

    There are other helpful effects coming along: returning the class allows to name it what ever the user wants it to have... to overcome the typical name space issue in JavaScript (taken care of in requre.js/AMD module technique for the browser).

    Loading a bare metal OWmem and extending it with what ever space allows and benefits the application the most. I could even see your 'room for improvement' methods using this technique: in bare metal empty defined but already called, and allowed to be overridden on demand. Other methods could be any data conversion types / helper methods to be added
    in addition what you already have to make OWmem generically usable.

    Dynamic extensions should work with minification. I do though not know about how it works with save().

  • Looks great,

    Shouldn't this.ow.write(cmd[i],i==3); be this.ow.write(cmd[i],i==(cmd.length-1));­ though? I should probably add 'arrays as arguments to OneWire.write' and 'multiple OneWire read' to the bug tracker too?

    Can we change the name to something like DS2xxx as well? If there is any other OneWire memory out there (I imagine there is) then OWmem could be confusing.

    What do you think about:

    exports.connect = function(ow, pgsz, cap,n,device) {
      if (pgsz=="DS2431") return new OWmem(ow, ....);
      return new OWmem(ow, pgsz, cap,n,device);
    };
    

    Not sure what you think of @allObjects suggestion about connect? I guess you could do something like:

    OWmem.connect = function(ow, pgsz, cap,n,device) {
    };
    exports = OWmem;
    

    So we keep the 'connect' form which is used all over, but if you don't call connect then you get the base class. I'm not sure you really need that though, and you can always get to the list of prototypes from the instantiated object very easily.

  • Re: i==3
    We only want to leave the power on when we issue the copy scratchpad command, not for other commands. This is the only time that s() gets called with a 4 element array, all other times it's called with 1 or 3 element array - hence checking for i==3 is true for the write scratchpad command only.

    Re: allObjects' comment about connect - Hmm. Not sure what to do here. Am I understanding correctly that the issue is that if you don't export the whole class, then you can't add things to the prototype? And thus, if you had multiple eeproms and wanted to add somethign to the prototypes (like the helper functions I mentioned above for converting numbers to bytes or w/e). What are the disadvantages of exporting the class?

    DS2xxx? Done.
    Worth noting - there aren't any other EEPROMs for the OneWire bus as far as I can tell, just the line from Dallas Semi.

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

DS24B33/DS2431 eeprom

Posted by Avatar for Gordon @Gordon

Actions