Tanuki_Bayashin’s diary

電子工作を趣味としています。最近はラズベリーパイPicoというマイコンを使って楽しんでいます

傾斜計に応用したカルマンフィルターの動特性

※なにか気になる点がありましたらコメント欄にご記入ください。また、工作や回路を製作する場合には、細かい作業などに対して、細心の注意を払われるようお願いいたします。

目次

1.はじめに

 前回の投稿でカルマンフィルターに関する記事を載せましたが、今回はその動特性をご覧いただきたいと思います。
 プログラム言語のPythonにて記述しており、Tkinterとよばれるモジュールを用いました。(ほどんどコピペです)
 ソースコードは以下にあるものを参考にしました。とても役に立ちました。

imagingsolution.net

※これらの記事の中に出てくるカルマンフィルターについては、トランジスタ技術2019年7月号の記事を出典としています。

ご参考:前回の記事
tanuki-bayashin.hatenablog.com
出典:トランジスタ技術 2019年7月号 第1章第2節(P.39~)のあたり
toragi.cqpub.co.jp

2.ソースコード

 以下にソースコードを掲載します。

import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import numpy as np
import threading, serial, time

""" グローバル変数の定義
"""
value = [0.0, 0.0]
y1 = np.zeros(100)
y2 = np.zeros(100)
x = np.linspace(0, 10, 100)

ser = None
thread_button = None
thread_uart = None

class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.master.title('matplotlib graph')

        #-----------------------------------------------

        # matplotlib配置用フレーム
        frame = tk.Frame(self.master)
        
        # matplotlibの描画領域の作成
        fig = Figure(facecolor='azure')
        # 座標軸の作成
        self.ax = fig.add_subplot(1, 1, 1)
        self.ax.set_xlim(0, 10)
        self.ax.set_ylim(-10, 100)
        # matplotlibの描画領域とウィジェット(Frame)の関連付け
        self.fig_canvas = FigureCanvasTkAgg(fig, frame)
        # matplotlibのツールバーを作成
        self.toolbar = NavigationToolbar2Tk(self.fig_canvas, frame)
        # matplotlibのグラフをフレームに配置
        self.fig_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

        # フレームをウィンドウに配置
        frame.pack()

        # ボタンの作成
        button = tk.Button(self.master, text = "Draw Graph", command = self.button_click)
        # 配置
        button.pack(side = tk.BOTTOM)

        #-----------------------------------------------

    def button_click(self):
        global thread_button, value

        # 表示するデータの作成
        for i in range(99):
            y1[99-i] = y1[98-i]
            y2[99-i] = y2[98-i]
            
        y1[0] = float(value[0])
        y2[0] = float(value[1])

        # グラフの描画
        self.ax.cla()
        self.ax.set_facecolor((1.0,1.0,0.0,0.2))
        self.ax.plot(x, y1, color="g")
        self.ax.plot(x, y2, color="r")

        # 表示
        self.fig_canvas.draw()
        
        thread_button = threading.Timer(0.01, self.button_click)
        thread_button.start()

# str文字列がfloat()変換できるかどうかを判定する
def is_float(s):
    try:
        float(s)
    except:
        return False
    return True

def uart():
    global value, error, ser, thread_uart
    line = ser.readline()
    data = line.decode('utf-8', errors='ignore').strip()
    data = data.split(',')

    term = [0.0, 0.0]
    for i in range(2):
        if len(data) < 2:
            break
    
        if is_float(data[i]):
            if ((float(data[i]) < -180.0) or (float(data[i]) > 180.0)):
                continue
            term[i] = float(data[i])
            value[i] = '{0:7.4f}'.format(term[i])

    thread_uart = threading.Timer(0.01, uart)
    thread_uart.start()


#=================================================
# プログラムの起点
#=================================================
if __name__  == '__main__':

    COM="COM17"
    bitRate=115200
    ser = serial.Serial(COM, bitRate, timeout=0.1)

    thread_uart = threading.Thread(target=uart)
    thread_uart.start()

    root = tk.Tk()
    app = Application(master=root)
    app.mainloop()

    print('program end')
    ser.close()

解説:
1ー5行: 必要なモジュールをimportしています
7行ー: グローバル変数の定義です
18行ー: クラスを定義しています。
     実を言いますとここから48行目まで何をしているのかはっきりとは分かっていないです。なんとなく、Tkinterで描くウィンドウ内にmatplotlibにて表示する部分を確保しているのかな、というぐらいです。なので詳しい説明はできないです。悪しからず。

52行ー: クラス内の関数(メソッド) button_click() を定義しています。
  55-58行  表示するデータを1つずつずらしています
  60,61行  下で定義された関数 uart() にて取得したデータ valueを受け取っています。
  63-70行  表示されているグラフを1度消去した後、再描画しています。
  72,73行  0.01秒おきにこの処理を繰り返すための処理です。

75行-: クラス外で定義されている関数です。文字列sがfloat に型変換できるかそうでないかを判別する関数です。

83行ー: これもクラス外の関数です。uart の機能により、ラズベリーパイPicoから送られてくるデータを受信しています。
  送られてくるデータは2つ一組で、それらを別々の変数 value に格納しています。
  こちらもよく分かっていない部分があるので、詳しい説明はできないです。
  
  85-87行  送られてきたデータを一旦dataというリスト型変数に格納します。
  89-98行  それらがfloat型に変換できることと、-180~180以内に収まっていることを確認した後、float型に変換し、valuという変数に格納します。
  100ー101行 0.01秒おきにこの処理を繰り返すための処理です。

104行ー: このブログラムはここから始まります。
109-111行 シリアル通信に関する処理です。ここではbps115200にてCOM17を開いています。
113-114行 上で定義した関数 uart() をスレッドとしてスタートしています。

116-118行 Tkinter のオブジェクトを生成し、クラスで定義した処理(オブジェクト)も生成し、mainループとして無限に繰り返す手続きをしているようです。
120-121行 終了時の処理です。(シリアル通信のオブジェクトを閉じています)

解説は以上です。

3.動画

 Youtubeにアップした動画を以下に示します。カルマンフィルターにより、加速度センサーより求めた傾斜角のデータから、ノイズが取り除かれている様子がよく分かります。
 また、傾斜の度合いを変化させても、大きく遅れることなく追従しているのが確認できるかと思います。

youtu.be

動画 カルマンフィルターの動特性 ~倒立振子への応用に向けて~

4.まとめ

 前回の記事では見せられなかったカルマンフィルターの動特性を紹介しました。PythonにてTkinterというモジュールを使い、オシロスコープもどきの画像が作れたのは面白かったです。

今回の内容にて、以下のことを示すことができたかと思います。

  • 加速度センサーからのデータをもとに計算した傾斜角の生のデータに、カルマンフィルターを適用したところ、ノイズが大きく軽減できた。
  • 傾斜角が変化したときの応答も、大きく遅れることのない追従特性が確認できた。

早く倒立振子を立たせたいです。