DIY Stompboxes => Digital & DSP => Topic started by: niektb on June 19, 2021, 06:20:24 AM
Title: MIDI Foot Controller using Arduino
Post by: niektb on June 19, 2021, 06:20:24 AM
Hey all!
I thought I'd do some design documentation for the Locrius' MIDI Master: a tiny midi controller I'm working on as present for my brother! ;D Hopefully it can be of use for y'all!
Initially the point is to control a Boss RC-500, but I wanted to make it easy to adapt so I can also control my own Line6 M5 with minimal changes in the code :)
First of all, the schematic and the PCB lay-out! I made it to fit 3 footswitches inside a 1590A. To save space, I opted for a 3.5mm jack as MIDI out (there are cables for this) Very easy as you can see. I used a cheap Arduino Nano clone I bought on eBay (for like $3.50) because it gives me an easy way of programming and debugging :).
I had it custom drilled and UV printed at Tayda Electronics (if you want a drill template / Vector Graphics, I can give it to you :)). Not without hiccups though lol, I didn't vectorize the fonts: :o
Assembling the box, it all first barely so this means I engineered it perfectly 8) :P I covered the inside of the lid with isolation tape, to prevent shorts with the USB connector :)
Onto the software! I want to detect 3 switches simultaneously without any delays and with proper software debouncing, so I'm inclined to use some form of an RTOS / Schedular. I turned towards Manny Peterson's HeliOS (https://github.com/MannyPeterson/HeliOS (https://github.com/MannyPeterson/HeliOS)) which I used in the past and is quite easy to use ánd lightweight (in contrary to f.e. FreeRTOS).
I have a Github Repository here! https://github.com/niektb/Midi-Master-Software Feedback on the code is welcome! Especially if you know how to remove the code duplication ;D
In the current state (19th of June) it can detect Press, Hold and Release from 3 different switches asynchronously and prints this over serial. It can also measure the duration of the press (not sure if im going to use it, but it might be possible to do some tap tempo or expression control?). Midi functionality isn't implemented yet! As a proof of concept:
Thanks for reading! I'll update the post when I implement more software functionality! ;D
Title: Re: MIDI Foot Controller using Arduino
Post by: potul on June 21, 2021, 04:18:39 AM
Hi
Nice project, and a good present for your brother.
I haven't gone through your code, but my 2 cents is that using RTOS for simple midi messaging and button detection is an overkill. If you are familiar with it and ok working with RTOS, then move on, it will not hurt. But it's not really needed. The timing needs for button detection and midi are not really very demanding.
Mat
Title: Re: MIDI Foot Controller using Arduino
Post by: ElectricDruid on June 21, 2021, 05:08:24 AM
+1 agree with Potul. RTOS sounds like a lot to add.
I've done this type of thing using a polling loop, and even then, I had a timer to trigger the loop only every 0.5 millisecond and slow it down a bit. MIDI bytes arrive once per millisecond (roughly) even at full tilt, and you probably don't want to scan the buttons even that fast, since they'll need debouncing, and faster scanning tends to make debouncing into a bigger problem.
Alternatively, I've also done MIDI using interrupts (this is a good idea for the basic parsing of Real Time messages, which are separate from the main MIDI messages and can occur at any point). Since MIDI bytes are slow this doesn't add a crushing overhead. MIDI was designed to work on 1980's processors like the 8051, remember!
The project looks good. I love the cute little enclosure! Very nice. And well done getting everything squashing in there so neatly too.
Title: Re: MIDI Foot Controller using Arduino
Post by: niektb on June 21, 2021, 02:16:47 PM
Thanks for your kind compliments ;D
You are both correct! A full-blown RTOS would normally be way too much for handling 3 buttons and MIDI :) However, it's not thát complicated. HeliOS leans more towards scheduler territory and is non-preemptive which means that a lot of the RTOS headaches do not exist (of course, at the expensive of the 'Real-Time' aspect) ;) Look at the code! It's really not that bad ;) (except for the code duplication i mentioned earlier). It makes me totally lazy cause i can write three independent loops and totally don't care about interaction, love it! 8) ;D Also, I like to have some form of easy extensibility! Think more of it as a platform which I could easily use for other digitally controlled pedals I may or may not create at some point in the future (yep, pretty vague ;) )
unsigned long ldt1 = 0; // last debounce time unsigned long ftt1; // first tap time unsigned long frt1; // first release time bool wfr1 = false; // wait for release
unsigned long ldt2 = 0; // last debounce time unsigned long ftt2; // first tap time unsigned long frt2; // first release time bool wfr2 = false; // wait for release
unsigned long ldt3 = 0; // last debounce time unsigned long ftt3; // first tap time unsigned long frt3; // first release time bool wfr3 = false; // wait for release
/* TASKS */ void taskSW1(xTaskId id_) { int button = digitalRead(pin_sw1); // reset debouncing timer if the switch changed, due to noise or pressing: if (button != last_button1) ldt1 = millis();
// If the button state is stable for at least [debounce_delay], fire body of the statement if ((millis() - ldt1) > debounce_delay) {
// Button and last_button represent the 'unstable' input that gets updated continuously. // These are used for debouncing. // s1state is the stable input that can be used for reading button presses. if (button != s1state) s1state = button;
void taskSW2(xTaskId id_) { int button = digitalRead(pin_sw2); // reset debouncing timer if the switch changed, due to noise or pressing: if (button != last_button2) ldt2 = millis();
// If the button state is stable for at least [debounce_delay], fire body of the statement if ((millis() - ldt2) > debounce_delay) {
// Button and last_button represent the 'unstable' input that gets updated continuously. // These are used for debouncing. // s2state is the stable input that can be used for reading button presses. if (button != s2state) s2state = button;
void taskSW3(xTaskId id_) { int button = digitalRead(pin_sw3); // reset debouncing timer if the switch changed, due to noise or pressing: if (button != last_button3) ldt3 = millis();
// If the button state is stable for at least [debounce_delay], fire body of the statement if ((millis() - ldt3) > debounce_delay) {
// Button and last_button represent the 'unstable' input that gets updated continuously. // These are used for debouncing. // s3state is the stable input that can be used for reading button presses. if (button != s3state) s3state = button;
id = xTaskAdd("TASKSW1", &taskSW1); xTaskStart(id);
id = xTaskAdd("TASKSW2", &taskSW2); xTaskStart(id);
id = xTaskAdd("TASKSW3", &taskSW3); xTaskStart(id);
id = xTaskAdd("TASKSERIAL", &taskSerial); xTaskWait(id); }
void loop() { xHeliOSLoop(); }
Title: Re: MIDI Foot Controller using Arduino
Post by: ElectricDruid on June 21, 2021, 05:54:57 PM
I agree that does make things impressively simple to write. Nice. :)
Title: Re: MIDI Foot Controller using Arduino
Post by: potul on June 22, 2021, 01:59:29 AM
HelIOs looks interesting... I might give it a try in a project someday.
Regarding code duplication, the only thing that comes to my mind is that you could use a function for all the button debouncing and action triggering, where you pass the button number as a parameter, and call it from each task. You would need then to use arrays for each button related variable. Like ldt[1] instead of ldt1
Title: Re: MIDI Foot Controller using Arduino
Post by: Blackaddr on June 22, 2021, 07:55:03 AM
It sounds like you're already well into HellOs, but if somebody also has similar requirements and finds this forum thread, I suggest they look at the TeensyThreads that ships with Teensyduino.
It provides threading and basic scheduling on the Teensy without any OS, and it provides both an Arduino library style interface as well as the C++ std::thread syntax and also provides mutex support. I've used it quite a bit for separating overhead work (like low-speed comms and button/rotary encoder/potentiometer) processing onto secondary, threads with scheduled time limits to ensure my real-time audio doesn't take a hit.
Title: Re: MIDI Foot Controller using Arduino
Post by: potul on June 22, 2021, 12:45:18 PM
It sounds like you're already well into HellOs, but if somebody also has similar requirements and finds this forum thread, I suggest they look at the TeensyThreads that ships with Teensyduino.
Does this one work with typical Arduinos, or is it Teensy only?
Title: Re: MIDI Foot Controller using Arduino
Post by: Blackaddr on June 23, 2021, 06:49:24 AM
It sounds like you're already well into HellOs, but if somebody also has similar requirements and finds this forum thread, I suggest they look at the TeensyThreads that ships with Teensyduino.
Does this one work with typical Arduinos, or is it Teensy only?
It's for Teensy 3.x & 4.x.
Title: Re: MIDI Foot Controller using Arduino
Post by: niektb on June 24, 2021, 03:15:44 PM
It sounds like you're already well into HellOs, but if somebody also has similar requirements and finds this forum thread, I suggest they look at the TeensyThreads that ships with Teensyduino.
Does this one work with typical Arduinos, or is it Teensy only?
It's for Teensy 3.x & 4.x.
Aaaah yes, I'm working with an Atmega328-based arduino which is much more limited in speed and memory, so I would need something really lightweight :) I'm thinking of using a class to remove the code duplication, got that suggestion from a co-worker of mine. Not sure though, as introducing OOP would make it instantly less beginner-friendly :)
Title: Re: MIDI Foot Controller using Arduino
Post by: niektb on June 28, 2021, 02:20:59 PM
First version is working! Still have to tidy up the code (as I need to figure out some details on how to construct the classes properly :)) and implement additional features but the concept stands! (left and right are - and +, middle button goes to predefined favorite)
Title: Re: MIDI Foot Controller using Arduino
Post by: Sweetalk on July 04, 2021, 05:25:46 AM
Check out the Notes and Volts MIDI controller tutorial. The code is a really good starting point to make almost anything in a midi controller.
Title: Re: MIDI Foot Controller using Arduino
Post by: niektb on August 13, 2021, 03:08:58 PM
Update! I finished my code during the holiday but had no internet to share. So after a delay here it is! Might not be the easiest code if you're not familiar with classes but overal I think it's pretty readable :icon_mrgreen:
class Switch { public: // Constructor // @param pin Pin number of the switch Switch(const uint8_t pin);
// Destructor ~Switch();
// Function to execute in the task // @param id_ ID of the calling task // @param pre command to send to TaskMan on Switch press // @param hold command to send to TaskMan on Switch hold // @param rela command to send to TaskMan on Switch release void TaskFunction(xTaskId id_, const char pre[4], const char hold[4], const char rela[4]);
private: // Pin number of the switch const uint8_t PIN;
// Stable input that can be used for reading button int switchState; // Previous stable input state int prevSwitchState = HIGH;
// Last debounce time uint32_t lastDebounceTime; // First tap time uint32_t firstTapTime; // First release time uint32_t firstReleaseTime;
// Wait for release bool WaitForRelease;
// Raw previous button state used for debouncing int prevButtonState = HIGH; };
// reset debouncing timer if the switch changed, due to noise or pressing: if (buttonState != this->prevButtonState) { this->lastDebounceTime = millis(); }
// if the button state is stable for at least [debounce_delay], fire body of the statement if ((millis() - this->lastDebounceTime) > debounce_delay) { // buttonState and lastButtonState represent the 'unstable' input that gets updated continuously. // these are used for debouncing. // switchState is the stable input that can be used for reading button presses. if (buttonState != this->switchState) { this->switchState = buttonState; }
I'm also working on a branch that supports the Boss RC-500 looper (which is the pedal the design originally was meant for, as that's the pedal my brother has)
Title: Re: MIDI Foot Controller using Arduino
Post by: niektb on March 25, 2022, 12:48:45 PM
Just a small update, the author of the RTOS I used, has listened to my feedback and implemented a good deal of changes which means I can greatly simplify my code... Update incoming!
Title: Re: MIDI Foot Controller using Arduino
Post by: potul on March 26, 2022, 03:09:09 AM
Great! Keep us informed.
Title: Re: MIDI Foot Controller using Arduino
Post by: Sweetalk on March 27, 2022, 05:18:44 AM
Why don't you use the arduino MIDI library?, is pretty easy.
Title: Re: MIDI Foot Controller using Arduino
Post by: niektb on March 28, 2022, 04:10:17 AM
Why don't you use the arduino MIDI library?, is pretty easy.
Hmm right now it's one function with 2 statements inside, can it get much easier? ;D plus using a library would increase the memory usage :)
Title: Re: MIDI Foot Controller using Arduino
Post by: niektb on March 30, 2022, 01:24:26 PM
This is the code that you would end up with! That is, i'm still thinking about something to minimize the definition of the footswitches (that is, put the default values somewhere else/define it only once) but this how it would roughly look :) Unfortunately I don't have a device around here to test it in practice so it might contain some mistakes :icon_eek:
// SWITCH // Define struct to pass to task typedef struct footsw { int port; bool sstate; bool last_sstate; bool last_button;
unsigned long ldt; // last debounce time unsigned long ftt; // first tap time unsigned long frt; // first release time bool wfr; // wait for release char sw_press[4]; char sw_hold[4]; char sw_release[4]; } footsw;
// MIDI PARAM const uint8_t fav_preset = 7; // preset 8 uint8_t current_preset = fav_preset; const int PC = 192; //const int CC = 176;
int button = digitalRead(sw.port); // reset debouncing timer if the switch changed, due to noise or pressing: if (button != sw.last_button) sw.ldt = millis();
// If the button state is stable for at least [debounce_delay], fire body of the statement if ((millis() - sw.ldt) > debounce_delay) {
// Button and last_button represent the 'unstable' input that gets updated continuously. // These are used for debouncing. // sstate is the stable input that can be used for reading button presses. if (button != sw.sstate) sw.sstate = button;
// Check if all tasks are created correctly if (taskSW1 && taskSW2 && taskSW3 && taskMan /*&& taskSerial*/) { xTaskResume(taskSW1); xTaskResume(taskSW2); xTaskResume(taskSW3); xTaskWait(taskMan); //xTaskWait(taskSerial);
using a library would increase the memory usage :)
That does not really follow. Especially not when you're using an RTOS for convenience?
That said, if one is only sending Program Changes a couple of Serial.write() is all you need...
Touché on the RTOS lol but one could argue that because of that, memory needs to be preserved in other areas ;) But yes, the main reason is that importing a library would allow me to remove a function with two serial writes inside... Doesn't feel worth it :)