こんにちは、ENGかぴです。
Seeeduino XIAOに標準搭載されているライブラリを使用しパルス波形を生成することでブザーを鳴らすことができます。ブザーを鳴らす原理が分かっていればタイマ割り込みを使用することでブザーの音程を変えながら鳴らすことができます。
今回は標準ライブラリであるtone()を使用する方法とタイマ割り込みを使用する方法の2つを紹介します。タイマ割り込みを使用する方法は、マイコンを問わず同じ考え方でブザーを鳴らすことができるため参考になると思います。
analogWrite()を使ってPWMのデューティ比を変えてブザーを鳴らす方法を下記にまとめています。
Seeeduino XIAOで動作確認したことについてリンクをまとめています。
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
Seeeduino XIAOでブザーを鳴らす方法
ブザーを鳴らすためには振動波形を与える必要があります。パルス波形のように出力が一定でなく振幅が上下しているような波形を与えるイメージになります。ブザーの音程はパルス波形の周波数を変更することで実現できます。
標準ライブラリを使用する方法
Seeeduino XIAOはArduinoと互換性があるため標準ライブラリが使用できます。ブザーを鳴らすために準備されているtone()関数を使用することで簡単にブザーを鳴らすことができます。
#define PIN_PULSE 8
void loop() {
tone(PIN_PULSE, 1000, 100); //8ピンにキャリア周波数1000msのパルスを100ms間出力
delay(1000);
//noTone(6); //ピンを変更する場合に必要(8ピンから6ピンに変更してブザーを鳴らす場合など)
tone(PIN_PULSE, 2000, 100);
delay(1000);
}
tone(引数1, 引数2, 引数3)において引数1にはピン番号、引数2には振動波形の周波数、引数3には振動波形を出力する期間を指定します。引数3に値を指定しない場合は出力を続ける指定となります。
実装する際の注意事項はパルス波形が出力される前にtone()関数を上書きすると状態変化によるウェイトなどによって出力されない状態になるので一度tone()を発行したら変更するまで上書きしないように実装する必要があります。実装例ではtone()発効後、delay(1000)で遅延しています。
キャリア周波数の制御について
tone()関数を使用せずにSeeeduino XIAOのタイマライブラリであるTimerTC3.hを使用して割り込み設定を行い振動波形を生成します。
振動波形のデューティ比は1:1(50%)としています。デューティ比が低すぎると電圧が低下するため音が小さくなることがあります。
標準ライブラリのtone()を使用した場合はデューティ比が1:1(50%)固定となりますが、タイマライブラリによる方法はキャリア周波数を変更しながらデューディ比の変更ができます。デューティ比を変化させる場合は説明図のcntmax/2を可変することで実現できます。
#include <TimerTC3.h>
void setup() {
TimerTc3.initialize(10); //10us毎に割り込み
TimerTc3.attachInterrupt(TimerCnt); //TimerCntにコールバック
pinMode(PIN_PULSE, OUTPUT);
}
/* callback function add */
void TimerCnt(){
++cnt10us;
if( ++cnt >= cntmax ){
cnt = 0;
}
if( cnt > cnthalf ){ //MAX値の半分でDOのHとLを切り替えて振動波形を生成
digitalWrite(PIN_PULSE,LOW);
}
else{
digitalWrite(PIN_PULSE,HIGH);
}
}
TimerTc3.initialize(10)とすることで10us毎にコールバックであるTimerCnt()に遷移します。TimerCnt()では10us毎にカウントしてながら一定の値を超えるとDOの出力を切り替えています。
cntmaxを変更することでキャリア周波数を変更することができます。またcnthalfを変更することでデューディ比を変更することができます。
キャリア周波数とデューティ比を同時に変更したい場合はSeeeduino XIAOの内部のレジスタを操作する必要がありますが、簡単に使用できて動作確認ができることがArduino環境の良さであるためレジスタの操作せずとも代用できる方法としてタイマ割り込みを使う方法を選択しています。
割り込みの考え方と注意事項については下記でまとめています。
PICマイコン(PIC12F675)の割り込みの考え方と注意事項
PIC12F675を元にまとめていますが、考え方はマイコンを問わず共通なので参考になると思います。
動作確認
Seeeduino XIAOのDOを使って圧電ブザーを鳴らします。動作確認用のプログラムはタイマ割り込みを使用したものと標準関数のtone()を使用したものの2通りで確認しています。
標準ライブラリのtone()を使用する方法とタイマ割り込みを使ってブザーを鳴らす方法の動作確認は同じ回路を使用します。
ブザーはインピーダンスが高いものを使用していれば抵抗は不要ですが、短絡故障したときにマイコンに負荷がかかることから抵抗で電流制限を行っています。抵抗を大きくしすぎるとブザーの音が小さくなったり鳴らなくなるので注意が必要です。
ブザーと抵抗で分圧されることになるのでブザーに対して十分に電圧が供給できれば抵抗値であれば任意の値で問題ありませんが、短絡故障したとき0.3mAに制限したい場合は10kΩとなります。
ブザーを持続して鳴らしていると耳障りに感じたため100ms間鳴らした後900msはOFFするようにしています。
SWはブザーの音程を切り替えるために実装しています。ボタンを押したときのチャタリング防止対策をソフト内で行っています。
どちらのソフトにおいてもSWを押すことで音程が変更され「ピッ」と1秒間隔でブザーが鳴っていることが確認でき音程も同じでした。(音程がずれているかもしれませんが気にならないレベルです。)
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
tone()を使用した場合のスケッチ例:
#include <TimerTC3.h>
#define PIN_PULSE 8
#define PIN_DI 5
#define DI_FILT_MAX 4
#define FILT_MIN 1
#define TIME_UP 0
#define TIME_OFF -1
#define BASETIME 10
#define BUZ_OFF 100
struct DIFILT_TYP{
uint8_t wp;
uint8_t buf[DI_FILT_MAX];
uint8_t di1;
};
bool btnflg1;
uint8_t buzmode;
int16_t timdifilt;
int16_t timbuzon;
int16_t cnt10ms;
DIFILT_TYP difilt;
void ChngBuz();
void DiFilter();
void setup() {
pinMode(PIN_DI,INPUT_PULLUP);
TimerTc3.initialize(1000); //10us毎に割り込み
TimerTc3.attachInterrupt(TimerCnt); //TimerCntにコールバック
timdifilt = FILT_MIN;
timbuzon = BUZ_OFF;
}
void loop() {
DiFilter();
if(difilt.di1 == 0){
if(btnflg1){
btnflg1 = false;
if( ++buzmode >= 4 ){
buzmode = 0;
}
}
}else{
btnflg1 = true;
}
if( cnt10ms >= BASETIME ){
cnt10ms -= BASETIME;
if( timdifilt > TIME_UP ){
--timdifilt;
}
if( timbuzon > TIME_UP ){
--timbuzon;
}
}
if( timbuzon == TIME_UP ){
timbuzon = BUZ_OFF;
ChngBuz();
}
}
/* callback function add */
void TimerCnt(){
++cnt10ms;
}
/* DiFilter function add */
void DiFilter(){
if( timdifilt == TIME_UP ){
difilt.buf[difilt.wp] = digitalRead(PIN_DI);
if( difilt.buf[0] == difilt.buf[1] &&
difilt.buf[1] == difilt.buf[2] &&
difilt.buf[2] == difilt.buf[3] ){ //4回一致を確認
difilt.di1 = difilt.buf[0];
}
if( ++difilt.wp >= DI_FILT_MAX ){
difilt.wp = 0;
}
timdifilt = FILT_MIN;
}
}
/* Buzzer change function add */
void ChngBuz(){
switch(buzmode){
case 0:
tone(PIN_PULSE, 1000, 100); //キャリア周波数1000Hz
break;
case 1:
tone(PIN_PULSE, 1250, 100); //キャリア周波数1250Hz
break;
case 2:
tone(PIN_PULSE, 1667, 100); //キャリア周波数1667Hz
break;
case 3:
tone(PIN_PULSE, 2500, 100); //キャリア周波数2500Hz
break;
case 4:
tone(PIN_PULSE, 5000, 100); //キャリア周波数5000Hz
break;
}
}
タイマ割り込みを使用した場合のスケッチ例:
#include <TimerTC3.h>
#define PIN_PULSE 8
#define PIN_DI 5
#define CAREER_MAX1 100 //キャリア周波数1000Hz
#define CAREER_MAX2 80 //キャリア周波数1250Hz
#define CAREER_MAX3 60 //キャリア周波数1667Hz
#define CAREER_MAX4 40 //キャリア周波数2500Hz
#define CAREER_MAX5 20 //キャリア周波数5000Hz
#define TIME_UP 0
#define TIME_OFF -1
#define BUZ_MAX 10
#define BASETIME 1000
#define BUZ_ON 10
#define BUZ_OFF 90
#define DI_FILT_MAX 4
#define FILT_MIN 1
struct DIFILT_TYP{
uint8_t wp;
uint8_t buf[DI_FILT_MAX];
uint8_t di1;
};
uint16_t cntmax = CAREER_MAX1;
uint16_t cnt;
uint16_t cnthalf;
int16_t cnt10us;
int16_t timbuzon;
int16_t timbuzoff;
int16_t timdifilt;
DIFILT_TYP difilt;
bool btnflg1;
uint8_t buzmode;
void ChngBuz();
void DiFilter();
void setup() {
TimerTc3.initialize(10); //10us毎に割り込み
TimerTc3.attachInterrupt(TimerCnt); //TimerCntにコールバック
pinMode(PIN_PULSE, OUTPUT);
pinMode(PIN_DI, INPUT_PULLUP);
cnthalf = cntmax/2;
timbuzon = BUZ_ON;
timdifilt = FILT_MIN;
}
void loop() {
DiFilter();
if(difilt.di1 == 0){
if(btnflg1){
btnflg1 = false;
ChngBuz();
}
}else{
btnflg1 = true;
}
if( cnt10us >= BASETIME ){
cnt10us -= BASETIME;
if(timbuzon > TIME_UP){
--timbuzon;
}
if(timbuzoff > TIME_UP){
--timbuzoff;
}
if( timdifilt > TIME_UP ){
--timdifilt;
}
}
if( timbuzon == TIME_UP ){
timbuzon = TIME_OFF;
timbuzoff = BUZ_OFF;
}
if( timbuzoff == TIME_UP ){
timbuzoff = TIME_OFF;
timbuzon = BUZ_ON;
}
}
/* callback function add */
void TimerCnt(){
++cnt10us;
if( ++cnt >= cntmax ){
cnt = 0;
}
if(timbuzon > TIME_UP){
if( cnt > cnthalf ){ //
digitalWrite(PIN_PULSE,LOW);
}
else{
digitalWrite(PIN_PULSE,HIGH);
}
}
}
/* DiFilter function add */
void DiFilter(){
if( timdifilt == TIME_UP ){
difilt.buf[difilt.wp] = digitalRead(PIN_DI);
if( difilt.buf[0] == difilt.buf[1] &&
difilt.buf[1] == difilt.buf[2] &&
difilt.buf[2] == difilt.buf[3] ){ //4回一致を確認
difilt.di1 = difilt.buf[0];
}
if( ++difilt.wp >= DI_FILT_MAX ){
difilt.wp = 0;
}
timdifilt = FILT_MIN;
}
}
/* Buzzer change function add */
void ChngBuz(){
switch(buzmode){
case 0:
cntmax = CAREER_MAX1;
break;
case 1:
cntmax = CAREER_MAX2;
break;
case 2:
cntmax = CAREER_MAX3;
break;
case 3:
cntmax = CAREER_MAX4;
break;
case 4:
cntmax = CAREER_MAX5;
break;
}
cnthalf = cntmax/2;
if( ++buzmode >= 4 ){
buzmode = 0;
}
}
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
最後まで、読んでいただきありがとうございました。