ESP32-S3 のみで構成する周波数カウンタ.正常にカウントできる上限はおよそ31MHz.

ESP32用の周波数カウンタのライブラリとしては FreqCountESP というのがあったので試してみたが,測定値が数10カウントくらいばらつく(ゲート制御をソフトウェアでやっている?)ので自分で作ることにした.コードは以下のとおり.(ただし,LCD表示コードは含みません.コードの動作確認はできていますがその保証やサポートはしませんのでご了承ください)
// // f_counter.ino // // Frequncy counter // ESP32-S3 // // December 8, 2022 T.Uebo JF3HZB // #include <driver/pcnt.h> #include "soc/pcnt_struct.h" // // GPIO 4 :カウンター入力 // GPIO 5 :TB出力,ゲート制御入力,ラッチ割り込み // (ピンは使用しているが何もつながない) // #define PULSE_INPUT_PIN 4 // カウンターパルス入力 #define GATE_CTRL_PIN 5 // ゲート信号入力 #define TIME_BASE 5 // Time Base 出力 #define LATCH_EN 5 // カウント値ラッチ割込み入力 #define F_RESO 10 //Hz #define F_ADJ 1.0f //0.9999999f #define LEDC_CHANNEL_0 0 //channel max 15 #define LEDC_TIMER_BIT 14 //max 14bit #define LEDC_BASE_FREQ (int)((float)F_RESO * 0.9f) #define LEDC_DUTY (int)((float)(1<<LEDC_TIMER_BIT) * 0.9f) int count_ovf = 0; int fcount; int frequency; pcnt_isr_handle_t user_isr_handle = NULL; static void IRAM_ATTR pcnt_intr_handler(void *arg); void IRAM_ATTR pcnt_intr_handler(void *arg) { PCNT.int_clr.val = BIT(PCNT_UNIT_0); //割り込みクリア if(PCNT.status_unit[PCNT_UNIT_0].cnt_thr_h_lim_lat_un) count_ovf++; } void latch_fcount() { int16_t cc; pcnt_get_counter_value(PCNT_UNIT_0, &cc); fcount = (int)cc + count_ovf * 32768; pcnt_counter_clear(PCNT_UNIT_0); count_ovf = 0; } void setup(void) { Serial.begin(115200); // Frequncy Counter, Unit0/ch0 pcnt_config_t pcnt_config; pcnt_config.pulse_gpio_num = PULSE_INPUT_PIN; pcnt_config.ctrl_gpio_num = GATE_CTRL_PIN; pcnt_config.lctrl_mode = PCNT_MODE_DISABLE; pcnt_config.hctrl_mode = PCNT_MODE_KEEP; pcnt_config.channel = PCNT_CHANNEL_0; pcnt_config.unit = PCNT_UNIT_0; pcnt_config.pos_mode = PCNT_COUNT_INC; pcnt_config.neg_mode = PCNT_COUNT_DIS; pcnt_config.counter_h_lim = 32767; pcnt_config.counter_l_lim = -32768; pcnt_unit_config(&pcnt_config); pcnt_event_enable(PCNT_UNIT_0, PCNT_EVT_H_LIM); //pcnt_event_enable(PCNT_UNIT_0, PCNT_EVT_L_LIM); pcnt_counter_pause(PCNT_UNIT_0); pcnt_counter_clear(PCNT_UNIT_0); pcnt_counter_resume(PCNT_UNIT_0); //Start // オーバーフロー割込み許可 pcnt_isr_register(pcnt_intr_handler, NULL, 0, &user_isr_handle); pcnt_intr_enable(PCNT_UNIT_0); // タイムベース信号用PWM出力の設定 ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_BIT); ledcAttachPin(TIME_BASE, LEDC_CHANNEL_0); ledcWrite(LEDC_CHANNEL_0, LEDC_DUTY); attachInterrupt(LATCH_EN, latch_fcount, FALLING); } void loop(void) { frequency=(int)( (double)fcount * F_ADJ ) * F_RESO; Serial.println(frequency); }
GPIO4に信号を入力する.GPIO5はTime base 信号出力,ゲート信号入力,ラッチトリガに使用している.GPIO5はNCにしておくこと.
カウンタ,タイマ,PWMモジュールの初期化が大部分で,あとは割込みでカウンタのオーバーフローの計数とゲートが閉じたあとのカウント値の算出をしている.
Time Base信号として ledc を使ってPWM信号を生成しているが,周波数カウンタとして必要な精度では設定できない(このような用途は想定されていないのだろう).どれくらいの誤差が発生するかは調べていない.ledc に拠らずPWMモジュールにバインドされているTimerのレジスタに直接分周比を書けば正確な設定ができると思う.が基準発振がCPUクロックなのでそもそも誤差があるだろうし,面倒なのでF_ADJ という定数をかけて合わせ込むことにした.
F_RESOを変更すれば一応分解能を変えられるが,変えると誤差も変わるだろうし対応が面倒なのでラジオ組み込み用なら10Hz固定でいいだろう.
計測用にするなら,PWMをなくして(ledc 関連のコードを削除),GPIO5 に外部から 高精度のTime Base 信号を入れる方がいい.
また,カウントの上限が31MHz程度だが,パルスカウンタを4個持っていてそれぞれに入力が2chあるので,それらにパルスを振り分けてカウントさせてやれば分解能を落とさずに上限周波数を上げられる.