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

Dec2008
coskx

1.はじめに

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

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

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

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

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

DownLoad

 

2.テストプログラム1

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

 

のこぎり波生成 normalPWM.c

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

ITU ch3 を用いる
クロックは3.125MHz(φ/8)に設定する
コンペアマッチAでカウンタクリア
TCNT=0;   TIOCA3立ち上がり
TCNT=GRB: TIOCA3立ち下がり
TCNT=GRA: TCNT=0
3125/8=390.625      → 3.125MHzクロック391サンプルで8.00KHzサンプル音  ○
391クロック中最初の50サンプルのHを置く これは割り込み処理のための時間稼ぎ
8ビットの音声データは0から255の値を持つので,
音声データ  0 50のH区間+341のL区間
音声データ255 305のH区間+ 86のL区間
となる
割り込み周波数は8kHzになる

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


#define Padd 50
#define PWMPERIOD 391

void initTimer3Int(void)
{
    ITU3.TCR.BIT.CCLR=1;  /*GRAのコンペアマッチでTCNTをクリア これはなくてもよいはず*/
    ITU3.TCR.BIT.CKEG=0;  /*立ち上がりエッジでカウント*/
    ITU3.TCR.BIT.TPSC=3;  /*内部クロックφ/8でカウント 3.125MHz*/
    ITU3.GRA=PWMPERIOD-1;  /*割り込みの周期を3.125MHzで391カウント:8kHz(割り込み周波数)に指定*/
    ITU3.GRB=Padd;      /*最少PWMデューティ3.125MHzで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 を用いる
クロックは3.125MHz(φ/8)に設定する
コンペアマッチAでカウンタクリア
TCNT=0;   TIOCA3立ち上がり
TCNT=GRB: TIOCA3立ち下がり
TCNT=GRA: TCNT=0
3125/8=390.625      → 3.125MHzクロック391サンプルで8.00KHzサンプル音  ○
391クロック中最初の50サンプルのHを置く これは割り込み処理のための時間稼ぎ
8ビットの音声データは0から255の値を持つので,
音声データ  0 50のH区間+341のL区間
音声データ255 305のH区間+ 86のL区間
となる
割り込み周波数は8kHzになる

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

#define Padd 50
#define PWMPERIOD 391

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=3;  /*内部クロックφ/8でカウント 3.125MHz*/
    ITU3.GRA=PWMPERIOD-1;  /*割り込みの周期を3.125MHzで391カウント:8kHz(割り込み周波数)に指定*/
    ITU3.GRB=Padd;      /*最少PWMデューティ3.125MHzで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信号として出力するプログラムを作成する。音声信号は32kHzでPWM出力されるが,全音声データを出力した後は,
1秒間何もせず,再び音声信号が出力され,,また1秒の休みを行うというように循環動作を行う。
32kHzの音が聞こえるはずだが,人間には聞こえない。

8kHzサンプリングデータを32kHzで出力するので,同じ値を4回出している

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

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

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

AKI-H8/3048の場合はTIOCA3はCN1-16になる
**********************************************************/
#include <3048fone.h>
#include "h8_3048fone.h"
#include "wavsample.h"   /*音響信号データの配列*/

#define Padd 50
#define PWMPERIOD 391

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でカウント 12.5MHz*/
    ITU3.GRA=PWMPERIOD-1;  /*割り込みの周期を12.5MHzで391カウント:32kHz(割り込み周波数)に指定*/
    ITU3.GRB=Padd;      /*最少PWMデューティ12.5MHzで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<3352;j++);    /*3352は実測によって求めた値 20MHz駆動*/
    }
}

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 old=0;
    static int counter=0;
    clearTimer3Flag();  /*タイマ割り込みフラグのクリア 忘れないこと*/
   
    /*ここで ITU3.GRB にPWM値+Paddを与える*/
    ITU3.GRB=(unsigned int)((unsigned char)samplewave[ptr])+(unsigned int)Padd;
    if ((counter&3)==3) ptr++;

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

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