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

Status
Not open for further replies.
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.
View 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 ???
 
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. :)
 
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.
 
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:
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
 
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
 
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);
}
 
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:
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:
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
 
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


// ======================================================================
 
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 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
 
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
 
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
 
Status
Not open for further replies.
Back
Top