BLE seems to be manipulating serial data buffer

hoschi
Posts: 4
Joined: Wed Jan 26, 2022 4:21 pm

BLE seems to be manipulating serial data buffer

Postby hoschi » Thu Jan 27, 2022 3:07 pm

Looking here for good advises how to further nail down a strange issue with BLE and the ESP32.

My application is a typical midi use-case. Using a D1 Wemos board with a self-designed midi interface, I use Pin 21 for midi-transmit and pin 16 for midi-receive. On top, I have another Midi connection using BLE to connect to mobile devices. All in all, my application has two midi connections, (1) physical midi connection and (2) BLE midi connection. For (1) I am using this midi library
https://github.com/FortySevenEffects/ar ... di_library

While using my larger application during rehearsals, I detected strange behavior every now and then. Thus, I reduced the application further and further to find the source of the issue. Here is my current sketch:
  1. #include <OpenBleMidi.h>
  2. #include <string>
  3. #include <MIDI.h>
  4.  
  5. #define TX_MIDI 21
  6. #define RX_MIDI 16
  7.  
  8. #define HX_STOMP_MIDI_CHANNEL 4
  9. #define PROGRAM_CHANGE_NOTE_C_m1 0
  10.  
  11. #define ON 0x7f
  12. #define OFF 0x00
  13.  
  14. MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, hxMidiInterface);
  15.  
  16. OpenBleMidi openBleMidi;
  17.  
  18. // #define WITH_BLE
  19.  
  20. #ifdef WITH_BLE
  21. class HelixMorph :  public IOpenBleMidi {
  22. #else
  23. class HelixMorph {
  24. #endif
  25.    
  26. private:
  27.   uint8_t m_midiPacket[5] = {0x80, 0x80, 0xB0, 0x53, 0x01};
  28. public:
  29.   // ------------------------------------------------------------
  30.   // IOpenBleMidi
  31.   // ------------------------------------------------------------
  32.   virtual void onBleMidiControlChange(uint8_t cc, uint8_t val, uint8_t ch) {
  33.     Serial.print("BLE Control Change: "); Serial.print(cc); Serial.print(", value: "); Serial.print(val); Serial.print(", channel: "); Serial.println(ch);
  34.   }
  35.   virtual void onBleMidiNoteOn(uint8_t cc, uint8_t val, uint8_t ch) {
  36.     Serial.print("BLE Note On: "); Serial.print(cc); Serial.print(", channel: "); Serial.println(ch);
  37.   }
  38.   virtual void onBleMidiNoteOff(uint8_t cc, uint8_t val, uint8_t ch) {
  39.     Serial.print("BLE Note Off: "); Serial.print(cc); Serial.print(", channel: "); Serial.println(ch);
  40.   }
  41.   virtual void onBleMidiProgramChange(uint8_t pc, uint8_t ch) {
  42.     Serial.print("BLE Program Change: "); Serial.print(pc); Serial.print(", channel: "); Serial.println(ch);
  43.   }
  44.   virtual void onBleMidiSystemExlusive(uint16_t byteCnt, uint8_t *bytes) {
  45.     Serial.print("System Exclusive, data: ");
  46.     for(int i=0; i<byteCnt; i++) {
  47.       Serial.print(bytes[i]);
  48.       Serial.print(" ");
  49.     }
  50.     Serial.println();
  51.   }
  52.   virtual void onBleMidiConnect() {
  53.     Serial.println("BLE Connected to client");
  54.   }
  55.   virtual void onBleMidiDisconnect() {
  56.     Serial.println("BLE Disconneted from client");
  57.   }
  58.   // ------------------------------------------------------------
  59.   // Midi
  60.   // ------------------------------------------------------------
  61.   static void onNoteOn(byte channel, byte note, byte velocity) {
  62.     Serial.print("OpenHxStomp onNoteOn"); Serial.print(", note: "); Serial.print(note); Serial.print(", velocity: "); Serial.print(velocity); Serial.print(", ch: "); Serial.println(channel);
  63.     if(channel != HX_STOMP_MIDI_CHANNEL) {
  64.       Serial.println("****** WRONG MIDI CHANNEL ***************************************");
  65.       return;
  66.     }
  67.  
  68.     // for testing only
  69.     if(velocity != 78 && velocity != 79) {
  70.       Serial.println("*** WRONG VELOCITY **********************************************");
  71.     }
  72.    
  73.     switch(note) {
  74.       case(PROGRAM_CHANGE_NOTE_C_m1):
  75.         break;
  76.       default:
  77.         Serial.println("*** DEFAULT IN CASE *******************************************");
  78.         break;
  79.     }
  80.   }
  81.  
  82.   static void onNoteOff(byte channel, byte note, byte velocity) {
  83.     Serial.print("OpenHxStomp onNoteOff"); Serial.print(", note: "); Serial.print(note); Serial.print(", velocity: "); Serial.print(velocity); Serial.print(", ch: "); Serial.println(channel);
  84.     Serial.println("*** NOTE OFF **************************************************");
  85.   }
  86. };
  87.  
  88.  
  89. HelixMorph helixMorph;
  90.  
  91. void setup() {
  92.   Serial.begin(115200);
  93. #ifdef WITH_BLE
  94.   openBleMidi.setup("HelixMorph", &Serial);
  95.   openBleMidi.registerIOpenBleMidiCb(&helixMorph);
  96. #endif
  97.   hxMidiInterface.setHandleNoteOn(HelixMorph::onNoteOn);
  98.   hxMidiInterface.setHandleNoteOff(HelixMorph::onNoteOff);
  99.   Serial2.begin(31250, SERIAL_8N1, RX_MIDI, TX_MIDI);
  100. }
  101.  
  102. void loop() {
  103.   hxMidiInterface.read();
  104. }


Besides the code above I wrote a small python script that sends midi Note On messages to the physical connection in a loop:


  1.     def test_02(self):
  2.         # make sure we start at 27b
  3.         self.set_preset(79)
  4.         time.sleep(1)
  5.  
  6.         num_tests = 10000
  7.         for i in range(0, num_tests):
  8.             print('Running test ' + str(i) + ' of ' + str(num_tests))
  9.             self.midi_if.send_midi_message(mido.Message('note_on', channel=3, note=0, velocity=78))
  10.             time.sleep(0.1)
  11.             self.midi_if.send_midi_message(mido.Message('note_on', channel=3, note=0, velocity=79))
  12.             time.sleep(0.1)

If you take a look onto my ESP32 code you'll see the following line:

  1. #define WITH_BLE

I use this define as a switch - if the define is there, I compile my solution with midi BLE functionality. If I comment the line off, the solution will build without BLE.

If I run my application without BLE functionality everything works as expected. I believe I sent more than 100000 midi messages to my microcontroller without any issue - everything was received as I expected.

If I activate the BLE some messages get corrupted (rough guess 1 of 2000). As some of you might know, a Note On message consists of three bytes. The first byte also carries the midi channel information. Byte 2 is the note and byte 3 the velocity. I have different corruptions – sometimes the Note On message is detected as Note Off (so I guess the first byte gets corrupted), sometimes the note are velocity values are simply wrong.

I had a deeper look into the midi library I use and found this function which parses the incoming midi data:
  1. // Private method: MIDI parser
  2. template<class SerialPort, class Settings>
  3. bool MidiInterface<SerialPort, Settings>::parse()
  4. {
  5.     if (mSerial.available() == 0)
  6.         // No data available.
  7.         return false;
  8.  
  9.     // Parsing algorithm:
  10.     // Get a byte from the serial buffer.
  11.     // If there is no pending message to be recomposed, start a new one.
  12.     //  - Find type and channel (if pertinent)
  13.     //  - Look for other bytes in buffer, call parser recursively,
  14.     //    until the message is assembled or the buffer is empty.
  15.     // Else, add the extracted byte to the pending message, and check validity.
  16.     // When the message is done, store it.
  17.  
  18.     const byte extracted = mSerial.read();
  19.  
  20.     Serial.print("MIDI::parse() after mSerial.read(): "); Serial.println(extracted);
  21.  
  22.     // Ignore Undefined
  23.     if (extracted == 0xf9 || extracted == 0xfd)
  24. (...)


I added the Serial.print commands and figured out, that the data is already corrupted at this stage.

All in all, it look like the BLE functionality has influences on the serial data buffer used to receive the physical midi data. Is this possible? How could I further check this issue? Any idea what I could try?

For completeness, here is the code for midi BLE functionality I moved into a library called OpenBleMidi:
  1. #include "OpenBleMidi.h"
  2. #include <MIDI.h>
  3.  
  4. uint8_t OpenBleMidi::deviceConnected = 0;
  5.  
  6. class OpenBleMidiServerCallbacks: public BLEServerCallbacks {
  7.  
  8. private:
  9.   OpenBleMidi *m_pOpenBleMidi;
  10. public:
  11.   OpenBleMidiServerCallbacks(OpenBleMidi *pOpenBleMidi) {
  12.     m_pOpenBleMidi = pOpenBleMidi;
  13.   }
  14.   void onConnect(BLEServer* pServer) {
  15.     m_pOpenBleMidi->onBleMidiConnect();
  16.     OpenBleMidi::deviceConnected = 1;
  17.   };
  18.  
  19.   void onDisconnect(BLEServer* pServer) {
  20.     m_pOpenBleMidi->onBleMidiDisconnect();
  21.     OpenBleMidi::deviceConnected = 0;
  22.   }
  23. };
  24.  
  25. class OpenBleMidiCharacteristicCallbacks: public BLECharacteristicCallbacks {
  26.  
  27. private:
  28.   OpenBleMidi *m_pOpenBleMidi;
  29. public:
  30.   OpenBleMidiCharacteristicCallbacks(OpenBleMidi *pOpenBleMidi) {
  31.     m_pOpenBleMidi = pOpenBleMidi;
  32.   }
  33.  
  34.    void onWrite(BLECharacteristic *pCharacteristic) {
  35.      
  36.       std::string rxValue = pCharacteristic->getValue();
  37.       uint8_t midiMessageLength = rxValue.length();
  38.       uint8_t processedBytes = 0;
  39.       uint8_t type, note, velocity, channel, d1, d2;
  40.      
  41.       // timestamp #1 and timestamp #2
  42.       processedBytes += 2;
  43.      
  44.       while(processedBytes < midiMessageLength) {
  45.        
  46.         channel = rxValue[processedBytes] % 16;
  47.         type = rxValue[processedBytes++] - channel;
  48.  
  49.         switch(type) {
  50.           case midi::ControlChange:
  51.             note = rxValue[processedBytes++];
  52.             velocity = rxValue[processedBytes++];
  53.             m_pOpenBleMidi->onBleMidiControlChange(note, velocity, channel+1);
  54.             break;
  55.           case midi::NoteOn:
  56.             note = rxValue[processedBytes++];
  57.             velocity = rxValue[processedBytes++];
  58.             channel = rxValue[processedBytes++];
  59.             m_pOpenBleMidi->onBleMidiNoteOn(note, velocity, channel+1);
  60.             break;
  61.           case midi::NoteOff:
  62.             note = rxValue[processedBytes++];
  63.             velocity = rxValue[processedBytes++];
  64.             channel = rxValue[processedBytes++];
  65.             m_pOpenBleMidi->onBleMidiNoteOff(note, velocity, channel+1);
  66.             break;
  67.           case midi::ProgramChange:
  68.             note = rxValue[processedBytes++];
  69.             m_pOpenBleMidi->onBleMidiProgramChange(note, channel+1);
  70.             break;
  71.           case midi::SystemExclusive: {
  72.             uint8_t *sysexData = new uint8_t[midiMessageLength];
  73.             uint8_t byteCnt = 0;
  74.             for(int i=0 ; i<midiMessageLength ; i++) {
  75.               if(0x80 == rxValue[i])
  76.                 continue;
  77.               sysexData[byteCnt++] = rxValue[i];
  78.             }
  79.             m_pOpenBleMidi->onBleMidiSystemExlusive(byteCnt, sysexData);
  80.             break;
  81.           }
  82.           default:
  83.             break;
  84.          
  85.         }
  86.         // skip one byte as we expect the next timestamp
  87.         processedBytes++;
  88.       }
  89.     }
  90. };
  91.  
  92.  
  93. OpenBleMidi::OpenBleMidi() {
  94.  
  95. }
  96.  
  97. void OpenBleMidi::registerIOpenBleMidiCb(IOpenBleMidi *p_pCb) {
  98.   m_pIOpenBleMidiCb = p_pCb;
  99. }
  100.  
  101. void OpenBleMidi::onBleMidiControlChange(uint8_t cc, uint8_t val, uint8_t ch) {
  102.   if(m_pIOpenBleMidiCb)
  103.     m_pIOpenBleMidiCb->onBleMidiControlChange(cc, val, ch);
  104.   else if(m_pSerialMonitor)
  105.     m_pSerialMonitor->println("No IOpenBleMidi callback registered so far");
  106. }
  107.  
  108. void OpenBleMidi::onBleMidiNoteOn(uint8_t note, uint8_t val, uint8_t ch) {
  109.   if(m_pIOpenBleMidiCb)
  110.     m_pIOpenBleMidiCb->onBleMidiNoteOn(note, val, ch);
  111.   else if(m_pSerialMonitor)
  112.     m_pSerialMonitor->println("No IOpenBleMidi callback registered so far");
  113. }
  114.  
  115. void OpenBleMidi::onBleMidiNoteOff(uint8_t note, uint8_t val, uint8_t ch) {
  116.   if(m_pIOpenBleMidiCb)
  117.     m_pIOpenBleMidiCb->onBleMidiNoteOff(note, val, ch);
  118.   else if(m_pSerialMonitor)
  119.     m_pSerialMonitor->println("No IOpenBleMidi callback registered so far");
  120. }
  121.  
  122. void OpenBleMidi::onBleMidiProgramChange(uint8_t pc, uint8_t ch) {
  123.   if(m_pIOpenBleMidiCb)
  124.     m_pIOpenBleMidiCb->onBleMidiProgramChange(pc, ch);
  125.   else if(m_pSerialMonitor)
  126.     m_pSerialMonitor->println("No IOpenBleMidi callback registered so far");
  127. }
  128.  
  129. void OpenBleMidi::onBleMidiSystemExlusive(uint16_t byteCnt, uint8_t* bytes) {
  130.   if(m_pIOpenBleMidiCb)
  131.     m_pIOpenBleMidiCb->onBleMidiSystemExlusive(byteCnt, bytes);
  132.   else if(m_pSerialMonitor)
  133.     m_pSerialMonitor->println("No IOpenBleMidi callback registered so far");
  134. }
  135.  
  136. void OpenBleMidi::onBleMidiConnect() {
  137.   if(m_pIOpenBleMidiCb)
  138.     m_pIOpenBleMidiCb->onBleMidiConnect();
  139.   else if(m_pSerialMonitor)
  140.     m_pSerialMonitor->println("No IOpenBleMidi callback registered so far");
  141. }
  142.  
  143. void OpenBleMidi::onBleMidiDisconnect() {
  144.   if(m_pIOpenBleMidiCb)
  145.     m_pIOpenBleMidiCb->onBleMidiDisconnect();
  146.   else if(m_pSerialMonitor)
  147.     m_pSerialMonitor->println("No IOpenBleMidi callback registered so far");
  148. }
  149.  
  150. void OpenBleMidi::setup(std::string sz_productName, HardwareSerial *pSerialMonitor) {
  151.  
  152.   m_szProductName = sz_productName;
  153.   m_pSerialMonitor = pSerialMonitor;
  154.  
  155.   if(m_pSerialMonitor)
  156.     m_pSerialMonitor->print("Initialising BLE device... ");
  157.    
  158.   BLEDevice::init(m_szProductName);
  159.   // Create the BLE Server
  160.   BLEServer *pServer = BLEDevice::createServer();
  161.   pServer->setCallbacks(new OpenBleMidiServerCallbacks(this));
  162.   BLEDevice::setEncryptionLevel((esp_ble_sec_act_t)ESP_LE_AUTH_REQ_SC_BOND);
  163.  
  164.   if(m_pSerialMonitor)
  165.     m_pSerialMonitor->println("Done!");
  166.  
  167.   if(m_pSerialMonitor)
  168.     m_pSerialMonitor->print("Creating MIDI BLE Service... ");
  169.    
  170.   // Create the BLE Service
  171.   BLEService *pService = pServer->createService(BLEUUID(MIDI_SERVICE_UUID));
  172.  
  173.   // Create a BLE Characteristic
  174.   m_pCharacteristic = pService->createCharacteristic(
  175.                       BLEUUID(MIDI_CHARACTERISTIC_UUID),
  176.                       BLECharacteristic::PROPERTY_READ   |
  177.                       BLECharacteristic::PROPERTY_NOTIFY |
  178.                       BLECharacteristic::PROPERTY_WRITE_NR
  179.                     );
  180.   m_pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
  181.   if(m_pSerialMonitor)
  182.     m_pSerialMonitor->println("Done!");
  183.  
  184.   // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
  185.   // Create a BLE Descriptor
  186.   m_pCharacteristic->addDescriptor(new BLE2902());
  187.   OpenBleMidiCharacteristicCallbacks *pBleMidiCallbacks = new OpenBleMidiCharacteristicCallbacks(this);
  188.   m_pCharacteristic->setCallbacks(pBleMidiCallbacks);
  189.   if(m_pSerialMonitor)
  190.     m_pSerialMonitor->print("Starting BLE service... ");
  191.   pService->start();
  192.   if(m_pSerialMonitor)
  193.     m_pSerialMonitor->println("Done!");
  194.  
  195.   if(m_pSerialMonitor)
  196.     m_pSerialMonitor->print("Starting advertisement... ");
  197.   BLESecurity *pSecurity = new BLESecurity();
  198.   pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);
  199.   pSecurity->setCapability(ESP_IO_CAP_NONE);
  200.   pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
  201.  
  202.   pServer->getAdvertising()->addServiceUUID(MIDI_SERVICE_UUID);
  203.   pServer->getAdvertising()->start();
  204.   if(m_pSerialMonitor)
  205.     m_pSerialMonitor->println("Done!");
  206.  
  207.   if(m_pSerialMonitor) {
  208.     m_pSerialMonitor->print("BLE service is running - device discoverable as: ");
  209.     m_pSerialMonitor->println(m_szProductName.c_str());
  210.   }
  211. }
  212.  
  213. void OpenBleMidi::sendControlChange(uint8_t control, uint8_t value, uint8_t channel) {
  214.     if(0 == OpenBleMidi::deviceConnected) {
  215.       if(m_pSerialMonitor)
  216.         m_pSerialMonitor->println("No connection to any BLE device - can't send CC message");
  217.       return;
  218.     }
  219.     uint8_t midiPacket[] = {0x80, 0x80, 0xB0 | channel, control, value};
  220.     m_pCharacteristic->setValue(midiPacket, 5); // packet, length in bytes
  221.     m_pCharacteristic->notify();
  222.   }
  223.  
  224. void OpenBleMidi::sendProgramChange(uint8_t program, uint8_t channel) {
  225.   if(0 == OpenBleMidi::deviceConnected) {
  226.     if(m_pSerialMonitor)
  227.       m_pSerialMonitor->println("No connection to any BLE device - can't send PC message");
  228.     return;
  229.   }
  230.   uint8_t midiPacket[] = {0x80, 0x80, 0xC0 | channel, program};
  231.   m_pCharacteristic->setValue(midiPacket, 4); // packet, length in bytes
  232.   m_pCharacteristic->notify();
  233. }
  234.  

hoschi
Posts: 4
Joined: Wed Jan 26, 2022 4:21 pm

Re: BLE seems to be manipulating serial data buffer

Postby hoschi » Fri Mar 18, 2022 10:04 am

After more testing, I figured out that this was a hardware issue.

I was using a DC/DC step-down converter. Those devices use coils and it is widely known that those coils may inference with any wireless technology...

I didn't know that and learned it the hard way. After switching to old-fashioned LM7805 to obtain 5V for the ESP32 I didn't get any buffer corruptions.

Regards

Who is online

Users browsing this forum: gfvalvo and 67 guests