Help adding E-Ink display support

Posted on
Page
of 2
/ 2
Next
  • I'm trying to add a eink display to the libs/graphics module.

    My goals are to
    First have this excisable from just a normal to normal microcontrollers.
    Second possible port this to the bangleJS library. There's a watch that uses the same exact display i'm working with. https://watchy.sqfmi.com/

    My first question is how do you use a display without using BangleJS software suit.
    I see this example
    https://www.espruino.com/Onboard+LCD
    I see the lcd_pcd8544.h/c files but i don't understand how they get accessed from the javascript side. There's no binding javascript wrapper comment code?

  • So this page loads the pcd8544 from a javascript version of the library
    https://www.espruino.com/Onboard+LCD

    Here's the code
    https://www.espruino.com/modules/PCD8544­.js

    With the list of other modules being here.
    https://www.espruino.com/modules/

  • Hi - so this is a GDEH0154D67 display? I don't think we have any driver for that unfortunately.

    Basically, you have two options:

    • Implement all the whole display code in C - you'd need to then build a special firmware with this in to make it work. It'll be a bit faster, but a lot more work and less flexible.
    • Implement a display driver in JS

    The vast majority of display drivers for Espruino are actually written in JS, for example all the ones at https://www.espruino.com/Graphics#graphi­cs-drivers

    You can just develop the driver in the Web IDE, and then if you want to you can contribute it back to https://github.com/espruino/EspruinoDocs­/tree/master/devices (which would be great!) - there's a tutorial at https://www.espruino.com/Writing+Modules­ and you could one of those existing graphics drivers listed above as a base.

    Honestly, the JS module is the way I'd say was preferable - Espruino's Graphics library can usually do all the heavy lifting for you, and then all you have to do is write the relevant JS code to push the graphics buffer you get out over SPI to the display. For epaper especially the slightly reduced speed over native won't be a massive issue as the update takes a while anyway.

    If you have issues I'm happy to try and help with the code you'd need to get it working

  • @Gordon, Thanks for the info.

    I'm currently working on making a JS module right now. But I'm stuck because it says the module can't be found.

    My setup

    • installed the IDE from chrome
    • local module - located in eink/eink.js
    • require(eink)

    I'm i suppose to just put the code in the main file? I saw export is not supported.

  • Just to make sure:

    • are you feeding a string into require, e.g. require("eink")?

    If that wasn't it:

    • does it work better if you rename the file from eink.js to just eink?

    or

    • change to require("eink.js")?

    My reasoning for these proposals is based on these links:
    https://www.espruino.com/ReferenceBANGLE­JS2#l__global_require
    https://github.com/espruino/BangleApps/b­lob/master/modules/README.md (specifically for Bangle.js development, not sure to what degree it applies to Espruino in general)

  • My bad. In my code it is require("eink")

    I did try eink.js, but that didn't solve anything. The function require will look for any file that is either .min.js or .js.

    I found this that i'm going to try.
    https://github.com/espruino/BangleApps/b­lob/master/modules/README.md

    This section talks about the proper way to test a module.

  • I'd actually create a new tab in the IDE for your eink file, and choose to save to storage as a file called 'eink'.

    Then you can upload, and go to the other tab and then upload that one to test.

    Or... if I'm just wanting to iterate quickly, sometimes I just put exports={}; at the head of the file, put the module contents in, and the code that uses it at the bottom (using export.fn() instead of require("eink").fn), and upload that to flash.

    At least then you can just change+upload+test very quickly.

  • I'm currently working on some e-ink screens, and I have working JS code for the Waveshare 1.54 3-color and a Weact 2.19" (very similar: SSD168x drivers). I think it should work with a watchy screen. LMK how you're progressing and I can share my code (still not a module, just need some time). BTW: I'm using my own board file for an NRF52832 breakout board, so it doesn't have any Bangle code built-in.

  • So this is what my module looks like
    https://gist.github.com/brendena/154afdf­fb2355c1cc845b67c6c258dbe

    I have the exact same displays.
    I have it setup to work with color and just black and white. I'm trying to figure out how to do partial display updates. But i can't seem to get it to work.

  • I'm using the NRF52840 dev board. So you'll most likely going to have to swap the SPI pins

  • By partial, do you mean "partial update" (updating a subsection of the screen) or partial (i.e. "fast") refresh? You've got your partialFlip() implementing a little of both. I've never used partial screen refresh as it's a lot of work to save a few milliseconds while the screen still takes 2 sec to display. As for a "partial refresh", you need to set it up first with a few commands (reinitialize, set the partial LUT), then when you flip, you include the extra command (0x22, 0xcf) before update (0x20).

    Looks like your partial update is requesting the updated dimensions of the buffer, but it seems to return the entire buffer area. Therefore, you're recreating the entire buffer in JS and sending it, so it takes quite a bit of time.

    I'm working on getting partial refresh put into your sample. It seems my Watchy is not honoring the busy pin well...

  • I've got some code that may be helpful. I'm able to do a partial refresh on my Watchy using this, although the LUT is not quite right (seems very dark compared to regular full refresh). First of all, I extended some of your commands.. I didn't see you calling cmd(0x12) (software reset) so I created a fullReset():

    SSD16xx.prototype.fullReset = function() {
      return new Promise((resolve)=>{
        digitalWrite(this.resetPin, 0);
        setTimeout(()=>{
          digitalWrite(this.resetPin, 1);
          this.sc(0x12);
          this.partial=false;
          setTimeout(resolve,this.hwResetTimeOut);­
        },this.hwResetTimeOut);
      });
    };
    

    I also use this to send commands WITH data, then wait (if necessary) for the RST pin (some commands are so fast that a watch on RST falling doesn't register):

    SSD16xx.prototype.waitCmdData = function(command, data){
      return new Promise((resolve,reject)=>{
        console.log("sending a busy command!");
        this.sc(command);
        this.psd();
        this.sd(data);
        if(this.busyPin.read()) 
          setWatch(resolve, this.busyPin, { repeat:false, edge:'falling' });
        else
          resolve();
      });
    };
    

    This sets up using partial refresh by uploading a special LUT:

    SSD16xx.prototype.setFastRefresh = function() {
      const WF_PARTIAL = new Uint8Array([ 0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0­,0x0,0x0,0x80,0x80,0x0,0x0,0x0,0x0,0x0,0­x0,0x0,0x0,0x0,0x0,0x40,0x40,0x0,0x0,0x0­,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0­,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0­,0x0,0x0,0x0,0x0, 0xF,0x0,0x0,0x0,0x0,0x0,0x1,0x1,0x1,0x0,­0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,­0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,­0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,­0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,­0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,­0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,­0x0,0x0,0x0,0x0,0x22,0x22,0x22,0x22,0x22­,0x22,0x0,0x0,0x0,0x02,0x17,0x41,0xB0,0x­32,0x28,
        ]);	
    
      this.fullReset().then(()=>{
        this.waitCmdData(0x32, WF_PARTIAL.slice(0,153)).then(()=>{
          this.scd(0x3F, WF_PARTIAL[153]);
          this.scd(0x03, WF_PARTIAL[154]);
          this.scd(0x04, WF_PARTIAL.slice(155, 158)); 
          this.scd(0x2C, WF_PARTIAL[158]);
          this.scd(0x37, [0,0,0,0,0,0x40,0,0,0,0]); 
          this.waitCmdData(0x3C, 0x80).then(()=>{
            this.partial = true;
          });
        });
      });
      
    };
    

    It sets an internal flag (partial) so the flip() command knows what to send:

    SSD16xx.prototype.flip = function() {
        this.scd(0x4E, 0);
        this.scd(0x4F, [0,0]);
        this.scd(0x24, this.g.bw.buffer);
      
        if(this.display.coloredDisplay){
            this.scd(0x4E, 0);
            this.scd(0x4F, [0,0]);
            this.scd(0x26, this.g.cw.buffer);
        }
      
        if(this.partial) {
          print("flip:partial");
          this.scd(0x22, 0xcf);
        } else {
          print("flip:full");
          //this.scd(0x22, 0xF7);
        }
        return this.refreshDisplay();
    };
    

    That much works for me on the Watchy. Hope it helps. Like I said, LUTs are tricky things, so your results may be better/worse. I'm still trying to figure the out...

  • This is awesome! Thanks so much. I was wondering if you could post the entire thing into a github Gist. For some reason i'm getting some weird results when i try to compile it on my end.

    Also i'm excited your working with a watchy as well. What are you trying to do with it.

  • E-ink is cool!

  • ink is cool

    Very cool

  • So I updated the code here with your changes and this is the result i got. Now the display updates without a full refresh, but only partly. Does yours look perfect. Also how did you find that LUT table and some of the other info. The datasheets are extremely bad.

    For me this video gave me a lot more info on how these work.
    https://www.youtube.com/watch?v=MsbiO8EA­sGw

    https://gist.github.com/brendena/154afdf­fb2355c1cc845b67c6c258dbe


    1 Attachment

    • 20231122_144051.jpg
  • This looks great! When you're happy with this I'd love to try and pull the module in to the main Espruino repo - I guess it could replace the SSD1606 module as well?

  • I guess it could replace the SSD1606 module as well?

    Thats kind of the goal. It "should/want" it to work with many of the SSD16xx devices in a general form. But i'm guessing it would be best to leave the SDD1606 up for backwards support.

    I plan to make a pull request when i have all my e-ink screens work properly.

    I also need to figure out the best way to show known configurations. In the SDD1606 library it was planed to have all the configurations in the library code itself. But the LUT tables can be fairly large, like 150+ bytes. That's potentially for each different display driver. So i don't know if its best to put that into the library as code or have like a readme where they can paste there configurations for there given display.

  • Here is a gist of what works on my Watchy:
    https://gist.github.com/yngv27/aa5f56259­7d734928c5d5a1b622c2c13

    it runs through your original demo, then does a partial refresh demo. It needs to be cleaned up before becoming a module.

    Not sure if this is compatible with SSD1606 or not, I don't have one to try. I know it's not compatible with IL3829 (an older 1.54 screen).

    It's not perfect, no. On my screen the regular update creates faint, but crisp edges for text and shapes, whereas the partial refresh thickens everything up (it's easier to read, just not as precise). I'm still trying to figure this stuff out.

    As for the LUT, it's from the Heltec site where my first 1.54 came from:
    https://github.com/HelTecAutomation/e-in­k/blob/master/src/DEPG0150BxS810FxX_BW.h­

    Screens vary a LOT from what I understand, so maybe try a Waveshare source file too?

  • As for the Watchy, I wanted an e-ink watch to develop with. But I eschew the big compile, push full firmware approach for just tweaking watch faces, so I'm a huge fan of Espruino everywhere. Fortunately, it's an ESP32 so I just pushed a existing build to it and it ran OOB. It also ran out of battery in 30 minutes... as ESP32s need a lot of power management to last on a battery. I hope to tweak the firmware to do what the Watchy code does: shutdown completely and wake on RTC or button press. Some day...

  • i don't know if its best to put that into the library as code or ...

    Interesting about the LUTs - yes, best keep them out of the library if we're targeting multiple devices. Maybe we could have separate library files with them in? For example:

    var screen = require("SSD16xx").connect(
      {
        display: {
          bpp : 1,
          displaySizeX      : 200,
          displaySizeY      : 200,
          lutRegisterData   : require("SSD1681_lut")
          coloredDisplay    : 0,
        },
        ...
      }
    );
    

    One other thing to add - if you've got a big Uint8Array you can use E.toUint8Array(atob("....")) with a base64 encoded blob - it'll be quicker to decode than numbers and should take less space :)

  • What tablet is that? I think I need one...

  • I get the same thing here... there's some trouble with the Promises somewhere. Did you try running my gist? I've got it working on both Watchy and a 52832 breakout board with a WaveShare. I can't look into it right now though...

  • As for the Watchy, I wanted an e-ink watch to develop with

    Thats my thought as well. I have a watchy watch and a pine time watch. Both have a OS that is compiled with all the apps. So it a pain to work with. Espruino seems like the best thing for this.

  • So the text seem to drawing seemed to be transformed on the X axis with your Gist. So i added the calls to set the position after you refreshed the screen. Now the text looks great

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

Help adding E-Ink display support

Posted by Avatar for user156811 @user156811

Actions