-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathAVR-NANO-UNO-MEGA.ino
206 lines (176 loc) · 6.36 KB
/
AVR-NANO-UNO-MEGA.ino
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
// 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) 2024 Upside Down Labs - [email protected]
// Author: Deepak Khatri
//
// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
// Our mission is to make neuroscience affordable and accessible for everyone.
// By supporting us with your purchase, you help spread innovation and open science.
// Thank you for being part of this journey with us!
#include <Arduino.h>
/**
** Select your board from list below
** Uncomment only your board macro
**/
// #define BOARD_NANO_CLONE
// #define BOARD_NANO_CLASSIC
// #define BOARD_UNO_R3
// #define BOARD_GENUINO_UNO
// #define BOARD_UNO_CLONE
#define BOARD_MEGA_2560_R3
// #define BOARD_MEGA_2560_CLONE
// Board specific macros
// Do not edit anything!
#if defined(BOARD_UNO_R3)
#define BOARD_NAME "UNO-R3"
#define NUM_CHANNELS 6
#elif defined(BOARD_GENUINO_UNO)
#define BOARD_NAME "GENUINO-UNO"
#define NUM_CHANNELS 6
#elif defined(BOARD_UNO_CLONE)
#define BOARD_NAME "UNO-CLONE"
#define NUM_CHANNELS 6
#elif defined(BOARD_NANO_CLASSIC)
#define BOARD_NAME "NANO-CLASSIC"
#define NUM_CHANNELS 8
#elif defined(BOARD_NANO_CLONE)
#define BOARD_NAME "NANO-CLONE"
#define NUM_CHANNELS 8
#elif defined(BOARD_MEGA_2560_R3)
#define BOARD_NAME "MEGA-2560-R3"
#define NUM_CHANNELS 16
#elif defined(BOARD_MEGA_2560_CLONE)
#define BOARD_NAME "MEGA-2560-CLONE"
#define NUM_CHANNELS 16
#else
#error "Board type not defined. Please define a valid BOARD_NAME macro."
#endif
// Common macros
#define SAMP_RATE 250
#define BAUD_RATE 115200
#define HEADER_LEN 3 // Header = SYNC_BYTE_1 + SYNC_BYTE_2 + Counter
#define PACKET_LEN (NUM_CHANNELS * 2 + HEADER_LEN + 1) // Packet length = Header + Data + END_BYTE
#define SYNC_BYTE_1 0xC7 // Packet first byte
#define SYNC_BYTE_2 0x7C // Packet second byte
#define END_BYTE 0x01 // Packet last byte
// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
// Global constants and variables
uint8_t packetBuffer[PACKET_LEN]; // The transmission packet
uint8_t currentChannel; // Current channel being sampled
uint16_t adcValue = 0; // ADC current value
bool timerStatus = false; // SATUS bit
bool bufferReady = false; // Buffer ready status bit
bool timerStart() {
timerStatus = true;
digitalWrite(LED_BUILTIN, HIGH);
// Enable Timer1 Compare A interrupt
return TIMSK1 |= (1 << OCIE1A);
}
bool timerStop() {
timerStatus = false;
bufferReady = false;
digitalWrite(LED_BUILTIN, LOW);
// Disable Timer1 Compare A interrupt
return TIMSK1 &= ~(1 << OCIE1A);
}
// ISR for Timer1 Compare A match (called based on the sampling rate)
ISR(TIMER1_COMPA_vect) {
if (!timerStatus or Serial.available()) {
timerStop();
return;
}
// Set bufferReady status bit to true
bufferReady = true;
}
void timerBegin(float sampling_rate) {
cli(); // Disable global interrupts
// Set prescaler division factor to 16
sbi(ADCSRA, ADPS2); // 1
cbi(ADCSRA, ADPS1); // 0
cbi(ADCSRA, ADPS0); // 0
// Calculate OCR1A based on the interval
// OCR1A = (16MHz / (Prescaler * Desired Time)) - 1
// Prescaler options: 1, 8, 64, 256, 1024
unsigned long ocrValue = (16000000 / (8 * sampling_rate)) - 1;
// Configure Timer1 for CTC mode (Clear Timer on Compare Match)
TCCR1A = 0; // Clear control register A
TCCR1B = 0; // Clear control register B
TCNT1 = 0; // Clear counter value
// Set the calculated value in OCR1A register
OCR1A = ocrValue;
// Set CTC mode (WGM12 bit) and set the prescaler to 8
TCCR1B |= (1 << WGM12) | (1 << CS11); // Prescaler = 8
sei(); // Enable global interrupts
}
void setup() {
Serial.begin(BAUD_RATE);
while (!Serial) {
; // Wait for serial port to connect. Needed for native USB
}
// Status LED
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
// Initialize packetBuffer
packetBuffer[0] = SYNC_BYTE_1; // Sync 0
packetBuffer[1] = SYNC_BYTE_2; // Sync 1
packetBuffer[2] = 0; // Packet counter
packetBuffer[PACKET_LEN - 1] = END_BYTE; // End Byte
// Setup timer
timerBegin(SAMP_RATE);
}
void loop() {
// Send data if the buffer is ready and the timer is activ
if (timerStatus and bufferReady) {
// ADC value Reading, Converting, and Storing:
for (currentChannel = 0; currentChannel < NUM_CHANNELS; currentChannel++) {
// Read ADC input
adcValue = analogRead(currentChannel);
// Store current values in packetBuffer to send.
packetBuffer[((2 * currentChannel) + HEADER_LEN)] = highByte(adcValue); // Write High Byte
packetBuffer[((2 * currentChannel) + HEADER_LEN + 1)] = lowByte(adcValue); // Write Low Byte
}
// Increment the packet counter
packetBuffer[2]++;
// Send the packetBuffer to the Serial port
Serial.write(packetBuffer, PACKET_LEN);
// Reset the bufferReady flag
bufferReady = false;
}
if (Serial.available()) {
String command = Serial.readStringUntil('\n');
command.trim(); // Remove extra spaces or newline characters
command.toUpperCase(); // Normalize to uppercase for case-insensitivity
if (command == "WHORU") // Who are you?
{
Serial.println(BOARD_NAME);
} else if (command == "START") // Start data acquisition
{
timerStart();
} else if (command == "STOP") // Stop data acquisition
{
timerStop();
} else if (command == "STATUS") // Get status
{
Serial.println(timerStatus ? "RUNNING" : "STOPPED");
} else {
Serial.println("UNKNOWN COMMAND");
}
}
}