こんにちは、ENGかぴです。
マイコンでPWMを生成することができるようになるとブザーを鳴らすことができます。またPWMのキャリア周波数を変更することでブザーの音程を変えることができます。キャリア周波数はタイマのオーバーフローするまで時間を操作することで実現できます。
PIC12F675でブザーを鳴らして音程を変えていますが、マイコンを問わず同じ考え方でブザーを鳴らすことができるため参考になると思います。
PIC12F675を使ってマイコンの動きを勉強するためにPIC12F675の機能でできることについてまとめています。
PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ
PIC12F675で振動波形を生成する方法
PWMはキャリア周波数を一定にしながら波形のデューティー比を変えることで電圧をコントロールする方法です。一方でブザーを音程を変えながら鳴らすためには振動波形を与える必要があります。
キャリア周波数の制御について
キャリア周波数を制御する方法はタイマーの初期値を変更することで実現できます。キャリア周波数を高くする場合は初期値を高めに設定し、キャリア周波数を低くする場合は初期値を低めに設定します。
振動波形を生成するのが目的なのでデューティー比は1:1で問題ありません。デューティー比が低すぎると電圧が低下するため音が小さくなることがあります。
振動波形を生成するのみでよいのでタイマーオーバーフローしたときにポートのHとLを切り替えることで音を鳴らすことができます。
広告
電子ブザーの使い方
ブザーには電子ブザー(アクティブ)や圧電ブザー(パッシブ)があります。
圧電ブザーはアナログ入力で制御できるため複雑に音をコントロールする際に使用します。
電圧を加えただけで音を出したい場合は電子ブザーを使用します。
ブザーの構造でコイルが使われている場合、電流を流し続けると発熱することや逆起電力が発生することがあるため電流を常に流さないようにすることが必要です。
パルス波形を与えて電流が流れ続けないようにしながら電圧を加えることが推奨されるためどちらのブザーを使うにしてもパルス波形を与えることが必要です。
圧電ブザー(パッシブブザー)
圧電ブザーは電圧をかけただけでは音はなりません。音声波形のように数kHzの振動波形を入力することで音が鳴ります。極性があるものもあるので注意が必要です。
マイコンのポートをHとLを頻繁に切り替えることで音声波形を模擬することで音を鳴らすことができます。
電子ブザー(アクティブブザー)
電子ブザーはケース内に発振回路を内蔵しており一定の電圧をかけると音が鳴ります。マイコンのポートをHにするだけで一定の音程の音が鳴ります。極性があるので注意が必要です。
音程が一定でもブザーで通知することができるため警報を実装したりする際に使用することがあります。
圧電ブザーと電子ブザーのどちらも振動波形を調整することで音程をコントロールすることができます。圧電ブザーであればキャリア周波数を変更して音程を変えることができ、電子ブザーであればデューティ比を変更すると音程を変えることができます。
動作確認してみても正直そこまで差がないと感じています。単に通知用のブザーとして使用するならどちらでも良いと思います。
PICマイコン(PIC12F675)の初期化
PICマイコンを使用するためには、マイコンをどのような機能で使用するのかを選択する初期化を行う必要があります。
初期化部分を実装する
キャリア周波数を変更するためにAD変換器を使用します。初期化部分は下記プログラムと同様ですので考え方については下記記事を参考にしてください。
変更点はデューティ比を固定したままキャリア周波数が変更できるように割り込み関数内で振動波形を生成するためにポートのHとLを切り替える処理を実装しています。
TMR0でキャリア周波数を制御する
ブザーを鳴らすためにはキャリア周波数が目安として1kHz以上(周期が1ms)の振動波形が必要です。キャリア周波数が低すぎると音が鳴らなくなります。
TMR1を使用してもよいのですが、TMR0はプリスケーラなしで256usでタイムオーバーフローするため細かな振動波形が作りやすいのでTMR0を使用します。
プリスケーラは1:4としているので最大で256×4= 1024usのタイマーになっています。2回分のタイムオーバで1周期になるので1周期の時間は2048usになるので最小のキャリア周波数は488Hzになります。
振動波形を確実に生成するためにタイマー0の初期値の最大を0xF0(250)に制限しています。最大時のキャリア周波数は25kHzになります。キャリア周波数の可変範囲は484Hzから25kHzになります。
振動波形を実装する
SW1を押すとLED1が点灯しGP4から振動波形が出力開始します。可変抵抗VR1を変更するとAD変換の値が変更されるため振動波形のキャリア周波数が変化しブザーの音程を変えることができます。
//-----------------割り込み関数-------------------------
void __interrupt() intr( void){
if( TMR1IE == 1 && TMR1IF == 1){ //タイマ1の割り込みであるか
TMR1IF = 0;//割り込みフラグをクリア
++cnt10ms;
difiltflg = BIT_SET;
TMR1ON = 0;
TMR1H = TIME_START_H;//タイマ初期化
TMR1L = TIME_START_L;//タイマ初期化
TMR1ON = 1;//タイマ1ON
}
if( T0IE == 1 && T0IF == 1){
T0IF = 0;
if( gpio3_buf){
GPIO4 =~GPIO4; //出力を反転
}else{
GPIO4 = BIT_CLR;
}
CntData = (CntMax >> 2); //2回シフトして10ビットのADデータを8ビットのデータとした
if( CntData >= PULSE_LIMIT){
CntData = PULSE_LIMIT;
}
TMR0 = CntData;
}
if( ADIE == 1 && ADIF == 1){
ADIF = 0;
adfiltflg = BIT_SET;
}
}
AD変換は10ビットの分解能であるためタイマー0の初期値として採用する際には8ビットのデータに変換する必要があります。
ブザーは圧電ブザーと電子ブザーのどちらも音程が変更でき同じように鳴ります。電子ブザーの方は抵抗を入れていることで電圧降下が起こり電子ブザーよりも音が小さく聞こえます。
ソースコード全体
ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、コメントに誤記が含まれていることもありますが、参考にお使いいただければと思います。
// CONFIG
#pragma config FOSC = INTRCIO
#pragma config WDTE = ON
#pragma config PWRTE = OFF
#pragma config MCLRE = OFF
#pragma config BOREN = OFF
#pragma config CP = OFF
#pragma config CPD = OFF
#include <xc.h>
//--------------定数定義----------------------------
#define _XTAL_FREQ 4000000 //発振周波数 4MHz
#define TIME_START_H 0xD8 //タイマスタート位置 65536- 55536 = 10000
#define TIME_START_L 0xF0 //タイマスタート位置 65536- 55536 = 10000
#define BIT_SET 1
#define BIT_CLR 0
#define CNT_OFF -1
#define AD_NUM 4 //移動平均フィルタのサンプリング数
#define DI_NUM 4
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define TIME_ADCNT_MAX 1 //100ms経過
#define TIME_BASE_MAX 1 //ベースタイマカウント値
#define TIME_WAIT_MAX 10
#define PULSE_LIMIT 0xF0
typedef struct{
char wp;
unsigned short buf[ AD_NUM ];
}FILT_DATA;
//-----------------変数定義--------------------------
unsigned short CntMax; //Duty最大値一時保管用
unsigned short CntData;
FILT_DATA AdData;
FILT_DATA DiData;
char adfiltflg = BIT_CLR; //ADフィルタ起動
char difiltflg = BIT_CLR; //DIフィルタ起動
unsigned char cnt10ms;
char TimerAdStart = TIME_OFF;
char timWait = TIME_OFF;
char gpio3_buf;
//-----------------関数定義---------------------------
void ADFilter(void);
void mainTimer(void);
void DiFilter(void);
void main(void) {
//各種初期化
ADCON0 = 0x81; //ADコンバータの電源をON。結果は右詰。
ANSEL = 0x11; //GP0をアナログ入力に使用。変換クロックは8TOSC
CMCON = 0x07; //コンパレータ使用しない
TRISIO = 0x09; //GP0,GP3入力、その他出力
GPIO = 0x00; //ポートの設定 1:High 0:Low
OPTION_REG = 0x01; //プリスケーラ4 TMR0で使用
//割り込み設定
INTCON = 0xC0; //PIE1許可
PIE1 = 0x41; //AD変換終了・TMR1割り込み許可
//初期化
//タイマ起動
T1CON = BIT_CLR;
TMR1H = TIME_START_H;//タイマ初期化
TMR1L = TIME_START_L;//タイマ初期化
cnt10ms = 0;
TMR1ON = BIT_SET;//タイマ1ON
TMR1IF = BIT_CLR;//割り込みクリア
TMR0 = 0;
T0IF = BIT_CLR;//割り込みクリア
//DIフィルタ初期起動
timWait = TIME_WAIT_MAX;
while( timWait > TIME_UP){
CLRWDT();
mainTimer();
DiFilter();
}
timWait = TIME_OFF;
//TMR0を起動
TMR0 = 0;
T0IE =BIT_SET; //TMR0割り込み許可
T0IF = BIT_CLR; //割り込みクリア
//AD変換起動
GO_DONE = BIT_SET;
while(1){
CLRWDT();
mainTimer();
DiFilter();
ADFilter();
}
}
//-----------------割り込み関数-------------------------
void __interrupt() intr( void){
if( TMR1IE == 1 && TMR1IF == 1){ //タイマ1の割り込みであるか
TMR1IF = 0; //割り込みフラグをクリア
++cnt10ms;
difiltflg = BIT_SET;
TMR1ON = 0;
TMR1H = TIME_START_H; //タイマ初期化
TMR1L = TIME_START_L; //タイマ初期化
TMR1ON = 1;//タイマ1ON
}
if( T0IE == 1 && T0IF == 1){
T0IF = 0;
if( gpio3_buf){
GPIO4 =~GPIO4;
}else{
GPIO4 = BIT_CLR;
}
CntData = (CntMax >> 2);
if( CntData >= PULSE_LIMIT){
CntData = PULSE_LIMIT;
}
TMR0 = CntData;
}
if( ADIE == 1 && ADIF == 1){
ADIF = 0;
adfiltflg = BIT_SET;
}
}
//----------タイマー管理----------------------------------
void mainTimer(void){
if( cnt10ms >= TIME_BASE_MAX){
cnt10ms -=TIME_BASE_MAX;
if( TimerAdStart > TIME_UP ){
--TimerAdStart;
}
if( timWait > TIME_UP ){
--timWait;
}
}
if( TimerAdStart == TIME_UP){
if( GO_DONE == BIT_CLR){
GO_DONE = 1;
TimerAdStart = TIME_ADCNT_MAX;
}
}
}
//--------モード切替部のDIフィルタ----------------------------
void DiFilter(void){
if( difiltflg ){
difiltflg = BIT_CLR;
DiData.buf[ DiData.wp ] = GPIO3;
if( DiData.buf[ 0 ] == DiData.buf[ 1 ] &&
DiData.buf[ 1 ] == DiData.buf[ 2 ] &&
DiData.buf[ 2 ] == DiData.buf[ 3 ] ){
//比較して一致したら値を採用
gpio3_buf = DiData.buf[ 0 ];
}
if( ++DiData.wp >= DI_NUM) DiData.wp = 0;
}
}
//--------ADフィルタ----------------------------
void ADFilter(void){
unsigned short set;
char i;
if( adfiltflg ){
adfiltflg = BIT_CLR;
AdData.buf[ AdData.wp ] = ADRESH;
AdData.buf[ AdData.wp ] = ( AdData.buf[ AdData.wp ] << 8) + ADRESL;
set = 0;
for( i = 0; i < AD_NUM; i++ ){
set += AdData.buf[ i ];
}
set = set >> 2;
if(++AdData.wp >= AD_NUM){
AdData.wp=0;
}
CntMax = set;
}
}
//---------------------end file----------------------------
関連リンク
PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。
PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ
PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ
最後まで、読んでいただきありがとうございました。