UMEHOSHI ITA TOP PAGE COMPUTER SHIEN LAB
[UMEHOSHI ITA]の制御で使っているIC「PIC32MX270F256B-I/SO」のフラッシュメモリには、テスト用プログラムが書き込まれいています。
以降では、このプログラムを「テスト・ウメ・フラッシュ」と呼ぶことにして解説します。
また、「テスト・ウメ・フラッシュ」を利用したユーザー用のプログラムを
「ウメ・エディットプログラム」と呼ぶことにします。
「ウメ・エディットプログラム」の開発では「umehoshiEditツール」が必要で、
その取得や最初の操作情報は、こちらを参照してください。
(「PICKit3などの書き込みツール」をお持ちの方で、「テスト・ウメ・フラッシュ」を利用しないで、
「MPLAB X IDE」の開発環境ですべてをプログラミングする場合の情報ではありません。)
「テスト・ウメ・フラッシュ」をどのように利用して、「ウメ・エディットプログラム」を
作るかの解説で、「umehoshiEditツール」の使用例を示しています。
各サンプルは、このWebページ上でドラック&コピーして、貼り付けしてご利用ください。
各種確認プログラム | 左記に必要な部品の追加例 |
---|---|
とくに必要ありません。 (D1のLEDは、あるとよい) | |
PWM対応の部品追加例 | |
ADC 対応の部品追加例 | |
BEEP SWITCH 対応の部品追加例 | |
Reset SW, Type-A, CN2 部品追加例 | |
U20,D4,D5,NPN, D3 部品追加例 | |
CN11,CN-12 部品追加例 | |
U19 部品追加例 | |
U17にRN4020の部品を追加する例 |
[UMEHOSHI ITA]単体で動作させる時に必要な部品の追加です。
これは、こちらのリンク先に、このページに至る前の実験情報があります。
[UMEHOSHI ITA]のPORTB5の端子をON・OFFすることで、音を鳴らすことができます。
(PORTB5の端子の端子には、別途にLED D2のON・OFFも兼用しています。)
下記ソースではTimer2 と連係させたOC2(Output Compare modules:出力コンペアモジュール)の割り込み利用して、change()でPWDの幅を変更しています。
(OC2により、PR2の値とTimer2のカウントが一致するまでPORTB5の出力を1にするPWDのハードを利用しています。)
なお「テスト・ウメ・フラッシュ」を利用したユーザー用のプログラム(ウメ・エディットプログラム)では、
_MEMO3(0x80009000番地)を先頭アドレスとする0x0F00byte(3840byte)記憶域があり、これをポインタ変数で管理します。
下記C言語ソースは、この記憶域を使って実験しています。(この記憶域はRAMなので、ROMのように消去の手続きが不要で実験向きです)
_MEMO3番地記憶の容量が少ないので、音の品質が多少悪くてもデータ量を抑える目標とし、4ビット(16段階)のデータでパルス変調としています。
_MEMO3の格納は、2つのデータを下位4ビットと上位4ビットの1バイト埋め込んで管理しています。
#include <xc.h> // pb5_pwd_c #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;// 上記のPWDのデータテーブルのアクセス用添え字 char *pwd_datas=(char *)(0x80009000); // PWDのデータ添え字群の記憶領域の先頭アドレス int pwd_size=0; // 上記ポインタから始まったデータのサイズ int pwd_idx=0; // 上記アクセス用添え字 int pwd_count=0; // pwd_datasの各要素の上位、下位それぞれのデータを(NREP=6)回使い、(NREP+NREP)までのカウンタ void change() // OC2割り込み関数 (PWMのデューティ サイクルの変更 { change_count++;//割り込み回数計測用 (約44100Hzの周期でカウント) if(pwd_count == 0){ // 下位4ビットの周期へ idx_base = pwd_datas[pwd_idx] & 0x0f; OC2RS = pwd_base[idx_base]; }else if(pwd_count == NREP){ // 上位4ビットの周期へ idx_base = (pwd_datas[pwd_idx] >> 4) & 0x0f; OC2RS = pwd_base[idx_base]; } if(++pwd_count == (NREP+NREP)){ //上位4ビットが終わって次の要素周期へ pwd_count=0; pwd_idx++; if( pwd_idx >= pwd_size ) {//周期要素を配列を使い終わった pwd_idx = 0; OC2RS=0; OC2CONbits.ON =0; // OC2の動作OFF } } IFS0CLR = _IFS0_OC2IF_MASK; // Clear the OC2 interrupt flag } void pb5_pwd(char *pwd_adr, int size) { idx_base=0; // PWDのデータテーブルのアクセス用添え字 pwd_datas = pwd_adr; // PWDのデータ記憶領域の先頭アドレス pwd_size = size; // PWDのデータサイズ pwd_count = 0; // PWDのデータアクセス用添え字 // 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の動作スタート } __attribute__((address( 0x80005000 ))) void main(void); void main() { PORTBINV = 0x8000; // LED1を反転 change_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) pb5_pwd((char *) 0x80009000, 3451);// 指定番地(_MEMO3)より指定サイズデータを鳴らす while(count_now < count_end && OC2CONbits.ON == 1){ // 10秒間、または再生中の実行 count_now = _CP0_GET_COUNT();//50ナノでカウントするコアタイマーの値を取得 } PORTBINV = 0x8000;// LED1を反転 _send_decimal(change_count,10); _send_string(" <==change_count\r\n"); }
# 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 from circuit import integral_circuit, differentiator,getTestSingnal WAV_FILE_PATH="え_何.wav"# 変換対象のこの音声が記憶されるサウンドファイル starting_address=0x80009000 # RAM領域データ設定開始アドレス #領域EEPPOMアドレスであれば(0x9D020000) setting_limit_address=0x80009000+0x0F00 # 設定限界アドレス EEPPOM領域であれば(0x9D03E400) path=r"C:\Microchip\umehoshiEdit\samples\_20240305\PB5_PWD\data.bin.hex" print(f"starting_address:{setting_limit_address:016X}で「UME専用Hexファイル:{path}」を出力") 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へ time_array=[n * (1/framerate) for n in range(len(uint16_np))] # 過度特性の時間プロットデータ #uint16_np=np.array([getTestSingnal(t*5e-6) for t in range(int(20e-3/5e-6))], np.uint16) # uint16_nにテストデータを使う #time_array=[t*5e-6 for t in range(int(20e-3/5e-6))] # 上記テストデータの時間プロットデータ plt.subplot(5, 1, 1) plt.plot(time_array,uint16_np) # オリジナルのサウンド(uint16)の視覚化 def getSingnal(elapsed_time: float): # 外部リスト変数のuint16_nを参照し、経過時間から信号源のデータを得る period = time_array[1] idx=int(elapsed_time/period) if idx >= len(uint16_np) : idx = len(uint16_np)-1 # 最後のデータ val = uint16_np[idx] return val # 積分回路を通す uint16_np=uint16_np.astype(np.float) # getSingnal入力源を変更 R1=1.2e3 C1=1e-6 v0=getSingnal(0) q1=v0*C1 Vc1_list = integral_circuit(getSingnal, time_array, R1 , C1, q1 , dt=0.5e-6) # 積分回路の出力リスト plt.subplot(5, 1, 2) plt.plot(time_array, Vc1_list) # シミュレーション結果のアナログプロット # 微分回路を通す uint16_np=Vc1_list # getSingnal入力源を変更 R1=2.2e3 C1=1e-6 v0=getSingnal(0) q1=v0*C1 Vc1_list = differentiator(getSingnal, time_array, R1 , C1, q1 , dt=0.5e-6) # 微分回路の出力リスト plt.subplot(5, 1, 3) plt.plot(time_array, Vc1_list) # シミュレーション結果のアナログプロット # Vc1_listをuint16 に変換してuint16_npにセット uint16_np=np.array(Vc1_list)-min(Vc1_list) # 負のデータを無しに調整 uint16_np*=65535/max(uint16_np) # 0から65535に調整 uint16_np=uint16_np.astype(np.uint16) # 符号無し整数に変換 plt.subplot(5, 1, 4) plt.plot(time_array, uint16_np) #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バイナリーのuint16_np)を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.subplot(5, 1, 5) 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") 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のデータ")上記の の部分で、integral_circuitモジュールの積分と微分を使っています。
from typing import Callable # 積分回路のシミュレーション関数 def integral_circuit(getV1:Callable, time_array:list, R1:float , C1:float, q1=0 , dt=0.5e-6)->list: '第1引数:getV1は経過時間を引数にしてその時の入力電圧を返す関数を登録するための引数' '第2引数:time_arrayのリスト要素のタイミングで戻り値ので出力電圧のリストが作る' '第3,4引数:R1は積分回路の抵抗値(単位はオーム)と、C1はコンデンサ(単位はファラット)' '第5,6引数:q1はCに蓄えられた初期電荷(単位はクローン)と、dtはシミュレーションで使う微小時間(単位は秒)' Vc1_list=[q1/C1] time_idx=1 time_next=time_array[time_idx] time_now=0 while True: v1=getV1(time_now) i=(v1-q1/C1)/R1 q1+=i*dt time_now+=dt #print('-', end="") if time_now > time_next: #print(f"{time_idx:5}:{time_now:10.7f}, {q1/C1}v") Vc1_list.append(q1/C1) time_idx+=1 if time_idx >= len(time_array): break time_next=time_array[time_idx] return Vc1_list # 微分回路のシミュレーション関数 def differentiator(getV1:Callable, time_array:list, R1:float , C1:float, q1=0 , dt=0.5e-6)->list: '第1引数:getV1は経過時間を引数にしてその時の入力電圧を返す関数を登録するための引数' '第2引数:time_arrayのリスト要素のタイミングで戻り値ので出力電圧のリストが作る' '第3,4引数:R1は積分回路の抵抗値(単位はオーム)と、C1はコンデンサ(単位はファラット)' '第5,6引数:q1はCに蓄えられた初期電荷(単位はクローン)と、dtはシミュレーションで使う微小時間(単位は秒)' time_now=0 v1=getV1(time_now) Vc1_list=[v1-q1/C1] time_idx=1 time_next=time_array[time_idx] while True: v1=getV1(time_now) i=(v1-q1/C1)/R1 q1+=i*dt # print('-', end="") if time_now > time_next: # print(f"{time_idx:5}:{time_now:10.7f}, {q1/C1}v") Vc1_list.append(v1-q1/C1) time_idx+=1 if time_idx >= len(time_array): break time_next=time_array[time_idx] time_now+=dt return Vc1_list # 信号の変化を示すテストデータと関連関数------------------------------ array=[(0,0), (5e-3,0), (10e-6,3.3), (480e-6,3.3), (10e-6,0), (480e-6,0), (10e-6,3.3), (480e-6,3.3), (10e-6,0), (480e-6,0), (10e-6,3.3), (480e-6,3.3), (10e-6,0), (480e-6,0), (10e-6,3.3), (480e-6,3.3), (10e-6,0), (480e-6,0), (10e-6,3.3), (480e-6,3.3), (10e-6,0), (480e-6,0), (10e-6,3.3), (480e-6,3.3), (10e-6,0), (480e-6,0), (10e-6,3.3), (480e-6,3.3), (10e-6,0), (480e-6,0), (10e-6,3.3), (480e-6,3.3), (10e-6,0), (480e-6,0), (10e-6,3.3), (480e-6,3.3), (10e-6,0), (480e-6,0), (10e-6,3.3), (480e-6,3.3), (10e-6,0), (480e-6,0), (550e-6,0), ] # 各要素のtapleで[0]のデータが前の要素からの経過時間、[1] がその経過時の電圧値 def getTestSingnal(elapsed_time: float): # 外部リスト変数のarrayを参照し、経過時間から信号源のデータを得る prev_cumul_time=array[0][0] # 繰り返しで、前の経過時間 prev_val=array[0][1] i=1 len_array = len(array) while i < len_array : cumul_time = prev_cumul_time + array[i][0] # 経過時間 #print( i, cumul_time ,elapsed_time) if cumul_time >= elapsed_time : gradient=(array[i][1]-prev_val)/(array[i][0]) val=gradient*(elapsed_time-prev_cumul_time) + prev_val return val prev_cumul_time = cumul_time prev_val=array[i][1] i+=1 # return array[i-1][1] if __name__ == '__main__': import matplotlib.pyplot as plt time_array=[v * 5e-6 for v in range(int(20e-3/5e-6))] # 過度特性の時間プロットデータ val_array=[getTestSingnal(x) for x in time_array] # 回路の入力電圧のプロットデータ plt.plot(time_array, val_array) # 入力源のデータをセット Vc1_list = integral_circuit(getTestSingnal, time_array, R1=2.2e3 , C1=1e-6, q1=0 , dt=0.5e-6) # 積分回路の出力リスト plt.plot(time_array, Vc1_list) # 積分回路の出力のプロット Vc1_list = differentiator(getTestSingnal, time_array, R1=2.2e3 , C1=1e-6, q1=0 , dt=0.5e-6) # 積分回路の出力リスト plt.plot(time_array, Vc1_list) # 積分回路の出力のプロット plt.show()前述のwav_to_UME.pyは、上記モジュールの積分と微分の関数を利用いています。
C:\Users\suzuki\Music>python wav_to_UME.py starting_address:0000000080009F00で「UME専用Hexファイル:C:\Microchip\umehoshiEdit\samples\_20240305\PB5_PWD\data.bin.hex」を出力 え_何.wavサウンドファイルから、再生用のバイト列を取得 フレームレート:48000Hz、サンプリング周期: 20.83u秒 チャンネル数: 2 サンプル幅数: 2 フレームの数:41414, 時間:0.8627916666666666秒 オリジナルのサウンドの0.8627916666666666 秒を再生する。 サウンドデータ(16bitPCMバイナリー)からpulse_modulationで使う値(符号なし16bitのnumpy配列)に変換 最小値:0, 値の幅:65534 データ数:41414 サウンドデータ(16bitPCMバイナリーのuint16_np)を4bitデータに変換し、6個のサンプル数を1つまとめる 最小値:0, 最大値:15 データ数:41414から6902個になった。 4ビット要素の2つを、1バイトにまとめて、リストにする。 生成したデータ:3451byte C言語用の配列初期化データを、テキストファイルとして出力(最大で2950バイトの出力制限) last_address:0000000080009D7B, 3451byteのデータ C:\Users\suzuki\Music>上記の実行で、"え_何.wav"のサウンドファイルのオリジナル音と、変換後の音がPC上で確認できます。
S108000900000777777777777777777777777777777777B S1080009010007777777777888888888888888888888864 ・・・・・これを[UMEHOSHI ITA]にUSB経由で転送すれば、上記で作られたサウンドのPWDのデータで 0x80009000のアドレスに、 3451バイトが設定されます。
以下で、オリジナルの音、目標、実験で鳴った音(録音)、などの実験結果を示します。
オリジナルファイル("え_何.wav")44100Hz,16BitPCM |
オリジナルファイル16bitを、積分、微分、増幅、後に分解能4ビットに変換してPCで鳴らした音 なお、上記の積分回路や微分回路の時定数が最も適正かどうかの実験もしていません。 オリジナルのデータ6個から1個を抽出した目標となる音 目標となる音(音質を落としたデータでPC上のPythonで鳴らした音) |
上記と同じで、オリジナルファイル16bitを、積分、微分、増幅、後に分解能4ビットに変換ししたデータを使って圧電サンダーで鳴らした音 残念ながら大きな効果が得られませんでした。 数値的に良くなった検証をしていませんが、少し雑音が減ってインパクトが付いた感じで、チョット聴きやすくなったように感じます。 音量は大きく感じられませんでした。 |
上記と同じで、オリジナルファイル16bitを、積分、微分、増幅、後に分解能4ビットに変換ししたデータを使ってスピーカーで鳴らした音 手持ちのスピーカーで、制限抵抗を2.2Kから47Ωに変更して実験した結果で、こちらの方が聞き取れました。 |
上記で作成したpb5_pwd関数を、「テスト・ウメ・フラッシュ」のAPIのようにROMに書き込んで、すぐ呼び出せるように使う例を、 このページで示します。