こんにちは。Yukiです。
今回は、TIM1を使ってPWM出力をしてみたいと思います。
PWMの概要
PWMはタイマーのカウンタと比較することで、作り出すことができます。
これはいわゆるハードウェアタイマーと言われるものです。
下の図は、PWM出力の図です。
CH32V003マイコンは、TIMx_ARRレジスタに達すると、0に戻るような動作をします。
TIM1でPWM出力
TIM2は、前回割り込みで使用したので、今回はTIM1を使用します。
ちなみに、TIM1はアドバンスドタイマーとして位置づけられており、その名の通り、高性能なタイマーです。
もしかしたらですが、TIM2を使ったほうが後々、幸せになれるかもしれません。
(CH32V003F4P6をタイマーが2Chしかないので、しょうがないんですけどね)
ただ、watchdogをタイマーとして使用する方法があるのかもしれません。(詳しくはしりません。とにかく今回はTIM1を使用します)
今回はTIM1_CH1(PD2)でPWM出力を行います。
また、TIM1_RM(リマップ)は 初期値 00で行います。
それでは早速やっていきましょう。
予め、bit_replace関数を宣言しておき、ビット列の置き換えを容易にします。
(2bit以上の置き換えが必要な際に使用します)
// ビット置き換え関数
// 引数 data:置き換え前のビット列 | byte:置き換えるビット | len:置き換えるビットの長さ | shift:シフト数
//
uint32_t bit_replace(uint32_t data, uint32_t byte, uint8_t len, uint8_t shift) {
uint32_t mask = ~(((1 << len) - 1) << shift);
data &= mask;
data |= byte << shift;
return data;
}
次にPLLを有効にして、システムクロックを48MHzにします。
(任意です)
//クロック設定
// クロック設定 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);
//クロックソース PLLに変更 (SW)
RCC->CFGR0 = bit_replace(RCC->CFGR0, 0b10, 2, 0);
//HCLKプリスケーラ 0 (HPRE)
RCC->CFGR0 = bit_replace(RCC->CFGR0, 0b0000, 4, 4);
次に、GPIODを有効化します。
//GPIOD 有効
RCC->APB2PCENR |= RCC_IOPDEN;
次に、PD2をマルチプレクサファンクションモードでプッシュプルとして出力設定します。
//PD2 出力モード (50MHz)
GPIOD->CFGLR = bit_replace(GPIOD->CFGLR, 0b11 , 2, 8);
//PD2 マルチプレクサファンクションプッシュプルモード
GPIOD->CFGLR = bit_replace(GPIOD->CFGLR, 0b10, 2, 10);
次に、TIM1を有効にします。
//TIM1 有効
RCC->APB2PCENR |= RCC_TIM1EN;
次に、アップデートイベント発生を有効にします。
//Update Event Generation有効
TIM1->SWEVGR |= TIM_UG;
次に、PWM出力の大本である、メイン出力を有効化します。
//TIM1 CHメイン出力有効
TIM1->BDTR |= TIM_MOE;
次に、TIM1_CH1 (PD2)の出力を許可します。
これにより、PD2にPWMが出力されるようになります。
//TIM1_CH1 (PD2) 出力許可
TIM1->CCER |= TIM_CC1E;
次に、TIM1_CH1をPWMモードに設定します。
今回はPWM1モードにしました。
//TIM1_CH1 PWMモード1
TIM1->CHCTLR1 = bit_replace(TIM1->CHCTLR1, 0b110, 3, 4);
次に、TIM1_CH1のプリロードを有効化します。
//TIM1_CH1 プリロード有効
TIM1->CHCTLR1 |= TIM_OC1PE;
次に、TIM1のオートプリロードを有効にします。
//オートプリロード有効
TIM1->CTLR1 |= TIM_ARPE;
次に、TIM1のプリスケーラとオートリロード値を設定します。
TIM1->PSC = 0;
TIM1->ATRLR = 1000;
今回だと、システムクロック 48MHzなので、
48MHz ÷ (プリスケーラ値 0 + 1) = 48MHz
48MHz ÷ ATRLR 1000 = 48kHz
ということで、48kHzがPWM周波数となります。
次に、PWMのDuty比を設定します。
TIM1->CH1CVR = 500;
今回だと、ATRLR が 1000 CH1CVRが500なので、 Duty比 50%となります。
最後にTIM1のカウントを有効にします。
//TIM1 カウント有効
TIM1->CTLR1 |= TIM_CEN;
後は、お好きなようにDuty比をセットして遊びましょう。
int i= 0;
while(1){
i++;
TIM1->CH1CVR = i;
if(i > 999){
i = 0;
}
for(int j = 0;j <= 20000; j++){
__asm("NOP");
}
}
こんな感じにすれば、のこぎり波みたいな出力になります。
回路図
実際の動作
黄色のジャンパ線はオシロスコープに接続しています。
このように、周波数固定で、パルス幅だけ変化しています。
PWMですね。
余談
余談なのですが、加湿器を買いました。
ただ、湿度計がないので、作ろうかなと思っています。
前にAliExpressで買ったAHT20があった気がするので、それで作ろうと思います。