• Hello,

    was trying to use fonts included in GT24L24A2Y SPI font chip, these chips are often used in smart watches and fitness trackers to provide fonts in many languages. Basically it is relatively normal SPI flash memory with font data is some format. Th structure of fixed width fonts for this chip is documented here https://github.com/RichardBsolut/GT24L24­A2Y I tried to read is as ordinary SPI flash via W25 module into Graphics.createArrayBuffer and it works and the description of offsets and sizes looks correct and I can see letters when I create buffer in correct format. However the buffer must be created with vertical_byte:true parameter. If I put the data directly into setFontCustom it is not looking good because it does not accept this vertical format.

    Any idea how to load/convert/rotate such font data in memory via some Espruino graphics/bitmap/image magic so I could then pass the buffer directly to setFontCustom ?

    I have attached screenshot how I can load individual letters of https://github.com/RichardBsolut/GT24L24­A2Y#font-clock-4-22x32

    And BTW here is video of example code running on this fittness tracker, it is mostly same as P8 smartwatch code linked previously here. This F07 is ~$14-$16 aliexpress item and can be updated to Espruino without taking apart.


    1 Attachment

    • Capture.PNG
  • ...next thing is a Femto Espruino with plug-able SPI chips like 40+ years ago the PC with 'user'-plug-able io/memory/what-ever cards... ;-)

  • Got it partly solved. I can at least draw it to screen letter by letter. I also mapped the fontchip as ordinary flash via SPIFLASH in board file so it is accesible via Flash module and it can be easily read. Then I found Graphics.asImage which works only when called with asImage("string") (not "object" due to vertical_byte:true ?) and I can draw it via drawImage. so e.g. to draw big digit "3" from the 40x64 font I do it like this:

    var f=require("Flash");
    var fg=Graphics.createArrayBuffer(40,64,1,{v­ertical_byte : true});
    fg.buffer=f.read(320,0x60000000+0x199e0e­+3*322);
    fg.buffer[0]=0;
    g.drawImage(fg.asImage("string"),0,0);
    

    So it works and I can make my own drawString letter by letter. May be even better for large clock numbers.

    Also looks like the first byte of each letter is real letter width so the font is in fact proportional (and I need to zero the data before drawing). Is there way to draw only part of larger image? The width is 40 but letter '1' is only 33 pixels wide. I don't see any way to take/draw only some part of the image both in Graphics.asImage and Graphics.drawImage. Maybe transparency could solve it too but there is no way to provide that either with asImage("string") or drawImage options.

  • Tried to avoid vertical_byte:true in final image to feed it to createCustomFont but it made no difference - still see random garbage. I made following method to convert it to single buffer which is not in vertical format

    function createFont(off,bs,w,h,c){
      var ptr=0x60000000+off;
      var f=require("Flash");
      var totw=w*c; // todo take from letters
      var font=Graphics.createArrayBuffer(totw,h,1­);
      var lg=Graphics.createArrayBuffer(w,h,1,{ver­tical_byte:true});
      var ll=lg.buffer.length;
      var x=0;
      for (var i=0;i<c;i++){
        lg.buffer=f.read(ll,ptr+i*bs);
        lw=lg.buffer[0];lg.buffer[0]=0;
        font.drawImage(lg.asImage("string"),x,0)­;
        x+= w;//(lw>0)?lw:w;
      }
      return font;
    }
    

    and the result is attached, but when creating font from it like this

    g.setFontCustom(ft.asImage("string"),32,­16,32)
    

    or this

    g.setFontCustom(E.toString(ft.buffer),32­,16,32)
    

    it is still not right so the format is still something else.


    1 Attachment

    • Capture.PNG
  • take a look at this online tool

    http://ebfc.mattbrailsford.com/

  • I know about it, not sure how it could help me in this case. I am trying to do it directly on the device on the fly as the data is already there. More helpful is this https://github.com/espruino/Espruino/blo­b/master/scripts/create_custom_font.js but still I didn't get what is correct format from that code.

    The page http://www.espruino.com/Fonts#custom-fon­ts says "You'll need a string containing a column-first, most significant bit first, 1 bit per pixel bitmap" which is still not enough for me to understand. Also when looking at font sources like https://github.com/espruino/Espruino/blo­b/master/libs/graphics/bitmap_font_6x8.c­ it looks like the data is in rows, not columns.

  • I guess I got it, fontchip has data in columns too but second byte is 8 bits from second column (= {vertical_byte:true})while espruino custom font array need all bytes of first column first an then second column starts. So i'll try to swap the bytes around and see.

  • @fanoush, I like our dedication to details... the challenges get you really going!

  • got it working, last catch was lsb to msb conversion (did via table, named M2L in code below)

    function createFont(off,bs,w,h,c,isprop){
      var ptr=0x60000000+off;
      var f=require("Flash");
      var hbytes=h>>3;
      var bmp=new Uint8Array(w*hbytes*c);
      var widths=isprop ? Uint8Array(c) : w;
      var bmpidx=0;
      for (var i=0;i<c;i++){
        var buff=f.read(bs,ptr+i*bs);
        var wprop=w;
        if (isprop){wprop=1+buff[0];widths[i]=wprop­;buff[0]=0;}
        var x,y;bi=0;
        for(y=0;y<hbytes;y++){
          for(x=0;x<wprop;x++)
            bmp[bmpidx+x*hbytes+y]=M2L[buff[bi+x]];
          bi+=w;
        }
        bmpidx+=wprop*hbytes;
        E.kickWatchdog();
      }
      if (isprop){
        return { font: E.toString(Uint8Array(bmp.buffer,0,bmpid­x)), widths: E.toString(widths)};
      }
      return { font: E.toString(bmp), widths: w};
    }
    

    then to set font https://github.com/RichardBsolut/GT24L24­A2Y#font-clock-2-34x48 I use it like

    fnt=createFont(0x19de96,206,34,48,14,tru­e)
    g.setFontCustom(fnt.font,0x30,fnt.widths­,48)
    

    however the code is a bit slow for fonts with more characters so I need to ping the watchdog

    and BTW found out that GT24L24A2Y is otherwise pretty normal 2MB SPI flash that I can write and erase and space over 0x1A3000 is empty so I could perhaps convert those fonts once and write back in better format to load them directly next time

  • Great! Glad you got it sorted! You can do msb:true when creating Graphics which might help you?

    It's possible that you could read the data with vertical_byte:true in one graphics, then blit to another Graphics with msb:true (and maybe rotation?) if you wanted something faster?

  • It could help for lsb to msb conversion but there is at least one other issue: there is no Graphics.createArrayBuffer format that would produce something that Graphics.setFontCustom would consume. Or at least I did not find one. The one with vertical_byte:true is the most similar (=uses columns) and is what the fontchip natively uses too but it is not the same, setFontCustom requires all bytes of first column first, while with vertical_byte:true it is not like that, there second byte contains pixels from second column. while setFontCustom wants in second byte pixels 8-15 of first column. So this is the longest operation here, switching order of same (column pixel) bytes around. So something like vertical_line instead of vertical_byte would be it.

    As for rotation, yes maybe this could do it after all in one extra step, render it from first one with vertical_byte to second one (already done in post #4 - the single bitmap with all letters, this rotates columns into rows) but also setup rotation on destination (so rows are written back to columns in correct order.) Not sure when the rotation is applied - if the bytes are indeed stored rotated or when later reading pixels from it, will check.

    Anyway the biggest issue for me was to understand format of data that Graphics.setFontCustom accepts, the rest is more clear now. There is even arm native library for this fontchip in some github procects (e.g. here or here) so I could use it to get all fonts out of it and with proper encoding. As maybe there are more fonts there than documented at https://github.com/RichardBsolut/GT24L24­A2Y

  • Ahh - I think this is what you need - this code creates the font in Graphics directly: https://github.com/espruino/EspruinoDocs­/blob/master/modules/Font5x7Numeric7Seg.­js

    var gr = Graphics.createArrayBuffer(H,digits.leng­th*W+2,1,{msb:true});
    gr.setRotation(3,1);
    //...
    var font = gr.asImage().buffer;
    var widths = E.toString(widths);
    g.setFontCustom(font, 46, widths, H);
    

    If there's somewhere you think I can improve the docs around setFontCustom let me know though - I know it's not great.

    I guess there's the possibility that something that renders direct from flash could be built into Graphics, or that setFontCustom could have extra options?

  • I am back to this topic since the DK08 watch clock screen could use some nice fonts and has this fontchip too. (BTW it is in sale for $14 now) Thanks for the last suggestion, tried again and it (almost) works!!! The {msb:true} with setRotation(3,1) was the key. So my current version is something like this

    function createFont(off,bs,w,h,c){
      var f=require("Flash");
      var fg=Graphics.createArrayBuffer(w,h,1,{ver­tical_byte:true}); // one letter
      var fnt=Graphics.createArrayBuffer(h,w*c,1,{­msb:true}); // whole font in correct format
      fnt.setRotation(3,1);
      off+=0x60000000;
      var len=w*h/8;
      var x=0;
      while(c--){
        fg.buffer=f.read(len,off);off+=bs;
        fg.buffer[0]=0; // letter width for proportional font, clear
        fnt.drawImage(fg.asImage(),x,0);
        x+=w;
      }
      return fnt;
    //  return new Uint8Array(fnt.asImage().buffer);
    //return E.toString(fnt.asImage().buffer);
    }
    
    /*
    var fnt=createFont(0x199e0e,322,40,64,10);
    g.setFontCustom(E.toString(fnt),0x30,40,­64);
    g.drawString("01234");
    g.flip();
    */
    

    However currently the biggest issue for me is that setFontCustom accepts only String type as the font bitmap and I have byte array. Unfortunately for larger fonts than just few letters E.toString() mostly returns 'undefined' possibly because the font buffer is too big so it cannot make flat string out out of that due to low/fragmented memory. Is there Uint8Array to String conversion that would always succeed i.e. share the same buffer? or could setFontCustom easily accept Uint8Array too? Now it fails with Uncaught Error: Font bitmap must be a String as per
    https://github.com/espruino/Espruino/blo­b/master/libs/graphics/jswrap_graphics.c­#L1070

  • just a followup - I found kind of a workaround for converting big array to string - writing it to Storage :-) Hopefully there is even advantage that the font data is used directly from flash, saving RAM. Here is code snippet that creates font, writes it to Storage and then creates font from that. And it shows on screen just fine :-)

    // for font offsets and sizes see https://github.com/RichardBsolut/GT24L24­A2Y
    function createFont(off,bs,w,h,c,isprop){
      var f=require("Flash");
      var fg=Graphics.createArrayBuffer(w,h,1,{ver­tical_byte:true}); // one letter in fontchip layout
      var fnt=Graphics.createArrayBuffer(h,w*c,1,{­msb:true}); // whole font in Espruino layout
      fnt.setRotation(3,1); // needed to match espruino font layout
      off+=0x60000000;//fix data offset, spi flash mapped here
      var len=w*h/8;
      var x=0,i=0;
      var lw=w;
      var widths=isprop ? Uint8Array(c) : w;
      while(i<c){
        fg.buffer=f.read(len,off);off+=bs;
        if (isprop){
          lw=1+fg.buffer[0];
          widths[i]=lw;
          fg.buffer[0]=0; // real width of letter if proportional, clear
        }
        fnt.drawImage(fg.asImage(),x,0);
        x+=lw;
        i++;
        E.kickWatchdog();
      }
      const ret=fnt.asImage().buffer;
      fg=null;fnt=null;
        if (isprop){
        return { font: ret, widths: widths, height:h};
      }
      return { font: ret, widths: w, height:h};
    }
    
    var fnt=createFont(0x199e0e,322,40,64,14,tru­e);
    //g.setFontCustom(E.toString(fnt.font),0­x30,E.toString(fnt.widths),64);
    flen=E.sum(fnt.widths)*8;
    var s=require("Storage");
    s.write("FNT-64x40.wdt",fnt.widths)
    s.write("FNT-64x40.bin",new Uint8Array(fnt.font,0,flen)) // font is proportional so array is smaller
    g.setFontCustom(s.read("FNT-64x40.bin"),­0x30,s.read("FNT-64x40.wdt"),64)
    g.drawString("01234");
    g.flip();
    

    But anyway method to change type of byte array to string in place and share same data would be nice to have.

  • Just looked at this and E.toString should return the backing string if it exists (you could check with trace(buffer) what it actually is I guess.

    Is there a reason you're doing E.toString(fnt.asImage().buffer) vs just E.toString(fnt.buffer)? I'd have hoped that the last one there would have worked better (I don't think so, but it is possible that asImage ends up reallocating the buffer).

  • Is there a reason you're doing E.toString(fnt.asImage().buffer) vs just E.toString(fnt.buffer)

    no, just took it from your example in post #12, will check if it makes difference, I think I tried both. I have like 900 out of 2600 variables still free, with e.g. 176x176x4bit framebuffer allocated and also some Inline C code so there is memory but can be fragmented. This is 64x40 font so relatively large but even when taking just number digits (10 letters) E.toString often fails in this case. Will check what trace does, thanks.

  • I now just return whole Graphics from createFont and here is copy of session where E.toString fails, no asImage used this time

    >process.memory()
    ={ free: 799, usage: 1801, total: 2600, history: 147,
      gc: 0, gctime: 3.47900390625, blocksize: 16, stackEndAddress: 536926192, flash_start: 0,
      flash_binary_end: 356348, flash_code_start: 417792, flash_length: 524288 }
    >var fnt=createFont(0x199e0e,322,40,64,14,tru­e);
    ={
      font: Graphics: {
        buffer: new Uint8Array([0, 0, 0, 0, 0,  ... 0, 0, 0, 0, 0]).buffer
       },
      width: new Uint8Array([40, 34, 40, 40, 40, 40, 40, 40, 40, 40, 13, 13, 13, 13]),
      height: 64 }
    >process.memory()
    ={ free: 658, usage: 1942, total: 2600, history: 150,
      gc: 255, gctime: 3.81469726562, blocksize: 16, stackEndAddress: 536926192, flash_start: 0,
      flash_binary_end: 356348, flash_code_start: 417792, flash_length: 524288 }
    >E.toString(fnt.font.buffer)
    =undefined
    >trace(fnt.font.buffer)
    #2385[r1,l1] ArrayBuffer (offs 0, len 4480)  #2002[r1,l0] String [122 blocks] "\0\0\0\0\0\0\0\0\0\0\3\xFF\xFF\xC0\0\0\­0\0?
    ....lot of data....
    

    I guess the main issue is that https://www.espruino.com/Reference#l_E_t­oString says "Returns A String (or undefined if a Flat String cannot be created)"

    In this case I'd be happy with non flat string - the #2002[r1,l0] String [122 blocks] one. So something like E.toString(buffer,false) like E.getAddressOf() would probably do.
    Hmm but it is already taken, E.toString("Hello",false) gives "Hello\0".

  • I tried to replicate the issue in Bangle emulator and also other nrf52 board and with simple allocations of Graphics.createArrayBuffer until memory is low and calling E.toString on the buffers it just works. Maybe the memory is not fragmented enough. Those graphics buffers should be always guaranteed to be flat strings already? I am also suspecting possible memory corruption from my InlineC code now since I also have some random hangs and watchdog reboots after my code runs for some time.

  • I guess it could be a memory corruption issue? The new Espruino builds will automatically defragment memory if they have trouble allocating a Flat String, so ideally it wouldn't fail.

    But it's a bit frustrating not being able to get the backing string out of the ArrayBuffer - I was sure there was a way to do it, but it seems not...

  • The new Espruino builds will automatically defragment memory

    how new is new approximately? Days/weeks? Would it move e.g. inline C binary around? Or other flat strings so older E.getAddressOf is no longer valid? just asking, currently I take getAddressOf(x,true) immediately before calling native code and the code is position independent anyway so hopefully it does not matter. And there is no real multithreading so this should not happen when Inline C code is running(?) as you probably don't allocate stuff that could cause this defragmentation from interrrupt handler. I'll check for the memory corruption on other board with swd available, add same/similar code until it breaks.

  • Well, I guess I had several issues at once, possibly also memory corruption due some instability (did try to enable internal DCDC like 14 days ago, it was unstable so I turned it back off, restarted, rebooted but maybe it still left the cpu still in some broken state as very same code that was randomly unstable in now stable). But after trying with different device it looks like maybe there is some memory leak? I create software spi via new SPI() then delete it and variables go down by one. Tried few times and always free variables go down by one after deleting it. Also E.dumpFragmentation() shows some suspicious 'L' near the end which seems to be increasing after each attempt, see full log here
    https://gist.github.com/fanoush/c2d2be9e­2dcbd66894aa5951c0f410ac
    variables go from 602 to 601 and 600. Or is there other explanation for free variables going down like this? like command history or something else

  • also with clean device

    >var s=SPI();process.memory().free
    =2575
    >for(var i=0;i<2000;i++)s=SPI();process.memory().­free
    =570
    >E.dumpFragmentation()
    L#LL#############L###LLL#L#LLLLLLLLLLLLL­LLL#L##L###L#LLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LL
    LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL­LLLLLLLLLLLLLLLLLLLLLLL   L #
       #  [#L](http://forum.espruino.com/search/?q­=%23L)#L# L
     
    =undefined
    >
    

    EDIT:
    same with s=Serial() or s=I2C()

  • created https://github.com/espruino/Espruino/iss­ues/1923 , looks like it is more complicated and a bit off-topic here

  • Wow, thanks! Just tracked this down and will fix it in a second - amazed this was never spotted before!

  • Ok, latest version has this fixed

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

setFontCustom bitmap format vs vertical_byte:true - GT24L24A2Y fontchip in F07 fitness tracker

Posted by Avatar for fanoush @fanoush

Actions