タッチパッド付き40%キーボードの製作メモ
はじめに
タッチパッド付き40%キーボードを設計・製作したので、概要を紹介させていただきます。
作ったもの
- 真ん中に小さいタッチパッド(TPS43)を付けた、カラムスタッガードの一体型44キーのキーボードです。
- ケースは、FreeCADで設計し、3Dプリントにしました。
- マウスレイヤーは、タッチパッドのなぞり方でON/OFFが切り替わるようにしました。
キーレイアウト
このようにしました。相変わらず迷走中です。
_BASEレイヤー
- 文字入力用です。
-
Fn.E
は、タップだとEnter
です。 -
Fn.M
、Fn.H
は、タップだと無変換
、変換
です。 -
RAlt
は、タップだとBS
です。
_NUMレイヤー
- 数字・記号の入力用です。
-
Fn.M(無変換)
とFn.H(変換)
を同時ホールドしている間、ONにします。 -
Fn.C
またはFn.E
をホールドすると、Shift
+このレイヤーがNになります。
_CURSORレイヤー
- vi風カーソル、各種移動、右クリメニュー、よく使うファンクションキー用です。
-
Fn.M(無変換)
またはFn.H(変換)
のどちらかをホールドしている間、ONにします。 - Excelでカーソル移動と
Shift
やCtrl
の同時押しを多用するので、d
とf
のところに配置しています。
_FUNCレイヤー
- ファンクションキー入力用などです。
-
Fn.C
とFn.M(無変換)
の同時ホールド またはFn.E
とFn.H(変換)
の同時ホールドをしている間、ONにします。
_MOUSEレイヤー
- マウスボタンなどです。
- タッチパッドを素早く時計回りに1回なぞると、ONになります。
- タッチパッドを素早く反時計回りに1回なぞるか、
TG
をタップすると、OFFになります。
回路とPCB、プレートの設計
- 今回は、ファームウェア書き込みの手軽さとコスパで、waveshareのRP2040-zeroを使いました。
- タッチパッドは、qmkでサポートしているAzoteqのIQS5xxを採用してるものが良さそうなので、AzoteqのTPS43とスイッチサイエンスのSSCI-080798にしました。(どちらもI2C接続なので配線の仕方は同じ。実際組み立てに使ったのはTPS43。)
- 最初にブレッドボードで試してみて、どちらも問題なく動きました。
回路図
- キースイッチとダイオードは、いつもどおりといった感じです。
- タッチパッド向けのI2C配線で、プルアップ抵抗とコンデンサが2個ずつあるのは、表面実装とスルーホール実装のどちらでも付けられるように考えたもの。(実際に使ったのは表面実装の抵抗。プルアップ抵抗はなくても動いたけど一応付けました。コンデンサは組立時に付けるのやめました。)
PCB
- 最初にスイッチとダイオードのフットプリントを並べました。
- そして、SW1〜38・43・44を傾ける前、SW39・42を10度、SW40・41を20度傾けた状態でEdge.Cutsの線を書いて、ネジ穴・ネジ穴パッドを配置し、左右ごとにグルーピングしました。
- その後、グループごとに10度ずつ傾けました。
- 左右を傾けた後、中央のフットプリントとネジ穴を配置し、残りのEdge.Cutsの線を書いた後、配線しました。
- タッチパッドのフットプリントはフットプリントエディタで書きました。
- できるだけキーボード全体をコンパクトにしたかったので、中央のスペースはタッチパッドがギリギリ収まる程度の幅だけにしています。
- 真ん中の四角い穴は、タッチパッドの配線を通して、余長をまるめるためのものです。
- 図では、ゾーンの塗りつぶしは一旦消しています。
スイッチプレート(トッププレート)
- KiCADでPCBと一緒に作りました。
- PCBと同じ大きさです。
- スイッチ穴は、スイッチのフットプリントのUser.Eco1レイヤーに書いてあったものをそのまま使っています。
- プレートのネジ穴は、PCBのネジ穴・ネジ穴パッドの中心に合わせて、直径2.3mmの円を書きました。
- PCB、プレート、ケースのネジは基本的にM2にしました。
- スイッチサイエンスのタッチパッドの取り付け穴だけ、直径3.2mmです。
- 真ん中の四角い穴は、タッチパッドの配線を通すためのものです。
ケースの設計
前作まではステンレスプレートのサンドイッチだったのですが、頑丈すぎて机にぶつけると机が削れたりするので、今回は3Dプリントでケースを作ることにしました。
方針
できるだけコンパクトにしたいので、PCBの形に合わせることにしました。
寸法の検討
平面上の配置はKiCADで書いたPCB・プレートで分かりますが、高さ・厚さが分からないので、3DCADで立体を作る前に、いったんlibreCADで断面の検討をしました。
libreCADは初挑戦でしたが、ネット情報が豊富だったのでたすかりました。
断面の設計
- PCB・プレートとケースの隙間は0.5mmにして、ケースのサイドの厚みは3mmにしました。
- ケースの底の厚みは、ちょっと厚すぎる感じはするものの5mmにしました。
- ネジ類はM2の平ネジとなべ小ねじ(頭Φ3.5〜4.5mm)、M2六角ナット(対辺4mm、厚さ1.6mm)、平ワッシャー(Φ6mm、厚さ0.4mm)を使う想定にしました。
- ケースの縁の高さは、親指でキーを押したときにぶつからないようにしました。
- TPS43の表面にかぶせる板が何mmまで大丈夫なのか心配だったため、とりあえず1mmにしつつ、あとで頑張れば厚さを変更できるように、セパレート設計にしました。
FreeCADでスケッチ
ここからFreeCADの作業です。FreeCADも初挑戦でしたが、こちらはかなりネット情報が豊富でしたので、どうにかなりました。以下の記載は、ネット情報を参考にしつつも、よく分からなかったところは手探りでやった結果ですので、正しい作法ではないかもしれません。あくまで素人の製作メモということで、参考程度に見ていただければと思います。
下書きスケッチ
最初に、各パーツのスケッチを書く際の基準とするための下書きスケッチを作成しました。
- Part Designワークベンチでボディを作成し、XY平面のスケッチを作成します。
- PCBの外形やスイッチの配置に合わせて、線を引きます。(これがずれると全部ずれるので注意。)
- ネジ穴も下書きします。
ケース外側のスケッチ
普通は内側の角張ったスケッチを書いて、それを押し出して立体にした後、外側に厚みを作り、さらにフィレットで角を丸めるようです。
しかしながら、うまくできなかったので、角を丸めた外側のスケッチで外側の立体を作り、同様に作った内側の立体を減算して、殻を作ることにしました。
まずは、ケース外側のスケッチです。
- 下書きスケッチと同じボディで、XY平面のスケッチを作成します。
- 下書きスケッチを表示して、外部ジオメトリの作成ツールで、基準にしたい下書きの線を選びます。
- ケース外側の線を書いていき、基準線に拘束していきます。(一致拘束や距離拘束)
- ときどき自動で設定される拘束が、過剰拘束になって線がオレンジ色になるので、そのときは過剰な拘束を消します。(一致拘束したら、自動でついていた水平拘束とぶつかるなど。)
ケース内側のスケッチ
書き方は外側と同様です。
ネジポストと内側構造物のスケッチ
ネジポストの土台、それとケース外枠を縦横につなぐ梁です。複雑なスケッチにするとパーツに押し出せないので、単純な閉じた図形を複数作って、それぞれパーツに押し出したあとで結合しました。
下図↓は、ポストの基礎部分のスケッチです。
下図↓は、縦の梁のスケッチです。
下図↓は、横の梁のスケッチです。
下図↓は、プレートを乗せるためのポストのスケッチです。
下図↓は、PCBを乗せるためのポストのスケッチです。
下図↓は、PCBを支える梁のスケッチです。
プレートをネジで固定し、PCBもネジで固定したうえで、さらに、PCBの裏側をこれで支えるので、かなりカチカチな固定になります。
あらかじめ、PCBのほうの設計で、他の実装部品と干渉しないようにしています。
微妙な隙間があった場合に、打鍵時に音が出るかもしれないので、組立時はこれとPCBの間に絶縁ビニールテープを貼っています。
ネジ穴のスケッチ
ネジ穴やナット穴のザグリをあけるときに減算で使うためのパーツのスケッチです。
下図↓は、ナット穴ザグリのスケッチです。
下図↓は、ネジ穴のスケッチです。
RP2040-zeroの裏側部分のスケッチ
RP2040-zeroの裏側になる部分のスケッチです。
ファームウェアを書き込む際にBOOTボタンを押すので、その穴も作ります。
ケースの底を作るためのスケッチ
ケースの底を7度の角度でカットするためのパーツのスケッチです。
ほかのスケッチはXY平面ですが、これはYZ平面です。
これをX軸方向に押し出したパーツと、ケース外側や内側を押し出したパーツで、共通集合をつくることで、底が斜めになります。
タッチパッドの台の外側のスケッチ
タッチパッド(TPS43)を乗せるための台のスケッチです。
下図↓は、外側部分です。
下図↓は、内側をくり抜くためのパーツのスケッチです。
FreeCADでパーツを作成
作ったスケッチを押し出して、パーツを作成しました。
そして、結合(加算)、差集合(減算)、共通集合などのブーリアン演算で、パーツをつなげました。
ケースの殻になる部分
スケッチの押し出しはこんな感じでした。
- Partワークベンチで、モデルのパネルから押し出したいスケッチを選択します。
- データのAttachmentの位置で、z軸を高さ・厚さの設計に合わせて調整します。
- ツールバーで押し出しをクリックするとタスクが開くので、押し出す長さを指定して、ソリッド作成にチェック付けて、適用・OK。
下図↓は、ケース外側のスケッチを押し出したパーツです。
下図↓は、ケース外側を斜めカットする直前です。
ブーリアン演算はこんな感じでした。
- Partワークベンチで、母体となるパーツを選択。
- ツールバーでブーリアン演算をクリックするとタスクが開くので、演算の種類と2番めの図形を選んで、適用。
下図↓は、ケース外側を斜めカットしたものです。
下図↓は、同様にケース内側を斜めカットしたものです。
下図は、斜めカットした外側から、斜めカットした内側を減算したものです。
内側の構造物
下図↓は、ネジポストの基礎や縦横の梁のスケッチをそれぞれ押し出したパーツを重ねて表示したものです。
下図↓は、上記のパーツを結合したものです。
下図↓は、更にプレート・PCB用のポスト、PCB支えをを結合したものです。
内側の構造物を底を斜めにするため、下図↓のケース外側の斜めカット後のパーツと共通集合をとります。
下図↓は、底を斜めカットした後です。
ケースの殻と内側構造物の結合
結合前に、ケース殻の縁にフィレットをかけました。
複雑な形になってからだと、丸みをかけた先が別のエッジや面にあたってしまい、うまく行かないことがあるようです。単純なパーツのうちに、半径の大きいフィレットから先にかけていくと良いようでした。
下図↓は、縁にフィレットをかけたケース殻です。
下図↓は、ケース殻と内側構造物を結合したものです。結合の順番がイマイチだったので、RP2040-zeroの裏側部分が、ケース殻で塞がれてしまいました。
下図↓は、改めてRP2040-zeroの下をくり抜いたものです。
ネジ穴・ザグリ作成
ケースの結合が終わったら、ケースにネジ穴をあけて、ナット用のザグリを作りました。
下図↓は、差集合をとるためのネジ穴・ザグリパーツです。
下図↓は、ネジ穴・ザグリをあけた後の上面です。
このあたりで、内側構造物がやり過ぎな(ネジポストが多い、梁が太くて多い)感じがしてきましたが、もはや手直しが面倒だったので、このまま突き進みました。
下図↓は、ネジ穴・ザグリをあけた後の底面です。
USB穴作成
つづいて、ケースの背面の方に、USB端子の穴をあけました。
- ネジ穴・ザグリ穴をあけた後のケースのUSB穴をあけたい面を選択して、スケッチをアタッチします。
- 穴のスケッチを書いたら、押し出してパーツを作ります。
- ケース本体から差集合をとります。
下図↓は、USB穴をあけた後です。
ケースの足パッド作成
ケースの裏面に、足パッド用のくぼみをつけました。
- ケース本体裏側のくぼみをつけたい面を選択して、スケッチをアタッチします。
- くぼみのスケッチを書いたら、押し出してパーツを作ります。
- ケース本体から差集合をとります。
下図↓は、くぼみをつけた後です。
タッチパッド(TPS43用)の台を作成
TPS43を乗せるための台を作りました。
下図↓は、中身をくり抜く前の外側で、ベースのパーツの上に、台形のパーツを結合したあとのものです。
下図↓は、中身をくり抜くための図形で、土台用、真ん中用、吹き抜け用の3つを結合したあとのものです。
下図↓は、中身をくり抜いた後です。
下図↓は、ネジ穴・ザグリをあけるためのものです。
下図↓は、ネジ穴・ザグリをあけた後の表面です。
下図↓は、ネジ穴・ザグリをあけた後の裏面です。
下図↓の緑色のパーツは、内側でタッチパッド(TPS43)とその表面パネルを支えるための台です。あらかじめテスト用に調達したTPS43の現物とデータシートを確認して、表面実装部品にぶつからないようにしています。強く押しても壊れないように、梁を入れました。
表面パネルは1mmのプラスチック板で自作です。
ケースの3Dデータの仕上がりチェック
ひととおりパーツが出来上がったら、メジャーツールで2点間や辺と辺の距離が設計通りの値になっているか確認しました。
下図↓は、出来上がった全体像。
下図↓は、タッチパッドの隙間を図っている様子。
下図↓は、ネジ穴ザグリの深さを図っている様子。
発注
PCBと3Dプリントは、JLCPCBを利用しました。3Dプリントの料金は、材質だけでなく、使用する材料の量によってもだいぶ違うので、内側の構造物をもっとシンプルで薄くすればよかったと後悔しました。とりあえず最初の練習製作みたいなものなので、一番安かった素材(9600レジン、白)にしました。
スイッチプレートは、切断堂さんに発注しました。ステンレス板のオーダーカットです。
スイッチなどは、TALP KEYBOADさんから購入しました。
ネジ類はアマゾン、コネクタ類は秋月電子さんやマルツさんから購入しました。
製作
JLCPCBは、注文後10日で届きました。プレートは見積もり確認やりとり含めて10日弱でした。さっそく納品物を確認して組み立てです。
主要パーツ
届いた主要パーツです。(タッチパッドのフタは、自分でプラ板を切ったもの。)
仮組みチェック
とりあえず仮組みしてみて、うまくはまるか、ネジ穴があっているかなどをチェックしました。
ケースとPCB
ネジ穴、高さはねらいどおりだったので、一安心。
プレートとタッチパッド(スイッチサイエンス)
プレートもピッタリはまりました。スイッチサイエンスのタッチパッドは今回使いませんでしたが、つける場合はこんな感じ。
タッチパッド(Azoteq_TPS43)の台
Azoteq_TPS43をはめる台です。こちらも寸法がピッタリねらいどおりでした。パーツ同士のクリアランスは0.1mmずつでちょうどでした。
はんだ付け
表面実装の小さい部品からはんだ付けしました。小さいものは、ピンセットを折り曲げた道具で押さえました。
はんだ付けが終わったら、組み立てる前にスイッチソケット1つずつ、スイッチを付けて動作テストをしました。(組立後に発覚すると大変なので。今回、順次3台作って、ダイオードの不良、向き間違い、はんだ付け忘れがそれぞれ1件ずつ見つかり、直しました。)
テストは、主にxevコマンドを使っています。
はんだ付けの様子
ダイオードのはんだ付け
ひととおりはんだ付けが終わったところ
組み立て
はんだ付けとテスト後、いよいよ組み立てです。
タッチパッド台
タッチパッド台の中身パーツの梁の1つが配線と干渉したので、ヤスリで少し削りました。
(中のT字の縦棒とコネクタの距離が近くて、配線を通す隙間が足りなかった。)
穴の位置
BOOTボタンを押す穴は問題ありませんでした。先を出さない状態のペンで押せました。
USB穴の位置関係の仕上がりも問題なしでした。
USB Type-Cのマグネット端子も問題なくはまりました。
ただ、ちょっとザグリが浅かったようで、外すのが少しやりにくかったです。
裏側
ネジ穴からネジやナットが飛び出すことなく、きちんと収まりました。
足パッドも付けました。
ネジが多すぎて、見た目がイマイチです。。。
組み立て完了!!
組み立てが終わったところです。デザイン性のない角張った代物です。。。
ファームウェア(qmk)
ブレッドボードでタッチパッドの事前テストをしている段階から作っていたのですが、実際に使いながら調整しました。
キーボード名はkplj44、レイアウトはdefaultです。
キーボードのrules.mk
タッチパッドを使うので、キーボードのrules.mkでドライバを指定しました。
POINTING_DEVICE_ENABLE = yes
POINTING_DEVICE_DRIVER = azoteq_iqs5xx
キーのピン割付
いつもどおりで代わり映えしませんが、キーボードのinfo.jsonに書きました。
kplj44/info.json
{
"manufacturer": "kpl",
"keyboard_name": "kplj44",
"maintainer": "kpl",
"bootloader": "rp2040",
"diode_direction": "COL2ROW",
"features": {
"bootmagic": true,
"command": false,
"console": false,
"extrakey": true,
"mousekey": true,
"nkro": true,
"rgblight": false
},
"matrix_pins": {
"cols": ["GP2", "GP3", "GP4", "GP5", "GP6", "GP7", "GP8", "GP9", "GP10", "GP11", "GP12", "GP13"],
"rows": ["GP14", "GP15", "GP26", "GP27"]
},
"processor": "RP2040",
"url": "",
"usb": {
"device_version": "1.0.0",
"pid": "0x16C0",
"vid": "0x27DB"
},
"layouts": {
"LAYOUT_1": {
"layout": [
{ "label": "SW", "matrix": [0, 0], "x": 0, "y": 0 },
{ "label": "SW", "matrix": [0, 1], "x": 1, "y": 0 },
{ "label": "SW", "matrix": [0, 2], "x": 2, "y": 0 },
{ "label": "SW", "matrix": [0, 3], "x": 3, "y": 0 },
{ "label": "SW", "matrix": [0, 4], "x": 4, "y": 0 },
{ "label": "SW", "matrix": [0, 5], "x": 5, "y": 0 },
{ "label": "SW", "matrix": [0, 6], "x": 6, "y": 0 },
{ "label": "SW", "matrix": [0, 7], "x": 7, "y": 0 },
{ "label": "SW", "matrix": [0, 8], "x": 8, "y": 0 },
{ "label": "SW", "matrix": [0, 9], "x": 9, "y": 0 },
{ "label": "SW", "matrix": [0, 10], "x": 10, "y": 0 },
{ "label": "SW", "matrix": [0, 11], "x": 11, "y": 0 },
{ "label": "SW", "matrix": [1, 0], "x": 0, "y": 1 },
{ "label": "SW", "matrix": [1, 1], "x": 1, "y": 1 },
{ "label": "SW", "matrix": [1, 2], "x": 2, "y": 1 },
{ "label": "SW", "matrix": [1, 3], "x": 3, "y": 1 },
{ "label": "SW", "matrix": [1, 4], "x": 4, "y": 1 },
{ "label": "SW", "matrix": [1, 5], "x": 5, "y": 1 },
{ "label": "SW", "matrix": [1, 6], "x": 6, "y": 1 },
{ "label": "SW", "matrix": [1, 7], "x": 7, "y": 1 },
{ "label": "SW", "matrix": [1, 8], "x": 8, "y": 1 },
{ "label": "SW", "matrix": [1, 9], "x": 9, "y": 1 },
{ "label": "SW", "matrix": [1, 10], "x": 10, "y": 1 },
{ "label": "SW", "matrix": [1, 11], "x": 11, "y": 1 },
{ "label": "SW", "matrix": [2, 0], "x": 0, "y": 2 },
{ "label": "SW", "matrix": [2, 1], "x": 1, "y": 2 },
{ "label": "SW", "matrix": [2, 2], "x": 2, "y": 2 },
{ "label": "SW", "matrix": [2, 3], "x": 3, "y": 2 },
{ "label": "SW", "matrix": [2, 4], "x": 4, "y": 2 },
{ "label": "SW", "matrix": [2, 5], "x": 5, "y": 2 },
{ "label": "SW", "matrix": [2, 6], "x": 6, "y": 2 },
{ "label": "SW", "matrix": [2, 7], "x": 7, "y": 2 },
{ "label": "SW", "matrix": [2, 8], "x": 8, "y": 2 },
{ "label": "SW", "matrix": [2, 9], "x": 9, "y": 2 },
{ "label": "SW", "matrix": [2, 10], "x": 10, "y": 2 },
{ "label": "SW", "matrix": [2, 11], "x": 11, "y": 2 },
{ "label": "SW", "matrix": [3, 2], "x": 2, "y": 3 },
{ "label": "SW", "matrix": [3, 3], "x": 3, "y": 3 },
{ "label": "SW", "matrix": [3, 4], "x": 4, "y": 3 },
{ "label": "SW", "matrix": [3, 5], "x": 5, "y": 3 },
{ "label": "SW", "matrix": [3, 6], "x": 6, "y": 3 },
{ "label": "SW", "matrix": [3, 7], "x": 7, "y": 3 },
{ "label": "SW", "matrix": [3, 8], "x": 8, "y": 3 },
{ "label": "SW", "matrix": [3, 9], "x": 9, "y": 3 }
]
}
}
}
I2Cのピン割付
kplj44/mcuconf.hで指定しました。
#include_next <mcuconf.h>
#undef RP_I2C_USE_I2C0
#define RP_I2C_USE_I2C0 TRUE
#undef RP_I2C_USE_I2C1
#define RP_I2C_USE_I2C1 FALSE
タッチパッド(TPS43)のパラメータ
kplj44/config.hで指定しました。
#define I2C_DRIVER I2CD0
#define I2C1_SDA_PIN GP0
#define I2C1_SCL_PIN GP1
#define AZOTEQ_IQS5XX_TPS43 1
#define AZOTEQ_IQS5XX_TAP_ENABLE true
#define AZOTEQ_IQS5XX_TWO_FINGER_TAP_ENABLE true
#define AZOTEQ_IQS5XX_PRESS_AND_HOLD_ENABLE true
#define AZOTEQ_IQS5XX_SCROLL_ENABLE true
//スクロールを認識しやすく
#define AZOTEQ_IQS5XX_SCROLL_INITIAL_DISTANCE 10
詳細はqmkのドキュメントに書いてありました。
キーマップのrules.mk
キーオーバーライドとタップダンスを使うので、キーマップのrules.mkで指定しました。
KEY_OVERRIDE_ENABLE = yes
TAP_DANCE_ENABLE = yes
キーマップのパラメータ
キーマップ(タップダンス)のパラメータは、キーマップのconfig.hで指定しました。
#define TAPPING_TERM 200
#define PERMISSIVE_HOLD
#undef TAPPING_TOGGLE
#define TAPPING_TOGGLE 2
タッチパッド関係の処理
keymap.cに書きました。
ドラッグスクロールとスピード調整
マウスレイヤーでDLG_SCRL
を押している間、タッチパッドの移動はスクロールとして扱うようにしました。process_record_userで状態を切り替えます。
また、タッチパッドの移動量が、4Kモニターだと一度で画面の半分程度にしかならず、ウィンドウの移動の際などに不便に感じたので、早く動かしたときは移動量を増やすようにしました。(常に早くすると細かい操作ができないので、移動量に応じて切り替えるようにしました。)
ドラッグスクロールとスピード調整の処理(抜粋)
// ドラッグスクロール
static bool set_scrolling = false;
// ...中略...
// キーイベントのコールバック
bool process_record_user(uint16_t keycode, keyrecord_t *record){
// キーに応じて
switch (keycode) {
// ...中略...
case MY_SCRL: {
set_scrolling = record->event.pressed;
return true;
} break;
// ...中略...
default: {
} break;
}
return true;
}
pointing_device_task_kbで、スクロール処理と移動スピード調整をします。
本来は、pointing_device_task_userのほうがよいようですが、後述のほうでpointing_device_task_kbに書く必要があったので、こちらにまとめて書きました。
// スクロールスピード調整
#define SCROLL_DIVISOR 20.0
// スクロール量
static float scroll_accumulated_h = 0;
static float scroll_accumulated_v = 0;
// 移動スピード
#define SPEED_NORMAL 1.0 // 普通
#define SPEED_MIDDLE 2.5 // 少し早い
#define SPEED_HIGH 3.0 // 早い
#define speed_sw_ct_mid 20 // 少し早くする移動量
#define speed_sw_ct_high 50 // 早くする移動量
// ...中略...
// タッチパッドのセンサーデータのコールバック
report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
// ...中略...
// 移動スピード
float mouse_speed = SPEED_NORMAL;
// レポートの移動量
int16_t cur_x = mouse_report.x;
int16_t cur_y = mouse_report.y;
int16_t cur_h = mouse_report.h;
int16_t cur_v = mouse_report.v;
// ...中略...
// ドラッグスクロール と スクロールスピード調整
if (set_scrolling) {
scroll_accumulated_h += (float)cur_x / SCROLL_DIVISOR;
scroll_accumulated_v += (float)cur_y / SCROLL_DIVISOR;
mouse_report.x = 0;
mouse_report.y = 0;
} else {
if (ax >= speed_sw_ct_high || ay >= speed_sw_ct_high) {
mouse_speed = SPEED_HIGH;
} else if (ax >= speed_sw_ct_mid || ay >= speed_sw_ct_mid) {
mouse_speed = SPEED_MIDDLE;
} else {
mouse_speed = SPEED_NORMAL;
}
scroll_accumulated_h += (float)cur_h / SCROLL_DIVISOR;
scroll_accumulated_v += (float)cur_v / SCROLL_DIVISOR;
mouse_report.x = (int8_t)((float)cur_x * mouse_speed);
mouse_report.y = (int8_t)((float)cur_y * mouse_speed);
}
mouse_report.h = (int8_t)scroll_accumulated_h;
mouse_report.v = (int8_t)scroll_accumulated_v;
scroll_accumulated_h -= (int8_t)scroll_accumulated_h;
scroll_accumulated_v -= (int8_t)scroll_accumulated_v;
return mouse_report;
}
マウスレイヤーのON/OFF切り替え
qmkの自動マウスレイヤー機能は評判があまり良くないようなのと、キーで切り替えるのも煩わしいので、タッチパッドのなぞり方でマウスレイヤーのON/OFFを切り替えることにしました。
最初の方にも書きましたが、タッチパッドを素早く時計回りに1回なぞるとON、素早く反時計回りに1回なぞるかTG
をタップするとOFFです。
キーの方は、マウスレイヤーにTG(_MOUSE)
を配置しただけです。
マウスレイヤー切り替えの処理(抜粋)
// ...前略...
// 移動方向を判定する最小移動量
#define detect_ct_on 5
// 前回判定した方向
static char prev_d = ' ';
// 判定した方向の履歴
static char direct_hist[5];
// タッチパッドのセンサーデータのコールバック
report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
// レポートの移動量
int16_t cur_x = mouse_report.x;
int16_t cur_y = mouse_report.y;
int16_t cur_h = mouse_report.h;
int16_t cur_v = mouse_report.v;
// 移動方向判断用
int16_t ax;
int16_t ay;
char dx;
char dy;
char cur_d;
// 時計回り判断用
char R1[5] = "RULD";
char R2[5] = "DRUL";
char R3[5] = "LDRU";
char R4[5] = "ULDR";
// 反時計回り判断用
char L1[5] = "DLUR";
char L2[5] = "RDLU";
char L3[5] = "URDL";
char L4[5] = "LURD";
// 横移動判断
if (cur_x < 0) {
ax = -cur_x;
dx = 'L';
} else if (cur_x > 0) {
ax = cur_x;
dx = 'R';
} else {
ax = 0;
dx = ' ';
}
// 縦移動判断
if (cur_y < 0) {
ay = -cur_y;
dy = 'U';
} else if (cur_y >0) {
ay = cur_y;
dy = 'D';
} else {
ay = 0;
dy = ' ';
}
// 移動量が閾値以上なら判定履歴に入れる
if ((ax >= detect_ct_on) || (ay >= detect_ct_on)) {
if (ax > ay) {
cur_d = dx;
} else {
cur_d = dy;
}
} else {
cur_d = 'X';
}
if (prev_d != cur_d) {
direct_hist[3] = direct_hist[2];
direct_hist[2] = direct_hist[1];
direct_hist[1] = direct_hist[0];
direct_hist[0] = cur_d;
prev_d = cur_d;
if (strcmp(direct_hist, R1) == 0 || strcmp(direct_hist, R2) == 0 || strcmp(direct_hist, R3) == 0 || strcmp(direct_hist, R4) == 0) {
// 時計回りなら _MOUSEレイヤーをonにする
layer_on(_MOUSE);
} else if (strcmp(direct_hist, L1) == 0 || strcmp(direct_hist, L2) == 0 || strcmp(direct_hist, L3) == 0 || strcmp(direct_hist, L4) == 0) {
// 反時計回りなら _MOUSEレイヤーをoffにする
layer_off(_MOUSE);
}
}
// ...中略...
return mouse_report;
}
なお、左右に素早く動かすとRXLXRXLX...と検知するので、direct_histをもう少し長くしてstrncmpで比較する文字数を使い分ければ、別なトリガも作れそうです。(今のところ必要性は感じていないけど)
タッチパッドを離して3秒後くらいの右クリックイベント
なぜかタッチパッドを動かし終わって手を離すと、その3秒後くらいに右クリックが送信される症状がでました。
どこが悪いのか分からなかったので、対症療法的に、不要な右クリックが来たらキャンセルするようにしました。
process_record_userやpointing_device_task_userでは捕捉できなかったので、pointing_device_task_kbに処理を書きました。
タッチパッドの不要な右クリックをキャンセルする処理(抜粋)
// ...前略...
// 最後に移動した時間
static uint16_t last_move_time;
// BTN2を無効にする経過時間
const uint16_t ms_btn2_cancel_time = 2000;
// タッチパッドのセンサーデータのコールバック
report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
// ...中略...
// 最後に移動した時間
if (cur_x != 0 || cur_y != 0 || cur_h != 0 || cur_v != 0) {
last_move_time = timer_read();
}
// ...中略...
// なぜかポインタ移動終了の3秒後くらいに右クリックが来るので、それを無効化
if (mouse_report.buttons && MOUSE_BTN2) {
if (timer_elapsed(last_move_time) > ms_btn2_cancel_time) {
mouse_report.buttons &= ~MOUSE_BTN2;
}
}
// ...中略...
return mouse_report;
}
keymap.c
参考までに、今回のkeymap.cの全体です。
#include QMK_KEYBOARD_H
// 日本語キー
#include "keymap_japanese.h"
// pushed状態の記憶用
static bool b_mhen = false;
static bool b_henk = false;
static bool b_caps = false;
static bool b_rsft = false;
static bool b_lsft = false;
static bool b_ent = false;
typedef enum {
TD_NONE,
TD_UNKNOWN,
TD_SINGLE_TAP,
TD_SINGLE_HOLD,
TD_DOUBLE_TAP,
TD_DOUBLE_HOLD,
TD_DOUBLE_SINGLE_TAP,
TD_TRIPLE_TAP,
TD_TRIPLE_HOLD
} td_state_t;
typedef struct {
bool is_press_action;
td_state_t state;
} td_tap_t;
enum {
TD_CAPS,
TD_ENT,
MY_SCRL
};
enum layers {
_BASE = 0,
_NUM,
_CURSOR,
_FUNC,
_MOUSE
};
// ドラッグスクロール
static bool set_scrolling = false;
// -------------------------------------------------------------------------
// タップダンス
// interruptedをタップとみなす
td_state_t cur_dance(tap_dance_state_t *state) {
switch (state->count) {
case 1: {
if (state->interrupted || !state->pressed) return TD_SINGLE_TAP;
else return TD_SINGLE_HOLD;
} break;
case 2: {
if (state->interrupted) return TD_DOUBLE_SINGLE_TAP;
else if (state->pressed) return TD_DOUBLE_HOLD;
else return TD_DOUBLE_TAP;
} break;
case 3: {
if (state->interrupted || !state->pressed) return TD_TRIPLE_TAP;
else return TD_TRIPLE_HOLD;
} break;
default: return TD_UNKNOWN;
}
}
// interruptedをホールドとみなす
td_state_t cur_dance2(tap_dance_state_t *state) {
switch (state->count) {
case 1: {
if (!state->interrupted && !state->pressed) return TD_SINGLE_TAP;
else return TD_SINGLE_HOLD;
} break;
case 2: {
if (!state->interrupted && !state->pressed) return TD_DOUBLE_TAP;
else return TD_DOUBLE_HOLD;
} break;
case 3: {
if (!state->interrupted && !state->pressed) return TD_TRIPLE_TAP;
else return TD_TRIPLE_HOLD;
} break;
default: return TD_UNKNOWN;
}
}
// タップダンス用の td_tap_t のインスタンス
// CapsLockキー
static td_tap_t caps_tap_state = {
.is_press_action = true,
.state = TD_NONE
};
// Enterキー
static td_tap_t ent_tap_state = {
.is_press_action = true,
.state = TD_NONE
};
// -------------------------------------------------------------------------
// タップダンスの処理
// --------------------------------------
// CapsLockキー
// Tap : もとに戻す
// Hold : Shift + _NUMレイヤー
// 変換or無変換と組合せのHold : _FUNCレイヤー
void caps_finished(tap_dance_state_t *state, void *user_date) {
caps_tap_state.state = cur_dance2(state);
switch (caps_tap_state.state) {
case TD_SINGLE_TAP:
case TD_DOUBLE_TAP:
case TD_TRIPLE_TAP: {
if (!b_rsft) unregister_code(KC_RSFT);
layer_off(_NUM);
layer_off(_FUNC);
layer_off(_CURSOR);
} break;
case TD_SINGLE_HOLD:
case TD_DOUBLE_HOLD:
case TD_TRIPLE_HOLD: {
if (!b_mhen && !b_henk) {
layer_on(_NUM);
layer_off(_FUNC);
} else {
layer_off(_NUM);
layer_on(_FUNC);
}
} break;
default: break;
}
}
void caps_reset(tap_dance_state_t *state, void *user_date) {
layer_off(_NUM);
switch (caps_tap_state.state) {
case TD_SINGLE_TAP:
case TD_DOUBLE_TAP:
case TD_DOUBLE_SINGLE_TAP:
case TD_TRIPLE_TAP: {
unregister_code(KC_RSFT);
} break;
case TD_SINGLE_HOLD:
case TD_DOUBLE_HOLD:
case TD_TRIPLE_HOLD: {
unregister_code(KC_RSFT);
if (b_mhen && b_henk) {
layer_on(_NUM);
layer_off(_CURSOR);
layer_off(_FUNC);
} else if (b_mhen || b_henk) {
layer_off(_NUM);
layer_on(_CURSOR);
layer_off(_FUNC);
} else {
layer_off(_NUM);
layer_off(_CURSOR);
layer_off(_FUNC);
}
} break;
default: break;
}
b_caps = false;
caps_tap_state.state = TD_NONE;
}
void caps_each_tap(tap_dance_state_t *state, void *user_date) {
layer_on(_NUM);
b_caps = true;
if (!b_mhen) register_code(KC_RSFT);
}
// --------------------------------------
// Enterキー
// Tap: Enter
// Hold: _NUM + RSFT
void ent_finished(tap_dance_state_t *state, void *user_date) {
ent_tap_state.state = cur_dance2(state);
switch (ent_tap_state.state) {
case TD_SINGLE_TAP: {
if (!b_lsft) unregister_code(KC_LSFT);
if (!b_mhen && !b_henk) {
layer_off(_NUM);
layer_off(_FUNC);
layer_off(_CURSOR);
}
tap_code(KC_ENT);
} break;
case TD_DOUBLE_TAP:
case TD_DOUBLE_SINGLE_TAP: {
if (!b_lsft) unregister_code(KC_LSFT);
if (!b_mhen && !b_henk) {
layer_off(_NUM);
layer_off(_FUNC);
layer_off(_CURSOR);
}
tap_code(KC_ENT);
tap_code(KC_ENT);
} break;
case TD_TRIPLE_TAP: {
if (!b_lsft) unregister_code(KC_LSFT);
if (!b_mhen && !b_henk) {
layer_off(_NUM);
layer_off(_FUNC);
layer_off(_CURSOR);
}
tap_code(KC_ENT);
tap_code(KC_ENT);
tap_code(KC_ENT);
} break;
case TD_SINGLE_HOLD:
case TD_DOUBLE_HOLD:
case TD_TRIPLE_HOLD: {
if (!b_mhen && !b_henk) {
register_code(KC_LSFT);
layer_on(_NUM);
layer_off(_FUNC);
} else {
layer_off(_NUM);
layer_on(_FUNC);
}
} break;
default: break;
}
}
void ent_reset(tap_dance_state_t *state, void *user_date) {
layer_off(_NUM);
switch (ent_tap_state.state) {
case TD_SINGLE_TAP:
case TD_DOUBLE_TAP:
case TD_DOUBLE_SINGLE_TAP:
case TD_TRIPLE_TAP: {
unregister_code(KC_LSFT);
} break;
case TD_SINGLE_HOLD:
case TD_DOUBLE_HOLD:
case TD_TRIPLE_HOLD: {
if (!b_lsft) unregister_code(KC_LSFT);
if (b_mhen && b_henk) {
layer_on(_NUM);
layer_off(_CURSOR);
layer_off(_FUNC);
} else if (b_mhen || b_henk) {
layer_off(_NUM);
layer_on(_CURSOR);
layer_off(_FUNC);
} else {
layer_off(_NUM);
layer_off(_CURSOR);
layer_off(_FUNC);
}
} break;
default: break;
}
b_ent = false;
ent_tap_state.state = TD_NONE;
}
void ent_each_tap(tap_dance_state_t *state, void *user_date) {
layer_on(_NUM);
b_ent = true;
if (!b_henk) register_code(KC_LSFT);
}
// -------------------------------------------------------------------------
// タップダンスのアクションの設定
tap_dance_action_t tap_dance_actions[] = {
[TD_CAPS] = ACTION_TAP_DANCE_FN_ADVANCED(caps_each_tap, caps_finished, caps_reset),
[TD_ENT] = ACTION_TAP_DANCE_FN_ADVANCED(ent_each_tap, ent_finished, ent_reset),
};
#define TDD_CAPS TD(TD_CAPS)
#define TDD_ENT TD(TD_ENT)
#define TG_MS TG(_MOUSE)
#define MY_LALTD LALT_T(KC_DEL)
#define MY_LALTB LALT_T(KC_BSPC)
#define MY_RALTD RALT_T(KC_DEL)
#define MY_RALTB RALT_T(KC_BSPC)
// -------------------------------------------------------------------------
// shift+2 " -> @
const key_override_t kor_at = ko_make_with_layers(MOD_MASK_SHIFT, KC_2, JP_AT, ~0);
// shift+6 & -> ^
const key_override_t kor_circ = ko_make_with_layers(MOD_MASK_SHIFT, KC_6, JP_CIRC, ~0);
// shift+7 ' -> &
const key_override_t kor_ampr = ko_make_with_layers(MOD_MASK_SHIFT, KC_7, JP_AMPR, ~0);
// shift+8 ( -> *
const key_override_t kor_astr = ko_make_with_layers(MOD_MASK_SHIFT, KC_8, JP_ASTR, ~0);
// shift+9 ) -> (
const key_override_t kor_lprn = ko_make_with_layers(MOD_MASK_SHIFT, KC_9, JP_LPRN, ~0);
// shift+0 -> )
const key_override_t kor_rprn = ko_make_with_layers(MOD_MASK_SHIFT, KC_0, JP_RPRN, ~0);
// shift+- = -> _
const key_override_t kor_unds = ko_make_with_layers(MOD_MASK_SHIFT, KC_MINS, JP_UNDS, ~0);
// = ^ -> =
// shift+= ~ -> +
const key_override_t kor_eql = ko_make_with_layers_and_negmods(0, JP_CIRC, JP_EQL, ~0, MOD_MASK_SHIFT);
const key_override_t kor_plus = ko_make_with_layers(MOD_MASK_SHIFT, JP_CIRC, JP_PLUS, ~0);
/* \ ] -> \ */
/* shift+\ } -> | */
const key_override_t kor_bsls = ko_make_with_layers_and_negmods(0, KC_BSLS, JP_BSLS, ~0, MOD_MASK_SHIFT);
const key_override_t kor_pipe = ko_make_with_layers(MOD_MASK_SHIFT, KC_BSLS, JP_PIPE, ~0);
// [ @ -> [
// shift+[ ` -> {
const key_override_t kor_lbrc = ko_make_with_layers_and_negmods(0, JP_AT, JP_LBRC, ~0, MOD_MASK_SHIFT);
const key_override_t kor_lcbr = ko_make_with_layers(MOD_MASK_SHIFT, JP_AT, JP_LCBR, ~0);
// ] [ -> ]
// shift+] { -> }
const key_override_t kor_rbrc = ko_make_with_layers_and_negmods(0, JP_LBRC, JP_RBRC, ~0, MOD_MASK_SHIFT);
const key_override_t kor_rcbr = ko_make_with_layers(MOD_MASK_SHIFT, JP_LBRC, JP_RCBR, ~0);
// shift+; + -> :
const key_override_t kor_coln = ko_make_with_layers(MOD_MASK_SHIFT, KC_SCLN, JP_COLN, ~0);
// ' : -> '
// shift+' * -> "
const key_override_t kor_quot = ko_make_with_layers_and_negmods(0, KC_QUOT, JP_QUOT, ~0, MOD_MASK_SHIFT);
const key_override_t kor_dquo = ko_make_with_layers(MOD_MASK_SHIFT, KC_QUOT, JP_DQUO, ~0);
// ` 全角半角 -> `
// shift+` shift+全角半角 -> ~
const key_override_t kor_grv = ko_make_with_layers_and_negmods(0, KC_GRV, JP_GRV, ~0, MOD_MASK_SHIFT);
const key_override_t kor_tild = ko_make_with_layers(MOD_MASK_SHIFT, KC_GRV, JP_TILD, ~0);
// shift+DEL -> BS
const key_override_t kor_bs = ko_make_with_layers(MOD_MASK_SHIFT, KC_DEL, KC_BSPC, ~0);
const key_override_t *key_overrides[] = {
&kor_at,
&kor_circ,
&kor_ampr,
&kor_astr,
&kor_lprn,
&kor_rprn,
&kor_unds,
&kor_eql,
&kor_plus,
&kor_bsls,
&kor_pipe,
&kor_lbrc,
&kor_lcbr,
&kor_rbrc,
&kor_rcbr,
&kor_coln,
&kor_quot,
&kor_dquo,
&kor_grv,
&kor_tild,
&kor_bs
};
// -------------------------------------------------------------------------
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
/* _BASE
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW1 SW2 SW3 SW4 SW5 SW6 SW7 SW8 SW9 SW10 SW11 SW12
* Tab Q W E R T Y U I O P -_
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW13 SW14 SW15 SW16 SW17 SW18 SW19 SW20 SW21 SW22 SW23 SW24
* TD_CAPS A S D F G H J K L ;: TD_Enter
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW25 SW26 SW27 SW28 SW29 SW30 SW31 SW32 SW33 SW34 SW35 SW36
* LShift Z X C V B N M ,< .> /? RShift
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW37 SW38 SW39 SW40 SW41 SW42 SW43 SW44
* Win LAlt Muhen Space LCtrl Henkan MY_RAltB Kana
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
*/
[_BASE] = LAYOUT_1(
KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_MINS,
TDD_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, TDD_ENT,
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT,
KC_LGUI, KC_LALT, JP_MHEN, KC_SPC, KC_LCTL, JP_HENK, MY_RALTB, JP_KANA
),
/*********************************************************************************************************************************
* _NUM
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW1 SW2 SW3 SW4 SW5 SW6 SW7 SW8 SW9 SW10 SW11 SW12
* Tab 1! 2@ 3# 4$ 5% 6^ 7& 8* 9( 0) -_
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW13 SW14 SW15 SW16 SW17 SW18 SW19 SW20 SW21 SW22 SW23 SW24
* TRNS + ~ ( ) = Left Down Up Right ;: TRNS
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW25 SW26 SW27 SW28 SW29 SW30 SW31 SW32 SW33 SW34 SW35 SW36
* TRNS [ ] " Enter ,< .> /? TRNS
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW37 SW38 SW39 SW40 SW41 SW42 SW43 SW44
* TRNS TRNS TRNS TRNS TRNS TRNS
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
*/
[_NUM] = LAYOUT_1(
KC_TAB, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS,
_______, JP_PLUS, JP_TILD, JP_LPRN, JP_RPRN, JP_EQL, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT, KC_SCLN, _______,
_______, XXXXXXX, XXXXXXX, KC_LBRC, KC_RBRC, JP_DQUO, XXXXXXX, KC_ENT, KC_COMM, KC_DOT, KC_SLSH, _______,
XXXXXXX, _______, _______, _______, _______, _______, _______, XXXXXXX
),
/*********************************************************************************************************************************
* _CURSOR
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW1 SW2 SW3 SW4 SW5 SW6 SW7 SW8 SW9 SW10 SW11 SW12
* ESC `~ F2 F3 F4 F5 SCRLOCK PgDwn PgUp [{ ]} =+
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW13 SW14 SW15 SW16 SW17 SW18 SW19 SW20 SW21 SW22 SW23 SW24
* TRNS App ~ RShift RCtrl Left Down Up Right '" TRNS
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW25 SW26 SW27 SW28 SW29 SW30 SW31 SW32 SW33 SW34 SW35 SW36
* TRNS DEL BS Caps N Enter Home End \| TRNS
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW37 SW38 SW39 SW40 SW41 SW42 SW43 SW44
* TRNS TRNS TRNS TRNS TRNS TRNS Eisu
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
*/
[_CURSOR] = LAYOUT_1(
KC_ESC, KC_GRV, KC_F2, KC_F3, KC_F4, KC_F5, KC_SCRL, KC_PGDN, KC_PGUP, KC_LBRC, KC_RBRC, KC_EQL,
_______, KC_APP, JP_TILD, KC_RSFT, KC_RCTL, XXXXXXX, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT, KC_QUOT, _______,
_______, XXXXXXX, KC_DEL, JP_CAPS, XXXXXXX, XXXXXXX, KC_N, KC_ENT, KC_HOME, KC_END, KC_BSLS, _______,
XXXXXXX, _______, _______, _______, _______, _______, _______, JP_EISU
),
/*********************************************************************************************************************************
* _FUNC
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW1 SW2 SW3 SW4 SW5 SW6 SW7 SW8 SW9 SW10 SW11 SW12
* ESC F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW13 SW14 SW15 SW16 SW17 SW18 SW19 SW20 SW21 SW22 SW23 SW24
* TRNS PriScrn VolDown VolUp Mute F12
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW25 SW26 SW27 SW28 SW29 SW30 SW31 SW32 SW33 SW34 SW35 SW36
* TRNS PAUSE BriDown BriUP Ins TRNS
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW37 SW38 SW39 SW40 SW41 SW42 SW43 SW44
* TRNS TRNS TRNS TRNS TRNS TRNS
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
*/
[_FUNC] = LAYOUT_1(
KC_ESC, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11,
_______, XXXXXXX, KC_PSCR, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, KC_VOLD, KC_VOLU, KC_MUTE, XXXXXXX, KC_F12,
_______, KC_PAUS, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, KC_BRID, KC_BRIU, XXXXXXX, KC_INS, _______,
XXXXXXX, _______, _______, _______, _______, _______, _______, XXXXXXX
),
/*********************************************************************************************************************************
* _MOUSE
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW1 SW2 SW3 SW4 SW5 SW6 SW7 SW8 SW9 SW10 SW11 SW12
* TG_MOUSE
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW13 SW14 SW15 SW16 SW17 SW18 SW19 SW20 SW21 SW22 SW23 SW24
* DRG_SCRL BTN2 BTN3 BTN1 BTN1 BTN3 BTN2 DRG_SCRL
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW25 SW26 SW27 SW28 SW29 SW30 SW31 SW32 SW33 SW34 SW35 SW36
*
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
* SW37 SW38 SW39 SW40 SW41 SW42 SW43 SW44
* LShift LCtrl RCtrl RShift
* +---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+---------+
*/
[_MOUSE] = LAYOUT_1(
TG_MS, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX,
XXXXXXX, MY_SCRL, MS_BTN2, MS_BTN3, MS_BTN1, XXXXXXX, XXXXXXX, MS_BTN1, MS_BTN3, MS_BTN2, MY_SCRL, XXXXXXX,
XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX,
XXXXXXX, XXXXXXX, KC_LSFT, KC_LCTL, KC_RCTL, KC_RSFT, XXXXXXX, XXXXXXX
)
};
// -------------------------------------------------------------------------
// 変換、無変換を押した時間
static uint16_t pressed_henk_time = 0;
static uint16_t pressed_mhen_time = 0;
static uint16_t prev_pressed_henk_time = 0;
static uint16_t prev_pressed_mhen_time = 0;
static uint16_t mem_keycode = 0;
// キーイベントのコールバック
bool process_record_user(uint16_t keycode, keyrecord_t *record){
// タップ判定
uint16_t prev_keycode = mem_keycode;
bool is_tapped = ((!record->event.pressed) && (keycode == prev_keycode));
mem_keycode = keycode;
// キーに応じて
switch (keycode) {
case JP_MHEN: {
if (record->event.pressed) { // 無変換を押した
b_mhen = true;
prev_pressed_mhen_time = pressed_mhen_time;
pressed_mhen_time = record->event.time;
if (b_henk) {
layer_on(_NUM); // 変換も押されていれば _NUM on, _CURSOR off
layer_off(_CURSOR);
} else if (b_caps || b_ent) {
layer_on(_FUNC); // 変換は押されていなくて、CapsまたはEnter が押されているときは _FUNC on
if (!b_rsft) unregister_code(KC_RSFT);
if (!b_lsft) unregister_code(KC_LSFT);
} else {
layer_on(_CURSOR); // 変換もCapsもEnterも押されていいなければ _CURSOR on
}
}
else { // 無変換を離した
b_mhen = false;
if (b_henk) {
layer_off(_NUM); // 変換が押されたままなら _NUM off, _CURSOR on
layer_on(_CURSOR);
} else {
layer_off(_CURSOR); // 変換が押されていないなら _CURSOR off
if (b_caps) {
layer_on(_NUM); // Capsが押されているなら _NUM on
register_code(KC_RSFT);
} else if (b_ent){
layer_on(_NUM); // Enterが押されているなら _NUM on
register_code(KC_LSFT);
} else {
layer_off(_FUNC); // CapsもEnterも押されていないなら、_FUNC off
}
}
if (is_tapped && (TIMER_DIFF_16(record->event.time, pressed_mhen_time) <= TAPPING_TERM)) {
tap_code(keycode); // タップなら無変換
}
}
return false;
} break;
case JP_HENK: {
if (record->event.pressed) { // 変換を押した
b_henk = true;
prev_pressed_henk_time = pressed_henk_time;
pressed_henk_time = record->event.time;
if (b_mhen) {
layer_on(_NUM); // 無変換も押されていれば _NUM on, _CURSOR off
layer_off(_CURSOR);
} else if (b_caps || b_ent) {
layer_on(_FUNC); // 無変換は押されていなくて、CapsまたはEnterが押されているときは _FUNC on
if (!b_rsft) unregister_code(KC_RSFT);
if (!b_lsft) unregister_code(KC_LSFT);
} else {
layer_on(_CURSOR); // 無変換もCapsもEnterも押されていなければ _CURSOR on
}
}
else { // 変換を離した
b_henk = false;
if (b_mhen) {
layer_off(_NUM); // 無変換が押されたままなら _NUM off, _CURSOR on
layer_on(_CURSOR);
} else {
layer_off(_CURSOR); // 無変換が押されていないなら _CURSOR off
if (b_caps) {
layer_on(_NUM); // Capsが押されているなら _NUM on
register_code(KC_RSFT);
} else if (b_ent) {
layer_on(_NUM); // Enterが押されているなら _NUM on
register_code(KC_LSFT);
} else {
layer_off(_FUNC); // Capsが押されていないなら、_FUNC off
}
}
if (is_tapped && (TIMER_DIFF_16(record->event.time, pressed_henk_time) <= TAPPING_TERM)) {
tap_code(keycode); // タップなら変換
}
}
return false;
} break;
case KC_RSFT: {
if (record->event.pressed) {
b_rsft = true;
register_code(KC_RSFT);
} else {
b_rsft = false;
if (!b_caps) unregister_code(KC_RSFT);
}
return false;
} break;
case KC_LSFT: {
if (record->event.pressed) {
b_lsft = true;
register_code(KC_LSFT);
} else {
b_lsft = false;
if (!b_ent) unregister_code(KC_LSFT);
}
return false;
} break;
case MY_SCRL: {
set_scrolling = record->event.pressed;
return true;
} break;
default: {
} break;
}
return true;
}
// -------------------------------------------------------------------------
// タッチパッド
// スクロールスピード調整
#define SCROLL_DIVISOR 20.0
// スクロール量
static float scroll_accumulated_h = 0;
static float scroll_accumulated_v = 0;
// 最後に移動した時間
static uint16_t last_move_time;
// BTN2を無効にする経過時間
const uint16_t ms_btn2_cancel_time = 2000;
// 移動スピード
#define SPEED_NORMAL 1.0 // 普通
#define SPEED_MIDDLE 2.5 // 少し早い
#define SPEED_HIGH 3.0 // 早い
#define speed_sw_ct_mid 20 // 少し早くする移動量
#define speed_sw_ct_high 50 // 早くする移動量
// 移動方向を判定する最小移動量
#define detect_ct_on 5
// 前回判定した方向
static char prev_d = ' ';
// 判定した方向の履歴
static char direct_hist[5];
// タッチパッドのセンサーデータのコールバック
report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
// レポートの移動量
int16_t cur_x = mouse_report.x;
int16_t cur_y = mouse_report.y;
int16_t cur_h = mouse_report.h;
int16_t cur_v = mouse_report.v;
// 移動方向判断用
int16_t ax;
int16_t ay;
char dx;
char dy;
char cur_d;
// 時計回り判断用
char R1[5] = "RULD";
char R2[5] = "DRUL";
char R3[5] = "LDRU";
char R4[5] = "ULDR";
// 反時計回り判断用
char L1[5] = "DLUR";
char L2[5] = "RDLU";
char L3[5] = "URDL";
char L4[5] = "LURD";
// 移動スピード
float mouse_speed = SPEED_NORMAL;
// 最後に移動した時間
if (cur_x != 0 || cur_y != 0 || cur_h != 0 || cur_v != 0) {
last_move_time = timer_read();
}
// 横移動判断
if (cur_x < 0) {
ax = -cur_x;
dx = 'L';
} else if (cur_x > 0) {
ax = cur_x;
dx = 'R';
} else {
ax = 0;
dx = ' ';
}
// 縦移動判断
if (cur_y < 0) {
ay = -cur_y;
dy = 'U';
} else if (cur_y >0) {
ay = cur_y;
dy = 'D';
} else {
ay = 0;
dy = ' ';
}
// 移動量が閾値以上なら判定履歴に入れる
if ((ax >= detect_ct_on) || (ay >= detect_ct_on)) {
if (ax > ay) {
cur_d = dx;
} else {
cur_d = dy;
}
} else {
cur_d = 'X';
}
if (prev_d != cur_d) {
direct_hist[3] = direct_hist[2];
direct_hist[2] = direct_hist[1];
direct_hist[1] = direct_hist[0];
direct_hist[0] = cur_d;
prev_d = cur_d;
if (strcmp(direct_hist, R1) == 0 || strcmp(direct_hist, R2) == 0 || strcmp(direct_hist, R3) == 0 || strcmp(direct_hist, R4) == 0) {
// 時計回りなら _MOUSEレイヤーをonにする
layer_on(_MOUSE);
} else if (strcmp(direct_hist, L1) == 0 || strcmp(direct_hist, L2) == 0 || strcmp(direct_hist, L3) == 0 || strcmp(direct_hist, L4) == 0) {
// 反時計回りなら _MOUSEレイヤーをoffにする
layer_off(_MOUSE);
}
}
// なぜかポインタ移動終了の3秒後くらいに右クリックが来るので、それを無効化
if (mouse_report.buttons && MOUSE_BTN2) {
if (timer_elapsed(last_move_time) > ms_btn2_cancel_time) {
mouse_report.buttons &= ~MOUSE_BTN2;
}
}
// ドラッグスクロール と スクロールスピード調整
if (set_scrolling) {
scroll_accumulated_h += (float)cur_x / SCROLL_DIVISOR;
scroll_accumulated_v += (float)cur_y / SCROLL_DIVISOR;
mouse_report.x = 0;
mouse_report.y = 0;
} else {
if (ax >= speed_sw_ct_high || ay >= speed_sw_ct_high) {
mouse_speed = SPEED_HIGH;
} else if (ax >= speed_sw_ct_mid || ay >= speed_sw_ct_mid) {
mouse_speed = SPEED_MIDDLE;
} else {
mouse_speed = SPEED_NORMAL;
}
scroll_accumulated_h += (float)cur_h / SCROLL_DIVISOR;
scroll_accumulated_v += (float)cur_v / SCROLL_DIVISOR;
mouse_report.x = (int8_t)((float)cur_x * mouse_speed);
mouse_report.y = (int8_t)((float)cur_y * mouse_speed);
}
mouse_report.h = (int8_t)scroll_accumulated_h;
mouse_report.v = (int8_t)scroll_accumulated_v;
scroll_accumulated_h -= (int8_t)scroll_accumulated_h;
scroll_accumulated_v -= (int8_t)scroll_accumulated_v;
return mouse_report;
}
おわりに
FreeCADでのケース設計が面白かったので、また作ってみたいと思います。
最近、USBマグネットに金属くずが付いていたのに気が付かず繋いで、2回続けてRP2040-zeroを壊してしまい、修理に苦労しました。次作るときは、コンスルー+押さえ具など、簡単に修理できる構造にしたいと思います。
Discussion
記事と関係ないことですが、すみません。
新しい入力方法を私も模索しておりまして、今回、マウスアナログ入力のテキストコピーソフトを作成したので、ぜひ使ってほしいと思い、ここに書いてます。もちろん無料です。https://zenn.dev/kemii/articles/7d6d9331cbaa0d
コメントありがとうございます。
マウスジェスチャーを記憶しようとすると、ご紹介頂いたような方法があるのですね。
難しそうですが、そういった手法に取り組む際は参考にさせていただきたいと思います。
反応が遅くなりましてすみませんでした。