Programming

For the programming I used:

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. IMAGE 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). IMAGE

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

../_images/ATtiny_x12.gif

Source

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)