こんにちは。Yukiです。
今回は、CH32V003J4M6でPWM出力 & 周波数測定してみたいと思います。
やりたいこと
今回、Amazonのレーザーモジュールを友達から貸していただけたので、これを使って遊んでみたいなと思ったからです。
そもそもレーザーモジュールってPWM出力(LEDみたいに)できるのかな?とか色々思ったのでやってみたいと思います。
必要なもの(今回使ったもの)
PWM出力(レーザー出力)側
| 品物 | 個数 | 参考URL | |
| 1 | レーザーモジュール | 1 | |
| 2 | CH32V003J4M6 | 1 | https://akizukidenshi.com/catalog/g/g118062/ |
| 3 | 2SC1815 | 1 | https://akizukidenshi.com/catalog/g/g106477/ |
| 4 | 1kΩ抵抗 | 1 | https://akizukidenshi.com/catalog/g/g125102/ |
| 5 | 10kΩ抵抗 | 1 | https://akizukidenshi.com/catalog/g/g125103/ |
周波数検知(受信)側
| 品物 | 個数 | 参考URL | |
| 1 | CH32V003J4M6 | 1 | https://akizukidenshi.com/catalog/g/g118062/ |
| 2 | フォトトランジスタ | 1 | https://akizukidenshi.com/catalog/g/g102325/ |
| 3 | 100kΩ 半可変抵抗 | 1 | https://akizukidenshi.com/catalog/g/g103283/ |
| 4 | 1kΩ抵抗 | 1 | https://akizukidenshi.com/catalog/g/g125104/ |
回路図

レーザーモジュールの内部に電流制限とかの回路が入っているので、抵抗とかはつけなくて良い(はず)です。適当に5Vを与えれば光ります。
友達から借りたもので、定格とか一切わからない(5Vということだけ知っている)ので、一応大電流が流れても大丈夫なようにトランジスタをつけています。もしかしたらなくても大丈夫かも(未検証)
まずはPWM出力から
今回は時短で、過去の自分が作ったライブラリを使用します。
MounRiver Studioであれば、この2つのファイル(Mylib_GPIO.h, Mylib_GPIO.c)を読み込ませてください。(Arudino感覚で使えるようにしています)
まあまずソースコードから貼り付けます。
#include <ch32v00x.h>
#include "Mylib_GPIO.h"
//Timer等で使う定数
#define HCLK_FREQ 48000000
//Laserを接続しているピン
#define LASER_OUT PC4
//a 動作クロックを48MHzにする
void clock_48MHz(void);
//TIM1をPWMで動作させる
void TIM1_PWM_init(void);
//TIM1_CH4をPWMとして出力するための初期設定
void TIM1_CH4_PWM_init(void);
//TIM1_PWMのプリスケーラーとオートプリロード値を設定
//この設定でPWM周波数が決まる
//例:systemClock 48MHz | プリスケーラ 0 | オートプリロード 1000
//SystemClock / (プリスケーラ値 + 1) = 48MHz ←これがTIMER動作の周波数
//TIMER動作周波数 / オートプリロード値 = 48khz ←これがPWM周波数
void TIM1_PWM_PRE_ATRLR_set(uint32_t psc_val, uint32_t atrl_val);
//PWMのFreq(周波数)設定をするため
//PWMは分解能が1000になる(周波数のきりが良いので1024ではない)ので注意
void TIM1_PWM_Freq_set(int freq);
//PWM1_CH4をPWM Duty比を設定
//Autoプリロード ÷ duty = duty比になる
void TIM1_CH4_PWM_duty(uint16_t duty);
//PWM1のカウント有効(これをONにしないと動作しない)
void TIM1_count_enable(void);
int main(void){
clock_48MHz();
//GPIOCを有効にする
GPIOC_init();
//TIM1を有効 & PWM出力にする
TIM1_PWM_init();
//PWM CH4を使うための設定
TIM1_CH4_PWM_init();
//PWMの周波数を1500Hzにする
TIM1_PWM_Freq_set(1500);
pinMode(LASER_OUT, F_OUTPUT);
TIM1_count_enable();
while(1){
//PWMのDuty比を50%に
TIM1_CH4_PWM_duty(500);
}
}
// Systemクロックを48MHzにする
void clock_48MHz(void){
//Clock設定
// Clock設定 PLL駆動
// ADCクロック:未定
// SYSCLK:48MHz
// HCLK 48MHz
// Core System Timer 48MHz
//PLL有効
RCC->CTLR |= RCC_PLLON;
//PLL安定動作まで待機
while((RCC->CTLR & RCC_PLLRDY) == 0);
//ADCプリスケーラ 16 (ADCPRE)
RCC->CFGR0 = bit_replace(RCC->CFGR0, 0b11100, 5, 11);
//Clockソース PLLに変更 (SW)
RCC->CFGR0 = bit_replace(RCC->CFGR0, 0b10, 2, 0);
//HCLKプリスケーラ 0 (HPRE)
RCC->CFGR0 = bit_replace(RCC->CFGR0, 0b0000, 4, 4);
}
//TIM1をPWMで動作させる
void TIM1_PWM_init(void){
//TIM1 有効
RCC->APB2PCENR |= RCC_TIM1EN;
//Update Event Generation有効
TIM1->SWEVGR |= TIM_UG;
//TIM1 CHメイン出力有効
TIM1->BDTR |= TIM_MOE;
}
//TIM1_CH4をPWMとして出力するための初期設定
void TIM1_CH4_PWM_init(void){
//TIM1_CH4 (PC4) 出力許可
TIM1->CCER |= TIM_CC4E;
//TIM1_CH4 PWMモード1
TIM1->CHCTLR2 = bit_replace(TIM1->CHCTLR2, 0b110, 3, 12);
//TIM1_CH4 プリロード有効
TIM1->CHCTLR2 |= TIM_OC4PE;
//Autoプリロード有効
TIM1->CTLR1 |= TIM_ARPE;
}
//TIM1_PWMのプリスケーラーとオートプリロード値を設定
//a この設定でPWM周波数が決まる
//a 例:systemClock 48MHz | プリスケーラ 0 | オートプリロード 1000
//SystemClock / (プリスケーラ値 + 1) = 48MHz ←これがTIMER動作の周波数
//TIMER動作周波数 / オートプリロード値 = 48khz ←これがPWM周波数
void TIM1_PWM_PRE_ATRLR_set(uint32_t psc_val, uint32_t atrl_val){
TIM1->PSC = psc_val;
TIM1->ATRLR = atrl_val;
}
//PWMのFreq(周波数)設定をするため
void TIM1_PWM_Freq_set(int freq){
TIM1_PWM_PRE_ATRLR_set(HCLK_FREQ / 1000 / freq, 1000);
}
//PWM1_CH4をPWM Duty比を設定
// autoプリロード ÷ duty = duty比になる
void TIM1_CH4_PWM_duty(uint16_t duty){
TIM1->CH4CVR = duty;
}
//PWM1のカウント有効(これをONにしないと実行できない)
void TIM1_count_enable(void){
//TIM1 カウント有効
TIM1->CTLR1 |= TIM_CEN;
}
いろいろカスタマイズしやすいようにいろんな関数に分けていますが、1つにしても問題ありません。
(ぶっちゃけTIM1カウント有効とか1行しかないので、逆に打ちこむ文字数増えてますしw)
めっちゃ簡単に解説
まず前提条件としてですが、CH32V003J4M6のタイマーは
- TIM1 (Advanced-control Timer)
- TIM2 (General-Purpose Timer)
- (SysTick)
の三種類しかありません。なので3つ以上周波数が異なるPWMを生成するのは難しいです。
あと、TIM1はAdvancedとついていることから高性能タイマーです。
TIM2は一般的なタイマーですね。(といってもPIC12Fとかと比べると滅茶苦茶高機能だけど)
今回は出力ピンがTIM1_CH4なので、TIM1で設定します。
この辺は、使うピンで使い分けてください。
//TIM1をPWMで動作させる
void TIM1_PWM_init(void){
//TIM1 有効
RCC->APB2PCENR |= RCC_TIM1EN;
//Update Event Generation有効
TIM1->SWEVGR |= TIM_UG;
//TIM1 CHメイン出力有効
TIM1->BDTR |= TIM_MOE;
}TIM1をPWMで動作させるための簡単な処理をしています。
まずはRCCレジスタを操作して、使用するペリフェラルを選択します。今回はTIM1を有効にしていますね。
TIM_UGはたしか、PWMの波形が変わるときのイベント設定だった気がします。
TIM1->BDTR |= TIM_MOE; はTIM1のCH出力有効です。これが大元なので、これをONにしないとTIM_CHxから信号は出てきません。
//TIM1_CH4をPWMとして出力するための初期設定
void TIM1_CH4_PWM_init(void){
//TIM1_CH4 (PC4) 出力許可
TIM1->CCER |= TIM_CC4E;
//TIM1_CH4 PWMモード1
TIM1->CHCTLR2 = bit_replace(TIM1->CHCTLR2, 0b110, 3, 12);
//TIM1_CH4 プリロード有効
TIM1->CHCTLR2 |= TIM_OC4PE;
//Autoプリロード有効
TIM1->CTLR1 |= TIM_ARPE;
}この関数はTIM1_CH4をPWMとして設定するために使っています。
TIM_CC4Eを1にすることで、TIM1_CH4からの出力が許可されます。今回だと、TIM1_CH4はPC4につながっているので、PC4からPWM波が出てきます。
次にTIM1_CH4をPWMモード1にしてあげます。
TIM1->CHCTLR2 = bit_replace(TIM1->CHCTLR2, 0b110, 3, 12);
bit_replaseは自分で作った関数で、0b110 を 12ビットシフトして突っ込む的な関数です。
(OR演算だけだと1が消せない AND演算だと0が消せないですが、それら関係なく、そのビットだけ書き換えられる的な関数です)
なんでややこしいですけど、つまり 0bx110 xxxx xxxx xxxx こういうことですね。(xは不定)
次にCH4のプリロードです。これをやっておくと、Duty比を変えたときに、次の波形更新時にDuty比が変わるようになります。
下のTIM_ARPEも同じようなものです。(こちらはAutoリロードレジスタを操作した場合に動作する)
//TIM1_PWMのプリスケーラーとオートプリロード値を設定
//a この設定でPWM周波数が決まる
//a 例:systemClock 48MHz | プリスケーラ 0 | オートプリロード 1000
//SystemClock / (プリスケーラ値 + 1) = 48MHz ←これがTIMER動作の周波数
//TIMER動作周波数 / オートプリロード値 = 48khz ←これがPWM周波数
void TIM1_PWM_PRE_ATRLR_set(uint32_t psc_val, uint32_t atrl_val){
TIM1->PSC = psc_val;
TIM1->ATRLR = atrl_val;
}
//PWMのFreq(周波数)設定をするため
void TIM1_PWM_Freq_set(int freq){
TIM1_PWM_PRE_ATRLR_set(HCLK_FREQ / 1000 / freq, 1000);
}//a ってやってるのは、MounRiver Studioが、コメントのときに日本語を先頭にするとバグるのでそれの対策です。あんまり気にしないでください。
この関数はTIM1のプリスケーラ(入力クロックを何分周するか)とオートプリロード(どこをカウンタの最大値にするか 最大値になったら0に戻る)の設定をしています。
下のTIM1_PWM_Freq_set関数は、オートプリロード値を固定した上で、特定の周波数を出す場合に使います。簡易的にPWM周波数を変えたいので作りました。
(ただ、freqに2000とかを入れると、なぜかDuty比が無条件にめちゃくちゃ小さくなります。原因不明です。レジスタ操作の順番が良くないか、エラッタですかね?)
//PWM1_CH4をPWM Duty比を設定
// autoプリロード ÷ duty = duty比になる
void TIM1_CH4_PWM_duty(uint16_t duty){
TIM1->CH4CVR = duty;
}PWM1_CH4のDuty比を設定します。上のTIM1_PWM_Freq_set関数を使った場合、MAX1000になります。なのでDuty比50%だと 500を入れれば良いことになりますね。(もしかしたらMAX 999かも。まあ結果は変わりません(爆))
//PWM1のカウント有効(これをONにしないと実行できない)
void TIM1_count_enable(void){
//TIM1 カウント有効
TIM1->CTLR1 |= TIM_CEN;
}最後にPWM1のカウント有効レジスタですね。これを実行した瞬間に、TIM1のカウントが始まり、PWM出力が開始されます。
実際の動作の様子



100Hzで発振させてみました。大丈夫そうですね。
ちなみに、点滅しているLEDとかは、振ると、光の跡が点線みたいになるのでわかります。
次に周波数を測定してみる – 回路図
CH32V003マイコンで周波数を測定してみます。

R3ですが、100kΩではなく1kΩです。(おそらく1kΩ + 可変抵抗の20kΩくらいだと思います)
フォトトランジスタは、可視光によく反応するタイプを選んでください。(赤外線用のタイプを買ってしまうと、反応が悪かったり悪くなかったりします)
ソースコード
このプログラムもCH32V003用Mylibを使用しています。
PWM用のところに貼ってあるので、そこから使用してください。
/* Project : Test_Ichijiteki
* Date: 2025/04/20
* User: Yuki (denshi-1996)
*/
#include <ch32v00x.h>
#include "Mylib_GPIO.h"
//Timer等で使う定数
#define HCLK_FREQ 48000000
#define PHOTO_IN PC2 //PhotoSensor(フォトダイオード)の接続端子
#define TX PD6 //UARTのTX端子
//Systemクロックを48MHzにする
void clock_48MHz(void);
void UART1_init(void);
void UART1_write(char data);
void UART1_print(char moji[]);
void UART1_num_print(int num);
//Time計測に使う
void SysTick_init(void);
//TIM2をカウンタとして使う
void TIM2_PWM_Input_init(void);
//Portに入力されたことから、周波数を測定する
//a 引数:Port 使用するポート | 戻り値:周波数(-1はタイムアウト)
//a この関数は立ち上がりパルスを読み取ったあとに関数を実行すること
int Freq_calc(int port);
int main(void){
clock_48MHz();
GPIOA_init();
GPIOC_init();
GPIOD_init();
AFIO_init();
pinMode(PHOTO_IN, INPUT); //input設定
pinMode(TX, F_OUTPUT); //float(ペリフェラル接続)
//TXをPD6としてリマップ
AFIO->PCFR1 |= AFIO_PCFR1_USART1_HIGH_BIT_REMAP;
UART1_init();
SysTick_init();
//TIM2のRemap PC2をCH2に割当
AFIO->PCFR1 &= ~AFIO_PCFR1_TIM2_REMAP_1;
AFIO->PCFR1 |= AFIO_PCFR1_TIM2_REMAP_0;
//TIM2_PWM_Input_init();
//int i[100];
while(1){
//Rise(立ち上がり)フラグが来たら
if(digitalRead(PHOTO_IN) == 1){
UART1_num_print(Freq_calc(PHOTO_IN));
UART1_print("\r\n"); //CRLF (改行)
}
}
}
// system(動作)クロックを48MHzにする
void clock_48MHz(void){
//clock(クロック)設定
// clock設定 PLL駆動
// ADCクロック:未定
// SYSCLK:48MHz
// HCLK 48MHz
// Core System Timer 48MHz
//PLL有効
RCC->CTLR |= RCC_PLLON;
//PLL安定動作まで待機
while((RCC->CTLR & RCC_PLLRDY) == 0);
//ADCプリスケーラ 16 (ADCPRE)
RCC->CFGR0 = bit_replace(RCC->CFGR0, 0b11100, 5, 11);
//clockソース PLLに変更 (SW)
RCC->CFGR0 = bit_replace(RCC->CFGR0, 0b10, 2, 0);
//HCLKプリスケーラ 0 (HPRE)
RCC->CFGR0 = bit_replace(RCC->CFGR0, 0b0000, 4, 4);
}
//UART1 初期化関数 (115200bps@48MHzで初期化)
void UART1_init(void){
//USART1有効
RCC->APB2PCENR |= RCC_USART1EN;
//USART1 送信(TE)有効
USART1->CTLR1 |= USART_CTLR1_TE;
//48MHzから115200bpsに変換
USART1->BRR = (26 << 4);
//USART1有効
USART1->CTLR1 |= USART_CTLR1_UE;
}
//UART1にて、8bit分のみ送信する関数
//char data: 1文字(または8bitデータ)
void UART1_write(char data){
USART1->DATAR = data;
//TX(送信)が終わるまで待機
USART1->STATR &= ~USART_STATR_TC;
while((USART1->STATR & USART_STATR_TC) == 0);
}
//UART1にて、文字列を送信する関数
//char moji[] : 文字列(\0をつけること)
void UART1_print(char moji[]){
//NULL文字が現れるまで繰り返す
for(int i = 0; moji[i] != '\0'; i++){
UART1_write(moji[i]);
}
}
//UART1にて、数字列を文字列に変換して送信する関数
//int num : 数字 (int)
void UART1_num_print(int num){
int num_array[15],i = 0;
if(num < 0){
num *= -1;
UART1_write('-');
}
if(num == 0){
UART1_write('0');
return;
}
while(num > 0){
num_array[i] = num % 10;
num /= 10;
i++;
}
for(int j = i - 1;j >= 0;j--){
UART1_write(num_array[j] + '0');
}
}
//Time計測に使う
void SysTick_init(void){
//STRE オートリロード有効 (最大値になると0にもどる)
SysTick->CTLR |= (1 << 3);
//HCLKをそのまま入力として使う
//HCLK/8 0にするとHCLK/8になる
SysTick->CTLR |= (1 << 2);
//SysTickクロック開始
SysTick->CTLR |= (1 << 0);
}
//TIM2をカウンタとして使う
void TIM2_PWM_Input_init(void){
RCC->APB1PCENR |= RCC_APB1Periph_TIM2; //Timer2有効
//CC2Sを0b10に設定 IC2をTI1にマッピング
TIM2->CHCTLR1 &= ~TIM_CC2S_1;
TIM2->CHCTLR1 |= TIM_CC2S_0;
//TIM2 CC2有効
TIM2->CCER |= TIM_CC2E;
TIM2->PSC = 0;
TIM2->CTLR1 |= TIM_CEN; //counter有効
}
//Portに入力されたことから、周波数を測定する (約50Hz~)
//a 引数:Port 使用するポート | 戻り値:周波数(-1はタイムアウト)
//a この関数は立ち上がりパルスを読み取ったあとに関数を実行すること
int Freq_calc(int port){
SysTick->CNT = 0; //Countを0に
//PLUSEパルスが10回くるまで待つ
for(int pluse = 0; pluse < 9;pluse++){
//LOWになるまで待つ
while(digitalRead(port) == 1){
if(SysTick->CNT > 9150000){
return -1;
}
}
//HIGHになるまで待つ
while(digitalRead(port) == 0){
if(SysTick->CNT > 9150000){
return -1;
}
}
}
//LOWになるまで待つ
while(digitalRead(port) == 1){
if(SysTick->CNT > 9150000){
return -1;
}
}
long count = SysTick->CNT;
return (10 * HCLK_FREQ / count);
//return count;
}
プログラム 簡単に解説
また簡単に解説というか覚書しておきます。
// system(動作)クロックを48MHzにする
void clock_48MHz(void){
//clock(クロック)設定
// clock設定 PLL駆動
// ADCクロック:未定
// SYSCLK:48MHz
// HCLK 48MHz
// Core System Timer 48MHz
//PLL有効
RCC->CTLR |= RCC_PLLON;
//PLL安定動作まで待機
while((RCC->CTLR & RCC_PLLRDY) == 0);
//ADCプリスケーラ 16 (ADCPRE)
RCC->CFGR0 = bit_replace(RCC->CFGR0, 0b11100, 5, 11);
//clockソース PLLに変更 (SW)
RCC->CFGR0 = bit_replace(RCC->CFGR0, 0b10, 2, 0);
//HCLKプリスケーラ 0 (HPRE)
RCC->CFGR0 = bit_replace(RCC->CFGR0, 0b0000, 4, 4);
}クロックを簡単に48MHzにするための関数です。
実行するだけで48MHz駆動になります。
//UART1 初期化関数 (115200bps@48MHzで初期化)
void UART1_init(void){
//USART1有効
RCC->APB2PCENR |= RCC_USART1EN;
//USART1 送信(TE)有効
USART1->CTLR1 |= USART_CTLR1_TE;
//48MHzから115200bpsに変換
USART1->BRR = (26 << 4);
//USART1有効
USART1->CTLR1 |= USART_CTLR1_UE;
}次にUART1初期化の関数です。
UART1はmain関数内で、TXをPD6としてリマップしているので、今回はTX→PD6としています。
ちなみにですが、このプログラムだと、115200bpsでの通信になります。
//UART1にて、8bit分のみ送信する関数
//char data: 1文字(または8bitデータ)
void UART1_write(char data){
USART1->DATAR = data;
//TX(送信)が終わるまで待機
USART1->STATR &= ~USART_STATR_TC;
while((USART1->STATR & USART_STATR_TC) == 0);
}
//UART1にて、文字列を送信する関数
//char moji[] : 文字列(\0をつけること)
void UART1_print(char moji[]){
//NULL文字が現れるまで繰り返す
for(int i = 0; moji[i] != '\0'; i++){
UART1_write(moji[i]);
}
}
//UART1にて、数字列を文字列に変換して送信する関数
//int num : 数字 (int)
void UART1_num_print(int num){
int num_array[15],i = 0;
if(num < 0){
num *= -1;
UART1_write('-');
}
if(num == 0){
UART1_write('0');
return;
}
while(num > 0){
num_array[i] = num % 10;
num /= 10;
i++;
}
for(int j = i - 1;j >= 0;j--){
UART1_write(num_array[j] + '0');
}
}UART1にてデータを送信する関数系です。
基本的には、UART1_write関数が1byteのみ送信する関数になります。例えばですがUART1_write(‘a’)とかってやれば、 a が送信されます。
次に、UART1_print関数は、文字列を送信できる関数になります。例えばですが、UART1_print(“Hello World\r\n”); とすれば、 Hello World (改行) が送信されます。
最後に、UART1_num_print関数です。
数字(int , long型)を送信できます。レジスタ値なども数字なので、10進数として送信できます。
//Time計測に使う
void SysTick_init(void){
//STRE オートリロード有効 (最大値になると0にもどる)
SysTick->CTLR |= (1 << 3);
//HCLKをそのまま入力として使う
//HCLK/8 0にするとHCLK/8になる
SysTick->CTLR |= (1 << 2);
//SysTickクロック開始
SysTick->CTLR |= (1 << 0);
}次に、SysTick関数です。CH32V003マイコンは、そもそも
- TIM1 (Advanced-control Timer)
- TIM2 (General-Purpose Timer)
- SysTick
の3種類しかタイマー(カウンタ)がないので、今回はSysTickを使って時間計算させようというわけです。
ちなみにですが、PWM出力や、割り込みなどをさせたいということであれば、タイマー(TIM)は残しておいたほうが後々幸せになれます。
今回は、使用するクロックをHCLKとしています。(つまり 1 / 48M = 20.8nsくらいで1カウントされます)
//Portに入力されたことから、周波数を測定する (約50Hz~)
//a 引数:Port 使用するポート | 戻り値:周波数(-1はタイムアウト)
//a この関数は立ち上がりパルスを読み取ったあとに関数を実行すること
int Freq_calc(int port){
SysTick->CNT = 0; //Countを0に
//PLUSEパルスが10回くるまで待つ
for(int pluse = 0; pluse < 9;pluse++){
//LOWになるまで待つ
while(digitalRead(port) == 1){
if(SysTick->CNT > 9150000){
return -1;
}
}
//HIGHになるまで待つ
while(digitalRead(port) == 0){
if(SysTick->CNT > 9150000){
return -1;
}
}
}
//LOWになるまで待つ
while(digitalRead(port) == 1){
if(SysTick->CNT > 9150000){
return -1;
}
}
long count = SysTick->CNT;
return (10 * HCLK_FREQ / count);
//return count;
}この関数で周波数を測定しています。といっても、やっていることは簡単で、パルスが10回くるまで待って、経過時間を出しているだけです。なぜ10パルスなのかですが、1パルス分を10回回して平均取るよりも早そうだなと思ったからです。(あと、関数のオーバーヘッドなども減ると思ったので)
一応ですが、ある程度の時間経っても戻ってこなかった場合は、-1を返すようにしています。(今の設定だと約50Hz(余裕を持って100Hzくらい)から測定できます)
ちなみにTIM2_PWM_Input_init関数もありますが、これは使っていません。(タイマー キャプチャ機能の使い方が全くわからん!)
動作の様子

見えにくいですが、右上にあるLEDみたいなのがフォトトランジスタです。(ごちゃごちゃしていて、すみません)

今回この関数は、Hzの表記で出てきます。

しかし、やはり誤差は大きいのかな?といった印象です。(といっても、44Hzの差なんですけどね)
±200くらいだったら余裕だと思います。
例えばですが、1000Hz, 2000Hz, 3000Hzみたいな感じであれば、確実に測定できると思います。
余談1

48kHzまで入れてみましたが、やはり波形が鈍りますね。正直いうと、フォトトランジスタ側がここまでの周波数に対応していないのかなと思います。
ちなみにですが、データシート的には応答時間が立ち上がり/たち下がり 10us なので、50kHzが限界です。実際にはもっと下がると思うので、まあ行けたとしても20kHzくらいですかね。
余談2
周波数検知は、タイマーのキャプチャ機能を使おうと思ったのですが、意味がわからないのでやめました。(多分割り込み処理しないと行けないんだと思います。割り込みめんどくさい)
結論としてはソフトウェアでやったほうが楽です。(20分くらいで実装できますし)
やっぱりCH32V003マイコンは速くていいですね。安いしおすすめです。
(書いてから知ったけど、15000語は草)


コメント