PIC24F64GA002とFreeRTOS
目 次
  1. はじめに
  2. 概要
  3. FreeRTOS
  4. FFT
  5. アナログ入力
       ADコンバーター設定
       ADコンバーター割り込み
  6. PWM
  7. UART
  8. タイマー割り込み
  9. ソースとプロジェクトファイル
  10. 感想

はじめに
しばらく見ないうちにPICとかAVRなどのチップマイコンの発展は著しく、32ビット版まで現れる始末です。チップマイコンは単独で使ってこそ 威力を発揮すると考えていたのでRTOSは不要と思っていました。 ところが32bitになればLinuxが動きますし、一般的な PICあるいは AVRには FreeRTOSがある。 16bitの能力であればプリエンティブ制御しても実用的であると思われ、ここでは PIC16bitで 一番安価な PIC24FJ64GA002で試作する。 開発システムは Microchip社製 MPLABとC30コンパイラを使用する。 プログラマとデバッガは同様に Microchip社製 PICkit3を使用する。

概要
PICはチップマイコンであり、チップで閉鎖的に完成しているので、外部部品をなるべく使用せずに作る事を目標とする。
何が良いかと思ったが、さし当たってベンチマークのつもりでFFTを動かす。チップのハードとしてはデジタル入出力、UART、アナログ入力、パルス幅変調あたりをサポートする予定です。

FreeRTOS
FreeRTOSの概要はここにあります。
まずFreeRTOSのデモプログラムから出発する。デモとしてPIC24FJ128GA010がある。これをPIC24FJ64GA002で動かさなければならない。デモ・ディレクトリにあるFreeRTOSConfig.hで定義している
#include <p24FJ128GA010.h> を
次の行で入れ替える。
#include <p24FJ64GA002.h>
これで良いかと思われたが、PIC24FJ128GA010の100pinに対してPIC24FJ64GA002は28pinしかない。
GA010ではほとんど必要なかった、ややこしい、pin割付をGA002では行わなければならない。
28pinしかないので複数の機能から一つをpinに割り付けなければならない。
また、一部の機能(JTAG)は使用しない設定にして、PICkit3を用いてデバックしている。
これらはMPLABのConfigure=>Select Device...とConfigure=>Configureation Bits...で指定し直せば 自動的にプロジェクトに取り込まれるが、pin割付は明示的にコードで指定しなければならない。

このデモの Configuration Bits... 例
Address Value Field Category Setting
ABFC 79FF POSCMOD Primary Oscillator Select Primary Oscillator Disable
I2C1SEL I2C1 Pin Location Select Primary SCL1/SDA1 Pins
IOL1WAY IOLOCK Protection Once IOLOCK is set, cannot be changed
OSCIOFNC Primary Oscillator Output Function OSCO pin has clock out function
FCKSM Clock Switching and Monitor Sw Disabled, Mon Disabled
FNOSC Oscillator Select Fast RC Oscillator with PLL module (FRCPLL)
SOSCSEL Sec Oscillator Select Default Secondary Oscillator (SOSC)
WUPTSEL wake-up timer select Legacy Wake-up Timer
IESO Internal External Switch Over Mode Disable
ABFE 377F WDTPS Watchdog Timer Postscaler 1:32,768
FWPSA WDT Prescaler 1:128
WINDIS Watchdog Timer Window Non-Window mode
FWDTEN Watchdog Timer Enable Disable
ICS Comm Channel Select EMUC1/EMUD1 shared with PGC1/PGD1
GWRP General Code Segment Write Protect Disable
GSS General Code Segment Code Protect Disable
JTAGEN JTAG Port Enable Disable
PIC24FJ64GA002のpin割付表
機能 pin
MCLR 1
AN0 2
AN1 3
PGD1 4
PGC1 5
6
7
GND 8
9
PWM1 (RB4) 10
11
12
13
PWM2 (RB5) 14
pin 機能
28 VDD
27 GND
26 (RB15)
25 (RB14)
24 (RB13)
23 (RB12)
22 (RB11)
21 (RB10)
20 Vcap
19 GND
18 uart tx (RB9)
17 uart rx (RB8)
16
15
FreeRTOSのデモプログラムには下記のものがある
待ち行列でタスクがブロックされる様子 vStartBlockingQueueTasks
Integer計算を続けてエラーの発生テスト vStartIntegerMathTasks
LEDの点滅用コルーチン vStartFlashCoRoutines
UART用のルーチン(これは活用) vAltStartComTestTasks
タスクの時限ブロックのテスト vCreateBlockTimeTasks();
デモルーチンの実行情報 vCheckTask
LCDがあれば、その表示用タスク xLCDQueue = xStartLCDTask();
コルーチンがあれば vStartHookCoRoutines();
高精度タイマーのテスト vSetupTimerTest
などがあるが、デモが動くことを確認後、コルーチンは必要になるまで削除する。上記でUARTだけは修正して残しコンソールとして活用し、あとは削除する。これですっきりするし、裸のFreeRTOSが良く見える。
すっきりさせた状態で各インプリメントを行い、最後にFlashCoRoutineを入れる。
Heap及びstack
FreeRTOSが使用するHeap及びスタックはFreeRTOSConfig.hにあるconfigTOTAL_HEAP_SIZEで定義する。このHeap範囲でstackとpvPortMalloc等のメモリの取得が実行される。ここでは事実上メモリーマネージメントをしていないHeap1を採用している(説明はここにあります)、これは強く推奨されます。今までのトラブルシュートの経験上、少なからぬトラブルがメモリーマネージメントに起因するもであったからです。

FFT
dsPICにはDSP用FFTがMicrochipから提供されている。これは固定小数点演算でありながら充分なダイナミックレンジを持つQ15,Q16Formatで記述されている。残念ながらPIC24Fでは、このDSP命令は搭載されていない。というより、dsPICからDSP命令をはずしたのがPIC24Fのようだ。
そこで、使えそうなTom Robertsさんの固定小数点演算FFTを載せてみた。色々な理由から複素FFTを使いたいのですが、ここでは、どこまで使用できるかを検証する意味で、同梱されている実数FFTを使い実行時間を計ってみた。
PIC24FJ64GA002のRAM容量は8Kbあり、半分の4kをデータに使用するとすれば、1データは16bit2バイトであるから、2048点までの計算ができることになる。
点数 変換+逆変換実行時間 変換のみ実行時間
256 11msec(実測) 5.5msec(推測)
512 26msec(実測) 13msec(推測)+
1024 56msec(実測) 28msec(推測)
2048 --- ---
あまり早くはないが、クロック周波数並みの速度は出ている。まあまあかな。
ここから、お遊びでアナログ入力から音声256点(40khzで6.4msec)取り込み後リアルタイムで数帯域の周波数強度をPWMを使ってLEDで発光強度で示すものを製作する。
Tom RobertsさんのFFTを移植時の注意点
  • int変数はlong変数に読み替える。
  • sinテーブルは限られたRAM上のデータセグメントに置くのはもったいないので、64kbもあるコードセグメントに置く。
    指定はProject => Build Options...=> Project => MPLAB C30 => Memory Model =>Location of Constant で Constants in Code spaceを選択する
  • FFTに限らないがsmallモデルでは6144バイトまでしか届かないので、それ以上は__attribute__((far)) をつけてデータエリアを定義しなければならない。
  • なお、このfix_fftr()関数は未完成です、一見完成しているように見えますが余計な処理が入っている代わりに肝心の処理が入っていない。次の処理を加えなければならない。
    A[k] = X[k]-(1+i*W(n)^k)/2*(X[k}-conjg(X[n/2-k]))
    A[n/2-k]=X[n/2-k]+conjg(1+i*W(n)^k)/2*(X[k}-conjg(X[n/2-k]))
 このタスクはローカル変数もそれなりに使用するので、タスクの最小スタック量にプラス100bytes程度増やして置かなければならない。蛇足ながら、必要量ぎりぎりに設定した場合、これに(必要なら)割り込みが使用するスタックも考慮しなければならない。
/* Short test program to accompany fix_fft.c */
/*-------- FreeRTOS defines ---------*/
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "fft.h"

static void vFFTTask( void *pvParameters );
#define fftQUEUE_SIZE           3

extern xQueueHandle xFFTQueue;
__attribute__((far)) short x[N*2];

#define N         256      /* ( FFT_SIZE) */
#define log2N     8        /**/

xQueueHandle xStartFFTTask( void )
{
    xQueueHandle xFFTQueue;
    /* データ受信同期用待ち行列の生成 */
    xFFTQueue = xQueueCreate( fftQUEUE_SIZE, sizeof( xFFTMessage ) );
  /* タスクの生成 */
    xTaskCreate( vFFTTask, ( signed portCHAR * ) "FFT",
           configMINIMAL_STACK_SIZE+100, NULL, tskIDLE_PRIORITY + 1, NULL );
    return xFFTQueue;
}
/*-------------------------------------*/
static void vFFTTask(void *pvParameters)
{
    xFFTMessage xFFT;
    static int i, j, scale;
    static unsigned diff;
    short *fx;
    /* プライベート・エリアからメモリを取得する */
    fx = pvPortMalloc(sizeof(short)*N);
    /* タスクループ */
    while(1){
        /* データの収集を待つ */
        xQueueReceive( xFFTQueue, &xFFT, portMAX_DELAY );
        /* ダブルバッファからデータを取得する */
        j = (xFFT.fftSel == 0)? 0 : 256;
        /* 実数変換用に並べ替える */
        /* 符号付き2進に変換し12288に正規化する */
        for (i=0; N; i++){
            if (i & 0x01))
                fx[(N+i)>>1] = (x[i+j]-512)*24;;
            elsee
                fx[i>>1] = (x[i+j]-512)*24;;
        }
        /* FFT変換 */
        fix_fftr(fx, log2N, 0);
#if SPECTRUM
        /* 各周波数領域の二乗総和を求める */
        for(i=0; i<4; i++) sp.spec[i] = 0;
        for (i= 1; i< 4; i++) sp.spec[0] += ((long)fx[i]*fx[i]+fx[i+N/2]*fx[i+N/2]);
        for (i= 4; i<16; i++) sp.spec[1] += ((long)fx[i]*fx[i]+fx[i+N/2]*fx[i+N/2]);
        for (i=16; i<32; i++) sp.spec[2] += ((long)fx[i]*fx[i]+fx[i+N/2]*fx[i+N/2]);
        for (i=32; i<64; i++) sp.spec[3] += ((long)fx[i]*fx[i]+fx[i+N/2]*fx[i+N/2]);
        /* 平均及び平方根(RMS)を求め明度表示するタスクへ渡す */
        xQueueSend( xFFTWQueue, &sp, portMAX_DELAY );
#endif
        /* 逆変換で元に戻す */
        scale = fix_fftr(fx, log2N, 1);
        /* 実数変換用のデータを並べ替えて元に戻す 
        正規化(12288)を戻し符号なし2進数に戻す */
        int k = (j == 0)? 512 : 768;
        for (i=0; i<N; i++) {
            if (i & 0x01)
               x[i+k] = (fx[(N+i)>>1] << scale)/24+512;
            else
               x[i+k] = (fx[   i >>1] << scale)/24+512;
        }
    }
}
/*
平均及び平方根(RMS)を求め明度表示するタスク
これは時間内に終わらない。そこで溜まったQUEUEを
間引き最新のデータに同期する。
*/
static void vPwrTask(void *pvParameters)
{
    Spectrum sp;

    while(1){
        if (xQueueReceive( xFFTWQueue, &sp, portMAX_DELAY) == pdTRUE){
           while( xQueueReceive( xFFTWQueue, &sp, 0) == pdTRUE);
           sp.spec[0] = sqrt(sp.spec[0] / 3);
           sp.spec[1] = sqrt(sp.spec[1] / 12);
           sp.spec[2] = sqrt(sp.spec[2] / 16);
           sp.spec[3] = sqrt(sp.spec[3] / 32);
           SetDCOC2PWM((long)(PR2+1)*(PR2-1-sp.spec[0])/160);      
           SetDCOC3PWM((long)(PR2+1)*(PR2-1-sp.spec[1])/160);      
           SetDCOC4PWM((long)(PR2+1)*(PR2-1-sp.spec[2])/160);
           SetDCOC5PWM((long)(PR2+1)*(PR2-1-sp.spec[3])/160);
        }
    }
}
 おや、パワースペクトルを計算するには窓関数がかかっていない?これはテスト用に逆変換して音声出力するためであり、どこまで使えるかの検証なので悪しからず。
なお、fix_fftrの修正したものを次に示す。
int fix_fftr(short f[], int m, int inverse)
{
    int i, N = 1<<(m-1), scale = 0;
    short tt, *fr=&f[0], *fi=&f[N];
    short n, n2, tsin, tcos;
    short real1, imag1, real2, imag2; 
    long  tr, ti, sr, si;

    n = N*2;
    n2 = n/2;
    if (inverse){
        for(i=1; i<n/4; i++){
            tsin = Sinewave[(1024/n)*i];
            tcos = Sinewave[(1024/n)*i+256];
            tr = fr[i]-fr[n2-i];
            ti = fi[i]+fi[n2-i];
            sr = ((tr*(32767-tsin)+ti*tcos)/2) >> 15;
            si = ((ti*(32767-tsin)-tr*tcos)/2) >> 15;
            real1 = fr[   i] - sr;
            imag1 = fi[   i] - si;
            real2 = fr[n2-i] + sr;
            imag2 = fi[n2-i] - si;
            fr[   i] = real1;
            fi[   i] = imag1;
            fr[n2-i] = real2;
            fi[n2-i] = imag2;
        }
        fr[0] = (fr[0] + fr[n2])/2;
        fi[0] = (fi[0] - fi[n2])/2;
        scale = fix_fft(fi, fr, m-1, inverse);
    }
    if (! inverse){
        scale = fix_fft(fi, fr, m-1, inverse);
        for(i=1; i<n/4; i++){
            tsin = Sinewave[(1024/n)*i];
            tcos = Sinewave[(1024/n)*i+256];
            tr = fr[i]-fr[n2-i];
            ti = fi[i]+fi[n2-i];
            sr = ((tr*(32767-tsin)-ti*tcos)/2) >> 15;
            si = ((ti*(32767-tsin)+tr*tcos)/2) >> 15;
            real1 = fr[   i] - sr;
            imag1 = fi[   i] - si;
            real2 = fr[n2-i] + sr;
            imag2 = fi[n2-i] - si;
            fr[   i] = real1;
            fi[   i] = imag1;
            fr[n2-i] = real2;
            fi[n2-i] = imag2;
        }
        fr[n2] =  fr[0];
        fi[n2] = -fi[0];   
    }
    return scale;
}

アナログ入力
PIC24FのADコンバータは多くのモードを備えていあるが、ここではタイマーに依る周期取り込みを例として取り上げる。
タイマ3を取り込み周期発生源として使用する。
ADコンバータ設定関数
#define USE_AND_OR
#include "adc.h"
#include "timer.h"
int adc_setup(unsigned portBASE_TYPE  ports)
{
    /* タイマ3を内部クロック16MHz、プリスケーラ8 (=>2Mhz)に設定
       トリガ周期を2Mhz/200=10Khzとする*/
    OpenTimer3(T3_ON | T3_PS_1_8 | T3_SOURCE_INT, 200-1);   

    /* 各トリガ時、CH0及びCH1のADCサンプリングを実行する
       16回の変換(2chを8回)ごとに割り込みを発生する。
    */
    if(ports==2){    
        OpenADC10(
            ADC_MODULE_ON | ADC_CLK_TMR3 | ADC_AUTO_SAMPLING_ON ,
            ADC_SCAN_ON | ADC_INTR_8_CONV, 
            ADC_SAMPLE_TIME_5 | ADC_CONV_CLK_6Tcy,
            ~(ENABLE_AN1_DIG | ENABLE_AN0_DIG), 
            ADC_SCAN_AN1 | ADC_SCAN_AN0);
    }
    SetChanADC10(0);
    /* 割り込み初期化 */
    ConfigIntADC10(ADC_INT_ENABLE | ADC_INT_PRI_2);
}

アナログ入力割り込み関数
例えば音声であれば40Khz程度のサンプリング周波数ですので、もしその頻度で割り込めば、処理時間は25usec程になります。ということはC言語で見て数ステップしか実行できない。しかし、ADCは最大16個のバッファを持っているので、これを使用すれば1桁ほど余裕が出てくる。
それでも、タスクにデータを送信する余裕は無いので、ダブルバッファでタスクとのデータの受け渡しを行う。
バッファがFULLになると(ブロックしている)該当タスクへバッファを指定してシグナル(待ち行列)を送信する。
__attribute__((far)) extern short dbuff[2][256];

void  __attribute__((interrupt, no_auto_psv)) _ADC1Interrupt(void)
{
    static short AnIdx=0;
    static short AnSel=0;
    portBASE_TYPE xHPTaskWoken; 
    xFFTMessage xFFT;
    int i;

    IFS0bits.AD1IF = 0;             /* 割り込みクリア */
    if (AnIdx >= N){
        xFFT.fftSel = AnSel;
        xHPTaskWoken = pdFALSE; 
        xQueueSendFromISR( xFFTQueue, &xFFT, &xHPTaskWoken );
        if( xHPTaskWoken ) {
/* 次の行は削除すべきだろう。割り込みから抜ける時間がかかりすぎる。
  待てば、いずれ該当タスクは起動されるであろう */
            taskYIELD(); 
        }
        AnIdx = 0;
        AnSel = (AnSel==0)? 1: 0;
    }
    for (i=0; i<8; i++)
        dbuff[AnSel][AnIdx++] = ReadADC10(0);
    /* 第2チャンネルのデータは捨てていますがテストなのでご容赦*/
}

PWM
 PIC24FJ64GA002は5chの16Bit Compare/PWM 出力を持っています。これを使って気軽にパルス幅変調が出来ます。ここではPWM周波数として音声も再生できる100khzを選択します。なんか、すごい事になっています。
変調自体は他のタスク等のタイミングで行いますので、ここではドライバのみを説明する。
#include "timer.h"
#include "outcompare.h"
/*
 パルス幅変調初期化設定関数
*/
int pwm_setup(void)
{
    /* タイマ2を内部クロック16MHz、プリスケーラ1、
        PWM周期160(=100khz) PWM周期はPR2に記憶される
    */
    OpenTimer2(T2_ON | T2_PS_1_1 | T2_SOURCE_INT, 160-1);
    /* pin割付 */
    RPOR2bits.RP4R = 18; /*OC1 -> RP4*/
    /* OC1をPWMモードで初期化 */
    OpenOC1(OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE, PR2+1, 0);

    RPOR2bits.RP5R = 19; /*OC2 -> RP5*/
    OpenOC2(OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE, PR2+1, 0);
}
 変調ステートメントの例として音声を取り込みそのままPWM出力する例を下に示す。ADCの0chからUnSigned10bitデータ(MAX1024)を読み込み、タイマ2のPWM周期(PR2)で正規化します。なお、この計算は16bitだとオーバーフローするのでlongにキャストして計算する。
SetDCOC1PWM((long)(PR2+1)*ReadADC10(0)/1024);

UART
PIC24FJ64GA002はUART2chを持っています。 コンソールはPICでは一般的にLCDを使うようですが、外付け部品を最小にして作るというコンセプトから、UARTをコンソールに使用する。 なお、レベルコンバータが必要であるが、MAX202で作ったコンバータをケーブル側で用意した。 便利なので一つ持っていると便利です。

タスク構成はデモプログラム(comtest.c)そのままです。これに送信用及び受信用待ち行列を付け加えて、送受信タスクとしています。

疑問なんですが_U2TXInterruptで1文字送出毎に割り込む設定になっている。キューにデータが無い場合等、以後割り込みが掛からなくなりそうですが・・・
実はxSerialPutCharのxQueueSend()の後ろで小細工して割り込みを発生するようにしてあります。それで余計なxserialPut/GetChar()がはいっています。(serial.cを参照)
xQueueHandle xComTxQueue;
xQueueHandle xComRxQueue;
#define ComRxQUEUE_SIZE 16
#define ComTxQUEUE_SIZE 3
/*-----------------------------------------------------------*/

void vAltStartComTasks( unsigned portBASE_TYPE uxPriority, unsigned long ulBaudRate, 
                                         unsigned portBASE_TYPE uxLED )
{
        /* ポートの初期化 */
        xSerialPortInitMinimal( ulBaudRate, comBUFFER_LEN );
        xComTxQueue = xQueueCreate( ComTxQUEUE_SIZE, sizeof( xComTxMessage ) );
        xComRxQueue = xQueueCreate( ComRxQUEUE_SIZE, sizeof( xComRxMessage ) );

        /* タスクを生成する。Txの優先度はRxより低く設定する。 */
        xTaskCreate( vComTxTask, ( signed char * ) "COMTx", comSTACK_SIZE, 
                NULL, uxPriority - 1, ( xTaskHandle * ) NULL );
        xTaskCreate( vComRxTask, ( signed char * ) "COMRx", comSTACK_SIZE,
                NULL, uxPriority, ( xTaskHandle * ) NULL );
}
/*-----------------------------------------------------------*/
/* UART送信タスク */
static portTASK_FUNCTION( vComTxTask, pvParameters )
{
        signed char     cByteToSend;
        portTickType    xTimeToWait;
        xComTxMessage   xMessage;
        portCHAR        *pcstring;

        for( ;; ){
                while( xQueueReceive( xComTxQueue, &xMessage, portMAX_DELAY ) != pdPASS );
                pcstring = xMessage.pcMessage;
                while(*pcstring){
                        xSerialPutChar( NULL/*xPort*/, *pcstring++, comNO_BLOCK );
                }
        }
} 
/*lint !e715 !e818 pvParameters is required for a task function */
/*                                even if it is not referenced. */
/*-----------------------------------------------------------*/
/* UART受信タスク */
static portTASK_FUNCTION( vComRxTask, pvParameters )
{
        signed char cExpectedByte, cByteRxed;
        portBASE_TYPE xResyncRequired = pdFALSE, xErrorOccurred = pdFALSE;
        xComRxMessage   xMessage;

        for( ;; ){
                /* 1文字入力後無条件に待ち行列に出力する。受信タスクが無ければ、
                        このループはいずれブロックする。 */
                xSerialGetChar( xPort, &cByteRxed, comRX_BLOCK_TIME );
                xMessage.pcMessage = &cByteRxed;
                xQueueSend( xComRxQueue, &xMessage, portMAX_DELAY );
        }
}

タイマー割り込み
 タイマー割り込みの例として音声データの出力タイミングの発生を取上げる。 音声データを40khzでPWM出力する。本来はリングバッファを構成するのであるが、40khzでは最大で25usec程の割り込み処理時間しか無いので、まともなバッファは構成できない。
 一般に、この様な場合シリアルインターフェースを持つMCP4922等とSPIを使用すると思う。SPIはFIFOがあり、その分余裕が生まれるので、バッファ構成も色々考えられる。
 ここでは音声入力及びその処理と同期して出力データが生成されるので、これを単純に入出力が同期しているとして制御なしで使用する。
/**************************
* タイマー初期化設定関数
***************************/
int timer_setup(void)
{
/* 音声出力用 */
/* タイマ4の初期設定 プリスケーラ1/8 内部クロック 16Mhz/8/50=40kc */
    OpenTimer4(T4_ON | T4_PS_1_8 | T4_SOURCE_INT, 50-1);
    ConfigIntTimer4(T4_INT_PRIOR_2 | T4_INT_ON);
}

/***************************
  音声用timer割り込み処理       
****************************/
void __attribute__((interrupt, shadow)) _T4Interrupt(void)
{
    /* 追っかけるように再生するため、少し手前からスタートする*/
    static short PlayIdx=480;
    
    IFS1bits.T4IF = 0;    /* 割り込みクリア */ 
    PlayIdx &= 0x1ff;
    /* PWM出力を行う */
    SetDCOC1PWM((long)(PR2+1)*x[PlayIdx++]/1024);
}

ソースとプロジェクトファイル
 MPLABをデフォルトでインストールした場合、周辺機器のヘッダは
C:\Program Files\Microchip\MPLAB C30\support\peripheral_24F
にあり、ドライバは
C:\Program Files\Microchip\MPLAB C30\src\peripheral_24F\libpPIC24F\pmc
にあります。
しかし、あまりに大量であり、かつ細分化されているので必要なものをカレントディレクトリへ転送して使用しています。
 これらはLib形式にして使用すれば手間が無いのですが、大規模なプログラムで無い限り、ソース・デバッグを可能にしておくのが得策だと思う。

感想
 リソースの少ないCPUのRTOSはプリミッティブなAPIを持つものが望ましいと考えていた。FreeRTOSはより小さな核を持ち、その上で、少ないリソースを活用しようとしていると見られる。しかし、クリティカルな場面では、まだ動作が重い。そのような時は、単純な割り込み処理とかアッセンブラを使え、ということなのだろう。しかし、100%の負荷を与えても、もくろみ通りの動作をけなげにもこなしているのにはちょっと感心した。おそらくオーバヘッドと機能のバランスは良いところにあるのではないだろうか。
 44khzの音声を(途中FFTみたいな重い処理を入れて)取り扱おうと思ったが、結局16Khz〜20khzが限界だった。主に、その周波数での割り込み処理を必要とする音声出力でのCPUリソースの消費量だった。チップ内で全て処理するのであれば当然だと思う。一般的にはSPI機能とSPIを持つDAコンバーターを使うのであろう。その場合もう少し処理周波数を上げることが出来る。
PICは扱い難いとの先入観を持っていたが

ご意見ご質問はここ
Guest No.