枯れた開発ツール。

PIC開発をしていて、最新の Microchip MPLAB X IDE 5.15 にアップグレードしてみたんですが、ICD3 の調子がおかしいのです。
まず、動作が異様に遅い気がします。これは書き込みもデバッグも。4.15 でやっていたときにはこんなことはなかったのですが。
それから MCU がフリーズします。正常に動作するときもあるし、しないときもあります。それまで動いていたコードが、build & write したら動かなくなったり。とにかく動きがおかしいのです。

そこで、アンインストールしていなかった 4.15 を使って書き込みしてみると、シュッと終わります。早いです。おかしいです。しかもちゃんと動きます。
もしかしたら ICD3 のファームウェアバージョンかもしれないと思ったんですが、ICD3 のファームウェアは単体ではダウンロードできないようで、過去のバージョンの MPLAB X IDE をダウンロード、インストールして書き換えないとだめなようです。

XC8 コンパイラと IDE のバージョンは特に関連はありませんが、ICD3 や PICkit3 などのデバッガ/プログラマのファームウェアと IDE との関連はあるようです。
ただ、XC8 も AVR にも対応?するようになったようで、ここはやっぱり 1.45 に戻しておこうと思います。
おそらくですが、AVR への対応を ICD3 ファームウェアに取り込んだことでなにか不具合が起きているのかもしれませんね。

過去のバージョンの IDE は、MPLAB Ecosystem Downloads Archiveから入手できるようになっています。

それぞれのバージョンでのファームウェアの場所は、C:\Program Files (x86)\Microchip\MPLABX\v4.xx\mplab_platform\mplablibs\modules\ext\ICD3.jar のなかにアーカイブされているようです。なので、この jar ファイルを展開してみればバージョンが分かります。
手元にある範囲では、

4.15 -> ICD3 FW 01.51.08
4.20 -> ICD3 FW 01.51.12
5.15 -> ICD3 FW 01.55.01

となっていました。

4.20 でやってみたところどうやら調子がいいようなので、5.15 はやめて 4.20 を使うことにします。やっぱり新しいバージョンというのはごにょごにょ。タイトルの「枯れた」というのは悪い意味ではなく、「十分に使われてバグ出しが終わっている」という意味です。

それにしても、開発ツール系のバージョンによる不具合というのは、わりといちばん最後まで可能性として考慮されないので、原因を探るのに思い切り遠回りしてしまいますね。

Cygwin 3.x。

3月5日に Cygwin 3.0.2 がリリースされたようです。

Cygwin version
The most recent version of the Cygwin DLL is 3.0.1.

Note that Cygwin version 2.5.2 was the last version supporting Windows XP and Server 2003. (Instructions for obtaining that version)

3.0.0のリリースは見逃していたんですが、WinXPをサポートしているのは 2.5.2 までで、2.5.2 の Cygwin をインストールするならこっちを見てね、だそうです。

What changed:
-------------

- Windows 10 1803 or later and WSL installed:

Starting with 3.0.0, mkdir(2) automatically created directories within
the Cygwin installation dir as case sensitive. This badly breaks
interoperability with remote machines trying to access these dirs.
Therefore, disable this as default. You can still create case-sensitive
dirs via `chattr +C ...'

3.0.2 では Windows 10 1803 以降でかつ WSL がインストールされている場合に、mkdir(2) でケースセンシティブなディレクトリ作成を行っていたのを、デフォルトではしないようにしたようです。3.0.0-1 のリリースノートはこれですかね。

鉄ユニクロネジ。

インパクトドライバで鉄・ユニクロネジが千切れたので、インパクトドライバでちょっと硬めの木にネジを打つときには、ステンレスのコーススレッドなどを使いましょう。
普通のドリルドライバならユニクロでもよさそうです。

ボッシュのコードレスインパクトドライバでネジが切れちゃいましたよ…とほほ。

CuriosityでmTouchを試してみる。その11 ボタンの詳細設定

正直、mTouch でここまでネタが続くとは思っていなかったんですが、結構奥が深いですね。

さて、ここまでやってきた mTouch ですが、現状の設定ではボタンをホールドすると裏で定期的に動いているキャリブレーションが働いて、「ボタンの容量が定常状態である」と判断し、「ボタンはタッチされていない」と判定されてしまいます。これは内部で MTOUCH_BUTTON_PRESSTIMEOUT に定義された回数のカウンタ値で動作しているようです。

ということで、MCC のドキュメントから、ボタンの詳細設定について和訳してみます。元のドキュメントはmTouch® Button/Proximity Sensor Configurationです。

The Button or Proximity Sensor is an abstraction layer over the physical sensor which decouples the signal acquisition module and the decoding module. The benefit of this abstraction is that, regardless of what acquisition method is being used for the sensor, the Button or Proximity Sensor decoding works the same way.

ボタンあるいは近接センサは物理センサ上の抽象レイヤで、信号取得モジュールとデコードモジュールを分離するものである。この抽象化の利点は、センサに対する取得メソッドがどうであれ、ボタンあるいは近接センサのデコードが同じ方法で動作することである。
(ちょっと意味がわかりません…)

Common Button/Proximity Sensor Configuration

Interface Method

The mTouch Sensing Solutions library allows your application code to obtain button/proximity sensor status in two ways: through a callback function or through polling. There are examples of how to use these two methods in the "Step by Step Example" section.

mTouchセンシングソリューションライブラリは、アプリケーションにボタン/近接センサの状態を利用できる2つの方法を提供する: コールバック関数またはポーリングである。「ステップバイステップ例」セクションにはこの2つのメソッドの利用法がある。

Press/Activation Timeout

Press/Activation Timeout is a mechanism used to release a stuck proximity sensor. If a button/proximity sensor is pressed or activated for a number of consecutive counts, the button’s or proximity sensor's state will be reset. Each button/proximity sensor has its own time-out counter to track the press time. The underlying press time-out counter will increment each time the MTOUCH_Service_Mainloop() call is performed. The counts parameter acts as a threshold to each button’s or proximity sensor's press time-out counter. Upon the time-out’s counter exceeding the counts value, the button/proximity sensor state will be reset.

プレス/アクティベーションタイムアウトは、近接センサが固まった(動作しなくなった)のをリリースするためのメカニズムである。もしボタン/近接センサが一定の連続カウント時間押されあるいは活性化されることが続いたときに、ボタンあるいは近接センサの状態がリセットされる。それぞれのボタン/近接センサは押下時間を測定するための独自のタイムカウンタを持っている。タイムアウトカウンタは MTOUCH_Service_Mainloop() が呼ばれるたびに加算される。(MCC設定の)カウントパラメータはそれぞれのボタン/近接センサのタイムアウトカウンタのスレッショルドとして振る舞い、タイムアウトカウンタがこのカウント値を超えた場合には、ボタン/近接センサの状態がリセットされる。

Negative Capacitance Recovery

Enabling this feature allows the quick recovery of a button’s or proximity's baseline if negative capacitance is detected. The negative capacitance means the baseline value is greater than the reading for a button. The consecutive decodes parameter specifies the number of consecutive decoding cycles (MTOUCH_Service_Mainloop() call) with negative capacitance that the program has to detect before resetting the button/proximity state.

この機能を有効にすると、負の容量が検出されたときにボタンまたは近接のベースラインを速やかに復帰させることができる。負の容量とは、ボタンの読み値よりもベースラインの値が大きい状態を指す。連続的なデコードパラメータは、プログラムがボタン/近接状態のリセットをする前に検出しなくてはならない連続した負の容量のデコードサイクル(MTOUCH_Service_Mainloop()の呼び出し)の数を指定する。

Reading Filter

To minimize the impact of impulse noise, the sensor output goes through a low-pass filter. The output of the filter is the reading of the button/proximity sensor. A higher filter level setting provides an improved signal-to-noise ratio (SNR) under noisy conditions while increasing the total time for measurement, possibly resulting in increased power consumption and response time.

インパルスノイズの影響を最小化するため、センサ出力はローパスフィルタを通過する。フィルタの出力はボタン/近接センサの読み値である。より高いフィルタレベルが設定されると信号-ノイズ比(SNR)を改善するが、ノイズの多い環境下での測定時間が増加し、おそらく消費電力と応答時間が増大する。

Deviation Integration Filter

To help the sensitivity of the proximity sensor, a deviation integrator is used. A larger gain value results in more sensitivity.

近接センサの感度を改善するため、偏差値積分が使用される。ゲイン値を大きくすると、感度が増大する。
(よく意味がわかりません)

Median Filter

To gain a greater SNR for the proximity sensor, a median filter can be used after the reading filter. The value of the median filter window decides how much historical data will be stored. Similar to the reading filter, a higher value provides more noise immunity but longer response time.

近接センサでより大きなSNRを得るために、読み値フィルタのあとでメディアンフィルタを使用することができる。メディアンフィルタウィンドウの値は、いくつの過去データを保存するかを決定する。読み値フィルタと同様に、高い値はノイズ除去に優れるが応答時間が伸びる。

Debounce

To prevent multiple detect events for a single touch action, a debounce algorithm is used. The algorithm works in a similar manner to debounce on a mechanical pushbutton. The Count parameter defines the number of consecutive touches that exceed the detect threshold. Once the consecutive touch count is exceeded the touch is reported as valid. If any single touch does not exceed the touch threshold while the debounce algorithm is running then the consecutive touch count is reset to and the debounce algorithm will restart. The debounce algorithm is applied to both touch/press debounce and release debounce for buttons/proximity sensors.

シングルタッチ動作での複数のイベント検出を防ぐため、デバウンスアルゴリズムが使用される。アルゴリズムはメカ的プッシュボタンのでバウンスと同様の働きをする。カウントパラメータは検出閾値を超えた連続タッチの数を定義する。連続タッチのカウントが閾値を超えたら、タッチが有効とレポートされる。デバウンスアルゴリズムが動作しているときにシングルタッチがタッチ閾値を超えない場合には、連続タッチカウントはリセットされてデバウンスアルゴリズムが再起動される。デバウンスアルゴリズムはボタン/近接センサでのタッチ/プレスデバウンスとリリースデバウンスの両方に適用される。

Suspend / Disable

Individual buttons and proximity sensors can now be suspended or disabled by application code.
  • Use MTOUCH_Button_Disable() / MTOUCH_Proximity_Disable() to disable a button / proximity sensor. A disabled button/proximity sensor will stop its sensor scanning and baseline updating.
  • Use MTOUCH_Button_Suspend() / MTOUCH_Proximity_Suspend() to suspend a button/proximity sensor. A suspended button/proximity sensor will stop its sensor scanning but will scan periodically for baseline updating.
  • Use MTOUCH_Button_Resume() / MTOUCH_Proximity_Resume() to reactivate a disabled/suspended button / proximity sensor. The button/proximity sensor will continue with the existing sensor configuration.

個々のボタンと近接センサはアプリケーションコードからサスペンドあるいは無効にできる。(以下略)

Reburst

Reburst is used to improve the system response time while resolving detect state for both buttons and proximity sensors. When a sensor threshold is exceeded, multiple measurements are taken on that sensor to resolve the detect status. Sensors that are not part of the AKSGroup or may be disabled to reduce the processing time. Three modes for Reburst are used:
  • Reburst All : No sensor suspension takes place, all sensors reburst to determine detect status.
  • Reburst Unresolved : Reburst sensors only part of same AKS Group, all others suspended (reduced processing).
  • Reburst None : standard acquisition only.

リバーストはボタンと近接センサの両方で状態検出を解決する際のシステム応答時間を改善する。センサ閾値を超えた際に、状態検出のために当該センサに対して複数の計測がなされる。処理時間を削減するために、AKSグループに属さない、あるいは無効化されたセンサ。リバーストには3つのモードがある。(以下略 - というか文章が不明瞭)

Hysteresis

Hysteresis provides a band gap for detect thresholds when moving between detect and no detect states. It is expressed as a percentage of the detect threshold. Once a sensor goes into detect, the threshold level is reduced (by the hysteresis % value). This prevents sensor dither in and out of detect if the signal level is close to original threshold level.

ヒステリシスは検出およち非検出状態の間で移行する際の検出閾値のバンドギャップを提供する。(以下略)

Individual Button/Proximity Sensor Configuration

Name

This input field allows you to modify the name of the selected button/proximity sensor. This name can be used in the code to reference this button/proximity sensor.

(省略)

Sensor

This selection box allows you to select a hardware sensor to be associated with this button. A hardware sensor can be associated with multiple buttons/proximity sensors. For example, if you want to achieve proximity detection and touch detection on the same physical sensor, this sensor can be associated with a button and a proximity sensor.

この選択ボックスはこのボタンに関連付けるハードウェアセンサを選択する。ハードウェアセンサは複数のボタン/近接センサに関連付けることができる。たとえば、近接検出とタッチ検出を同じ物理センサで実現したいときには、このセンサをボタンと近接センサの両方に関連付けられる。

Threshold

The signal deviation is defined as the difference between a button’s reading and the baseline value. The threshold input field allows you to determine the press threshold value for signal deviation before the button will be in the pressed state. The release threshold is 50% of the press threshold so that there is a hysteresis to release the button.

(省略)

Deviation Scaling

The signal deviation will be scaled down from a signed 16-bit integer to a signed 8-bit. If the deviation after scaling down is greater than 127, then the value will be clipped to 127 (0x7F). The deviation scaling parameter controls how many bits will shift to the right. The smaller the value is, the more sensitive the button will get. The goal is to shift the scaled-down deviation to be a value between 64 and 127 when the button is pressed, in order to get the full dynamic range.

(省略)

AKS Group

AKS® stands for Adjacent Key Suppression. In designs where the sensors are placed close together or configured for high sensitivity, multiple buttons might report a detection simultaneously. To allow applications to determine the intended single touch, the mTouch library makes it possible to configure multiple buttons/proximity sensors in an AKS group. When a group of buttons/proximity sensors is in the same AKS group, only the first strongest button/proximity sensor will report detection. The button/proximity sensor continues to report detection until its deviation or delta falls below its detection threshold. As long as this sensor is in the pressed state, no other button/proximity sensors in the same AKS group will report detection even if another button/proximity sensor's deviation becomes stronger in the same AKS group.

AKSとは隣接キー抑制のこと。センサ同士が近い状態に配置され、あるいは高感度に設定された場合、複数のボタンが同時に検出されたことをレポートするかもしれない。(以下略)

Individual Hysteresis

The common hysteresis levels can be replaced for each button/proximity sensor by activating the individual hysteresis checkbox. Then a hysteresis level can be selected from the dropdown menu. Other sensors will be assigned the common hysteresis level unless they are also modified.

共通ヒステリシスレベルは個別のヒステリシスチェックボックを有効にすることでボタン/近接センサごとに上書きできる。(以下略)

という感じでしょうか。以下略の部分は必要な人は頑張ってください。

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

とりあえずなんとかできそうなアタリがついたところで、やってみたいと思います。

まずは mTouch CVD で AFA を有効にしたソースを生成します。このとき、TIMER4 や TIMER6 を使うので、プロジェクトに追加して適切に設定しておきます。
また TIMER2 の身代わりとなる TIMER0 も追加して、割り込みを有効にするように設定しておきます。

すると、mTouch 専用に 設定された tmr2.[ch] と、tmr0/4/6.[ch] が出力されているはずですなのでこれをいじります。同時に mtouch_sensor.[ch] もいじります。

まず最初に、生成されたファイルをコミットして差分が取れるようにしておきます。

次に tmr0.c を tmr2.c と同様の内容になっているかチェックします。すでに MCC で設定しているので、動作としては同じことができるはずです。もしも設定が違っていたら、データシートとにらみ合いながら修正します。

終わったら mtouch_sensor.c です。
tmr2.h をインクルードしているところを、tmr0.h に変更します。
TMR2_LoadPeriodRegister() は TMR0_Reload() に変更します。
TMR2 のところをすべて TMR0 に変更します。
また、T2CONbits.T2CKPS を T0CON1bits.T0CKPS に変更します。
diff -r d49f637e026f mcc_generated_files/interrupt_manager.c
--- a/mcc_generated_files/interrupt_manager.c Fri Mar 01 13:34:06 2019 +0900
+++ b/mcc_generated_files/interrupt_manager.c Sat Mar 02 08:50:47 2019 +0900
@@ -58,11 +58,7 @@
     }
     else if(INTCONbits.PEIE == 1)
     {
-        if(PIE1bits.TMR2IE == 1 && PIR1bits.TMR2IF == 1)
-        {
-            TMR2_ISR();
-        } 
-        else if(PIE1bits.TMR1IE == 1 && PIR1bits.TMR1IF == 1)
+        if(PIE1bits.TMR1IE == 1 && PIR1bits.TMR1IF == 1)
         {
             TMR1_ISR();
         } 
diff -r d49f637e026f mcc_generated_files/mtouch/mtouch_sensor.c
--- a/mcc_generated_files/mtouch/mtouch_sensor.c Fri Mar 01 13:34:06 2019 +0900
+++ b/mcc_generated_files/mtouch/mtouch_sensor.c Sat Mar 02 08:50:47 2019 +0900
@@ -37,7 +37,7 @@
 #include 
 #include 
 
-#include "../tmr2.h"
+#include "../tmr0.h"
 
 #include "mtouch_sensor.h"
 #include "mtouch_sensor_scan.h"
@@ -177,7 +177,7 @@
  */
 void MTOUCH_Sensor_Scan_Initialize(void)
 {
-    T2CONbits.T2CKPS = 0x0;
+    T0CON1bits.T0CKPS = 0x0;
 
     ADCON0 = (uint8_t)0;                            /* overwrite the ADC configuration for mTouch scan */
     ADCON1 = (uint8_t)( 0x1<<7 | 0x2<<4 | 0x0 );
@@ -311,9 +311,9 @@
     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();
+    TMR0_SetInterruptHandler(Sensor_Acq_ExecuteScan);  /* Use timer0 to schedule the scan */
+    TMR0_Reload(sample_period);
+    TMR0_StartTimer();
     
     sensor_globalFlags.interrupted = false;
     
@@ -329,7 +329,7 @@
     } while(sensor_globalFlags.packet_done == 0);
 
 
-    TMR2_StopTimer();
+    TMR0_StopTimer();
     ADCON0 = ADCON0_temp;       /* restore the previous ADC configuration */
     ADCON1 = ADCON1_temp;
     ADACT = ADACT_temp;
@@ -630,4 +630,4 @@
     {
        mtouch_sensor[name].oversampling =  (mtouch_sensor_packetcounter_t)(value);
     }
-}
\ No newline at end of file
+}
diff -r d49f637e026f mcc_generated_files/tmr2.c
--- a/mcc_generated_files/tmr2.c Fri Mar 01 13:34:06 2019 +0900
+++ b/mcc_generated_files/tmr2.c Sat Mar 02 08:50:47 2019 +0900
@@ -55,8 +55,6 @@
   Section: Global Variables Definitions
 */
 
-void (*TMR2_InterruptHandler)(void);
-
 /**
   Section: TMR2 APIs
 */
@@ -71,17 +69,11 @@
     // TMR2 0; 
     TMR2 = 0x00;
 
-    // Clearing IF flag before enabling the interrupt.
+    // Clearing IF flag.
     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;
+    // T2CKPS 1:64; T2OUTPS 1:1; TMR2ON on; 
+    T2CON = 0x07;
 }
 
 void TMR2_StartTimer(void)
@@ -116,28 +108,17 @@
    PR2 = periodVal;
 }
 
-void TMR2_ISR(void)
+bool TMR2_HasOverflowOccured(void)
 {
-
-    // clear the TMR2 interrupt flag
-    PIR1bits.TMR2IF = 0;
-
-    if(TMR2_InterruptHandler)
+    // check if  overflow has occurred by checking the TMRIF bit
+    bool status = PIR1bits.TMR2IF;
+    if(status)
     {
-        TMR2_InterruptHandler();
+        // Clearing IF flag.
+        PIR1bits.TMR2IF = 0;
     }
+    return status;
 }
-
-
-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()
-}
-
 /**
   End of File
 */
\ No newline at end of file
diff -r d49f637e026f mcc_generated_files/tmr2.h
--- a/mcc_generated_files/tmr2.h Fri Mar 01 13:34:06 2019 +0900
+++ b/mcc_generated_files/tmr2.h Sat Mar 02 08:50:47 2019 +0900
@@ -291,76 +291,38 @@
 
 /**
   @Summary
-    Timer Interrupt Service Routine
+    Boolean routine to poll or to check for the match flag on the fly.
 
   @Description
-    Timer Interrupt Service Routine is called by the Interrupt Manager.
+    This function is called to check for the timer match flag.
+    This function is used in timer polling method.
 
   @Preconditions
-    Initialize  the TMR2 module with interrupt before calling this isr.
+    Initialize  the TMR2 module before calling this routine.
 
   @Param
     None
 
   @Returns
-    None
-*/
-void TMR2_ISR(void);
-
-/**
-  @Summary
-    Set Timer Interrupt Handler
-
-  @Description
-    This sets the function to be called during the ISR
-
-  @Preconditions
-    Initialize  the TMR2 module with interrupt before calling this.
-
-  @Param
-    Address of function to be set
-
-  @Returns
-    None
-*/
- void TMR2_SetInterruptHandler(void (* InterruptHandler)(void));
-
-/**
-  @Summary
-    Timer Interrupt Handler
-
-  @Description
-    This is a function pointer to the function that will be called during the ISR
+    true - timer match has occurred.
+    false - timer match has not occurred.
 
-  @Preconditions
-    Initialize  the TMR2 module with interrupt before calling this isr.
-
-  @Param
-    None
-
-  @Returns
-    None
-*/
-extern void (*TMR2_InterruptHandler)(void);
-
-/**
-  @Summary
-    Default Timer Interrupt Handler
+  @Example
+    
+    while(1)
+    {
+        // check the match flag
+        if(TMR2_HasOverflowOccured())
+        {
+            // Do something else...
 
-  @Description
-    This is the default Interrupt Handler function
-
-  @Preconditions
-    Initialize  the TMR2 module with interrupt before calling this isr.
-
-  @Param
-    None
-
-  @Returns
-    None
+            // Reload the TMR2 value
+            TMR2_Reload();
+        }
+    }
+    
 */
-void TMR2_DefaultInterruptHandler(void);
-
+bool TMR2_HasOverflowOccured(void);
 
  #ifdef __cplusplus  // Provide C++ Compatibility
 
これだけでいいはずなので、コンパイルしてみます。
いくつか警告が出ていますが、これはエラーチェックを厳密にしているためのようで、変更前のプロジェクトでも同様の警告が出ていたので無視します。

で、結論ですが、ちゃんと動きました。オシロスコープでも、mTouch 波形はタイミングを調整されて出ているのが確認できました。

バイナリライブラリでなにか処理をされていたら手が出ないかなとも考えていたのですが、すべてCソースレベルで実現されているようです。Cソースながら中身はインラインアセンブラで組まれている関数もありましたが、問題ないようです。

ということで、mTouch で専有されているピリオドタイマを開放して、TIMER0 をピリオドタイマに割り当てることはできました。これによって TIMER2/4/6 を自分の目的に割り当てることもできるようになりました。

ChocolateyとScoop。

Windowsでアプリケーションをインストールするときに、pacmanとかrpm、あるいはaptのようなパッケージマネージャを使うと楽になるんじゃ、みたいな話で、Chocolateyというのもがあるのは知っていたんですが、Scoopというのもあるのを見つけました。

PowerShell 3 以降と .NETFramework 4.5 以降が必要なようですが、コマンドラインからアプリの管理ができるようです。

So What?には、なんでScoopなのさ、という疑問に答えるような問答があります。
もしもあなたが、
  • プログラマか開発者である。
  • PCのセットアップに、山ほどあるウェブサイトに行ってダウンロードしてクリックしまくるようなことをしたくない。
  • 特にGitようなツールの場合にはコマンドラインで快適に操作するほうがいい。
  • UNIXツールに馴染みがあってWindows上にももっとそういうのがあったらいいと思ってる。
  • apt-getやHomebrewを使ったときに「こりゃすげぇ」と思う。
ならば、Scoopはそういう人を念頭にデザインされている。

というようなことが書いてあります。
いろいろ制限もあるようですが、そういうのもあるということで。

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()
}
あたりで割り込み処理などを行っています。

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

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

SWDなら3線でいいとはいうものの。

 安価で優秀なデバッグプローブはないかしら、と探したら、 Raspberry Pi Debug Probe というのがあったんですが、これは3線がにゅるんと出てるだけです。 もちろんSWDなら3線を繋げばいいのではありますが、汎用的に考えるなら5x2のリボンケーブルコネクタが欲し...