Forum Rule: Always post complete source code & details to reproduce any issue!
Page 2 of 2 FirstFirst 1 2
Results 26 to 44 of 44

Thread: Tx only needed oSoftserial lib for MIDI on T3.6 -

  1. #26
    Member
    Join Date
    May 2018
    Location
    Portland, OR USA
    Posts
    38
    Quote Originally Posted by SteveBar View Post
    Thanks Paul...
    I modified the image and super imposed the 0/1 stream... looks like the right bits are getting thru. I'll double check my "format" of the MIDI bit stream.
    Attachment 16447
    I tried all variations and sent the buffer:
    both forward & reverse order... flipped polarity start/stop bit... padded buffer w/ 0 or 1 ...
    ...none resulted in the desired interpretation from MIDI-OX.

    Is it missing a pre-amble of some sort ???

  2. #27
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,576
    Maybe you're inverting some (or most) of the bits?

    Data bits should be logic high for 1s and logic low for 0s. Start bits should be logic low. Stop bits should be logic high. Idle time not transmitting any data should also be logic high.

    It's pretty easy to run the code and post screenshots when it's a complete program I can copy & paste into Arduino. If you want to take another try, just post another program.

  3. #28
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,155
    That isn't the correct interpretation of the bits. The idle state of the line is to be in a '1' state. The start bit is a zero. That is followed by eight data bits and then the stop bit is a '1'.

    This is the bit stream from the photo
    Code:
    01101111011100001101100000000111111
    ^        ^  ^        ^
    |        |  |        |<- Framing error - not a stop bit
    |        |  |<- Start
    |        |
    |        |<- Stop bit
    |
    |<- Start bit
    The first data byte is 01111011 (0x7B). The second one would be discarded because of the framing error - invalid stop bit.

    To figure out the bits required, do this. First write out the hex bytes and their bit string
    Code:
    90 10010000
    3C 00111100
    7F 01111111
    Now reverse the bit string because RS232 sends low order bit first

    Code:
    90 10010000   00001001
    3C 00111100   00111100
    7F 01111111   11111110
    Now add a start bit (0) on the front and a stop bit (1) on the end

    Code:
    90 10010000  0000010011
    3C 00111100  0001111001
    7F 01111111  0111111101
    Now string them together to form the required bit stream
    Code:
    000001001100011110010111111101
    You can then prepend and append as many ones as you like. NOTE that this bit string would be transmitted from left to right. Your interrupt routine is sending low order bit first so you would have to reverse this bit string before using it.

    But I think this would be a lot less prone to errors if you wrote the interrupt routine such that it transmits a byte at a time (low order bit first) and handles the start and stop bits.

    Pete
    P.S. your interrupt routine is also inverting the bits. My result above assumes that the bits aren't inverted.

  4. #29
    Member
    Join Date
    May 2018
    Location
    Portland, OR USA
    Posts
    38
    Thanks Paul for the scope pics!

    ITS WORKING! It was a couple things...
    • LSB v MSB, it turns out LSB bit first!
    • Pad the buffer with 1's
    • inverse logic, the not "!" is removed from the code
    • the bits are in forward order now, a different mask technique is used from the first code post


    Below is the updated code... plays a C4 (0x3C) note like a bat out of hell!

    I'll add these corrections back into the 8x Bit-banger code and post that in a little bit.

    Code:
    // ==================== Soft MIDI Tx ======================
          
        #define MAX_BITS 64
        #define TX_PIN   36
        
        volatile uint8_t gTxReadHead = 0;   // location of head to "bang-out" a bit from ring buffer
            
        // NoteOn(0x90) + Ch1(0x00), Note C4(0x3C), Vel127(0x7F) => 0 1001 0000 1 - 0 0011 1100 1 - 0 0111 1111 1; MSB 1st (wrong)
        // NoteOn(0x90) + Ch1(0x00), Note C4(0x3C), Vel127(0x7F) => 0 0000 1001 1 - 0 0011 1100 1 - 0 1111 1110 1; LSB first
        
        uint64_t msgNoteOn = 0b0000010011000111100101111111011111111111111111111111111111111111;
        
        // Setup bit-bang 32usec timer tic (31250 BAUD)
        IntervalTimer sMIDITx_Timer;
    
    
    // ========================================================
    
    void BitBangPin() {   // called from interrupt timer
    
          boolean theBit;
          uint64_t mask = 0x8000000000000000;
          
          theBit = (mask & (msgNoteOn << gTxReadHead));  // shift the buffer left and then AND 'mask', a 1 in the MSB
          digitalWriteFast(TX_PIN, theBit);              // write the bit to the pin from the buff location of the "read head"
          gTxReadHead = (gTxReadHead+1) % MAX_BITS;      // inc the "Read Head" of the buffer with wrapping
          
          //Serial.println(String(theBit) + " <-" + gTxReadHead);  // make timer val 32000 to slow debug output
        
      
    } // end fcn _SetPins
    
    // ========================================================
        
    void setup() {
      
       pinMode(TX_PIN, OUTPUT);
     
       //setup up Interrupt Timer     
       sMIDITx_Timer.priority(35);                // 35 = very high priority 
    
      delay(100);
       
       // make timer val 32000 for debug output
       sMIDITx_Timer.begin(BitBangPin, 32);       // 32 usec == 31250 BAUD for MIDI
    
       Serial.println("MIDI Bit Bang running...");
    }
    
    // ========================================================
    
    void loop() {
      
      // nothing in loop - fcn BitBangPins called from timer
    
    }
    Last edited by SteveBar; 04-18-2019 at 10:37 PM.

  5. #30
    Member
    Join Date
    May 2018
    Location
    Portland, OR USA
    Posts
    38
    Thanks el_supremo! I think you pointed out all the right issues! Thanks so much for taking the time. I just reposted the new MIDI bit stream test data and some small changes in the code. These seem to match your suggestions.
    Steve

  6. #31
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,155
    There is still a bug in the code. The first time it sends the message, it will not send the first character correctly.
    You initialize gTxReadHead to zero. But the interrupt routine increments this BEFORE it is used and therefore the first time into the interrupt routine you will shift msgNoteOn one bit to the left which throws away the high order bit. After that the code will work because after the word has been sent, the modulo operation will correctly change gTxReadHead to zero.

    Pete

  7. #32
    Member
    Join Date
    May 2018
    Location
    Portland, OR USA
    Posts
    38
    Thanks Pete! I edited the code above and moved the gTXReadHead inc below the write.

  8. #33
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,155
    This is the sort of thing I was suggesting. It only needs a normal MIDI byte string and its length. The interrupt routine takes care of the start and stop bits and sending the data bits low-order bit first. Tested with a T3.6 sending through pin 1 to an SY77 synthesizer. (I already had pin 1 wired up for MIDI Tx).

    Pete


    Code:
    // ==================== Soft MIDI Tx ======================
    
    // Bit-banging MIDI transmission demo by Pete (El_Supremo)
    
    #define TX_PIN   1 //36
    
    // Setup bit-bang 32usec timer tic (31250 BAUD)
    IntervalTimer sMIDITx_Timer;
    
    // C4 on
    uint8_t message1[] = {0x90, 0x3C, 0x7F};
    // C4 off
    uint8_t message2[] = {0x90, 0x3C, 0x0};
    // E4 on
    uint8_t message3[] = {0x90, 0x41, 0x7F};
    // E4 off
    uint8_t message4[] = {0x90, 0x41, 0x0};
    // ========================================================
    
    /*
    bit_state:
    0   idle    send a 1
    1   start   send the start bit (0) and go to state 2
    2   data    send 8 data bits, low order bit first, then
                  go to state 3
    3   stop    send a stop bit (1). If more data go to state 1.
                  Otherwise state 0.
    */
    volatile uint8_t bit_state = 0;
    volatile uint8_t *msg = NULL;
    volatile uint16_t nbytes;
    volatile uint8_t bit_mask = 1;
     
    // called from interrupt timer
    void BitBangPin(void)
    {
      switch(bit_state) {
      case 0: //idle
        digitalWriteFast(TX_PIN, 1);
        break;
    
      case 1: // start
        // Send the start bit and go to state 2
        digitalWriteFast(TX_PIN, 0);
        bit_state = 2;
        // reset the bit_mask to low-order bit
        bit_mask = 1;
        break;
    
      case 2: // data
        // Transmit the next data bit
        digitalWriteFast(TX_PIN, *msg & bit_mask);
        bit_mask <<= 1;
        if(bit_mask == 0) {
          // Finished the 8 data bits
          msg++;
          nbytes--;
          // Send the stop bit
          bit_state = 3;
        }
        break;
    
      case 3: // stop
        digitalWriteFast(TX_PIN, 1);
        // If there's another byte, go back to state 1
        // otherwise we're done
        if(nbytes == 0) {
          // Turn transmission off
          bit_state = 0;
        } else {
          // Back to state 1 to send the next start bit
          bit_state = 1;
        }
        break;
      }
    } // end fcn _SetPins
    
    // ========================================================
    
    void setup(void)
    {
      pinMode(TX_PIN, OUTPUT);
    
      //setup up Interrupt Timer
      // 35 = very high priority
      sMIDITx_Timer.priority(35);
    
      delay(100);
    
      // make timer val 32000 for debug output
      // 32 usec == 31250 BAUD for MIDI
      sMIDITx_Timer.begin(BitBangPin, 32);
    
      Serial.println("MIDI Bit Bang running...");
    }
    
    void send_string(uint8_t *message, uint16_t msglen)
    {
      // If a transmission is in progress just sit around
      // twiddling our thumbs
      while(bit_state)delay(10);
      // Set up the transmission
      __disable_irq();
      msg = message;
      nbytes = msglen;
      bit_mask = 1;
      bit_state = 1;
      __enable_irq();
    }
    // ========================================================
    
    void loop(void)
    {
      send_string(message1,sizeof(message1));
      delay(200);
      send_string(message2,sizeof(message2));
      delay(100);
    
      send_string(message3,sizeof(message3));
      delay(100);
      send_string(message4,sizeof(message4));
      delay(300);
    }

  9. #34
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,155
    I can't count. Note E4 is 0x40, not 0x41.

    Pete

  10. #35
    Member
    Join Date
    May 2018
    Location
    Portland, OR USA
    Posts
    38
    Thanks again Pete.
    I’m planning on writing a function called “sendMIDImsg”, that is very similar to the standard midi lib function. This fcn will do all the midi formatting/translating to a bit stream and put those bits into a ring buffer.

    On the backside of the ring buffer the “bit-bang” fcn will empty the bits to the digital pins.
    I plan on doing eight concurrent MIDI streams, one per “port” aka pin, in parallel. In fact I already have the bit-bang function working with eight parallel buffers.

    It works about 99.9% of the time. I’m still trying to figure out why on rare occasion a missed formed bitstream comes out, when I pound on the buttons that change the device mode and does screen updates. It must be because some other lib is turning off the interrupts. Btw The source code I posted before was just a snippet from a much larger code base. I plan on going through all the libraries today and I’ll post a list. Maybe somebody has some insights into how “well behaved” each one of these are w.r.t. interrupt timers.

    I really appreciate you participating in this thread. I’m not a particularly strong programmer so any comments on making the code more efficient would be awesome. I’ll post the source code here in stages as it becomes available.
    Thanks
    Steve
    Last edited by SteveBar; 04-19-2019 at 04:10 PM.

  11. #36
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,576
    Quote Originally Posted by SteveBar View Post
    so any comments on making the code more efficient would be awesome.
    Gonna repeat my earlier suggestion. Yeah, it involves using 3 Teensy 3.6 boards, but it would be so much more efficient!

  12. #37
    Member
    Join Date
    May 2018
    Location
    Portland, OR USA
    Posts
    38
    3 ARM procs a might be UART efficient, but maybe not cost efficient. :-)

    That reminds me, our original plan was to use the T4.0. How close is that? We'd really rather target that platform.

    The project uses the following libs:

    SPI.h, ssd1351.h + Adafruit_GFX.h + gfxfont.h, USBHost_t36.h, MIDI.h (Serial & USB Host & USB Device), & FastLED.h

    -Steve
    Last edited by SteveBar; 04-23-2019 at 01:46 AM.

  13. #38
    Member
    Join Date
    May 2018
    Location
    Portland, OR USA
    Posts
    38
    Quote Originally Posted by SteveBar View Post
    It works about 99.9% of the time...
    Steve
    UPDATE:
    Issue: every ~6sec there is a corrupt message coming out of the bit-banged MIDI out pin, ONLY under intense conditions:
    • Updating 26 NeoPixels every 125ms (typically only updated once per user change) and,
    • Updating the OLED display every 125ms (typically only updated once per user change) and,
    • Reading MIDI from 2 UART ports (1/8th notes @ 300 BPM) and,
    • Routing MIDI messages from both MIDI ports to all MIDI out ports (6 UART + 8 bit-banged + 4 USB host + USB device)


    Isolating the NeoPixel driver fixed this issue. I did some homework and narrowed the timer interrupt conflict to the FastLED driver interrupt. There is a great article here. Looks like 3 wire "NeoPixel" LED strips are similar to serial in that there is no clock, so the pulse width of the data signal must be exact (30usec in the NeoPix case). In the article Daniel Garcia talks about the driver for 3-wire LED strips and for the Teensy there is an option to allow interrupts to occur briefly between NeoPixel data-stream state changes. This is why I think it works most of the time.

    We are going to try 4-wire clocked LEDs (APA102) on SPI. I believe clocked data streams can "live with" various clock pulse widths so they don't require exact clock pulse widths (hence no interrupts used) and are more resilient to interrupt delays from other libs.

    BTW the project uses the following libs:
    SPI.h, ssd1351.h + Adafruit_GFX.h + gfxfont.h, USBHost_t36.h, MIDI.h (Serial & USB Host & USB Device), & FastLED.h

    Steve

  14. #39
    Member
    Join Date
    May 2018
    Location
    Portland, OR USA
    Posts
    38
    Here's the next version of the "8x Soft MIDI Tx" test code.
    This code snippet sends MIDI Note-On messages every 62.5 msec (120 BMP - 1/8th notes) to all 8 Pins with notes C4, E4, G4, C5, E5, G5, C6, C3.
    Steve

    Code:
          
        uint8_t gNumPorts = 1;                    // number of soft MIDI TX ports
        uint8_t gTxPin[8] = {0,0,0,0, 0,0,0,0};   // MIDI Tx Pin(s) def
            
        const uint8_t MaxBits = 64;                             // 64 bits (using uint64_t var)
        volatile uint8_t gTxReadHead[8] = {0,0,0,0, 0,0,0,0};   // position to head in ring buffer
        
        // test data
        // NoteOn(0x90) + Ch1(0x00), Note C4(0x3C), Vel127(0x7F) => 0 0000 1001 1 - 0 0011 1100 1 - 0 1111 1110 1; LSP first
    
        uint64_t msgBUFF[8] = {
            
            0b0000010011000111100101111111011111111111111111111111111111111111,  // C4 0x3C
            0b0000010011000000010101111111011111111111111111111111111111111111,  // E4 0x40
            0b0000010011011000010101111111011111111111111111111111111111111111,  // G4 0x43
            0b0000010011000010010101111111011111111111111111111111111111111111,  // C5 0x48
    
            0b0000010011000110010101111111011111111111111111111111111111111111,  // E5 0x4C
            0b0000010011011110010101111111011111111111111111111111111111111111,  // G5 0x4F
            0b0000010011000101010101111111011111111111111111111111111111111111,  // C6 0x54
            0b0000010011000001100101111111011111111111111111111111111111111111   // C3 0x30
         };
    
        const uint64_t g64mask = 0x8000000000000000;
        
        // Setup bit-bang 32usec timer tic (31250 BAUD)
        IntervalTimer sMIDITx_Timer;
    
    
    // ======================================================================
        
    void setup() {
    
        // init sMIDITx
        sMIDITxPorts();
    
        Serial.println("MIDI Bit Bang running...");
       
    }
    
    // ======================================================================
    
    void loop() {
      
      // nothing in loop - fcn BitBangPins called from timer
    
    }
    
    // ======================================================================
    
    void sMIDITxPorts() {  // setup the pins and interrupt timer 
    
        gNumPorts = 8;    // the number of MIDI TX ports defined
        gTxPin[0] = 35;   // PIN used for bit banging the MIDI Tx
        gTxPin[1] = 36;   
        gTxPin[2] = 37;   
        gTxPin[3] = 38;   
        gTxPin[4] = 39;   
        gTxPin[5] = 28;   
        gTxPin[6] = 29;   
        gTxPin[7] = 30;   
    
        // setup the soft MIDI Tx PINs
        for (byte i=0; i<gNumPorts; i++) pinMode(gTxPin[i], OUTPUT);
        
       //setup up Interrupt Timer     
       sMIDITx_Timer.priority(10);             // 10 = very high priority 
       sMIDITx_Timer.begin(BitBangPins, 32);   // 32 usec == 31250 BAUD for MIDI
    
        
    } // end fcn Constructor sMIDITxPorts
    
    
    // ======================================================================
    
    void BitBangPins() {   // called from interrupt timer
      
      boolean theBit;
      sMIDITx_Timer.update(32); // reset int timer tic to 32 usec
      
      for (byte i=0; i<gNumPorts; i++) { // for each Pin...
          
          theBit = ( g64mask & ( msgBUFF[i] << gTxReadHead[i] ));   // shift the buffer left and then AND 'mask', a 1 in the MSB
          digitalWriteFast( gTxPin[i], theBit);                     // write the bit to the pin from the buff location of the "read head"
          gTxReadHead[i] = (gTxReadHead[i]+1) % MaxBits;            // inc the "Read Head" of the buffer with wrapping
    
          // used to set BPM while debugging... 
          if (gTxReadHead[7] == 31) sMIDITx_Timer.update(62500);    // 120BMP 1/8th notes - set int timer tic to 62.5 msec
                
      } // end for
      
    } // end fcn _SetPins
    
    
    // ======================================================================
    
    //NOT USED YET...         
    void send_sMIDITxMsg(byte MSGtype, byte data1, byte data2, byte ch, byte port) {
    
        // TODO: in bound MIDI messages must be formatted into bit streams and placed into the ring buffers (based on port) 
      
    } // end fcn send_sMsg
    
    
    // ======================================================================

  15. #40
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,155
    I forgot to ask you this before: why do you use 64-bits? The longest standard MIDI message is 3 bytes which easily fits into 32 bits even with the added start and stop bits. System Exclusive messages, if you use them, can be any number of bytes and would have to be handled specially anyway. So uint32_t would not only be more compact but also faster to send because you wouldn't end up sending 32+ bits of '1' between each message.

    Pete

  16. #41
    Member
    Join Date
    May 2018
    Location
    Portland, OR USA
    Posts
    38
    Quote Originally Posted by el_supremo View Post
    I forgot to ask you this before: why do you use 64-bits? The longest standard MIDI message is 3 bytes which easily fits into 32 bits even with the added start and stop bits. System Exclusive messages, if you use them, can be any number of bytes and would have to be handled specially anyway. So uint32_t would not only be more compact but also faster to send because you wouldn't end up sending 32+ bits of '1' between each message.

    Pete
    Iím using 64 bits now with the presumption that I would be feeding the ring buffer from the send_sMIDITxMsg function in real time, in the next version. This version is only using the 64bit predefined variables as test data. Also I thought it would be good for the buffer to be able to hold two 3byte messages. Iíve never done this before so Iíll need to do some experimenting. I was actually worried that it isnít big enough :-)
    Plus thereís so much memory space in the T3.6 that I really donít have to worry about 32 bytes (4x8).

    As far as SysEx goes Iím not sure at all how I might handle that, and Iím kind of pushing it out of my brain until I get a handle on the non-SysEx version. :-)
    -Steve

  17. #42
    Member
    Join Date
    May 2018
    Location
    Portland, OR USA
    Posts
    38
    Yikes... 64 bit operations aren't always working!!! i.e. bit-shift <<, >> and print. Oy!
    Maybe I will be using a 32 bit ring buffer...
    - Steve

  18. #43
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,155
    64 bit operations aren't always working!!!
    Can you post example code which demonstrates it?

    Pete

  19. #44
    Member
    Join Date
    May 2018
    Location
    Portland, OR USA
    Posts
    38
    HI All,
    Here's an update:

    I got the whole thing working and it is really cool. Well, not SysEx yet, but all the 1,2,& 3 byte MIDI messages including the real time messages: Clk, Start, Stop, & Continue (F8,FA,FB,&FC). I will post the code when I get some time, but it is tightly integrated into a much bigger project so it may take a bit of prodding. My intentions are to share it with the community.

    A note on interrupts... since the MIDI timing is asynchronous and the data timing is critical it is susceptible to other higher priority interrupts. I had to use switch my NeoPixels (3-wire) for APA107 (4-wire) LED strip. The 3-wire LED strips are asynchronous, which means since there is no "clock signal wire" there must be rock solid data timing. The 4-wire LED strips are synchronous, which means there is a "clock signal wire" as well as a "data signal wire".

    BTW... in researching this topic I realized that calling the 4th wire a "clock" is a bit of a misnomer because the 4th wire is not really a clock. It is simply a wire that carries a message aka trigger (when the signal transitions from low to high or visa-versa) that tells the receiver that a new piece of data is ready on the data wire, so the data timing is not important. When the receiver gets a trigger it reads the data line. This is why using a 4-wire LED strip driver doesn't need to use interrupt timers. When FastLED.h is called with LED_TYPE = APA102 there are no interrupts used.

    Steve

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •