PIC based MIDI controller?

Started by tempus, March 28, 2018, 03:24:44 PM

Previous topic - Next topic

tempus

Hey all;

I'm looking into designing and building a PIC based MIDI controller that will send MIDI messages to a laptop to switch on and off different VST effects. Surprisingly, I'm having a very difficult time finding any info on any PIC based MIDI controller. Has anyone made one, or can anyone point me in the right direction? I have some idea of what to do hardware wise, but seeing some sample code would be a huge help.

Thanks


potul

I built a PIC based MIDI foot controller long ago. But it was programmed in ASM. What PIC are you planning to use? Do you need to program it in C?

ElectricDruid

Ermmm...google? There's a ton of stuff on PIC MIDI controllers, surely?

That search query "PIC MIDI controllers" brings up a lot of useful results. What makes you say this is very difficult?

Tom

tempus

Ive got a couple of 16f737s around, and I figured I'd use one of them. I've programmed my previous PIC projects in asm.

ED, I have searched it up, but so far have found few useful results. There are a lot more things for Arduino, and a lot of the PIC articles just kind of explain how to accomplish it without going into any useful detail. Do you have a link that would be helpful?

Thanks

markseel

#4
Disclaimer: Possible conflict of interest in this post since I am the creator of FlexFX and therefore may not be the most objective person to respond ...

Look at FlexFX the module.  It's a small 1"x1.4" module with a USB connector on it.  It has pins for power, ground, I2C, UART, and I2S (for audio).  Can be USB powered or self powered.  I2C and UART pins can be used for GPIO to sense foot switches or you can use I2C to interface to a port expander that interfaces to foot switches.  FlexFX was intended to support stomp box and high channel count USB/I2S audio applications with lots of DSP processing power ... of which you need none of.

But you can still use a FlexFX module and the free SDK (on GitHub) to sense switches and send MIDI to the host computer over USB.  You'd then have a USB MIDI controller with GPIO's.  The host computer would then see a MIDI device when you plug this in via USB.  If interested I can write up a quick code example - this might be a good example to add to the GitHub doco.

FlexFX will also have MIDI beat clock, MTC and MPC generation support.

https://github.com/markseel/flexfx_kit/blob/master/README.md

BTW the production of FlexFX modules, potentiometer/ADC/DAC main boards (to plug module into), and custom stomp box cases are being KickStarted in a couple of weeks.

ElectricDruid

Hi Tempus,

I've got some example code. It's for a set of MIDI bass pedals that I got off a dead electric organ and turned into a midi pedalboard.
I was going to post it here, but it exceeds the 20K message limit (didn't know about that til now!). Email me  (link below) and I'll send you a copy:

https://electricdruid.net/contact/

It uses a buffer to store outgoing MIDI bytes, and an interrupt pulls another byte out of the buffer when the last one has sent. That way the main code isn't held up waiting for a MIDI message to send. You can quickly dump a whole lot of stuff into the buffer and you're good to go. It will continue to send in the background. It uses 16F73 which isn't a million miles for the chip you suggested. If you're handy with ASM, you should be able to pull out the MIDI bits you need and chuck the rest away.

Tom

potul

I have a MIDI controller project using a 16f88

check it out  here:

http://www.diystompboxes.com/smfforum/index.php?topic=91291.0

You can see how the basic MIDI is done in the code.

Mat

tempus

You guys are awesome (and much more adventuresome than me)! Thank you so much for the help. Seeing the sample code clears up a lot of things and gets me on my way nicely. Your projects are much more involved and complex than mine, but I think I've figured some things out:

All I want to do is send a MIDI program change message to my laptop (via a recording interface), so I don't really need most of the stuff you guys have in your designs do I? Could I get away with just:

         movlw   0xC0   ;status byte = program change on Ch1
         movwf   TXREG ;send status program change on Ch1 to MIDI device
         call    senddla   ;wait 35ms for byte to be sent
         movlw   0x00   ;program #0
         movwf   TXREG   ;send prog change to #0 to MIDI device

?

I have the delay in there because my osc is running at 4MHz, so I figure the first byte won't be sent by the time the 2nd byte gets there without it. Oh, and I'm pretty sure I sorted out how to set up my UART.

Thanks again

ElectricDruid

Yes, if you don't mind hanging about waiting for the bytes to send, something like that will work.

One way to minimise the wait is to do a polled loop rather than a fixed delay of X msecs. You should be able to test either TXIF (in PIR4) or TRMT in TXSTA, with slightly different but equivalent results.
So something like this:

      movlw   0xC0   ;status byte = program change on Ch1
      movwf   TXREG ;send status program change on Ch1 to MIDI device
<memory page?>
      btfss    PIR4, TXIF      ; Has TXREG cleared yet?
      goto.    $-1
      ; Yes, it's clear, so send the next byte
         movlw   0x00   ;program #0
         movwf   TXREG   ;send prog change to #0 to MIDI device

HTH,
Tom

tempus

#9
Hey Tom

Thanks again for your ongoing help.

That's definitely a better way to do it. A couple of questions though:

1. what do you mean by <memory page?>
2. What does $-1 do? I've seen this command in various code examples, and searches don't turn anything up - I figured this one out - it just goes to the previous line, so TXIF will continue to be tested until it is actually clear, yes?
3. Does the TXREG register clear once the byte has been sent (I'm assuming yes)?

Thanks

ElectricDruid

Quote from: tempus on March 31, 2018, 09:07:38 AM
Hey Tom

Thanks again for your ongoing help.

That's definitely a better way to do it. A couple of questions though:

1. what do you mean by <memory page?>

I mean that I can't remember off the top of my head what memory page PIR4 is in, and you may not be in the correct page when you enter the code, in which case you'd have to change it. I'm talking about "bank switching" which is another way of saying the same thing. The <memory page?> was just a reminder to think about which bank you're in.
The code I sent you included some macros for bank switching - just type "Bank0" or "Bank1" or whatever and the compiler pastes in the correct code to set up the RP0 and RP1 STATUS flags.

Quote
2. What does $-1 do? I've seen this command in various code examples, and searches don't turn anything up - I figured this one out - it just goes to the previous line, so TXIF will continue to be tested until it is actually clear, yes?
Yep, exactly. In some situations this can be dangerous because you can finish up in a loop which never ends. But if you're reasonably sure that the condition *has* to occur sooner or later, it's safe enough. In this case, we can be sure the byte will send - we just don't know if it will be received!

Quote
3. Does the TXREG register clear once the byte has been sent (I'm assuming yes)?
I don't know, and it doesn't matter. What we do know is that once it's been sent, we can safely stick another byte in there ready for sending. Whether it gets zeroed or not in between isn't really important.

Quote
Thanks

You're welcome.

HTH,
Tom

Blackaddr

Making a MIDI controller using a Teensy LC would be very easy way since you've got the Arudino MIDI library at your disposal, as well as Debounce and Rotary Encoder libraries ,not to mention analog libraries for reading pot values.

Disclosure, I'm the creator of the Teensy Guitar audio board, so I'm biased as well in favour of the Teensy platform. The Teensy LC does not work with the Audio library (no sound procesing) but it's ideal for a MIDI controlller IMO.
Blackaddr Audio
Digital Modelling Enthusiast
www.blackaddr.com

ElectricDruid

I totally agree that using a Teensy LC or any other variant is a very easy way to make a MIDI controller and there are many available libraries that make this simple.

However...I can't really go along with such a course of action because it's COMPLETE OVERKILL!! Even the Teensy LC is a 32-bit, 48MHz processor with 62K flash and 8K RAM.
The 16F767 you were talking about is a 20MHz 8-bit chip with 8KWord of Flash and 368 *bytes* of RAM. And it's more than adequate for the task. If you don't believe me, check out the processors in any synth from the 1980's and then get an idea of what they made them do!! About ten times what we'd expect nowadays, in short.

So, yeah. One solution to any problem of this sort is "throw more computing power at it" and often that's the easiest solution. It may not be cheapest, but unless you're building 10,000 or 100,000 units, how much do you care about the extra $5? Probably not much. Multiply up and it becomes a big deal, but that's not our situation so go for your life.

Sorry, not trying to be a downer, and specifically not trying to diss the excellent work of those in the Teensy world (Blackaddr amongst them). I'm just a big fan of "doing more with less", rather than "doing less with more". I'm old-skool, I guess - I am...learned assembly on 6502.

Tom


potul

Quote from: ElectricDruid on March 31, 2018, 06:21:47 PM
I totally agree that using a Teensy LC or any other variant is a very easy way to make a MIDI controller and there are many available libraries that make this simple.

However...I can't really go along with such a course of action because it's COMPLETE OVERKILL!! Even the Teensy LC is a 32-bit, 48MHz processor with 62K flash and 8K RAM.
The 16F767 you were talking about is a 20MHz 8-bit chip with 8KWord of Flash and 368 *bytes* of RAM. And it's more than adequate for the task. If you don't believe me, check out the processors in any synth from the 1980's and then get an idea of what they made them do!! About ten times what we'd expect nowadays, in short.

So, yeah. One solution to any problem of this sort is "throw more computing power at it" and often that's the easiest solution. It may not be cheapest, but unless you're building 10,000 or 100,000 units, how much do you care about the extra $5? Probably not much. Multiply up and it becomes a big deal, but that's not our situation so go for your life.

Sorry, not trying to be a downer, and specifically not trying to diss the excellent work of those in the Teensy world (Blackaddr amongst them). I'm just a big fan of "doing more with less", rather than "doing less with more". I'm old-skool, I guess - I am...learned assembly on 6502.

Tom
I agree, but lately I find myself going the arduino route as well for this kind of projects. You can find arduino nano clones for around 3 $, and it's very convenient for a number of reasons:
-Tons of libraries available
-Easy programming
-No need for a programmer
-Power regulator included

On the downside, I still haven't found a way to emulate it that I like. I always develop and debug my software in an emulator, and MPLAB was my friend for PICs.

BTW, teensy is really an expensive overkill here. There are much cheaper small arduinos that can do the job

tempus

After having double (and triple checked) my code, I have been unsuccessful in sending any MIDI data. I've changed things slightly so that now I'm attempting to send button press messages using CCs (the software that I'm trying to control via MIDI accepts CC data as inputs). Here's an example of my switching code:

         movlw   0xB1         ;status byte = CC on Ch1
         movwf   TXREG          ;send status CC on Ch1 to laptop
         btfss   PIR1, TXIF      ;has TXREG cleared yet?
          goto    $-1            ;yes, it's clear, so send the next byte
         movlw   0x00         ;CC 0
         movwf   TXREG         ;send CC 0 to laptop
         btfss   PIR1, TXIF      ;has TXREG cleared yet?
          goto    $-1            ;yes, it's clear, so send the next byte
         movlw   0x7f         ;CC value 127
         movwf   TXREG         ;send CC value 127 to laptop

Does it look OK? Also, I noticed this when reading through the 16f737 data sheet:

"The TXREG register is loaded with data in
software. The TSR register is not loaded until the Stop
bit has been transmitted from the previous load. As
soon as the Stop bit is transmitted, the TSR is loaded
with new data from the TXREG register (if available)."

What is a Stop bit and how do I transmit one? Is it already present in my MIDI command/data bytes?

Also, when I was testing my wiring, I discovered that there was +5v sitting on my TX pin. Is that normal? Referring again to the datasheet, I found:

"Bit SPEN (RCSTA<7>) and bits TRISC<7:6> have
to be set in order to configure pins RC6/TX/CK and
RC7/RX/DT as the Universal Synchronous
Asynchronous Receiver Transmitter."

I have this in my code:

   movlw   0x00         ;load w with 0's
         movwf   TRISC         ;make PORTC outputs

which should make PORTC all outputs, but it appears that I have to do something else to enable the TX pin, but I'm not sure what. I know that TRISC<7> means "TRISC bit 7, but what does TRISC<7:6> mean?

Thanks

ElectricDruid

Quote from: tempus on April 02, 2018, 10:41:51 AM
Here's an example of my switching code:

         movlw   0xB1         ;status byte = CC on Ch1
         movwf   TXREG          ;send status CC on Ch1 to laptop
         btfss   PIR1, TXIF      ;has TXREG cleared yet?
          goto    $-1            ;yes, it's clear, so send the next byte
         movlw   0x00         ;CC 0
         movwf   TXREG         ;send CC 0 to laptop
         btfss   PIR1, TXIF      ;has TXREG cleared yet?
          goto    $-1            ;yes, it's clear, so send the next byte
         movlw   0x7f         ;CC value 127
         movwf   TXREG         ;send CC value 127 to laptop

Does it look OK?

Yes, it looks ok. PIR1 and TXREG are both in Bank0, so that should be fine.

Quote
Also, I noticed this when reading through the 16f737 data sheet:

"The TXREG register is loaded with data in
software. The TSR register is not loaded until the Stop
bit has been transmitted from the previous load. As
soon as the Stop bit is transmitted, the TSR is loaded
with new data from the TXREG register (if available)."

What is a Stop bit and how do I transmit one? Is it already present in my MIDI command/data bytes?
Yes. you don't have to send it yourself. As long as the UART is configured correctly for MIDI, it gets sent as part of the byte.


QuoteAlso, when I was testing my wiring, I discovered that there was +5v sitting on my TX pin.
Is that normal?

Yes.

Quote
Referring again to the datasheet, I found:

"Bit SPEN (RCSTA<7>) and bits TRISC<7:6> have
to be set in order to configure pins RC6/TX/CK and
RC7/RX/DT as the Universal Synchronous
Asynchronous Receiver Transmitter."

I have this in my code:

   movlw   0x00         ;load w with 0's
         movwf   TRISC         ;make PORTC outputs

which should make PORTC all outputs, but it appears that I have to do something else to enable the TX pin, but I'm not sure what. I know that TRISC<7> means "TRISC bit 7, but what does TRISC<7:6> mean?

It means "TRISC bits 6 through 7" - so both of them. <3:6> would be bits 3,4,5, and 6.

The SPEN bit ("EN" is an enable of some kind; "Serial Port" in this case) of the RCSTA register (one of the set-up/status registers for the UART) needs to be set too. Is that covered in your UART config code?

Sounds to me like you're very close. You've probably got one small bug in the UART config code.

Do you have a MIDI monitor so that you can see if you're sending valid bytes, even if not valid MIDI messages? I once struggled for ages with some MIDI code only to eventually connect it to a MIDI monitor and discover that it had been sending bytes correctly all along, but because I'd got a stray byte inversion command in the code somewhere (so 255 was being turned into 0) it wasn't producing any valid MIDI messages at all.

HTH,
Tom

tempus

Thanks again for your reply ED.

I do have a MIDI monitor, which shows no MIDI activity at all when I try to send a command. Searching more through the datasheet, I found this:

"When setting up an Asynchronous Transmission,
follow these steps:
1. Initialize the SPBRG register for the appropriate
baud rate. If a high-speed baud rate is desired,
set bit BRGH (see Section 11.1 "AUSART
Baud Rate Generator (BRG)").
2. Enable the asynchronous serial port by clearing
bit SYNC and setting bit SPEN.
3. If interrupts are desired, then set enable bit TXIE.
4. If 9-bit transmission is desired, then set transmit
bit TX9.
5. Enable the transmission by setting bit TXEN
which will also set bit TXIF.
6. If 9-bit transmission is selected, the ninth bit
should be loaded in bit TX9D.
7. Load data to the TXREG register (starts
transmission).
8. If using interrupts, ensure that GIE and PEIE
(bits 7 and 6) of the INTCON register are set."

I had already done these things, but does the order they are executed in matter? Here's what I've got in my code:

start       bsf      STATUS,RP0      
         bcf      STATUS,RP1      ;bank 1
         bsf    OSCCON,IRCF2    ;set int osc 110=4Mhz   
         bsf    OSCCON,IRCF1         
         bcf    OSCCON,IRCF0
         movlw   0x01         ;load w with 00000001     
         movwf   SPBRG         ;Set Transmit Baud Rate to 31.25KHz
         movlw   0x22         ;load w with 00100010     
         movwf   TXSTA         ;Set up to transmit 8-bit, asynch, lo-speed, BRGH lo      
         movlw   0x0F         ;conifgure all pins as
         movwf   ADCON1         ;   digital inputs
            bcf      OPTION_REG,7   ;enable PORTB pullups
         movlw   0x00         ;load w with 00000000
         movwf   TRISA         ;make PORTA outputs
         movlw   0xFF         ;load w with 1's
         movwf   TRISB         ;make PORTB inputs   
         movlw   0x60         ;load w with 01100000
         movwf   TRISC         ;make PORTC outputs, enable TX and RX (pin 17 and 18)
         bcf      STATUS,  RP0   ;bank 0
         movlw   0x80         ;load w with 10000000     
         movwf   RCSTA         ;enable UART
         movlw   0x00         ;load PORTA with 00000000
         movwf   PORTA         ;turn all LEDs off

Anything out of line there?


ElectricDruid

Hi Tempus,

Ok, let's have a look.

First: Have you *actually got* a 4MHz clock? I usually do a LED flash "hello world" to test this. Something like this:

FlashLED:
bsf   TEST_LED
nop
nop
nop
bcf  TEST_LED
nop
nop
goto  FlashLED


4MHz is a 1MHz instruction cycle, so 8 instructions gives us a 125KHz flash rate. Worth a check.

Next, you're using the low speed baud rate mode, so we have:

   Baud Rate = FOSC/(64(X + 1))

You stick 1 into SPBRG, so X=1, so:

   Baud rate = 4MHz/128 = 31250

So that looks fine.

Setting up the ports...Aha!

RC7 is RX, RC6 is TX. You've got those the wrong way around, Input should be Output, and vice versa.

Try that and see if it springs to life.

HTH,
Tom

tempus

Hey Tom;

Thanks again for your reply.

OK, so I loaded PORTC with 11000000, which had no effect on the results. Also, the datasheet says:

"Bit SPEN (RCSTA<7>) and bits TRISC<7:6> have
to be set in order to configure pins RC6/TX/CK and
RC7/RX/DT as the Universal Synchronous
Asynchronous Receiver Transmitter. "

Does that mean the both TRISC bits 7 and 6 need to be ones (which would make them both inputs), or do they mean set bit 7 to 1 and bit 6 to 0 (thus making 7 an in and 6 an out)? You would think that the TX bit would need to be configured as an output and RX an input....

Great idea to check the FOSC - I never would have thought of that technique. Checking with my scope I get a frequency of 110.1kHz, which would give 110.1 x 8 = 880.8 x 4 = 3.5232MHz. This would give us a baud rate of 3.5232MHz/128 = 27525. Is this close enough, or should I do some oscillator tweaking? Also, can we trust that my digital scope is actually giving an accurate reading of the flash rate? I messed around with OSCTUNE and got it up to 125KHz, but it had no effect on the results

On another note, when I build the project in MPASM, I get the following error messages:

...25 : Register in operand not in bank 0.  Ensure that bank bits are correct.

26 : Register in operand not in bank 0.  Ensure that bank bits are correct.

27 : Register in operand not in bank 0.  Ensure that bank bits are correct.


etc

The line numbers are all commands executed on Bank 1 (which is set in the 1st 2 lines of code). I'm assuming that MPASM is in err, since we've demonstrated that the OSC has been set at 4MHz?

ElectricDruid

Quote from: tempus on April 03, 2018, 06:48:48 PM
Hey Tom;

Thanks again for your reply.

OK, so I loaded PORTC with 11000000, which had no effect on the results. Also, the datasheet says:

"Bit SPEN (RCSTA<7>) and bits TRISC<7:6> have
to be set in order to configure pins RC6/TX/CK and
RC7/RX/DT as the Universal Synchronous
Asynchronous Receiver Transmitter. "

Does that mean the both TRISC bits 7 and 6 need to be ones (which would make them both inputs), or do they mean set bit 7 to 1 and bit 6 to 0 (thus making 7 an in and 6 an out)? You would think that the TX bit would need to be configured as an output and RX an input....

Exactly! They mean that "you need to set the bits" not "the bits need to be set". You need TRISC set with bit 7 to 1/Input and bit 6 set to 0/output. With Bit 6 set to 1, you've got TX set as an input and the UARTs output can't reach the pin - nothing will happen.
Also, you mean you changed TRISC, not PORTC. Messing with PORTC won't do anything useful.


Quote
Great idea to check the FOSC - I never would have thought of that technique. Checking with my scope I get a frequency of 110.1kHz, which would give 110.1 x 8 = 880.8 x 4 = 3.5232MHz. This would give us a baud rate of 3.5232MHz/128 = 27525. Is this close enough, or should I do some oscillator tweaking? Also, can we trust that my digital scope is actually giving an accurate reading of the flash rate? I messed around with OSCTUNE and got it up to 125KHz, but it had no effect on the results

No, that probably means that I forgot that "goto" uses two instruction cycles, which makes that loop nine instructions. 1MHz/9 = 111KHz.
So your 'scope is close enough.

Quote
On another note, when I build the project in MPASM, I get the following error messages:

...25 : Register in operand not in bank 0.  Ensure that bank bits are correct.

26 : Register in operand not in bank 0.  Ensure that bank bits are correct.

27 : Register in operand not in bank 0.  Ensure that bank bits are correct.

etc

The line numbers are all commands executed on Bank 1 (which is set in the 1st 2 lines of code). I'm assuming that MPASM is in err, since we've demonstrated that the OSC has been set at 4MHz?

Yep, it's useful to check that these lines match up with the ones where you've actually set Bank1. This is a "warning" not an "error", so its just a reminder and doesn't stop the code compiling. If it gets to bugging you, you can disable the warnings by sticking the following directive at the top of the code:

   Errorlevel -302

I usually put this under the usual "INCLUDE <p16fxxx.inc>" line.

HTH,
Tom