ESP-WROOM-02(ESP8266)で和音を鳴らしてみた

ESP8266 Arduinoで和音を鳴らしてみた。
まだ不可解な現象がありますが、ソフトウェアPWMの特性だということにして、これ以上深堀するのはやめます。

回路

ESP8266の4番ピンにトランジスタとスピーカをつなげています。
試していないけど、イヤホンジャックなどをコンデンサ通してつなげても動くと思います。

ソフトウェア

ESP8266のArduinoを使って和音を鳴らしてみようと思います。
矩形波の場合のみうまく動くことを確認しました。(ノコギリ波にするとうまく動かないことも確認しました)

PWMを32kHz周期に設定し、timer0の割り込みを10000クロック後(160MHzの場合は16kHz周期)に設定します。
ndという変数にタイマ割り込みのたびにdd足していきます。これがオシレータです。
矩形波を作りたいのでndの16ビット目が0か1かで0か256を返すような計算式を作ります。


((nd>>15)&1 == 1)?0:256

これをAnalogWriteすることで音が鳴ります。
音階を作るには少し計算をする必要があります。
ndの16ビット目をみているのでddが1の場合は16K/2^16 Hzの音が鳴るはず(0.24414..という音にならない周波数だけど)
で、ddを変数として意図した周波数を鳴らすための数式は

f:id:inajob:20180225210000p:plain

となる。
式変形して

f:id:inajob:20180225210059p:plain

これを音階ごとに計算すればよい。こうやって計算した値をtonesという変数に用意しています。

和音が鳴らしたいので上記のような変数を4つばかり配列として作成して、足し算します。
AnalogWriteは0-1024までなのでうまくその範囲に収まるように調整します。

以上で和音が作成できます。
ソースコードはこんな感じ。

 

#include <ESP8266WiFi.h>
#include <SoftwareSerial.h>
SoftwareSerial mySerial(2, 3); // RX, TX


const unsigned int tones[] = {
0,
1802  , // A  1
1909  , // A+ 2
2023  , // B  3
2143  , // C  4
2271  , // C+ 5
2406  , // D  6
2549  , // D+ 7
2700  , // E  8
2861  , // F  9
3031  , // F+ 10
3211  , // G 11
3402  , // G+12
3604  , // A 13
3819  , // A+14
4046  , // B 15
4286  , // C 16
4541  , // C+17
4811  , // D 18
5098  , // D+19
5401  , // E 20
5722  , // F 21
6062  , // F+22
6422  , // G 23
6804  , // G+24
7209  , // A 25
7638  , // A+26
8092  , // B 27
8573  , // B+28
};

const unsigned int melody[3][16] = {
{ 4, 6, 8, 9,11,13,15,16,16,15,13,11, 9, 8, 6, 4 },
{ 8, 0,11, 0,15, 0,18, 0,13, 0, 9, 8, 0, 4, 9, 8 },
{16, 0,20, 0,23, 0, 0,28, 0,27, 0,23, 0,20, 0, 0 },
};
int pos = 0;

volatile unsigned int nd[4] = {0,0,0,0};
volatile unsigned int dd[4] = {0, 0, 0, 0};
volatile unsigned int out;

void timer0_ISR (void) {
  timer0_write(ESP.getCycleCount() + 10000L ); // 160M/16K
  nd[0] += dd[0];
  nd[1] += dd[1];
  nd[2] += dd[2];
  nd[3] += dd[3];

  out = (((nd[0] >> 16)&1 == 1)?0:256) +
        (((nd[1] >> 16)&1 == 1)?0:256) +
        (((nd[2] >> 16)&1 == 1)?0:256) +
        (((nd[3] >> 16)&1 == 1)?0:256);
  analogWrite(4, out);
}

void setup() {
  WiFi.mode(WIFI_OFF);

  analogWriteFreq(32000); // pwm 32kHz

  noInterrupts();
  timer0_isr_init();
  timer0_attachInterrupt(timer0_ISR);
  timer0_write(ESP.getCycleCount() + 10000L ); // 160M/16K
  interrupts();

  Serial.begin(115200);
}

unsigned int counter;

void loop() {
  counter ++;
  if(counter%100000 == 0){
    dd[0] = tones[melody[0][pos]];
    dd[1] = tones[melody[1][pos]];
    dd[2] = tones[melody[2][pos]];
    pos ++;
    if(pos >= 16){
      pos = 0;
    }
    Serial.println("tick");
  }

}

 

動画

 

何かおかしい

矩形波はこれでよい感じに鳴ったのだが、ノコギリ波や、ほかの波形を鳴らそうとするとなぜか音にならない。
おそらくこれはESP8266のPWMがソフトウェアで実装されていることにより、頻繁に値が変わるような波形では速度が追い付いていないのではないか?と思われる。

何か気づいたことがある人は教えてほしいです。