# Programming For the programming I used: - [Arduino IDE](https://docs.arduino.cc/software/ide/) and [build-in library](https://docs.arduino.cc/built-in-examples/) 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](https://github.com/espressif/esp-idf) for the XIAO ESP32-S3 and the [MIDI implementation](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/device/tusb_midi) from [tinyUSB](https://github.com/hathach/tinyusb) ## Key Module ::::{grid} :::{grid-item} **ATtiny412 code** ```arduino #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** ```arduino #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); } ``` ::: :::{grid-item} I used the [previously build](../assignments/electronics_production.md#ftdi-usb-bridge) Serial [FTDI USB Bridge](https://gitlab.fabcloud.org/pub/programmers/programmer-serial-d11c) with this [UPDI Adapter board](https://leomcelroy.com/svg-pcb/?file=hello.serial-UPDI.3.js) to flash the Attiny MCUs. ![IMAGE](../media/final_project/programm_test_attiny.jpg) I first tested the communication of the 2 MCUs and Later I tested reading the serial signal with: ```bash minicom -D /dev/ttyACM0 -b 115200 # ^^^^^^^^^ Baudrate # ^^^^^^^^^^^^^^^ path of the Serial adapter ``` (using the same adapter :>, just used other pins). ![IMAGE](../media/final_project/programm_test_key_press.jpg) ::: :::: Complicated here was finding the right pins to create the order of them but I found [this repo](https://github.com/SpenceKonde/megaTinyCore/tree/master/megaavr/extras) of the megaTinyCore where all the right pins and association in the libraries is documented for all the ATtiny processors :D ```{figure} ../media/final_project/ATtiny_x12.gif [Source](https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/ATtiny_x12.md) ``` ## Testing Key Presses Then I just tested the keypresses (here just with `digitalRead()`) and it worked :> ```{video} ../media/final_project/test_key_press.mp4 :width: 100% ``` ## XIAO Reading Serial Then I First tested the Serial Read based on [this simple example](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/uart/uart_echo): ```c #include "driver/gpio.h" #include "driver/uart.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "sdkconfig.h" #include #include #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); } ```` ```{video} ../media/final_project/test_keyboard_input.mp4 :width: 100% ``` And after that worked, I implemented the MIDI part I tested in [Networking-Week](./assignments/networking_and_communications.md#usb-midi) which is based on [this example](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/device/tusb_midi): ```c #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 #include #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](https://github.com/rncbc/qpwgraph) to connect the midi interface with my synthesiser ([fluidsynth](https://github.com/FluidSynth/fluidsynth)) and ....: ```{video} ../media/final_project/play_piano2.mp4 :width: 100% ``` It worked :D ( the video is a bit silent)