Firmware implementation notes - jkominek/piano-conversion GitHub Wiki

Inter-board protocol

The main board and the ADC boards communicate over 3Mbit/s full duplex RS485 links. While ADC boards will not normally need nearly that much bandwidth, maxing out the data rate was chosen to reduce communication latency. As a convenient side effect, it makes a significant amount of bandwidth available during calibration activities.

Initial setup

The main board will, after powering up the ADC board, check to see if the board is in factory bootloader mode. It will take actions as necessary to upgrade the ADC board firmware, and then command it to boot said firmware. Communications during this time will be covered by ST AN3155.

Framing

After successful boot, all messages will use HDLC asynchronous framing. This ensures both ends can determine message boundaries with ease, and detect errors.

Message types

This represents what we'll call version 0 of the protocol. There is no need for forwards/backwards compatibility, as the main board should be able to reflash the ADC boards to make them compatible with it. We'll try to avoid assigning message codes with a high bit of 1, to leave a stable space for anyone doing custom things in their firmware.

Message structure should probably be something like:

  • Frame boundary - 0x7E
  • Message code - one byte, see below
  • Message ID - two bytes, incrementing integer ID for this channel/direction
  • Variable length data as appropriate for the message
  • Parity byte, XOR everything after the frame boundary
  • Frame boundary - 0x7E

Many messages from the ADC board should contain the clock, which would just be a 32 bit count of systicks, wrapping around. This will let the main board adjust for any variation in the ADC board oscillator, since it just uses the built-in RC oscillator. It may either take those values and just apply a timing correction on its end, or compute adjusted low pass filter coefficients and send them back to the ADC board.

name sent by Code description
ack Both 0x00 Positive response to a command. Will include the message ID being ack'd.
nack Both 0x01 Negative response. Will include the message ID being nack'd.
reboot Main 0x02 ADC board to reboot. Should ack first.
successful boot ADC 0x03 ADC announces reboot. Should follow with a status info message when initialization is complete.
blink LED Main 0x04 For physical identification purposes, ADC board should blink an LED.
status request Main 0x05 Main board wants full status from ADC board
status info ADC 0x06 Full status report from ADC board. Should include some unique ID for the board (STM32s should have something burned in that we can use) so that we can identify which board is on what port automatically.
clock ADC 0x07 If the ADC board hasn't generated any clocked messages recently, it should send this. Recently should be maybe once per minute, according to the ADC timer.
sensor trip ADC 0x08 Sent when an ADC board has detected reportable motion (key press, hammer strike, key release)
configure sensor trip points / filtering Main 0x09 Specifies how the ADC board should monitor a sensor; filter characteristics, rising/falling edge, what values to monitor around.
raw sensor data ADC 0x0A A block of raw sensor data
configure sensor stats collection Main 0x0B Configuration for collecting stats (min, max, etc) about channel data, for calibration purposes
block of sensor stats ADC 0x0C Statistics for all sensors since the last message.
configure sensor streaming Main 0x0D Tells ADC board to stream all raw sample data for the specified channels
internal error ADC 0x7F Internally generated error. Probably followed by a reboot.

Firmware design

ADC Board

No RTOS here. Just bare C. Command handling via the UARTs and the USB port.

Main feature will be ADC and DMA configuration to sample all of the ADCs at a constant rate. In normal mode, the ADCs will all be low pass filtered, and the signals monitored according to whether they're on keys, or hammers, etc. In a calibration mode, one or more ADCs can have all of their data streamed off the board via UART or USB.

Messages to the board will configure the trip points / slopes / whatever for sensing, command high data rate mode for individual sensors, reboot the board, etc.

Main board

The main board will run FreeRTOS. The UART interrupts are to be considered the highest priority. They'll receive messages, roughly categorize them, and then dispatch them into FreeRTOS queues, for various threads to handle.

The highest priority thread will be the one handling MIDI generation. It will have to, internally, prioritize high-velocity note-ons, followed by low-velocity note-ons, and then note-offs at the lowest priority. Once that's done, it will dispatch MIDI messages to all of the currently active destinations. Networked MIDI sinks first, then UART-based MIDI sinks, then finally the USB output. (Reversed latency order) It should also dump all MIDI into a larger queue, so that it can be written to SD card, if that feature is active.

The next thread priority will concern itself with scanning the pedals, and making determinations about what pedal MIDI messages to send.

Beneath that will be threads for scanning/updating the various other pieces of hardware attached to the system, and writing the overall MIDI queue to SD card.

Some things to remember when implementing the firmware

Velocity and timing

This occurred to me after reading http://forum.pianoworld.com/ubbthreads.php/topics/3062860/re-recommendation-on-basic-audio-interface.html#Post3062860

We'll likely have the hammer velocity computed before the hammer would actually hit a string and begin to make noise, if only by some small fraction of a second. But when the hammer is moving slowly, our computation of velocity will finish with much more time before the impact, than if the hammer were moving quickly. We'll need to adjust for that, and send lower velocity messages slightly later.

⚠️ **GitHub.com Fallback** ⚠️