SSブログ

真夜中の訪問者2(ソフト編)

■タイミング設計について
前回、Audacityで玄関インターホンの発生周波数を測定したと書きましたが、時間変化と発生周波数の関係が読み取れません。そこでパソコンに 「Spek」という名前のソフトをインストールしました。これで時系列にスペクトラムを表示してくれます。
キーワードは、ubuntu Spek 音響スペクトラムアナライザ
s玄関エントランス・スペクトラム.png
グラフでは玄関インターホンの鳴動音。縦軸が周波数、横軸が時間軸を表しています。
このグラフ(わかりにくいです)と実際のオーディオ波形から、判定時間を決めました。
100usのサンプリング周期で8個(メモリ限界でした)なので、10KHzですが、ナイキスト周波数は5KHzとなります。1区間は5/4=1.25KHzの範囲です。玄関インターホンが鳴動すると、3区間目のレベルが設定値を越えるのをとらえて判定しています。(これが正解かどうかはわかりません)

■プログラムの流れ
●コンデンサマイクからの入力を100usの周期でAD変換
●AD変換値からゼロ点を求める
●FFT処理
●結果から2乗和の平方根を算出
●計算値が判定レベル以上ならLEDを点滅

■動作
      ●起動確認のため、通電するとLEDが2回点滅します。
      ●電池残量確認のため、30秒に1回点滅します。
      ●検知の判定は2段階で、レベルとタイミングは固定値です。
     ①マイク入力にある周波数範囲で入力があった場合、
     ②それから一定時間後に、そのマイク入力が無音を検出し、
     ③その後、一定時間後にレベル以上を検出すると、玄関の呼び出しと 判定します
     ●呼び出しがあればLEDの点滅を20秒間続けます。

■開発環境について
●対象マイコンはPIC12F1840 クロック:内部 32MHz駆動
●グラフィックLCD(ST7735液晶モジュール)はきむ茶工房さんのskST7735ライブラリをまるごと使用しています。ただし、メモリサイズの関係からフォントは使用しませんでした。(#include "font.h"をコメントアウトしています)。
●MPLAB X IDEはv5.25@linux mint 18.3
●使用コンパイラはXC8 (v.1.45) 。コンパイル後のデータメモリは206バイト(80%)プログラムメモリは3583バイト(87%)でした。
●ライターはPICKIT3を使用しています。デバッグ用ビルドではコンパイルエラーが発生し、いろいろなメッセージをみかけました。このメッセージ自体がコピーできないので原因追求がしにくかったです。
●LCD用SPI通信とデバッガとの相性が悪いようです。FFT演算結果の確認をする時はLCD表示をOFFにすることで対応しました。もっと良い方法があるのかもしれません。
●LCDが誤表示をする時があります。ライブラリの問題ではないことは確認できましたが、よくわからないのでLCDを表示する直前に割り込みを禁止しています。di()とei()はマクロで、定義はヘッダーで定義済みなので気楽に使用しています。
●configのLVP(Low voltage programming enable)はONですよ。
■プログラムリスト
/*   動作クロックは 32MHz タイマー1は10msに設定。
 *                                                                   PIC              ST7735      
 *                                                                   SCK     ←→  SCL
 *  1 VDD              8 Vss                               SDO     ←→  SDA
 *  2 LED               7 SDO                             VDD     ←→  RES
 *  3 AN3in            6 SCK                        GLCD_RS ←→  DC
 *  4 MCLR            5 GLCD_RS                VDD     ←→  BL  
 *                                              
 *                 PICKIT3のピン番号
 *                               ⑥未使用
 * 7 RA0(ICSPDAT)  ④
 * 6 RA1(ICSPCLK)  ⑤
 *                               ③GND
 *       ____                ②VDD
 * 4 RA3(MCLR)       ①    
 * 
 * LCD_TFTのスペックは ST7735の緑タブ品(基板は1.8inch 128*120 RGB と記載あり)
 *                端子を上にして左が①             接続するマイコンのpin番号
 *                 ①GND                                  ⑧ :GND
 *                 ②VCC                                   ① :VCC
 *                 ③SCL                                   ⑥ :SCL
 *                 ④SDA                                   ⑦ :SDO 
 *                 ⑤RES                                   ⑧ :VCC
 *                 ⑥DC                                     ② :LCD_RS
 *                 ⑦CS                                     ⑤ :LCD_CS
 *                 ⑧BL                                      ① :VCC
 * 
 */

#pragma config FOSC     = INTOSC //内蔵osc使用
#pragma config WDTE     = OFF //watch dog timer
#pragma config PWRTE    = OFF //Power up timer enable
#pragma config MCLRE    = ON  //Mclr/Vpp select bit
#pragma config CP       = OFF  //Code Protection bit
#pragma config CPD      = OFF   //Data Protection bit
#pragma config BOREN    = OFF  //Brown-out Reset enable bit 
#pragma config CLKOUTEN = OFF  //Clock out enable bit
#pragma config IESO     = OFF  //internal External switchover
#pragma config FCMEN    = OFF //Fall safe Clock monitor
 
#pragma config WRT    = OFF  //Flash Memory protection 
#pragma config PLLEN  = ON   //PLL enable bit 4*PLL
#pragma config STVREN = ON  //stack overflow/underflow reset
#pragma config BORV   = HI     //Brown-out reset Voltage
#pragma config LVP    = ON     //Low voltage programming enable
#include <xc.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <pic12f1840.h>

#define   xdebug 0
●LCD表示のライブラリ
LCD表示のライブラリは、きむ茶工房ガレージハウスさんの「TFT LCD(ST7735)モジュールに表示を行なう(skSDlib編)」を引用しました。

#include  "skSPIlib.h"
#include  "skST7735x.h"
//#include  "font.h"  

#define _XTAL_FREQ  32000000

#define PRESCALE  6
#define T0COUT  131

#define ON   1
#define OFF  0

#define samples 8

#define  upper_max  250
#define  lower_min    0

#define buffsize 8

#define Level1 120
#define Level2  50
#define Level3 120

int16_t   n;               // データ点数
int16_t   theta;           // m*2/n
int16_t  ar[samples];      //int32_t  入力データの実部(n点)
int16_t  ai[samples];      //int32_t  入力データ虚部(n点)
        
int16_t  spc[samples];

uint8_t incnt;
uint8_t AD_flag;
     
uint8_t spi_mode;
uint8_t acnt;
uint8_t dat;
uint8_t data;
uint8_t dat1;
uint8_t disp_mul;
uint8_t ad_mul;
uint8_t ii;
int16_t ad_buff1[buffsize];
uint8_t addata;
int16_t  mysqrt(uint16_t );
uint8_t prescaler;

//TMR1割り込み
uint16_t sec_tmr;
uint8_t  tmr_sw1;
uint8_t  tmr_sw2;
uint8_t  tmr_sw3;
uint8_t  tmr_sw4;
uint8_t  tmr_sw5;
uint8_t  tmr_sw6;
uint8_t  tmr_flg;

uint16_t sectimer1;
uint16_t sectimer2;
uint16_t sectimer3;
uint16_t sectimer4;
uint16_t sectimer5;
uint16_t sectimer6;

uint16_t xxx;
uint8_t sincnt;
uint8_t loop;
static const int8_t sin[] ={ //8*16  正弦テーブル(m点。sin(0..π))
                     0x00,0x03,0x06,0x09,0x0C,0x0F,0x12,0x15,
                     0x18,0x1C,0x1F,0x22,0x25,0x28,0x2B,0x2E,
                     0x30,0x33,0x36,0x39,0x3C,0x3F,0x41,0x44,
                     0x47,0x49,0x4C,0x4E,0x51,0x53,0x55,0x58,
                     0x5A,0x5C,0x5E,0x60,0x62,0x64,0x66,0x68,
                     0x6A,0x6C,0x6D,0x6F,0x70,0x72,0x73,0x75,
                     0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7C,
                     0x7D,0x7E,0x7E,0x7F,0x7F,0x7F,0x7F,0x7F,
                     0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7E,0x7E,
                     0x7D,0x7C,0x7C,0x7B,0x7A,0x79,0x78,0x77,
                     0x76,0x75,0x73,0x72,0x70,0x6F,0x6D,0x6C,
                     0x6A,0x68,0x66,0x64,0x62,0x60,0x5E,0x5C,
                     0x5A,0x58,0x55,0x53,0x51,0x4E,0x4C,0x49,
                     0x47,0x44,0x41,0x3F,0x3C,0x39,0x36,0x33,
                     0x30,0x2E,0x2B,0x28,0x25,0x22,0x1F,0x1C,
                     0x18,0x15,0x12,0x0F,0x0C,0x09,0x06,0x03,
                    };

static const int8_t cos[]={ //8*16 余弦テーブル(m点。cos(0..π))
                     0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7E,0x7E,
                     0x7D,0x7C,0x7C,0x7B,0x7A,0x79,0x78,0x77,
                     0x76,0x75,0x73,0x72,0x70,0x6F,0x6D,0x6C,
                     0x6A,0x68,0x66,0x64,0x62,0x60,0x5E,0x5C,
                     0x5A,0x58,0x55,0x53,0x51,0x4E,0x4C,0x49,
                     0x47,0x44,0x41,0x3F,0x3C,0x39,0x36,0x33,
                     0x30,0x2E,0x2B,0x28,0x25,0x22,0x1F,0x1C,
                     0x18,0x15,0x12,0x0F,0x0C,0x09,0x06,0x03,
                     0x00,-0x03,-0x06,-0x09,-0x0C,-0x0F,-0x12,-0x15,
                    -0x18,-0x1C,-0x1F,-0x22,-0x25,-0x28,-0x2B,-0x2E,
                    -0x30,-0x33,-0x36,-0x39,-0x3C,-0x3F,-0x41,-0x44,
                    -0x47,-0x49,-0x4C,-0x4E,-0x51,-0x53,-0x55,-0x58,
                    -0x5A,-0x5C,-0x5E,-0x60,-0x62,-0x64,-0x66,-0x68,
                    -0x6A,-0x6C,-0x6D,-0x6F,-0x70,-0x72,-0x73,-0x75,
                    -0x76,-0x77,-0x78,-0x79,-0x7A,-0x7B,-0x7C,-0x7C,
                    -0x7D,-0x7E,-0x7E,-0x7F,-0x7F,-0x7F,-0x7F,-0x7F,
                    };

static const int8_t sintbl[] ={       // 8個
                     0x00,0x5A,0x00,-0x5A,0x00,0x5A,0x00,-0x5A,  
                     };

●割り込みルーチン

void __interrupt () InterTimer( void ){
    if(INTCONbits.TMR0IF && INTCONbits.TMR0IE){
        INTCONbits.TMR0IF = 0;   //基本タイマーは100uS
        TMR0 = T0COUT;
    }

    if(PIR1bits.TMR1IF && PIE1bits.TMR1IE){
        TMR1H =  99;             //10ms timer
        TMR1L = 192;

        sec_tmr ++ ;              //電池の生存確認用に30秒に一度点灯
        if(sec_tmr > 3000){       //10ms*100 = 1sec
            sec_tmr = 0;      
            PORTAbits.RA5 = 1;     //LED ON
            tmr_sw1 = ON;
        }
        if(tmr_sw1 == ON ){        //ON時間タイマー
            sectimer1++;
            if(sectimer1 > 25){      //LED 250ms ON
                sectimer1 = 0;
                PORTAbits.RA5 = 0; //LED OFF
                tmr_sw1 = OFF;
            }
        }

        //報知のシーケンスが検出レベルに達した時(tmr_sw2がONした時) 点滅動作へ
        if(tmr_sw2 == ON){         //検出後の点滅タイマー
            sectimer2++;
            if(sectimer2 > 25){    //点滅のON時間250ms
                tmr_sw2 = OFF;
                sectimer2 = 0;
                PORTAbits.RA5 = OFF;     //LED OFF
                tmr_sw3 = ON;
            }
        }

        if(tmr_sw3 == ON){         //検出後の点滅タイマー OFF時間
            sectimer3++;
            if(sectimer3 > 40){    //点滅のOFF時間400ms
                tmr_sw3 = OFF;
                tmr_sw2 = ON;
                tmr_sw4 = ON;
                sectimer3 = 0;
                PORTAbits.RA5 = ON;     //LED 
            }
        }

        if(tmr_sw4 == ON ){  //sw2がON検出後の点滅している時間
            sectimer4++;
            if(sectimer4 > 2000){      //点滅を20秒で終了
                tmr_sw2 = OFF;
                tmr_sw3 = OFF;
                tmr_sw4 = OFF;
                sectimer4 = 0;                
                PORTAbits.RA5 = OFF;     //LED 
            }
        }

        if(tmr_sw5 == ON ){
            sectimer5++;
            if((sectimer5 >= 175) && (sectimer5 <= 300) ) {           //2375
                if( data < Level2){       //規定時間経過した時無音か
                    tmr_sw5 = OFF;
                    tmr_sw6 = ON;
                    sectimer6 = 0;
                }
            }
        }

        if(tmr_sw6 == ON) {
            sectimer6++;
            if((sectimer6 >= 150) && (sectimer6 <= 325) ) {           //3875
                if( data > Level3){   //規定時間経過した時レベルあるか
                    tmr_sw6 = OFF;
                    tmr_sw2 = ON;       //同時にLEDの点滅タイマーを起動
                    sectimer6 = 0;
                }
            }
        }
        
        if((sectimer5 > 3000)||(sectimer6 > 3000)) { //カウンターのリセット
            tmr_sw5 = OFF;
            tmr_sw6 = OFF;
            sectimer5 = 0;
            sectimer6 = 0;
        }
        
        PIR1bits.TMR1IF = 0;     
    }
}

●平方根ルーチン
以下のコードはプログラムモグモグさんの「平方根なんてビット演算ですればいいじゃない」より引用しました。重複防止のため、変数名を変えています。

        int16_t mysqrt( uint16_t xxx ){
        int16_t  aaa = 0;
        int16_t  ccc = 0;
        int16_t  yyy = 0;
        int16_t  iii = 0;
  
        int16_t  ttt = (int16_t )xxx;
  
        while(ttt >>= 1){
            ++iii;
        }
        for(iii += iii & 1; iii >= 0; iii -= 2){
            ccc = (yyy << 1 | 1) <= xxx >> iii;
            aaa = aaa << 1 | ccc;
            yyy = yyy << 1 | ccc;
            xxx -= ccc * yyy << iii;
            yyy += ccc;
        }
        return aaa;
     } 


void main(void){
        OSCCONbits.SPLLEN = 1;              
        OSCCONbits.IRCF = 0b1110;  // 内蔵発振周波数は8MHz
        OSCCONbits.SCS = 0b00;	  // クロックソースは内蔵発振or外部クロック                
        
        //IO_init
        APFCONbits.SDOSEL = 0;       // SDOに割り付け
        TRISA = 0b00011000;          //RA5:O, RA4:I, RA3:I,  RA2:O, RA1:O, RA0:O
        PORTAbits.RA5 = 0;

        ANSELAbits.ANSA4 = 1;         // AN3(RA4) only           
        TRISAbits.TRISA4 = 1;          
        
        ADCON0 = 0b00001101;           // AN3(RA4) only
        ADCON1 = 0b00010000;
        OPTION_REGbits.nWPUEN = 0;    // 弱プルアップ 0がenable  
        WPUA   = 0b00101100;

       
        //電源投入時 2回LED点滅
        PORTAbits.RA5 = 1;
        __delay_ms(200);
        PORTAbits.RA5 = 0;
        __delay_ms(400);
        PORTAbits.RA5 = 1;
        __delay_ms(300);
        PORTAbits.RA5 = 0;

if(xdebug){
        SSP1CON1bits.WCOL = 0;
        SSP1CON1bits.SSPOV = 0;
        
        SSP1CON1bits.SSPEN = 1;     //SPI disenable
        SSP1CON1bits.SSPM  = 2;      //SPI_MASTER 2:Fosc/64
        SSP1STATbits.SMP   = 0;        //sampling data:middle
        SSP1CON1bits.CKP = 0;         //アイドル時 Lで、立ち上がりで送信
        SSP1STATbits.CKE = 1;
        SSP1CON1bits.SSPEN = 1;    //SPI enable
}

 //
 // Graphic LCD (SPI) ST7735_TFT-LCD
 //
    if(xdebug){    
        GLCD_DisplayInit();
        GLCD_begin(CHIP_ST7735X | DISPLAY_TYPE_1,BLACK_SCREEN) ;
        __delay_ms(100);
        GLCD_ColorSet(0,0,63) ;        // 青色
        GLCD_Box(15,25,150,120);
        GLCD_CLS(0);                    //画面クリア 0:黒 1:白
    }
        prescaler = PRESCALE;
        OPTION_REGbits.TMR0CS = 0;
        OPTION_REGbits.TMR0SE = 0;
        OPTION_REGbits.PSA = 0;
        OPTION_REGbits.PS = prescaler - 1;
        TMR0 = T0COUT;
        INTCONbits.TMR0IE = 1;      // Timer 0 Enable
        INTCONbits.TMR0IF = 0;      // interrupt Flag clear

        //Timer1 init
        T1CONbits.TMR1ON = 1;
        T1CONbits.TMR1CS = 0;       //fosc/4
        T1CONbits.T1CKPS = 1;       //prescaler 1:2^1
        TMR1H = 99;                          //10ms timer
        TMR1L = 192;
        PIE1bits.TMR1IE = 1;
        INTCONbits.PEIE = 1;
        
        //AD secdtion
        n = samples;
        incnt = 0;
        disp_mul = 3;
        ad_mul = 3; 
        INTCONbits.GIE = 1;


    while(1){
        for(incnt = 0; incnt < samples ; incnt++){              
            ADCON0bits.GO_nDONE = 1;
            while(ADCON0bits.GO_nDONE);

            addata = ADRESH;
            if( addata > upper_max ) addata = upper_max ;
            ad_buff1[incnt] = (int16_t)addata;         
            __delay_us(100);
        }
●FFT演算処理
以下のFFTに関わるコードはBlackfin空挺団::Blog「FFTを固定小数点化するblackfin(6)」より引用しました。16ビット→8ビットのため、変数のサイズ、castの明確化などを変更しています。

            int8_t m;
            int8_t mh;
            int8_t i;
            int8_t j;
            int8_t k;

            int16_t wr;
            int16_t wi;
            int16_t xr;
            int16_t xi;
        
            int16_t rr_b;
            int16_t ii_b;
            int16_t ri_b;
            int16_t ir_b;
            
            int16_t AD_sum;
            int16_t Average;
            uint8_t ll;
            uint8_t mm;
            
            AD_sum = 0;
            theta = 64 * 2/n;
            for( acnt = 0; acnt < samples ; acnt ++){
                ar[acnt] = (int16_t) ad_buff1[acnt];

                AD_sum += ar[acnt] ;
                ai[acnt] = 0;
                
            }

            Average = AD_sum / samples ;           

            for(mm = 0; mm < samples; mm++){
                ar[mm] = (ar[mm] - Average) << ad_mul;
            }
/* FFT演算の検証のためのテスト用データをAD変換バッファに書き込む
================================================================
            //ar[]← SinWave
            for(sincnt = 0; sincnt < samples ; sincnt++){
                ar[sincnt] += ((int16_t) sintbl[sincnt]) ;
            }
=================================================================
*/

            for (m = n; m > 1; m /= 2) {
                mh = m/2;
                for (i = 0; i < mh; i++) {
                    wr = (int16_t) cos[theta * i];
                    wi = (int16_t) sin[theta * i];
                    for (j = i; j < n; j += m) {
                        k = j + mh;
                        xr = (ar[j] - ar[k]) >> 1 ;
                        xi = (ai[j] - ai[k]) >> 1 ;
                        ar[j] = (ar[j] + ar[k]) >> 1 ;
                        ai[j] = (ai[j] + ai[k]) >> 1 ;
                        rr_b =  wr * xr;
                        ii_b =  wi * xi;
                        ri_b =  wr * xi;
                        ir_b =  wi * xr;
                        ar[k] = (rr_b - ii_b) >> 8; // >>15   
                        ai[k] = (ri_b + ir_b) >> 8; // >>15
                    }
                } 
                theta *= 2;
            }
    // unscrambler 
            i = 0;
            for (j = 1; j < n - 1; j++) {
                for (k = n >> 1; k > (i ^= k); k >>= 1);
                if (j < i) {
                    xr = ar[j];
                    xi = ai[j];
                    ar[j] = ar[i];
                    ai[j] = ai[i];
                    ar[i] = xr;
                    ai[i] = xi;
                }
            }

        if(xdebug){    
                di();
                GLCD_ColorSet( 63, 63, 63) ;       // 白色63all→0all
                GLCD_Line(2,2,2,132) ;             // index yline
                GLCD_Line(2,2,162,2) ;             // index xline
                GLCD_Line(160,2,160,132) ;         // index yline
                GLCD_ColorSet( 63, 63 , 0) ;       // 黄色
                GLCD_Line( 2, 2    , 160  ,2    ); // 0
                GLCD_Line( 2, 2+127, 160  ,2+127); // 横線が 128(0x7F) のレベル
                for(ii = 0;ii < samples ;ii++ ){
                    if(ii <  4) GLCD_ColorSet( 63, 63, 0) ;              // 黄色
                    if(ii == 4) GLCD_ColorSet( 63,  0, 0) ;              // 赤色
                    if(ii >  4) GLCD_ColorSet(  0,  0,63) ;              // 青色
                    GLCD_Line( 15+ii*10, 0, 15+ii*10,3);   //目盛り
                }
                ei();

        }        
                for( ii = 0 ; ii < samples ; ii++ ){
                    spc[ii] =   ((ar[ii] * ar[ii]) + (ai[ii] * ai[ii])) << 4;  

                    if( spc[ii] < 0 )  spc[ii] = 32767 ;
                    dat = (uint8_t) mysqrt( spc[ii] );
                    if(ii == 2) data = dat;
                    dat1 = dat >> 1;               //LCD表示用データ
                    if(dat1 > 128 )  dat1 = 128;   //Yのはみ出し防止
                    
                }
       if(xdebug){
           __delay_ms(2000) ;                  //適当な数値
              di();
              GLCD_CLS(0);                    //画面クリア 0:黒 1:白
              ei();
       }
            if(data >= Level1) { 
                if((tmr_sw5 == OFF) && (tmr_sw6 == OFF)){
                    tmr_sw5 = ON;
                    sectimer5 = 0;
                } 
            }
    }
}

■もっと、どうにかしたい時は
もっと精密に設計するためには、サンプリング数をもっと増やせば可能です。メモリーサイズが多ければ周波数を細分化できます。ネットで調べていたらTI製のLMC567というCMOSトーンデコーダを見つけました。オリジナルメーカーは不明ですが、JRCのNJM567のCMOS版だそうです。形状がDIP8P。アリババで今でも手に入ります。検知周波数はCRで決めることができますが、メカニカルスイッチで切替えるのは面白くないので、デジタルポテンショメータICのマイクロチップ社MCP41010の抵抗と組み合わせるともっと精度がいいものができそうです。アナログ+デジタルのハイブリッド構成で計測器レベルで作れそうです。
作ってから気がついたのですが、電池がすぐになくなってしまいます。特性をみると、CR1220は35mAhでした。220mAhのCR2032の方がよかったかもしれません。基板スペースも問題ないですね。でも、多分、プロならスリープ状態にしておいてマイク入力レベルで目を覚ます方式にするのでしょう。
 ■謝辞
前回にも言いましたがFFT(高速フーリエ変換)についての知識はほとんどなく、そのプログラムについても全くの素人です。この課題を実現するため、下記のブログにある、プログラムリストを引用しました。特に固定小数点によるFFTについては、酔漢さんのBlackfin空挺団::blogの「FFTを固定小数点化する(1)〜(6)」では基礎的な知識からマイコン実現での問題点について丁寧に教えていただきました。でも、決して、各著作者様へのお問い合わせはしないようにおねがいします。
//********************************************************
(1)Blackfin空挺団::Blog「FFTを固定小数点化する(6)」
(2)プログラムモグモグ「平方根なんてビット演算ですればいいじゃない」
https://itchyny.hatenablog.com/entry/20101222/1293028538
(3)きむ茶工房ガレージハウス「TFT LCD(ST7735)モジュールに表示を行なう(skSDlib編)」
http://zattouka.net/GarageHouse/micon/MPLAB/18F25K22/GLCD/ST7735/ST7735x_2.htm

炭酸100%のハイボールがめっちゃうまいそうです。炭酸メーカーでバーボンのハイボールを作れるのですが、こずかいが・・・・・。今年の夏の目標にしましょう。
※このブログで書かれた内容によって生じた損害等の一切の責任を
負いかねますので、予めご了承下さい。

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

真夜中の訪問者

キーボードを鳴らすのは夜が多い。朝からとか昼間とかにガンガンと音楽ではなくてキュイーンとかブィーンとかの音を鳴らす(弾くとは言っていない)にはちょっと気がひける。夜もヘッドフォンをつけてすることになる。しかし、これがなかなか落ち着かないのだ。没頭している間に電話が鳴ったら、何かの急用で来客があったらどうしようかと。真夜中に人が来ることなんて無いと思っていたら、何の連絡もなしに、実際に人が来ることがあるのだ・・・・。
それはある日のこと、夜中の3時に突然ガス警報器が作動し、警報を発した。対処の方法がわからず警報機を取り外して様子をみていたら、それから1時間ほどして玄関に来訪者があった。警備会社の社員らしく、様子を見に来たという。経緯を簡単に話してお引取りをいただいたが、真夜中に寝ぼけ姿ではなく、普通の服装で出てきたのは相当不審に思えたであろう(都会ではなく地方では特に)。

■玄関インターホンの検知
あまりに不安が募ると おちおちとしていられないので、来客や電話の検知をしてやろうと思い立ちました。ヘッドフォンに周囲の音を拾ってシンセ音とミキシングするというのではおもしろくありません。それではと、マイクに設定値以上の入力レベルがあればお知らせサインを出力するプログラムを作ってみましたが、敏感にしすぎたのか、やたらと警報が発生し、閉口しました。そこで周波数で判別してみようと思い立ちました。こういう時はそう、(みんな大好き)フーリエ変換なのだ。と言いつつ、そんな知識はまったく持ち合わせていません。FFTとかバタフライ演算などというキーワードは自慢ではないが聞いたことがある程度です。ネットの情報を探ってみた。そこで得た情報を元に、実現の条件をまとめてみる。

●なるべくコンパクトにしたい
●計算速度を考えるとやっぱりFFT(高速フーリエ変換)でしょう
●計測器を作るわけではないので、精度はそこそこにし、周波数範囲も可聴範囲で。
●浮動小数点演算ではなく、固定小数点演算が簡単そう。
●回転因子(これなに?)の演算は難しそうなので、ここは三角関数をメインにします。

■固定小数点とは何か
上にも書きましたがFFTを説明できるほどの能力はまったくありません。さらに固定小数点とは????なのでキーワードで検索してください。
キーワードは、FFT 固定小数点演算


■ターゲットの音源
当面のターゲットは玄関インターフォンの音色。この周波数はどうして測ればいいのか。パソコンでは音楽関連にはAudacityというプログラムがあります。音楽・音声に関しては思いつくことはほとんど出来るので重宝しています。このソフトの解析タブにスペクトラム表示というのが用意されているのでこれを利用することにしました。

キーワードは、Audacity スペクトラム表示 前後を反転

余談ですが、Audacityに関して感動したことがあります。さる有名な曲は、放送禁止になった元の曲を逆回転で再生して着想を得たという話がありました。実際に確認してみようと、テープで、モノーラルで録音した音をステレオで再生したらその逆回転の音を得ることが出来ました。この作業は準備に時間がかかり、結果は数分というものだったのです。これがAudacityではコマンド一発でできたことで感動しました。ローマ字で watasi wa bakadesu と書き、逆にしてusedaka bawisataw と録音し、逆再生するとちゃんと言葉になって聞こえるというのも音の真髄に触れるようで感動ものです。

■PICマイコンの謎
ネットでFFT関連のプログラムを検索すると、PICについてはほぼすべてが dspicに関するもので、8ビットマイコンシリーズではほとんど紹介がありません。理由はプログラムメモリーが少ない、メモリーも不十分だからです。だったらどこまで出来るのかを確認してみようと思いました。

■回路図
FFTcaller.png

■使用する部品のリスト
部品リスト 数量 単価
PIC12F1840-I/P 1 110
NJU7043D 1 70
ECマイクロホン C9767 1 100(4個で)
半固定VR(50KΩ)
1 40
LED 赤Φ5(Φ3の方が良好) 1 20
バッテリケース タカチ HU1220
1 176
電池 CR1220 (3V) 1 510(5個で)
片面ユニバーサル基板 60×28 
1 -
清涼菓子ケース 1 -

●PIC12F1840のマニュアルは英文ですが、同じファミリーの12F1822のが和文です。
●デジタルとアナログ回路の分離についてはトランジスタ技術2020年3月号p.64の回路(下記)を引用しました。マイク増幅回路のノイズがほぼ解消しました。
●OPアンプはレールトゥレール入出力(フルスイング)である必要があります。
●液晶モジュールはST7735R(緑色タブ)が使用可能ですが、クリップで接続し使用する形になります。接続した状態でのデバッガ操作に問題がありました。
●PICKIT3との接続もクリップで行います。
■組み立て
sPIC_164849.JPGsPIC_164825.JPG









●基板は清涼菓子のケースに収容しますが、フリスク品がなく、よく似たものにしました。
●マイコンやOPアンプに8P DIPソケットを使用すると、約1.5mmはみ出します。
●LEDはΦ5のものを使用しましたが、約2mm飛び出します。
●電池ケースはタカチ製がピッタリです。最適な形状のLR44は1.5V(残念)なので注意
●チップ抵抗やコンデンサはパターン面に配置しましたが、固定は接着剤のB-7000を使用しました。スマホの修理では必須部品です。熱が加わると固定が外れます。

sPIC_170340.JPGsPIC_170359.JPG







Curtis Fullerの「BLUES-ette」を聴きながらバーボンを飲むと、一気に1960年ニューヨークのファイブスポットカフェにジャンプできますぜ。

********************************************************
以下の記事から回路を引用しました。
(1)「ライセンスフリー!MP3ソフトウェアプレーヤの製作」
    白阪一郎様  トラジスタ技術 2020年3月号 p.64 CQ出版社
※このブログで書かれた内容によって生じた損害等の一切の責任を負いかねますので、予めご了承下さい。

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

メンテナンスしよう

 ずっと前からの課題だった、DX7の液晶バックライト化とコイン電池の交換をしようと思い立ちました。ネットで情報を探ると、本当に親切丁寧に教えてくれるページが多いです。
ネットの情報、特にyoutubeの動画を見ながら本体を分解し、LCDモジュールに部品を取り付けると見事にバックライト化は完了しました。(おしまい、おしまい)・・・・・これが理想なのですが、そうはいかなかったので備忘録に書いておくことにしました。

■液晶のバックライト化
数年前からNOZ CELICAさんの「NOZ's Stylish Sound♪」を拝見していました。そのYAMAHA「DX7」の液晶ディスプレイ交換♪ のページの内容を参考にしました。
●交換に使用するバックライト付き液晶
NOZさんのページで、KD-162-626LPSG-LGという型番のバックライト液晶が紹介されています。このページがきっかけで電子部品通販サイトの鈴商で数年前に購入しました。残念なことに現在は取り扱いがないようです。しかし、親切なことに、この部品の取説を置いてくれています。httpsで暗号化されていないとブラウザに注意されます。
キーワードは、鈴商、KD-162-626LPSG-LG pdf

SPIC_20200203_160121.JPGこの製造元のCrystalfontz社をネットで調べると、キャラクターLCDのページにCFAH1602シリーズという仕様の近いものがありました。(同等かは不明です)



DX7/9 SERVICE MANUALによると、元のLCD(バックライト無し)は日立製LM016という品番だそうです。これにバックライトが付いたLCDが日立(ルネサス?)から出ているのか出ていたのかはわかりません。
キーワードは、YAMAHA、DX7/9、Service Manual 
ネットをざっと調べたところでは、HD44780コントローラを使用して、16×2行のLCD部品は見つかりますが、外形が異なるとか、ピン配列が違うものばかりでした
●基板接続用のコネクタ
液晶モジュール基板には16個の穴が空いていますが、DX7の元からのコネクタは14極なので、受け側のベース付ポストは14極の日圧製 S14B-XH-A を使用しました。形状はトップ型ではなく、サイド型でないと取り付け時に泣くに泣けないことになります。電子部品店に今でもあるのかとちょっと心配でしたが、ちゃんと置いてありました。
SPIC_20200203_165914.JPG元のLCDモジュールと上に付いている化粧パネルを分離するのが一番苦労するところです。無理に引き剥がすことはせず、ホットエアーを使います。ドライヤーでももちろんOKですが、ノズルの形状によりスポット状に熱風が当たりますので作業性が抜群です。
余談ですが、スマートフォンの部品固定には接着剤が多用されていますので、スマホの修理では必須アイテムだと思います。価格的には、工業用がウン十万していましたが、数千円で買える物もあります。それでも購入には相当悩んだのですが、パネルを3回ほど交換して、元は取れたと思いました。
●改造作業
バックライト用の電源を5Vラインから取るのですが、モジュールのピン配列に注意です。XH14Pの1番ピンの隣が16ピンで、その隣が15ピンです。1ピンはVssですから、16ピンのLED(-)とは隣同士。2ピンはVddなので15ピンのLED(+)とはアキシャルのリードが配線に便利です。

SPIC_20200203_204506.JPGSPIC_20200203_165408.JPGCrystalfontzの説明書ではLCDへの供給電圧がTyp 5Vのところ、バックライトLED側はTyp4.2Vです。0.8Vならダイオードの電圧降下分でいけると考え、高速スイッチングダイオードにしました。
1N4148のアキシャルリードタイプを取り付けてみましたが、5Vが4.6Vくらいしか落ちません。では、という訳で2本直列にしてみると4.2Vになりました。


輝度の調整は何もしませんでしたが、結局高くついたので抵抗にしておけばよかった。
カタログを見ていたらLCDモジュールのバックライトLEDに制限抵抗が入っていないのに気付きました。なので、ダイオードで電圧降下するのはやめて抵抗(22Ω以上)に変更します。画像もそのうち変更します。(どうもすいませんでした)

■バッテリー交換
バッテリーはCR2032なので、タカチ製コイン電池ホルダー CH7410、電池ホルダーを包み込む用の熱収縮チューブ、基板から電池端子を外すための100Wのはんだごてなどを用意しました。ところが、テスターで両端の電圧を測ってみると何と3.02Vもあります。基板を外した上に、さらに電池の端子を外すのはちょっと面倒なので、今回は保留にしました。


炭酸メーカーで作った炭酸を飲むとほんとに美味しい。でもこれがいつの間にか強炭酸になり、そのうちに強強炭酸にエスカレートしていく予感が・・・・。


※このブログで書かれた内容によって生じた損害等の一切の責任を負いかねますので、予めご了承下さい。

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。