PWM信号発生とDCモータのPWM制御

20140530 coskx
T.SATOH

1. はじめに

PWMはパルス幅変調(Pulse Width Modulation)といって,ディジタル信号のHとLの長さを変化させて,指令値を作る方法である。
例えば,LEDを高速に点滅させて,点滅が人間の目にわからないようにした状態でHの時間とLの時間の比を変化させるとLEDの明るさが変化したように見える。
モータの駆動時において,高速にスイッチのON-OFFを繰り返し,ONになっている時間とOFFになっている時間の比を変更することに よって,見かけ上,モータにかける電圧を変更する駆動も可能になる。
次の図はPWM信号の例である。周期的なON-OFFの信号で,その周期はPWM周期と呼ばれる。また(ONになっている時間)÷ (PWM周期)のことはデューティ比と呼ばれる。

図1.1 PWM信号の例


STM32F051はタイマユニットを使って,PWM信号を生成することができる。
PWM周期を与える値Bとデューティ比に関与する値Aを予め与えておくと,動作原理は,次のようになっている。
与えられたクロックによりカウントアップするカウンタがあり,カウンタの値は0からカウントアップし,Bに達すると,0に戻り,再びカウントアップするよ うになっている。(周期Bでの自走カウンタ)タイマユニットにはビット出力機能があり,カウンタの値と指示された値Aを比較し,カウンタ値の方が小さい時 にHを,大きい時にLを出すようになっている。
その結果,0<カウンタの値<AではHが,A<カウンタの値<BではLが出力され,この出力が繰り返し継続されることになる。
ここで値BはPWM周期を与え,値Aはデューティ比を決めることになる。正確には元になるクロックの周期×BでPWM周期が決まり,A÷Bがデューティ比を表すことになる

図1.2 PWM信号の生成

STM32F051はこのような動作をするタイマユニットを複数持っている。
ここではTIM1というタイマユニットを使うことにする。
TIM1は同じ周期でデューティ比の異なるPWM信号を4つ同時に発生することができる。(4チャンネル出力)
つまり,Bの値は1つだけであるが,Aの値を4つ持つことができる。

2. PWM信号発生のための準備

まず最初は出力ピンを決める。マニュアルdm00039193.pdfのTable 14.によれば,TIM1_CH1,TIM1_CH2,TIM1_CH3,TIM1_CH4は,AF2の設定でPA8,PA9,PA10,PA11になっていることがわかる。

出力ピンがわかったところで,3つの設定が必要となる。

2.1 GPIOピンの設定
GPIOピンの設定は,クロックを供給し,PA8,PA9,PA10,PA11をAFモードにし,PA8,PA9,PA10,PA11を TIM1_CH1,TIM1_CH2,TIM1_CH3,TIM1_CH4の機能に変更する。ただしこの設定は最初にやらず,タイマユニットの初期化が終 わってから行う。
init_TIM1Pins();

2.2 タイマユニットの設定

タイマユニットの設定は,与えるクロック周波数を定めるため,システムクロック何個分を1クロックにするかを示すprescaler,Bの値を与えるperiodを与えて行う。この2つの値を設定し,TIM1を有効にし,TIM1のPWM出力も有効にしている。
プログラム例では,TIM1に与えるクロック周波数が100kHzになるように,prescalerに値を与え,100カウント=1msecで1PWM周期にしようとしている。
正確に言うと,プリスケーラカウンタは0からprescalerにセットした値までを繰り返すので,プリスケーラとして1000を与えたい場合には prescalerの値としては999にしなければならない。よって,TIM1に与えるクロック周波数が100kHzになるように,prescalerに 値を与えるためには,
(SystemCoreClock / 100000 ) - 1
を設定している。
また,periodも同様な仕組みをしているのでperiod=100-1を与えている。リファレンスマニュアルdm00031936.pdf(RM0091)のFigure 86.に説明がある。Figure 86の説明ではARRがperiodの値であり8が設定された例である。カウンタは0から8までの9クロック周期で動作しているのがわかる。
また,CCRの設定値が0から8を取り,0の時デューティ比0%,8の時デューティ比100%になることが読み取れる。
Init_Timer(prescaler, period);

2.3 PWMチャンネルの設定

PWMチャンネルの設定は,設定関数内で,PWMモード1に設定し,とりあえず出力パルス幅は0(これはAの値に対応する)に設定する。
ここで,プリロード設定をとりあえず有効にしておく。あとで効果を検証する。
引数では正論理の出力に設定する。外部接続機器が負論理で信号を受けることになっている場合は引数で負論理を設定する。
Init_PWMChannel(TIM_OCPolarity_High);

2.4 PWMパルス幅設定

PWMのパルス幅は,TIM1のCCR1からCCR4に設定すればよい。ここでは0から100までの値が設定できる。

3. プログラム例

TIM1の4つのチャンネルを用いて4つのPWM波形を生成する。
PWM周期1msec,4つのパルス幅は0から100までの値で設定できるようにする。
起動直後に,CH1:デューティ比10%,CH2:デューティ比30%,CH3:デューティ比60%,CH4:デューティ比 90%で出力する。
その後,CH1はデューティ比10%と70%が適当な時間間隔で切り替わるようにしているデモプログラムとなっている。

4チャンネルPWM生成プログラム
PA8,PA9,PA10,PA11に生成される
#include <stm32f0xx.h>
#include <stdint.h>
#include "stm32f0xx_conf.h"
#include "stm32f0_discovery.h"
#include <stdio.h>
//4chのPWM波形をTIM1で生成する

// RRR,CCR are 16-bit registers.
// prescaler is 16-bit
// BRRの設定値(PWM周期)は所望が100カウントなら99
// その時のCRR(パルス幅)は0-100まで

void init_TIM1Pins(void);
void Init_Timer(int prescaler, int period);
void Init_PWMChannel(int Polarity);
void setTIM1CH1(int16_t pulsewidth);
void setTIM1CH2(int16_t pulsewidth);
void setTIM1CH3(int16_t pulsewidth);
void setTIM1CH4(int16_t pulsewidth);

int main(void)
{
    uint16_t period = 0;
    uint16_t prescaler = 0;
    prescaler = (SystemCoreClock / 100000 ) - 1;
    period = 100 - 1; //これで周期は1msec パルス幅は0-100に設定できる
    Init_Timer(prescaler, period);
    Init_PWMChannel(TIM_OCPolarity_High);
    init_TIM1Pins();

    setTIM1CH1(10);
    setTIM1CH2(30);
    setTIM1CH3(60);
    setTIM1CH4(90);

    /* Infinite loop */
    while (1)
    {
        volatile int i=0;
        for (i=0; i<1000000; i++);  //しばらく何もしない
        setTIM1CH1(70);
        for (i=0; i<1000000; i++);  //しばらく何もしない
        setTIM1CH1(10);
    }
}

void init_TIM1Pins(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    /* GPIOA Clocks enable */
    RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOA, ENABLE);

    /* GPIOA Configuration: Channel 1, 2, 3 and 4 as alternate function push-pull */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_2);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_2);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_2);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_2);
}

void Init_Timer(int prescaler, int period)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 , ENABLE);

    TIM_TimeBaseInitTypeDef timerInitStructure;
    TIM_TimeBaseStructInit(&timerInitStructure);
    timerInitStructure.TIM_Prescaler = prescaler;
    timerInitStructure.TIM_Period = period;
    TIM_TimeBaseInit(TIM1, &timerInitStructure);
    TIM_Cmd(TIM1, ENABLE);
    /* TIM1 Main Output Enable */
    TIM_CtrlPWMOutputs(TIM1, ENABLE);
}

//出力の極性設定をPolarityで指示する
// 正論理出力 TIM_OCPolarity_High
// 不論理出力 TIM_OCPolarity_Low
void Init_PWMChannel(int Polarity)
{
    TIM_OCInitTypeDef outputChannelInit;
    TIM_OCStructInit(&outputChannelInit);
    outputChannelInit.TIM_OCMode = TIM_OCMode_PWM1;
    outputChannelInit.TIM_Pulse = 0;
    outputChannelInit.TIM_OutputState = TIM_OutputState_Enable;
    outputChannelInit.TIM_OCPolarity = Polarity;

    TIM_OC1Init(TIM1, &outputChannelInit);
    TIM_OC2Init(TIM1, &outputChannelInit);
    TIM_OC3Init(TIM1, &outputChannelInit);
    TIM_OC4Init(TIM1, &outputChannelInit);
    //以下のpreloadの設定を行うと,パルス幅変化が綺麗に起こる
    //しかし,設定しないほうが所望のデューティー比に早く変化する。
    //今ペアマッチでHLを切り替えているのではなく,常時比較の結果で出力信号を作っているからそうなる。
    TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
    TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
    TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);
    TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);

}

void setTIM1CH1(int16_t pulsewidth)
{
    TIM1->CCR1 = pulsewidth;
}

void setTIM1CH2(int16_t pulsewidth)
{
    TIM1->CCR2 = pulsewidth;
}

void setTIM1CH3(int16_t pulsewidth)
{
    TIM1->CCR3 = pulsewidth;
}

void setTIM1CH4(int16_t pulsewidth)
{
    TIM1->CCR4 = pulsewidth;
}


生成されたPWM波形



PWM周期1msec,デューティ比10%の波形
PWM周期1msec,デューティ比70%の波形



デューティ比70%から,10%に切り替わるところ
Init_PWMChannel中で,preloadを有効にしている時の様子である。
デューティ比の切り替えは,CCRxの値の変更で行われるが,preloadが有効になっていると,
CCRxの値の変更の後の,カウンタクリアの瞬間に切り替わるので,このように綺麗になる。





デューティ比10%から,70%に切り替わるところ
Init_PWMChannel中で,preloadを無効にした時の様子である。
(コメントアウトした)
デューティ比の切り替えは,矢印のところで起こっている。矢印のところでは,それまでLだったのだが,デューティ比70%に変化したので,Hになって70%のところまで続いている。
変化の仕方は,綺麗ではないが,実用的である。
デューティ比70%から,10%に切り替わるところ
Init_PWMChannel中で,preloadを無効にした時の様子である。
(コメントアウトした)
デューティ比の切り替えは,矢印のところで起こっている。矢印のところでは,それまでHだったのだが,デューティ比10%に変化したので,Lになって100%まで続いている。
変化の仕方は,綺麗ではないが,実用的である。


4. DCモータのPWM駆動

DCモータを正逆転可能にするにはH型ブリッジドライバ回路が使われる。小電力では,フルブリッジドライバICTA8429Hなどが使われる。大電力では,MOSFETを使用したフルブリッジドライバを使う。

例えば,フルブリッジドライバIC TA8429Hを使うと次の様に接続する。
ただしそのままつなぐことは出来ない。
TIM2_CH1.CH2はH=3Vであるが,TA8429Hへの入力のHは7V以上である。何らかの方法で電圧レベル変換が必要である。
5V系へのレベル変換ならレベルシフタICが入手が楽だが,この場合は例えば次のようにする。


ここではTIM1_CH1,TIM1_CH2で駆動するようになっている。フルブリッジドライバIC TA8429Hの動作仕様は次のようになっている。

モータドライバIC「TA8429H」の動作定義

IN1

IN2

DCモータ

0

0

静止

0

1

正転

1

0

逆転

1

1

ショートブレーキ


TIM1は4つのチャンネルを持っているので2つのモータを制御できる。TIM1_CH1とTIM1_CH2でモータ1を,TIM1_CH3とTIM1_CH4でモータ2を制御する。
PWM周期を1msecとし,指令値は-100から100までを使う。正の値は正転を,負の値は逆転を指令するものとする。指令値の絶対値はデューティ比を表すものとする。

プログラムは次のようになる。

2つのモータのPWM制御
モータ1用ドライバへの2つの入力にはPA8,PA9から出力する。
モータ2用ドライバ
への2つの入力にはPA10,PA11から出力する。
#include <stm32f0xx.h>
#include <stdint.h>
#include "stm32f0xx_conf.h"
#include "stm32f0_discovery.h"
#include <stdio.h>

//DCモータ2個のPWM波形を生成する。
// motor1 motor2
//TIM1使用
//ピン配置
//motor1  F : PA8
//motor1  R : PA9
//motor2  F : PA10
//motor2  R : PA11
//
// PWM周期1msec
// 指令値 -100から100 (府の値は逆回転用)
//        正の値の時 FにPWM信号を出す
//        負の値の時 RにPWM信号を出す
// brake  FとRに100%出力 brake modeを持つモータドライバのみ利用可


//4chのPWM波形をTIM1で生成する
// RRR,CCR are 16-bit registers.
// prescaler is 16-bit
// BRRの設定値(PWM周期)は所望が100カウントなら99
// その時のCRR(パルス幅)は0-100まで

void init_Motor(int Polarity);
void drive_Motor1(int commandvalue);
void drive_Motor2(int commandvalue);
void brake_Motor1();
void brake_Motor2();


void init_TIM1Pins(void);
void Init_Timer(int prescaler, int period);
void Init_PWMChannel(int Polarity);
void setTIM1CH1(int16_t pulsewidth);
void setTIM1CH2(int16_t pulsewidth);
void setTIM1CH3(int16_t pulsewidth);
void setTIM1CH4(int16_t pulsewidth);

int main(void)
{
    init_Motor(TIM_OCPolarity_High);

    /* Infinite loop */
    while (1)
    {
        volatile int i=0;
        for (i=0; i<1000000; i++);
        drive_Motor1(70);
        drive_Motor2(70);
        for (i=0; i<1000000; i++);
        drive_Motor1(10);
        drive_Motor2(10);
        for (i=0; i<1000000; i++);
        drive_Motor1(-30);
        drive_Motor2(-30);
        for (i=0; i<1000000; i++);
        drive_Motor1(-90);
        drive_Motor2(-90);
        for (i=0; i<1000000; i++);
        brake_Motor1();
        brake_Motor2();
    }
}

//周期は1msec パルス幅は0-100に設定する
//出力の極性設定をPolarityで指示する
// 正論理出力 TIM_OCPolarity_High
// 不論理出力 TIM_OCPolarity_Low
void init_Motor(int Polarity)
{
    uint16_t period = 0;
    uint16_t prescaler = 0;
    prescaler = (SystemCoreClock / 100000 ) - 1;
    period = 100 - 1;
    Init_Timer(prescaler, period);
    Init_PWMChannel(Polarity);
    init_TIM1Pins();
}

//0を出力することを常に優先する。
//モータ指令値の2つの出力に同時にHが出ないようにするためである。
//   -100 <= commanvalue <= 100
void drive_Motor1(int commandvalue)
{
    if (100<commandvalue) {
        setTIM1CH2(0);
        setTIM1CH1(100);
    } else if (0<=commandvalue) {
        setTIM1CH2(0);
        setTIM1CH1(commandvalue);
    } else if (-100<=commandvalue) {
        setTIM1CH1(0);
        setTIM1CH2(-commandvalue);
    } else {
        setTIM1CH1(0);
        setTIM1CH2(100);
    }
}

//   -100 <= commanvalue <= 100
void drive_Motor2(int commandvalue)
{
    if (100<commandvalue) {
        setTIM1CH4(0);
        setTIM1CH3(100);
    } else if (0<=commandvalue) {
        setTIM1CH4(0);
        setTIM1CH3(commandvalue);
    } else if (-100<=commandvalue) {
        setTIM1CH3(0);
        setTIM1CH4(-commandvalue);
    } else {
        setTIM1CH3(0);
        setTIM1CH4(100);
    }
}

void brake_Motor1()
{
    setTIM1CH2(0);
    setTIM1CH1(100);
    setTIM1CH2(100);
}

void brake_Motor2()
{
    setTIM1CH4(0);
    setTIM1CH3(100);
    setTIM1CH4(100);
}

/////////////////////////////////////////////////
void init_TIM1Pins(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    /* GPIOA Clocks enable */
    RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOA, ENABLE);

    /* GPIOA Configuration: Channel 1, 2, 3 and 4 as alternate function push-pull */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;  //オープンドレインにするときは GPIO_OType_OD
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_2);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_2);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_2);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_2);
}

void Init_Timer(int prescaler, int period)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 , ENABLE);

    TIM_TimeBaseInitTypeDef timerInitStructure;
    TIM_TimeBaseStructInit(&timerInitStructure);
    timerInitStructure.TIM_Prescaler = prescaler;
    timerInitStructure.TIM_Period = period;
    TIM_TimeBaseInit(TIM1, &timerInitStructure);
    TIM_Cmd(TIM1, ENABLE);
    /* TIM1 Main Output Enable */
    TIM_CtrlPWMOutputs(TIM1, ENABLE);
}

//出力の極性設定をPolarityで指示する
// 正論理出力 TIM_OCPolarity_High
// 不論理出力 TIM_OCPolarity_Low
void Init_PWMChannel(int Polarity)
{
    TIM_OCInitTypeDef outputChannelInit;
    TIM_OCStructInit(&outputChannelInit);
    outputChannelInit.TIM_OCMode = TIM_OCMode_PWM1;
    outputChannelInit.TIM_Pulse = 0;
    outputChannelInit.TIM_OutputState = TIM_OutputState_Enable;
    outputChannelInit.TIM_OCPolarity = Polarity;

    TIM_OC1Init(TIM1, &outputChannelInit);
    TIM_OC2Init(TIM1, &outputChannelInit);
    TIM_OC3Init(TIM1, &outputChannelInit);
    TIM_OC4Init(TIM1, &outputChannelInit);
    //以下のpreloadの設定を行うと,パルス幅変化が綺麗に起こる
    //しかし,設定しないほうが所望のデューティー比に早く変化する。
    //今ペアマッチでHLを切り替えているのではなく,常時比較の結果で出力信号を作っているからそうなる。
    //TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
    //TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
    //TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);
    //TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);

}

void setTIM1CH1(int16_t pulsewidth)
{
    TIM1->CCR1 = pulsewidth;
}

void setTIM1CH2(int16_t pulsewidth)
{
    TIM1->CCR2 = pulsewidth;
}

void setTIM1CH3(int16_t pulsewidth)
{
    TIM1->CCR3 = pulsewidth;
}

void setTIM1CH4(int16_t pulsewidth)
{
    TIM1->CCR4 = pulsewidth;
}