wvogel日記

自分用の技術備忘録が多めです.

メダマトーン製作

はじめに

昨今の半導体不足で,秋月での買い物は遅れに遅れ泣く泣く街に繰り出したり(digikeyは3日後には家に届きました...!)していましたが,家にある部品だけを使って子供用(2-3歳)のおもちゃを作ることにしました.

対象年齢2-3歳ということで,小さい子でも持ちやすいサイズに納める必要があります. また,操作が複雑なのは好ましくありません.姪っ子は時々バイオリンを触りにきますが,弓の往復よりも簡単な操作方法であることが一つの条件と考えます.

そこで,超音波センサを使って,床(天井)からの距離で奏でる音階が変わる電子楽器を作ることにしました. 超音波センサは,発信用・受信用センサが二つあり人の眼のようにも見えますから,有名な電子楽器オタマトーンをもじって,メダマトーンと名付けましょう.オタマトーンは指板上で指をスライドしたり,お口をぱくぱくさせる運動が必要ですが,メダマトーンは箱を上下させるだけですから,2,3歳の子でも十分扱うことが可能です.

f:id:wvogel00:20210918171523j:plain
メダマトーン 完成図

部品選定

まず初めに筐体を決めます.幸い家にいくつかタカチのケースがあったので実物をもとに検証することができました.結果,手持ちのケースでは子供にはやや大きすぎるきらいがあると判明したため,小さくてcuteなSW-55Bに決定しました. 寸法上,超音波センサHC-SR04が枠内ぴったりに収まる形状ですが,その分回路部に許される包絡域は非常に限られます.5V動作の超音波センサに必要な昇圧回路の試作では1cm四方に収めることができましたがもう一度実装するのが面倒になったのでamazonで適当なモジュールを買って分解することにしました.測定では4.97V出ていたので問題ないでしょう.

www.monotaro.com

3942 Adafruit Industries LLC | センサ、トランスデューサ | DigiKey

https://www.amazon.co.jp/gp/product/B077XRWK3Q/ref=ppx_yo_dt_b_asin_title_o04_s00?ie=UTF8&psc=1

メダマトーンに過不足ないフラットパッケージのマイコンは手持ちになく,また手元にある基板は2.54mmピッチなので,8pin DIP 12F683を採用しました. これはGP2からPWM出力が可能なので,横着実装で音階を奏でるにはぴったりです.

製作

回路を引いて基板に実装しました.若干苦労したので記録

f:id:wvogel00:20210918231203p:plain
回路図
f:id:wvogel00:20210918170919j:plain
実装後

写真に見えている面の背面に超音波センサが取り付いていますので,超音波センサと基板の間はカプトンテープで絶縁しています.

ボタン電池の下側に見えているのが昇圧基板です.購入時にはUSB端子がついていますが,そんなものがあっては入りませんのでカットしました.また,高さを稼ぐために部品を載せている基板には厚さ0.4mmの片面基板を採用しています. ただ,このままでは蓋が閉まりませんのでさらに筐体内部の空間を稼ぐために,超音波センサについている推奨発振子の位置に穴を開けた他,半田面のリード線をことごとくカットしました.一部カット漏れがあり,蓋を閉めるとショートして常にスイッチが入りっぱなしになったのでこれも修正します.

しかしまだ問題があります. 箱側面に見えている緑色スイッチはタクトスイッチなのですが,これの固定に苦労しました.

  1. メタルロッックで固着 ... タクトスイッチのボタン面についている出っ張りにより箱と密着できず剥がれる
  2. エポキシパテ+メタルロックで固着 ... 圧力の加わる方向に支えがないので剥がれる

結局,エポキシパテでボタン—基板間を充填することで固着しました. ソースコードも掲載しておきます.もし制作する場合は,下記改造が考えられます

  • 測距の変化率が大きい時に音が大きく変化するのを改良
  • PWMの変化幅を増やし対応オクターブを増やす
/*
 * File:   main.c
 * Author: toriiwataru
 * Created on September 17, 2021, 23:47 JST
 */

// CONFIG
#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select bit (MCLR pin function is digital input, MCLR internally tied to VDD)
#pragma config CP = OFF         // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown Out Detect (BOR enabled)
#pragma config IESO = OFF       // Internal External Switchover bit (Internal External Switchover mode is disabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled)

#include <xc.h>
#define _XTAL_FREQ 4000000 //8MHz

#define HIGH        1
#define LOW         0
#define PWM         GPIObits.GP2
#define ECHO        GPIObits.GP5        //INPUT
#define TRIGGER     GPIObits.GP4
#define BUTTON      GPIObits.GP1        //INPUT
#define N           8

char freqList[N] = {239,213,190,179,159,142,126,119};   //C,D,..,C
char avgList[N] = {239,239,239,239,239,239,239,239};

void StopPWM()
{
    CCP1CON = 0x00;
}
void StartPWM()
{
    CCP1CON = 0x0c;
}

void SetPWM(char v)
{
    PR2 = v;
    CCPR1L = v/2;       //duty cycle 1:1
}

void Initialize()
{
    OSCCON = 0x60;      //4MHz    
    TRISIO = 0x22;      // gp5, gp1 is input
    CMCON0 = 0x07;      //CIN pins as I/O
    ANSEL  = 0x00;
    GPIO   = 0x00;
    
    //setting PWM
    PR2 = freqList[0];
    CCPR1L = freqList[0]/2;       //duty cycle
    StartPWM();
    T2CON = 0x03;       //prescaler=16
    T2CONbits.TMR2ON = 1;//T2 ON
    TMR2 = 0;
}

void DoReMi()
{
    for(int i = 0; i < N; i++)
    {
        SetPWM(freqList[i]);
        __delay_ms(200);
    }
}

int MeasureEcho()
{
    int counter = 0;
    
    TRIGGER = HIGH;
    __delay_us(10);
    TRIGGER = LOW;
    
    while(ECHO == LOW)  //wait for rising edge
        ;
    
    while(ECHO == HIGH) //wait for falling edge
    {
        counter++;
        __delay_us(10);
    }
    __delay_ms(20);
    return counter;
}

char IsPressed()
{
    char isPressed = !BUTTON;
    for(int i = 0; i < N; i++)
    {
        __delay_us(200);
        isPressed &= !BUTTON;
    }
    return isPressed;
}

void main(void) {
    char prevValue = 0;
    
    Initialize();
    DoReMi();
    
    while(1)
    {
        int avg = 0;
        const int threshold = 20;
        
        if(IsPressed())
            StartPWM();
        else
            StopPWM();
        
        for(int i = 1; i < N; i++)
            avgList[i-1] = avgList[i];
        avgList[N-1] = MeasureEcho();
        for(int i = 0; i < N; i++)
            avg += avgList[i];
        
        int value = freqList[0] - (2*avg)%freqList[0];
        
        if(   prevValue-value > prevValue/threshold
           || value-prevValue > prevValue/threshold)
            SetPWM(value);
        
        prevValue = value;
    }
}