Firmware implementation notes - jkominek/piano-conversion GitHub Wiki
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.
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.
After successful boot, all messages will use HDLC asynchronous framing. This ensures both ends can determine message boundaries with ease, and detect errors.
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. |
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.
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.
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.