-
https://www.aliexpress.com/item/1005004728587963.html
The board is easy to solder too, plus you get two buttons.I should add: I removed the original SQUARE display as it was cracked on delivery; that leaves the entire 12-pin array of solder points so if you can solder at 0.6mm you can get 3.3,GND,and 5 GPIOs (for the display CS,RST,SDA,SCL,DC). These are tricky and fragile solder points so I just used two here and secured them
-
-
I found 3 pads on the back that were not listed on @fanoush's Magic3 pin table: D13 (unmarked, grouped with SXD,TXD,D+,D-), D28 (unmarked, to the far right of that cluster), and D27 (unmarked, to the near right, below and to the left of D28). I also reused I2C SDA/SCL as I have removed the touchscreen. Of course, that's the ACCEL lines as well, so you'd have to write a cooperative routine that makes sure they don't interfere with each other.
Also good for hacking is the DT28 (also cheap on AE). No "new" pins that I found, but you can find the touchscreen pads (SDA,SCL,INT,RST). When I get a chance I can check the RX/TX to see what they are...
-
@fanoush Lots of things to try, thank you. So far, it's still going (nearly 2 days now) so that's a good sign that it was simply the SPI flash not sleeping. But if I do choose to use it, I'll have to know some of these tricks.
-
totally agree I don't know why they share those lines.. and this one actually shares data and clock with display, SPI flash AND accelerometer! However, as I've removed the display and the touchscreen, I've grabbed the touchscreen SDA/SCL for my e-ink display, so that's not shared.
However, I'm not initializing the accelerometer at all, so I'm hoping it's not doing anything (yet). My intention was to use a very basic JS driver and simply ignore any requests if the SPI flash is low. Klunky, but it should do for now.
Right now the watch has been running off charger for 19hours, but NRF.getBattery() is reporting 3.02 so not expecting it to last the night... this is with manually setting the SPI flash to sleep (current firmware isn't using it for Storage, but it is compiled in so I can get it via require("Flash"))
-
-
I have Espruino running on a DT28 smartwatch (NRF52832) with its display removed. I've soldered wires to the main board to run an e-ink display, which works fine. The watch has an 8MB SPI flash chip which I've compiled in and it works, but with power issues. My first pass I was getting nothing back with every first call (call .list() get nothing, call it again, get file list). This sounded like it needed to be woken up, so I added the flag
'DEFINES += -DSPIFLASH_SLEEP_CMD', # SPI flash needs to be explicitly slept and woken up
and it that stopped the errors, but now my battery dies in about 2 days (as opposed to 10 days without flash at all). I don't have an easy way to measure the power draw other than battery life (a 200mAh which I charger for 2 hours).BTW: SPI flash shares NO GPIOs with the display. It DOES share CLK/DATA with the accelerometer, but I do not initialize or use that at all (yet).
All thoughts appreciated. Board file is
import pinutils; info = { 'name' : "DT28 smartwatch", 'boardname' : 'DT28', # visible in process.env.BOARD 'default_console' : "EV_BLUETOOTH", 'variables' : 2565, 'bootloader' : 1, 'binary_name' : 'e_%v_dt28.hex', 'build' : { 'optimizeflags' : '-Os', 'libraries' : [ 'BLUETOOTH', 'GRAPHICS', 'JIT', ], 'makefile' : [ 'DEFINES+=-DCONFIG_GPIO_AS_PINRESET', # Allow the display MOSI pin to work 'DEFINES += -DCONFIG_NFCT_PINS_AS_GPIOS', 'DEFINES+=-DNRF_BLE_GATT_MAX_MTU_SIZE=53 -DNRF_BLE_MAX_MTU_SIZE=53', # increase MTU from default of 23 'DEFINES+=-DUSE_FONT_6X8', 'DEFINES += -DSPIFLASH_SLEEP_CMD', # SPI flash needs to be explicitly slept and woken up 'DEFINES+=-DBLE_HIDS_ENABLED=1 -DBLUETOOTH_NAME_PREFIX=\'"DT28"\'', 'DFU_PRIVATE_KEY=targets/nrf5x_dfu/dfu_private_key.pem', 'NRF_BL_DFU_INSECURE=1', 'DFU_SETTINGS=--application-version 0xff --hw-version 52 --sd-req 0x8C,0x91', 'INCLUDE += -I$(ROOT)/libs/misc',c' ] } }; save_code_pages = 20; chip = { 'part' : "NRF52832", 'family' : "NRF52", 'package' : "QFN48", 'ram' : 64, 'flash' : 512, 'speed' : 64, 'usart' : 1, 'spi' : 1, 'i2c' : 1, 'adc' : 1, 'dac' : 0, 'saved_code' : { 'page_size' : 4096, #'address' : ((118 - save_code_pages) * 4096), # Bootloader at 0xF8000 #'pages' : save_code_pages, 'flash_available' : 512 - ((35 + 8 + 2)*4), # Softdevice 5.0 uses 35 pages of flash, bootloader 8, FS 2. Each page is 4 kb. 'address' : 0x60000000, # put this in external spiflash (see below) 'pages' : 2048, # 8MB of the 16MB external flash }, }; devices = { 'BTN1' : { 'pin' : 'D29', 'pinstate' : 'IN_PULLDOWN'}, 'BTN2' : { 'pin' : 'D30', 'pinstate' : 'IN_PULLDOWN'}, 'BTN3' : { 'pin' : 'D31'}, # Fake button: bootloader wants THREE #'VIBRATE' : { 'pin' : 'D22' }, # Pin negated in software #'LCD' : { # unused for bootloader 'SPIFLASH' : { 'pin_sck' : 'D9', 'pin_mosi' : 'D8', 'pin_miso' : 'D7', 'pin_cs' : 'D6', 'size' : 8192*1024, # 16MB 'memmap_base' : 0x60000000 # map into the address space (in software) } }; # left-right, or top-bottom order board = { 'left' : [ 'VDD', 'VDD', 'RESET', 'VDD','5V','GND','GND','','','D3','D4','D28','D29','D30','D31'], 'right' : [ 'D27', 'D26', 'D2', 'GND', 'D25','D24','D23', 'D22','D20','D19','', 'D18','D17','D16','D15','D14','D13','D12','D11','', 'D10','D9','D8','D7','D6','D5','D21','D1','D0'], '_notes' : { 'D6' : "Serial console RX", 'D8' : "Serial console TX" } }; board["_css"] = """ [#board](https://forum.espruino.com/search/?q=%23board) { width: 528px; height: 800px; top: 0px; left : 200px; background-image: url(img/NRF52832DK.jpg); } [#boardcontainer](https://forum.espruino.com/search/?q=%23boardcontainer) { height: 900px; } [#left](https://forum.espruino.com/search/?q=%23left) { top: 219px; right: 466px; } [#right](https://forum.espruino.com/search/?q=%23right) { top: 150px; left: 466px; } .leftpin { height: 17px; } .rightpin { height: 17px; } """; def get_pins(): pins = pinutils.generate_pins(0,31) # 32 General Purpose I/O Pins. pinutils.findpin(pins, "PD2", True)["functions"]["ADC1_IN0"]=0; pinutils.findpin(pins, "PD3", True)["functions"]["ADC1_IN1"]=0; pinutils.findpin(pins, "PD4", True)["functions"]["ADC1_IN2"]=0; pinutils.findpin(pins, "PD5", True)["functions"]["ADC1_IN3"]=0; pinutils.findpin(pins, "PD28", True)["functions"]["ADC1_IN4"]=0; pinutils.findpin(pins, "PD29", True)["functions"]["ADC1_IN5"]=0; pinutils.findpin(pins, "PD30", True)["functions"]["ADC1_IN6"]=0; pinutils.findpin(pins, "PD31", True)["functions"]["ADC1_IN7"]=0; # Make buttons and LEDs negated pinutils.findpin(pins, "PD29", True)["functions"]["NEGATED"]=0; pinutils.findpin(pins, "PD30", True)["functions"]["NEGATED"]=0; # everything is non-5v tolerant for pin in pins: pin["functions"]["3.3"]=0; return pins
-
-
No setTimeout(); has to be a check for BUSY going low (or is already low). You can say "don't do anything" while it's doing a full redraw and right now that's exactly what I am doing, but someone else who may use this module may not heed that and see issues and not understand. So it's trying to foolproof the driver. In particular, I would override g.flip()/g.setPartial()/g.reset() so they all get queued and only perform in the intended order and when BUSY is low
-
I'd share my code, but I don't feel it's ready.. I'd like to implement a queue for all commands, since it's possible that during a full refresh (which will return to the event loop) another command could be requested at a bad time. But then I can share a simple clock that updates every minute (fast refresh) an updates full every hour. I just have some other work ahead of that... :-/
-
My understanding is you don't get both.. you get one or the other. Mode 1 for slow, but no ghosting and known state (you should always START with this), or Mode 2 for faster, some ghosting. And according to what I've read, make sure you revert to full refresh regularly to keep the screen "healthy"
From the spec, I see parameters B1 and B9 for command 0x22... but I don't understand their use. I use CF for a Mode 2, and if you don't call the command it appears to default to Mode 1 (after reset anyway)
-
I'm pretty sure that's full refresh (mode 1) and partial refresh (mode 2). From the recommendations, it says if you use partial (which looks nicer and is faster), make sure that you occasionally do a full refresh to reset the display to known values. Also, power it off whenever you can.. not only does it save power, but the higher voltage can cause permanent damage.
In my case, I'm setting up my desk clock to do a partial refresh every minute, and full refresh on the hour (like a visual "chime"). But I haven't powered it down (or put it to sleep) in 3 weeks! Guess I'd better get on that...
-
there may be other commands that need to be "waited" for. If you look at C source for these (heltec, waveshare) you'll see the spots where they wait for BUSY to clear. seeing BUSY go hi/lo a lot means your just sending commands, should not be a problem. But if the timing is off, you'll get weird effects like you've been seeing
-
You can run Espruino on both of those. I just flashed the Watchy the other day and it works while plugged in (just drains the battery quickly). Pinetime is running an NRF52832 so it runs Espruino as well, but I think you have to open the case to get to the SWD pins (the default bootloader is not compatible with Espruino OTA update).
-
Glad to hear. You lit a fire under me to get the whole BUSY thing working the way I wanted. In my haste, my original code just used timeouts for calls, which meant you had to be very careful when you called subsequent functions. So thanks for that! I think I'll fallback on timeouts for when the target device is a low-memory MCU (no Promises).
-
-
-
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...
-
Here is a gist of what works on my Watchy:
https://gist.github.com/yngv27/aa5f562597d734928c5d5a1b622c2c13it 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-ink/blob/master/src/DEPG0150BxS810FxX_BW.hScreens vary a LOT from what I understand, so maybe try a Waveshare source file too?
-
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,0x0,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,0x32,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...
-
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'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.
-
-
https://www.aliexpress.com/item/1005002480008305.html
I see "That product can't be shipped to your address"