2015年11月25日水曜日

ATtiny13でDDS波形出力の実験

前回の「ATtinyをいっぱい並べるオシレーターの妄想」からもう少し考えてみた。

マスターとスレーブのやり取りはシリアル通信かな~と思ってたが、アナログシンセの制御方法のCVとGATEをそのまま踏襲すればそれぞれ1線の計2線で済むのでは?

と、妄想が広がった。

IC間の通信をアナログ値でやるという(^q^;

ATtiny13の周辺機能にはシリアル通信のハードウェアはないが(SPIでプログラムできるのだから本当はあるんだろうけど、よくわからない)立派なADCが搭載されているのでこれを使うという妄想。

ATtiny13をVCOとして使う妄想図


アナログっぽいところは赤、デジタルっぽいところは青で色分けした。

CV、Gateというのは周波数と音量を設定する電圧値だ。ADCでデジタル値に変換してからマイコンで処理して、周辺機能のPWMで疑似アナログ値の波形を出力する。

これをアナログ回路のLPFでパルス波形(高周波数)を取り去ってアナログ波形を出力するというもの。

CV、Gateの電圧はマスターのAVR(とかPSoCとかLPCとか・・・)が計算して出力する。

入力側のClockはシステム全体の同期をとるためのクロック。

Latchはこの信号を受け取った時にスレーブ・オシレーター達がいっせーので出力電圧(PWM出力なのでPWMのDuty比)を切り替える。

Selectは今設定されているCV、Gateの値が自分宛のものか判断する(SPIのSSのぱくり)

メモ:


  • Selectはスレーブごとに信号線を割り当てるので工夫すればここにシリアル・データを乗せられるかも?
  • Latchで同期をとれればClockでシステム全体の同期をとる必要はないのかな?(よくわかりません)


ATtiny13AでDDS波形出力


とにかく周波数可変で波形を出力できないと妄想も深まらないので、今まで使ってきたDDSでテストプログラムを書いてみた。

 /*  
  * ATtinyVCO.c  
  *  
  * Created: 2015/11/25 14:07:46  
  * Author: gizmo  
  */   
 #define F_CPU     9600000ul  
 #include <avr/io.h>  
 #include <avr/pgmspace.h>  
 #include <util/delay.h>  
 #include <stdint.h>  
 #define SAMPLE_CLOCK     (8000.0f)  
 #define POW_2_16          (65536ul)  
 #define FREQUENCY_MAX     (2000.0f)  
 // Saw up wave table  
 const PROGMEM uint8_t sawUpTable[] = {  
      0,     1,     2,     3,     4,     5,     6,     7,     8,     9,     10,     11,     12,     13,     14,     15,  
      16,     17,     18,     19,     20,     21,     22,     23,     24,     25,     26,     27,     28,     29,     30,     31,  
      32,     33,     34,     35,     36,     37,     38,     39,     40,     41,     42,     43,     44,     45,     46,     47,  
      48,     49,     50,     51,     52,     53,     54,     55,     56,     57,     58,     59,     60,     61,     62,     63,  
      64,     65,     66,     67,     68,     69,     70,     71,     72,     73,     74,     75,     76,     77,     78,     79,  
      80,     81,     82,     83,     84,     85,     86,     87,     88,     89,     90,     91,     92,     93,     94,     95,  
      96,     97,     98,     99,     100,     101,     102,     103,     104,     105,     106,     107,     108,     109,     110,     111,  
      112,     113,     114,     115,     116,     117,     118,     119,     120,     121,     122,     123,     124,     125,     126,     127  
 };  
 volatile uint16_t phaseAccumlator;  
 volatile uint16_t tuningWord;  
 volatile uint8_t amp;  
 //=============================================================================  
 // 波形生成  
 //  
 // ----------------------------------------------------------------------------  
 // setDDSParameter()  
 // parameter: frequency: 生成する周波数  
 //  
 void setDDSParameter(float frequency)  
 {  
      tuningWord = (int16_t)(frequency * POW_2_16 / SAMPLE_CLOCK);  
 }  
 // ----------------------------------------------------------------------------  
 // generateWave()  
 // return: 出力値(0..255)  
 //  
 uint8_t generateSawWave()  
 {  
      uint8_t index;  
      phaseAccumlator += tuningWord;  
      // 右へシフト: 16bit -> 7bit(128個)  
      index = phaseAccumlator >> 8;  
      return pgm_read_byte(&sawUpTable[index]);  
 }  
 //=============================================================================  
 // 波形生成  
 //  
 // ----------------------------------------------------------------------------  
 // setPWMDuty()  
 // parameter: value: 設定するDuty比(0..255)  
 //  
 void setPWMDuty(uint8_t value)  
 {  
      // PWMのデューティー比を設定  
      OCR0A = value;  
 }  
 //=============================================================================  
 // ADC  
 //  
 // ----------------------------------------------------------------------------  
 // getCV()  
 // return: CV値(0..4095)  
 //  
 uint16_t getCV() {  
      // ADC_CVの値を取得  
      return 2048;  
 }  
 // ----------------------------------------------------------------------------  
 // getGate()  
 // return: Gate値(0..255)  
 //  
 uint8_t getGate() {  
      // ADC_GATEの値を取得  
      return 255;  
 }  
 //=============================================================================  
 // ラッチ割込み  
 //  
 //ISR()  
 //     generateSawWave()を呼び出し  
 //     amp値を乗算  
 //     PWM DACに出力値を設定  
 //=============================================================================  
 // メイン・ルーチン  
 //  
 int main()  
 {  
      uint16_t cv;  
      uint8_t v;  
      //-------------------------------------------------------------------------  
      // PORT設定  
      //-------------------------------------------------------------------------  
      DDRB = 0b00000001;     // PB0(OC0A): PWM out  
      // Debug用  
      //  
      DDRB |= 0b00000010;     // PB1  
      //-------------------------------------------------------------------------  
      // PWM設定  
      //-------------------------------------------------------------------------  
      // TCCR0A = 0;  
      // TCCR0B = 0;  
      //-------------------------------------------------------------------------  
      // 波形生成モード: WGM0: 0:1:1  
      // 高速PWM(モード3)  
      TCCR0A |= (1 << WGM01) | (1 << WGM00);  
      TCCR0B |= (0 << WGM02);  
      //-------------------------------------------------------------------------  
      // コンペア・アウトプットA: COM0A: 1:0  
      // コンペア・マッチでOC0Aクリア、TOPでOC0Aセット  
      TCCR0A |= (1 << COM0A1) | (0 << COM0A0);  
      //-------------------------------------------------------------------------  
      // クロック設定: CS0: 0:0:1  
      // 分周なし  
      TCCR0B |= (0 << CS02) | (0 << CS01) | (1 << CS00);  
      // TCCR0A = 0b10000011;  
      // TCCR0B = 0B00000010;  
      // 初期設定 1kHz Duty:50%  
      //  
      //setDDSParameter(1000.f);  
      //setPWMDuty(127);  
      for (;;) {  
           // Debug用: PB1をH  
           PORTB |= 0b00000010;  
           cv = getCV();  
           setDDSParameter(cv * FREQUENCY_MAX / 4096.0f);  
           amp = getGate();  
           v = generateSawWave();  
           setPWMDuty(v);  
           // Debug用: PB1をL  
           PORTB &= 11111101;  
           _delay_us(125);     // 8,000Hz  
      }  
 }  
 // EOF  


ADCの値取得とかLatchによる波形生成の同期は割愛した。

あとで修正/拡張/いじりやすいように冗長ぎみに書いておいた。


配線図

ADC入力は実装していないのでPWM出力とタイミング計測用の「Check」のみ。

ATtiny13のピンアサイン


PWM出力は使用によりP5(OC0A)かP6(OC0B)のどちらか。ATtiny13は8Pinなので周辺機能をいろいろ使おうと思うとPin割り当てをちゃんと考えないといけない。

今のところ
P1:ADC0 (Gate/analog)
P2: CLKI (Master Clock)
P3: ADC2 (CV/analog)
P4: GND
P5: PWM (Output)
P6: Select
P7: Latch
P8: VCC
という使い方にしようと思っている。

いろいろ計測


PWMの素の出力


Duty比50%の素のPWM波形で、周波数36.44kHzになっている。

9.6MHz(Fuse bit: l:7A h:FF)駆動させていてTimer0が8bitなので256カウントでオーバーフローするので

9.6MHz / 256 = 37.5kHz

PWMだと意外と低いもんだなあ(^q^;

ATtiny13を20MHz駆動させても78.125kHzだ。

この辺をストップバンドにしてLPFを設計すればいいのかな。よくわからないので実験しながら。

一応、DDSでノコギリ波らしきPWM波形の出力はできた。


浮動小数点演算


プログラムではサンプリング周波数8kHzでノコギリ波を出力させているが、途中で浮動小数点演算をしていてかなりメモリを食うみたいだ。浮動小数点演算をしているところをコメントアウトすると消費メモリー量がかなり減る。

浮動小数点演算ありのAtmel Studioのoutput

Task "RunOutputFileVerifyTask"
Program Memory Usage : 1014 bytes   99.0 % Full
Data Memory Usage : 5 bytes   7.8 % Full
Done executing task "RunOutputFileVerifyTask".

浮動小数点演算をコメントアウトしたAtmel Studioのoutput

Task "RunOutputFileVerifyTask"
Program Memory Usage : 290 bytes   28.3 % Full
Data Memory Usage : 5 bytes   7.8 % Full
Done executing task "RunOutputFileVerifyTask".

浮動小数点演算のルーチンは引き算すると700byteぐらいなのかな?

これを32bit整数演算に置き換えてメモリを節約できるかどうか。

メモ:


去年tozさんが書かれていた「tinyぴゅんぴゅん」の記事を読むとADCの待ち時間もなんか危なそうだ。

Gate(音量)の掛け算は整数演算でうまく処理できればいいが、単純な波形出力でもかなり制約があるのでムリなら後段のMixerでGateをうけて音量制御できるようにするとか・・・

素子数が増えると作る気なくしそう(TqT;

う~ん(^q^;;;;