OCXO と GPS 1PPS その3 | tech - 氾濫原 というのを3年前に書いてからやる気をうしなって放置してましたが、STM32F103 bluepill と組合せて、10MHz GPSDO をつくってます。今度は完成させたい。前よりは進捗していて、現状では 1mHz 未満ぐらい (0.1ppb) に誤差を抑えることができています。
PWM や DAC の出力範囲を変える
- PWM 信号は普通 0-5V などだが、1.5V-3.5V とかに変換したい
- その際、電源ソースを変更したい。
- PWMソースとは別の安定した電源から 1.5V-3.5V をとりだしたい
このようにMOSFETのレベルシフタと分圧抵抗でオフセットさせる。
出力を計算する
回路シミュレータ使って試行錯誤するのが一番簡単だが、一応自分で計算して、直接抵抗比を求められるようにしておく。
入力電圧 を図のような回路に入力したときの を求める。
テブナンの定理を使って計算していく。 と で分圧されている部分を内部抵抗のある電圧源に置き換える。このため、 を外した状態での の値 と、その内部抵抗 を求める。 は と で分圧された値なので、。内部抵抗を求める場合、既存の電圧源はショートして考えれば良いので、 と の並列合成抵抗 。
ここで は と によって分圧された出力なので、。
以下の通り
出力電圧範囲と中心電圧から抵抗値を求める。
入出力関数 を以下のようにする
入力電圧の最小・最大がそれぞれ のとき、出力電圧範囲 と中心電圧 はそれぞれ以下のようになる。
連立方程式を適当に計算すると、 を決めたときのほかの抵抗の値は以下のようになる。
入力電圧の最小値 が0の場合は
JS
JS のコードにするとこういう感じ
const V_center = 2.5;
const V_range = 1;
const V_SS = -5;
const V_DD = 5;
const V_min = 0;
const V_max = 5;
const R_1 = 11e3;
const R_2 = ((2*R_1*V_SS-2*R_1*V_DD)*V_range)/((V_min+V_max-2*V_SS)*V_range+(2*V_center-2*V_SS)*V_min+(2*V_SS-2*V_center)*V_max);
const R_3 = -((2*R_1*V_SS-2*R_1*V_DD)*V_range)/((V_min+V_max-2*V_DD)*V_range+(2*V_center-2*V_DD)*V_min+(2*V_DD-2*V_center)*V_max);
// re-calculate R_23 V_23 for reference
const R_23 = (R_2 * R_3) / (R_2 + R_3);
const V_23 = (V_DD - V_SS) * (R_3 / (R_2 + R_3)) + V_SS;
console.log({R_1, R_2, R_3, R_23, V_23});
for (let V_in of [0, 2.5, 5]) {
const V_out=(R_2*R_3*V_in+R_1*R_2*V_SS+R_1*R_3*V_DD)/(R_2*R_3+R_1*R_3+R_1*R_2);
console.log({V_in, V_out});
}
このエントリを参照するエントリ
関連エントリー
lombok のシンボルが見つからないと言われ続ける場合
clean しても rebuild しても解決しない場合 inner class を利用していてかつ、inner class を import している可能性が考えられる。
この場合、inner class の import 文を削除し、完全修飾するようにすれば通るようになる。
エラーコードからは lombok が原因かはわからないし、それが innner class 絡みだということもわからない。lombok や inner class が原因か?ということに気付いたところでこの問題の9割は解決している。なのでこのエントリは実際には空虚で無意味な、躓いたということを知らせるだけのエントリである。
関連エントリー
12bit DAC 2つで 24bit DAC?
MCP4922 などの使用例に書いてあるやつを考えてみる。http://akizukidenshi.com/download/MCP4922.pdf この方法で考えられる問題は
- 誤差の扱いかたが複雑になる
- 2つのDACの温度係数の違い
- DNL/INL の扱い
- 外部抵抗の正確さ
- 短期的なグリッチ
- 同時に値をセットできるわけではないため
- 粗いほうのDACを調整したときに生じる DNL による歪み
- 制御の仕方が面倒
低速かつ相対的に細かい値を扱うには使えるかもしれない。絶対値が重要な場合はかなり使うのが難しいと思う。
以下のとき1:1000
B がおおざっぱな調整、A が精密調整用の DAC を担うことになっている。A で B の 1000分の1 を追加で補正する。 理論上は 4096分の1にすれば最大の解像度を得られるが DNL (微分非直線性) があるためそうはいかない。
DNL が±0.75LSBであるため、1.5LSB分は余裕をもって二重に出力できないと、どうやっても出力できない電圧が発生する。
1:1000 のときの伝達関数をグラフ化すると以下のようになる。Y軸が出力電圧で、X軸は24bitの出力コード。実際は DNL があり、それぞれの直線の傾きがランダムに変化する
見ての通りで同じ出力電圧にする場合でも複数の値のとりかたがある。どれをどう選ぶかは制御のアルゴリズム次第で、結構やっかい。
必然的に相対値の調整が重要なはずなので、前回からの差分をうけとって、精密調整しつつ、範囲をはずれたら粗調整を動かすというのをヒステリシスにやる必要があると思う。
関連エントリー
STM32F103 で内部温度センサを読み出す
内部に温度センサを内蔵しており、追加の部品なしにある程度の温度は知ることができる。あまり正確とはいえないが相対的に温度の動きを見るには十分
ADC1 の 16ch に繋がっている。データシート通りだが以下の手順を踏む
- ADC1_IN16 を選択する
- 17.1μs のサンプル時間にする
- ADC_CR2 の TSVREFE ビットをセットする
- 温度センサおよびV_{REFINT}がパワーオンする
- ADON で変換する
- V_{SENSE} を読みだす
- 温度(℃) = (V_{25} - V_{SENSE}) / Avg_Slope + 25 を計算する
V_{25} と Avg_Slope は個別のデータシートに書いてある。STM32F103 の場合
- V_{25} : 1.34〜1.52 (typ=1.43) V
- Avg_Slope: 4.0〜4.6 (typ=4.3) mV/℃
17.1μs は、ADCクロックが12MHz なら 17.1e-6 / (1/12e6) = 205.2サイクル以上 、6MHz なら 102.6サイクル以上。どっちにしろサンプル時間レジスタは 0b111 (239.5サイクル) しかない。
static void setupADC(void) { rccEnableADC1(true); ADC1->CR1 = 0; ADC1->CR2 = ADC_CR2_ADON; ADC1->CR2 = ADC_CR2_ADON | ADC_CR2_RSTCAL; while ((ADC1->CR2 & ADC_CR2_RSTCAL) != 0) ; ADC1->CR2 = ADC_CR2_ADON | ADC_CR2_CAL; while ((ADC1->CR2 & ADC_CR2_CAL) != 0) ; ADC1->SMPR1 |= ( (0b111<<ADC_SMPR1_SMP17_Pos) | (0b111<<ADC_SMPR1_SMP16_Pos) ); } static uint16_t startADC(uint8_t channel) { ADC1->CR2 |= ADC_CR2_TSVREFE; const uint8_t count = 1; ADC1->SQR1 = (count-1) << ADC_SQR1_L_Pos; ADC1->SQR2 = 0; ADC1->SQR3 = (channel & 0b11111) << ADC_SQR3_SQ1_Pos; ADC1->CR2 |= ADC_CR2_SWSTART; // write same bit to start conversion ADC1->CR2 |= ADC_CR2_ADON; while ((ADC1->SR & ADC_SR_EOC) == 0) ; return ADC1->DR & 0xffff; } static float getTemp(void) { float adc = 0; const uint8_t len = 10; for (int i = 0; i < len; i++) { adc += (float)startADC(16); } adc /= len; const float V_sense = adc / (float)(1<<12) * 3.3; const float V_25 = 1.43; const float Avg_Slope = 4.3e-3; const float temp = (V_25 - V_sense) / Avg_Slope + 25; return temp; }