// quick-fix for "macro "min" passed 3 arguments, but takes 2" error and "expected unqualified-id before '(' token"
#include <Arduino.h>
#undef max
#undef min
#undef abs
// quick-fix end
#include <vector>
#include <queue>
#include <algorithm>
#include <Wire.h>
#include <MCP23017.h>
#include <VL53L0X.h>
#include <TFTv2.h>
#include <SPI.h>
#include "DFRobotDFPlayerMini.h"
#define MCP_ADDRESS 0x20 // A2/A1/A0 = LOW
#define WAIT_TIME_MS 100
#define SENS_TIMEOUT 500 // 500 ms sensor timeout
#define SENS_PERIOD 25 // 50 ms read period
#define DIST_MAX 40
#define DIST_MIN 5
#define DIST_SMA_WINDOW 10
#define DIST_GESTURE 4
#define DRAW_GRID 60
#define DRAW_OFFSET_BUTTON_X 30
#define DRAW_OFFSET_BUTTON_Y 100
#define DRAW_OFFSET_CAPTION_X 20
#define DRAW_OFFSET_CAPTION_Y 90
#define DRAW_BUTTON_RADIUS 25
//#define PRINT_SENSOR_DATA
#define AUDIO
#ifdef AUDIO
DFRobotDFPlayerMini dfPlayer;
#endif
MCP23017 mcp(MCP_ADDRESS,5);
VL53L0X* sensors[4][4];
uint16_t sens_raw[4][4];
uint8_t sens_addr[4][4];
// TODO: use optimzed ring buffer
std::deque<uint16_t> sens_hist[4][4];
uint16_t sens_sma[4][4];
struct ButtonEvent{
bool flag;
int x;
int y;
long timestamp;
};
ButtonEvent cursorCurr = {false, -1, -1, 0};
ButtonEvent cursorPrev = {false, -1, -1, 0};
ButtonEvent press = {false, -1, -1, 0};
ButtonEvent release = {false, -1, -1, 0};
const char* btnText[4][4] = {
{"1", "2", "3", "C"},
{"4", "5", "6", "Del"},
{"7", "8", "9", "OK"},
{"*", "0", "#", "I"}
};
uint16_t btnTextSize[4][4] = {
{3,3,3,3},
{3,3,3,2},
{3,3,3,2},
{3,3,3,3}
};
uint16_t btnBackground[4][4] = {
{BLACK, BLACK, BLACK, RED},
{BLACK, BLACK, BLACK, YELLOW},
{BLACK, BLACK, BLACK, GREEN},
{BLACK, BLACK, BLACK, BLUE},
};
char pin[] = "----";
int pinCurrDigit = 0;
void handlerNum(int x, int y){
pin[pinCurrDigit] = btnText[x][y][0];
pinCurrDigit = (++pinCurrDigit) % 4;
}
void handlerNoOp(int x, int y){}
void handlerClear(int x, int y){
pinCurrDigit = 0;
strcpy(pin, "----");
}
void handlerBack(int x, int y){
if(--pinCurrDigit < 0)
pinCurrDigit = 3;
pin[pinCurrDigit] = '-';
}
typedef void (*ButtonHandler)(int x, int y);
ButtonHandler btnHandler[4][4] = {
{handlerNum, handlerNum, handlerNum, handlerClear},
{handlerNum, handlerNum, handlerNum, handlerBack},
{handlerNum, handlerNum, handlerNum, handlerClear},
{handlerNoOp, handlerNum, handlerNoOp, handlerNoOp}
};
const int REDRAW_MULT = 3;
int redraw_count = 0;
// ================================================================
void setup(){
Serial.println("initializing display...");
TFT_BL_ON; //turn on the background light
Tft.TFTinit(); //init TFT library
Tft.drawHorizontalLine(0,60, 240, WHITE);
Tft.drawString("PIN:", 20, 20, 3, WHITE, TextOrientation::PORTRAIT);
Tft.drawString(pin, 100, 20, 3, WHITE, TextOrientation::PORTRAIT);
delay(2000);
for(int i = 0; i < 4; i++){
for(int k = 0; k < 4; k++){
Tft.fillCircle(DRAW_OFFSET_BUTTON_X + k*DRAW_GRID, DRAW_OFFSET_BUTTON_Y + i*DRAW_GRID, DRAW_BUTTON_RADIUS-2, btnBackground[i][k]);
Tft.drawString(btnText[i][k], DRAW_OFFSET_CAPTION_X + k*DRAW_GRID, DRAW_OFFSET_CAPTION_Y + i*DRAW_GRID, btnTextSize[i][k], WHITE, TextOrientation::PORTRAIT);
Tft.drawCircle(DRAW_OFFSET_BUTTON_X + k*DRAW_GRID, DRAW_OFFSET_BUTTON_Y + i*DRAW_GRID, DRAW_BUTTON_RADIUS, WHITE);
}
}
delay(WAIT_TIME_MS);
#ifdef AUDIO
Serial.println("initializing audio...");
Serial1.begin(9600);
if (!dfPlayer.begin(Serial1)) {
Serial.println(("error initializing Serial1 for DFPlayer"));
}
dfPlayer.volume(20); //Set volume value. From 0 to 30
dfPlayer.play(1); //Play next mp3 every 3 second.
#endif
Serial.println("initializing sensors...");
for(int i = 0; i < 4; i++){
for(int k = 0; k < 4; k++){
sensors[i][k] = new VL53L0X();
}
}
Wire.begin();
mcp.Init();
Serial.println("setting port modes...");
mcp.setPortMode(B11111111, MCP_PORT::A);
mcp.setPortMode(B11111111, MCP_PORT::B);
delay(WAIT_TIME_MS);
mcp.setPort(B00000000, MCP_PORT::A);
mcp.setPort(B00000000, MCP_PORT::B);
delay(WAIT_TIME_MS);
Serial.println("activating sensors...");
// activating sensors one by one
for(int i = 0; i < 4; i++){
for(int k = 0; k < 4; k++){
int idx = i*4 + k;
// mask goes 00000001 -> 00000011 -> 00000111 ...
uint8_t mask = ((B00000001 << ((idx % 8)+1))) - 1 ;
mcp.setPort(mask, idx < 8 ? MCP_PORT::A : MCP_PORT::B);
delay(WAIT_TIME_MS);
sensors[i][k]->init(true);
delay(WAIT_TIME_MS);
sensors[i][k]->setAddress((uint8_t) (idx + 1));
sens_addr[i][k] = sensors[i][k]->getAddress();
}
}
for(int i = 0; i < 4; i++){
for(int k = 0; k < 4; k++){
sensors[i][k]->setTimeout(SENS_TIMEOUT);
}
}
for(int i = 0; i < 4; i++){
for(int k = 0; k < 4; k++){
sensors[i][k]->startContinuous(SENS_PERIOD);
}
}
Serial.print("setup done");
}
// ================================================================
unsigned long prevTime;
unsigned long currTime;
void loop(){
// currTime = millis();
// Serial.println(currTime - prevTime);
// prevTime = prevTime;
// read sensors
for(int i = 0; i < 4; i++){
for(int k = 0; k < 4; k++){
sens_raw[i][k] = sensors[i][k]->readRangeContinuousMillimeters();
if(sensors[i][k]->timeoutOccurred()){Serial.print("timeout read");}
}
}
// maintain sensor queues
for(int i = 0; i < 4; i++){
for(int k = 0; k < 4; k++){
// if sensor value within defined valid window, put it in the queue
if(sens_raw[i][k] > DIST_MIN && sens_raw[i][k] < DIST_MAX)
sens_hist[i][k].push_back(sens_raw[i][k]);
// ... make queue run empty without valid values
else {
if (sens_hist[i][k].size() > 0)
sens_hist[i][k].pop_front();
}
// limit queue size
if(sens_hist[i][k].size() > DIST_SMA_WINDOW)
sens_hist[i][k].pop_front();
}
}
uint16_t min = UINT16_MAX;
cursorCurr.flag = false;
cursorCurr.x = -1;
cursorCurr.y = -1;
// check if finger / cursor present
for(int i = 0; i < 4; i++){
for(int k = 0; k < 4; k++){
uint32_t res = 0;
for (auto dist = sens_hist[i][k].cbegin(); dist != sens_hist[i][k].cend(); ++dist){
res += *dist;
} // for hist
if(sens_hist[i][k].size() > 0){
res /= sens_hist[i][k].size();
sens_sma[i][k] = res;
}
else{
sens_sma[i][k] = UINT16_MAX;
}
//if(sens_sma[i][k] < min){
if(sens_raw[i][k] < min && sens_raw[i][k] > DIST_MIN && sens_raw[i][k] < DIST_MAX){
min = sens_raw[i][k];
cursorCurr.flag = true;
cursorCurr.x = i;
cursorCurr.y = k;
}
}
}
// if cursor detected, check for button press
if(cursorCurr.flag){
int diffOverall =
*std::max_element(sens_hist[cursorCurr.x][cursorCurr.y].cbegin(), sens_hist[cursorCurr.x][cursorCurr.y].cend()) -
*std::min_element(sens_hist[cursorCurr.x][cursorCurr.y].cbegin(), sens_hist[cursorCurr.x][cursorCurr.y].cend());
diffOverall = std::abs(diffOverall);
bool down = true;
bool up = true;
int diffSum = 0;
for (auto dist = sens_hist[cursorCurr.x][cursorCurr.y].cbegin(); dist != sens_hist[cursorCurr.x][cursorCurr.y].cend(); ++dist){
if(dist != sens_hist[cursorCurr.x][cursorCurr.y].cbegin()){
auto prev = std::prev(dist);
int diff = *dist - *prev;
diffSum += diff;
//Serial.print(diff);
//Serial.print("\t");
if(diff < 0){
up = false;
}
else if (diff > 0){
down = false;
}
else{
up = down = false;
}
}
}
//Serial.println("");
if(diffSum < -DIST_GESTURE){
//if(down && diffOverall > DIST_GESTURE){
Serial.println("down");
press.flag = true;
press.timestamp = millis();
press.x = cursorCurr.x;
press.y = cursorCurr.y;
}
if(diffSum > DIST_GESTURE){
//if(up && diffOverall > DIST_GESTURE){
Serial.println("up");
release.timestamp = millis();
release.flag = true;
release.timestamp = millis();
release.x = cursorCurr.x;
release.y = cursorCurr.y;
}
if(press.flag && release.flag && release.timestamp - press.timestamp < 1000){
Serial.print("clicked ");
Serial.println(btnText[cursorCurr.x][cursorCurr.y]);
// call corresponding button handler
btnHandler[cursorCurr.x][cursorCurr.y](cursorCurr.x, cursorCurr.y);
press.flag = false;
release.flag = false;
dfPlayer.stop();
dfPlayer.play(2);
}
if(cursorPrev.x != cursorCurr.x || cursorPrev.y != cursorCurr.y){
//Serial.println("cursorChanged");
#ifdef AUDIO
dfPlayer.stop();
dfPlayer.play(1);
#endif
}
}
cursorPrev = cursorCurr;
//char data[512];
// Serial.println(cursor);
// for(int i = 0; i < 4; i++){
// for(int k = 0; k < 4; k++){
// sprintf(data, "|%5d", sens_addr[i][k]-1);
// Serial.print(data);
// Serial.print("\t");
// sprintf(data, "%5d", sens_raw[i][k]);
// Serial.print(data);
// Serial.print("\t");
// //sprintf(data, "%5d", sens_hist[i][k].size());
// sprintf(data, "%5d", sens_sma[i][k]);
// Serial.print(data);
// Serial.print("\t");
// }
// Serial.println("");
// }
// Serial.println("=========================================");
if(++redraw_count >= REDRAW_MULT){
redraw_count = 0;
for(int i = 0; i < 4; i++){
for(int k = 0; k < 4; k++){
//Tft.drawString(pin, 100, 20, 3, WHITE, TextOrientation::PORTRAIT);
Tft.drawCircle(DRAW_OFFSET_BUTTON_X + i * DRAW_GRID, DRAW_OFFSET_BUTTON_Y + k * DRAW_GRID, DRAW_BUTTON_RADIUS, (k==cursorCurr.x && i==cursorCurr.y) ? (RED|BLUE) : WHITE);
}
}
Tft.fillRectangle(100, 20, 100, 25, BLACK);
Tft.drawString(pin, 100, 20, 3, WHITE, TextOrientation::PORTRAIT);
}
}
Comments