(SSD1306)M5Stamp S3で高速フーリエ変換して表示して遊ぶ(FFT)




x 過去ログをみよ

結果


![o_coq351.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/ede08f06-735f-ab00-6cb1-2cefd76252e1.jpeg)




プログラム



```c++




//秋月のOLEDとアイテンドウのOLEDのアドレスは3C
//SSD1306_no_vram_graph_fft_c_S3_1

//インクルド(SSD1306用)
#include <Arduino.h>
#include <Wire.h>


//FFT関連(1) 開始↓
/***********************************************************
    fft.c -- FFT (高速Fourier変換)
***********************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PI 3.14159265358979323846
/*
  関数{\tt fft()}の下請けとして三角関数表を作る.
*/
static void make_sintbl(int n, double sintbl[])
{
    int i, n2, n4, n8;
    double c, s, dc, ds, t;

    n2 = n / 2;  n4 = n / 4;  n8 = n / 8;
    t = sin(PI / n);
    dc = 2 * t * t;  ds = sqrt(dc * (2 - dc));
    t = 2 * dc;  c = sintbl[n4] = 1;  s = sintbl[0] = 0;
    for (i = 1; i < n8; i++) {
        c -= dc;  dc += t * c;
        s += ds;  ds -= t * s;
        sintbl[i] = s;  sintbl[n4 - i] = c;
    }
    if (n8 != 0) sintbl[n8] = sqrt(0.5);
    for (i = 0; i < n4; i++) {
        sintbl[n2 - i] = sintbl[i];
        sintbl[i + n2] = - sintbl[i];
    }
}
/*
  関数{\tt fft()}の下請けとしてビット反転表を作る.
*/
static void make_bitrev(int n, int bitrev[])
{
    int i, j, k, n2;

    n2 = n / 2;  i = j = 0;
    for ( ; ; ) {
        bitrev[i] = j;
        if (++i >= n) break;
        k = n2;
        while (k <= j) {  j -= k;  k /= 2;  }
        j += k;
    }
}
/*
  高速Fourier変換 (Cooley--Tukeyのアルゴリズム).
  標本点の数 {\tt n} は2の整数乗に限る.
  {\tt x[$k$]} が実部, {\tt y[$k$]} が虚部 ($k = 0$, $1$, $2$,
  \ldots, $|{\tt n}| - 1$).
  結果は {\tt x[]}, {\tt y[]} に上書きされる.
  ${\tt n} = 0$ なら表のメモリを解放する.
  ${\tt n} < 0$ なら逆変換を行う.
  前回と異なる $|{\tt n}|$ の値で呼び出すと,
  三角関数とビット反転の表を作るために多少余分に時間がかかる.
  この表のための記憶領域獲得に失敗すると1を返す (正常終了時
  の戻り値は0).
  これらの表の記憶領域を解放するには ${\tt n} = 0$ として
  呼び出す (このときは {\tt x[]}, {\tt y[]} の値は変わらない).
*/
int fft(int n, double x[], double y[])
{
    static int    last_n = 0;    /* 前回呼出し時の {\tt n} */
    static int    *bitrev = NULL; /* ビット反転表 */
    static double *sintbl = NULL; /* 三角関数表 */
    int i, j, k, ik, h, d, k2, n4, inverse;
    double t, s, c, dx, dy;

    /* 準備 */
    if (n < 0) {
        n = -n;  inverse = 1;  /* 逆変換 */
    } else inverse = 0;
    n4 = n / 4;
    if (n != last_n || n == 0) {
        last_n = n;
        if (sintbl != NULL) free(sintbl);
        if (bitrev != NULL) free(bitrev);
        if (n == 0) return 0;  /* 記憶領域を解放した */
        //sintbl = malloc((n - n4) * sizeof(double));
        sintbl = (double *)malloc(10000);
        //bitrev = malloc(n * sizeof(int));
        bitrev = (int    *)malloc(10000);
        if (sintbl == NULL || bitrev == NULL) {
            fprintf(stderr, "記憶領域不足\n");  return 1;
        }
        make_sintbl(n, sintbl);
        make_bitrev(n, bitrev);
    }
    for (i = 0; i < n; i++) {    /* ビット反転 */
        j = bitrev[i];
        if (i < j) {
            t = x[i];  x[i] = x[j];  x[j] = t;
            t = y[i];  y[i] = y[j];  y[j] = t;
        }
    }
    for (k = 1; k < n; k = k2) {    /* 変換 */
        h = 0;  k2 = k + k;  d = n / k2;
        for (j = 0; j < k; j++) {
            c = sintbl[h + n4];
            if (inverse) s = - sintbl[h];
            else         s =   sintbl[h];
            for (i = j; i < n; i += k2) {
                ik = i + k;
                dx = s * y[ik] + c * x[ik];
                dy = c * y[ik] - s * x[ik];
                x[ik] = x[i] - dx;  x[i] += dx;
                y[ik] = y[i] - dy;  y[i] += dy;
            }
            h += d;
        }
    }
    if (! inverse)    /* 逆変換でないならnで割る */
        for (i = 0; i < n; i++) {  x[i] /= n;  y[i] /= n;  }
    return 0;  /* 正常終了 */
}

#define N 64
//FFT関連(1)終了↑


//インクルド(SSD1306用)
//#include <Arduino.h>
//#include <Wire.h>


//定義(SSD1306)
#define MAX_PAGE                   (7)
#define MAX_COL                    (127)

#define COMMAND_MODE               0x80 // continuation bit is set!
#define DATA_MODE                  0x40

#define SET_COLUMN_ADDRESS         0x21 // takes two bytes, start address and end address of display data RAM
#define SET_PAGE_ADDRESS           0x22 // takes two bytes, start address and end address of display data RAM

#define SET_MEMORY_ADDRESSING_MODE 0x20 // takes one byte as given above
#define HORIZONTAL_ADDRESSING_MODE 0x00

#define SET_SEGMENT_REMAP_0        0xA0 // column address 0 is mapped to SEG0 (Reset)
#define SET_SEGMENT_REMAP_127      0xA1 // column address 127 is mapped to SEG0

#define SET_COMMON_REMAP_0         0xC0 // row address  0 is mapped to COM0 (Reset)
#define SET_COMMON_REMAP_63        0xC8 // row address 63 is mapped to COM0


//I2Cに配列を転送する
void write_s(uint8_t *str1, uint8_t len1) {

  Wire.beginTransmission(  0x3c  );

  for (int ii = 0; ii < len1; ii++) {

    //一文字出力
    Wire.write(*str1 ++);

  }//for

  Wire.endTransmission();

}//write_s


//セットページアドレス
void setPageAddress(uint8_t start, uint8_t end)
{
  uint8_t databytes[6] = {COMMAND_MODE, SET_PAGE_ADDRESS, COMMAND_MODE, start, COMMAND_MODE, end};
  write_s(databytes, 6);
}//setPageAddress


//セットカラムアクセス
void setColumnAddress(uint8_t start, uint8_t end)
{
  uint8_t databytes[6] = {COMMAND_MODE, SET_COLUMN_ADDRESS, COMMAND_MODE, start, COMMAND_MODE, end};
  write_s(databytes, 6);
}//setColumnAddress


//セットメモリーアドレシングモード
void setMemoryAddressingMode()
{
  uint8_t databytes[4] = {COMMAND_MODE, SET_MEMORY_ADDRESSING_MODE, COMMAND_MODE, HORIZONTAL_ADDRESSING_MODE};
  write_s(databytes, 4);
}//setMemoryAddressingMode


//セットディスプレー Flip
void setDisplayFlip(int left, int down)
{
  if ( left == 0) {
    // column address   0 is mapped to SEG0 (Reset)
    //_sendCommand(SET_SEGMENT_REMAP_0);
    uint8_t databytes[2] = {COMMAND_MODE, SET_SEGMENT_REMAP_0};
    write_s(databytes, 2);
  } else {
    // column address 127 is mapped to SEG0
    //_sendCommand(SET_SEGMENT_REMAP_127);
    uint8_t databytes[2] = {COMMAND_MODE, SET_SEGMENT_REMAP_127};
    write_s(databytes, 2);
  }//end if

  if ( down == 0) {
    // Reset mode
    //_sendCommand(SET_COMMON_REMAP_0);
    uint8_t databytes[2] = {COMMAND_MODE, SET_COMMON_REMAP_0};
    write_s(databytes, 2);
  } else {
    // Flip Up/Down (Need to rewrite display before H effect shows)
    //_sendCommand(SET_COMMON_REMAP_63);
    uint8_t databytes[2] = {COMMAND_MODE, SET_COMMON_REMAP_63};
    write_s(databytes, 2);
  }//end if
}//setDisplayFlip


//8バイト分まとめて出力
void put8byte(int qq) {

  //データの配列の定義
  static uint8_t databytes[9] = {DATA_MODE, 0, 0, 0, 0, 0, 0, 0, 0};

  static int byte_count = 1;

  databytes[byte_count] = qq;
  byte_count++;

  if ( byte_count > 8 ) { //8バイト分、貯まったら
    write_s(databytes, 9);
    byte_count = 1;
  } // endif

} //put8byte


//ドットを打つ
void Dot(int x, int y, int c) {

  static int b_count = 0; //ビットカウント
  static int qq = 0;      //一時

  qq = qq | ( c << b_count);

  b_count++;

  if ( b_count > 7 ) { //1バイト分、貯まったら

    put8byte(qq); //SSD1306に出力

    qq = 0;
    b_count = 0;

  }//end if

}//DOt


//キャラクターRAMの内容
char y_[] = {
  //1    2    3   4   5   6   7   8

    1 ,  2 ,  3 , 4 , 5 , 6 , 7 , 8  , //1
    0 ,  0 ,  0 , 0 , 0 , 0 , 0 , 0  , //2 
    0 ,  0 ,  0 , 0 , 0 , 0 , 0 , 0  , //3
    0 ,  0 ,  0 , 0 , 0 , 0 , 0 , 0  , //4

    0 ,  0 ,  0 , 0 , 0 , 0 , 0 , 0  , //5
    0 ,  0 ,  0 , 0 , 0 , 0 , 0 , 0  , //6 
    0 ,  0 ,  0 , 0 , 0 , 0 , 0 , 0  , //7
    0 ,  0 ,  0 , 0 , 0 , 0 , 0 , 0  , //8

    0 ,  0 ,  0 , 0 , 0 , 0 , 0 , 0  , //9
    0 ,  0 ,  0 , 0 , 0 , 0 , 0 , 0  , //A 
    0 ,  0 ,  0 , 0 , 0 , 0 , 0 , 0  , //B
    0 ,  0 ,  0 , 0 , 0 , 0 , 0 , 0  , //C

    0 ,  0 ,  0 , 0 , 0 , 0 , 0 , 0  , //D
    0 ,  0 ,  0 , 0 , 0 , 0 , 0 , 0  , //E 
    0 ,  0 ,  0 , 0 , 0 , 0 , 0 , 0  , //F
   56 , 57 , 58 ,59 ,60 ,61 ,62 ,63    //G

};


//再表示
void display(void) {

  int y; int x;

  //範囲の設定 (OLED内部のx,yカウンターを初期化してホームポジション0,0に)
  setPageAddress(0, MAX_PAGE);  // all pages
  setColumnAddress(0, MAX_COL); // all columns

  for (int ii = 0; ii < 8192; ii++) {

    //SSD1306のバッファーの配置順のxとyを求める
    y = ((ii & 0b0001110000000000 ) >> 7) + ( ii & 0b0111);
    x =  (ii & 0b0000001111111000) >> 3;

    //↓開始　グラフのメイン処理 x=0...127 , y=0...64 
    if (y == y_[x]) {
      Dot(x, y, 1); //白のドットを打つ
    } else {
      Dot(x, y, 0); //黒のドットを打つ
    }//end if
    //↑終了

  }//for ii

}//display


//SSD1306の初期化
void display_begin(void) {

  //debug
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  Serial.println();
  Serial.println("START");
  Serial.println();

  //I2Cの初期化
  Wire.begin(); //H743
  delay(200);
  //Wire.setClock(2000000); //速度の変更 (I2C高速化 2Mhz)
  //Wire.setClock(1000000); //速度の変更 (I2C高速化 1Mhz)
  Wire.setClock(800000);  //速度の変更 (I2C高速化 800khz)
  //Wire.setClock(400000);  //速度の変更 (I2C高速化 400khz)
  //Wire.setClock(200000);  //速度の変更 (I2C高速化 200khz)
  //Wire.setClock(100000);  //速度の変更 (I2C高速化 100khz)
  delay(200);

  //SSD1306の初期化スペル(魔法)
  //0x80,0x8D,0x80,0x14,0x80,0xAF
  write_s( (uint8_t*) "\200\215\200\024\200\257", 6);
  delay(100);

  //セットメモリーアドレシングモード (画面の終端に来たら画面の先頭に)
  setMemoryAddressingMode();

  //セットディスプレー Flip (画面の向きを変える)
  setDisplayFlip(0, 1);

}//display_begin


//初期化
void setup() {

  //SSD1306の初期化
  display_begin();
  
}//setup


//メインループ
void loop() {

    //FFT関連(2) 開始↓
    int i; //ループカウント
    //FFT配列
    static double x1[N], y1[N], x2[N], y2[N], x3[N], y3[N];

    //FFT変換
    for (i = 0; i < N; i++) {
        x1[i] = x2[i] = 6 * cos( 6 * PI * i / N)
                      + 4 * sin(18 * PI * i / N);
        y1[i] = y2[i] = 0;
    }
    if (fft(N, x2, y2)){
    //return 1;
    return;
    }

    //逆変換 debug
    for (i = 0; i < N; i++) {
        x3[i] = x2[i];  y3[i] = y2[i];
    }
    if (fft(-N, x3, y3)){
    //return 1;
    return;
    }

    //debug シリアル出力
    Serial.print("      元のデータ    フーリエ変換  逆変換\n");
    for (i = 0; i < N; i++){
        Serial.print(i); 
        Serial.print("|");
        Serial.print(x1[i]); Serial.print(' ');
        Serial.print(y1[i]);
        Serial.print("|");
        Serial.print(x2[i]); Serial.print(' ');
        Serial.print(y2[i]);
        Serial.print("|");
        Serial.print(x3[i]); Serial.print(' ');
        Serial.print(y3[i]);
        Serial.println();
    }//for debug
    //FFT関連(2) 終了↑


    //グラフへ値のセット
    for (i = 0; i < N; i++){

        y_[i+ 0] = (x1[i]*2)+18+((64-(18+18))/2);

        y_[i+64] = x2[i]*5;
        
    }//for


  //y_[0]=32; //debug

  display(); //再表示
  delay(3000); //3秒待つ

}//loop


```