NFC ではありません...
RFCとは
A/D コンバータの一種で、CR 時定数を利用することで抵抗器そのものを求める方式です。なんか通常の ADC と違ってワクワクしてきますね。
テクニカルマニュアルには「CR 発振方式の A/D 変換器 (R/F 変換器) です」と書かれていました。
この変換方式には、外付けでリファレンス抵抗、発振用キャパシタ、そして測定したい抵抗(センサ抵抗と呼ばれる)が必要です。
変換方式
AC モードと DC モードがありますが、今回は簡単のために DC モードをメインに解説していきます。個人的に結構理解に時間がかかりましたがとても面白いと思います。
このように回路接続をします。今回は SENBn は使いません。
動作は以下のようです。ちょと違うところがありますが、わかりやすさを優先します。具体的にはテクニカルマニュアルの波形を見て理解してください。これは参考程度ということでね。
- リファレンス抵抗とキャパシタを用いて CR 1次遅れ系波形を生成する。すなわち、抵抗上端にパルスを送る。
- VDD を印加している状態で、キャパシタ上端 (RFINn) が上限閾値に達したら、次に GND レベルを印加。すると RFINn は下降していき、下限閾値に達したら VDD を印加する。
- 規定回数パタパタさせたら、その発振に要した時間を記録する。正確には、ペリフェラル設定で指定したクロックのパタパタ回数を記録する。
- 続いて、上の時間と同じ期間だけセンサ抵抗とキャパシタで発振させる。このときのクロックパタパタ回数を記録する。
- パタパタ回数の比が、リファレンス抵抗とセンサ抵抗の比に関係する。
ちょっと遠回しな書き方かも知れませんが、要するにリファレンス抵抗の発振時間と同じだけセンサ抵抗で発振させ、その回数を比べることで抵抗が計算可能ということです。これでダイレクトに抵抗値が出てきます。図に表すとこんな感じかと思います。
計算方法
時定数はそのまま CR であることは既知と思います。これを用いると導出できますが、今回は細かくは触れずに式を示します。基準抵抗の発振回数(初期カウンタ値)を n, センサ抵抗の発振回数を m と置くと、式で表すと以下の感じです。
$$ \frac{R_\mathrm{sen}}{R_\mathrm{ref}} \simeq \frac{n}{m} $$
ここでイコールではなく近似とした理由は、最初のリファレンス発振回数によって値がずれるからです。深い意味はありません。
また、C に依存しないことも分かると思います(丁寧にやるならば、左辺は CR を使うと分かりやすいです。ですが2つの発振で用いているキャパシタは同一なため、約分されて消えます)。
コード
長いですが、そのまま貼り付けます。よくわからんコメントは察してください()
前回の記事で言った、レジスタマクロの自作はまた今度で...
#include <c17_regs.h>
// Addr of P0MODSEL is wrong!!
#include "clg.h"
#include "t16.h"
#include "t16b.h"
// ToDo
/* RFC
* SNDA
* LCD
*/
#define RFC0CLK REG(0x5440)
#define RFC0CTL REG(0x5442)
#define RFC0TRG REG(0x5444)
#define RFC0MC (*(volatile unsigned long *)0x5446)
#define RFC0MCL REG(0x5446)
#define RFC0MCH REG(0x5448)
#define RFC0TC (*(volatile unsigned long *)0x544a)
#define RFC0TCL REG(0x544a)
#define RFC0TCH REG(0x544c)
#define RFC0INTF REG(0x544e)
#define RFC0INTE REG(0x5450)
void clock_init(void) {
CLGOSC |= 1 << 2; // OSC3EN, 4MHz by default.
CLGOSC |= 1 << 1; // OSC1EN, 32,768Hz ext.
while( !(CLGINTF & (1 << 2)) );
while( !(CLGINTF & (1 << 1)) );
MSCPROT = 0x96;
CLGSCLK |= 2 << 0;
MSCPROT = 0;
}
// P00: SENB0
// P01: SENA0
// P02: REF0
// P03: RFIN0
void rfc_dc_init(void) {
P0MODSEL |= 0x0E; // Connect to Peripherals, RFC0 by default.
RFC0CLK = 1; // OSC1 / 1
RFC0INTE = 0x13; // Ref, SenA, OVF
ITCLV8 |= 1 << 0;
RFC0CTL |= 1;
}
#define N 32768
#define RFC_nCONV 3
typedef struct {
unsigned long int TCinit;
unsigned int nConversion;
unsigned long int M[RFC_nCONV];
} RFCInfo;
volatile static RFCInfo rfcInfo;
void rfc_start(void) {
RFC0MCL = (0x1000000L - N) & 0xFFFF;
RFC0MCH = (0x1000000L - N) >> 16;
RFC0TCL = 0;
RFC0TCH = 0;
RFC0TRG = 1; // Ref
}
float rfc_get_senA(float Rref) {
float m_ave = 0;
for(int i = 0; i < rfcInfo.nConversion; i++) {
m_ave += (float)rfcInfo.M[i] / rfcInfo.nConversion;
}
const float ratio = N / m_ave;
return Rref * ratio;
}
__attribute__((interrupt_handler))
void _vector20_handler(void) {
if( RFC0INTF & (1 << 0) ) { // Ref
RFC0INTF = 1 << 0;
rfcInfo.TCinit = RFC0TC;
RFC0TRG = 1 << 1; // SenA
}
if( RFC0INTF & (1 << 1) ) { // SenA
RFC0INTF = 1 << 1;
rfcInfo.M[rfcInfo.nConversion++] = RFC0MC;
if( rfcInfo.nConversion < RFC_nCONV ) {
RFC0MCL = 0;
RFC0MCH = 0;
RFC0TCL = rfcInfo.TCinit & 0xFFFF;
RFC0TCH = rfcInfo.TCinit >> 16;
RFC0TRG = 1 << 1;
}else{
volatile float RsenA = rfc_get_senA(98.2f);
(void)RsenA;
rfcInfo.nConversion = 0;
rfc_start();
}
}
if( RFC0INTF & (1 << 4) ) { // OVF
RFC0INTF = 1 << 4;
}
}
int main(void) {
clock_init();
rfc_dc_init();
rfc_start();
while(1) {
}
}
測定結果
3回測定の下で、n を変更していったときのグラフを示します。やっぱり 初期カウンタ値に大きく依存しますね。オーバーフローに注意。
精度に関して
難しい話になるので細かいことは言いませんでしたが、RFC のカウンタ幅は 24bit あります。やりようによっては逐次型 ADC の精度を優に超えます。
んで、この RFC の精度は色々なパラメータがあるんですよね。これを決めるのは主に以下と思います。
- カウンタ発振クロック
- 速ければ速いほど精度が出るが、カウンタがオーバーフローしやすくなる
- リファレンス発振回数 (
RFC0MC
初期値)- こちらも同上。この値が大きければ、センサ発振時間を長く取れて精度が増す
- キャパシタ容量
- 大きければ精度が増す。
- これらは被測定抵抗に合わせてうまくチューニングする必要あり。
〆
通常の ADC と違って面白い測定方式でした。
外付け回路が必要になりますが、単純な四則演算で被測定抵抗を測定することが出来ますし、計算負荷の少ないものだと思います。各社マイコンをこれまで見ていきましたが、かなり独特のものかと思います。
金がかかっている分、気合を入れて汁を吸うように紹介していきます!!