2026年3月1日日曜日

Pico FFT - PIO パルス信号 スペクトラム表示

Raspberry Pi PicoによるFFT Spectrum AnalyzerにPIOによる1kHzクロックパルスをOLEDにスペクトラム表示しました。
※OLEDスペクトラム表示は github.com/JR3XNW【Filter_sweep_test_SpectrumDisplay_Bar01.ino】プログラムを使用しました。
 





参考サイト
●github.com/JR3XNW
●ラズピコを使ってステレオのスペクトラムアナライザを作る


1kHz 90度位相差クロック 
上ーGPIO0 下ーGPIO1 (1000Hz)





















基本波と高調波のスペクトラム
方形波は基本波と高調波を重ね合わせたものです。基本波1KHzと高調波が見れます(サンプリング周期調整しないとうまく表示ができない)
結線
GPIO 0ーGPIO26(ADC 0)
GPIO 1ーGPIO27(ADC 1)
OLED     Pico
SDA   -   GP16
SCL   -   GP17
VCC   -   3V3
GND   -   GND





CRローパス・フィルタの計算
(サンプル)CRローパス・フィルタ数計算ツール
http://sim.okawa-denshi.jp/Fkeisan.htm

カットオフ周波数 = 1000Hz
R=1.7kΩ(手持ち抵抗)
C=0.1uF

(GPIO 1)       (GPIO 27(ADC 1))
GPIO 0 ーー1.7kΩーーGPIO 26(ADC 0)
                                 |                           
                               0.1uF
                                 |
                               GND






GPIO 1・2にCRローパス・フィルタを入れてGPIO 26・27(ADC1・2)に入力













CRローパス・フィルタを入れた波形






















ローパス・フィルタ後のスペクトラム
高調波が1部残っていますが大部分が消えています。











クロックをミックスしたスペクトラム
ADC 0・1にローパス・フィルタを入れたクロックをプログラムでミックスしたスペクトラム表示。









プログラム Arduino IDE【ボード:Raspberry Pi Pico】
//JR3XNW Filter_sweep_test_SpectrumDisplay_Bar01.ino

#include <hardware/clocks.h>
#include <pico/stdlib.h>
#include <hardware/pio.h>
#include "nco.h"

#include <Arduino.h>
#include <U8g2lib.h>
#include <arduinoFFT.h> // v2.0.2
#include <Wire.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0); //ディスプレイ設定
ArduinoFFT<double> FFT;  // v2.0.2 Explicit data types using templates

const int samples = 128; // Number of FFT samples.
double ADC0[samples];      
double ADC1[samples];
double vReal[samples];
double vImag[samples] = {0};

// frequency sweep function.
void frequencySweep(double *vReal, double *vImag, double *ADC0, double *ADC1) {  
  for (int i = 0; i < samples; i++) {
    ADC0[i] = analogRead(26);       // 波形データー取得(fs:4096)
    ADC1[i] = analogRead(27);
    delayMicroseconds(19);       //サンプリング周期調整
  }
  for (int i = 0; i < samples; i++) {  
    double sigI =  (ADC0[i] - 2048)*3.3 / 4096.0; //GP26信号入力(電圧に換算)  
    double sigQ =  (ADC1[i] - 2048)*3.3 / 4096.0; //GP27信号入力(電圧に換算)
    //double sig = sigI;    
    double sig = sigI*sigQ;
    vReal[i] = sig;  
    vImag[i] = 0;      
  }
}
// FFT and OLED display functions (line graph display).
void displayFFT(double *vReal, double *vImag) {
  FFT.windowing(vReal, samples, FFTWindow::Hamming, FFTDirection::Forward); //vReal 窓関数
  FFT.windowing(vImag, samples, FFTWindow::Hamming, FFTDirection::Forward); //vImag 窓関数
  FFT.compute(vReal, vImag, samples, FFTDirection::Forward);  //FFT処理(複素数で計算)
  FFT.complexToMagnitude(vReal, vImag, samples);  // 複素数を実数に変換
  // Clear graph drawing area (overwrite with background color).
  // Here, the graph area is overwritten with a black rectangle.
  u8g2.setDrawColor(0); // Set background color (usually black).
  u8g2.drawBox(0, 0, 128, 52); // Clear graph area.■(始点のX座標, 始点のY座標, 幅, 高さ)
  u8g2.setDrawColor(1); // Re-set the drawing color (white).
  for (int i = 1; i < (samples / 2); i++) {
    int x = map(i, 1, samples / 3.3, 0, 128); //周波数軸(現数値,現下限,現上限,変換下限,変換上限)
    int y = map(vReal[i], 0, 4, 53, 0); //信号強度(現数値,現下限,現上限,変換下限,変換上限)
    u8g2.drawLine(x, 53, x, y); //スペクトラム表示(始点X座標, 始点Y座標, 終点X座標, 終点Y座標)
  }
  // Send buffer to update screen.
  u8g2.sendBuffer();
}


void setup() {
    //set_sys_clock_khz(133000, true); //1MHz単位で変更可能
    //set_sys_clock_pll(12000000*125, 6, 2); //システムクロック125000000Hz
    set_sys_clock_pll(12000000*133, 6, 2); //システムクロック133000000Hz
    float system_clock_frequency = 133000000; //システムクロック  
    PIO pio = pio0; //pioを指定
    uint sm;        
    uint offset = pio_add_program(pio, &nco_program); //offset変数←アセンブラのアドレス      
    nco_program_init(pio, sm, offset);  //PIOの初期化    
    float adjusted_frequency = 1000; //クロック周波数設定
    float divider = system_clock_frequency/(4 * adjusted_frequency); //周波数分周      
    pio_sm_set_clkdiv(pio, sm, divider);  //クロック出力  

  Wire.setSDA(16);  //SDA
  Wire.setSCL(17);  //SCL
  Wire.begin();  
  u8g2.begin();
  // Redraw the static content (lines and texts) each time to prevent flickering
  u8g2.drawFrame(0, 0, 128, 54); //周波数軸:□(座標X,座標Y,幅,高さ) 
  for (int xl = 10; xl < 128; xl += 13) {
    u8g2.drawLine(xl, 53, xl, 55); //周波数目盛(始点X座標, 始点Y座標, 終点X座標, 終点Y座標)
  }
  u8g2.setFont(u8g2_font_micro_tr); // Set the font for the numbers
  u8g2.drawStr(10, 64, "1k"); //(X座標, Y座標, 文字列)
  u8g2.drawStr(23, 64, "2k");
  u8g2.drawStr(36, 64, "3k");
  u8g2.drawStr(49, 64, "4k");
  u8g2.drawStr(62, 64, "5k");
  u8g2.drawStr(75, 64, "6k");
  u8g2.drawStr(88, 64, "7k");
  u8g2.drawStr(101, 64, "8k");
  u8g2.drawStr(114, 64, "9k");
}

void loop() {
  for(int i = 0; i < samples; i += 1){    
    frequencySweep(vReal, vImag, ADC0, ADC1);
    displayFFT(vReal, vImag); // Call a function to display FFT results.  
 }
}


nco.h
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //

#pragma once

#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif

// --- //
// nco //
// --- //

#define nco_wrap_target 0
#define nco_wrap 3

static const uint16_t nco_program_instructions[] = {
            //     .wrap_target
    0xe000, //  0: set    pins, 0                    
    0xe001, //  1: set    pins, 1                    
    0xe003, //  2: set    pins, 3                    
    0xe002, //  3: set    pins, 2                    
            //     .wrap
};

#if !PICO_NO_HARDWARE
static const struct pio_program nco_program = {
    .instructions = nco_program_instructions,
    .length = 4,
    .origin = -1,
};

static inline pio_sm_config nco_program_get_default_config(uint offset) {
    pio_sm_config c = pio_get_default_sm_config();
    sm_config_set_wrap(&c, offset + nco_wrap_target, offset + nco_wrap);
    return c;
}

static inline void nco_program_init(PIO pio, uint sm, uint offset) {
    // PIOステートマシンコンフィグのデフォルト値を取得  
    pio_sm_config c = nco_program_get_default_config(offset);
    // Map the state machine's OUT pin group to one pin, namely the `pin`
    // parameter to this function. 
    //ピンと、ピンの数を指定
    sm_config_set_set_pins(&c, 0, 2);
    // Set this pin's GPIO function (connect PIO to the pad)
    //GPIOをPIO 0に割り当てる
    pio_gpio_init(pio, 0);
    //GPIOをPIO 1に割り当てる
    pio_gpio_init(pio, 1);
    //出力電流2mA
    gpio_set_drive_strength(0, GPIO_DRIVE_STRENGTH_2MA);
    gpio_set_drive_strength(1, GPIO_DRIVE_STRENGTH_2MA);
    //スルーレート 遅い
    gpio_set_slew_rate(0, GPIO_SLEW_RATE_SLOW);
    gpio_set_slew_rate(1, GPIO_SLEW_RATE_SLOW);
    // Set the pin direction to output at the PIO
    //PIOでピンの方向を出力に設定する
    pio_sm_set_consecutive_pindirs(pio, sm, 0, 2, true);
    //set pio divider
    //クロック周波数分周 1
    sm_config_set_clkdiv(&c,1);
    // Load our configuration, and jump to the start of the program
    //プログラムを読込、先頭から実行
    pio_sm_init(pio, sm, offset, &c);
    // Set the state machine running
    //PIOステートマシンを有効にする
    pio_sm_set_enabled(pio, sm, true);
}

#endif





0 件のコメント:

コメントを投稿

WaveGene - 多機能 高精度 テスト信号発生ソフト

 WaveGene - 多機能 高精度 テスト信号発生ソフトを使ってみました。  ●サイン波、矩形波、三角波、ノコギリ波、パルス列、パルス列(+-)、ホワイトノイズ、ピンクノイズ、M系列ノイズ(MLS) ●変調(AM、DSB、FM、PM、PWM) ●スイープ(周波数、振幅、位相)...