データシートにまさにこれという値は載っていないので計算する必要がある。VCC などによって出力インピーダンスが変わってくる。

たとえば Toshiba の TC74HCU04 の場合、「出力電圧」は以下のようになっている。

または

で出力インピーダンス(最小)がわかる。H レベルのときは 4.5V のときだと (4.5 - 4.18) / 4e-3 = 80,、6.0V のときだと (6.0 - 5.68) / 5.2e-3 = 61。5V で中間ぐらいとすると 71Ω ぐらいか。

  1. トップ
  2. tech
  3. 74HC04 の出力インピーダンス

OCXO と GPS 1PPS その3 | tech - 氾濫原 というのを3年前に書いてからやる気をうしなって放置してましたが、STM32F103 bluepill と組合せて、10MHz GPSDO をつくってます。今度は完成させたい。前よりは進捗していて、現状では 1mHz 未満ぐらい (0.1ppb) に誤差を抑えることができています。

  • 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});
}
  1. トップ
  2. tech
  3. PWM や DAC の出力範囲を変える

clean しても rebuild しても解決しない場合 inner class を利用していてかつ、inner class を import している可能性が考えられる。

この場合、inner class の import 文を削除し、完全修飾するようにすれば通るようになる。

エラーコードからは lombok が原因かはわからないし、それが innner class 絡みだということもわからない。lombok や inner class が原因か?ということに気付いたところでこの問題の9割は解決している。なのでこのエントリは実際には空虚で無意味な、躓いたということを知らせるだけのエントリである。

  1. トップ
  2. tech
  3. lombok のシンボルが見つからないと言われ続ける場合

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 があり、それぞれの直線の傾きがランダムに変化する

見ての通りで同じ出力電圧にする場合でも複数の値のとりかたがある。どれをどう選ぶかは制御のアルゴリズム次第で、結構やっかい。

必然的に相対値の調整が重要なはずなので、前回からの差分をうけとって、精密調整しつつ、範囲をはずれたら粗調整を動かすというのをヒステリシスにやる必要があると思う。

  1. トップ
  2. tech
  3. 12bit DAC 2つで 24bit DAC?

内部に温度センサを内蔵しており、追加の部品なしにある程度の温度は知ることができる。あまり正確とはいえないが相対的に温度の動きを見るには十分

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;
}
  1. トップ
  2. tech
  3. STM32F103 で内部温度センサを読み出す

  • Alternate function の再配置(remap)の確認 AFIO_MAPR
    • STM32CubeMX でペリフェラルのピン設定を確認してたりすると、知らぬまに再配置ピンを使うつもりでいることがある
  • GPIO の設定 (GPIOx_CRL GPIOx_CRH)
    • Alternate Function を出力にする場合、ピンの設定が Alternate push-pull などになっているかを確認する
  • 他の Alternate function が有効になっていないか
    • 特にデバッグ関係のピンはリセット直後にデフォルトで有効 (すなわち GPIO が無効) なので、かぶっている場合は自分でオフにする必要がある
      • SWG の場合 JTMS/SWDIO と JTCK/SWCLK しか使わないので、SWJ_CFG ビットを適切に設定して JTAG 用のピンを GPIO 用に使えるように再配置するのを習慣にしておくとハマりにくい
  • ペリフェラルの設定
    • 正しくプリスケーラーが設定されているかなど
  1. トップ
  2. tech
  3. STM32F103 で意図した出力がでないときのチェックリスト

$ vim
[1]    37147 killed     vim

みたいになってエラーも出ず、一切起動しない。ビルドした src/vim は起動するが、make install 後のバイナリでこの現象が起こる。ファイルサイズも異なるなるのでインストールプロセスを疑ったところ strip を OS 標準のものにしたら正常に起動するようになった。

  1. トップ
  2. tech
  3. GNU strip (GNU Binutils) 2.33.1 が入った環境で vim を make install すると起動しない

STM32F103C8 のやっすいボード (bluepill などと呼ばれているらしい) については数年前に書いた STM32F103 C8 T6 の安いボードでLチカ (platformio + mbed) が、これを題材にして ChibiOS の環境を用意する。

レポジトリとChibiOSの依存の追加

適当に git repo を作って ChibiOS を submodule に追加する。もちろんただダウンロードしてきてコピーしても良いがとにかくレポジトリ内に全コードを入れておく

mkdir foo
cd foo
git init
git submodule add git@github.com:ChibiOS/ChibiOS.git

demo から一番近い設定を持ってくる

できれば同じ MCU のボードだと変更点がすくなくてよい。Bluepill は Maplemini というののクローンの一種らしいので、そこからコピーしてくる。

cp -r ChibiOS/demos/STM32/RT-STM32F103-MAPLEMINI/* .

Makefileを書きかえる

CHIBIOS  := ./ChibiOS 

ボード定義を自分で作りなおす

手元にあるボードと、既にあるボード定義はだいたい一致していないので、修正する必要がある。

Makefile に以下のような行がある。

include $(CHIBIOS)/os/hal/boards/MAPLEMINI_STM32_F103/board.mk  

このディレクトリをまるっと自分のディレクトリにコピーする

cp -r ChibiOS/os/hal/boards/MAPLEMINI_STM32_F103 STM32F103_BLUEPILL

board.mk を編集する。まずはパスを変更したディレクトリにあわせるだけ。

# List of all the board related files.
BOARDSRC = STM32F103_BLUEPILL/board.c

# Required include directories
BOARDINC = STM32F103_BLUEPILL

# Shared variables
ALLCSRC += $(BOARDSRC)
ALLINC  += $(BOARDINC)

board.h を確認して、必要であれば編集する。

まずクロック設定があっているか確認する。実際の基板のものとあってないと何もかもがおかしくなる。後述する mcuconf.h と組合せてよくチェックする。

#define STM32_LSECLK            32768
#define STM32_HSECLK            8000000

VAL_GPIOACRL など、GPIO の初期設定を必要に応じて変更する。とはいえアプリケーションコード (main.c) で、palSetPadMode などで、あとから変えることもできるので必須ではない。

ここまででベースはおわり。make は通るはず

ほかのチェックポイント

  • cfg/mcuconf.h で各クロックのプリスケーラーなどが正しいか確認する
  • cfg/mcuconf.h で STM32_ST_USE_TIMER で指定したタイマーは OS によって予約されるので適切にする
  • cfg/mcuconf.h で利用する HAL ドライバーを有効にする。
  • cfg/halconf.h で必要なサブシステム設定を有効にする。

備考: STM32F103 での Alternate function の設定方法は?

入力として使う alternate function は、ピンに特別な設定は必要なく、PAL_MODE_INPUT などにして、ペリフェラル側の設定を有効にするだけで良い。

出力として使う alternate function は、ピンにもPAL_MODE_STM32_ALTERNATE_PUSHPULL を指定する必要がある。

(STM32F401 などとは設定方法が違う)

備考: 「これをChibiOSでやる方法がわからん」

ChibiOS の HAL ドライバのインターフェイスは MCU ごとに柔軟に設定できるようになっている。裏をかえすと、基本的に MCU ごとに設定方法を変えなければならない。

一番簡単なのは定義を読むことで、ChibiOS/os のディレクトリで git grep したりして、必要なソースコードを読むのがてっとりばやい。ChibiOS は幸い、あまり変なコードはなく素直に読みくだせるようになっている。

特殊なことをしないなら HAL ドライバを使ったほうが圧倒的に楽だが、もちろん利用せずに自力でレジスタを設定しても良い。データシートを読みながらペリフェラルを設定する場合は結局こうすることになる。その場合 mcuconf.h で利用するペリフェラルのドライバは無効にすること。

感想

できたものは https://github.com/cho45/STM32F103C8-ChibiOS

STM32 の開発環境で悩んでいて、ちょうどよく気にいるものがなかなくていろいろ試している。STM32 デバイスでの例が多く抽象化やビルドシステムもちょうどよく手に収まる感じで、今のところいい感じ。

ChibiOS は RTOS のライセンスがGPLであるのが若干やっかいだが、OSS ならまぁ GPL 採用すればいいので気にしないことにする。

  1. トップ
  2. tech
  3. STM32F103C8 で ChibiOS を使ってみる

以下をいれるととりあえずおさまる。ccls と相性が悪い??

let g:lsp_insert_text_enabled = 0
let g:lsp_text_edit_enabled = 0  

あとラベルが勝手に補完に含まれるのはinitialization_optionsになんか指定するとよいらしい?

	   au User lsp_setup call lsp#register_server({
		  \ 'name': 'ccls',
		  \ 'cmd': {server_info->['ccls']},
		  \ 'root_uri': {server_info->lsp#utils#path_to_uri(lsp#utils#find_nearest_parent_file_directory(lsp#utils#get_buffer_path(), 'compile_commands.json'))},
		  \ 'initialization_options': #{
		  \    cache: #{directory : '/tmp/ccls_cache'},
		  \    completion: #{detailedLabel: v:false}
		  \ },
		  \ 'whitelist': ['c', 'cpp', 'objc', 'objcpp', 'cc'],
		  \ })
https://github.com/prabirshrestha/vim-lsp/issues/328#issuecomment-555304328
  1. トップ
  2. tech
  3. ccls + vim-lsp で補完時に後続のwhitespaceが削除される

測定方法について

以下を根拠に測定する

測定前の知識

アマチュアの F3E の占有周波数帯幅の許容値は 40kHz (ただし 430-440MHz では 30kHz)。ただし事実上の帯域幅は 16kHz 程度になっていることが多い。

周波数変調は振幅が周波数偏移になるため、最大振幅でどれぐらい周波数偏移をするかを決めなければならない。アマチュアで通常使われてる狭帯域FMで、最大振幅時の最大周波数偏移は ±5kHz。

測定準備

標準変調度とは、通常、規定の最大周波数偏移許容値を100%としたものであり、最大周波数偏移の許容値が規定されていない場合は工事設計書の設計値(工事設計書に記載される値)の最大周波数偏移を100%としたものである。基準周波数偏移とは、試験機器の最大周波数偏移が規則で規定されていない場合、測定のための基準点の周波数偏移である。

で F3E では、「標準変調度又は基準周波数偏移(位)」は「正弦波1kHzで最大周波数偏移の70%」になる。測定時には「基準周波数偏移の入力から10dB増加(擬似音声)」

アマチュアのF3Eの場合「標準変調度とは (略) 工事設計書の設計値(工事設計書に記載される値)の最大周波数偏移を100%としたものである」が適用される。工事設計書の設計値の最大周波数偏移は通常±5kHzなので、つまり 1kHz 入力したときに、5 * 0.7 = 3.5kHz 周波数偏移するように入力ゲインを変更し、疑似音声はこのときのレベルに +10dB することになる。

前に書いた「特定の変調指数になるようにゲインを調整したい場合」にそって 1kHz で変調指数 3.5 になるようにして、疑似音声をそれの +10dB で出力できるように信号源のゲインを調整する。

今回は温度制限がかかるので、出力は 5W (37dBm) でやる。(本来は最大でやらなければならない)

1kHz で最大周波数偏位 3500Hz にあわせる。


(52013416 - 52006833) / 2 = 3291Hz

測定周波数

FM なので 50MHz 帯で測定することにする。別表第三十五 証明規則第2条第1項第12号に掲げる無線設備の試験方法によれば、50MHzは50〜54MHzと、2MHz以上の帯域があり、この場合の試験周波数は3波になる。

  • 下限周波数に 60kHz 加えた周波数
    • 50060000Hz
  • 中央の周波数
    • 52000000Hz
  • 上限周波数から60kHz減じた周波数
    • 53940000Hz

正直めんどい。同じことを3回やるだけなので、今回は省略して中央のみで行う。

帯域外領域

占有周波数帯幅の許容値が40kHz なので 帯域外領域は 2.5倍して ±100kHz。
無変調で測定する。

「30MHzを超え54MHz以下」「1Wを超え50W以下」の条件では「1mW以下であり、かつ、基本周波数の平均電力より60dB低い値」

スプリアス領域

「50μW以下又は基本周波数の搬送波電力より70dB低い値」-13dBm または搬送波電力-70dB以下ならよい

RBW=1kHz 9kHz〜150kHz

RBW=10kHz 150kHz〜30MHz

RBW=100kHz 30MHz〜520MHz

ref

https://www.hakodate-ct.ac.jp/~moriya/class/5SCE_Exp/text05-1.pdf
http://ja5fp.org/bessel.pdf

  1. トップ
  2. tech
  3. スペアナでスプリアス測定してみる3 KX3 FMの測定

最初に知ること

  • 周波数変調は、入力信号 (変調信号) の振幅が周波数の変化に変わる
    • 変調後の最大周波数偏移は入力の振幅によって決まる
    • 振幅の最大値のときの最大周波数偏移は場合によって異なる
      • 広帯域FM: ±75kHz (FM放送)
      • 広帯域FM: ±5kHz (アマチュア FM)
      • 狭帯域FM: ±2.5kHz (アマチュア FM-N)

「振幅の最大値のときの最大周波数偏移」と「ある入力に対する最大周波数偏移」で若干ややこしいので注意。単に「最大周波数偏移」といったときどっちのことを言っているか気をつける。

スペアナで見る FM スペクトラム

1kHz の正弦波で変調した場合、キャリアを中心にして、1kHz ごとにピークが立つ。各ピークのレベルについては後述

入力信号周波数を変化させるとこのように変化する。ピークの幅と、ピークの振幅それぞれが変化する。

変調指数

ある周波数の入力信号 を変調して、最大周波数偏移が だった場合、変調指数 は以下のようになる。

変調指数には以下のような特徴がある

  • 入力信号が大きくなるほど、高い変調指数になる (Δf は入力信号振幅が大きいほど大きくなるので)
  • 入力信号周波数が低いほど高い変調指数になる
  • 変調指数は側波帯のピークの数と対応している。変調指数 m + 1 番目の側波帯ピークまでで電力の99%が伝達される
  • 変調指数が高いほどSNRが良い

各ピークの振幅

出力のキャリア周波数の振幅及び、側波帯の各ピークの振幅は、変調指数をとる第1種ベッセル関数にしたがう。

縦(y)軸が振幅で、横(x)軸が変調指数。J(n,m) は変調指数 m のときの、側波帯ピークn番目(ただし0はキャリア)の振幅をあらわす。負の値が出るがスペアナで観測できるのは絶対値。

変調指数が一定の値、例えば 2.4 とかになるとキャリアの振幅は0になる。


入力=500Hz (最大周波数偏位=1200Hz)


入力=1000Hz (最大周波数偏位=2400Hz)


入力=1500Hz (最大周波数偏位=3600Hz)

どれも最初のキャリアゼロにあわせているので変調指数は2.4。結果、最大周波数偏位・入力ゲインが変わる。変調指数が同じなので、各ピークの大きさはどれも一致するはずだが、実際は出力バンドパスフィルタとかの影響で帯域が広くなると誤差が増える。

スペクトラムから最大周波数偏移を知りたい場合 (ゼロキャリアをさがす)

最大周波数偏移は入力信号振幅によるため、同じ振幅を持ち、キャリア周波数振幅が0となるように入力信号周波数を加減する。キャリア周波数振幅が最初に0となったとき m=2.4 なので、入力信号周波数 * m で最大周波数偏移を求めることができる。

ただしこの方法は、入力信号周波数を変化させても、無線機内部で振幅が変化しないことを前提としているので、イコライザやコンプレッサーなどがあり、周波数応答がフラットではない場合は成立しない。

スペクトラムから最大周波数偏移を知りたい場合 (瞬時周波数偏位を直接読む)

RBWを十分に大きくして、FM波全体を見るようにする。この状態で Max Hold / Min Hold して偏位の最大周波数と最小周波数をさがす。少し測定に時間がかかるのと、スペアナのスイープタイミングと入力信号周波数が互いに素になっていないと最悪いつまでたっても結果がでないのが欠点

マーカーを2つ使い、Max Hold / Min Hold それぞれのトレースで同ゲインにくるように調整する。そのときの周波数の値を読んで、差を計算する。これが最小と最大の周波数偏位になる。(51008700 - 51004100) = 4600 なので、最大周波数偏位はこの半分の ±2300Hz になる。この画像の場合は信号周波数が 1000Hz なので、変調指数は 2.3。

特定の変調指数になるようにゲインを調整したい場合

例えば 1000Hz で変調指数 3.5、つまり最大周波数偏位を3500Hzにしたい場合。これは変調指数から振幅を求めて、近くなるようにゲインを調整するのが一番簡単。


ベッセル関数を使って各ピークを求めて、それにあうようにする。

これでやって調整して、上記の方法で、最大周波数偏位を測定しなおすとより良い。

(51009800 - 51003050) / 2 = 3375 (最大周波数偏位)。3375 / 1000 = 3.375 (変調指数)。なかなかぴったりにはいかない…

  1. トップ
  2. tech
  3. スペアナでFMの信号を見る