圧電ブザーでメロディを奏でる
前回、NanoPi NEOのハードウェアPWMでLEDの明るさを制御してみました。
PWMが制御できるなら圧電ブザーも鳴らせるはず、ということで試してみます。せっかくなのでちょっとしたメロディを奏でてみたいと思いますが、音楽的な出来栄えや再生品質には期待しないでください。
圧電ブザー回路
圧電ブザーは「村田製作所 他励振式圧電サウンダ φ17mm PKM17EPPH4001-B0」を使います。
村田製作所 他励振式圧電サウンダ φ17mm PKM17EPPH4001-B0
回路といってもPWM端子と圧電ブザーを接続するのみです。NanoPi NEOのPWM端子については前述の前回の記事を参照ください。
メロディを奏でるには
メロディを奏でる、ということは音階(ドレミ)を鳴動するということになります。音程(Pitch)を制御するにはPWMの周波数を制御します。
音色は波形できまるため、duty比を調整することで多少の音色変化は期待できるのですが、今回はduty比50%の矩形波にしておきます。
音階としては1オクターブの範囲、半音(#とか♭とか)はなしで制御することにします。音程と周波数・periodの関係は下表の通りになります。
音程 | 周波数[Hz] | period[nsec] |
---|---|---|
ド(C) | 261.626 | 3822250 |
レ(D) | 293.665 | 3405241 |
ミ(E) | 329.628 | 3033723 |
ファ(F) | 349.228 | 2863459 |
ソ(G) | 391.995 | 2551053 |
ラ(A) | 440.000 | 2272727 |
シ(B) | 493.883 | 2024771 |
メロディを奏でるには音楽的なことも少し決めなければなりません。今回は4/4拍子でテンポ(BPM)は80とします。ということは、4分音符が1分間に80個。なので4部音符の時間は750[msec]になります。
C言語で超簡易版MMLを実装する
せっかくなので、超簡単なMMLもどきを実装してメロディを奏でてみます。MML(Music Macro Language)とは音楽演奏内容をテキストで記述するもので、シーケンサー的なことを言語記述で行うものです[1]歴史は長く、筆者も1980年代頃にMZ-2500やX68000でYMOやゲームミュージックを打ち込んで鳴らしていました。。
MMLは基本として「音程+音符」の形で記述します。例えばド(C)の8分音符を鳴らす場合”c8″と書きます。今回は超簡易実装なので以下のルールで進めます。
- デフォルトは4分音符
- 音程は小文字アルファベットのみ
とある有名な童謡を上記ルールでMML記述すると以下のようになります。
gee2fdd2cdefggg2
geeefdddceggeee2
dddddef2eeeeefg2
geeefdd2ceggeee2
さて、何の曲でしょうかね笑。演奏してみましょう。
超簡易版MMLプログラム(C言語)でメロディを演奏
プログラムを打ち込み、コンパイル・実行します。
$ vi buzzer-melody-playing.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define BPM (80LLU)
#define NSEC (1000000000LLU)
#define WHOLENOTETIME ((60LLU * NSEC) / BPM * 4LLU)
const unsigned long periodTbl[] = {
2272727, // 440.000 a4
2024771, // 493.883 b4
3822250, // 261.626 c4
3405241, // 293.665 d4
3033723, // 329.628 e4
2863459, // 349.228 f4
2551053 // 391.995 g4
};
static void noteout( unsigned long period, unsigned long timensec )
{
int fd = -1;
int ret;
struct stat st = { 0 };
unsigned long periodValue;
unsigned long dutyValue;
char valueStr[16];
int strSize;
struct timespec tms;
struct timespec tms_before;
struct timespec tms_after;
unsigned long long timensec_adjustment;
if( period == 0U ) {
return;
}
printf("[DEBUG]period=%lu, timensec=%lu\n", period, timensec );
clock_gettime(CLOCK_MONOTONIC, &tms_before);
// Set period value.
fd = open("/sys/class/pwm/pwmchip0/pwm0/period",O_RDWR);
periodValue = period;
strSize = snprintf(valueStr ,16, "%lu", periodValue);
if(write(fd, valueStr, strSize) != strSize) {
perror("write-5");
exit(EXIT_FAILURE);
}
close(fd);
// Set duty cycle value.
fd = open("/sys/class/pwm/pwmchip0/pwm0/duty_cycle",O_RDWR);
dutyValue = period / 2U;
strSize = snprintf(valueStr, 16, "%lu", dutyValue);
if(write(fd, valueStr, strSize) != strSize) {
perror("write-6");
exit(EXIT_FAILURE);
}
close(fd);
// Set pwm0 enable.
fd = open("/sys/class/pwm/pwmchip0/pwm0/enable",O_WRONLY);
if(write(fd, "1", 1) != 1) {
perror("write-7");
exit(EXIT_FAILURE);
}
close(fd);
clock_gettime(CLOCK_MONOTONIC, &tms_after);
timensec_adjustment = timensec - ((tms_after.tv_sec * NSEC + tms_after.tv_nsec)
- (tms_before.tv_sec * NSEC + tms_before.tv_nsec)) - 7500U;
tms.tv_sec = timensec_adjustment / NSEC;
tms.tv_nsec = timensec_adjustment % NSEC;
nanosleep(&tms, NULL);
// Set pwm0 disable.
fd = open("/sys/class/pwm/pwmchip0/pwm0/enable",O_WRONLY);
if(write(fd, "0", 1) != 1) {
perror("write-8");
exit(EXIT_FAILURE);
}
close(fd);
tms.tv_sec = 0U;
tms.tv_nsec = 7500U;
nanosleep(&tms, NULL);
return;
}
static unsigned long getPeriod( char pitch )
{
return periodTbl[pitch - 'a'];
}
static unsigned long getTime( unsigned long number )
{
return WHOLENOTETIME / number;
}
static void mmlplay( const char *mml )
{
const char *p = mml;
unsigned long period = 0U;
unsigned long timensec = 0U;
unsigned long number = 0U;
timensec = getTime ( 4U );
while( *p != '\0' ) {
if( *p >= 'a' && *p <= 'g' ) {
noteout( period, timensec );
period = getPeriod( *p );
number = 0U;
timensec = getTime ( 4U );
} else if( *p >= '0' && *p <= '9' ) {
number = number * 10U + *p - '0';
timensec = getTime ( number );
}
p++;
}
noteout( period, timensec );
return;
}
int main()
{
int fd = -1;
int ret;
struct stat st = { 0 };
unsigned long periodValue;
unsigned long dutyValue;
char valueStr[16];
int strSize;
// Unexport pwm0 if it exists.
ret = stat("/sys/class/pwm/pwmchip0/pwm0", &st);
if(ret == 0) {
fd = open("/sys/class/pwm/pwmchip0/unexport",O_WRONLY);
if(write(fd, "0", 1) != 1) {
perror("write-1");
exit(EXIT_FAILURE);
}
close(fd);
}
// Export pwm0.
fd = open("/sys/class/pwm/pwmchip0/export",O_WRONLY);
if(write(fd, "0", 1) != 1) {
perror("write-3");
exit(EXIT_FAILURE);
}
close(fd);
// Set pwm0 disable.
fd = open("/sys/class/pwm/pwmchip0/pwm0/enable",O_WRONLY);
if(write(fd, "0", 1) != 1) {
perror("write-2");
exit(EXIT_FAILURE);
}
close(fd);
// Set polarity to normal.
fd = open("/sys/class/pwm/pwmchip0/pwm0/polarity",O_WRONLY);
if(write(fd, "normal", 6) != 6) {
perror("write-4");
exit(EXIT_FAILURE);
}
close(fd);
sleep(1);
mmlplay( "gee2fdd2cdefggg2" );
mmlplay( "geeefdddceggeee2" );
mmlplay( "dddddef2eeeeefg2" );
mmlplay( "geeefdd2ceggeee2" );
exit(EXIT_SUCCESS);
}
$ gcc buzzer-melody-playing.c $ sudo ./a.out
おわかりいただけただろうか?
ということで、冒頭に「音楽的な質には期待しないでください」と書いた意味が理解いただけたかと思います笑。
今回の記事で使用した機材紹介
Xsdjasd NanoPi NEO用開発ボード + ヒートシンク + メタルケースキット Allwinner H3 クアッドコア 512MB RAM Openwrt/LEDE コンプリート マシン
13% オフPenkeef NanoPi NEO オープン ソース H3 開発ボード + ヒートシンク DDR3 RAM 512MB クアッドコア -A7 Openwrt Armbian
¥3,229 (2024年12月5日 14:42 GMT +09:00 時点 - 詳細はこちら価格および発送可能時期は表示された日付/時刻の時点のものであり、変更される場合があります。本商品の購入においては、購入の時点で当該の Amazon サイトに表示されている価格および発送可能時期の情報が適用されます。)lucka NanoPi NEO用開発ボード + ヒートシンク + メタルケースキット Allwinner H3 クアッドコア 512MB RAM Openwrt/LEDE コンプリート マシン
¥4,660 (2024年12月5日 14:42 GMT +09:00 時点 - 詳細はこちら価格および発送可能時期は表示された日付/時刻の時点のものであり、変更される場合があります。本商品の購入においては、購入の時点で当該の Amazon サイトに表示されている価格および発送可能時期の情報が適用されます。)KIOXIA(キオクシア) 旧東芝メモリ microSD 64GB UHS-I Class10 (最大読出速度100MB/s) Nintendo Switch動作確認済 国内サポート正規品 メーカー保証5年 KLMEA064G
15% オフエレコム エコ USBケーブル スマートフォン対応 2.0 A-microB 1.2m U2C-JAMB12BK
58% オフTP-Link WIFI 無線LAN 子機 11n/11g/b デュアルモード対応モデル 英語パッケージ TL-WN725N(EU)
16% オフELEGOO 50 PCS オスメスジャンパーワイヤ200mm (170 タイポイント ブレッドボード付き)
¥880 (2024年12月5日 22:25 GMT +09:00 時点 - 詳細はこちら価格および発送可能時期は表示された日付/時刻の時点のものであり、変更される場合があります。本商品の購入においては、購入の時点で当該の Amazon サイトに表示されている価格および発送可能時期の情報が適用されます。)共立電気計器 (KYORITSU) 1012 キューマルチメータ
29% オフRIGOL デジタル・オシロスコープ DS1102Z-E 2アナログチャンネル+100MHz周波数帯域+1GSa/sリアルタイム・サンプルレート+24Mポイントレコード長+8bit高解像度+最大10V/div垂直軸レンジ+最高30000wfms/s高速波形取り込みレート【国内正規品】【メーカー直営3年保証】【日本語取扱説明書対応】【ハイコストパフォーマンス】
5% オフ注釈
↑1 | 歴史は長く、筆者も1980年代頃にMZ-2500やX68000でYMOやゲームミュージックを打ち込んで鳴らしていました。 |
---|
コメント