mirror of
https://github.com/google/mozc-devices.git
synced 2025-11-08 16:53:28 +03:00
Add mozc dial version.
Co-authored-by: Takashi Toyoshima <toyoshim@google.com> Co-authored-by: Shun Ikejima <ikejima@google.com>
This commit is contained in:
2
mozc-dial/firmware/.gitignore
vendored
Normal file
2
mozc-dial/firmware/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.vscode
|
||||
build
|
||||
61
mozc-dial/firmware/README.md
Normal file
61
mozc-dial/firmware/README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Firmware Development Guide
|
||||
|
||||
For the entire keyboard assembly, please refer to the [Build Guide](../buildguide.md).
|
||||
|
||||
## Directory List
|
||||
|
||||
- prebuilt/ : Pre-built firmware for each board
|
||||
- common/ : Source files for libraries used in common by each board
|
||||
- main/ : Source files and project for the 9-dial edition main chip
|
||||
- sub/ : Source files and project for the 9-dial edition sub chip
|
||||
- one_dial/ : Source files and project for the 1-dial edition Raspberry Pi Pico
|
||||
|
||||
## Pre-built Firmware
|
||||
|
||||
We have included pre-built firmware under `prebuilt/`, so you can use them as is if no changes are needed.
|
||||
|
||||
Use `main.uf2` and `sub.uf2` for the 9-dial edition, and `one_dial.uf2` for the 1-dial edition.
|
||||
|
||||
## Guide to Developing Your Own Firmware
|
||||
|
||||
Install [Visual Studio Code](https://code.visualstudio.com/) and add the [Raspberry Pi Pico extension](https://marketplace.visualstudio.com/items?itemName=raspberry-pi.raspberry-pi-pico).
|
||||
|
||||
From the `Raspberry Pi Pico Projects` extension in the Activity Bar, use `Import Project` to open each project's directory.
|
||||
If you are using the extension for the first time, the development environment installation will begin. Once the project is imported, you can develop using `CMake` and `Run and Debug` in the Activity Bar.
|
||||
|
||||
As with the official Pico series boards, the 9-dial edition has `SWCLK` and `SWDIO` land pairs labeled `SWC/D` on the board for each chip. By preparing development equipment that supports SWD, such as the official Debug Probe, you can perform step execution in the editor.
|
||||
|
||||
### Standard Output
|
||||
|
||||
On the 9-dial edition, there are lands labeled `TX1` for the main chip and `TX2` for the sub-chip on the board. With the standard settings, the standard output is output from these pins as UART at a speed of 115200.
|
||||
|
||||
On the other hand, by changing `pico_enable_stdio_uart` to `0` and `pico_enable_stdio_semihosting` to `1` in `CMakeLists.txt`, you can switch the output to go through the debugger. This is convenient as you don't need to wire TX, but be aware that the operation will stop at the output point if debugger support is not enabled. It will also stop when the debugger is not attached, so it cannot be used for release builds. Debugger support can be enabled with `monitor arm semihosting enable`, but it is tedious to set it every time you start the debugger, so it is convenient to register it in each command setting in `.vscode/launch.json` as follows.
|
||||
|
||||
```
|
||||
"postLaunchCommands": [
|
||||
"monitor arm semihosting enable"
|
||||
]
|
||||
```
|
||||
|
||||
### Using PICO W or PICO 2 (W)
|
||||
|
||||
The 1-dial edition is set to use the official Raspberry Pi Pico board, but you can support other boards by changing the board setting from `pico` to `pico_w`, `pico2`, `pico2_w`, etc. in `set(PICO_BOARD pico CACHE STRING "Board type")` in `CMakeFiles.txt`. You should also be able to support 3rd party boards with `none`, etc.
|
||||
|
||||
### About EEPROM
|
||||
|
||||
Since the board setting for the 9-dial edition is `none`, a general-purpose driver for EEPROM will be included with a prescaler setting of 4. This should work with most EEPROMs. If you are using the reference parts, we have confirmed that the driver for W25Q080 works with a prescaler setting of 2, similar to the official board. By creating a `board_name.h` in `~/.pico-sdk/sdk/2.2.0/src/boards/include/boards/` and defining
|
||||
|
||||
```
|
||||
#define PICO_BOOT_STAGE_2_CHOOSE_W25Q080 1
|
||||
#define PICO_FLASH_SPI_CLKDIV 2
|
||||
```
|
||||
|
||||
and specifying it in the board settings of `CMakeFiles.txt`, you can optimize EEPROM access via QSPI in case of a cache miss and draw out further performance.
|
||||
|
||||
### About Sensor Adjustment
|
||||
|
||||
By default, the sensor is pulled up by an internal resistor. If you want to adjust it by pulling up with an external resistor, change `gpio_pull_up(gpio);` to `gpio_disable_pulls(gpio);` in `PhotoSensor::PhotoSensor()` in `photo_sensor.cc`.
|
||||
|
||||
Alternatively, it is also possible to pull up with a combined resistance of the internal and external resistors while the internal resistor is enabled. In this case, the internal resistor has a nominal value of 50-80KΩ and is connected in parallel with the external resistor.
|
||||
|
||||
For details, please check the [Board Assembly Guide](../board/README.md).
|
||||
61
mozc-dial/firmware/README_ja.md
Normal file
61
mozc-dial/firmware/README_ja.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# ファームウェア開発ガイド
|
||||
|
||||
キーボードの組み立て全体については[ビルドガイド](../buildguide_ja.md)を参照してください。
|
||||
|
||||
## ディレクトリ一覧
|
||||
|
||||
- prebuilt/ : 各ボード向けに事前にビルドしたファームウェア
|
||||
- common/ : 各ボード共通で用いられるライブラリのソースファイル
|
||||
- main/ : 9ダイヤル版メインチップ用のソースファイルとプロジェクト
|
||||
- sub/ : 9ダイヤル版サブチップ用のソースファイルとプロジェクト
|
||||
- one_dial/ : 1ダイヤル版 Raspberry Pi Pico用のソーとファイルとプロジェクト
|
||||
|
||||
## 事前にビルドしたファームウェア
|
||||
|
||||
`prebuilt/`以下に事前にビルドしたファームウェアを収めていますので、変更が不要の場合はこれらをそのまま用いることができます。
|
||||
|
||||
9ダイヤル板は`main.uf2`と`sub.uf2`を、1ダイヤル板は`one_dial.uf2`を使用します。
|
||||
|
||||
## 独自のファームウェア開発の手引き
|
||||
|
||||
[Visual Studio Code](https://code.visualstudio.com/) をインストールし、[Raspberry Pi Pico拡張](https://marketplace.visualstudio.com/items?itemName=raspberry-pi.raspberry-pi-pico) を追加します。
|
||||
|
||||
Activity Barの`Raspberry Pi Pico Projects`の拡張機能から`Import Project`を使い、各プロジェクトのディレクトリを開いてください。
|
||||
拡張をはじめて使う場合、開発環境のインストールが始まります。プロジェクトが取り込まれると、Activity Barの`CMake`や`Run and Debug`を使って開発できるようになります。
|
||||
|
||||
公式のPicoシリーズボードに搭載されているように、9ダイヤル板でも基板上に`SWC/D`と書かれた`SWCLK`と`SWDIO`のランドペアがそれぞれのチップごとに用意されています。公式のDebug Probe等のSWDをサポートした開発機材を用意する事で、エディタ上でステップ実効等が可能です。
|
||||
|
||||
### 標準出力について
|
||||
|
||||
9ダイヤル板では基板上にメインチップ用の`TX1`、サブチップ用の`TX2`と書かれたランドが用意されており、標準の設定では標準出力はこれらのピンから速度115200のUARTとして出力されています。
|
||||
|
||||
一方で`CMakeLists.txt`の`pico_enable_stdio_uart`を`0`に、`pico_enable_stdio_semihosting`を`1`に変更する事で、出力をデバッガ経由に切り替える事ができます。TXを配線せずに済むので便利ですが、デバッガのサポートを有効にしないと出力のところで動作停止するので注意が必要です。デバッガがアタッチされていない時も同様に停止するので、リリースビルドには使えません。デバッガのサポートは`monitor arm semihosting enable`で有効に出来ますが、デバッガ起動ごとに毎回設定するのは大変なので、`.vscode/launch.json`の各コマンド設定内に以下のように登録しておくと便利です。
|
||||
|
||||
```
|
||||
"postLaunchCommands": [
|
||||
"monitor arm semihosting enable"
|
||||
]
|
||||
```
|
||||
|
||||
### PICO WやPICO 2 (W)の利用
|
||||
|
||||
1ダイヤル板は公式のRaspberry Pi Picoボードを使うように設定してありますが、`CMakeFiles.txt`内の`set(PICO_BOARD pico CACHE STRING "Board type")`のところでボード設定を`pico`から`pico_w`、`pico2`、`pico2_w`等に変更する事で他のボードにも対応できるかと思います。3rd party製のボードについても`none`等で対応できるかと思います。
|
||||
|
||||
### EEPROMについて
|
||||
|
||||
9ダイヤル版はボード設定を`none`にしているので、EEPROMについては汎用のドライバが分周4で組み込まれます。これでだいたいのEEPROMで動作するはずです。リファレンスの部品を使っている場合は、公式ボードと同様にW25Q080用のドライバが分周2で動作する事を確認しています。`~/.pico-sdk/sdk/2.2.0/src/boards/include/boards/`に`ボード名.h`を作成し、その中で
|
||||
|
||||
```
|
||||
#define PICO_BOOT_STAGE_2_CHOOSE_W25Q080 1
|
||||
#define PICO_FLASH_SPI_CLKDIV 2
|
||||
```
|
||||
|
||||
などを定義し、`CMakeFiles.txt`のボード設定で指定する事で、キャッシュミス時のQSPI経由でのEEPROMアクセスを最適化し、さらなる性能を引き出す事も可能です。
|
||||
|
||||
### センサーの調整について
|
||||
|
||||
標準ではセンサーは内部抵抗でpull-upされています。外部抵抗によりpull-upで調整したい場合、`photo_sensor.cc`の`PhotoSensor::PhotoSensor()`内にある`gpio_pull_up(gpio);`を`gpio_disable_pulls(gpio);`に変更してください。
|
||||
|
||||
あるいは、内部抵抗を有効にしたまま外部抵抗との合成抵抗でpull-upする事も可能です。この場合、内部抵抗は公称値で50-80KΩ、外部抵抗とは並列接続になります。
|
||||
|
||||
詳細は[基板組立ガイド](../board/README_ja.md)を確認してください。
|
||||
45
mozc-dial/firmware/common/dial_controller.cc
Normal file
45
mozc-dial/firmware/common/dial_controller.cc
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#include "dial_controller.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint8_t kBasePosition = 0;
|
||||
|
||||
uint8_t ConvertGrayToBinary(uint8_t gray) {
|
||||
uint8_t binary = gray;
|
||||
uint8_t mask = gray >> 1;
|
||||
|
||||
while (mask) {
|
||||
binary ^= mask;
|
||||
mask >>= 1;
|
||||
}
|
||||
return binary;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DialController::DialController() {}
|
||||
|
||||
void DialController::Update(uint8_t sensor_gray_code) {
|
||||
position_ = ConvertGrayToBinary(sensor_gray_code);
|
||||
max_position_ = std::max(max_position_, position_);
|
||||
if (position_ == kBasePosition && max_position_ != kBasePosition) {
|
||||
decided_position_ = max_position_;
|
||||
max_position_ = kBasePosition;
|
||||
}
|
||||
}
|
||||
|
||||
bool DialController::IsBasePosition() const {
|
||||
return position_ == kBasePosition;
|
||||
}
|
||||
|
||||
std::optional<uint8_t> DialController::PopDecidedPosition() {
|
||||
auto result = decided_position_;
|
||||
decided_position_ = std::nullopt;
|
||||
return result;
|
||||
}
|
||||
28
mozc-dial/firmware/common/dial_controller.h
Normal file
28
mozc-dial/firmware/common/dial_controller.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#ifndef COMMON_DIAL_CONTROLLER_H
|
||||
#define COMMON_DIAL_CONTROLLER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
class DialController {
|
||||
public:
|
||||
DialController();
|
||||
DialController(const DialController&) = delete;
|
||||
DialController& operator=(const DialController&) = delete;
|
||||
~DialController() = default;
|
||||
|
||||
void Update(uint8_t sensor_gray_code);
|
||||
bool IsBasePosition() const;
|
||||
std::optional<uint8_t> PopDecidedPosition();
|
||||
|
||||
private:
|
||||
uint8_t position_ = 0u;
|
||||
uint8_t max_position_ = 0u;
|
||||
std::optional<uint8_t> decided_position_;
|
||||
};
|
||||
|
||||
#endif // COMMON_DIAL_CONTROLLER_H
|
||||
108
mozc-dial/firmware/common/motor_controller.cc
Normal file
108
mozc-dial/firmware/common/motor_controller.cc
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#include "motor_controller.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <initializer_list>
|
||||
|
||||
#include "hardware/gpio.h"
|
||||
#include "pico/time.h"
|
||||
|
||||
static constexpr int kMotorStepIntervalInUs = 2000;
|
||||
|
||||
// Activate 1 of 8 motor drivers in a specific step phase.
|
||||
class MotorController::Decoder {
|
||||
public:
|
||||
Decoder(int8_t a0, int8_t a1, int8_t a2, int8_t en)
|
||||
: a0_(a0), a1_(a1), a2_(a2), en_(en) {
|
||||
for (int8_t gpio : {en, a0, a1, a2}) {
|
||||
gpio_init(gpio);
|
||||
gpio_set_dir(gpio, GPIO_OUT);
|
||||
gpio_put(gpio, false);
|
||||
}
|
||||
}
|
||||
Decoder(const Decoder&) = delete;
|
||||
Decoder& operator=(const Decoder&) = delete;
|
||||
~Decoder() = default;
|
||||
|
||||
void Select(uint8_t index) {
|
||||
hard_assert(index < 8);
|
||||
gpio_put(a0_, index & 1);
|
||||
gpio_put(a1_, index & 2);
|
||||
gpio_put(a2_, index & 4);
|
||||
gpio_put(en_, true);
|
||||
}
|
||||
void Unselect() { gpio_put(en_, false); }
|
||||
|
||||
private:
|
||||
int8_t a0_;
|
||||
int8_t a1_;
|
||||
int8_t a2_;
|
||||
int8_t en_;
|
||||
};
|
||||
|
||||
// static
|
||||
bool MotorController::OnAlarm(repeating_timer* t) {
|
||||
static_cast<MotorController*>(t->user_data)->Step();
|
||||
return true;
|
||||
}
|
||||
|
||||
MotorController::MotorController(Mode mode) {
|
||||
if (mode == Mode::k9Motor) {
|
||||
// Each decoder is responsible for a specific phase to driver 1 of 8 motor
|
||||
// drivers. As we have 4 phases, it can drive 4 motors at the same time, and
|
||||
// by time division multiplexing, we make them drive 8 motors.
|
||||
decoder_[0] = std::make_unique<Decoder>(1, 2, 3, 4);
|
||||
decoder_[1] = std::make_unique<Decoder>(5, 6, 7, 8);
|
||||
decoder_[2] = std::make_unique<Decoder>(9, 10, 11, 12);
|
||||
decoder_[3] = std::make_unique<Decoder>(13, 14, 15, 16);
|
||||
}
|
||||
|
||||
// 9th motor driver is controlled via GPIOs directly.
|
||||
for (int8_t gpio : {17, 18, 19, 20}) {
|
||||
gpio_init(gpio);
|
||||
gpio_set_dir(gpio, GPIO_OUT);
|
||||
gpio_put(gpio, false);
|
||||
}
|
||||
|
||||
add_repeating_timer_us(-kMotorStepIntervalInUs, OnAlarm, this, &timer_);
|
||||
}
|
||||
|
||||
MotorController::~MotorController() {}
|
||||
|
||||
void MotorController::Start(uint8_t index) {
|
||||
hard_assert(index < kNumOfMotors);
|
||||
started_[index] = true;
|
||||
}
|
||||
|
||||
void MotorController::Stop(uint8_t index) {
|
||||
hard_assert(index < kNumOfMotors);
|
||||
started_[index] = false;
|
||||
}
|
||||
|
||||
void MotorController::Step() {
|
||||
// Drive first 4 motor drivers in even phases, and later 4 ones in odd
|
||||
// phases.
|
||||
size_t start_index = (phase_ & 1) ? kNumOfPhases : 0;
|
||||
size_t half_phase = phase_ >> 1;
|
||||
for (size_t i = start_index; i < start_index + kNumOfPhases; ++i) {
|
||||
if (!decoder_[i]) {
|
||||
continue;
|
||||
}
|
||||
if (started_[i]) {
|
||||
decoder_[(i + half_phase) % kNumOfPhases]->Select(i);
|
||||
} else {
|
||||
decoder_[(i + half_phase) % kNumOfPhases]->Unselect();
|
||||
}
|
||||
}
|
||||
// Drive the last 9th motor drivers.
|
||||
bool on = started_[8] && phase_ & 1;
|
||||
gpio_put(17, on && half_phase == 0);
|
||||
gpio_put(18, on && half_phase == 1);
|
||||
gpio_put(19, on && half_phase == 2);
|
||||
gpio_put(20, on && half_phase == 3);
|
||||
|
||||
phase_ = (phase_ + 7) % 8;
|
||||
}
|
||||
38
mozc-dial/firmware/common/motor_controller.h
Normal file
38
mozc-dial/firmware/common/motor_controller.h
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#ifndef COMMON_MOTOR_CONTROLLER_H_
|
||||
#define COMMON_MOTOR_CONTROLLER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "pico/time.h"
|
||||
|
||||
class MotorController final {
|
||||
public:
|
||||
enum class Mode { k1Motor, k9Motor };
|
||||
explicit MotorController(Mode = Mode::k9Motor);
|
||||
MotorController(const MotorController&) = delete;
|
||||
MotorController& operator=(const MotorController&) = delete;
|
||||
~MotorController();
|
||||
|
||||
void Start(uint8_t index);
|
||||
void Stop(uint8_t index);
|
||||
|
||||
private:
|
||||
class Decoder;
|
||||
|
||||
static bool OnAlarm(repeating_timer* t);
|
||||
void Step();
|
||||
|
||||
static constexpr size_t kNumOfPhases = 4;
|
||||
static constexpr size_t kNumOfMotors = 9;
|
||||
std::unique_ptr<Decoder> decoder_[kNumOfPhases];
|
||||
bool started_[kNumOfMotors] = { false };
|
||||
uint8_t phase_ = 0;
|
||||
repeating_timer timer_;
|
||||
};
|
||||
|
||||
#endif // COMMON_MOTOR_CONTROLLER_H_
|
||||
32
mozc-dial/firmware/common/photo_sensor.cc
Normal file
32
mozc-dial/firmware/common/photo_sensor.cc
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#include "photo_sensor.h"
|
||||
|
||||
#include "hardware/gpio.h"
|
||||
|
||||
PhotoSensor::PhotoSensor(int8_t bit0,
|
||||
int8_t bit1,
|
||||
int8_t bit2,
|
||||
int8_t bit3,
|
||||
int8_t bit4,
|
||||
int8_t bit5)
|
||||
: bits({bit0, bit1, bit2, bit3, bit4, bit5}) {
|
||||
for (int8_t gpio : bits) {
|
||||
gpio_init(gpio);
|
||||
gpio_set_dir(gpio, GPIO_IN);
|
||||
gpio_pull_up(gpio); // Built-in 50-80K pull-up
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t PhotoSensor::Read() {
|
||||
uint8_t value = 0;
|
||||
for (size_t bit = 0; bit < bits.size(); ++bit) {
|
||||
if (bits[bit] == kNotUsed) {
|
||||
break;
|
||||
}
|
||||
value |= gpio_get(bits[bit]) ? (1 << bit) : 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
33
mozc-dial/firmware/common/photo_sensor.h
Normal file
33
mozc-dial/firmware/common/photo_sensor.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#ifndef COMMON_PHOTO_SENSOR_H_
|
||||
#define COMMON_PHOTO_SENSOR_H_
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
class PhotoSensor final {
|
||||
public:
|
||||
PhotoSensor(int8_t bit0,
|
||||
int8_t bit1 = kNotUsed,
|
||||
int8_t bit2 = kNotUsed,
|
||||
int8_t bit3 = kNotUsed,
|
||||
int8_t bit4 = kNotUsed,
|
||||
int8_t bit5 = kNotUsed);
|
||||
PhotoSensor(const PhotoSensor&) = delete;
|
||||
PhotoSensor& operator=(const PhotoSensor&) = delete;
|
||||
~PhotoSensor() = default;
|
||||
|
||||
uint8_t Read();
|
||||
|
||||
private:
|
||||
static constexpr int8_t kNotUsed = -1;
|
||||
static constexpr size_t kMaxWidth = 6;
|
||||
|
||||
std::array<int8_t, kMaxWidth> bits;
|
||||
};
|
||||
|
||||
#endif // COMMON_PHOTO_SENSOR_H_
|
||||
145
mozc-dial/firmware/common/usage_tables.cc
Normal file
145
mozc-dial/firmware/common/usage_tables.cc
Normal file
@@ -0,0 +1,145 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#include "usage_tables.h"
|
||||
|
||||
namespace usage_tables {
|
||||
|
||||
const std::vector<uint8_t> a = {
|
||||
0x00, // no input (blank area between the base and the first position)
|
||||
0x2f, // @
|
||||
0x34, // :
|
||||
0x87, // _
|
||||
0x13, // p
|
||||
0x33, // ;
|
||||
0x38, // /
|
||||
0x12, // o
|
||||
0x0f, // l
|
||||
0x37, // .
|
||||
0x0c, // i
|
||||
0x0e, // k
|
||||
0x36, // ,
|
||||
0x18, // u
|
||||
0x0d, // j
|
||||
0x10, // m
|
||||
0x1c, // y
|
||||
0x0b, // h
|
||||
0x11, // n
|
||||
0x17, // t
|
||||
0x0a, // g
|
||||
0x05, // b
|
||||
0x15, // r
|
||||
0x09, // f
|
||||
0x19, // v
|
||||
0x08, // e
|
||||
0x07, // d
|
||||
0x06, // c
|
||||
0x1a, // w
|
||||
0x16, // s
|
||||
0x1b, // x
|
||||
0x14, // q
|
||||
0x04, // a
|
||||
0x1d, // z
|
||||
0x39, // <CAPS>
|
||||
0x2c, // <SPACE>
|
||||
};
|
||||
const std::vector<uint8_t> fn_a = {};
|
||||
const std::vector<uint8_t> modifier_a = {};
|
||||
|
||||
const std::vector<uint8_t> b = {
|
||||
0x2e, // ^ (~)
|
||||
0x29, // <ESC>
|
||||
0x2b, // <TAB>
|
||||
};
|
||||
const std::vector<uint8_t> fn_b = {};
|
||||
const std::vector<uint8_t> modifier_b = {};
|
||||
|
||||
const std::vector<uint8_t> c = {
|
||||
0x00, // <SHIFT>
|
||||
0x00, // <CTRL>
|
||||
0x00, // <ALT>
|
||||
0x00, // <FN>
|
||||
};
|
||||
const std::vector<uint8_t> fn_c = {};
|
||||
const std::vector<uint8_t> modifier_c = {
|
||||
kModifierLeftShift,
|
||||
kModifierLeftCtrl,
|
||||
kModifierLeftAlt,
|
||||
kModifierLeftGUI,
|
||||
};
|
||||
|
||||
const std::vector<uint8_t> d = {
|
||||
0x00, // no input
|
||||
0x28, // <ENTER>
|
||||
};
|
||||
const std::vector<uint8_t> fn_d = {};
|
||||
const std::vector<uint8_t> modifier_d = {};
|
||||
|
||||
const std::vector<uint8_t> e = {
|
||||
0x00, // no input
|
||||
0x4d, // <END>
|
||||
0x4e, // <PAGE DOWN>
|
||||
0x4b, // <PAGE UP>
|
||||
0x4a, // <HOME>
|
||||
0x49, // <INS>
|
||||
0x4c, // <DEL>
|
||||
};
|
||||
const std::vector<uint8_t> fn_e = {};
|
||||
const std::vector<uint8_t> modifier_e = {};
|
||||
|
||||
const std::vector<uint8_t> f = {
|
||||
0x00, // no input
|
||||
0x4f, // <RIGHT>
|
||||
0x52, // <UP>
|
||||
0x50, // <LEFT>
|
||||
0x51, // <DOWN>
|
||||
};
|
||||
const std::vector<uint8_t> fn_f = {};
|
||||
const std::vector<uint8_t> modifier_f = {};
|
||||
|
||||
const std::vector<uint8_t> g = {
|
||||
0x55, // KP-*
|
||||
0x56, // KP-/
|
||||
0x63, // KP-.
|
||||
};
|
||||
const std::vector<uint8_t> fn_g = {};
|
||||
const std::vector<uint8_t> modifier_g = {};
|
||||
|
||||
const std::vector<uint8_t> h = {
|
||||
0x57, // KP-+
|
||||
0x56, // KP--
|
||||
0x67, // KP-=
|
||||
};
|
||||
const std::vector<uint8_t> fn_h = {};
|
||||
const std::vector<uint8_t> modifier_h = {};
|
||||
|
||||
const std::vector<uint8_t> i = {
|
||||
0x00, // no input
|
||||
0x26, // 9
|
||||
0x25, // 8
|
||||
0x24, // 7
|
||||
0x23, // 6
|
||||
0x22, // 5
|
||||
0x21, // 4
|
||||
0x20, // 3
|
||||
0x1f, // 2
|
||||
0x1e, // 1
|
||||
0x27, // 0
|
||||
};
|
||||
const std::vector<uint8_t> fn_i = {
|
||||
0x00, // no input
|
||||
0x42, // F9
|
||||
0x41, // F8
|
||||
0x40, // F7
|
||||
0x3f, // F6
|
||||
0x3e, // F5
|
||||
0x3d, // F4
|
||||
0x3c, // F3
|
||||
0x3b, // F2
|
||||
0x3a, // F1
|
||||
0x43, // F10
|
||||
};
|
||||
const std::vector<uint8_t> modifier_i = {};
|
||||
|
||||
} // namespace usage_tables
|
||||
54
mozc-dial/firmware/common/usage_tables.h
Normal file
54
mozc-dial/firmware/common/usage_tables.h
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#ifndef COMMON_USAGE_TABLES_
|
||||
#define COMMON_USAGE_TABLES_
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
namespace usage_tables {
|
||||
|
||||
static constexpr uint8_t kModifierLeftCtrl = (1 << 0);
|
||||
static constexpr uint8_t kModifierLeftShift = (1 << 1);
|
||||
static constexpr uint8_t kModifierLeftAlt = (1 << 2);
|
||||
static constexpr uint8_t kModifierLeftGUI = (1 << 3);
|
||||
static constexpr uint8_t kModifierRightCtrl = (1 << 4);
|
||||
static constexpr uint8_t kModifierRightShift = (1 << 5);
|
||||
static constexpr uint8_t kModifierRightAlt = (1 << 6);
|
||||
static constexpr uint8_t kModifierRightGUI = (1 << 7);
|
||||
|
||||
extern const std::vector<uint8_t> a;
|
||||
extern const std::vector<uint8_t> b;
|
||||
extern const std::vector<uint8_t> c;
|
||||
extern const std::vector<uint8_t> d;
|
||||
extern const std::vector<uint8_t> e;
|
||||
extern const std::vector<uint8_t> f;
|
||||
extern const std::vector<uint8_t> g;
|
||||
extern const std::vector<uint8_t> h;
|
||||
extern const std::vector<uint8_t> i;
|
||||
|
||||
extern const std::vector<uint8_t> fn_a;
|
||||
extern const std::vector<uint8_t> fn_b;
|
||||
extern const std::vector<uint8_t> fn_c;
|
||||
extern const std::vector<uint8_t> fn_d;
|
||||
extern const std::vector<uint8_t> fn_e;
|
||||
extern const std::vector<uint8_t> fn_f;
|
||||
extern const std::vector<uint8_t> fn_g;
|
||||
extern const std::vector<uint8_t> fn_h;
|
||||
extern const std::vector<uint8_t> fn_i;
|
||||
|
||||
extern const std::vector<uint8_t> modifier_a;
|
||||
extern const std::vector<uint8_t> modifier_b;
|
||||
extern const std::vector<uint8_t> modifier_c;
|
||||
extern const std::vector<uint8_t> modifier_d;
|
||||
extern const std::vector<uint8_t> modifier_e;
|
||||
extern const std::vector<uint8_t> modifier_f;
|
||||
extern const std::vector<uint8_t> modifier_g;
|
||||
extern const std::vector<uint8_t> modifier_h;
|
||||
extern const std::vector<uint8_t> modifier_i;
|
||||
|
||||
} // namespace usage_tables
|
||||
|
||||
#endif // COMMON_USAGE_TABLES_
|
||||
353
mozc-dial/firmware/common/usb_device.cc
Normal file
353
mozc-dial/firmware/common/usb_device.cc
Normal file
@@ -0,0 +1,353 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#include "usb_device.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/regs/usb.h"
|
||||
#include "hardware/resets.h"
|
||||
#include "hardware/structs/usb.h"
|
||||
#include "pico/types.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint8_t kControlEndpoint = 0u;
|
||||
|
||||
UsbDevice* sUsbDevice = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" void isr_usbctrl(void) {
|
||||
UsbDevice::HandleInterrupt();
|
||||
}
|
||||
|
||||
// static
|
||||
void UsbDevice::HandleInterrupt() {
|
||||
uint32_t status = usb_hw->ints;
|
||||
|
||||
if (status & USB_INTS_SETUP_REQ_BITS) {
|
||||
static_cast<usb_hw_t*>(hw_clear_alias_untyped(usb_hw))->sie_status =
|
||||
USB_SIE_STATUS_SETUP_REC_BITS;
|
||||
sUsbDevice->HandleSetupRequest();
|
||||
}
|
||||
if (status & USB_INTS_BUFF_STATUS_BITS) {
|
||||
sUsbDevice->HandleBufferStatus();
|
||||
}
|
||||
if (status & USB_INTS_BUS_RESET_BITS) {
|
||||
static_cast<usb_hw_t*>(hw_clear_alias_untyped(usb_hw))->sie_status =
|
||||
USB_SIE_STATUS_BUS_RESET_BITS;
|
||||
sUsbDevice->HandleBusReset();
|
||||
}
|
||||
}
|
||||
|
||||
UsbDevice::UsbDevice(
|
||||
const DeviceDescriptor& device_descriptor,
|
||||
const ConfigurationDescriptor& configuration_descriptor,
|
||||
const std::vector<InterfaceDescriptor>& interface_descriptors,
|
||||
const std::vector<EndPointDescriptor>& endpoint_descriptors,
|
||||
const std::vector<std::string>& strings)
|
||||
: device_descriptor_(device_descriptor),
|
||||
configuration_descriptor_(configuration_descriptor),
|
||||
interface_descriptors_(interface_descriptors),
|
||||
endpoint_descriptors_(endpoint_descriptors),
|
||||
strings_(strings) {
|
||||
hard_assert(sUsbDevice == nullptr);
|
||||
sUsbDevice = this;
|
||||
|
||||
reset_unreset_block_num_wait_blocking(RESET_USBCTRL);
|
||||
memset(usb_dpram, 0, sizeof(*usb_dpram));
|
||||
|
||||
irq_set_enabled(USBCTRL_IRQ, true);
|
||||
|
||||
usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS;
|
||||
usb_hw->pwr =
|
||||
USB_USB_PWR_VBUS_DETECT_BITS | USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN_BITS;
|
||||
usb_hw->main_ctrl = USB_MAIN_CTRL_CONTROLLER_EN_BITS;
|
||||
usb_hw->sie_ctrl = USB_SIE_CTRL_EP0_INT_1BUF_BITS;
|
||||
usb_hw->inte = USB_INTS_BUFF_STATUS_BITS | USB_INTS_BUS_RESET_BITS |
|
||||
USB_INTS_SETUP_REQ_BITS;
|
||||
|
||||
sending_data_.push_back(std::span<const uint8_t>());
|
||||
receiving_data_.push_back(std::span<uint8_t>());
|
||||
|
||||
uint32_t dpram_offset = offsetof(usb_device_dpram_t, epx_data);
|
||||
for (size_t i = 0; i < endpoint_descriptors_.size(); ++i) {
|
||||
uint32_t control =
|
||||
EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER |
|
||||
(endpoint_descriptors_[i].bmAttributes << EP_CTRL_BUFFER_TYPE_LSB) |
|
||||
dpram_offset;
|
||||
if (endpoint_descriptors_[i].bEndpointAddress & kDirIn) {
|
||||
usb_dpram->ep_ctrl[i].in = control;
|
||||
} else {
|
||||
usb_dpram->ep_ctrl[i].out = control;
|
||||
}
|
||||
dpram_offset += endpoint_descriptors_[i].wMaxPacketSize;
|
||||
sending_data_.push_back(std::span<const uint8_t>());
|
||||
receiving_data_.push_back(std::span<uint8_t>());
|
||||
}
|
||||
size_t endpoints = sending_data_.size();
|
||||
in_next_pid_.resize(endpoints);
|
||||
out_next_pid_.resize(endpoints);
|
||||
|
||||
static_cast<usb_hw_t*>(hw_set_alias_untyped(usb_hw))->sie_ctrl =
|
||||
USB_SIE_CTRL_PULLUP_EN_BITS;
|
||||
}
|
||||
|
||||
void UsbDevice::FillConfigurations(std::vector<uint8_t>& buffer) {
|
||||
buffer.clear();
|
||||
buffer.reserve(configuration_descriptor_.wTotalLength);
|
||||
std::span<const uint8_t> config_span(
|
||||
reinterpret_cast<const uint8_t*>(&configuration_descriptor_),
|
||||
sizeof(configuration_descriptor_));
|
||||
buffer.insert(buffer.end(), config_span.begin(), config_span.end());
|
||||
for (const auto& interface_descriptor : interface_descriptors_) {
|
||||
std::span<const uint8_t> interface_span(
|
||||
reinterpret_cast<const uint8_t*>(&interface_descriptor),
|
||||
sizeof(interface_descriptor));
|
||||
buffer.insert(buffer.end(), interface_span.begin(), interface_span.end());
|
||||
}
|
||||
for (const auto& endpoint_descriptor : endpoint_descriptors_) {
|
||||
std::span<const uint8_t> endpoint_span(
|
||||
reinterpret_cast<const uint8_t*>(&endpoint_descriptor),
|
||||
sizeof(endpoint_descriptor));
|
||||
buffer.insert(buffer.end(), endpoint_span.begin(), endpoint_span.end());
|
||||
}
|
||||
}
|
||||
|
||||
void UsbDevice::GetDescriptor(volatile SetupPacket* setup) {
|
||||
uint8_t type = setup->wValue >> 8;
|
||||
switch (type) {
|
||||
case kDescriptorTypeDevice:
|
||||
Send(kControlEndpoint, std::span<const uint8_t>(
|
||||
reinterpret_cast<const uint8_t*>(&device_descriptor_),
|
||||
std::min(sizeof(device_descriptor_),
|
||||
static_cast<size_t>(setup->wLength))));
|
||||
break;
|
||||
case kDescriptorTypeConfiguration: {
|
||||
FillConfigurations(setup_buffer_);
|
||||
Send(kControlEndpoint, std::span<const uint8_t>(
|
||||
setup_buffer_.data(),
|
||||
std::min(setup_buffer_.size(),
|
||||
static_cast<size_t>(setup->wLength))));
|
||||
break;
|
||||
}
|
||||
case kDescriptorTypeString: {
|
||||
uint8_t index = setup->wValue & 0xff;
|
||||
setup_buffer_.clear();
|
||||
if (index == 0) {
|
||||
setup_buffer_.reserve(4);
|
||||
setup_buffer_.push_back(0x04);
|
||||
setup_buffer_.push_back(kDescriptorTypeString);
|
||||
setup_buffer_.push_back(0x09);
|
||||
setup_buffer_.push_back(0x04);
|
||||
} else if (index <= strings_.size()) {
|
||||
size_t size = strings_[index - 1].size() * 2 + 2;
|
||||
setup_buffer_.reserve(size);
|
||||
setup_buffer_.push_back(size);
|
||||
setup_buffer_.push_back(kDescriptorTypeString);
|
||||
for (size_t i = 0; i < strings_[index - 1].size(); ++i) {
|
||||
setup_buffer_.push_back(strings_[index - 1][i]);
|
||||
setup_buffer_.push_back(0);
|
||||
}
|
||||
} else {
|
||||
setup_buffer_.reserve(2);
|
||||
setup_buffer_.push_back(0x02);
|
||||
setup_buffer_.push_back(kDescriptorTypeString);
|
||||
}
|
||||
Send(kControlEndpoint, std::span<const uint8_t>(
|
||||
setup_buffer_.data(),
|
||||
std::min(setup_buffer_.size(),
|
||||
static_cast<size_t>(setup->wLength))));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
printf("unsupported get descriptor: $%02x\n", type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: better to return STALL on unsupported requests.
|
||||
void UsbDevice::HandleSetupRequest(volatile SetupPacket* setup) {
|
||||
uint8_t type =
|
||||
setup->bmRequestType & ~(kRecipientInterface | kRecipientEndPoint);
|
||||
if (type == kDirOut) {
|
||||
switch (setup->bRequest) {
|
||||
case kRequestClearFeature:
|
||||
AcknowledgeOutRequest();
|
||||
break;
|
||||
case kRequestSetAddress:
|
||||
SetAddress(setup);
|
||||
break;
|
||||
case kRequestSetConfiguration:
|
||||
SetConfiguration(setup);
|
||||
break;
|
||||
default:
|
||||
printf("unsupported setup out: $%02x\n", setup->bRequest);
|
||||
break;
|
||||
}
|
||||
} else if (type == kDirIn) {
|
||||
switch (setup->bRequest) {
|
||||
case kRequestGetDescriptor:
|
||||
GetDescriptor(setup);
|
||||
break;
|
||||
default:
|
||||
printf("unsupported setup in: $%02x\n", setup->bRequest);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UsbDevice::AcknowledgeOutRequest() {
|
||||
Send(kControlEndpoint, std::span<uint8_t>({}));
|
||||
}
|
||||
|
||||
void UsbDevice::Send(uint8_t endpoint, std::span<const uint8_t> data) {
|
||||
if (endpoint == kControlEndpoint) {
|
||||
in_next_pid_[endpoint] = 1u;
|
||||
}
|
||||
sending_data_[endpoint] = data;
|
||||
SendInternal(endpoint);
|
||||
}
|
||||
|
||||
void UsbDevice::Receive(uint8_t endpoint, std::span<uint8_t> data) {
|
||||
receiving_data_[endpoint] = data;
|
||||
out_next_pid_[endpoint] = 1u;
|
||||
ReceiveInternal(endpoint);
|
||||
}
|
||||
|
||||
void UsbDevice::SendInternal(uint8_t endpoint) {
|
||||
size_t transfer_size = std::min(sending_data_[endpoint].size(), GetMaxPacketSize(endpoint));
|
||||
uint8_t* buffer = GetTransferBuffer(endpoint);
|
||||
memcpy(buffer, sending_data_[endpoint].data(), transfer_size);
|
||||
uint32_t pid =
|
||||
in_next_pid_[endpoint] ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID;
|
||||
in_next_pid_[endpoint] ^= 1u;
|
||||
usb_dpram->ep_buf_ctrl[endpoint].in =
|
||||
transfer_size | USB_BUF_CTRL_AVAIL | USB_BUF_CTRL_FULL | pid;
|
||||
|
||||
size_t remaining_size = sending_data_[endpoint].size() - transfer_size;
|
||||
if (remaining_size) {
|
||||
sending_data_[endpoint] =
|
||||
std::span(&sending_data_[endpoint][transfer_size], remaining_size);
|
||||
} else {
|
||||
sending_data_[endpoint] = std::span<const uint8_t>();
|
||||
}
|
||||
}
|
||||
|
||||
void UsbDevice::ReceiveInternal(uint8_t endpoint) {
|
||||
size_t transfer_size = std::min(receiving_data_[endpoint].size(), GetMaxPacketSize(endpoint));
|
||||
uint32_t pid =
|
||||
out_next_pid_[endpoint] ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID;
|
||||
usb_dpram->ep_buf_ctrl[endpoint].out =
|
||||
transfer_size | USB_BUF_CTRL_AVAIL | pid;
|
||||
}
|
||||
|
||||
void UsbDevice::HandleSetupRequest() {
|
||||
volatile SetupPacket* setup =
|
||||
reinterpret_cast<volatile SetupPacket*>(&usb_dpram->setup_packet);
|
||||
HandleSetupRequest(setup);
|
||||
}
|
||||
|
||||
void UsbDevice::HandleBufferStatus() {
|
||||
uint32_t status = usb_hw->buf_status;
|
||||
uint8_t endpoint_bits = (1 + endpoint_descriptors_.size()) * 2;
|
||||
for (uint8_t i = 0; i < endpoint_bits; ++i) {
|
||||
uint32_t bit = 1 << i;
|
||||
uint8_t endpoint = i >> 1;
|
||||
bool out = i & 1;
|
||||
if ((status & bit) == 0) {
|
||||
continue;
|
||||
}
|
||||
static_cast<usb_hw_t*>(hw_clear_alias_untyped(usb_hw))->buf_status = bit;
|
||||
if (out) {
|
||||
OnReceived(endpoint,
|
||||
usb_dpram->ep_buf_ctrl[endpoint].out & USB_BUF_CTRL_LEN_MASK);
|
||||
} else {
|
||||
OnSent(endpoint,
|
||||
usb_dpram->ep_buf_ctrl[endpoint].in & USB_BUF_CTRL_LEN_MASK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UsbDevice::HandleBusReset() {
|
||||
address_ = 0;
|
||||
ready_ = false;
|
||||
usb_hw->dev_addr_ctrl = 0;
|
||||
should_set_address_ = false;
|
||||
for (auto& data : sending_data_) {
|
||||
data = std::span<const uint8_t>();
|
||||
}
|
||||
for (auto& data : receiving_data_) {
|
||||
data = std::span<uint8_t>();
|
||||
}
|
||||
}
|
||||
|
||||
void UsbDevice::OnSent(uint8_t endpoint, uint32_t length) {
|
||||
if (should_set_address_) {
|
||||
usb_hw->dev_addr_ctrl = address_;
|
||||
should_set_address_ = false;
|
||||
} else if (length == 0) {
|
||||
return;
|
||||
} else if (sending_data_[endpoint].empty()) {
|
||||
Receive(endpoint, {});
|
||||
OnCompleteToSend(endpoint);
|
||||
} else {
|
||||
SendInternal(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
void UsbDevice::OnReceived(uint8_t endpoint, uint32_t length) {
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
size_t receive_size =
|
||||
std::min(receiving_data_[endpoint].size(), static_cast<size_t>(length));
|
||||
uint8_t* buffer = GetTransferBuffer(endpoint);
|
||||
memcpy(receiving_data_[endpoint].data(), buffer, receive_size);
|
||||
size_t remaining_size = receiving_data_[endpoint].size() - receive_size;
|
||||
if (remaining_size) {
|
||||
receiving_data_[endpoint] =
|
||||
std::span(&receiving_data_[endpoint][receive_size], remaining_size);
|
||||
ReceiveInternal(endpoint);
|
||||
} else {
|
||||
receiving_data_[endpoint] = std::span<uint8_t>();
|
||||
Send(endpoint, {});
|
||||
}
|
||||
}
|
||||
|
||||
void UsbDevice::SetAddress(volatile SetupPacket* setup) {
|
||||
address_ = setup->wValue & 0xff;
|
||||
should_set_address_ = true;
|
||||
AcknowledgeOutRequest();
|
||||
}
|
||||
|
||||
void UsbDevice::SetConfiguration(volatile SetupPacket* setup) {
|
||||
ready_ = true;
|
||||
AcknowledgeOutRequest();
|
||||
}
|
||||
|
||||
size_t UsbDevice::GetMaxPacketSize(uint8_t endpoint) {
|
||||
if (endpoint == kControlEndpoint) {
|
||||
return device_descriptor_.bMaxPacketSize0;
|
||||
} else if (endpoint <= endpoint_descriptors_.size()) {
|
||||
return endpoint_descriptors_[endpoint - 1].wMaxPacketSize;
|
||||
}
|
||||
hard_assert(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t* UsbDevice::GetTransferBuffer(uint8_t endpoint) {
|
||||
if (endpoint == kControlEndpoint) {
|
||||
return usb_dpram->ep0_buf_a;
|
||||
}
|
||||
uint8_t* buffer = usb_dpram->epx_data;
|
||||
for (size_t i = 1; i < endpoint; ++i) {
|
||||
buffer += endpoint_descriptors_[i].wMaxPacketSize;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
145
mozc-dial/firmware/common/usb_device.h
Normal file
145
mozc-dial/firmware/common/usb_device.h
Normal file
@@ -0,0 +1,145 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#ifndef COMMON_USB_DEVICE_H_
|
||||
#define COMMON_USB_DEVICE_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class UsbDevice {
|
||||
public:
|
||||
struct SetupPacket {
|
||||
uint8_t bmRequestType;
|
||||
uint8_t bRequest;
|
||||
uint16_t wValue;
|
||||
uint16_t wIndex;
|
||||
uint16_t wLength;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct DeviceDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint16_t bcdUSB;
|
||||
uint8_t bDeviceClass;
|
||||
uint8_t bDeviceSubClass;
|
||||
uint8_t bDeviceProtocol;
|
||||
uint8_t bMaxPacketSize0;
|
||||
uint16_t idVendor;
|
||||
uint16_t idProduct;
|
||||
uint16_t bcdDevice;
|
||||
uint8_t iManufacturer;
|
||||
uint8_t iProduct;
|
||||
uint8_t iSerialNumber;
|
||||
uint8_t bNumConfigurations;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ConfigurationDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint16_t wTotalLength;
|
||||
uint8_t bNumInterfaces;
|
||||
uint8_t bConfigurationValue;
|
||||
uint8_t iConfiguration;
|
||||
uint8_t bmAttributes;
|
||||
uint8_t bMaxPower;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct InterfaceDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bInterfaceNumber;
|
||||
uint8_t bAlternateSetting;
|
||||
uint8_t bNumEndpoints;
|
||||
uint8_t bInterfaceClass;
|
||||
uint8_t bInterfaceSubClass;
|
||||
uint8_t bInterfaceProtocol;
|
||||
uint8_t iInterface;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct EndPointDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bEndpointAddress;
|
||||
uint8_t bmAttributes;
|
||||
uint16_t wMaxPacketSize;
|
||||
uint8_t bInterval;
|
||||
} __attribute__((packed));
|
||||
|
||||
static constexpr uint8_t kDirOut = 0x00u;
|
||||
static constexpr uint8_t kDirIn = 0x80u;
|
||||
static constexpr uint8_t kTypeClass = 0x20;
|
||||
static constexpr uint8_t kRecipientInterface = 0x01;
|
||||
static constexpr uint8_t kRecipientEndPoint = 0x02;
|
||||
|
||||
static constexpr uint8_t kRequestClearFeature = 0x01u;
|
||||
static constexpr uint8_t kRequestSetAddress = 0x05u;
|
||||
static constexpr uint8_t kRequestGetDescriptor = 0x06u;
|
||||
static constexpr uint8_t kRequestSetConfiguration = 0x09u;
|
||||
|
||||
static constexpr uint8_t kDescriptorTypeDevice = 0x01u;
|
||||
static constexpr uint8_t kDescriptorTypeConfiguration = 0x02u;
|
||||
static constexpr uint8_t kDescriptorTypeString = 0x03u;
|
||||
static constexpr uint8_t kDescriptorTypeInterface = 0x04u;
|
||||
static constexpr uint8_t kDescriptorTypeEndPoint = 0x05u;
|
||||
|
||||
static constexpr uint8_t kEndPointAttributeInterrupt = 0x03u;
|
||||
|
||||
static void HandleInterrupt();
|
||||
|
||||
UsbDevice(const DeviceDescriptor& device_descriptor,
|
||||
const ConfigurationDescriptor& configuration_descriptor,
|
||||
const std::vector<InterfaceDescriptor>& interface_descriptors,
|
||||
const std::vector<EndPointDescriptor>& endpoint_descriptors,
|
||||
const std::vector<std::string>& strings);
|
||||
UsbDevice(const UsbDevice&) = delete;
|
||||
UsbDevice& operator=(const UsbDevice&) = delete;
|
||||
virtual ~UsbDevice() = default;
|
||||
|
||||
protected:
|
||||
virtual void FillConfigurations(std::vector<uint8_t>& buffer);
|
||||
virtual void GetDescriptor(volatile SetupPacket* setup);
|
||||
virtual void HandleSetupRequest(volatile SetupPacket* setup);
|
||||
virtual void OnCompleteToSend(uint8_t endpoint) {};
|
||||
|
||||
void AcknowledgeOutRequest();
|
||||
void Send(uint8_t endpoint, std::span<const uint8_t> data);
|
||||
void Receive(uint8_t endpoint, std::span<uint8_t> data);
|
||||
|
||||
private:
|
||||
void HandleSetupRequest();
|
||||
void HandleBufferStatus();
|
||||
void HandleBusReset();
|
||||
|
||||
void SendInternal(uint8_t endpoint);
|
||||
void ReceiveInternal(uint8_t endpoint);
|
||||
|
||||
void OnSent(uint8_t endpoint, uint32_t length);
|
||||
void OnReceived(uint8_t endpoint, uint32_t length);
|
||||
|
||||
void SetAddress(volatile SetupPacket*);
|
||||
void SetConfiguration(volatile SetupPacket*);
|
||||
|
||||
size_t GetMaxPacketSize(uint8_t endpoint);
|
||||
uint8_t* GetTransferBuffer(uint8_t endpoint);
|
||||
|
||||
const DeviceDescriptor& device_descriptor_;
|
||||
const ConfigurationDescriptor& configuration_descriptor_;
|
||||
const std::vector<InterfaceDescriptor>& interface_descriptors_;
|
||||
const std::vector<EndPointDescriptor>& endpoint_descriptors_;
|
||||
const std::vector<std::string>& strings_;
|
||||
|
||||
uint8_t address_ = 0;
|
||||
bool should_set_address_ = false;
|
||||
bool ready_ = false;
|
||||
std::vector<uint8_t> in_next_pid_ = {0u};
|
||||
std::vector<uint8_t> out_next_pid_ = {0u};
|
||||
std::vector<uint8_t> setup_buffer_;
|
||||
std::vector<std::span<const uint8_t>> sending_data_;
|
||||
std::vector<std::span<uint8_t>> receiving_data_;
|
||||
};
|
||||
|
||||
#endif // COMMON_USB_DEVICE_H_
|
||||
94
mozc-dial/firmware/common/usb_hid_device.cc
Normal file
94
mozc-dial/firmware/common/usb_hid_device.cc
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#include "usb_hid_device.h"
|
||||
|
||||
UsbHidDevice::UsbHidDevice(
|
||||
const DeviceDescriptor& device_descriptor,
|
||||
const ConfigurationDescriptor& configuration_descriptor,
|
||||
const std::vector<InterfaceDescriptor>& interface_descriptors,
|
||||
const std::vector<EndPointDescriptor>& endpoint_descriptors,
|
||||
const HidDescriptor& hid_descriptor,
|
||||
const std::vector<uint8_t>& report_descriptor,
|
||||
const std::vector<std::string>& strings)
|
||||
: UsbDevice(device_descriptor,
|
||||
configuration_descriptor,
|
||||
interface_descriptors,
|
||||
endpoint_descriptors,
|
||||
strings),
|
||||
configuration_descriptor_(configuration_descriptor),
|
||||
interface_descriptors_(interface_descriptors),
|
||||
endpoint_descriptors_(endpoint_descriptors),
|
||||
hid_descriptor_(hid_descriptor),
|
||||
report_descriptor_(report_descriptor) {}
|
||||
|
||||
void UsbHidDevice::Report(uint8_t endpoint, std::span<const uint8_t> report) {
|
||||
Send(endpoint, report);
|
||||
}
|
||||
|
||||
void UsbHidDevice::FillConfigurations(std::vector<uint8_t>& buffer) {
|
||||
buffer.clear();
|
||||
buffer.reserve(configuration_descriptor_.wTotalLength);
|
||||
std::span<const uint8_t> config_span(
|
||||
reinterpret_cast<const uint8_t*>(&configuration_descriptor_),
|
||||
sizeof(configuration_descriptor_));
|
||||
buffer.insert(buffer.end(), config_span.begin(), config_span.end());
|
||||
for (const auto& interface_descriptor : interface_descriptors_) {
|
||||
std::span<const uint8_t> interface_span(
|
||||
reinterpret_cast<const uint8_t*>(&interface_descriptor),
|
||||
sizeof(interface_descriptor));
|
||||
buffer.insert(buffer.end(), interface_span.begin(), interface_span.end());
|
||||
}
|
||||
std::span<const uint8_t> hid_span(
|
||||
reinterpret_cast<const uint8_t*>(&hid_descriptor_),
|
||||
sizeof(hid_descriptor_));
|
||||
buffer.insert(buffer.end(), hid_span.begin(), hid_span.end());
|
||||
for (const auto& endpoint_descriptor : endpoint_descriptors_) {
|
||||
std::span<const uint8_t> endpoint_span(
|
||||
reinterpret_cast<const uint8_t*>(&endpoint_descriptor),
|
||||
sizeof(endpoint_descriptor));
|
||||
buffer.insert(buffer.end(), endpoint_span.begin(), endpoint_span.end());
|
||||
}
|
||||
}
|
||||
|
||||
void UsbHidDevice::GetDescriptor(volatile SetupPacket* setup) {
|
||||
uint8_t type = setup->wValue >> 8;
|
||||
switch (type) {
|
||||
case UsbHidDevice::kDescriptorTypeReport:
|
||||
Send(0, std::span<const uint8_t>(report_descriptor_));
|
||||
break;
|
||||
default:
|
||||
UsbDevice::GetDescriptor(setup);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UsbHidDevice::HandleSetupRequest(volatile SetupPacket* setup) {
|
||||
uint8_t type =
|
||||
setup->bmRequestType & ~(kRecipientInterface | kRecipientEndPoint);
|
||||
if (type == (kDirOut | kTypeClass)) {
|
||||
switch (setup->bRequest) {
|
||||
case kHidRequestSetReport:
|
||||
receive_buffer_.resize(setup->wLength);
|
||||
Receive(0, receive_buffer_);
|
||||
break;
|
||||
case kHidRequestSetIdle:
|
||||
AcknowledgeOutRequest();
|
||||
break;
|
||||
case kHidRequestSetProtocol:
|
||||
printf("set protocol: %s\n", setup->wValue ? "report" : "boot");
|
||||
AcknowledgeOutRequest();
|
||||
break;
|
||||
default:
|
||||
AcknowledgeOutRequest();
|
||||
printf("unsupported HID class setup out: $%02x\n", setup->bRequest);
|
||||
break;
|
||||
}
|
||||
} else if (type == (kDirIn | kTypeClass)) {
|
||||
AcknowledgeOutRequest();
|
||||
printf("unsupported HID class setup in: $%02x\n", setup->bRequest);
|
||||
} else {
|
||||
UsbDevice::HandleSetupRequest(setup);
|
||||
}
|
||||
}
|
||||
60
mozc-dial/firmware/common/usb_hid_device.h
Normal file
60
mozc-dial/firmware/common/usb_hid_device.h
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#ifndef COMMON_USB_HID_DEVICE_H_
|
||||
#define COMMON_USB_HID_DEVICE_H_
|
||||
|
||||
#include "usb_device.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class UsbHidDevice : public UsbDevice {
|
||||
public:
|
||||
struct HidDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint16_t bcdHID;
|
||||
uint8_t bCountryCode;
|
||||
uint8_t bNumDescriptors;
|
||||
uint8_t bReportDescriptorType;
|
||||
uint16_t wDescriptorLength;
|
||||
} __attribute__((packed));
|
||||
|
||||
static constexpr uint8_t kHidRequestSetReport = 0x09u;
|
||||
static constexpr uint8_t kHidRequestSetIdle = 0x0au;
|
||||
static constexpr uint8_t kHidRequestSetProtocol = 0x0bu;
|
||||
|
||||
static constexpr uint8_t kDescriptorTypeHid = 0x21u;
|
||||
static constexpr uint8_t kDescriptorTypeReport = 0x22u;
|
||||
|
||||
UsbHidDevice(const DeviceDescriptor& device_descriptor,
|
||||
const ConfigurationDescriptor& configuration_descriptor,
|
||||
const std::vector<InterfaceDescriptor>& interface_descriptors,
|
||||
const std::vector<EndPointDescriptor>& endpoint_descriptors,
|
||||
const HidDescriptor& hid_descriptor,
|
||||
const std::vector<uint8_t>& report_descriptor,
|
||||
const std::vector<std::string>& strings);
|
||||
UsbHidDevice(const UsbHidDevice&) = delete;
|
||||
UsbHidDevice& operator=(const UsbHidDevice&) = delete;
|
||||
~UsbHidDevice() override = default;
|
||||
|
||||
void Report(uint8_t endpoint, std::span<const uint8_t> report);
|
||||
|
||||
private:
|
||||
// Implement UsbDevice.
|
||||
void FillConfigurations(std::vector<uint8_t>& buffer) override;
|
||||
void GetDescriptor(volatile SetupPacket* setup_packet) override;
|
||||
void HandleSetupRequest(volatile SetupPacket* setup_packet) override;
|
||||
|
||||
const ConfigurationDescriptor& configuration_descriptor_;
|
||||
const std::vector<InterfaceDescriptor>& interface_descriptors_;
|
||||
const std::vector<EndPointDescriptor>& endpoint_descriptors_;
|
||||
const HidDescriptor& hid_descriptor_;
|
||||
const std::vector<uint8_t>& report_descriptor_;
|
||||
std::vector<uint8_t> receive_buffer_;
|
||||
};
|
||||
|
||||
#endif // COMMON_USB_HID_DEVICE_H_
|
||||
203
mozc-dial/firmware/common/usb_hid_keyboard.cc
Normal file
203
mozc-dial/firmware/common/usb_hid_keyboard.cc
Normal file
@@ -0,0 +1,203 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#include "usb_hid_keyboard.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint8_t kManufacturerStringIndex = 1;
|
||||
constexpr uint8_t kProductStringIndex = 2;
|
||||
constexpr uint8_t kSerialNumberStringIndex = 3;
|
||||
|
||||
constexpr uint8_t kModifierReportIndex = 0;
|
||||
constexpr uint8_t kKeycodeReportIndex = 2;
|
||||
|
||||
UsbDevice::DeviceDescriptor device_descriptor = {
|
||||
.bLength = sizeof(UsbDevice::DeviceDescriptor),
|
||||
.bDescriptorType = UsbDevice::kDescriptorTypeDevice,
|
||||
.bcdUSB = 0x0110,
|
||||
.bDeviceClass = 0,
|
||||
.bDeviceSubClass = 0,
|
||||
.bDeviceProtocol = 0,
|
||||
.bMaxPacketSize0 = 64,
|
||||
.idVendor = 0,
|
||||
.idProduct = 0,
|
||||
.bcdDevice = 0,
|
||||
.iManufacturer = kManufacturerStringIndex,
|
||||
.iProduct = kProductStringIndex,
|
||||
.iSerialNumber = kSerialNumberStringIndex,
|
||||
.bNumConfigurations = 1};
|
||||
|
||||
std::vector<UsbDevice::EndPointDescriptor> endpoint_descriptors = {
|
||||
{.bLength = sizeof(UsbDevice::EndPointDescriptor),
|
||||
.bDescriptorType = UsbDevice::kDescriptorTypeEndPoint,
|
||||
.bEndpointAddress = UsbDevice::kDirIn | 1,
|
||||
.bmAttributes = UsbDevice::kEndPointAttributeInterrupt,
|
||||
.wMaxPacketSize = 64,
|
||||
.bInterval = 10},
|
||||
{.bLength = sizeof(UsbDevice::EndPointDescriptor),
|
||||
.bDescriptorType = UsbDevice::kDescriptorTypeEndPoint,
|
||||
.bEndpointAddress = UsbDevice::kDirOut | 1,
|
||||
.bmAttributes = UsbDevice::kEndPointAttributeInterrupt,
|
||||
.wMaxPacketSize = 64,
|
||||
.bInterval = 10}};
|
||||
|
||||
std::vector<UsbDevice::InterfaceDescriptor> interface_descriptors = {
|
||||
{.bLength = sizeof(UsbDevice::InterfaceDescriptor),
|
||||
.bDescriptorType = UsbDevice::kDescriptorTypeInterface,
|
||||
.bInterfaceNumber = 0,
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = static_cast<uint8_t>(endpoint_descriptors.size()),
|
||||
.bInterfaceClass = 3, // HID
|
||||
.bInterfaceSubClass = 1, // Boot
|
||||
.bInterfaceProtocol = 1, // Keyboard
|
||||
.iInterface = 0}};
|
||||
|
||||
std::vector<uint8_t> report_descriptor = {
|
||||
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
|
||||
0x09, 0x06, /* USAGE (Keyboard) */
|
||||
0xa1, 0x01, /* COLLECTION (Application) */
|
||||
0x05, 0x07, /* USAGE_PAGE (Keyboard) */
|
||||
0x19, 0xe0, /* USAGE_MINIMUM (224) */
|
||||
0x29, 0xe7, /* USAGE_MAXIMUM (231) */
|
||||
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
|
||||
0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
|
||||
0x75, 0x01, /* REPORT_SIZE (1) */
|
||||
0x95, 0x08, /* REPORT_COUNT (8) */
|
||||
0x81, 0x02, /* INPUT (Data,Var,Abs); Modifier byte */
|
||||
0x75, 0x08, /* REPORT_SIZE (8) */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x81, 0x01, /* INPUT (Constant); Reserved byte */
|
||||
0x75, 0x01, /* REPORT_SIZE (1) */
|
||||
0x95, 0x05, /* REPORT_COUNT (5) */
|
||||
0x05, 0x08, /* USAGE_PAGE (LEDs) */
|
||||
0x19, 0x01, /* USAGE_MINIMUM (1) */
|
||||
0x29, 0x05, /* USAGE_MAXIMUM (5) */
|
||||
0x91, 0x02, /* OUTPUT (Data,Var,Abs); LED report */
|
||||
0x75, 0x03, /* REPORT_SIZE (3) */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x91, 0x01, /* OUTPUT (Constant); LED report padding */
|
||||
0x75, 0x08, /* REPORT_SIZE (8) */
|
||||
0x95, 0x06, /* REPORT_COUNT (6) */
|
||||
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
|
||||
0x25, 0x65, /* LOGICAL_MAXIMUM (101) */
|
||||
0x05, 0x07, /* USAGE_PAGE (Keyboard) */
|
||||
0x19, 0x00, /* USAGE_MINIMUM (0) */
|
||||
0x29, 0x65, /* USAGE_MAXIMUM (101) */
|
||||
0x81, 0x00, /* INPUT (Data,Ary,Abs) */
|
||||
0xC0, /* END_COLLECTION */
|
||||
};
|
||||
|
||||
UsbHidDevice::HidDescriptor hid_descriptor = {
|
||||
.bLength = sizeof(UsbHidDevice::HidDescriptor),
|
||||
.bDescriptorType = UsbHidDevice::kDescriptorTypeHid,
|
||||
.bcdHID = 0x0110,
|
||||
.bCountryCode = 0x00,
|
||||
.bNumDescriptors = 1,
|
||||
.bReportDescriptorType = UsbHidDevice::kDescriptorTypeReport,
|
||||
.wDescriptorLength = static_cast<uint16_t>(report_descriptor.size())};
|
||||
|
||||
UsbDevice::ConfigurationDescriptor configuration_descriptor = {
|
||||
.bLength = sizeof(configuration_descriptor),
|
||||
.bDescriptorType = UsbDevice::kDescriptorTypeConfiguration,
|
||||
.wTotalLength = static_cast<uint16_t>(
|
||||
sizeof(UsbDevice::ConfigurationDescriptor) +
|
||||
sizeof(UsbDevice::InterfaceDescriptor) +
|
||||
sizeof(UsbDevice::EndPointDescriptor) * endpoint_descriptors.size() +
|
||||
sizeof(UsbHidDevice::HidDescriptor)),
|
||||
.bNumInterfaces = static_cast<uint8_t>(interface_descriptors.size()),
|
||||
.bConfigurationValue = 1,
|
||||
.iConfiguration = 0,
|
||||
.bmAttributes = 0xc0,
|
||||
.bMaxPower = 250};
|
||||
|
||||
std::vector<std::string> strings = {"", "", ""};
|
||||
|
||||
} // namespace
|
||||
|
||||
UsbHidKeyboard::UsbHidKeyboard(uint16_t vendor_id,
|
||||
uint16_t product_id,
|
||||
uint16_t version,
|
||||
std::string vendor_name,
|
||||
std::string product_name,
|
||||
std::string version_name)
|
||||
: UsbHidDevice(device_descriptor,
|
||||
configuration_descriptor,
|
||||
interface_descriptors,
|
||||
endpoint_descriptors,
|
||||
hid_descriptor,
|
||||
report_descriptor,
|
||||
strings) {
|
||||
device_descriptor.idVendor = vendor_id;
|
||||
device_descriptor.idProduct = product_id;
|
||||
device_descriptor.bcdDevice = version;
|
||||
strings[kManufacturerStringIndex - 1] = vendor_name;
|
||||
strings[kProductStringIndex - 1] = product_name;
|
||||
strings[kSerialNumberStringIndex - 1] = version_name;
|
||||
|
||||
report_.resize(8);
|
||||
}
|
||||
|
||||
void UsbHidKeyboard::SetAutoKeyRelease(bool enabled) {
|
||||
auto_key_release_ = enabled;
|
||||
}
|
||||
|
||||
void UsbHidKeyboard::SetModifiers(uint8_t modifiers) {
|
||||
modifiers_ = modifiers;
|
||||
}
|
||||
|
||||
void UsbHidKeyboard::PressByUsageId(uint8_t usage_id) {
|
||||
keycodes_.insert(usage_id);
|
||||
if (reporting_) {
|
||||
dirty_ = true;
|
||||
} else {
|
||||
Report();
|
||||
}
|
||||
}
|
||||
|
||||
void UsbHidKeyboard::ReleaseByUsageId(uint8_t usage_id) {
|
||||
auto it = keycodes_.find(usage_id);
|
||||
if (it != keycodes_.end()) {
|
||||
keycodes_.erase(it);
|
||||
if (reporting_) {
|
||||
dirty_ = true;
|
||||
} else {
|
||||
Report();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UsbHidKeyboard::OnCompleteToSend(uint8_t endpoint) {
|
||||
reporting_ = false;
|
||||
if (dirty_) {
|
||||
Report();
|
||||
}
|
||||
}
|
||||
|
||||
void UsbHidKeyboard::Report() {
|
||||
report_[0] = modifiers_;
|
||||
report_[1] = 0x00;
|
||||
if (keycodes_.size() > 6) {
|
||||
// Phantom state
|
||||
for (size_t i = 2; i < report_.size(); ++i) {
|
||||
report_[i] = 0x01;
|
||||
}
|
||||
} else {
|
||||
size_t i = 2;
|
||||
for (size_t keycode : keycodes_) {
|
||||
report_[i++] = keycode;
|
||||
}
|
||||
while (i < 8) {
|
||||
report_[i++] = 0x00;
|
||||
}
|
||||
}
|
||||
dirty_ = false;
|
||||
if (auto_key_release_ && !keycodes_.empty()) {
|
||||
keycodes_.clear();
|
||||
modifiers_ = 0;
|
||||
dirty_ = true;
|
||||
}
|
||||
reporting_ = true;
|
||||
UsbHidDevice::Report(1, report_);
|
||||
}
|
||||
49
mozc-dial/firmware/common/usb_hid_keyboard.h
Normal file
49
mozc-dial/firmware/common/usb_hid_keyboard.h
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#ifndef COMMON_USB_HID_KEYBOARD_H_
|
||||
#define COMMON_USB_HID_KEYBOARD_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "usb_hid_device.h"
|
||||
|
||||
class UsbHidKeyboard : public UsbHidDevice {
|
||||
public:
|
||||
UsbHidKeyboard(uint16_t vendor_id,
|
||||
uint16_t product_id,
|
||||
uint16_t version,
|
||||
std::string vendor_name,
|
||||
std::string product_name,
|
||||
std::string version_name);
|
||||
UsbHidKeyboard(const UsbHidKeyboard&) = delete;
|
||||
UsbHidKeyboard& operator=(const UsbHidKeyboard&) = delete;
|
||||
~UsbHidKeyboard() override = default;
|
||||
|
||||
void SetAutoKeyRelease(bool enabled);
|
||||
|
||||
void SetModifiers(uint8_t modifiers);
|
||||
uint8_t GetModifiers() const { return modifiers_; }
|
||||
|
||||
void PressByUsageId(uint8_t usage_id);
|
||||
void ReleaseByUsageId(uint8_t usage_id);
|
||||
|
||||
private:
|
||||
// Implement UsbDevice.
|
||||
void OnCompleteToSend(uint8_t endpoint) override;
|
||||
|
||||
void Report();
|
||||
|
||||
bool auto_key_release_ = false;
|
||||
bool reporting_ = false;
|
||||
bool dirty_ = false;
|
||||
uint8_t modifiers_ = 0;
|
||||
std::set<uint8_t> keycodes_;
|
||||
std::vector<uint8_t> report_;
|
||||
};
|
||||
|
||||
#endif // COMMON_USB_HID_KEYBOARD_H_
|
||||
78
mozc-dial/firmware/main/CMakeLists.txt
Normal file
78
mozc-dial/firmware/main/CMakeLists.txt
Normal file
@@ -0,0 +1,78 @@
|
||||
# Generated Cmake Pico project file
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# Initialise pico_sdk from installed location
|
||||
# (note this can come from environment, CMake cache etc)
|
||||
|
||||
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
|
||||
if(WIN32)
|
||||
set(USERHOME $ENV{USERPROFILE})
|
||||
else()
|
||||
set(USERHOME $ENV{HOME})
|
||||
endif()
|
||||
set(sdkVersion 2.2.0)
|
||||
set(toolchainVersion 14_2_Rel1)
|
||||
set(picotoolVersion 2.2.0)
|
||||
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
|
||||
if (EXISTS ${picoVscode})
|
||||
include(${picoVscode})
|
||||
endif()
|
||||
# ====================================================================================
|
||||
set(PICO_BOARD none CACHE STRING "Board type")
|
||||
|
||||
# Pull in Raspberry Pi Pico SDK (must be before project)
|
||||
include(pico_sdk_import.cmake)
|
||||
|
||||
project(main C CXX ASM)
|
||||
|
||||
# Initialise the Raspberry Pi Pico SDK
|
||||
pico_sdk_init()
|
||||
|
||||
# Add executable. Default name is the project name, version 0.1
|
||||
|
||||
add_executable(main
|
||||
../common/dial_controller.cc
|
||||
../common/dial_controller.h
|
||||
../common/photo_sensor.cc
|
||||
../common/photo_sensor.h
|
||||
../common/usage_tables.cc
|
||||
../common/usage_tables.h
|
||||
../common/usb_device.cc
|
||||
../common/usb_device.h
|
||||
../common/usb_hid_device.cc
|
||||
../common/usb_hid_device.h
|
||||
../common/usb_hid_keyboard.cc
|
||||
../common/usb_hid_keyboard.h
|
||||
i2c_controller.cc
|
||||
i2c_controller.h
|
||||
main.cc
|
||||
)
|
||||
|
||||
pico_set_program_name(main "main")
|
||||
pico_set_program_version(main "0.1")
|
||||
|
||||
# Modify the below lines to enable/disable output over UART/USB/SEMIHOSTING
|
||||
pico_enable_stdio_uart(main 1)
|
||||
pico_enable_stdio_usb(main 0)
|
||||
pico_enable_stdio_semihosting(main 0)
|
||||
|
||||
# Add the standard library to the build
|
||||
target_link_libraries(main
|
||||
pico_stdlib)
|
||||
|
||||
# Add the standard include files to the build
|
||||
target_include_directories(main PRIVATE
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
|
||||
# Add any user requested libraries
|
||||
target_link_libraries(main
|
||||
hardware_i2c
|
||||
)
|
||||
|
||||
pico_add_extra_outputs(main)
|
||||
47
mozc-dial/firmware/main/i2c_controller.cc
Normal file
47
mozc-dial/firmware/main/i2c_controller.cc
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#include "i2c_controller.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "hardware/gpio.h"
|
||||
|
||||
static constexpr int kI2CSpeedInHz = 400 * 1000;
|
||||
|
||||
I2CController::I2CController(i2c_inst_t* i2c, int8_t sda, int8_t scl)
|
||||
: i2c_(i2c) {
|
||||
hard_assert(i2c == i2c0 || i2c == i2c1);
|
||||
gpio_init(sda);
|
||||
gpio_init(scl);
|
||||
gpio_set_function(sda, GPIO_FUNC_I2C);
|
||||
gpio_set_function(scl, GPIO_FUNC_I2C);
|
||||
gpio_pull_up(sda);
|
||||
gpio_pull_up(scl);
|
||||
i2c_init(i2c_, kI2CSpeedInHz);
|
||||
}
|
||||
|
||||
bool I2CController::Read(uint8_t device_addr,
|
||||
uint8_t mem_addr,
|
||||
std::span<uint8_t> data) {
|
||||
uint8_t addr[1] = {mem_addr};
|
||||
int result = i2c_write_blocking(i2c_, device_addr, addr, 1, true);
|
||||
if (result != 1) {
|
||||
return false;
|
||||
}
|
||||
result = i2c_read_blocking(i2c_, device_addr, data.data(), data.size(), false);
|
||||
return result == data.size();
|
||||
}
|
||||
|
||||
bool I2CController::Write(uint8_t device_addr,
|
||||
uint8_t mem_addr,
|
||||
std::span<uint8_t> data) {
|
||||
std::vector<uint8_t> buf;
|
||||
buf.reserve(data.size() + 1);
|
||||
buf.push_back(mem_addr);
|
||||
buf.insert(buf.end(), data.begin(), data.end());
|
||||
int rc = i2c_write_blocking(i2c_, device_addr, buf.data(), buf.size(), false);
|
||||
return rc == buf.size();
|
||||
}
|
||||
27
mozc-dial/firmware/main/i2c_controller.h
Normal file
27
mozc-dial/firmware/main/i2c_controller.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#ifndef I2C_CONTROLLER_H_
|
||||
#define I2C_CONTROLLER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
#include "hardware/i2c.h"
|
||||
|
||||
class I2CController final {
|
||||
public:
|
||||
I2CController(i2c_inst_t* i2c, int8_t sda, int8_t scl);
|
||||
I2CController(const I2CController&) = delete;
|
||||
I2CController& operator=(const I2CController&) = delete;
|
||||
~I2CController() = default;
|
||||
|
||||
bool Write(uint8_t device_addr, uint8_t mem_addr, std::span<uint8_t> data);
|
||||
bool Read(uint8_t device_addr, uint8_t mem_addr, std::span<uint8_t> data);
|
||||
|
||||
private:
|
||||
i2c_inst_t* i2c_ = nullptr;
|
||||
};
|
||||
|
||||
#endif // I2C_CONTROLLER_H_
|
||||
170
mozc-dial/firmware/main/main.cc
Normal file
170
mozc-dial/firmware/main/main.cc
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
#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<PhotoSensor, 8> 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
|
||||
};
|
||||
|
||||
std::array<DialController, 9> dials = {
|
||||
DialController(), // A
|
||||
DialController(), // B
|
||||
DialController(), // C
|
||||
DialController(), // D
|
||||
DialController(), // E
|
||||
DialController(), // F
|
||||
DialController(), // G
|
||||
DialController(), // I
|
||||
|
||||
DialController(), // H
|
||||
};
|
||||
|
||||
std::array<const std::vector<uint8_t>*, 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<const std::vector<uint8_t>*, 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<const std::vector<uint8_t>*, 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");
|
||||
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<uint8_t> i2c_buffer(2);
|
||||
bool fn = false;
|
||||
|
||||
while (true) {
|
||||
uint16_t motor_start_bitmap = 0;
|
||||
for (size_t i = 0; i < sensors.size(); ++i) {
|
||||
dials[i].Update(sensors[i].Read());
|
||||
if (!dials[i].IsBasePosition()) {
|
||||
motor_start_bitmap |= (1 << i);
|
||||
}
|
||||
}
|
||||
// Read the remote sensor H value via I2C.
|
||||
bool rc = i2c.Read(68, 0, std::span<uint8_t>({i2c_buffer.data(), 1}));
|
||||
if (rc) {
|
||||
dials[8].Update(i2c_buffer[0]);
|
||||
if (!dials[8].IsBasePosition()) {
|
||||
motor_start_bitmap |= (1 << 8);
|
||||
}
|
||||
}
|
||||
|
||||
// Drive all motors via I2C.
|
||||
i2c_buffer[0] = motor_start_bitmap & 0xff;
|
||||
i2c_buffer[1] = motor_start_bitmap >> 8;
|
||||
rc = i2c.Write(68, 0, 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<uint8_t> 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<const std::vector<uint8_t>*, 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
121
mozc-dial/firmware/main/pico_sdk_import.cmake
Normal file
121
mozc-dial/firmware/main/pico_sdk_import.cmake
Normal file
@@ -0,0 +1,121 @@
|
||||
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||
|
||||
# This can be dropped into an external project to help locate this SDK
|
||||
# It should be include()ed prior to project()
|
||||
|
||||
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
||||
# following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
||||
# disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
|
||||
endif ()
|
||||
|
||||
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
|
||||
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
|
||||
endif()
|
||||
|
||||
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
|
||||
|
||||
if (NOT PICO_SDK_PATH)
|
||||
if (PICO_SDK_FETCH_FROM_GIT)
|
||||
include(FetchContent)
|
||||
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||
endif ()
|
||||
FetchContent_Declare(
|
||||
pico_sdk
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
)
|
||||
|
||||
if (NOT pico_sdk)
|
||||
message("Downloading Raspberry Pi Pico SDK")
|
||||
# GIT_SUBMODULES_RECURSE was added in 3.17
|
||||
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
|
||||
FetchContent_Populate(
|
||||
pico_sdk
|
||||
QUIET
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
GIT_SUBMODULES_RECURSE FALSE
|
||||
|
||||
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
|
||||
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
|
||||
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
|
||||
)
|
||||
else ()
|
||||
FetchContent_Populate(
|
||||
pico_sdk
|
||||
QUIET
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
|
||||
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
|
||||
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
|
||||
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
|
||||
)
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||
endif ()
|
||||
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||
else ()
|
||||
message(FATAL_ERROR
|
||||
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||
)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||
|
||||
include(${PICO_SDK_INIT_CMAKE_FILE})
|
||||
73
mozc-dial/firmware/one_dial/CMakeLists.txt
Normal file
73
mozc-dial/firmware/one_dial/CMakeLists.txt
Normal file
@@ -0,0 +1,73 @@
|
||||
# Generated Cmake Pico project file
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# Initialise pico_sdk from installed location
|
||||
# (note this can come from environment, CMake cache etc)
|
||||
|
||||
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
|
||||
if(WIN32)
|
||||
set(USERHOME $ENV{USERPROFILE})
|
||||
else()
|
||||
set(USERHOME $ENV{HOME})
|
||||
endif()
|
||||
set(sdkVersion 2.2.0)
|
||||
set(toolchainVersion 14_2_Rel1)
|
||||
set(picotoolVersion 2.2.0)
|
||||
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
|
||||
if (EXISTS ${picoVscode})
|
||||
include(${picoVscode})
|
||||
endif()
|
||||
# ====================================================================================
|
||||
set(PICO_BOARD pico CACHE STRING "Board type")
|
||||
|
||||
# Pull in Raspberry Pi Pico SDK (must be before project)
|
||||
include(pico_sdk_import.cmake)
|
||||
|
||||
project(one_dial C CXX ASM)
|
||||
|
||||
# Initialise the Raspberry Pi Pico SDK
|
||||
pico_sdk_init()
|
||||
|
||||
# Add executable. Default name is the project name, version 0.1
|
||||
|
||||
add_executable(one_dial
|
||||
../common/dial_controller.cc
|
||||
../common/dial_controller.h
|
||||
../common/motor_controller.cc
|
||||
../common/motor_controller.h
|
||||
../common/photo_sensor.cc
|
||||
../common/photo_sensor.h
|
||||
../common/usage_tables.cc
|
||||
../common/usage_tables.h
|
||||
../common/usb_device.cc
|
||||
../common/usb_device.h
|
||||
../common/usb_hid_device.cc
|
||||
../common/usb_hid_device.h
|
||||
../common/usb_hid_keyboard.cc
|
||||
../common/usb_hid_keyboard.h
|
||||
one_dial.cc
|
||||
)
|
||||
|
||||
pico_set_program_name(one_dial "one_dial")
|
||||
pico_set_program_version(one_dial "0.1")
|
||||
|
||||
# Modify the below lines to enable/disable output over UART/USB/SEMIHOSTING
|
||||
pico_enable_stdio_uart(one_dial 1)
|
||||
pico_enable_stdio_usb(one_dial 0)
|
||||
pico_enable_stdio_semihosting(one_dial 0)
|
||||
|
||||
# Add the standard library to the build
|
||||
target_link_libraries(one_dial
|
||||
pico_stdlib)
|
||||
|
||||
# Add the standard include files to the build
|
||||
target_include_directories(one_dial PRIVATE
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
|
||||
pico_add_extra_outputs(one_dial)
|
||||
56
mozc-dial/firmware/one_dial/one_dial.cc
Normal file
56
mozc-dial/firmware/one_dial/one_dial.cc
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
|
||||
#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"
|
||||
|
||||
int main() {
|
||||
// GPIO0 and 1 are used for stdout, and stdin by default.
|
||||
stdio_init_all();
|
||||
|
||||
// GPIO17, 18, 19, and 20 are used for motor phase control.
|
||||
MotorController motor_controller(MotorController::Mode::k1Motor);
|
||||
|
||||
// GPIO2, 3, 4, 5, 6, and 7 are used for 6bit photo sensing.
|
||||
PhotoSensor photo_sensor(2, 3, 4, 5, 6, 7);
|
||||
|
||||
DialController dial_controller;
|
||||
|
||||
UsbHidKeyboard usb_hid_keyboard(
|
||||
/*vendor_id=*/0x6666, /*product_id=*/0x2025,
|
||||
/*version=*/0x0101, /*vendor_name=*/"Gboard DIY prototype",
|
||||
/*product_name=*/"Gboard Dial version", /*version_name=*/"1 Dial");
|
||||
usb_hid_keyboard.SetAutoKeyRelease(true);
|
||||
|
||||
while (true) {
|
||||
dial_controller.Update(photo_sensor.Read());
|
||||
if (dial_controller.IsBasePosition()) {
|
||||
motor_controller.Stop(8);
|
||||
} else {
|
||||
motor_controller.Start(8);
|
||||
}
|
||||
// `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<uint8_t> decided_position =
|
||||
dial_controller.PopDecidedPosition();
|
||||
if (decided_position && *decided_position <= usage_tables::a.size()) {
|
||||
// Adjust the index as it is 1-based.
|
||||
uint8_t usage = usage_tables::a[*decided_position - 1];
|
||||
if (usage) {
|
||||
usb_hid_keyboard.PressByUsageId(usage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
121
mozc-dial/firmware/one_dial/pico_sdk_import.cmake
Normal file
121
mozc-dial/firmware/one_dial/pico_sdk_import.cmake
Normal file
@@ -0,0 +1,121 @@
|
||||
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||
|
||||
# This can be dropped into an external project to help locate this SDK
|
||||
# It should be include()ed prior to project()
|
||||
|
||||
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
||||
# following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
||||
# disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
|
||||
endif ()
|
||||
|
||||
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
|
||||
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
|
||||
endif()
|
||||
|
||||
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
|
||||
|
||||
if (NOT PICO_SDK_PATH)
|
||||
if (PICO_SDK_FETCH_FROM_GIT)
|
||||
include(FetchContent)
|
||||
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||
endif ()
|
||||
FetchContent_Declare(
|
||||
pico_sdk
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
)
|
||||
|
||||
if (NOT pico_sdk)
|
||||
message("Downloading Raspberry Pi Pico SDK")
|
||||
# GIT_SUBMODULES_RECURSE was added in 3.17
|
||||
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
|
||||
FetchContent_Populate(
|
||||
pico_sdk
|
||||
QUIET
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
GIT_SUBMODULES_RECURSE FALSE
|
||||
|
||||
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
|
||||
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
|
||||
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
|
||||
)
|
||||
else ()
|
||||
FetchContent_Populate(
|
||||
pico_sdk
|
||||
QUIET
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
|
||||
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
|
||||
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
|
||||
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
|
||||
)
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||
endif ()
|
||||
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||
else ()
|
||||
message(FATAL_ERROR
|
||||
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||
)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||
|
||||
include(${PICO_SDK_INIT_CMAKE_FILE})
|
||||
BIN
mozc-dial/firmware/prebuilt/main.uf2
Normal file
BIN
mozc-dial/firmware/prebuilt/main.uf2
Normal file
Binary file not shown.
BIN
mozc-dial/firmware/prebuilt/one_dial.uf2
Normal file
BIN
mozc-dial/firmware/prebuilt/one_dial.uf2
Normal file
Binary file not shown.
BIN
mozc-dial/firmware/prebuilt/sub.uf2
Normal file
BIN
mozc-dial/firmware/prebuilt/sub.uf2
Normal file
Binary file not shown.
75
mozc-dial/firmware/sub/CMakeLists.txt
Normal file
75
mozc-dial/firmware/sub/CMakeLists.txt
Normal file
@@ -0,0 +1,75 @@
|
||||
# Generated Cmake Pico project file
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# Initialise pico_sdk from installed location
|
||||
# (note this can come from environment, CMake cache etc)
|
||||
|
||||
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
|
||||
if(WIN32)
|
||||
set(USERHOME $ENV{USERPROFILE})
|
||||
else()
|
||||
set(USERHOME $ENV{HOME})
|
||||
endif()
|
||||
set(sdkVersion 2.2.0)
|
||||
set(toolchainVersion 14_2_Rel1)
|
||||
set(picotoolVersion 2.2.0)
|
||||
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
|
||||
if (EXISTS ${picoVscode})
|
||||
include(${picoVscode})
|
||||
endif()
|
||||
# ====================================================================================
|
||||
set(PICO_BOARD none CACHE STRING "Board type")
|
||||
#set(PICO_BOARD pico CACHE STRING "Board type")
|
||||
|
||||
# Pull in Raspberry Pi Pico SDK (must be before project)
|
||||
include(pico_sdk_import.cmake)
|
||||
|
||||
project(sub C CXX ASM)
|
||||
|
||||
# Initialise the Raspberry Pi Pico SDK
|
||||
pico_sdk_init()
|
||||
|
||||
# Add executable. Default name is the project name, version 0.1
|
||||
|
||||
add_executable(sub
|
||||
../common/motor_controller.cc
|
||||
../common/motor_controller.h
|
||||
../common/photo_sensor.cc
|
||||
../common/photo_sensor.h
|
||||
i2c_device.cc
|
||||
i2c_device.h
|
||||
sub.cc
|
||||
)
|
||||
|
||||
pico_set_program_name(sub "sub")
|
||||
pico_set_program_version(sub "0.1")
|
||||
|
||||
# Modify the below lines to enable/disable output over UART/USB/SEMIHOSTING
|
||||
pico_enable_stdio_uart(sub 1)
|
||||
pico_enable_stdio_usb(sub 0)
|
||||
pico_enable_stdio_semihosting(sub 0)
|
||||
|
||||
# Add the standard library to the build
|
||||
target_link_libraries(sub
|
||||
pico_i2c_slave
|
||||
pico_stdlib
|
||||
)
|
||||
|
||||
# Add the standard include files to the build
|
||||
target_include_directories(sub PRIVATE
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
|
||||
# Add any user requested libraries
|
||||
target_link_libraries(sub
|
||||
hardware_i2c
|
||||
hardware_timer
|
||||
hardware_clocks
|
||||
)
|
||||
|
||||
pico_add_extra_outputs(sub)
|
||||
73
mozc-dial/firmware/sub/i2c_device.cc
Normal file
73
mozc-dial/firmware/sub/i2c_device.cc
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#include "i2c_device.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/i2c.h"
|
||||
#include "pico/i2c_slave.h"
|
||||
|
||||
static constexpr int kI2CSpeedInHz = 400 * 1000;
|
||||
|
||||
namespace {
|
||||
I2CDevice* sI2CDevice = nullptr;
|
||||
}; // namespace
|
||||
|
||||
// static
|
||||
void I2CDevice::HandleEvent(i2c_inst_t* i2c, i2c_slave_event_t event) {
|
||||
sI2CDevice->HandleEventInternal(i2c, event);
|
||||
}
|
||||
|
||||
I2CDevice::I2CDevice(i2c_inst_t* i2c,
|
||||
uint8_t sda,
|
||||
uint8_t scl,
|
||||
uint8_t address) {
|
||||
hard_assert(sI2CDevice == nullptr);
|
||||
sI2CDevice = this;
|
||||
|
||||
gpio_init(sda);
|
||||
gpio_init(scl);
|
||||
gpio_set_function(sda, GPIO_FUNC_I2C);
|
||||
gpio_set_function(scl, GPIO_FUNC_I2C);
|
||||
gpio_pull_up(sda);
|
||||
gpio_pull_up(scl);
|
||||
|
||||
i2c_init(i2c, kI2CSpeedInHz);
|
||||
i2c_slave_init(i2c, address, &I2CDevice::HandleEvent);
|
||||
}
|
||||
|
||||
void I2CDevice::SetReadHandler(ReadHandler handler) {
|
||||
reader_ = handler;
|
||||
}
|
||||
|
||||
void I2CDevice::SetWriteHandler(WriteHandler handler) {
|
||||
writer_ = handler;
|
||||
}
|
||||
|
||||
void I2CDevice::HandleEventInternal(i2c_inst_t* i2c, i2c_slave_event_t event) {
|
||||
switch (event) {
|
||||
case I2C_SLAVE_RECEIVE:
|
||||
if (!address_ready_) {
|
||||
address_ = i2c_read_byte_raw(i2c);
|
||||
address_ready_ = true;
|
||||
} else {
|
||||
if (writer_) {
|
||||
writer_(address_, i2c_read_byte_raw(i2c));
|
||||
} else {
|
||||
printf("ignore write at $%02x: $%02x\n", address_, i2c_read_byte_raw(i2c));
|
||||
}
|
||||
++address_;
|
||||
}
|
||||
break;
|
||||
case I2C_SLAVE_REQUEST:
|
||||
i2c_write_byte_raw(i2c, reader_ ? reader_(address_) : 0);
|
||||
address_++;
|
||||
break;
|
||||
case I2C_SLAVE_FINISH:
|
||||
address_ready_ = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
37
mozc-dial/firmware/sub/i2c_device.h
Normal file
37
mozc-dial/firmware/sub/i2c_device.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#ifndef I2C_DEVICE_H_
|
||||
#define I2C_DEVICE_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
#include "hardware/i2c.h"
|
||||
#include "pico/i2c_slave.h"
|
||||
|
||||
class I2CDevice final {
|
||||
public:
|
||||
using ReadHandler = std::function<uint8_t(uint8_t /*address*/)>;
|
||||
using WriteHandler = std::function<void(uint8_t /*address*/, uint8_t /*value*/)>;
|
||||
|
||||
I2CDevice(i2c_inst_t* i2c, uint8_t sda, uint8_t scl, uint8_t address);
|
||||
I2CDevice(const I2CDevice&) = delete;
|
||||
I2CDevice& operator=(const I2CDevice&) = delete;
|
||||
~I2CDevice() = default;
|
||||
|
||||
void SetReadHandler(ReadHandler handler);
|
||||
void SetWriteHandler(WriteHandler handler);
|
||||
|
||||
private:
|
||||
static void HandleEvent(i2c_inst_t* i2c, i2c_slave_event_t event);
|
||||
void HandleEventInternal(i2c_inst_t* i2c, i2c_slave_event_t event);
|
||||
|
||||
ReadHandler reader_ = nullptr;
|
||||
WriteHandler writer_ = nullptr;
|
||||
bool address_ready_ = false;
|
||||
uint8_t address_ = 0;
|
||||
};
|
||||
|
||||
#endif // I2C_DEVICE_H_
|
||||
121
mozc-dial/firmware/sub/pico_sdk_import.cmake
Normal file
121
mozc-dial/firmware/sub/pico_sdk_import.cmake
Normal file
@@ -0,0 +1,121 @@
|
||||
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||
|
||||
# This can be dropped into an external project to help locate this SDK
|
||||
# It should be include()ed prior to project()
|
||||
|
||||
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
||||
# following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
||||
# disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
|
||||
endif ()
|
||||
|
||||
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
|
||||
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
|
||||
endif()
|
||||
|
||||
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
|
||||
|
||||
if (NOT PICO_SDK_PATH)
|
||||
if (PICO_SDK_FETCH_FROM_GIT)
|
||||
include(FetchContent)
|
||||
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||
endif ()
|
||||
FetchContent_Declare(
|
||||
pico_sdk
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
)
|
||||
|
||||
if (NOT pico_sdk)
|
||||
message("Downloading Raspberry Pi Pico SDK")
|
||||
# GIT_SUBMODULES_RECURSE was added in 3.17
|
||||
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
|
||||
FetchContent_Populate(
|
||||
pico_sdk
|
||||
QUIET
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
GIT_SUBMODULES_RECURSE FALSE
|
||||
|
||||
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
|
||||
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
|
||||
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
|
||||
)
|
||||
else ()
|
||||
FetchContent_Populate(
|
||||
pico_sdk
|
||||
QUIET
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
|
||||
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
|
||||
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
|
||||
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
|
||||
)
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||
endif ()
|
||||
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||
else ()
|
||||
message(FATAL_ERROR
|
||||
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||
)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||
|
||||
include(${PICO_SDK_INIT_CMAKE_FILE})
|
||||
70
mozc-dial/firmware/sub/sub.cc
Normal file
70
mozc-dial/firmware/sub/sub.cc
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2025 Google Inc.
|
||||
// Use of this source code is governed by an Apache License that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "hardware/uart.h"
|
||||
#include "pico/stdio.h"
|
||||
#if LIB_PICO_STDIO_UART
|
||||
#include "pico/stdio_uart.h"
|
||||
#endif
|
||||
|
||||
#include "../common/motor_controller.h"
|
||||
#include "../common/photo_sensor.h"
|
||||
#include "i2c_device.h"
|
||||
|
||||
namespace {
|
||||
|
||||
MotorController motor_controller;
|
||||
PhotoSensor sensor_h(26, 27);
|
||||
|
||||
uint8_t i2c_reader(uint8_t address) {
|
||||
switch (address) {
|
||||
case 0: // Read Sensor H
|
||||
return sensor_h.Read();
|
||||
default:
|
||||
return 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
void i2c_writer(uint8_t address, uint8_t value) {
|
||||
switch (address) {
|
||||
case 0: // Set Motor 1-8 State
|
||||
for (uint8_t motor = 0; motor < 8; ++motor) {
|
||||
if (value & (1 << motor)) {
|
||||
motor_controller.Start(motor);
|
||||
} else {
|
||||
motor_controller.Stop(motor);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1: // Set Motor 9 State
|
||||
if (value & 1) {
|
||||
motor_controller.Start(8);
|
||||
} else {
|
||||
motor_controller.Stop(8);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
#if LIB_PICO_STDIO_UART
|
||||
// Customized initialization to use only GPIO 0 for TX.
|
||||
stdio_uart_init_full(uart0, /*baud_rate=*/115200, /*tx_pin=*/0,
|
||||
/*rx_pin=*/-1);
|
||||
#else
|
||||
stdio_init_all();
|
||||
#endif
|
||||
|
||||
I2CDevice i2c(/*i2c=*/i2c0, /*sda=*/28, /*scl=*/29, /*address=*/68);
|
||||
i2c.SetReadHandler(i2c_reader);
|
||||
i2c.SetWriteHandler(i2c_writer);
|
||||
|
||||
while (true) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user