• カテゴリ 雑記 の最新配信
  • RSS
  • RDF
  • ATOM

ブログ - 雑記カテゴリのエントリ

自爆防止

カテゴリ : 
雑記
2024-10-10 10:40
車もコンピュータのアシストなしではエンジンすらかからない世で、何もかも人任せというのはあり得ず。
形態はラジコン型であっても、こちらもコンピュータが介在していることには違いは無く。しかし受信機のパルスを取り込んでモータの指令電圧に変換しているだけに過ぎず、スピコンと同等の事をしているだけ。
土俵センサで多少なりとも自爆を回避してくれさえすれば、人力ポーリングの負荷は軽減できるだろう。

技術

出稽古

カテゴリ : 
雑記
2024-10-8 11:20
あっという間に大会まで1週間を切り、季節も長袖の出番に。
大したフォローもできない中、今日も今日とて出稽古しながら切磋琢磨。
スピードもだんだん本番仕様に。

技術

UD.....

カテゴリ : 
雑記
2024-9-13 8:40
いつまで続くの灼熱の世。
台風のせい(おかげ)で1ヶ月の熟成期間ができてしまった。

テスターの松本殿も忙しい中お付き合い。
パラメータは緩いままだが、操縦感覚をつかむべく練習。
このサイズで150W 30Vオーバー 起動電流80A越えなモータ2本の運用はなかなかシビれるわん。
耐性は何もかもブスバー頼み。

出稽古しながら微調整に勤しんでおられる。

技術

UD....

カテゴリ : 
雑記
2024-8-25 19:50
外は暑いし実装は灼熱、動いてしまえばアンプは冷え冷え。
MPU変わるとこんなに違うか?
とにかくあと1週間。



技術

UD...

カテゴリ : 
雑記
2024-8-19 15:40
今更だが最後のご奉公のつもりでお絵かき。
基本的には前作と大差無く、気まぐれでRasPiを載せてみた。
が、火を入れたら瞬殺orz。


技術

DXL Yシリーズ試食3

カテゴリ : 
雑記
2024-7-2 16:40
またまた前回に引き続きYシリーズをお試し。

負荷をつなげていない状態で評価してもあまり意味は無いのだが、パワーが有り余って危険なのでご了承のほど。
その代わりと言っては何だが、最大加速度と急峻な指令値の変更を伴う運転を少し長めに行いつつ、温度を含めて観察しておいた。
ちなみに後ろで回っているCyberGearは比較用。


技術

DXL Yシリーズ試食2

カテゴリ : 
雑記
2024-6-28 17:50
前回に引き続きYシリーズをお試し。

ギア無しのモデルは台車の足回りの駆動に使うケースもあるかという事で、速度制御もやっておく。見てくれは先のコードと大差無く、動作モードと指令先が違うだけ。こちらも例に漏れず扱う数値が大きい。
import time
from pyDXL import *
dx = DXLProtocolV2('/dev/ttyAMA0', 57600, 0.05)
ID = 1
dx.Write8(ID, 512, 0)
if input('Clear? (y/N) ') == 'y':
dx.TxPacket(ID, DXLProtocolV2.INST_CLEAR, (0x01, 0x44, 0x58, 0x4c, 0x22))
dx.RxPacket(timeout = 5.0)
time.sleep(1)
dx.Write8(ID, 33, 1)
dx.Write8(ID, 512, 1)
for i in range(-550000, 550000, 550000 >> 4):
dx.Write32(ID, 528, i)
time.sleep(0.5)
dx.Write8(ID, 512, 0)
YM080-230-R099-RHもつなぎ、テキトウに位置決め制御と速度制御を行わせた動画を。簡単に動かすにしても机に置いただけだと暴れまくって危険なので、重しにくくりつけておいた。


技術

DXL Yシリーズ試食1

カテゴリ : 
雑記
2024-6-24 9:10
DXLシリーズはモデルが大きく変わると運用方法が変わってしまう。せめてモデルが違っていても同じアイテム名のアドレス程度は同じであって欲しいのだが、Yシリーズも例に漏れず他のモデルとの互換性は低い。
毎回ドキュメントを読み返すのも何なのでDX2LIBを真似てModel Noを元にグループ化し、一部の共通するアイテムのアドレスを引っ張り出せる簡単なクラスを作成

次に電源だが、既存との互換性は一切ない。それなりに大きいターミナルブロックが2箇所設けられているが、単独で消費する電流程度しかまかなえないので、DXLをまたぎながらマルチドロップ接続する事は避けた方が良さそうだ。またYM70とYM80ではターミナルブロックのサイズが異なるので、使い回しができない。
通信はRS-485という事で、電気的に既存のDXL用のI/Fが使える。シグナルGNDは電源と共通で、D+とD-のみの1.25mmピッチ2ピンのコネクタが用意されている。筐体のサイズに対してコネクタが華奢なのと、自前でツイストシールド線を使った場合のシールド設置処置が少々面倒。


今回は諸々の電気的な不都合を無視した上で4ピンのEHコネクタに変換するケーブルを作成し、例のRasPi Zero 2W + キャリアボードに接続。


Pingを叩いたら応答するので電源と通信はOK。しかしLEDが点滅し続ける状態でトルクイネーブルができない。コントロールテーブルばかりを眺めていたので見逃していたが、e-manualの冒頭に記載されているバックアップバッテリに関する注意書きを読み飛ばしていた。筐体に内蔵されているバックアップバッテリのリード線が外れているので接続。


しかしまだ動かない。Clear Errorを行わないといけないようだ。特殊なパケットだが書いてある通りのバイト列を送信するのみ、DXL Wizardを使うのであればその操作方法に従うまで。
 dx.TxPacket(ID, DXLProtocolV2.INST_CLEAR, (0x01, 0x44, 0x58, 0x4c, 0x22))
 dx.RxPacket(timeout = 5.0)
なおError Clearはステータスパケットの返答までの時間が4秒程度と長かったり、その処理の直後はインストラクションパケットを受け取らない期間があったりと変な癖が見つかったので、pyDXL.pyのRead関数にtimeoutの引数を追加しておいた。

ようやく下準備が整ったところで、まずは位置決め制御を行ってみる。その所作は他のモデルと大差無く、トルクディスエーブル→動作モード設定→トルクイネーブル→目標位置へ任意の値の書き込み、といった段取り。今回試したYM070-210-M001-RHの場合、360度の範囲で動かそうとすると最大分解能が19ビットとかなり大きい数値を扱う事になる。
import time
from pyDXL import *
dx = DXLProtocolV2('/dev/ttyAMA0', 57600, 0.05)
ID = 1
dx.Write8(ID, 512, 0)
if input('Clear? (y/N) ') == 'y':
dx.TxPacket(ID, DXLProtocolV2.INST_CLEAR, (0x01, 0x44, 0x58, 0x4c, 0x22))
dx.RxPacket(timeout = 5.0)
time.sleep(1)
dx.Write8(ID, 33, 3)
dx.Write8(ID, 512, 1)
for i in range(-262144, 262144, 512):
dx.Write32(ID, 532, i)
dx.Write8(ID, 512, 0)

といったところで1時間が経過したので一旦休憩、続きは次回に。

技術

PMXネタつながりで

カテゴリ : 
雑記
2024-4-25 9:50
事のついでで恐縮だが、キャリアボードの評価をお願いしている方からKO B3Mがマトモに動かないという指摘があったので、そのついでに作っておいた。10年前にこねくり回すのを諦めてから重い腰を上げて弄ったが、結局のところB3Mの癖は拭い切れないまま。

こちらも事のついでだが、少し毛色の違うサーボを引っ張り出して触っておいたレポートを。

外観が何かに酷似しているのは気のせいとして、サーボモータとして最低限の制御を行うついでに、テキストベースのプロトコルを被せているところが毛色の違いかと。ようするに俺サーボ。
適当なシリアルターミナル上でポチポチキーボードを打てばとりあえず動くが、プログラムからテキストで通信させるとなると却って面倒。こちらも例に漏れずPythonで記述するためのクラスが用意されているが、シリアル通信部分を隠蔽するだけで実質何もしておらず、結局正規表現任せ。

これを2個使い相互リンクをやらせてみた(単に互いの位置偏差を電流指令しているだけだが)。実機を持つ方はたかが知れているのでコードを晒すまでもないが、2軸への電流制御モード設定・ゲイン設定・電流指令・現在値取得が1パケットで済み、要点のみであれば実質数行で済む(PeriodicTimerは周期タイマクラス)。
#!/usr/bin/python3
#
# Link two servos with virtual wires

import os, platform, psutil, time, kbhit, serial, numpy as np
from PeriodicTimer import PeriodicTimer
from jp200class import packet

intervalms = 20

try:
pr = psutil.Process(os.getpid())
if platform.system() == 'Windows':
pr.nice(psutil.HIGH_PRIORITY_CLASS)
else:
pr.nice(-10)
except:
print('info:run with sudo')

# present value
present_dt = [('ang',float),('cur',float),('velo',float)]
present = np.empty(2, dtype = present_dt)
# calculated current command value
tcur = [0.0] * 2

#--------------------------------
# Periodically executed functioin
#--------------------------------
def cycfunc(*p):
sv = p[0]
acks = []
# deviation from each other's position
tcur = (
np.clip((present[1]['ang'] - present[0]['ang']) * 0.15, -1000.0, 1000.0),
np.clip((present[0]['ang'] - present[1]['ang']) * 0.15, -1000.0, 1000.0)
)
# send current command and current position acquisition to JP200
sv.request(
'#0EX=4SG2=9000;5500;10TC=%dTP=1000CACVCC'
'#1EX=4SG2=9000;5500;10TC=%dTP=1000CACVCC'
%(tcur[0], tcur[1]), acks
)
# analyze response
if acks:
for ack in acks:
m = sv.p2.findall(ack)
id = -1
for n in m:
try:
if n[0] == '#' and int(n[1]) >= 0: id = int(n[1])
elif n[0] == 'CA' and id >= 0: present[id]['ang'] = float(n[1])
elif n[0] == 'CC' and id >= 0: present[id]['cur'] = float(n[1])
elif n[0] == 'CV' and id >= 0: present[id]['velo'] = float(n[1])
except:
pass
if id >= 0: print('[%d] %4d %8d %5d %5d '%(id, tcur[id], present[id]['ang'], present[id]['velo'], present[id]['cur']), end = '')
print(end = '\r')

# instantiate kbhit
kb = kbhit.KBHit()
# open UART
ser = serial.Serial('/dev/ttyAMA0', 2000000)
print(ser.name)
# instantiate JP200
sv = packet(ser, 0.02)
# instantiate periodic timer & start
tm = PeriodicTimer(intervalms / 1000, cycfunc, [sv])

acks = []
k = ''
while k != 'q':
# waiting for key input
try:
k = kb.getch()
except UnicodeDecodeError:
pass
print(k, end = '\n', flush = True)

# move to origin position
if k == 'h':
tm.stop()
sv.request('#0EX=1TA=18000TP=1000#1EX=1TA=18000TP=1000', acks)
# stop timer
elif k == 's':
tm.stop()
tm.start()
# start timer
elif k == 'e':
tm.stop()
sv.request('#0EX=0#1EX=0', acks)
elif k == 'c':
tm.stop()
sv.request('#0EX=8TP=1000#1EX=8TP=1000', acks)
time.sleep(5)
sv.request('#0EX=8TP=-1000#1EX=8TP=-1000', acks)
time.sleep(5)
sv.request('#0EX=0#1EX=0', acks)
else:
print('XXXXXXXXX')
tm.stop()

sv.request('#0EX=0#1EX=0', acks, True)
ser.close()
kb.set_normal_term()
del sv, ser, kb

os._exit(0)
今回はRZ/V2LのDRP-AIを評価したベンチで使ったサーボをZero 2 Wでも試してみたに過ぎないが、こんな具合に使える(長いだけで大して面白くないので再生するまでもなく)。

事のついでと書いている大本の理由は、ここ最近XL330/XC330が軒並み通信できないベンチに遭遇して痛い目にあっており、動かすチャンスがあるときは一通り試しておかないと気が気でないものだから(XC330/XL330はボーレートの許容誤差が狭い)。

技術

KONDO PMX試食3

カテゴリ : 
雑記
2024-4-18 11:20
前回に引き続き小出し第3弾。

その前に、今までPMXのボーレートを3Mbpsとしていたが、バックグラウンドで動かしているアプリケーションによって受信データの欠損やズレが散見されていた。そのために態々リトライする関数を用意した訳だが。
原因を追求すべく色々試した結果、Zero 2の都合で時折受信データを取りこぼしていると考えられ、最終的にはボーレートにも依存するところまで判明した。OSが過負荷状態であっても、今のところ1.5Mbps以下であれば受信データを取りこぼさないようだ。

それでは本題に。
前回の漫画にどうしてXC330-T181-Tがつながっていたかの答えでもあるが、常々やってみたかったのが、異なる異メーカのサーボを同時に使うという少々危険なネタ。
これをやってみようと思ったのは、DXLに用意されているV1/V2の通信プロトコルを混在させている実例があったのと、PMXの通信プロトコルがB3MやKRSシリーズのプロトコルに比べ格段に堅牢になっていたからでもある。また単一メーカに依存して無い物ねだりする事を無くす事も狙い。
ちなみにB3MやKRSでは深く考えるまでもなく絶対的に無理、というか危険極まりないのでやってはならない。またDXLもV1とV2を混在させるとV1側の挙動がおかしくなるのは既知なので、実質的に無理。プロトコルの混在を実現するには、各サーボが自身には無関係なパケットを即時破棄する事と、自身に関係のあるパケットを取りこぼさないというそこそこ厳しい条件が求められる。それらは当然こちらでどうにかできる話ではないので、完全に自己責任のネタである。

では徐にやってみる。
PMXはID=0でボーレートを1Mbps、DXLはID=1でボーレートを1Mbpsとした。そもそもプロトコルが異なるのでお互いにIDが同じであっても構わないだろう。ライブラリは前回同様にpyPMX.pypyDXL.pyを使用。以下は1つのコードになっているか、PMXとDXLを各々個別のスレッドで処理させ、互いの存在は全く意識していない。オープンしたポートのインスタンスとLockを両スレッド共通に使用している。いずれも正弦波を元に位置決め制御を行いつつ、現在位置を取得してモニタ用の変数に書き込んでいる。ループ中には適当な時間のsleepを入れておいた方が良いだろう。
#!/usr/bin/python3
from pyPMX import *
from pyDXL import *
import math, kbhit, time, serial, threading

# Communication task with PMX
def func1(arg):
id = 0
# Class instantiation
pmx = PMXProtocol(arg[0], lock = arg[1])
# Setting initial conditions
pmx.MemREAD(id, 400, 6) # Clear error flag
pmx.MotorWRITE(id, 2, ()) # Torque free
pmx.MemWRITE8(id, 501, (1, 0b11111)) # Control mode (All)
pmx.MotorWRITE(id, 1, ()) # Torque enable
while arg[2]:
ang = int(math.sin(time.time()) * 9000.0)
r = pmx.MotorWRITE(id, 0, (ang,)) # Angle command
if r != None:
arg[3] = r[1][0]
time.sleep(0.001)
pmx.MotorWRITE(id, 2, ()) # Torque free
del pmx

# Communication task with DXL
def func2(arg):
id = 1
cnt = 0
led = 0
# Class instantiation
dx2 = DXLProtocolV2(arg[0], lock = arg[1])
# Setting initial conditions
dx2.Write8(id, 64, 1) # Torque enable
while arg[2]:
ang = int(math.sin(time.time()) * 1024.0) + 2047
dx2.Write32(id, 116, ang) # Angle command
r = dx2.Read32(id, 132, signed = True) # Read feedback values
if r != None:
arg[4] = r
cnt += 1
if cnt % 10 == 0:
led ^= 1
dx2.Write8(id, 65, led) # Update LED status
time.sleep(0.001)
dx2.Write8(id, 65, 0) # LED off
dx2.Write8(id, 64, 0) # Torque free
del dx2

kb = kbhit.KBHit()
# Open serial port
com = serial.Serial('/dev/ttyAMA0', 1000000, timeout=0.005)
lock = threading.Lock()
act = True
v1 = 0
v2 = 0
SharedVar = [com, lock, act, v1, v2]

# Start thread
th1 = threading.Thread(target = func1, args = [SharedVar])
th2 = threading.Thread(target = func2, args = [SharedVar])
th1.start()
th2.start()

# Loop until keyboard input
while True:
if kb.kbhit():
break
print(f' pmx:{SharedVar[3]:6d}, dxl:{SharedVar[4]:6d}', end = '\r')
time.sleep(0.05)

# End
SharedVar[2] = False
th1.join()
th2.join()

del kb, lock, com, th1, th2
print()
結果、いずれのサーボもスムーズに動くし、更に長時間運転してみても各関数はエラーを吐いていない。
色々虐め始めるとボロも出て来るだろうが、サーボそのものが不可解な挙動をしない限り、もしかしたら新たな使い道が見い出せるかも知れない。

キャリアボードに装備した機能の検証を含んだ操作している様子を動画にしておいた。

以上で久々に新鮮な気持ちで遊興したレポートを終える。

技術