Browse Source

add MIDI support

Thomas Buck 8 months ago
parent
commit
682a67e653
25 changed files with 1516 additions and 68 deletions
  1. 17
    0
      CMakeLists.txt
  2. 1
    1
      debug.sh
  3. 1
    1
      flash.sh
  4. 26
    0
      include/console.h
  5. 56
    0
      include/log.h
  6. 4
    1
      include/main.h
  7. 45
    0
      include/ring.h
  8. 1
    0
      include/sequence.h
  9. 129
    0
      include/tusb_config.h
  10. 3
    9
      include/ui.h
  11. 29
    0
      include/usb.h
  12. 29
    0
      include/usb_cdc.h
  13. 28
    0
      include/usb_descriptors.h
  14. 30
    0
      include/usb_midi.h
  15. 187
    0
      src/console.c
  16. 6
    1
      src/lcd.c
  17. 113
    0
      src/log.c
  18. 53
    32
      src/main.c
  19. 111
    0
      src/ring.c
  20. 4
    0
      src/sequence.c
  21. 141
    23
      src/ui.c
  22. 80
    0
      src/usb.c
  23. 117
    0
      src/usb_cdc.c
  24. 236
    0
      src/usb_descriptors.c
  25. 69
    0
      src/usb_midi.c

+ 17
- 0
CMakeLists.txt View File

@@ -40,6 +40,13 @@ target_sources(drumkit PUBLIC
40 40
     src/pulse.c
41 41
     src/sequence.c
42 42
     src/ui.c
43
+    src/log.c
44
+    src/ring.c
45
+    src/console.c
46
+    src/usb.c
47
+    src/usb_cdc.c
48
+    src/usb_descriptors.c
49
+    src/usb_midi.c
43 50
 
44 51
     pico-ssd1306/ssd1306.c
45 52
 )
@@ -73,6 +80,9 @@ target_link_libraries(drumkit
73 80
     hardware_adc
74 81
     hardware_gpio
75 82
     hardware_pwm
83
+    pico_unique_id
84
+    tinyusb_device
85
+    tinyusb_board
76 86
 )
77 87
 
78 88
 # enable usb output, disable uart output
@@ -83,3 +93,10 @@ pico_enable_stdio_uart(drumkit 0)
83 93
 target_compile_definitions(drumkit PUBLIC PICO_RP2040_USB_DEVICE_ENUMERATION_FIX=1)
84 94
 
85 95
 pico_add_extra_outputs(drumkit)
96
+
97
+# print binary size
98
+# https://github.com/raspberrypi/pico-sdk/issues/1196#issuecomment-1429404558
99
+add_custom_command(TARGET drumkit
100
+    POST_BUILD
101
+    COMMAND ${PICO_GCC_TRIPLE}-size --format=gnu ${CMAKE_CURRENT_BINARY_DIR}/drumkit.elf
102
+)

+ 1
- 1
debug.sh View File

@@ -18,7 +18,7 @@
18 18
 
19 19
 set -euo pipefail
20 20
 
21
-SERIAL=/dev/serial/by-id/usb-Raspberry_Pi_Pico_*
21
+SERIAL=/dev/serial/by-id/usb-xythobuz_LARS_*
22 22
 
23 23
 echo -n Waiting for serial port to appear
24 24
 until [ -e $SERIAL ]

+ 1
- 1
flash.sh View File

@@ -18,7 +18,7 @@
18 18
 
19 19
 set -euo pipefail
20 20
 
21
-SERIAL=/dev/serial/by-id/usb-Raspberry_Pi_Pico_*
21
+SERIAL=/dev/serial/by-id/usb-xythobuz_LARS_*
22 22
 DISK=/dev/disk/by-label/RPI-RP2
23 23
 MOUNT=/mnt/tmp
24 24
 

+ 26
- 0
include/console.h View File

@@ -0,0 +1,26 @@
1
+/*
2
+ * console.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __CONSOLE_H__
20
+#define __CONSOLE_H__
21
+
22
+void cnsl_init(void);
23
+void cnsl_run(void);
24
+void cnsl_handle_input(const void *buf, size_t len);
25
+
26
+#endif // __CONSOLE_H__

+ 56
- 0
include/log.h View File

@@ -0,0 +1,56 @@
1
+/*
2
+ * log.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __LOG_H__
20
+#define __LOG_H__
21
+
22
+#include <stdarg.h>
23
+#include <stdbool.h>
24
+#include <inttypes.h>
25
+#include "pico/stdlib.h"
26
+
27
+// for output that is stored in the debug log.
28
+// will be re-played from buffer when terminal connects
29
+#ifndef PICOWOTA
30
+#define debug(fmt, ...) debug_log(true, \
31
+        "%08" PRIu32 " %s:%d: " fmt "\r\n", \
32
+        to_ms_since_boot(get_absolute_time()), \
33
+        __func__, __LINE__, \
34
+        ##__VA_ARGS__)
35
+#else // PICOWOTA
36
+#define debug(fmt, ...) debug_log(true, \
37
+        fmt "\r\n", \
38
+        ##__VA_ARGS__)
39
+#endif // PICOWOTA
40
+
41
+// for interactive output. is not stored or re-played.
42
+#define print(fmt, ...) debug_log(false, fmt, ##__VA_ARGS__)
43
+#define println(fmt, ...) debug_log(false, fmt "\r\n", ##__VA_ARGS__)
44
+
45
+void debug_log(bool log, const char *format, ...) __attribute__((format(printf, 2, 3)));
46
+void debug_wait_input(const char *format, ...) __attribute__((format(printf, 1, 2)));
47
+void debug_log_va(bool log, const char *format, va_list args);
48
+
49
+void log_dump_to_usb(void);
50
+
51
+void debug_handle_input(const void *buff, size_t len);
52
+
53
+#include "ring.h"
54
+struct ring_buffer *log_get(void);
55
+
56
+#endif // __LOG_H__

+ 4
- 1
include/main.h View File

@@ -27,6 +27,9 @@ enum hw_versions {
27 27
 
28 28
 extern enum hw_versions hw_type;
29 29
 
30
-void handle_serial_input(void);
30
+void main_loop_hw(void);
31
+
32
+void reset_to_bootloader(void);
33
+void reset_to_main(void);
31 34
 
32 35
 #endif // __MAIN_H__

+ 45
- 0
include/ring.h View File

@@ -0,0 +1,45 @@
1
+/*
2
+ * ring.h
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __RING_BUFFER_H__
20
+#define __RING_BUFFER_H__
21
+
22
+#include <stddef.h>
23
+#include <stdint.h>
24
+#include <stdbool.h>
25
+
26
+struct ring_buffer {
27
+    void *buffer;
28
+    size_t size;
29
+    size_t el_len;
30
+    size_t head, tail;
31
+    bool full;
32
+};
33
+#define RB_INIT(b, s, e) { .buffer = b, .size = s, .el_len = e, .head = 0, .tail = 0, .full = false }
34
+
35
+void rb_add(struct ring_buffer *rb, const void *data, size_t length);
36
+#define rb_push(rb, v) rb_add(rb, v, 1)
37
+size_t rb_len(struct ring_buffer *rb);
38
+#define rb_space(rb) ((rb)->size - rb_len(rb))
39
+void rb_dump(struct ring_buffer *rb, void (*write)(const void *, size_t), size_t skip);
40
+void rb_move(struct ring_buffer *rb, void (*write)(const void *, size_t));
41
+void rb_peek(struct ring_buffer *rb, void *buf);
42
+void rb_pop(struct ring_buffer *rb, void *buf);
43
+size_t rb_get(struct ring_buffer *rb, void *data, size_t length);
44
+
45
+#endif // __RING_BUFFER_H__

+ 1
- 0
include/sequence.h View File

@@ -44,6 +44,7 @@ void sequence_init(void);
44 44
 void sequence_set_bpm(uint32_t new_bpm);
45 45
 uint32_t sequence_get_bpm(void);
46 46
 
47
+void sequence_set_ms(uint32_t new_ms);
47 48
 uint32_t sequence_get_ms(void);
48 49
 
49 50
 void sequence_set_beats(uint32_t new_beats);

+ 129
- 0
include/tusb_config.h View File

@@ -0,0 +1,129 @@
1
+/*
2
+ * Extended from TinyUSB example code.
3
+ *
4
+ * Copyright (c) 2022 - 2024 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * The MIT License (MIT)
7
+ *
8
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
9
+ *
10
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ * of this software and associated documentation files (the "Software"), to deal
12
+ * in the Software without restriction, including without limitation the rights
13
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ * copies of the Software, and to permit persons to whom the Software is
15
+ * furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in
18
+ * all copies or substantial portions of the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ * THE SOFTWARE.
27
+ *
28
+ */
29
+
30
+#ifndef _TUSB_CONFIG_H_
31
+#define _TUSB_CONFIG_H_
32
+
33
+#ifdef __cplusplus
34
+ extern "C" {
35
+#endif
36
+
37
+//--------------------------------------------------------------------
38
+// COMMON CONFIGURATION
39
+//--------------------------------------------------------------------
40
+
41
+// defined by board.mk
42
+#ifndef CFG_TUSB_MCU
43
+  #error CFG_TUSB_MCU must be defined
44
+#endif
45
+
46
+// RHPort number used for device can be defined by board.mk, default to port 0
47
+#ifndef BOARD_DEVICE_RHPORT_NUM
48
+  #define BOARD_DEVICE_RHPORT_NUM     0
49
+#endif
50
+
51
+// RHPort max operational speed can defined by board.mk
52
+// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed
53
+#ifndef BOARD_DEVICE_RHPORT_SPEED
54
+  #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \
55
+       CFG_TUSB_MCU == OPT_MCU_NUC505  || CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X)
56
+    #define BOARD_DEVICE_RHPORT_SPEED   OPT_MODE_HIGH_SPEED
57
+  #else
58
+    #define BOARD_DEVICE_RHPORT_SPEED   OPT_MODE_FULL_SPEED
59
+  #endif
60
+#endif
61
+
62
+// Device mode with rhport and speed defined by board.mk
63
+#if   BOARD_DEVICE_RHPORT_NUM == 0
64
+  #define CFG_TUSB_RHPORT0_MODE     (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
65
+#elif BOARD_DEVICE_RHPORT_NUM == 1
66
+  #define CFG_TUSB_RHPORT1_MODE     (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
67
+#else
68
+  #error "Incorrect RHPort configuration"
69
+#endif
70
+
71
+#ifndef CFG_TUSB_OS
72
+#define CFG_TUSB_OS               OPT_OS_NONE
73
+#endif
74
+
75
+// CFG_TUSB_DEBUG is defined by compiler in DEBUG build
76
+// #define CFG_TUSB_DEBUG           0
77
+
78
+/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
79
+ * Tinyusb use follows macros to declare transferring memory so that they can be put
80
+ * into those specific section.
81
+ * e.g
82
+ * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
83
+ * - CFG_TUSB_MEM_ALIGN   : __attribute__ ((aligned(4)))
84
+ */
85
+#ifndef CFG_TUSB_MEM_SECTION
86
+#define CFG_TUSB_MEM_SECTION
87
+#endif
88
+
89
+#ifndef CFG_TUSB_MEM_ALIGN
90
+#define CFG_TUSB_MEM_ALIGN          __attribute__ ((aligned(4)))
91
+#endif
92
+
93
+//--------------------------------------------------------------------
94
+// DEVICE CONFIGURATION
95
+//--------------------------------------------------------------------
96
+
97
+#ifndef CFG_TUD_ENDPOINT0_SIZE
98
+#define CFG_TUD_ENDPOINT0_SIZE    64
99
+#endif
100
+
101
+//------------- CLASS -------------//
102
+#define CFG_TUD_HID               0
103
+#define CFG_TUD_CDC               1
104
+#define CFG_TUD_MSC               0
105
+#define CFG_TUD_MIDI              1
106
+#define CFG_TUD_VENDOR            0
107
+
108
+// HID buffer size Should be sufficient to hold ID (if any) + Data
109
+#define CFG_TUD_HID_EP_BUFSIZE    16
110
+
111
+// CDC FIFO size of TX and RX
112
+#define CFG_TUD_CDC_RX_BUFSIZE   (TUD_OPT_HIGH_SPEED ? 512 : 64)
113
+#define CFG_TUD_CDC_TX_BUFSIZE   (TUD_OPT_HIGH_SPEED ? 512 : 64)
114
+
115
+// CDC Endpoint transfer buffer size, more is faster
116
+#define CFG_TUD_CDC_EP_BUFSIZE   (TUD_OPT_HIGH_SPEED ? 512 : 64)
117
+
118
+// MIDI FIFO size of TX and RX
119
+#define CFG_TUD_MIDI_RX_BUFSIZE  (TUD_OPT_HIGH_SPEED ? 512 : 64)
120
+#define CFG_TUD_MIDI_TX_BUFSIZE  (TUD_OPT_HIGH_SPEED ? 512 : 64)
121
+
122
+// MIDI Endpoint transfer buffer size, more is faster
123
+#define CFG_TUD_CDC_MIDI_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
124
+
125
+#ifdef __cplusplus
126
+ }
127
+#endif
128
+
129
+#endif /* _TUSB_CONFIG_H_ */

+ 3
- 9
include/ui.h View File

@@ -21,18 +21,10 @@
21 21
 
22 22
 #include <stdint.h>
23 23
 
24
-enum ui_modes {
25
-    UI_MODE = 0,
26
-    UI_BPM,
27
-    UI_BANK,
28
-    UI_LENGTH,
29
-
30
-    UI_NUM_MODES
31
-};
32
-
33 24
 enum machine_modes {
34 25
     MODE_LOOPSTATION = 0,
35 26
     MODE_DRUMMACHINE,
27
+    MODE_MIDI,
36 28
 
37 29
     MACHINE_NUM_MODES
38 30
 };
@@ -41,6 +33,8 @@ void ui_init(void);
41 33
 void ui_encoder(int32_t val);
42 34
 void ui_run(void);
43 35
 
36
+void ui_midi_set(uint8_t channel, uint8_t note, uint8_t velocity);
37
+
44 38
 enum machine_modes ui_get_machinemode(void);
45 39
 
46 40
 #endif // __UI_H__

+ 29
- 0
include/usb.h View File

@@ -0,0 +1,29 @@
1
+/*
2
+ * usb.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __USB_H__
20
+#define __USB_H__
21
+
22
+#include <stdbool.h>
23
+
24
+void usb_init(void);
25
+void usb_run(void);
26
+
27
+bool usb_is_connected(void);
28
+
29
+#endif // __USB_H__

+ 29
- 0
include/usb_cdc.h View File

@@ -0,0 +1,29 @@
1
+/*
2
+ * usb_cdc.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __USB_CDC_H__
20
+#define __USB_CDC_H__
21
+
22
+#include <stddef.h>
23
+#include <stdint.h>
24
+#include <stdbool.h>
25
+
26
+void usb_cdc_write(const void *buf, size_t count);
27
+void usb_cdc_set_reroute(bool reroute);
28
+
29
+#endif // __USB_CDC_H__

+ 28
- 0
include/usb_descriptors.h View File

@@ -0,0 +1,28 @@
1
+/*
2
+ * usb_descriptors.h
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef USB_DESCRIPTORS_H_
20
+#define USB_DESCRIPTORS_H_
21
+
22
+#include "pico/unique_id.h"
23
+
24
+extern char string_pico_serial[2 * PICO_UNIQUE_BOARD_ID_SIZE_BYTES + 1];
25
+
26
+void usb_descriptor_init_id(void);
27
+
28
+#endif /* USB_DESCRIPTORS_H_ */

+ 30
- 0
include/usb_midi.h View File

@@ -0,0 +1,30 @@
1
+/*
2
+ * usb_midi.h
3
+ *
4
+ * Copyright (c) 2024 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#ifndef __USB_MIDI_H__
20
+#define __USB_MIDI_H__
21
+
22
+#include <stdint.h>
23
+
24
+#define MIDI_MAX_CH 16
25
+
26
+void usb_midi_tx(uint8_t channel, uint8_t note, uint8_t velocity);
27
+
28
+void usb_midi_run(void);
29
+
30
+#endif // __USB_MIDI_H__

+ 187
- 0
src/console.c View File

@@ -0,0 +1,187 @@
1
+/*
2
+ * console.c
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <inttypes.h>
20
+#include <string.h>
21
+#include <unistd.h>
22
+#include <stdio.h>
23
+
24
+#include "pico/stdlib.h"
25
+#include "hardware/watchdog.h"
26
+
27
+#include "log.h"
28
+#include "usb_cdc.h"
29
+#include "main.h"
30
+#include "console.h"
31
+
32
+#define CNSL_BUFF_SIZE 64
33
+#define CNSL_REPEAT_MS 500
34
+
35
+static char cnsl_line_buff[CNSL_BUFF_SIZE + 1];
36
+static uint32_t cnsl_buff_pos = 0;
37
+
38
+static char cnsl_last_command[CNSL_BUFF_SIZE + 1];
39
+
40
+static char cnsl_repeated_command[CNSL_BUFF_SIZE + 1];
41
+static bool repeat_command = false;
42
+static uint32_t last_repeat_time = 0;
43
+
44
+static void cnsl_interpret(const char *line) {
45
+    if (strlen(line) == 0) {
46
+        if ((strlen(cnsl_last_command) > 0) && (strcmp(cnsl_last_command, "repeat") != 0)) {
47
+            // repeat last command once
48
+            println("repeating command \"%s\"", cnsl_last_command);
49
+            cnsl_interpret(cnsl_last_command);
50
+            println();
51
+        }
52
+        return;
53
+    } else if (strcmp(line, "repeat") == 0) {
54
+        if (!repeat_command) {
55
+            // mark last command to be repeated multiple times
56
+            strncpy(cnsl_repeated_command, cnsl_last_command, CNSL_BUFF_SIZE + 1);
57
+            last_repeat_time = to_ms_since_boot(get_absolute_time()) - 1001;
58
+            repeat_command = true;
59
+        } else {
60
+            // stop repeating
61
+            repeat_command = false;
62
+        }
63
+    } else if ((strcmp(line, "help") == 0)
64
+            || (strcmp(line, "h") == 0)
65
+            || (strcmp(line, "?") == 0)) {
66
+        println("LARS Firmware Usage:");
67
+        println("");
68
+        println("  reset - reset back into this firmware");
69
+        println("   \\x18 - reset to bootloader");
70
+        println(" repeat - repeat last command every %d milliseconds", CNSL_REPEAT_MS);
71
+        println("   help - print this message");
72
+        println("");
73
+        println("Press Enter with no input to repeat last command.");
74
+        println("Use repeat to continuously execute last command.");
75
+        println("Stop this by calling repeat again.");
76
+    } else if (strcmp(line, "reset") == 0) {
77
+        reset_to_main();
78
+    } else {
79
+        println("unknown command \"%s\"", line);
80
+    }
81
+
82
+    println();
83
+}
84
+
85
+void cnsl_init(void) {
86
+    cnsl_buff_pos = 0;
87
+    for (int i = 0; i < CNSL_BUFF_SIZE + 1; i++) {
88
+        cnsl_line_buff[i] = '\0';
89
+        cnsl_last_command[i] = '\0';
90
+        cnsl_repeated_command[i] = '\0';
91
+    }
92
+}
93
+
94
+static int32_t cnsl_find_line_end(void) {
95
+    for (uint32_t i = 0; i < cnsl_buff_pos; i++) {
96
+        if ((cnsl_line_buff[i] == '\r') || (cnsl_line_buff[i] == '\n')) {
97
+            return i;
98
+        }
99
+    }
100
+    return -1;
101
+}
102
+
103
+void cnsl_run(void) {
104
+    if (repeat_command && (strlen(cnsl_repeated_command) > 0)
105
+            && (strcmp(cnsl_repeated_command, "repeat") != 0)) {
106
+        uint32_t now = to_ms_since_boot(get_absolute_time());
107
+        if (now >= (last_repeat_time + CNSL_REPEAT_MS)) {
108
+            println("repeating command \"%s\"", cnsl_repeated_command);
109
+            cnsl_interpret(cnsl_repeated_command);
110
+            println();
111
+
112
+            last_repeat_time = now;
113
+        }
114
+    } else {
115
+        if (repeat_command) {
116
+            println("nothing to repeat");
117
+        }
118
+        repeat_command = false;
119
+    }
120
+}
121
+
122
+void cnsl_handle_input(const void *buf, size_t len) {
123
+    if ((cnsl_buff_pos + len) > CNSL_BUFF_SIZE) {
124
+        debug("error: console input buffer overflow! %lu > %u", cnsl_buff_pos + len, CNSL_BUFF_SIZE);
125
+        cnsl_init();
126
+    }
127
+
128
+    memcpy(cnsl_line_buff + cnsl_buff_pos, buf, len);
129
+    cnsl_buff_pos += len;
130
+
131
+    // handle backspace and local echo
132
+    for (ssize_t i = cnsl_buff_pos - len; i < (ssize_t)cnsl_buff_pos; i++) {
133
+        if ((cnsl_line_buff[i] == '\b') || (cnsl_line_buff[i] == 0x7F)) {
134
+            if (i > 0) {
135
+                // overwrite previous character and backspace
136
+                for (ssize_t j = i; j < (ssize_t)cnsl_buff_pos - 1; j++) {
137
+                    cnsl_line_buff[j - 1] = cnsl_line_buff[j + 1];
138
+                }
139
+                cnsl_buff_pos -= 2;
140
+            } else {
141
+                // just remove the backspace
142
+                for (ssize_t j = i; j < (ssize_t)cnsl_buff_pos - 1; j++) {
143
+                    cnsl_line_buff[j] = cnsl_line_buff[j + 1];
144
+                }
145
+                cnsl_buff_pos -= 1;
146
+            }
147
+
148
+            usb_cdc_write((const uint8_t *)"\b \b", 3);
149
+
150
+#ifndef NDEBUG
151
+            serial_write((const uint8_t *)"\b \b", 3);
152
+#endif
153
+
154
+            // check for another backspace in this space
155
+            i--;
156
+        } else {
157
+            usb_cdc_write((const uint8_t *)(cnsl_line_buff + i), 1);
158
+
159
+#ifndef NDEBUG
160
+            serial_write((const uint8_t *)(cnsl_line_buff + i), 1);
161
+#endif
162
+        }
163
+    }
164
+
165
+    int32_t line_len = cnsl_find_line_end();
166
+    if (line_len < 0) {
167
+        // user has not pressed enter yet
168
+        return;
169
+    }
170
+
171
+    // convert line to C-style string
172
+    cnsl_line_buff[line_len] = '\0';
173
+
174
+    cnsl_interpret(cnsl_line_buff);
175
+
176
+    // store command for eventual repeats
177
+    strncpy(cnsl_last_command, cnsl_line_buff, CNSL_BUFF_SIZE + 1);
178
+
179
+    // clear string and move following data over
180
+    uint32_t cnt = line_len + 1;
181
+    if (cnsl_line_buff[line_len + 1] == '\n') {
182
+        cnt++;
183
+    }
184
+    memset(cnsl_line_buff, '\0', cnt);
185
+    memmove(cnsl_line_buff, cnsl_line_buff + cnt, sizeof(cnsl_line_buff) - cnt);
186
+    cnsl_buff_pos -= cnt;
187
+}

+ 6
- 1
src/lcd.c View File

@@ -17,11 +17,14 @@
17 17
  */
18 18
 
19 19
 #include "hardware/i2c.h"
20
+#include "hardware/watchdog.h"
20 21
 
21 22
 #include "ssd1306.h"
22 23
 
23 24
 #include "buttons.h"
25
+#include "console.h"
24 26
 #include "main.h"
27
+#include "usb.h"
25 28
 #include "lcd.h"
26 29
 
27 30
 static i2c_inst_t *gpio_i2c_proto = i2c0;
@@ -45,8 +48,10 @@ void lcd_debug_buttons(void) {
45 48
     buttons_callback(lcd_debug_buttons_callback);
46 49
 
47 50
     while (1) {
51
+        watchdog_update();
48 52
         buttons_run();
49
-        handle_serial_input();
53
+        usb_run();
54
+        cnsl_run();
50 55
 
51 56
         if (changed) {
52 57
             changed = false;

+ 113
- 0
src/log.c View File

@@ -0,0 +1,113 @@
1
+/*
2
+ * log.c
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#include <stdio.h>
20
+#include <string.h>
21
+
22
+#include "hardware/watchdog.h"
23
+
24
+#include "main.h"
25
+#include "usb_cdc.h"
26
+#include "ring.h"
27
+#include "log.h"
28
+
29
+static uint8_t log_buff[4096] = {0};
30
+static struct ring_buffer log_rb = RB_INIT(log_buff, sizeof(log_buff), 1);
31
+
32
+static uint8_t line_buff[256] = {0};
33
+static volatile bool got_input = false;
34
+
35
+static void add_to_log(const void *buff, size_t len) {
36
+    rb_add(&log_rb, buff, len);
37
+}
38
+
39
+struct ring_buffer *log_get(void) {
40
+    return &log_rb;
41
+}
42
+
43
+static void log_dump_to_x(void (*write)(const void *, size_t)) {
44
+    if (rb_len(&log_rb) == 0) {
45
+        return;
46
+    }
47
+
48
+    int l = snprintf((char *)line_buff, sizeof(line_buff), "\r\n\r\nbuffered log output:\r\n");
49
+    if ((l > 0) && (l <= (int)sizeof(line_buff))) {
50
+        write(line_buff, l);
51
+    }
52
+
53
+    rb_dump(&log_rb, write, 0);
54
+
55
+    l = snprintf((char *)line_buff, sizeof(line_buff), "\r\n\r\nlive log:\r\n");
56
+    if ((l > 0) && (l <= (int)sizeof(line_buff))) {
57
+        write(line_buff, l);
58
+    }
59
+}
60
+
61
+void log_dump_to_usb(void) {
62
+    log_dump_to_x(usb_cdc_write);
63
+}
64
+
65
+void debug_log_va(bool do_log, const char *format, va_list args) {
66
+    int l = vsnprintf((char *)line_buff, sizeof(line_buff), format, args);
67
+
68
+    if (l < 0) {
69
+        // encoding error
70
+        l = snprintf((char *)line_buff, sizeof(line_buff), "%s: encoding error\r\n", __func__);
71
+    } else if (l >= (ssize_t)sizeof(line_buff)) {
72
+        // not enough space for string
73
+        l = snprintf((char *)line_buff, sizeof(line_buff), "%s: message too long (%d)\r\n", __func__, l);
74
+    }
75
+    if ((l > 0) && (l <= (int)sizeof(line_buff))) {
76
+        usb_cdc_write(line_buff, l);
77
+
78
+        if (do_log) {
79
+            add_to_log(line_buff, l);
80
+        }
81
+    }
82
+}
83
+
84
+void debug_log(bool do_log, const char* format, ...) {
85
+    va_list args;
86
+    va_start(args, format);
87
+    debug_log_va(do_log, format, args);
88
+    va_end(args);
89
+}
90
+
91
+void debug_handle_input(const void *buff, size_t len) {
92
+    (void)buff;
93
+
94
+    if (len > 0) {
95
+        got_input = true;
96
+    }
97
+}
98
+
99
+void debug_wait_input(const char *format, ...) {
100
+    va_list args;
101
+    va_start(args, format);
102
+    debug_log_va(false, format, args);
103
+    va_end(args);
104
+
105
+    got_input = false;
106
+    usb_cdc_set_reroute(true);
107
+
108
+    while (!got_input) {
109
+        main_loop_hw();
110
+    }
111
+
112
+    usb_cdc_set_reroute(false);
113
+}

+ 53
- 32
src/main.c View File

@@ -16,20 +16,22 @@
16 16
  * See <http://www.gnu.org/licenses/>.
17 17
  */
18 18
 
19
-#include <stdio.h>
20 19
 #include "pico/stdlib.h"
21 20
 #include "pico/bootrom.h"
22 21
 #include "hardware/watchdog.h"
23 22
 
24 23
 #include "adc.h"
25 24
 #include "buttons.h"
25
+#include "console.h"
26 26
 #include "encoder.h"
27 27
 #include "lcd.h"
28 28
 #include "led.h"
29
+#include "log.h"
29 30
 #include "logo.h"
30 31
 #include "pulse.h"
31 32
 #include "sequence.h"
32 33
 #include "ui.h"
34
+#include "usb.h"
33 35
 #include "main.h"
34 36
 
35 37
 #define WATCHDOG_PERIOD_MS 100
@@ -45,7 +47,7 @@ static void debug_buttons_callback(enum buttons btn, bool v) {
45 47
     debug_buttons[btn] = v;
46 48
 }
47 49
 
48
-static void reset_to_bootloader(void) {
50
+void reset_to_bootloader(void) {
49 51
     lcd_draw_bye();
50 52
 
51 53
 #ifdef PICO_DEFAULT_LED_PIN
@@ -55,17 +57,46 @@ static void reset_to_bootloader(void) {
55 57
 #endif // PICO_DEFAULT_LED_PIN
56 58
 }
57 59
 
58
-void handle_serial_input(void) {
59
-    int c = getchar_timeout_us(0);
60
-    if (c == 0x18) {
61
-        reset_to_bootloader();
62
-    } else if (c != PICO_ERROR_TIMEOUT) {
63
-        printf("%c", c);
60
+void reset_to_main(void) {
61
+    watchdog_enable(1, 0);
62
+    while (1);
63
+}
64
+
65
+static void encoder_handle(void) {
66
+    static int32_t last_epos = 0;
67
+    int32_t epos = encoder_pos();
68
+    if (epos != last_epos) {
69
+        ui_encoder(epos - last_epos);
70
+        last_epos = epos;
64 71
     }
65 72
 }
66 73
 
74
+static void sleep_ms_wd(uint32_t ms) {
75
+    for (uint32_t i = 0; i < ms; i++) {
76
+        watchdog_update();
77
+        sleep_ms(1);
78
+    }
79
+}
80
+
81
+void main_loop_hw(void) {
82
+    watchdog_update();
83
+
84
+    usb_run();
85
+    cnsl_run();
86
+    buttons_run();
87
+    encoder_run();
88
+    sequence_run();
89
+    pulse_run();
90
+    ui_run();
91
+
92
+    encoder_handle();
93
+}
94
+
67 95
 int main(void) {
68
-    stdio_init_all();
96
+    watchdog_enable(WATCHDOG_PERIOD_MS, 1);
97
+
98
+    cnsl_init();
99
+    usb_init();
69 100
 
70 101
     gpio_init(gpio_hw_detect);
71 102
     gpio_set_dir(gpio_hw_detect, GPIO_IN);
@@ -90,9 +121,11 @@ int main(void) {
90 121
     // read out button state for debug options
91 122
     buttons_callback(debug_buttons_callback);
92 123
     for (uint i = 0; i < (DEBOUNCE_DELAY_MS + 5); i++) {
124
+        watchdog_update();
93 125
         buttons_run();
94
-        handle_serial_input();
95
-        sleep_ms(1);
126
+        usb_run();
127
+        cnsl_run();
128
+        sleep_ms_wd(1);
96 129
     }
97 130
 
98 131
     // handle special button combos on boot
@@ -108,8 +141,10 @@ int main(void) {
108 141
         uint32_t last = to_ms_since_boot(get_absolute_time());
109 142
         bool state = false;
110 143
         while (debug_buttons[BTN_CLICK]) {
144
+            watchdog_update();
111 145
             buttons_run();
112
-            handle_serial_input();
146
+            usb_run();
147
+            cnsl_run();
113 148
             uint32_t now = to_ms_since_boot(get_absolute_time());
114 149
             if ((now - last) >= 250) {
115 150
                 state = !state;
@@ -120,9 +155,11 @@ int main(void) {
120 155
     } else {
121 156
         // show splash for a bit and animate LEDs
122 157
         for (uint i = 0; i < LED_COUNT; i++) {
123
-            handle_serial_input();
158
+            watchdog_update();
159
+            usb_run();
160
+            cnsl_run();
124 161
             led_set(i, true);
125
-            sleep_ms(LOGO_INIT_MS / LED_COUNT);
162
+            sleep_ms_wd(LOGO_INIT_MS / LED_COUNT);
126 163
         }
127 164
     }
128 165
 
@@ -134,26 +171,10 @@ int main(void) {
134 171
     sequence_init();
135 172
     ui_init();
136 173
 
137
-    printf("init done\n");
138
-    watchdog_enable(WATCHDOG_PERIOD_MS, 1);
139
-
140
-    int32_t last_epos = 0;
174
+    debug("init done\n");
141 175
 
142 176
     while (1) {
143
-        watchdog_update();
144
-        buttons_run();
145
-        encoder_run();
146
-        sequence_run();
147
-        pulse_run();
148
-        ui_run();
149
-
150
-        int32_t epos = encoder_pos();
151
-        if (epos != last_epos) {
152
-            ui_encoder(epos - last_epos);
153
-            last_epos = epos;
154
-        }
155
-
156
-        handle_serial_input();
177
+        main_loop_hw();
157 178
     }
158 179
 
159 180
     return 0;

+ 111
- 0
src/ring.c View File

@@ -0,0 +1,111 @@
1
+/*
2
+ * ring.c
3
+ *
4
+ * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+#define MIN(x, y) ((x < y) ? x : y)
20
+
21
+#include <string.h>
22
+
23
+#include "ring.h"
24
+
25
+void rb_add(struct ring_buffer *rb, const void *data, size_t length) {
26
+    for (size_t i = 0; i < length; i++) {
27
+        memcpy(rb->buffer + rb->head * rb->el_len, data + i * rb->el_len, rb->el_len);
28
+
29
+        if (rb->full && (++(rb->tail) == rb->size)) {
30
+            rb->tail = 0;
31
+        }
32
+
33
+        if (++(rb->head) == rb->size) {
34
+            rb->head = 0;
35
+        }
36
+
37
+        rb->full = ((rb->head) == (rb->tail));
38
+    }
39
+}
40
+
41
+size_t rb_len(struct ring_buffer *rb) {
42
+    if (rb->head == rb->tail) {
43
+        if (rb->full) {
44
+            return rb->size;
45
+        } else {
46
+            return 0;
47
+        }
48
+    } else if (rb->head > rb->tail) {
49
+        return rb->head - rb->tail;
50
+    } else {
51
+        return rb->size - rb->tail + rb->head;
52
+    }
53
+}
54
+
55
+void rb_dump(struct ring_buffer *rb, void (*write)(const void *, size_t), size_t skip) {
56
+    if (rb_len(rb) <= skip) {
57
+        return;
58
+    }
59
+
60
+    if (rb->head > rb->tail) {
61
+        if ((rb->head - rb->tail) > skip) {
62
+            write(rb->buffer + ((rb->tail + skip) * rb->el_len), rb->head - rb->tail - skip);
63
+        }
64
+    } else {
65
+        if ((rb->size - rb->tail) > skip) {
66
+            write(rb->buffer + ((rb->tail + skip) * rb->el_len), rb->size - rb->tail - skip);
67
+        }
68
+
69
+        skip -= MIN(skip, rb->size - rb->tail);
70
+        if (rb->head > skip) {
71
+            write(rb->buffer + (skip + rb->el_len), rb->head - skip);
72
+        }
73
+    }
74
+}
75
+
76
+void rb_move(struct ring_buffer *rb, void (*write)(const void *, size_t)) {
77
+    rb_dump(rb, write, 0);
78
+    rb->head = 0;
79
+    rb->tail = 0;
80
+    rb->full = false;
81
+}
82
+
83
+void rb_peek(struct ring_buffer *rb, void *buf) {
84
+    if (rb_len(rb) == 0) {
85
+        return;
86
+    }
87
+
88
+    memcpy(buf, rb->buffer + rb->tail * rb->el_len, rb->el_len);
89
+}
90
+
91
+void rb_pop(struct ring_buffer *rb, void *buf) {
92
+    if (rb_len(rb) == 0) {
93
+        return;
94
+    }
95
+
96
+    memcpy(buf, rb->buffer + rb->tail * rb->el_len, rb->el_len);
97
+    rb->tail++;
98
+    if (rb->tail >= rb->size) {
99
+        rb->tail = 0;
100
+    }
101
+}
102
+
103
+size_t rb_get(struct ring_buffer *rb, void *data, size_t length) {
104
+    size_t count = 0;
105
+    while ((length > 0) && (rb_len(rb) > 0)) {
106
+        rb_pop(rb, data + count * rb->el_len);
107
+        count++;
108
+        length--;
109
+    }
110
+    return count;
111
+}

+ 4
- 0
src/sequence.c View File

@@ -56,6 +56,10 @@ uint32_t sequence_get_bpm(void) {
56 56
     return 60000 / (ms_per_beat * beats);
57 57
 }
58 58
 
59
+void sequence_set_ms(uint32_t new_ms) {
60
+    ms_per_beat = new_ms;
61
+}
62
+
59 63
 uint32_t sequence_get_ms(void) {
60 64
     return ms_per_beat;
61 65
 }

+ 141
- 23
src/ui.c View File

@@ -26,16 +26,60 @@
26 26
 #include "lcd.h"
27 27
 #include "led.h"
28 28
 #include "sequence.h"
29
+#include "usb.h"
30
+#include "usb_midi.h"
29 31
 #include "ui.h"
30 32
 
31 33
 #define BAT_FETCH_MS 66
32 34
 
35
+enum ui_settings {
36
+    SETTING_MODE = 0,
37
+
38
+    // loop station
39
+    SETTING_SPEED,
40
+
41
+    // drum machine
42
+    SETTING_BPM,
43
+    SETTING_BANK,
44
+    SETTING_LENGTH,
45
+
46
+    // midi
47
+    SETTING_CH_RX,
48
+    SETTING_CH_TX,
49
+
50
+    SETTING_NUM_MODES
51
+};
52
+
53
+static bool allowed_settings[MACHINE_NUM_MODES][SETTING_NUM_MODES] = {
54
+    // MODE_LOOPSTATION
55
+    {
56
+        true, true,
57
+        false, false, false,
58
+        false, false,
59
+    },
60
+
61
+    // MODE_DRUMMACHINE
62
+    {
63
+        true, false,
64
+        true, true, true,
65
+        false, false,
66
+    },
67
+
68
+    // MODE_MIDI
69
+    {
70
+        true, false,
71
+        false, false, false,
72
+        true, true,
73
+    },
74
+};
75
+
33 76
 static bool rec_held_down = false;
34
-static enum ui_modes ui_mode = 0;
77
+static enum ui_settings ui_setting = 0;
35 78
 static enum machine_modes machine_mode = 0;
36 79
 static uint32_t last_bat_fetch = 0;
37 80
 static float last_voltage = 0.0f;
38 81
 static float last_percentage = 0.0f;
82
+static uint8_t midi_rx = 0, midi_tx = 0;
39 83
 
40 84
 enum machine_modes ui_get_machinemode(void) {
41 85
     return machine_mode;
@@ -46,19 +90,20 @@ static void ui_redraw(void) {
46 90
     char val[64] = {0};
47 91
     char bat[64] = {0};
48 92
 
49
-    switch (ui_mode) {
50
-        case UI_BPM: {
93
+    switch (ui_setting) {
94
+        case SETTING_BPM: {
51 95
             snprintf(mode, sizeof(mode) - 1, "BPM:");
52 96
             snprintf(val, sizeof(val) - 1, "%"PRIu32, sequence_get_bpm());
53 97
             break;
54 98
         }
55 99
 
56
-        case UI_MODE: {
100
+        case SETTING_MODE: {
57 101
             if ((machine_mode == MODE_LOOPSTATION) && (sequence_get_ms() != 0)) {
58 102
                 snprintf(mode, sizeof(mode) - 1, "Mode: %"PRIu32"ms", sequence_get_ms());
59
-            }else {
103
+            } else {
60 104
                 snprintf(mode, sizeof(mode) - 1, "Mode:");
61 105
             }
106
+
62 107
             switch (machine_mode) {
63 108
                 case MODE_LOOPSTATION: {
64 109
                     snprintf(val, sizeof(val) - 1, "Loop");
@@ -70,6 +115,11 @@ static void ui_redraw(void) {
70 115
                     break;
71 116
                 }
72 117
 
118
+                case MODE_MIDI: {
119
+                    snprintf(val, sizeof(val) - 1, "MIDI");
120
+                    break;
121
+                }
122
+
73 123
                 default: {
74 124
                     printf("%s: invalid machine mode: %d\n", __func__, machine_mode);
75 125
                     machine_mode = 0;
@@ -77,24 +127,43 @@ static void ui_redraw(void) {
77 127
                     return;
78 128
                 }
79 129
             }
130
+
80 131
             break;
81 132
         }
82 133
 
83
-        case UI_LENGTH: {
134
+        case SETTING_LENGTH: {
84 135
             snprintf(mode, sizeof(mode) - 1, "Length:");
85 136
             snprintf(val, sizeof(val) - 1, "%"PRIu32, sequence_get_beats());
86 137
             break;
87 138
         }
88 139
 
89
-        case UI_BANK: {
140
+        case SETTING_BANK: {
90 141
             snprintf(mode, sizeof(mode) - 1, "Bank:");
91 142
             snprintf(val, sizeof(val) - 1, "%"PRIu32, sequence_get_bank());
92 143
             break;
93 144
         }
94 145
 
146
+        case SETTING_SPEED: {
147
+            snprintf(mode, sizeof(mode) - 1, "Speed:");
148
+            snprintf(val, sizeof(val) - 1, "%"PRIu32, sequence_get_ms());
149
+            break;
150
+        }
151
+
152
+        case SETTING_CH_RX: {
153
+            snprintf(mode, sizeof(mode) - 1, "Rx-Ch:");
154
+            snprintf(val, sizeof(val) - 1, "%"PRIu8, midi_rx + 1);
155
+            break;
156
+        }
157
+
158
+        case SETTING_CH_TX: {
159
+            snprintf(mode, sizeof(mode) - 1, "Tx-Ch:");
160
+            snprintf(val, sizeof(val) - 1, "%"PRIu8, midi_tx + 1);
161
+            break;
162
+        }
163
+
95 164
         default: {
96
-            printf("%s: invalid mode: %d\n", __func__, ui_mode);
97
-            ui_mode = 0;
165
+            printf("%s: invalid setting: %d\n", __func__, ui_setting);
166
+            ui_setting = 0;
98 167
             ui_redraw();
99 168
             return;
100 169
         }
@@ -166,16 +235,20 @@ static void ui_buttons_drummachine(enum buttons btn, bool val) {
166 235
     }
167 236
 }
168 237
 
238
+static void ui_buttons_midi(enum buttons btn, bool val) {
239
+    // TODO
240
+    (void)btn;
241
+    (void)val;
242
+}
243
+
169 244
 static void ui_buttons(enum buttons btn, bool val) {
170 245
     switch (btn) {
171 246
         case BTN_CLICK: {
172 247
             if (val) {
173
-                ui_mode = (ui_mode + 1) % UI_NUM_MODES;
174
-
175
-                // allow other ui modes only in drumkit mode
176
-                if (machine_mode == MODE_LOOPSTATION) {
177
-                    ui_mode = 0;
178
-                }
248
+                // only allow settings for this mode
249
+                do {
250
+                    ui_setting = (ui_setting + 1) % SETTING_NUM_MODES;
251
+                } while (!allowed_settings[machine_mode][ui_setting]);
179 252
 
180 253
                 ui_redraw();
181 254
             }
@@ -194,6 +267,11 @@ static void ui_buttons(enum buttons btn, bool val) {
194 267
                     break;
195 268
                 }
196 269
 
270
+                case MODE_MIDI: {
271
+                    ui_buttons_midi(btn, val);
272
+                    break;
273
+                }
274
+
197 275
                 default: {
198 276
                     printf("%s: invalid mode: %d\n", __func__, machine_mode);
199 277
                     machine_mode = 0;
@@ -211,14 +289,15 @@ void ui_encoder(int32_t val) {
211 289
         return;
212 290
     }
213 291
 
214
-    switch (ui_mode) {
215
-        case UI_BPM: {
292
+    switch (ui_setting) {
293
+        case SETTING_BPM: {
216 294
             sequence_set_bpm(sequence_get_bpm() + val);
217 295
             break;
218 296
         }
219 297
 
220
-        case UI_MODE: {
298
+        case SETTING_MODE: {
221 299
             int32_t tmp = machine_mode + val;
300
+
222 301
             while (tmp < 0) {
223 302
                 tmp += MACHINE_NUM_MODES;
224 303
             }
@@ -226,6 +305,11 @@ void ui_encoder(int32_t val) {
226 305
                 tmp -= MACHINE_NUM_MODES;
227 306
             }
228 307
 
308
+            // midi only when connected to pc
309
+            if ((tmp == MODE_MIDI) && !usb_is_connected()) {
310
+                tmp = (tmp + 1) % MACHINE_NUM_MODES;
311
+            }
312
+
229 313
             enum machine_modes prev_mode = machine_mode;
230 314
             machine_mode = tmp;
231 315
 
@@ -240,28 +324,47 @@ void ui_encoder(int32_t val) {
240 324
                     led_set(i, false);
241 325
                 }
242 326
 
243
-                // enable static LEDs in loopstation mode
244 327
                 if (machine_mode == MODE_LOOPSTATION) {
328
+                    sequence_set_beats(MAX_BEATS);
329
+
330
+                    // enable static LEDs in loopstation mode
245 331
                     led_set(NUM_CHANNELS, true);
246 332
                     led_set(NUM_CHANNELS + 4, true);
333
+                } else if (machine_mode == MODE_DRUMMACHINE) {
334
+                    sequence_set_beats(LED_COUNT);
247 335
                 }
248 336
             }
249 337
             break;
250 338
         }
251 339
 
252
-        case UI_LENGTH: {
340
+        case SETTING_LENGTH: {
253 341
             sequence_set_beats(sequence_get_beats() + val);
254 342
             break;
255 343
         }
256 344
 
257
-        case UI_BANK: {
345
+        case SETTING_BANK: {
258 346
             sequence_set_bank(sequence_get_bank() + val);
259 347
             break;
260 348
         }
261 349
 
350
+        case SETTING_SPEED: {
351
+            sequence_set_ms(sequence_get_ms() + val);
352
+            break;
353
+        }
354
+
355
+        case SETTING_CH_RX: {
356
+            midi_rx = (uint8_t)(midi_rx + val) % MIDI_MAX_CH;
357
+            break;
358
+        }
359
+
360
+        case SETTING_CH_TX: {
361
+            midi_tx = (uint8_t)(midi_tx + val) % MIDI_MAX_CH;
362
+            break;
363
+        }
364
+
262 365
         default: {
263
-            printf("%s: invalid mode: %d\n", __func__, ui_mode);
264
-            ui_mode = 0;
366
+            printf("%s: invalid setting: %d\n", __func__, ui_setting);
367
+            ui_setting = 0;
265 368
             ui_encoder(val);
266 369
             return;
267 370
         }
@@ -270,6 +373,21 @@ void ui_encoder(int32_t val) {
270 373
     ui_redraw();
271 374
 }
272 375
 
376
+void ui_midi_set(uint8_t channel, uint8_t note, uint8_t velocity) {
377
+    if (machine_mode != MODE_MIDI) {
378
+        return;
379
+    }
380
+
381
+    if (channel != midi_rx) {
382
+        return;
383
+    }
384
+
385
+    note = note % NUM_CHANNELS;
386
+    if (velocity > 0) {
387
+        sequence_handle_button_loopstation(BTN_A + note, false);
388
+    }
389
+}
390
+
273 391
 void ui_init(void) {
274 392
     buttons_callback(ui_buttons);
275 393
 

+ 80
- 0
src/usb.c View File

@@ -0,0 +1,80 @@
1
+/*
2
+ * Extended from TinyUSB example code.
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * The MIT License (MIT)
7
+ *
8
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
9
+ *
10
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ * of this software and associated documentation files (the "Software"), to deal
12
+ * in the Software without restriction, including without limitation the rights
13
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ * copies of the Software, and to permit persons to whom the Software is
15
+ * furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in
18
+ * all copies or substantial portions of the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ * THE SOFTWARE.
27
+ *
28
+ */
29
+
30
+#include "bsp/board.h"
31
+#include "tusb.h"
32
+
33
+#include "log.h"
34
+#include "usb_descriptors.h"
35
+#include "usb_midi.h"
36
+#include "usb.h"
37
+
38
+static bool usb_connected = false;
39
+
40
+void usb_init(void) {
41
+    usb_descriptor_init_id();
42
+
43
+    board_init();
44
+    tusb_init();
45
+}
46
+
47
+void usb_run(void) {
48
+    tud_task();
49
+    usb_midi_run();
50
+}
51
+
52
+// Invoked when device is mounted
53
+void tud_mount_cb(void) {
54
+    debug("device mounted");
55
+    usb_connected = true;
56
+}
57
+
58
+// Invoked when device is unmounted
59
+void tud_umount_cb(void) {
60
+    debug("device unmounted");
61
+    usb_connected = false;
62
+}
63
+
64
+// Invoked when usb bus is suspended
65
+// remote_wakeup_en : if host allow us  to perform remote wakeup
66
+// Within 7ms, device must draw an average of current less than 2.5 mA from bus
67
+void tud_suspend_cb(bool remote_wakeup_en) {
68
+    debug("device suspended wakeup=%d", remote_wakeup_en);
69
+    usb_connected = false;
70
+}
71
+
72
+// Invoked when usb bus is resumed
73
+void tud_resume_cb(void) {
74
+    debug("device resumed");
75
+    usb_connected = tud_mounted();
76
+}
77
+
78
+bool usb_is_connected(void) {
79
+    return usb_connected;
80
+}

+ 117
- 0
src/usb_cdc.c View File

@@ -0,0 +1,117 @@
1
+/*
2
+ * Extended from TinyUSB example code.
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * The MIT License (MIT)
7
+ *
8
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
9
+ *
10
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ * of this software and associated documentation files (the "Software"), to deal
12
+ * in the Software without restriction, including without limitation the rights
13
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ * copies of the Software, and to permit persons to whom the Software is
15
+ * furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in
18
+ * all copies or substantial portions of the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ * THE SOFTWARE.
27
+ *
28
+ */
29
+
30
+#include "bsp/board.h"
31
+#include "tusb.h"
32
+
33
+#include "console.h"
34
+#include "log.h"
35
+#include "main.h"
36
+#include "usb_descriptors.h"
37
+#include "usb_cdc.h"
38
+
39
+#define ENTER_BOOTLOADER_MAGIC 0x18
40
+#define DISABLE_CDC_DTR_CHECK
41
+
42
+static bool reroute_cdc_debug = false;
43
+
44
+void usb_cdc_write(const void *buf, size_t count) {
45
+#ifndef DISABLE_CDC_DTR_CHECK
46
+    if (!tud_cdc_connected()) {
47
+        return;
48
+    }
49
+#endif // DISABLE_CDC_DTR_CHECK
50
+
51
+    // implemented similar to Pico SDK stdio usb
52
+    uint32_t len = 0;
53
+    while (len < count) {
54
+        uint32_t n = count - len;
55
+        uint32_t available = tud_cdc_write_available();
56
+
57
+        // only write as much as possible
58
+        if (n > available) {
59
+            n = available;
60
+        }
61
+
62
+        len += tud_cdc_write(buf + len, n);
63
+
64
+        // run tud_task to actually move stuff from FIFO
65
+        tud_task();
66
+        tud_cdc_write_flush();
67
+    }
68
+}
69
+
70
+void usb_cdc_set_reroute(bool reroute) {
71
+    reroute_cdc_debug = reroute;
72
+}
73
+
74
+static void cdc_task(void) {
75
+    const uint32_t cdc_buf_len = 64;
76
+
77
+    if (tud_cdc_available()) {
78
+        char buf[cdc_buf_len + 1];
79
+        uint32_t count = tud_cdc_read(buf, cdc_buf_len);
80
+
81
+        if ((count >= 1) && (buf[0] == ENTER_BOOTLOADER_MAGIC)) {
82
+            reset_to_bootloader();
83
+        } else if (reroute_cdc_debug) {
84
+            debug_handle_input((const uint8_t *)buf, count);
85
+        } else {
86
+            cnsl_handle_input((const uint8_t *)buf, count);
87
+        }
88
+    }
89
+}
90
+
91
+// invoked when cdc when line state changed e.g connected/disconnected
92
+void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) {
93
+    (void) itf;
94
+    (void) rts;
95
+
96
+    static bool last_dtr = false;
97
+
98
+    if (dtr && !last_dtr) {
99
+        // clear left-over console input
100
+        cnsl_init();
101
+
102
+        // show past history
103
+        log_dump_to_usb();
104
+
105
+        debug("terminal connected");
106
+    } else if (!dtr && last_dtr) {
107
+        debug("terminal disconnected");
108
+    }
109
+
110
+    last_dtr = dtr;
111
+}
112
+
113
+// invoked when CDC interface received data from host
114
+void tud_cdc_rx_cb(uint8_t itf) {
115
+    (void) itf;
116
+    cdc_task();
117
+}

+ 236
- 0
src/usb_descriptors.c View File

@@ -0,0 +1,236 @@
1
+/*
2
+ * Extended from TinyUSB example code.
3
+ *
4
+ * Copyright (c) 2022 - 2024 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * The MIT License (MIT)
7
+ *
8
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
9
+ *
10
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ * of this software and associated documentation files (the "Software"), to deal
12
+ * in the Software without restriction, including without limitation the rights
13
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ * copies of the Software, and to permit persons to whom the Software is
15
+ * furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in
18
+ * all copies or substantial portions of the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ * THE SOFTWARE.
27
+ *
28
+ */
29
+
30
+#include "tusb.h"
31
+
32
+#include "usb_descriptors.h"
33
+
34
+#ifndef PICOWOTA
35
+
36
+/*
37
+ * A combination of interfaces must have a unique product id,
38
+ * since PC will save device driver after the first plug.
39
+ * Same VID/PID with different interface e.g MSC (first),
40
+ * then CDC (later) will possibly cause system error on PC.
41
+ *
42
+ * Auto ProductID layout's Bitmap:
43
+ *   [MSB]         HID | MSC | CDC          [LSB]
44
+ */
45
+#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n))
46
+#define USB_PID (0x2300 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) \
47
+    | _PID_MAP(HID, 2) | _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) )
48
+
49
+#define USB_VID   0xCafe
50
+#define USB_BCD   0x0200
51
+
52
+//--------------------------------------------------------------------+
53
+// Device Descriptors
54
+//--------------------------------------------------------------------+
55
+
56
+tusb_desc_device_t const desc_device =
57
+{
58
+    .bLength            = sizeof(tusb_desc_device_t),
59
+    .bDescriptorType    = TUSB_DESC_DEVICE,
60
+    .bcdUSB             = USB_BCD,
61
+
62
+    /*
63
+     * Use Interface Association Descriptor (IAD) for CDC
64
+     * As required by USB Specs IAD's subclass must be common class (2)
65
+     * and protocol must be IAD (1)
66
+     */
67
+    .bDeviceClass       = TUSB_CLASS_MISC,
68
+    .bDeviceSubClass    = MISC_SUBCLASS_COMMON,
69
+    .bDeviceProtocol    = MISC_PROTOCOL_IAD,
70
+
71
+    .bMaxPacketSize0    = CFG_TUD_ENDPOINT0_SIZE,
72
+
73
+    .idVendor           = USB_VID,
74
+    .idProduct          = USB_PID,
75
+    .bcdDevice          = 0x0100,
76
+
77
+    .iManufacturer      = 0x01,
78
+    .iProduct           = 0x02,
79
+    .iSerialNumber      = 0x03,
80
+
81
+    .bNumConfigurations = 0x01
82
+};
83
+
84
+// Invoked when received GET DEVICE DESCRIPTOR
85
+// Application return pointer to descriptor
86
+uint8_t const * tud_descriptor_device_cb(void) {
87
+    return (uint8_t const *) &desc_device;
88
+}
89
+
90
+//--------------------------------------------------------------------+
91
+// Configuration Descriptor
92
+//--------------------------------------------------------------------+
93
+
94
+enum {
95
+    ITF_NUM_CDC = 0,
96
+    ITF_NUM_CDC_DATA,
97
+    ITF_NUM_MIDI,
98
+    ITF_NUM_MIDI_STREAMING,
99
+    ITF_NUM_TOTAL
100
+};
101
+
102
+#define  CONFIG_TOTAL_LEN  (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_MIDI_DESC_LEN)
103
+
104
+#define EPNUM_CDC_NOTIF 0x82
105
+#define EPNUM_CDC_OUT   0x02
106
+#define EPNUM_CDC_IN    0x83
107
+#define EPNUM_MIDI_OUT   0x03
108
+#define EPNUM_MIDI_IN    0x84
109
+
110
+uint8_t const desc_configuration[] = {
111
+    // Config number, interface count, string index, total length, attribute, power in mA
112
+    TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
113
+
114
+    // Interface number, string index, EP notification address and size, EP data address (out, in) and size.
115
+    TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, CFG_TUD_CDC_EP_BUFSIZE),
116
+
117
+    // Interface number, string index, EP Out & EP In address, EP size
118
+    TUD_MIDI_DESCRIPTOR(ITF_NUM_MIDI, 5, EPNUM_MIDI_OUT, EPNUM_MIDI_IN, CFG_TUD_CDC_MIDI_BUFSIZE)
119
+};
120
+
121
+#if TUD_OPT_HIGH_SPEED
122
+// Per USB specs: high speed capable device must report device_qualifier and other_speed_configuration
123
+
124
+// other speed configuration
125
+uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN];
126
+
127
+// device qualifier is mostly similar to device descriptor since we don't change configuration based on speed
128
+tusb_desc_device_qualifier_t const desc_device_qualifier = {
129
+    .bLength            = sizeof(tusb_desc_device_qualifier_t),
130
+    .bDescriptorType    = TUSB_DESC_DEVICE_QUALIFIER,
131
+    .bcdUSB             = USB_BCD,
132
+
133
+    .bDeviceClass       = TUSB_CLASS_MISC,
134
+    .bDeviceSubClass    = MISC_SUBCLASS_COMMON,
135
+    .bDeviceProtocol    = MISC_PROTOCOL_IAD,
136
+
137
+    .bMaxPacketSize0    = CFG_TUD_ENDPOINT0_SIZE,
138
+    .bNumConfigurations = 0x01,
139
+    .bReserved          = 0x00
140
+};
141
+
142
+// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request
143
+// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete.
144
+// device_qualifier descriptor describes information about a high-speed capable device that would
145
+// change if the device were operating at the other speed. If not highspeed capable stall this request.
146
+uint8_t const* tud_descriptor_device_qualifier_cb(void) {
147
+    return (uint8_t const*) &desc_device_qualifier;
148
+}
149
+
150
+// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request
151
+// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
152
+// Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa
153
+uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) {
154
+    (void) index; // for multiple configurations
155
+
156
+    // other speed config is basically configuration with type = OHER_SPEED_CONFIG
157
+    memcpy(desc_other_speed_config, desc_configuration, CONFIG_TOTAL_LEN);
158
+    desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG;
159
+
160
+    // this example use the same configuration for both high and full speed mode
161
+    return desc_other_speed_config;
162
+}
163
+
164
+#endif // highspeed
165
+
166
+// Invoked when received GET CONFIGURATION DESCRIPTOR
167
+// Application return pointer to descriptor
168
+// Descriptor contents must exist long enough for transfer to complete
169
+uint8_t const * tud_descriptor_configuration_cb(uint8_t index) {
170
+    (void) index; // for multiple configurations
171
+
172
+    // This example use the same configuration for both high and full speed mode
173
+    return desc_configuration;
174
+}
175
+
176
+#endif // PICOWOTA
177
+
178
+//--------------------------------------------------------------------+
179
+// String Descriptors
180
+//--------------------------------------------------------------------+
181
+
182
+char string_pico_serial[2 * PICO_UNIQUE_BOARD_ID_SIZE_BYTES + 1];
183
+
184
+void usb_descriptor_init_id(void) {
185
+    pico_get_unique_board_id_string(string_pico_serial, sizeof(string_pico_serial));
186
+}
187
+
188
+#ifndef PICOWOTA
189
+
190
+// array of pointer to string descriptors
191
+char const* string_desc_arr [] = {
192
+    (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
193
+    "xythobuz",                    // 1: Manufacturer
194
+    "LARS",                        // 2: Product
195
+    string_pico_serial,            // 3: Serials, should use chip ID
196
+    "Debug Serial",                // 4: CDC Interface
197
+    "LARS MIDI",                   // 5: MIDI Interface
198
+};
199
+
200
+static uint16_t _desc_str[32];
201
+
202
+// Invoked when received GET STRING DESCRIPTOR request
203
+// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
204
+uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
205
+    (void) langid;
206
+
207
+    uint8_t chr_count;
208
+
209
+    if ( index == 0) {
210
+        memcpy(&_desc_str[1], string_desc_arr[0], 2);
211
+        chr_count = 1;
212
+    } else {
213
+        // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
214
+        // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
215
+
216
+        if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL;
217
+
218
+        const char* str = string_desc_arr[index];
219
+
220
+        // Cap at max char
221
+        chr_count = strlen(str);
222
+        if ( chr_count > 31 ) chr_count = 31;
223
+
224
+        // Convert ASCII string into UTF-16
225
+        for(uint8_t i=0; i<chr_count; i++) {
226
+            _desc_str[1+i] = str[i];
227
+        }
228
+    }
229
+
230
+    // first byte is length (including header), second byte is string type
231
+    _desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2*chr_count + 2);
232
+
233
+    return _desc_str;
234
+}
235
+
236
+#endif // PICOWOTA

+ 69
- 0
src/usb_midi.c View File

@@ -0,0 +1,69 @@
1
+/*
2
+ * Extended from TinyUSB example code.
3
+ *
4
+ * Copyright (c) 2022 - 2023 Thomas Buck (thomas@xythobuz.de)
5
+ *
6
+ * The MIT License (MIT)
7
+ *
8
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
9
+ *
10
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ * of this software and associated documentation files (the "Software"), to deal
12
+ * in the Software without restriction, including without limitation the rights
13
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ * copies of the Software, and to permit persons to whom the Software is
15
+ * furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in
18
+ * all copies or substantial portions of the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ * THE SOFTWARE.
27
+ *
28
+ */
29
+
30
+#include "bsp/board.h"
31
+#include "tusb.h"
32
+
33
+#include "log.h"
34
+#include "ui.h"
35
+#include "usb_midi.h"
36
+
37
+#define MIDI_CABLE_NUM 0 // MIDI jack associated with USB endpoint
38
+
39
+#define MIDI_NOTE_OFF 0x80
40
+#define MIDI_NOTE_ON 0x90
41
+
42
+void usb_midi_tx(uint8_t channel, uint8_t note, uint8_t velocity) {
43
+    uint8_t packet[3] = {
44
+        ((velocity == 0) ? MIDI_NOTE_OFF : MIDI_NOTE_ON) | channel,
45
+        note, velocity,
46
+    };
47
+
48
+    tud_midi_stream_write(MIDI_CABLE_NUM, packet, sizeof(packet));
49
+}
50
+
51
+void usb_midi_run(void) {
52
+    while (tud_midi_available()) {
53
+        uint8_t packet[4];
54
+        tud_midi_packet_read(packet);
55
+        //debug("rx: %02X %02X %02X %02X", packet[0], packet[1], packet[2], packet[3]);
56
+
57
+        uint8_t type = packet[1] & 0xF0;
58
+        uint8_t channel = packet[1] & 0x0F;
59
+        uint8_t note = packet[2];
60
+        uint8_t velocity = packet[3];
61
+        //debug("rx: type=%02X channel=%d note=%d velocity=%d", type, channel, note, velocity);
62
+
63
+        if (type == MIDI_NOTE_OFF) {
64
+            ui_midi_set(channel, note, 0);
65
+        } else if (type == MIDI_NOTE_ON) {
66
+            ui_midi_set(channel, note, (velocity == 0) ? 127 : velocity);
67
+        }
68
+    }
69
+}

Loading…
Cancel
Save