STM32 + LL でPWM出力を行う
STM32マイコンのペリフェラル関連記事を一覧にまとめました。
モーターの制御やブザーの音を鳴らすことなどで使用するPWM出力について書いていきたいと思います。使用するマイコンはSTM32F405RGT、TIMは汎用タイマのTIM2を使用していきます。今回は、私のマイクロマウスについているブザーをPWMを使用して、音階を鳴らしていきたいと思います。
リファレンスマニュアルを確認する
PWMモードを確認する
PWMモードの設定に必要そうなことをまとめると以下の通りです。
ARR1レジスタで設定した周波数とCCRx2レジスタの設定されているDutyサイクルを比較して出力をしてくれるそうです。
このモードと、タイマのプリスケーラの役割を読むと、ARRレジスタの値を399に設定をしたら、CRRxレジスタの値を40にしたときはDutyが約1割、120にしたときは約3割のように出力を設定できるみたいです。周波数の決定方法はプリスケーラの分周期とARRレジスタの設定された値に依存するということもわかりました。タイマのプリスケーラの役割については各自読んでみてください。
また、PWMモードに関してわかったことでプログラムを書く上で必要そうだと思い抽出した点は以下の通りです。
- PWMモードの設定はチャンネルごとに行う必要あり。
- PWMを出力する前にチャンネルごとに出力を許可する必要あり
- PWMを出力する前にタイマのカウントを有効にする必要があり。
- PWMモード動作中は常にARRレジスタとCRRxレジスタが比較されている。
- TIMはPWMモード1,2を設定が可能(今回はモード1を使用します)。
今回使用するレジスタに関しての確認は各自でお願いします。
↑レジスタの確認方法の参考例(エンコーダモードでTIM1/8のレジスタを記事内で確認しています。)
STM32CubeMXの設定
ピン設定、PWM設定は以下の通りです。
プリスケーラの設定値、再リロードレジスタの設定値に関してはTIMに供給されるクロック源によって変更をお願いします。Clock Configrationを確認しながら設定をしましょう。
Clock Configrationの設定は次の通りです。
TIM2のプリスケーラの設定値は、PWMの初期設定終了後に周期が1kHzになるようにしています。PWMの周期は次の式で計算することが可能です。
pwm_frequency = APB1_TimerClocks / (Prescaler+1) / (AutoReloadRegister+1)
STM32F405RGTのTIM2のクロック供給源はAPB1 Timer Clocksから供給されていることを踏まえて、今回の設定値を当てはめてみると次のように計算できます。
pwm_frequency = 80,000,000 / (799+1) / (99+1) = 1000
したがって、1kHzに設定できていることがわかると思います。この計算式の理由や、TIMのクロック供給源がどこかについては各自でデータシートを読んでいただければと思います。
Project Managerで使用するAPIをHALからLLに変更したのち、コードを生成してもらいましょう。
ソースコードを実装する
生成されたソースコードを確認する
MX_TIM3_Init関数を確認していきます。ピンの設定、プリスケーラ、再リロードレジスタ、チャンネルの設定をすべて行ってくれていることがわかりました。
PWMをチャするためには、チャンネルごとの出力設定、タイマのカウンタを有効化をしてあげればできそうだということも同時にわかりました。
これらを踏まえてプログラムを実装していきたいと思います。
ブザーの音階を設定の仕方を考える
ブザーの音階の設定方法を考えたいと思います。ブザーでは単音しか鳴らすことができないので単音の音階を知る必要があるので調べたものを表にまとめます。
周波数の計算はSTM32CubeMXの設定で示した式を使用しました。また、プリスケーラの値を変更することで周波数を変更していこうと考えているので、今回のタイマの設定をもとに近似計算したプリスケーラの設定値を表に付け加えています。
表:音階とプリスケーラの設定値
音階 | 周波数(Hz) | プリスケーラの設定値 |
C4 | 261.626 | 3058 |
D4 | 277.183 | 2886 |
E4 | 293.665 | 2724 |
F4 | 311.127 | 2571 |
G4 | 415.305 | 1926 |
A4 | 440.000 | 1818 |
B4 | 493.883 | 1620 |
プログラムを書いていく
今回は、buzzerを鳴らすためのプログラムになるため、buzzer.c/hを作成してそこにソースコードを実装していきたいと思います。
buzzer.c
/*
* buzzer.c
* Author: sora
*/
#include "buzzer.h"
#include "tim.h"
#include "main.h"
void TIM2_PWM_Start(void)
{
LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2);
LL_TIM_EnableCounter(TIM2);
}
void TIM2_PWM_Out(uint32_t pwm)
{
if ( pwm > 99 ) pwm = 99;
LL_TIM_OC_SetCompareCH2(TIM2, pwm);
}
void setBuzzerMonophonic(uint16_t scale, uint16_t time_beep)
{
LL_TIM_SetPrescaler(TIM2, scale);
TIM2_PWM_Out(99);
LL_mDelay(time_beep);
TIM2_PWM_Out(0);
}
buzzer.h
/*
* buzzer.h
* Author: sora
*/
#ifndef __BUZZER_H
#define __BUZZER_H
#include <stdint.h>
// music scale
#define NORMAL 799U
#define C_SCALE 3058U
#define D_SCALE 2886U
#define E_SCALE 2724U
#define F_SCALE 2571U
#define G_SCALE 1926U
#define A_SCALE 1818U
#define B_SCALE 1620U
#define C_H_SCALE 1529U
void TIM2_PWM_Start(void);
void TIM2_PWM_Out(uint32_t pwm);
void setBuzzerMonophonic(uint16_t scale, uint16_t time_beep);
#endif /* __BUZZER_H */
こんな感じで実装してみました。マイコンのプログラム起動時にTIM2_PWM_Startを実行したあとは、main内でsetBuzzerMonophonic関数に音階、鳴らす時間を入れることでブザーを指定した音階、時間で鳴らすプログラムです。比較レジスタに数値を入れる前に、再ロードレジスタの値よりも大きな値が入らないようにしています。
LLライブラリの関数や、レジスタをどのように設定しているかどうかに関しですが、関数名をみればやっていることが何かわかると思うので説明は省略します。気になる方は、関数の処理の中に飛ぶなり、リファレンスマニュアルを読むなりしてください。
さいごに
LLAPIを使用してPWMの出力の仕方を書いてみました。前回までで、データシートのレジスタの確認や、読み解くことをしてきたので今回は少し説明の量を減らしてみました。データシートを読む練習だと思って読んでいただければいいなぁと思っています。
話は変わりますが、ブログの記事の下にコメント欄を追加してみました。質問等ございましたらここにしていただければと思います。
次回はADCの1chの変換の仕方を書いていこうと思います。
ブザーの参考回路
ブザーは、マイコンのポートの出力電流では音をしっかりと鳴らすことができないため、FETでスイッチングを行っています。
参考回路は以下の通りです。