├── .gitignore ├── LICENSE.md ├── ble-starter └── ble-starter.ino ├── ble-to-din └── ble-to-din.ino ├── din-to-ble └── din-to-ble.ino ├── documentation └── example Tsunami loop packet.txt ├── midi-lib-starter └── midi-lib-starter.ino ├── readme.md ├── standard-midi-ble └── standard-midi-ble.ino └── test-programs ├── ble-test └── ble-test.ino ├── midi-test └── midi-test.ino ├── parserUnitTest └── parserUnitTest.ino └── serial-test └── serial-test.ino /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## SparkFun Useful stuff 3 | ################# 4 | 5 | ## AVR Development 6 | *.eep 7 | *.elf 8 | *.lst 9 | *.lss 10 | *.sym 11 | *.d 12 | *.o 13 | *.srec 14 | *.map 15 | 16 | ## Notepad++ backup files 17 | *.bak 18 | 19 | ## BOM files 20 | *bom* 21 | 22 | ################# 23 | ## Eclipse 24 | ################# 25 | 26 | *.pydevproject 27 | .project 28 | .metadata 29 | bin/ 30 | tmp/ 31 | *.tmp 32 | *.bak 33 | *.swp 34 | *~.nib 35 | local.properties 36 | .classpath 37 | .settings/ 38 | .loadpath 39 | 40 | # External tool builders 41 | .externalToolBuilders/ 42 | 43 | # Locally stored "Eclipse launch configurations" 44 | *.launch 45 | 46 | # CDT-specific 47 | .cproject 48 | 49 | # PDT-specific 50 | .buildpath 51 | 52 | 53 | ############# 54 | ## Eagle 55 | ############# 56 | 57 | # Ignore the board and schematic backup files and lock files 58 | *.b#? 59 | *.s#? 60 | *.l#? 61 | *.lck 62 | 63 | 64 | ################# 65 | ## Visual Studio 66 | ################# 67 | 68 | ## Ignore Visual Studio temporary files, build results, and 69 | ## files generated by popular Visual Studio add-ons. 70 | 71 | # User-specific files 72 | *.suo 73 | *.user 74 | *.sln.docstates 75 | 76 | # Build results 77 | [Dd]ebug/ 78 | [Rr]elease/ 79 | *_i.c 80 | *_p.c 81 | *.ilk 82 | *.meta 83 | *.obj 84 | *.pch 85 | *.pdb 86 | *.pgc 87 | *.pgd 88 | *.rsp 89 | *.sbr 90 | *.tlb 91 | *.tli 92 | *.tlh 93 | *.tmp 94 | *.vspscc 95 | .builds 96 | *.dotCover 97 | 98 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 99 | #packages/ 100 | 101 | # Visual C++ cache files 102 | ipch/ 103 | *.aps 104 | *.ncb 105 | *.opensdf 106 | *.sdf 107 | 108 | # Visual Studio profiler 109 | *.psess 110 | *.vsp 111 | 112 | # ReSharper is a .NET coding add-in 113 | _ReSharper* 114 | 115 | # Installshield output folder 116 | [Ee]xpress 117 | 118 | # DocProject is a documentation generator add-in 119 | DocProject/buildhelp/ 120 | DocProject/Help/*.HxT 121 | DocProject/Help/*.HxC 122 | DocProject/Help/*.hhc 123 | DocProject/Help/*.hhk 124 | DocProject/Help/*.hhp 125 | DocProject/Help/Html2 126 | DocProject/Help/html 127 | 128 | # Click-Once directory 129 | publish 130 | 131 | # Others 132 | [Bb]in 133 | [Oo]bj 134 | sql 135 | TestResults 136 | *.Cache 137 | ClientBin 138 | stylecop.* 139 | ~$* 140 | *.dbmdl 141 | Generated_Code #added for RIA/Silverlight projects 142 | 143 | # Backup & report files from converting an old project file to a newer 144 | # Visual Studio version. Backup files are not needed, because we have git ;-) 145 | _UpgradeReport_Files/ 146 | Backup*/ 147 | UpgradeLog*.XML 148 | 149 | 150 | ############ 151 | ## Windows 152 | ############ 153 | 154 | # Windows image file caches 155 | Thumbs.db 156 | 157 | # Folder config file 158 | Desktop.ini 159 | 160 | 161 | ############# 162 | ## Mac OS 163 | ############# 164 | 165 | .DS_Store 166 | 167 | 168 | ############# 169 | ## Linux 170 | ############# 171 | 172 | # backup files (*.bak on Win) 173 | *~ 174 | 175 | 176 | ############# 177 | ## Python 178 | ############# 179 | 180 | *.py[co] 181 | 182 | # Packages 183 | *.egg 184 | *.egg-info 185 | dist 186 | build 187 | eggs 188 | parts 189 | bin 190 | var 191 | sdist 192 | develop-eggs 193 | .installed.cfg 194 | 195 | # Installer logs 196 | pip-log.txt 197 | 198 | # Unit test / coverage reports 199 | .coverage 200 | .tox 201 | 202 | #Translations 203 | *.mo 204 | 205 | #Mr Developer 206 | .mr.developer.cfg 207 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | SparkFun License Information 2 | ============================ 3 | 4 | SparkFun uses two different licenses for our files — one for hardware and one for code. 5 | 6 | Hardware 7 | --------- 8 | 9 | **SparkFun hardware is released under [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/).** 10 | 11 | Note: This is a human-readable summary of (and not a substitute for) the [license](http://creativecommons.org/licenses/by-sa/4.0/legalcode). 12 | 13 | You are free to: 14 | 15 | Share — copy and redistribute the material in any medium or format 16 | Adapt — remix, transform, and build upon the material 17 | for any purpose, even commercially. 18 | The licensor cannot revoke these freedoms as long as you follow the license terms. 19 | Under the following terms: 20 | 21 | Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 22 | ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. 23 | No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 24 | Notices: 25 | 26 | You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. 27 | No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. 28 | 29 | 30 | Code 31 | -------- 32 | 33 | **SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT).** 34 | 35 | The MIT License (MIT) 36 | 37 | Copyright (c) 2016 SparkFun Electronics 38 | 39 | Permission is hereby granted, free of charge, to any person obtaining a copy 40 | of this software and associated documentation files (the "Software"), to deal 41 | in the Software without restriction, including without limitation the rights 42 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 43 | copies of the Software, and to permit persons to whom the Software is 44 | furnished to do so, subject to the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be included in all 47 | copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 50 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 51 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 52 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 53 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 54 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 55 | SOFTWARE. 56 | -------------------------------------------------------------------------------- /ble-starter/ble-starter.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define BLUE_STAT_PIN 7 // LED on pin 7 4 | #define RED_STAT_PIN 11 // LED on pin 11 5 | #define GREEN_STAT_PIN 12 // LED on pin 12 6 | #define BTN_PIN 6 // User button 7 | 8 | // create peripheral instance, see pinouts above 9 | //const char * localName = "nRF52832 MIDI"; 10 | BLEPeripheral blePeripheral; 11 | BLEService service("03B80E5A-EDE8-4B33-A751-6CE34EC4C700"); 12 | BLECharacteristic characteristic("7772E5DB-3868-4112-A1A9-F2669D106BF3", BLERead | BLEWriteWithoutResponse | BLENotify, 20 ); 13 | BLEDescriptor descriptor = BLEDescriptor("2902", 0); 14 | 15 | void setup() { 16 | Serial.begin(115200); 17 | delay(3000); 18 | Serial.println("Program Started"); 19 | 20 | //Setup diag leds 21 | pinMode(BLUE_STAT_PIN, OUTPUT); 22 | pinMode(RED_STAT_PIN, OUTPUT); 23 | pinMode(GREEN_STAT_PIN, OUTPUT); 24 | digitalWrite(BLUE_STAT_PIN, 1); 25 | digitalWrite(RED_STAT_PIN, 1); 26 | digitalWrite(GREEN_STAT_PIN, 1); 27 | 28 | //Setup nRF52832 user button 29 | pinMode(BTN_PIN, INPUT_PULLUP); 30 | 31 | setupBLE(); 32 | 33 | } 34 | 35 | 36 | void loop() 37 | { 38 | BLECentral central = blePeripheral.central(); 39 | if (central) { 40 | while (central.connected()) { 41 | digitalWrite(GREEN_STAT_PIN, 0); 42 | //Check if data exists coming in from BLE 43 | if (characteristic.written()) { 44 | digitalWrite(RED_STAT_PIN, 0); 45 | 46 | //Receive the written packet and parse it out here. 47 | Serial.print("Rx size: "); 48 | Serial.println(characteristic.valueLength()); 49 | uint8_t * buffer = (uint8_t*)characteristic.value(); 50 | Serial.print("0x"); 51 | for( int i = 0; i < characteristic.valueLength(); i++ ){ 52 | if( buffer[i] < 0x10 ) Serial.print("0"); 53 | Serial.print( buffer[i], HEX ); 54 | } 55 | Serial.println(); 56 | 57 | digitalWrite(RED_STAT_PIN, 1); 58 | } 59 | } 60 | 61 | } 62 | digitalWrite(BLUE_STAT_PIN, 1); 63 | digitalWrite(GREEN_STAT_PIN, 1); 64 | delay(500); 65 | } 66 | 67 | void setupBLE() 68 | { 69 | blePeripheral.setLocalName("BLE MIDI Starter"); //local name sometimes used by central 70 | blePeripheral.setDeviceName("BLE MIDI Starter"); //device name sometimes used by central 71 | //blePeripheral.setApperance(0x0000); //default is 0x0000, what should this be? 72 | blePeripheral.setAdvertisedServiceUuid(service.uuid()); //Advertise MIDI UUID 73 | 74 | // add attributes (services, characteristics, descriptors) to peripheral 75 | blePeripheral.addAttribute(service); 76 | blePeripheral.addAttribute(characteristic); 77 | blePeripheral.addAttribute(descriptor); 78 | 79 | // set initial value 80 | characteristic.setValue(0); 81 | 82 | // set event handlers - Alternate ways of checking for BLE activity 83 | //characteristic.setEventHandler(BLEWritten, BLEWrittenCallback); 84 | //characteristic.setEventHandler(BLESubscribed, BLESubscribedCallback); 85 | //characteristic.setEventHandler(BLEUnsubscribed, BLEUnsubscribedCallback); 86 | 87 | blePeripheral.begin(); 88 | } 89 | -------------------------------------------------------------------------------- /ble-to-din/ble-to-din.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "nrf52.h" 3 | #include 4 | 5 | #define BLUE_STAT_PIN 7 // LED on pin 7 6 | #define RED_STAT_PIN 11 // LED on pin 11 7 | #define GREEN_STAT_PIN 12 // LED on pin 12 8 | #define BTN_PIN 6 // User button 9 | 10 | // create peripheral instance, see pinouts above 11 | //const char * localName = "nRF52832 MIDI"; 12 | BLEPeripheral blePeripheral; 13 | BLEService service("03B80E5A-EDE8-4B33-A751-6CE34EC4C700"); 14 | BLECharacteristic characteristic("7772E5DB-3868-4112-A1A9-F2669D106BF3", BLERead | BLEWriteWithoutResponse | BLENotify, 20 ); 15 | BLEDescriptor descriptor = BLEDescriptor("2902", 0); 16 | 17 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI); 18 | 19 | void setup() { 20 | delay(1000); 21 | 22 | //Setup diag leds 23 | pinMode(BLUE_STAT_PIN, OUTPUT); 24 | pinMode(RED_STAT_PIN, OUTPUT); 25 | pinMode(GREEN_STAT_PIN, OUTPUT); 26 | digitalWrite(BLUE_STAT_PIN, 1); 27 | digitalWrite(RED_STAT_PIN, 1); 28 | digitalWrite(GREEN_STAT_PIN, 1); 29 | 30 | //Setup nRF52832 user button 31 | pinMode(BTN_PIN, INPUT_PULLUP); 32 | 33 | setupBLE(); 34 | 35 | // Initiate MIDI communications, listen to all channels 36 | MIDI.begin(MIDI_CHANNEL_OMNI); 37 | MIDI.turnThruOff(); 38 | 39 | // The nRF52832 converts baud settings to the discrete standard rates. 40 | // Use the nrf52.h names to write a custom value, 0x7FFC80 after beginning midi 41 | NRF_UARTE_Type * myUart; 42 | myUart = (NRF_UARTE_Type *)NRF_UART0_BASE; 43 | myUart->BAUDRATE = 0x7FFC80; 44 | 45 | //Write data to the serial output pin to make sure the serial output is working. 46 | //Sometimes serial output only allows 1 byte out then hangs. Resetting the 47 | //nRF52832 resolves the issue 48 | digitalWrite(RED_STAT_PIN, 0); 49 | MIDI.sendNoteOn(42, 66, 1); 50 | delay(500); 51 | MIDI.sendNoteOff(42, 66, 1); 52 | digitalWrite(RED_STAT_PIN, 1); 53 | 54 | } 55 | 56 | void loop() 57 | { 58 | BLECentral central = blePeripheral.central(); 59 | if(digitalRead(BTN_PIN) == 0){ 60 | digitalWrite(GREEN_STAT_PIN, 0); 61 | MIDI.sendNoteOff(0x45, 80, 1); 62 | delay(100); 63 | digitalWrite(GREEN_STAT_PIN, 1); 64 | } 65 | if (central) { 66 | //Prep the timestamp 67 | msOffset = millis(); 68 | 69 | digitalWrite(BLUE_STAT_PIN, 0); 70 | // central connected to peripheral 71 | 72 | while (central.connected()) { 73 | digitalWrite(GREEN_STAT_PIN, 0); 74 | //If connected, send midi data by the button here 75 | if(digitalRead(BTN_PIN) == 0){ 76 | digitalWrite(GREEN_STAT_PIN, 0); 77 | MIDI.sendNoteOn(0x45, 80, 1); 78 | delay(100); 79 | MIDI.sendNoteOff(0x45, 80, 1); 80 | digitalWrite(GREEN_STAT_PIN, 1); 81 | } 82 | //Check if data exists coming in from BLE 83 | if (characteristic.written()) { 84 | digitalWrite(RED_STAT_PIN, 0); 85 | processPacket(); 86 | digitalWrite(RED_STAT_PIN, 1); 87 | } 88 | } 89 | } 90 | //No longer connected. Turn off the LEDs. 91 | digitalWrite(BLUE_STAT_PIN, 1); 92 | digitalWrite(GREEN_STAT_PIN, 1); 93 | //Delay to show off state for a bit 94 | delay(100); 95 | } 96 | 97 | //This function decodes the BLE characteristics and calls transmitMIDIonDIN 98 | //if the packet contains sendable MIDI data. 99 | void processPacket() 100 | { 101 | //Receive the written packet and parse it out here. 102 | uint8_t * buffer = (uint8_t*)characteristic.value(); 103 | uint8_t bufferSize = characteristic.valueLength(); 104 | 105 | //Pointers used to search through payload. 106 | uint8_t lPtr = 0; 107 | uint8_t rPtr = 0; 108 | //lastStatus used to capture runningStatus 109 | uint8_t lastStatus; 110 | //Decode first packet -- SHALL be "Full MIDI message" 111 | lPtr = 2; //Start at first MIDI status -- SHALL be "MIDI status" 112 | //While statement contains incrementing pointers and breaks when buffer size exceeded. 113 | while(1){ 114 | lastStatus = buffer[lPtr]; 115 | if( (buffer[lPtr] < 0x80) ){ 116 | //Status message not present, bail 117 | return; 118 | } 119 | //Point to next non-data byte 120 | rPtr = lPtr; 121 | while( (buffer[rPtr + 1] < 0x80)&&(rPtr < (bufferSize - 1)) ){ 122 | rPtr++; 123 | } 124 | //look at l and r pointers and decode by size. 125 | if( rPtr - lPtr < 1 ){ 126 | //Time code or system 127 | transmitMIDIonDIN( lastStatus, 0, 0 ); 128 | } else if( rPtr - lPtr < 2 ) { 129 | transmitMIDIonDIN( lastStatus, buffer[lPtr + 1], 0 ); 130 | } else if( rPtr - lPtr < 3 ) { 131 | transmitMIDIonDIN( lastStatus, buffer[lPtr + 1], buffer[lPtr + 2] ); 132 | } else { 133 | //Too much data 134 | //If not System Common or System Real-Time, send it as running status 135 | switch( buffer[lPtr] & 0xF0 ) 136 | { 137 | case 0x80: 138 | case 0x90: 139 | case 0xA0: 140 | case 0xB0: 141 | case 0xE0: 142 | for(int i = lPtr; i < rPtr; i = i + 2){ 143 | transmitMIDIonDIN( lastStatus, buffer[i + 1], buffer[i + 2] ); 144 | } 145 | break; 146 | case 0xC0: 147 | case 0xD0: 148 | for(int i = lPtr; i < rPtr; i = i + 1){ 149 | transmitMIDIonDIN( lastStatus, buffer[i + 1], 0 ); 150 | } 151 | break; 152 | default: 153 | break; 154 | } 155 | } 156 | //Point to next status 157 | lPtr = rPtr + 2; 158 | if(lPtr >= bufferSize){ 159 | //end of packet 160 | return; 161 | } 162 | } 163 | } 164 | 165 | //This function takes a midi packet as input and calls the appropriate library 166 | //function to transmit the data. It's a little redundant because the library 167 | //reforms midi data from the calls and sends it out the serial port. 168 | // 169 | //Ideally, the MIDI BLE object would feed a MIDI library object as a serial 170 | //object removing all of this code. 171 | // 172 | //A benefit of this redundant code is that it's easy to filter messages, and 173 | //exposes how the library works. 174 | void transmitMIDIonDIN( uint8_t status, uint8_t data1, uint8_t data2 ) 175 | { 176 | uint8_t channel = status & 0x0F; 177 | channel++; 178 | uint8_t command = (status & 0xF0) >> 4; 179 | switch(command) 180 | { 181 | case 0x08: //Note off 182 | MIDI.sendNoteOff(data1, data2, channel); 183 | break; 184 | case 0x09: //Note on 185 | MIDI.sendNoteOn(data1, data2, channel); 186 | break; 187 | case 0x0A: //Polyphonic Pressure 188 | MIDI.sendAfterTouch(data1, data2, channel); 189 | break; 190 | case 0x0B: //Control Change 191 | MIDI.sendControlChange(data1, data2, channel); 192 | break; 193 | case 0x0C: //Program Change 194 | MIDI.sendProgramChange(data1, channel); 195 | break; 196 | case 0x0D: //Channel Pressure 197 | MIDI.sendAfterTouch(data2, channel); 198 | break; 199 | case 0x0E: //Pitch Bend 200 | MIDI.send(midi::PitchBend, data1, data2, channel); 201 | break; 202 | case 0x0F: //System 203 | switch(status) 204 | { 205 | case 0xF1: //MTC Q frame 206 | MIDI.sendTimeCodeQuarterFrame( data1 ); 207 | break; 208 | case 0xF2: //Song position 209 | MIDI.sendSongPosition(( (uint16_t)(data1 & 0x7F) << 7) | (data2 & 0x7F)); 210 | break; 211 | case 0xF3: //Song select 212 | MIDI.sendSongSelect( data1 ); 213 | break; 214 | case 0xF6: //Tune request 215 | MIDI.sendTuneRequest(); 216 | break; 217 | case 0xF8: //Timing Clock 218 | case 0xFA: //Start 219 | case 0xFB: //Continue 220 | case 0xFC: //Stop 221 | case 0xFE: //Active Sensing 222 | case 0xFF: //Reset 223 | MIDI.sendRealTime( (midi::MidiType)status ); 224 | break; 225 | default: 226 | break; 227 | } 228 | break; 229 | default: 230 | break; 231 | } 232 | } 233 | 234 | void setupBLE() 235 | { 236 | blePeripheral.setLocalName("BLE to DIN"); //local name sometimes used by central 237 | blePeripheral.setDeviceName("BLE to DIN"); //device name sometimes used by central 238 | //blePeripheral.setApperance(0x0000); //default is 0x0000, what should this be? 239 | blePeripheral.setAdvertisedServiceUuid(service.uuid()); //Advertise MIDI UUID 240 | 241 | // add attributes (services, characteristics, descriptors) to peripheral 242 | blePeripheral.addAttribute(service); 243 | blePeripheral.addAttribute(characteristic); 244 | blePeripheral.addAttribute(descriptor); 245 | 246 | // set initial value 247 | characteristic.setValue(0); 248 | 249 | blePeripheral.begin(); 250 | } 251 | -------------------------------------------------------------------------------- /din-to-ble/din-to-ble.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "nrf52.h" 3 | #include 4 | 5 | #define BLUE_STAT_PIN 7 // LED on pin 7 6 | #define RED_STAT_PIN 11 // LED on pin 11 7 | #define GREEN_STAT_PIN 12 // LED on pin 12 8 | #define BTN_PIN 6 // User button 9 | 10 | // create peripheral instance, see pinouts above 11 | //const char * localName = "nRF52832 MIDI"; 12 | BLEPeripheral blePeripheral; 13 | BLEService service("03B80E5A-EDE8-4B33-A751-6CE34EC4C700"); 14 | BLECharacteristic characteristic("7772E5DB-3868-4112-A1A9-F2669D106BF3", BLERead | BLEWriteWithoutResponse | BLENotify, 20 ); 15 | BLEDescriptor descriptor = BLEDescriptor("2902", 0); 16 | 17 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI); 18 | 19 | void setup() { 20 | delay(1000); 21 | 22 | //Setup diag leds 23 | pinMode(BLUE_STAT_PIN, OUTPUT); 24 | pinMode(RED_STAT_PIN, OUTPUT); 25 | pinMode(GREEN_STAT_PIN, OUTPUT); 26 | digitalWrite(BLUE_STAT_PIN, 1); 27 | digitalWrite(RED_STAT_PIN, 1); 28 | digitalWrite(GREEN_STAT_PIN, 1); 29 | 30 | //Setup nRF52832 user button 31 | pinMode(BTN_PIN, INPUT_PULLUP); 32 | 33 | setupBLE(); 34 | 35 | // Initiate MIDI communications, listen to all channels 36 | MIDI.begin(MIDI_CHANNEL_OMNI); 37 | MIDI.turnThruOff(); 38 | 39 | // The nRF52832 converts baud settings to the discrete standard rates. 40 | // Use the nrf52.h names to write a custom value, 0x7FFC80 after beginning midi 41 | NRF_UARTE_Type * myUart; 42 | myUart = (NRF_UARTE_Type *)NRF_UART0_BASE; 43 | myUart->BAUDRATE = 0x7FFC80; 44 | 45 | //Write data to the serial output pin to make sure the serial output is working. 46 | //Sometimes serial output only allows 1 byte out then hangs. Resetting the 47 | //nRF52832 resolves the issue 48 | digitalWrite(RED_STAT_PIN, 0); 49 | MIDI.sendNoteOn(42, 66, 1); 50 | delay(500); 51 | MIDI.sendNoteOff(42, 66, 1); 52 | digitalWrite(RED_STAT_PIN, 1); 53 | 54 | } 55 | 56 | void loop() 57 | { 58 | BLECentral central = blePeripheral.central(); 59 | if(digitalRead(BTN_PIN) == 0){ 60 | digitalWrite(GREEN_STAT_PIN, 0); 61 | MIDI.sendNoteOff(0x45, 80, 1); 62 | delay(100); 63 | digitalWrite(GREEN_STAT_PIN, 1); 64 | } 65 | if (central) { 66 | //Prep the timestamp 67 | msOffset = millis(); 68 | 69 | digitalWrite(BLUE_STAT_PIN, 0); 70 | // central connected to peripheral 71 | 72 | while (central.connected()) { 73 | digitalWrite(GREEN_STAT_PIN, 0); 74 | //If connected, send midi data by the button here 75 | if(digitalRead(BTN_PIN) == 0){ 76 | digitalWrite(GREEN_STAT_PIN, 0); 77 | MIDI.sendNoteOn(0x45, 80, 1); 78 | delay(100); 79 | MIDI.sendNoteOff(0x45, 80, 1); 80 | digitalWrite(GREEN_STAT_PIN, 1); 81 | } 82 | //Check if data exists coming in from BLE 83 | if (characteristic.written()) { 84 | digitalWrite(RED_STAT_PIN, 0); 85 | //hang to give the LED time to show (not necessary if routines are here) 86 | delay(10); 87 | digitalWrite(RED_STAT_PIN, 1); 88 | } 89 | //Check if data exists coming in from the serial port 90 | parseMIDIonDIN(); 91 | } 92 | } 93 | //No longer connected. Turn off the LEDs. 94 | digitalWrite(BLUE_STAT_PIN, 1); 95 | digitalWrite(GREEN_STAT_PIN, 1); 96 | //Delay to show off state for a bit 97 | delay(100); 98 | } 99 | 100 | //This function is called to check if MIDI data has come in through the serial port. If found, it builds a characteristic buffer and sends it over BLE. 101 | void parseMIDIonDIN() 102 | { 103 | //Check MIDI object for new data. 104 | if ( MIDI.read()) 105 | { 106 | digitalWrite(RED_STAT_PIN, 0); 107 | 108 | uint8_t msgBuf[5]; //Outgoing buffer 109 | 110 | //Calculate timestamp. 111 | uint16_t currentTimeStamp = millis() & 0x01FFF; 112 | 113 | msgBuf[0] = ((currentTimeStamp >> 7) & 0x3F) | 0x80; //6 bits plus MSB 114 | msgBuf[1] = (currentTimeStamp & 0x7F) | 0x80; //7 bits plus MSB 115 | 116 | uint8_t statusByte = ((uint8_t)MIDI.getType() | ((MIDI.getChannel() - 1) & 0x0f)); 117 | 118 | switch (MIDI.getType()) 119 | { 120 | //2 Byte Channel Messages 121 | case midi::NoteOff : 122 | case midi::NoteOn : 123 | case midi::AfterTouchPoly : 124 | case midi::ControlChange : 125 | case midi::PitchBend : 126 | msgBuf[2] = statusByte; 127 | msgBuf[3] = MIDI.getData1(); 128 | msgBuf[4] = MIDI.getData2(); 129 | characteristic.setValue(msgBuf, 5); 130 | break; 131 | //1 Byte Channel Messages 132 | case midi::ProgramChange : 133 | case midi::AfterTouchChannel : 134 | msgBuf[2] = statusByte; 135 | msgBuf[3] = MIDI.getData1(); 136 | characteristic.setValue(msgBuf, 4); 137 | break; 138 | //System Common Messages 139 | case midi::TimeCodeQuarterFrame : 140 | msgBuf[2] = 0xF1; 141 | msgBuf[3] = MIDI.getData1(); 142 | characteristic.setValue(msgBuf, 4); 143 | break; 144 | case midi::SongPosition : 145 | msgBuf[2] = 0xF2; 146 | msgBuf[3] = MIDI.getData1(); 147 | msgBuf[4] = MIDI.getData2(); 148 | characteristic.setValue(msgBuf, 5); 149 | break; 150 | case midi::SongSelect : 151 | msgBuf[2] = 0xF3; 152 | msgBuf[3] = MIDI.getData1(); 153 | characteristic.setValue(msgBuf, 4); 154 | break; 155 | case midi::TuneRequest : 156 | msgBuf[2] = 0xF6; 157 | characteristic.setValue(msgBuf, 3); 158 | break; 159 | //Real-time Messages 160 | case midi::Clock : 161 | msgBuf[2] = 0xF8; 162 | characteristic.setValue(msgBuf, 3); 163 | break; 164 | case midi::Start : 165 | msgBuf[2] = 0xFA; 166 | characteristic.setValue(msgBuf, 3); 167 | break; 168 | case midi::Continue : 169 | msgBuf[2] = 0xFB; 170 | characteristic.setValue(msgBuf, 3); 171 | break; 172 | case midi::Stop : 173 | msgBuf[2] = 0xFC; 174 | characteristic.setValue(msgBuf, 3); 175 | break; 176 | case midi::ActiveSensing : 177 | msgBuf[2] = 0xFE; 178 | characteristic.setValue(msgBuf, 3); 179 | break; 180 | case midi::SystemReset : 181 | msgBuf[2] = 0xFF; 182 | characteristic.setValue(msgBuf, 3); 183 | break; 184 | //SysEx 185 | case midi::SystemExclusive : 186 | // { 187 | // // Sysex is special. 188 | // // could contain very long data... 189 | // // the data bytes form the length of the message, 190 | // // with data contained in array member 191 | // uint16_t length; 192 | // const uint8_t * data_p; 193 | // 194 | // Serial.print("SysEx, chan: "); 195 | // Serial.print(MIDI.getChannel()); 196 | // length = MIDI.getSysExArrayLength(); 197 | // 198 | // Serial.print(" Data: 0x"); 199 | // data_p = MIDI.getSysExArray(); 200 | // for (uint16_t idx = 0; idx < length; idx++) 201 | // { 202 | // Serial.print(data_p[idx], HEX); 203 | // Serial.print(" 0x"); 204 | // } 205 | // Serial.println(); 206 | // } 207 | break; 208 | case midi::InvalidType : 209 | default: 210 | break; 211 | } 212 | digitalWrite(RED_STAT_PIN, 1); 213 | } 214 | 215 | } 216 | 217 | void setupBLE() 218 | { 219 | blePeripheral.setLocalName("DIN to BLE"); //local name sometimes used by central 220 | blePeripheral.setDeviceName("DIN to BLE"); //device name sometimes used by central 221 | //blePeripheral.setApperance(0x0000); //default is 0x0000, what should this be? 222 | blePeripheral.setAdvertisedServiceUuid(service.uuid()); //Advertise MIDI UUID 223 | 224 | // add attributes (services, characteristics, descriptors) to peripheral 225 | blePeripheral.addAttribute(service); 226 | blePeripheral.addAttribute(characteristic); 227 | blePeripheral.addAttribute(descriptor); 228 | 229 | // set initial value 230 | characteristic.setValue(0); 231 | 232 | blePeripheral.begin(); 233 | } 234 | -------------------------------------------------------------------------------- /documentation/example Tsunami loop packet.txt: -------------------------------------------------------------------------------- 1 | Rx size: 13 2 | 0x9BE3B06201E3B00640E3B02600 3 | Rx size: 13 4 | 0xB4F5B06201F5B00603F5B0263F 5 | Rx size: 13 6 | 0xBB90B0620190B0060390B0264B 7 | Rx size: 13 8 | 0xBBAFB06201AFB00603AFB02664 9 | Rx size: 17 10 | 0xBBC4B06201C4B00603C4B02671CFB06201 11 | Rx size: 9 12 | 0xBBCFB00604CFB0260A 13 | Rx size: 13 14 | 0xBBDCB06201DCB00604DCB02632 15 | Rx size: 13 16 | 0xBBECB06201ECB00604ECB0265A 17 | Rx size: 13 18 | 0xBBFBB06201FBB00605FBB02602 19 | Rx size: 13 20 | 0xBC8BB062018BB006058BB0262A 21 | Rx size: 13 22 | 0xBC9BB062019BB006059BB02652 23 | Rx size: 13 24 | 0xBCABB06201ABB00605ABB0266B 25 | Rx size: 13 26 | 0xBCBBB06201BBB00606BBB02613 27 | Rx size: 13 28 | 0xBCCBB06201CBB00606CBB02620 29 | Rx size: 13 30 | 0xBCDBB06201DBB00606DBB02639 31 | Rx size: 13 32 | 0xBCECB06201ECB00606ECB02652 33 | Rx size: 13 34 | 0xBD8CB062018CB006068CB0265F 35 | Rx size: 13 36 | 0xBDBAB06201BAB00606BAB0266B 37 | Rx size: 13 38 | 0xBDCEB06201CEB00607CEB02604 39 | Rx size: 13 40 | 0xBDE9B06201E9B00607E9B02611 41 | 42 | Decode: 43 | 0xBDE9B06201E9B00607E9B02611 44 | 0xBD: timestampHigh 45 | 46 | 0xE9: timestampLow 47 | 0xB0: MIDI status 48 | 0x62: data 49 | 0x01: data 50 | 51 | 0xE9: timestampLow 52 | 0xB0: MIDI status 53 | 0x06: data 1 54 | 0x07: data 2 55 | 56 | 0xE9: timestampLow 57 | 0xB0: MIDI status 58 | 0x26: data 1 59 | 0x11: data 2 60 | 61 | A full midi message can be: 62 | messages | bytes 63 | 1 | 5 64 | 2 | 9 65 | 3 | 13 66 | 4 | 17 67 | ... 68 | 69 | a running status midi message can be 70 | *1 | 5 (is full status) 71 | 2 | 7 72 | 3 | 9 73 | 4 | 11 74 | 5 | 13 75 | ... 76 | 77 | So packet length does not indicate type. Packets are decoded by MSB (all data lacks msb) 78 | 79 | -------------------------------------------------------------------------------- /midi-lib-starter/midi-lib-starter.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "nrf52.h" 3 | #include 4 | 5 | #define BLUE_STAT_PIN 7 // LED on pin 7 6 | #define RED_STAT_PIN 11 // LED on pin 11 7 | #define GREEN_STAT_PIN 12 // LED on pin 12 8 | #define BTN_PIN 6 // User button 9 | 10 | // create peripheral instance, see pinouts above 11 | //const char * localName = "nRF52832 MIDI"; 12 | BLEPeripheral blePeripheral; 13 | BLEService service("03B80E5A-EDE8-4B33-A751-6CE34EC4C700"); 14 | BLECharacteristic characteristic("7772E5DB-3868-4112-A1A9-F2669D106BF3", BLERead | BLEWriteWithoutResponse | BLENotify, 20 ); 15 | BLEDescriptor descriptor = BLEDescriptor("2902", 0); 16 | 17 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI); 18 | 19 | void setup() { 20 | delay(1000); 21 | 22 | //Setup diag leds 23 | pinMode(BLUE_STAT_PIN, OUTPUT); 24 | pinMode(RED_STAT_PIN, OUTPUT); 25 | pinMode(GREEN_STAT_PIN, OUTPUT); 26 | digitalWrite(BLUE_STAT_PIN, 1); 27 | digitalWrite(RED_STAT_PIN, 1); 28 | digitalWrite(GREEN_STAT_PIN, 1); 29 | 30 | //Setup nRF52832 user button 31 | pinMode(BTN_PIN, INPUT_PULLUP); 32 | 33 | setupBLE(); 34 | 35 | // Initiate MIDI communications, listen to all channels 36 | MIDI.begin(MIDI_CHANNEL_OMNI); 37 | MIDI.turnThruOff(); 38 | 39 | // The nRF52832 converts baud settings to the discrete standard rates. 40 | // Use the nrf52.h names to write a custom value, 0x7FFC80 after beginning midi 41 | NRF_UARTE_Type * myUart; 42 | myUart = (NRF_UARTE_Type *)NRF_UART0_BASE; 43 | myUart->BAUDRATE = 0x7FFC80; 44 | 45 | //Write data to the serial output pin to make sure the serial output is working. 46 | //Sometimes serial output only allows 1 byte out then hangs. Resetting the 47 | //nRF52832 resolves the issue 48 | digitalWrite(RED_STAT_PIN, 0); 49 | MIDI.sendNoteOn(42, 66, 1); 50 | delay(500); 51 | MIDI.sendNoteOff(42, 66, 1); 52 | digitalWrite(RED_STAT_PIN, 1); 53 | 54 | } 55 | 56 | 57 | void loop() 58 | { 59 | BLECentral central = blePeripheral.central(); 60 | //Send midi data by the press of the button to test while running. 61 | if(digitalRead(BTN_PIN) == 0){ 62 | digitalWrite(GREEN_STAT_PIN, 0); 63 | MIDI.sendNoteOn(0x45, 80, 1); 64 | delay(100); 65 | MIDI.sendNoteOff(0x45, 80, 1); 66 | digitalWrite(GREEN_STAT_PIN, 1); 67 | } 68 | if (central) { 69 | while (central.connected()) { 70 | digitalWrite(GREEN_STAT_PIN, 0); 71 | //If connected, send midi data by the button here 72 | if(digitalRead(BTN_PIN) == 0){ 73 | digitalWrite(GREEN_STAT_PIN, 0); 74 | MIDI.sendNoteOn(0x45, 80, 1); 75 | delay(100); 76 | MIDI.sendNoteOff(0x45, 80, 1); 77 | digitalWrite(GREEN_STAT_PIN, 1); 78 | } 79 | //Check if data exists coming in from BLE 80 | if (characteristic.written()) { 81 | digitalWrite(RED_STAT_PIN, 0); 82 | processPacket(); 83 | digitalWrite(RED_STAT_PIN, 1); 84 | } 85 | //Check if data exists coming in from the serial port 86 | parseMIDIonDIN(); 87 | } 88 | } 89 | //No longer connected. Turn off the LEDs. 90 | digitalWrite(BLUE_STAT_PIN, 1); 91 | digitalWrite(GREEN_STAT_PIN, 1); 92 | //Delay to show off state for a bit 93 | delay(100); 94 | } 95 | 96 | void processPacket() 97 | { 98 | //Receive the written packet and parse it out here. 99 | uint8_t * buffer = (uint8_t*)characteristic.value(); 100 | uint8_t bufferSize = characteristic.valueLength(); 101 | //hang to give the LED time to show (not necessary if routines are here) 102 | delay(10); 103 | } 104 | 105 | void parseMIDIonDIN() 106 | { 107 | if ( MIDI.read()) 108 | { 109 | digitalWrite(RED_STAT_PIN, 0); 110 | //hang to give the LED time to show (not necessary if routines are here) 111 | delay(10); 112 | digitalWrite(RED_STAT_PIN, 1); 113 | } 114 | 115 | } 116 | 117 | void setupBLE() 118 | { 119 | blePeripheral.setLocalName("MIDI BLE Starter"); //local name sometimes used by central 120 | blePeripheral.setDeviceName("MIDI BLE Starter"); //device name sometimes used by central 121 | //blePeripheral.setApperance(0x0000); //default is 0x0000, what should this be? 122 | blePeripheral.setAdvertisedServiceUuid(service.uuid()); //Advertise MIDI UUID 123 | 124 | // add attributes (services, characteristics, descriptors) to peripheral 125 | blePeripheral.addAttribute(service); 126 | blePeripheral.addAttribute(characteristic); 127 | blePeripheral.addAttribute(descriptor); 128 | 129 | // set initial value 130 | characteristic.setValue(0); 131 | 132 | // set event handlers - Alternate ways of checking for BLE activity 133 | //characteristic.setEventHandler(BLEWritten, BLEWrittenCallback); 134 | //characteristic.setEventHandler(BLESubscribed, BLESubscribedCallback); 135 | //characteristic.setEventHandler(BLEUnsubscribed, BLEUnsubscribedCallback); 136 | 137 | blePeripheral.begin(); 138 | } 139 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## BLE MIDI Converter 2 | This repository is for code that converts BLE midi into DIN midi. It is discussed in the SparkFun [MIDI BLE Tutorial](https://learn.sparkfun.com/tutorials/midi-ble-tutorial) 3 | 4 | **Contents** 5 | * standard-midi-ble -- The main application to create a BLE MIDI dongle. 6 | * ble-to-din -- Just the BLE packet parser to serial MIDI out 7 | * din-to-ble -- Just the serial MIDI in to BLE packet builder 8 | * midi-lib-starter -- an empty shell with MIDI and BLE configured 9 | * ble-starter -- an empty shell with just BLE configured 10 | * test-programs 11 | * ble-test -- simple rx of BLE packets, prints sizes 12 | * midi-test -- simple TX of midi messages over DIN 13 | * parserUnitTest -- Exercise of BLE MIDI packet decoder 14 | * serial-test -- Simple serial test 15 | * documentation -- Right now, only output of ble-test during Tsunami app transmission 16 | 17 | ### Hardware requirements 18 | * nRF52832 Breakout 19 | * MIDI shield -- only populate MIDI jacks. 20 | 21 | Connections: 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
nRF Pin
MIDI Shield Pin
GNDGND
3.3V
5V
26(RX)
RX
27(TX)
TX
117 (Red LED)
12
6 (Green LED)
52 | 53 | ### Software 54 | **Requirements** 55 | * nRF52832 Arduino board package -- see [nRF52832 Breakout Hookup Guide](https://learn.sparkfun.com/tutorials/nrf52832-breakout-board-hookup-guide) 56 | * [BLE library](https://github.com/sandeepmistry/arduino-BLEPeripheral/) -- [API doc](https://github.com/sandeepmistry/arduino-BLEPeripheral/blob/master/API.md) 57 | * [FortySevenEffects Midi Library](https://github.com/FortySevenEffects/arduino_midi_library) 58 | 59 | **Status** 60 | * BLE to Serial MIDI 61 | * Decodes all forms of BLE MIDI data 62 | * SysEx ignored 63 | * BLE timestamps ignored 64 | * Serial MIDI to BLE 65 | * Converts all MIDI messages to Full type 66 | * Applies timestamp 67 | * SysEx ignored 68 | 69 | **Bugs / Future Work** 70 | * Serial port locks at boot sometimes, needs reset button press. 71 | * When uploading a lot of tests, sometimes connection won't hold - connecting other devices sometimes resolves this, or try renaming the device in firmware. 72 | 73 | -------------------------------------------------------------------------------- /standard-midi-ble/standard-midi-ble.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "nrf52.h" 3 | #include 4 | 5 | #define BLUE_STAT_PIN 7 // LED on pin 7 6 | #define RED_STAT_PIN 11 // LED on pin 11 7 | #define GREEN_STAT_PIN 12 // LED on pin 12 8 | #define BTN_PIN 6 // User button 9 | 10 | // create peripheral instance, see pinouts above 11 | //const char * localName = "nRF52832 MIDI"; 12 | BLEPeripheral blePeripheral; 13 | BLEService service("03B80E5A-EDE8-4B33-A751-6CE34EC4C700"); 14 | BLECharacteristic characteristic("7772E5DB-3868-4112-A1A9-F2669D106BF3", BLERead | BLEWriteWithoutResponse | BLENotify, 20 ); 15 | BLEDescriptor descriptor = BLEDescriptor("2902", 0); 16 | 17 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI); 18 | 19 | void setup() { 20 | delay(1000); 21 | 22 | //Setup diag leds 23 | pinMode(BLUE_STAT_PIN, OUTPUT); 24 | pinMode(RED_STAT_PIN, OUTPUT); 25 | pinMode(GREEN_STAT_PIN, OUTPUT); 26 | digitalWrite(BLUE_STAT_PIN, 1); 27 | digitalWrite(RED_STAT_PIN, 1); 28 | digitalWrite(GREEN_STAT_PIN, 1); 29 | 30 | //Setup nRF52832 user button 31 | pinMode(BTN_PIN, INPUT_PULLUP); 32 | 33 | setupBLE(); 34 | 35 | // Initiate MIDI communications, listen to all channels 36 | MIDI.begin(MIDI_CHANNEL_OMNI); 37 | MIDI.turnThruOff(); 38 | 39 | // The nRF52832 converts baud settings to the discrete standard rates. 40 | // Use the nrf52.h names to write a custom value, 0x7FFC80 after beginning midi 41 | NRF_UARTE_Type * myUart; 42 | myUart = (NRF_UARTE_Type *)NRF_UART0_BASE; 43 | myUart->BAUDRATE = 0x7FFC80; 44 | 45 | //Write data to the serial output pin to make sure the serial output is working. 46 | //Sometimes serial output only allows 1 byte out then hangs. Resetting the 47 | //nRF52832 resolves the issue 48 | digitalWrite(RED_STAT_PIN, 0); 49 | MIDI.sendNoteOn(42, 66, 1); 50 | delay(500); 51 | MIDI.sendNoteOff(42, 66, 1); 52 | digitalWrite(RED_STAT_PIN, 1); 53 | 54 | } 55 | 56 | void loop() 57 | { 58 | BLECentral central = blePeripheral.central(); 59 | if(digitalRead(BTN_PIN) == 0){ 60 | digitalWrite(GREEN_STAT_PIN, 0); 61 | MIDI.sendNoteOff(0x45, 80, 1); 62 | delay(100); 63 | digitalWrite(GREEN_STAT_PIN, 1); 64 | } 65 | if (central) { 66 | digitalWrite(BLUE_STAT_PIN, 0); 67 | // central connected to peripheral 68 | 69 | while (central.connected()) { 70 | digitalWrite(GREEN_STAT_PIN, 0); 71 | //If connected, send midi data by the button here 72 | if(digitalRead(BTN_PIN) == 0){ 73 | digitalWrite(GREEN_STAT_PIN, 0); 74 | MIDI.sendNoteOn(0x45, 80, 1); 75 | delay(100); 76 | MIDI.sendNoteOff(0x45, 80, 1); 77 | digitalWrite(GREEN_STAT_PIN, 1); 78 | } 79 | //Check if data exists coming in from BLE 80 | if (characteristic.written()) { 81 | digitalWrite(RED_STAT_PIN, 0); 82 | processPacket(); 83 | digitalWrite(RED_STAT_PIN, 1); 84 | } 85 | //Check if data exists coming in from the serial port 86 | parseMIDIonDIN(); 87 | } 88 | } 89 | //No longer connected. Turn off the LEDs. 90 | digitalWrite(BLUE_STAT_PIN, 1); 91 | digitalWrite(GREEN_STAT_PIN, 1); 92 | //Delay to show off state for a bit 93 | delay(100); 94 | } 95 | 96 | //This function decodes the BLE characteristics and calls transmitMIDIonDIN 97 | //if the packet contains sendable MIDI data. 98 | void processPacket() 99 | { 100 | //Receive the written packet and parse it out here. 101 | uint8_t * buffer = (uint8_t*)characteristic.value(); 102 | uint8_t bufferSize = characteristic.valueLength(); 103 | 104 | //Pointers used to search through payload. 105 | uint8_t lPtr = 0; 106 | uint8_t rPtr = 0; 107 | //lastStatus used to capture runningStatus 108 | uint8_t lastStatus; 109 | //Decode first packet -- SHALL be "Full MIDI message" 110 | lPtr = 2; //Start at first MIDI status -- SHALL be "MIDI status" 111 | //While statement contains incrementing pointers and breaks when buffer size exceeded. 112 | while(1){ 113 | lastStatus = buffer[lPtr]; 114 | if( (buffer[lPtr] < 0x80) ){ 115 | //Status message not present, bail 116 | return; 117 | } 118 | //Point to next non-data byte 119 | rPtr = lPtr; 120 | while( (buffer[rPtr + 1] < 0x80)&&(rPtr < (bufferSize - 1)) ){ 121 | rPtr++; 122 | } 123 | //look at l and r pointers and decode by size. 124 | if( rPtr - lPtr < 1 ){ 125 | //Time code or system 126 | transmitMIDIonDIN( lastStatus, 0, 0 ); 127 | } else if( rPtr - lPtr < 2 ) { 128 | transmitMIDIonDIN( lastStatus, buffer[lPtr + 1], 0 ); 129 | } else if( rPtr - lPtr < 3 ) { 130 | transmitMIDIonDIN( lastStatus, buffer[lPtr + 1], buffer[lPtr + 2] ); 131 | } else { 132 | //Too much data 133 | //If not System Common or System Real-Time, send it as running status 134 | switch( buffer[lPtr] & 0xF0 ) 135 | { 136 | case 0x80: 137 | case 0x90: 138 | case 0xA0: 139 | case 0xB0: 140 | case 0xE0: 141 | for(int i = lPtr; i < rPtr; i = i + 2){ 142 | transmitMIDIonDIN( lastStatus, buffer[i + 1], buffer[i + 2] ); 143 | } 144 | break; 145 | case 0xC0: 146 | case 0xD0: 147 | for(int i = lPtr; i < rPtr; i = i + 1){ 148 | transmitMIDIonDIN( lastStatus, buffer[i + 1], 0 ); 149 | } 150 | break; 151 | default: 152 | break; 153 | } 154 | } 155 | //Point to next status 156 | lPtr = rPtr + 2; 157 | if(lPtr >= bufferSize){ 158 | //end of packet 159 | return; 160 | } 161 | } 162 | } 163 | 164 | //This function takes a midi packet as input and calls the appropriate library 165 | //function to transmit the data. It's a little redundant because the library 166 | //reforms midi data from the calls and sends it out the serial port. 167 | // 168 | //Ideally, the MIDI BLE object would feed a MIDI library object as a serial 169 | //object removing all of this code. 170 | // 171 | //A benefit of this redundant code is that it's easy to filter messages, and 172 | //exposes how the library works. 173 | void transmitMIDIonDIN( uint8_t status, uint8_t data1, uint8_t data2 ) 174 | { 175 | uint8_t channel = status & 0x0F; 176 | channel++; 177 | uint8_t command = (status & 0xF0) >> 4; 178 | switch(command) 179 | { 180 | case 0x08: //Note off 181 | MIDI.sendNoteOff(data1, data2, channel); 182 | break; 183 | case 0x09: //Note on 184 | MIDI.sendNoteOn(data1, data2, channel); 185 | break; 186 | case 0x0A: //Polyphonic Pressure 187 | MIDI.sendAfterTouch(data1, data2, channel); 188 | break; 189 | case 0x0B: //Control Change 190 | MIDI.sendControlChange(data1, data2, channel); 191 | break; 192 | case 0x0C: //Program Change 193 | MIDI.sendProgramChange(data1, channel); 194 | break; 195 | case 0x0D: //Channel Pressure 196 | MIDI.sendAfterTouch(data2, channel); 197 | break; 198 | case 0x0E: //Pitch Bend 199 | MIDI.send(midi::PitchBend, data1, data2, channel); 200 | break; 201 | case 0x0F: //System 202 | switch(status) 203 | { 204 | case 0xF1: //MTC Q frame 205 | MIDI.sendTimeCodeQuarterFrame( data1 ); 206 | break; 207 | case 0xF2: //Song position 208 | MIDI.sendSongPosition(( (uint16_t)(data1 & 0x7F) << 7) | (data2 & 0x7F)); 209 | break; 210 | case 0xF3: //Song select 211 | MIDI.sendSongSelect( data1 ); 212 | break; 213 | case 0xF6: //Tune request 214 | MIDI.sendTuneRequest(); 215 | break; 216 | case 0xF8: //Timing Clock 217 | case 0xFA: //Start 218 | case 0xFB: //Continue 219 | case 0xFC: //Stop 220 | case 0xFE: //Active Sensing 221 | case 0xFF: //Reset 222 | MIDI.sendRealTime( (midi::MidiType)status ); 223 | break; 224 | default: 225 | break; 226 | } 227 | break; 228 | default: 229 | break; 230 | } 231 | } 232 | 233 | //This function is called to check if MIDI data has come in through the serial port. If found, it builds a characteristic buffer and sends it over BLE. 234 | void parseMIDIonDIN() 235 | { 236 | uint8_t msgBuf[5]; //Outgoing buffer 237 | 238 | //Calculate timestamp. 239 | uint16_t currentTimeStamp = millis() & 0x01FFF; 240 | 241 | msgBuf[0] = ((currentTimeStamp >> 7) & 0x3F) | 0x80; //6 bits plus MSB 242 | msgBuf[1] = (currentTimeStamp & 0x7F) | 0x80; //7 bits plus MSB 243 | 244 | //Check MIDI object for new data. 245 | if ( MIDI.read()) 246 | { 247 | digitalWrite(RED_STAT_PIN, 0); 248 | uint8_t statusByte = ((uint8_t)MIDI.getType() | ((MIDI.getChannel() - 1) & 0x0f)); 249 | switch (MIDI.getType()) 250 | { 251 | //2 Byte Channel Messages 252 | case midi::NoteOff : 253 | case midi::NoteOn : 254 | case midi::AfterTouchPoly : 255 | case midi::ControlChange : 256 | case midi::PitchBend : 257 | msgBuf[2] = statusByte; 258 | msgBuf[3] = MIDI.getData1(); 259 | msgBuf[4] = MIDI.getData2(); 260 | characteristic.setValue(msgBuf, 5); 261 | break; 262 | //1 Byte Channel Messages 263 | case midi::ProgramChange : 264 | case midi::AfterTouchChannel : 265 | msgBuf[2] = statusByte; 266 | msgBuf[3] = MIDI.getData1(); 267 | characteristic.setValue(msgBuf, 4); 268 | break; 269 | //System Common Messages 270 | case midi::TimeCodeQuarterFrame : 271 | msgBuf[2] = 0xF1; 272 | msgBuf[3] = MIDI.getData1(); 273 | characteristic.setValue(msgBuf, 4); 274 | break; 275 | case midi::SongPosition : 276 | msgBuf[2] = 0xF2; 277 | msgBuf[3] = MIDI.getData1(); 278 | msgBuf[4] = MIDI.getData2(); 279 | characteristic.setValue(msgBuf, 5); 280 | break; 281 | case midi::SongSelect : 282 | msgBuf[2] = 0xF3; 283 | msgBuf[3] = MIDI.getData1(); 284 | characteristic.setValue(msgBuf, 4); 285 | break; 286 | case midi::TuneRequest : 287 | msgBuf[2] = 0xF6; 288 | characteristic.setValue(msgBuf, 3); 289 | break; 290 | //Real-time Messages 291 | case midi::Clock : 292 | msgBuf[2] = 0xF8; 293 | characteristic.setValue(msgBuf, 3); 294 | break; 295 | case midi::Start : 296 | msgBuf[2] = 0xFA; 297 | characteristic.setValue(msgBuf, 3); 298 | break; 299 | case midi::Continue : 300 | msgBuf[2] = 0xFB; 301 | characteristic.setValue(msgBuf, 3); 302 | break; 303 | case midi::Stop : 304 | msgBuf[2] = 0xFC; 305 | characteristic.setValue(msgBuf, 3); 306 | break; 307 | case midi::ActiveSensing : 308 | msgBuf[2] = 0xFE; 309 | characteristic.setValue(msgBuf, 3); 310 | break; 311 | case midi::SystemReset : 312 | msgBuf[2] = 0xFF; 313 | characteristic.setValue(msgBuf, 3); 314 | break; 315 | //SysEx 316 | case midi::SystemExclusive : 317 | // { 318 | // // Sysex is special. 319 | // // could contain very long data... 320 | // // the data bytes form the length of the message, 321 | // // with data contained in array member 322 | // uint16_t length; 323 | // const uint8_t * data_p; 324 | // 325 | // Serial.print("SysEx, chan: "); 326 | // Serial.print(MIDI.getChannel()); 327 | // length = MIDI.getSysExArrayLength(); 328 | // 329 | // Serial.print(" Data: 0x"); 330 | // data_p = MIDI.getSysExArray(); 331 | // for (uint16_t idx = 0; idx < length; idx++) 332 | // { 333 | // Serial.print(data_p[idx], HEX); 334 | // Serial.print(" 0x"); 335 | // } 336 | // Serial.println(); 337 | // } 338 | break; 339 | case midi::InvalidType : 340 | default: 341 | break; 342 | } 343 | digitalWrite(RED_STAT_PIN, 1); 344 | } 345 | 346 | } 347 | 348 | void setupBLE() 349 | { 350 | blePeripheral.setLocalName("nRF52832 MIDI"); //local name sometimes used by central 351 | blePeripheral.setDeviceName("nRF52832 MIDI"); //device name sometimes used by central 352 | //blePeripheral.setApperance(0x0000); //default is 0x0000, what should this be? 353 | blePeripheral.setAdvertisedServiceUuid(service.uuid()); //Advertise MIDI UUID 354 | 355 | // add attributes (services, characteristics, descriptors) to peripheral 356 | blePeripheral.addAttribute(service); 357 | blePeripheral.addAttribute(characteristic); 358 | blePeripheral.addAttribute(descriptor); 359 | 360 | // set initial value 361 | characteristic.setValue(0); 362 | 363 | blePeripheral.begin(); 364 | } 365 | -------------------------------------------------------------------------------- /test-programs/ble-test/ble-test.ino: -------------------------------------------------------------------------------- 1 | // Import libraries (BLEPeripheral depends on SPI) 2 | #include 3 | 4 | // create peripheral instance, see pinouts above 5 | const char * localName = "nRF52832 MIDI2"; 6 | BLEPeripheral blePeripheral; 7 | 8 | // create one or more services 9 | BLEService service("03B80E5A-EDE8-4B33-A751-6CE34EC4C700"); 10 | 11 | // create one or more characteristics 12 | BLECharacteristic characteristic("7772E5DB-3868-4112-A1A9-F2669D106BF3", BLERead | BLEWriteWithoutResponse | BLENotify, 20 ); 13 | 14 | // create one or more descriptors (optional) 15 | BLEDescriptor descriptor = BLEDescriptor("2901", "value"); 16 | 17 | #define LED_PIN 7 // LED on pin 7 18 | #define RED_STAT_PIN 11 // LED on pin 7 19 | #define GREEN_STAT_PIN 12 // LED on pin 7 20 | #define BTN_PIN 6 21 | 22 | void setup() { 23 | pinMode(LED_PIN, OUTPUT); 24 | pinMode(RED_STAT_PIN, OUTPUT); 25 | pinMode(GREEN_STAT_PIN, OUTPUT); 26 | pinMode(BTN_PIN, INPUT_PULLUP); 27 | digitalWrite(LED_PIN, 1); 28 | digitalWrite(RED_STAT_PIN, 1); 29 | digitalWrite(GREEN_STAT_PIN, 1); 30 | 31 | Serial.begin(115200); 32 | delay(3000); 33 | Serial.println("Program Started"); 34 | setupBLE(); 35 | 36 | 37 | } 38 | 39 | void loop() { 40 | BLECentral central = blePeripheral.central(); 41 | 42 | if (central) { 43 | digitalWrite(LED_PIN, 0); 44 | // central connected to peripheral 45 | 46 | while (central.connected()) { 47 | digitalWrite(GREEN_STAT_PIN, 0); 48 | // central still connected to peripheral 49 | if (characteristic.written()) { 50 | digitalWrite(RED_STAT_PIN, 0); 51 | Serial.println(freeMemory()); 52 | 53 | Serial.print("Rx size: "); 54 | Serial.println(characteristic.valueLength()); 55 | uint8_t * buffer = (uint8_t*)characteristic.value(); 56 | Serial.print("0x"); 57 | for( int i = 0; i < characteristic.valueLength(); i++ ){ 58 | if( buffer[i] < 0x10 ) Serial.print("0"); 59 | Serial.print( buffer[i], HEX ); 60 | } 61 | Serial.println(); 62 | digitalWrite(RED_STAT_PIN, 1); 63 | } 64 | } 65 | } 66 | digitalWrite(LED_PIN, 1); 67 | digitalWrite(GREEN_STAT_PIN, 1); 68 | delay(500); 69 | Serial.println(freeMemory()); 70 | } 71 | 72 | void setupBLE() 73 | { 74 | blePeripheral.setLocalName(localName); // optional 75 | blePeripheral.setAdvertisedServiceUuid(service.uuid()); // optional 76 | 77 | // add attributes (services, characteristics, descriptors) to peripheral 78 | blePeripheral.addAttribute(service); 79 | blePeripheral.addAttribute(characteristic); 80 | blePeripheral.addAttribute(descriptor); 81 | 82 | // set initial value 83 | characteristic.setValue(0); 84 | 85 | // set event handlers 86 | characteristic.setEventHandler(BLEWritten, BLEWrittenCallback); 87 | characteristic.setEventHandler(BLESubscribed, BLESubscribedCallback); 88 | characteristic.setEventHandler(BLEUnsubscribed, BLEUnsubscribedCallback); 89 | 90 | blePeripheral.begin(); 91 | } 92 | 93 | // callback signature 94 | void BLEWrittenCallback(BLECentral& central, BLECharacteristic& characteristic) { 95 | // .... 96 | 97 | } 98 | 99 | // callback signature 100 | void BLESubscribedCallback(BLECentral& central, BLECharacteristic& characteristic) { 101 | // .... 102 | 103 | } 104 | 105 | // callback signature 106 | void BLEUnsubscribedCallback(BLECentral& central, BLECharacteristic& characteristic) { 107 | // .... 108 | 109 | } 110 | // event - BLEWritten, BLESubscribed, or BLEUnsubscribed 111 | 112 | #ifdef __arm__ 113 | // should use uinstd.h to define sbrk but Due causes a conflict 114 | extern "C" char* sbrk(int incr); 115 | #else // __ARM__ 116 | extern char *__brkval; 117 | #endif // __arm__ 118 | 119 | int freeMemory() { 120 | char top; 121 | #ifdef __arm__ 122 | return &top - reinterpret_cast(sbrk(0)); 123 | #elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151) 124 | return &top - __brkval; 125 | #else // __arm__ 126 | return __brkval ? &top - __brkval : &top - __malloc_heap_start; 127 | #endif // __arm__ 128 | } -------------------------------------------------------------------------------- /test-programs/midi-test/midi-test.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "nrf52.h" 3 | 4 | // Simple tutorial on how to receive and send MIDI messages. 5 | // Here, when receiving any message on channel 4, the Arduino 6 | // will blink a led and play back a note for 1 second. 7 | 8 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial, midiA); 9 | 10 | static const unsigned ledPin = 7; // LED pin on Arduino Uno 11 | 12 | void setup() 13 | { 14 | Serial.begin(9600); 15 | pinMode(ledPin, OUTPUT); 16 | midiA.begin(MIDI_CHANNEL_OMNI); // Launch MIDI and listen to channel 4 17 | NRF_UARTE_Type * myUart; 18 | myUart = (NRF_UARTE_Type *)NRF_UART0_BASE; 19 | myUart->BAUDRATE = 0x7FFC80; 20 | //uint16_t * address; 21 | //address * = (uint16_t*)(NRF_UART0_BASE); 22 | } 23 | 24 | void loop() 25 | { 26 | if (midiA.read()) // If we have received a message 27 | { 28 | digitalWrite(ledPin, 0); 29 | midiA.sendNoteOn(42, 127, 1); // Send a Note (pitch 42, velo 127 on channel 1) 30 | delay(1000); // Wait for a second 31 | midiA.sendNoteOff(42, 0, 1); // Stop the note 32 | digitalWrite(ledPin, 1); 33 | } 34 | } 35 | 36 | 37 | //nrfBaudRate = 0x7FFC80; //For 31250 baud -------------------------------------------------------------------------------- /test-programs/parserUnitTest/parserUnitTest.ino: -------------------------------------------------------------------------------- 1 | //Run on 328p for ease 2 | #include 3 | 4 | uint8_t note_off[5] = {0x80, 0x80, 0x80, 0x40, 0x40}; 5 | uint8_t note_on[5] = {0x80, 0x80, 0x90, 0x40, 0x40}; 6 | uint8_t test_1[13] = {0xBD, 0xE9, 0xB0, 0x62, 0x01, 0xE9, 0xB0, 0x06, 0x07, 0xE9, 0xB0, 0x26, 0x11}; 7 | uint8_t note_on_rs[9] = {0x80, 0x80, 0x90, 0x3C, 0x40, 0x3D, 0x40, 0x3E, 0x40 }; 8 | uint8_t prog_change[8] = {0x80, 0x80, 0xC0, 0x3C, 0x80, 0x90, 0x40, 0x40 }; 9 | uint8_t channel_Pressure[7] = {0x80, 0x80, 0xD0, 0x3C, 0x80, 0xD0, 0x3C }; 10 | uint8_t channel_Pressure_rs[7] = {0x80, 0x80, 0xD0, 0x3C, 0x3D, 0x3E, 0x3F }; 11 | uint8_t Pitch_bend[9] = {0x80, 0x80, 0xE0, 0x3C, 0x40, 0xFD, 0x80, 0x3E, 0x40 }; 12 | 13 | class FakeCharacteristic 14 | { 15 | public: 16 | FakeCharacteristic(void){dataLength = 0;}; 17 | uint8_t * value(void){return dataBuffer;}; 18 | uint8_t valueLength(void){return dataLength;}; 19 | uint8_t * dataBuffer; 20 | uint8_t dataLength; 21 | }; 22 | 23 | FakeCharacteristic characteristic; 24 | 25 | void setup() { 26 | Serial.begin(115200); 27 | Serial.println("Unit test started"); 28 | 29 | characteristic.dataBuffer = test_1;//Set test data 30 | characteristic.dataLength = sizeof(test_1); 31 | showData();//Show data 32 | processPacket();//Run test 33 | 34 | characteristic.dataBuffer = note_on_rs;//Set test data 35 | characteristic.dataLength = sizeof(note_on_rs); 36 | showData();//Show data 37 | processPacket();//Run test 38 | 39 | 40 | characteristic.dataBuffer = prog_change;//Set test data 41 | characteristic.dataLength = sizeof(prog_change); 42 | showData();//Show data 43 | processPacket();//Run test 44 | 45 | characteristic.dataBuffer = channel_Pressure;//Set test data 46 | characteristic.dataLength = sizeof(channel_Pressure); 47 | showData();//Show data 48 | processPacket();//Run test 49 | 50 | characteristic.dataBuffer = channel_Pressure_rs;//Set test data 51 | characteristic.dataLength = sizeof(channel_Pressure); 52 | showData();//Show data 53 | processPacket();//Run test 54 | 55 | characteristic.dataBuffer = Pitch_bend;//Set test data 56 | characteristic.dataLength = sizeof(Pitch_bend); 57 | showData();//Show data 58 | processPacket();//Run test 59 | 60 | 61 | } 62 | 63 | void loop() { 64 | } 65 | 66 | void showData( void ) 67 | { 68 | Serial.println(); 69 | uint8_t * buffer = (uint8_t*)characteristic.value(); 70 | Serial.print("Test MIDI BLE packet: 0x"); 71 | for( int i = 0; i < characteristic.valueLength(); i++ ){ 72 | if( buffer[i] < 0x10 ) Serial.print("0"); 73 | Serial.print( buffer[i], HEX ); 74 | Serial.print(", "); 75 | } 76 | Serial.println(); 77 | 78 | } 79 | 80 | //This normally connects to the midi system 81 | void transmitMIDIonDIN( uint8_t status, uint8_t data1, uint8_t data2 ){ 82 | Serial.print("Sending: 0x"); 83 | Serial.print(status, HEX); 84 | Serial.print(", 0x"); 85 | Serial.print(data1, HEX); 86 | Serial.print(", 0x"); 87 | Serial.print(data2, HEX); 88 | Serial.println(); 89 | } 90 | 91 | //******This is the UUT****** 92 | 93 | //This function decodes the BLE characteristics and calls transmitMIDIonDIN 94 | //if the packet contains sendable MIDI data. 95 | void processPacket() 96 | { 97 | //Receive the written packet and parse it out here. 98 | uint8_t * buffer = (uint8_t*)characteristic.value(); 99 | uint8_t bufferSize = characteristic.valueLength(); 100 | 101 | //Pointers used to search through payload. 102 | uint8_t lPtr = 0; 103 | uint8_t rPtr = 0; 104 | //lastStatus used to capture runningStatus 105 | uint8_t lastStatus; 106 | //Decode first packet -- SHALL be "Full MIDI message" 107 | lPtr = 2; //Start at first MIDI status -- SHALL be "MIDI status" 108 | //While statement contains incrementing pointers and breaks when buffer size exceeded. 109 | while(1){ 110 | lastStatus = buffer[lPtr]; 111 | if( (buffer[lPtr] < 0x80) ){ 112 | //Status message not present, bail 113 | return; 114 | } 115 | //Point to next non-data byte 116 | rPtr = lPtr; 117 | while( (buffer[rPtr + 1] < 0x80)&&(rPtr < (bufferSize - 1)) ){ 118 | rPtr++; 119 | } 120 | //look at l and r pointers and decode by size. 121 | if( rPtr - lPtr < 1 ){ 122 | //Time code or system 123 | transmitMIDIonDIN( lastStatus, 0, 0 ); 124 | } else if( rPtr - lPtr < 2 ) { 125 | transmitMIDIonDIN( lastStatus, buffer[lPtr + 1], 0 ); 126 | } else if( rPtr - lPtr < 3 ) { 127 | transmitMIDIonDIN( lastStatus, buffer[lPtr + 1], buffer[lPtr + 2] ); 128 | } else { 129 | //Too much data 130 | //If not System Common or System Real-Time, send it as running status 131 | switch( buffer[lPtr] & 0xF0 ) 132 | { 133 | case 0x80: 134 | case 0x90: 135 | case 0xA0: 136 | case 0xB0: 137 | case 0xE0: 138 | for(int i = lPtr; i < rPtr; i = i + 2){ 139 | transmitMIDIonDIN( lastStatus, buffer[i + 1], buffer[i + 2] ); 140 | } 141 | break; 142 | case 0xC0: 143 | case 0xD0: 144 | for(int i = lPtr; i < rPtr; i = i + 1){ 145 | transmitMIDIonDIN( lastStatus, buffer[i + 1], 0 ); 146 | } 147 | break; 148 | default: 149 | break; 150 | } 151 | } 152 | //Point to next status 153 | lPtr = rPtr + 2; 154 | if(lPtr >= bufferSize){ 155 | //end of packet 156 | return; 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /test-programs/serial-test/serial-test.ino: -------------------------------------------------------------------------------- 1 | unsigned long msOffset = 0; 2 | 3 | #define MAX_MS 0x01FFF //13 bits, 8192 dec 4 | 5 | 6 | void setup() 7 | { 8 | delay(3000); //5 seconds delay for enabling to see the start up comments on the serial board 9 | Serial.end(); 10 | Serial.begin(115200); 11 | while(Serial.available()){ 12 | Serial.read(); 13 | } 14 | Serial.println("Sketch started"); 15 | } 16 | 17 | void loop() 18 | { 19 | //unsigned long currentMillis = millis(); 20 | //if(currentMillis < 5000){ 21 | // if(msOffset > 5000){ 22 | // //it's been 49 days, millis rolled. 23 | // while(msOffset > 5000){ 24 | // //roll msOffset - this should preserve current ~8 second count. 25 | // msOffset += MAX_MS; 26 | // } 27 | // } 28 | //} 29 | //while(currentMillis >= (unsigned long)(msOffset + MAX_MS)){ 30 | // msOffset += MAX_MS; 31 | // Serial.print("offset inc. New timestamp: "); 32 | // Serial.println(currentMillis - msOffset); 33 | //} 34 | //unsigned long currentTimeStamp = currentMillis - msOffset; 35 | uint16_t currentTimeStamp = millis() & 0x01FFF; 36 | if (Serial.available()) // If we have received a message 37 | { 38 | Serial.print("Timestamp (ms): "); 39 | Serial.println(currentTimeStamp); 40 | Serial.print("0x"); 41 | Serial.println(Serial.read(), HEX); 42 | } 43 | } 44 | 45 | 46 | //nrfBaudRate = 0x7FFC80; //For 31250 baud 47 | --------------------------------------------------------------------------------