PIC でサイレン音を再現する(DDS 使用)

以前、PIC だけで救急車のピーポー音を再現しようとしましたが、和音をプログラムだけでやろうとすると中々厳しい物があり断念してました。その後、NE555 を複数使ってミックスする方法も考えましたが、あまりにもスマートではない。

何か方法はない物かと放置してましたが、秋月電子にダイレクト・デジタル・シンセサイザー(DDS)キットなるものがあり、これを使えば希望の周波数の音を簡単に得られるということで、今度はコレを利用して実験してみようと思います。

▼ ということで、早速 DDS キットを購入しました。

このキットの詳細・製作等は他に譲ります。

さて、今回は PIC で制御したいわけですから、シリアルモード前提で部品を半田付けしていきました。

ちなみに正月の事で、おせちを食べながらの半田付けです。

キットが完成したらタイミングチャートに従ってシリアルでコマンドを送るだけです。


シリアルでコマンドを送るだけ


PIC 初心者にとって皆目見当が付きません。

ということで、ググってみます。 ところが、これが意外に目的の情報が見付からない。

DDS をマイコンで制御するといっても PIC じゃなく AVR だったり。 PIC でも C 言語や BASIC 言語での解説だったり。

「あの〜 PIC アセンブラでのサンプルソースが欲しいんですけどぉ・・・」

そして、やっとこちらで見つけることが出来ました。
untitled

ここにあるプログラムソースを命令一覧表とニラめっこしながら解読すること 2 時間! なんとなく分かってきたので、DDS への制御部分だけを抜き出し、いつもの 12F629 用に書き直して、とりあえず 1Khz(1024Hz) の周波数を発振させるようにしてみました。

プログラムを書き込んでブレッドボード上に組み、DDS の出力にアンプ付きスピーカーと周波数カウンタをつないで電源オン!

・・・出ないの

ピーという音が聞こえてくるはずなのですが、スピーカーは沈黙を守ったままです。

DDS への送信部分はサンプルソースをそのままコピーしたので間違っているわけがない。それではと、ブレッドボード上をもう一度確認してみると 3 本の制御線のうち、クロックとストローブが入れ替わってる! → 入れ替えて電源オン! → 出ないの・・・

プログラムソースを再度確認、あっ送信コマンドが違ってた! → 修正コマンド値 OK! → 電源オン! → 出ないの・・・

これ壊れてるんじゃね? 一度パラレルモードで試してみよう! → DIP スイッチを付けて 1Khz に設定してっと! → 電源オン! → ピー

壊れてはいないようです。ならなんで出ないの!?

その後ソフトもハードも何度も見直しましたが、どうしても発振させることができません。 もう半ば諦めかけた今日この頃、ようやくスピーカーからピーが聞こえてきたのです! 1Khz に 1 週間ですか。

問題点は二つ。 一つはお恥ずかしいのですが、ブレッドボード上に組んだ PIC の − 側の配線をしてなかったこと。 いつの間にかジャンパワイヤが抜けていました。 そら動かんわな。

そしてもう一つは、PIC の初期設定後、DDS へのコマンド送信前にウェイトを入れること。 12F629 は内部 4Mhz クロックで動かすので、タイミングチャートにある min 25ns,50ns,100ns のパルス幅の規定に十分余裕がある(この場合の PIC の 1 命令は 1us)のですが、PIC 初期化後に続く DDS 初期化命令がうまくなされないようなのです。

これは電源 ON による内部の立ち上がりに PIC と DDS とでは時間差があるためだと思われます(PIC は初期化命令まで進んでいるのに DDS 側はまだコマンドを受け付ける状態にまで立ち上がっていない)。

ここに待ち時間を入れる(1ms)ことにより、なんの問題もなく発振してくれるようになりました。

▼ やったー! 思わず拍手しましたよ。

さて、お待ちかね? プログラムリストです。

;*********************************************
;PIC12F629で秋月のDDSを制御する
;PA0(クロック),1(データ),2(ストローブ)
; テスト1 1KHZ 出力
;2011/01/03 ver.1(初版)
;*********************************************
list p=12f629
#include

__CONFIG _CP_OFF & _CPD_OFF & _BODEN_OFF & _MCLRE_OFF & _WDT_OFF & _PWRTE_ON & _INTRC_OSC_CLKOUT

;*********************************************
DAT_HIGH EQU 0x20 ;Data Higher Byte
DAT_MID EQU 0x21 ;Data Middle Byte
DAT_LOW EQU 0x22 ;Data Lower Byte
DCNT EQU 0x23 ;ビットカウンタ
TEMP EQU 0x24 ;テンポラリ

i EQU 0x25 ;ループカウンタ用変数
j EQU 0x26 ;ループカウンタ用変数
k EQU 0x27 ;ループカウンタ用変数

ORG 0x000
GOTO INIT

;割り込みハンドラ
ORG 0x004
RETFIE

;*********************************************
;初期化開始
INIT
;内部クロック補正
bsf STATUS,RP0 ;BANK=1
call 0x3ff
movwf OSCCAL
bcf STATUS,RP0 ;BANK=0
;割り込みを禁止
clrf INTCON
;コンパレータ使用禁止
movlw 0x07
movwf CMCON
;GPIO 0/1/2 を出力に設定
bsf STATUS,RP0 ;BANK=1
movlw b'11110000';GPIO 0/1/2 を出力に使用
movwf TRISIO
bcf STATUS,RP0 ;BANK=0
bsf GPIO,0 ;クロックを1
bcf GPIO,1 ;データを0
bsf GPIO,2 ;ストローブを1

CLRF DAT_LOW ;データクリア
CLRF DAT_MID ;データクリア
CLRF DAT_HIGH ;データクリア
;*********************************************
;メインルーチン
DDS_INIT
movlw d'1' ;1ms遅延(これがないと正常に機能しない場合あり)
movwf i
call DELAYms

;コマンド1(DDSの出力をONにする)
MOVLW B'00001111' ;0000 1111 をWへ
CALL CMD ;アドレス&コマンドをDDSへ送信

MOVLW 0 ;LOW
CALL DSND

MOVLW 0 ;MID
CALL DSND

MOVLW 0 ;HIGH
CALL DSND

MOVLW 0 ;24、25ビット目ダミー
CALL DUMY

BCF GPIO,2 ;ストローブアウト、DDSへの送信完了
BSF GPIO,2

DDS_OUT_1K
;周波数1KHZ(1024Hz)を出力
MOVLW B'00000000'
MOVWF DAT_LOW ;最初の8ビットセット
MOVLW B'00000100'
MOVWF DAT_MID ;次の8ビットセット
MOVLW B'00000000'
MOVWF DAT_HIGH ;次の8ビットセット
CALL DDS ;コマンドC送信

MAIN

GOTO MAIN

;*******************************************
;**** DDS送信サブルーチン
;*******************************************

;**** コマンドC(CH1 データセット & アウトプット) 送信
DDS
MOVLW B'01100111' ;0110 0111
CALL CMD ;command out
MOVF DAT_LOW,W ;LOW
CALL DSND ;最初の8ビット
MOVF DAT_MID,W ;MID
CALL DSND ;次の8ビット
MOVF DAT_HIGH,W ;HIGH
CALL DSND ;次の8ビット
MOVLW 0 ;ダミー2ビット
CALL DUMY
BCF GPIO,2 ;ストローブアウト、DDSへの送信完了
BSF GPIO,2

RETURN

;**** ダミービット(24,25 bit目)送信サブルーチン
DUMY
MOVWF TEMP ;Wレジスタの値をTEMPへセーブ
MOVLW 2 ;ダミーは2ビット
MOVWF DCNT
CALL DDS_LP
RETURN

;**** アドレス&コマンド アウトプットサブルーチン
CMD
MOVWF TEMP ;Wレジスタの値をTEMPへセーブ
MOVLW 7 ;アドレスとコマンドは7ビット
MOVWF DCNT ;シフトカウンタにセット
CALL DDS_LP
RETURN

;**** データ アウトプットサブルーチン
DSND
MOVWF TEMP ;Wレジスタの値をTEMPへセーブ
MOVLW 8 ;
MOVWF DCNT ;ビットカウンタ
CALL DDS_LP
RETURN

;**** ビット送信サブルーチン ****
DDS_LP
RRF TEMP,F ;TEMPの内容をキャリーを通して右へシフト、結果はFへ
BTFSC STATUS,C ;キャリーフラグがゼロだったら一つ先の命令へジャンプ、1だったら次の命令へ
GOTO DDS_1
BCF GPIO,1 ;データセット 0
GOTO DDS_CK
DDS_1
BSF GPIO,1 ;データセット
DDS_CK
BCF GPIO,0 ;クロック L
BSF GPIO,0 ;クロック H
;クロック L -> H で 1 ビット送信完了
DECFSZ DCNT,F ;DCNTの内容を-1して結果をFに入れ、結果がゼロになったら一つ先の命令へジャンプ
GOTO DDS_LP
RETURN

;*********************************************
;ミリ秒遅延ルーチン(1ms 〜 254ms まで)
;内部発振モード4Mhz用
;1命令サイクル = 1/4 クロック = 1x10^6 = 1/1,000,000 = 1μS

DELAYms: ;ix1000サイクル
CLRW ;Wをクリア
ADDWF i,W ;メインルーチンでのミリ秒設定値 i を
MOVWF j ;ループカウンタ j にセットする
;---------------------------
DELAY1k: ;1000サイクルの遅延ループ
MOVLW d'249' ;ループカウンタkに249をセットする
MOVWF k
LOOP1k:
NOP
DECFSZ k,f
GOTO LOOP1k ;2+4x249-1
;---------------------------
DECFSZ j,f ;3xj+997xj=1,000xj
GOTO DELAY1k
RETURN ;メインルーチンに戻る

END

救急車のピーポー音は 2 つの周波数の音を一定周期で切り替えてやるだけで良いですね。 次回、それをやってみましょう。

▼ こちらも合わせてご覧ください。
ヘタのモノ好き - トップページ