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