【NanoPi NEO/Armbian】PWMを使い圧電ブザーでメロディを奏でてみる

シングルボードコンピュータ(SBC)

圧電ブザーでメロディを奏でる

前回、NanoPi NEOのハードウェアPWMでLEDの明るさを制御してみました。

【NanoPi NEO/Armbian】PWMでLチカならぬLフワ?
PWMを使用可能にするには 前回はGPIO制御でLEDの点灯・消灯を 今回はハードウェアPWM制御でLEDの明るさを変えてみましょう。 NanoPi NEOは1チャネルのPWM(PWM0)を搭載していますが、ArmbianのデフォルトではP...

PWMが制御できるなら圧電ブザーも鳴らせるはず、ということで試してみます。せっかくなのでちょっとしたメロディを奏でてみたいと思いますが、音楽的な出来栄えや再生品質には期待しないでください。

圧電ブザー回路

圧電ブザーは「村田製作所 他励振式圧電サウンダ φ17mm PKM17EPPH4001-B0」を使います。

回路といってもPWM端子と圧電ブザーを接続するのみです。NanoPi NEOのPWM端子については前述の前回の記事を参照ください。

メロディを奏でるには

メロディを奏でる、ということは音階(ドレミ)を鳴動するということになります。音程(Pitch)を制御するにはPWMの周波数を制御します。

音色は波形できまるため、duty比を調整することで多少の音色変化は期待できるのですが、今回はduty比50%の矩形波にしておきます。

音階としては1オクターブの範囲、半音(#とか♭とか)はなしで制御することにします。音程と周波数・periodの関係は下表の通りになります。

音程周波数[Hz]period[nsec]
ド(C)261.6263822250
レ(D)293.6653405241
ミ(E)329.6283033723
ファ(F)349.2282863459
ソ(G)391.9952551053
ラ(A)440.0002272727
シ(B)493.8832024771
※periodは四捨五入

メロディを奏でるには音楽的なことも少し決めなければなりません。今回は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

おわかりいただけただろうか?

ということで、冒頭に「音楽的な質には期待しないでください」と書いた意味が理解いただけたかと思います笑。

今回の記事で使用した機材紹介

注釈

注釈
1 歴史は長く、筆者も1980年代頃にMZ-2500やX68000でYMOやゲームミュージックを打ち込んで鳴らしていました。

コメント

Amazon プライム対象
タイトルとURLをコピーしました