元高専生のロボット作り

元高専生のロボット作り

主にプログラミング, 電子系について書きます。たまに機械系もやります。メモ代わりの記事ばっか書きます

銅張基板のすずメッキ

片面銅張基板などを切削してプリント基板を作ることがあります。

f:id:sgrsn1711:20190927135032j:plain

銅張基板は秋月などで売っています。

akizukidenshi.com

Amazonでも売ってる

NCフライス盤や基板加工機などで切削して部品を実装すると以下のようにプリント基板を自作することができます。

f:id:sgrsn1711:20190927135059j:plain

しかし、表面は銅がむき出しですので、空気や皮脂などによって変質、変色してしまう恐れがあります。 以下は実際に劣化してしまった基板です。 基板の一部が黒ずんでしまっています。

f:id:sgrsn1711:20190927135123j:plain

よって、さびにくいスズを使用してのメッキを施してから基板を加工しました。

メッキ手順

用意するものは以下です。

  • サンポール(薬局で手に入る)
  • 計量カップ
  • 容器(タッパーとか)
  • すずはんだ(99%すず、鉛フリー)
  • 片面銅張基板
  • ワニ口クリップ
  • 配線
  • 電池1本

0. 銅張基板の表面を軽く研磨する

表面の酸化膜を軽く除去します。 終わったらキムワイプなどでふき取りましょう。

1. 2%塩酸を作成する

サンポールがだいたい10%塩酸なので、5倍希釈して2%塩酸を作成します。 サンポール50mLに水200mLぐらいがちょうどよいです。

f:id:sgrsn1711:20190927135153j:plain

2. 配線する

プラス側(陽極)にはんだ、マイナス側(陰極)に基板です。 基板とはんだが触れないようにしてください。

f:id:sgrsn1711:20190927140929p:plain

f:id:sgrsn1711:20190927135216j:plain

3. 電源をいれる

標準電位がスズ-0.1375V、銅0.34Vなので電池一本(1.5V)あれば足りますね。

電源を入れると以下のように、被メッキ材から水素が泡となって発生します。

f:id:sgrsn1711:20190927135238j:plain

4. 洗浄

反応が終了したら、基板を取り出してふき取ります。

以下は元の銅張基板とメッキ処理した基板の比較です。

f:id:sgrsn1711:20190927135341j:plain

またメッキ処理した基板も同様に切削してプリント基板を作成することが可能です。

f:id:sgrsn1711:20190927135715j:plain

3DプリンタのGコード編集

私はAnycubic Kosselという3Dプリンタを使用しています。

この3Dプリンタのために、制御ソフトのうちPronterfaceというソフトを使用しています。

PronterfaceはGコードを読み込んで制御します。

また、Gコード作成のためにCuraというソフトを使用しています。

で、今回は3Dプリンタで印刷中にちょっとモデルの形状を変えたいなというときの小技です。

Gコードの編集

まず、CuraでGコードを作成すると以下のような感じの.gcodeファイルが出力されます。

M190 S70.000000
M109 S185.000000
;Sliced at: Sat 01-06-2019 14:18:39
;Basic settings: Layer height: 0.2 Walls: 1 Fill: 5
;Print time: 2 hours 55 minutes
;Filament used: 15.35m 45.0g
;Filament cost: None
;M190 S70 ;Uncomment to add your own bed temperature line
;M109 S185 ;Uncomment to add your own temperature line
G21        ;metric values
G90        ;absolute positioning
M82        ;set extruder to absolute mode
M107       ;start with the fan off
G28 X0 Y0  ;move X/Y to min endstops
G28 Z0     ;move Z to min endstops
G1 Z15.0 F7800 ;move the platform down 15mm
G92 E0                  ;zero the extruded length
G1 F200 E3              ;extrude 3mm of feed stock
G92 E0                  ;zero the extruded length again
G1 F7800
;Put printing message on LCD screen
M117 Printing...

;Layer count: 250
;LAYER:0
M107
G0 F7800 X58.250 Y58.250 Z0.300
;TYPE:SKIRT
G1 F1200 X-58.250 Y58.250 E7.26526
G1 X-58.250 Y-58.250 E14.53052
G1 X58.250 Y-58.250 E21.79578
G1 X58.250 Y58.250 E29.06104
G1 F3000 E24.06104
G0 F7800 X54.250 Y54.250
;TYPE:WALL-INNER
G1 F3000 E29.06104
G1 F1200 X-54.250 Y54.250 E35.82740
G1 X-54.250 Y-54.250 E42.59376
・
・
・
;LAYER:1
M106 S255
G0 F7800 X-54.250 Y54.250 Z0.500
;TYPE:WALL-INNER
G1 F1620 X-54.250 Y-54.250 E1533.79380
G1 X54.250 Y-54.250 E1538.30471
・
・
・

・
・
・
G0 F7800 X52.306 Y-54.074
G1 F3000 X51.924 Y-53.692 E3933.01334
M107
G1 F3000 E3928.01334
G0 F7800 X51.924 Y-53.692 Z55.000
;End GCode
M104 S0                     ;extruder heater off
M140 S0                     ;heated bed heater off (if you have it)
G91                                    ;relative positioning
G1 E-1 F300                            ;retract the filament a bit before lifting the nozzle, to release some of the pressure
G1 Z+0.5 E-5 X-20 Y-20 F7800 ;move Z up a bit and retract filament even more
G28 X0 Y0                              ;move X/Y to min endstops, so the head is out of the way
M84                         ;steppers off
G90                         ;absolute positioning


Z座標が変わるごとに;LAYER:1とかコメントが挟んであるのでわかりやすいです。

モデルのある高さから上を作り変えたい場合は、対応したZ座標を探して、そこから上のコードをすべて削除してください。

Gコードの編集はこれだけです。

Pronterfaceでの操作

Pronterfaceでは、3Dプリンタが上記で設定した高さまで到達したらPauseボタンを押して待機させてください。

そしたら、すぐにLoad Fileボタンから編集したGコードを読み込ましてください。

これで、Printボタンを押せば、編集したGコードの高さから印刷を開始してくれます。

Pauseボタンで待機しているだけなので、ヒーターの設定とかは必要ないです。

ブラシDCモータのPWM制御シミュレーション

ブラシDCモーターにPWM制御をかけたときの電流とか回転数が見たくなりました。

ステップ入力を印加した時と、PWMのデューティ比を徐々に上げたときで、それぞれシミュレーションします。

モデリング

以下ではモーターの微分方程式を解いていきます。つまり積分の式を求める。
しかし、シミュレーションでは微分方程式をそのまま使うので、積分の式が欲しい人以外は読み飛ばしてよいです。

回路方程式

まずは、モーターのモデリングです。

電圧v(t)を印加したブラシDCモータの等価回路は以下の図のようにします。
抵抗R、インダクタンスL、逆起電力e(t)としてモータ内の要素を表します。

f:id:sgrsn1711:20190520184542p:plain

この回路の微分方程式は以下で表されます。

 v(t)=L \frac{d i(t)}{d t}+R i(t)+e(t)

この微分方程式の解を求めます。

定数変化法によって解いていくと

まずは斉次、

 L \frac{d i(t)}{d t}+R i(t)=0

の解は、

 i(t)=C e^{-\frac{R}{L} t} (Cは積分定数)

CをC1(t)として、

 i(t)=C_{1}(t) e^{-\frac{R}{L} t}

またこの微分

 \frac{d i(t)}{d t}=\frac{d C_{1}(t)}{d t} e^{-\frac{R}{L} t}-\frac{R}{L} C_{1}(t) e^{-\frac{R}{L} t}

をそれぞれ最初の式に代入して、

 v(t)-e(t)=L \frac{d C_{1}(t)}{d t} e^{-\frac{R}{L} t}

が得られます。

C1(t)について解くと、

 C_{1}(t)=\int_{0}^{t} \frac{1}{L}\{v(\tau)-e(\tau)\} e^{\frac{R}{L} \tau} d \tau+C_{1}

(C_{1}は積分定数)

と求まります。したがって、代入すれば、

 i(t)=e^{-\frac{R}{L} t}\left\{\int_{0}^{t} \frac{1}{L}\{v(\tau)-e(\tau)\} e^{\frac{R}{L} \tau} d \tau+C_{1}\right\}

とi(t)の式が求まりました。
しかし、まだ終わりではありません。

逆起電力e(t)の式を求める必要があります。

逆起電力e(t)はモータの角速度に比例し、以下の式で表されます。Kは逆起電力定数です。

 e(t)=K \omega(t)

機械系の運動方程式

ω(t)を求めるためにブラシDCモーターの機械的モデリングを行います。

ブラシDCモータをトルクT(t)を入力した軸として図のようにします。

f:id:sgrsn1711:20190520190918p:plain

この機械系の微分方程式は以下の式で表されます。

 T(t)=J \frac{d \omega(t)}{d t}+D \omega(t)

やはり定数変化法で解くと、同様に

 C_{2}(t)=\int_{0}^{t} \frac{1}{J} T(\tau) e^{\frac{D}{J} \tau} d \tau+C_{2}

 \omega(t)=e^{-\frac{D}{J} t}\left\{\int_{0}^{t} \frac{1}{J} T(\tau) e^{\frac{D}{J} \tau} d \tau+C_{2}\right\}
(C_{2}は積分定数)

とω(t)の式が得られました。

また、T(t)は電流i(t)に比例し、以下の式で表されます。

 T(t)=K i(t)


式のまとめ

微分
今回つかうのはこちら。

 v(t)=L \frac{d i(t)}{d t}+R i(t)+e(t)
 e(t)=K \omega(t)
 T(t)=J \frac{d \omega(t)}{d t}+D \omega(t)
 T(t)=K i(t)

積分

 \begin{array}{l}{i(t)=e^{-\frac{R}{L} t}\left[\int_{0}^{t} \frac{1}{L}\{v(\tau)-e(\tau)\} e^{\frac{R}{L}} d \tau+C_{1}\right]} \\ {e(t)=K \omega(t)}\end{array}

 \begin{array}{l}{\omega(t)=e^{-\frac{D}{J} t}\left[\int_{0}^{t} \frac{1}{J} T(\tau) e^{\frac{D}{J} \tau} d \tau+C_{2}\right]} \\ {T(t)=K i(t)}\end{array}

シミュレーション
ステップ応答と、PWM入力の応答でそれぞれシミュレーションします。
pythonでコードを書いて実行します。
scipyでシミュレーションして図はmatplotlibで出力。

コード
デューティ比は0.5です。

from scipy.integrate import odeint
import numpy as np
import matplotlib.pyplot as plt

# maxon RE40 グラファイトブラシ, 150 Watt 製品番号 148867
R = 0.299       #[Ω]
L = 0.082e-3    #[H]
J = 142.0e-7    #[kgm^2]
tau_m = 4.67e-3 #[sec]
D = J/tau_m     #[Nm sec/rad]
E = 24.0        #[V]
K = 30.2e-3     #[Nm/A]
f = 20e+3

def motorStep(var, t):
    i = var[0]
    omega = var[1]
    e = K*omega
    T = K*i
    if t < 0.000002:
        v = 0
    else:
        v = E
    didt = 1/L * (v - R*i - e)
    domegadt = 1/J * (T - D*omega)
    return [didt, domegadt]

def motorFromPWM(var, t):
    i = var[0]
    omega = var[1]
    e = K*omega
    T = K*i
    if t < 0.000002:
        pwm = 0.0
    else:
        pwm = ( (np.sin(2*np.pi*f*t)+0.9999) ).astype(np.int)*E
        pwm = pwm.astype(np.float)
    didt = 1/L * (pwm - R*i - e)
    domegadt = 1/J * (T - D*omega)
    return [didt, domegadt]

var_0 = [0.0, 0.0]
t = np.arange(0, 0.1, 0.000001)
var_list_step = odeint(motorStep, var_0, t)
var_list_pwm = odeint(motorFromPWM, var_0, t)

fig, ax = plt.subplots(1,2)
ax[0].plot(t, var_list_step[:, 0], label='step', ls='-')
ax[0].plot(t, var_list_pwm[:, 0], label='pwm', ls='-.')
ax[0].set_xlabel('t')
ax[0].set_ylabel('i[A]')
ax[0].legend(loc='best')
ax[0].grid(ls=':')
ax[0].set_title('Current')
ax[1].plot(t, var_list_step[:, 1]*60/(2*np.pi), label='step', ls='-')
ax[1].plot(t, var_list_pwm[:, 1]*60/(2*np.pi), label='pwm', ls='-.')
ax[1].set_xlabel('t')
ax[1].set_ylabel('$\omega[rpm]$')
ax[1].legend(loc='best')
ax[1].grid(ls=':')
ax[1].set_title('Speed')
plt.subplots_adjust(wspace=0.4)
plt.savefig('MotorSimResult.png')

出力結果

f:id:sgrsn1711:20200411181209p:plain

左は電流、右は回転数です。
デューティ比を0.5に設定してあるのでPWM制御した場合の方だと電流が約1/2倍になっていますね。
始動電流もある程度抑えられています。

実験

完全に別ものですが、モータに流れる電流を電流センサで読み取ってみました。

モーターはRS-540
センサはACS714
印加電圧は8 V程度
制御はmbed LPC1768です。

実験結果

単純に電圧を印可した場合(左)、デューティ比を線形に上げた場合(右)です。

f:id:sgrsn1711:20190520194136p:plain

確かにデューティ比が1になったときに始動電流らしきものが発生しています。

I2C対応のLCDをPICから動かす

AmazonでI2Cで操作できるLCDモジュールが650円で売ってます。
I2C対応のLCDモジュールでは一番安いのでは?


ArduinoではLiquidCrystal_I2Cというライブラリを使用すれば簡単に動かせます。

今回はこれをPICで動かしました。

PICはPIC24FV32KA302を使用して、MPLABXでコードを書きます。


やりかた

New Projectから空のプロジェクトを作成します。

まずは、MCCを使用してI2C周りの設定を行います。

MCCを開いたら、Device ResourcesからI2C→I2C1[PIC24/dsPIC33/PIC32MM .......]を選択し、Project Resourcesに追加します。

I2Cの詳しい設定はだいたいデフォルトでよかったはず。

一応動いたときのスクショ

f:id:sgrsn1711:20190516164903p:plain

ModeはMasterで100kHzね。

Pin ModuleはWPUにチェック。

f:id:sgrsn1711:20190516165007p:plain

そしたらProject Resources欄のGenerateをクリックして設定完了。

コード

動いたコードから切り貼りしたので抜けとかあるかも
文句くれればすぐ直す

多分誰もやんないし大丈夫でしょ

LCD.h

#include <stdio.h>
#include <stdlib.h>
#include <xc.h>

// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80

// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00

// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00

// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00

// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00

// flags for backlight control
#define LCD_BACKLIGHT 0x08
#define LCD_NOBACKLIGHT 0x00

#define En 0b00000100  // Enable bit
#define Rw 0b00000010  // Read/Write bit
#define Rs 0b00000001  // Register select bit

#define LCD_ADD 0x27

void LCD_Init();
void begin(uint8_t cols, uint8_t lines, uint8_t dotsize);
void display();
void clear();
void home();
void noBacklight();
void backlight();
void setCursor(uint8_t col, uint8_t row);
size_t print(const uint8_t *buffer, size_t size);
void command(uint8_t value);
void send(uint8_t value, uint8_t mode);
void write4bits(uint8_t value);
void expanderWrite(uint8_t _data);
void pulseEnable(uint8_t _data);
void printIIC(uint8_t value);
inline size_t write(uint8_t value);

LCD.c

#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
#include "LCD.h"
#include "defined.h"
#include "typedefs.h"

#include "mcc_generated_files/i2c1.h"

#define FCY 2000000UL

#include <libpic30.h>

#include "mcc_generated_files/system.h"

uint8_t _Addr;
uint8_t _displayfunction;
uint8_t _displaycontrol;
uint8_t _displaymode;
uint8_t _numlines;
uint8_t _cols;
uint8_t _rows;
uint8_t _backlightval;

I2C1_MESSAGE_STATUS status;

void LCD_Init(){            //LCD????
    _Addr = LCD_ADD;
    _cols = 16;
    _rows = 2;
    _backlightval = LCD_NOBACKLIGHT;
    _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
    begin(_cols, _rows, LCD_5x8DOTS);
}

void begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
	if (lines > 1) {
		_displayfunction |= LCD_2LINE;
	}
	_numlines = lines;

	if ((dotsize != 0) && (lines == 1)) {
		_displayfunction |= LCD_5x10DOTS;
	}

	__delay_ms(50); 
  
	expanderWrite(_backlightval);	// backlight off
	__delay_ms(1000);

  	//put the LCD into 4 bit mode
	// this is according to the hitachi HD44780 datasheet
	// figure 24, pg 46

	write4bits(0x03 << 4);
  	 __delay_us(4500);
	write4bits(0x03 << 4);
	__delay_us(4500);
	write4bits(0x03 << 4); 
	__delay_us(150);
	write4bits(0x02 << 4);
    
	command(LCD_FUNCTIONSET | _displayfunction);  
	_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
	display();
	clear();
	_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
	command(LCD_ENTRYMODESET | _displaymode);
	home();
}

void display() {
	_displaycontrol |= LCD_DISPLAYON;
	command(LCD_DISPLAYCONTROL | _displaycontrol);
}

void clear(){
	command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero
	__delay_us(2000);  // this command takes a long time!
}

void home(){
	command(LCD_RETURNHOME);  // set cursor position to zero
	__delay_us(2000);  // this command takes a long time!

}

void noBacklight() {
	_backlightval=LCD_NOBACKLIGHT;
	expanderWrite(0);
}

void backlight() {
	_backlightval=LCD_BACKLIGHT;
    expanderWrite(0);
}

void setCursor(uint8_t col, uint8_t row){
	int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
	if ( row > _numlines ) {
		row = _numlines-1;    // we count rows starting w/0
	}
	command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
}

size_t print(const uint8_t *buffer, size_t size){
  size_t n = 0;
  while (size--) {
    if (write(*buffer++)) n++;
    else break;
  }
  return n;
}

void command(uint8_t value) {
	send(value, 0);
}

void send(uint8_t value, uint8_t mode) {
	uint8_t highnib=value&0xf0;
	uint8_t lownib=(value<<4)&0xf0;
        write4bits((highnib)|mode);
	write4bits((lownib)|mode);
}

void write4bits(uint8_t value) {
	expanderWrite(value);
    __delay_ms(1);
	pulseEnable(value);
    __delay_ms(1);
}

uint8_t writeBuffer[3] = {};

void pulseEnable(uint8_t _data){
	expanderWrite((uint8_t)(_data | 0b00000100));	// En high
    __delay_us(100);    //on 1us, sent wrong value...
	
	expanderWrite((uint8_t)_data & (~En));	// En 
    __delay_us(100);    //on 1us, sent wrong value...
}

void expanderWrite(uint8_t _data){
    writeBuffer[0] = (uint8_t)((_data) | _backlightval);
    I2C1_MasterWrite(writeBuffer, 1, LCD_ADD, &status);
}

inline size_t write(uint8_t value){
	send(value, Rs);
	return 1;
}

メイン
main.c

#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
#include "LCD.h"

#include "mcc_generated_files/i2c1.h"

#define FCY 2000000UL

#include <libpic30.h>

#include "mcc_generated_files/system.h"

int main(void)
{
    I2C1_Initialize();
    LCD_Init();
    backlight();

    setCursor(0, 0);
    print("Hello", 5);
    setCursor(0, 1);
    print("World", 5);
    
    return 1;
}

モータにPWM制御したときの電圧の実効値

DCモーターのPWM制御するときの話です。

PWMのデューティ比を線形に動かしても制御しても電圧は線形になりません。

電力で考える必要があります。

以下の図のようにPWM制御されたドライバがデューティ比Dで電圧を出力します。
電流IもV/RでPWMに従います。
さらに電力P=VIでPWMに従います。

f:id:sgrsn1711:20190429151521p:plain

この時のΔtの間にドライバのする仕事Wは、

 \begin{aligned} W &=\int_{\Delta t} P d t \\ &=D T \frac{V^{2}}{R} N \\ &=D \frac{V^{2}}{R} \Delta t \end{aligned}

で、Δtで除して仕事率Pは、

 \begin{aligned} P &=\frac{W}{\Delta t} \\ &=D \frac{V^{2}}{R} \end{aligned}

ですね。

ここで、電圧Vaを印加されたモータのする仕事率Pmを考えると、

 P_{m}=\frac{V_{a}^{2}}{R}

です。
PWM制御された電圧を印加すると、

 P_{m}=P

なので、結局、

 \begin{array}{l}{V_{a}^{2}=D V^{2}} \\ {V_{a}=\sqrt{D} V}\end{array}

という式が得られました。

つまり、PWMのデューティ比は電圧の係数ではなく電力の係数になっているということです。

メカナムホイールロボットの制御

メカナムホイール4輪ロボットの制御をしました。

運動学を求める

メカナムホイールロボットを以下の図のようにします。

ホイールの角度は基本的に45°であるため,角車輪のx軸とy軸に対する速度の寄与は同じになります.
図より、各ホイールの周速度 v_1, v_2, v_3, v_4とすれば、


\begin{equation}
\begin{aligned}
& \mathrm{v}_1=\mathrm{v}_{\mathrm{x}}+\mathrm{v}_{\mathrm{y}}+\left(l_x+l_y\right) \omega \\
& \mathrm{v}_2=-\mathrm{v}_{\mathrm{x}}+\mathrm{v}_{\mathrm{y}}+\left(l_x+l_y\right) \omega \\
& \mathrm{v}_3=-\mathrm{v}_{\mathrm{x}}-\mathrm{v}_{\mathrm{y}}+\left(l_x+l_y\right) \omega \\
& \mathrm{v}_4=\mathrm{v}_{\mathrm{x}}-\mathrm{v}_{\mathrm{y}}+\left(l_x+l_y\right) \omega
\end{aligned}
\end{equation}


と求まります。

行列表示すると以下のようになります。


 \begin{bmatrix} { v _ { 1 } } \\ { v _ { 2 } } \\ { v _ { 3 } } \\ { v _ { 4 } } \end{bmatrix}  =  \begin{bmatrix}{ 1 } & { 1 } & { l_x + l_y } \\ { -1 } & { 1 } & { l_x + l_y } \\ { -1 } & { -1 } & { l_x + l_y } \\ { 1 } & { -1 } & { l_x + l_y } \end{bmatrix} \begin{bmatrix}{ \dot { x } } \\ { \dot { y } } \\ { \dot { \theta } } \end{bmatrix}



ただし、速度ベクトル  [{\dot { x } } ,  { \dot { y } } ,  { \dot { \theta } }] はロボットフレーム{ R }から見た速度です。

実際に制御するときには慣性フレーム{ I }から見たらどう動けばいいのかを考えたいです。
ロボットフレームでの速度ベクトルは、慣性フレームでの速度ベクトルから以下の式で表されます。


\begin{bmatrix}{ \dot { x } } \\ { \dot { y } } \\ { \dot { \theta } }\end{bmatrix}  =  \begin{bmatrix} { \cos \theta } & { \sin \theta } & { 0 } \\ { - \sin \theta } & { \cos \theta } & { 0 } \\ { 0 } & { 0 } & { 1 } \end{bmatrix}   \begin{bmatrix} { \dot { x } _ { I } } \\ { \dot { y } _ { I } } \\ { \dot { \theta } _ { I } } \end{bmatrix}

よって、前の式に代入して、以下の式が得られます。


 \begin{bmatrix} { v _ { 1 } } \\ { v _ { 2 } } \\ { v _ { 3 } } \\ { v _ { 4 } } \end{bmatrix} = \begin{bmatrix}{ 1 } & { 1 } & { l_x + l_y } \\ { -1 } & { 1 } & { l_x + l_y } \\ { -1 } & { -1 } & { l_x + l_y } \\ { 1 } & { -1 } & { l_x + l_y } \end{bmatrix} \begin{bmatrix} { \cos \theta } & { \sin \theta } & { 0 } \\ { - \sin \theta } & { \cos \theta } & { 0 } \\ { 0 } & { 0 } & { 1 } \end{bmatrix}  \begin{bmatrix}{ \dot { x } _ { l } } \\ { \dot { y } _ { I } } \\ { \dot { \theta } _ { I } } \end{bmatrix}

これが運動学になります。
この式を用いればロボットの速度及び角速度から、各車輪の速度が求められます。

さて、運動学が求まったのでロボットを制御することができます。

とりあえず、制御データの受け渡しブロック図でも書いてみます。

だいたいこんなもんですかね。経路計画のところが若干フワフワしてますね。
簡単に目標位置と現在位置の差分を分割してやればいいんじゃないかな。

つづきはまた今度

おまけ ついでに順?運動学を求める

普通はオドメトリ用の車輪を別に用意したりするのですが、
車輪の滑りがないという仮定をすれば、各車輪の角速度からロボットの速度が求められます。

ここで簡単のため係数行列を Aとおきます。


 \begin{bmatrix} { v _ { 1 } } \\ { v _ { 2 } } \\ { v _ { 3 } } \\ { v _ { 4 } } \end{bmatrix}  = A \begin{bmatrix} { \dot { x } } \\ { \dot { y } } \\ { \dot { \theta } } \end{bmatrix}

運動学を求めるには、係数行列 A逆行列を求めればよいです。
逆行列 Xが求まれば、

 \begin{bmatrix}{ \dot { x } } \\ { \dot { y } } \\ { \dot { \theta } } \end{bmatrix} = X  \begin{bmatrix} { v _ { 1 } } \\ { v _ { 2 } } \\ { v _ { 3 } } \\ { v _ { 4 } } \end{bmatrix}
のように運動学が求まります。

しかし、求めた係数行列 Aは4×3行列であるため、疑似逆行列(一般逆行列)というものを用います。
 Aに対して疑似逆行列は以下の式で求まります。
 A ^ { + } = \left( A ^ { T } A \right) ^ { - 1 } A ^ { T }

手計算で求めるのは大変なので私はmatlabを使いました。(手計算してもいい)
使用したmatlabコードです。

% φをxとおいた

syms lx ly;
A = [1 1 lx+ly; -1 1 lx+ly; -1 -1 lx+ly; 1 -1 lx+ly];
pinv(A)

出力が以下のように出るので、

ans =
 
[            1/4,            -1/4,            -1/4,             1/4]
[            1/4,             1/4,            -1/4,            -1/4]
[1/(4*(lx + ly)), 1/(4*(lx + ly)), 1/(4*(lx + ly)), 1/(4*(lx + ly))]

求まった疑似逆行列 A ^ { + }を用いて、


\begin{bmatrix} { \dot { x } } \\ { \dot { y } } \\ { \dot { \theta } } \end{bmatrix} =  \frac{1}{4} \begin{bmatrix} { 1 } & { -1 } & { -1 } & { 1 } \\ { 1 } & { 1 } & { -1 } & { -1 } \\ { \frac { 1 } { l_x+l_y } } & { \frac { 1 } { l_x+l_y } } & { \frac { 1 } { l_x+l_y } } & { \frac { 1 } { l_x+l_y } } \end{bmatrix} \begin{bmatrix} { v _ { 1 } } \\ { v _ { 2 } } \\ { v _ { 3 } } \\ { v _ { 4 } } \end{bmatrix}

と求まります。
これが各車輪の角速度からロボットの速度を求める運動学になります。

pythonでWebカメラから文字認識を行う

OCR(Optical Character Recognition/Reader)ってやつです。

Anacondaを使用してます

まずはモジュールのインストール

conda install -c brianjmcguirk pyocr

OCRツールのtesseractをいれます。

ここにいろいろ書いてある。
github.com

学習済みデータを保存しておく必要があります。
英語用のeng.traineddataと日本語用のjpn.traineddataをとりあえずダウンロードして保存します。

私の場合はAnacondaを使用していたので以下のディレクトリに保存しました。
C:\Users\myName\Anaconda3\envs\myEnv\Library\bin\tessdata


まずはWebカメラなしで画像から文字認識をできるか確認

from PIL import Image
import cv2
import sys
import pyocr
import pyocr.builders
import time

tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("No OCR tool found")
    sys.exit(1)

tool = tools[0]
langs = tool.get_available_languages()
lang = langs[0]
last_txt = ""

frame = cv2.imread("OCRtest_imgae.png")
orgHeight, orgWidth = frame.shape[:2]
size = (int(orgWidth/4), int(orgHeight/4))
glay = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
image = cv2.resize(glay, size)
txt = tool.image_to_string(
    Image.fromarray(image),
    lang="jpn",
    builder=pyocr.builders.TextBuilder(tesseract_layout=6)
)
if len(txt) != 0 and txt != last_txt:
    last_txt = txt
    print( txt )

画像はこれね
f:id:sgrsn1711:20190107191517p:plain
画像の名前はOCRtest_imgae.pngとしてください。

WebカメラOCR

画像でできることを確認したので、画像読み取りのところをwebカメラに置き換える
以下のようになります。

Webカメラですが、私はLogicoolのc270mをAmazonから購入しました。
今では価格が上がっていますね。まあ安心感はあるでしょうけど


他のだと、webカメラは今のところこれが安くてよさげ

コード

from PIL import Image
import cv2
import sys
import pyocr
import pyocr.builders
import time

tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("No OCR tool found")
    sys.exit(1)
tool = tools[0]

langs = tool.get_available_languages()
lang = langs[0]
capture = cv2.VideoCapture(0)
last_txt = ""
while True:
    ret, frame = capture.read()
    orgHeight, orgWidth = frame.shape[:2]
    size = (int(orgWidth/4), int(orgHeight/4))
    glay = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    image = cv2.resize(glay, size)
    t = time.time()
    txt = tool.image_to_string(
        Image.fromarray(image),
        lang="eng",
        builder=pyocr.builders.TextBuilder(tesseract_layout=6)
    )
    #print(time.time() - t)
    if len(txt) != 0 and txt != last_txt:
        last_txt = txt
        print( txt )

    cv2.imshow("Capture", image)
       
    if cv2.waitKey(33) >= 0:
        break

cv2.destroyAllWindows()