ソフトウェアによる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信号の例


STM32F303はタイマユニットがあるのでそれを用いればよいが,ソフトウェアのsystick割り込み(インターバルタイマ割り込み)を使ってPWM信号を生成することができる。
PWM周期を与える値Bとデューティ比に関与する値Aを予め与えておくと,動作原理は,次のようになっている。
インターバルタイマ割り込み処理により,割り込み発生ごとに割り込みルーチン内でカウンタの値を0からカウントアップし,Bに達すると,0に戻り,再びカ ウントアップするようにしておく。(周期Bでの自走カウンタ)割り込みルーチン内でカウンタの値と指示された値Aを比較し,カウンタ値の方が小さい時 にHを,大きい時にLを指定されたGPIOピンに出すようにしておけば良い。
その結果,0<カウンタの値<AではHが,A<カウンタの値<BではLが出力され,この出力が繰り返し継続されることになる。
ここで値BはPWM周期を与え,値Aはデューティ比を決めることになる。正確には割り込み周期×BでPWM周期が決まり,A÷Bがデューティ比を表すことになる。

図1.2 PWM信号の生成


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

ま ず最初は出力ピンを決める。PA8,PA9およびPA10に出力することとする。

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

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

2.2 Systick割り込みの設定

割り込み周期をPWM周期の1/100にして,PWM指令値を0から100まで使えるように設定する。

2.3 割り込みハンドラの用意

ハンドラの名前(割り込み関数の名前)は決まっていて,startup_stm32f30x.sのg_pfnVectors:で始まるベクタテーブル中に書いてある。
SysTick_HandlerがSystick割り込みを受けた時に動作するハンドラの名前である。

2.4 PWMパルス幅設定

PWMのパルス幅は,PWMperiod=100;で範囲がきまるので,ここでは0から100までの値が設定できる。

3. プログラム例

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

4チャンネルPWM生成プログラム
PA8,PA9,PA10に生成される
/*
 * PA8,PA9,PA10に対して3つのPWM信号を生成する
 * タイマユニットを使わず,タイマ割り込みソフトウェア方式とする
 * PWM周期は2msとし,PWM指令値は0から100とする
 * 実現するためには インターバルタイマ割り込み周期=0.02msec 割り込み周波数=50000Hz
 */
/* Includes ------------------------------------------------------------------*/
#include <stm32f30x.h>
#include <stdint.h>
#include "stm32f30x_conf.h"
#include "stm32f3_discovery.h"
#include <stdio.h>

void initIntervaltimer(int32_t frequency);
void init_PWMPins(void);

int32_t PWMwidth8;
int32_t PWMwidth9;
int32_t PWMwidth10;
int32_t PWMperiod;

int main(void)
{
    /* SysTick end of count event each 1ms */
    initIntervaltimer(50000); /* 500000Hz, 50000 interrupts/sec */

    init_PWMPins();
    PWMwidth8=10;
    PWMwidth9=30;
    PWMwidth10=60;
    PWMperiod=100;  /*PWM指令値の範囲は0-100*/

    /* Infinite loop */
    while (1)
    {
        volatile int i=0;
        for (i=0; i<1000000; i++);
        PWMwidth8=70;
        for (i=0; i<1000000; i++);
        PWMwidth8=10;
    }
}

/*インターバルタイマー割り込み初期設定 割り込み周波数[Hz]を引数で渡す*/
void initIntervaltimer(int32_t frequency)
{
    RCC_ClocksTypeDef RCC_Clocks;
    RCC_GetClocksFreq(&RCC_Clocks);
    SysTick_Config(RCC_Clocks.HCLK_Frequency / frequency);
}

void init_PWMPins(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

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

    /* GPIOA Configuration */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    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);
}

//インターバルタイマー割り込み関数
void SysTick_Handler(void)
{
    static int32_t count=0;
    if (count<PWMwidth8) GPIOA ->BSRR |= (1<<8); //Turn GPIOA Pin 8 On
    else  GPIOA ->BRR |= (1<<8); //Turn GPIOA Pin 8 Off
    if (count<PWMwidth9) GPIOA ->BSRR |= (1<<9); //Turn GPIOA Pin 9 On
    else  GPIOA ->BRR |= (1<<9); //Turn GPIOA Pin 9 Off
    if (count<PWMwidth10) GPIOA ->BSRR |= (1<<10); //Turn GPIOA Pin 10 On
    else  GPIOA ->BRR |= (1<<10); //Turn GPIOA Pin 10 Off

    if (++count==PWMperiod) count=0;
}



生成されたPWM波形



PWM周期2msec,デューティ比70%から10%へ遷移時の波形

デューティ比の切り替えは,矢印のところで起こっている。矢印のところまでは,デューティ比10%であった。それまでLだったのだが,デューティ比70%に変化したので,Hになって70%のところまでHが続いている。
PWM周期2msec,デューティ比10%から70%へ遷移時の波形

デューティ比の切り替えは,矢印のところで起こっている。矢印のところまでは,デューティ比70%であった。それまでHだったのだが,デューティ比10%に変化したので,Lになっている。

4. DCモータのPWM駆動

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

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


ここではソフトウェアインターバルタイマ割り込みでPA8,PA9で駆動するようになっている。フルブリッジドライバIC TA8429Hの動作仕様は次のようになっている。

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

IN1

IN2

DCモータ

0

0

静止

0

1

正転

1

0

逆転

1

1

ショートブレーキ


プログラムを改良してモータを制御してみよう。PA8,PA9でモータを制御する。
PWM周期を2msecとし,指令値は-100から100までを使う。正の値は正転を,負の値は逆転を指令するものとする。指令値の絶対値はデューティ比を表すものとする。

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

モータのPWM制御
モータ用ドライバへの2つの入力にはPA8,PA9から出力する。

/* Includes ------------------------------------------------------------------*/
#include <stm32f30x.h>
#include <stdint.h>
#include "stm32f30x_conf.h"
#include "stm32f3_discovery.h"
#include <stdio.h>

//DCモータ2個のPWM波形を生成する。
//ピン配置
//motor  F : PA8
//motor  R : PA9

//
// PWM周期2msec
// 指令値 -100から100 (府の値は逆回転用)
//        正の値の時 FにPWM信号を出す
//        負の値の時 RにPWM信号を出す
// brake  FとRに100%出力 brake modeを持つモータドライバのみ利用可


void initIntervaltimer(int32_t frequency);
void init_PWMPins(void);

void init_Motor();
void drive_Motor(int commandvalue);
void brake_Motor();

int32_t PWMwidth8;
int32_t PWMwidth9;
int32_t PWMperiod;

int main(void)
{
    init_Motor();

    /* Infinite loop */
    while (1)
    {
        volatile int i=0;
        for (i=0; i<3000000; i++);
        drive_Motor(70);
        for (i=0; i<3000000; i++);
        drive_Motor(10);
        for (i=0; i<3000000; i++);
        drive_Motor(-30);
        for (i=0; i<3000000; i++);
        drive_Motor(-90);
        for (i=0; i<3000000; i++);
        brake_Motor();
    }
}

//周期は2msec パルス幅は0-100に設定する
//出力の極性設定をPolarityで指示する
// 正論理出力 TIM_OCPolarity_High
// 不論理出力 TIM_OCPolarity_Low
void init_Motor(int Polarity)
{
    /* SysTick end of count event each 1ms */
    initIntervaltimer(50000); /* 500000Hz, 50000 interrupts/sec */

    init_PWMPins();
    PWMwidth8=0;
    PWMwidth9=0;
    PWMperiod=100;  /*PWM指令値の範囲は0-100*/
}

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

void brake_Motor()
{
    PWMwidth9=0;
    PWMwidth8=100;
    PWMwidth9=100;
}

/*インターバルタイマー割り込み初期設定 割り込み周波数[Hz]を引数で渡す*/
void initIntervaltimer(int32_t frequency)
{
    RCC_ClocksTypeDef RCC_Clocks;
    RCC_GetClocksFreq(&RCC_Clocks);
    SysTick_Config(RCC_Clocks.HCLK_Frequency / frequency);
}

void init_PWMPins(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

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

    /* GPIOA Configuration: PA8,PA9 as push-pull */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    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);
}

//インターバルタイマー割り込み関数
//offにすることを優先する
void SysTick_Handler(void)
{
    static int32_t count=0;
    if (PWMwidth8<=count) GPIOA ->BRR |= (1<<8); //Turn GPIOA Pin 8 Off
    if (PWMwidth9<=count) GPIOA ->BRR |= (1<<9); //Turn GPIOA Pin 9 Off
    if (count<PWMwidth8) GPIOA ->BSRR |= (1<<8); //Turn GPIOA Pin 8 On
    if (count<PWMwidth9) GPIOA ->BSRR |= (1<<9); //Turn GPIOA Pin 9 On

    if (++count==PWMperiod) count=0;
}