|
| 1 | +// SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries |
| 2 | +// |
| 3 | +// SPDX-License-Identifier: MIT |
| 4 | + |
| 5 | +// pio-usb is required for rp2040 host |
| 6 | +#include "pio_usb.h" |
| 7 | +#include "Adafruit_TinyUSB.h" |
| 8 | + |
| 9 | +// Pin D+ for host, D- = D+ + 1 |
| 10 | +#ifndef PIN_USB_HOST_DP |
| 11 | +#define PIN_USB_HOST_DP 16 |
| 12 | +#endif |
| 13 | + |
| 14 | +// Pin for enabling Host VBUS. comment out if not used |
| 15 | +#ifndef PIN_5V_EN |
| 16 | +#define PIN_5V_EN 18 |
| 17 | +#endif |
| 18 | + |
| 19 | +#ifndef PIN_5V_EN_STATE |
| 20 | +#define PIN_5V_EN_STATE 1 |
| 21 | +#endif |
| 22 | + |
| 23 | +// USB Host object |
| 24 | +Adafruit_USBH_Host USBHost; |
| 25 | + |
| 26 | +// Serial output for RunCPM |
| 27 | +SerialPIO pio_serial(1 /* RX of the sibling board */, SerialPIO::NOPIN); |
| 28 | + |
| 29 | +void setup() { |
| 30 | +} |
| 31 | + |
| 32 | +void loop() { |
| 33 | +} |
| 34 | + |
| 35 | +void setup1() { |
| 36 | + |
| 37 | +#if 0 |
| 38 | + while ( !Serial ) delay(10); // wait for native usb |
| 39 | + Serial.println("Core1 setup to run TinyUSB host with pio-usb"); |
| 40 | +#endif |
| 41 | + |
| 42 | + // Check for CPU frequency, must be multiple of 120Mhz for bit-banging USB |
| 43 | + uint32_t cpu_hz = clock_get_hz(clk_sys); |
| 44 | + if ( cpu_hz != 120000000UL && cpu_hz != 240000000UL ) { |
| 45 | + while ( !Serial ) delay(10); // wait for native usb |
| 46 | + Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 120 Mhz\r\n", cpu_hz); |
| 47 | + Serial.printf("Change your CPU Clock to either 120 or 240 Mhz in Menu->CPU Speed \r\n"); |
| 48 | + while (1) delay(1); |
| 49 | + } |
| 50 | + |
| 51 | +#ifdef PIN_5V_EN |
| 52 | + pinMode(PIN_5V_EN, OUTPUT); |
| 53 | + digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE); |
| 54 | +#endif |
| 55 | + |
| 56 | + pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG; |
| 57 | + pio_cfg.pin_dp = PIN_USB_HOST_DP; |
| 58 | + USBHost.configure_pio_usb(1, &pio_cfg); |
| 59 | + |
| 60 | + // run host stack on controller (rhport) 1 |
| 61 | + // Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the |
| 62 | + // host bit-banging processing works done in core1 to free up core0 for other works |
| 63 | + USBHost.begin(1); |
| 64 | + |
| 65 | + // this `begin` is a void function, no way to check for failure! |
| 66 | + pio_serial.begin(115200); |
| 67 | +} |
| 68 | + |
| 69 | +int old_ascii = -1; |
| 70 | +uint32_t repeat_timeout; |
| 71 | +const uint32_t repeat_time = 150; |
| 72 | + |
| 73 | +void send_ascii(uint8_t code) { |
| 74 | + old_ascii = code; |
| 75 | + repeat_timeout = millis() + repeat_time; |
| 76 | + if (code > 32 && code < 127) { |
| 77 | + Serial.printf("'%c'\r\n", code); |
| 78 | + } else { |
| 79 | + Serial.printf("'\\x%02x'\r\n", code); |
| 80 | + } |
| 81 | + pio_serial.write(code); |
| 82 | +} |
| 83 | + |
| 84 | +void loop1() |
| 85 | +{ |
| 86 | + uint32_t now = millis(); |
| 87 | + uint32_t deadline = repeat_timeout - now; |
| 88 | + if (old_ascii >= 0 && deadline > INT32_MAX) { |
| 89 | + repeat_timeout += repeat_time; |
| 90 | + deadline = repeat_timeout - now; |
| 91 | + send_ascii(old_ascii); |
| 92 | + } else if (old_ascii < 0) { |
| 93 | + deadline = UINT32_MAX; |
| 94 | + } |
| 95 | + tuh_task_ext(deadline, false); |
| 96 | +} |
| 97 | + |
| 98 | +// Invoked when device with hid interface is mounted |
| 99 | +// Report descriptor is also available for use. |
| 100 | +// tuh_hid_parse_report_descriptor() can be used to parse common/simple enough |
| 101 | +// descriptor. Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, |
| 102 | +// it will be skipped therefore report_desc = NULL, desc_len = 0 |
| 103 | +void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) { |
| 104 | + (void)desc_report; |
| 105 | + (void)desc_len; |
| 106 | + uint16_t vid, pid; |
| 107 | + tuh_vid_pid_get(dev_addr, &vid, &pid); |
| 108 | + |
| 109 | + Serial.printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance); |
| 110 | + Serial.printf("VID = %04x, PID = %04x\r\n", vid, pid); |
| 111 | + |
| 112 | + uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); |
| 113 | + if (itf_protocol == HID_ITF_PROTOCOL_KEYBOARD) { |
| 114 | + Serial.printf("HID Keyboard\r\n"); |
| 115 | + if (!tuh_hid_receive_report(dev_addr, instance)) { |
| 116 | + Serial.printf("Error: cannot request to receive report\r\n"); |
| 117 | + } |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +// Invoked when device with hid interface is un-mounted |
| 122 | +void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { |
| 123 | + Serial.printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); |
| 124 | +} |
| 125 | + |
| 126 | +#define FLAG_ALPHABETIC (1) |
| 127 | +#define FLAG_SHIFT (2) |
| 128 | +#define FLAG_NUMLOCK (4) |
| 129 | +#define FLAG_CTRL (8) |
| 130 | +#define FLAG_LUT (16) |
| 131 | + |
| 132 | +const char * const lut[] = { |
| 133 | + "!@#$%^&*()", /* 0 - shifted numeric keys */ |
| 134 | + "\r\x1b\10\t -=[]\\#;'`,./", /* 1 - symbol keys */ |
| 135 | + "\n\x1b\177\t _+{}|~:\"~<>?", /* 2 - shifted */ |
| 136 | + "\3\4\2\1", /* 3 - arrow keys RLDU */ |
| 137 | + "/*-+\n1234567890.", /* 4 - keypad w/numlock */ |
| 138 | + "/*-+\n\xff\2\xff\4\xff\3\xff\1\xff\xff.", /* 5 - keypad w/o numlock */ |
| 139 | +}; |
| 140 | + |
| 141 | +struct keycode_mapper { |
| 142 | + uint8_t first, last, code, flags; |
| 143 | +} keycode_to_ascii[] = { |
| 144 | + { HID_KEY_A, HID_KEY_Z, 'a', FLAG_ALPHABETIC, }, |
| 145 | + |
| 146 | + { HID_KEY_1, HID_KEY_9, 0, FLAG_SHIFT | FLAG_LUT, }, |
| 147 | + { HID_KEY_1, HID_KEY_9, '1', 0, }, |
| 148 | + { HID_KEY_0, HID_KEY_0, ')', FLAG_SHIFT, }, |
| 149 | + { HID_KEY_0, HID_KEY_0, '0', 0, }, |
| 150 | + |
| 151 | + { HID_KEY_ENTER, HID_KEY_ENTER, '\n', FLAG_CTRL }, |
| 152 | + { HID_KEY_ENTER, HID_KEY_SLASH, 2, FLAG_SHIFT | FLAG_LUT, }, |
| 153 | + { HID_KEY_ENTER, HID_KEY_SLASH, 1, FLAG_LUT, }, |
| 154 | + |
| 155 | + { HID_KEY_F1, HID_KEY_F1, 0x1e, 0, }, // help key on xerox 820 kbd |
| 156 | + |
| 157 | + { HID_KEY_ARROW_RIGHT, HID_KEY_ARROW_UP, 3, FLAG_LUT }, |
| 158 | + |
| 159 | + { HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 4, FLAG_NUMLOCK | FLAG_LUT }, |
| 160 | + { HID_KEY_KEYPAD_DIVIDE, HID_KEY_KEYPAD_DECIMAL, 5, FLAG_LUT }, |
| 161 | +}; |
| 162 | + |
| 163 | + |
| 164 | +bool report_contains(const hid_keyboard_report_t &report, uint8_t key) { |
| 165 | + for (int i = 0; i < 6; i++) { |
| 166 | + if (report.keycode[i] == key) return true; |
| 167 | + } |
| 168 | + return false; |
| 169 | +} |
| 170 | + |
| 171 | +hid_keyboard_report_t old_report; |
| 172 | + |
| 173 | +void process_event(uint8_t dev_addr, uint8_t instance, const hid_keyboard_report_t &report) { |
| 174 | + bool alt = report.modifier & 0x44; |
| 175 | + bool shift = report.modifier & 0x22; |
| 176 | + bool ctrl = report.modifier & 0x11; |
| 177 | + bool caps = old_report.reserved & 1; |
| 178 | + bool num = old_report.reserved & 2; |
| 179 | + uint8_t code = 0; |
| 180 | + |
| 181 | + if (report.keycode[0] == 1 && report.keycode[1] == 1) { |
| 182 | + // keyboard says it has exceeded max kro |
| 183 | + return; |
| 184 | + } |
| 185 | + |
| 186 | + // something was pressed or release, so cancel any key repeat |
| 187 | + old_ascii = -1; |
| 188 | + |
| 189 | + for (auto keycode : report.keycode) { |
| 190 | + if (keycode == 0) continue; |
| 191 | + if (report_contains(old_report, keycode)) continue; |
| 192 | + |
| 193 | + /* key is newly pressed */ |
| 194 | + if (keycode == HID_KEY_NUM_LOCK) { |
| 195 | + num = !num; |
| 196 | + } else if (keycode == HID_KEY_CAPS_LOCK) { |
| 197 | + caps = !caps; |
| 198 | + } else { |
| 199 | + for (const auto &mapper : keycode_to_ascii) { |
| 200 | + if (!(keycode >= mapper.first && keycode <= mapper.last)) |
| 201 | + continue; |
| 202 | + if (mapper.flags & FLAG_SHIFT && !shift) |
| 203 | + continue; |
| 204 | + if (mapper.flags & FLAG_NUMLOCK && !num) |
| 205 | + continue; |
| 206 | + if (mapper.flags & FLAG_CTRL && !ctrl) |
| 207 | + continue; |
| 208 | + if (mapper.flags & FLAG_LUT) { |
| 209 | + code = lut[mapper.code][keycode - mapper.first]; |
| 210 | + } else { |
| 211 | + code = keycode - mapper.first + mapper.code; |
| 212 | + } |
| 213 | + if (mapper.flags & FLAG_ALPHABETIC) { |
| 214 | + if (shift ^ caps) { |
| 215 | + code ^= ('a' ^ 'A'); |
| 216 | + } |
| 217 | + } |
| 218 | + if (ctrl) code &= 0x1f; |
| 219 | + if (alt) code ^= 0x80; |
| 220 | + send_ascii(code); |
| 221 | + break; |
| 222 | + } |
| 223 | + } |
| 224 | + } |
| 225 | + |
| 226 | + uint8_t leds = (caps | (num << 1)); |
| 227 | + if (leds != old_report.reserved) { |
| 228 | + Serial.printf("Send LEDs report %d (dev:instance = %d:%d)\r\n", leds, dev_addr, instance); |
| 229 | + // no worky |
| 230 | + auto r = tuh_hid_set_report(dev_addr, instance/*idx*/, 0/*report_id*/, HID_REPORT_TYPE_OUTPUT/*report_type*/, &leds, sizeof(leds)); |
| 231 | + Serial.printf("set_report() -> %d\n", (int)r); |
| 232 | + } |
| 233 | + old_report = report; |
| 234 | + old_report.reserved = leds; |
| 235 | +} |
| 236 | + |
| 237 | +// Invoked when received report from device via interrupt endpoint |
| 238 | +void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) { |
| 239 | + if ( len != sizeof(hid_keyboard_report_t) ) { |
| 240 | + Serial.printf("report len = %u NOT 8, probably something wrong !!\r\n", len); |
| 241 | + } else { |
| 242 | + process_event(dev_addr, instance, *(hid_keyboard_report_t*)report); |
| 243 | + } |
| 244 | + // continue to request to receive report |
| 245 | + if (!tuh_hid_receive_report(dev_addr, instance)) { |
| 246 | + Serial.printf("Error: cannot request to receive report\r\n"); |
| 247 | + } |
| 248 | +} |
0 commit comments