編集履歴一覧に戻る
verylowfreqのアイコン画像

verylowfreq が 2021年07月25日13時12分25秒 に編集

初版

タイトルの変更

+

ATmega328PでQMKなキーボード(マクロパッド)を作る

タグの変更

+

自作キーボード

+

AVR

+

ATmega328P

+

QMK

メイン画像の変更

メイン画像が設定されました

本文の変更

+

## 概要 USB機能を内蔵していないATmega328PでQMKファームウェアを用いて、Webブラウジング向けの補助キーボードを製作しました。 ## 本体外形 横x奥x高さ: 150x75x30 mm キースイッチは10個、ダイヤルが1つです。ダイヤル側のケースの内側にメイン基板を埋め込んでいます。キースイッチは天板のみで支えています(キースイッチ側に基板はなく、空中配線)。 ## キーアサイン ダイヤルは時計回りでマウスホイールダウン、反時計回りでマウスホイールアップです。使用しているロータリエンコーダは押し込み操作もできますが、機能の割り当てはなし(今のところ配線すらしていない)。 ネットサーフィン時に、片手にマウスを持ち、もう片側の手をこのキーボードに添えた状態で、だらだらと快適にブラウジングできることを目指しました。 **レイヤー1** ``` . [Ctrl+Shift+Tab] [Ctrl+Tab] [ESC / Fn] [Page Up] [Arrow Up] [Page Down] [Ctrl] [Arrow Left] [Arrow Down] [Arrow Right] ``` **レイヤー2** ``` . [(None)] [Audio Mute] [ESC / Fn] [Home] [_____] [End] [Shift] [Audio Volume Down] [_____] [Audio Volume Up] ``` ## ATmega328PでUSB通信 自作キーボードでAVRを使う場合、USB機能を内蔵したATmega32U4(を搭載したArduino Pro Micro系)を使うことが多いようですが、ATmega328PはUSB機能を内蔵していません。 しかし、"V-USB"というファームウェアがあり、これはソフトウェアでピンを制御してUSB通信を行うことができます。通信はUSB 1.1 Low Speed (最大1.5Mbps)。ATtiny85やATtiny2313にも対応しています。 QMKはV-USBにも対応しており、MCUにATmega328(P)を指定すると自動的にV-USBスタックを使用するようになっています。 V-USB (Objective Development Software GmbH) : https://www.obdev.at/products/vusb/index.html また、ファームウェア書き込み用のブートローダーに、V-USBによりUSB HIDデバイスとして振る舞うUSBaspLoaderというものがあり、これも書き込みます。ICSPライターとしてよく使われるUSBasp互換の振舞いをするので、標準のavrdudeで書き込み可能です。 USBaspLoader : https://github.com/baerwolf/USBaspLoader ## 部品 **購入部品** (参考合計額: 1803円) | 部品名 | 数量 | 参考単価 | メモ | | ---- | ---- | ---- | ---- | | AVR ATmega328P-PU | 1 | 240 | | | 水晶振動子 16MHz | 1 | 30 | 16MHzならソフトウェア側の改変が不要なので無難 | | コンデンサ 22pF | 2 | 10 | 水晶振動子用 | | ツェナーダイオード 3.6V | 2 | 10 | 3.3Vではない。詳細は後述 | | 抵抗 1.5k | 1 | 1 | USB D-のプルアップ用 | | 抵抗 75R | 2 | 1 | USB D+,D-に挟む。本当は68Rらしいけど、手元にあった75Rを使用 | | コンデンサ 0.1uF | 適量 | 10 | おまじない用 | | ダイオード 1N5819| 10 | 10 | キースイッチ配線用。これはたまたま手元にあった品種で、小信号向けならだいたい何でもよい | | | ロータリエンコーダ | 1 | 80 | 手元にあった詳細不明のモジュールを使用。信号線はモジュール内でプルアップ済み | | USBケーブル(Aコネクタ) | 1 | 100 | ケーブルの片方をぶったぎって使用 | | ピンヘッダ | 適量 | __ | | | 配線材 | 適量 | __ | | | キーキャップ | 10 | 50 | 手元になかったので、つなぎとしてAmazonで激安品をテキトーに買いました(フルキーボード分で1000円) | | キースイッチ | 10 | 70 | フロントパネルのみで固定できるものなら何でも | **3Dプリンタで印刷** ![3D CAD 1](https://camo.elchika.com/65c4497b2299948f5893854d0d4daff0738bb77b/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32376633303665652d656131352d343334662d626333342d6162343139336638333839392f36323037623437352d343232372d343334612d613739662d383062343235333265393737/) ![3D CAD 2](https://camo.elchika.com/10cc24b5ca4e94a7fab32c544ec17abd22569cbe/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32376633303665652d656131352d343334662d626333342d6162343139336638333839392f35336530653962642d313966622d343164302d623039612d643332633466336666376532/) - キースイッチ側ボトムケース - キースイッチ側フロントパネル(キースイッチ保持も兼ねる) - ダイヤル側ボトムケース - ダイヤル側フロントパネル - ダイヤル - ケース連結パーツ(簡易) 手元の3Dプリンターの印刷ボリュームが小さいので分割していますが、一体にしたほうが良いと思います。 ## 回路図 ![回路図](https://camo.elchika.com/5b9ad74d4b7605888eba709c51ebdf8d00d8c363/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32376633303665652d656131352d343334662d626333342d6162343139336638333839392f66613035623663652d613935322d343534382d396564392d356336353730326264616664/) 電源はUSBからの5Vをそのまま使用します。USBの信号線(D+,D-)は3.3Vレベルなので、ツェナーダイオードで電圧を落とします。以下のサイトの記述より、3.3Vではなく3.6V品を使用します。 レギュレータなどを挟んで電源を3.3Vに降圧し、AVRも3.3V動作にする(ピン出力も自動で3.3Vになる)というのもありますが、AVRの動作周波数的に厳しいので、ツェナーダイオードが無難なようです。 V-SUB Hardware Considerations : http://vusb.wikidot.com/hardware USB 1.1 Low Speedのデバイスであることをホスト側に示すために、USB D-は1.5kでプルアップします。 USB D-はAVRのPD3、D+はAVRのPD4へつなぎます。これはQMKのデフォルトのピンアサインですが、変更はできるようです。USBaspLoaderのデフォルトのアサインとは異なるので、そちらでは設定の変更が必要です。 USBaspLoaderの書き込み待機トリガーとしてPD5を割り当てています(変更可能)。タクトスイッチだとチャタリングにより誤動作しやすいので、ジャンパーが良いです。AVRの起動時(リセット時)にジャンパがセットされていれば、USBaspLoaderが書き込みを待機するようになり、ジャンパを引き抜くと、ユーザープログラムの実行がはじまります。また、今回はQMK側でコードを追加し、QMKの実行中にジャンパをセットして任意のキーを押下してもリセットがかかるようにしています。 ## ハードウェア各部写真 ![内部全体](https://camo.elchika.com/2efabaa7f42f7824d52fcd4041ef5423d66b4018/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32376633303665652d656131352d343334662d626333342d6162343139336638333839392f31316363303563662d613833322d343334612d383862632d363437623331343631353631/) ![メイン基板](https://camo.elchika.com/61da1cc1ee113552c11a9f438789238920ecd330/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32376633303665652d656131352d343334662d626333342d6162343139336638333839392f63363435353238662d306264612d343366372d393865662d616132396436333262346438/) 配線が汚いです。ちょうど手元に切れ端が余っていたので使い慣れていないスルーホール基板を使ってしまったのと、部品レイアウトを下書きせずにはんだ付けをはじめてしまい、部品のつけなおしが発生したのが悪い。 ![キーマトリクス](https://camo.elchika.com/ec2df15b3015279246082277f902098dd5a09293/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32376633303665652d656131352d343334662d626333342d6162343139336638333839392f35316638393266302d656264392d343962332d393139312d616433303264303637626362/) キーマトリクスは1回間違えて、全て配線しなおしてます(ダイオードを挟む位置を間違えた)。回路図を見ずに配線するのが悪い。 ケースの高さが薄すぎて、内部がけっこう狭いです。全体的に厚くするか、フロントパネルを傾斜させて、奥側だけでも厚みを持たせたほうが良かった。 スイッチの保持はフロントパネルに嵌め込んでいるのみなのですが、小さめのサイズであることと、文字入力みたいにバシバシ打ち込むわけではないので、使用上はとくに不便は感じていないです。固定が甘いので、キーキャップを外すのは大変ですが(キーキャップより先にスイッチが外れてしまうため)。 ## 雑な製作手順 1. パーツを集めます。 1. ケースを設計して印刷します。 1. ブレッドボードで回路を仮組みします。たまにUSB通信が不安定だったりします。 1. 基板に回路を組みます。 2. USBaspLoaderをカスタマイズします。 3. ICSPライターでUSBaspLoaderを書き込みます。 4. QMKファームウェアをカスタマイズします。 5. USBaspLoaderでQMKファームウェアを書き込みます。 6. できあがり。 ## USBaspLoaderの変更箇所 MCUの指定と、IOピンの変更をします。 **Makefile.inc** チップの変更(既存の行を書き換え) ``` DEVICE=atmega328p ``` IOポートの設定(以下の行を追記) ``` DEFINES += -DUSB_CFG_INTPORT_BIT=2 -DUSB_CFG_DPLUS_BIT=2 -DUSB_CFG_DMINUS_BIT=3 -DJUMPER_PORT=D -DJUMPER_BIT=4 ``` ## QMKのカスタマイズ 新たなキーボードを定義します。キーマトリクスやロータリエンコーダの配線の指定、USB通信ピンの指定、キーアサインをします。また、USBaspLoader向けにコードを追加します。 また、ロータリエンコーダのステップ数の調整をしています。 **rules.mk** (抜粋) ``` MCU = atmega328p ARCH = AVR BOOTLOADER = USBasp MOUSEKEY_ENABLE = yes EXTRAKEY_ENABLE = yes BOOTLOADER_SIZE = 2048 ENCODER_ENABLE = yes ``` **config.h** (抜粋) ``` #define MATRIX_ROWS 3 #define MATRIX_COLS 4 #define DIODE_DIRECTION COL2ROW #define MATRIX_ROW_PINS { C3, C4, C5 } #define MATRIX_COL_PINS { B2, B3, B4, B5 } #define ENCODERS_PAD_A { D5 } #define ENCODERS_PAD_B { D6 } #define ENCODER_RESOLUTION 2 ``` **mykeyboard.h** (抜粋) ``` #define LAYOUT( \ k00, k01, k02, k03, \ k10, k11, k12, k13, \ k20, k21, k22, k23 \ ) { \ { k00, k01, k02, k03 }, \ { k10, k11, k12, k13 }, \ { k20, k21, k22, k23 } \ } ``` **keymap/default/keymap.c** (抜粋) ``` enum layer_names { _BASE, _FN }; const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { [_BASE] = LAYOUT( KC_NO, RCS(KC_TAB), LCTL(KC_TAB), KC_NO, LT(_FN, KC_ESC), KC_PGUP, KC_UP, KC_PGDN, KC_LCTRL, KC_LEFT, KC_DOWN, KC_RIGHT ), [_FN] = LAYOUT( KC_NO, KC_NO, KC_MUTE, KC_NO, KC_TRNS, KC_HOME, KC_TRNS, KC_END, KC_LSHIFT, KC_AUDIO_VOL_DOWN, KC_TRNS, KC_AUDIO_VOL_UP ) }; void keyboard_post_init_user(void) { setPinInputHigh(RESET_JUMPER_PIN); } bool process_record_user(uint16_t keycode, keyrecord_t *record) { if (!readPin(RESET_JUMPER_PIN)) { reset_keyboard(); return false; // Not reached. } return true; } bool encoder_update_user(uint8_t index, bool clockwise) { if (clockwise) { tap_code(KC_WH_U); } else { tap_code(KC_WH_D); } return true; } ``` ## ファームウェアの書き換え手順 1. USBaspLoaderを待機させます。(次のいずれかの手順) - ジャンパをセットしてUSBを差し込む。 - キーボード使用中にジャンパをセットし、 任意のキーを押下する。 2. (初回のみ)ZadigでデバイスにWinUSBドライバを当てます。 3. (任意)パソコン側で `avrdude -p m328p -c USBasp`を実行し、接続を確認します。 4. パソコン側で`avrdude -p m328p -c USBasp -U flash:w:firmware.hex:i`を実行し、書き込みます。 5. ジャンパ線を外します。自動でユーザープログラムが実行されます。 Windowsで書き込む場合は、Arduino IDE付属のavrdudeを使うこともできます。その場合は、Arduino IDEのルートディレクトリの下の`hardware/tools/avr/bin/`で、`avrdude -C ../etc/avrdude.conf -p m328p -c USBasp -U flash:w:firmware.hex:i`みたいな感じにします(設定ファイルを引数で明示する)。 ## その他 - 一部の環境で、パソコン起動時のUEFIの段階でこのキーボードが接続されているとUSBデバイスの認識でスタックするのか、起動処理が進まない事象を確認しています(手元のThinkPad X250)。原因は不明。 - 上記の設定とコードだとロータリーエンコーダの回転方向を二重に間違えて解釈しています(たぶん)。`config.h`にて`#define ENCODER_DIRECTION_FLIP`を加え、`keymap.c`でのロータリエンコーダーへのキー割り当てを逆にするほうが良いでしょう。 - 上記のコードだけだと実用上支障ないのですが、`tap_code()`の呼び出しでキー押下に続くキー解除の信号が正しく送られないことがあるようなので、`config.h`で`#define TAP_CODE_DELAY 100`などとするのが良いようです(公式ドキュメントによる。値は要調整かも)。 - USB D-, D+のポートの割り当ては、それぞれ次のファイルで定義されています。 - USBaspLoaderでは`firmware/bootloaderconfig.h`でデフォルト値が定義されており、`Makefile.inc`でgccへの引数として上書き可能。 - QMKでは`tmk_core/protocol/vusb/usbconfig.h`でデフォルト値が定義されており、`config.h`で上書き可能。