// Copyright 2025 Google Inc. // Use of this source code is governed by an Apache License that can be found // in the LICENSE file. #include #include #include #include #include "hardware/uart.h" #if LIB_PICO_STDIO_UART #include "pico/stdio_uart.h" #endif #include "pico/stdlib.h" #include "../common/dial_controller.h" #include "../common/motor_controller.h" #include "../common/photo_sensor.h" #include "../common/usage_tables.h" #include "../common/usb_hid_keyboard.h" #include "i2c_controller.h" int main() { #if LIB_PICO_STDIO_UART stdio_uart_init_full(uart0, /*baud_rate=*/115200, /*tx_pin=*/0, /*rx_pin=*/-1); #else stdio_init_all(); #endif I2CController i2c(i2c0, /*sda=*/28, /*scl=*/29); // Sensor A, B, C, D, E, F, G, I (note: H is missing here as it's accessed via // I2C) std::array sensors = { PhotoSensor(1, 2, 3, 4, 5, 6), // A PhotoSensor(7, 8), // B PhotoSensor(9, 10, 11), // C PhotoSensor(12, 13), // D (3 sensors are implemented, but...) PhotoSensor(15, 16, 17), // E PhotoSensor(18, 19, 20), // F PhotoSensor(21, 22), // G (3 sensors are implemented, but...) PhotoSensor(24, 25, 26, 27), // I }; const std::array sensor_indices = { 0, 1, 2, 3, 4, 5, 6, -1, 7 }; std::array dials = { DialController(), // A DialController(), // B DialController(), // C DialController(), // D DialController(), // E DialController(), // F DialController(), // G DialController(), // I DialController(), // H }; std::array*, 9> usages = { &usage_tables::a, // A &usage_tables::b, // B &usage_tables::c, // C &usage_tables::d, // D &usage_tables::e, // E &usage_tables::f, // F &usage_tables::g, // G &usage_tables::i, // I &usage_tables::h, // H }; std::array*, 9> fn_usages = { &usage_tables::fn_a, // A &usage_tables::fn_b, // B &usage_tables::fn_c, // C &usage_tables::fn_d, // D &usage_tables::fn_e, // E &usage_tables::fn_f, // F &usage_tables::fn_g, // G &usage_tables::fn_i, // I &usage_tables::fn_h, // H }; std::array*, 9> modifiers = { &usage_tables::modifier_a, // A &usage_tables::modifier_b, // B &usage_tables::modifier_c, // C &usage_tables::modifier_d, // D &usage_tables::modifier_e, // E &usage_tables::modifier_f, // F &usage_tables::modifier_g, // G &usage_tables::modifier_i, // I &usage_tables::modifier_h, // H }; UsbHidKeyboard usb_hid_keyboard( /*vendor_id=*/0x6666, /*product_id=*/0x2025, /*version=*/0x0109, /*vendor_name=*/"Gboard DIY prototype", /*product_name=*/"Gboard Dial version", /*version_name=*/"9 Dial rev2"); usb_hid_keyboard.SetAutoKeyRelease(true); // Wait for a while, just in case, so that the sub-controller can be ready on // I2C. sleep_ms(100); std::vector i2c_buffer(1); bool fn = false; while (true) { uint16_t motor_start_bitmap = 0; for (size_t i = 0; i < sensor_indices.size(); ++i) { if (sensor_indices[i] < 0) { // Read the remote sensor H value via I2C. if (i2c.Read(68, 0, i2c_buffer)) { dials[i].Update(i2c_buffer[0]); } } else { // Read one of the local sensors. dials[i].Update(sensors[sensor_indices[i]].Read()); } if (!dials[i].IsBasePosition()) { motor_start_bitmap |= (1 << i); } } // Drive all motors via I2C. // Use non-burst mode for better stability. i2c_buffer[0] = motor_start_bitmap & 0xff; i2c.Write(68, 0, i2c_buffer); i2c_buffer[0] = motor_start_bitmap >> 8; i2c.Write(68, 1, i2c_buffer); // Check all dials to see if we have pending inputs to send over USB HID. for (size_t i = 0; i < dials.size(); ++i) { // `decided_position` is std::nullopt while the dial is not moved at the // base position, or moving. But once it returns to the base position from // non-base positions, it will have a 1 or a larger value that indicates // the position where the dial starts returning. std::optional decided_position = dials[i].PopDecidedPosition(); if (!decided_position) { continue; } // One-time flip to use an alternative set of usages, by the Fn key. std::array*, 9>& current_usages = fn ? fn_usages : usages; // Adjust the index as it is 1-based. size_t index = *decided_position - 1; if (index < current_usages[i]->size()) { uint8_t usage = (*current_usages[i])[index]; if (usage) { usb_hid_keyboard.PressByUsageId(usage); fn = false; } } if (index < modifiers[i]->size()) { uint8_t modifier = (*modifiers[i])[index]; if (modifier == usage_tables::kModifierLeftGUI) { // This device interprets the left GUI key as a one-time keymap // alternate key instead of the normal GUI modifier key use. fn = true; } else { usb_hid_keyboard.SetModifiers( (usb_hid_keyboard.GetModifiers() | modifier)); } } } } }