diff --git a/README.md b/README.md index c71afd4..e78c55a 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ Please refer to the README in each directory for more details. * /mozc-yunomi for firmware, schematics, and PCB layout. * [Gboard bar version](https://g.co/____), [blog post](https://japan.googleblog.com/2022/10/gboard-2022.html) (Oct 1, 2022) * /mozc-bar for firmware, schematics, and PCB layout. +* [Gboard CAPS version](https://g.co/CAPS), [blog post](https://japan.googleblog.com/2023/10/caps.html) (Oct 1, 2023) + * /mozc-caps for firmware and hardware design. # Background stories (in Japanese) diff --git a/mozc-caps/README.md b/mozc-caps/README.md new file mode 100644 index 0000000..6b6a998 --- /dev/null +++ b/mozc-caps/README.md @@ -0,0 +1,161 @@ +# Gboard CAPS version + +This directory contains the firmware and hardware design for Gboard CAPS +version, which was released on **Oct 1, 2023**. + +This is not an officially supported Google product. + +![Gboard CAPS version](./images/header.jpg) + +## Contents + +The directory structure is as follows: + +- hardware/ : STL files for mechanical parts. +- firmware/ : Arduino sketch. + +## Building Gboard CAPS Version + +### Parts + +- M5StickC Plus +- SS-10GL13 micro switch +- [INC-100 bump cap insert](https://www.midori-helmet.jp/pickup/inc-100/) +- [Grove connector cable](https://akizukidenshi.com/catalog/g/gC-16938/) +- M3x10mm screw x 8 +- M2x8mm screw x 2 +- rubber band x 4 +- hot glue +- heat shrink tubing +- 160mm x 160mm x T2.5mm MDF board (or substitute a 3D printed part; see next + section) + +### Hardware + +#### Step 1: Prepare required parts + +3D printed parts: + +- [bump cap insert mount](./hardware/bump_cap_insert_mount.stl) x 2 + \ + ![bump cap insert mount](./images/bump_cap_insert_mount.png) +- [sliding plate](./hardware/sliding_plate.stl) x 2 + \ + ![sliding plate](./images/sliding_plate.png) +- [top plate angle bracket mount](./hardware/top_plate_angle_bracket_mount.stl) + x 2 + \ + ![top plate angle bracket mount](./images/top_plate_angle_bracket_mount.png) +- [switch mount](./hardware/switch_mount.stl) x 1 + \ + ![switch mount](./images/switch_mount.png) + +Other parts: + +- top plate: made of 160mm x 160mm x T2.5mm MDF board + ![top plate](./images/top_board.png) \ + Alternatively, you can 3D print this + part ([top_plate.stl](./hardware/top_plate.stl)) + +#### Step 2: Wiring + +Connect the NO and the C terminals of the micro switch to the black and yellow +wires of the Grove cable, in any order. (These will be connected to port G33 and +GND of M5StickC Plus.) +\ +![switch with wire harness](./images/switch_harness.jpg) + +#### Step 3: Build + +1. Put 2 rubber bands across the two latches on the sliding plates. + \ + ![a](./images/attach_rubber_band.jpg) + +1. Attach one of the top plate angle bracket mounts to one of the sliding plates + and fasten using M3 screws. Repeat for the other set. + \ + ![outer_rail to top_plate_holder](./images/assembly_1.png) + +1. Fasten the angle bracket mount and sliding plate assemblies to the top plate + using M3 screws \ + ![top_plate_holder to top_plate 1](./images/assembly_2.png) + \ + ![top_plate_holder to top_plate 2](./images/assembly_3.png) + +1. Screw the micro switch to the switch mount. + \ + ![micro switch screwed on to a mount](./images/switch_with_mount.jpg) + +1. Glue the switch mount to one of the sliding plates. Check that the switch can + be pressed by testing using one of the bump cap insert mounts. + \ + ![a](./images/switch_pressed.jpg) + +1. Attach the mounts to the left and right side of the cap, and adhere using hot + glue. \ + ![a](./images/mount_inner_rail.jpg) + +1. Combine the two parts by attaching the sliding plates to the rails on the + bump cap insert mounts. + +1. Attach M5StickC Plus and connect the other side of the Grove connector that + is attached to the micro switch. + +### Firmware + +#### Step 1: Environment setup + +- Arduino IDE +- Libraries + - [Madgwick library](https://github.com/arduino-libraries/MadgwickAHRS) + - You can install from Arduino IDE's Library Manager. + - [ESP32 BLE Keyboard library](https://github.com/T-vK/ESP32-BLE-Keyboard) + - You can download zip file from github and install from Sketch -> Include + Library -> Add .ZIP Library menu in Arduino IDE. + - [IMU Library](https://github.com/yamaguchi-am/m5stack-examples) + - Download + [imu.h](https://github.com/yamaguchi-am/m5stack-examples/blob/main/imu/imu.h) + and + [imu.cpp](https://github.com/yamaguchi-am/m5stack-examples/blob/main/imu/imu.cpp) + from this repository and put them into firmware/ directory. + +#### Step 2: Compile and upload + +- Compile and upload the mozc-caps.ino firmware to the M5StickC Plus using + Arduino IDE. + +#### Step 3: Calibration + +- IMU (gyro sensor) calibration + - After powering on the M5StickC Plus, the firmware will run calibration for + the IMU. + - While the display shows "calibrating" message, keep the device still. + - You can also trigger the calibration process by long-pressing Button A. +- Setting the origin direction + - When Button A is pressed, the origin direction is set to the current + orientation of the device. + +#### Step 4: Connect + +Connect the keyboard via Bluetooth to a PC or other device that has Google +Japanese Input/Gboard installed. + +Note: This keyboard keeps sending keycodes even before you press the key. It is +for showing a preview of the selected character to be input on the connected +device. + +## License + +``` +Licensed under the Apache License, Version 2.0 (the "License"); you may +not use this file except in compliance with the License. You may obtain +a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` diff --git a/mozc-caps/firmware/mozc-caps/.gitignore b/mozc-caps/firmware/mozc-caps/.gitignore new file mode 100644 index 0000000..6713f3f --- /dev/null +++ b/mozc-caps/firmware/mozc-caps/.gitignore @@ -0,0 +1,5 @@ +imu.cpp +imu.h +debug.cfg +debug_custom.json +esp32.svd diff --git a/mozc-caps/firmware/mozc-caps/mozc-caps.ino b/mozc-caps/firmware/mozc-caps/mozc-caps.ino new file mode 100644 index 0000000..18f10ce --- /dev/null +++ b/mozc-caps/firmware/mozc-caps/mozc-caps.ino @@ -0,0 +1,172 @@ +#include +#include + +#include "imu.h" + +// Comment out the line below to use English alphabet mode. +#define JAPANESE_ + +#ifdef JAPANESE_ +// Japanese (hiragana) mode. Must be used with romaji input mode. + +// clang-format off +const char* characters[] = { + "a", "i", "u", "e", "o", + "ka", "ki", "ku", "ke", "ko", + "ga", "gi", "gu", "ge", "go", + "sa", "si", "su", "se", "so", + "za", "zi", "zu", "ze", "zo", + "ta", "ti", "tu", "te", "to", + "da", "di", "du", "de", "do", + "na", "ni", "nu", "ne", "no", + "ha", "hi", "hu", "he", "ho", + "ba", "bi", "bu", "be", "bo", + "pa", "pi", "pu", "pe", "po", + "ma", "mi", "mu", "me", "mo", + "ya", "yu", "yo", + "ra", "ri", "ru", "re", "ro", + "wa", "wo", "nn", + "lya", "lyu", "lyo", "ltu", + "-", +}; +// clang-format on + +#else + +// English alphabet mode. + +const char* characters[] = {"a", "b", "c", "d", "e", "f", "g", "h", "i", + "j", "k", "l", "m", "n", "o", "p", "q", "r", + "s", "t", "u", "v", "w", "x", "y", "z", " "}; + +#endif + +// A class that handles character input, interactively showing +// unsettled characater on the target device. +class Compositor { + public: + Compositor() : last_character_unsettled_(false) {} + // TODO: add commit() which commits the Japanese composition by Enter key. + void enter(); + void select(const char* str); + + private: + bool last_character_unsettled_; +}; + +const float kSamplingFrequency = 100.; +const float kIntervalMicros = 1e6 / kSamplingFrequency; +const uint8_t PUSH_SW = G33; + +BleKeyboard bleKeyboard("mozc-caps"); +M5StackIMUManager imu; +Compositor compositor; +long last_time_micros; +float yaw_origin = 180; +int current_key = 0; +int last_key = 0; + +void Compositor::enter() { + if (!last_character_unsettled_) { + return; + } + last_character_unsettled_ = false; +} + +void Compositor::select(const char* str) { + if (last_character_unsettled_) { + // It is assumed there is only one unsettled character, + // as the IME should be set to the romaji input mode. + bleKeyboard.write('\b'); + } + for (const char* p = str; *p; p++) { + bleKeyboard.write(*p); + } + last_character_unsettled_ = true; +} + +void setup() { + M5.begin(); + M5.Lcd.setRotation(3); + M5.Lcd.fillScreen(BLACK); + imu.Init(); + last_time_micros = micros(); + pinMode(PUSH_SW, INPUT_PULLUP); + bleKeyboard.begin(); +} + +void loop() { + float gyro_x, gyro_y, gyro_z, acc_x, acc_y, acc_z; + M5.IMU.getGyroData(&gyro_x, &gyro_y, &gyro_z); + M5.IMU.getAccelData(&acc_x, &acc_y, &acc_z); + // Convert coordinate system to use the rotation around the Y axis as "yaw" + // angle. + imu.Update(kSamplingFrequency, gyro_z, gyro_y, -gyro_x, acc_z, acc_y, -acc_x); + float pitch, roll, yaw; + imu.GetAHRSData(&pitch, &roll, &yaw); + + M5.update(); + if (M5.BtnA.wasPressed()) { + yaw_origin = yaw; + } + if (M5.BtnA.pressedFor(1000)) { + imu.Init(); + } + + float relative_angle = yaw - yaw_origin; + if (relative_angle > 180) { + relative_angle -= 360; + } else if (relative_angle < -180) { + relative_angle += 360; + } + + bool key_pressed = false; + if (digitalRead(PUSH_SW) == LOW) { + key_pressed = true; + } + + // The range of rotation angle [deg] from the center, used for selecting + // characters. + const float kAngleRange = 80; + int num_keys = sizeof(characters) / sizeof(characters[0]); + float position = (relative_angle + kAngleRange) / kAngleRange / 2 * num_keys; + int key_index = static_cast(position + 0.5); + if (key_index < 0) key_index = 0; + if (key_index > num_keys - 1) key_index = num_keys - 1; + float residual = position - key_index; + if (residual < -0.5) residual += 1; + if (residual > 0.5) residual -= 1; + if (residual < 0) { + residual = -residual; + } + if (residual < 0.4 && !key_pressed) { + current_key = key_index; + if (current_key != last_key) { + compositor.select(characters[current_key]); + } + last_key = current_key; + } + if (key_pressed) { + compositor.enter(); + } + + M5.Lcd.setTextSize(1); + M5.Lcd.setTextColor(OLIVE, 0x000000); + M5.Lcd.setCursor(30, 110); + if (imu.Ready()) { + M5.Lcd.printf(" "); + } else { + M5.Lcd.printf("calibrating gyro sensors..."); + } + + M5.Lcd.setTextSize(7); + M5.Lcd.setCursor(0, 24); + M5.Lcd.setTextColor(key_pressed ? YELLOW : DARKGREEN, 0x000000); + M5.Lcd.printf(" %3s ", characters[current_key]); + + long next_time_micros = last_time_micros + kIntervalMicros; + while (micros() < next_time_micros) { + delay(1); + } + last_time_micros = next_time_micros; +} diff --git a/mozc-caps/hardware/bump_cap_insert_mount.stl b/mozc-caps/hardware/bump_cap_insert_mount.stl new file mode 100644 index 0000000..9f6a42d Binary files /dev/null and b/mozc-caps/hardware/bump_cap_insert_mount.stl differ diff --git a/mozc-caps/hardware/sliding_plate.stl b/mozc-caps/hardware/sliding_plate.stl new file mode 100644 index 0000000..c3fdc48 Binary files /dev/null and b/mozc-caps/hardware/sliding_plate.stl differ diff --git a/mozc-caps/hardware/switch_mount.stl b/mozc-caps/hardware/switch_mount.stl new file mode 100644 index 0000000..ab33e1f Binary files /dev/null and b/mozc-caps/hardware/switch_mount.stl differ diff --git a/mozc-caps/hardware/top_plate.stl b/mozc-caps/hardware/top_plate.stl new file mode 100644 index 0000000..fa1578f Binary files /dev/null and b/mozc-caps/hardware/top_plate.stl differ diff --git a/mozc-caps/hardware/top_plate_angle_bracket_mount.stl b/mozc-caps/hardware/top_plate_angle_bracket_mount.stl new file mode 100644 index 0000000..6d20216 Binary files /dev/null and b/mozc-caps/hardware/top_plate_angle_bracket_mount.stl differ diff --git a/mozc-caps/images/assembly_1.png b/mozc-caps/images/assembly_1.png new file mode 100644 index 0000000..e8c3fe9 Binary files /dev/null and b/mozc-caps/images/assembly_1.png differ diff --git a/mozc-caps/images/assembly_2.png b/mozc-caps/images/assembly_2.png new file mode 100644 index 0000000..c38b5ca Binary files /dev/null and b/mozc-caps/images/assembly_2.png differ diff --git a/mozc-caps/images/assembly_3.png b/mozc-caps/images/assembly_3.png new file mode 100644 index 0000000..bef1819 Binary files /dev/null and b/mozc-caps/images/assembly_3.png differ diff --git a/mozc-caps/images/attach_rubber_band.jpg b/mozc-caps/images/attach_rubber_band.jpg new file mode 100644 index 0000000..921521b Binary files /dev/null and b/mozc-caps/images/attach_rubber_band.jpg differ diff --git a/mozc-caps/images/bump_cap_insert_mount.png b/mozc-caps/images/bump_cap_insert_mount.png new file mode 100644 index 0000000..e6bab19 Binary files /dev/null and b/mozc-caps/images/bump_cap_insert_mount.png differ diff --git a/mozc-caps/images/header.jpg b/mozc-caps/images/header.jpg new file mode 100644 index 0000000..f1c0211 Binary files /dev/null and b/mozc-caps/images/header.jpg differ diff --git a/mozc-caps/images/mount_inner_rail.jpg b/mozc-caps/images/mount_inner_rail.jpg new file mode 100644 index 0000000..43e9d65 Binary files /dev/null and b/mozc-caps/images/mount_inner_rail.jpg differ diff --git a/mozc-caps/images/sliding_plate.png b/mozc-caps/images/sliding_plate.png new file mode 100644 index 0000000..786dccc Binary files /dev/null and b/mozc-caps/images/sliding_plate.png differ diff --git a/mozc-caps/images/switch_harness.jpg b/mozc-caps/images/switch_harness.jpg new file mode 100644 index 0000000..60b2adc Binary files /dev/null and b/mozc-caps/images/switch_harness.jpg differ diff --git a/mozc-caps/images/switch_mount.png b/mozc-caps/images/switch_mount.png new file mode 100644 index 0000000..3a34dee Binary files /dev/null and b/mozc-caps/images/switch_mount.png differ diff --git a/mozc-caps/images/switch_pressed.jpg b/mozc-caps/images/switch_pressed.jpg new file mode 100644 index 0000000..f51bf6c Binary files /dev/null and b/mozc-caps/images/switch_pressed.jpg differ diff --git a/mozc-caps/images/switch_with_mount.jpg b/mozc-caps/images/switch_with_mount.jpg new file mode 100644 index 0000000..98187f7 Binary files /dev/null and b/mozc-caps/images/switch_with_mount.jpg differ diff --git a/mozc-caps/images/top_board.png b/mozc-caps/images/top_board.png new file mode 100644 index 0000000..000f377 Binary files /dev/null and b/mozc-caps/images/top_board.png differ diff --git a/mozc-caps/images/top_plate_angle_bracket_mount.png b/mozc-caps/images/top_plate_angle_bracket_mount.png new file mode 100644 index 0000000..4c6d74f Binary files /dev/null and b/mozc-caps/images/top_plate_angle_bracket_mount.png differ