meideru blog

家電メーカーで働いているmeideruのブログです。主に技術系・ガジェット系の話を書いています。

【祝】PICマイコンで開発していた戦車型のロボットが完成!

      2018/10/20

うれしいお知らせです!やっと完成しました!

ここのところ何かと話題にしていますが、今、戦車型のロボットを開発中です(*´ω`*)(開発と言えるほど高度なものなのかどうかは微妙なところですがw)今日は、今、開発している戦車型のロボットについて記事を書きたいと思います。概要まだ開発途中ですが、こんな...

 

戦車型のロボットが完成しました!

@作った戦車型のロボット
PICマイコンで作った戦車型のロボット

 

今日は、このロボットについて記事を書きます!

概要

メイン基板(マイコンボード)のスリープスイッチ(電源スイッチ)を押すと起動します。

@メイン基板(マイコンボード)
実装したモータコントローラ

起動後はプログラミングされた通りにロボットが動きます。

なお、現時点でセンサは付いていないので、状況に合わせた動的な動きはできません。(今後改善する予定です)

構成

このロボットは主に6つで構成されています。

  • マイコンボード
  • モータコントローラ
  • ギアボックス
  • キャタピラ
  • ユニーサルプレート
  • ネジ

の6つです。

マイコンボード

マイコンボードはこんな感じで作成しました。

@自作のマイコンボード(表面)
自作のマイコンボード(表面)

 

表面はこんな感じです。

マイコンはPICマイコン(16F1827)です。

左のタクトスイッチはスリープボタン(電源ボタン)で、右のタクトスイッチはリセットボタンです。

LEFTとRIGHTのコネクタ(メス)からは、左右のモータをコントロールするためのPWM信号を出力されます。

POWは、POWERのことで電源(9V)を入力するためのコネクタ(メス)です。

OUTは、POWER(9V)をモータコントローラに向けて出力するためのコネクタ(メス)です。

 

3端子レギュレータで9V→3.3Vに落としてマイコンに電源を供給しています。

パスコンは100μFを使っています。普通は0.01μFなどの容量を使うのが普通らしいのですが、モータドライバで大きな電流を使うので、それだとダメでした。

 

@マイコンボード(裏面)
自作のマイコンボードの裏面

 

裏面はこんな感じです。

配線が汚いですね。複雑なハンダ付けをしたのは初めてなので許してください(;´・ω・)

 

@実装したモータコントローラ
実装したモータコントローラ

 

こんな感じで本体に実装しました。

コネクタにケーブルをつないだら様になりますねw

それから、ユニバーサルプレートに結束バンドで直接、基板をくっつけています。ショートする恐れはないと思います(>_<)

モータコントローラ

モータコントローラはこんな感じです。

@モータコントローラ(表面)
自作のモータコントローラの表面

 

モータドライバは東芝のTA7291Pを使用しています。

パスコンは1つで2つのモータコントローラに接続しています。それぞれのモータドライバに別々に接続するために2つ用意したほうが良かったんでしょうか?電子工作初心者なので、その辺はよくわかりません(; ・`д・´)

5つのコネクタはそれぞれ、電源入力(9V)、左右のモータの制御信号入力、左右のモータへの出力電圧です。

 

@実装したモータコントローラ
実装したモータコントローラ見辛いと思いますが、こんな感じで実装しています。

マイコンボードと同様に、結束バンドで直接、ユニバーサルプレートにくっつけています。

ギアボックス

ギアボックスはタミヤ製品を使用しています。アマゾンで購入しました。

この製品ではギア比を4段階で選べます。そして、このロボットでは一番高いギア比を使用しています。

キャタピラ

キャタピラもタミヤ製品でアマゾンで購入しました。(というか今回のロボットで使用している部品は全てタミヤ製品ですw)

色々な大きさを組み合わせて、キャタピラの長さを調節することができます。

ユニバーサルプレート

ユニバーサルプレートもタミヤ製品です。

2枚セットです。今回作ったロボットでは3枚使用しているので、2セット注文しました。

ネジ

ネジもタミヤ製品!全部タミヤ製品です!

(ただし、どの製品を買ったかは忘れてしまいました(;´・ω・))

ソースコード

ソースコードはこんな感じです。

PICマイコン(16F1827)向けで、コンパイラはXC8です。

// 戦車型のロボット
// PIC16F1827

// ピンの割り当てについて
// RA0
// RA1
// RA2
// RA3(出力) - 左モータ信号1(CCP,PWM)
// RA4(出力) - 左モータ信号2(CCP,PWM)
// RA5(入力) - リセットボタン(MCLR)
// RA6
// RA7
// Vdd(不明) - 9V
// Vss(不明) - GND
// RB0(出力) - 右モータ信号1(CCP,PWM)
// RB1(入力) - スリープボタン(IOC)
// RB2(出力) - LED(緑)
// RB3(出力) - LED(赤)
// RB4
// RB5
// RB6(出力) - 右モータ信号2(CCP,PWM)
// RB7

// PWM周波数は500Hzとする
// CCP1(RB0), CCP2(RB6) - 右モータ
// CCP3(RA3), CCP4(RA4) - 左モータ

#include <xc.h>
#include <stdbool.h>

// delay_ms(x) のための定義
#define _XTAL_FREQ 8000000

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = ON       // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF      // PLL Enable (4x PLL disabled)
#pragma config STVREN = OFF     // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will not cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

// プロトタイプ宣言
bool InitDevice();                                  // デバイスの初期設定 
bool ChangeRightMotorSpeed(int direct, int per);  // 右モータの速度を変更する(1で前進,2で後進)
bool ChangeLeftMotorSpeed(int direct, int per);   // 左モータの速度を変更する(1で前進,2で後進)
bool ChangeFrontMotorSpeed(int per);              // モータを前進させる速度を変更
bool ChangeBackMotorSpeed(int per);               // モータを後進させる速度を変更
bool ChangeStopMotorSpeed(void);                  // モータを停止させる
bool ChangeBrakeMotorSpeed(bool left, bool right);   // 左右のモータにブレーキをかける
bool ChangeRightTurnMotorSpeed(int per);            // モータを右ターンさせるための速度を変更
bool ChangeLeftTurnMotorSpeed(int per);          // モータを左ターンさせるための速度を変更
bool ChangeGreenLed(int mode);                     // LED(緑)を変化させる(0がOFF, 1がON, 2が反転)
bool ChangeRedLed(int mode);                       // LED(赤)を変化させる(0がOFF, 1がON, 2が反転)
int GetGreenLedStatus();                          // 現在の緑LEDの状態を取得する(0がOFF, 1がON)
int GetRedLedStatus();                            // 現在の赤LEDの状態を取得する(0がOFF, 1がON)


// 割り込み処理
void interrupt ISR(void)
{
    // スリープモード(外部信号による割り込み処理)
    if(IOCBFbits.IOCBF1 == 1)
    {        
        // チャタリングが終了するまで待機
        __delay_ms(20);
        
        // IOC割り込み信号がHIGHであることを確認
        if(PORTBbits.RB1 == 1)
        {   
            // ボタンが離されたら先に進む
            __delay_ms(20);
            while(PORTBbits.RB1);
            __delay_ms(20);
            
            // PWMのDUTYのバックアップ
            char CCP1BK = CCPR1L;
            char CCP2BK = CCPR2L;
            char CCP3BK = CCPR3L;
            char CCP4BK = CCPR4L;
            
            // 緑LEDが点灯しているかどうか
            int GreenLedBK = GetGreenLedStatus();
            // 赤LEDが点灯しているかどうか
            int RedLedBK = GetRedLedStatus();
            
            // PWM出力をOFFに変更(ノイズでマイコンがスリープから復帰しないようにするため)
            CCPR1L = 0;
            CCPR2L = 0;
            CCPR3L = 0;
            CCPR4L = 0;
            
            // 緑LEDを消灯
            ChangeGreenLed(0);
            // 赤LEDを消灯
            ChangeRedLed(0);
            
            // IOCピンの割り込みフラッグをFALSE
            IOCBFbits.IOCBF1 = 0;
            
            // 待機(コンデンサを放電しきってノイズでスリープから復帰しないようにするため)
            __delay_ms(500);
            
            // スリープモードに移る
            SLEEP();
            NOP();
            
            // スリープから復帰したときのチャタリング防止
            __delay_ms(20);
             while(PORTBbits.RB1);
            __delay_ms(20);
            
            // 緑ランプを点灯
            GreenLedBK ? ChangeGreenLed(1) : ChangeGreenLed(0);+
            // 赤ランプを点灯
            RedLedBK ? ChangeRedLed(1) : ChangeRedLed(0);
            
            // PWMのDUTYを元に戻す
            CCPR1L = CCP1BK;
            CCPR2L = CCP2BK;
            CCPR3L = CCP3BK;
            CCPR4L = CCP4BK;
        }
    }
    
    // 割り込みフラッグをFALSE
    IOCBFbits.IOCBF1 = 0;
}


int main(void)
{
	// 初期設定を行う
	if (!InitDevice())
    {
		// 失敗したとき
		return 0;
	}
    
    // メインループ
	while (1)
	{
        // 前進する
        ChangeFrontMotorSpeed(50);
        __delay_ms(3000);

        // ブレーキ
        ChangeBrakeMotorSpeed(true, true);
        __delay_ms(1000);        

        // 後進する
        ChangeBackMotorSpeed(50);
        __delay_ms(3000);
        
        // ブレーキ
        ChangeBrakeMotorSpeed(true, true);
        __delay_ms(1000);
        
        // 停止する
        ChangeStopMotorSpeed();
        __delay_ms(1000);
        
        // 右ターンする
        ChangeRightTurnMotorSpeed(60);
        __delay_ms(3000);

        // ブレーキ
        ChangeBrakeMotorSpeed(true, true);
        __delay_ms(1000);
        
        // 停止する
        ChangeStopMotorSpeed();
        __delay_ms(1000);

        // 左ターンする
        ChangeLeftTurnMotorSpeed(60);
        __delay_ms(3000);
        
        // ブレーキ
        ChangeBrakeMotorSpeed(true, true);
        __delay_ms(1000);
        
        // 停止する
        ChangeStopMotorSpeed();
        __delay_ms(1000);
	}
    
	return 0;
}


// デバイスの初期設定
bool InitDevice()
{
	// クロックを設定
	OSCCON = 0b01110010;        // クロックを8MHzに設定
    
	// ピンの入出力に設定
	TRISA = 0b00100000;
	TRISB = 0b00000010;

	// すべてのピンをデジタル出力に設定
	ANSELA = 0b00000000;
	ANSELB = 0b00000000;
    
    // すべてのピンの出力をOFFに変更
    PORTA = 0b00000000;
    PORTB = 0b00000000;

	// CCPのピンのモード設定
	CCP1CON = 0b00001100;   // CCP1をPWMモードに設定(かつCCPR1Lの下位2ビットを0に設定)
	CCP2CON = 0b00001100;   // CCP2をPWMモードに設定(かつCCPR2Lの下位2ビットを0に設定)
	CCP3CON = 0b00001100;   // CCP3をPWMモードに設定(かつCCPR3Lの下位2ビットを0に設定)
	CCP4CON = 0b00001100;   // CCP4をPWMモードに設定(かつCCPR4Lの下位2ビットを0に設定)

	// CCP1, CCP2のピンを設定
	APFCON0bits.CCP1SEL = 1;    // RB0 を CCP1 に設定
    APFCON0bits.CCP2SEL = 0;    // RB6 を CCP2 に設定
    
	// CCPにタイマを割り当てる
	CCPTMRS = 0b00000000;   // CCP1~CCP4のタイマをそれぞれTimer2

	// タイマの設定
	T2CON = 0b00000110;     // TIMER2を有効にしてスケールをすべて16に設定
	PR2 = 249;              // PWM Periodを2.00 * 10^-3(= (249(PR2) + 1) * 1 / 8000000(CLOCK) * 16(Prescale))(500Hz)
    
    // 割り込み処理の設定
    INTCONbits.GIE = 1;     // グローバルな割り込みを有効
    INTCONbits.IOCIE = 1;   // IOCの割り込みを有効
    IOCBPbits.IOCBP1 = 1;   // RB1を立ち上がりを検出するIOCピンに設定
    
    // モータを停止させる
    ChangeStopMotorSpeed();
    
    // 赤LEDを点滅→点灯にする(3回点滅)
    for(int i = 1; i <= 5; i++)
    {
        // 点滅
        ChangeRedLed(2);
        __delay_ms(500);    
    }
    
    // 赤LEDを消灯
    ChangeRedLed(0);
    
    // 緑LEDを点灯
    ChangeGreenLed(1);
    
	return true;
}


// 右モータの速度を変更する(1で前進,2で後進)
bool ChangeRightMotorSpeed(int direct, int per)
{
	// 変数
	char speed;
    
	switch (per)
	{
		// 0%出力
	case 0:
		speed = 0b00000000;
		break;

		// 10%出力
	case 10:
		speed = 0b00011001;
		break;

		// 20%出力
	case 20:
		speed = 0b00110010;
		break;

		// 30%出力
	case 30:
		speed = 0b01001011;
		break;

		// 40%出力
	case 40:
		speed = 0b01100100;
		break;

		// 50%出力
	case 50:
		speed = 0b01111101;
		break;

		// 60%出力
	case 60:
		speed = 0b10010110;
		break;

		// 70%出力
	case 70:
		speed = 0b10101111;
		break;

		// 80%出力
	case 80:
		speed = 0b11001000;
		break;

		// 90%出力
	case 90:
		speed = 0b11100001;
		break;

		// 100%出力
	case 100:
		speed = 0b11111010;
		break;

		// エラー
	default:
		return false;
	}

	// 方向を決定
	if (direct == 1)
	{ // 前進
		CCPR1L = 0b00000000;
		CCPR2L = speed;
	}
	else if (direct == 2)
	{ // 後進
        CCPR1L = speed;
		CCPR2L = 0b00000000;
	}
    else
    {
        return false;
    }

	return true;
}

// 左モータの速度を変更する(1で前進,2で後進)
bool ChangeLeftMotorSpeed(int direct, int per)
{
	// 変数
	char speed;

	switch (per)
	{
		// 0%出力
	case 0:
		speed = 0b00000000;
		break;

		// 10%出力
	case 10:
		speed = 0b00011001;
		break;

		// 20%出力
	case 20:
		speed = 0b00110010;
		break;

		// 30%出力
	case 30:
		speed = 0b01001011;
		break;

		// 40%出力
	case 40:
		speed = 0b01100100;
		break;

		// 50%出力
	case 50:
		speed = 0b01111101;
		break;

		// 60%出力
	case 60:
		speed = 0b10010110;
		break;

		// 70%出力
	case 70:
		speed = 0b10101111;
		break;

		// 80%出力
	case 80:
		speed = 0b11001000;
		break;

		// 90%出力
	case 90:
		speed = 0b11100001;
		break;

		// 100%出力
	case 100:
		speed = 0b11111010;
		break;

		// エラー
	default:
		return false;
	}

	// 方向を決定
	if (direct == 1)
	{ // 前進
		CCPR3L = 0b00000000;
		CCPR4L = speed;
	}
	else if (direct == 2)
	{ // 後進
        CCPR3L = speed;
		CCPR4L = 0b00000000;
    }
    else
    {
        return false;
    }

	return true;
}


// モータを前進させる速度を変更
bool ChangeFrontMotorSpeed(int per)
{
    // モータを指定の速度で前進させる
    if(ChangeRightMotorSpeed(1, per) && ChangeLeftMotorSpeed(1, per))
    {
        return true;
    }
    
    return false;
}

// モータを後進させる速度を変更
bool ChangeBackMotorSpeed(int per)
{
    // モータを指定の速度で後進させる
    if(ChangeRightMotorSpeed(2, per) && ChangeLeftMotorSpeed(2, per))
    {
        return true;
    }
    
    return false;
}

// モータを停止させる
bool ChangeStopMotorSpeed(void)
{
    // モータを停止させる
    if(ChangeRightMotorSpeed(1, 0) && ChangeLeftMotorSpeed(1, 0))
    {
        return true;
    }
    
    return false;
}

// 左右のモータにブレーキをかける
bool ChangeBrakeMotorSpeed(bool left, bool right)
{
    // 左
    if(left)
    {
        CCPR3L = 0b11111111;
		CCPR4L = 0b11111111;
    }
    
    // 右
    if(right)
    {
        CCPR1L = 0b11111111;
		CCPR2L = 0b11111111;
    }
    
    return true;
}

// モータを右ターンさせるための速度を変更
bool ChangeRightTurnMotorSpeed(int per)
{
    // モータを指定の速度に変更して右ターンさせる
    if(ChangeRightMotorSpeed(1, per) && ChangeLeftMotorSpeed(2,per))
    {
        return true;
    }
    
    return false;
}

// モータを左ターンさせるための速度を変更
bool ChangeLeftTurnMotorSpeed(int per)
{
    // モータを指定の速度に変更して左ターンさせる
    if(ChangeRightMotorSpeed(2, per) && ChangeLeftMotorSpeed(1, per))
    {
        return true;
    }
    
    return false;
}


// LED(緑)を変化させる(0がOFF, 1がON, 2が反転)
bool ChangeGreenLed(int mode)
{
    // モードに合わせて分岐
    switch(mode)
    {
        // LED(緑)をOFF
        case 0:
            PORTBbits.RB2 = 0;
            break;
            
        // LED(緑)をON
        case 1:
            PORTBbits.RB2 = 1;
            break;
            
        // LED(緑)を反転
        case 2:        
        // 緑LEDを反転する
        if(PORTBbits.RB2 == 1)
        {
            PORTBbits.RB2 = 0;
        }
        else
        {
            PORTBbits.RB2 = 1;
        }
        break;
        
        // その他
        default:
            return false;
    }
    
    return true;
}

// LED(赤)を変化させる(0がOFF, 1がON, 2が反転)
bool ChangeRedLed(int mode)
{
    // モードに合わせて分岐
    switch(mode)
    {
        // LED(赤)をOFF
        case 0:
            PORTBbits.RB3 = 0;
            break;
            
        // LED(赤)をON
        case 1:
            PORTBbits.RB3 = 1;
            break;
            
        // LED(赤)を反転
        case 2:        
        // 赤LEDを反転する
        if(PORTBbits.RB3 == 1)
        {
            PORTBbits.RB3 = 0;
        }
        else
        {
            PORTBbits.RB3 = 1;
        }
        break;
        
        // その他
        default:
            return false;
    }
    
    return true;
}

// 現在の緑LEDの状態を取得する(0がOFF, 1がON)
int GetGreenLedStatus()
{
    return PORTBbits.RB2;
}

// 現在の赤LEDの状態を取得する(0がOFF, 1がON)
int GetRedLedStatus()
{
    return PORTBbits.RB3;
}

 

起動すると、前進→後進→左旋回→右旋回、するようにプログラミングしました。

また、スリープスイッチ(電源スイッチ)は割り込み処理で実行するようにしています。

リセットスイッチはMCLR端子で行うようにしています。

動作

動作はこんな感じです。YouTubeにアップしました。

勉強になったこと

機械工学(?)を学べた

これを機械工学と呼べるほど高級なものなのかどうかはわかりませんが、機械工学の雰囲気だけは知ることができました(*’▽’)

実は、ある程度完成してから、ある問題が浮上しました。それは、ギア比が適切でなかったことです。

トルク不足で右旋回・左旋回をすることができませんでした。しかも、前進・後進は高速で動いていました。これは、ギアボックスのギア比を調節すれば解決できました。

恥ずかしながらギアボックスを使用するまで、「トルク」や「ギア比」の意味すら知りませんでした(;´∀`)

大学での物理学は真面目に講義を受けていなかったので・・・w

とっても勉強になりました!

ハードウェアの設計を実践的に学べた

一番勉強になったのはこれですね。ハードウェアの設計を実践的に学べました。

(ここで言うハードウェアとは電子回路のことを指します。)

私は、今までプログラミングを中心に勉強してきたので、電子回路については知識で知っている程度で、ハンダゴテを使って実践したりすることは、ほとんどしてきませんでした。

いざやってみると、トラブルの連続でした。

例えば、スイッチが思うように動作しないという問題がありました。結論から言うと、これは「チャタリング」という問題でした。そして、プログラム側でチャタリングが終了するまで待機するようにしたところ、解決することができました。

他には、モータの出力を全開にするとマイコンがリセットされてしまうという問題も起こりました。これは、マイコンのパスコンを大容量にすることで解決することができました。

このように、講義では学べないようなことが実践で学べたのは、とても良かったと思います。

反省点

反省点がいくつかあります。

回路図を書くべきだった

回路図を書くべきでしたね。ブログにこの記事を書いているときに思いましたw

皆さんには完成したマイコンボードとモータコントローラをお見せしていますが、肝心なのは回路がどうなっているのかという点ですよねw

それがないので、解り難いと思います。

反省します(; ・`д・´)

センサを組み込むべきだった

上述した通り、このロボットにはセンサが組み込まれていません。なので、状況に応じた動的な動きが全くできませんw

でも、これは今からでも解決できる問題でもあります。今からでも組み込めるからです。

後日、組み込めたら組み込みたいと思います。

とりあえず、前方と下側に距離センサを付けようと思います。

これで、前方の障害物を回避したり、落下しそうになったら旋回するようになるはずです。

ロボットのバランスが悪すぎる

このロボットは後方に質量が寄っているため、坂などを上ろうとすると、ひっくりかえってしまいますw

戦車なのに致命的な欠点ですねw

 

以上です!

ノシ

 

PS

自動車の免許証を取るために、今日から教習所に通います!

 - 技術系