TOPに戻る

ラズパイ5 +Python+CANopenでモータを回す ⑬ モーション CiA 402の規格<Profile Position Mode (PPM)-後編> canppm2.py

 前回のプログラムcanppm.pyの一部をを関数化、整理しました。プログラム名はcanppm2.pyです。位置決め部分をmove()という関数にしたので、mainのプログラムが見通しが良くなりました。

 mainの最初で、

#resetNmt()
node.nmt.send_command(0x1)   # NMT start

 NMTのリセットをせずに、NMTをスタートさせるだけに変更しました。これは、MEXE02の接続が切れないための対策です。

import canopen
import time
import logging
# logging.basicConfig(level=logging.DEBUG)

# Start with creating a network representing one CAN bus
network = canopen.Network()

# Connect to the CAN bus
# sudo ip link set can0 up type can bitrate 1000000
network.connect(bustype='socketcan', channel='can0')
print("\n===start  ID=12 OrientalMotor===\n")

# Add some nodes with corresponding Object Dictionaries
node = canopen.BaseNode402(12,'Downloads/BLVD-KRD_CANopen_V200.eds')
network.add_node(node)

def resetNmt():
    # all nodes simulaneously as a broadcast message
    network.nmt.state = 'RESET'
    time.sleep(0.5)
    # Reset this node
    node.nmt.state = 'RESET'
    node.nmt.wait_for_bootup(15)
    node.nmt.state = 'RESET COMMUNICATION'
    node.nmt.wait_for_bootup(15)
    node.nmt.send_command(0x1)   # NMT start
    network.check()
    print('---node state = {0}\n'.format(node.nmt.state))

def startState_machine():
    node.setup_402_state_machine()
    node.sdo[0x6040].raw = 0x0010  # Fault Reset
    time.sleep(0.5)
    node.state = 'SWITCH ON DISABLED'
    time.sleep(0.1)  
    timeout = time.time() + 15
    node.state = 'READY TO SWITCH ON'
    while node.state != 'READY TO SWITCH ON':
        if time.time() > timeout:
            raise Exception('Timeout when trying to change state')
        time.sleep(0.01)
    time.sleep(0.1)  
    timeout = time.time() + 15
    node.state = 'SWITCHED ON'
    while node.state != 'SWITCHED ON':
        if time.time() > timeout:
            raise Exception('Timeout when trying to change state')
        time.sleep(0.001)
    time.sleep(0.1)  
    timeout = time.time() + 15
    node.state = 'OPERATION ENABLED'
    while node.state != 'OPERATION ENABLED':
        if time.time() > timeout:
            raise Exception('Timeout when trying to change state')
        time.sleep(0.001)
    time.sleep(0.1) 
    print("---SWITCH state ready\n") 
    network.check()
    print('---switch state = {0}\n'.format(node.state))

def move(targetPosition):
    node.rpdo[1]['Controlword'].phys  = 0x000f
    node.rpdo[1].transmit()
    time.sleep(0.02)
    node.rpdo[3]['Controlword'].phys  = 0x005f
    node.rpdo[3]['Target position'].phys  = targetPosition
    node.rpdo[3].transmit()
    time.sleep(0.02)

def dispParameter():
    Modes_of_Operation = node.sdo[0x6060].raw
    print('#Modes of Operation',Modes_of_Operation)
    Modes_of_Operation_Display  = node.sdo[0x6061].raw
    print('#Modes of Operation Display',Modes_of_Operation_Display,'\n')
    Target_Position = node.sdo[0x607a].raw
    print('#Target Position',Target_Position)
    Target_Velocity = node.sdo[0x60ff].raw
    print('#Target Velocity', Target_Velocity,'\n')
    Position_Actual_Value = node.sdo[0x6040].raw
    print('#Position Actual Value',Position_Actual_Value)
    Velocity_Actual_Value = node.sdo[0x606c].raw
    print('#Velocity Actual Value', Velocity_Actual_Value,'\n')
    Profile_velocity = node.sdo[0x6081].raw
    print('#Profile velocity',Profile_velocity)
    Profile_acceleration = node.sdo[0x6083].raw
    print('#Profile acceleration',Profile_acceleration)
    Profile_deceleration = node.sdo[0x6084].raw
    print('#Profile deceleration', Profile_deceleration,'\n')

def dispPosition():
    node.tpdo[3].wait_for_reception()
    print('Target position {:4d}'.format(node.tpdo[3]['Position actual value'].phys))

#----main----------------

#resetNmt()
node.nmt.send_command(0x1)   # NMT start
network.sync.start(0.1)  # 100ms
startState_machine()
time.sleep(0.2)

#node.sdo[0x6060].raw = 0x0001 #  Modes of operation <- Profile Position Mode
node.sdo[0x607a].raw = 0       # Target position
node.sdo[0x6081].raw = 3000    # Profile velocity
node.sdo[0x6083].raw = 1000    # Profile acceleration
node.sdo[0x6084].raw = 1000    # Profile deceleration

dispParameter()
dispPosition()

print(' ===start move===\n')
move(50000)
time.sleep(3)
dispPosition()

move(100000)
time.sleep(3)
dispPosition()

move(-150000)
time.sleep(3)
dispPosition()

#-------------------
print("\n---closed---")
node.sdo[0x6040].raw = 0x0080  # Fault Reset
node.nmt.send_command(0x02)  # NMT remote stop
node.nmt.send_command(0x81)  # NMT reset
time.sleep(0.5)
node.nmt.send_command(0x82)  # NMT Reset Communication
network.sync.stop()
network.disconnect()

 実行した様子です。

減速を変える

 回転を実施して読み出したTarget positionがきっちした値になっていません。3秒待ってというのも根拠がないのですが、ほぼ、このぐらいの回転数だと落ち着いた位置になっているから採用しています。本来なら、目標位置に到達したというのをステータスのいずれかのビットで判断するというのが正しいはずですが、うまい事例が見つかっていません。

 減速の加速度がおおきすぎたのではないかと思い、500、200、50と変更して実行しました。500と200は少し最後の位置が0に近くなりましたが、50ぐらいの誤差があります。20では逆に200ぐらいにずれが多くなりました。

  Profile deceleration 1000

 では、Profile velocity 3000を1000と低速にしましたが、改善されません。

 0に近づけるには、別な複雑な組み合わせがあるのかもしれません。

◆関連があるかもしれない

Position window (6067h) (現在の設定値 18)

現在の目標位置(Target position +(Position offset)に対して、現在位置(Position actual value)が到達したとみなす位置の差を設定します。位置エンコーダの実際の値がこの位置ウィンドウ内にある場合、目標位置に到達したと見なされます。

 初期設定時に次の1行を追加して設定を変更してみました。

node.sdo[0x6067].raw = 6  # Position window default:18

 実行しても、ほとんど位置に変化はありませんでした。

◆ちょっと関連があるかもしれないが実験していない

Following error window (6065h)

Position demand value (6062h)と Position actual value (6064h)の差に対して、Statusword (6041h)
の Following error (bit 13)を 1 にする閾値を設定します。単位はユーザー指定単位です。

実行時に軸を手でつまむ

 動作時に、軸を手でつまんで負荷がかかるようにしました。main部分のプログラムを修正して、連続的に0->30000->-30000とTarget positionを変化させています。resetNmt()を実行すると、MEXE02の通信が止まってしまうので、node.nmt.send_command(0x1)   # NMT startだけを実行しました。

 回転しているとき、軸を手でつまむと、トルクに変動が見られます。

 位置、速度に関してPID制御が、トルクに対してもPID制御が行われていて、負荷が変動しても、目標位置へ正しく到達できるようになっています。

 EPOS4では、それらを制御するPやIのgainを含めた計算方法が、EPOS4 Application NotesのCONTROLLER ARCHITECTUREに解説があります。PやIを設定するオブジェクト・ディクショナリのindexも用意されています。ツールには、チューニングするソフトも用意されています。

 コントローラBLVD-KRDのオブジェクト・ディクショナリには、それらのindexはありません。

 EPOS4では、モータの種類、ギアの種類、エンコーダの種類が多く、組み合わせたときの性能が予測できないぐらい多岐にわたります。なので、チューニング・ツールで、適切なP、I、Dのgainなどを設定しないとうまく動かず、振動が出たり、電流が流れすぎたりしてしまいます。

 「BLMR5100K-A-B」 + 「BLVD-KRD」では、エンコーダのスペックも公表されていません。ギアは選択できます。それらを含めてチューニング済みの製品だと考えられます。

#----main----------------

#resetNmt()
node.nmt.send_command(0x1)   # NMT start
network.sync.start(0.1)  # 100ms
startState_machine()
time.sleep(0.2)

#node.sdo[0x6060].raw = 0x0001 #  Modes of operation <- Profile Position Mode
node.sdo[0x607a].raw = 0       # Target position
node.sdo[0x6081].raw = 3000    # Profile velocity
node.sdo[0x6083].raw = 1000    # Profile acceleration
node.sdo[0x6084].raw = 1000    # Profile deceleration

dispParameter()
dispPosition()

print(' ===start move===\n')
for i in range(10):
    move(30000)
    time.sleep(0.1)
    dispPosition()

    move(-30000)
    time.sleep(0.1)
    dispPosition()

 MEXE02の(m16) トレースモニタ機能を用いて動きを見ています。

 たとえば、搬送車などを作ったときに、上に乗せる荷物の分量によってどういう変動をしているかなどを確認できそうです。

連載 ラズパイ5 +Python+CANopenでモータを回す

(1) 構成と環境(オリエンタルモーター の「BLMR5100K-A-B」 + 「BLVD-KRD」)

(2) サポート・ソフト MEXE02

(3) PythonでSDOの読み出し(仮)caninfo.py

(4) CANopenのベーシックな規格とSDO/PDO、オブジェクト・ディクショナリ<前編>

(5) CANopenのベーシックな規格とSDO/PDO、オブジェクト・ディクショナリ<後編>

(6) 二つの状態遷移(NMTとStatus Machine)

(7) CANバス信号を見る<前編>canreset.py candump0.py

(8) CANバス信号を見る<中編>Arduino

(9) CANバス信号を見る<後編>CANopenのデコードができ無償で使えるツールAnalog Devices TMCL-IDE

(10) モーション CiA 402の規格<Homing mode> canHome.py

(11) モーション CiA 402の規格<Profile Position Mode (PPM)-前編> canPPMread.py

(12) モーション CiA 402の規格<Profile Position Mode (PPM)-中編> caninfo2.py canppm.py

(13) モーション CiA 402の規格<Profile Position Mode (PPM)-後編> canppm2.py

(14) モーション CiA 402の規格<Profile Velocity Mode (PVM)> campvm.py