Custom firmware: lighting up buttons and banks

Okay! With help from Franco, I got some basic button feedback working: a simple app where I can select a color from a palette, and then when I press the buttons in my main grid, they are lit up in that color. I’m capturing the button presses by tapping into DigitalInputs::CheckIfChanged() and I’m setting the buttons using feedbackHw.SetChangeDigitalFeedback().

I figured I was bypassing the bank system by doing it that way, but decided to experiment a little with banks, to see if I could use it to switch between different button settings. So I went into kilowhat and added a second bank, using two buttons to switch between banks. I noticed on bank 1, my app worked, but on bank 2 it was ignored. But my button settings were not preserved across bank switches (I assume because I was bypassing the banks to set button colors directly).

Then I removed the banks, actually reset to the default configuration in Kilowhat, and now my app doesn’t work at all. In the serial monitor, I can see when I tap buttons just as before, and when I apparently go to set buttons to colors, but nothing actually lights up.

So I don’t know why my code isn’t working any more, and I really don’t understand the bank system.

I guess my questions are:

  • Why did my app stop working when I messed with the Kilowhat config & banks?
  • What’s the best way for me to set button colors. is SetChangeDigitalFeedback() a bad approach?
  • How do banks actually work? Is it a good way to let me switch quickly between diff button settings?

And maybe one more general question: should I build my app by hooking into your existing code? Overall, I just need to be able to:

  • Catch button & encoder events
  • Set button & encoder colors/values
  • Catch and send midi events
  • Tap into the timer for internal clock (already experimented with this a little)

Your code is complex enough that it’s not always obvious where best to tap in.


Okay I experimented a bit more, tried using DigitalInputs::SetDigitalValue() instead of SetChangeDigitalFeedback() but I can no longer make any of the buttons light up at all. What should I be doing?

Is it possible to set serial logging to be always on, so I don’t have to send the sysex every time I reboot?

well HELL, problem solved. Somewhere in there I reset all the buttons to fixed color mode, and I was setting a flag wrong so they didn’t turn on at all. When I put them back to valueToColor, everything worked fine.

Well that’s a week I’ll never get back! But I do better understand the code I’m modifying, especially after I put SERIALPRINTLNs everywhere.

1 Like

Hello @mike!

I am very sorry for delaying this answer

I see you solved part of the problem!

The default configuration has FIXED colors setting for all buttons and switches, and this only light up when receiving the correct MIDI messages with MAX value and turn off only when receiving the MIN value.
Value to color setting allows them to light up with different colors based on the color table shown in Kilowhat’s manual, according to which value is sent to the controller with the corresponding MIDI message.

Banks are quite complicated to work with in the code.
They are accessed via “bank shifters” which can be encoder switches or buttons.
Each encoder switch and button has an ID
Encoders are ID’d from 0 to the number of encoders the controller has, and buttons start from # of encoders +1 up to # of encoder + # of buttons
Whenever there is a press or a release, the code checks if the component is a shifter or not.
If it is a shifter, it will do the bank operation that needs to be done, and then skip to the next component, so if it is a shifter, nothing will be executed that is placed after the function CheckIfBankShifter()
The code will bring to RAM the configuration of the appropriate bank, and all the components will start behaving according to this new bank configuration.
You can disable bank functionality for each component type by uncommenting the following lines in Defines.h:

  • To catch encoder rotary events I would place my code in the function SendRotaryMessage() in the encoders.ino file
  • To catch encoder switch events I would place my code in the function SwitchAction(), also in encoders.ino
  • To play with encoder feedback, rotary or switch, use the feedbackHw.SetChangeEncoderFeedback() function.
    You can see examples all across the code, so you can set the flags correctly.
    Be careful with special configurations like Double CC and Vumeters.
  • To catch button events I would set code into the function DigitalAction() in digital.ino
  • To set digital feedback, use the feedbackHw.SetChangeDigitalFeedback() function. This one is easier than the encoders.
  • To catch MIDI events, you have several handlers in comms.ino. All of them call the ProcessMidi() function passing the data of the message needed to work with it. This function is quite time-critical, in order to be able to receive large bulks of data.
  • Tapping into the timer is also time-critical. Great power comes with great responsibility!
    Otherwise, large feedback updates won’t display correctly. You remember this from our early stages :stuck_out_tongue:

Uncomment the following line in Defines.h:

// #define WAIT_FOR_SERIAL

If you load a code with this define uncommented, as long as you don’t unplug the controller, each time it resets, it will keep the SERIAL on.
So if you send a sysex to reset, or if you upload code, it will still work.
If you unplug the controller (remove power), then you will have to send the sysex again once.

I know, it’s quite huge. It was no easy task to account to so many different possibilities of hardware combinations, and optimize for all of them.
You are making great progress though!

Again, sorry for taking so long to answer, hopefully you get some more insight from this answer.
Let me know if you have more questions!

Thanks for the reply!

I thought banks might be a good way to make updates offline and then swap in a whole set of things at once – basically, each of my sequencer modules would have its own bank and they could just update their settings and I could swap out which gets displayed. But it occurred to me that probably takes a lot of RAM and I may want to be careful with it. Anyway, I can send updates to the feedbackhw and just call Update() when I want it all to show, which should work ok.

Thanks for the tips on where to tap into the code. For digital events, I was tapping into CheckIfChanged() so I’ll move it as you suggest. It’s working okay though.

I’ve started tapping into the timer, but I know I’ll have to keep it tight there or in the MIDI response. Thanks for providing the loop timing debug function. That should come in handy.

Finally, I managed to brick my controller last night, very exciting. I was having trouble loading with clean firmware too, since it kept rebooting and it was hard to get the firmware loader to catch it long enough to put it into Kilomux. I tried both FirmwareUploader and FirmwareManager, eventually got it to work. Any tips on getting it back into bootloader mode when it’s broken and rebooting like that?

Anyway, that aside I have some basic functionality working – tracking button presses, lighting the buttons up in various colors, using the timer to count out beats. Need to hook it up to MIDI and get it doing something! But it’s been fun to get back to this and work on it again – I have a lot of sketches for the new UI.

One more question: I tried putting my Init() call in the setup() function in setup.ino but it doesn’t seem to get called. So at the moment, it’s happening the first time I catch a timer event. What happens during the boot sequence?

Aside from initializing things, I want to turn off the rainbow display and run my own animation, but I couldn’t get it to work properly. And since I’ll be leaving most of your functionality in, I was thinking about using the rainbow-on-startup setting as a sort of config. If that’s on, it will run as a normal Yaeltex controller as configured in Kilowhat; if it’s off, it will enable the Hachi logic and disable the controller stuff.

It seems like a good idea!
You have a lot of buttons, so you might not be able to use 8 banks, but 4 should fit with no problem, depending on how much data you add with your settings.

ahahha it’s like the first times that you burn your fingers soldering something or the first time you pop a polarized capacitor

When something like this happens you can use a DIN5 midi cable making a loopback connecting the MIDI IN and MIDI OUT ports.


If by any chance, after a firmware update you don’t see your controller listed as a USB device anymore, we set a hard entry to the bootloader.

For this you’ll need a MIDI cable. Hopefully, you have one of those, and if you don’t, there is still a way to make it work:

Steps for this are:

  1. Unplug USB.
  2. Plug the MIDI OUT port to the MIDI IN port with a DIN5 MIDI cable. If you don’t have a MIDI DIN5 cable, it’ll be a little harder, but it can be done. You’d need to wire like in this picture, both pins 4 and 5 on the MIDI OUT connector to its matching pin on the MIDI IN connector.
  3. Plug the USB, and you should see that the status LED cycles through different colors. This indicates bootloader mode is active.
  4. You should see the green “KilomuxBOOT” label in the firmware loader app.
  5. Send the firmware to the controller.

From Kilowhat you can disable the rainbow animation.
There is a back and forth on the setup with the AUX microcontroller, in which the MAIN reports if to the AUX if the animation is ON or OFF.
Then the MAIN waits until the AUX reports the end of the animation.
If this communication doesn’t happen, then the MAIN will wait forever and ever (unless you comment the line that waits)
This all happens in the following lines in the setup:

 // SERIALPRINTLN("Waiting for rainbow...");
// Initialize brigthness and power configuration

// Wait for rainbow animation to end 

And you could place your animation right after this code and use the following config check to know if rainbow is ON or OFF:

if ( config->board.rainbowOn == false ) {

Where did you call the function?
I’d call it right before these lines almost at the end of the setup() function:

// Print valid message

Thanks for the tips! I’m still making some updates, but I got basic internal clock and MIDI notes working, so here’s my first sequence:

Doesn’t look like much yet, but it’s coming :slight_smile:

1 Like

and yes, if you remember seeing something like this ~2 years ago, I did have Hachi working with my Yaeltex, but that was running on a Mac with the Yaeltex as a MIDI controller. firmware is MUCH cooler.

1 Like

so, any suggestions on how to approach save/load of sequence data? it looks like I should go into MEMORY_HOST.ino and add methods to allocate and read/write my own blocks of data. how do I know where I can put it? and how much persistent storage is there anyway?

Hello @mike!

The EEPROM has 64 kB.

How much you have left depends on how many components you have and how many banks you use.

Each encoder takes 56 bytes in EEPROM
Each digital button takes 28 bytes in EEPROM

You have 8 encoders and 188 digital buttons, so if my math is right, each bank on your controller uses 5712 bytes. It’s one of the heavy ones.
Add ~180 bytes for the General config area, but this one is only used once.

If you only use one bank, your EEPROM is used up to the address ~5900

Also the higher memory area near the end is used for the Remember state feature.
This one also depends on how many components you have and how many banks you use,
But the maximum size for this is ~13000 bytes.

So the area in between 6000xnumBanks and 51000 (64k - 13k) is free to use.

Thanks for the info! I just got save & load of data working in the last few days. At the moment, just hardcoding the block of memory I’m using, but I’ll want to calculate it based on banks, or at least check if there’s more than 1 bank so I don’t overwrite.

1 Like