PWMを用いた8kHz8bitサンプルの音声信号の出力

Dec2008
coskx

1.はじめに

 8kHz8bitサンプルのリニアPCM音声データを出力するには,通常は8kHzのタイマ割り込みでDACより出力すればよいが,PWM信号としてD級動作するアンプで動作させたい場合はPWM信号で出力するのがよい。
 考え方としては,H8/3048の16MHzクロックを用いて,4分周の4MHzでITUを動作させ,500カウントで割り込みをかけると8kHの割り込みになるが,この時同じITUでPWMを発生させる。PWM信号のH区間の長さは,音響信号が8bitであることに由来して0から255の長さを持つことになるのだが,定数を加えて0+α〜255+αの長さを持ってもよい。
 ITUでPWM信号を作るにはPWMモードとし,GRAに周期クロック数である500をセットし,GRBにH区間終了の値をセットすればよい。

 たとえば GRAに500(正確には499),GRBに200がセットされている時,カウンタTCNTは0からカウントアップし,500に達するとカウンタTCNTはゼロに戻り,再びカウントを開始する。この時TIOCAはTCNT=0の時立ち上がり(H区間開始),TCNT=200の時立ち下がる(H区間終了)

   
  TCNT=0  TCNT=GRB   TCNT=GRA(ここでTCNT=0にクリアされる)
  ↓    ↓      ↓
  ┌────┐      ┌──
  │    │      │  PWM信号(ITOCA)
──┘    └──────┘  
  ↑
  割り込み(GRBの書き換え)

 ここで大事なことは,割り込み作業中でGRBの書き換えは,TCNT=GRBになる前に行われていなければならないことである。
 これを実現するため,GRBには音響信号のサンプル値はそのまま使わずに,音響信号サンプル値に200加算した値をセットする。
 そうすると,0から255の音声データについては,200から455の値をGRBにセットするようにすればよい。
 (最大値が500を超えないようになっているからこれでOK)

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

DownLoad

 

2.テストプログラム1

のこぎり波状の値を割り込み関数内で生成してPWM信号を発生するプログラムを次に示す。
音を出してみると,256サンプル周期なので8000/256=31.25Hzののこぎり波音となる。
(実際聞いてみるとブツブツブツ・・・のように聞こえる)
割り込み関数が正常に動作していることを視覚的に確かめるために,LED0の点滅を行うようにしている。
ITUはch3を使用している。

 

のこぎり波生成 normalPWM.c

/**********************************************************
「8kHzサンプリングのリニアPCM音声データ」のPWMによる出力
8kHzのPWM周波数をもちいるので8kHzのベース音が耳ざわり

時間割り込みによって8KHzにてPWM信号を出力
ITU ch3 を用いる
クロックは4MHzに設定する
コンペアマッチAでカウンタクリア
TCNT=0;   TIOCA3立ち上がり
TCNT=GRB: TIOCA3立ち下がり
TCNT=GRA: TCNT=0
4000000/8000=500      → 4MHzクロック500サンプルで8.00KHzサンプル音  ○
500クロック中最初の200サンプルのHを置く これは割り込み処理のための時間稼ぎ
8ビットの音声データは0から255の値を持つので,
音声データ  0 200のH区間+300のL区間
音声データ255 455のH区間+ 45のL区間
となる
割り込み周波数は8kHzになる

AKI-H8/3048の場合はTIOCA3はCN1-16になる
**********************************************************/
#include <3048f.h>
#include "h8_3048.h"


#define Padd 200
#define PWMPERIOD 500
void initTimer3Int(void)
{
    ITU3.TCR.BIT.CCLR=1;  /*GRAのコンペアマッチでTCNTをクリア これはなくてもよいはず*/
    ITU3.TCR.BIT.CKEG=0;  /*立ち上がりエッジでカウント*/
    ITU3.TCR.BIT.TPSC=2;  /*内部クロックφ/4でカウント 4MHz*/
    ITU3.GRA=PWMPERIOD-1;  /*割り込みの周期を4MHzで500カウント:8kHz(割り込み周波数)に指定*/
    ITU3.GRB=Padd;      /*最少PWMデューティ4MHzでPに指定*/
    ITU3.TIER.BIT.IMIEA=1; /*TCNT=GRAとなったときの割り込み要求を許可*/
    ITU3.TIER.BIT.OVIE=0;  /*オーバー・アンダーフロー発生時の割り込みを禁止*/
    ITU3.TIER.BIT.IMIEB=0;  /*TCNT=GRBとなったときの割り込みを禁止*/
    ITU.TMDR.BIT.PWM3=1;  /*ITUのPWMに使うchをPWMモードに設定*/
    ITU3.TIOR.BIT.IOB=0;  /*TIOCB3にPWM信号を出力しないように設定*/
                          /*TIOCA3にPWM信号が出てくる*/
    ITU.TSTR.BIT.STR3=1;  /*カウントスタート(PWM信号が出力される)*/
}

main()
{
    initLed();
    initTimer3Int(); /*PWM出力初期化 TIOCA3*にPWM信号が出てくる*/
    E_INT();        /*CPU割り込み許可*/
    while(1);       /*なにもしないループ*/
}

#pragma asm
    .SECTION    MYVEC, DATA, LOCATE=H'000090
    .ORG        H'000090  ;IMIA3
    .DATA.L     _TimerIntFunc
    .SECTION    P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm

#define clearTimer3Flag() (ITU3.TSR.BIT.IMFA=0)
#pragma interrupt (TimerIntFunc)
void TimerIntFunc() /*タイマ割り込みルーチン*/
{
    static int counter=0;
    clearTimer3Flag();  /*タイマ割り込みフラグのクリア 忘れないこと*/
   
    /*ここで ITU3.GRB にPWM値+Paddを与える*/
    /*この例ではのこぎり歯状の出力になる*/
    ITU3.GRB=(counter&0xff)+Padd;

    /*この先は割り込み動作検証のためのLED点滅*/
    counter++;
    if (counter&0x1000) turnOnLed(0);
    else turnOffLed(0);
}

 

3.テストプログラム2

windowsのリニアwavファイル(モノラル8kHz8bit)を変換してテキストデータファイルをつくり,cの定数配列データにして,
これをPWM信号として出力するプログラムを作成する。音声信号は8kHzでPWM出力されるが,全音声データを出力した後は,
1秒間何もせず,再び音声信号が出力され,,また1秒の休みを行うというように循環動作を行う。
「よい天気・・・」という音声が聞こえたらOK。
8kHzのピーという音が聞こえるので,4kHzカットオフのローパスフィルタを通すとよい。

 

サンプル音響信号の出力 normalPWMvoice.c

/**********************************************************
「8kHzサンプリングのリニアPCM音声データ」のPWMによる出力
8kHzのPWM周波数をもちいるので8kHzのベース音はざわりである
カットオフ=4kHzのアナログローパスフィルタが必要

時間割り込みによって8KHzにてPWM信号を出力
ITU ch3 を用いる
クロックは4MHzに設定する

コンペアマッチAでカウンタクリア
TCNT=0;   TIOCA3立ち上がり
TCNT=GRB: TIOCA3立ち下がり
TCNT=GRA: TCNT=0
4000000/8000=500      → 4MHzクロック500サンプルで8.00KHzサンプル音  ○
500クロック中最初の200サンプルのHを置く これは割り込み処理のための時間稼ぎ
8ビットの音声データは0から255の値を持つので,
音声データ  0 200のH区間+300のL区間
音声データ255 455のH区間+ 45のL区間
となる
割り込み周波数は8kHzになる

AKI-H8/3048の場合はTIOCA3はCN1-16になる
**********************************************************/
#include <3048f.h>
#include "h8_3048.h"     /*led関係しか使っていない*/
#include "wavsample.h"   /*音響信号データの配列*/

#define Padd 200
#define PWMPERIOD 500

const int numberDatas=sizeof(samplewave)/sizeof(char);
volatile int ptr;

void initTimer3Int(void)
{
    ITU3.TCR.BIT.CCLR=1;  /*GRAのコンペアマッチでTCNTをクリア これはなくてもよいはず*/
    ITU3.TCR.BIT.CKEG=0;  /*立ち上がりエッジでカウント*/
    ITU3.TCR.BIT.TPSC=2;  /*内部クロックφ/4でカウント 4MHz*/
    ITU3.GRA=PWMPERIOD-1;  /*割り込みの周期を4MHzで500カウント:8kHz(割り込み周波数)に指定*/
    ITU3.GRB=Padd;      /*最少PWMデューティ4MHzでPに指定*/
    ITU3.TIER.BIT.IMIEA=1; /*TCNT=GRAとなったときの割り込み要求を許可*/
    ITU3.TIER.BIT.OVIE=0;  /*オーバー・アンダーフロー発生時の割り込みを禁止*/
    ITU3.TIER.BIT.IMIEB=0;  /*TCNT=GRBとなったときの割り込みを禁止*/
    ITU.TMDR.BIT.PWM3=1;  /*ITUのPWMに使うchをPWMモードに設定*/
    ITU3.TIOR.BIT.IOB=0;  /*TIOCB3にPWM信号を出力しないように設定*/
                          /*TIOCA3にPWM信号が出てくる*/
    ITU.TSTR.BIT.STR3=1;  /*カウントスタート(PWM信号が出力される)*/
}

void msecwait(int msec)
/*msec間なにもしない時間稼ぎ関数*/
{
    int i,j;
    for (i=0;i<msec;i++) {
        for (j=0;j<2646;j++);    /*2646は実測によって求めた値*/
    }
}

main()
{
    initLed();
    initTimer3Int(); /*PWM出力初期化 TIOCA3*にPWM信号が出てくる*/
    E_INT();        /*CPU割り込み許可*/
    while(1) {
        ptr=0;
        turnOnLed(1);
        while (ptr<numberDatas);
        turnOffLed(1);
        msecwait(1000);
    }
}

#pragma asm
    .SECTION    MYVEC, DATA, LOCATE=H'000090
    .ORG        H'000090  ;IMIA3
    .DATA.L     _TimerIntFunc
    .SECTION    P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm

#define clearTimer3Flag() (ITU3.TSR.BIT.IMFA=0)
#pragma interrupt (TimerIntFunc)
void TimerIntFunc() /*タイマ割り込みルーチン*/
{
    static int counter=0;
    clearTimer3Flag();  /*タイマ割り込みフラグのクリア 忘れないこと*/
   
    /*ここで ITU3.GRB にPWM値+Paddを与える*/
    if (ptr<numberDatas) {
        ITU3.GRB=(unsigned int)((unsigned char)samplewave[ptr])+(unsigned int)Padd;
        ptr++;
    }

    /*この先は割り込み動作検証のためのLED点滅*/
    counter++;
    if (counter&0x1000) turnOnLed(0);
    else turnOffLed(0);
}

上記プログラムにインクルードされているリニアPCM音声データファイルは次のようになっている。
このファイルはwindowsのリニアwavファイル(モノラル8kHz8bit)を変換して作成されている。
(ダウンロードしたサンプルプログラム中に変換ツールが入っている)
注意 H8/3048のROMに書き込む場合はサイズがROMサイズを超えないようにすること
   ROMサイズは128kbyteだから元の音声データは16秒程度が限界のはず

音響信号データファイル wavsample.h

/*音響信号の配列 const宣言になっているのでROM上に定数として置かれる*/
const char samplewave[]={
  0x7e, 0x7e, 0x80, 0x7e, 0x80, 0x80, 0x80, 0x80, 0x7e, 0x7e, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e,
  0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7e, 0x7e, 0x80, 0x7e,
  0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x80, 0x7e, 0x7e, 0x7e, 0x7f, 0x80, 0x80, 0x7e,
  0x80, 0x80, 0x80, 0x80, 0x80, 0x7e, 0x7e, 0x80, 0x7e, 0x80, 0x7e, 0x7e, 0x80, 0x7e, 0x7f, 0x7e,
 : **以下省略**
};

 

4.テストプログラム3

windowsのリニアwavファイル(モノラル8kHz8bit)を変換してテキストデータファイルをつくり,cの定数配列データにして,
これをPWM信号として出力するプログラムを作成する。音声信号は16kHzでPWM出力されるが,全音声データを出力した後は,
1秒間何もせず,再び音声信号が出力され,,また1秒の休みを行うというように循環動作を行う。
16kHzのピーという音が聞こえるが,あまり気にならないと思われるが,気になる場合は8kHzカットオフのローパスフィルタ
を用いればよい。
8kHzサンプリングデータを16kHzで出力するので,ちょっと工夫が必要
。コードを読むこと。

サンプル音響信号の出力 normalPWMvoice16k.c

/**********************************************************
「8kHzサンプリングのリニアPCM音声データ」のPWMによる出力
16kHzのPWM周波数をもちいるので16kHzのベース音は若い人しか聞こえず
耳ざわりではない

時間割り込みによって16KHzにてPWM信号を出力
ITU ch3 を用いる
クロックは8MHzに設定する
コンペアマッチAでカウンタクリア
TCNT=0;   TIOCA3立ち上がり
TCNT=GRB: TIOCA3立ち下がり
TCNT=GRA: TCNT=0
8000000/16000=500      → 8MHzクロック500個で16.00KHz出力  ○
500クロック中最初の100サンプルのHを置く これは割り込み処理のための時間稼ぎ
8ビットの音声データは0から255の値を持つので,
音声データ  0 200のH区間+300のL区間
音声データ255 455のH区間+ 45のL区間
となる
割り込み周波数は16kHzになる
また8kHzサンプリングデータから16kHz出力を扱うので,
2つの出力時刻において1つの入力データを出す必要がある
割り込み周波数は16kHzになる

AKI-H8/3048の場合はTIOCA3はCN1-16になる
**********************************************************/
#include <3048f.h>
#include "h8_3048.h"     /*led関係しか使っていない*/
#include "wavsample.h"   /*音響信号データの配列*/

#define Padd 200
#define PWMPERIOD 500

const unsigned int numberDatas=sizeof(samplewave)/sizeof(char);
volatile unsigned int ptr;

void initTimer3Int(void)
{
    ITU3.TCR.BIT.CCLR=1;  /*GRAのコンペアマッチでTCNTをクリア これはなくてもよいはず*/
    ITU3.TCR.BIT.CKEG=0;  /*立ち上がりエッジでカウント*/
    ITU3.TCR.BIT.TPSC=1;  /*内部クロックφ/2でカウント 8MHz*/
    ITU3.GRA=PWMPERIOD-1;  /*割り込みの周期を8MHzで500カウント:16kHz(割り込み周波数)に指定*/
    ITU3.GRB=Padd;      /*最少PWMデューティ4MHzでPに指定*/
    ITU3.TIER.BIT.IMIEA=1; /*TCNT=GRAとなったときの割り込み要求を許可*/
    ITU3.TIER.BIT.OVIE=0;  /*オーバー・アンダーフロー発生時の割り込みを禁止*/
    ITU3.TIER.BIT.IMIEB=0;  /*TCNT=GRBとなったときの割り込みを禁止*/
    ITU.TMDR.BIT.PWM3=1;  /*ITUのPWMに使うchをPWMモードに設定*/
    ITU3.TIOR.BIT.IOB=0;  /*TIOCB3にPWM信号を出力しないように設定*/
                          /*TIOCA3にPWM信号が出てくる*/
    ITU.TSTR.BIT.STR3=1;  /*カウントスタート(PWM信号が出力される)*/
}

unsigned int numberDatas2;

void msecwait(int msec)
/*msec間なにもしない時間稼ぎ関数*/
{
    int i,j;
    for (i=0;i<msec;i++) {
        for (j=0;j<2646;j++);    /*2646は実測によって求めた値*/
    }
}

main()
{
    numberDatas2 = numberDatas<<1;
    initLed();
    initTimer3Int(); /*PWM出力初期化 TIOCA3*にPWM信号が出てくる*/
    E_INT();        /*CPU割り込み許可*/
    while(1) {
        ptr=0;
        turnOnLed(1);
        while (ptr<numberDatas2);
        turnOffLed(1);
        msecwait(1000);
    }
}

#pragma asm
    .SECTION    MYVEC, DATA, LOCATE=H'000090
    .ORG        H'000090  ;IMIA3
    .DATA.L     _TimerIntFunc
    .SECTION    P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm

#define clearTimer3Flag() (ITU3.TSR.BIT.IMFA=0)
#pragma interrupt (TimerIntFunc)
void TimerIntFunc() /*タイマ割り込みルーチン*/
{
    static int old=0;
    static int counter=0;
    clearTimer3Flag();  /*タイマ割り込みフラグのクリア 忘れないこと*/
   
    /*ここで ITU3.GRB にPWM値+Paddを与える*/
    if (ptr<numberDatas2) {
        if (ptr&1) {
            ITU3.GRB=((old+(unsigned int)((unsigned char)samplewave[(ptr>>1)+1]))>>1)+(unsigned int)Padd;
        } else {
            ITU3.GRB=(old=(unsigned int)((unsigned char)samplewave[ptr>>1]))+(unsigned int)Padd;
        }
        ptr++;
    }

    /*この先は割り込み動作検証のためのLED点滅*/
    counter++;
    if (counter&0x1000) turnOnLed(0);
    else turnOffLed(0);
}

上記プログラムにインクルードされているリニアPCM音声データファイルは「3.テストプログラム2」で用いたものと同じである。