Programming¶
For the programming I used:
Arduino IDE and build-in library for the ATtiny MCUs (because I didn’t manage to get the AVR-GCC working, I need a bit more time for that)
ESP-IDF for the XIAO ESP32-S3 and the MIDI implementation from tinyUSB
Key Module¶
ATtiny412 code
#define NUM_OF_KEYS 5
int keys[NUM_OF_KEYS] = {5, 4, 3, 1, 2};
void setup() {
Serial.begin(115200);
for (int i = 0; i < NUM_OF_KEYS; i++) {
pinMode(keys[i], INPUT_PULLUP);
}
}
void loop() {
uint8_t pressed_keys = 0;
for ( int i = 0; i < NUM_OF_KEYS; i++) {
if (analogRead(keys[i]) < 1000) {
pressed_keys += 1 << i;
}
}
Serial.write(pressed_keys);
delay(20);
}
ATtiny1624 Code
#define MAX_ANALOG 1000
#define NUM_OF_KEYS 7
int keys[NUM_OF_KEYS] = {8, 9, 10, 3, 2, 1, 0};
void setup() {
Serial.begin(115200);
for (int i = 0; i < NUM_OF_KEYS; i++) {
pinMode(keys[i], INPUT_PULLUP);
}
}
void loop() {
uint16_t pressed_keys = 0;
uint8_t five_pressed = 0;
five_pressed = Serial.read();
for ( int i = 0; i < NUM_OF_KEYS; i++) {
if (analogRead(keys[i]) < 1000) {
pressed_keys += 1 << (i + 5);
}
}
pressed_keys += five_pressed;
Serial.write(pressed_keys);
Serial.write(pressed_keys>>8);
delay(20);
}
I used the previously build Serial FTDI USB Bridge with this UPDI Adapter board to flash the Attiny MCUs.
I first tested the communication of the 2 MCUs and Later I tested reading the serial signal with:
minicom -D /dev/ttyACM0 -b 115200
# ^^^^^^^^^ Baudrate
# ^^^^^^^^^^^^^^^ path of the Serial adapter
(using the same adapter :>, just used other pins).
Complicated here was finding the right pins to create the order of them but I found this repo of the megaTinyCore where all the right pins and association in the libraries is documented for all the ATtiny processors :D

Testing Key Presses¶
Then I just tested the keypresses (here just with digitalRead()
) and it worked :>
XIAO Reading Serial¶
Then I First tested the Serial Read based on this simple example:
#include "driver/gpio.h"
#include "driver/uart.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include <stdint.h>
#include <stdio.h>
#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define BYTE_TO_BINARY(byte) \
((byte) & 0x80 ? '1' : '0'), ((byte) & 0x40 ? '1' : '0'), \
((byte) & 0x20 ? '1' : '0'), ((byte) & 0x10 ? '1' : '0'),\
((byte) & 0x08 ? '1' : '0'), ((byte) & 0x04 ? '1' : '0'),\
((byte) & 0x02 ? '1' : '0'), ((byte) & 0x01 ? '1' : '0')
#define SERIAL_TXD (45)
#define SERIAL_RXD (44)
#define SERIAL_PORT_NUM (1)
#define SERIAL_BAUD_RATE (115200)
static const char *TAG = "UART TEST";
#define BUF_SIZE (1024)
static void echo_task(void *arg) {
/* Configure parameters of an UART driver,
* communication pins and install the driver */
uart_config_t uart_config = {
.baud_rate = SERIAL_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
ESP_ERROR_CHECK(
uart_driver_install(SERIAL_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, 0));
ESP_ERROR_CHECK(uart_param_config(SERIAL_PORT_NUM, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(SERIAL_PORT_NUM, SERIAL_TXD, SERIAL_RXD,
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
// Configure a temporary buffer for the incoming data
// uint16_t *data = (uint16_t *) malloc(BUF_SIZE);
uint8_t data[2] = {0, 0};
while (1) {
// Read data from the UART
int len =
uart_read_bytes(SERIAL_PORT_NUM, &data, 2, 20 / portTICK_PERIOD_MS);
// Write data back to the UART
// uart_write_bytes(SERIAL_PORT_NUM, &data, 2);
if (len) {
uint16_t keys = data[0] + (data[1] << 8);
ESP_LOGI(TAG, "Recv str: "BYTE_TO_BINARY_PATTERN"
"BYTE_TO_BINARY_PATTERN"\n",BYTE_TO_BINARY(data[1]), BYTE_TO_BINARY(data[0]));
// ESP_LOGI(TAG, "Recv %x; Len: %d\n", keys, len);
}
}
}
void app_main(void) {
xTaskCreate(echo_task, "uart_echo_task", 4096, NULL, 10, NULL);
}
And after that worked, I implemented the MIDI part I tested in Networking-Week which is based on this example:
#include "driver/gpio.h"
#include "driver/uart.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tinyusb.h"
#include <stdint.h>
#include <stdlib.h>
#define SERIAL_TXD (45)
#define SERIAL_RXD (44)
#define SERIAL_PORT_NUM (1)
#define SERIAL_BAUD_RATE (115200)
#define BUF_SIZE (1024)
static const char *TAG = "ESP Keyboard";
/** Helper defines **/
// Interface counter
enum interface_count {
#if CFG_TUD_MIDI
ITF_NUM_MIDI = 0,
ITF_NUM_MIDI_STREAMING,
#endif
ITF_COUNT
};
// USB Endpoint numbers
enum usb_endpoints {
// Available USB Endpoints: 5 IN/OUT EPs and 1 IN EP
EP_EMPTY = 0,
#if CFG_TUD_MIDI
EPNUM_MIDI,
#endif
};
/** TinyUSB descriptors **/
#define TUSB_DESCRIPTOR_TOTAL_LEN \
(TUD_CONFIG_DESC_LEN + CFG_TUD_MIDI * TUD_MIDI_DESC_LEN)
/**
* @brief String descriptor
*/
static const char *s_str_desc[5] = {
// array of pointer to string descriptors
(char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
"NSt", // 1: Manufacturer
"OSMK", // 2: Product
"424242", // 3: Serials, should use chip ID
"OSMK Keyboard", // 4: MIDI
};
/**
* @brief Configuration descriptor
*
* This is a simple configuration descriptor that defines 1 configuration and a
* MIDI interface
*/
static const uint8_t s_midi_cfg_desc[] = {
// Configuration number, interface count, string index, total length,
// attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_COUNT, 0, TUSB_DESCRIPTOR_TOTAL_LEN, 0, 100),
// Interface number, string index, EP Out & EP In address, EP size
TUD_MIDI_DESCRIPTOR(ITF_NUM_MIDI, 4, EPNUM_MIDI, (0x80 | EPNUM_MIDI), 64),
};
static void midi_task_read_example(void *arg) {
// The MIDI interface always creates input and output port/jack descriptors
// regardless of these being used or not. Therefore incoming traffic should be
// read (possibly just discarded) to avoid the sender blocking in IO
uint8_t packet[4];
bool read = false;
for (;;) {
vTaskDelay(1);
while (tud_midi_available()) {
read = tud_midi_packet_read(packet);
if (read) {
ESP_LOGI(TAG,
"Read - Time (ms since boot): %lld, Data: %02hhX %02hhX "
"%02hhX %02hhX",
esp_timer_get_time(), packet[0], packet[1], packet[2],
packet[3]);
}
}
}
}
// Basic MIDI Messages
#define NOTE_OFF 0x80
#define NOTE_ON 0x90
#define NUM_NOTES 12
static uint8_t notes[NUM_NOTES] = {24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35};
void keypress_as_midi(void *args) {
static uint8_t const cable_num = 0; // MIDI jack associated with USB endpoint
static uint8_t const channel = 0; // 0 for channel 1
// Configure a temporary buffer for the incoming data
uint8_t serial_data[2] = {0, 0};
uint16_t last_key_bits = 0;
while (1) {
// Read data from the UART
int len = uart_read_bytes(SERIAL_PORT_NUM, serial_data, (BUF_SIZE - 1),
20 / portTICK_PERIOD_MS);
// Write data back to the UART
// uart_write_bytes(SERIAL_PORT_NUM, &data, 2);
if (len) {
uint16_t key_bits = serial_data[0] + (serial_data[1] << 8);
// key_bits = key_bits ^ 0b111111111111;
ESP_LOGI(TAG, "Recieved Diff: %x", key_bits);
uint16_t key_diff = last_key_bits ^ key_bits;
for (int i = 0; i < NUM_NOTES; i++) {
if (key_diff & (1 << i)) {
if (key_bits & (1 << i)) {
uint8_t note_on[3] = {NOTE_ON | channel, notes[i] + 12, 127};
tud_midi_stream_write(cable_num, note_on, 3);
} else {
uint8_t note_off[3] = {NOTE_OFF | channel, notes[i] + 12, 0};
tud_midi_stream_write(cable_num, note_off, 3);
}
}
}
last_key_bits = key_bits;
}
vTaskDelay(20 / portTICK_PERIOD_MS);
}
}
void app_main(void) {
ESP_LOGI(TAG, "UART Init");
/* Configure parameters of an UART driver,
* communication pins and install the driver */
uart_config_t uart_config = {
.baud_rate = SERIAL_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
ESP_ERROR_CHECK(
uart_driver_install(SERIAL_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, 0));
ESP_ERROR_CHECK(uart_param_config(SERIAL_PORT_NUM, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(SERIAL_PORT_NUM, SERIAL_TXD, SERIAL_RXD,
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
ESP_LOGI(TAG, "UART Init DONE");
ESP_LOGI(TAG, "USB initialization");
tinyusb_config_t const tusb_cfg = {
.device_descriptor = NULL, // If device_descriptor is NULL,
// tinyusb_driver_install() will use Kconfig
.string_descriptor = s_str_desc,
.string_descriptor_count = sizeof(s_str_desc) / sizeof(s_str_desc[0]),
.external_phy = false,
.configuration_descriptor = s_midi_cfg_desc,
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
ESP_LOGI(TAG, "USB initialization DONE");
ESP_LOGI(TAG, "MIDI key task init");
// Read received MIDI packets
xTaskCreate(keypress_as_midi, "keypress_as_midi", 8 * 1024, NULL, 5, NULL);
ESP_LOGI(TAG, "MIDI read task init");
xTaskCreate(midi_task_read_example, "midi_task_read_example", 4 * 1024, NULL,
5, NULL);
}
I then connected it with a USB-C cable to my PC, used qpwgraph to connect the midi interface with my synthesiser (fluidsynth) and ….:
It worked :D ( the video is a bit silent)