meideru blog

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

PICマイコン(16F1827)のPWMのやり方

      2018/10/20

今、PICマイコンを使った戦車型のロボットを作っています。

最終的にはプログラミングで、このロボットを制御できるようにします。プログラミングで制御できるということはマイコンが必要です。

PICマイコンは8bitで18ピンの16F1827を採用します。このPICを採用する理由は、最大で4つのピンで同時にPWMを出力できるからです。戦車型のロボットでは最低でも左右のモータ2つを制御する必要があるので、ピッタリだと思います。

一般的に、マイコンでモータを制御するにはPWMを使用します。

@戦車型のロボット
開発中のPICマイコンを使った戦車型のロボット(余談ですが、キャタピラやギアボックス等はタミヤ製を使用しています。タミヤ最高!!)

 

今日は、PICマイコン(16F1827)のPWMのやり方を書こうと思います。

PWMとは

PWM(Pulse Width Modulation)とは、デジタル出力を疑似的にアナログ出力に見せかける技術です。

原理はONとOFFを高速でスイッチングして、時間的に電圧の平均を変化させることによるものです。

下の画像を見ていただけると理解しやすいかと思います。赤が電圧の平均です。ONとOFFを高速で切り替えることによって電圧の平均が変化していますね。これがPWMです。

@Wikipediaより引用しました
PWMの原理

もう1つ大切なのは、なぜマイコンではPWMが使われるかということです。

それは、マイコンではアナログ出力ができないからです。(バカっぽい答えですみませんw)

本題に入る前に、PWMの設定に必要なことを説明します

PWMについて簡単に説明したところで、本題のPWMのやり方について説明しようと思いますが、その前にいくつか説明しておこうと思います。

以下はすべて公式のデータシートを参考にしています。

【参考】
PIC16F1827のデータシート

16F1827にはPWM出力できる端子が4つある

16F1827には、PWM出力できる端子が4つあります。

  • CCP1
  • CCP2
  • CCP3
  • CCP4

の4つです。

CCPとは

CCPとは、Capture/Compare/PWMの略です。つまり、CCPという端子はこの3つの機能の中から選んで使えるということです。

この3つの機能のどれを使うかは、CCPxCONレジスタで設定します。

xにはそれぞれに対応する数字が入ります。例えばCCP1端子で3つの機能のうちどれを使うかの設定はCCP1CONレジスタを設定します。

Timerとは

CCP端子のPWM機能を使用するには、Timer(タイマ)の機能を使う必要があります。(ちなみにCaptureとCompareでもTimerを使用する必要があります)

上の方で、PWMの原理とはONとOFFを高速で切り替えて、時間的に電圧の平均を変化させることだという話をしました。

つまり、基準となる時間が必要なのです。そして、その役割をTimerが果たします。

 

下はデータシートより切り取った画像です。

@PWMとTimer
PICマイコンのPWMとTimerの関係

TMRx(xは数字)というワードが出てきているのがわかると思います。これがTimerです。

このようにTimerが時間的な基準になります。

 

16F1827ではCCPに割り当てることのできるTimer(タイマ)がTimer2/Timer4/Timer6の3つあります。

CCP1~CCP4のそれぞれにどのTimer(Timer2,Timer4,Timer6)を割り当てるかはCCPTMRSレジスタを設定します。

PWMのやり方

では本題のPWMのやり方について書いていきたいと思います。

目標

ここではCCP3(RA3ピン)とCCP4(RA4ピン)でPWMを出力できるように設定することを目標に解説していきたいと思います。

@PICマイコン(16F1827)のピンの配置
PICマイコン(16F1827)のピンの配置

具体的には、CCP3とCCP4のPWM出力を0%~100%まで10%刻みで調節できるようにしたいと思います。

また、クロックはPICマイコン内部のものを使用し、8MHzで駆動させたいと思います。

(コンパイラはXC8を想定しています。)

OSCCONレジスタの設定

OSCCONレジスタについては、データシートのP67に載っています。

OSCCONレジスタは、PICマイコンのクロック数を設定するためのレジスタです。

今回は、8MHzで駆動させようと思うので、以下のように設定します。

// クロックを設定
OSCCON = 0b01110010;    // クロックを8MHzに設定

1~0bitは、外部のクロックを使用するか外部のクロックを使用するかの設定です。今回は内部のクロックを使用しようと思うので、10を設定します。内部クロックを使用する設定の場合は0bitは何でもいいので11でもOKです。

6~3bitは、クロック数の設定です。8MHzで駆動させるためには、1110を設定します。

他のbitは無視します。

TRISA,TRISBレジスタの設定

TRISAレジスタとTRISBレジスタについては、それぞれデータシートのP122とP127に載っています。

TRISAレジスタはRA0~RA7ピンを、TRISBレジスタはRB0~RB7ピンを入力か出力かを設定するレジスタです。

今回はすべてのピンを出力にします。以下のように設定してください。

// すべてのピンを出力に設定
TRISA = 0b00000000;
TRISB = 0b00000000;

TRISAレジスタの0bitはRA0ピンの設定、TRISAレジスタの4bitはRA4ピンの設定、のようにbitとピン番号が対応しています。

0は出力、1は入力です。

CCPxCONレジスタの設定

CCPxCONレジスタについては、データシートのP204に載っています。

CCPxCON(xは数字)レジスタは、CCPx(xは数字)がCapture/Compare/PWMのうちどれとして働くのかを設定するレジスタです。それと、後で説明するCCPRxHレジスタの下位2bitの設定の役割も兼ねています。

例えば、CCP3の設定を行いたいときは、CCP3CONレジスタを設定します。CCP1を設定したいときはCCP1CONレジスタを設定します。それぞれの番号xが対応しています。

今回は以下のように設定します。

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

CCP3CONはCCP3ピンの設定を、CCP4CONはCCP4ピンの設定を行っています。xがそれぞれの数字に対応しています。

7~6bitは、Enhanced PWMをを使用するときの設定です。今回は普通のPWMを使用するので、この設定はデタラメでよいです。というかそもそもCCP3ピンとCCP4ピンはEnhanced PWMには対応していないので無視でかまいません。とりあえず、00と設定します。(Enhanced PWMについて詳しく知りたい方はデータシートをご覧ください。ここでは割愛します。)

5~4bitは、PRxの下位2bitです。PRxはTimer(タイマ)の周期の設定に関係するレジスタです。詳しくは後で説明しますので、とりあえず00にします。

3~0bitは、CCPxピンが何の役割(Capture/Compare/PWM)を果たすのかの設定です。PWMを使用するには1100と入力します。下位2bitは任意のものでいいので、1111でも1101とかでも構いません。

CCPTMRSレジスタの設定

CCPTMRSレジスタについては、データシートのP227に載っています。

CCPTMRSレジスタは、CCPxにTimer2/Timer4/Timer6のどれを割り当てるかを決めるレジスタです。

今回は、CCP3とCCP4の両方ともTimer2を割り当てることにします。以下のように設定します。

// CCPにタイマを割り当てる
CCPTMRS = 0b00000000;   // CCP3とCCP4のタイマをそれぞれTimer2

7~6bitは、CCP4にどのTimerを割り当てるかの設定。

5~4bitは、CCP3にどのTimerを割り当てるかの設定

3~2bitは、CCP2にどのTimerを割り当てるかの設定。

1~0bitは、CCP1にどのTimerを割り当てるかの設定となっています。

00はTimer2の割り当て、01はTimer4の割り当て、10はTimer6の割り当て、11はReserveの割り当てとなっています。

今回は、CCP3とCCP4にTimer2を割り当てるので、3~2bitを00、1~0bitを00とします。CCP1とCCP2は使用しないのでデタラメでよいです。とりあえず、0で埋めておきます。

TxCONレジスタの設定

TxCONレジスタについては、データシートのP191に載っています。

TxCON(xは数字)レジスタは、TimerX(xは数字)の設定を行うレジスタです。

今回使用するのはTimer2なので、T2CONレジスタの設定を行えばよいということになります。

以下のように設定します。

// タイマの設定
T2CON = 0b00000100;     // TIMER2を有効にしてスケールをすべて1に設定

7bitは、無視。

6~3bitは、ポストスケールの設定。

2bitは、TimerX(T2CONレジスタの場合はTimer2)が有効かどうかの設定。

1~0bitは、プリスケールの設定、となっています。

ポストスケールとプリスケールの説明については面倒なので割愛します。とりあえず、2bit以外はすべて0で埋めます。

PRxレジスタの設定

PRx(xは数字)レジスタは、TimerX(xは数字)の周期を設定を行うレジスタです。

以下の図を参考にしてください。

@PWMとTimer
PICマイコンのPWMとTimerの関係

この図を見ると理解しやすいかと思います。

TMRx = PRxと書いてあります。つまり、Timerの周期はPRxで決定するという意味です。

そして、Timerの周期はPWMの周期となります。

 

以下のように設定します。

// タイマの設定
PR2 = 255;

PR = 255と設定するとPWMの周期が決まります。

@PWMの周期の式
16F1827のPWMの周期の式Toscはクロック数の逆数のことです。それと、T2CONレジスタで(TMRx Prescale Value)は1と設定しています。

つまり今回は

PWM Period = [(255) + 1] * 4 * (1/8000000) * 1 = 1.28 * 10^(-4)となります。

CCPRxLレジスタの設定

CCPRxLレジスタは、CCPxで出力されるPWMのデューティー比を決定するレジスタです。

デューティー比とは下図のPulse Widthのことです。

@PWMのデューティー比
PICマイコンのPWMとTimerの関係TMRx = CCPRxH:CCPxCON<5:4>と書いてあります。

つまり、Pulse Width(デューティー比)はCCPRxHとCCPxCONの5~4bitで決まるという意味です。

 

CCPRxHについては、データシートのP212に以下のように注意書きがあります。

The PWM duty cycle is latched from CCPRxL into CCPRxH.

簡単に言えば、CCPRxHレジスタを設定する際はCCPRxLレジスタを設定してくださいということです。

 

Pulse Width(デューティー比)の設定については以下のような式です。

@Pulse Width(デューティー比)の関係式
PICマイコン(16F1827)のPulse Width(デューティー比)の関係式計算の例を示します。

80%の出力を行うときは、23-3より計算します。

CCPRxL:CCPxCON<5:4> = (Duty Cycle Ration) * 4(PRx + 1) = 0.8 * 4 * (255 + 1) = 819.2(10進数)≒ 1100110011(2進数) ≒ 1100110000(2進数)

前にも説明した通り、CCPxCON<5:4>は00に固定してあるので、式より求める解も00に合わせます。

 

基本的な説明は以上です!

これでPWMは扱えるはずです!

サンプルソース

ソースコードの説明

コンパイラはXC8です。

CCP3とCCP4に同時にPWMを出力します。

配線

配線はこんな感じです。

本来ならばブレッドボードにくっつくくらいの短い配線を使うべきなのですが、長いジャンパ線しか持っていないので、それを使っています。

見づらくて申し訳ありません・・・。

@配線
PICマイコン(16F1827)でPWMのテストの配線

 

電源は3Vを使用します。PICマイコンではデジタル信号は電源と同じ電圧が出力されるので、PWMのデューティー比が100%のときは3V出力されることになります。

RA3とRA4からPWM信号が出力されます。

ソースコード

それでは、サンプルのソースコードを示します。

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

// CCP3とCCP4をモータ出力(PWM)に割り当てる
// CCP3 - 右モータ
// CCP4 - 左モータ

#include <xc.h>
#include <stdbool.h>    // boolを使用するためのヘッダ

// 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 = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#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 = HI        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), high trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

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

// プロトタイプ宣言
bool InitDevice();                         // デバイスの初期設定 
bool ChangeRightMotorSpeed(int per);     // 右モータの速度を変更する
bool ChangeLeftMotorSpeed(int per);      // 左モータの速度を変更する

int main(void)
{
    // 初期設定を行う
    if(!InitDevice()){
        // 失敗したとき
        return 0;
    }
    
    while(1)
    {
        // 左右モータの出力を0%に変更
        ChangeRightMotorSpeed(0);
        ChangeLeftMotorSpeed(0);
        __delay_ms(1000);
        
        // 左右モータの出力を10%に変更
        ChangeRightMotorSpeed(10);
        ChangeLeftMotorSpeed(10);
        __delay_ms(1000);
        
        // 左右モータの出力を20%に変更
        ChangeRightMotorSpeed(20);
        ChangeLeftMotorSpeed(20);
        __delay_ms(1000);
        
        // 左右モータの出力を30%に変更
        ChangeRightMotorSpeed(30);
        ChangeLeftMotorSpeed(30);
        __delay_ms(1000);
        
        // 左右モータの出力を40%に変更
        ChangeRightMotorSpeed(40);
        ChangeLeftMotorSpeed(40);
        __delay_ms(1000);

        // 左右モータの出力を50%に変更
        ChangeRightMotorSpeed(50);
        ChangeLeftMotorSpeed(50);
        __delay_ms(1000);
        
        // 左右モータの出力を60%に変更
        ChangeRightMotorSpeed(60);
        ChangeLeftMotorSpeed(60);
        __delay_ms(1000);
        
        // 左右モータの出力を70%に変更
        ChangeRightMotorSpeed(70);
        ChangeLeftMotorSpeed(70);
        __delay_ms(1000);
        
        // 左右モータの出力を80%に変更
        ChangeRightMotorSpeed(80);
        ChangeLeftMotorSpeed(80);
        __delay_ms(1000);
        
        // 左右モータの出力を90%に変更
        ChangeRightMotorSpeed(90);
        ChangeLeftMotorSpeed(90);
        __delay_ms(1000);
        
        // 左右モータの出力を100%に変更
        ChangeRightMotorSpeed(100);
        ChangeLeftMotorSpeed(100);
        __delay_ms(1000);
    }
    
    return 0;
}


// デバイスの初期設定
bool InitDevice()
{
    // クロックを設定
    OSCCON = 0b01110010;    // クロックを8MHzに設定
    
    // すべてのピンを出力に設定
    TRISA = 0b00000000;
    TRISB = 0b00000000;
    
    // すべてのピンをデジタル出力に設定
    ANSELA = 0b00000000;
    ANSELB = 0b00000000;
    
    // CCPのピンのモード設定
    CCP3CON = 0b00001100;   // CCP3をPWMモードに設定(かつCCPR3Lの下位2ビットを0に設定)
    CCP4CON = 0b00001100;   // CCP4をPWMモードに設定(かつCCPR4Lの下位2ビットを0に設定)
    
    // CCPにタイマを割り当てる
    CCPTMRS = 0b00000000;   // CCP3とCCP4のタイマをそれぞれTimer2
    
    // タイマの設定
    T2CON = 0b00000100;     // TIMER2を有効にしてスケールをすべて1に設定
    PR2 = 255;              // PWM Periodを1.28 * 10^4(= (255(PR2) + 1) * 1 / 8000000(CLOCK) * 1(Prescale))
    
    return true;
}


// 右モータの速度を変更する
bool ChangeRightMotorSpeed(int per)
{
    switch(per)
    {
        // 0%出力
        case 0:
            CCPR3L = 0b00000000;
            break;
            
        // 10%出力
        case 10:
            CCPR3L = 0b00011001;
            break;
            
        // 20%出力
        case 20:
            CCPR3L = 0b00110011;
            break;
            
        // 30%出力
        case 30:
            CCPR3L = 0b01001100;
            break;
            
        // 40%出力
        case 40:
            CCPR3L = 0b01100110;
            break;
            
        // 50%出力
        case 50:
            CCPR3L = 0b10000000;
            break;
            
        // 60%出力
        case 60:
            CCPR3L = 0b10011001;
            break;
            
        // 70%出力
        case 70:
            CCPR3L = 0b10110011;
            break;
            
        // 80%出力
        case 80:
            CCPR3L = 0b11001100;
            break;
            
        // 90%出力
        case 90:
            CCPR3L = 0b11100110;
            break;
            
        // 100%出力
        case 100:
            CCPR3L = 0b11111110;
            break;
            
        // エラー
        default:
            return false;
            break;
    }
    
    return true;
}

// 左モータの速度を変更する
bool ChangeLeftMotorSpeed(int per)
{
    switch(per)
    {
        // 0%出力
        case 0:
            CCPR4L = 0b00000000;
            break;
            
        // 10%出力
        case 10:
            CCPR4L = 0b00011001;
            break;
            
        // 20%出力
        case 20:
            CCPR4L = 0b00110011;
            break;
            
        // 30%出力
        case 30:
            CCPR4L = 0b01001100;
            break;
            
        // 40%出力
        case 40:
            CCPR4L = 0b01100110;
            break;
            
        // 50%出力
        case 50:
            CCPR4L = 0b10000000;
            break;
            
        // 60%出力
        case 60:
            CCPR4L = 0b10011001;
            break;
            
        // 70%出力
        case 70:
            CCPR4L = 0b10110011;
            break;
            
        // 80%出力
        case 80:
            CCPR4L = 0b11001100;
            break;
            
        // 90%出力
        case 90:
            CCPR4L = 0b11100110;
            break;
            
        // 100%出力
        case 100:
            CCPR4L = 0b11111110;
            break;
            
        // エラー
        default:
            return false;
            break;
    }
    
    return true;
}

 

RA3とRA4から0%から10秒おきに10%刻みで出力を上げていくプログラムです。

100%まで達すると再び0%から繰り返されます。

結果

YouTubeに動画をアップロードしました。

@動画

 

テスターでRA4から出力されるPWM信号を計測しています。

徐々に電圧が上がっていき、電源電圧と同じ3V付近でピークを迎えて、0Vに戻りますね。

成功です!

 

以上です!

ノシ!

 

PS

今日の記事はとても長くなってしまいました。12000文字前後です(>_<)

 - 技術系