6. ルービックキューブ解法マシンの作成

# 目的

実物のルービックキューブを物理的に回転させるマシンを作成しました。
ここでいう「回転」とは、『キューブの6面を独立に「時計回り」「反時計回り」に 90度単位で 自由に回転させる』ことを指します。



# 使用キューブ

最終的に、中国メーカーの「Gan 365 i Carry 2」キューブを使用しました。
_アリエクで購入 3,167円(送料無料)_

<img width="200" alt="gan-i-carry2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/e81e1c36-9617-4dad-871c-bac34b8da0e9.png" />

このキューブを使用した理由は、センターキャプに 4つのくぼみがあり、ここをつまんでセンターキューブを回すと、その面の他8個のキューブが一緒に回転する構造になっているためです。

<img width="200" alt="center-cap.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/ef751d78-6468-4883-8ac3-6149d9ef084f.png" />


他のキューブも試しましたが、面が回りません。
これらは、センターキューブをつまんで回すと、センターキューブだけが回ってしまい 面が回転しませんでした。

    × 公式ルービックキューブ ver2.1
    × 公式ルービックスピードキューブエントリー
    × GAN 356M Lite Frost
    ○ GAN 356 i Carry 2



# 使用モーター

<img width="100" alt="17HS24-1206S.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/805341a1-486d-4141-ada6-d397b53f21c0.jpeg" />


トルクが有って、正確にステップを刻むことができる、NEMA17 等の 高価な ステッピングモーターを使うのが安全確実かと思うのですが、6面用に6個のモーターを購入する予算がありません。
そこで、安価な 12V仕様 28BYJ-48 ユニポーラー ステッピングモーターを使うことにしました。
_アクエリで購入 10個 1,892円(送料込み)_

[<img width="240" alt="28BYJ-48-W03" src="https://akizukidenshi.com/img/goods/L/116710.jpg" />](https://akizukidenshi.com/catalog/g/g116710/)

_90°回すのに 約1秒かかりますが、今回は モーター性能よりもパーツ代節約を優先しました。_

モーター制御基板(ULN2003)は、5V仕様をそのまま使用しています。搭載しているトランジスタアレイ`ULN2003AN`は耐圧40Vのため、問題ありません。
_アクエリで購入 10個 643円(送料込み)_

[<img width="200" alt="28BYJ-48-pcb.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/f39cd229-ad64-4461-95fd-1686d9a56cc3.png" />](https://electronicwork.shop/items/62864f168d26fa06d0b3222e)



# パネル、センターキャップつまみ

`パネル`（モーターステー） と `センターキャップつまみ` は自分で設計して、3Dプリンタで印刷しました。


| 部位 | イメージ | STLファイルURL |
|:-:|:-:|:-:|
| パネル<br>（モーターステー）  | <img width="300" alt="facePanel.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/7a11b9d4-3c8c-4cdd-bea5-3af7822cf3e8.png" /> | [https://nak435.com/RubiksCube/STL/facePanel.stl](https://www.viewstl.com/?url=https://nak435.com/RubiksCube/STL/facePanel.stl)  |
| センターキャップつまみ  | <img width="100" alt="cubeJoint.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/b303151c-acf7-4c8e-9d28-0d1dfa3c1d27.png" />|  [https://nak435.com/RubiksCube/STL/cubeJoint.stl](https://www.viewstl.com/?url=https://nak435.com/RubiksCube/STL/cubeJoint.stl) |
| パネル、モーター、ツマミ<br>を組み立てたイメージ| <img width="150" alt="assembled.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/145d8f18-a98a-4863-8643-daf3000f870e.png" />  | [https://nak435.com/RubiksCube/STL/assembled.stl](https://www.viewstl.com/?url=https://nak435.com/RubiksCube/STL/assembled.stl)  |
| パネル6枚を<br>組み立てたイメージ  |<img width="300" alt="cubeFace.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/ff7757a3-9368-46b2-ac65-07ccf99849ea.png" /> | [https://nak435.com/RubiksCube/STL/cubeFace.stl](https://www.viewstl.com/?url=https://nak435.com/RubiksCube/STL/cubeFace.stl)  |
| モーター制御基板(ULN2003)スペーサー  |<img width="200" alt="ULN2003_Spacer.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/69f1c93b-0404-45fd-8bfb-86a37b9baa0e.png" /> | [https://nak435.com/RubiksCube/STL/ULN2003_Spacer.stl](https://www.viewstl.com/?url=https://nak435.com/RubiksCube/STL/ULN2003_Spacer.stl)  |



# モーター制御

28BYJ-48 モーターの制御は「1-2相励磁」方式を採用。
Python コードは ↓こちらのサイトを参考にさせていただきました。

https://logikara.blog/pico-stepping/



`Raspberry Pi Pico`を使用し、6個のモーターを制御するため（4pin / モーター = ）24ピンを占有します。
<br>

#### ・ ピンアサイン；

| モーター<br>（制御基板） | 面 | IN1 | IN2 | IN3 | IN4 |
|:-:|:-:|:-:|:-:|:-:|:-:|
| #1 | F | GP0  | GP1  | GP2  | GP3  |
| #2 | D | GP4  | GP5  | GP6  | GP7  |
| #3 | R | GP8  | GP9  | GP10  | GP11  |
| #4 | L | GP15  | GP14  | GP13  | GP12  |
| #5 | U | GP16  | GP17  | GP18  | GP19  |
| #6 | B | GP20  | GP21  | GP22  | GP26  |

<img width="440" alt="pico-pin.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/2b5e4a5e-babc-4bbf-9ff7-c46fab55f142.png" />



# キューブ 回転 プログラム

前述のサイトのコードを参考に、MicroPython で書きました。

```python:main.py（キューブの回転）
from machine import Pin
import utime

motors_pos = { 'F':0, 'D':1, 'R':2, 'L':3, 'U':4, 'B':5 }
motors_pin_number = [
#   IN1 IN2 IN3 IN4
    [ 0,  1,  2,  3], #F
    [ 4,  5,  6,  7], #D
    [ 8,  9, 10, 11], #R
    [15, 14, 13, 12], #L
    [16, 17, 18, 19], #U
    [20, 21, 22, 26]  #B
]
motors_pin = [[Pin(pin, Pin.OUT) for pin in pins] for pins in motors_pin_number]

# 1-2 phase excitation patterns
puls_pattern = [
    [1, 0, 0, 0],
    [1, 1, 0, 0], 
    [0, 1, 0, 0], 
    [0, 1, 1, 0],
    [0, 0, 1, 0], 
    [0, 0, 1, 1], 
    [0, 0, 0, 1], 
    [1, 0, 0, 1]
]
last_index = len(puls_pattern)

# 正転の方向
motors_dir = [
# True: CW
# False: CCW
#   F      D      R      L      U      B
    False, False, False, False, False, False 
]

motors_steps = [
#    CW    x2    CCW   x2
    [1024, 2048, 1024, 2048], #F
    [1024, 2048, 1024, 2048], #D
    [1034, 2048, 1026, 2048], #R
    [1038, 2048, 1024, 2048], #L
    [1024, 2048, 1024, 2048], #U
    [1024, 2048, 1024, 2048], #B
]

interval_ms = 1 #(ms)

def drive_motor(motor, direction:bool, step):
    motor_pin = motors_pin[motor]
    pattern_number = (last_index - 1) if direction else 0

    for _ in range(step):
        pattern_number += 1 if direction else -1
        pattern_number %= last_index

        for n in range(4): # IN1-2-3-4
            motor_pin[n].value(puls_pattern[pattern_number][n])
        utime.sleep_ms(interval_ms)

    # motor power off
    for n in range(4): motor_pin[n].value(0)
    
def checkFaceLetter(c) -> bool:
    return (c in motors_pos) 

prev_s, s = '', ''
prev = False
try:
    while True:
        print('> ', end='')
        if prev:
            s = prev_s
            print(s)
        else:    
            prev_s = s
            s = input().upper()
        
        item = s.split(' ')
        if len(item) == 0: continue
        if len(item[0]) == 0: continue
        if item[0] == 'S':
            if not checkFaceLetter(item[1]):
                print(item[0], 'invalid item 2', item[1])
                continue
            motor = motors_pos[item[1]]
            step = int(item[2])
            if not motors_dir[motor]: step = -step
            dir = step > 0
            if step < 0: step = -step # step = abs(step)
            drive_motor(motor, dir, step)

        elif item[0] == 'Q': #quit
            break 
        elif item[0] == 'Y':
            prev = True
            continue
        else: # face turns
            if not checkFaceLetter(item[0][0]):
                print('invalid item 1', item[0][0])
                continue
            motor = motors_pos[item[0][0]]
            steps = motors_steps[motor]
            dir = motors_dir[motor]
            if len(item[0]) == 1:
                dirn = 0 if dir else 2
                drive_motor(motor, dir, steps[dirn])
                
            elif len(item[0]) == 2:
                if item[0][1] == "'": # reverse
                    dir = not dir
                    dirn = 0 if dir else 2
                    drive_motor(motor, dir, steps[dirn])
                elif item[0][1] == "2": # x2
                    dirn = 0 if dir else 2
                    drive_motor(motor, dir, steps[dirn + 1])
                else:
                    print('invalid command', item[0][1])

            else:
                print('?', item[0])

        prev = False

except Exception as e:
    print(e)
    import sys
    print(sys.print_exception(e))
    
    import machine
    machine.soft_reset() #restart
```

**説明；**

- モーター仕様によると 90°回転はステップ数=1024、180°回転はステップ数=2048ですが、モーター個体差、正転/逆転差などを考慮して、モーターごとにステップ数を保持して 微調整できるようにした<br>　
- 標準入力から「回転記号」を受け取り、指示されたモーターを回転させる。<br>「回転記号」は「面の回転」を示す、次の18コマンドのみをサポート

	| 正転90° コマンド | U | R | F | D | L | B |
	|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
	| 逆転90° コマンド | U' | R' | F' | D' | L' | B' |
	| 正転180° コマンド | U2 | R2 | F2 | D2 | L2 | B2 |
<br>

- 「回転記号」以外に `S`, `Y`, `Q`コマンドをサポート　

    - `S`コマンド

        モーター位相を原点リセットさせる機能を持っていないため、<br>`S`コマンドを使用して、目視で位置合わせする

	    ```text
	    S face [-]steps
	        face: U | R | F | D | L | B
	        steps:  1 〜（正数） 正転させるステップ数
	               -1 〜（負数） 逆転させるステップ数
	    ```
	<br>
    
    - `Y`コマンド

        直前のコマンドを再実行<br>　

    - `Q`コマンド

        Quit（無限ループから抜ける）





# モーターの電源

DC12V仕様ですが、少しでもトルクを稼ぐために、安定化電源装置から14Vを印加しました（効果は不明ですが）。
また、モーター回転を停止した時点で、IN1=2=3=4=0 として 電源をOFFにします。今回の使い方では、静止時トルクは不要なため、モーターをOFFとすることで 過度な発熱を防ぎます。

<img width="200" alt="IMG_0249.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/a3c290e5-aa65-4788-8889-14274cab0047.png" /> ![IMG_0249-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/376ae6a6-accb-4503-ae11-cb1efd535c12.jpeg)


モーター回転中の電圧は 13.5V, 電流 0.15A 程度でした。 




# 動作風景

[前回の記事](https://qiita.com/nak435/items/f7658e0932ba4a07f13c)で書いた ルービックキューブアプリ に [シリアル通信](https://swiftpackageindex.com/armadsen/ORSSerialPort)を追加して、アプリから 上記コマンドを送信することで アプリと連動させています。

### ・ 崩す

<iframe width="323" height="575" src="https://www.youtube.com/embed/YWY8UgaLdVA" title="ルービックキューブ回転マシン（崩す）" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

<br>ご覧のとおり、このキューブは 多少の回転不足があっても、引っかかりながら回転させることができます。マシンの弱点をキューブが補ってくれる優秀なキューブです。
とはいっても、回転ステップ数を調整し なるべく引っかかりを抑えたいと思います。


### ・ 揃える

前項の 崩した 状態から 揃えます。

_単なる逆再生ではなく、最適手順で揃えます（崩し30手に対し、揃え21手）_
_[以前の記事](https://qiita.com/nak435/items/b44254bd31296c4a5905)で紹介した、[kociemba](https://github.com/muodov/kociemba) を使用しています。_



<iframe width="323" height="575" src="https://www.youtube.com/embed/ENkRY6oYX0o" title="ルービックキューブ回転マシン（揃える）" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>



# 見栄え


透明(?) PLA を使用して出力しましたが、6枚のパネルが至近距離でキューブを取り囲んでいるので、見栄えがよくありません。
モーターの軸を延長し、キューブ全体が見通せるぐらい 距離を空けるようにするか、透明なアクリルパネルで作成すれば、見栄えが良くなると思います。

_なお、ルービックキューブ 自体には、何の加工も施していません。_

<br><br>


<iframe width="323" height="323" src="https://www.youtube.com/embed/FSnV5SK0zvc" title="ルービックキューブ回転マシン（アクリルパネル版）" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

_せっかくなので、透明アクリルパネルで作り直しました。見えやすい。。。_



# 並列動作化

対面するモーター（`U`と`D`、`R`と`L`、`F`と`B`）は、互いに面が干渉しないため、同時に回すことができます。
そこで、Raspberry Pi Pico が搭載する RP2040 の 2つのコアを使って、並列動作化しました。
それほど、登場する場面は多くありませんが、全体時間の短縮に役立ちます。

<iframe width="420" height="420" src="https://www.youtube.com/embed/-WegkXkMh5w" title="ルービックキューブ回転マシン（並列動作化）" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

以前は、シリアル通信で一手づつ送り込んでいましたが、並列化の実装にあたり、一度に全部を送信する方式に変更したことで、一手づつの通信が発生しない分だけ 若干ですが 動作時間が短縮されています。



# worldwide

["Rubik's Cube solving robot"](https://www.google.com/search?q=Rubik%27s+Cube+solving+robot) を検索すると、世界中で多くの方が 独自の方法で ルービックキューブ解法マシン を作成されています。
<br>

- 7 Rubik's Cube World Record Robots - Fastest & New Inventions
_少し古い動画ですが 変遷が分かります_

https://www.youtube.com/watch?v=GJKdbGAF9H0

- 「パズルキューブを最速で解くロボット」三菱電機
_つい先日まで世界最速でした（2024.5.21〜2025.5.11）_

https://www.youtube.com/watch?v=59qgzzSD1tk

- 現在の世界最速「米パデュー大学」_2025.5.12_

https://youtu.be/sXxh6QuY2ew


# さいごに

NEMA17 ステッピングモーターが買えるようになったら、もっと高速に回せるマシンを作りたいと思います。
さすがに 世界記録レベルは無理でしょうが、現在より10倍速い2秒程度を目指したい。。。


<br><br>**以上**