-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathIoTuz.cpp
555 lines (472 loc) · 17.1 KB
/
IoTuz.cpp
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
/***************************************************
IoTuz class to provide basic driver methods for the ESP32 based IoTuz board
from LCA 2017: https://github.com/CCHS-Melbourne/iotuz-esp32-hardware
By Marc MERLIN <marc_soft@merlins.org>
License: Apache 2.0.
Required libraries:
- Wire.h to support the pcf8574 port expander
****************************************************/
#include <Wire.h>
#include "IoTuz.h"
volatile int16_t encoder0Pos = 0;
// These need to be global because all sketches use them as a global
// tft adafruit library (games use tft2 a separate library, for which we skip the init
// since the adafruit init works fine for both)
#ifndef WROVER
// software SPI ought to work on IoTuz, but I get a white screen sometimes if I use it.
//Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, SPI_MOSI, SPI_CLK, TFT_RST, SPI_MISO);
// Whereas this shorter version works reliably for me
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
#else
WROVER_KIT_LCD tft;
#endif
// If you are lacking the ESP32 patch, you will get no error, but the LEDs will not work
#ifdef NEOPIXEL
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, RGB_LED_PIN, NEO_GRB + NEO_KHZ800);
#else
rgbVal pixels[NUMPIXELS];
#endif
// ADXL345
Adafruit_ADXL345_Unified accel = Adafruit_ADXL345_Unified(12345);
// Temp/Humidity/Pressure
Adafruit_BME280 bme;
// Touch screen
XPT2046_Touchscreen ts(TS_CS_PIN); // Param 2 - NULL - No interrupts)
IRrecv irrecv(IR_RX_PIN);
// Must be global to be passed to the IRRecord library.
decode_results IR_result;
// If enabled by the user, call every millisecond and run the Aiko
// event loop.
void onTimer() {
// Only run ISR loop events (those must be ISR safe obviously)
Events.loop(true);
}
// Aiko can be run from the sketch's mail loop by simply calling Events.loop();
// but if you want Aiko events to be run from an ISR while the main loop is busy
// doing something else, then you call this once, and it'll setup a timer to call
// your code on interval at a millisecond precision (could be lower if needed)
//
// Warning, you CANNOT call Serial or other things that take too long,
// take locks, or generate interrupts since you will already be inside
// an interrupt.
// No interrupt may spend more than 300ms, or the interrupt will time out with an
// error like this: Guru Meditation Error: Core 0 panic'ed (Interrupt wdt timeout on CPU1)
void IoTuz::enable_aiko_ISR() {
// ESP32 timer
hw_timer_t *timer;
// 3 timers, choose #3, 80 divider nanosecond precision, 1 to count up
timer = timerBegin(3, 80, 1);
timerAttachInterrupt(timer, &onTimer, 1);
// every 1,000ns = 1ms, autoreload = true
timerAlarmWrite(timer, 1000, true);
timerAlarmEnable(timer);
}
void read_encoder_ISR()
{
static uint8_t old_AB = 0;
// grey code
// http://hades.mech.northwestern.edu/index.php/Rotary_Encoder
// also read up on 'Understanding Quadrature Encoded Signals'
// https://www.pjrc.com/teensy/td_libs_Encoder.html
// another interesting lib: https://github.com/0xPIT/encoder/blob/arduino/ClickEncoder.cpp
static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
old_AB <<= 2;
old_AB |= ((digitalRead(ENCODERB_PIN))?(1<<1):0) | ((digitalRead(ENCODERA_PIN))?(1<<0):0);
encoder0Pos += ( enc_states[( old_AB & 0x0f )]);
}
// Talking to the port expander:
// I2C/TWI success (transaction was successful).
#define ku8TWISuccess 0
// I2C/TWI device not present (address sent, NACK received).
#define ku8TWIDeviceNACK 2
// I2C/TWI data not received (data sent, NACK received).
#define ku8TWIDataNACK 3
// I2C/TWI other error.
#define ku8TWIError 4
void IoTuz::pcf8574_write_(uint8_t dt)
{
uint8_t error;
#ifndef WROVER
Wire.beginTransmission(I2C_EXPANDER);
// Serial.print("Writing to I2CEXP: ");
// Serial.println(dt);
Wire.write(dt);
error = Wire.endTransmission();
if (error != ku8TWISuccess) {
// FIXME: do something here if you like
}
#endif
}
// To clear bit #7, send 128
void IoTuz::i2cexp_clear_bits(uint8_t bitfield)
{
// set bits to clear to 0, all other to 1, binary and to clear the bits
_i2cexp &= (~bitfield);
pcf8574_write_(_i2cexp);
}
// To set bit #7, send 128
void IoTuz::i2cexp_set_bits(uint8_t bitfield)
{
_i2cexp |= bitfield;
pcf8574_write_(_i2cexp);
}
uint8_t IoTuz::i2cexp_read()
{
#ifndef WROVER
// For read to work, we must have sent 1 bits on the ports that get used as input
// This is done by i2cexp_clear_bits called in setup.
Wire.requestFrom(I2C_EXPANDER, 1); // FIXME: deal with returned error here?
uint8_t read = ~Wire.read(); // Apparently one has to invert the bits read
// When no buttons are pushed, this returns 0x91, which includes some ports
// we use as output, so we do need to filter out the ports used as read.
// Serial.println(read, HEX);
return read;
#endif
}
int16_t IoTuz::read_encoder()
{
return encoder0Pos;
}
int8_t IoTuz::encoder_changed() {
static int16_t old_encoder0Pos = 0;
int8_t encoder0Diff = encoder0Pos - old_encoder0Pos;
old_encoder0Pos = encoder0Pos;
return encoder0Diff;
}
// There is no separate method to return button state changed
// because this one already does it, look for PUSHED and RELEASED
ButtState IoTuz::_but(uint8_t button)
{
static bool but[9];
#ifdef WROVER
uint8_t butt_state = !digitalRead(ENCODER_BUT_PIN);
#else
uint8_t butt_state = i2cexp_read() & button;
#endif
if (butt_state && !but[button])
{
but[button] = true;
Serial.println("Button Pushed");
return BUT_PUSHED;
}
if (!butt_state && but[button])
{
but[button] = false;
Serial.println("Button Released");
return BUT_RELEASED;
}
return (butt_state?BUT_DOWN:BUT_UP);
}
ButtState IoTuz::butEnc()
{
return(_but(I2CEXP_ENC_BUT));
}
ButtState IoTuz::butA()
{
return(_but(I2CEXP_A_BUT));
}
ButtState IoTuz::butB()
{
return(_but(I2CEXP_B_BUT));
}
void IoTuz::read_joystick(bool showdebug)
{
#ifdef WROVER
joyValueX = analogRead(JOYSTICK_X_PIN);
joyValueY = 4096-analogRead(JOYSTICK_Y_PIN);
#else
joyValueX = 4096-analogRead(JOYSTICK_X_PIN);
joyValueY = analogRead(JOYSTICK_Y_PIN);
#endif
joyBtn = !digitalRead(JOYSTICK_BUT_PIN);
// Sadly on my board, the middle is 1785/1850 and not 2048/2048 and it's not the same
// on other boards, so add a huge dead center:
joyRelX = map(joyValueX, 0, 1600, -5, 0);
joyRelY = map(joyValueY, 0, 1600, -5, 0);
if (joyValueX > 1600) joyRelX = map(constrain(joyValueX, 2400, 4095), 2400, 4095, 0, 5);
if (joyValueY > 1600) joyRelY = map(constrain(joyValueY, 2400, 4095), 2400, 4095, 0, 5);
if (showdebug) {
// print the results to the serial monitor:
Serial.print("X Axis = ");
Serial.print(joyValueX);
Serial.print("/rel: ");
Serial.print(joyRelX);
Serial.print("\t Y Axis = ");
Serial.print(joyValueY);
Serial.print("/rel: ");
Serial.print(joyRelY);
Serial.print("\t Joy Button = ");
Serial.println(joyBtn);
}
}
// True turns the BL on
void IoTuz::screen_bl(bool state) {
#ifndef WROVER
state ? i2cexp_clear_bits(I2CEXP_LCD_BL_CTR) : i2cexp_set_bits(I2CEXP_LCD_BL_CTR);
#else
pinMode(LCD_BL_CTR, OUTPUT);
digitalWrite(LCD_BL_CTR, LOW);
#endif
}
float IoTuz::battery_level()
{
// return volts, 75 is what I hand calculated as a ratio
return(float(analogRead(BAT_PIN)/75.0));
}
void IoTuz::reset_tft() {
tft.setRotation(3);
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.scrollTo(0);
}
// maxlength is the maximum number of characters that need to be deleted before writing on top
// 55x30 characters
void IoTuz::tftprint(uint8_t x, uint8_t y, uint8_t maxlength, char *text) {
if (maxlength > 0) tft.fillRect(x*6, y*8, maxlength*6, 8, ILI9341_BLACK);
tft.setCursor(x*6, y*8);
tft.println(text);
}
TS_Point IoTuz::get_touch() {
// Clear (i.e. set) CS for TS before talking to it
i2cexp_clear_bits(I2CEXP_TOUCH_CS);
// Calling getpoint calls SPI.beginTransaction with a speed of only 2MHz, so we need to
// reset the speed to something faster before talking to the screen again.
TS_Point p = ts.getPoint();
// Then disable it again so that talking SPI to LCD doesn't reach TS
i2cexp_set_bits(I2CEXP_TOUCH_CS);
if (ts_revX) p.x = 4096 - p.x;
if (ts_revY) p.y = 4096 - p.y;
// Tell the caller that the touch is bogus or none happened by setting z to 0.
if (p.z < minpressure || p.z > maxpressure) p.z = 0;
// I've seen bogus touches where both x and y were 0
if (p.x == 0 && p.y == 0) p.z = 0;
return p;
}
void IoTuz::touchcoord2pixelcoord(uint16_t *pixel_x, uint16_t *pixel_y, uint16_t pixel_z) {
// Pressure goes from 1000 to 2200 with a stylus but is unreliable,
// 3000 if you mash a finger in, let's say 2048 range
// Colors are 16 bits, so multiply pressure by 32 to get a color range from pressure
// X goes from 320 to 3900 (let's say 3600), Y goes from 200 to 3800 (let's say 3600 too)
// each X pixel is 11.25 dots of resolution on digitizer, and 15 dots for Y.
Serial.print("Converted touch coordinates ");
Serial.print(*pixel_x);
Serial.print("x");
Serial.print(*pixel_y);
Serial.print(" / pressure:");
Serial.print(pixel_z);
//*pixel_x = constrain((*pixel_x-320)/11.25, 0, 319);
//*pixel_y = constrain((*pixel_y-200)/15, 0, 239);
*pixel_x = map(*pixel_x, ts_minX, ts_maxX, 0, tftw);
*pixel_y = map(*pixel_y, ts_minY, ts_maxY, 0, tfth);
Serial.print(" to pixel coordinates ");
Serial.print(*pixel_x);
Serial.print("x");
Serial.println(*pixel_y);
}
void IoTuz::_getMinMaxTS () {
TS_Point p;
uint16_t untouch = 0;
do {
p = get_touch();
sprintf(tft_str, "%d", p.x);
tftprint(18, 15, 4, tft_str);
sprintf(tft_str, "%d", p.y);
tftprint(30, 15, 4, tft_str);
if (p.x < ts_minX) { ts_minX = p.x; untouch = 0; }
if (p.y < ts_minY) { ts_minY = p.y; untouch = 0; }
if (p.x > ts_maxX) { ts_maxX = p.x; untouch = 0; }
if (p.y > ts_maxY) { ts_maxY = p.y; untouch = 0; }
sprintf(tft_str, "%d", ts_minX);
tftprint(21, 16, 4, tft_str);
sprintf(tft_str, "%d", ts_maxX);
tftprint(33, 16, 4, tft_str);
sprintf(tft_str, "%d", ts_minY);
tftprint(21, 17, 4, tft_str);
sprintf(tft_str, "%d", ts_maxY);
tftprint(33, 17, 4, tft_str);
if (!p.z) untouch++;
if (!(untouch % 100)) {
Serial.print("Untouch count: ");
Serial.println(untouch);
}
} while (untouch < 300);
}
void IoTuz::calibrateScreen() {
TS_Point p;
Serial.print("Old calibration data MinX: ");
Serial.print(ts_minX);
Serial.print(", MaxX: ");
Serial.print(ts_maxX);
Serial.print(", MinY: ");
Serial.print(ts_minY);
Serial.print(", MaxY: ");
Serial.print(ts_maxY);
Serial.println();
ts_minX = ts_minY = ts_maxX = ts_maxY = 2048;
tftprint(15, 12, 0, "Touch Screen Calibration");
tftprint(15, 13, 0, "Please Draw the 4 lines");
tftprint(15, 14, 0, " From center to corner ");
tftprint(15, 15, 0, "X: 0000 Y: 0000 ");
tftprint(15, 16, 0, "MinX: 0000, MaxX: 0000");
tftprint(15, 17, 0, "MinY: 0000, MaxY: 0000");
tft.drawLine(32 , 24, 0, 0, ILI9341_BLUE);
// Wait for touch
do { p = get_touch();} while (!p.z);
if (p.x > 2048) {
ts_revX = true;
tftprint(15, 18, 0, "X Reversed");
}
if (p.y > 2048) {
ts_revY = true;
tftprint(26, 18, 0, "Y Reversed");
}
// Now that ts_revXY are set, get_touch will invert returned
// coordinates to make handling easier.
_getMinMaxTS();
tft.drawLine(287 , 24, 319, 0, ILI9341_BLUE);
_getMinMaxTS();
tft.drawLine(32 , 215, 0, 239, ILI9341_BLUE);
_getMinMaxTS();
tft.drawLine(287 , 215, 319, 239, ILI9341_BLUE);
_getMinMaxTS();
Serial.print("New calibration data MinX: ");
Serial.print(ts_minX);
Serial.print(", MaxX: ");
Serial.print(ts_maxX);
Serial.print(", MinY: ");
Serial.print(ts_minY);
Serial.print(", MaxY: ");
Serial.print(ts_maxY);
Serial.println();
tftprint(30, 20, 0, "Done!");
delay(3000);
}
IoTuz::IoTuz()
{
// Any write to I2CEXP should contain those mask bits so allow reads to work later
_i2cexp = I2CEXP_IMASK;
pinMode (ENCODERA_PIN, INPUT_PULLUP);
pinMode (ENCODERB_PIN, INPUT_PULLUP);
pinMode(SPI_MOSI, OUTPUT);
pinMode(SPI_MISO, INPUT);
pinMode(SPI_CLK, OUTPUT);
// Joystick Setup
pinMode(JOYSTICK_BUT_PIN, INPUT_PULLUP);
// TFT Setup
pinMode(TFT_CS, OUTPUT);
pinMode(TFT_DC, OUTPUT);
pinMode(TFT_RST, OUTPUT);
pinMode(IR_RX_PIN, INPUT);
#ifndef WROVER
pinMode(IR_TX_PIN, OUTPUT);
pinMode(BAT_PIN, INPUT);
#endif
}
void IoTuz::begin()
{
Serial.begin(115200);
Serial.println("Serial Begin");
// required for i2exp to work but redundant because called by other drivers below too
//Wire.begin();
// ESP32 requires an extended begin with pin mappings (which is not supported by the
// adafruit library), so we do an explicit begin here and then the other SPI libraries work
// with hardware SPI as setup here (they will do a second begin without pin mappings and
// that will be ignored).
// Actually the TFT lib works without this extra begin, but the touch screen library will
// not, so let's keep it.
#ifndef WROVER
SPI.begin(SPI_CLK, SPI_MISO, SPI_MOSI);
#endif
// Turn off TFT by default.
// Note this also initializes the read bits on PCF8574 by setting them to 1 as per I2CEXP_IMASK
i2cexp_set_bits(I2CEXP_LCD_BL_CTR);
Serial.println("ILI9341 Test!");
tft.begin();
// read diagnostics (optional but can help debug problems)
uint8_t x = tft.readcommand8(ILI9341_RDMODE);
Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
x = tft.readcommand8(ILI9341_RDMADCTL);
Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
x = tft.readcommand8(ILI9341_RDPIXFMT);
Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
x = tft.readcommand8(ILI9341_RDIMGFMT);
Serial.print("Image Format: 0x"); Serial.println(x, HEX);
x = tft.readcommand8(ILI9341_RDSELFDIAG);
Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX);
tft.setRotation(3);
tftw = tft.width(), tfth = tft.height();
Serial.print("Resolution: "); Serial.print(tftw);
Serial.print(" x "); Serial.println(tfth);
Serial.println(F("Done!"));
#ifndef WROVER
// init accel
if(!accel.begin()) {
/* there was a problem detecting the adxl345 ... check your connections */
Serial.println("Ooops, no ADXL345 detected ... Check your wiring!");
}
accel.setRange(ADXL345_RANGE_16_G);
sensor_t sensor;
accel.getSensor(&sensor);
Serial.println("------------------------------------");
Serial.print ("Sensor: "); Serial.println(sensor.name);
Serial.print ("Driver Ver: "); Serial.println(sensor.version);
Serial.print ("Unique ID: "); Serial.println(sensor.sensor_id);
Serial.print ("Max Value: "); Serial.print(sensor.max_value); Serial.println(" m/s^2");
Serial.print ("Min Value: "); Serial.print(sensor.min_value); Serial.println(" m/s^2");
Serial.print ("Resolution: "); Serial.print(sensor.resolution); Serial.println(" m/s^2");
Serial.println("------------------------------------");
Serial.println("");
if(!bme.begin()) {
/* there was a problem detecting the adxl345 ... check your connections */
Serial.println("Ooops, no BME280 detected ... Check your wiring!");
}
// Wait a little bit for the BME to capture data
delay(100);
Serial.print("Temperature = ");
Serial.print(bme.readTemperature());
Serial.println(" *C");
Serial.print("Pressure = ");
Serial.print(bme.readPressure() / 100.0F);
Serial.println(" hPa");
Serial.print("Approx. Altitude = ");
Serial.print(bme.readAltitude(1013.25));
Serial.println(" m");
Serial.print("Humidity = ");
Serial.print(bme.readHumidity());
Serial.println(" %");
#endif
Serial.println("Enable rotary encoder ISR:");
// Initialize rotary encoder reading and decoding
attachInterrupt(ENCODERA_PIN, read_encoder_ISR, CHANGE);
attachInterrupt(ENCODERB_PIN, read_encoder_ISR, CHANGE);
Serial.println("Enable IR receiver ISR:");
// Sigh, enabling this hardware interrupt made I2C unreliable.
// Thankfully this HAL patch fixes the issue
// https://github.com/espressif/arduino-esp32/issues/286
irrecv.enableIRIn();
Serial.println(F("Turning on LEDs"));
#ifdef NEOPIXEL
// Tri-color APA106 LEDs Setup
// Mapping is actually Green, Red, Blue (not RGB)
// Init LEDs to very dark (show they work, but don't blind)
pixels.begin();
// This first pixelcolor is ignored, not sure why
pixels.setPixelColor(0, 10, 10, 10);
pixels.setPixelColor(1, 5, 5, 5);
pixels.show();
// this one works.
//pixels.setPixelColor(0, 10, 10, 10);
//pixels.show();
#else
ws2812_init(RGB_LED_PIN, LED_WS2812B);
pixels[0] = makeRGBVal(20, 20, 20);
pixels[1] = makeRGBVal(0, 20, 0);
Serial.println("Before colors");
ws2812_setColors(NUMPIXELS, pixels);
Serial.println("After colors");
#endif
// This is interesting to log in case the last setup hangs
Serial.println("IoTuz Setup done");
}
// vim:sts=4:sw=4