複数(24個)のRCサーボ制御信号をインターバルタイマ割り込みで作る
異なるパルス幅を持つパルスを各ビットのセット・リセットで生成する

Copyright(C) 21Jan2010
coskx

24のRCサーボを同時に制御するための24の独立したパルス幅のパルスの生成が必要である。24のITUを使えば簡単であるが,ITUは5つしかないので工夫が必要となる。RCサーボの周期は20msec程度で,その中のパルスの幅は1.0〜2.0msec程度である。そこでITU1によるインターバルタイマ割り込みを限界まで短い周期で動作させ,割り込み回数を数えながら,24個の各ビットをセット・リセットしながらパルスを順番に作ればうまくゆきそう。
この方法では,CPUの外部に外付け回路を必要としない。
最短インターバルタイマ割り込みを目指し,15μsecで動作させている。

この方法の特徴
(1)外付け回路不要
(2)パルス幅は15μsec(誤差が0.5%程度ある)刻みの設定となる

出力は,3つのポート(このサンプルではP1,PA,PBの3つのポート)の各8ビットに指定したパルスが出力される。ただし,立ち上がりは,全ビット一斉で,各ビットは指定された幅のパルスになったビットから順に立ち下がる。

パルス幅を小さい順にソートすると,立ち下げるべき時刻とビット番号が順に並ぶ。
最初にすべてのビットを立ち上げます。割り込み関数内では,次に立ち下げるべき時刻になったかどうかを監視し,その時刻になったら,該当するビットを立ち下げるようにする。
すべてのビットを立ち下げて,しばらくすると,PWM周期に対応する時刻が来るので,時刻カウンタをリセットして,先の動作を続る。

どこかのビットのパルス幅を変更するのは,すべてのビットを立ち下げてからしばらくの間に行います。


 

プログラムのダウンロード

DownLoad

RCservoCTRL24s.c

このプログラムでは時間の単位をμsecにこだわっているが,CPUのクロックに合わせた時間の単位を用いれば,
PWM幅誤差の少ない動作になる。

/*
24系統RCサーボパルス発生システム
15μsecのインターバルタイマ割り込みを用いて,
ソフトウェアで24系統のパルスをポートから出力する。
ITU1のインターバルタイマを用いる。
PWM周期:20msec。

OUTPORT0 bit0 : 系統0
OUTPORT0 bit1 : 系統1
OUTPORT0 bit2 : 系統2
OUTPORT0 bit3 : 系統3
    :
OUTPORT1 bit0 : 系統8
OUTPORT1 bit1 : 系統9
OUTPORT1 bit2 : 系統10
OUTPORT1 bit3 : 系統11
    :
OUTPORT2 bit0 : 系統16
OUTPORT2 bit1 : 系統17
OUTPORT2 bit2 : 系統18
OUTPORT2 bit3 : 系統19
    :
OUTPORT2 bit7 : 系統23

このプログラムでのシリアル通信制御テスト
A,B,C,...V,W,Xの入力 系統0〜24のパルスをそれぞれ50μsec長くする
a,b,c,...v,w,xの入力 系統0〜24のパルスをそれぞれ50μsec短くする
0の入力              系統0〜24のパルスをすべて1500μsecの長さにする
1の入力              系統0〜24のパルスをすべて異なる長さにする
*/

#include "3048fone.h"
#include "h8_3048fone.h"

#define OUTPORT0 P1
#define OUTPORT1 PA
#define OUTPORT2 PB

#define PERIOD 20000 /*PWM周期(=20msec)*/
#define INTERVAL 15  /*割り込み周期15μsec*/

typedef struct {
    unsigned char bytedata[3];
} tribyte_t;

typedef struct {
    int pulseWidth;
    int bitnum;
} pulsewidth_t;

/*パルス幅は最初1000,1000,1200,1000,1000,1000,2000,2000μsec*/
/*              1200,1100,2000,1200,1800,1700,1700,2000μsec*/
/*              1500,1100,2000,1300,1400,1500,2000,1600μsec*/
/*とする*/
/*★注意1 使用していない系統の値は1500に固定すること*/
/*★注意2 値は1000〜2000とする*/
int pulseWidth[24]={  /*単位μsec*/
    1000,1000,1200,1000,1000,1000,2000,2000,
    1200,1100,2000,1200,1800,1700,1700,2000,
    1500,1100,2000,1300,1400,1500,2000,1600
};

tribyte_t tribyte[26]; /*切り替えビットパタン*/
int switchtime[26]; /*INTERVAL μsecで数えての切り替え時刻*/
int switchindex; /*ポインタ*/
volatile int switchtimecurrent; /*次の切り替え時刻(INTERVAL μsecで数える)*/
volatile int tick; /*現在時刻(INTERVAL μsecで数える)*/
int period; /*PWM周期(INTERVAL μsecで数える)*/

pulsewidth_t b_pulseWidth[24];
tribyte_t b_tribyte[26];
int b_switchtime[26];
const tribyte_t initstate={{0xff,0xff,0xff}}; /*初期状態*/
const tribyte_t mask[24]={
    {0xfe,0xff,0xff},{0xfd,0xff,0xff},{0xfb,0xff,0xff},{0xf7,0xff,0xff},
    {0xef,0xff,0xff},{0xdf,0xff,0xff},{0xbf,0xff,0xff},{0x7f,0xff,0xff},
    {0xff,0xfe,0xff},{0xff,0xfd,0xff},{0xff,0xfb,0xff},{0xff,0xf7,0xff},
    {0xff,0xef,0xff},{0xff,0xdf,0xff},{0xff,0xbf,0xff},{0xff,0x7f,0xff},
    {0xff,0xff,0xfe},{0xff,0xff,0xfd},{0xff,0xff,0xfb},{0xff,0xff,0xf7},
    {0xff,0xff,0xef},{0xff,0xff,0xdf},{0xff,0xff,0xbf},{0xff,0xff,0x7f},
};

void turndownBit(tribyte_t *p,int bitnum)
{
    int i;
    for (i=0;i<3;i++) {
        p->bytedata[i]&=mask[bitnum].bytedata[i];
    }
}


/*スイッチング情報を作成するb_のついた変数に作る*/
/*有効データ数を返す*/
int makeSwitchInfo(void)
{
    int i,j;
    pulsewidth_t tmp;
    for (i=0;i<24;i++) {
        b_pulseWidth[i].pulseWidth=(pulseWidth[i]+(INTERVAL>>1))/INTERVAL;
        b_pulseWidth[i].bitnum=i;
    }
    /*小さい順に並べ替え*/
    for (i=0;i<24-1;i++) {
        for (j=i+1;j<24;j++) {
            if (b_pulseWidth[j].pulseWidth<b_pulseWidth[i].pulseWidth) {
                tmp=b_pulseWidth[j];
                b_pulseWidth[j]=b_pulseWidth[i];
                b_pulseWidth[i]=tmp;
            }
        }
    }
    b_tribyte[0]=initstate;
    b_switchtime[0]=0;
    j=0;
    for (i=0;i<24;i++) {
        if (b_pulseWidth[i].pulseWidth==b_switchtime[j]) {
            turndownBit(&b_tribyte[j],b_pulseWidth[i].bitnum);
        } else {
            j++;
            b_tribyte[j]=b_tribyte[j-1];
            turndownBit(&b_tribyte[j],b_pulseWidth[i].bitnum);
            b_switchtime[j]=b_pulseWidth[i].pulseWidth;
        }
    }
    b_switchtime[++j]=0;
    b_tribyte[j]=b_tribyte[j-1];
    return j+1;
}

/*スイッチング情報を実使用データ領域にコピーする*/
void moveSwitchingData(int num)
{
    int i;
    for (i=0;i<num;i++) {
        tribyte[i]=b_tribyte[i];
        switchtime[i]=b_switchtime[i];
    }
}

/*スイッチング情報の生成と書き換え*/
/*スイッチング情報の書き換えはこの関数で行う*/
void renewSwitchingData(void)
{
    int num;
    num=makeSwitchInfo();
    /*パルス生成作業中はスイッチング情報書き換えは行わず,待ちになる*/
    while (tick<1500/INTERVAL && switchtimecurrent && period-100<tick);
    moveSwitchingData(num);
}

main()
{
    int num;
    char ch;
    int i;
    initLed();
    initSCI1();
    initTimer1Int(INTERVAL); /*INTERVALμsecのインターバルタイマ割り込み*/
    OUTPORT0.DDR=0xff; /*ポートの出力設定*/
    OUTPORT1.DDR=0xff; /*ポートの出力設定*/
    OUTPORT2.DDR=0xff; /*ポートの出力設定*/
    switchindex=switchtimecurrent=tick=0;
    period=PERIOD/INTERVAL;
    num=makeSwitchInfo();
    moveSwitchingData(num);
    E_INT();
    startTimer1();
    turnOnLed(0);
    while (1) {
        ch=getCharSCI1();
        if ('A'<=ch && ch<='X') {
            i=ch-'A';
            pulseWidth[i]+=50;
            if (2000<pulseWidth[i]) pulseWidth[i]=2000;
        } else if ('a'<=ch && ch<='x') {
            i=ch-'a';
            pulseWidth[i]-=50;
            if (pulseWidth[i]<1000) pulseWidth[i]=1000;
        } else if (ch=='0') {
            for (i=0;i<24;i++) pulseWidth[i]=1500;
        } else if (ch=='1') {
            for (i=0;i<24;i++) pulseWidth[i]=1000+40*i;
        }
        renewSwitchingData();
    }
}

#pragma asm
    .SECTION    MYVEC, DATA, LOCATE=H'000070
    .ORG        H'000070  ;IMIA1
    .DATA.L     _TimerIntFunc
    .SECTION    P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm
#define clearTimer1Flag() (ITU1.TSR.BIT.IMFA=0)

#pragma interrupt (TimerIntFunc)
void TimerIntFunc() /*タイマ割り込みルーチン*/
{
    static int cnt=0,blink=0;
    char *p;
    if (tick++==switchtimecurrent) {
        p=(char *)&tribyte[switchindex];
        OUTPORT0.DR.BYTE=*p++;
        OUTPORT1.DR.BYTE=*p++;
        OUTPORT2.DR.BYTE=*p;
        switchtimecurrent=switchtime[++switchindex];
    } else if (tick==period) {
        switchindex=switchtimecurrent=tick=0;
        if (++cnt==25) {
            cnt=0;
            P5.DR.BIT.B1=blink=1-blink;
        }
    }
    clearTimer1Flag();  /*タイマステータスフラグのクリア 忘れないこと*/
}

オシロスコープによる波形観察

ch1はP1B0の信号で,20msec周期で1000μsec幅のパルスが見えている。これは系統0のパルスである。
ch2はP1B7の信号で,20msec周期で2000μsec幅のパルスが見えている。これは系統7のパルスである。
どちらも同期したところ(位相ずれがない)にパルスが発生している。

参考 最適化前のインターバルタイマ割り込み関数
    理解しやすいと思われるので掲載

最適化前のインターバルタイマ割り込み関数

/* 動作原理がわかるように,最適化をしていないバージョン*/
/* これだと3048_16MHzで割り込み周期28μsecまで*/
void TimerIntFunc() /*タイマ割り込みルーチン*/
{
    static int cnt=0,blink=0;
    if (tick==switchtimecurrent) {
        OUTPORT0.DR.BYTE=tribyte[switchindex].bytedata[0];
        OUTPORT1.DR.BYTE=tribyte[switchindex].bytedata[1];
        OUTPORT2.DR.BYTE=tribyte[switchindex].bytedata[2];
        switchindex++;
        switchtimecurrent=switchtime[switchindex];
    }
    tick++;
    if (tick==period) {
        switchindex=switchtimecurrent=tick=0;
        cnt++;
        if (cnt==25) {
            cnt=0;
            blink=1-blink;
            if (blink) turnOnLed(1);
            else turnOffLed(1);
        }
    }
    clearTimer1Flag();  /*タイマステータスフラグのクリア 忘れないこと*/
}