多機能マウスを自作する。

ラズパイピコ

 Raspberry Pi Picoを使って多機能マウスを自作しました。ホイールを2つつけて、表計算ソフトを上下・左右・拡大縮小できるようにしています。また、マウス操作で画面のスクリーンショットを撮り、画像ファイルとして保存できるようにしました。

広告

使用する電子部品

 光学式マウスセンサーについてはこちらの記事と同じもの(MX8650A)を使用しました。またホイールについても、今回は既存のマウスから取り出したものを使用しました。ホイール(ロータリーエンコーダ)は秋月電子等でも買えるようです。

電子部品個数備考
1個Raspberry Pi Pico
1個光学式マウスセンサー
ダイ○ーで買ってきたマウスから取り出しました。センサ(MX8650)自体に加えて光学レンズ、10μF&100μFのコンデンサ、LED、抵抗も使われているものをそのまま使用。
2個ロータリーエンコーダ(マウスホイール)
今回は上記と一緒に市販のマウスから取り出したものを使用しました。
4個タクトスイッチ
左右のクリックボタン、ホイールの押しボタン用に4個使用しました。

配線・構成

 光学マウスセンサーのMX8650については、こちらで解説しているので細かい説明は省略します。今回はロータリーエンコーダやタクトスイッチと一緒に下記のように構成しました。タクトスイッチは、マウスの左クリック、右クリック、ホイール(ロータリーエンコーダ)の押し込み(2個)に対応させています。

CircuitPythonのコード

 光学マウスセンサーのMX8650を動かすコード、マウスホイール(ロータリエンコーダ)の動きを検知するコード、タクトスイッチ等を制御するコードに別れています。MX8650は上記でも書いている通り別の記事にもしていますが、コードを再掲します。

#OptiMouse.py

import digitalio
from board import *
import time

class OptiMouse:
    
    sclkPin = digitalio.DigitalInOut(GP19)
    sdioPin = digitalio.DigitalInOut(GP18)
    
    #コンストラクタ
    def __init__(self):        
        self.sclkPin.direction = digitalio.Direction.OUTPUT
        self.sdioPin.direction = digitalio.Direction.INPUT
        self.sdioPin.pull = digitalio.Pull.DOWN
        
    #micro秒sleep
    def microsleep(self, microsec):
        now = time.monotonic_ns()
        flag=True
        while flag==True:
            if (now+microsec*1000) < time.monotonic_ns():
                flag=False
    
    #MX8650の開始
    def begin(self):
        self.sclkPin.direction = digitalio.Direction.OUTPUT        
        #センサを同期させる
        self.sclkPin.value=1
        self.microsleep(5)
        self.sclkPin.value=0
        self.microsleep(1)
        self.sclkPin.value=1
        self.microsleep(1000000)

    #MX8650からデータを読み込む
    def readRegister(self, address):
        self.sclkPin.direction = digitalio.Direction.OUTPUT        
        #アドレスを書き込む
        self.sdioPin.direction = digitalio.Direction.OUTPUT  
        for i in reversed(range(8)):
            self.sclkPin.value=0
            self.sdioPin.value=(address & (1 << i))
            self.sclkPin.value=1
        #データを読み出す
        self.sdioPin.direction = digitalio.Direction.INPUT
        self.sdioPin.pull = digitalio.Pull.DOWN
        #self.microsleep(0)
        r = 0
        for i in reversed(range(8)):
            self.sclkPin.value=0
            self.sclkPin.value=1
            r |= (self.sdioPin.value << i)
        self.microsleep(100)
        return r;

    #MX8650にデータを書き込む
    def writeRegister(self, address, data):
        self.sclkPin.direction = digitalio.Direction.OUTPUT  
        #MSB(最上位ビット)をhighにし書き込みモードにする
        address |= 0x80
        #アドレスを書き込む
        self.sdioPin.direction = digitalio.Direction.OUTPUT 
        for i in reversed(range(8)):
            self.sclkPin.value=0
            self.sdioPin.value=(address & (1 << i))
            self.sclkPin.value=1
        #データを書き込む
        for i in reversed(range(8)):
            self.sclkPin.value=0
            self.sdioPin.value=(data & (1 << i))
            self.sclkPin.value=1
#RotaryEnc.py

import digitalio
from board import *
import time

class RotaryEnc:
        
    #ロータリーエンコーダのピン
    rtrPinA = digitalio.DigitalInOut(GP0)
    rtrPinB = digitalio.DigitalInOut(GP1)

    #ロータリーエンコーダからデータを読み込む
    state=0
    exstate=0
    direction=0
    val=0
    
    #コンストラクタ
    def __init__(self, num):
        #ロータリーエンコーダのピンを割り当て治す
        self.rtrPinA.deinit()
        self.rtrPinB.deinit()
        if num==0:
            self.rtrPinA = digitalio.DigitalInOut(GP13)
            self.rtrPinB = digitalio.DigitalInOut(GP14)
        else:
            self.rtrPinA = digitalio.DigitalInOut(GP22)
            self.rtrPinB = digitalio.DigitalInOut(GP12)
        #ピンをプルアップでインプットモードにする
        self.rtrPinA.direction = digitalio.Direction.INPUT
        self.rtrPinA.pull = digitalio.Pull.UP
        self.rtrPinB.direction = digitalio.Direction.INPUT
        self.rtrPinB.pull = digitalio.Pull.UP

    #ロータリーエンコーダーの動作を取得
    def readRotaryEnc(self):
        if self.rtrPinA.value==0 and self.rtrPinB.value==0:
            self.state=0
        elif self.rtrPinA.value==1 and self.rtrPinB.value==0:
            self.state=1
        elif self.rtrPinA.value==0 and self.rtrPinB.value==1:
            self.state=2
        else:
            self.state=3
        #読み取り時間を得るために少し待つ
        time.sleep(0.001)
        if self.state != self.exstate:
            if self.val==0:
                if (self.exstate == 0 and self.state == 1) or (self.exstate == 1 and self.state == 3) or (self.exstate == 2 and self.state == 0) or (self.exstate == 3 and self.state == 2):
                    self.direction=1
                    self.val=self.direction
                elif (self.exstate == 0 and self.state == 2) or (self.exstate == 1 and self.state == 0) or (self.exstate == 2 and self.state == 3) or (self.exstate == 3 and self.state == 1):
                    self.direction=-1
                    self.val=self.direction
            self.exstate=self.state
        else:
            self.direction=0
            self.val=0
import usb_hid
from adafruit_hid.mouse import Mouse
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
import time
import digitalio
from board import *

import OptiMouse
import RotaryEnc

#電源
vddPin = digitalio.DigitalInOut(GP17)
vddPin.direction = digitalio.Direction.OUTPUT
vddPin.value=True

#マウスの準備
mouse = Mouse(usb_hid.devices)
mx8650=OptiMouse.OptiMouse()
time.sleep(0.1)
mx8650.begin()
time.sleep(0.1)
#CPIを変更する(6はCPI1200)
mx8650.writeRegister(0x06,6)
time.sleep(1)

#ロータリーエンコーダの準備
reL = RotaryEnc.RotaryEnc(0)
reR = RotaryEnc.RotaryEnc(1)

#キーボードの準備(2個目のマウスホイールは、キーボードのショートカットキーと対応させている)
keyboard = Keyboard(usb_hid.devices)

#ボタンの準備
#左ボタン
btnL = digitalio.DigitalInOut(GP10)
btnL.direction = digitalio.Direction.INPUT
btnL.pull = digitalio.Pull.UP
clkflagL=0
#右ボタン
btnR = digitalio.DigitalInOut(GP21)
btnR.direction = digitalio.Direction.INPUT
btnR.pull = digitalio.Pull.UP
clkflagR=0
#左ホイールボタン
btnHL = digitalio.DigitalInOut(GP11)
btnHL.direction = digitalio.Direction.INPUT
btnHL.pull = digitalio.Pull.UP
clkflagHL=0
#右ホイールボタン
btnHR = digitalio.DigitalInOut(GP20)
btnHR.direction = digitalio.Direction.INPUT
btnHR.pull = digitalio.Pull.UP
clkflagHR=0

#スクリーンショットを撮る
def takeScrShot():
    #windowsでスクショを撮って保存するショートカット
    """
    keyboard.press(Keycode.WINDOWS)
    keyboard.send(Keycode.PRINT_SCREEN)
    keyboard.release(Keycode.WINDOWS)
    """
    #ubuntuでスクショを撮って保存するショートカット
    keyboard.press(Keycode.SHIFT)
    keyboard.send(Keycode.PRINT_SCREEN)
    keyboard.release(Keycode.SHIFT)
    
while True:        
    #ロータリーエンコーダの検出
    reR.readRotaryEnc()
    reL.readRotaryEnc()
    #マウスセンサーの検出
    motionstatus=mx8650.readRegister(0x02)
    time.sleep(0.001)
    s8dx=mx8650.readRegister(0x03)
    s8dy=mx8650.readRegister(0x04)
    dx=-(s8dx & 0b10000000) | (s8dx & 0b01111111)
    dy=-(s8dy & 0b10000000) | (s8dy & 0b01111111)
    if reR.val!=0:
        #右ホイールの動作
        keyboard.press(Keycode.SHIFT)
        mouse.move(0, 0, reR.val)
        keyboard.release(Keycode.SHIFT)
        rebtncntr = 0
    elif reL.val!=0:
        #左ホイールの動作
        mouse.move(0, 0, reL.val)
        rebtncntr = 0
    else:
        #マウスとしての動作    
        mouse.move(dy, dx, 0)
        #ボタンの動作検知
        #左ボタン
        if btnL.value==0:
            if clkflagL==0:
                mouse.click(mouse.LEFT_BUTTON)
            else:
                mouse.press(mouse.LEFT_BUTTON)
            clkflagL=1
        elif clkflagL==1:
            mouse.release(mouse.LEFT_BUTTON)
            clkflagL=0
        #右ボタン
        if btnR.value==0:
            if clkflagR==0:
                mouse.click(mouse.RIGHT_BUTTON)
            else:
                mouse.press(mouse.RIGHT_BUTTON)
            clkflagR=1
        elif clkflagR==1:
            mouse.release(mouse.RIGHT_BUTTON)
            clkflagR=0
        #左ホイールボタン
        if btnHL.value==0:
            if clkflagHL==0:
                #左右のホイールボタンが両方押されればスクリーンショットを撮る
                if clkflagHR==1:
                    takeScrShot()
                else:
                    mouse.click(mouse.MIDDLE_BUTTON)
            else:
                mouse.press(mouse.MIDDLE_BUTTON)
            clkflagHL=1
        elif clkflagHL==1:
            mouse.release(mouse.MIDDLE_BUTTON)
            clkflagHL=0
        #右ホイールボタン
        if btnHR.value==0:
            if clkflagHR==0:
                #左右のホイールボタンが両方押されればスクリーンショットを撮る
                if clkflagHL==1:
                    takeScrShot()
                else:
                    keyboard.press(Keycode.CONTROL)
            clkflagHR=1
        elif clkflagHR==1:
            keyboard.release(Keycode.CONTROL)
            clkflagHR=0

vddPin.value=False #出力LOW   

動作の様子

 マウスの筐体は3dプリンターで作成しました。機能としては上記の配線・プログラムで動作するのですが、クリックの感触が硬いなど、使い心地はまだまだ改善が必要な感じです。インターフェースはこだわるとキリが無さそう。

コメント

タイトルとURLをコピーしました