diff --git a/CMakeLists.txt b/CMakeLists.txt index a6d8302..976656b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,12 +10,18 @@ project(rp2040_hid) pico_sdk_init() add_executable(rp2040_hid + src/buttons.c src/main.c + src/rotary.c + src/sevenseg.c + src/statusleds.c src/usb_config.c ) -# Add pico_stdlib library which aggregates commonly used features -target_link_libraries(rp2040_hid pico_stdlib pico_unique_id tinyusb_device tinyusb_board) +pico_generate_pio_header(rp2040_hid ${CMAKE_CURRENT_LIST_DIR}/src/sevenseg.pio) +pico_generate_pio_header(rp2040_hid ${CMAKE_CURRENT_LIST_DIR}/src/shift_in.pio) +pico_generate_pio_header(rp2040_hid ${CMAKE_CURRENT_LIST_DIR}/src/statusleds.pio) +target_link_libraries(rp2040_hid pico_stdlib pico_unique_id tinyusb_device tinyusb_board hardware_pio) target_include_directories(rp2040_hid PRIVATE src) diff --git a/FlyWithLua/rpi2040_hid.lua b/FlyWithLua/rpi2040_hid.lua index fae7c81..b23cb98 100644 --- a/FlyWithLua/rpi2040_hid.lua +++ b/FlyWithLua/rpi2040_hid.lua @@ -1,20 +1,165 @@ -device = hid_open(0x1209, 1) +local bit = require("bit") +all_devs, number = create_HID_table() +local the_index +for k, v in pairs(all_devs) do + if v.vendor_id == 0x1209 and v.usage == 0 then + the_path = v.path + --print(v.path) + --print(v.usage) + end +end + +local button_offset = 320 +local button_hdg_sync = 1 +local button_alt_sync = 7 +local led_hdg = 0x0001 +local led_apr = 0x0002 +local led_nav = 0x0004 +local led_lvl = 0x0008 +local led_alt = 0x0010 +local led_fd = 0x0020 +local led_vnav = 0x0040 +local led_ap = 0x0080 +local led_vs = 0x0100 +local led_at = 0x0200 +local led_flc = 0x0400 +local led_spd = 0x0800 +local led_n1 = 0x1000 + +function table.clone(org) + return {table.unpack(org)} +end + +function send_app_report(device, hdg, leds, alt, vs, as) + hid_write(device, 2, bit.band(hdg, 0xff), bit.arshift(hdg, 8), bit.band(leds, 0xff), bit.arshift(leds, 8), + bit.band(alt, 0xff), bit.arshift(alt, 8), bit.band(vs, 0xff), bit.arshift(vs, 8), + bit.band(as, 0xff), bit.arshift(as, 8)) +end + +function build_leds() + local val = 0 + if status_hdg == 1 then + val = val + led_hdg + elseif status_hdg == 2 then + val = val + led_nav + end + if status_apr > 0 then + val = val + led_apr + end + if status_alt > 1 then + val = val + led_alt + end + if status_fd == 1 then + val = val + led_fd + elseif status_fd == 2 then + val = val + led_fd + led_ap + end + if status_vnav > 1 then + val = val + led_vnav + end + if status_altmode == 4 then + val = val + led_vs + elseif status_altmode == 5 then + val = val + led_flc + end + + return val +end + +if the_path ~= nil then + device = hid_open_path(the_path) +end if device == nil then print("No device!") else - dataref("low_vac", "sim/cockpit2/annunciators/low_vacuum", "readable") - local prev_low_vac - function low_vac_indicator() - if low_vac ~= prev_low_vac then - if low_vac > 0 then - hid_write(device, 2, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - else - hid_write(device, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + hid_set_nonblocking(device, 1) + dataref("ap_hdg", "sim/cockpit2/autopilot/heading_dial_deg_mag_pilot", "writable") + dataref("mag_hdg", "sim/cockpit2/gauges/indicators/heading_AHARS_deg_mag_copilot") + + dataref("ap_alt", "sim/cockpit2/autopilot/altitude_dial_ft", "writable") + dataref("cur_alt", "sim/cockpit2/gauges/indicators/altitude_ft_pilot") + + dataref("ap_vs", "sim/cockpit2/autopilot/vvi_dial_fpm", "writable") + dataref("cur_vvi", "sim/cockpit2/gauges/indicators/vvi_fpm_pilot") + + dataref("ap_as", "sim/cockpit2/autopilot/airspeed_dial_kts", "writable") + dataref("cur_ias", "sim/cockpit2/gauges/indicators/airspeed_kts_pilot") + + dataref("status_alt", "sim/cockpit2/autopilot/altitude_hold_status") + dataref("status_apr", "sim/cockpit2/autopilot/approach_status") + dataref("status_hdg", "sim/cockpit2/autopilot/heading_mode") + dataref("status_fd", "sim/cockpit2/autopilot/flight_director_mode") + dataref("status_vnav", "sim/cockpit2/autopilot/vnav_status") + dataref("status_altmode", "sim/cockpit2/autopilot/altitude_mode") + send_app_report(device, math.floor(ap_hdg + 0.5), build_leds(), math.floor(ap_alt + 0.5), -2, -2) + local prev_status_altmode = 0 + function usb_app() + local send_hdg = -1 + local send_alt = -1 + local send_vs = -1 + local send_as = -1 + local app_report = {0xffff, 0xffff, 0xffff, 0xffff} + local app_report_good = false + local loops = 100 + -- read app report from hid device, discarding all reports except the latest one to prevent input lag + repeat + local tmp_cnt + local tmp = {} + tmp_cnt, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10] = hid_read(device, 11) + if tmp_cnt >= 11 and tmp[0] == 2 then + app_report = {bit.bor(tmp[1], bit.lshift(tmp[2], 8)), + bit.bor(tmp[5], bit.lshift(tmp[6], 8)), + bit.bor(tmp[7], bit.lshift(tmp[8], 8)), + bit.bor(tmp[9], bit.lshift(tmp[10], 8))} + app_report_good = true end - prev_low_vac = low_vac + loops = loops - 1 + until tmp_cnt == 0 or tmp_cnt == nil or loops == 0 + -- autopilot heading + if button(button_offset + button_hdg_sync) and not last_button(button_offset + button_hdg_sync) then + mag_hdg_int = math.floor(mag_hdg + 0.5) + ap_hdg = mag_hdg_int + send_hdg = mag_hdg_int + elseif app_report_good and app_report[1] ~= 0xffff then + ap_hdg = app_report[1] end + -- autopilot altitude + if button(button_offset + button_alt_sync) and not last_button(button_offset + button_alt_sync) then + cur_alt_int = math.floor(cur_alt + 0.5) + ap_alt = cur_alt_int + send_alt = cur_alt_int + end + if app_report_good and app_report[2] ~= 0xffff then + ap_alt = app_report[2] + end + -- vertical speed + if status_altmode == 4 and prev_status_altmode ~= 4 then + send_vs = math.floor(cur_vvi + 0.5) + elseif status_altmode ~= 4 and prev_status_altmode == 4 then + send_vs = -2 + elseif app_report_good and app_report[3] ~= 0xffff then + if bit.band(app_report[3], 0x8000) ~= 0 then + ap_vs = -bit.band(bit.bnot(app_report[3]), 0x7fff) - 1 + else + ap_vs = app_report[3] + end + end + -- airspeed + if status_altmode == 5 and prev_status_altmode ~= 5 then + if cur_ias < 64 then + send_as = 64 + else + send_as = math.floor(cur_ias + 0.5) + end + elseif status_altmode ~= 5 and prev_status_altmode == 5 then + send_as = -2 + elseif app_report_good and app_report[4] ~= 0xffff then + ap_as = app_report[4] + end + send_app_report(device, send_hdg, build_leds(), send_alt, send_vs, send_as) + prev_status_altmode = status_altmode end - do_every_frame("low_vac_indicator()") + do_every_frame("usb_app()") end diff --git a/src/main.c b/src/main.c index 887ffff..3e7e0e5 100644 --- a/src/main.c +++ b/src/main.c @@ -1,11 +1,17 @@ +#include "buttons.h" +#include "pinmap.h" +#include "rotary.h" +#include "sevenseg.h" +#include "statusleds.h" + #include #include +#include #include #include #include "usb_config.h" -#define BUTTON1_GPIO 2 -#define LED1_GPIO 22 +PIO pio = pio0; static void hid_task(void); @@ -18,20 +24,147 @@ __attribute__((packed)) struct button_report { uint32_t buttons; }; +__attribute__((packed)) struct app_report { + int16_t hdg; + int16_t leds; + int16_t alt; + int16_t vs; + int16_t as; +}; + static struct light_data the_light_data; +static bool show_vs = false, show_as = false; + +static volatile unsigned hdg = 0; +static volatile unsigned alt = 0; +static volatile int vs = 0; +static volatile unsigned as = 0; + +#define BUTTON_HDG_SYNC 1 +#define BUTTON_ALT_SYNC 7 + +static void update_hdg_display(void) +{ + sevenseg_set_digit(0, hdg / 100); + sevenseg_set_digit(1, hdg / 10 % 10); + sevenseg_set_digit(2, hdg % 10); +} + +static void update_alt_display(void) +{ + int val = alt; + for (int i = 0; i < 5; ++i) { + if (val || i == 0) + sevenseg_set_digit(7 - i, val % 10); + else + sevenseg_set_digit(7 - i, LED_BLANK); + val /= 10; + } +} + +static void update_vs_display(void) +{ + if (show_vs) { + int val = vs; + unsigned val_abs = val < 0 ? -val : val; + for (int i = 0; i < 3; ++i) { + if (val_abs || i == 0) { + sevenseg_set_digit(11 - i, val_abs % 10); + } else { + sevenseg_set_digit(11 - i, LED_BLANK); + } + val_abs /= 10; + } + if (val >= 1000 || val <= -1000) { + sevenseg_set_digit(8, (val_abs % 10) | (val < 0 ? LED_DECIMAL : 0)); + } else if (val < 0) { + sevenseg_set_digit(8, LED_MINUS); + } else { + sevenseg_set_digit(8, LED_BLANK); + } + } else { + for (int i = 0; i < 4; ++i) { + sevenseg_set_digit(11 - i, LED_BLANK); + } + } +} + +static void update_as_display(void) +{ + if (show_as) { + int val = as; + for (int i = 0; i < 4; ++i) { + if (val || i == 0) + sevenseg_set_digit(15 - i, val % 10); + else + sevenseg_set_digit(15 - i, LED_BLANK); + val /= 10; + } + } else { + for (int i = 0; i < 4; ++i) { + sevenseg_set_digit(15 - i, LED_BLANK); + } + } +} + +void rotary_event(int rot_ch, bool ccw) +{ + /* if (ccw) */ + /* rotary_events = (rotary_events & ~(0x3<<(rot_ch*2))) | (0x1<<(rot_ch*2)); */ + /* else */ + /* rotary_events = (rotary_events & ~(0x3<<(rot_ch*2))) | (0x3<<(rot_ch*2)); */ + /* rotary_events |= 1<<(rot_ch*2 + ccw); */ + /* sevenseg_set_digit(0, rot_ch); */ + /* sevenseg_set_digit(1, ccw); */ + if (rot_ch == 3) { + if (ccw) { + hdg = hdg == 0 ? 359 : hdg - 1; + } else { + hdg = hdg == 359 ? 0 : hdg + 1; + } + update_hdg_display(); + } else if (rot_ch == 2) { + if (ccw) { + alt = alt == 0 ? 0 : ((alt + 99) / 100) * 100 - 100; + } else { + alt = alt == 50000 ? 50000 : (alt / 100) * 100 + 100; + } + update_alt_display(); + } else if (rot_ch == 1) { + if (ccw) { + vs = vs == -5000 ? -5000 : vs - 100; + } else { + vs = vs == 5000 ? 5000 : vs + 100; + } + update_vs_display(); + } else { + // TODO: knots / mach mode switch + if (ccw) { + as = as == 64 ? 64 : as - 1; + } else { + as = as == 205 ? 205 : as + 1; + } + update_as_display(); + } +} + int main() { setup_default_uart(); tusb_init(); - gpio_init(BUTTON1_GPIO); - gpio_set_dir(BUTTON1_GPIO, GPIO_IN); - gpio_pull_up(BUTTON1_GPIO); + rotary_init(); + sevenseg_init(); + buttons_init(); + statusleds_init(); - gpio_init(LED1_GPIO); - gpio_set_dir(LED1_GPIO, GPIO_OUT); - gpio_put(LED1_GPIO, true); + update_hdg_display(); + update_alt_display(); + update_vs_display(); + update_as_display(); + + irq_set_enabled(PIO0_IRQ_1, true); printf("Hello, world!\n"); while (1) { @@ -41,6 +174,8 @@ int main() return 0; } +static bool send_heading_reports = true, send_alt_reports = true, send_vs_reports = false, send_as_reports = false; + static void hid_task(void) { // Poll every 1ms @@ -51,18 +186,39 @@ static void hid_task(void) return; // poll interval not elapsed start_ms += interval_ms; - bool btn = !gpio_get(BUTTON1_GPIO); + if (start_ms % 10 == 0 && send_heading_reports) { + struct app_report report = { .hdg = -1, .alt = -1, .vs = -1, .as = -1 }; + if (send_heading_reports) + report.hdg = hdg; + if (send_alt_reports) + report.alt = alt; + if (send_vs_reports) + report.vs = vs; + if (send_as_reports) + report.as = as; + if (tud_hid_ready()) + tud_hid_n_report(ITF_NUM_HID, HID_REPORT_APP, &report, sizeof(report)); + } else { + static int btns_prev = 0; + unsigned btns = buttons_read() & 0xfffff; - // Remote wakeup - if (tud_suspended() && btn) { - // Wake up host if we are in suspend mode - // and REMOTE_WAKEUP feature is enabled by host - tud_remote_wakeup(); - } + if ((btns >> BUTTON_HDG_SYNC) & 1 && !((btns_prev >> BUTTON_HDG_SYNC) & 1)) + send_heading_reports = false; + if ((btns >> BUTTON_ALT_SYNC) & 1 && !((btns_prev >> BUTTON_ALT_SYNC) & 1)) + send_alt_reports = false; - if (tud_hid_ready()) { - struct button_report report = { .buttons = btn ? GAMEPAD_BUTTON_0 : 0 }; - tud_hid_n_report(ITF_NUM_HID, HID_REPORT_GAMEPAD, &report, sizeof(report)); + btns_prev = btns; + // Remote wakeup + if (tud_suspended() && btns) { + // Wake up host if we are in suspend mode + // and REMOTE_WAKEUP feature is enabled by host + tud_remote_wakeup(); + } + + if (tud_hid_ready()) { + struct button_report report = { .buttons = btns }; + tud_hid_n_report(ITF_NUM_HID, HID_REPORT_GAMEPAD, &report, sizeof(report)); + } } } @@ -70,11 +226,45 @@ void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_ uint16_t bufsize) { //printf("REPORT id %hhu type %d [0] %hhu size %hu\n", report_id, report_type, buffer[0], bufsize); - if (report_id == HID_REPORT_LIGHTS && report_type == HID_REPORT_TYPE_OUTPUT && - bufsize >= sizeof(struct light_data)) { - size_t i = 0; - memcpy(&the_light_data, buffer, sizeof(the_light_data)); - gpio_put(LED1_GPIO, the_light_data.leds[0] > 0x7f ? false : true); + if (report_id == HID_REPORT_APP && report_type == HID_REPORT_TYPE_OUTPUT && bufsize >= 2) { + struct app_report report; + memcpy(&report, buffer, sizeof(report)); + if (report.hdg >= 0) { + hdg = report.hdg; + update_hdg_display(); + send_heading_reports = true; + } + if (report.alt >= 0) { + alt = report.alt; + update_alt_display(); + send_alt_reports = true; + } + if (report.vs != -1) { + if (report.vs == -2) { + // disable vs mode + show_vs = false; + send_vs_reports = false; + } else { + vs = report.vs; + show_vs = true; + send_vs_reports = true; + } + update_vs_display(); + } + if (report.as != -1) { + if (report.as == -2) { + show_as = false; + send_as_reports = false; + } else { + as = report.as; + show_as = true; + send_as_reports = true; + } + update_as_display(); + } + if (report.leds >= 0) { + statusleds_set(report.leds); + } } // echo back anything we received from host @@ -111,17 +301,13 @@ uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_t if (report_type != HID_REPORT_TYPE_INPUT) return 0; switch (report_id) { - case HID_REPORT_LIGHTS: - act_len = sizeof(the_light_data) > reqlen ? reqlen : sizeof(the_light_data); - memcpy(buffer, &the_light_data, act_len); - return act_len; - case HID_REPORT_GAMEPAD: { - const bool btn = !gpio_get(BUTTON1_GPIO); - const struct button_report report = { .buttons = btn ? GAMEPAD_BUTTON_0 : 0 }; - act_len = sizeof(report) > reqlen ? reqlen : sizeof(report); - memcpy(buffer, &report, act_len); - return act_len; - } + /* case HID_REPORT_GAMEPAD: { */ + /* const bool btn = !gpio_get(BUTTON1_GPIO); */ + /* const struct button_report report = { .buttons = btn ? GAMEPAD_BUTTON_0 : 0 }; */ + /* act_len = sizeof(report) > reqlen ? reqlen : sizeof(report); */ + /* memcpy(buffer, &report, act_len); */ + /* return act_len; */ + /* } */ default: return 0; } diff --git a/src/rotary.c b/src/rotary.c index 62fbb06..b4008d8 100644 --- a/src/rotary.c +++ b/src/rotary.c @@ -39,7 +39,7 @@ static void rotary_timer_event(uint alarm_num) state[i] = rotary_state_idle; if (rot_gpio[i * 2] && !rot_gpio[i * 2 + 1]) { state[i] = rotary_state_A; - rotary_event(i, true); + rotary_event(i, false); } break; case rotary_state_A: @@ -47,7 +47,7 @@ static void rotary_timer_event(uint alarm_num) state[i] = rotary_state_idle; if (!rot_gpio[i * 2] && rot_gpio[i * 2 + 1]) { state[i] = rotary_state_B; - rotary_event(i, false); + rotary_event(i, true); } break; } diff --git a/src/usb_config.c b/src/usb_config.c index f0ad490..f5f8ce5 100644 --- a/src/usb_config.c +++ b/src/usb_config.c @@ -6,7 +6,7 @@ #include /* clang-format off */ -// Gamepad Report Descriptor Template with 32 buttons with following layout +// Gamepad Report Descriptor Template with 28 buttons with following layout // | Button Map (4 bytes) | #define RP2040_HID_REPORT_DESC_GAMEPAD(...) \ HID_USAGE_PAGE (HID_USAGE_PAGE_DESKTOP), \ @@ -14,10 +14,10 @@ HID_COLLECTION (HID_COLLECTION_APPLICATION), \ /* Report ID if any */ \ __VA_ARGS__ \ - /* 32 bit Button Map */ \ + /* 20 bit Button Map */ \ HID_USAGE_PAGE(HID_USAGE_PAGE_BUTTON), \ HID_USAGE_MIN(1), \ - HID_USAGE_MAX(32), \ + HID_USAGE_MAX(20), \ HID_LOGICAL_MIN(0), \ HID_LOGICAL_MAX(1), \ HID_REPORT_COUNT(32), \ @@ -25,23 +25,33 @@ HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ HID_COLLECTION_END -#define RP2040_HID_REPORT_DESC_LIGHTS(...) \ +#define RP2040_HID_REPORT_DESC_APP(...) \ HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \ HID_USAGE(0x00), \ HID_COLLECTION(HID_COLLECTION_APPLICATION), \ __VA_ARGS__ \ - HID_REPORT_COUNT(16), /*16 button lights */ \ - HID_REPORT_SIZE(8), \ - HID_LOGICAL_MIN(0x00), \ - HID_LOGICAL_MAX_N(0x00ff, 2), \ - HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), \ - HID_USAGE_MIN(1), \ - HID_USAGE_MAX(16), \ - HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ - HID_USAGE_MIN(1), \ - HID_USAGE_MAX(1), \ - HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \ - HID_COLLECTION_END + HID_REPORT_COUNT(5), \ + HID_REPORT_SIZE(16), \ + HID_LOGICAL_MIN(-1), \ + HID_LOGICAL_MAX(127), \ + HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), \ + HID_USAGE_MIN(1), \ + HID_USAGE_MAX(1), \ + HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ + HID_COLLECTION_END + + /* HID_REPORT_COUNT(16), /\*16 button lights *\/ \ */ + /* HID_REPORT_SIZE(8), \ */ + /* HID_LOGICAL_MIN(0x00), \ */ + /* HID_LOGICAL_MAX_N(0x00ff, 2), \ */ + /* HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), \ */ + /* HID_USAGE_MIN(1), \ */ + /* HID_USAGE_MAX(16), \ */ + /* HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ */ + /* HID_USAGE_MIN(1), \ */ + /* HID_USAGE_MAX(1), \ */ + /* HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \ */ + /* clang-format on */ @@ -129,7 +139,7 @@ uint8_t const *tud_descriptor_device_cb(void) } static uint8_t const desc_hid_report[] = { RP2040_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(HID_REPORT_GAMEPAD)), - RP2040_HID_REPORT_DESC_LIGHTS(HID_REPORT_ID(HID_REPORT_LIGHTS)) }; + RP2040_HID_REPORT_DESC_APP(HID_REPORT_ID(HID_REPORT_APP)) }; #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN) diff --git a/src/usb_config.h b/src/usb_config.h index 914567e..39a8b56 100644 --- a/src/usb_config.h +++ b/src/usb_config.h @@ -4,4 +4,4 @@ enum { ITF_NUM_HID, ITF_NUM_TOTAL }; -enum { HID_REPORT_GAMEPAD = 1, HID_REPORT_LIGHTS }; +enum { HID_REPORT_GAMEPAD = 1, HID_REPORT_APP };