CuriosityでmTouchを試してみる。その9 TIMER0編(1)

Data Visualizerを使ってデータを可視化する件はとりあえず置いといて。

mTouchはお仕着せのまま使うと簡単に使えることがわかりましたが、一方でMCCでのコード生成に「そうじゃなくて」という要望を加えるとなると、ちょっとハードルが高いですね。なにより "Generate" ボタンを押すと、生成されていたすべてのコードがチェックされ、変更があれば上書きされてしまうようですし。

自分の場合、PIC16F18346 にて mTouch を使いながら、同時にピリオドタイマが3つ欲しいという仕様を実現したいのです。そうなると、TIMER2/4/6 は3つとも同じ仕様なのでコードも基本部分は共通化できます。もちろんレジスタや割り込みフラグの位置などは違っていますが、流れとしては同じです。ところが MCC の mTouch では同じようにピリオドレジスタを実現できる TIMER0 は選択できません。

機械的コード生成を汎用的に考えるなら、PICの機種間で共通の TIMER2/4/6 のみに絞ったコードを生成するのは理にかなっていますから、これはしかたないとは思いますが、同時に自由度もなくなってしまっています。

それでも TIMER0 をピリオドタイマとして使いたければ、一旦生成されたコードを手作業でいじるしかありません。
ということで以下のようにやってみます。ただ、これはできるかどうかの実装テストなので簡単に。
  1. まずMCCでシステム設定、ペリフェラル、ピン割当などを行う。できればクロック設定や割り込みについても指定しておく。
  2. MCCでコード生成をする。
  3. バージョン管理システムにコードを登録する。
  4. コードをいじる。
MPLAB X IDEには、バージョン管理プラグインとして Git、Mercurial、Subversion がインストールされているので、それぞれ外部にツールがあれば利用できます。MPLAB X IDE Version ControlのページにUsing Mercurial Support in NetBeans IDEなどのリンクがあり、MPLAB X IDEのベースとなっているNetBeans IDEでのプラグインの使い方のページに飛んでいます。このページの右上のメニューから日本語を選べば、日本語の解説ページも参照できます。

で、方針は決まった感じですが、その前にまずは AFA(Automatic Frequency Adaptation)の機能をある程度把握しておかないと、無駄なことをやってしまう可能性があります。
The Automatic Frequency Adaptation (AFA) section allows you to enable the advanced noise immunity algorithm. The automatic frequency adaptation routine continuously tracks the amount of noise that is on each sensor and chooses a new scan frequency intelligently to avoid noise frequency and its harmonics. The routine requires an 8-bit timer to precisely control the scanning frequency for non-ADCC parts implementation. Due to this requirement, mTouch® will take ownership of the first available 8-bit timer module by default, if one is available when the automatic frequency adaptation routine is enabled.

「AFAによって高度なノイズ除去アルゴリズムを利用可能になります。自動周波数適応(AFA)ルーチンは、それぞれのセンサのノイズの総量を継続的に追いかけ、ノイズ周波数とその倍音成分を除去するための新たなスキャン周波数を賢く選択します。ルーチンはADCC以外の実装におけるスキャン周波数を精密に制御するための8bitタイマを必要とします。この必要条件によって、AFAルーチンが選択されたときには、mTouchはデフォルトで最初に利用可能な8bitタイマモジュールを専有するようになっています。」

という感じです。ADCCというのがよくわかりませんが、もしかしてADCのtypoかと思って調べてみたら Analog-to-Digital Converter with Computation だそうです。Computationは数値処理ですから、ADCとそれに伴う計算ということですね。
CVD方式のmTouchアルゴリズムでは、プリチャージとアクイジション期間、ADCサンプリングを繰り返すシーケンスと、そのシーケンスを起動するサイクルを制御するのと二種類があると思いますが、ノイズ除去に効くのはどちらでしょうね。ちょっとAPIリストとかも眺めてたんですが、端的な説明はありませんでした。とりあえず、ピリオドタイマでスキャン周波数を精密に決定する、という用途らしいので、TIMER0 でも使える、と踏んで進みます。

まず、TIMER0とTIMER2のレジスタを比較してみます。

TIMER0 TIMER2/4/6
COUNT
REGISTER
TMR0L TMRx
PERIOD
REGISTER
TMR0H カウントはTMR0L PRx カウントはTMRx
CONTROL
REGISTER 0
T0CON0 TxCON
bit7 T0EN bit7 '0’
bit6 '0’ bit6 TxOUTPS3 ポストスケーラ
1/1~1/16
bit5 T0OUT (RO) bit5 TxOUTPS2
bit4 T016BIT bit4 TxOUTPS1
bit3 T0OUTPS3 ポストスケーラ
1/1~1/16
bit3 TxOUTPS0
bit2 T0OUTPS2 bit2 TMRxON
bit1 T0OUTPS1 bit1 TxCKPS1 プリスケーラ
1,4,16,64
bit0 T0OUTPS0 bit0 TxCKPS0
CONTROL
REGISTER 1
T0CON1
bit7 T0CS2 クロックソース選択 FOSC/4のみ
bit6 T0CS1
bit5 T0CS0
bit4 T0ASYNC
bit3 T0CKPS3 プリスケーラ
1~32768
bit2 T0CKPS2
bit1 T0CKPS1
bit0 T0CKPS0
INTERRUPT
FLAG
REGISTER
PIR0 TMR0IF bit
PIR1/2 TMRxIF bit
INTERRUPT
ENABLE
REGISTER
PIE0 TMR0IE bit PIE1/2 TMRxEN bit
こうしてみると、クロックソース、プリスケーラ、ポストスケーラ、モード設定などが異なりますが、あとはカウントレジスタとピリオドレジスタのみなので、理論的にはできないことはなさそうです。
あとは実装がどうなっているのか、それを変更することができるのか、という点がポイントになりそうです。

mTouch の AFA(TIMER2を使用)を有効にした状態で MCC でソースを生成して眺めてみます。
TIMER2 関係は mtouch_sensor.c にありました。

まず、
#include "../tmr2.h"
でヘッダファイルを読み込んでいます。
次に、MTOUCH_Sensor_Scan_Initialize() で、TIMER2 のプリスケーラを指定しています。
/*
 * =======================================================================
 * MTOUCH_SensorScan_Initialize
 * =======================================================================
 *  initialization for ADC and Timer module
 */
void MTOUCH_Sensor_Scan_Initialize(void)
{
    T2CONbits.T2CKPS = 0x0;

    ADCON0 = (uint8_t)0;                            /* overwrite the ADC configuration for mTouch scan */
    ADCON1 = (uint8_t)( 0x1<<7 | 0x2<<4 | 0x0 );
    ADACT = (uint8_t)0;
}
そして、Sensor_Acq_ExecutePacket() で
/*
 * =======================================================================
 * Sensor_Acq_ExecutePacket()
 * =======================================================================
 */
static enum mtouch_sensor_error Sensor_Acq_ExecutePacket(mtouch_sensor_t* sensor)
{
    /* software CVD with AFA requires interrupt enabled */
    if(!(INTCONbits.GIE & INTCONbits.PEIE))
        return MTOUCH_SENSOR_ERROR_interrupt_notEnabled;
        
    enum mtouch_sensor_error        error = MTOUCH_SENSOR_ERROR_none;
    uint8_t ADCON0_temp;
    uint8_t ADCON1_temp;
    uint8_t ADACT_temp;
    

    ADCON0_temp = ADCON0;       /* store the current ADC configuration */
    ADCON1_temp = ADCON1;
    ADACT_temp = ADACT;
    MTOUCH_Sensor_Scan_Initialize();
    
                 
    Sensor_setScanFunction(sensor);  /* Setup the scan function */

    currentScannSensor = sensor->sensor_name;
    packet_counter  = sensor->oversampling;
    packet_sample = 0;
    sensor_globalFlags.packet_done = 0;
    packet_noise = 0;
    
    TMR2_SetInterruptHandler(Sensor_Acq_ExecuteScan);  /* Use timer2 to schedule the scan */
    TMR2_LoadPeriodRegister(sample_period);
    TMR2_StartTimer();
    
    sensor_globalFlags.interrupted = false;
    
    /* Perform packet samples */
    do
    {
        while(PIR1bits.ADIF == 0) 
        {
            if(sensor_globalFlags.packet_done == (uint8_t)1)
                break;
        }
        PIR1bits.ADIF = 0;    
    } while(sensor_globalFlags.packet_done == 0);


    TMR2_StopTimer();
    ADCON0 = ADCON0_temp;       /* restore the previous ADC configuration */
    ADCON1 = ADCON1_temp;
    ADACT = ADACT_temp;
    
    if(sensor_globalFlags.interrupted)
    {
        error = MTOUCH_SENSOR_ERROR_interruptedScan;
    }
    
    return error;
}
という処理を行っています。スタート、ストップ、ピリオドレジスタへのロードはいいとして、割り込みハンドラに Sensor_Acq_ExecuteScan() を割り当てているのでこれを追いかけてみます。
/*
 * =======================================================================
 * Sensor_Acq_ExecuteScan()
 * =======================================================================
 * Perform a single sample on the sensor. This is a local function and
 * requires that the ExecutePacket() function guarantees the correct PIC
 * and scanning configuration.
 *
 * This function is written to be independent of mainloop vs ISR context.
 */
static void Sensor_Acq_ExecuteScan(void)
{
    while(ADCON0bits.ADGO); 
    
    mtouch_sensor_adcsample_t result = ADRES;       /* result from previous scan */
    static mtouch_sensor_adcsample_t last_a,last_b;

    if(sensor_globalFlags.packet_done)
        return;
    
    if (packet_counter != (uint8_t)0)
    {
        #pragma switch time
        switch(packet_counter & 0x01)
        {
            case 0: Sensor_scanA();break;
            case 1: Sensor_scanB();break;
            default: break;
        }
        /* Accumulate previous sample result during the ADC conversion */
        if(packet_counter!=mtouch_sensor[currentScannSensor].oversampling)
        {
            if(packet_counter & 0x01)
            {    
                result = PIC_ADC_RESOLUTION - result;
                packet_noise += (mtouch_sensor_packetsample_t)abs(last_a-result);
                last_a = result;
            }
            else
            {
                packet_noise += (mtouch_sensor_packetsample_t)abs(last_b-result);
                last_b = result;
            }
            packet_sample += result;
        }
        packet_counter--;
    }
    else
    {
        packet_sample += result;
        packet_noise += (mtouch_sensor_packetsample_t)abs(last_b-result);
        sensor_globalFlags.packet_done = (uint8_t)1;
    }
}
長くなるのでコードは畳んでいますが、どうやらここではタイマをいじってはいないようです。 次に tmr2.c を眺めてみます。
/**
  Section: Global Variables Definitions
*/

void (*TMR2_InterruptHandler)(void);

/**
  Section: TMR2 APIs
*/

void TMR2_Initialize(void)
{
    // Set TMR2 to the options selected in the User Interface

    // PR2 255; 
    PR2 = 0xFF;

    // TMR2 0; 
    TMR2 = 0x00;

    // Clearing IF flag before enabling the interrupt.
    PIR1bits.TMR2IF = 0;

    // Enabling TMR2 interrupt.
    PIE1bits.TMR2IE = 1;

    // Set Default Interrupt Handler
    TMR2_SetInterruptHandler(TMR2_DefaultInterruptHandler);

    // T2CKPS 1:1; T2OUTPS 1:1; TMR2ON on; 
    T2CON = 0x04;
}
void TMR2_ISR(void)
{

    // clear the TMR2 interrupt flag
    PIR1bits.TMR2IF = 0;

    if(TMR2_InterruptHandler)
    {
        TMR2_InterruptHandler();
    }
}


void TMR2_SetInterruptHandler(void (* InterruptHandler)(void)){
    TMR2_InterruptHandler = InterruptHandler;
}

void TMR2_DefaultInterruptHandler(void){
    // add your TMR2 interrupt custom code
    // or set custom function using TMR2_SetInterruptHandler()
}
あたりで割り込み処理などを行っています。

ということは、この辺をいじってやればなんとかなるかもしれません。

というあたりで次回に続きます。

0 件のコメント:

コメントを投稿

AstroNvimでtelescope.nvimがエラーを吐いたとき。

Windowsの環境でAstroNvimをインストールして、Find Fileしたらtelescopeがエラーを吐いてきました。 メッセージは、 Failed to run `config` for telescope.nvim ...a/lazy/telescope.nvim/...