-
• #2
A few thoughts about the development on this watch:
- I don't really use the web IDE except for the REPL and to deploy files to storage. I'd prefer to use something more streamlined than a platform-specific IDE. Any advice ?
- I tend to use a lot of closures in my code, and I'm wondering if the overhead of that coding style is going to be significant enough to be a problem.
- A lot of the device-specific stuff feels like copy-pasting magic runes from a more advanced wizard.
- I don't really use the web IDE except for the REPL and to deploy files to storage. I'd prefer to use something more streamlined than a platform-specific IDE. Any advice ?
-
• #3
I don't have a precise idea yet of what I want to do with it, apart from restoring its time and fitness abilities.
Not sure about the fitness part, not even sure @jeffmer got the heartrate sensor working.
Any other projects on this watch I should know about ?
There is also eucWatch made by @enaon
https://github.com/enaon/eucWatch/tree/main
https://enaon.github.io/eucWatch/I tend to use a lot of closures in my code, and I'm wondering if the overhead of that coding style is going to be significant enough to be a problem.
closures are not an issue but low memory in general is
-
• #4
There is also eucWatch made by @enaon
Thanks for the pointer !
Not sure about the fitness part, not even sure @jeffmer got the heartrate sensor working.
I'm currently looking into the existing HRS code, any particular reason it would be difficult to implement ?
closures are not an issue but low memory in general is
What are the available strategies to profile memory usage on Espruino ?
-
• #5
I'm currently looking into the existing HRS code, any particular reason it would be difficult to implement ?
Okay, got it... the datasheet for the HRS3603 isn't exactly dev friendly... and then he disappeared into the rabbit hole
-
• #6
I managed to translate part of the driver mentioned above to js and get some readings. BPM values are all over the place, but that's a start...
-
• #7
Good find, I think we did not have any source and the datasheet did not say much.
When searching github now https://github.com/search?q=hrs3603.c&type=code there is something added about 1 year ago.Often the glue source comes with some linkable library with the HR/BP/SPO2 algorithm, without that you only get raw PPG data.
Also the trick can be with tuning it on the fly so that the PPG data is good quality see this example for VC31 in bangle https://github.com/espruino/Espruino/blob/master/libs/misc/hrm_vc31.c#L268
-
• #8
Thanks!
Edit: deleted wild guesswork, misread the datasheet. I don't know what I'm doing.
On the algorithm part, I didn't find the code using that driver (it seems to be dead code, replaced by a driver for the HX3605), but I expect to be able to use what jeffmer has for processing the signal.
-
• #9
I guess I'm an idiot. Readings were all over the place because I was reading noise. Led doesn't seem to turn on.
-
• #10
Here's my "driver" at this point, if anyone wants to give a shot at understanding what's missing.
// Driver for HX3603, see https://github.com/moyitai/5.26-sdk-1.0.2--integrate/blob/master/apps/common/device/hr_sensor/hrs3603.c var dev = wOS.i2c.bind(0x44); // device I2C address functionHX3603 (){ var conf_hrs = { // Clock SAMPLE_RATE : 25, // Hz PRF_CLK_NUM : 32000/SAMPLE_RATE, // PS1 PRF [0-255] PS1_INTERVAL_I2C : 64, // NOTE : 3600 datasheet reco // Phase Enable HRS_ENABLE : 1, PS0_ENABLE : 0, PS1_ENABLE : 1, TS_ENABLE : 0, // Oversampling rate 0:128 1:256 2:512 3:1024 HRS_PS0_TS_OSR : 3, PS1_OSR : 3, // LED & pulse delay HRS_LED : 1, HRS_CKAFE : 1, HRS_CKAFE_CYCLE : 50, PS1_LED : 1, PS1_CKAFE : 1, PS1_CKAFE_CYCLE : 50, // LED Driver current HRS_LEDDR_MSB : 3, PS1_LEDDR_MSB : 7, LEDDR_LSB : 7, // LES SEL ? LEDSEL_HRS : 1, LEDSEL_PS1 : 2, FORCE_LEDSEL : 0, RESET_CYCLE : 5, // DC signal canceling DCCANCEL_HRS_IDAC : 0, DCCANCEL_PS1_IDAC : 0, // EXT PD (power delivery) // Undocumented EXTPD_SEL : 1, EXTPD_PS1 : 1, EXTPD_HRS : 1, // TIA 1 | Integrator 0 TIA_PS1 : 1, TIA_HRS : 1, // TIA feedback resistor RFSEL_HRS : 6, RFSEL_PS1 : 1, // Integrator Capacitor ? HRS_PS0_TS_INT_CAP : 15, PS1_INT_CAP : 15, // Clock polatiry ? ADC_DATA_CLK_POL : 3, }; var mode_hrs = 1; var hrs = { dev:dev, disable: ()=>{ dev.writeByte(0x01, 0x01); dev.writeByte(0x02, 0x01); delay(50); dev.writeByte(0x1a, 0x13); }, enable: ()=>{ var c = conf_hrs; dev.writeByte(0x1e, 0x00); // Clock config dev.writeByte(0x10, c.PRF_CLK_NUM & 0xff); dev.writeByte(0x11, (c.PRF_CLK_NUM & 0x0f00)>>8); // Phase enable and OSR dev.writeByte(0x01, c.TS_ENABLE<<4 | c.PS0_ENABLE <<3 | c.HRS_ENABLE<<2 | c.HRS_PS0_TS_OSR); dev.writeByte(0x02, c.PS1_ENABLE<<2 | c.PS1_OSR); // LED enable and AFE clock enable dev.writeByte(0x03, c.HRS_LED<<3 | c.PS1_LED<<2 | c.HRS_CKAFE<<1 | c.PS1_CKAFE); // 00001111 // ?:0000|HRS_LED:1|PS1_LED:1|HRS_CKAFE:1|PS1_CKAFE:1 dev.writeByte(0x08, c.HRS_CKAFE_CYCLE); // hrs ledontime, 0~255, led on time = 4*hrsckafe_cycle*0.382us dev.writeByte(0x09, c.PS1_CKAFE_CYCLE); // ps1 ledontime, 0~255, led on time = 4*ps1_ckafe_cycle*0.382us // clock reset config dev.writeByte(0x04, c.RESET_CYCLE); // 00000101 // 3 bit RESET CYCLE sel // (2^(reset_cycle+1))-1*/ // PRF dev.writeByte(0x1c, c.PS1_INTERVAL_I2C); // PS1 PRF 0-255 - 25 mean 1s dev.writeByte(0x05, c.DCCANCEL_PS1_IDAC); // offset DCCANCEL PS1 dev.writeByte(0x06, c.DCCANCEL_HRS_IDAC); // offset DCCANCEL HRS/TS/PS0 dev.writeByte(0x07, c.LEDSEL_PS1<<4 | c.FORCE_LEDSEL<<3 | LEDSEL_HRS); // 00100010 // ?:0|LEDSEL_PS1:010|FORCE_LEDSEL:0|LEDSEL_HRS:001 dev.writeByte(0x0a, c.PS1_INT_CAP<<4 | c.HRS_PS0_TS_INT_CAP); // 11111111 // INTCAPSEL Integrator gain ? dev.writeByte(0x0b, c.PS1_LEDDR_MSB<<4 | c.HRS_LEDDR_MSB); // 01110011 // PS1:0111|HRS:0011 LEDDR [0-7] dev.writeByte(0xc4, c.EXTPD_SEL<<7 | (c.LEDDR_LSB&0x07)<<4 | 0x0f); // 11111111 // extpd_sel | leddr_lsb | 0x0f dev.writeByte(0x0d, c.RFSEL_HRS <<4 | c.RFSEL_PS1); // 01100001 // rfsel_hrs | rfsel_ps1 dev.writeByte(0x0c, c.RFSEL_PS1); // 00000001 // rfsel_ps1 feedback resistor ? dev.writeByte(0x0f, 0x00); // dev.writeByte(0x0e, c.EXTPD_PS1<<3 | c.EXTPD_HRS<<2 | c.TIA_PS1<<1 | c.TIA_HRS); // 00001111 extpd_ps1:1 | extpd_hrs:1 | tia_ps1:1 | tia_hrs:1 dev.writeByte(0x1b, 0x7f); // dev.writeByte(0xc2, 0x10); // Self test dev.writeByte(0xc3, 0xff); // dev.writeByte(0x18, c.ADC_DATA_CLK_POL); // adc_data_clk_pol dev.writeByte(0x1a, 0x12); // dev.writeByte(0x13, 0x02); // enable mode (ps1|hrs) dev.writeByte(0x12, 0x50); // dev.writeByte(0x20, 0x20); // close fifo int }, read_hrs:()=>{ var data = dev.readBytes(0xa0,12); var p0 = (data[0]) | (data[1]<<8) | (data[2]<<16); var p1 = (data[3]) | (data[4]<<8) | (data[5]<<16); return { als:[p0, p1], hrs: (p0>p1)?p0-p1:0}; } }; return hrs; }
And here's the module I use it with
var correlator = function(cspan) { var CMIN = 7; var CMAX = 37; var NSLOT = 128; var buffer = new Array(NSLOT); var next=0; cspan = cspan || 1; return { put: (v)=>{ buffer[next] = v; next = (next+1)%NSLOT; }, get bpm(){ var minCorr=0x7FFFFFFF; var span=0; for (var c=CMIN; c<CMAX; c++) { var s = 0; var a = (next-c*cspan) % NSLOT; var b = next; for (var i=0; i<(NSLOT-CMAX);i++){ var d = buffer[b]-buffer[a]; b = (b+1)%NSLOT; a = (a+1)%NSLOT; s += d*d; } if (s<minCorr) {minCorr = s; span = c;} } return span == 0 ? 0:(60000/(span*40)); } }; }; function hrm(){ var corr = correlator(); var iv; var hrm = { read:()=>{ var v = wOS.hrs.read_hrs(); corr.put(v.hrs); }, start: (record)=>{ if (iv) return; wOS.hrs.enable(); iv = setInterval(hrm.read, 40); }, stop: ()=>{ if (!iv) return; iv = clearInterval(iv); wOS.hrs.disable(); }, get bpm() { return corr.bpm(); }, }; return hrm; }
-
• #11
This is the datasheet for what seems to be an earlier iteration of the same chip architecture. Register addresses are not accurate and some are missing, but it looks overall closer to the 3603 than the 3605, 3300 or 3313 and provides a bit more info.
Edit : scratch that, it's basically the same as the 3313.
http://www.synercontech.com/Public/uploads/file/2019_10/20191020152311_81180.pdf
-
• #12
Le led driver current control implementation is described like this in the 3603 datasheet :
LED current range 3.2 to 204.8 mA
LED current resolution 6 Bits
...
The device has one internal current DAC 3bits output current control.
The output current control though register 0x0Bbit2:bit0for phase1
to phase3.And in the driver :
// LED Driver current HRS_LEDDR_MSB : 3, PS1_LEDDR_MSB : 7, LEDDR_LSB : 7, ... dev.writeByte(0x0b, c.PS1_LEDDR_MSB<<4 | c.HRS_LEDDR_MSB); dev.writeByte(0xc4, c.EXTPD_SEL<<7 | (c.LEDDR_LSB&0x07)<<4 | 0x0f);
Look vaguely consistent. Shame that the 0xC4 register isn't documented (among others).
-
• #13
One weird thing (to me, maybe it's normal) is that the I2C device needs to be 'primed' by a first read (write?) to its registers to have them initialized. The first read seems to be garbage.
Here's attached a dump of the registers after priming, with annotations.
1 Attachment
-
• #14
A few updates :
- Added some device drivers (accel, touch, buzzer)
- Added a context manager and a few utils to Graphics
- Started implementing an execution manager module, the objective is to have transition management between apps
- Added two simple clock apps (one is a reimplementation of slopeclock, not yet animated)
I'm not trying to be compatible with Bangle.js, maybe I'll end up implementing something very similar, maybe not.
I'd like to tinker a bit with low-level graphics eventually in order to implement some efficient masking, but I'm kind of afraid to brick my device in the process.
1 Attachment
- Added some device drivers (accel, touch, buzzer)
-
• #15
Nice :-)
but I'm kind of afraid to brick my device in the process.
Use watchdog, ping it only when button is not held. Something like this https://github.com/fanoush/ds-d6/blob/master/espruino/DFU/Magic3/code.js#L1C1-L6C1
will reboot if something freezes the interpreter event loop or you hold the button. With Rock you can even use the second button for that (BTN2)If you flashed my bootloader then it is standard Espruino bootloader so it should enter DFU mode when you hold button at reboot time for less than 3 seconds or it enters Espruino but avoids running any js code at startup if you hold it longer (test it now :-), if you put watchdog pinging on BTN2 then you could decide to just reboot (by holding BTN2) or also enter DFU or skip code at startup by holding both.
You could also enable watchdog directly inside C startup code if you modify Espruino FW and are afraid of freezing at FW initialization before js code is executed.
Also if you don't use or trust watchdog then keep battery level low and/or enable something that drains battery fast to let it die sooner (backlight, or maybe Serial port is more harmless - it should die in a day or two from full battery) and you can then hold BTN1 before you attach charger.
-
• #16
I do use the watchdog already, a lot of my early code comes from your demo and jeffmer's repo.
However what I'd like to do is to create a few graphics routines in C to avoid the overhead from the interpreter. At the moment I'm drawing overlays upon overlays to achieve masking, which is pretty suboptimal.
Gotta put my head into custom builds but that's a lot of foreign stuff to take in :).
Edit : That
jswrap_graphics.c
file is brutal. -
• #17
If we could have Kospet Rock with transflective display 🤤
Hi there !
I recently got a Kospet ROCK (C16) smartwatch, that I almost instantly turned into a near-useless Espruino-powered tinkering playground thanks to the instructions shared by @fanoush and @jeffmer.
I don't have a lot of experience in embedded stuff apart from playing a bit with adafruit's circuitpython, but I'm looking forward to make my watch vaguely useful again with some code of my own.
I don't have a precise idea yet of what I want to do with it, apart from restoring its time and fitness abilities.
Code will be here :
https://gitlab.com/ddelemeny/watchapps
Any other projects on this watch I should know about ?