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 ,Bluetoothなどの基板 の部品追加例

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

U19 部品追加例

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

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

ADC(Analog-to-digital converter)の利用

部品の取り付けやハード情報→こちらのページで紹介

上記左側にコンデンサマイクを2個(CN8とCN9の端子)取り付けた場合の例です。
その情報は2段のアンプを経て、AN0、AN1のADコンバータ入力端子に繋がれており これが入力対象です。


ADCは、Timer3の割込み間隔で動作しています。初期段階でTimer3はOFFになっているので、 これをONにするとADCも連動するようになっています。
ADCの分解能は、10bitですが16bitの符号なし整数(0〜1024の値)の情報で、1024個のデータが蓄えた後に それをUSBで連続送出仕組みが作られています。
内部ソースで、次の宣言があります。(common.h) (実際のソースコードは、 こちらを参照してください。)
#define ADC_BUFF_SIZE 1024 // チャンクサイズ(Chunk Size)
uint16_t adc_buffer0[2][ADC_BUFF_SIZE]; //AN0(CN8)
uint16_t adc_buffer1[2][ADC_BUFF_SIZE]; //AN1(CN9)
このように、CN8,CN9コネクタに接続される2つののADC入力に対するバッファを用意して使っています。
[2]の配列は一方がADCの割込みで記憶に使っている時、もう一方はTimer3でADC記憶データをUSBへ 送出するようになっており、この添え字切り替えてで、記憶対象と出力対象が切り替わるように作られています。

「テスト・ウメ・フラッシュ」で用意されるADCに関する変数とAPI関数を以下に示します。

マクロの表現概要
_set_adc_mode(c,t) ADCの対象とUSBで出力する際のモードを指定する関数で、ADCスタート前で使う。
c:ADCサンプリング対象のパラメタ
  1: CN8 、2: CN9 、3: CN8とCN9 (3がデフォオルト)
t: USBのデータ出力モードフラグ 1: TEXT MODE 、 0 : BINARY MODE
_set_adc_exe(n,f) ADCのスタートと終了を制御する。
n:1回分ADCサンプリングブロック数パラメタ
  1から63までのブロック数(1ブロックが1024ワード)
f: ループフラグで1で上記ブロックのサンプリングを繰り返す。
  0で上記ブロックサンプリング後に終了する。
  (既に動作中に0の指定で呼び出すと、nは無視されて現ブロック送信で終了)   (既に動作中に1の指定で呼び出すと、nは無視されて現ブロック送信後に、次のブロック数をnで指定可能)
以下で、これを使う例を示します。
#include <xc.h>//ADC.c
#include "common.h"

#define AdrStart	0x80005000
__attribute__((address( AdrStart  ))) void start (void);
void start()
{
	_RB15 = ! _RB15;// 動作確認用のD1 LEDの点灯を反転
	PR3=1249;	//サンプリング周波数を 16KHzに指定するパラメタ
	// (1/16000)/(1/40e6)/2-1=2500/2-1=1249
	_set_adc_mode(3,1);// CN8のAN0とCN9のAN1アナログサンプリングで出力テキストモード 
	_set_adc_exe(2 , 0);// 2block, loopしない1回だけのサンプリング実行スタート
}

上記のサンプリング周期設定のPR3の算出方法を示します。
サンプリングレートを f Hz とした場合、サンプリング周期は=1/f秒で求められます。
PR3は、「内部の周辺モジュール用クロック(周期:1/40e6)のカウント数」-1をを設定するものです。
また一つの逐次ADCをAN0、AN1を交互にサンプリングするプログラムが埋め込まれているため、 この2回分のサンプリングの周期を指定するためには、PR3を2で割った設定にする必要があります。
以上から、サンプリングレートを f Hzにしたい場合、
一つのチャンネルのサンプリング周期に使うPR3設定÷2=((1/f)/(1/40e6)-1)÷2の計算で求めることになります。

よってサンプリングレートを16KHzにする場合のPR3設定値は、
PR3=((1/16e3)/(1/40e6)-1)/2=(2500-1)/2=1249.5 となります。
上記の場合は、切り捨てた1249が設定値になります。この場合の実際のサンプリング周波数は、次のように求められます
PR3が1249の場合は、サンプリリング周期が(1249+1)*(1/40e6)*2=6.25e-05秒です。
周波数は、1/((1249+1)*(1/40e6)*2)=16000Hzとなります。

以下は、上記コードをビルドして実行した時の画面例です。
(1ブロックの1028ワードはCHUNKサイズと同じです)
2ブロックで、AN0とAN1の2つの入力を行うので、1028 × 2ブロック × 2入力 = 4096ワードのデータが並びます。
これを16進にすると、1000の値になりますが、これが上記Tの直後に表示している値の意味です。

実行の前に、「Tools」メニューの「ADC Plot」を選択しておくとよいでしょう。 グラフのイメージはデータの受信ごとに更新されます。
これにより下記のようなグラフイメージのフォームでグラフ表示ができます。
1ブロックを1028ワードとい管理なので、下記の横幅は2ブロックの2048個のデータがあることになります。

上記において、上の赤がCN8コネクタのAN0端子のサンプリング画像で、上の赤がCN9のAN1のサンプリング画像です。
同じ音源で試した例ですが、このようなバラツキがあります。(各素子のバラツキによると考えています。)
どちら側の音が大きいかを比較したい場合は、各基板に合わせてソフト的に調整するか、ハード的に調整する必要があります。
ハード的に調整する場合は、VR1とVR3に可変抵抗器などを取り付けることで、それぞれの増幅率を調整できます。
上記プログラムの実行で、「ADC_START」の直後に「T1000」の表示がありますが、 この先頭の「T」文字がテキストモードを意味し、続く「1000」の数字は16進で表現されたデータ数を意味します。
つまりこの直後に16ビットワードの情報が0x1000(つまり10進なら4096)個のデータが並んでいることを意味します。
「_set_adc_exe(2 , 0);」で、2ブロックを要求していますが、1ブロックが1048個のデータを意味します。
また「_set_adc_mode(3,1)」で、AN0とAN1の2入力のサンプリッグを指定しているので、交互に2つのデータが並びます
よって、[1ブロックの1024]×[2ブロック]×[2入力]=1024*2*2=4096のデータが並んでいます。
なお、1つのデータは16byteなので、4文字を意味します。下記の先頭は[01E5]を意味します。
なお[01][E5]の並びで、[01]が上位byte,[E5]が下位byteを意味します。
16個のデータ出力ごとに改行(\r\n)が追加されます。

R00800050000061
START:80005000

ADC_START
T1000
01E50203022F020501FE0201021402070253021001E901E501DD01D901F701CA
020601ED01F301E50253020101B101E701FB020A022A0204021801FE02240202
・・・・・省略・・・・・
0208020A020F01F2021D01DA01F801E7022A0201020E0201021201D901B901D5
01C101EB019F01EE017201F0020F021002470203022201F201E5020A02040209
ADC_END

上記のデータは、AN0とAN1の2つをサンプリングしており、[AN0]のAD変換値、[AN1]のAD変換値、・・と交互に並ぶように送信されます。
16進4桁のデータが所定の個数(上記例では4096個)に達すると「ADC_END」の文字列の出力で終わります。


上記のサンプリング周波数は、「PR3=1249;」の指定で 16KHzになっています。
これを指定しない場合は、デフォルトの8KHzになります。

さて、バイナリモード時はテキストモード時と逆で、リトルエンディアンになって改行は追加されません。
またバイナリー指定時は、「umehoshiEditツール」の「Communication」タグにデータの表示を行いません。
以下に例を示します。
この例では、CN9コネクタ入力(AN1)のAD変換した結果だけUSB出力します。 また 1ブロックのサンプリングで、周波数は 400KHzにした例です。

#include <xc.h>//ADC2.c
#include "common.h"

#define AdrStart	0x80005000
__attribute__((address( AdrStart  ))) void start (void);
void start()
{
	_RB15 = ! _RB15;// 動作確認用のD1 LEDの点灯を反転
	PR3=99; // 400KHz のサンプリング (1/400e3)/(1/40e6)-1 = 99 
	_set_adc_mode(2,0);// 2でCN9のAN1端子だけ出力、0でバイナリモード 
	_set_adc_exe(1 , 0);// 1block(1024ワード)のサンプリングを終えたら終了
}
左下が、これを実行した時の「umehoshiEditツール」の「Communication」タグにに表示される内容です。
 1blockの指定なので、下記グラフの横幅に1024個(0x0400個)のデータがあります。
ADC_START1
0400
 ADC_END

ADC_START0がCN8だけの上表示、
ADC_START1CN9だけの下表示
をします。

バイナリモードでは"ADC_START1\r\n"の直後に'T'がありません。単にデータ数の16進文字列と改行のみです。(これでバイナリかテキストモードか区別できます。)
上記は、個数が0400でこれは1ブロックの1024個を意味します。
このようにバイナリ時はデータの16進表示を行いません。
ブロック数も1でサンプリングがAN1の1チャンネルだけなので、1つのデータが並びます。
Timer3のPR3設定値でサンプリング周期が決まってレジスタに変換値が記憶されます。 そしてADCの割り込みでレジスタからメモリに記憶し、Timer3の割り込みでメモリからUSB出力を行っています。
この周期が短くなると、AD変換でメモリに記憶した速度でそれをUSBに出力することが不可能になります。
ですが、入出力をバッファリング(2ブロック分の1024×2ワード)で制御しているので、 USBの送信スピードを超えるAD変換速度にある程度対応でます。 ですから、_set_adc_exeの第2引数が1または2であればUSB送信速度よりはるかに速い場合でも、 サンプリングできるでしょう。
しかし、_set_adc_exeの第2引数が2を超えて指定して、PR3設定値でサンプリング周期を小さくすると、 USBの出力が間に合わなくなります。その例を以下に示します。
CN8のAN0端子だけを32KHzでサンプリングさせる例です。なおブロック数を5(最大設定は63)にしてループにしています。

#include <xc.h>//ADC3.c
#include "common.h"

#define AdrStart	0x80005000
__attribute__((address( AdrStart  ))) void start (void);
void start()
{
	_RB15 = ! _RB15;// 動作確認用のD1 LEDの点灯を反転
	PR3=624;	//サンプリング周波数を 32KHzに指定するパラメタ
	//(1/32000)/(1/40e6)/2-1=624
	_set_adc_mode(1,0);// CN8のAN0で出力バイナリモード 
	_set_adc_exe(5 , 1);// 5blockのUSB出力を, ループする指定でサンプリング実行スタート
}
「Tools」メニューの「ADC Plot」を選択した時のイメージも示します。
5ブロックなので横幅は1024×5=5120(=0x1400)個の データが並んでいることになります。
必ずしもこうなるとは限りません。接続しているPCの能力によるからです。
R00800050000061
START:80005000

ADC_START0
1400
ADC_END

これは、ADC取得データの送信が間に合わなないエラーが起きている状態です。
このような場合、デフォルトで「・・ーー ・・・・ ・・・・ ・・・・ ・・・ー 」の 通知音がでます。
(上記プログラムで、必ず起きる訳ではありません。PC側の受信能力によって起きない場合もあります。)
上記グラフで青の水平線が、AN0の場合のゼロの値です。ですから上記では4つの負のデータがあることになります。
ADCのデータは符号なし16進です。(10bitの分解能なので0〜1024の範囲です)
それなのに負のデータがあるのどうしてでしょうか?実はこれがサンプリングできなかった数を負として出力した箇所のマークなのです。
ADCの割込みでサンプリングしてバッファへの記憶、それに平行してTimer3の割込みによりバッファからUSB出力が行われます。
(2つのブロックがあり、一方に記憶中である時、もう一方の内容を出力する関係になっています。)
_set_adc_exeの第1引数が2以下でサンプリングを止める使い方であればこのエラーは起きません。 しかしより大きなブロックを使う場合や、 サンプリングのループモードで使う場合、USBの送信が追い付かないと、サンプリングが待たされることになります。
そのタイミングでは本来の周期のサンプリングが記憶できないので、その数をカウントしています。(その時、エラー情報のビットが記憶されます。)
その後、USBの送信でバッファに空きが出来て再び記憶できるようになった時、サンプリングできなかった数を負の値で記憶しています。
つまり、上記の負の所はその数だけサンプリングを記憶しなかったデータが隠れている所と判断して使う必要があります。
このようなエラーの大きな要因はUSB送信が追い付かないためなので、USB送信量を減らすことでエラーを回避できます。
例えば、AN0やAN1の両方でなくどちらか一方だけの出力指定をする場合は、両方のUSB送信よりデータ量が少なくなります。
また、テキストモードよりバイナリーモードにした方が、USB送信からのデータ量が少なくなります。
(なおPR3の設定値が99以下の場合は、速すぎる割り込み周期なのでUSBの出力速度が上がらない制御をしており、PR3のエラー限界値が示せません。)

さてこのエラーは、どのようなサンプリングで起きる可能性があるか? USBの転送速度から検証します。
このUSBはCDCプロトコで115200bps Parity無し 8bit 1StopBits の非同期通信です。
つまり、1byte転送で実質10bit以上使うことになります。
これは、論理的に115200bps÷10=11520byte/秒を超える速度の送信ができないということです。
ADCは2byteなので、1秒間転送可能数は11520byte÷2=5760個ということになります。
つまり場合、連続サンプリングレートは最大5.76Ksps(samples/second)程度と予想されます。
(Textモードで転送すると、1/2の速度で、2.88Kspsになります。)
 対してステレオ(音楽向け)のサンプリングレートは48kspsです。品質を落としたモノラルでも8Kspsの速度が必要です。
よって 音楽録音の、連続的サンプリングとUSB送信を永続的に行えません。
これを実現する可能性として、サンプリングデータを圧縮してしてからUSB送信するなどの手法があります。
_UME_ENCODE_ADC_BUFFのマクロ変数には何もしない関数が登録されていますが、 ここに、送信データの圧縮処理する関数を登録することで、対応できる可能性があります。

また現在使っているUSBの「CDC (Universal Serial Bus Communications Device Class)」ではなく、 「Audio Class」用に「テスト・ウメ・フラッシュ」全体のプログラムを作り直せば可能となるのでしょう。

また、ADCやTimer3の割り込み処理だけをそっくりと入れ替えて対応する方法もあります。以下でその解決する考え方を説明をします。



「テスト・ウメ・フラッシュ」ADCプログラムの仕組み

ADCは次の構成になっています。
「アナログ入力マルチプレクサ(MUX A またはMUX B) 」を介して 「サンプル/ ホールドアンプ(SHA)」に繋がっており、それが 「SAR(successive approximation register:逐次比較レジスタ型) ADC」 に入力されています。

[SHA] → [SAR ADC] という流れです。
まず、アナログ入力ピンを[SHA]に接続して、十分な時間をかけてサンプル電圧を安定させます。
(これは「アクイジション時間」(AcquisitionTime)と呼ばれます)
この制御は、プログラムで、AD1CON1bits.SAMPのセットとクリアで、それぞれでアクイジション時間の開始と終了を制御します。
これをセットすると、アナログ入力が[SHA]に接続されて、アクイジション時間以上経過後にクリアすると、 ホールド状態(アナログ入力の接続が切り離されて)になり、逐次AD変換が始まります。
(つまりAD1CON1bits.SAMP ビットをクリアする事によって逐次AD変換の開始を制御できます。)
逐次AD変換が始まって、変換したADCデータをレジスタ((ADC1BUF0〜ADC1BUFF))に格納するまでが 「変換時間」(ConversionTime)と呼ばれます。
最終的な変換の終了は、[AD1CON1bits.DONE]の監視で、1になれば終了です。
この終了までの「変換時間」は、[AD1CON1bits.SSRC]のADクロック設定にって決められます。
なお、一つのADCは、「アクイジション時間」「変換時間」の時間を必要とします。


さて、[AD1CON1bits.SAMP=1]により「アクイジション開始」後、自動的に[AD1CON1bits.SAMP]をクリアして逐次AD変換を始めることが でき、その場合は「AD1CON3bits.SAMC」の5bitを設定します。
また、割込みを使う場合は、[ AD1CON2bits.SMPI]の3bitで指定回数のサンプリングシーケンスを実行した後に割り込みを生成できます。
その場合は、[AD1CON1bits.ASAM = 1]のADC サンプリング自動開始ビットも指定が必要です。(AD1CON1bits.SSRC=0b000時で検証)
ASAMを1にすると逐次AD変換が終わった後に自動的にSAMP ビットをセットします。
それにより、例えばタイマー割り込みなどで、[AD1CON1bits.SAMP=1]にすれば、 アクイジションが開始されて終わると自動的に[AD1CON1bits.SAMP]をクリアして逐次AD変換が行われます。 この変換終了時にADC割込みがかかり、同時にSAMP ビットをセットセットされて、次のアクイジション開始されて繰り返されます。
なお、この割込みでASAMを0に自動設定して1回だけで終わりにする指定として「AD1CON1bits.CLRASAM=1」の指定が可能です。
次のコードで確認しました。(一部だけ紹介)
	AD1CON1bits.SSRC=0b000;//変換トリガ源選択ビット(b7-b5)
	AD1CON1bits.SAMP=1;//この2行で_ADC_VECTOR割込み処理を指定
	// ・・・・
	AD1CON1bits.ASAM=1;
	AD1CON1bits.CLRASAM=1;
	T4CONbits.ON = 1;// timer4割込みオン
//-----------------------------------------------------
void __ISR(_TIMER_3_VECTOR,IPL7SOFT)Timer3Handler(void)// Timer3の割り込み
{
	AD1CON1bits.SAMP=0;// サンプリング終了/ ホールドして逐次変換開始をトリガする  
	IFS0CLR = _IFS0_T3IF_MASK; // Clear the timer interrupt status flag   

	// ・・・・
}
//-----------------------------------------------------
void __ISR(_ADC_VECTOR,IPL76OFT)ADC12Handler(void)// ADCの割り込み
{
	// 逐次AD変換が終わって、レジスタに格納して呼び出される。
	// AD1CON1bits.ASAM=1により自動的に「AD1CON1bits.SAMP=1」が働き、サンプル状態になる。
	// 次のtimer4で、AD1CON1bits.SAMP=0が実行されてサンプリング終了を待つ。
}


なお、AD1CON1bits.SSRC=0b010に設定すると、Timer3 の周期一致時にサンプリング終了/ 変換開始をトリガします。
これは特別な割込みで、AD1CON1bits.CLRASAM=1やAD1CON1bits.ASAM=0を行ってもADC割り込みを停止できませんでした。
PIC32 デバイスには 2 種類の割り込み ( 永続的と非永続的 ) が存在します。永続的割り込みは、 割り込みの原因となっている問題がサービスされるまでアクティブであり続け、対応する割り 込みフラグはセットされたままです。 ISR は割り込みを引き起こした条件を取り除いた後に割り込みフラグをクリアする必要があります。
それが、IFS0CLR = _IFS0_T3IF_MASK;のような表現です。
(なお非永続的割り込みの場合、割り込みは割り込みコントローラに一度記録され、割り込みコント ローラはそれを CPU に提示します。CPU は、新しい割り込みが発生した時に割り込まれるだけです。)
さて 実際の内部(テスト・ウメ・フラッシュ)では、上記割り込み処理が次のようになっています。
void __ISR(_TIMER_3_VECTOR,IPL6SOFT)Timer3Handler(void)
{
   ((void (*)(struct ADC_BUFF*))handlers[_IDX_TIMER_3_FUNC])(p_buff);
	// この要素に記憶されるadc_usb_out関数で、ADC結果のバッファ用メモリ情報をUSBに出力している
}

void __ISR(_ADC_VECTOR,IPL7SOFT)ADC1Handler(void)
{
    ((void (*)(struct ADC_BUFF*))handlers[_IDX_ADC_1_FUNC])( p_buff );
	// この要素に記憶されるadc_umeFunc1_1関数で、ADC結果のレジスタをバッファ用メモリに記憶している
}
つまり、handlers配列に記憶される関数へのポインタが、各割り込み処理が行われています。
この配列要素は、
handlers[_IDX_ADC_1_FUNC] = adc_umeFunc1_1;
handlers[_IDX_TIMER_3_FUNC] = adc_usb_out;
と初期化ルーチンで記憶され、それが割り込みで呼び出されています。
よって、このhandlers配列の要素を変更することで、それぞれの割り込み処理を入れ替えることができます。

また、Timer3の初期化やADCの初期化も同様に入れ替えることができます。以下にTimer3の初期処理を示します。

Timer3の初期化

// Timer3 の初期化(ADCのサンプリング周期を作る)------------------------------------
void init_timer_3() {
    T3CON = 0x00000000; //typeB,16bit, [1:1]プリスケール
    T3CONbits.ON = 0; // typeBでON(b15)
    T3CONbits.SIDL = 0; // デバイスがアイドルモードに移行しても動作を継続する(b13)
    T3CONbits.TGATE = 0; // ゲート時間積算を無効にする(b7)
    T3CONbits.TCKPS = 0; // タイマ入力クロック 0〜3プリスケール値(b6-4)
    T3CONbits.TCS = 0; //内部の周辺モジュール用クロック(b1)
    // 以上より、タイマーのカウントアップ周期は、次のように算出される。
    // 「TCS = 0」で、周辺モジュールバス クロック(PBCLK) は40MHzになっている。
    //  よってカウント周期は 1/40e6=2.5e-08=250u秒
    //__asm__("nop");  
    TMR3 = 0x00000000; //16bitタイマの設定値(レジスタがカウントアップ)
    PR3 = 0x1;          //16bit周期レジスタビット設定(タイマーが働く最小値)  
    PR3 = 0x0000FFFF;   //16bit周期レジスタビット設定(16タイマーの最大値)
    PR3 = 40000-1;        //16bit周期レジスタビット設定( 割り込み周期が1m秒)
    PR3 = 907-1; //44,100Hzが音楽業界の標準で、この周期に近い割り込み周期(約0.23ミリ秒)
               // 1/(907 * (1/40e6))=44101.43329658214Hzの周波数になり、少しずれる。
               // 2つチャンネルなので、上記設定の実質的サンプリング周波数は÷2=約22050.7Hz
    PR3 = 1250/2-1;//32KHzのサンプリング周期(割り込み周期は2チェンネルで16Kの周期)
    PR3 = 1666/2-1;//24KHzのサンプリング周期(割り込み周期は2チェンネルで12Kの周期)
    PR3 = 2500/2-1;//16KHzのサンプリング周期(割り込み周期は2チェンネルで8Kの周期)

    IPC3SET = 6 << _IPC3_T3IP_POSITION; // Set priority level = 6
    IPC3SET = 2 << _IPC3_T3IS_POSITION; // Set sub-priority level = 2
    IFS0CLR = _IFS0_T3IF_MASK; // Clear the timer interrupt status flag
    IEC0SET = _IEC0_T3IE_MASK; // Timer3 Enable(割込み許可)
    //T3CONSET = _T3CON_TON_MASK;   // timer3 Start 
    //T3CON = 0b1000000000000000;   // timer3 機能ON 
}
これは「handlers[_IDX_INIT_TIMER_3] = init_timer_3;」と記憶されて、それが初期化ルーチンで呼び出されています。

以下に上記ADC割り込みの初期化を示します。

ADCの初期化

次のinit_adc()がADC初期化関数で my_adc.c で定義され、my_sys.cで初期化登録に使われていいます。
ADC関連は、特にたくさんのSFR (Special Function Registers)の設定を必要として、以下のように設定しています。
// ADCの初期化デフォルト関数------------------------------------------------------
void init_adc() {
    // ----- ADC 関連の初期設定 ここから[61104F_JP.pdf P17-4]
    TRISASET = 0x00000003; // RA0、RA1のポートを入力用に設定
    AD1CON1 = 0; // スタート、ストップ制御関連制御レジスタ
    AD1CON2 = 0; // 入力スキャン指定、出力バッファ指定などシーケンス指定制御レジスタ
    AD1CON3 = 0; // クロック関連指定制御レジスタ
    AD1CHS = 0; // アナログ入力のマルチプレクサの指定
    //AD1PCFG = 0; 当チップではこのポートコンフィグレーションレジスタは存在しない。
    AD1CSSL = 0; // 入力スキャン選択レジスタ

    // AD1CHS設定-----------------------------------
    AD1CHSbits.CH0NB = 0; // b31 CH0NB:0 MUX B 負極性入力選択ビット=VR- を選択する
    // b30-b28 未実装 :「0」として読み出し
    // b27-b24 CH0SB<3:0>: 0b0001 MUX B 正極性入力選択ビット=AN1 を選択する
    // b23 CH0NA:0  MUX A 負極性入力選択ビット=VR- を選択する
    // b22-b20 未実装 :「0」として読み出し
    // b19-b16 CH0SA<3:0>: 0b0000 MUX A 負極性入力選択ビット=AN0 を選択する
    // b15-b0 未実装 :「0」として読み出し
    // CH0NB XXX CH0SB CH0NA XXX CH0SA         XXXXXXXX XXXXXXXX
    // 0     000 0001  0     000 0000          00000000 00000000
    AD1CHS = 0x01000000; // MUX AにはAN0,MUX BにAN1 の入力を接続

    //AD1CON3設定--ADC 制御レジスタ 3 クロック関連指定制御レジスタ-----------------
    AD1CON3 = 0x0; // b31-b16 未実装 :「0」として読み出し
    AD1CON3bits.ADRC = 0; // b15 ADC変換クロック源の選択ビット 
    // 1: FRCクロック使用、0:周辺モジュール用クロック(PBCLK:40MHz)から生成
    // b14-b13 未実装 :「0」として読み出し
    AD1CON3bits.SAMC = 0b00010; // b12-b8  自動サンプリング時間ビット= 2TAD
    //上記はアナログ入力ピンをサンプル/ホールドアンプ(SHA)に接続するアクイジション時間
    AD1CON3bits.ADCS = 0b00000011; /// b7-b0  ADC 変換クロック選択ビット
    // ADCの変換時間(8bit) =  TPB * 2 * (ADCS<7:0> + 1) = 512 * TPB = TAD
    // XXXXXXXX XXXXXXXX    ADRC XX SAMC  ADCS
    // 00000000 00000000    0    00 00010 00111111
    AD1CON3 = 0x0000023F;

    // AD1CON2 (ADC 制御レジスタ 2)設定 --------------------
    // [61104F_JP.pdf P17-4]「17.4.11.2 2 つの入力マルチプレクサを交互に使う」
    AD1CON2 = 0x0;
    AD1CON2bits.VCFG = 0; //参照電圧にAVDDとAVSSを使う(b15-b13)
    AD1CON2bits.OFFCAL = 0; //オフセット校正モードを無効(b12)
    AD1CON2bits.CSCNA = 0; //入力をスキャンしない(b10)
    AD1CON2bits.BUFS = 0; //バッファ書き込みステータスビット(b7)
    // 1 = ADC はバッファ0x8〜0xF に書き込み中( ユーザは0x0〜0x7 にアクセス可能)
    // 0 = ADC はバッファ0x0〜0x7 に書き込み中( ユーザは0x8〜0xF にアクセス可能)
    AD1CON2bits.SMPI = 0b0001; //割り込みシーケンス選択ビット(b5-b2)
    // 上記は、2 回のサンプリング/ 変換が完了するたびに割り込む
    AD1CON2bits.BUFM = 1; //ADCデータ格納バッファモード選択ビット(b1))
    //上記 1: 2つの8ワードバッファ(ADC1BUF) を分割構成
    AD1CON2bits.ALTS = 1; //MUXAおよびMUXBを交互に使う指定(b0))
    //    VCFG OFFCA X CSCNA XX     BUFS X SMPI BUFM  ALTS
    //    000  0     0 0     00     0    0 0001 1     1
    AD1CON2 = 0x0007;

    // AD1CON1設定 ------------------------------------------
    AD1CON1 = 0; // b31-b16 未実装
    AD1CON1bits.ON = 1; //ADC モジュールを有効にする(b15)
    AD1CON1bits.SIDL = 0; //アイドル中もモジュールの動作を継続する(b13)
    AD1CON1bits.FORM = 0b000; //16 ビット符号なし整数の指定(b10-b8)
    AD1CON1bits.SSRC = 0b010; //変換トリガ源選択ビット(b7-b5)
    // 上記3ビット指定:Timer3 の周期一致時にサンプリング終了/ 変換開始をトリガする
    AD1CON1bits.CLRASAM = 0; //割り込み時変換停止ビット(bit4)
    // (0 = 次の変換シーケンスがバッファの内容を上書き)
    // (1 = 最初のADC 割り込みが発生した時点で変換を停止する)
    // SSRCがTimer3の時、CLRASAMの1を行ってもTimer3によるADC割り込みは停止できない。
    AD1CON1bits.ASAM = 1; //ADC サンプリング自動開始ビット(b2)
    //これをセットしないと、割込みが発生しなかった。
    // 変換完了後即座にサンプリングを開始する(SAMP ビットを自動的にセットする)
    AD1CON1bits.SAMP = 1; //ADC サンプリングイネーブルビット(b1)
    AD1CON1bits.DONE; //変換が完了すると1になる。(b0)  
    //    ON X SIDL XX FORM     SSRC CLRASAM X ASAM SAMP DONE
    //    1  0 0    00 000      010  0       0 1    1    0   
    AD1CON1 = 0x0046;

    IPC5SET = 7 << _IPC5_AD1IP_POSITION; // Set priority level = 7
    IPC5SET = 1 << _IPC5_AD1IS_POSITION; // Set sub-priority level = 1

    IFS0CLR = _IFS0_AD1IF_MASK; // Clear the ADC interrupt status flag
    IEC0SET = _IEC0_AD1IE_MASK; // ADC Enable(割込み許可)

    AD1CON1bits.ADON = 1; // Begin Sampling    
}
これは「handlers[_IDX_INIT_TIMER_3] = init_timer_3;」と記憶されて、それが初期化ルーチンで呼び出されています。
上記の初期設定で、下記のようにAN0とAN2を交互にサンプリングにする設定をしています。


ADCの割り込み(バッファへの記憶)とTimer3の割り込み(バッファへからの出力)を置き換える

以下ではADCの割り込み用のadc_umeFunc1_1関数を定義しています。 そこでは、ADCで得られたサンプリング値を記憶するレジスタからバッファメモリに記憶しています。
また、Timer3の割り込み用のadc_usb_out関数を定義しています。 そこでは、バッファメモリからUSB出力を行っています。
そして既存のADCの割り込み処理とTimer3の割り込みを、それぞれをここで定義したadc_umeFunc1_1関数とadc_usb_out関数に 置き換えています
このバッファメモリは、common.hで次のように定義しています。
struct ADC_CTRL {
    uint32_t counter_ADC;//USB出力数のカウント
    uint32_t count_end_ADC;//上記カウント目標値(block_size_ADCより設定)
    int loop_flag_ADC;// ループサンプリングで1
    int block_size_ADC;//サンプリングブロック数(1ブロックがADC_BUFF_SIZE)
    int set_sequence_flag;
    int out_channel_bits;    // 1:AN0 or 2:AN1 or 3:(AN0,AN1)
    void (*adc_out_usb)(uint32_t); // ADC USB output function
};
#define ADC_BUFF_SIZE 1024
#define ADC_OUT_NONE 2 // index_adc_out_blockの出力情報なしの定数
struct ADC_BUFF {
    uint16_t adc_buffer0[2][ADC_BUFF_SIZE]; //AN0(CN8)
    uint16_t adc_buffer1[2][ADC_BUFF_SIZE]; //AN1(CN9)
    int index_adc_block_sample; //録音中の添え字 0 or 1
    int index_adc_sample; // 録音位置
    int index_adc_out_block;// USB出力中の添え字 0 or 1 で、出力情報無し時は2がset
    int index_adc_out_pos; // USBの出力対象の添え字
    int adc_buff_data_numb;// 実際のusbで送信するする際のワードサイズ目標値
    struct ADC_CTRL *p_ctrl;
};
以下ではADCの割り込み用のadc_umeFunc1_1関数と、Timer3の割り込み用のadc_usb_out関数を定義して 既存の「テスト・ウメ・フラッシュ」の、この割り込み処理を 置き換える「ウメ・エディットプログラム」の例を示します。
#include <xc.h> // ADC_8K_AN3_2B.c  (AN0, AN1) 2block sampling
#include "common.h"

#define MY_ADC_ERR_OVERFLOW  0x0001 // ADC取得データの送信が間に合わなないエラー

#define AdrStart	0x80005000
__attribute__((address( AdrStart  ))) void start (void);

#define AdrStop	0x80006000
__attribute__((address( AdrStop))) void stop (void);

void adc_umeFunc1_1(struct ADC_BUFF *p_buff);//ADC割り込みルーチン
void adc_usb_out(struct ADC_BUFF *p_buff);//USBに出力するTimer3の割り込みルーチン

void start()
{
	_RB15 = ! _RB15 ;//LED反転

	_HANDLES[_IDX_ADC_1_FUNC]=  (void *)adc_umeFunc1_1;//ADCの割り込み(バッファに記憶)を自作の処理に置き換える
	_HANDLES[_IDX_TIMER_3_FUNC] = (void *)adc_usb_out;//Timer3割り込み(バッファの出力)を自作の処理に置き換える

	// (1/8000)/(1/40e6)/2-1=2499
	PR3=2499;	//サンプリング周波数を 8KHzに指定するパラメタ

	_set_adc_mode(3 , 0); // チャンネル指定と、bin/textの選択
	_set_adc_exe(2 , 0);  // ブロックのサンプリングを繰り返し数とループフラグ指定

	T3CONbits.ON = 0; // すぐにtimer3を停止し、以下で変更
	AD1CON2bits.ALTS = 1; // MUX AおよびMUX B入力マルチプレクサ設定を交互に使う
	AD1CON2bits.CSCNA = 0; // MUX A 入力マルチプレクサ 入力をスキャンしない。
	AD1CON2bits.SMPI = 1; // 割り込みあたりサンプリング 
	AD1CHSbits.CH0NA = 0; // MUX A チャンネル 0 の負極性入力に VR- を選択する
	AD1CHSbits.CH0NB = 0; // MUX B チャンネル 0 の負極性入力に VR- を選択する
	AD1CHSbits.CH0SA = 0; // MUX A チャンネル 0 の正極性入力に AN0 を選択する
	AD1CHSbits.CH0SB = 1; // MUX B チャンネル 0 の正極性入力に AN1 を選択する

	AD1CON3bits.SAMC = 0b01010; // b12-b8  自動サンプリング時間ビット= 2TAD
	//上記はアナログ入力ピンをサンプル/ホールドアンプ(SHA)に接続するアクイジション時間
	AD1CON3bits.ADCS = 0b00001011; /// b7-b0  ADC 変換クロック選択ビット
	// ADCの変換時間(8bit) =  TPB * 2 * (ADCS<7:0> + 1) = 512 * TPB = TAD

	IEC0CLR=_IEC0_T3IE_MASK;// // timer3の出力割り込み不許可★
	T3CONbits.ON = 1; // timer3 On 
	//デフォルトのままであれば、上記の黄色の部分の記述は不要です。ADC関連のSFR変更例として示しました。 
}

void stop()
{
	_set_adc_exe(1 , 0);// 現ブロック送信で終了
}

// ADCの新しい割り込み処理(AN0とAN1の交互サンプリング情報をp_buffに記憶)
void adc_umeFunc1_1(struct ADC_BUFF *p_buff) {
    static int no_send_count = 0; // 送信できない場合のデクリメント
    static uint16_t valAN0, valAN1;
    int flag_stop = 0;//★ADCの割り込みを終了するタイミングで1

    // 割り込み中で、下記バッファを必ず操作しなければならない。
    // (そうしないと、割込みが永続(persistent)のため、連続発生の不具合が生じる。)
    // バッファへの格納先を確認(ビットチェック格納先を判定)
    int idx = (AD1CON2 & _AD1CON2_BUFS_MASK) != 0;
    if (idx) {//1:ADC はバッファ0x8〜0xF に書き込み中(ユーザは0x0〜0x7 にアクセス可能)
        valAN0 = ADC1BUF0; //AN0
        valAN1 = ADC1BUF1; //AN1
    } else { //0: ADC はバッファ0x0〜0x7 に書き込み中(ユーザは0x8〜0xF にアクセス可能)   
        valAN0 = ADC1BUF8; //AN0
        valAN1 = ADC1BUF9; //AN1
    }
    if (p_buff->index_adc_sample >= ADC_BUFF_SIZE) {
        if (p_buff->index_adc_out_block != ADC_OUT_NONE) {

           IEC0SET=_IEC0_T3IE_MASK; // timer3の出力割り込み許可★ 
           //(adc_buffer0 or 1のバッファを記憶し終わったタイミング)
           if( PR3 < 500 ) {// 約80KHzを超えるサンプリングでは次の割り込みを不可
                flag_stop = 1;                
            }//★

            // エラーのサンプリング停止(メモリオーバー USB出力が間に合わない).
            no_send_count--;
            goto end_proc; // 下記のサンプリングをしない
        }
        // オルタネイト・バッファブが一杯なので、切り替える 
        p_buff->index_adc_sample = 0;
        int next_index_block_sample = p_buff->index_adc_block_sample;
        next_index_block_sample++;
        if (next_index_block_sample == 2) {
            next_index_block_sample = 0;
        }
        p_buff->index_adc_out_block = p_buff->index_adc_block_sample;
        p_buff->index_adc_block_sample = next_index_block_sample;

        if( flag_stop ){ // サンプルレートが高い場合、バッファを使い切ったら止める★
            IFS0CLR = _IFS0_AD1IF_MASK; // Clear ADC interrupt flag
            IEC0CLR = _IEC0_AD1IE_MASK; // ADC 割込み不許可
            return;
        }//★
    }

    if (no_send_count != 0) {// USB出力が間に合わないくて、出力されなかったデータ数?
        valAN0 = valAN1 = no_send_count; //出力できなかった数を(負の数)セット
        no_send_count = 0;
    }
    //valAN0 = valAN1 = cv;//デバック用データ設定.
    p_buff->adc_buffer0[p_buff->index_adc_block_sample][p_buff->index_adc_sample] = valAN0;
    p_buff->adc_buffer1[p_buff->index_adc_block_sample][p_buff->index_adc_sample] = valAN1;
    p_buff->index_adc_sample++;

end_proc:
    IFS0CLR = _IFS0_AD1IF_MASK; // Clear ADC interrupt flag  
}

int usb_receiver_disable_flag = 0;//受信を無効にするフラグ
int my_adc_set_err = 0;

// Timer3の新しい割り込み処理(p_buffの内容をUSBへ出力する)
void adc_usb_out(struct ADC_BUFF *p_buff) {
    static int flag_start_ADC = 1;
    static int flag_end_ADC = 0;
    static int i, idx_block, i_pos;
    static int16_t data;

    static int tim3_skip_count=0;// timer3が早すぎる場合の送出調整用★
    static int skip_timing =0;//★
    skip_timing = PR3;

    if( skip_timing < 100) { // 1つのサンプル周期が約400KHz以上?★
        skip_timing = 100 - skip_timing;
        tim3_skip_count++;
        if(tim3_skip_count < skip_timing ) {
            IFS0CLR = _IFS0_T3IF_MASK; // 次の割り込みを可能にする
            return;
        }
        tim3_skip_count =0;
     }//★

    struct ADC_CTRL *p_ctrl = p_buff->p_ctrl;

    if (p_buff->index_adc_out_block == ADC_OUT_NONE) {// USB送信するデータがない? .
        IFS0CLR = _IFS0_T3IF_MASK; // Clear the timer interrupt status flag
        return;
    }
    int size = _get_capacity();
    if (size < 50) {
        IFS0CLR = _IFS0_T3IF_MASK; // Clear the timer interrupt status flag
        return; //USB出力バッファに余裕がないので、次の呼び出しにする。  
    }
    if (_request_acc_outbuff(_ID_ACCESS_T3_TASK) == 0) {// ★
        IFS0CLR = _IFS0_T3IF_MASK; // Clear the timer interrupt status flag
        return;        //USB出力権限取得失敗で、、次の呼び出しにする。
    }

    // ADC DATA 送出.============>USB
    int text_mode = p_ctrl->adc_out_usb == (void (*)()) _HANDLES[_IDX_API_SEND_HEX_LOW]; 
    if (flag_start_ADC) {// ----------送出ブロック開始----------------
        p_buff->adc_buff_data_numb = ADC_BUFF_SIZE;
        ((void (*)(struct ADC_BUFF*))_UME_ENCODE_ADC_BUFF)(p_buff);
        if (p_ctrl->out_channel_bits == 0x3) { // AN0, AN1 両方の出力
            _send_string("\r\nADC_START\r\n");
            if (text_mode == 1) _send_char('T'); //TextMode
            _send_hex_low(p_ctrl->count_end_ADC); //送信データ数(X2))

        } else if (p_ctrl->out_channel_bits == 0x1) {//AN0の出力のみ
            _send_string("\r\nADC_START0\r\n");
            if (text_mode == 1)_send_char('T'); //TextMode
            _send_hex_low(p_ctrl->count_end_ADC); //送信データ数

        } else if (p_ctrl->out_channel_bits == 0x2) {//AN1の出力のみ
            _send_string("\r\nADC_START1\r\n");
            if (text_mode == 1)_send_char('T'); //TextMode
            _send_hex_low(p_ctrl->count_end_ADC); //送信データ数
        }
        _send_string("\r\n");
        usb_receiver_disable_flag = 1; //USB受信不可
        flag_start_ADC = 0; //開始処理を終えた〇
        flag_end_ADC = 0;

    } else if (p_ctrl->counter_ADC >= p_ctrl->count_end_ADC) {
        // ----------送出ブロック終了の処理------
        if (!flag_end_ADC) {
            flag_end_ADC = 0;
            flag_start_ADC = 1;
            _send_string("ADC_END\r\n");
            usb_receiver_disable_flag = 0; //USB受信不可解除
            if (p_ctrl->set_sequence_flag) {// set_ADCの登録がある時
                p_ctrl->set_sequence_flag = 0;
                if (p_ctrl->block_size_ADC != 0) {// 継続データ指示がある ?
                    // 目標サンプル数 設定があればセット
                    p_ctrl->count_end_ADC = p_buff->adc_buff_data_numb * p_ctrl->block_size_ADC;
                    if (p_ctrl->out_channel_bits == 0x3) {
                        p_ctrl->count_end_ADC <<= 1;  //AN0,AN1 の2つ分
                    }
                } else {
                    T3CONbits.ON = 0; // timer3停止
                    p_buff->index_adc_out_block = ADC_OUT_NONE;
                }
            }
            if (!p_ctrl->loop_flag_ADC) {//ループしない?
                T3CONbits.ON = 0; // timer3割込みオフ.
                p_ctrl->block_size_ADC = 0;
                p_buff->index_adc_out_block = ADC_OUT_NONE;
            }
            p_ctrl->counter_ADC = 0;
        }

    } else {// ------------- ADC データブロック送出中 -------------------------
        for (i = 0; i < 2; i++) {// ADCの値の1ワードを2つ分送出する。
            idx_block = p_buff->index_adc_out_block;
            i_pos = p_buff->index_adc_out_pos;
            if ((p_ctrl->out_channel_bits & 1) != 0) {
                data = p_buff->adc_buffer0[idx_block][i_pos]; //出力対象
                if (data < 0) my_adc_set_err=MY_ADC_ERR_OVERFLOW;
                p_ctrl->adc_out_usb(data);
                p_ctrl->counter_ADC++;
            }
            if ((p_ctrl->out_channel_bits & 2) != 0) {
                data = p_buff->adc_buffer1[idx_block][i_pos]; //出力対象
                if (data < 0) my_adc_set_err=MY_ADC_ERR_OVERFLOW;
                p_ctrl->adc_out_usb(data);
                p_ctrl->counter_ADC++;
            }
            ++p_buff->index_adc_out_pos;
            if (p_ctrl->counter_ADC % 16 == 0 && text_mode == 1) {
                // TEXT MODE 出力の場合だけ出力(バイナリ時は出力しない)
                _send_string("\r\n");
            }
            if (p_buff->index_adc_out_pos >= p_buff->adc_buff_data_numb) {
                p_buff->index_adc_out_pos = 0;
                p_buff->index_adc_out_block = ADC_OUT_NONE; // CHUNKブロックのUSB出力終了.
            }
        }
    }
    _release_acc_outbuff(_ID_ACCESS_T3_TASK);
     IFS0CLR = _IFS0_T3IF_MASK; // Clear the timer interrupt status flag
}
ここで定義したadc_umeFunc1_1関数とadc_usb_out関数で 既存の割り込み処理を置き換えています。
ですが、ここで示したadc_umeFunc1_1関数とadc_usb_out関数の内容は既存の割り込み処理と同じ内容です。
(既存の内容を示しているので、変更する場合はこれを参考に変更するとよいでしょう。)
この実行で得られるプロット例と、Communicationタブ内容を下記に示します。

START:80005000

ADC_START
1000
 ADC_END