粗大メモ置き場

個人用,たまーに来訪者を意識する雑記メモ

M5stackとRaspberry Pi zeroのBluetooth シリアル通信

ハードウェア

M5stackとRaspberry Pi zeroの2つのデバイス間でデータ通信することを目標にしています。

M5stackはディスプレイやボタンのインターフェイス用として,Raspberry Pi zeroはより高度な処理用として用います。 今回はM5stackが情報を送信するのみ,という仮定でやっています。

M5stackについて

M5Stackとはディスプレイ付きのマイコンその他もろもろ開発ボードです。

M5Stack Gray(9軸IMU搭載)

M5Stack Gray(9軸IMU搭載)

開発手段には以下の2つが存在します。

前者のほうがライブラリなどが豊富ですが,後者はPythonベースなので記述量が非常に少ないというメリットがあります。 僕はファームウェアの書き換えが上手く行かなかったので前者を採用しました。

なお,WindowsPCでシリアル通信をしたことない場合などはドライバがない可能性があるのでこちらを参照にすれば良いと思います。

Raspberry Pi zeroについて

いわゆるRaspberry Piの小型軽量版です。省スペースかつ省電力なのにDebianベースのLinux OSが動かせるのでわりと使えそうです。

ossyaritoori.hatenablog.com

M5stack側の準備

ここではArduino開発環境を前提に進めていきます。 このサイトを参考に進めると良いでしょう。

また,Arduinoのエディタは正直言ってあまり使い勝手は良くないので,ここではVScodeで開発をしました。

「ファイル」の「環境設定」から「外部のエディタを使用する」にチェックを入れることで他のエディタで編集した.inoファイルの変更内容をArduinoIDEに反映させることが出来ます。ただし,IDEの方から文章を変更できなくなるのは注意。

BluetoothSerial.hの利用

Bluetoothで単に送信するだけの関数は驚くほど簡単で,"BluetoothSerial.h"をインクルードして,BluetoothSerial クラスを呼ぶだけです。 その後,print,println,writeなどの関数で文字列や数値を送信することができます。

#include <M5Stack.h>
#include "BluetoothSerial.h"

byte counter;

BluetoothSerial bts;

void setup() {
  M5.begin();
  M5.Lcd.println("Bluetooth Now");

  Serial.begin(9600);
  bts.begin("M5Stack");//PC側で確認するときの名前

  counter = 0;
}

void loop() {
  bts.println(counter);
  counter++;
  delay(100);
}

Raspberry pi 側のセットアップ

M5stackが上記のようなプログラムですでに初期化・起動しているとします。

ペアリング,MACアドレスの取得

Bluetoothのペアリングはラズパイ側のGUIで設定する方法とCUIで設定する方法があります。その後の手順を考えるとCUIでやった方がいいのかなと思います。

  • CUIでペアリング:bluetoothctlの起動

bluetoothctlとコマンドを打つことでBluetoothの管理モードに移行します。

こんな感じの表記になるはずです。

pi@raspberrypi:~ $ bluetoothctl
[NEW] Controller B8:27:EB:8C:43:C6 raspberrypi [default]
[bluetooth]#

この後,以下のコマンドを順番に実行します。

agent on
default-agent
discoverable on
scan on

しばらくするとscanの結果として,次のような画面に移行するので先程設定したM5Stackの文字を探しましょう。

[bluetooth]# scan on
Discovery started
[CHG] Controller XX:XX:XX:XX:XX:XX Discovering: yes
[NEW] Device 30:AE:A4:4F:A3:76 M5Stack

私の場合はこの30:AE:A4:4F:A3:76がM5stackのMACアドレスということになります。(重要)

その後このMACアドレスを用いてpair <MACアドレス>とコマンドを実行することでペアリングは完了です。

以下のような画面が出るかと思います。その後は適当にquitして元のターミナルに戻ってください。

[bluetooth]# pair 30:AE:A4:4F:A3:76
Attempting to pair with 30:AE:A4:4F:A3:76
[CHG] Device 30:AE:A4:4F:A3:76 Connected: yes
Request confirmation
[agent] Confirm passkey 952397 (yes/no): yes
[CHG] Device 30:AE:A4:4F:A3:76 UUIDs: 000~
[CHG] Device 30:AE:A4:4F:A3:76 UUIDs: 000~
[CHG] Device 30:AE:A4:4F:A3:76 UUIDs: 000~
[CHG] Device 30:AE:A4:4F:A3:76 ServicesResolved: yes
[CHG] Device 30:AE:A4:4F:A3:76 Paired: yes
Pairing successful
[CHG] Device 30:AE:A4:4F:A3:76 ServicesResolved: no
[CHG] Device 30:AE:A4:4F:A3:76 Connected: no

接続も自動で行われるはずですが,不安ならば先程のbluetoothctlの画面にてconnect MACアドレスを実行すると良いでしょう。

バインドしてシリアル受信確認

接続しただけではデバイスとして認識できないのでrfcommというコマンドを使ってBindを行います。 例えば先程メモしたMACアドレスを用いて,

sudo rfcomm bind 1 30:AE:A4:4F:A3:76 2

のように実行することで,/dev/rfcomm1のポート2がBluetoothの入出力に対応するようになります。

シリアルにて流れてくる情報を単に閲覧したければ

cat /dev/rfcomm1 

とうって確認できます。

Pythonで受信した文字列を表示

先程バインドしたrfcomm1を引数にしてSerialモジュールを呼ぶことで通信が可能です。

import serial
ser=serial.Serial('/dev/rfcomm1')

while 1:
    print(ser.readline())

トラブルを避けるならKeyboardInterruptでser.close()を呼ぶなどした方が安全でしょうが,一応これでM5stackが発信している文字を確認できます。

逆にser.send()を用いてラズパイ側から送信した場合は,M5stack側でread()を用いて受信関数を書けば動作します。

ラズパイ起動時にプログラム実行

先程の接続やバインドなどをラズパイの起動時にキー入力なしでしたいと思った場合,/etc/rc.localというファイルに書き込むという手段があります。 root権限で実行されるので少し気をつける必要がありますが,とりあえず動かしたい場合はこれでも良いかと。

# connect
echo "connect 30:AE:A4:4F:A3:76 \nquit" | bluetoothctl

#bind
sudo rfcomm bind 1 30:AE:A4:4F:A3:76 2

# add your program below

何に使ったか

M5stackで作ったタイマーと組み合わせて起床時に徐々に明かりを明るくするIoT目覚ましセットを作成しました。 睡眠リズムの改善が求められる…

f:id:ossyaritoori:20190111231037p:plain
目覚ましセット

参考文献メモ置き場

記事で引用すると思ったけどしなかった参考文献たち:

Wifi接続などに関する参考文献

Raspberry Pi zero wh のセットアップ

最近セットアップ記事しか書けてない気がしますが気のせいです。

RaspberryPi Zero WH

正月の電子工作の延長でIoTごっこをしたいと思い,Raspberry Pi Zero WHを買いました。

特徴として,

  • 安い(680~1900円)
  • 小さい
  • 省電力

があります。単体では安いですが,地味に必要物が違ってくるのでいろいろセットになっているやつを買うのがいいと思います。(MicroUSB-USB A変換とか普通持ってないでしょう)

準備物は,

  • 上記セット
  • HDMIディスプレイ
  • PC,キーボード
  • microSDカード
  • USB電源(microUSB)

なお,はじめからsshで全部やるきの人はこの限りではないです。

Raspberry Pi Zero Wをディスプレイやキーボードなしで初期設定、Wi-Fi接続

OSのインストール

以前用いた手法にのっとってOSを入れます。

今回はRAMが512MBしかないのでUbuntuMateでも不安がありました。 Raspbianを代わりに使います。

デバッグ1:電源が着かない問題

最初に電源が着かないという問題に直面しました。 デバッグの方法として,

  • Vcc端子の電圧をチェックする
  • PCにUSB接続でつないで動作確認

があります。

[参照] 電源LEDが点灯しない!?のは当たり前。ゼロから始めるRaspberry Pi Zero(3)Raspberry Pi Zeroが壊れていないか確認する方法 | bokuraku.com

僕は2つ目のチェックで故障でないことを確認しました。なおZero WHには電源のすぐ横に緑色LEDがあるのでうまく行ったら光るはずです。

いくつか端子を抜き差ししたら治った(キーボードをごついやつから小さいものに変えたらうまく行った) ので,接触や供給電力の問題等もあるのかも?

固定IPの設定

通常はDHCPというIPアドレス自動割当の様式に従ってルータから適当に番号を振られてしまいます。 sshでアクセスしやすいよう固定IPを振ります。

固定IPの決定方法

ではどのIPを振っていいかということですが,末尾10以降はDHCPの自動割当に含まれていることが多いようなので2~9あたりで決めるのが良さそうです。

なお,192.168.x.1のようにIPの末尾を1にしてブラウザに入力すればルータの設定が見れます。

[参照] 自宅のLANでIPアドレスを固定するマイルールを決めてみる【自宅Wi-Fiの“わからない”をスッキリ!】 - INTERNET Watch

固定IPをふる

以下のブログなどを参照にしてIPを振ります。 qiita.com

# ↓ の行を追加
interface wlan0
SSID SSID名
inform IPアドレス/マスク
static routers=デフォルトゲートウェイ
static domain_name_servers=DNSサーバ

なんですけど,なんか僕の環境ではうまくふれてないんですね。直し次第修正します。

[参考になりそう] Static IP Addressing issues (Wlan0) - Raspberry Pi Forums

雑談:Nanoエディタ

geditやvscodeばかり使っていましたがSSHで接続するにあたりNanoエディタを使うことにしました。

下にGuideが出ていますが,Ctrl+<キー>で実行します。

誰も興味が無いnanoの基礎の基礎 - nanoはpicoの千倍なの! - - KAYAC engineers' blog

vi? 可能な限り避けたい。

SSH接続

sshで接続するときの基本的なコマンドは ssh ユーザ@IPアドレス -p ポート番号です。

例えばこんなふう。

ssh pi@192.168.x.x -p 22

Windows環境からSSHする

これにもいくつか手順があるようですが,以下のサイトの情報に従って進めました。

usado.jp

Raspberry Pi から高精度AD/DA変換ボードを用いてAD/DA入出力

Raspberry PiでAD/DA変換をする方法

Raspberry PiにはHigh/Lowのディジタル入出力が可能なGPIOがついていますが,アナログセンサデータを読んだりアナログ電圧を出力することはできません。 従ってこの記事で紹介されているようなAD/DAコンバータが必要になります。

回路を組むのはだるいのでAmazonで以下のAD/DA変換ボードを購入してみました。

高精度AD/DA変換ボード

高精度とうたっていますがカタログスペックは以下の通りです。

  • ADポート6チャンネル(16bit)
  • DAポート2チャンネル(12bit)

Vccは5VなのでDA出力が 0~5Vとすると,12bitで分割して大体8mVくらいの分解能があります。すごい。嘘やけど。

ボードのセットアップ

今回の環境は前回 SetupしたUbuntu MATEとします。ROSは使ってないのでROSの項は飛ばしてもOK。

bcm2835 のインストール

ここからtarファイルを落として7zで解凍します。

解凍した中にINSTALLという名前のファイルがあるのでその指示に従います。

一応実行するコマンドは以下の通りです。途中sudoが必要な点に注意。

./configure
make
sudo make check
sudo make install
make installcheck

でインストール状況をチェックできます。

提供されているサンプル

ここで配ってますが正直読むのは結構だるいので誰か解説してやってください。

コマンドラインからDA出力を制御する自分用ライブラリ

てなわけで自分でプログラムつくりました。 下記からCloneしてmakeすれば完了です。

github.com

こういうふうに5VとVcc,Vref そしてDA0,AD0とDA1,AD1を接続します。

makeした後に出てくる実行ファイルは2つです。

Vcc補正用プログラム:calib

このAD/DAコンバータでは正確な5Vが必要っぽいのですが,ラズパイから電力供給するとVccが微妙に5Vにならないという問題を抱えているため,その補正用です。

以下に実行結果を示します。

$ sudo ./calib
Supposed DA output[V] DA0:3.000000 DA1:3.000000 
Measured DA output V0 = 3.070409, V1 = 3.066153 
Supposed Vref = 5.058334 
magnitude offset: 1.011667, 1.010965

この実行結果から,3Vを出しているのに3.07Vが出ている(とADで読める)ズレが有ることがわかります。これでは12bitの分解能が台無しです。 この関係から実際のVcc=Vrefを5.058Vと推定しています。テスタで測ると5.053なので5.0Vとして考えるよりはマシになりそうです。

このときの5Vに対するVccの倍率を一番下の行で計算しています。僕の環境では1.01倍と出ていますね。

注意:原理は多分Vrefとの比較でADやDAのしきい値を変えているのでVrefの倍率分DAやADの値が変化すると読んで計算しています。もっといい方法なんかあるなら知りたいっす…

出力プログラム:dac8532_output

こちらでは,引数にDA0とDA1の出力電圧[V],Sleep時間[ms]を与えることでコマンドラインから出力を可能にしています。

$ sudo ./dac8532_output  <電圧1>  <電圧2>  <時間>  <補正> 

と打ちます。 <補正>項はデフォルトで1.01にしているので抜いてもOKです。

  • データ例

試しに4Vと2Vを出力させるよう

$ sudo ./dac8532_output  4 2 10  1.01

を実行しました。

結果は3.998Vと1.999Vでした。

Vccの電圧をきちんとしない限りカタログスペックの12bit分解能は正直出ないと思われます。 でもまぁそこまで気にしないのならそこそこの精度かなと思います。 5Vや0V等の上限下限近づいた場合はこの限りではないので注意。

Pythonからの実行

結局最近プログラムの多くをPythonで書くようになったのでDA出力もPythonからしたいです。

実はsubprocessというやつを用いるとコマンドライン実行をめちゃ簡単にできます。

import subprocess

da0 = 3
da1 = 4
duration = 20

subprocess.call(["./dac8532_test",str(da0),str(da1),str(duration)])

呼び出し先のプログラムにはsudo権限がいるのでこのPythonコードをsudoで実行すればいいわけです。 (/dev/memにアクセスするからのようですが,できればsudoなしでアクセスしたい…)

総括

カタログスペックは高いですが電源をちゃんとするとかしないと正直カタログのような超精度は出なさそうです。 ただ,ラズパイの上に乗って収まりが良く,はんだ付けなどなしで動作させられるのでアプリケーションによっては結構ありかも? 私は今回は車載用センサ処理に使っています。

久々にリポジトリ作りました。汚いですが指摘などあれば歓迎です。超暇なら直します。

Raspberry Pi + Ubuntu Mate + ROS のセットアップ

ラズパイに入れるOSの選定

Raspberry Piの公式で推されているのはRaspbianですが,Ubuntu系列も入れる事ができます。Raspbian使いづらい

ではなぜPopularなUbuntuではなくその亜種を使っているかというと,端的にリソースの問題だと思われます。(以下参考サイト) itsfoss.com これらはデスクトップ環境が異なっておりざっくりと聞きかじったところ以下のような特徴があるそうです。(上記サイト参照)

  • UbuntuGNOME 3 使用。RAMが4G以上推奨?
  • KubuntuKDE使用。RAM 2G以上推奨?
  • XubuntuXfce使用。RAM 1G程度でも動作。
  • Lubuntu:LXDE使用。RAM 1G程度でも動作。
  • Ubuntu MATE:GNOME 2使用。RAM 1G程度でも可。

もちろん必要RAMの少ない軽量なディストリビューションはあまり洗練された描画を行えないとのことですが,ラズパイ環境では贅沢できないのでXubuntu, Lubuntu, Ubuntu MATEで妥協する必要がありそうです。

筆者の主観的な選定基準

  • Raspbian:とりあえず安牌な選択肢2018年12月時点では個人的におすすめしないです

買ったばかりのときはRaspberry用に最もPopularと思われるRaspbianを用いました。特にやることが決まってない人やUnixのOSにこだわりがない人はこちらから始めてもよいでしょう。Mathematicaも入ってるし。

ossyaritoori.hatenablog.com

今回使うROS等特にUbuntuで使われているソフトを使いたい場合は軽量版のUbuntu亜種を使う人が多いようです。

ouroboros.hamazo.tv

簡易版があるとはいえ,Ubuntuそのものを使いたい事情があるならばそちらを入れることもあるでしょう。 事実MATEでいくつかうまくいかないことがあったりしたのでUbuntuならなんとかなったのかもしれません。 「Raspberry Pi 3」に「Ubuntu 16.04」を入れる(備忘録) - Qiita

準備する機器

2年前のものですが必要なものは変わってないです。SDカードと電源は気を使いましょう。 ossyaritoori.hatenablog.com

Ubuntu MATEのインストールと初期設定

Raspberry pi 3本体とmicroSDカード,マウス,キーボードその他ディスプレイ環境は整っているものとします。 その他に今回はWindowsのマシンを使用します。Ubuntuマシンを使う方はこのあたりは他の記事を参照にしてください。

microSDのフォーマット

買いたてのまっさらなmicroSDを使う人はこの章は飛ばしても結構です。

パーティションの削除(ある場合)

すでにブート環境を整えたりしてパーティションを作ってしまった場合,windowsではdiskpartというツールが用意されています。 スタートページまたはコマンドプロンプトからdiskpartを起動しましょう。

list diskとコマンドを打つとコンピュータが認識しているディスクが表示されます。512GのSSDと32GのSDカードが刺さっている場合は以下のようになります。

DISKPART> list disk

  ディスク      状態           サイズ   空き   ダイナ GPT
  ###                                          ミック
  ------------  -------------  -------  -------  ---  ---
  ディスク 0    オンライン           476 GB      0 B        *
  ディスク 1    オンライン            29 GB   109 MB

ちなみに,容量が減って見えるのは販売時は1GB=1000MB=1,000,000KB=1,000,000,000Bとして売っていいということになっているためらしいです。 つまり,販売時に32GBとあるメモリは実質 32/1.0243 = 29.8GBの容量になるわけですざけんな。

その後select disk 1でディスク1を選択し,list partitionパーティションを確認します。

DISKPART> SELECT DISK 1

ディスク 1 が選択されました。

DISKPART> LIST PARTITION

  Partition ###  Type                Size     Offset
  -------------  ------------------  -------  -------
  Partition 1    プライマリ               63 MB  4096 KB
  Partition 2    プライマリ               29 GB    67 MB

これをそれぞれのパーティションに入ってdelete partitionとうってすべてのパーティションを消し飛ばせます。なお,大文字小文字の区別はないようです。

DISKPART> SELECT PARTITION 1

パーティション 1 が選択されました。

DISKPART> DELETE PARTITION

DiskPart は選択されたパーティションを正常に削除しました。

すべてのパーティションを消したあと,以下のようにしてパーティションを再構成します。

DISKPART> CREATE PARTITION PRIMARY

DiskPart は指定したパーティションの作成に成功しました。
フォーマット

Windows側から読めないディスクを検知した場合,エクスプローラ起動時に「フォーマットしますか?」とのメッセージが出る仕様になってます。 メッセージに従ってFAT32でSDカードのフォーマットを行えばOKです。

microSDへのイメージ書き込み

次にUbuntuMATEのイメージを書き込みます。 まずは下準備として,

  1. ここからRaspberry Pi用のUbuntu MATEのイメージをダウンロードして解凍。
  2. DD for windowsをダウンロードしてインストール。

回線しだいですが10分程度かかるかと。

次に,DDforWindowsを管理者権限で実行します。

ディスクの選択の後,ファイル選択で先程解凍した.imgファイルを選択します。

ラズパイに挿して起動

いよいよ起動です。 起動後に現れるメッセージに従って進めていくと良いでしょう。

なお僕は一度Wifiの設定にてWPA-PEAPのネットワークにつなごうとして失敗して入れ直したのではじめはWifiに繋がずに進めるといいかもしれません。

起動後即やることは,WifiまたはEthernetにつないだ後に

sudo apt-get update
sudo apt-get upgrade

を行うことです。

これには1時間以上かかったので休憩するタイミングはここです。

Chromiumのインストール

自分の環境ではFirefoxがCrashしてしまうので,代わりにChromiumを使うことにしました。

sudo apt install chromium-browser

https://laptop.ninja/how-to-install-chromium-and-google-chrome-on-ubuntu-mate/laptop.ninja

ROS Kineticのインストール

さて,このあとは公式のガイドに従っていつものようにROSを入れればいいです。

実行するだけチートシート

どういうことが行われているかわかっているから手っ取り早く結果を見たい!という場合は以下のコマンドをコピペして順に実行すればいいです。

所要時間は1時間くらいだったかと思います。

sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
sudo apt-key adv --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-key 421C365BD9FF1F717815A3895523BAEEB01FA116
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install ros-kinetic-desktop-full
sudo rosdep init
rosdep update
echo "source /opt/ros/kinetic/setup.bash" >> ~/.bashrc 
source ~/.bashrc

ここから各種センサつないだりいろいろあると思いますがとりあえずはここまで。

matplotlibでアニメーションを作成,保存

一口にアニメーションといっても時間毎に図の更新がみたいだけの場合とその様子を動画に保存したい場合とがある。

閲覧用のアニメーション

plt.pause(interval)を用いることでノンブロッキングで現在の画像を更新していくことができる。

subplotsを用いた場合

ちょっと長くなるのが難点

import numpy as np
import matplotlib.pyplot as plt

# make data
data = [[i, np.sin(i/50)] for i in range(100)]

# prepare figure
fig,ax = plt.subplots(1,1)
ax.set_xlim(min([x[0] for x in data]),max([x[0] for x in data]))
ax.set_ylim(min([x[1] for x in data]),max([x[1] for x in data]))
xdata = []
ydata = []
line, = ax.plot(xdata,ydata)

# iterate
for dat in data:
  print(dat)
  xdata.append(dat[0])
  ydata.append(dat[1])
  line.set_data(xdata,ydata)
  
  plt.pause(0.1) # 10ms late

plt.figure()でやる方法

こっちのほうが楽。

import numpy as np
import matplotlib.pyplot as plt

# make data
data = [[i, np.sin(i/50)] for i in range(100)]

# prepare figure
xdata = []
ydata = []

# iterate
for dat in data:
  fig = plt.figure(1)
  xdata.append(dat[0])
  ydata.append(dat[1])
  plt.plot(xdata,ydata)
  plt.pause(0.1) # 10ms late
  plt.clf()

matplotlib.animationを使って図を保存する方法

matplotlibにはanimationという図を作成,保存するためのモジュールが存在する。

そのうち2つの手法が存在しイメージとしては以下のような感じである。

  • ArtistAnimation:図をリスト形式で溜め込んで最後にまとめる。重い…?
  • FuncAnimation:逐次的に図を作って追加していく。軽い…?

こちらはリアルタイムに描写というよりは,その後の保存に重点を置いている。一応plt.pause()とも連携できるはずだが,結構重いのでその辺は覚悟したほうが良いかも。

imagemagicのインストール

windows環境でやったのでLinuxの人は別の箇所を探して欲しいが,ffmpegとimagemagicをインストールすることでgifやmp4などの形式で動画を保存することができる。 実はimagemagicのインストール時にffmpegをインストールできるので実質imagemagicをインストールすれば良い。

以下のブログの手順に基づいてすすめていこう。 matplotlib のanimation を保存 - はしくれエンジニアもどきのメモ

1.imagemagicのインストール

こちらのダウンロードページに行き,Windows Binaryの一番上を選択してダウンロードすれば良い。 ダウンロード後はいろいろ聞かれるが基本的にYesで進めていけば良い。

2.matplotlibにimagemagicの場所を教える

お使いの環境のmatplotlibrcというファイルを操作する。ファイルの場所を確かめるには以下のコマンドを実行すると良い。

import matplotlib
matplotlib.matplotlib_fname()

例えば以下のような感じの場所に置いてある。 'C:\\<Python path>\\lib\\site-packages\\matplotlib\\mpl-data\\matplotlibrc'

そしたらメモ帳などで開いて,# animation.convert_path:となっているところの#をはずしてコメントアウトを解除し,

animation.convert_path: C:\Program Files\ImageMagick-7.0.1-Q16\magick.exe #みんなの環境だと多分違うぞ

のようにimagemagicのexeファイルのパスを与える。(エクスプローラから自分で調べること!

ArtistAnimationで図を作成

ArtistAnimationの図の作り方は非常にわかりやすく,matlabのようにplot情報をリストに入れていき,最後にそれらをつなげて動画にする。 自分でもコードなど書いたが,こちらのブログが大変わかりやすく,自分で書くまでもないと思ったのでコードを借りて以下に表記する。どうもありがとうございます。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

fig = plt.figure() #figure objectを取得.
ax = fig.add_subplot(111)

x = np.arange(0,10,1)

ims = [] #Line2D objectを入れるリストを用意
for time in range(x.shape[0]):
    im = ax.plot(x[0:time] ,x[0:time] ** 2) #Line2D objectを取得
    ims.append(im) #imsにappendする

#ArtistAnimation機能で,imsの中の画像を繋ぎ合わせる.
ani = animation.ArtistAnimation(fig, ims, interval = 100)

#imagemagickを使って,gif画像を保存
ani.save("test.gif", writer="imagemagick")

FuncAnimationで動画を作成保存する

こちらは,Animationの生成規則を与えて,逐次これを計算させるものになる。先程と同様の方がわかりやすい解説を上げていてくださり,まずはこちらを試してみると良いだろう。

以下に軽く私の理解を述べておく。URL先のコードで以下のように呼ばれているFuncAnimationでは,

  • 最初にキャンバスとなるfigureのハンドルfigをわたし
  • 逐次処理を実行するupdate()関数を渡す
  • updateに渡す引数を決めるfargsというのがオプションで存在し,
  • 画像間のinterval時間
  • 最大繰り返しフレーム数framesを渡す
ani = animation.FuncAnimation(fig, update, fargs = (x, y, ax1, ax2), interval = 100, frames = 10)

という風になっている。従ってframeが0から9に至るまで,すなわち,

for i in range(frame):
   update(i,args)

という感じになっているはず。 この他にもinit()という初期化関数を渡したりできる。

自分Verとしては例えば以下のようなものになる。ただし,ランダムな点をPlotしようとするので挙動はきっとクソ。 (Originalではちゃんとplotすべきデータがあったが簡略化させていただいた)

fig, ax = plt.subplots()
line, = ax.plot([], [], 'r',label='trajectory')
line2, = ax.plot([], [], 'kx',label='current position')
ax.grid()
xdata, ydata = [], []
plt.xlabel('x [m]')
plt.ylabel('y [m]')
plt.grid ='on' 



#def data_gen():
data =  [[x[0],x[1]] for x in np.random.rand(100,2)]

def init():
    ax.set_ylim(-0.2, 1.2)
    ax.set_xlim(-1.2, 0.2)
    line.set_data(xdata, ydata)
    #line2.set_data(xdata, ydata)
    return line,

def run(i):
    # update the data
    x, y = data[i]
    xdata.append(x)
    ydata.append(y)    

    line.set_data(xdata, ydata)
    line2.set_data(x,y)
    plt.legend(loc='upper left')

ani = FuncAnimation(fig, run,  blit=False, interval=10, frames = int(len(data))-1,
                            repeat=False, init_func=init)

ani.save("plot2.gif",writer='imagemagick')
plt.show()

f:id:ossyaritoori:20181201234856g:plain
こういう感じに出来上がるってわけ

Cannon Power Shot G9X MarkⅡで月を撮ってみた

最近手持ちのノートPCが死んで個人ファイル以外初期化しました。 ドキュメントやピクチャは消えませんがProgramFilesやAppDataが消えたので結構不便してます。

Cannon Power Shot G9X MarkⅡについて

訳あってこのコンデジを家で保管しています。

この機に少しコンパクトデジタルカメラについて調べたので軽く紹介すると、

  • 「一型センサ」と呼ばれる受光センサがコンデジとしては大きい部類
  • 電池を入れて218gと非常に軽量

というのが魅力的な点だと思います。

センササイズの大きさはそのまま画質に影響する1とのことで、夜景などを撮るときに如実に差が出るそうな。 何よりSonyのRX100シリーズより安めなのがよいです。

月を撮った

光学ズームは3倍までと弱いのですが撮れるらしいとのことで、以下のブログで月を撮った人のパラメータを参考にしました。 Canon PowerShot G7Xで、月面の撮影に挑戦:南の島旅

撮影条件

最初は手でもって試しましたが手振れの影響がひどいのでTHETAを買った時の三脚を使いました。そのままだとシャッターボタンを押したときの振動で三脚が振動してぶれたのでタイマー機能を使って振動を抑えてから写真を撮っています。

パラメータは

謎現象でフォーカスがなぜか勝手に近いところによるためマニュアルで固定しました。

結果

撮ってきた画像を拡大してトリミングしました。 確かに月の模様が見えています。 f:id:ossyaritoori:20181020231551p:plain

拡大するとちょっと荒いのがばれてしまいますが初めてでもこれくらいのは撮れるようですね。 f:id:ossyaritoori:20181020232743p:plain

なお、個人的興味でwaifuで高精細化できるか試してみましたがローパスがかかったような画像になってしまいました。 やはり複数撮って合成するしかないようですね。 f:id:ossyaritoori:20181020232721p:plain

EEIC後期実験演習例 Python Control版

前にjupyterで書いてみたは良いが重くてGithubで公開をためらった代物です。 Markdown変換してみました。

ossyaritoori.hatenablog.com

はじめに:Environment settings

Using anaconda you just need to activate following command.

pip install python-control

For further information see here. folllowing command is additional.

  • Textbook for this experiment is here.
# !python -m pip install --upgrade pip
# !pip install slycot 

# I failed to install these command

Initialization

Import matlab like package from control.matlab

from control.matlab import *    # MATLAB-like functions

basic feature

At first you can use tf function like matlab.

  • 下記は演習2の(1)の問題を例に取り上げます。
# Q2-1
num1 = [1,2]
den1 = [3,1]
sys = tf(num1,den1)
print(sys) # this is the system output
 s + 2
-------
3 s + 1

Load matplotlib for drawing figure

import matplotlib.pyplot as plt
%matplotlib nbagg

# import scipy
from scipy import arange 
# step responce 
# Generate step responses
plt.figure(1)
(y1a, T1a)=step(sys,T = arange(0, 10, 0.01))
plt.plot(T1a,y1a)
<IPython.core.display.Javascript object>

[<matplotlib.lines.Line2D at 0x15ae2ea4438>]

Error メモ

sysの関数を変更すると?ときどき'tuple' object is not callableというエラーが起きます。 イマイチ自信がないですがどこか変更してはいけない部分を変更しようとしているようです。

一度カーネルをRestartすると治るのでとりあえずはこれで勘弁してください。

# bode plot
bode1 = bode(sys)
<IPython.core.display.Javascript object>

演習4:2次系の応答

数式も打ち込みたいけど時間がないので割愛。 減衰比(ダンピング)を変えたらどうなる?という話です。

共振周波数を変えるVerもありましたが,ダンピングの例だけ示します。

Gs = []
K = 1
w_n = 10
Ys = []
Ts =[]
legend = []
# changing zeta

plt.figure(3)
for z in range(0,8):
    zeta = z * 0.2
    Gs.append(tf([K*w_n*w_n],[1,2*zeta*w_n,w_n*w_n]))
    (y1, T1)=step(Gs[-1],T = arange(0, 1, 0.001))
    Ys.append(y1)
    Ts.append(T1)
    # plot
    plt.plot(Ts[-1],Ys[-1])
    legend.append('\zeta is '+str(zeta))
    
plt.legend(legend)
plt.show()
<IPython.core.display.Javascript object>

  • どうせならbode線図も見てみましょう
plt.figure(4)
for G in Gs:
    bode(G)
    
<IPython.core.display.Javascript object>

演習5:ゼロ点の性質

終値の定理から行き着く値は1/15で0.06くらいになることはわかりきっている。 ではゼロ点はどこに寄与するのか?

よしんば過渡応答であったとしてどのように?

という設問であるが,私が思うに分子がs+1になるような極端な例がないとこの設問の意図は組みにくいであろう。 早急に実験レポートに反映されるべきである。

# 
sys5 = [tf([1],[1,8,15]), tf([1,4],[4,32,60]) , tf([1,50],[50,400,750]), tf([1,1],[1,8,15])]

plt.figure(5)
for sys in sys5:
    (y1, T1)=step(sys,T = arange(0, 1, 0.001))
    plt.plot(T1,y1)

plt.legend(['G1','G2','G3','Additional'])
plt.show()
<IPython.core.display.Javascript object>

レポートの解答で無難に言うならば,「遅いゼロ点はオーバーシュートを引き起こす。早い零点はまぁ無視しても良い。」ということになるが,これはどうしてか。 せっかくなのでBode線図も見てみよう

plt.figure(6)
for sys in sys5:
    print(sys)
    bode(sys)

plt.legend(['G1','G2','G3','Additional'])
<IPython.core.display.Javascript object>

      1
--------------
s^2 + 8 s + 15


      s + 4
-----------------
4 s^2 + 32 s + 60


       s + 50
--------------------
50 s^2 + 400 s + 750


    s + 1
--------------
s^2 + 8 s + 15






<matplotlib.legend.Legend at 0x15ae3a986a0>

遅いゼロ点ではゲインが不用意に盛り上がっていることが確認できる。 不要な周波数成分を増幅することにより変な振動が起きてしまう → オーバーシュートが出てくる といった感じである。

直感的に説明すると,「分子が1の場合,安定極であれば分母は単調増加して高周波ほどゲインが下がるのに対し,分子にsの項が加わると分子も増加する要素が加わるためsの寄与が相対的に高い遅い零点ではある周波数周辺でゲインが盛り上がる → オーバーシュート」という感じであろうか。

演習8

結構難しい気がする。問題の意図が汲めない… 一応低域の位相を遅らせることと引き換えに高域の位相を少し早めているって言うことなんだろうが。誰か解説手伝って。

余談:zpk記述

因数分解されたシステムは記述が面倒くさい。 zpk2tfというscipyの関数を用いることでゼロ点,極,DCゲインの3つからtfを生成可能だが,生成結果はtupleであり2変数を受け取るtfに直接食わせられない。

そんなときは*を用いた展開表現を用いることで,tupleを複数argumentとして認識させることが出来る。

参考

Gp =tf(*zpk2tf([],[0,-2,-10],40))
Gc =tf(*zpk2tf([-0.2,-1],[-0.02,-5],5))

plt.figure(7)
bode(Gp)
bode(Gp*Gc)
bode(feedback(Gp*Gc))

plt.legend(['Plant','Plant+C', 'Feedback'])
plt.show()
<IPython.core.display.Javascript object>

根軌跡

rlocusやってrlockfindがない!ってOctaveで毎年なるんですがpythonにもなかった…

tau = 0.01
plant = tf([1],[1,3,2,0])
gc = tf([-1.5],[-1.0/tau])

x1,y1 = rlocus(plant*gc)
<IPython.core.display.Javascript object>

Saving

データとか保存用になにかする(予定)