├── .gitignore ├── Definitions_Ganglion.h ├── Honor A Loved One.cpp ├── LICENSE ├── OpenBCI_Ganglion_Library.cpp ├── OpenBCI_Ganglion_Library.h ├── README.md ├── changelog.md ├── examples ├── DefaultGanglion │ └── DefaultGanglion.ino ├── GanglionNoWifi │ └── GanglionNoWifi.ino └── GanglionWithSD │ ├── GanglionWithSD.ino │ └── SD_Card_Stuff.ino ├── keywords.txt └── library.properties /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | .metadata/ 4 | RemoteSystemsTempFiles/ 5 | .vscode/ -------------------------------------------------------------------------------- /Definitions_Ganglion.h: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | 4 | #ifndef _Definitions_Ganglion_h 5 | #define _Definitions_Ganglion_h 6 | 7 | #define LED 23 8 | #define LED_DELAY_TIME 300 9 | #define RING_SIZE 101 10 | #define SERIAL_BUFFER_LENGTH 20 11 | #define MAX_BYTES_PER_PACKET 19 12 | 13 | // Byte IDs 14 | #define ID_RAW_DATA 0 15 | #define ID_SAMPLE_18_MIN 1 16 | #define ID_SAMPLE_18_MAX 100 17 | #define ID_SAMPLE_19_MIN 101 18 | #define ID_SAMPLE_19_MAX 200 19 | #define ID_Z_1 201 20 | #define ID_Z_2 202 21 | #define ID_Z_3 203 22 | #define ID_Z_4 204 23 | #define ID_Z_R 205 24 | #define ID_MULTI_PACKET 206 25 | #define ID_MULTI_PACKET_STOP 207 26 | 27 | // Serial Command Characters 28 | #define DEACTIVATE_CHANNEL_1 '1' 29 | #define DEACTIVATE_CHANNEL_2 '2' 30 | #define DEACTIVATE_CHANNEL_3 '3' 31 | #define DEACTIVATE_CHANNEL_4 '4' 32 | #define ACTIVATE_CHANNEL_1 '!' 33 | #define ACTIVATE_CHANNEL_2 '@' 34 | #define ACTIVATE_CHANNEL_3 '#' 35 | #define ACTIVATE_CHANNEL_4 '$' 36 | #define START_DATA_STREAM 'b' 37 | #define STOP_DATA_STREAM 's' 38 | #define ENABLE_ACCELEROMETER 'n' 39 | #define DISABLE_ACCELEROMETER 'N' 40 | #define ENABLE_SYNTHETIC_DATA '[' 41 | #define DISABLE_SYNTHETIC_DATA ']' 42 | #define SOFT_RESET 'v' 43 | #define REPORT_REGISTER_SETTINGS '?' 44 | #define ENABLE_OTA '>' 45 | #define OPENBCI_SAMPLE_RATE_SET '~' 46 | 47 | #define OPENBCI_CHANNEL_1 '1' 48 | #define OPENBCI_CHANNEL_2 '2' 49 | #define OPENBCI_CHANNEL_3 '3' 50 | #define OPENBCI_CHANNEL_4 '4' 51 | #define OPENBCI_CHANNEL_REF '5' 52 | 53 | #define OPENBCI_Z_TEST_START 'z' 54 | #define OPENBCI_Z_TEST_STOP 'Z' 55 | 56 | // MCP 57 | #define DEACTIVATE 0 58 | #define ACTIVATE 1 59 | #define ENABLE_0 0x000E0000 60 | #define ENABLE_1 0x000D0000 61 | #define ENABLE_2 0x000B0000 62 | #define ENABLE_3 0x00070000 63 | #define DISABLE_0 0x00010000 64 | #define DISABLE_1 0x00020000 65 | #define DISABLE_2 0x00040000 66 | #define DISABLE_3 0x00080000 67 | 68 | #define MCP_SS 7 // MCP Slave Select on Simblee pin 13 69 | #define MCP_DRDY 8 // MCP DataReady on Simblee pin 8 70 | #define MCP_RST 20 // MCP Reset: active low 71 | 72 | #define MCP_ADD 0x40 73 | #define MCP_READ 0x01 74 | #define MCP_WRITE 0x00 75 | 76 | #define DEV_ADD 0x40 77 | 78 | #define CHAN_0 0x00 //00 | | -GROUP 79 | #define CHAN_1 0x02 //01 |-TYPE _| 80 | #define CHAN_2 0x04 //02 | | -GROUP 81 | #define CHAN_3 0x06 //03 _| _| 82 | 83 | #define MOD_VAL 0x10 //08 | | 84 | #define PHASE 0x14 //0A | |-GROUP 85 | #define GAIN 0x16 //0B | _| 86 | #define STATUSCOM 0x18 //0C | | 87 | #define CONFIG_0 0x1A //0D | |-GROUP 88 | #define CONFIG_1 0x1C //0E | _| 89 | #define OFFCAL_0 0x1E //0F |-TYPE | -GROUP 90 | #define GAINCAL_0 0x20 //10 | _| 91 | #define OFFCAL_1 0x22 //11 | | -GROUP 92 | #define GAINCAL_1 0x24 //12 | _| 93 | #define OFFCAL_2 0x26 //13 | | -GROUP 94 | #define GAINCAL_2 0x28 //14 | _| 95 | #define OFFCAL_3 0x2A //15 | | -GROUP 96 | #define GAINCAL_3 0x2C //16 | _| 97 | #define LOK_CRC 0x3E //1F _| >-GROUP 98 | 99 | #define GAIN_1 0x00000000 100 | #define GAIN_2 0x00000249 101 | #define GAIN_4 0x00000492 102 | #define GAIN_8 0x000006CB 103 | #define GAIN_16 0x00000924 104 | #define GAIN_32 0x00000B6D 105 | 106 | // 0x0038E050 107 | // #define SAMPLE_25 0x00030000 108 | // #define SAMPLE_50 0x00020000 109 | // #define SAMPLE_100 0x00010000 110 | // #define SAMPLE_200 0x00000000 111 | 112 | 113 | //#define OPENBCI_NUMBER_OF_BYTES_SETTINGS_Z_TEST 3 114 | #define HALF_PERIOD 3000 115 | #define HALF_WAVE 100 116 | #define SHUNT 100 117 | #define SHUNT_SENSOR_GAIN 1000 118 | #define UA_SAMPLE_TIME 500 119 | #define UA_SAMPLE_LIMIT 1000 120 | 121 | 122 | /* 123 | * AD5621 124 | */ 125 | #define DAC_MASK 0x3FFC 126 | #define SHUNT_SENSOR 6 // reads the current shunt sensor 127 | #define DAC_SS 19 // DAC slave select 128 | #define Z_TEST_1 30 // pin control for Z test channel 1 129 | #define Z_TEST_2 29 // pin control for Z test channel 2 130 | #define Z_TEST_3 28 // pin control for Z test channel 3 131 | #define Z_TEST_4 25 // pin control for Z test channel 4 132 | #define Z_TEST_REF 22 // pin control for Z test channel REF 133 | // CONTROL BYTES 134 | #define DAC_EN 0x0000 // enables setting the DAC output 12 bit resolution 135 | #define DAC_1K 0x7000 // disables DAC with output tied 1K to GND 136 | #define DAC_100K 0x8000 // disables DAC with output tied 100K to GND 137 | #define DAC_Z 0xC000 // disables DAC with output in high Z 138 | #define DAC_MAX 0xFFFF // highest possible value of DAC = 4095 139 | 140 | 141 | 142 | 143 | /* 144 | * LIS2DH 145 | */ 146 | 147 | #define LIS2DH_SS 10 148 | #define LIS_DRDY 13 149 | 150 | #define READ_REG 0x80 151 | #define READ_MULTI 0x40 152 | 153 | #define LIS3DH_MODE 3 // c pol =1, c pha = 1, mode = 3 154 | 155 | #define STATUS_REG_AUX 0x07 156 | #define OUT_TEMP_L 0x0C 157 | #define OUT_TEMP_H 0x0D 158 | #define INT_COUNTER_REG 0x0E 159 | #define WHO_AM_I 0x0F 160 | #define TEMP_CFG_REG 0x1F 161 | #define CTRL_REG1 0x20 162 | #define CTRL_REG2 0x21 163 | #define CTRL_REG3 0x22 164 | #define CTRL_REG4 0x23 165 | #define CTRL_REG5 0x24 166 | #define CTRL_REG6 0x25 167 | #define REFERENCE 0x26 168 | #define STATUS_REG2 0x27 169 | #define OUT_X_L 0x28 170 | #define OUT_X_H 0x29 171 | #define OUT_Y_L 0x2A 172 | #define OUT_Y_H 0x2B 173 | #define OUT_Z_L 0x2C 174 | #define OUT_Z_H 0x2D 175 | #define FIFO_CTRL_REG 0x2E 176 | #define FIFO_SRC_REG 0x2F 177 | #define INT1_CFG 0x30 178 | #define INT1_SOURCE 0x31 179 | #define INT1_THS 0x32 180 | #define INT1_DURATION 0x33 181 | #define INT2_CFG 0x34 182 | #define INT2_SOURCE 0x35 183 | #define INT2_THS 0x36 184 | #define INT2_DURATION 0x37 185 | #define CLICK_CFG 0x38 186 | #define CLICK_SRC 0x39 187 | #define CLICK_THS 0x3A 188 | #define TIME_LIMIT 0x3B 189 | #define TIME_LATENCY 0x3C 190 | #define TIME_WINDOW 0x3D 191 | #define ACT_THS 0x3E 192 | #define ACT_DUR 0x3F 193 | 194 | /* 195 | * SD CARD 196 | */ 197 | #define SD_SS 17 198 | 199 | /** Wifi Stuff */ 200 | #define NUM_CHANNELS 4 201 | #define OPENBCI_WIFI_ATTACH '{' 202 | #define OPENBCI_WIFI_REMOVE '}' 203 | #define OPENBCI_WIFI_STATUS ':' 204 | #define OPENBCI_WIFI_RESET ';' 205 | 206 | #endif 207 | -------------------------------------------------------------------------------- /Honor A Loved One.cpp: -------------------------------------------------------------------------------- 1 | //==========================================================// 2 | // OpenBCI Kickstarter Pledge - Honor A Loved One // 3 | //==========================================================// 4 | 5 | /* 6 | Name Of Loved One To Be Honored -- Note About Honored Loved One 7 | C.R. Zimmerman & Family -- all the best 8 | Leonidas Hofmann -- Always bravely carrying on despite Tourette's and OCD. You rock! 9 | Silvia Escamilla -- Te admiro, por tu voluntad y por tu fuerza, por tu cariño y por tu alegría. 10 | Jillian Burns -- Gone to Young, Forever in our hearts. 11 | Grandma Wang -- My grandmother suffered a stroke in 2005 and this is dedicated to her. 12 | Kyle Scott Dickinson -- My awesome autistic brother, I'm also his personal care attendant at the north shore arc. -- He’s a great brother in fact he won't cry from pain yet only when I'm upset lol. 13 | Robert S. Patton (my Dad) -- My Dad's life was ended by Alzheimer's. Fortunately for him, the disease took its toll quickly, less than 5 years. For him, and so many others I know, knew, and will know I encourage the founders to continue their work. 14 | Ruth Turner -- You were always loved and are missed 15 | Mark Wiegand -- At some point in time and space, we make you walk again. 16 | Eleonora Pawlowska -- In memory of my beloved mother. I wish she was still here with all of us. 17 | Dr Frederick Fries -- RIP 2010. Love you dad. 18 | Leibniz -- Nothing is accomplished all at once, and it is one of my great maxims, and one of the most completely verified, that Nature makes no leaps: a maxim which I have called the law of continuity. 19 | Ubirakitan Maciel -- A person dedicated to make Neuroscience accessible to people 20 | A. Sterling -- My father survived a brain aneurysm 16 years ago He just turned 90! 21 | Broome Spiro -- Different in an excellent sort of way. 22 | Lauren S. -- Your depression doesn't need to define you 23 | Stephanie Y. -- Hope you found your dream. 24 | My dad -- Essential tremor 25 | Peter Beals -- artist, the kindest man I ever know 26 | Jacqueline --- RIP 27 | */ 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 OpenBCI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /OpenBCI_Ganglion_Library.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | /* 7 | OpenBCI Gagnlion Library 8 | Place the containing folder into your libraries folder inside the arduino folder in your Documents folder 9 | 10 | This library will work with a single OpenBCI Ganglion board 11 | 12 | Joel Murphy, Leif Percifield, AJ Keller, and Conor Russomanno made this. 13 | 14 | */ 15 | 16 | #include "OpenBCI_Ganglion_Library.h" 17 | #include 18 | 19 | // CONSTRUCTOR 20 | OpenBCI_Ganglion::OpenBCI_Ganglion(){ 21 | curSampleRate = SAMPLE_RATE_200; 22 | } 23 | 24 | // <<<<<<<<<<<<<<<<<<<<<<<<< BOARD WIDE FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 25 | 26 | 27 | void OpenBCI_Ganglion::initialize() { 28 | SPI.begin(); 29 | SPI.setFrequency(4000); 30 | SPI.setDataMode(SPI_MODE0); 31 | pinMode(LIS2DH_SS,OUTPUT); digitalWrite(LIS2DH_SS,HIGH); 32 | pinMode(LIS_DRDY,INPUT_PULLDOWN); 33 | pinMode(MCP_DRDY,INPUT); 34 | pinMode(MCP_SS,OUTPUT); digitalWrite(MCP_SS,HIGH); 35 | pinMode(MCP_RST,OUTPUT); digitalWrite(MCP_RST,LOW); 36 | delay(100); digitalWrite(MCP_RST,HIGH); 37 | pinMode(DAC_SS,OUTPUT); digitalWrite(DAC_SS,HIGH); 38 | for(int i=0; i<5; i++){ 39 | pinMode(impedanceSwitch[i],OUTPUT); digitalWrite(impedanceSwitch[i],LOW); 40 | } 41 | pinMode(LED, OUTPUT); 42 | digitalWrite(LED,LED_state); 43 | 44 | makeUniqueId(); // construct the name and serial number for advertisement 45 | SimbleeBLE_advdata = advdata; 46 | SimbleeBLE_advdata_len = sizeof(advdata); 47 | if (useSerial) { 48 | Serial.begin(115200); 49 | } else { 50 | Serial.begin(9600); 51 | SimbleeBLE.advertisementData = "Ganglion"; 52 | SimbleeBLE.begin(); 53 | SimbleeBLE.txPowerLevel = +4; 54 | } 55 | initSerialBuffer(); 56 | startFromScratch(gain); 57 | rssiTimer = millis(); 58 | } 59 | 60 | void OpenBCI_Ganglion::makeUniqueId() { 61 | uint64_t id = getDeviceId(); 62 | String stringy = String(getDeviceIdLow(), HEX); 63 | advdata[16] = (uint8_t)stringy.charAt(0); 64 | advdata[17] = (uint8_t)stringy.charAt(1); 65 | advdata[18] = (uint8_t)stringy.charAt(2); 66 | advdata[19] = (uint8_t)stringy.charAt(3); 67 | SimbleeBLE.manufacturerName = "openbci.com"; 68 | SimbleeBLE.modelNumber = "Ganglion"; 69 | SimbleeBLE.hardwareRevision = "1.0.1"; 70 | SimbleeBLE.softwareRevision = "3.0.2"; 71 | } 72 | 73 | void OpenBCI_Ganglion::blinkLED() { 74 | // blue LED blinks when BLE is not connected 75 | if(!wifi.present &&!BLEconnected){ 76 | if(millis()-LED_timer > LED_DELAY_TIME){ 77 | LED_timer = millis(); 78 | LED_state = !LED_state; 79 | digitalWrite(LED,LED_state); 80 | } 81 | } 82 | } 83 | 84 | void OpenBCI_Ganglion::startFromScratch(unsigned long g) { 85 | byte id; 86 | int ID; 87 | config_LIS2DH(); 88 | config_MCP3912(g); 89 | updateDAC(DACmidline); // place DAC into V/2 position 90 | loadString("OpenBCI Ganglion v"); loadlnString((char*)SimbleeBLE.softwareRevision); 91 | for (int i = 2; i <= advdata[0]; i++) { 92 | loadChar(advdata[i], false); 93 | } 94 | loadNewLine(); 95 | loadString("LIS2DH ID: "); id = LIS2DH_read(WHO_AM_I); loadHex(id,1,true); 96 | loadString("MCP3912 CONFIG_1: "); digitalWrite(MCP_SS, LOW); 97 | MCP_sendCommand(CONFIG_1, MCP_READ); ID = MCP_readRegister(); 98 | digitalWrite(MCP_SS, HIGH); loadHex(ID,3,true); 99 | loadNewLine(); 100 | prepToSendBytes(); sendSerialBytesBlocking(); 101 | 102 | loadlnString("send 'b' to start data stream"); 103 | loadlnString("send 's' to stop data stream"); 104 | loadString("use 1,2,3,4 to turn"); loadlnString(" OFF channels"); 105 | loadString("use !,@,#,$ to"); loadlnString(" turn ON channels"); 106 | loadString("send '?' to print"); loadlnString(" all registers"); 107 | loadlnString("send 'v' to initialize board"); 108 | prepToSendBytes(); 109 | sendSerialBytesBlocking(); 110 | loadString("send '[' ']' to enable/disable"); loadlnString(" synthetic square wave"); 111 | loadString("send 'z' 'Z' to start/stop");loadlnString(" impedance test"); 112 | loadString("send 'n','N' to enable/"); loadlnString("disable accelerometer"); 113 | loadString("send '{','}' to attach/"); loadlnString("remove wifi"); 114 | loadString("send ':' to get wifi"); loadlnString(" shield status"); 115 | loadString("send ';' to power on"); loadlnString(" reset wifi shield"); 116 | prepToSendBytes(); 117 | sampleCounter = 0xFF; 118 | } 119 | 120 | void OpenBCI_Ganglion::printAllRegisters_Serial() { 121 | if (!is_running) { 122 | LIS2DH_readAllRegs_Serial(); 123 | MCP_readAllRegs(); 124 | } 125 | } 126 | 127 | boolean OpenBCI_Ganglion::startRunning(void) { 128 | if (is_running == false) { 129 | if (wifi.tx) { 130 | wifi.sendGains(NUM_CHANNELS, getGains()); 131 | } 132 | is_running = true; 133 | sampleCounter = 0xFF; 134 | ringBufferLevel = 0xFF; 135 | if (streamSynthetic) { 136 | for (int i = 1; i < 4; i++) { 137 | channelData[i] = 0; 138 | } 139 | } 140 | if (useSerial) { 141 | digitalWrite(LED, HIGH); 142 | } 143 | if (useAccel){ enable_LIS2DH();} 144 | 145 | config_MCP3912(gain); 146 | MCP_turnOnChannels(); 147 | } 148 | return is_running; 149 | } 150 | 151 | void OpenBCI_Ganglion::processData() { 152 | MCP_dataReady = false; 153 | if(sampleCounter > 200){ 154 | sampleCounter = 0; 155 | ringBufferLevel = 0; 156 | } 157 | if (wifi.present && wifi.tx) { 158 | if (useAccel){ 159 | wifi.storeByteBufTx(PCKT_END | PACKET_TYPE_ACCEL); 160 | }else{ 161 | wifi.storeByteBufTx(PCKT_END | PACKET_TYPE_RAW_AUX); 162 | } 163 | wifi.storeByteBufTx(sampleCounter); 164 | } 165 | if (streamSynthetic) { 166 | updateSyntheticChannelData(); 167 | } else { 168 | updateMCPdata(); 169 | if (useAccel){ 170 | updateAccelerometerData(); 171 | } 172 | } 173 | if(wifi.present && wifi.tx){ 174 | wifi.flushBufferTx(); 175 | return; 176 | } 177 | 178 | if(useAccel == true){ 179 | if(accelOnEdge == true){ 180 | accelOnEdge = false; 181 | if(sampleCounter%2 == 0){ compressData19(); return; } 182 | } 183 | compressData18(); // compress deltas and send on even samples 184 | } else { 185 | if(accelOffEdge == true){ 186 | accelOffEdge = false; 187 | if(sampleCounter%2 == 0){ compressData18(); return; } 188 | } 189 | compressData19(); // compress deltas and send on even samples 190 | } 191 | } 192 | 193 | boolean OpenBCI_Ganglion::stopRunning(void) { 194 | if (is_running == true) { 195 | is_running = false; 196 | MCP_turnOffAllChannels(); 197 | disable_LIS2DH(); 198 | } 199 | streamSynthetic = false; 200 | return is_running; 201 | } 202 | 203 | int OpenBCI_Ganglion::changeChannelState_maintainRunningState(int chan, int start) { 204 | boolean was_running_when_called = is_running; 205 | //must stop running to turn channel on/off 206 | stopRunning(); 207 | if (start == 1) { 208 | if(!was_running_when_called){ 209 | loadString("Activating channel "); loadInt(chan, true); 210 | } 211 | channelMask &= channelEnable[chan - 1]; // turn on the channel 212 | } else { 213 | if(!was_running_when_called){ 214 | loadString("Deactivating channel "); loadInt(chan, true); 215 | } 216 | channelMask |= channelDisable[chan - 1]; // turn off the channel 217 | } 218 | if (!was_running_when_called){ 219 | prepToSendBytes(); 220 | } else { 221 | startRunning(); 222 | } 223 | } 224 | 225 | void OpenBCI_Ganglion::initSyntheticData() { 226 | for (int i = 1; i < 4; i++) { 227 | channelData[i] = 0; 228 | } 229 | } 230 | 231 | void OpenBCI_Ganglion::startRunningSynthetic() { 232 | previousTime = millis(); 233 | startRunning(); 234 | } 235 | 236 | void OpenBCI_Ganglion::updateSyntheticChannelData() { 237 | currentTime = millis(); 238 | 239 | if (currentTime - previousTime > syntheticFrequency) { 240 | previousTime = currentTime; 241 | syntheticValue = -syntheticValue; 242 | } 243 | 244 | for (int i = 0; i < 4; i++) { 245 | channelData[i] = syntheticValue; 246 | } 247 | } 248 | 249 | /* 250 | Send two samples in one packet by truncating the data to the MSBs. 251 | Each sample is a signed 24 bit integer, but is stored in a 32 bit integer. 252 | This means that only the 32nd (sign) and 1st-23rd bits have data. 253 | Truncate the 24 bit integer to 18 bits by removing the 6 LSBs. 254 | Move the sign bit to the new LSB place. 255 | Now the sample is in bits 1-18 where the first bit is the sign bit. 256 | Align the 2 samples x 4 channel x 18 bit samples into 18 bytes. 257 | */ 258 | void OpenBCI_Ganglion::compressData18() 259 | { 260 | bool even = (sampleCounter % 2) == 0; 261 | for (int i = 0; i < 4; i++) 262 | { 263 | bitWrite(channelData[i], 6, (bitRead(channelData[i], 31))); // Store the sign bit in bit 6 264 | channelData[i] = channelData[i] >> 6; // Then align the data to bits 1-18 265 | } 266 | if (even) 267 | { 268 | // Pack even samples first 269 | compression_ring[ringBufferLevel][0] = ((channelData[0] & 0x0003FC00) >> 10); 270 | compression_ring[ringBufferLevel][1] = ((channelData[0] & 0x000003FC) >> 2); 271 | compression_ring[ringBufferLevel][2] = ((channelData[0] & 0x00000003) << 6); 272 | compression_ring[ringBufferLevel][2] |= ((channelData[1] & 0x0003F000) >> 12); 273 | compression_ring[ringBufferLevel][3] = ((channelData[1] & 0x00000FF0) >> 4); 274 | compression_ring[ringBufferLevel][4] = ((channelData[1] & 0x0000000F) << 4); 275 | compression_ring[ringBufferLevel][4] |= ((channelData[2] & 0x0003C000) >> 14); 276 | compression_ring[ringBufferLevel][5] = ((channelData[2] & 0x00003FC0) >> 6); 277 | compression_ring[ringBufferLevel][6] = ((channelData[2] & 0x0000003F) << 2); 278 | compression_ring[ringBufferLevel][6] |= ((channelData[3] & 0x00030000) >> 16); 279 | compression_ring[ringBufferLevel][7] = ((channelData[3] & 0x0000FF00) >> 8); 280 | compression_ring[ringBufferLevel][8] = ((channelData[3] & 0x000000FF)); 281 | } 282 | else 283 | { 284 | // Pack odd samples second 285 | compression_ring[ringBufferLevel][9] = ((channelData[0] & 0x0003FC00) >> 10); 286 | compression_ring[ringBufferLevel][10] = ((channelData[0] & 0x000003FC) >> 2); 287 | compression_ring[ringBufferLevel][11] = ((channelData[0] & 0x00000003) << 6); 288 | compression_ring[ringBufferLevel][11] |= ((channelData[1] & 0x0003F000) >> 12); 289 | compression_ring[ringBufferLevel][12] = ((channelData[1] & 0x00000FF0) >> 4); 290 | compression_ring[ringBufferLevel][13] = ((channelData[1] & 0x0000000F) << 4); 291 | compression_ring[ringBufferLevel][13] |= ((channelData[2] & 0x0003C000) >> 14); 292 | compression_ring[ringBufferLevel][14] = ((channelData[2] & 0x00003FC0) >> 6); 293 | compression_ring[ringBufferLevel][15] = ((channelData[2] & 0x0000003F) << 2); 294 | compression_ring[ringBufferLevel][15] |= ((channelData[3] & 0x00030000) >> 16); 295 | compression_ring[ringBufferLevel][16] = ((channelData[3] & 0x0000FF00) >> 8); 296 | compression_ring[ringBufferLevel][17] = ((channelData[3] & 0x000000FF)); 297 | 298 | // Send on the even packet 299 | sendCompressedPacket18(); 300 | } 301 | } 302 | 303 | void OpenBCI_Ganglion::sendCompressedPacket18() { 304 | radioBuffer[0] = ringBufferLevel; 305 | for (int i = 0; i < 18; i++) { 306 | radioBuffer[i+1] = compression_ring[ringBufferLevel][i]; 307 | } 308 | if(useAccel){ 309 | if(ringBufferLevel%10 == 1){ radioBuffer[19] = axisData[0]; } 310 | if(ringBufferLevel%10 == 2){ radioBuffer[19] = axisData[1]; } 311 | if(ringBufferLevel%10 == 3){ radioBuffer[19] = axisData[2]; } 312 | } else if(useAux){ 313 | if(ringBufferLevel%10 == 1){ radioBuffer[19] = auxData[0]; } 314 | if(ringBufferLevel%10 == 2){ radioBuffer[19] = auxData[1]; } 315 | if(ringBufferLevel%10 == 3){ radioBuffer[19] = auxData[2]; } 316 | } 317 | ringBufferLevel++; 318 | if (BLEconnected) { 319 | SimbleeBLE.send(radioBuffer, 20); 320 | } 321 | } 322 | 323 | 324 | /* 325 | Send two samples in one packet by truncating the data to the MSBs. 326 | Each sample is a signed 24 bit integer, but is stored in a 32 bit integer. 327 | This means that only the 32nd (sign) and 1st-23rd bits have data. 328 | Truncate the 24 bit integer to 19 bits by removing the 5 LSBs. 329 | Move the sign bit to the new LSB place. 330 | Now the sample is in bits 1-19 where the first bit is the sign bit. 331 | Align the 2 samples x 4 channel x 19 bit samples into 19 bytes. 332 | */ 333 | void OpenBCI_Ganglion::compressData19() 334 | { 335 | bool even = (sampleCounter % 2) == 0; 336 | for (int i = 0; i < 4; i++) 337 | { 338 | bitWrite(channelData[i], 5, (bitRead(channelData[i], 31))); // Store the sign bit in bit 5 339 | channelData[i] = channelData[i] >> 5; // Then align the data to bits 1-19 340 | } 341 | if (even) 342 | { 343 | // Pack even samples first 344 | compression_ring[ringBufferLevel][0] = ((channelData[0] & 0x0007F800) >> 11); 345 | compression_ring[ringBufferLevel][1] = ((channelData[0] & 0x000007F8) >> 3); 346 | compression_ring[ringBufferLevel][2] = ((channelData[0] & 0x00000007) << 5); 347 | compression_ring[ringBufferLevel][2] |= ((channelData[1] & 0x0007C000) >> 14); 348 | compression_ring[ringBufferLevel][3] = ((channelData[1] & 0x00003FC0) >> 6); 349 | compression_ring[ringBufferLevel][4] = ((channelData[1] & 0x0000003F) << 2); 350 | compression_ring[ringBufferLevel][4] |= ((channelData[2] & 0x00060000) >> 17); 351 | compression_ring[ringBufferLevel][5] = ((channelData[2] & 0x0001FE00) >> 9); 352 | compression_ring[ringBufferLevel][6] = ((channelData[2] & 0x000001FE) >> 1); 353 | compression_ring[ringBufferLevel][7] = ((channelData[2] & 0x00000001) << 7); 354 | compression_ring[ringBufferLevel][7] |= ((channelData[3] & 0x0007F000) >> 12); 355 | compression_ring[ringBufferLevel][8] = ((channelData[3] & 0x00000FF0) >> 4); 356 | compression_ring[ringBufferLevel][9] = ((channelData[3] & 0x0000000F) << 4); 357 | } 358 | else 359 | { 360 | // Pack odd samples second 361 | compression_ring[ringBufferLevel][9] |= ((channelData[0] & 0x00078000) >> 15); 362 | compression_ring[ringBufferLevel][10] = ((channelData[0] & 0x00007F80) >> 7); 363 | compression_ring[ringBufferLevel][11] = ((channelData[0] & 0x0000007F) << 1); 364 | compression_ring[ringBufferLevel][11] |= ((channelData[1] & 0x00040000) >> 18); 365 | compression_ring[ringBufferLevel][12] = ((channelData[1] & 0x0003FC00) >> 10); 366 | compression_ring[ringBufferLevel][13] = ((channelData[1] & 0x000003FC) >> 2); 367 | compression_ring[ringBufferLevel][14] = ((channelData[1] & 0x00000003) << 6); 368 | compression_ring[ringBufferLevel][14] |= ((channelData[2] & 0x0007E000) >> 13); 369 | compression_ring[ringBufferLevel][15] = ((channelData[2] & 0x00001FE0) >> 5); 370 | compression_ring[ringBufferLevel][16] = ((channelData[2] & 0x0000001F) << 3); 371 | compression_ring[ringBufferLevel][16] |= ((channelData[3] & 0x00070000) >> 16); 372 | compression_ring[ringBufferLevel][17] = ((channelData[3] & 0x0000FF00) >> 8); 373 | compression_ring[ringBufferLevel][18] = ((channelData[3] & 0x000000FF)); 374 | 375 | // Send on the even packet 376 | sendCompressedPacket19(); 377 | } 378 | } 379 | 380 | void OpenBCI_Ganglion::sendCompressedPacket19() { 381 | radioBuffer[0] = ringBufferLevel + 100; 382 | for (int i = 0; i < 19; i++) { 383 | radioBuffer[i+1] = compression_ring[ringBufferLevel][i]; 384 | } 385 | ringBufferLevel++; 386 | if (BLEconnected) { 387 | SimbleeBLE.send(radioBuffer, 20); 388 | } 389 | } 390 | 391 | 392 | void OpenBCI_Ganglion::testImpedance() { 393 | if (!ACwaveTest) { 394 | uAsampleCounter = 0; 395 | negativeRunningTotal = positiveRunningTotal = positiveSampleCounter = negativeSampleCounter = 0; 396 | changeZtestForChannel(channelUnderZtest, 1); // trun on the current for testing 397 | realZeroPosition = getDACzeroPosition(); 398 | ACwaveTest = true; 399 | ACrising = false; 400 | int runningTotal = 0; 401 | for (int i = 0; i < 10; i++) { 402 | runningTotal += analogRead(SHUNT_SENSOR); 403 | delay(1); 404 | } 405 | currentCountsAtZeroAmps = (runningTotal / 10); 406 | maxPosCurrentCounts = minNegCurrentCounts = 0; 407 | updateDAC(realZeroPosition - HALF_WAVE); 408 | halfPeriodTimer = uAsampleTimer = micros(); 409 | } 410 | else 411 | { 412 | currentTime = micros(); // time critical activities! 413 | if (currentTime - halfPeriodTimer > HALF_PERIOD) { 414 | halfPeriodTimer = currentTime; 415 | if (ACrising) { 416 | updateDAC(realZeroPosition - HALF_WAVE); 417 | ACrising = false; 418 | edge = true; 419 | uAsampleTimer = currentTime; 420 | } else { 421 | updateDAC(realZeroPosition + HALF_WAVE); 422 | ACrising = true; 423 | edge = true; 424 | uAsampleTimer = currentTime; 425 | } 426 | } 427 | if (currentTime - uAsampleTimer > UA_SAMPLE_TIME) { 428 | if (uAsampleCounter > UA_SAMPLE_LIMIT) { 429 | updateDAC(realZeroPosition); 430 | ACwaveTest = false; 431 | positiveMean = positiveRunningTotal / positiveSampleCounter; 432 | negativeMean = negativeRunningTotal / negativeSampleCounter; 433 | changeZtestForChannel(channelUnderZtest, 0); // turn off the current switch 434 | // Serial.println("* Test Complete"); 435 | // int impedance = (HALF_WAVE * 2 * DAC_volts_per_count) / ((((maxPosCurrentCounts - minNegCurrentCounts) * ADC_volts_per_count)/SHUNT_SENSOR_GAIN)/(SHUNT));///2)); 436 | int _impedance = (HALF_WAVE * DAC_volts_per_count) / (((((maxPosCurrentCounts - minNegCurrentCounts)/2) * ADC_volts_per_count)/SHUNT_SENSOR_GAIN)/SHUNT); 437 | // Serial.print("positiveMax = "); Serial.println(maxPosCurrentCounts); 438 | // Serial.print("negativeMin = "); Serial.println(minNegCurrentCounts); 439 | // Serial.print("_impedance "); Serial.println(_impedance); 440 | double _imp = double(_impedance)/1000.0; 441 | double impedance = convertRawGanglionImpedanceToTarget(_imp); 442 | if (wifi.present && wifi.tx) { 443 | wifi.bufferTxClear(); 444 | wifi.storeByteBufTx(PCKT_END | PACKET_TYPE_IMPEDANCE); 445 | wifi.storeByteBufTx(channelUnderZtest); 446 | int integer = impedance; 447 | int digitCounter = 0; 448 | char digit[10]; 449 | while (integer > 0) { 450 | digit[digitCounter] = (integer % 10) + '0'; 451 | integer /= 10; 452 | digitCounter++; 453 | } 454 | for (int i = digitCounter - 1; i >= 0; i--) { 455 | wifi.storeByteBufTx(digit[i]); 456 | } 457 | wifi.flushBufferTx(); 458 | } else { 459 | initSerialBuffer(); 460 | loadInt(impedance, false); loadChar('Z', true); 461 | serialBuffer[0][0] = ID_Z_1 + (channelUnderZtest - 1); 462 | timeLastPacketSent = millis(); // prime the timer to send verbose packets 463 | bufferLevelCounter = 0; 464 | serialBytesToSend = true; sendSerialBytesBlocking(); 465 | } 466 | channelUnderZtest++; 467 | if (channelUnderZtest > 5) { channelUnderZtest = 1; } 468 | return; 469 | } 470 | 471 | currentCounts = (analogRead(SHUNT_SENSOR) - currentCountsAtZeroAmps); 472 | // Serial.println(currentCounts); // verbose feedback 473 | if (ACrising) { 474 | if(currentCounts > maxPosCurrentCounts){ maxPosCurrentCounts = currentCounts; } 475 | } else { 476 | if(currentCounts < minNegCurrentCounts){ minNegCurrentCounts = currentCounts; } 477 | } 478 | uAsampleTimer = currentTime; 479 | uAsampleCounter++; 480 | } 481 | } 482 | } 483 | 484 | void OpenBCI_Ganglion::changeZtestForChannel(int channel, int setting) { 485 | digitalWrite(impedanceSwitch[channel - 1], setting); 486 | } 487 | 488 | void OpenBCI_Ganglion::endImpedanceTest(){ 489 | testingImpedance = false; 490 | updateDAC(realZeroPosition); 491 | for(int i=1; i<=5; i++){ 492 | changeZtestForChannel(i, 0); 493 | } 494 | } 495 | 496 | double OpenBCI_Ganglion::convertRawGanglionImpedanceToTarget(double _actual){ 497 | //the following impedance adjustment calculations were derived using empirical values from resistors between 1,2,3,4,REF-->D_G 498 | double _target; 499 | //V1 -- more accurate for lower impedances (< 22kOhcm) -> y = 0.0034x^3 - 0.1443x^2 + 3.1324x - 10.59 500 | if(_actual <= 22.0){ 501 | // _target = (0.0004)*(pow(_actual,3)) - (0.0262)*(pow(_actual,2)) + (1.8349)*(_actual) - 6.6006; 502 | _target = (0.0034)*(pow(_actual,3)) - (0.1443)*(pow(_actual,2)) + (3.1324)*(_actual) - 10.59; 503 | } 504 | //V2 -- more accurate for higher impedances (> 22kOhm) -> y = 0.000009x^4 - 0.001x^3 + 0.0409x^2 + 0.6445x - 1 505 | else { 506 | _target = (0.000009)*(pow(_actual,4)) - (0.001)*pow(_actual,3) + (0.0409)*(pow(_actual,2)) + (0.6445)*(pow(_actual,1)) - 1; 507 | } 508 | 509 | return _target; 510 | 511 | } 512 | 513 | // <<<<<<<<<<<<<<<<<<<<<<<<< END OF BOARD WIDE FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 514 | // ************************************************************************************* 515 | // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< AD5621 DAC FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 516 | 517 | void OpenBCI_Ganglion::updateDAC(word DAC_pos) { // 518 | SPI.setDataMode(SPI_MODE1); // DAC uses MODE 1 519 | 520 | word command = (DAC_pos << 2) & DAC_MASK; 521 | byte highCommand = (command >> 8) & 0xFF; 522 | byte lowCommand = command & 0xFF; 523 | digitalWrite(DAC_SS, LOW); 524 | SPI.transfer(highCommand); 525 | SPI.transfer(lowCommand); 526 | digitalWrite(DAC_SS, HIGH); 527 | DAC_position = DAC_pos; 528 | 529 | SPI.setDataMode(SPI_MODE0); // Everything else uses MODE 0 530 | } 531 | 532 | void OpenBCI_Ganglion::updateDAC() { 533 | SPI.setDataMode(SPI_MODE1); // DAC uses MODE 1 534 | 535 | word command = (DAC_position << 2) & DAC_MASK; 536 | digitalWrite(DAC_SS, LOW); 537 | SPI.transfer(highByte(command)); 538 | SPI.transfer(lowByte(command)); 539 | digitalWrite(DAC_SS, HIGH); 540 | 541 | SPI.setDataMode(SPI_MODE0); // Everything else uses MODE 0 542 | } 543 | 544 | void OpenBCI_Ganglion::zeroDAC() { 545 | SPI.setDataMode(SPI_MODE1); // DAC uses MODE 1 546 | 547 | word command = (DAC_position << 2) & DAC_MASK; 548 | command |= DAC_1K; 549 | digitalWrite(DAC_SS, LOW); 550 | SPI.transfer(highByte(command)); 551 | SPI.transfer(lowByte(command)); 552 | digitalWrite(DAC_SS, HIGH); 553 | 554 | SPI.setDataMode(SPI_MODE0); // Everything else uses MODE 0 555 | } 556 | 557 | float OpenBCI_Ganglion::get_Zvalue(int DAC_pos) { 558 | DAC_voltage = (DAC_pos * DAC_volts_per_count); // - 1.5; 559 | Ohms = int(DAC_voltage / 0.00001); 560 | return Ohms; 561 | } 562 | 563 | 564 | word OpenBCI_Ganglion::getDACzeroPosition() { 565 | updateDAC(DACmidline); 566 | sampleNumber = 0; 567 | sampleTimer = micros(); 568 | while (sampleNumber < 100) { 569 | gotoTarget(0.0, noise); 570 | } 571 | return DAC_position; 572 | } 573 | 574 | void OpenBCI_Ganglion::readShuntSensor() { 575 | currentCounts = analogRead(SHUNT_SENSOR); 576 | // nAmp_Value = currentCounts * nAmps_per_count; 577 | uAmp_Value = float(currentCounts) * ADC_volts_per_count; 578 | // uAmp_Value -= 15.0; // 579 | uAmp_Value -= 1.5; 580 | } 581 | 582 | void OpenBCI_Ganglion::gotoTarget(float target, float n) { 583 | if (micros() - sampleTimer > gotoSampleTime) { 584 | sampleTimer = micros(); 585 | sampleNumber++; 586 | readShuntSensor(); 587 | if (uAmp_Value > target + n) { 588 | decreased++; 589 | DAC_position--; updateDAC(); 590 | } else if (uAmp_Value < target - n) { 591 | increased++; 592 | DAC_position++; updateDAC(); 593 | } else { 594 | steady++; 595 | } 596 | } 597 | } 598 | 599 | 600 | 601 | // <<<<<<<<<<<<<<<<<<<<<<<<< END OF AD5621 DAC FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 602 | // ************************************************************************************* 603 | // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LIS2DH FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 604 | 605 | 606 | 607 | void OpenBCI_Ganglion::updateAccelerometerData() { 608 | unsigned short _x, _y, _z = 0x0000; 609 | if(digitalRead(LIS_DRDY) == HIGH){ 610 | _x = LIS2DH_read16(OUT_X_L); 611 | _y = LIS2DH_read16(OUT_Y_L); 612 | _z = LIS2DH_read16(OUT_Z_L); 613 | 614 | axisData[0] = highByte(_x); // read out the 8bit axis values 615 | axisData[1] = highByte(_y); 616 | axisData[2] = highByte(_z); 617 | if(wifi.present && wifi.tx){ 618 | _x >>= 4; 619 | if(bitRead(_x,11) == 1){ _x |= 0xF000; } 620 | _y >>= 4; 621 | if(bitRead(_y,11) == 1){ _y |= 0xF000; } 622 | _z >>= 4; 623 | if(bitRead(_z,11) == 1){ _z |= 0xF000; } 624 | wifi.storeByteBufTx(highByte(_x)); 625 | wifi.storeByteBufTx(lowByte(_x)); 626 | wifi.storeByteBufTx(highByte(_y)); 627 | wifi.storeByteBufTx(lowByte(_y)); 628 | wifi.storeByteBufTx(highByte(_z)); 629 | wifi.storeByteBufTx(lowByte(_z)); 630 | } 631 | newAccelData = true; 632 | } 633 | 634 | } 635 | 636 | void OpenBCI_Ganglion::config_LIS2DH() { 637 | LIS2DH_write(TEMP_CFG_REG, 0xC0); // enable temperature sensor 638 | LIS2DH_write(CTRL_REG1, 0x20); // 10Hz data rate, high resolution, axis disabled 639 | LIS2DH_write(CTRL_REG3, 0x10); // DRDY1 INTERUPT ON INT_1 PIN 640 | LIS2DH_write(CTRL_REG4, 0x08); // 0x10 = +/- 4G, 0x00 = +/- 2G axis data continuous update 641 | 642 | } 643 | 644 | void OpenBCI_Ganglion::enable_LIS2DH() { 645 | LIS2DH_write(CTRL_REG1, 0x27); // 10Hz data rate, high resolution, axis enabled 646 | } 647 | 648 | void OpenBCI_Ganglion::disable_LIS2DH() { 649 | LIS2DH_write(CTRL_REG1, 0x20); // 10Hz data rate, high resolution, axis disabled 650 | } 651 | 652 | word OpenBCI_Ganglion::LIS2DH_readTemp() { 653 | word temp = 0; 654 | if ((LIS2DH_read(STATUS_REG_AUX) & 0x04) > 1) { // check for updated temp data... 655 | temp = LIS2DH_read16(OUT_TEMP_L); 656 | if (!is_running || BLEconnected) { 657 | loadString("Temperature "); loadInt(temp, false); loadlnString("*"); // 12, 1 658 | prepToSendBytes(); 659 | } 660 | } 661 | return temp; 662 | } 663 | 664 | byte OpenBCI_Ganglion::LIS2DH_read(byte reg) { 665 | reg |= READ_REG; 666 | digitalWrite(LIS2DH_SS, LOW); 667 | SPI.transfer(reg); 668 | byte inByte = SPI.transfer(0x00); 669 | digitalWrite(LIS2DH_SS, HIGH); 670 | return inByte; 671 | } 672 | 673 | 674 | void OpenBCI_Ganglion::LIS2DH_write(byte reg, byte value) { 675 | digitalWrite(LIS2DH_SS, LOW); 676 | SPI.transfer(reg); 677 | SPI.transfer(value); 678 | digitalWrite(LIS2DH_SS, HIGH); 679 | } 680 | 681 | short OpenBCI_Ganglion::LIS2DH_read16(byte reg) { 682 | short inData; 683 | reg |= READ_REG | READ_MULTI; 684 | digitalWrite(LIS2DH_SS, LOW); 685 | SPI.transfer(reg); 686 | inData = SPI.transfer(0x00) | (SPI.transfer(0x00) << 8); 687 | digitalWrite(LIS2DH_SS, HIGH); 688 | return inData; 689 | } 690 | 691 | float OpenBCI_Ganglion::getG(byte axis) { 692 | short counts = LIS2DH_read16(axis); 693 | float gValue = float(counts) * scale_factor_gs_per_count; 694 | return gValue; 695 | } 696 | 697 | void OpenBCI_Ganglion::LIS2DH_readAllRegs_Serial() { 698 | loadlnString("LIS2DH\nREG\tSetting"); 699 | byte inByte; 700 | byte reg = STATUS_REG_AUX | READ_REG; 701 | digitalWrite(LIS2DH_SS, LOW); 702 | SPI.transfer(reg); 703 | inByte = SPI.transfer(0x00); 704 | digitalWrite(LIS2DH_SS, HIGH); 705 | loadHex(reg & 0x7F, 1, false); 706 | loadString("\t", 1, false); loadHex(inByte, 1, true); 707 | digitalWrite(LIS2DH_SS, HIGH); 708 | reg = OUT_TEMP_L | READ_REG | READ_MULTI; 709 | digitalWrite(LIS2DH_SS, LOW); 710 | SPI.transfer(reg); 711 | for (int i = OUT_TEMP_L; i <= WHO_AM_I; i++) { 712 | inByte = SPI.transfer(0x00); 713 | loadHex(i, 1, false); 714 | loadChar('\t', false); loadHex(inByte, 1, true); 715 | } 716 | digitalWrite(LIS2DH_SS, HIGH); 717 | if (!wifi.present) { 718 | prepToSendBytes(); 719 | sendSerialBytesBlocking(); 720 | } 721 | reg = TEMP_CFG_REG | READ_REG | READ_MULTI; 722 | digitalWrite(LIS2DH_SS, LOW); 723 | SPI.transfer(reg); 724 | for (int i = TEMP_CFG_REG; i <= ACT_DUR; i++) { 725 | inByte = SPI.transfer(0x00); 726 | loadHex(i, 1, false); 727 | loadChar('\t', false); loadHex(inByte, 1, true); 728 | } 729 | digitalWrite(LIS2DH_SS, HIGH); 730 | loadNewLine(); 731 | if (!wifi.present) { 732 | prepToSendBytes(); 733 | sendSerialBytesBlocking(); 734 | } 735 | } 736 | 737 | // <<<<<<<<<<<<<<<<<<<<<<<<< END OF LIS2DH FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 738 | // ************************************************************************************* 739 | // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< MCP3912 FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 740 | 741 | 742 | void OpenBCI_Ganglion::config_MCP3912(unsigned long gain) { 743 | unsigned int config0 = 0x003C0050 | (curSampleRate << 13); // dither on max, boost 2x, OSR 4096, 744 | // digitalWrite(MCP_RST, LOW); delay(50); 745 | // digitalWrite(MCP_RST, HIGH); delay(300); 746 | digitalWrite(MCP_SS, LOW); 747 | MCP_sendCommand(GAIN, MCP_WRITE); 748 | MCP_writeRegister(gain); // GAIN_1, _2, _4, _8, _16, _32 749 | MCP_writeRegister(0x00B9000F); // STATUSCOM auto increment TYPES DR in HIZ 750 | MCP_writeRegister(config0); // CONFIG_0: 0x003CE050 | sample rate: 50, 100, 200, 400 751 | MCP_writeRegister(0x000F0000); // CONFIG_1: put the ADCs in reset, external oscillator 752 | digitalWrite(MCP_SS, HIGH); 753 | } 754 | 755 | void OpenBCI_Ganglion::updateMCPdata() { 756 | int byteCounter = 2; 757 | digitalWrite(MCP_SS, LOW); 758 | MCP_sendCommand(channelAddress[0], MCP_READ); // send request to read from CHAN_0 address 759 | for (int i = 0; i < 4; i++) { 760 | channelData[i] = MCP_readRegister(); // read the 24bit result into the long variable array 761 | if(wifi.present && wifi.tx){ 762 | for (int j = 16; j >= 0; j -= 8) { 763 | wifi.storeByteBufTx(channelData[i] >> j & 0xFF); // fill the raw data array for streaming 764 | byteCounter++; 765 | } 766 | } 767 | } 768 | if (wifi.present && wifi.tx) { 769 | for (int i = 0; i < 4 * 3; i++) { 770 | wifi.storeByteBufTx(0); 771 | } 772 | } 773 | digitalWrite(MCP_SS, HIGH); 774 | // this section corrects the sign on the long array 775 | for (int i = 0; i < 4; i++) { 776 | if ((channelData[i] & 0x00800000) > 0) { 777 | channelData[i] |= 0xFF000000; 778 | } else { 779 | channelData[i] &= 0x00FFFFFF; 780 | } 781 | } 782 | } 783 | 784 | void OpenBCI_Ganglion::MCP_sendCommand(byte address, byte rw) { 785 | byte command = DEV_ADD | address | rw; 786 | SPI.transfer(command); 787 | } 788 | 789 | 790 | long OpenBCI_Ganglion::MCP_readRegister() { 791 | 792 | long thisRegister = SPI.transfer(0x00); 793 | thisRegister <<= 8; 794 | thisRegister |= SPI.transfer(0x00); 795 | thisRegister <<= 8; 796 | thisRegister |= SPI.transfer(0x00); 797 | 798 | return thisRegister; 799 | } 800 | 801 | void OpenBCI_Ganglion::MCP_writeRegister(unsigned long setting) { 802 | byte thisByte = (setting & 0x00FF0000) >> 16; 803 | SPI.transfer(thisByte); 804 | thisByte = (setting & 0x0000FF00) >> 8; 805 | SPI.transfer(thisByte); 806 | thisByte = setting & 0x000000FF; 807 | SPI.transfer(thisByte); 808 | } 809 | 810 | 811 | void OpenBCI_Ganglion::MCP_turnOnChannels() { 812 | digitalWrite(MCP_SS, LOW); 813 | MCP_sendCommand(CONFIG_1, MCP_WRITE); 814 | MCP_writeRegister(channelMask); // turn on selected channels 815 | digitalWrite(MCP_SS, HIGH); 816 | 817 | } 818 | 819 | void OpenBCI_Ganglion::MCP_turnOffAllChannels() { 820 | digitalWrite(MCP_SS, LOW); 821 | MCP_sendCommand(CONFIG_1, MCP_WRITE); 822 | MCP_writeRegister(0x000F0000); // turn off all channels 823 | digitalWrite(MCP_SS, HIGH); 824 | } 825 | 826 | 827 | void OpenBCI_Ganglion::MCP_readAllRegs() { 828 | loadlnString("MCP3912\nREG\tSetting"); 829 | for (int i = MOD_VAL; i <= GAINCAL_3; i += 2) { 830 | if (i != 0x12) { 831 | digitalWrite(MCP_SS, LOW); 832 | MCP_sendCommand(i, MCP_READ); 833 | regVal = MCP_readRegister(); 834 | digitalWrite(MCP_SS, HIGH); 835 | MCP_printRegisterName(i); 836 | loadHex(regVal, 3, true); 837 | } 838 | } 839 | digitalWrite(MCP_SS, LOW); 840 | if (!wifi.present) { 841 | prepToSendBytes(); 842 | sendSerialBytesBlocking(); 843 | } 844 | delay(10); 845 | MCP_sendCommand(LOK_CRC, MCP_READ); 846 | regVal = MCP_readRegister(); 847 | digitalWrite(MCP_SS, HIGH); 848 | MCP_printRegisterName(LOK_CRC); 849 | loadHex(regVal, 3, true); 850 | prepToSendBytes(); 851 | if (!wifi.present) { 852 | sendSerialBytesBlocking(); 853 | } 854 | } 855 | 856 | void OpenBCI_Ganglion::MCP_printRegisterName(byte _address) { 857 | 858 | switch (_address) { 859 | case MOD_VAL: 860 | loadString("MOD_VAL ", 10, false); break; 861 | case GAIN: 862 | loadString("GAIN ", 10, false); break; 863 | case PHASE: 864 | loadString("PHASE ", 10, false); break; 865 | case STATUSCOM: 866 | loadString("STATUSCOM ", 10, false); break; 867 | case CONFIG_0: 868 | loadString("CONFIG_0 ", 10, false); break; 869 | case CONFIG_1: 870 | loadString("CONFIG_1 ", 10, false); break; 871 | case OFFCAL_0: 872 | loadString("OFFCAL_0 ", 10, false); break; 873 | case GAINCAL_0: 874 | loadString("GAINCAL_0 ", 10, false); break; 875 | case OFFCAL_1: 876 | loadString("OFFCAL_1 ", 10, false); break; 877 | case GAINCAL_1: 878 | loadString("GAINCAL_1 ", 10, false); break; 879 | case OFFCAL_2: 880 | loadString("OFFCAL_2 ", 10, false); break; 881 | case GAINCAL_2: 882 | loadString("GAINCAL_2 ", 10, false); break; 883 | case OFFCAL_3: 884 | loadString("OFFCAL_3 ", 10, false); break; 885 | case GAINCAL_3: 886 | loadString("GAINCAL_3 ", 10, false); break; 887 | case LOK_CRC: 888 | loadString("LOK_CRC ", 10, false); break; 889 | default: 890 | break; 891 | } 892 | 893 | } 894 | 895 | 896 | // <<<<<<<<<<<<<<<<<<<<<<<<< END OF MCP3912 FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 897 | // ************************************************************************************* 898 | // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< COM FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 899 | 900 | 901 | 902 | 903 | boolean OpenBCI_Ganglion::eventSerial() { 904 | 905 | if(clearForOTA){ 906 | clearForOTA = false; 907 | delay(100); 908 | ota_bootloader_start(); //begins OTA enabled state 909 | } 910 | 911 | if(BLEcharTail != BLEcharHead){ 912 | BLEcharTail++; 913 | if(BLEcharTail >19){ BLEcharTail = 0; } 914 | parseChar(BLEchar[BLEcharTail]); 915 | } 916 | 917 | while (Serial.available()) { 918 | inChar = Serial.read(); 919 | gotSerial = true; 920 | parseChar(inChar); 921 | } 922 | 923 | if (serialBytesToSend) { 924 | // send via BLE if available 925 | if (BLEconnected) { 926 | if ((millis() - timeLastPacketSent) > 15) { 927 | SimbleeBLE.send(serialBuffer[bufferLevelCounter], serialIndex[bufferLevelCounter]); 928 | bufferLevelCounter++; // get ready for next buffered packet 929 | if (bufferLevelCounter == bufferLevel + 1) { // when we send all the packets 930 | serialBytesToSend = false; // put down bufferToSend flag 931 | bufferLevel = 0; // initialize bufferLevel 932 | initSerialBuffer(); // initialize buffer 933 | } 934 | timeLastPacketSent = millis(); 935 | } 936 | } else { 937 | // send via Serial Port if no BLE available 938 | for (int i = 0; i <= bufferLevel; i++) { 939 | for (int j = 1; j < serialIndex[i]; j++) { 940 | Serial.write(serialBuffer[i][j]); 941 | } 942 | } 943 | serialBytesToSend = false; // put down bufferToSend flag 944 | bufferLevel = 0; // initialize bufferLevel 945 | initSerialBuffer(); // initialize buffer 946 | } 947 | } 948 | 949 | return gotSerial; 950 | } 951 | 952 | 953 | void OpenBCI_Ganglion::sendSerialBytesBlocking() { 954 | // send via BLE if available 955 | if (BLEconnected) { 956 | while (serialBytesToSend) { 957 | if ((millis() - timeLastPacketSent) > 15) { 958 | SimbleeBLE.send(serialBuffer[bufferLevelCounter], serialIndex[bufferLevelCounter]); 959 | bufferLevelCounter++; // get ready for next buffered packet 960 | if (bufferLevelCounter == bufferLevel + 1) { // when we send all the packets 961 | // Serial.println(serialBuffer[0][0]); 962 | serialBytesToSend = false; // put down bufferToSend flag 963 | bufferLevel = 0; // initialize bufferLevel 964 | initSerialBuffer(); // initialize bufffer 965 | } 966 | timeLastPacketSent = millis(); 967 | } 968 | } 969 | } else { 970 | // send via Serial Port if no BLE available 971 | for (int i = 0; i <= bufferLevel; i++) { 972 | for (int j = 1; j < serialIndex[i]; j++) { 973 | Serial.write(serialBuffer[i][j]); 974 | } 975 | } 976 | serialBytesToSend = false; // put down bufferToSend flag 977 | bufferLevel = 0; // initialize bufferLevel 978 | initSerialBuffer(); // initialize bufffer 979 | } 980 | } 981 | 982 | void OpenBCI_Ganglion::prepToSendBytes() { 983 | if (commandFromSPI) { 984 | if (wifi.present && wifi.tx) { 985 | wifi.sendStringLast(); 986 | } 987 | } else { 988 | if (serialIndex[bufferLevel] == 0) { 989 | bufferLevel--; // don't send an empty buffer! 990 | } 991 | if (bufferLevel > 0) { 992 | for (int i = 0; i < bufferLevel; i++) { 993 | serialBuffer[i][0] = ID_MULTI_PACKET; 994 | } 995 | } 996 | serialBuffer[bufferLevel][0] = ID_MULTI_PACKET_STOP; 997 | timeLastPacketSent = millis(); // prime the timer to send verbose packets 998 | bufferLevelCounter = 0; 999 | serialBytesToSend = true; 1000 | } 1001 | } 1002 | 1003 | void OpenBCI_Ganglion::loadNewLine() { 1004 | if (commandFromSPI) { 1005 | if (wifi.present && wifi.tx) { 1006 | wifi.sendStringMulti("\n"); 1007 | delay(1); 1008 | } 1009 | } else { 1010 | serialBuffer[bufferLevel][serialIndex[bufferLevel]] = '\n'; 1011 | serialIndex[bufferLevel]++; // count up the buffer size 1012 | if (serialIndex[bufferLevel] == SERIAL_BUFFER_LENGTH) { // when the buffer is full, 1013 | bufferLevel++; // next buffer please 1014 | } 1015 | } 1016 | } 1017 | 1018 | void OpenBCI_Ganglion::loadString(const char* thatString, int numChars, boolean addNewLine) { 1019 | if (commandFromSPI) { 1020 | if (wifi.present && wifi.tx) { 1021 | wifi.sendStringMulti(thatString); 1022 | delay(1); 1023 | } 1024 | } else { 1025 | for (int i = 0; i < numChars; i++) { 1026 | serialBuffer[bufferLevel][serialIndex[bufferLevel]] = thatString[i]; 1027 | serialIndex[bufferLevel]++; // count up the buffer size 1028 | if (serialIndex[bufferLevel] == SERIAL_BUFFER_LENGTH) { // when the buffer is full, 1029 | bufferLevel++; // next buffer please 1030 | } 1031 | } 1032 | } 1033 | if (addNewLine) { 1034 | loadNewLine(); 1035 | } 1036 | } 1037 | 1038 | void OpenBCI_Ganglion::loadString(const char* thatString) { 1039 | loadString(thatString, strlen(thatString), false); 1040 | } 1041 | 1042 | void OpenBCI_Ganglion::loadString(void) { 1043 | loadString(""); 1044 | } 1045 | 1046 | void OpenBCI_Ganglion::loadlnString(const char* thatString) { 1047 | loadString(thatString, strlen(thatString), true); 1048 | } 1049 | 1050 | void OpenBCI_Ganglion::loadlnString(void) { 1051 | loadlnString(""); 1052 | } 1053 | 1054 | void OpenBCI_Ganglion::printFailure() { 1055 | loadString("Failure: "); 1056 | } 1057 | 1058 | void OpenBCI_Ganglion::printSuccess() { 1059 | loadString("Success: "); 1060 | } 1061 | 1062 | void OpenBCI_Ganglion::printSampleRate() { 1063 | loadString("Sample rate is "); 1064 | loadString(getSampleRate()); 1065 | loadlnString("Hz"); 1066 | } 1067 | 1068 | void OpenBCI_Ganglion::loadChar(char thatChar, boolean addNewLine) { 1069 | // const char* temp[1]; 1070 | // temp[0] = (const char *)thatChar; 1071 | 1072 | if (commandFromSPI) { 1073 | if (wifi.present && wifi.tx) { 1074 | wifi.sendStringMulti(&thatChar); 1075 | delay(1); 1076 | } 1077 | } else { 1078 | serialBuffer[bufferLevel][serialIndex[bufferLevel]] = thatChar; 1079 | serialIndex[bufferLevel]++; // count up the buffer size 1080 | if (serialIndex[bufferLevel] == SERIAL_BUFFER_LENGTH) { // when the buffer is full, 1081 | bufferLevel++; // next buffer please 1082 | } 1083 | } 1084 | } 1085 | 1086 | void OpenBCI_Ganglion::loadHex(int hexBytes, int numBytes, boolean addNewLine) { 1087 | byte nibble; 1088 | int numBits = (numBytes * 8) - 4; 1089 | loadString("0x"); 1090 | for (int i = numBits; i >= 0; i -= 4) { 1091 | nibble = ((hexBytes >> i) & 0x0F) + '0'; 1092 | if (nibble > '9') { 1093 | nibble += 7; 1094 | } 1095 | loadChar((char)nibble, false); 1096 | } 1097 | if (addNewLine) { 1098 | loadNewLine(); 1099 | } 1100 | } 1101 | 1102 | 1103 | void OpenBCI_Ganglion::initSerialBuffer() { // initialize 2D serial buffer in normal mode 1104 | for (int i = 0; i < SERIAL_BUFFER_LENGTH; i++) { 1105 | serialIndex[i] = 1; // save byte 0 for the byte ID 1106 | } 1107 | } 1108 | 1109 | 1110 | void OpenBCI_Ganglion::loadInt(int i, boolean addNewLine) { 1111 | int integer = i; 1112 | int digitCounter = 0; 1113 | char digit[10]; 1114 | 1115 | while (integer > 0) { 1116 | digit[digitCounter] = (integer % 10) + '0'; 1117 | integer /= 10; 1118 | digitCounter++; 1119 | } 1120 | 1121 | for (int i = digitCounter - 1; i >= 0; i--) { 1122 | if (commandFromSPI) { 1123 | if (wifi.present && wifi.tx) { 1124 | wifi.sendStringMulti(digit[i]); 1125 | delay(1); 1126 | } 1127 | } else { 1128 | serialBuffer[bufferLevel][serialIndex[bufferLevel]] = digit[i]; 1129 | serialIndex[bufferLevel]++; // count up the buffer size 1130 | if (serialIndex[bufferLevel] == SERIAL_BUFFER_LENGTH) { // when the buffer is full, 1131 | bufferLevel++; // next buffer please 1132 | } 1133 | } 1134 | } 1135 | 1136 | if (addNewLine) { 1137 | loadNewLine(); 1138 | } 1139 | } 1140 | 1141 | void OpenBCI_Ganglion::parseCharWifi(char token) { 1142 | commandFromSPI = true; 1143 | parseChar(token); 1144 | commandFromSPI = false; 1145 | } 1146 | 1147 | // DECODE THE RECEIVED COMMAND CHARACTER 1148 | void OpenBCI_Ganglion::parseChar(char token) { 1149 | if(settingSampleRate){ processIncomingSampleRate(token); return; } 1150 | switch (token) { 1151 | // TURN OFF CHANNELS 1152 | case DEACTIVATE_CHANNEL_1: 1153 | changeChannelState_maintainRunningState(1, DEACTIVATE); break; 1154 | // Serial.println("Deactivate 1"); break; 1155 | case DEACTIVATE_CHANNEL_2: 1156 | changeChannelState_maintainRunningState(2, DEACTIVATE); break; 1157 | // Serial.println("Deactivate 2"); break; 1158 | case DEACTIVATE_CHANNEL_3: 1159 | changeChannelState_maintainRunningState(3, DEACTIVATE); break; 1160 | // Serial.println("Deactivate 3"); break; 1161 | case DEACTIVATE_CHANNEL_4: 1162 | changeChannelState_maintainRunningState(4, DEACTIVATE); break; 1163 | // Serial.println("Deactivate 4"); break; 1164 | // TURN ON CHANNELS 1165 | case ACTIVATE_CHANNEL_1: 1166 | changeChannelState_maintainRunningState(1, ACTIVATE); break; 1167 | // Serial.println("Activate 1"); break; 1168 | case ACTIVATE_CHANNEL_2: 1169 | changeChannelState_maintainRunningState(2, ACTIVATE); break; 1170 | // Serial.println("Activate 2"); break; 1171 | case ACTIVATE_CHANNEL_3: 1172 | changeChannelState_maintainRunningState(3, ACTIVATE); break; 1173 | // Serial.println("Activate 3"); break; 1174 | case ACTIVATE_CHANNEL_4: 1175 | changeChannelState_maintainRunningState(4, ACTIVATE); break; 1176 | // Serial.println("Activate 4"); break; 1177 | 1178 | case START_DATA_STREAM: 1179 | if (!BLEconnected && !wifi.present) { 1180 | loadString("BLE not connected"); 1181 | loadlnString(": abort startRunning"); 1182 | prepToSendBytes(); 1183 | } else if (!is_running){ 1184 | if(testingImpedance){ endImpedanceTest(); } 1185 | requestToStartRunning = true; 1186 | if (wifi.present && wifi.tx) { 1187 | loadlnString("Stream started"); 1188 | prepToSendBytes(); 1189 | delay(10); 1190 | } 1191 | startRunning(); // returns value of is_running = true 1192 | } 1193 | break; 1194 | case ENABLE_SYNTHETIC_DATA: 1195 | if (!is_running) { 1196 | loadlnString("enable square wave"); 1197 | prepToSendBytes(); 1198 | } 1199 | streamSynthetic = true; 1200 | break; 1201 | case DISABLE_SYNTHETIC_DATA: 1202 | if (!is_running) { 1203 | loadlnString("disable square wave"); 1204 | prepToSendBytes(); 1205 | } 1206 | streamSynthetic = false; 1207 | break; 1208 | case STOP_DATA_STREAM: 1209 | stopRunning(); // returns value of is_running = false 1210 | loadlnString("stop running"); 1211 | prepToSendBytes(); 1212 | break; 1213 | case ENABLE_ACCELEROMETER: 1214 | useAccel = true; 1215 | if (!is_running) { 1216 | loadlnString("accelerometer enabled"); 1217 | prepToSendBytes(); 1218 | } else if(BLEconnected){ 1219 | accelOnEdge = true; 1220 | } 1221 | enable_LIS2DH(); 1222 | break; 1223 | case DISABLE_ACCELEROMETER: 1224 | useAccel = false; 1225 | if (!is_running) { 1226 | loadlnString("accelerometer disabled"); 1227 | prepToSendBytes(); 1228 | } else if(BLEconnected){ 1229 | accelOffEdge = true; 1230 | } 1231 | disable_LIS2DH(); 1232 | break; 1233 | case SOFT_RESET: // CONFIG 1234 | if(is_running){ stopRunning(); } 1235 | startFromScratch(gain); 1236 | break; 1237 | case REPORT_REGISTER_SETTINGS: // PRINT ALL REGISTER VALUES 1238 | if(!is_running){ printAllRegisters_Serial(); } 1239 | break; 1240 | case OPENBCI_Z_TEST_START: // 'z' 1241 | if (is_running) { 1242 | wasRunningWhenCalled = true; 1243 | stopRunning(); 1244 | // Serial.println("stopRunning"); 1245 | } else { 1246 | wasRunningWhenCalled = false; 1247 | } 1248 | if (wifi.present && wifi.tx) { 1249 | loadlnString("Impedance started"); 1250 | prepToSendBytes(); 1251 | } 1252 | testingImpedance = true; 1253 | ACwaveTest = false; 1254 | channelUnderZtest = 1; 1255 | break; 1256 | case OPENBCI_Z_TEST_STOP: // 'Z' 1257 | // Serial.println("received Z"); 1258 | endImpedanceTest(); 1259 | if (wifi.present && wifi.tx) { 1260 | loadlnString("Impedance stopped"); 1261 | prepToSendBytes(); 1262 | } 1263 | if (wasRunningWhenCalled) { 1264 | startRunning(); 1265 | } 1266 | break; 1267 | case ENABLE_OTA: // '>' 1268 | requestForOTAenable = true; 1269 | if(!BLEconnected){ clearForOTA = true; } 1270 | break; 1271 | case OPENBCI_SAMPLE_RATE_SET: 1272 | settingSampleRate = true; 1273 | break; 1274 | case OPENBCI_WIFI_ATTACH: 1275 | if (wifi.attach()) { 1276 | printSuccess(); 1277 | loadlnString("Wifi attached"); 1278 | } else { 1279 | printFailure(); 1280 | loadlnString("Wifi not attached"); 1281 | } 1282 | prepToSendBytes(); 1283 | break; 1284 | case OPENBCI_WIFI_REMOVE: 1285 | if (wifi.remove()) { 1286 | printSuccess(); 1287 | loadlnString("Wifi removed"); 1288 | } else { 1289 | printFailure(); 1290 | loadlnString("Wifi not removed"); 1291 | } 1292 | prepToSendBytes(); 1293 | break; 1294 | case OPENBCI_WIFI_STATUS: 1295 | if (wifi.present) { 1296 | loadlnString("Wifi present"); 1297 | } else { 1298 | loadString("Wifi not present, send"); loadlnString(" { to attach the shield"); 1299 | } 1300 | prepToSendBytes(); 1301 | break; 1302 | case OPENBCI_WIFI_RESET: 1303 | wifi.reset(); 1304 | loadlnString("Wifi soft reset"); 1305 | prepToSendBytes(); 1306 | break; 1307 | default: 1308 | if(!BLEconnected){ 1309 | loadString("parseChar got: "); loadHex(token, 1, true); 1310 | prepToSendBytes(); 1311 | } 1312 | break; 1313 | } 1314 | } 1315 | 1316 | 1317 | // <<<<<<<<<<<<<<<<<<<<<<<<< END OF COM FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 1318 | // ************************************************************************************* 1319 | // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< SIMBLEE FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 1320 | 1321 | void SimbleeBLE_onConnect() { 1322 | ganglion.channelMask = 0x00000000; 1323 | ganglion.BLEconnected = true; 1324 | digitalWrite(LED,HIGH); 1325 | } 1326 | 1327 | void SimbleeBLE_onDisconnect() { 1328 | ganglion.BLEconnected = false; 1329 | if(!ganglion.writingToSD){ 1330 | ganglion.stopRunning(); 1331 | ganglion.endImpedanceTest(); 1332 | ganglion.useAccel = false; 1333 | ganglion.useAux = false; 1334 | ganglion.LED_timer = millis(); 1335 | } 1336 | if(ganglion.requestForOTAenable){ 1337 | ganglion.requestForOTAenable = false; 1338 | ganglion.clearForOTA = true; 1339 | } 1340 | } 1341 | 1342 | void SimbleeBLE_onReceive(char *data, int len) { 1343 | ganglion.BLEcharHead++; 1344 | if(ganglion.BLEcharHead >19){ ganglion.BLEcharHead = 0; } 1345 | ganglion.BLEchar[ganglion.BLEcharHead] = data[0]; 1346 | } 1347 | 1348 | // <<<<<<<<<<<<<<<<<<<<<<<<< END OF SIMBLEE FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 1349 | 1350 | void OpenBCI_Ganglion::setSampleRate(uint8_t newSampleRateCode) { 1351 | curSampleRate = (SAMPLE_RATE)newSampleRateCode; 1352 | config_MCP3912(gain); 1353 | } 1354 | 1355 | void OpenBCI_Ganglion::processIncomingSampleRate(char c) { 1356 | if (c == OPENBCI_SAMPLE_RATE_SET) { 1357 | printSuccess(); 1358 | printSampleRate(); 1359 | prepToSendBytes(); 1360 | } else if (isDigit(c)) { 1361 | uint8_t digit = c - '0'; 1362 | if (digit <= SAMPLE_RATE_200) { 1363 | if (!is_running) { 1364 | setSampleRate(digit); 1365 | printSuccess(); 1366 | printSampleRate(); 1367 | prepToSendBytes(); 1368 | } 1369 | } else { 1370 | if (!is_running) { 1371 | printFailure(); 1372 | loadlnString("sample value out of bounds"); 1373 | printSampleRate(); 1374 | prepToSendBytes(); 1375 | } 1376 | } 1377 | } else { 1378 | if (!is_running) { 1379 | printFailure(); 1380 | loadString("Invalid sample value."); 1381 | printSampleRate(); 1382 | prepToSendBytes(); 1383 | } 1384 | } 1385 | settingSampleRate = false; 1386 | } 1387 | 1388 | const char* OpenBCI_Ganglion::getSampleRate() { 1389 | switch (curSampleRate) { 1390 | case SAMPLE_RATE_25600: 1391 | return "25600"; 1392 | case SAMPLE_RATE_12800: 1393 | return "12800"; 1394 | case SAMPLE_RATE_6400: 1395 | return "6400"; 1396 | case SAMPLE_RATE_3200: 1397 | return "3200"; 1398 | case SAMPLE_RATE_1600: 1399 | return "1600"; 1400 | case SAMPLE_RATE_800: 1401 | return "800"; 1402 | case SAMPLE_RATE_400: 1403 | return "400"; 1404 | case SAMPLE_RATE_200: 1405 | default: 1406 | return "200"; 1407 | } 1408 | } 1409 | 1410 | /** 1411 | * Return an array of gains in coded ADS form i.e. 0-6 where 6 is x24 and so on. 1412 | * @return [description] 1413 | */ 1414 | uint8_t *OpenBCI_Ganglion::getGains(void) { 1415 | uint8_t gains[NUM_CHANNELS]; 1416 | for (uint8_t i = 0; i < NUM_CHANNELS; i++) { 1417 | gains[i] = 1; 1418 | } 1419 | return gains; 1420 | } 1421 | 1422 | OpenBCI_Ganglion ganglion; 1423 | -------------------------------------------------------------------------------- /OpenBCI_Ganglion_Library.h: -------------------------------------------------------------------------------- 1 | /* 2 | insert header here 3 | 4 | */ 5 | #ifndef _____OpenBCI_Ganglion_Library__ 6 | #define _____OpenBCI_Ganglion_Library__ 7 | 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "Definitions_Ganglion.h" 14 | 15 | class OpenBCI_Ganglion { 16 | public: 17 | 18 | 19 | typedef enum PACKET_TYPE { 20 | PACKET_TYPE_ACCEL, 21 | PACKET_TYPE_RAW_AUX, 22 | PACKET_TYPE_USER_DEFINED, 23 | PACKET_TYPE_ACCEL_TIME_SET, 24 | PACKET_TYPE_ACCEL_TIME_SYNC, 25 | PACKET_TYPE_RAW_AUX_TIME_SET, 26 | PACKET_TYPE_RAW_AUX_TIME_SYNC, 27 | PACKET_TYPE_IMPEDANCE 28 | }; 29 | 30 | typedef enum SAMPLE_RATE { 31 | SAMPLE_RATE_25600, 32 | SAMPLE_RATE_12800, 33 | SAMPLE_RATE_6400, 34 | SAMPLE_RATE_3200, 35 | SAMPLE_RATE_1600, 36 | SAMPLE_RATE_800, 37 | SAMPLE_RATE_400, 38 | SAMPLE_RATE_200 39 | }; 40 | 41 | OpenBCI_Ganglion(); 42 | 43 | SAMPLE_RATE curSampleRate; 44 | 45 | void processIncomingSampleRate(char); 46 | void setSampleRate(uint8_t); 47 | const char * getSampleRate(void); 48 | 49 | void initialize(void); 50 | void makeUniqueId(void); 51 | void blinkLED(void); 52 | void startFromScratch(unsigned long); 53 | void printAllRegisters_Serial(); 54 | void processData(void); 55 | boolean startRunning(void); 56 | boolean stopRunning(void); 57 | // void sendChannelData(void); 58 | int changeChannelState_maintainRunningState(int, int); 59 | void initSyntheticData(void); 60 | void startRunningSynthetic(void); 61 | void updateSyntheticChannelData(void); 62 | void buildRawPacket(void); 63 | void sendRawPacket(void); 64 | void compressData18(void); 65 | void compressData19(void); 66 | void sendCompressedPacket18(void); 67 | void sendCompressedPacket19(void); 68 | void resendPacket(byte); // this trip to the past will happen in the future 69 | void testImpedance(void); 70 | void endImpedanceTest(void); 71 | double convertRawGanglionImpedanceToTarget(double); 72 | void updateDAC(word); 73 | void updateDAC(void); 74 | void zeroDAC(void); 75 | float get_Zvalue(int); 76 | word getDACzeroPosition(); 77 | void logData_Serial(void); 78 | void readShuntSensor(void); 79 | void gotoTarget(float, float ); 80 | void rampTest(void); 81 | void changeZtestForChannel(int, int); 82 | void updateAccelerometerData(void); 83 | void config_LIS2DH(void); 84 | void enable_LIS2DH(void); 85 | void disable_LIS2DH(void); 86 | word LIS2DH_readTemp(void); 87 | byte LIS2DH_read(byte); 88 | void LIS2DH_write(byte, byte); 89 | short LIS2DH_read16(byte); 90 | float getG(byte); 91 | void LIS2DH_readAllRegs_Serial(); 92 | // int MCP_ISR(uint32_t); 93 | void config_MCP3912(unsigned long); 94 | void updateMCPdata(void); 95 | void MCP_sendCommand(byte, byte); 96 | long MCP_readRegister(void); 97 | void MCP_writeRegister(unsigned long); 98 | void MCP_turnOnChannels(void); 99 | void MCP_turnOffAllChannels(void); 100 | void MCP_readAllRegs_Serial(void); 101 | void MCP_printRegisterName_Serial(byte); 102 | void MCP_readAllRegs(void); 103 | void MCP_printRegisterName(byte); 104 | boolean eventSerial(void); 105 | void sendSerialBytesBlocking(void); 106 | void prepToSendBytes(void); 107 | void loadNewLine(void); 108 | void loadString(const char*, int, boolean); 109 | void loadString(const char*); 110 | void loadString(void); 111 | void loadlnString(const char*); 112 | void loadlnString(void); 113 | void loadChar(char, boolean); 114 | void loadHex(int, int, boolean); 115 | void initSerialBuffer(void); 116 | void loadInt(int i, boolean); 117 | void parseChar(char); 118 | void parseCharWifi(char); 119 | void printFailure(void); 120 | void printSampleRate(void); 121 | void printSuccess(void); 122 | uint8_t * getGains(void); 123 | 124 | boolean settingSampleRate = false; 125 | 126 | uint8_t advdata[21] = 127 | { 128 | 20, // length // 0 129 | 0x09, // complete local name type // 1 130 | 0x47, // 'G' // 2 131 | 0x61, // 'a' // 3 132 | 0x6E, // 'n' // 4 133 | 0x67, // 'g' // 5 134 | 0x6C, // 'l' // 6 135 | 0x69, // 'i' // 7 136 | 0x6F, // 'o' // 8 137 | 0x6E, // 'n' // 9 138 | 0x20, // ' ' // 10 139 | 0x31, // '1' // 11 140 | 0x2E, // '.' // 12 141 | 0x33, // '3' // 13 142 | 0x20, // ' ' // 14 143 | 0x28, // '(' // 15 144 | 0x54, // 'T' // 16 145 | 0x41, // 'A' // 17 146 | 0x43, // 'C' // 18 147 | 0x4f, // 'O' // 19 148 | 0x29, // ')' // 20 149 | }; 150 | 151 | // int LED_delayTime = 300; 152 | unsigned int LED_timer = 0; 153 | boolean LED_state = true; 154 | boolean is_running = false; 155 | boolean streamSynthetic = false; 156 | boolean serialBytesToSend = false; 157 | char serialBuffer[SERIAL_BUFFER_LENGTH][SERIAL_BUFFER_LENGTH]; 158 | int serialIndex[SERIAL_BUFFER_LENGTH]; 159 | unsigned long timeLastPacketSent; // used to time sending verbose BLE packets 160 | int bufferLevel; 161 | int bufferLevelCounter; 162 | boolean wasRunningWhenCalled = false; 163 | boolean useAux = false; 164 | boolean newAuxData = false; 165 | char auxData[3]; 166 | char inChar; 167 | boolean gotSerial = false; 168 | boolean commandFromSPI = false; 169 | 170 | // >>>> LIS2DH STUFF <<<< 171 | 172 | boolean useAccel = false; 173 | int8_t axisData[3]; // holds accelerometer x,y,z 174 | boolean accelOnEdge = false; 175 | boolean accelOffEdge = false; 176 | byte ID; // holds LIS2DH device ID 177 | float scale_factor_gs_per_count = 0.016; // assume +/-4g, normal mode. 8bits left justified 178 | volatile boolean LIS_dataReady = false; 179 | boolean newAccelData = false; 180 | 181 | 182 | // >>>> MCP3912 STUFF <<<< 183 | byte compression_ring[RING_SIZE][MAX_BYTES_PER_PACKET]; 184 | int ringBufferLevel = 0; // const int compressionMask = 0xFFFFFFF8; 185 | int channelData[4]; // holds channel data 186 | byte rawChannelData[24]; 187 | volatile byte sampleCounter = 0xFF; // sample counter, for real 188 | volatile boolean MCP_dataReady = false; 189 | volatile boolean zeroth = false; 190 | volatile boolean first = false; 191 | 192 | 193 | unsigned int sampleNumber; 194 | unsigned long channelMask = 0x00000000; // used to turn on selected channels 195 | unsigned long channelEnable[4] = {ENABLE_0, ENABLE_1, ENABLE_2, ENABLE_3}; 196 | unsigned long channelDisable[4] = {DISABLE_0, DISABLE_1, DISABLE_2, DISABLE_3}; 197 | byte channelAddress[4] = {CHAN_0,CHAN_1,CHAN_2,CHAN_3}; 198 | unsigned long gain = GAIN_1; 199 | // unsigned long sps = SAMPLE_200; 200 | boolean requestToStartRunning = false; 201 | unsigned long currentTime; 202 | unsigned long previousTime; 203 | unsigned long thisStampTime; 204 | unsigned long thatStampTime; 205 | int timeDifference; 206 | int syntheticValue = 8000000; 207 | unsigned long regVal; 208 | 209 | 210 | // >>>> IMPEDANCE TESTING STUFF <<<< 211 | 212 | boolean testingImpedance = false; 213 | int channelUnderZtest; 214 | float uAmp_Value = 0.0; // value of measured current 215 | float ADC_volts_per_count = 3.0/1023.0; //0.00293255 // 3/((2^10)-1) 216 | // float DAC_uVolts_per_count = 3.0/4095.0; // .0007236; // (3/((2^12)-1)) 217 | float DAC_volts_per_count = 3.0/4095.0; //0.0007236; // 3/((2^12)-1) 218 | float noise = 0.1; 219 | short DAC_position; // 12bit DAC resolution (4096) ~0.8mV DAC, ~5nA tXCS 220 | float DAC_voltage; // DAC position converted to volts 221 | short DACmidline = 2047; 222 | int Ohms; 223 | short DACmask; // used in update to add control bits 224 | boolean increment = true; 225 | boolean impedanceTesting = false; 226 | int impedanceSwitch[5] = {Z_TEST_1, Z_TEST_2, Z_TEST_3, Z_TEST_4, Z_TEST_REF}; 227 | int currentChannelSetting; 228 | int leadOffSetting; 229 | int currentChannelZeroPosition; 230 | int currentChannelPlusTenPosition; 231 | int currentChannelMinusTenPosition; 232 | boolean rampTesting = false; 233 | boolean Z_noiseTesting = false; 234 | boolean plusTen = false; 235 | boolean minusTen = false; 236 | boolean zero = false; 237 | unsigned long testTimer; 238 | int testTime = 10000; // 10 second test time to collect data 239 | unsigned long sampleTimer; 240 | int rampSampleTime = 100; // 100 millisecond time between samples (10Hz) 241 | int noiseSampleTime = 10000; // 10000 microsecond time between samples (100Hz) 242 | int gotoSampleTime = 2000; // 243 | int numCurrentMeasurements = 4; 244 | int currentMeasurementCounter; 245 | 246 | // AC WAVEFORM STUPH 247 | boolean ACwaveTest = false; 248 | unsigned long halfPeriodTimer; // used to time each cycle of the AC wave 249 | 250 | word realZeroPosition = 2048; // have to discover the '0Amps' point later 251 | boolean ACrising = true; // start the square wave going up 252 | int Z_testTime = 1000; // test for a second? 253 | unsigned long Z_testTimer; // 254 | 255 | unsigned long uAsampleTimer; 256 | int halfWave = 100; 257 | int uAsampleCounter; 258 | int currentCounts; 259 | int maxPosCurrentCounts; 260 | int minNegCurrentCounts; 261 | int currentCountsAtZeroAmps; 262 | int peakCurrentCounts; 263 | int positiveRunningTotal; 264 | int negativeRunningTotal; 265 | int positiveSampleCounter; 266 | int negativeSampleCounter; 267 | boolean edge = false; 268 | int increased, decreased, steady; 269 | int negativeMean, positiveMean; 270 | int positiveEdge, negativeEdge, negativeEdgeCounter, positiveEdgeCounter; 271 | 272 | // <<<< BLE STUFF >>>> 273 | char radioBuffer[20]; 274 | char resendBuffer[20]; 275 | boolean BLEconnected = false; 276 | char BLEchar[20]; 277 | int BLEcharHead = 0; 278 | int BLEcharTail = 0; 279 | unsigned long rssiTimer; 280 | boolean requestForOTAenable = false; 281 | boolean clearForOTA = false; 282 | boolean writingToSD = false; 283 | boolean useSerial = false; // used for testing stuph 284 | int syntheticFrequency = 500; 285 | 286 | }; 287 | 288 | extern OpenBCI_Ganglion ganglion; 289 | 290 | #endif 291 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenBCI_Ganglion_Libraries 2 | Firmware Libraries for the OpenBCI Ganglion Board 3 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # v2.0.1 2 | 3 | ### Bug Fixes 4 | 5 | * Adding a delay after sending multipacket spi messages improves reliability! Some messages like sample rate got chopped up :rocket: 6 | 7 | # v2.0.0 8 | 9 | ### New Features 10 | 11 | * Added Wifi Shield Compatibility 12 | * Added ability to change sample rates 13 | 14 | ### Breaking Changes 15 | 16 | * Changed MCP3912 setup to NOT accept sample rate passed into it 17 | 18 | ## Release Candidate 4 19 | 20 | ### Bug Fixes 21 | 22 | * Fix bug where accelerometer data was not placed in the right positions with WiFi. 23 | * Went through every command and made sure a response was sent over wifi. 24 | * Closes #209 - Impedance was not working on wifi, I created a custom packet type `0xC7` which is for impedance sent as `utf8` over wifi 25 | 26 | ## Release Candidate 3 27 | 28 | ### Bug Fixes 29 | 30 | * LED light could have held in off state if connected to wifi shield while LED is off. 31 | 32 | ## Release Candidate 2 33 | 34 | ### Bug Fixes 35 | 36 | * LED light could have held in off state if connected to wifi shield while LED is off. 37 | 38 | ### Enhancements 39 | 40 | * Accelerometer enabled by default with wifi. 41 | 42 | ## Release Candidate 1 43 | 44 | Initial release candidate 45 | 46 | ## Beta4 47 | 48 | ### Enhancements 49 | 50 | * Needed to update sample rate setting functions to match the Cyton responses and thus work with wifi drivers. 51 | * Add more helper printing functions to reduce code string foot prints such as `::printSampleRate()`, `::printFailure()`, and `::printSuccess()` 52 | 53 | ## Beta3 54 | 55 | ### New Features 56 | 57 | * Send gains after connecting to Wifi shield 58 | 59 | ## Beta2 60 | 61 | The overall goal was to clean the wifi code out of the library so it would not be needed when you are building a bare board. 62 | 63 | ### Bug Fixes 64 | 65 | * Weird timing issue with wifi shield 66 | 67 | ### Breaking Changes 68 | 69 | * Removed all wifi code and put into [new library](https://github.com/OpenBCI/OpenBCI_Wifi_Master_Library) that must be included! The new library is a called [OpenBCI_Wifi_Master_Library](https://github.com/OpenBCI/OpenBCI_Wifi_Master_Library). It is simply included when wifi is wanted. 70 | * Removed `.loop()` function from library and all other `wifi*.()` functions. 71 | * `DefaultGanglion.ino` now has wifi code. 72 | 73 | ### Files 74 | 75 | * Removed `WifiGanglion.ino` 76 | * Add `GanglionNoWifi.ino` example 77 | 78 | ## Beta1 79 | 80 | * Initial release with wifi 81 | 82 | # v1.1.2 83 | 84 | ### Bug fixes 85 | 86 | * Changed the BLE on receive to use a ring buffer. See #2 87 | 88 | # v1.1.1 89 | 90 | * Changed the way SD file names are generated to allow for OTA programming. 91 | 92 | # v1.0.0 93 | 94 | * Initial Release 95 | -------------------------------------------------------------------------------- /examples/DefaultGanglion/DefaultGanglion.ino: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* 4 | Uesd to test the basic functionality of the Ganglion Board 5 | Targets a Simblee. LIS2DH, MCP3912, AD5621 on board 6 | 7 | Made by Joel Murphy, Leif Percifield, AJ Keller, and Conor Russomanno for OpenBCI, Inc. 2016 8 | 9 | 10 | MUST CHANGE THE SPI PINS IN THE variants.h FILE 11 | #define SPI_INTERFACE NRF_SPI0 12 | #define PIN_SPI_SS (26u) //(6u) 13 | #define PIN_SPI_MOSI (18u) //(5u) 14 | #define PIN_SPI_MISO (15u) //(3u) 15 | #define PIN_SPI_SCK (16u) //(4u) 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | void setup() { 23 | // Bring up the Ganglion 24 | ganglion.initialize(); 25 | attachPinInterrupt(MCP_DRDY, MCP_ISR, LOW); 26 | // Bring up wifi 27 | wifi.begin(true, true); 28 | } 29 | 30 | 31 | void loop() { 32 | 33 | if(ganglion.MCP_dataReady){ 34 | ganglion.processData(); 35 | } 36 | 37 | ganglion.blinkLED(); 38 | 39 | ganglion.eventSerial(); 40 | 41 | if(ganglion.testingImpedance){ 42 | ganglion.testImpedance(); 43 | } 44 | // Call to wifi loop 45 | wifi.loop(); 46 | 47 | if (wifi.hasData()) { 48 | // Read one char from the wifi shield 49 | char newChar = wifi.getChar(); 50 | // Send to the board library 51 | ganglion.parseCharWifi(newChar); 52 | } 53 | 54 | if (!wifi.sentGains) { 55 | if(wifi.present && wifi.tx) { 56 | ganglion.useAccel = true; 57 | ganglion.enable_LIS2DH(); 58 | ganglion.LED_state = true; 59 | digitalWrite(LED, HIGH); 60 | wifi.sendGains(4, ganglion.getGains()); 61 | } 62 | } 63 | } // end of loop 64 | 65 | 66 | int MCP_ISR(uint32_t dummyPin) { // gotta have a dummyPin... 67 | 68 | ganglion.MCP_dataReady = true; 69 | ganglion.sampleCounter++; 70 | 71 | return 0; // gotta return nothing, somehow... 72 | } 73 | -------------------------------------------------------------------------------- /examples/GanglionNoWifi/GanglionNoWifi.ino: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* 4 | Uesd to test the basic functionality of the Ganglion Board 5 | Targets a Simblee. LIS2DH, MCP3912, AD5621 on board 6 | 7 | Made by Joel Murphy, Leif Percifield, AJ Keller, and Conor Russomanno for OpenBCI, Inc. 2016 8 | 9 | 10 | MUST CHANGE THE SPI PINS IN THE variants.h FILE 11 | #define SPI_INTERFACE NRF_SPI0 12 | #define PIN_SPI_SS (26u) //(6u) 13 | #define PIN_SPI_MOSI (18u) //(5u) 14 | #define PIN_SPI_MISO (15u) //(3u) 15 | #define PIN_SPI_SCK (16u) //(4u) 16 | */ 17 | 18 | #include 19 | 20 | void setup() { 21 | // Bring up the Ganglion 22 | ganglion.initialize(); 23 | attachPinInterrupt(MCP_DRDY, MCP_ISR, LOW); 24 | } 25 | 26 | 27 | void loop() { 28 | 29 | if(ganglion.MCP_dataReady){ 30 | ganglion.processData(); 31 | } 32 | 33 | ganglion.blinkLED(); 34 | 35 | ganglion.eventSerial(); 36 | 37 | if(ganglion.testingImpedance){ 38 | ganglion.testImpedance(); 39 | } 40 | } // end of loop 41 | 42 | int MCP_ISR(uint32_t dummyPin) { // gotta have a dummyPin... 43 | 44 | ganglion.MCP_dataReady = true; 45 | ganglion.sampleCounter++; 46 | 47 | return 0; // gotta return nothing, somehow... 48 | } 49 | -------------------------------------------------------------------------------- /examples/GanglionWithSD/GanglionWithSD.ino: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* 4 | Uesd to test the basic functionality of the Ganglion Board 5 | Targets a Simblee. LIS2DH, MCP3912, AD5621 on board 6 | 7 | Made by Joel Murphy, Leif Percifield, and AJ Keller for OpenBCI, Inc. 2016 8 | 9 | 10 | MUST CHANGE THE SPI PINS IN THE variants.h FILE Lines 88-92 11 | #define SPI_INTERFACE NRF_SPI0 12 | #define PIN_SPI_SS (26u) //(6u) 13 | #define PIN_SPI_MOSI (18u) //(5u) 14 | #define PIN_SPI_MISO (15u) //(3u) 15 | #define PIN_SPI_SCK (16u) //(4u) 16 | */ 17 | 18 | #include 19 | #include 20 | 21 | 22 | boolean SDfileOpen = false; // Set true by SD_Card_Stuff.ino on successful file open 23 | 24 | unsigned long thisTime; 25 | unsigned long thatTime; 26 | 27 | void setup() { 28 | 29 | ganglion.initialize(); 30 | attachPinInterrupt(MCP_DRDY, MCP_ISR, LOW); 31 | 32 | } 33 | 34 | 35 | void loop() { 36 | 37 | if(ganglion.MCP_dataReady){ 38 | 39 | ganglion.processData(); 40 | 41 | 42 | if(SDfileOpen) { // Verify the SD file is open 43 | // Write to the SD card 44 | writeDataToSDcard(ganglion.sampleCounter); 45 | } 46 | } 47 | 48 | ganglion.blinkLED(); 49 | 50 | if(ganglion.eventSerial()){ 51 | sdProcessChar(ganglion.inChar); // check for an SD related command 52 | } 53 | 54 | if(ganglion.testingImpedance){ 55 | ganglion.testImpedance(); 56 | } 57 | 58 | 59 | 60 | } // end of loop 61 | 62 | 63 | int MCP_ISR(uint32_t dummyPin) { // gotta have a dummyPin... 64 | 65 | ganglion.MCP_dataReady = true; 66 | ganglion.sampleCounter++; 67 | 68 | return 0; // gotta return nothing, somehow... 69 | } 70 | -------------------------------------------------------------------------------- /examples/GanglionWithSD/SD_Card_Stuff.ino: -------------------------------------------------------------------------------- 1 | 2 | #define BLOCK_5MIN 11000 3 | #define BLOCK_15MIN 33000 4 | #define BLOCK_30MIN 66000 5 | #define BLOCK_1HR 131000 6 | #define BLOCK_2HR 261000 7 | #define BLOCK_4HR 521000 8 | #define BLOCK_12HR 1561000 9 | #define BLOCK_24HR 3122000 10 | 11 | #define OVER_DIM 20 // make room for up to 20 write-time overruns 12 | 13 | 14 | char fileSize = '0'; // SD file size indicator 15 | int blockCounter = 0; 16 | 17 | uint32_t BLOCK_COUNT; 18 | SdFile openfile; // want to put this before setup... 19 | Sd2Card card(SD_SS);// SPI needs to be init'd before here 20 | SdVolume volume; 21 | SdFile root; 22 | uint8_t* pCache; // array that points to the block buffer on SD card 23 | uint32_t MICROS_PER_BLOCK = 4000; // block write longer than this will get flaged 24 | uint32_t bgnBlock, endBlock; // file extent bookends 25 | int byteCounter = 0; // used to hold position in cache 26 | //int blockCounter; // count up to BLOCK_COUNT with this 27 | boolean openvol; 28 | boolean cardInit = false; 29 | boolean fileIsOpen = false; 30 | 31 | struct { 32 | uint32_t block; // holds block number that over-ran 33 | uint32_t micro; // holds the length of this of over-run 34 | } over[OVER_DIM]; 35 | uint32_t overruns; // count the number of overruns 36 | uint32_t maxWriteTime; // keep track of longest write time 37 | uint32_t minWriteTime; // and shortest write time 38 | uint32_t t; // used to measure total file write time 39 | 40 | int fileHundreds, fileTens, fileOnes; // enumerate succesive files on card and store number in EEPROM 41 | char currentFileName[] = "OBCI_000.csv"; // file name will enumerate 000 to 999 42 | char elapsedTime[] = {"\n%Total time mS:\n"}; // 17 43 | char minTime[] = { "%min Write time uS:\n"}; // 20 44 | char maxTime[] = { "%max Write time uS:\n"}; // 20 45 | char overNum[] = { "%Over:\n"}; // 7 46 | char blockTime[] = { "%block, uS\n"}; // 11 74 chars + 2 32(16) + 2 16(8) = 98 + (n 32x2) up to 24 overruns... 47 | char stopStamp[] = { "%STOP AT\n"}; // used to stamp SD record when stopped by PC 48 | char startStamp[] = { "%START AT\n"}; // used to stamp SD record when started by PC 49 | 50 | 51 | char sdProcessChar(char character) { 52 | ganglion.gotSerial = false; 53 | switch (character) { 54 | case 'A': // 5min 55 | case 'S': // 15min 56 | case 'F': // 30min 57 | case 'G': // 1hr 58 | case 'H': // 2hr 59 | case 'J': // 4hr 60 | case 'K': // 12hr 61 | case 'L': // 24hr 62 | case 'a': // 512 blocks 63 | fileSize = character; 64 | SDfileOpen = setupSDcard(character); 65 | break; 66 | case 'j': // close the file, if it's open 67 | if(SDfileOpen){ 68 | SDfileOpen = closeSDfile(); 69 | } 70 | break; 71 | case 's': 72 | if(SDfileOpen) { 73 | stampSD(DEACTIVATE); 74 | } 75 | break; 76 | case 'b': 77 | if(SDfileOpen) { 78 | stampSD(ACTIVATE); 79 | } 80 | break; 81 | default: 82 | break; 83 | } 84 | return character; 85 | 86 | } 87 | 88 | 89 | boolean setupSDcard(char limit){ 90 | 91 | if(!cardInit){ 92 | if(!card.init(SPI_HALF_SPEED, SD_SS)) { 93 | if(!ganglion.is_running) { 94 | ganglion.loadString("initialization failed. Things to check:",39,false); 95 | ganglion.loadString("* is a card is inserted?",24,true); 96 | } 97 | // card.init(SPI_FULL_SPEED, SD_SS); 98 | } else { 99 | if(!ganglion.is_running) { 100 | ganglion.loadString("Wiring is correct and a card is present.",40,true); 101 | } 102 | cardInit = true; 103 | } 104 | if (!volume.init(card)) { // Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32 105 | if(!ganglion.is_running) { 106 | ganglion.loadString("Could not find FAT16/FAT32 partition. Make sure you've formatted the card",73,true); 107 | } 108 | return fileIsOpen; 109 | } else { 110 | if(!ganglion.is_running) { 111 | ganglion.loadString("Card Initialized",16,true); 112 | } 113 | } 114 | } 115 | 116 | // use limit to determine file size 117 | switch(limit){ 118 | case 'h': 119 | BLOCK_COUNT = 50; break; 120 | case 'a': 121 | BLOCK_COUNT = 256; break; 122 | case 'A': 123 | BLOCK_COUNT = BLOCK_5MIN; break; 124 | case 'S': 125 | BLOCK_COUNT = BLOCK_15MIN; break; 126 | case 'F': 127 | BLOCK_COUNT = BLOCK_30MIN; break; 128 | case 'G': 129 | BLOCK_COUNT = BLOCK_1HR; break; 130 | case 'H': 131 | BLOCK_COUNT = BLOCK_2HR; break; 132 | case 'J': 133 | BLOCK_COUNT = BLOCK_4HR; break; 134 | case 'K': 135 | BLOCK_COUNT = BLOCK_12HR; break; 136 | case 'L': 137 | BLOCK_COUNT = BLOCK_24HR; break; 138 | default: 139 | if(!ganglion.is_running) { 140 | ganglion.loadString("invalid BLOCK count",19,true); 141 | } 142 | return fileIsOpen; 143 | break; 144 | } 145 | 146 | openvol = root.openRoot(volume); 147 | int numFiles = root.ls(0x00); 148 | incrementFileCounter(numFiles+1); 149 | openfile.remove(root, currentFileName); // if the file is over-writing, let it! 150 | if (!openfile.createContiguous(root, currentFileName, BLOCK_COUNT*512UL)) { 151 | if(!ganglion.is_running) { 152 | ganglion.loadString("createContiguous fail",21,true); 153 | } 154 | cardInit = false; 155 | } //else{Serial.print("got contiguous file...");delay(1);} 156 | // get the location of the file's blocks 157 | if (!openfile.contiguousRange(&bgnBlock, &endBlock)) { 158 | if(!ganglion.is_running) { 159 | ganglion.loadString("get contiguousRange fail",24,true); 160 | } 161 | cardInit = false; 162 | } //else{Serial.print("got file range...");delay(1);} 163 | // grab the Cache 164 | pCache = (uint8_t*)volume.cacheClear(); 165 | // tell card to setup for multiple block write with pre-erase 166 | if (!card.erase(bgnBlock, endBlock)){ 167 | if(!ganglion.is_running) { 168 | ganglion.loadString("erase block fail",16,true); 169 | } 170 | cardInit = false; 171 | } //else{Serial.print("erased...");delay(1);} 172 | if (!card.writeStart(bgnBlock, BLOCK_COUNT)){ 173 | if(!ganglion.is_running) { 174 | ganglion.loadString("writeStart fail",15,true); 175 | } 176 | cardInit = false; 177 | } else{ 178 | fileIsOpen = true; 179 | delay(1); 180 | } 181 | digitalWrite(SD_SS,HIGH); // release the spi 182 | // initialize write-time overrun error counter and min/max wirte time benchmarks 183 | overruns = 0; 184 | maxWriteTime = 0; 185 | minWriteTime = 65000; 186 | byteCounter = 0; // counter from 0 - 512 187 | blockCounter = 0; // counter from 0 - BLOCK_COUNT; 188 | if(fileIsOpen == true){ // send corresponding file name to controlling program 189 | ganglion.writingToSD = true; 190 | ganglion.loadString("Writing to file: ",17,false); 191 | for(int i=0; i<12; i++){ 192 | ganglion.loadChar(currentFileName[i],false); 193 | } 194 | ganglion.loadNewLine(); 195 | } 196 | ganglion.prepToSendBytes(); 197 | return fileIsOpen; 198 | } 199 | 200 | boolean closeSDfile(){ 201 | if(fileIsOpen){ 202 | digitalWrite(SD_SS,LOW); // take spi 203 | card.writeStop(); 204 | openfile.close(); 205 | digitalWrite(SD_SS,HIGH); // release the spi 206 | fileIsOpen = false; 207 | ganglion.writingToSD = false; 208 | if(!ganglion.is_running){ // verbosity. this also gets insterted as footer in openFile 209 | ganglion.loadString("Total Elapsed Time: ",20,false); ganglion.loadInt(t,false); ganglion.loadString(" mS",3,true); //delay(10); 210 | ganglion.loadString("Max write time: ",16,false); ganglion.loadInt(maxWriteTime,false); ganglion.loadString(" uS",3,true); //delay(10); 211 | ganglion.loadString("Min write time: ",16,false); ganglion.loadInt(minWriteTime,false); ganglion.loadString(" uS",3,true); //delay(10); 212 | ganglion.loadString("Overruns: ",10,false); ganglion.loadInt(overruns,true); 213 | if (overruns > 0) { 214 | uint8_t n = overruns > OVER_DIM ? OVER_DIM : overruns; 215 | Serial.println("fileBlock\tmicros"); 216 | for (uint8_t i = 0; i < n; i++) { 217 | ganglion.loadInt(over[i].block,false); ganglion.loadChar('\t',false); ganglion.loadInt(over[i].micro,true); 218 | } 219 | } 220 | // ganglion.sendEOT(); 221 | } 222 | }else{ 223 | if(!ganglion.is_running) { 224 | ganglion.loadString("No open file to close",21,true); 225 | // ganglion.sendEOT(); 226 | } 227 | } 228 | ganglion.prepToSendBytes(); 229 | return fileIsOpen; 230 | } 231 | 232 | void writeDataToSDcard(byte sampleNumber){ 233 | boolean addComma = true; 234 | // convert 8 bit sampleCounter into HEX 235 | convertToHex(sampleNumber, 1, addComma); 236 | // convert 24 bit channelData into HEX 237 | for (int currentChannel = 0; currentChannel < 4; currentChannel++){ 238 | convertToHex(ganglion.channelData[currentChannel], 5, addComma); 239 | if(currentChannel == 2){ 240 | addComma = false; 241 | if(ganglion.newAuxData || ganglion.newAccelData) {addComma = true;} // format CSV 242 | } 243 | } 244 | 245 | if(ganglion.newAuxData == true){ 246 | // convert auxData into HEX 247 | for(int currentChannel = 0; currentChannel < 3; currentChannel++){ 248 | convertToHex(ganglion.auxData[currentChannel], 1, addComma); 249 | if(currentChannel == 1) addComma = false; 250 | } 251 | ganglion.newAuxData = false; 252 | }// end of aux data log 253 | else if(ganglion.newAccelData == true){ // if we have accelerometer data to log 254 | // convert 16 bit accelerometer data into HEX 255 | for (int currentChannel = 0; currentChannel < 3; currentChannel++){ 256 | convertToHex(ganglion.axisData[currentChannel], 1, addComma); 257 | if(currentChannel == 1) addComma = false; 258 | } 259 | ganglion.newAccelData = false; 260 | }// end of accelerometer data log 261 | 262 | // add aux data logging... 263 | } 264 | 265 | 266 | void writeCache(){ 267 | if(blockCounter > BLOCK_COUNT) return; 268 | uint32_t tw = micros(); // start block write timer 269 | digitalWrite(SD_SS,LOW); // take spi 270 | if(!card.writeData(pCache)) { 271 | // if (!ganglion.is_running) { 272 | Serial.println("block write fail"); 273 | // ganglion.sendEOT(); 274 | // } 275 | } // write the block 276 | digitalWrite(SD_SS,HIGH); //ganglion.csHigh(SD_SS); // release spi 277 | tw = micros() - tw; // stop block write timer 278 | if (tw > maxWriteTime) maxWriteTime = tw; // check for max write time 279 | if (tw < minWriteTime) minWriteTime = tw; // check for min write time 280 | if (tw > MICROS_PER_BLOCK) { // check for overrun 281 | if (overruns < OVER_DIM) { 282 | over[overruns].block = blockCounter; 283 | over[overruns].micro = tw; 284 | } 285 | overruns++; 286 | } 287 | byteCounter = 0; // reset 512 byte counter for next block 288 | blockCounter++; // increment BLOCK counter 289 | if(blockCounter == BLOCK_COUNT-1){ 290 | t = millis() - t; 291 | if(ganglion.stopRunning() == false){ 292 | ganglion.loadString("SD control: stopRunning",23,true); 293 | } 294 | writeFooter(); 295 | } 296 | if(blockCounter == BLOCK_COUNT){ 297 | SDfileOpen = closeSDfile(); 298 | BLOCK_COUNT = 0; 299 | } // we did it! 300 | } 301 | 302 | 303 | void incrementFileCounter(int numFiles){ 304 | fileOnes = numFiles%10; 305 | fileTens = (numFiles/10)%10; 306 | fileHundreds = (numFiles/100)%10; 307 | currentFileName[5] = fileHundreds + '0'; 308 | currentFileName[6] = fileTens + '0'; 309 | currentFileName[7] = fileOnes + '0'; 310 | } 311 | 312 | void stampSD(boolean state){ 313 | unsigned long time = millis(); 314 | if(state){ 315 | for(int i=0; i<10; i++){ 316 | pCache[byteCounter] = startStamp[i]; 317 | byteCounter++; 318 | if(byteCounter == 512){ 319 | writeCache(); 320 | } 321 | } 322 | } else { 323 | for(int i=0; i<9; i++){ 324 | pCache[byteCounter] = stopStamp[i]; 325 | byteCounter++; 326 | if(byteCounter == 512){ 327 | writeCache(); 328 | } 329 | } 330 | } 331 | convertToHex(time, 7, false); 332 | } 333 | 334 | void writeFooter(){ 335 | for(int i=0; i<17; i++){ 336 | pCache[byteCounter] = elapsedTime[i]; 337 | byteCounter++; 338 | } 339 | convertToHex(t, 7, false); 340 | 341 | for(int i=0; i<20; i++){ 342 | pCache[byteCounter] = minTime[i]; 343 | byteCounter++; 344 | } 345 | convertToHex(minWriteTime, 7, false); 346 | 347 | for(int i=0; i<20; i++){ 348 | pCache[byteCounter] = maxTime[i]; 349 | byteCounter++; 350 | } 351 | convertToHex(maxWriteTime, 7, false); 352 | 353 | for(int i=0; i<7; i++){ 354 | pCache[byteCounter] = overNum[i]; 355 | byteCounter++; 356 | } 357 | convertToHex(overruns, 7, false); 358 | for(int i=0; i<11; i++){ 359 | pCache[byteCounter] = blockTime[i]; 360 | byteCounter++; 361 | } 362 | if (overruns) { 363 | uint8_t n = overruns > OVER_DIM ? OVER_DIM : overruns; 364 | for (uint8_t i = 0; i < n; i++) { 365 | convertToHex(over[i].block, 7, true); 366 | convertToHex(over[i].micro, 7, false); 367 | } 368 | } 369 | for(int i=byteCounter; i<512; i++){ 370 | pCache[i] = NULL; 371 | } 372 | writeCache(); 373 | } 374 | 375 | // CONVERT RAW BYTE DATA TO HEX FOR SD STORAGE 376 | void convertToHex(long rawData, int numNibbles, boolean useComma){ 377 | 378 | for (int currentNibble = numNibbles; currentNibble >= 0; currentNibble--){ 379 | byte nibble = (rawData >> currentNibble*4) & 0x0F; 380 | if (nibble > 9){ 381 | nibble += 55; // convert to ASCII A-F 382 | } 383 | else{ 384 | nibble += 48; // convert to ASCII 0-9 385 | } 386 | pCache[byteCounter] = nibble; 387 | byteCounter++; 388 | if(byteCounter == 512){ 389 | writeCache(); 390 | } 391 | } 392 | if(useComma == true){ 393 | pCache[byteCounter] = ','; 394 | }else{ 395 | pCache[byteCounter] = '\n'; 396 | } 397 | byteCounter++; 398 | if(byteCounter == 512){ 399 | writeCache(); 400 | } 401 | }// end of byteToHex converter 402 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | ganglion KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | initialize KEYWORD2 15 | processData KEYWORD2 16 | blinkLED KEYWORD2 17 | eventSerial KEYWORD2 18 | testImpedance KEYWORD2 19 | MCP_ISR KEYWORD2 20 | enable_LIS2DH KEYWORD2 21 | useAccel KEYWORD2 22 | LED_state KEYWORD2 23 | LED KEYWORD2 24 | getGains KEYWORD2 25 | 26 | ####################################### 27 | # Constants (LITERAL1) 28 | ####################################### 29 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=OpenBCI_Ganglion_Library 2 | version=3.0.2 3 | author=Joel Murphy, Conor Russomanno, Leif Percifield, AJ Keller 4 | maintainer=Ioannis Smanis, Richard Waltman, Tharun Iyer 5 | sentence=The library for using and collecting data from the OpenBCI Ganglion boards. 6 | paragraph=This library is designed to be used with the Simblee. Use the DefaulGanglion.ino for the firmware that ships with the Ganglion. See the examples for stripped down versions of the board. See the learning pages at docs.openbci.com for more info! 7 | category=Device Control 8 | url=https://github.com/OpenBCI/OpenBCI_Ganglion_Library 9 | architectures=Simblee 10 | --------------------------------------------------------------------------------