UMEHOSHI ITA TOP PAGE    COMPUTER SHIEN LAB

[UMEHOSHI ITA]の制御で使っているIC「PIC32MX270F256B-I/SO」のフラッシュメモリには、テスト用プログラムが書き込まれいています。
以降では、このプログラムを「テスト・ウメ・フラッシュ」と呼ぶことにして解説します。
また、「テスト・ウメ・フラッシュ」を利用したユーザー用のプログラムを 「ウメ・エディットプログラム」と呼ぶことにします。
「ウメ・エディットプログラム」の開発では「umehoshiEditツール」が必要で、 その取得や最初の操作情報は、こちらを参照してください。
(「PICKit3などの書き込みツール」をお持ちの方で、「テスト・ウメ・フラッシュ」を利用しないで、 「MPLAB X IDE」の開発環境ですべてをプログラミングする場合の情報ではありません。)
「テスト・ウメ・フラッシュ」をどのように利用して、「ウメ・エディットプログラム」を 作るかの解説で、「umehoshiEditツール」の使用例を示しています。
各サンプルは、このWebページ上でドラック&コピーして、貼り付けしてご利用ください。

各種確認プログラム左記に必要な部品の追加例

USBで接続されるホストにメッセージ(文字列)を送信する。

とくに必要ありません。
(D1のLEDは、あるとよい)

モータ制御、PWM (Timer2利用)

PWM対応の部品追加例

ADC(A/Dコンバータ)を使う(Timer3利用)

ADC 対応の部品追加例

BEEP(ブザー音で、デフォルトのCORE Timer、Timer1を利用)

BEEP SWITCH 対応の部品追加例

[UMEHOSHI ITA]単体で動作させる

Reset SW, Type-A, CN2 部品追加例

赤外線制御

U20,D4,D5,NPN, D3 部品追加例

UART を介して、Bluetoothで制御

CN11,CN-12 部品追加例

UART を介して、ESP32-WROOM-32DをBASIC制御

U19 部品追加例

UART を介して、RN4020(BLE)で制御

U17にRN4020の部品を追加する例

[UMEHOSHI ITA]のパルス変調で、圧電サンダーを鳴らす実験

[UMEHOSHI ITA]単体で動作させる時に必要な部品の追加です。


以下のコードでは、[UMEHOSHI ITA]のPORTB5の端子をON・OFFするパルス変調で、音を鳴らしています。
(PORTB5の端子の端子には、別途にLED D2のON・OFFも兼用しています。)
[UMEHOSHI ITA]の「テスト・ウメ・フラッシュ」を使うプログラムは、変数として使える総メモリ数が 4096byte程度です。
それ以外で自由に使えるRAM領域は、_MEMO3を先頭アドレスとする0x0F00byte(3840byte)しか在りません。
また自由に使えるEEPROM領域は、(0x9D03FC00-0x9D020000)の範囲で、130048byteです。
よって、メモリに記憶したデータで鳴らす場合、音のデータの格納数を少しでも減らす工夫を一番に考えて作る必要があります。
(音の品質が多少悪くても、データ量を抑える目標で考えます。)

[UMEHOSHI ITA]のタイマー割り込みでによるパルス変調で、圧電サンダーを鳴らす?

タイマ(timer4)割り込み処理で、byte配列のビット例に従って、PORTB5のビットをON・OFFします。
この割り込みのコードは、 (my_sys_ch.htmlのinit_timer_4_5()とtimer4_sample()のソースを参考に作っています。)
T4CONbits.TCKPS = 0;の設定で [1:1]プリスケールにすることで、timer4カウントアップ周期に内部PBCLKクロック (周辺モジュールバス クロックサイクルイクルの40Mz ) を使っています。
そして割り込み周期は、カウントアップの目標値はPR4の代入値で決まります。
例えば、1KHzの周期にするカウント値は、「1KHzの周期/TPBCLK周期=(1/1e3)/(1/40e6)-1=39999」で決まります。
そして、_HANDLES[_IDX_TIMER_4_FUNC] へ代入した関数のswitchingがこの周期で呼び出されます。
このような処理の場合、カウントアップの目標値(PR4)を小さくし過ぎると、その周期内に割り込み関数の処理が間にお合わなくなり、 目標の周期の処理ができなくなります。
そこで、作った処理が確実に実行できるかの検証が大事です。

以下は別途のコアタイマーで1秒の割り込み関数「core_TIMER_SUB_FUNC()」を作り、この割り込み内で10回数えることで10秒の計測時間を作り、その間だけスイッチング(switching)の動作回数を数えて、 10秒間で実行したswitching()の回数を数え、処理できているかを検証しています
#include <xc.h>
#include <stdlib.h>
#include "common.h"

#define TARAGET_PIN 0x0020 // スイッチング対象のPORT B端子

char buffer[100]="";
char bin_data_array[] = {	// 5μのスイッチング時に1KHzで1サイクル分のバイト数は「 (1/1e3) / 5e-6 / 8 = 25 」と算出
			// 25byteの半分が1、残り半分が1であれば、矩形波形りなり、それは 5μのスイッチング周期で、1KHzの周波数になります。
	0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,//10byte
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//10byte
	0x00,0x00,		0x0f,		0xff,0xff,
}; // スイッチングデータが並ぶバイナリ

char *bin_data = bin_data_array;
int bin_mask = 1;
int bin_idx = 0;

int switching_count=0;// switching の実行回数をカウント


void  switching() // スイッチング関数で、 timer4() 割り込み関数
{
    switching_count++;//スイッチング回数計測用

    if( bin_idx >= sizeof(bin_data_array) ) {
        bin_idx = 0;// bin_data のデータをループ再生
        // return; //returnするとループしない。(コメントになっているとループ再生)
    }
    if (bin_data[bin_idx] & bin_mask) PORTBSET = TARAGET_PIN; // ON 
    else PORTBCLR = TARAGET_PIN; // OFF

    bin_mask <<= 1;
    if( bin_mask >= 256 ){
        bin_idx+=1;
        bin_mask=1;
    }
}

// コアタイマーで作る1秒周期の割り込みに使う関数。
// これを、これで10回実行で10秒間の期間を判定し、その間だけswitching() の割り込みを可能にする。
// この間で、switching() を実行した回数を得て、その回数からswitching() 周期が正しいかを検証する。
void core_TIMER_SUB_FUNC()
{
	static int count=0;
	if( count == 0) {			//最初の割り込み
		PORTBCLR = 0x8000;// LED1をOFF 
		T4CONbits.ON = 1;	// timer4のswitching()の割り込み スタート
	}else if(count == 10){		//10秒後の割り込み
		PORTBSET = 0x8000;// LED1をON 
		T4CONbits.ON = 0; // switching()の割り込み ストップ
		_send_decimal(switching_count,10);//  PR4=199;の時、このカウント数は2000002であった。
		_send_string("\nEnd program\r\n");
		//PR4=399; //	割り込み周期 (399+1)*(1/40e6) =  10μ 秒(10秒間のカウント数は1000001)
		//PR4=199; //	割り込み周期 (199+1)*(1/40e6) =  5μ  秒(10秒間のカウント数は2000002)
		//PR4=159; //	割り込み周期 (159+1)*(1/40e6) =  4μ  秒(10秒間のカウント数は2499993)本来は2500000のはず
		//PR4=119; //	割り込み周期 (119+1)*(1/40e6) =  3μ  秒(10秒間のカウント数は2666659)本来は3333333のはず
		//PR4=100; //	割り込み周期 (100+1)*(1/40e6) =  2.5μ秒(10秒間のカウント数は2712983)本来は4000000のはず
		//PR4= 79; //	割り込み周期 ( 79+1)*(1/40e6) =  2  μ秒(10秒間のカウント数は2714110)本来は5000000のはず
		PR4=199; //	割り込み周期 (199+1)*(1/40e6) =  5μ  秒(10秒間のカウント数は2000002)
	}
	count++;
}

__attribute__((address( 0x80005000 ))) void main(void);
void main()
{
	_send_string("start program\r\n");
	
	// Timer4の割り込み初期化------------------
	T4CON =0x00000000;//typeB,16bit, [1:1]プリスケール
	TMR4=0x00000000; //16bitタイマの設定値(レジスタがカウントアップ)

	//IPC4bits.T4IP = 6; // Set priority level = 6(2番に大きな優先度)
	//IPC4bits.T4IS = 0;// Set sub-priority level = 3(最大値)
	IFS0bits.T4IF = 0;// Clear the timer interrupt status flag
	IEC0bits.T4IE = 1;// Timer4 Enable(割込み許可).
	_HANDLES[_IDX_TIMER_4_FUNC] = switching;// timer4割り込み関数を登録

	_UM_CP0_SET_COMPARE=(long)(1.0/(1/40e6)/2); // コアタイマを1秒周期の割り込み間隔に変更
	// (コアタイマは、2 システムクロック周期でインクリメントします)
	_HANDLES[_IDX_CORE_TIMER_SUB_FUNC]= core_TIMER_SUB_FUNC;// コアタイマの割り込み関数を登録
}

  のコメント範囲は、TMR4の設定値を変更して実験した値の結果です。
上記の実行結果のメモ「PR4=159; // 割り込み周期 (159+1)*(1/40e6) = 4μ 秒(10秒間のカウント数は2499993)本来は2500000のはず」で示したように、
4μ秒の設定にすると、switching() 関数の割り込みが間に合わなくなっています。
つまり、この使い方では5μ秒以上(PR4=199以上の設定)のスイッチング制御しかできない結果になりました

なお、 Raspberry Pi Picoを使って、add_repeating_timer_usを利用した switching()のコールバック関数の限界周期が50μ秒以上の結果と得られているので、 それと比べると、より細かい制御ができそうです。

[UMEHOSHI ITA]のスイッチングに専念した繰り返しで音を鳴らす。(タイマー割り込みを使わない) 限界の周期を探す!

以下は、switching() を、10秒間繰り返しするint0_FUNC()を作って、その間にswitching()を何回実行するを数えるプログラムです。
10秒間を計測する方法は、コアタイマーのカウント値「_CP0_GET_COUNT()」を利用しています。
(コアタイマーは (1/40e6)*2 =50ナノ秒で一つカウントします。よってこれを 10/(1/40e6)/2 =200000000.0数えて10秒経過したと判断している)。
switching()の関数の起動は、SW2スイッチを押すことで行っています。
それはINT0のエッジトリガーの外部割り込みで起動するようにしています。(補足3を参照)
#include <xc.h>
#include <stdlib.h>
#include "common.h"

#define PERIOD_N 100 // コアタイマーのカウントで指定するswitchingの呼び出し周期:5μ秒周期
//コアタイマーは (1/40e6)*2 =50ナノ秒で一つカウントします。(20であれば1μ秒周期を意味する)
// 10/((1/40e6)*2 * PERIOD_N )=目標のカウント値で、実行値がこれに近いほど正しい周期でswitching()をしている。
//10μ秒周期:10/((1/40e6)*2 *200 )= 1000000  に対して、実行値は 953207
// 5μ秒周期:10/((1/40e6)*2 *100 )= 2000000  に対して、実行値は1834739 
// 4μ秒周期:10/((1/40e6)*2 * 80 )= 2500000  に対して、実行値は2223505
// 3μ秒周期:10/((1/40e6)*2 * 60 )=3333333.33に対して、実行値は2899525
// 2μ秒周期:10/((1/40e6)*2 * 40 )= 5000000  に対して、実行値は4057275
// 1μ秒周期:10/((1/40e6)*2 * 20 )=10000000  に対して、実行値は4839274 
// 以上が、PERIOD_Nを20,40,60,80,100,200と変更した場合の実行結果

#define TARAGET_PIN 0x0020 // スイッチング対象のPORT B端子

char buffer[100]="";
char bin_data_array[] = {	// 5μのスイッチング時に1KHzで1サイクル分のバイト数は「 (1/1e3) / 5e-6 / 8 = 25 」と算出
			// 25byteの半分が1、残り半分が1であれば、矩形波形りなり、それは 5μのスイッチング周期で、1KHzの周波数になります。
	0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,//10byte
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//10byte
	0x00,0x00,		0x0f,		0xff,0xff,
}; // スイッチングデータが並ぶバイナリ
char *bin_data = bin_data_array;
int bin_mask = 1;
int bin_idx = 0;

int switching_count=0;// switching の実行回数をカウント
unsigned long next_count = 0;

int switching() // bin_dataの配列内容でスイッチングする関数。
{
    switching_count++;//スイッチング回数計測用

    if( bin_idx >= sizeof(bin_data_array) ) {
        bin_idx = 0;// bin_data のデータをループ再生
        // return 0; //returnするとループしない。(コメントになっているとループ再生)
    }
    if (bin_data[bin_idx] & bin_mask) PORTBSET = TARAGET_PIN; // ON 
    else PORTBCLR = TARAGET_PIN; // OFF
    bin_mask <<= 1;
    if( bin_mask >= 256 ){
        bin_idx+=1;
        bin_mask=1;
    }
    return 1; 
}

// SW2のスイッチのエッジトリガーのINT0で実行させる関数
// これを、これで10回実行で10秒間の期間を判定し、その間だけswitching() の割り込みを可能にする。
// この間で、switching() を実行した回数を得て、その回数からswitching() 周期が正しいかを検証する。
void int0_FUNC()
{
	IEC0CLR = _IEC0_INT0IE_MASK;// INT0割り込みを無効化(これが無いと、ちゃちたリングで再帰処理が生じる)

	long count_end = _CP0_GET_COUNT() + 10/(1/40e6)/2; //200000000の追加が10秒後のカウント値
	PORTBINV = 0x8000;// LED1を反転
	while(_CP0_GET_COUNT() < count_end){ // 10秒間の実行
		next_count = _CP0_GET_COUNT() + PERIOD_N;
		switching();
		while( _CP0_GET_COUNT() < next_count) { }
	}
	_send_decimal(switching_count,10);
	_send_string(" <==switching_count\r\n");
	//IFS0CLR = _IFS0_INT0IF_MASK;// 割り込みフラグをクリアする
}

__attribute__((address( 0x80005000 ))) void main(void);
void main()
{
	IEC0CLR = _IEC0_INT0IE_MASK;// INT0割り込みを無効化
	_HANDLES[_IDX_EXTERNAL_0_VECTOR] = int0_FUNC; // RB7 がSW2(白)に繋がっている。これが押された割り込み
	INTCONSET = _INTCON_INT0EP_MASK; // INT0をエッジトリガとして設定
	IPC0bits.INT0IP = 3; // 割り込みの優先度を設定(0から7の範囲で指定され、0が最も高い)
	IFS0CLR = _IFS0_INT0IF_MASK; // INT0割り込みフラグをクリア
	IEC0SET = _IEC0_INT0IE_MASK; // INT0割り込みを有効化
	_send_string("start program\r\n");
}
  のコメント範囲は、PERIOD_N の設定値を変更して実験した値の結果です。
PERIOD_N の実行例は次のようになります。
start program
  +1834739 <==switching_count
これは、10秒間でswitching() を2000000回実行できるはずの所を、1834739回しか実行できていないという結果です。
この結果は、ページ先頭で示したタイマ(timer4)割り込み処理でswitching() を呼び出し方法より遅い結果になっています。

割り込みを使わないスイッチングに専念した繰り返しで音を鳴らす限界の周期を探す!

前のコードは、SW2スイッチの押したエッジの外部割り込み(INT0)処理の中で、繰り返しを行っています。
それにより、mainの処理をリターンして「テスト・ウメ・フラッシュ」のUSB受信ループが止まらないようにして、 別途割り込みで10秒の判定しながらPERIOD_Nの回数だけ進んだコアタイマ回数判定で、switching() しています。
これにより、外部割り込み(INT0)処理の中でも、USB出力でswitching()の回数の表示を可能にしています。
この場合、優先度の高い割り込みが間に入る可能性があり、これが10秒間における希望の回数に達していない原因になっているのではと? 予想できます。
また、PERIOD_Nのコアタイマ回数判定で、コアタイマの現在の値を求める_CP0_GET_COUNT()を複数回使っており、この複数箇所間の取得のズレが累積されて問題が生じているコードであると判断しました。
この2つの問題を無くすため、外部割り込み(INT0)を使わないでmainで10秒のループを作る次のシンプルなコードで、再度検証して見ました。
#include <xc.h>
#include <stdlib.h>
#include "common.h"

#define PERIOD_N 100 // コアタイマーのカウントで指定するswitchingの呼び出し周期:5μ秒周期
//コアタイマーは (1/40e6)*2 =50ナノ秒で一つカウントします。(20であれば1μ秒周期を意味する)


#define TARAGET_PIN 0x0020 // スイッチング対象のPORT B5端子

char buffer[100]="";
char bin_data_array[] = {	// 5μのスイッチング時に1KHzで1サイクル分のバイト数は「 (1/1e3) / 5e-6 / 8 = 25 」と算出
			// 25byteの半分が1、残り半分が1であれば、矩形波形りなり、それは 5μのスイッチング周期で、1KHzの周波数になります。
	0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,//10byte
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//10byte
	0x00,0x00,		0x0f,		0xff,0xff,
}; // スイッチングデータが並ぶバイナリ
char *bin_data = bin_data_array;
int bin_mask = 1;
int bin_idx = 0;
int switching_count=0;// switching の実行回数をカウント

int switching() // bin_dataの配列内容でスイッチングする関数。
{
    switching_count++;//スイッチング回数計測用
    if( bin_idx >= sizeof(bin_data_array) ) {
        bin_idx = 0;// bin_data のデータをループ再生
        // return 0; //returnするとループしない。(コメントになっているとループ再生)
    }
    if (bin_data[bin_idx] & bin_mask) PORTBSET = TARAGET_PIN; // ON 
    else PORTBCLR = TARAGET_PIN; // OFF
    bin_mask <<= 1;
    if( bin_mask >= 256 ){
        bin_idx+=1;
        bin_mask=1;
    }
    return 1; 
}

__attribute__((address( 0x80005000 ))) void main(void);
void main()
{
	PORTBINV = 0x8000;// LED1を反転
	switching_count = 0;
	unsigned long next_count = 0;
	unsigned long count_now = _CP0_GET_COUNT();//(1/40e6)*2 =50ナノ秒でカウントするコアタイマーの値を取得
	unsigned long count_end = count_now  + 200000000;//の追加が10秒後のカウント値(10/50e-9=200000000)
	while(count_now < count_end){ // 10秒間の実行
		while( count_now < next_count) {
			count_now = _CP0_GET_COUNT();//50ナノでカウントするコアタイマーの値を取得
		}		
		switching();// switching_countをインクリメントしてPORT B5の端子をスイッチング
		//switching_count++;//スイッチング回数計測用
		next_count = count_now + PERIOD_N; // PERIOD_Nが100であれば5μ秒でswitching_count++を実行
	}
	PORTBINV = 0x8000;// LED1を反転
	_send_decimal(switching_count,10);
	_send_string(" <==switching_count\r\n");
}
上記の実行例です。
  +1957696 <==switching_count
これは、10秒間でswitching();を2000000回実行できるはずの所を、1957696回しか実行できていないという結果で、前述のコードより正確のはずですが、42304カウント足りません。
PERIOD_Nを20,40,60,80,100,200と変更した場合の実行結果と、予想カウント数の誤差を以下に示します。
PERIOD_Nの目標のカウント値は、switching();を指定周期にするためのカウント値です。10/((1/40e6)*2 * PERIOD_N )が、10秒で実行する実行回数の理論値です。
この理論値と実行値の回数との差を誤差として表記しました。
10μ秒周期:10/((1/40e6)*2 *200 )= 1000000  に対して、実行値は 979202 この誤差は  20798カウント
 5μ秒周期:10/((1/40e6)*2 *100 )= 2000000  に対して、実行値は1957696 この誤差は  42304カウント
 4μ秒周期:10/((1/40e6)*2 * 80 )= 2500000  に対して、実行値は2380332 この誤差は 119668カウント
 3μ秒周期:10/((1/40e6)*2 * 60 )= 3333333.3に対して、実行値は3099891 この誤差は 233442カウント
 2μ秒周期:10/((1/40e6)*2 * 40 )= 5000000  に対して、実行値は4263107 この誤差は 736893カウント
 1μ秒周期:10/((1/40e6)*2 * 20 )=10000000  に対して、実行値は4263111 この誤差は5736889カウント
これは、この手法で正確な制御は出来ないという結果です。
なお、switching();の部分をコメントにして、この行の次の「witching_count++;」のコメントを外して実行してみました。
switching();の代わりに「witching_count++;」を行わせた。)
この実行例は、次のようにカウント値が減るという不可解な結果になりました。
  +1939003 <==switching_count

[UMEHOSHI ITA]のパルス変調で、ドレミの音を圧電サンダーを鳴らす実験

[UMEHOSHI ITA]の圧電サンダーは使用チップのPIC32MX270F256BのPB5の端子に繋がっています。
この端子はOC2、(Output Compare modules:出力コンペアモジュール)の出力を出せるような構造になっており、 それを使うと、Timer2 または Timer3と連係したPWMハードを利用する制御が可能になります。

以下では、OC2をTimer2と連係したPWMモードで動作させて音を出す試みを行います。
下記右のデータはこのページで作った「ドレミファソラシド」の音を再生で作ったオリジナルファイルで行います。
下記左の音は、垂直の分解能が16ビットから4ビットに減らして、垂直の分解能も同じ大きさのレベルのパルス6個並べて1/6に情報量が減らした音です。
(右下の音16段階のパルス変調ではありませんが、同じレベルを6個並べたPCでの再生音です。「約7350Hz、分解能4ビット」相当)
オリジナルファイル(doremi.wav) 左リンクよりダウンロード可能
44100Hz,16BitPCM
目標となる音(音質を落としたデータ音)
「約7350Hz、分解能4ビット」


[UMEHOSHI ITA]で使っているチップ(PIC32MX270F256B)は出力コンペアモジュール(Output Compare modules)が5つ存在し、 それを制御するOutput Compare Control RegistersがOC1CON〜OC5CONまで存在します。
そして、圧電サンダーに繋がるB5の端子はOC2を利用するように決まっています。(OUT PIN SELECTIONの設定)
デューティー比 (デューティーサイクル)を決めるために数えるベースクロックにTimer2を使います。
OC2内では、このカウンターを比較して、OC2のHIGHのパルス幅を指定する機能(PWMモード)があり、それを利用します。
サウンドデータの44100Hzの周期は、1/44100 = 2.267573696μ秒で、このタイミングでPWMのデューティ サイクルの変更を行わせます。

Timerの割り込み周期を(1/44100)にして動作できることの確認
音楽業界で使う44100Hzの周期で関数を呼ぶ(映像業界は48000Hzですが)

44100Hzの周期はTimer2の割り込みを使ってみます。(割り込みでPWMの周期を変更する予定で、その割り込み動作を検証する)
次のようなコードで、チョット検証してみました。
T2CON =0x00000000;の設定で、typeB,16bit, [1:1]プリスケールにより、TMR2は自動的に(1/40e6=25ナノ秒)カウントアップします。
PWMの周期は、PR2 と TMR2で決まります。TMR2はカウントアップの目標値はPR2の代入値です。
そして、PR2 と TMR2が一致すると、TMR2はリセットされて0に戻り、割り込みが発生します。
よって、PR2のカウント数は(1/44100)/(1/40e6) =907.02947845805から1を引いて切り下げた近似値の、906を設定しました。
この割り込みを1秒間の割り込み回数を数えるコードが、次のコードです。
#include <xc.h>
#include "common.h"
int change_count = 0; // change()の割り込み回数計数用 
void  change() // timer2() 割り込み関数 (PWMのデューティ サイクルの変更予定
{
	change_count++;//割り込み回数計測用 (約44100Hzの周期でカウント)
	IFS0CLR = 0x00000200; // Clear the timer interrupt status flag
}
__attribute__((address( 0x80005000 ))) void main(void);
void main()
{
	// Timer2の割り込み初期化------------------
	T2CON =0x00000000;//typeB,16bit, [1:1]プリスケール これで(1/40e6)の周期で、TMR2をカウントアップ
	TMR2=0x0000; //16bitタイマの設初期値(上記設定周期でカウントして、PR2と一致するとクリアして割り込み)
	PR2=906; //16bitタイマの設定値(周期設定用  =int((1/44100)/(1/40e6)-1)
	_HANDLES[_IDX_TIMER_2_FUNC] = change;// timer2割り込み関数を登録

	PORTBINV = 0x8000;// LED1を反転
	unsigned long count_now = _CP0_GET_COUNT();//(1/40e6)*2 =50ナノ秒でカウントするコアタイマーの値を取得
	unsigned long count_end = count_now  + 20000000;//の追加が1秒後のカウント値(1/50e-9=20000000)
	change_count = 0;
	T2CONbits.ON = 1; // Timer2の動作スタート
	while(count_now < count_end){ // 1秒間の実行
		count_now = _CP0_GET_COUNT();//50ナノでカウントするコアタイマーの値を取得
	}
	PORTBINV = 0x8000;// LED1を反転
	_send_decimal(change_count,10);
	_send_string(" <==change_count\r\n");
}
この実行結果は次のようになり、想定内です。(1/44100)/(1/40e6)-1 =906.02947845805の値を、総数点以下を切り捨ててPR2に設定のため)
START:80005000
   +44101 <==change_count


上記1/44100の周期の割り込み周期で、PWDのデューティサイクルを変更して圧電サンダーを鳴らす実験1(Timer2割り込み利用)

「UMEHOSHI ITA」では、圧電サンダーがPIC32MX270F256BでPB5端子に繋がっており、PB5はOC2(Output Compare module 2)の出力端子に割り振ることができます。
これにより、OC2をPWDモードにすることで希望のデューティサイクルでパルス幅を出力できます。
この設定は次の仕様により、RPB5Rbits.RPB5R = 0x5; とOUTPUT PIN SELECTIONを設定します。
(microchip様の仕様書より)
次この1/44100の周期でPWDのデューティサイクルの変更周期を設定します。
以下ではCO2(Output Compare module2制御レジスタ)で、デューティ サイクルのクロック源としてTimer2を指定して使っています。
その場合OC2Rレジスタの内容がTimer2のカウント値と比較されて、OC2Rが大きい間だけHIGH になります。
OC2Rレジスタの設定も可能ですが、以下ではデューティ比設定をOC2RSだけに設定します。
OC2RSに設定した値は、Timer2の周期タイミングでOC2Rに自動設定されるからで、このダブルバッファにより周期変更タイミングで PWM動作のグリッチを防ぐ働きを担っています。
なお、デューティ サイクルレジスタ OC2R に 0000h を書き込むと、OC2 ピンは LOW のままになり(0% デューティ サイクル )、 OC2RSの値がタイマ周期レジスタPR2よりも大きい場合、ピンはHIGHのままになります。(100%デューティ サイクル )
また、OC2R が PR2 に等しい場合、OC2ピンは 1 タイムベースカウント値だけ LOW になり、他の全てのカウント値では HIGH になります。
以上の仕様(参考:https://ww1.microchip.com/downloads/jp/DeviceDoc/61111E_JP.pdf)から、次のコードで簡単に鳴らしてみました。
なおPR2=906の設定で、単純にOC2RSを906や1や0にすると、PR2の周期でPB5が切り替わるだけという結果が得られたので、OC2RSの設定範囲は(2から905)の値にしました。
(OC2RSに905を設定した場合を最大として、最後のタイムベースカウント値だけ LOW となります。905-2=903段階の変化が可能なので、情報量はmath.log(903,2)=9.82です)
1/44100の周期ではれば、約44回で1サイクルになるデータを使えば約1KHzの音になります。(計算値は、1.002kHzです。)
(2から905)の範囲で、44個の要素の配列がsinカーブになる値を、次の式で生成してみました。
import math
import matplotlib.pyplot as plt
y=[ int((-math.cos(x * 2*math.pi/44)+1)/2  *903+2 )  for x in range(44)]
plt.plot(y)
plt.show()
for n,v in enumerate(y):
    if n > 0 and n % 10 == 0 : print()
    print(v, end=", ")


2, 6, 20, 42, 73, 112, 157, 209, 265, 326,
389, 453, 517, 580, 641, 697, 749, 794, 833, 864,
886, 900, 905, 900, 886, 864, 833, 794, 749, 697,
641, 580, 517, 453, 389, 326, 265, 209, 157, 112,
73, 42, 20, 6,
#include <xc.h>
#include "common.h"
int change_count = 0; // change()の割り込み回数計数用 

short int pwd_datas[]={
2, 6, 20, 42, 73, 112, 157, 209, 265, 326,
389, 453, 517, 580, 641, 697, 749, 794, 833, 864,
886, 900, 905, 900, 886, 864, 833, 794, 749, 697,
641, 580, 517, 453, 389, 326, 265, 209, 157, 112,
73, 42, 20, 6,
};
int pwd_idx=0; // 上記アクセス用添え字
void  change() // timer2() 割り込み関数 (PWMのデューティ サイクルの変更予定
{
	change_count++;//割り込み回数計測用 (約44100Hzの周期でカウント)
	if( pwd_idx >= sizeof(pwd_datas)/2 ) pwd_idx  = 0;
	OC2RS = pwd_datas[pwd_idx++];
	IFS0CLR = 0x00000200; // Clear the timer interrupt status flag
}
__attribute__((address( 0x80005000 ))) void main(void);
void main()
{
	// Timer2の初期化---(割り込み指定を含む)---------------
	T2CON =0x00000000;//typeB,16bit, [1:1]プリスケール これで(1/40e6)の周期で、TMR2をカウントアップ
	TMR2=0x0000; //16bitタイマの設初期値(上記設定周期でカウントして、PR2と一致するとクリアして割り込み)
	PR2=906; //16bitタイマの設定値(周期設定用  =int((1/44100)/(1/40e6)-1)
	_HANDLES[_IDX_TIMER_2_FUNC] = change;// timer2割り込み関数を登録
	IPC2SET = 0x0000000C; // Set priority level = 3
	IEC0SET = 0x00000200;//T2IE Timer2 Enable(割込み許可)

	// Output Compare module 2の初期化------------------
	OC2CON = 0x0000;// Turn off OC2 while doing setup. 
	RPB5Rbits.RPB5R = 0x5; // RB5をOC2の出力にする。
	OC2CONbits.SIDL = 0; // アイドルモード中も動作を継続する
	OC2CONbits.OC32 = 0; //OC2R<15:0> およびOC2RS<15:0> を使って16 ビットタイマ源と比較する
	OC2CONbits.OCTSEL = 0; // Timer2 をこの出力コンペア モジュールのクロック源として使う
	OC2CONbits.OCM = 5; // OC2 をPWM モードにし、フォルトピンを無効にする
	//OC2R = 200;//出力コンペアx コンペアレジスタの初期値
	OC2RS = 905;//出力コンペアx セカンダリコンペアレジスタ(デューティー比設定用で上記設定の最大値 )

	T2CONbits.ON = 1; // Timer2の動作スタート
	OC2CONbits.ON =1;	// OC2の動作スタート
	PORTBINV = 0x8000;// LED1を反転
	unsigned long count_now = _CP0_GET_COUNT();//(1/40e6)*2 =50ナノ秒でカウントするコアタイマーの値を取得
	unsigned long count_end = count_now  + 200000000;//の追加が10秒後のカウント値(10/50e-9=200000000)
	change_count = 0;

	while(count_now < count_end){ // 10秒間の実行
		count_now = _CP0_GET_COUNT();//50ナノでカウントするコアタイマーの値を取得
	}
	PORTBINV = 0x8000;// LED1を反転
	OC2RS=2;
	OC2CONbits.ON =0;	// OC2の動作OFF
	_send_decimal(change_count,10);
	_send_string(" <==change_count\r\n");
}
これを使って生成された音は余計な周期的と思われる雑音が出て、期待した正弦波の1KHzとは程遠い結果になりました。
圧電サンダーの電圧波形を見ると、予期しないパルスが次のイメージのように存在しました。

予想として、 change()の呼び出しによる周期変更(OC2RSへの代入)を、Timer2の割り込みで行っていることが、正しくないのでは?
と考えて以下で、OC2の割り込みを使うように変更したコードを下記に示します。

PWDのデューティサイクルを変更をTimer2割り込みでなく、OC2割り込み利用に変更


前述のコードでTimer2の割り込みを止めて、代わりにOC2割り込みを利用したコードに変更した場合、上記の波形が得られました。
つまり、OC2RSのPWMのデューティー比(デューティーサイクル)レジスタ変更をタイマー割り込みで行うのは間違いで、 下記のようにOC2の割り込みで正しい波形になりました。
#include <xc.h>
#include "common.h"
int change_count = 0; // change()の割り込み回数計数用 

short int pwd_datas[]={
2, 6, 20, 42, 73, 112, 157, 209, 265, 326,
389, 453, 517, 580, 641, 697, 749, 794, 833, 864,
886, 900, 905, 900, 886, 864, 833, 794, 749, 697,
641, 580, 517, 453, 389, 326, 265, 209, 157, 112,
73, 42, 20, 6,
};
int pwd_idx=0; // 上記アクセス用添え字
void  change() // OC2割り込み関数 (PWMのデューティ サイクルの変更
{
	change_count++;//割り込み回数計測用 (約44100Hzの周期でカウント)
	if( pwd_idx >= sizeof(pwd_datas)/2 ) pwd_idx  = 0;
	OC2RS = pwd_datas[pwd_idx++];
	//IFS0CLR = 0x00000200; // Clear the timer interrupt status flag
	IFS0CLR = _IFS0_OC2IF_MASK;	// Clear the OC2 interrupt flag★
}
__attribute__((address( 0x80005000 ))) void main(void);
void main()
{
	// Timer2の初期化------------------
	T2CON =0x00000000;//typeB,16bit, [1:1]プリスケール これで(1/40e6)の周期で、TMR2をカウントアップ
	TMR2=0x0000; //16bitタイマの設初期値(上記設定周期でカウントして、PR2と一致するとクリアして割り込み)
	PR2=906; //16bitタイマの設定値(周期設定用  =int((1/44100)/(1/40e6)-1)
	//_HANDLES[_IDX_TIMER_2_FUNC] = change;// timer2割り込み関数を登録
	//IPC2SET = 0x0000000C; // Set priority level = 3
	IEC0CLR = 0x00000200;//T2IE Timer2 desable(割込み不許可)★

	// Output Compare module 2の初期化-(割り込み指定を含む)-----------------
	OC2CON = 0x0000;// Turn off OC2 while doing setup. 
	RPB5Rbits.RPB5R = 0x5; // RB5をOC2の出力にする。
	OC2CONbits.SIDL = 0; // アイドルモード中も動作を継続する
	OC2CONbits.OC32 = 0; //OC2R<15:0> およびOC2RS<15:0> を使って16 ビットタイマ源と比較する
	OC2CONbits.OCTSEL = 0; // Timer2 をこの出力コンペア モジュールのクロック源として使う
	OC2CONbits.OCM = 5; // OC2 をPWM モードにし、フォルトピンを無効にする
	_HANDLES[_IDX_OUTPUT_COMPARE_2_VECTOR] = change;// OC2割り込み関数を登録
	IFS0CLR = _IFS0_OC2IF_MASK;	// Clear the OC2 interrupt flag
	IEC0SET = _IEC0_OC2IE_MASK;	// Enable OC2 interrupt 
	IPC2bits.OC2IP = 7;// Set OC1 interrupt priority to 7
	IPC2bits.OC2IS = 3;// Set Subpriority to 3, maximum
	//OC2R = 0;//出力コンペア1 コンペアレジスタの初期値
	OC2RS = 905;//出力コンペア1 セカンダリコンペアレジスタ(デューティー比設定用用 )

	T2CONbits.ON = 1; // Timer2の動作スタート
	OC2CONbits.ON =1;	// OC2の動作スタート
	PORTBINV = 0x8000;// LED1を反転
	unsigned long count_now = _CP0_GET_COUNT();//(1/40e6)*2 =50ナノ秒でカウントするコアタイマーの値を取得
	unsigned long count_end = count_now  + 200000000;//の追加が1秒後のカウント値(10/50e-9=200000000)
	change_count = 0;

	while(count_now < count_end){ // 10秒間の実行
		count_now = _CP0_GET_COUNT();//50ナノでカウントするコアタイマーの値を取得
	}
	PORTBINV = 0x8000;// LED1を反転
	_send_decimal(change_count,10);
	_send_string(" <==change_count\r\n");
}
  の部分のTimer2割り込みを止めて、  のOC2割り込み設定を追加しています。
なお、OC2割り込み設定は、 microchip社様の仕様書(INTERRUPT IRQ, VECTOR AND BIT LOCATIONのページ)を、
OC2の設定は、 microchip社様の仕様書(Output Compare Control Registersのページ)日本語資料を、 参考に作りました。

以上のコードを使って、ほぼ予想通りの正弦波の1KHzの波形が得られました。
しかし、音は殆ど聞こえない状況の結果になりました。微かに聞こえているようなので、アンプを通せばそれなりの音になりそうです。
ですが、圧電サンダーで鳴らす目標からすると、使い物になりません。
前回の余計なインパクト的波形を含む音は、それなりの音量になるですが、これが圧電サンダーを扱う難しさなのでしょう。

少ないメモリサイズで(音質を落として)、圧電サンダーを鳴らす?

前述の実験結果より、それなりのPWD(ベース周波数が40MHz、分解能が約10bitで1/44100周期)で、 圧電サンダーを鳴らそうとすると音量が足りない(アンプが必要?)結果になりました。
インパクト的な波形のものであれば、それなりの音量が得られるで、そうであれば、逆に音質を落としてインパクト的な波形にすれば?
つまり品質が悪いがそれなりの音が出る可能性があるかも?と考えられます。
元々情報量を減らして、少ないメモリで再生させたい目標でもあったので、最初の目標で示したように4ビット(16段階)のパルス変調にします。
そこで、デューティ サイクルレジスタ OC2R に設定する(2から905)の範囲を16段階に訳けて見ました。
次のPythonコードで得られる配列です。
pdata=[int(n*903/15)+2 for n in range(16)] 
print(pdata) # 結果「[2, 62, 122, 182, 242, 303, 363, 423, 483, 543, 604, 664, 724, 784, 844, 905]」
これを4ビットで選んでデューティ比に指定します。(16ビットの音を4bitにする→必要メモリを1/4にできる)

また、同じデューティ比を6回利用して鳴らす方法にします。
これは、サンプリング周波数を1/6の(44100/6=7350Hz)にしたことに相当します。(必要メモリを1/6にできる)
以上により、44100Hz,16BITで1秒の音に必要な44100*2=88200byteは、88200/4/6=3675byteで済みます。
以上の目標で、前述のchange() 関数を変更してみました。
#include <xc.h>
#include "common.h"
int change_count = 0; // change()の割り込み回数計数用 

#define NREP 6 // 1つ(4bit)のデータを、約(1/4410)秒周期で繰り返し使う回数(水平分解能指定パラメタ)

short int pwd_base[]={2, 62, 122, 182, 242, 303, 363, 423, 483, 543, 604, 664, 724, 784, 844, 905};
int idx_base=0;

char bin_data_array[]={// 下位4ビットと、上位4ビットで1byteが2つデータ(1byteで0.2721ミリ秒)
0x40, 0xb8, 0xaf, 0x26, 	// 8つのデータ
};// 上記4byteで1つのデータを(NREP=6)回使う。0.272*4=約0.92ミリ(約919Hzの波形)


char *pwd_datas=bin_data_array;
int pwd_idx=0; // 上記アクセス用添え字
int count_out=0; // pwd_datasの各要素の上位、下位それぞれのデータを(NREP=6)回使い、(NREP+NREP)までのカウンタ

void change() // OC2割り込み関数 (PWMのデューティ サイクルの変更
{
	change_count++;//割り込み回数計測用 (約44100Hzの周期でカウント)
	if(count_out == 0){		// 下位4ビットの周期へ
		idx_base = pwd_datas[pwd_idx] & 0x0f;
		OC2RS = pwd_base[idx_base];
	}else if(count_out == NREP){	// 上位4ビットの周期へ
		idx_base = (pwd_datas[pwd_idx] >> 4) & 0x0f;
		OC2RS = pwd_base[idx_base];
	}
	if(++count_out == (NREP+NREP)){ //上位4ビットが終わって次の要素周期へ
		count_out=0;
		pwd_idx++;
		if( pwd_idx >= sizeof(bin_data_array) ) {//周期要素を配列を使い終わった
			pwd_idx = 0;
			// OC2CONbits.ON =0;	// OC2の動作OFF
		}
	}
	IFS0CLR = _IFS0_OC2IF_MASK;	// Clear the OC2 interrupt flag
}
__attribute__((address( 0x80005000 ))) void main(void);
void main()
{
	// Timer2の初期化------------------
	T2CON =0x00000000;//typeB,16bit, [1:1]プリスケール これで(1/40e6)の周期で、TMR2をカウントアップ
	T2CONbits.TCKPS = 0;// 1:1プリスケール値
	TMR2=0x0000; //16bitタイマの設初期値(上記設定周期でカウントして、PR2と一致するとクリアして割り込み)
	PR2=906; //16bitタイマの設定値(周期設定用  =int((1/44100)/(1/40e6)-1)

	IEC0CLR = 0x00000200;//T2IE Timer2 desable(割込み不許可)

	// Output Compare module 2の初期化-(割り込み指定を含む)-----------------
	OC2CON = 0x0000;// Turn off OC2 while doing setup. 
	RPB5Rbits.RPB5R = 0x5; // RB5をOC2の出力にする。
	OC2CONbits.SIDL = 0; // アイドルモード中も動作を継続する
	OC2CONbits.OC32 = 0; //OC2R<15:0> およびOC2RS<15:0> を使って16 ビットタイマ源と比較する
	OC2CONbits.OCTSEL = 0; // Timer2 をこの出力コンペア モジュールのクロック源として使う
	OC2CONbits.OCM = 5; // OC2 をPWM モードにし、フォルトピンを無効にする

	_HANDLES[_IDX_OUTPUT_COMPARE_2_VECTOR] = change;// OC2割り込み関数を登録
	IFS0CLR = _IFS0_OC2IF_MASK;	// Clear the OC2 interrupt flag
	IEC0SET = _IEC0_OC2IE_MASK;	// Enable OC2 interrupt 
	IPC2bits.OC2IP = 7;// Set OC1 interrupt priority to 7
	IPC2bits.OC2IS = 3;// Set Subpriority to 3, maximum
	//OC2R = 0;//出力コンペア1 コンペアレジスタの初期値
	//OC2RS = 905;//出力コンペア1 セカンダリコンペアレジスタ(デューティー比設定用用 )

	T2CONbits.ON = 1; // Timer2の動作スタート
	OC2CONbits.ON =1;	// OC2の動作スタート
	PORTBINV = 0x8000;// LED1を反転
	unsigned long count_now = _CP0_GET_COUNT();//(1/40e6)*2 =50ナノ秒でカウントするコアタイマーの値を取得
	unsigned long count_end = count_now  + 200000000;//の追加が10秒後のカウント値(10/50e-9=200000000)
	change_count = 0;

	while(count_now < count_end){ // 10秒間の実行
		count_now = _CP0_GET_COUNT();//50ナノでカウントするコアタイマーの値を取得
	}
	PORTBINV = 0x8000;// LED1を反転

	OC2RS=0;
	OC2CONbits.ON =0;	// OC2の動作OFF
	_send_decimal(change_count,10);
	_send_string(" <==change_count\r\n");
}
pwd_datasが指し示す文字列の1byteの「下位4ビットと上位4ビット」で2つデータ指定し、それぞれのデータを(NN=6)回使うパルス変調です。
よって1byteのデータは、『(1/44100)*2*6=0.00027210884353741496=約0.27ミリ秒』です。
(正確か計算値=(1/40e6)*(906+1)*2*6=0.0002721秒です。)
上記0x40, 0xb8, 0xaf, 0x26,の8つのデータをPythonで可視化すると、次ようになります。
import math
import matplotlib.pyplot as plt
NREP=6 # 1つ(4bit)のデータを、約(1/4410)秒周期で繰り返し使う回数
pwd_base=[2, 62, 122, 182, 242, 303, 363, 423, 483, 543, 604, 664, 724, 784, 844, 905]
pwd_datas=[0x40, 0xb8, 0xaf, 0x26,] # 4bitで上記テーブル内パルス幅を指定する(4byteなので、8個のデータ)
pwd_idx=0; # 上記アクセス用添え字
count_out=0;
y=[]
while pwd_idx < len(pwd_datas):
	if(count_out == 0):		# 下位4ビットの周期へ
		idx_base = pwd_datas[pwd_idx] & 0x0f
	elif(count_out == NREP):	# 上位4ビットの周期へ
		idx_base = (pwd_datas[pwd_idx] >> 4) & 0x0f
	y.append( pwd_base[idx_base])
	if(count_out == (NREP+NREP)):# 上位4ビットが終わって次の要素周期へ
		pwd_idx+=1
		count_out=-1
	count_out+=1

plt.plot(y)
plt.show()
上記の大きさのパルス変調を行う上記のC言語ソースをビルド、転送して実行させると、音が少し聞こえるようになりました。
実行時の検証のため圧電サンダーの電圧波形を確認しました。次の波形です。(pwd_datasのデータを少し変更したイメージと比較しています。)
2, 62, 122, 182, 242, 303, 363, 423, 483, 543, 604, 664, 724, 784, 844, 905
12, 62, 122, 182, 242, 303, 363, 423, 483, 543, 604, 664, 724, 784, 844, 805
上記2つ音量の違いは、分かりませんでした。

パルス変調のスイッチングで使うベース周波数を40MHzから5MHzに変更して音を比較してみる

Timer2で使うベースクロックを、8分の1に変更します。次の設定です。(bit 5-4を01ビットにします。)
T2CONbits.TCKPS = 1;// 1:8プリスケール値
これにより、PWDの幅を指定する周期が8倍になります。
それでOC2の割り込み周期を指定するPR2=906; を、PR2=112;に変更します。
これでOC2の割り込み周期は、(1/40e6)*8*(112+1) = 22.6e-06 = 22.6μ秒で、22.6757μ秒(44100Hzの周期)よりチョット短くなります。
この短くなった周期に合わせて、周期指定テーブルのpwd_base内容を約1/8の次へテーブルに変更します。
short int pwd_base[]={2, 7, 15, 22, 30, 37, 45, 52, 60, 67, 75, 83, 90, 98, 105, 110};

以上の3箇所の変更で、5MHzのパルス変調に変更して、発生音を聴いてみましたが、音質の違いは分かりませんでした。
(なお44100Hzの周期で、同じPWD幅のパルスを(NREP=6)回連続して出している部分(44100Hzの1/6相当の分解能)は前回と同じ制御です。)

ドレミの録音した音を再生して比較する実験その1 デューティー比変更周期は1/44100*6 = 0.136ミリ秒

前述の単音(約1KHzの正弦波に近い波形)では違いが聞き取れなかったので、ドレミの録音した音を再生して比較の実験を行いました。
その再生の比較データを指定する方法は、前述C言語ソースを変更で対応する場合、次の2通りが容易にできる状況です。
  1. bin_data_arrayの配列内容を変更する方法
  2. bin_data_arrayの配列の代わりに別途記憶領域でデータを設定し、そのポインタをpwd_datasに設定する方法
両者とも、別途のwavファイルからデータを抽出して両者のデータ記憶箇所に設定する必要があり、それを行うPythonのコードを次のように作りました。
doremi.wavのファイルから、「約7350Hz、分解能4ビット」の初期化データ生成するコードです。
# wav_to_UME.py 『wavファイルからパルス変調用ファイルを作成する』
import wave
# 参考https://docs.python.org/ja/3/library/wave.html
import numpy as np
import matplotlib.pyplot as plt

WAV_FILE_PATH="doremi.wav"# 変換対象のこの音声が記憶されるサウンドファイル

def getframedatas_by_wav(wav_file_path:str): # wavファイルからフレームレートと、16bitPCMバイナリーを取得
   print(f"{wav_file_path}サウンドファイルから、再生用のバイト列を取得")
   wavefile = wave.open(wav_file_path, "r")
   framerate = wavefile.getframerate() # フレームレート(1秒間のデータ出力数)
   print(f"フレームレート:{framerate}Hz、サンプリング周期:{1/framerate*1e6:6.2f}u秒") 
   nchannel=wavefile.getnchannels()
   print("チャンネル数:", nchannel)
   sampwidth=wavefile.getsampwidth()
   print("サンプル幅数:", sampwidth)
   if sampwidth != 2:
      print(f"サンプル幅数が2(PCM 16bit)以外に対応していません")
      exit()
   nframes = wavefile.getnframes() # 全フレーム内データ数
   print(f"フレームの数:{nframes}, 時間:{nframes/framerate}秒") 
   buffer = wavefile.readframes(nframes) # 全フレームを読み取る
   wavefile.close() # ファイルを閉じる 
   if nchannel == 2: # 2チャンネルであれば、左チャンネルだけ抽出
      ba=bytearray()
      for i,c in enumerate(list(buffer)):
         if i%4==0: ba.append(c)# 左チャンネル抽出(0,1)、右なら(3,4)
         if i%4==1: ba.append(c)
      buffer = bytes(ba)
   return framerate, buffer

framerate, buffer=getframedatas_by_wav(WAV_FILE_PATH) # wavファイルからフレームレートと、16bitPCMバイナリーを取得

print(f"オリジナルのサウンドの{len(buffer)/2/framerate} 秒を再生する。")
import pyaudio # サウンド再生用
audio = pyaudio.PyAudio()
format=audio.get_format_from_width(width=2) # ストリームを読み書きするときのデータ型
stream = audio.open(format=format,channels=1,rate=framerate,output=True)
stream.write(buffer)  # オリジナルのサウンドの音を確認

print(f"サウンドデータ(16bitPCMバイナリー)からpulse_modulationで使う値(符号なし16bitのnumpy配列)に変換")
int16_np=np.frombuffer(buffer, dtype="int16")
uint16_np=int16_np+(2<<14) # int16をunsigned int 16へ
#plt.plot(uint16_np)  # オリジナルのサウンド(uint16)の視覚化
#plt.show() 

minV=min(uint16_np)
value_width=max(uint16_np)-minV
print(f"最小値:{minV}, 値の幅:{value_width}")
print(f"データ数:{uint16_np.shape[0]}")

NREP=6 # 1つ(4bit)のデータを、約(1/4410)秒周期で繰り返し使う回数(水平分解能指定パラメタ)
count_out=0 # NREPの回数カウント用で、この回数分のデータ
nrep_list =[]
prev_val=0
print(f"サウンドデータ(16bitPCMバイナリー)を4bitデータに変換し、{NREP}個のサンプル数を1つまとめる")
datas_4bit=[] # 4bitに変換したデータの記憶用リスト
for i in range(uint16_np.shape[0]):
    val= uint16_np[i]
    nrep_list.append(val)
    count_out+=1
    if count_out == NREP:
       val=nrep_list[0]
       #val=sum(nrep_list)/len(nrep_list) # ここで、データの変化に対応した特殊抽出の制御検討が可能
       #if val > prev_val : val = max(nrep_list)
       #elif val < prev_val : val = min(nrep_list)
       #prev_val=val
       idx_v= int( (val-minV)/value_width*16 )
       idx_v = 15 if idx_v >= 16 else idx_v 
       datas_4bit.append(idx_v)
       count_out=0
       nrep_list =[]

print(f"最小値:{min(datas_4bit)}, 最大値:{max(datas_4bit)}")
print(f"データ数:{uint16_np.shape[0]}から{len(datas_4bit)}個になった。")
plt.plot(datas_4bit)
plt.show() 

# datas_4bitの4bitデータリストを内容を4096倍してバイナリ化し、その音を再生
buffer2=(np.array( [[x<<12]*NREP for x in datas_4bit] ).flatten()-(2<<14)).astype(np.int16).tobytes()
stream.write(buffer2)  # 変換した音を確認

print("4ビット要素の2つを、1バイトにまとめて、リストにする。")
bin_data_array=[]
for n,bit4 in enumerate(datas_4bit): # (249600バイト以下にしないとリンクエラーの可能性あり)
    if n % 2 == 0:
        v = bit4
    else:
        v |= bit4<<4
        bin_data_array.append(v)
        #print(f" '\\x{v:02X}',", end="")

print(f"生成したデータ:{len(bin_data_array)}byte")

num_maxdata=2950 # UMEHOSHI ITAにおいて3000の配列要素数のデータはビルドで失敗した。
count_byte=0
print(f"C言語用の配列初期化データを、テキストファイルとして出力(最大で{num_maxdata}バイトの出力制限)")
with open("bin_data_array.txt", "w") as f: # C言語配列用パルス変調のデータを書き込み
    for n,v in enumerate(bin_data_array): 
        if n > 0 and n % 10 == 0 : 
            f.write("\n")
        if count_byte >= num_maxdata: break
        s=f" '\\x{v:02X}',"
        #print(s, end="")
        f.write(s)
        count_byte += 1

starting_address=0x9D020000     #領域EEPPOMデータ設定開始アドレス # RAM領域データ設定開始アドレスであれば(0x80009000)
setting_limit_address=0x9D03E400 # 設定限界アドレス # RAM領域データ設定開始アドレスであれば(0x80009000+0x0F00)

path=r"C:\Microchip\umehoshiEdit\samples\_20240305\6_timer\data.bin.hex"
print(f"starting_address:{setting_limit_address:016X}で「UME専用Hexファイル:{path}」を出力")

def data_to_ume_hex_faile(path, datas_list, starting_address, limit_address):
    'datas_listが8bitデータのリストのファイル化で、starting_addressの番地から'
    'UMEHOSHI ITA用の「S」から始まる設定データ群を「UME専用Hexファイル」として出力'
    idx=0
    address16 = starting_address
    with open(path, "w") as f: # UMEHOSHI C言語配列用パルス変調のデータを書き込み
        while idx < len(datas_list) and address16 < limit_address:
            nn = 16
            if idx+nn >= len(datas_list):nn = len(datas_list)-idx
            if address16+nn >= limit_address:nn = limit_address-address16
            s=f"S{nn:02X}{address16:08X}00"
            for n in range(nn):
                s+=f"{datas_list[idx+n]:02X}"
            checksum=sum([ord(c) for c in s])
            s+=f"{((0x100-checksum)) & 0x0ff:02X}"
            #print(s)
            f.write(f"{s}\n")
            idx += nn
            address16+=nn
        return address16

last_address=data_to_ume_hex_faile(path, bin_data_array, starting_address, setting_limit_address)
print(f"last_address:{last_address:016X}, {last_address-starting_address}byteのデータ")
上記の実行例です。
doremi.wavサウンドファイルから、再生用のバイト列を取得
フレームレート:44100Hz、サンプリング周期: 22.68u秒
チャンネル数: 2
サンプル幅数: 2
フレームの数:132372, 時間:3.0016326530612245秒
オリジナルのサウンドの3.0016326530612245 秒を再生する。
サウンドデータ(16bitPCMバイナリー)からpulse_modulationで使う値(符号なし16bitのnumpy配列)に変換
最小値:8029, 値の幅:50405
データ数:132372
サウンドデータ(16bitPCMバイナリー)を4bitデータに変換し、6個のサンプル数を1つまとめる
最小値:0, 最大値:15
データ数:132372から22062個になった。
4ビット要素の2つを、1バイトにまとめて、リストにする。
生成したデータ:11031byte
C言語用の配列初期化データを、テキストファイルとして出力(最大で2950バイトの出力制限)
starting_address:000000009D03E400で「UME専用Hexファイル:data.bin.hex」を出力
last_address:000000009D022B17, 11031byteのデータ
  の部分コード実行で、bin_data_array.txtのファイルが生成されます。
このデータをコピーして、前述C言語ソースの0x40, 0xb8, 0xaf, 0x26, の代わりに貼り付けて利用する場合に使います(手法の1です)
このデータの一部を下記に示します。
 '\x77', '\x77', '\x77', '\x77', '\x77', '\x77', '\x77', '\x77', '\x77', '\x87',
 '\x67', '\x86', '\x68', '\x76', '\x98', '\x9A', '\x88', '\x77', '\x77', '\x87',
このようなデータが、この例では11031個生成されるのですが、実際には2950個だけ制限されて出力されます。
(2950個に制限した理由は、UMEHOSHI ITAの「ウメ・エディットプログラム」を使う場合のメモリ容量による限界によるものです。) ですから、短い周期的な波形の初期化にしか、この手法は使えないことになります。

もう一つの手法は、「bin_data_arrayの配列の代わりに別途記憶領域でデータを設定し、そのポインタをpwd_datasに設定する方法」で、 上記pythonの実行で生成される「data.bin.hex」のファイルを使う方法です。
この生成されたファイルの一部を以下に示します。
S109D020000007777777777777777778767866876989A5F
S109D02001000887777877878988956865754767767776D
これは0x9D020000番地から始まる[UMEHOSHI ITA]へのメモリ設定コードなので、「umehoshiEditツール」など使って転送することで記憶域を設定する手法です。
pythonソース内のstarting_address=0x9D020000で配置スタート番地を指定しています。
これをデータ記憶の先頭アドレスになるように、Cのソースを変更あする必要があります。
前述Cソースで、char *pwd_datas=bin_data_array;の箇所を、
        char *pwd_datas==(char *)(0x9D020000); と変更します。

そして、この記憶域のサイス条件の変更も必要で、次の箇所です。
if( pwd_idx >= sizeof(bin_data_array) ) を、 if( pwd_idx >= 11031 ) に変更します。
この11031の部分は、Pythonの実行結果から得られます。

これをRAMの領域の番地を指定することができます。[UMEHOSHI ITA]では自由に使えるRAM領域番地は0x80009000で、そこから0x0F00byteを利用できますが 少ないので、この実験で使う分解能の場合、(1byteで0.2721ミリ秒)*0x0F00=1.045秒程度の再生しかできません。
よって、上記例でROM領域を指定していました。
ROM領域であれば0x9D020000〜0x9D03E400なので、0.2721*(0x9D03E400-0x9D020000)=約33.7秒再生できる予想となります。(約7350Hz、4ビットの分解能時です。)

以上の実行で得られた音は次のようになりました。残念ながらドレミの音からかなり外れて、音量も得られませんでした。
(下記で聴くことができます。PWD用のパルスベース周波数:40MHz 、約7350Hz、4ビットの分解能時)


   参考にPICOで実験した結果のXの動画リンクを、ここで示します。
なお、PWD用のパルスベース周波数:40MHzを5MHzにした場合と聞き比べました。下記がその音です。


ですが、両者に音質に違いを聞き取れませんでした。
(両者の音データは、同じデータで同じ記憶域を使って再生しています。)

ドレミの録音した音を再生して比較する実験その2 デューティー比変更周期は1/44100*3 = 約0.0680ミリ秒

前回の音質が再生(PWDデューティー比変更周期は1/44100*6)の状況は特に高音が正しく鳴っていないと感じられます。
これは、6回は同じPWDのパルス幅で行っており、実質の水平サンプリング周期は44100Hz/6 = 7350Hzに相当します。
これを、44100Hz/3 = 14700Hz相当で試します。
これは前回の6パルス(同じPWD周期パルスを6回)を3パルスにすることで可能です。
これにより同じ長さの音を再生する場合、前回のデータ数に対して2倍のサイズが必要になります。
doremi.wavのファイルから、「約14700Hz、分解能4ビット」の初期化データ生成するPythonのコードは、span 前述で紹介した wav_to_UME.py において、
NREP=6 # 1つ(4bit)のデータを、約(1/4410)秒周期で繰り返し使う回数(水平分解能指定パラメタ)を 次のように変更するだけです。
NREP=3
doremi.wavサウンドファイルから、再生用のバイト列を取得
フレームレート:44100Hz、サンプリング周期: 22.68u秒
チャンネル数: 2
サンプル幅数: 2
フレームの数:132372, 時間:3.0016326530612245秒
オリジナルのサウンドの3.0016326530612245 秒を再生する。
サウンドデータ(16bitPCMバイナリー)からpulse_modulationで使う値(符号なし16bitのnumpy配列)に変換
最小値:8029, 値の幅:50405
データ数:132372
サウンドデータ(16bitPCMバイナリー)を4bitデータに変換し、3個のサンプル数を1つまとめる
最小値:0, 最大値:15
データ数:132372から44124個になった。
4ビット要素の2つを、1バイトにまとめて、リストにする。
生成したデータ:22062byte
C言語用の配列初期化データを、テキストファイルとして出力(最大で2950バイトの出力制限)
starting_address:000000009D03E400で「UME専用Hexファイル:C:\Microchip\umehoshiEdit\samples\_20240305\6_timer\data.bin.hex」を出力
last_address:000000009D02562E, 22062byteのデータ

このPythonコード実行で得られたdata.bin.hexを「UMEHOSHI ITA」に転送して書き込みする訳ですが、 、前述で行ったようにEEPROM領域を使った記憶域を 改めて異なるデータを書き込んで再び利用する場合、 予め次のようなコードの実行で消去する必要があります。
以下が0x9D020000〜0x9D03F000の範囲と、
0x9D03F800〜0x9D03FC00の範囲を消去するコードです。
この範囲が「ウメ・エディットプログラム」において、
データやプログラムコードを自由に配置できるEEPROM領域です。
下記が、erase.cのソースで、
 右が、実行用「UME専用Hexコマンド」です。
これをUSBを介して転送することで、メモリに書き込めます。
#include <xc.h>
#include <stdlib.h>
#include "common.h"
__attribute__((address( 0x80005000 ))) void main(void);
void main()
{
unsigned int clradr[] ={ // for x in range(0x9D020000, 0x9D040000, 0x400): print(f"0x{x:8X},")
0x9D020000,0x9D020400,0x9D020800,0x9D020C00,0x9D021000,0x9D021400,
0x9D021800,0x9D021C00,0x9D022000,0x9D022400,0x9D022800,0x9D022C00,
0x9D023000,0x9D023400,0x9D023800,0x9D023C00,0x9D024000,0x9D024400,
0x9D024800,0x9D024C00,0x9D025000,0x9D025400,0x9D025800,0x9D025C00,
0x9D026000,0x9D026400,0x9D026800,0x9D026C00,0x9D027000,0x9D027400,
0x9D027800,0x9D027C00,0x9D028000,0x9D028400,0x9D028800,0x9D028C00,
0x9D029000,0x9D029400,0x9D029800,0x9D029C00,0x9D02A000,0x9D02A400,
0x9D02A800,0x9D02AC00,0x9D02B000,0x9D02B400,0x9D02B800,0x9D02BC00,
0x9D02C000,0x9D02C400,0x9D02C800,0x9D02CC00,0x9D02D000,0x9D02D400,
0x9D02D800,0x9D02DC00,0x9D02E000,0x9D02E400,0x9D02E800,0x9D02EC00,
0x9D02F000,0x9D02F400,0x9D02F800,0x9D02FC00,0x9D030000,0x9D030400,
0x9D030800,0x9D030C00,0x9D031000,0x9D031400,0x9D031800,0x9D031C00,
0x9D032000,0x9D032400,0x9D032800,0x9D032C00,0x9D033000,0x9D033400,
0x9D033800,0x9D033C00,0x9D034000,0x9D034400,0x9D034800,0x9D034C00,
0x9D035000,0x9D035400,0x9D035800,0x9D035C00,0x9D036000,0x9D036400,
0x9D036800,0x9D036C00,0x9D037000,0x9D037400,0x9D037800,0x9D037C00,
0x9D038000,0x9D038400,0x9D038800,0x9D038C00,0x9D039000,0x9D039400,
0x9D039800,0x9D039C00,0x9D03A000,0x9D03A400,0x9D03A800,0x9D03AC00,
0x9D03B000,0x9D03B400,0x9D03B800,0x9D03BC00,0x9D03C000,0x9D03C400,
0x9D03C800,0x9D03CC00,0x9D03D000,0x9D03D400,0x9D03D800,0x9D03DC00,
0x9D03E000,0x9D03E400,
//0x9D03F000,この部分は__vector_dispatchで消去してはいけない。
//0x9D03F400,この部分は__vector_dispatchで消去してはいけない。
0x9D03F800,
//0x9D03FC00, パワーオン起動用などの起動ポインタなどがある。
};
	PORTBCLR = 0x8000;// LED1を消去
	int i;
	for(i=0; i < sizeof(clradr)/sizeof(unsigned int); i++){
		_nvm_erase_page(clradr[i]);
	}
	PORTBSET = 0x8000;// LED1を点灯
	_send_string("END program\r\n");
}
S108000500000F0FDBD270C02BFAF0802BEAF21F0A00307
S1080005010000080023C1400C427E0504324F0010224D3
S1080005020002128600021304000B414000C0000000012
S10800050300088BF023C00800334246143AC1000C0AF87
S108000504000201400080000000000A0023CF0404234FC
S1080005050000000438C1000C28F801002001000C427D8
S108000506000211082000400428C2120400009F86000F0
S108000507000000000001000C28F010042241000C2AFDA
S1080005080001000C28F7C00422CEFFF4014000000008E
S10800050900088BF023C00800334286143AC00A0023C8F
S10800050A000A44142340000428C0080033CD0506424BC
S10800050B00009F840000000000021E8C0030C02BF8F91
S10800050C0000802BE8F1002BD270800E00300000000AB
S10800050D000454E442070726F6772616D0D0A00000097
S10800050E0000000029D0004029D0008029D000C029DAF
S10800050F0000010029D0014029D0018029D001C029DAA
S1080005100000020029D0024029D0028029D002C029DBB
S1080005110000030029D0034029D0038029D003C029DB6
S1080005120000040029D0044029D0048029D004C029DB1
S1080005130000050029D0054029D0058029D005C029DAC
S1080005140000060029D0064029D0068029D006C029DA7
S1080005150000070029D0074029D0078029D007C029DA2
S1080005160000080029D0084029D0088029D008C029D9D
S1080005170000090029D0094029D0098029D009C029D98
S10800051800000A0029D00A4029D00A8029D00AC029D77
S10800051900000B0029D00B4029D00B8029D00BC029D72
S10800051A00000C0029D00C4029D00C8029D00CC029D66
S10800051B00000D0029D00D4029D00D8029D00DC029D61
S10800051C00000E0029D00E4029D00E8029D00EC029D5C
S10800051D00000F0029D00F4029D00F8029D00FC029D57
S10800051E0000000039D0004039D0008039D000C039DAA
S10800051F0000010039D0014039D0018039D001C039DA5
S1080005200000020039D0024039D0028039D002C039DB6
S1080005210000030039D0034039D0038039D003C039DB1
S1080005220000040039D0044039D0048039D004C039DAC
S1080005230000050039D0054039D0058039D005C039DA7
S1080005240000060039D0064039D0068039D006C039DA2
S1080005250000070039D0074039D0078039D007C039D9D
S1080005260000080039D0084039D0088039D008C039D98
S1080005270000090039D0094039D0098039D009C039D93
S10800052800000A0039D00A4039D00A8039D00AC039D72
S10800052900000B0039D00B4039D00B8039D00BC039D6D
S10800052A00000C0039D00C4039D00C8039D00CC039D61
S10800052B00000D0039D00D4039D00D8039D00DC039D5C
S10800052C00000E0039D00E4039D00F8039D00FC039D55
S10800052D000211080005600C0100C00CA2C4D004015B0
S10800052E0002618A400030063302338040029006010ED
S10800052F0000300E7300600E0102330C7000000A398C2
S1080005300000300A3882128A700000083B821208700D7
S1080005310000F00C3302338C3001100E01021306000E2
S1080005320002138E5000000A3980300A3880400A898B8
S1080005330000700A8880800A9980B00A9880C00AA9874
S1080005340000F00AA88000083AC040088AC080089AC6C
S1080005350001000A52410008424F2FFA714FCFF8AAC34
S1080005360000300C3302338C3002A00E01021306000DF
S1080005370002138E5000000A3980300A3880400A524C1
S10800053800004008424FBFFA714FCFF83AC2100001045
S108000539000000000000500E0102330C7000000A398F1
S10800053A0002128A700000083B8212087000F00C330BE
S10800053B0002338C3000D00E010213060002138E500C6
S10800053C0000000A38C0400A88C0800A98C0C00AA8C55
S10800053D000000083AC040088AC080089AC1000A52487
S10800053E00010008424F6FFA714FCFF8AAC0300C33021
S10800053F0002338C3000700E010213060002138E500CF
S1080005400000000A38C0400A52404008424FCFFA71484
S108000541000FCFF83AC0600C0102138C5000000A39079
S1080005420000100A52401008424FCFFA714FFFF83A043
S0880005430000800E0030000000031

前述のPythonの実行で得られたデータのバイト列は、コンソールに「22062byteのデータ」と表示され、 このサイズが反映されるように、前述したC言語のソースを変更します。
それは、if( pwd_idx >= sizeof(bin_data_array) ) を、 if( pwd_idx >= 22062 ) にします。
また、同じPWD周期パルスを6回の指定を3パルスにする次の変更を行います。
それは、#define NREP 6 // 1つ(4bit)のデータを、約(1/4410)秒周期で繰り返し使う回数(水平分解能指定パラメタ)
#define NREP 3にします。
以上で、デューティー比変更周期は1/44100*3として 音がきめ細かく鳴ることを期待した結果の音は、次のように鳴りました。

雑音が少なくなったように感じましたが、本来の音も小さくなり、より聴きにくくなったように感じます。

ドレミの録音した音を再生して比較する実験その3 デューティー比変更周期は1/44100*1 = 約0.0226ミリ秒

上記コードで得られた音で期待の音質が得られなかったので、 約(1/4410)秒のオリジナルデータと同じ周期でパルス変調データを変更する方法で、次の音になりました。
垂直分解能は前述と同じで、オリジナルデータの16ビットに対して、それを4ビットに情報量を減らして鳴らしています。

  1. 前述のerase.cで、「UMEHOSHI ITA」の0x9D020000〜0x9D03F000の範囲と、 0x9D03F800〜0x9D03FC00の範囲を消去。
  2. 前述のpythonソース(wav_to_UME.py)でで、「NREP=6」を「NREP=1」に変更して実行して、"doremi.wav"からdata.bin.hexを生成し、 それで、EEPROMにデータを書き込みます。このpython実行時にデータサイズ(66186バイト情報)を取得
  3. 前述のC言語ソースでで、#define NREP 1のコードに変更して、 前述で得サイズの66186で、if( pwd_idx >= 66186 ) の条件に適用。
上記の音を確認すると、音量が下がって聞き取りにくく、音の鮮明さが失われていると感じます。


音質を落すことで少ないデータにし、パルス変調で圧電サンダーを鳴らす実験結果のまとめ

下記オリジナルファイル(doremi.wav)を、データ量を減らして、鳴らした実験結果です。
オリジナルファイル(doremi.wav)44100Hz,16BitPCM (264.744Kbyteの再生音)

「44100Hz÷6=約7350Hz、分解能4ビット」(11.031Kbyteの再生音)
PCでオリジナルファイル16bitを4bitにして、オリジナルのデータ6個から1個を抽出した目標となる音
目標となる音(音質を落としたデータでPC上のPythonで鳴らした音)
UMEHOSHI ITA で鳴らした音を録音した再生音(11.031Kbyteの再生音)
「44100Hz÷6約7350Hz、分解能4ビット」相当の音を、PWDのベースクロックを40MHzで鳴らした音

株式会社村田製作所の(PKM13EPYH4000-A0)圧電サンダーを使った場合、
音質を落としてなるべく音量を上げる検討の結果、使う場合に許容しがたいのですが、ここで使った設定が最も良いと分かりました。
UMEHOSHI ITA で鳴らした音を録音した再生音(11.031Kbyteの再生音)
「44100Hz÷6約7350Hz、分解能4ビット」相当の音を、PWDのベースクロックを5MHzで鳴らした音
上記の40MHzの音と聞き比べて、音質や音量の違いが分からなかった。
その結果より、将来的にデータを細かく制御できる可能性から、上記の40MHzのスイッチング周期の方が良いと判断できた。
UMEHOSHI ITA で鳴らした音を録音した再生音 (上記よりサンプリング周期を2分の1:データサイスを2倍にして精度を上げた22.062Kbyteの再生音)
「44100Hz÷3約14700Hz、分解能4ビット」相当の音を、PWDのベースクロックを40MHzで鳴らした音

上の7350Hzの音と比べて、音のメリハリが無くなっって、聴きにくくなった。(音量が小さくなったが、雑音も少し減ったように感じる)
UMEHOSHI ITA で鳴らした音を録音した再生音(上記よりサンプリング周期を3分の1:データサイスを3倍にして精度を上げた66.186Kbyteの再生音)
約44100Hz、分解能4ビット」相当の音を、PWDのベースクロックを40MHzで鳴らした音
上の14700Hzの音と比べて、より音のメリハリが無くなっって、聴きにくくなった。(音量が小さくなったが、雑音も少し減ったように感じる)

圧電サンダーに繋がるPIC32MX270F256BのB5の端子をオープンドレインにして鳴らしてみる試み

使用チップ(PIC32MX270F256)の各 I/O ピンは、別々に通常のデジタル出力またはオープンドレイン出力として設定できます。
現在の圧電サンダーにかかる電圧が3.3Vなので、これを5Vまで上げて鳴らす実験です。

「UMEHOSHI ITA」で使っているPIC32MX270F256は、各入出力ビットをオープンドレインやプルアップ、プルダウンのハードへソフトで設定可能です。
以下は、  の部分で圧電サンダーに接続しているPORT B5をオープンドレインにし、 プルアップ、プルダウンを無効してから圧電サンダーを鳴らしています。
圧電サンダーは前述のドレミで最良と判断したコードです。
#include <xc.h>
#include "common.h"
int change_count = 0; // change()の割り込み回数計数用 

#define NREP 6 // 1つ(4bit)のデータを、約(1/4410)秒周期で繰り返し使う回数(水平分解能指定パラメタ)

short int pwd_base[]={2, 62, 122, 182, 242, 303, 363, 423, 483, 543, 604, 664, 724, 784, 844, 905};
int idx_base=0;

char *pwd_datas=(char *)(0x9D020000);
int pwd_idx=0; // 上記アクセス用添え字
int count_out=0; // pwd_datasの各要素の上位、下位それぞれのデータを(NREP=6)回使い、(NREP+NREP)までのカウンタ

void change() // OC2割り込み関数 (PWMのデューティ サイクルの変更
{
	change_count++;//割り込み回数計測用 (約44100Hzの周期でカウント)
	if(count_out == 0){		// 下位4ビットの周期へ
		idx_base = pwd_datas[pwd_idx] & 0x0f;
		OC2RS = pwd_base[idx_base];
	}else if(count_out == NREP){	// 上位4ビットの周期へ
		idx_base = (pwd_datas[pwd_idx] >> 4) & 0x0f;
		OC2RS = pwd_base[idx_base];
	}
	if(++count_out == (NREP+NREP)){ //上位4ビットが終わって次の要素周期へ
		count_out=0;
		pwd_idx++;
		if( pwd_idx >= 11031 ) {//周期要素を配列を使い終わった
			pwd_idx = 0;
			// OC2CONbits.ON =0;	// OC2の動作OFF
		}
	}
	IFS0CLR = _IFS0_OC2IF_MASK;	// Clear the OC2 interrupt flag
}
__attribute__((address( 0x80005000 ))) void main(void);
void main()
{
	ODCBSET = 0x0020; // スイッチング対象のPORT B5出力端子をオープンドレインに設定
	CNPUBCLR = 0x0020; // スイッチング対象のPORT B5出力端子をpull-up不使用に設定
	CNPDBCLR = 0x0020; // スイッチング対象のPORT B5出力端子をpull-down不使用に設定

	// Timer2の初期化------------------
	T2CON =0x00000000;//typeB,16bit, [1:1]プリスケール これで(1/40e6)の周期で、TMR2をカウントアップ
	T2CONbits.TCKPS = 0;// 1:1プリスケール値
	TMR2=0x0000; //16bitタイマの設初期値(上記設定周期でカウントして、PR2と一致するとクリアして割り込み)
	PR2=906; //16bitタイマの設定値(周期設定用  =int((1/44100)/(1/40e6)-1)
	IEC0CLR = 0x00000200;//T2IE Timer2 desable(割込み不許可)

	// Output Compare module 2の初期化-(割り込み指定を含む)-----------------
	OC2CON = 0x0000;// Turn off OC2 while doing setup. 
	RPB5Rbits.RPB5R = 0x5; // RB5をOC2の出力にする。
	OC2CONbits.SIDL = 0; // アイドルモード中も動作を継続する
	OC2CONbits.OC32 = 0; //OC2R<15:0> およびOC2RS<15:0> を使って16 ビットタイマ源と比較する
	OC2CONbits.OCTSEL = 0; // Timer2 をこの出力コンペア モジュールのクロック源として使う
	OC2CONbits.OCM = 5; // OC2 をPWM モードにし、フォルトピンを無効にする

	_HANDLES[_IDX_OUTPUT_COMPARE_2_VECTOR] = change;// OC2割り込み関数を登録
	IFS0CLR = _IFS0_OC2IF_MASK;	// Clear the OC2 interrupt flag
	IEC0SET = _IEC0_OC2IE_MASK;	// Enable OC2 interrupt 
	IPC2bits.OC2IP = 7;// Set OC1 interrupt priority to 7
	IPC2bits.OC2IS = 3;// Set Subpriority to 3, maximum
	//OC2R = 0;//出力コンペア1 コンペアレジスタの初期値
	//OC2RS = 905;//出力コンペア1 セカンダリコンペアレジスタ(デューティー比設定用で上記設定の最大値 )

	T2CONbits.ON = 1; // Timer2の動作スタート
	OC2CONbits.ON =1;	// OC2の動作スタート
	PORTBINV = 0x8000;// LED1を反転
	unsigned long count_now = _CP0_GET_COUNT();//(1/40e6)*2 =50ナノ秒でカウントするコアタイマーの値を取得
	unsigned long count_end = count_now  + 200000000;//の追加が10秒後のカウント値(10/50e-9=200000000)
	change_count = 0;
	while(count_now < count_end){ // 10秒間の実行
		count_now = _CP0_GET_COUNT();//50ナノでカウントするコアタイマーの値を取得
	}
	PORTBINV = 0x8000;// LED1を反転

	OC2RS=0;
	OC2CONbits.ON =0;	// OC2の動作OFF
	_send_decimal(change_count,10);
	_send_string(" <==change_count\r\n");

	count_out = 0;//ウメ・エディットプログラムのグローバル変数はリセットで初期化しないので、連続実行のために初期化
}
[UMEHOSHI ITA]のPortB5のデフォルトは、オープンドレインでなくトーテムポール出力型です。
また、プルアップ無し、プルダウン在りがパワーオン時のデフォルトです。
これらの設定は電源オン時に設定されるもので、リセット時では初期化されません。
よって、「ウメ・エディットプログラム」で電源を切らずにリセット操作などで連続的に利用する場合は、上記にように初期設定コードが必要となります。
同様に、「ウメ・エディットプログラム」ではリセット操作でグローバル変数の初期化は行われません。
実行コード内に、グローバル変数の初期設定コードを埋め込む必要があります。

さて、最終的に圧電サンダーの端子を外部プルアップ抵抗100KΩで5Vにに接続する半田付けの修正や、
内蔵pull-downの無効化を行いましたが、音量は大きくなりませんでした。
その場合、ほかにも圧電サンダーの端子が他のLED D2と兼用している不都合があり、LED2の取り付けを外す必要があります。
そうしないと、その電流制限用のR8の560Ωが圧電サンダーと並列接続する状態になるため、圧電サンダーの端子のLowレベルの電位が下がりません。

現状でこれ以上の音量を上げるためには、アンプ回路を追加するしかないと思われます。


この続きの実験をこのページに示します。



補足1: タイマクロックの立ち上がりエッジ毎に TMR カウントレジスタをインクリメントします。
タイマは TMR カウントレジスタ値が PR 周期レジスタ値に一致するまでインクリメントし続けます。
一致すると、次のタイマクロック サイクルで TMRカウントレジスタが 0x0000 にリセットした後にインクリメントを自動的に再開します。
なお、PR 周期レジスタ値を 0x0000 に設定すると、次のタイマクロック サイクルで TMR カウントレジスタ値は 0x0000 にリセットしますが、 インクリメントは再開しません。


補足2:タイマーのモードにタイプ Aと タイプ Bがあり、タイプ Aは16ビット固定ですがCPU スリープモード中も動作でます。
(なお、タイプ Aは、外部クロック源を使えて非同期動作でスリープモード中も動作できますがデバイス依存で、通常はTimer1だけで使います。)
そしてこの作品では、タイプ Bの指定で作っています。
タイプ Bでは、Timer4:の下位ハーフワード (16 ビット ) とTimer5:の上位ハーフワード (16 ビット ) を使った32ビットも可能ですが、 ここでは、Timer4だけ使った16ビットタイマーを使っています。
(Timer2とTimer3で32ビットカウンタも可能です。)


補足3:SW2スイッチのINT0外部割り込み 参考 DS60001108H_JP-19P
外部割り込みの極性を変更すると、割り込み要求がトリガされる場合があります。
あらかじめ割り込みを無効にしてから極性を変更し、割り込みフラグをクリアし、最後に割り込みを再度有効にする事を推奨します。


補足4:IOの設定変更について
PIC32MX270F256は、各入出力ビットをオープンドレイン、プルアップ、プルダウンのハードはソフトでの設定が可能で、その例を示します。
以下は、PORTBの各設定例のコードです。
	ODCBSET = 0x0020; // スイッチング対象のPORT B5出力端子をオープンドレインに設定
	CNPUBCLR = 0x0020; // スイッチング対象のPORT B5出力端子をpull-up不使用に設定
	CNPDBCLR = 0x0020; // スイッチング対象のPORT B5出力端子をpull-down不使用に設定

	ODCBCLR = 0x0020; // スイッチング対象のPORT B5出力端子をオープンドレイン不使用に設定
	CNPDBSET = 0x0020; // スイッチング対象のPORT B5出力端子をpull-downに設定
	CNPUBSET = 0x0020; // スイッチング対象のPORT B5出力端子をpull-upに設定
(参考: https://ww1.microchip.com/downloads/jp/DeviceDoc/61120E_JP.pdf#page=3
https://ww1.microchip.com/downloads/en/DeviceDoc/PIC32MX1XX2XX-28-36-44-PIN-DS60001168K.pdf#page=136

オープンドレイン、プルアップ、プルダウンの変更は容易に可能で、以下の手続きは不要です。
ですが RPnR および [pin name]R レジスタへの書き込みに関して、 予期せぬ割り当て変更を防ぐために 以下の 2 つの機能を備えています。(61120E_JP.pdf#page=11)
  1. 制御レジスタのロックシーケンス (CFGCONbits.IOLOCKが0で変更を可能)
    未確認ですが、IOLOCK ビットをセットまたはクリアするには、ロック解除シーケンスを実行しなければならない?
    	SYSKEY = 0xAA996655; // キー1を書き込む
    	SYSKEY = 0x556699AA; // キー2を書き込む
    
  2. コンフィグレーション ビット選択のロック機能で、複数回の変更が禁止できる。
    DEVCFG3bits.IOL1WAYが1の時、CFGCONbits.IOLOCKが1にした以降で、制御レジスタへの書き込みを不可にする機能です。
    	CFGCONbits.IOLOCK=0; // ロック解除
    	DEVCFG3bits.IOL1WAY = 1;// これ以降で、FGCONbits.IOLOCK=1にすると、変更できなくなる。
    	//  RPnR および [pin name]R レジスタへの書き込み
    	FGCONbits.IOLOCK=1;