/
jswrap_puck.c
1848 lines (1686 loc) · 56.2 KB
/
jswrap_puck.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
*
* Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* ----------------------------------------------------------------------------
* This file is designed to be parsed during the build process
*
* Contains JavaScript interface for Puck.js
* ----------------------------------------------------------------------------
*/
#include "jswrap_puck.h"
#include "jsinteractive.h"
#include "jsdevices.h"
#include "jshardware.h"
#include "jsdevices.h"
#include "jspin.h"
#include "jsflags.h"
#include "jstimer.h"
#include "jswrap_bluetooth.h"
#include "nrf_gpio.h"
#include "nrf_delay.h"
#include "nrf5x_utils.h"
#include "ble_gap.h"
#include "jsflash.h" // for jsfRemoveCodeFromFlash
#include "jsi2c.h" // accelerometer/etc
#include "bluetooth.h"
#include "app_timer.h"
#define MAG3110_ADDR 0x0E
#define LIS3MDL_ADDR 0x1E // MAG_ADDR
#define MMC5603NJ_ADDR 0x30
#define I2C_TIMEOUT 100000
JshI2CInfo i2cMag;
JshI2CInfo i2cAccel;
JshI2CInfo i2cTemp;
PuckVersion puckVersion;
IOEventFlags puckAccelChannel = EV_NONE;
APP_TIMER_DEF(m_poll_timer_id);
const Pin PUCK_IO_PINS[] = {1,2,4,6,7,8,23,24,28,29,30,31};
#define IR_INPUT_PIN 25 // Puck v2
#define IR_FET_PIN 27 // Puck v2
#define FET_PIN 26 // Puck v2
// For Puck.js lite we don't define LED3, for the bootloader's sake - but we define it here so we can self-test it's not connected
#ifndef LED3_PININDEX
#define LED3_PININDEX 3
#define LED3_ONSTATE 1
#endif
bool mag_enabled = false; //< Has the magnetometer been turned on?
uint16_t mag_power; // est mag power in uA
int16_t mag_reading[3]; //< magnetometer xyz reading
//int mag_zero[3]; //< magnetometer 'zero' reading, only for Puck 2.1 right now
volatile bool mag_data_ready = false;
bool accel_enabled = false; //< Has the accelerometer been turned on?
uint16_t accel_power; // est mag power in uA
int16_t accel_reading[3];
int16_t gyro_reading[3];
bool mag_wait();
void mag_read();
int getMagAddr() {
switch (puckVersion) {
case PUCKJS_1V0: return MAG3110_ADDR;
case PUCKJS_2V0: return LIS3MDL_ADDR;
case PUCKJS_2V1: return MMC5603NJ_ADDR;
default: return 0;
}
}
JsVar *jswrap_puck_getHardwareVersion() {
switch (puckVersion) {
case PUCKJS_1V0: return jsvNewFromInteger(1);
case PUCKJS_2V0: return jsvNewFromInteger(2);
case PUCKJS_2V1: return jsvNewFromFloat(2.1);
case PUCKJS_LITE_1V0: return jsvNewFromString("Lite 1");
default: return NULL;
}
}
const char *jswrap_puck_getHardwareVersionString() {
switch (puckVersion) {
case PUCKJS_1V0: return "1";
case PUCKJS_2V0: return "2";
case PUCKJS_2V1: return "2.1";
case PUCKJS_LITE_1V0: return "Lite 1";
default: return "(Unknown Version)";
}
}
void jswrap_puck_notAvailableException(const char *hardware) {
jsExceptionHere(JSET_ERROR, "%s not available on Puck.js %s", hardware, jswrap_puck_getHardwareVersionString());
}
JsVar *to_xyz(int16_t d[3], double scale) {
JsVar *obj = jsvNewObject();
if (!obj) return 0;
jsvObjectSetChildAndUnLock(obj,"x",jsvNewFromFloat(d[0]*scale));
jsvObjectSetChildAndUnLock(obj,"y",jsvNewFromFloat(d[1]*scale));
jsvObjectSetChildAndUnLock(obj,"z",jsvNewFromFloat(d[2]*scale));
return obj;
}
/// MAG3110 I2C implementation - write pin
void wr(int pin, bool state) {
if (state) {
nrf_gpio_pin_set(pin); nrf_gpio_cfg_output(pin);
nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_PULLUP);
} else {
nrf_gpio_pin_clear(pin);
nrf_gpio_cfg_output(pin);
}
}
/// MAG3110 I2C implementation - read pin
bool rd(int pin) {
return nrf_gpio_pin_read(pin);
}
/// MAG3110 I2C implementation - delay
void dly() {
volatile int i;
for (i=0;i<10;i++);
}
/// MAG3110 I2C implementation - show error
void err(const char *s) {
jsiConsolePrintf("I2C: %s\n", s);
}
/// MAG3110 I2C implementation has i2c started?
bool started = false;
/// MAG3110 I2C implementation - start bit
void i2c_start() {
if (started) {
// reset
wr(MAG_PIN_SDA, 1);
dly();
wr(MAG_PIN_SCL, 1);
int timeout = I2C_TIMEOUT;
while (!rd(MAG_PIN_SCL) && --timeout); // clock stretch
if (!timeout) err("Timeout (start)");
dly();
}
if (!rd(MAG_PIN_SDA)) err("Arbitration (start)");
wr(MAG_PIN_SDA, 0);
dly();
wr(MAG_PIN_SCL, 0);
dly();
started = true;
}
/// MAG3110 I2C implementation - stop bit
void i2c_stop() {
wr(MAG_PIN_SDA, 0);
dly();
wr(MAG_PIN_SCL, 1);
int timeout = I2C_TIMEOUT;
while (!rd(MAG_PIN_SCL) && --timeout); // clock stretch
if (!timeout) err("Timeout (stop)");
dly();
wr(MAG_PIN_SDA, 1);
dly();
if (!rd(MAG_PIN_SDA)) err("Arbitration (stop)");
dly();
started = false;
}
/// MAG3110 I2C implementation - write bit
void i2c_wr_bit(bool b) {
wr(MAG_PIN_SDA, b);
dly();
wr(MAG_PIN_SCL, 1);
dly();
int timeout = I2C_TIMEOUT;
while (!rd(MAG_PIN_SCL) && --timeout); // clock stretch
if (!timeout) err("Timeout (wr)");
wr(MAG_PIN_SCL, 0);
wr(MAG_PIN_SDA, 1); // stop forcing SDA (needed?)
}
/// MAG3110 I2C implementation - read bit
bool i2c_rd_bit() {
wr(MAG_PIN_SDA, 1); // stop forcing SDA
dly();
wr(MAG_PIN_SCL, 1); // stop forcing SDA
int timeout = I2C_TIMEOUT;
while (!rd(MAG_PIN_SCL) && --timeout); // clock stretch
if (!timeout) err("Timeout (rd)");
dly();
bool b = rd(MAG_PIN_SDA);
wr(MAG_PIN_SCL, 0);
return b;
}
/// MAG3110 I2C implementation - write byte, true on ack, false on nack
bool i2c_wr(uint8_t data) {
int i;
for (i=0;i<8;i++) {
i2c_wr_bit(data&128);
data <<= 1;
}
return !i2c_rd_bit();
}
/// MAG3110 I2C implementation - read byte
uint8_t i2c_rd(bool nack) {
int i;
int data = 0;
for (i=0;i<8;i++)
data = (data<<1) | (i2c_rd_bit()?1:0);
i2c_wr_bit(nack);
return data;
}
/// Write to I2C register on magnetometer
void mag_wr(int addr, int data) {
int iaddr = getMagAddr();
if (puckVersion == PUCKJS_1V0) { // MAG3110
i2c_start();
i2c_wr(iaddr<<1);
i2c_wr(addr);
i2c_wr(data);
i2c_stop();
wr(MAG_PIN_SDA, 1);
wr(MAG_PIN_SCL, 1);
} else {
unsigned char buf[2];
buf[0] = addr;
buf[1] = data;
jsi2cWrite(&i2cMag, iaddr, 2, buf, true);
}
}
/// Read from I2C register on magnetometer
void mag_rd(int addr, unsigned char *data, int cnt) {
int iaddr = getMagAddr();
if (puckVersion == PUCKJS_1V0) { // MAG3110
i2c_start();
i2c_wr(iaddr<<1);
i2c_wr(addr);
i2c_start();
i2c_wr(1|(iaddr<<1));
for (int i=0;i<cnt;i++) {
data[i] = i2c_rd(i==(cnt-1));
}
i2c_stop();
wr(MAG_PIN_SDA, 1);
wr(MAG_PIN_SCL, 1);
} else {
unsigned char buf[1];
buf[0] = addr;
jsi2cWrite(&i2cMag, iaddr, 1, buf, false);
jsi2cRead(&i2cMag, iaddr, cnt, data, true);
}
}
void mag_pin_on() {
jshPinSetValue(MAG_PIN_PWR, 1);
jshPinSetValue(MAG_PIN_SCL, 1);
jshPinSetValue(MAG_PIN_SDA, 1);
jshPinSetState(MAG_PIN_SCL, JSHPINSTATE_GPIO_OUT_OPENDRAIN_PULLUP);
jshPinSetState(MAG_PIN_SDA, JSHPINSTATE_GPIO_OUT_OPENDRAIN_PULLUP);
jshPinSetState(MAG_PIN_PWR, JSHPINSTATE_GPIO_OUT);
if (puckVersion == PUCKJS_1V0) {
// IRQ line on Puck.js 1v0 is often 0 - don't pull up
jshPinSetState(MAG_PIN_INT, JSHPINSTATE_GPIO_IN);
} else if (puckVersion == PUCKJS_2V0) {
// using DRDY for data
jshPinSetState(MAG_PIN_DRDY, JSHPINSTATE_GPIO_IN);
}
}
// Turn magnetometer on and configure. If instant=true we take a reading right away
bool mag_on(int milliHz, bool instant) {
//jsiConsolePrintf("mag_on\n");
mag_pin_on();
mag_power = 0;
if (puckVersion == PUCKJS_1V0) { // MAG3110
jshDelayMicroseconds(2500); // 1.7ms from power on to ok
if (instant) milliHz = 80000;
int reg1 = 0;
if (milliHz == 80000) { reg1 |= (0x00)<<3; mag_power = 900; }
else if (milliHz == 40000) { reg1 |= (0x04)<<3; mag_power = 550; }
else if (milliHz == 20000) { reg1 |= (0x08)<<3; mag_power = 275; }
else if (milliHz == 10000) { reg1 |= (0x0C)<<3; mag_power = 137; }
else if (milliHz == 5000) { reg1 |= (0x10)<<3; mag_power = 69; }
else if (milliHz == 2500) { reg1 |= (0x14)<<3; mag_power = 34; }
else if (milliHz == 1250) { reg1 |= (0x18)<<3; mag_power = 17; }
else if (milliHz == 630) { reg1 |= (0x1C)<<3; mag_power = 8; }
else if (milliHz == 310) { reg1 |= (0x1D)<<3; mag_power = 8; }
else if (milliHz == 160) { reg1 |= (0x1E)<<3; mag_power = 8; }
else if (milliHz == 80) { reg1 |= (0x1F)<<3; mag_power = 8; }
else return false;
jshDelayMicroseconds(2000); // 1.7ms from power on to ok
mag_wr(0x11, 0x80/*AUTO_MRST_EN*/ + 0x20/*RAW*/); // CTRL_REG2
mag_wr(0x10, reg1 | 1); // CTRL_REG1, samplerate + active
} else if (puckVersion == PUCKJS_2V0) { // LIS3MDL
jshDelayMicroseconds(10000); // takes ages to start up
if (instant) milliHz = 80000;
bool lowPower = false;
int reg1 = 0x80; // temp sensor, low power
if (milliHz == 80000) { reg1 |= 7<<2; mag_power = 900; }
else if (milliHz == 40000) { reg1 |= 6<<2; mag_power = 550; }
else if (milliHz == 20000) { reg1 |= 5<<2; mag_power = 275; }
else if (milliHz == 10000) { reg1 |= 4<<2; mag_power = 137; }
else if (milliHz == 5000) { reg1 |= 3<<2; mag_power = 69; }
else if (milliHz == 2500) { reg1 |= 2<<2; mag_power = 34; }
else if (milliHz == 1250) { reg1 |= 1<<2; mag_power = 17; }
else if (milliHz <= 630) { /*if (milliHz == 630 || milliHz == 625)*/
// We just go for the lowest power mode
reg1 |= 0<<2; mag_power = 8;
lowPower = true;
}
else return false;
mag_wr(0x21, 0x00); // CTRL_REG2 - full scale +-4 gauss
mag_wr(0x20, reg1); // CTRL_REG1
mag_wr(0x23, 0x02); // CTRL_REG4 - low power, LSb at higher address (to match MAG3110)
mag_wr(0x22, lowPower ? 0x20 : 0x00); // CTRL_REG3 - normal or low power, continuous measurement
mag_wr(0x24, 0x40); // CTRL_REG5 - block data update
} else if (puckVersion == PUCKJS_2V1) { // MMC5603NJ
jshDelayMicroseconds(20000); // wait for boot
int hz = (milliHz+500) / 1000;
if (hz<1) hz=1;
bool highPower = false; // for 1kHz
if (instant || hz>255) {
hz=255; // max speed in default mode
highPower = true;
}
mag_power = 13 * hz;
/* mag_zero[0]=0;
mag_zero[1]=0;
mag_zero[2]=0;
int mag_min[3];
int mag_max[3];
mag_wr(0x1B, 0b00001001); // IC0 - SET - plus single measurement
mag_wait();
mag_read();
memcpy(mag_min, mag_reading, sizeof(mag_reading));
mag_wr(0x1B, 0b00010001); // IC0 - RESET - plus single measurement
mag_wait();
mag_read();
memcpy(mag_max, mag_reading, sizeof(mag_reading));*/
mag_wr(0x1A, hz); // ODR - hz
mag_wr(0x1C, 3); // IC1 - 1.2ms (fastest sample time)
mag_wr(0x1B, 0b10100000); // IC0 - auto SR, continuous mode enable
mag_wr(0x1D, 0b00011001| (highPower?0x80:0)); // IC2 - periodic set every 25, continuous mode enable
/* mag_zero[0] = (mag_min[0]+mag_max[0])/2;
mag_zero[1] = (mag_min[1]+mag_max[1])/2;
mag_zero[2] = (mag_min[2]+mag_max[2])/2;
jsiConsolePrintf("min %d,%d,%d\n", mag_min[0], mag_min[1], mag_min[2]);
jsiConsolePrintf("max %d,%d,%d\n", mag_max[0], mag_max[1], mag_max[2]);
jsiConsolePrintf("zer %d,%d,%d\n", mag_zero[0], mag_zero[1], mag_zero[2]);
mag_reading[0]=0;
mag_reading[1]=0;
mag_reading[2]=0;*/
// read data anyway, to ensure data ready status bit it cleared
mag_read();
if (!instant) // if we're instant, don't start a timer as we just want to read in the main thread
app_timer_start(m_poll_timer_id, APP_TIMER_TICKS(1000000 / milliHz, APP_TIMER_PRESCALER), NULL);
} else {
// not supported
return false;
}
// if instant, we take a reading right away
if (instant) {
mag_wait();
mag_read();
if (puckVersion == PUCKJS_2V1) { // MMC5603NJ
/* On newest batch of MMC5603NJ (used from June 2022) the first read after power on
* doesn't always seem to be reliable, so if we're instant (in which case magnetometer
* is working at ~1000Hz) then if the data seems wrong we'll wait until we get a good reading */
int timeout = 5;
while (mag_reading[0]==-32768 && mag_reading[1]==-32768 && mag_reading[2]==-32768 && --timeout) {
mag_wait();
mag_read();
}
}
}
return true;
}
// Wait for magnetometer IRQ line to be set
bool mag_wait() {
int timeout = 0;
unsigned char buf[1];
if (puckVersion == PUCKJS_1V0) { // MAG3110
timeout = I2C_TIMEOUT*2;
while (!nrf_gpio_pin_read(MAG_PIN_INT) && --timeout);
} else if (puckVersion == PUCKJS_2V0) { // LIS3MDL
timeout = 400;
do {
mag_rd(0x27, buf, 1); // STATUS_REG
} while (!(buf[0]&0x08) && --timeout); // ZYXDA
} else if (puckVersion == PUCKJS_2V1) { // MMC5603NJ
timeout = 400;
do {
mag_rd(0x18, buf, 1); // Status
} while ((!(buf[0]&0x40)) && --timeout); // check for Meas_m_done
}
if (!timeout) {
jsExceptionHere(JSET_INTERNALERROR, "Timeout (Magnetometer)");
return false;
}
return true;
}
// Read a value
void mag_read() {
unsigned char buf[9];
if (puckVersion == PUCKJS_1V0) { // MAG3110
mag_rd(0x01, buf, 6); // OUT_X_MSB
mag_reading[0] = (int16_t)((buf[0]<<8) | buf[1]);
mag_reading[1] = (int16_t)((buf[2]<<8) | buf[3]);
mag_reading[2] = (int16_t)((buf[4]<<8) | buf[5]);
} else if (puckVersion == PUCKJS_2V0) { // LIS3MDL
mag_rd(0x28, buf, 6);
mag_reading[0] = (int16_t)((buf[0]<<8) | buf[1]);
mag_reading[1] = (int16_t)((buf[2]<<8) | buf[3]);
mag_reading[2] = (int16_t)((buf[4]<<8) | buf[5]);
} else if (puckVersion == PUCKJS_2V1) { // MMC5603NJ
/*mag_rd(0x00, buf, 9);
mag_reading[0] = ((buf[0]<<12) | (buf[1]<<4) | (buf[6]>>4)) - (1<<19);
mag_reading[1] = ((buf[2]<<12) | (buf[3]<<4) | (buf[7]>>4)) - (1<<19);
mag_reading[2] = ((buf[4]<<12) | (buf[5]<<4) | (buf[8]>>4)) - (1<<19);*/
mag_rd(0x00, buf, 6);
mag_reading[0] = ((buf[0]<<8) | buf[1]) - 32768;
mag_reading[1] = ((buf[2]<<8) | buf[3]) - 32768;
mag_reading[2] = ((buf[4]<<8) | buf[5]) - 32768;
}
}
// Get temperature, shifted left 8 bits
int mag_read_temp() {
unsigned char buf[2];
if (puckVersion == PUCKJS_1V0) { // MAG3110
mag_rd(0x0F, buf, 1); // DIE_TEMP
return ((int8_t)buf[0])<<8;
} else if (puckVersion == PUCKJS_2V0) { // LIS3MDL
mag_rd(0x2E, buf, 2); // TEMP_OUT_L
int16_t t = buf[0] | (buf[1]<<8);
// 20 degree offset based on tests here
return (int)(t >> 3) + (20 << 8);
} else if (puckVersion == PUCKJS_2V1) { // MMC5603NJ
mag_wr(0x1B, 0x02); // Take temperature measurement
// Wait for completion
int timeout = 100;
do {
mag_rd(0x18, buf, 1); // Status
} while (!(buf[0]&0x80) && --timeout); // check for Meas_t_done
// get measurement
mag_rd(0x07, buf, 1); // Status
return (buf[0] - 75) << 8;
} else
return -1;
}
// Turn magnetometer off
void mag_off() {
if (puckVersion == PUCKJS_2V1)
app_timer_stop(m_poll_timer_id);
//jsiConsolePrintf("mag_off\n");
nrf_gpio_cfg_default(MAG_PIN_SDA);
nrf_gpio_cfg_default(MAG_PIN_SCL);
nrf_gpio_cfg_default(MAG_PIN_INT);
nrf_gpio_cfg_default(MAG_PIN_DRDY);
nrf_gpio_pin_clear(MAG_PIN_PWR);
nrf_gpio_cfg_output(MAG_PIN_PWR);
}
void peripheralPollHandler() {
if (puckVersion == PUCKJS_2V1) {
unsigned char buf[1];
mag_rd(0x18, buf, 1); // Status
if (buf[0]&0x40) {// check for Meas_m_done
mag_read();
mag_data_ready = true;
jshHadEvent();
}
}
}
bool accel_on(int milliHz) {
// CTRL1_XL / CTRL2_G
int reg = 0;
bool gyro = true;
accel_power = 0;
if (milliHz<12500) { // 1.6Hz, no gyro
reg = 11<<4;
gyro = false;
accel_power = 40;
} else if (milliHz==12500) { reg=1<<4; accel_power = 350; } // 12.5 Hz (low power)
else if (milliHz==26000) { reg=2<<4;accel_power = 450; } // 26 Hz (low power)
else if (milliHz==52000) { reg=3<<4; accel_power = 600; }// 52 Hz (low power)
else if (milliHz==104000) { reg=4<<4; accel_power = 1700; }// 104 Hz (normal mode)
else if (milliHz==208000) { reg=5<<4; accel_power = 3000; }// 208 Hz (normal mode)
else if (milliHz==416000) { reg=6<<4; accel_power = 5300; }// 416 Hz (high performance)
else if (milliHz==833000) { reg=7<<4; accel_power = 5500; }// 833 Hz (high performance)
else if (milliHz==1660000) { reg=8<<4; accel_power = 5500; }// 1.66 kHz (high performance)
else return false;
jshPinSetState(ACCEL_PIN_INT, JSHPINSTATE_GPIO_IN);
#ifdef ACCEL_PIN_PWR
jshPinSetState(ACCEL_PIN_PWR, JSHPINSTATE_GPIO_OUT);
#endif
jshPinSetState(ACCEL_PIN_SCL, JSHPINSTATE_GPIO_OUT_OPENDRAIN_PULLUP);
jshPinSetState(ACCEL_PIN_SDA, JSHPINSTATE_GPIO_OUT_OPENDRAIN_PULLUP);
#ifdef ACCEL_PIN_PWR
jshPinSetValue(ACCEL_PIN_PWR, 1);
#endif
jshPinSetValue(ACCEL_PIN_SCL, 1);
jshPinSetValue(ACCEL_PIN_SDA, 1);
jshDelayMicroseconds(20000); // 20ms boot from app note
// LSM6DS3TR
unsigned char buf[2];
buf[0] = 0x15; buf[1]=0x10; // CTRL6-C - XL_HM_MODE=1, low power accelerometer
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true);
buf[0] = 0x16; buf[1]=0x80; // CTRL6-C - G_HM_MODE=1, low power gyro
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true);
buf[0] = 0x18; buf[1]=0x38; // CTRL9_XL Acc X, Y, Z axes enabled
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true);
buf[0] = 0x10; buf[1]=reg | 0b00001011; // CTRL1_XL Accelerometer, +-4g, 50Hz AA filter
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true);
buf[0] = 0x11; buf[1]=gyro ? reg : 0; // CTRL2_G Gyro, 250 dps, no 125dps limit
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true);
buf[0] = 0x12; buf[1]=0x44; // CTRL3_C, BDU, irq active high, push pull, auto-inc
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true);
buf[0] = 0x0D; buf[1]=3; // INT1_CTRL - Gyro/accel data ready IRQ
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true);
return true;
}
// Read a value
void accel_read() {
unsigned char buf[12];
// FIXME: If just the accelerometer is on, we could avoid reading the gyro and read only 6 bytes
buf[0] = 0x22; // OUTX_L_G
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 1, buf, false);
jsi2cRead(&i2cAccel, ACCEL_ADDR, 12, buf, true);
gyro_reading[0] = (buf[1]<<8) | buf[0];
gyro_reading[1] = (buf[3]<<8) | buf[2];
gyro_reading[2] = (buf[5]<<8) | buf[4];
accel_reading[0] = (buf[7]<<8) | buf[6];
accel_reading[1] = (buf[9]<<8) | buf[8];
accel_reading[2] = (buf[11]<<8) | buf[10];
}
void accel_wait() {
int timeout;
unsigned char buf[1];
timeout = 400;
do {
buf[0] = 0x1E; // STATUS_REG
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 1, buf, false);
jsi2cRead(&i2cAccel, ACCEL_ADDR, 1, buf, true);
//jsiConsolePrintf("M %d\n", buf[0]);
} while (!(buf[0]&3) && --timeout); // ZYXDA
if (!timeout) jsExceptionHere(JSET_INTERNALERROR, "Timeout (Accelerometer)");
}
// Turn accelerometer off
void accel_off() {
#ifdef ACCEL_PIN_PWR
nrf_gpio_cfg_input(ACCEL_PIN_SDA, NRF_GPIO_PIN_NOPULL);
nrf_gpio_cfg_input(ACCEL_PIN_SCL, NRF_GPIO_PIN_NOPULL);
nrf_gpio_cfg_input(ACCEL_PIN_INT, NRF_GPIO_PIN_NOPULL);
nrf_gpio_pin_clear(ACCEL_PIN_PWR);
nrf_gpio_cfg_output(ACCEL_PIN_PWR);
#else
unsigned char buf[2];
buf[0] = 0x10; buf[1]=0; // CTRL1_XL - power down
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true);
buf[0] = 0x11; buf[1]=0; // CTRL2_G - power down
jsi2cWrite(&i2cAccel, ACCEL_ADDR, 2, buf, true);
#endif
}
/*JSON{
"type": "class",
"class" : "Puck",
"ifdef" : "PUCKJS"
}
Class containing [Puck.js's](http://www.puck-js.com) utility functions.
*/
/*JSON{
"type" : "variable",
"name" : "FET",
"generate_full" : "26",
"ifdef" : "PUCKJS",
"return" : ["pin",""]
}
On Puck.js V2 (not v1.0) this is the pin that controls the FET, for high-powered
outputs.
*/
/*JSON{
"type" : "staticmethod",
"class" : "Puck",
"name" : "mag",
"ifdef" : "PUCKJS",
"generate" : "jswrap_puck_mag",
"return" : ["JsVar", "An Object `{x,y,z}` of magnetometer readings as integers" ]
}
Turn on the magnetometer, take a single reading, and then turn it off again.
An object of the form `{x,y,z}` is returned containing magnetometer readings.
Due to residual magnetism in the Puck and magnetometer itself, with no magnetic
field the Puck will not return `{x:0,y:0,z:0}`.
Instead, it's up to you to figure out what the 'zero value' is for your Puck in
your location and to then subtract that from the value returned. If you're not
trying to measure the Earth's magnetic field then it's a good idea to just take
a reading at startup and use that.
With the aerial at the top of the board, the `y` reading is vertical, `x` is
horizontal, and `z` is through the board.
Readings are in increments of 0.1 micro Tesla (uT). The Earth's magnetic field
varies from around 25-60 uT, so the reading will vary by 250 to 600 depending on
location.
*/
JsVar *jswrap_puck_mag() {
if (!PUCKJS_HAS_MAG) {
jswrap_puck_notAvailableException("Magnetometer");
return 0;
}
/* If not enabled, turn on and read. If enabled,
* just pass out the last reading */
if (!mag_enabled) {
mag_on(0, true /*instant*/); // takes a reading right away
mag_off();
} else if (puckVersion == PUCKJS_2V1) { // MMC5603NJ
// magnetometer is on, but let's poll it quickly
// to see if we have any new data
unsigned char buf[1];
mag_rd(0x18, buf, 1); // Status
if (buf[0]&0x40) // check for Meas_m_done
mag_read();
}
return to_xyz(mag_reading, 1);
}
/*JSON{
"type" : "staticmethod",
"class" : "Puck",
"name" : "magTemp",
"ifdef" : "PUCKJS",
"generate" : "jswrap_puck_magTemp",
"return" : ["float", "Temperature in degrees C" ]
}
Turn on the magnetometer, take a single temperature reading from the MAG3110
chip, and then turn it off again.
(If the magnetometer is already on, this just returns the last reading obtained)
`E.getTemperature()` uses the microcontroller's temperature sensor, but this
uses the magnetometer's.
The reading obtained is an integer (so no decimal places), but the sensitivity
is factory trimmed. to 1°C, however the temperature offset isn't - so
absolute readings may still need calibrating.
*/
JsVarFloat jswrap_puck_magTemp() {
if (!PUCKJS_HAS_MAG) {
jswrap_puck_notAvailableException("Magnetometer");
return NAN;
}
int t;
if (!mag_enabled) {
mag_on(0, true /*instant*/); // takes a reading right away
t = mag_read_temp();
mag_off();
} else
t = mag_read_temp();
return ((JsVarFloat)t) / 256.0;
}
/*JSON{
"type" : "event",
"class" : "Puck",
"name" : "mag",
"params" : [["xyz","JsVar","an object of the form `{x,y,z}`"]],
"ifdef" : "PUCKJS"
}
Called after `Puck.magOn()` every time magnetometer data is sampled. There is
one argument which is an object of the form `{x,y,z}` containing magnetometer
readings as integers (for more information see `Puck.mag()`).
Check out [the Puck.js page on the
magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals) for more
information.
```JS
Puck.magOn(10); // 10 Hz
Puck.on('mag', function(e) {
print(e);
});
// { "x": -874, "y": -332, "z": -1938 }
```
*/
/*JSON{
"type" : "event",
"class" : "Puck",
"name" : "accel",
"params" : [["e","JsVar","an object of the form `{acc:{x,y,z}, gyro:{x,y,z}}`"]],
"ifdef" : "PUCKJS"
}
Only on Puck.js v2.0
Called after `Puck.accelOn()` every time accelerometer data is sampled. There is
one argument which is an object of the form `{acc:{x,y,z}, gyro:{x,y,z}}`
containing the data.
```JS
Puck.accelOn(12.5); // default 12.5Hz
Puck.on('accel', function(e) {
print(e);
});
//{
// "acc": { "x": -525, "y": -112, "z": 8160 },
// "gyro": { "x": 154, "y": -152, "z": -34 }
//}
```
The data is as it comes off the accelerometer and is not scaled to 1g. For more
information see `Puck.accel()` or [the Puck.js page on the
magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals).
*/
/*JSON{
"type" : "staticmethod",
"class" : "Puck",
"name" : "magOn",
"ifdef" : "PUCKJS",
"generate" : "jswrap_puck_magOn",
"params" : [
["samplerate","float","The sample rate in Hz, or undefined"]
]
}
Turn the magnetometer on and start periodic sampling. Samples will then cause a
'mag' event on 'Puck':
```
Puck.magOn();
Puck.on('mag', function(xyz) {
console.log(xyz);
// {x:..., y:..., z:...}
});
// Turn events off with Puck.magOff();
```
This call will be ignored if the sampling is already on.
If given an argument, the sample rate is set (if not, it's at 0.63 Hz). The
sample rate must be one of the following (resulting in the given power
consumption):
* 80 Hz - 900uA
* 40 Hz - 550uA
* 20 Hz - 275uA
* 10 Hz - 137uA
* 5 Hz - 69uA
* 2.5 Hz - 34uA
* 1.25 Hz - 17uA
* 0.63 Hz - 8uA
* 0.31 Hz - 8uA
* 0.16 Hz - 8uA
* 0.08 Hz - 8uA
When the battery level drops too low while sampling is turned on, the
magnetometer may stop sampling without warning, even while other Puck functions
continue uninterrupted.
Check out [the Puck.js page on the
magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals) for more
information.
*/
void jswrap_puck_magOn(JsVarFloat hz) {
if (!PUCKJS_HAS_MAG) {
jswrap_puck_notAvailableException("Magnetometer");
return;
}
if (mag_enabled) {
jswrap_puck_magOff();
// wait 1ms for power-off
jshDelayMicroseconds(1000);
}
int milliHz = (int)((hz*1000)+0.5);
if (milliHz<=0) milliHz=630;
if (!mag_on(milliHz, false /*not instant*/)) {
jsExceptionHere(JSET_ERROR, "Invalid sample rate %f - must be 80, 40, 20, 10, 5, 2.5, 1.25, 0.63, 0.31, 0.16 or 0.08 Hz", hz);
}
/* Setting a watch will push an event which will let Espruino
* go around the idle loop and call jswrap_puck_idle - which
* is where we actually collect the data. */
if (puckVersion == PUCKJS_1V0) {
jshPinWatch(MAG_PIN_INT, true, JSPW_NONE);
jshPinSetState(MAG_PIN_INT, JSHPINSTATE_GPIO_IN);
} else if (puckVersion == PUCKJS_2V0) {
jshPinWatch(MAG_PIN_DRDY, true, JSPW_NONE);
jshPinSetState(MAG_PIN_DRDY, JSHPINSTATE_GPIO_IN);
} // 2v1 doesn't have an IRQ line - we poll with peripheralPollHandler
mag_enabled = true;
}
/*JSON{
"type" : "staticmethod",
"class" : "Puck",
"name" : "magOff",
"ifdef" : "PUCKJS",
"generate" : "jswrap_puck_magOff"
}
Turn the magnetometer off
*/
void jswrap_puck_magOff() {
if (!PUCKJS_HAS_MAG) {
jswrap_puck_notAvailableException("Magnetometer");
return;
}
if (mag_enabled) {
if (puckVersion == PUCKJS_1V0) {
jshPinWatch(MAG_PIN_INT, false, JSPW_NONE);
} else if (puckVersion == PUCKJS_2V0) {
jshPinWatch(MAG_PIN_DRDY, false, JSPW_NONE);
} // 2v1 doesn't have an IRQ line
mag_off();
}
mag_enabled = false;
}
/*JSON{
"type" : "staticmethod",
"class" : "Puck",
"name" : "magWr",
"generate" : "jswrap_puck_magWr",
"params" : [
["reg","int",""],
["data","int",""]
],
"ifdef" : "PUCKJS"
}
Writes a register on the LIS3MDL / MAX3110 Magnetometer. Can be used for
configuring advanced functions.
Check out [the Puck.js page on the
magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals) for more
information and links to modules that use this function.
*/
void jswrap_puck_magWr(JsVarInt reg, JsVarInt data) {
if (!PUCKJS_HAS_MAG) {
jswrap_puck_notAvailableException("Magnetometer");
return;
}
mag_wr(reg, data);
}
/*JSON{
"type" : "staticmethod",
"class" : "Puck",
"name" : "magRd",
"generate" : "jswrap_puck_magRd",
"params" : [
["reg","int",""]
],
"return" : ["int",""],
"ifdef" : "PUCKJS"
}
Reads a register from the LIS3MDL / MAX3110 Magnetometer. Can be used for
configuring advanced functions.
Check out [the Puck.js page on the
magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals) for more
information and links to modules that use this function.
*/
int jswrap_puck_magRd(JsVarInt reg) {
if (!PUCKJS_HAS_MAG) {
jswrap_puck_notAvailableException("Magnetometer");
return -1;
}
unsigned char buf[1];
mag_rd(reg, buf, 1);
return buf[0];
}
// Turn temp sensor on
void temp_on() {
jshPinSetValue(TEMP_PIN_PWR, 1);
jshPinSetValue(TEMP_PIN_SCL, 1);
jshPinSetValue(TEMP_PIN_SDA, 1);
jshPinSetState(TEMP_PIN_PWR, JSHPINSTATE_GPIO_OUT);
jshPinSetState(TEMP_PIN_SCL, JSHPINSTATE_GPIO_OUT);
jshPinSetState(TEMP_PIN_SDA, JSHPINSTATE_GPIO_OUT);
jshPinSetState(TEMP_PIN_SCL, JSHPINSTATE_GPIO_OUT_OPENDRAIN_PULLUP);
jshPinSetState(TEMP_PIN_SDA, JSHPINSTATE_GPIO_OUT_OPENDRAIN_PULLUP);
jshDelayMicroseconds(50000); // wait for startup and first reading... could this be faster?
}
// Turn temp sensor off
void temp_off() {
nrf_gpio_cfg_input(TEMP_PIN_SDA, NRF_GPIO_PIN_NOPULL);
nrf_gpio_cfg_input(TEMP_PIN_SCL, NRF_GPIO_PIN_NOPULL);
nrf_gpio_pin_clear(TEMP_PIN_PWR);
nrf_gpio_cfg_output(TEMP_PIN_PWR);
}
/*JSON{
"type" : "staticmethod",
"class" : "Puck",
"name" : "getTemperature",
"ifdef" : "PUCKJS",
"generate" : "jswrap_puck_getTemperature",
"return" : ["float", "Temperature in degrees C" ]
}
On Puck.js v2.0 this will use the on-board PCT2075TP temperature sensor, but on
Puck.js the less accurate on-chip Temperature sensor is used.
*/
JsVarFloat jswrap_puck_getTemperature() {
if (PUCKJS_HAS_TEMP_SENSOR) {
temp_on();
unsigned char buf[2];
// 'on' is the default
//buf[1] = 1; // CONF
//buf[0] = 0; // on
//jsi2cWrite(&i2cTemp,TEMP_ADDR, 1, buf, false);
buf[0] = 0; // TEMP
jsi2cWrite(&i2cTemp,TEMP_ADDR, 1, buf, false);
jsi2cRead(&i2cTemp, TEMP_ADDR, 2, buf, true);
int t = (buf[0]<<3) | (buf[1]>>5);
if (t&1024) t-=2048; // negative
temp_off();
return t / 8.0;
} else {
return jshReadTemperature();
}
}
/*JSON{
"type" : "staticmethod",
"class" : "Puck",
"name" : "accelOn",
"ifdef" : "PUCKJS",
"generate" : "jswrap_puck_accelOn",
"params" : [
["samplerate","float","The sample rate in Hz, or `undefined` (default is 12.5 Hz)"]
]
}
Accepted values are:
* 1.6 Hz (no Gyro) - 40uA (2v05 and later firmware)
* 12.5 Hz (with Gyro)- 350uA
* 26 Hz (with Gyro) - 450 uA
* 52 Hz (with Gyro) - 600 uA
* 104 Hz (with Gyro) - 900 uA
* 208 Hz (with Gyro) - 1500 uA
* 416 Hz (with Gyro) (not recommended)
* 833 Hz (with Gyro) (not recommended)
* 1660 Hz (with Gyro) (not recommended)
Once `Puck.accelOn()` is called, the `Puck.accel` event will be called each time
data is received. `Puck.accelOff()` can be called to turn the accelerometer off.
For instance to light the red LED whenever Puck.js is face up:
```
Puck.on('accel', function(a) {
digitalWrite(LED1, a.acc.z > 0);
});
Puck.accelOn();
```