-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathBlinky-Keys-Serial.ino
More file actions
169 lines (149 loc) · 5.71 KB
/
Blinky-Keys-Serial.ino
File metadata and controls
169 lines (149 loc) · 5.71 KB
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
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Copyright (c) 2025 Krishnanshu Mittal - krishnanshu@upsidedownlabs.tech
// Copyright (c) 2025 Aman Maheshwari - aman@upsidedownlabs.tech
// Copyright (c) 2025 Deepak Khatri - deepak@upsidedownlabs.tech
// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
#include <Arduino.h>
// ----------------- USER CONFIGURATION -----------------
#define SAMPLE_RATE 512 // samples per second
#define BAUD_RATE 115200
#define INPUT_PIN A0
// EEG Envelope Configuration
#define ENVELOPE_WINDOW_MS 100 // Smoothing window in milliseconds
#define ENVELOPE_WINDOW_SIZE ((ENVELOPE_WINDOW_MS * SAMPLE_RATE) / 1000)
// Double Blink Detection Configuration
const unsigned long BLINK_DEBOUNCE_MS = 250; // minimal spacing between individual blinks
const unsigned long DOUBLE_BLINK_MS = 600; // max time between the two blinks
unsigned long lastBlinkTime = 0; // time of most recent blink
unsigned long firstBlinkTime = 0; // time of the first blink in a pair
unsigned long secondBlinkTime = 0;
unsigned long triple_blink_ms = 600;
int blinkCount = 0; // how many valid blinks so far (0–2)
// EEG Processing Variables
float envelopeBuffer[ENVELOPE_WINDOW_SIZE] = {0};
int envelopeIndex = 0;
float envelopeSum = 0;
float currentEEGEnvelope = 0;
float BlinkThreshold = 75.0;
// --- Filter Functions ---
// High-Pass Butterworth IIR digital filter, generated using filter_gen.py.
// Sampling rate: 512.0 Hz, frequency: 5.0 Hz.
// Filter is order 2, implemented as second-order sections (biquads).
// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
float highpass(float input)
{
float output = input;
{
static float z1, z2; // filter section state
float x = output - -1.91327599*z1 - 0.91688335*z2;
output = 0.95753983*x + -1.91507967*z1 + 0.95753983*z2;
z2 = z1;
z1 = x;
}
return output;
}
// Band-Stop Butterworth IIR digital filter, generated using filter_gen.py.
// Sampling rate: 512.0 Hz, frequency: [48.0, 52.0] Hz.
// Filter is order 2, implemented as second-order sections (biquads).
// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
float Notch(float input)
{
float output = input;
{
static float z1, z2; // filter section state
float x = output - -1.58696045*z1 - 0.96505858*z2;
output = 0.96588529*x + -1.57986211*z1 + 0.96588529*z2;
z2 = z1;
z1 = x;
}
{
static float z1, z2; // filter section state
float x = output - -1.62761184*z1 - 0.96671306*z2;
output = 1.00000000*x + -1.63566226*z1 + 1.00000000*z2;
z2 = z1;
z1 = x;
}
return output;
}
// EEG Envelope Detection Function
float updateEEGEnvelope(float sample) {
float absSample = fabs(sample); // Rectify EEG signal
// Update circular buffer and running sum
envelopeSum -= envelopeBuffer[envelopeIndex];
envelopeSum += absSample;
envelopeBuffer[envelopeIndex] = absSample;
envelopeIndex = (envelopeIndex + 1) % ENVELOPE_WINDOW_SIZE;
return envelopeSum / ENVELOPE_WINDOW_SIZE; // Return moving average
}
void setup() {
Serial.begin(BAUD_RATE);
delay(100);
pinMode(INPUT_PIN, INPUT);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
delay(300);
digitalWrite(LED_BUILTIN, LOW);
}
void loop() {
static unsigned long lastMicros = micros();
unsigned long now = micros(), dt = now - lastMicros;
lastMicros = now;
static long timer = 0;
timer -= dt;
if(timer <= 0){
timer += 1000000L / SAMPLE_RATE;
int raw = analogRead(INPUT_PIN);
float filt = highpass(Notch(raw));
currentEEGEnvelope = updateEEGEnvelope(filt);
}
// Double blink detection
unsigned long nowMs = millis();
// 1) Did we cross threshold and respect per‑blink debounce?
if (currentEEGEnvelope > BlinkThreshold && (nowMs - lastBlinkTime) >= BLINK_DEBOUNCE_MS) {
lastBlinkTime = nowMs; // mark this blink
// 2) Count it
if (blinkCount == 0) {
// first blink of the pair
firstBlinkTime = nowMs;
blinkCount = 1;
// Serial.println("First blink detected");
}
else if (blinkCount == 1 && (nowMs - firstBlinkTime) <= DOUBLE_BLINK_MS) {
// double blink detected - send right arrow key
secondBlinkTime = nowMs;
blinkCount = 2;
// Serial.println("Second blink registered, waiting for triple…");
}
else if (blinkCount==2 && (nowMs - secondBlinkTime) <= triple_blink_ms)
{
Serial.println("Triple blink detected!");
blinkCount=0;
}
else {
// either too late or extra blink → restart sequence
firstBlinkTime = nowMs;
blinkCount = 1;
// Serial.println("Blink sequence restarted");
}
}
// if we were in “2 blinks” but no third arrived in time → treat as a real double
if (blinkCount == 2 && (nowMs - secondBlinkTime) > triple_blink_ms) {
Serial.println("Double blink detected");
blinkCount = 0;
}
// 3) Timeout: if we never got the second blink in time, reset
if (blinkCount == 1 && (nowMs - firstBlinkTime) > DOUBLE_BLINK_MS) {
blinkCount = 0;
}
}