├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── mouse.c ├── main.c ├── pico_sdk_import.cmake ├── keycodes.h ├── tusb_config.h ├── keyboard.c ├── README.md └── hid_app.c /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | include(pico_sdk_import.cmake) 3 | project(usb2sun C CXX ASM) 4 | 5 | set(CMAKE_C_STANDARD 11) 6 | set(CMAKE_CXX_STANDARD 17) 7 | 8 | pico_sdk_init() 9 | 10 | add_executable(usb2sun 11 | main.c 12 | hid_app.c 13 | mouse.c 14 | keyboard.c 15 | ) 16 | target_include_directories(usb2sun PUBLIC ${CMAKE_CURRENT_LIST_DIR}) 17 | target_link_libraries(usb2sun PUBLIC pico_stdlib tinyusb_host tinyusb_board) 18 | pico_enable_stdio_usb(usb2sun 0) 19 | pico_enable_stdio_uart(usb2sun 0) 20 | pico_set_program_url(usb2sun "https://github.com/jgilje/usb2sun") 21 | pico_add_extra_outputs(usb2sun) 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Joakim L. Gilje 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /mouse.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | static bool serial_data_in_tail = false; 8 | static bool updated = false; 9 | static int32_t delta_x = 0; 10 | static int32_t delta_y = 0; 11 | #define NO_BUTTONS 0x7 12 | static char btns = NO_BUTTONS; 13 | static uint32_t interval = 40; 14 | 15 | #define UART_MOUSE_ID uart0 16 | #define UART_MOUSE_TX_PIN 0 17 | void mouse_uart_init() { 18 | uart_init(UART_MOUSE_ID, 1200); 19 | gpio_set_function(UART_MOUSE_TX_PIN, GPIO_FUNC_UART); 20 | uart_set_hw_flow(UART_MOUSE_ID, false, false); 21 | uart_set_format(UART_MOUSE_ID, 8, 1, UART_PARITY_NONE); 22 | gpio_set_outover(UART_MOUSE_TX_PIN, GPIO_OVERRIDE_INVERT); 23 | } 24 | 25 | static inline int32_t clamp(int32_t value, int32_t min, int32_t max) { 26 | if (value < min) return min; 27 | else if (value > max) return max; 28 | return value; 29 | } 30 | 31 | static uint32_t push_head_packet() { 32 | uart_putc_raw(UART_MOUSE_ID, btns | 0x80); 33 | uart_putc_raw(UART_MOUSE_ID, delta_x); 34 | uart_putc_raw(UART_MOUSE_ID, delta_y); 35 | btns = NO_BUTTONS; 36 | delta_x = 0; 37 | delta_y = 0; 38 | serial_data_in_tail = true; 39 | return 25; 40 | } 41 | 42 | static uint32_t push_tail_packet() { 43 | uart_putc_raw(UART_MOUSE_ID, delta_x); 44 | uart_putc_raw(UART_MOUSE_ID, delta_y); 45 | delta_x = 0; 46 | delta_y = 0; 47 | serial_data_in_tail = false; 48 | return 15; 49 | } 50 | 51 | void mouse_tx() { 52 | static uint32_t start_ms = 0; 53 | if ((board_millis() - start_ms) < interval) { 54 | return; 55 | } 56 | start_ms += interval; 57 | 58 | if (updated) { 59 | if (serial_data_in_tail) { 60 | interval = push_tail_packet(); 61 | updated = (btns != NO_BUTTONS); 62 | } else { 63 | interval = push_head_packet(); 64 | } 65 | } 66 | } 67 | 68 | void process_mouse_report(hid_mouse_report_t const * report) { 69 | btns = ((report->buttons & MOUSE_BUTTON_LEFT) ? 0 : 4) 70 | | ((report->buttons & MOUSE_BUTTON_MIDDLE) ? 0 : 2) 71 | | ((report->buttons & MOUSE_BUTTON_RIGHT) ? 0 : 1) 72 | ; 73 | delta_x += report->x; 74 | delta_y += -report->y; 75 | delta_x = clamp(delta_x, -127, 127); 76 | delta_y = clamp(delta_y, -127, 127); 77 | updated = true; 78 | } 79 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * USB2Sun 3 | * Connect a USB keyboard and mouse to your Sun workstation! 4 | * 5 | * Joakim L. Gilje 6 | * Adapted from the TinyUSB Host examples 7 | */ 8 | 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "hardware/watchdog.h" 16 | #include "pico/stdlib.h" 17 | 18 | extern void keyboard_uart_init(); 19 | extern void mouse_uart_init(); 20 | extern void mouse_tx(); 21 | void led_blinking_task(void); 22 | 23 | int last_keyboard_connected = 0; 24 | int last_mouse_connected = 0; 25 | int keyboard_connected = 0; 26 | int mouse_connected = 0; 27 | static int watchdog_enabled = 0; 28 | 29 | int main(void) { 30 | board_init(); 31 | 32 | tuh_init(BOARD_TUH_RHPORT); 33 | 34 | keyboard_uart_init(); 35 | mouse_uart_init(); 36 | printf("USB2Sun Init\n"); 37 | 38 | while (true) { 39 | tuh_task(); 40 | led_blinking_task(); 41 | 42 | mouse_tx(); 43 | if (board_millis() > 15000 && keyboard_connected == 0 && mouse_connected == 0 && watchdog_enabled == 0) { 44 | printf("No USB devices, rebooting via watchdog\n"); 45 | watchdog_enable(100, 1); 46 | watchdog_enabled = 1; 47 | } 48 | } 49 | 50 | return 0; 51 | } 52 | 53 | //--------------------------------------------------------------------+ 54 | // Blinking Task 55 | //--------------------------------------------------------------------+ 56 | void led_blinking_task(void) 57 | { 58 | const uint32_t interval_ms = 1000; 59 | static uint32_t start_ms = 0; 60 | 61 | static bool led_state = false; 62 | 63 | if (last_keyboard_connected != keyboard_connected) { 64 | for (int i = 0; i < 3; ++i) { 65 | board_led_write(1); 66 | sleep_ms(50); 67 | board_led_write(0); 68 | sleep_ms(50); 69 | } 70 | last_keyboard_connected = keyboard_connected; 71 | return; 72 | } 73 | 74 | if (last_mouse_connected != mouse_connected) { 75 | for (int i = 0; i < 5; ++i) { 76 | board_led_write(1); 77 | sleep_ms(150); 78 | board_led_write(0); 79 | sleep_ms(150); 80 | } 81 | last_mouse_connected = mouse_connected; 82 | return; 83 | } 84 | 85 | if (keyboard_connected || mouse_connected) { 86 | if (board_millis() - start_ms < 2000) { 87 | board_led_write(0); 88 | } else if (board_millis() - start_ms < 2100) { 89 | board_led_write(1); 90 | } else { 91 | start_ms = board_millis(); 92 | } 93 | return; 94 | } 95 | 96 | // Blink every interval ms 97 | if ( board_millis() - start_ms < interval_ms) return; // not enough time 98 | start_ms += interval_ms; 99 | 100 | board_led_write(led_state); 101 | led_state = 1 - led_state; // toggle 102 | } 103 | 104 | //--------------------------------------------------------------------+ 105 | // TinyUSB Callbacks 106 | //--------------------------------------------------------------------+ 107 | 108 | void tuh_mount_cb(uint8_t dev_addr) 109 | { 110 | // application set-up 111 | printf("\n\nA device with address %d is mounted\n\n", dev_addr); 112 | } 113 | 114 | void tuh_umount_cb(uint8_t dev_addr) 115 | { 116 | // application tear-down 117 | printf("\n\nA device with address %d is unmounted \r\n", dev_addr); 118 | } 119 | -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | # GIT_SUBMODULES_RECURSE was added in 3.17 33 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") 34 | FetchContent_Declare( 35 | pico_sdk 36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 37 | GIT_TAG master 38 | GIT_SUBMODULES_RECURSE FALSE 39 | ) 40 | else () 41 | FetchContent_Declare( 42 | pico_sdk 43 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 44 | GIT_TAG master 45 | ) 46 | endif () 47 | 48 | if (NOT pico_sdk) 49 | message("Downloading Raspberry Pi Pico SDK") 50 | FetchContent_Populate(pico_sdk) 51 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 52 | endif () 53 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 54 | else () 55 | message(FATAL_ERROR 56 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 57 | ) 58 | endif () 59 | endif () 60 | 61 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 62 | if (NOT EXISTS ${PICO_SDK_PATH}) 63 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 64 | endif () 65 | 66 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 67 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 68 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 69 | endif () 70 | 71 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 72 | 73 | include(${PICO_SDK_INIT_CMAKE_FILE}) 74 | -------------------------------------------------------------------------------- /keycodes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Sources: 3 | * 4 | * https://github.com/xelalexv/suniversal/blob/master/suniversal/sun_to_usb.h 5 | * Part no. 800-6802-12 - Sun Types Keyboard and Mouse Product Notes 6 | * http://vtda.org/docs/computing/Sun/hardware/800-6802-12_Type5KeyboardandMouseProductNotes_RevA_Oct93.pdf 7 | * http://shikasan.net/sunkey/sunkey_e.html 8 | */ 9 | 10 | #ifndef _KEYCODES_H_ 11 | #define _KEYCODES_H_ 12 | 13 | #include 14 | #include "tusb.h" 15 | 16 | static const uint8_t usb2sun[256] = { 17 | [HID_KEY_HELP] = 0x76, 18 | [HID_KEY_ESCAPE] = 0x1d, 19 | [HID_KEY_F1] = 0x05, 20 | [HID_KEY_F2] = 0x06, 21 | [HID_KEY_F3] = 0x08, 22 | [HID_KEY_F4] = 0x0a, 23 | [HID_KEY_F5] = 0x0c, 24 | [HID_KEY_F6] = 0x0e, 25 | [HID_KEY_F7] = 0x10, 26 | [HID_KEY_F8] = 0x11, 27 | [HID_KEY_F9] = 0x12, 28 | [HID_KEY_F10] = 0x07, 29 | [HID_KEY_F11] = 0x09, 30 | [HID_KEY_F12] = 0x0b, 31 | [HID_KEY_PRINT_SCREEN] = 0x16, 32 | [HID_KEY_SCROLL_LOCK] = 0x17, 33 | [HID_KEY_PAUSE] = 0x15, 34 | [HID_KEY_MUTE] = 0x2d, 35 | [HID_KEY_VOLUME_DOWN] = 0x2, 36 | [HID_KEY_VOLUME_UP] = 0x4, 37 | [HID_KEY_POWER] = 0x30, 38 | 39 | [HID_KEY_GRAVE] = 0x2a, 40 | [HID_KEY_1] = 0x1e, 41 | [HID_KEY_2] = 0x1f, 42 | [HID_KEY_3] = 0x20, 43 | [HID_KEY_4] = 0x21, 44 | [HID_KEY_5] = 0x22, 45 | [HID_KEY_6] = 0x23, 46 | [HID_KEY_7] = 0x24, 47 | [HID_KEY_8] = 0x25, 48 | [HID_KEY_9] = 0x26, 49 | [HID_KEY_0] = 0x27, 50 | [HID_KEY_MINUS] = 0x28, 51 | [HID_KEY_EQUAL] = 0x29, 52 | [HID_KEY_BACKSPACE] = 0x2b, 53 | 54 | [HID_KEY_TAB] = 0x35, 55 | [HID_KEY_Q] = 0x36, 56 | [HID_KEY_W] = 0x37, 57 | [HID_KEY_E] = 0x38, 58 | [HID_KEY_R] = 0x39, 59 | [HID_KEY_T] = 0x3a, 60 | [HID_KEY_Y] = 0x3b, 61 | [HID_KEY_U] = 0x3c, 62 | [HID_KEY_I] = 0x3d, 63 | [HID_KEY_O] = 0x3e, 64 | [HID_KEY_P] = 0x3f, 65 | [HID_KEY_BRACKET_LEFT] = 0x40, 66 | [HID_KEY_BRACKET_RIGHT] = 0x41, 67 | [HID_KEY_BACKSLASH] = 0x58, 68 | 69 | [HID_KEY_CAPS_LOCK] = 0x77, 70 | [HID_KEY_A] = 0x4d, 71 | [HID_KEY_S] = 0x4e, 72 | [HID_KEY_D] = 0x4f, 73 | [HID_KEY_F] = 0x50, 74 | [HID_KEY_G] = 0x51, 75 | [HID_KEY_H] = 0x52, 76 | [HID_KEY_J] = 0x53, 77 | [HID_KEY_K] = 0x54, 78 | [HID_KEY_L] = 0x55, 79 | [HID_KEY_SEMICOLON] = 0x56, 80 | [HID_KEY_APOSTROPHE] = 0x57, 81 | [HID_KEY_ENTER] = 0x59, 82 | 83 | [HID_KEY_SHIFT_LEFT] = 0x63, 84 | [HID_KEY_Z] = 0x64, 85 | [HID_KEY_X] = 0x65, 86 | [HID_KEY_C] = 0x66, 87 | [HID_KEY_V] = 0x67, 88 | [HID_KEY_B] = 0x68, 89 | [HID_KEY_N] = 0x69, 90 | [HID_KEY_M] = 0x6a, 91 | [HID_KEY_COMMA] = 0x6b, 92 | [HID_KEY_PERIOD] = 0x6c, 93 | [HID_KEY_SLASH] = 0x6d, 94 | [HID_KEY_SHIFT_RIGHT] = 0x6e, 95 | 96 | [HID_KEY_CONTROL_LEFT] = 0x4c, 97 | [HID_KEY_GUI_LEFT] = 0x78, // left triangle 98 | [HID_KEY_ALT_LEFT] = 0x13, 99 | [HID_KEY_SPACE] = 0x79, 100 | [HID_KEY_ALT_RIGHT] = 0x0d, // alt graph 101 | [HID_KEY_GUI_RIGHT] = 0x7a, // right triangle 102 | [HID_KEY_CONTROL_RIGHT] = 0x43, // compose 103 | 104 | [HID_KEY_INSERT] = 0x2c, 105 | [HID_KEY_HOME] = 0x34, 106 | [HID_KEY_PAGE_UP] = 0x60, 107 | [HID_KEY_DELETE] = 0x42, 108 | [HID_KEY_END] = 0x4a, 109 | [HID_KEY_PAGE_DOWN] = 0x7b, 110 | 111 | [HID_KEY_ARROW_UP] = 0x14, 112 | [HID_KEY_ARROW_LEFT] = 0x18, 113 | [HID_KEY_ARROW_DOWN] = 0x1b, 114 | [HID_KEY_ARROW_RIGHT] = 0x1c, 115 | 116 | [HID_KEY_NUM_LOCK] = 0x62, 117 | [HID_KEY_KEYPAD_DIVIDE] = 0x2e, 118 | [HID_KEY_KEYPAD_MULTIPLY] = 0x2f, 119 | [HID_KEY_KEYPAD_SUBTRACT] = 0x47, 120 | [HID_KEY_KEYPAD_ADD] = 0x47, 121 | [HID_KEY_KEYPAD_ENTER] = 0x5a, 122 | [HID_KEY_KEYPAD_COMMA] = 0x32, 123 | [HID_KEY_KEYPAD_0] = 0x5e, 124 | [HID_KEY_KEYPAD_1] = 0x70, 125 | [HID_KEY_KEYPAD_2] = 0x71, 126 | [HID_KEY_KEYPAD_3] = 0x72, 127 | [HID_KEY_KEYPAD_4] = 0x5b, 128 | [HID_KEY_KEYPAD_5] = 0x5c, 129 | [HID_KEY_KEYPAD_6] = 0x5d, 130 | [HID_KEY_KEYPAD_7] = 0x44, 131 | [HID_KEY_KEYPAD_8] = 0x45, 132 | [HID_KEY_KEYPAD_9] = 0x46, 133 | }; 134 | 135 | #define SUN_KEY_STOP 0x01 136 | #define SUN_KEY_AGAIN 0x03 137 | #define SUN_KEY_PROPS 0x19 138 | #define SUN_KEY_UNDO 0x1a 139 | #define SUN_KEY_FRONT 0x31 140 | #define SUN_KEY_COPY 0x33 141 | #define SUN_KEY_OPEN 0x48 142 | #define SUN_KEY_PASTE 0x49 143 | #define SUN_KEY_FIND 0x5f 144 | #define SUN_KEY_CUT 0x61 145 | 146 | #endif 147 | -------------------------------------------------------------------------------- /tusb_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #ifndef _TUSB_CONFIG_H_ 27 | #define _TUSB_CONFIG_H_ 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | //--------------------------------------------------------------------+ 34 | // Board Specific Configuration 35 | //--------------------------------------------------------------------+ 36 | 37 | #if CFG_TUSB_MCU == OPT_MCU_RP2040 38 | // change to 1 if using pico-pio-usb as host controller for raspberry rp2040 39 | #define CFG_TUH_RPI_PIO_USB 0 40 | #define BOARD_TUH_RHPORT CFG_TUH_RPI_PIO_USB 41 | #endif 42 | 43 | // RHPort number used for host can be defined by board.mk, default to port 0 44 | #ifndef BOARD_TUH_RHPORT 45 | #define BOARD_TUH_RHPORT 0 46 | #endif 47 | 48 | // RHPort max operational speed can defined by board.mk 49 | #ifndef BOARD_TUH_MAX_SPEED 50 | #define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED 51 | #endif 52 | 53 | //-------------------------------------------------------------------- 54 | // COMMON CONFIGURATION 55 | //-------------------------------------------------------------------- 56 | 57 | // defined by compiler flags for flexibility 58 | #ifndef CFG_TUSB_MCU 59 | #error CFG_TUSB_MCU must be defined 60 | #endif 61 | 62 | #ifndef CFG_TUSB_OS 63 | #define CFG_TUSB_OS OPT_OS_PICO 64 | #endif 65 | 66 | #ifndef CFG_TUSB_DEBUG 67 | #define CFG_TUSB_DEBUG 0 68 | #endif 69 | 70 | // Enable Host stack 71 | #define CFG_TUH_ENABLED 1 72 | 73 | // Default is max speed that hardware controller could support with on-chip PHY 74 | #define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED 75 | 76 | /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. 77 | * Tinyusb use follows macros to declare transferring memory so that they can be put 78 | * into those specific section. 79 | * e.g 80 | * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) 81 | * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) 82 | */ 83 | #ifndef CFG_TUH_MEM_SECTION 84 | #define CFG_TUH_MEM_SECTION 85 | #endif 86 | 87 | #ifndef CFG_TUH_MEM_ALIGN 88 | #define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4))) 89 | #endif 90 | 91 | //-------------------------------------------------------------------- 92 | // CONFIGURATION 93 | //-------------------------------------------------------------------- 94 | 95 | // Size of buffer to hold descriptors and other data used for enumeration 96 | #define CFG_TUH_ENUMERATION_BUFSIZE 256 97 | 98 | #define CFG_TUH_HUB 1 99 | #define CFG_TUH_CDC 0 100 | #define CFG_TUH_HID (3*CFG_TUH_DEVICE_MAX) // typical keyboard + mouse device can have 3-4 HID interfaces 101 | #define CFG_TUH_MSC 0 102 | #define CFG_TUH_VENDOR 0 103 | 104 | // max device support (excluding hub device) 105 | #define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1) // hub typically has 4 ports 106 | 107 | //------------- HID -------------// 108 | #define CFG_TUH_HID_EPIN_BUFSIZE 64 109 | #define CFG_TUH_HID_EPOUT_BUFSIZE 64 110 | 111 | #ifdef __cplusplus 112 | } 113 | #endif 114 | 115 | #endif /* _TUSB_CONFIG_H_ */ 116 | -------------------------------------------------------------------------------- /keyboard.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "keycodes.h" 8 | 9 | #define UART_KBD_ID uart1 10 | #define UART_KBD_IRQ UART1_IRQ 11 | #define UART_KBD_TX_PIN 4 12 | #define UART_KBD_RX_PIN 5 13 | 14 | void on_keyboard_rx(); 15 | 16 | void keyboard_uart_init() { 17 | uart_init(UART_KBD_ID, 1200); 18 | gpio_set_function(UART_KBD_TX_PIN, GPIO_FUNC_UART); 19 | gpio_set_function(UART_KBD_RX_PIN, GPIO_FUNC_UART); 20 | uart_set_hw_flow(UART_KBD_ID, false, false); 21 | uart_set_format(UART_KBD_ID, 8, 1, UART_PARITY_NONE); 22 | irq_set_exclusive_handler(UART_KBD_IRQ, on_keyboard_rx); 23 | irq_set_enabled(UART_KBD_IRQ, true); 24 | uart_set_irq_enables(UART_KBD_ID, true, false); 25 | gpio_set_inover(UART_KBD_RX_PIN, GPIO_OVERRIDE_INVERT); 26 | gpio_set_outover(UART_KBD_TX_PIN, GPIO_OVERRIDE_INVERT); 27 | } 28 | 29 | // RX interrupt handler 30 | void on_keyboard_rx() { 31 | while (uart_is_readable(UART_KBD_ID)) { 32 | // printf("System command: "); 33 | uint8_t ch = uart_getc(UART_KBD_ID); 34 | 35 | switch (ch) { 36 | case 0x01: // reset 37 | // printf("Reset\n"); 38 | uart_putc_raw(UART_KBD_ID, 0xff); 39 | uart_putc_raw(UART_KBD_ID, 0x04); 40 | uart_putc_raw(UART_KBD_ID, 0x7f); 41 | break; 42 | case 0x02: // bell on 43 | // printf("Bell on\n"); 44 | break; 45 | case 0x03: // bell off 46 | // printf("Bell off\n"); 47 | break; 48 | case 0x0a: // click on 49 | // printf("Click on\n"); 50 | break; 51 | case 0x0b: // click off 52 | // printf("Click off\n"); 53 | break; 54 | case 0x0e: // led command 55 | // printf("Led\n"); 56 | { 57 | uint8_t led = uart_getc(UART_KBD_ID); 58 | } 59 | break; 60 | case 0x0f: // layout command 61 | // printf("Layout\n"); 62 | uart_putc_raw(UART_KBD_ID, 0xfe); 63 | uart_putc_raw(UART_KBD_ID, 0x00); 64 | break; 65 | default: 66 | // printf("Unknown system command: 0x%02x\n", ch); 67 | break; 68 | }; 69 | } 70 | } 71 | 72 | static uint8_t hotkey_combo = KEYBOARD_MODIFIER_LEFTSHIFT | KEYBOARD_MODIFIER_LEFTCTRL; 73 | static uint8_t active_key_list[128]; 74 | static uint8_t active_keys = 0; 75 | 76 | void process_kbd_report(hid_keyboard_report_t const *report) { 77 | uint8_t current_key_list[128]; 78 | memset(current_key_list, 0, sizeof(current_key_list)); 79 | 80 | if (report->modifier != 0) { 81 | current_key_list[usb2sun[HID_KEY_SHIFT_LEFT]] = report->modifier & KEYBOARD_MODIFIER_LEFTSHIFT ? 1 : 0; 82 | current_key_list[usb2sun[HID_KEY_CONTROL_LEFT]] = report->modifier & KEYBOARD_MODIFIER_LEFTCTRL ? 1 : 0; 83 | current_key_list[usb2sun[HID_KEY_ALT_LEFT]] = report->modifier & KEYBOARD_MODIFIER_LEFTALT ? 1 : 0; 84 | current_key_list[usb2sun[HID_KEY_GUI_LEFT]] = report->modifier & KEYBOARD_MODIFIER_LEFTGUI ? 1 : 0; 85 | current_key_list[usb2sun[HID_KEY_SHIFT_RIGHT]] = report->modifier & KEYBOARD_MODIFIER_RIGHTSHIFT ? 1 : 0; 86 | current_key_list[usb2sun[HID_KEY_CONTROL_RIGHT]] = report->modifier & KEYBOARD_MODIFIER_RIGHTCTRL ? 1 : 0; 87 | current_key_list[usb2sun[HID_KEY_ALT_RIGHT]] = report->modifier & KEYBOARD_MODIFIER_RIGHTALT ? 1 : 0; 88 | current_key_list[usb2sun[HID_KEY_GUI_RIGHT]] = report->modifier & KEYBOARD_MODIFIER_RIGHTGUI ? 1 : 0; 89 | } 90 | 91 | uint8_t i = 0; 92 | if (report->modifier == hotkey_combo) { 93 | current_key_list[usb2sun[HID_KEY_SHIFT_LEFT]] = 0; 94 | current_key_list[usb2sun[HID_KEY_CONTROL_LEFT]] = 0; 95 | switch (report->keycode[i]) { 96 | case HID_KEY_F1: 97 | current_key_list[SUN_KEY_STOP] = 1; 98 | break; 99 | case HID_KEY_F2: 100 | current_key_list[SUN_KEY_AGAIN] = 1; 101 | break; 102 | case HID_KEY_1: 103 | current_key_list[SUN_KEY_PROPS] = 1; 104 | break; 105 | case HID_KEY_2: 106 | current_key_list[SUN_KEY_UNDO] = 1; 107 | break; 108 | case HID_KEY_Q: 109 | current_key_list[SUN_KEY_FRONT] = 1; 110 | break; 111 | case HID_KEY_W: 112 | current_key_list[SUN_KEY_COPY] = 1; 113 | break; 114 | case HID_KEY_A: 115 | current_key_list[SUN_KEY_OPEN] = 1; 116 | break; 117 | case HID_KEY_S: 118 | current_key_list[SUN_KEY_PASTE] = 1; 119 | break; 120 | case HID_KEY_Z: 121 | current_key_list[SUN_KEY_FIND] = 1; 122 | break; 123 | case HID_KEY_X: 124 | current_key_list[SUN_KEY_CUT] = 1; 125 | break; 126 | } 127 | i++; 128 | } 129 | for (; i < 6; i++) { 130 | if (report->keycode[i]) { 131 | current_key_list[usb2sun[report->keycode[i]]] = 1; 132 | } 133 | } 134 | 135 | for (uint8_t i = 0; i < 128; i++) { 136 | if (active_key_list[i] && !current_key_list[i]) { 137 | uart_putc_raw(UART_KBD_ID, i | 0x80); 138 | active_key_list[i] = 0; 139 | active_keys--; 140 | } 141 | if (!active_key_list[i] && current_key_list[i]) { 142 | uart_putc_raw(UART_KBD_ID, i); 143 | active_key_list[i] = 1; 144 | active_keys++; 145 | } 146 | } 147 | 148 | if (active_keys == 0) { 149 | uart_putc_raw(UART_KBD_ID, 0x7f); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | USB2Sun 2 | ======= 3 | While I've found many projects for connecting the Sun serial based keyboards to a modern 4 | machine via USB, I've never found anything to connect a USB keyboard onto a classical 5 | Sun workstation. 6 | 7 | This project allows you to connect a USB keyboard and mouse to a Sun workstation, using a Raspberry Pico as a bridge. 8 | 9 | I've used two SN74LVC244AN as level shifters, one for 3.3V to 5V (from Pico to Sun), 10 | and one for 5V to 3.3V (from Sun to Pico). There are probably smaller shifters than 11 | the octal shifter I found, but I couldn't find any. 12 | 13 | The Pico board is powered by the Sun workstation's 5V supply. 14 | 15 | This project is currently targetting Pico SDK 1.5.1, and the corresponding TinyUSB version. 16 | 17 | Features 18 | -------- 19 | * Emulate the left keyboard function keys by entering LeftShift+LeftControl and the corresponding key 20 | 21 | | PC | Function | 22 | |-----|------------| 23 | | F1 | Stop | 24 | | F2 | Again | 25 | | 1 | Props | 26 | | 2 | Undo | 27 | | Q | Front | 28 | | W | Copy | 29 | | A | Open | 30 | | S | Paste | 31 | | Z | Find | 32 | | X | Cut | 33 | 34 | Breaking into OpenFirmware would be LeftShift+LeftControl+F1+A 35 | 36 | Missing Features 37 | ---------------- 38 | * Keyboard power on. Shorting minidin pin 7 to 5V rail powers on the system, however, the 5V rail is unpowered when the system is off - so the Pico is also unpowered in this state. 39 | * LED indicators (num - caps and scroll lock). 40 | * Switching layouts. PC or Unix layout and international layout (the emulated keyboard responds US for now). 41 | 42 | Build 43 | ----- 44 | Make sure the Pico SDK has been checked out, as detailed by the Pi foundation. 45 | 46 | mkdir build && cd build 47 | PICO_SDK_PATH=../../pico-sdk cmake .. 48 | make 49 | 50 | If your flashing with a Raspberry Pi, flash the firmware using 51 | 52 | openocd -f interface/raspberrypi-swd.cfg -f target/rp2040.cfg -c "program usb2sun.elf verify reset exit" 53 | 54 | Or copy usb2sun.uf2 onto the Pico when it's in flash mode. 55 | 56 | Usage 57 | ----- 58 | Connect a USB mouse and keyboard using the OTG adapter. Wireless combo sets will work best, as they do not require a hub. 59 | 60 | The project can be either plugged directly into the keyboard port on the workstation, or daisy-chained to a Sun keyboard. If daisy-chaining to the keyboard, do not connect the UART1 pins (6 and 7), only connect UART0 TX (1) to the Mouse TX pin on the minidin. 61 | 62 | Parts required 63 | -------------- 64 | * Raspberry Pico 65 | * 8 pin minidin male cable (I used 66 | https://no.rs-online.com/web/p/din-cable-assemblies/0463518) 67 | * 2 SN74LVC244AN 68 | * USB OTG adapter 69 | 70 | Wiring 71 | ------ 72 | Raspberry Pico 73 | 74 | UART0 TX * Pin 1-----|USB|------Pin 40 * 5V 75 | * Pin 2 `‾‾‾´ Pin 39 * 76 | * Pin 3 Pin 38 * GND 77 | * Pin 4 Pin 37 * 78 | * Pin 5 Pin 36 * 3V3 79 | UART1 TX * Pin 6 Pin 35 * 80 | UART1 RX * Pin 7 Pin 34 * 81 | * Pin 8 Pin 33 * 82 | * Pin 9 .--------. Pin 32 * 83 | * Pin 10 | | Pin 31 * 84 | * Pin 11 | RP2040 | Pin 30 * 85 | * Pin 12 `--------´ Pin 29 * 86 | * Pin 13 Pin 28 * 87 | * Pin 14 Pin 27 * 88 | * Pin 15 Pin 26 * 89 | * Pin 16 Pin 25 * 90 | * Pin 17 π Pin 24 * 91 | * Pin 18 Pin 23 * 92 | * Pin 19 Pin 22 * 93 | * Pin 20----------------Pin 21 * 94 | 95 | 5V to 3V3 level shifter 96 | 97 | GND * Pin 1----------------Pin 20 * 3V3 98 | KBD RX * Pin 2 Pin 19 * 99 | * Pin 3 Pin 18 * UART1 RX 100 | * Pin 4 Pin 17 * 101 | * Pin 5 SN74LVC244AN Pin 16 * 102 | * Pin 6 Pin 15 * 103 | * Pin 7 Pin 14 * 104 | * Pin 8 Pin 13 * 105 | * Pin 9 Pin 12 * 106 | GND * Pin 10----------------Pin 11 * 107 | 108 | 3V3 to 5V level shifter 109 | 110 | GND * Pin 1----------------Pin 20 * 5V 111 | UART1 TX * Pin 2 Pin 19 * 112 | * Pin 3 Pin 18 * KBD TX 113 | UART0 TX * Pin 4 Pin 17 * 114 | * Pin 5 SN74LVC244AN Pin 16 * MOUSE TX 115 | * Pin 6 Pin 15 * 116 | * Pin 7 Pin 14 * 117 | * Pin 8 Pin 13 * 118 | * Pin 9 Pin 12 * 119 | GND * Pin 10----------------Pin 11 * 120 | 121 | MiniDin-8 plug 122 | 123 | , - ~._.~ - , 124 | , ' ' , 125 | , 6 7 8 , 126 | , , 127 | , , 128 | , 3 4 5 , 129 | , , 130 | , , 131 | ,_ 1 2 _, 132 | `-. .-´ 133 | ' - , _ _ _ , -' 134 | 135 | | Pin | Function | 136 | |-----|------------| 137 | | 1 | GND | 138 | | 2 | GND | 139 | | 3 | 5V | 140 | | 4 | MOUSE TX | 141 | | 5 | KBD RX | 142 | | 6 | KBD TX | 143 | | 8 | 5V | 144 | -------------------------------------------------------------------------------- /hid_app.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2021, Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #include 27 | #include 28 | 29 | extern volatile int keyboard_connected; 30 | extern volatile int mouse_connected; 31 | extern void process_kbd_report(hid_keyboard_report_t const *report); 32 | extern void process_mouse_report(hid_mouse_report_t const * report); 33 | 34 | //--------------------------------------------------------------------+ 35 | // MACRO TYPEDEF CONSTANT ENUM DECLARATION 36 | //--------------------------------------------------------------------+ 37 | 38 | // If your host terminal support ansi escape code such as TeraTerm 39 | // it can be use to simulate mouse cursor movement within terminal 40 | #define USE_ANSI_ESCAPE 0 41 | 42 | #define MAX_REPORT 4 43 | 44 | // static uint8_t const keycode2ascii[128][2] = { HID_KEYCODE_TO_ASCII }; 45 | 46 | // Each HID instance can has multiple reports 47 | static struct 48 | { 49 | uint8_t report_count; 50 | tuh_hid_report_info_t report_info[MAX_REPORT]; 51 | }hid_info[CFG_TUH_HID]; 52 | 53 | static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len); 54 | 55 | //--------------------------------------------------------------------+ 56 | // TinyUSB Callbacks 57 | //--------------------------------------------------------------------+ 58 | 59 | // Invoked when device with hid interface is mounted 60 | // Report descriptor is also available for use. tuh_hid_parse_report_descriptor() 61 | // can be used to parse common/simple enough descriptor. 62 | // Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped 63 | // therefore report_desc = NULL, desc_len = 0 64 | void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len) 65 | { 66 | printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance); 67 | 68 | // Interface protocol (hid_interface_protocol_enum_t) 69 | const char* protocol_str[] = { "None", "Keyboard", "Mouse" }; 70 | uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); 71 | 72 | printf("HID Interface Protocol = %s\r\n", protocol_str[itf_protocol]); 73 | if ( itf_protocol == HID_ITF_PROTOCOL_KEYBOARD ) 74 | { 75 | printf("Keyboard Connected!\n"); 76 | ++keyboard_connected; 77 | } 78 | 79 | if ( itf_protocol == HID_ITF_PROTOCOL_MOUSE ) 80 | { 81 | printf("Mouse Connected!\n"); 82 | ++mouse_connected; 83 | } 84 | 85 | // By default host stack will use activate boot protocol on supported interface. 86 | // Therefore for this simple example, we only need to parse generic report descriptor (with built-in parser) 87 | if ( itf_protocol == HID_ITF_PROTOCOL_NONE ) 88 | { 89 | hid_info[instance].report_count = tuh_hid_parse_report_descriptor(hid_info[instance].report_info, MAX_REPORT, desc_report, desc_len); 90 | printf("HID has %u reports \r\n", hid_info[instance].report_count); 91 | } 92 | 93 | // request to receive report 94 | // tuh_hid_report_received_cb() will be invoked when report is available 95 | if ( !tuh_hid_receive_report(dev_addr, instance) ) 96 | { 97 | printf("Error: cannot request to receive report\r\n"); 98 | } 99 | } 100 | 101 | // Invoked when device with hid interface is un-mounted 102 | void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) 103 | { 104 | uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); 105 | if ( itf_protocol == HID_ITF_PROTOCOL_KEYBOARD ) 106 | { 107 | printf("Keyboard Disconnected!\n"); 108 | --keyboard_connected; 109 | } 110 | if ( itf_protocol == HID_ITF_PROTOCOL_MOUSE ) 111 | { 112 | printf("Mouse Disconnected!\n"); 113 | --mouse_connected; 114 | } 115 | printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); 116 | } 117 | 118 | // Invoked when received report from device via interrupt endpoint 119 | void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) 120 | { 121 | uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); 122 | 123 | switch (itf_protocol) 124 | { 125 | case HID_ITF_PROTOCOL_KEYBOARD: 126 | TU_LOG2("HID receive boot keyboard report\r\n"); 127 | process_kbd_report( (hid_keyboard_report_t const*) report ); 128 | break; 129 | 130 | case HID_ITF_PROTOCOL_MOUSE: 131 | TU_LOG2("HID receive boot mouse report\r\n"); 132 | process_mouse_report( (hid_mouse_report_t const*) report ); 133 | break; 134 | 135 | default: 136 | // Generic report requires matching ReportID and contents with previous parsed report info 137 | process_generic_report(dev_addr, instance, report, len); 138 | break; 139 | } 140 | 141 | // continue to request to receive report 142 | if ( !tuh_hid_receive_report(dev_addr, instance) ) 143 | { 144 | printf("Error: cannot request to receive report\r\n"); 145 | } 146 | } 147 | 148 | //--------------------------------------------------------------------+ 149 | // Generic Report 150 | //--------------------------------------------------------------------+ 151 | static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) 152 | { 153 | (void) dev_addr; 154 | 155 | uint8_t const rpt_count = hid_info[instance].report_count; 156 | tuh_hid_report_info_t* rpt_info_arr = hid_info[instance].report_info; 157 | tuh_hid_report_info_t* rpt_info = NULL; 158 | 159 | if ( rpt_count == 1 && rpt_info_arr[0].report_id == 0) 160 | { 161 | // Simple report without report ID as 1st byte 162 | rpt_info = &rpt_info_arr[0]; 163 | }else 164 | { 165 | // Composite report, 1st byte is report ID, data starts from 2nd byte 166 | uint8_t const rpt_id = report[0]; 167 | 168 | // Find report id in the arrray 169 | for(uint8_t i=0; iusage_page == HID_USAGE_PAGE_DESKTOP ) 196 | { 197 | switch (rpt_info->usage) 198 | { 199 | case HID_USAGE_DESKTOP_KEYBOARD: 200 | TU_LOG1("HID receive keyboard report\r\n"); 201 | // Assume keyboard follow boot report layout 202 | process_kbd_report( (hid_keyboard_report_t const*) report ); 203 | break; 204 | 205 | case HID_USAGE_DESKTOP_MOUSE: 206 | TU_LOG1("HID receive mouse report\r\n"); 207 | // Assume mouse follow boot report layout 208 | process_mouse_report( (hid_mouse_report_t const*) report ); 209 | break; 210 | 211 | default: break; 212 | } 213 | } 214 | } 215 | --------------------------------------------------------------------------------