2025年10月1日水曜日

Pico SDR - FIR・IIRフィルタ 受信テスト

github.com/JR3XNW FIR・IIRフイルタ及びライブラリFIR・IIRフイルタを使用しRaspberry Pi Picoによる、Si5351A 7MHz VFO→直交ミキサ→ADC入力→SSB検波【demod = adc_I - adc_Q】→FIR・IIRフィルタ→PWM出力→スピーカー  受信テストを行いました。    
●同ブログ


動画





回路図
























直交ミキサ基板
直交ミキサ回路はuSDXトランシーバーで使用された回路(FTS3253代替74HC4053使用)
●同ブログ



アンプ基板
ブレットボード用にアンプ基板を製作

NJM386BDアンプ

プログラム  Arduino IDE【ボード:Raspberry Pi Pico】
#include <Wire.h>
#include <Arduino.h>
#include <U8g2lib.h>
#include <FIR.h>
#include <IIRFilter.h>

const int audio_Pin = 16; //Audio output GP16
const int adc_PinI = 26; //I Signal ADC0 GP26
const int adc_PinQ = 27; //Q signal ADC1 GP27
const int SW1Pin = 22; //modeSW
const int SW2Pin = 17; //NOP
float gain = 3.0; //audio gain

//Si5351A関係の定義
#define Si5351A_ADDR 0x60
#define OUTPUT_CTRL 3 //Output Enable Control
#define CLK0_CTRL 16  //CLK0 Control
#define CLK1_CTRL 17  //CLK1 Control
#define CLK2_CTRL 18  //CLK2 Control
#define MSNA_ADDR 26  //Multisynth NA Parameters
#define MSNB_ADDR 34  //Multisynth NB Parameters
#define MS0_ADDR 42   //Multisynth0 Parameters
#define MS1_ADDR 50   //Multisynth1 Parameters
#define MS2_ADDR 58   //Multisynth2 Parameters
#define CLK0_PHOFF 165  //CLK0 Initial Phase Offset
#define CLK1_PHOFF 166  //CLK1 Initial Phase Offset
#define PLL_RESET 177   //PLL Reset
#define XTAL_LOAD_C 183 //Crystal Internal Load Capacitance

uint32_t frequency;
const uint32_t XtalFreq = 25000000; //25MHz
uint32_t P1;
uint32_t P2;
uint32_t P3;
uint32_t PllFreq;
uint32_t l;
float f;
uint8_t mult;
uint32_t num;
uint32_t denom;
uint32_t divider;
char PLL;
uint8_t PLL_ADDR;
uint8_t MS_ADDR;

// OLED設定
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0);

//Rotary endoder関係の定義
#define ENC_A 21
#define ENC_B 20
volatile uint8_t old_value = 0x11;
volatile uint8_t value = 0;
volatile uint8_t D;
volatile int8_t count = 0;

//STEP関係の定義
#define SW_STEP 5
uint16_t STEP = 1000; //STEP 初期値

//VFO関係の定義
const uint32_t LOW_FREQ = 7000000; //下限周波数
const uint32_t HI_FREQ = 7200000; //上限周波数
uint32_t FREQ = LOW_FREQ; //VFO周波数初期値
uint32_t FREQ_OLD = FREQ; //周波数の前の値
int16_t df = 130; //周波数補正値(Hz)
String freqt = String(FREQ);
String fH_old = "";
String fM_old = "";
String fL_old = "";

//ROtary encoder 割込みサービスルーチン
void rotary(){
  value = (digitalRead(ENC_B)<<1) | digitalRead(ENC_A);
  if(old_value != value){
    D = ((old_value << 1) ^ value) & 3;
    if(D < 2){
      count += 1;
    }else{
      count -= 1;
    }
  if(count >= 4){
    FREQ += STEP;
    count = 0;
  }else if(count <= -4){
    FREQ -= STEP;
    count = 0;
  }
  FREQ = constrain(FREQ,LOW_FREQ,HI_FREQ); //VFOの下限と上限を超えないように
  old_value = value;
  }
}

//Si5351のレジスタに1バイトデータを書き込む。
void Si5351_write(byte Reg , byte Data)
{
Wire.beginTransmission(Si5351A_ADDR);
Wire.write(Reg);
Wire.write(Data);
Wire.endTransmission();
}

//Si5351Aの初期化
void Si5351_init(){
Si5351_write(OUTPUT_CTRL,0xFF); //Reg3 Disable Output
Si5351_write(CLK0_CTRL,0x80); //Reg16 CLOCK0 Power down
Si5351_write(CLK1_CTRL,0x80); //Reg17 CLOCK1 Power down
Si5351_write(CLK2_CTRL,0x80); //Reg18 CLOCK2 Power down
Si5351_write(XTAL_LOAD_C,0x92); //Reg183 Crystal Load Capasitance=8pF
Si5351_write(PLL_RESET,0xA0); //Reg177 Reset PLLA and PLLB
Si5351_write(CLK0_CTRL,0x4F); //Reg16 CLOCK0 Power up
Si5351_write(CLK1_CTRL,0x4F); //Reg17 CLOCK0 Power up
// Si5351_write(CLK2_CTRL,0x4F); //Reg18 CLOCK0 Power up
Si5351_write(OUTPUT_CTRL,0xFC); //Reg3 Enable CLOCK0,CLOCK1
}

//PLLの設定
void PLL_Set(char Pll,uint32_t Freq,uint32_t Div){
PllFreq = Div * Freq; //fvco =d * fout
mult = PllFreq / XtalFreq; //整数部 mult = a = fvco / fxtal
l = PllFreq % XtalFreq; // L = fvco % fxtal
f = l;
f *= 1048575;
f /= XtalFreq;
num = f;
denom = 1048575;

P1 = (uint32_t)(128 * ((float)num /(float)denom)); //128*(b/c)
P1 = (uint32_t)(128 * (uint32_t)(mult) + P1 - 512); //128*a + (128*(b/c))-512
P2 = (uint32_t)(128 * ((float)num / (float)denom)); //128*(b/c)
P2 = (uint32_t)(128 * num -denom * P2); //128*b–c*(128*(b/c))
P3=denom;

if (Pll == 'A')
{
PLL_ADDR = MSNA_ADDR;
}else
{
PLL_ADDR = MSNB_ADDR;
}
Parameter_write(PLL_ADDR,P1,P2,P3);
}

//MultiSynth(分周器)のセット
void MS_Set(uint8_t MS_No,uint32_t Div){
P1 = 128 * Div - 512;
P2 = 0;
P3 = 1;
switch(MS_No){
case 0:
MS_ADDR = MS0_ADDR;
break;
case 1:
MS_ADDR = MS1_ADDR;
break;
case 2:
MS_ADDR = MS2_ADDR;
break;
default:
MS_ADDR = MS0_ADDR;
}
Parameter_write(MS_ADDR,P1,P2,P3);
}

//レジスタにパラメータP1,P2,P3を書き込む。
void Parameter_write(uint8_t REG_ADDR,uint32_t Pa1,uint32_t Pa2,uint32_t Pa3)
{
Si5351_write(REG_ADDR + 0,(Pa3 & 0x0000FF00) >> 8);
Si5351_write(REG_ADDR + 1,(Pa3 & 0x000000FF));
Si5351_write(REG_ADDR + 2,(Pa1 & 0x00030000) >> 16);
Si5351_write(REG_ADDR + 3,(Pa1 & 0x0000FF00) >> 8);
Si5351_write(REG_ADDR + 4,(Pa1 & 0x000000FF));
Si5351_write(REG_ADDR + 5,((Pa3 & 0x000F0000) >> 12) | ((Pa2 & 0X000F0000) >> 16));
Si5351_write(REG_ADDR + 6,(Pa2 & 0x0000FF00) >> 8);
Si5351_write(REG_ADDR + 7,(Pa2 & 0x000000FF));
}

//周波数表示
void Freq_Disp(long frequency){
  freqt = String(frequency);
  String fH = freqt.substring(0,1);
  String fM = freqt.substring(1,4);
  String fL = freqt.substring(4,6);
 //STEP表示  
  u8g2.clearBuffer();                 // バッファのクリア
  u8g2.setFont(u8g2_font_timB10_tf);  
  u8g2.setCursor(10,15);  
  if (STEP == 10)u8g2.print("10Hz");
  if (STEP == 100)u8g2.print("100Hz");
  if (STEP == 1000)u8g2.print("1KHz");
  if (STEP == 10000)u8g2.print("10KHz");
  u8g2.sendBuffer();
  Mode_Disp();  
//MHzの表示
  u8g2.setFont(u8g2_font_timB24_tf);  
  u8g2.setCursor(10,50);
  u8g2.print(fH);    
  fH_old = fH;
//KHzの表示    
  u8g2.setCursor(35,50);
  u8g2.print(fM);    
  fM_old = fM;
//Hzの表示  
  u8g2.setCursor(90,50);
  u8g2.print(fL);
  u8g2.sendBuffer();  
  fL_old = fL;  
}

//STEP表示
void Step_Disp(){
  u8g2.clearBuffer();                 // バッファのクリア
  u8g2.setFont(u8g2_font_timB10_tf);  
  u8g2.setCursor(10,15);
  if (STEP == 10)u8g2.print("10Hz");
  if (STEP == 100)u8g2.print("100Hz");
  if (STEP == 1000)u8g2.print("1KHz");
  if (STEP == 10000)u8g2.print("10KHz");  
  u8g2.sendBuffer();
  Freq_Disp(FREQ);
}

//STEP切り替え
void Set_Step(){
  if (STEP == 10){
    STEP = 10000;
  }else{
    STEP /= 10;
  }
  delay(10);
  Step_Disp();
  while(digitalRead(SW_STEP) == LOW){
    delay(10);
  }
}

void si5351(){
//PLLAのセット 900MHz,CLK0=10MHz
//N(divider) = fvco(PLLA) / fout(CLK0)
frequency = FREQ;
divider = 900000000 / frequency; //divider = fvco / fout
if (divider % 2) divider--;
PLL_Set('A',frequency,divider);

//CLK0の設定
Si5351_write(CLK0_CTRL,0x4C); //Reg16 Sorce PLLA
MS_Set(0,divider);
Si5351_write(CLK0_PHOFF,0);//CLK0 delay 0

//CLK1の設定
Si5351_write(CLK1_CTRL,0x4C); //Reg17 Sorce PLLA
MS_Set(1,divider);
Si5351_write(CLK1_PHOFF,divider);//Reg166 delay T/4 (90degree)
Si5351_write(PLL_RESET,0xA0); //Reg177 PLLA and PLLB
}

enum DemodulationMode { NON,FIR1,FIR2,IIR1,IIR2};
DemodulationMode currentMode = NON;

//モード表示
void Mode_Disp(){
  u8g2.setFont(u8g2_font_timB10_tf);
  u8g2.setCursor(90,15);  
 if (currentMode == 0){
  u8g2.print("NON");
 }
 if (currentMode == 1){
  u8g2.print("FIR");
 }  
 if (currentMode == 2){
  u8g2.print("FIRh");
 }  
 if (currentMode == 3){
  u8g2.print("IIR");
 }  
 if (currentMode == 4){
  u8g2.print("IIRh");
 }  
}


//FIR Cutoff-3KHz LPF
// https://github.com/JR3XNW/pico-Program-Lab/blob/main/Filter_sweep_test_SpectrumDisplay_FIR.inoD
const int filterLength = 31; // Number of filter taps.
float filterBuffer[filterLength] = {0.0};
float firCoeffs[filterLength] = { // Put the FIR filter coefficients here.
   0.001695,0.001201,-0.000905,-0.004228,-0.005427,0.0,
   0.011365,0.018584,0.00825,-0.021236,-0.048939,-0.03959,
   0.029858,0.145107,0.254511,0.299507,0.254511,0.145107,
   0.029858,-0.03959,-0.048939,-0.021236,0.00825,0.018584,
   0.011365,0.0,-0.005427,-0.004228,-0.000905,0.001201,0.001695
};
int bufferIndex = 0;

// Update to FIR filter function
float firFilter(float input) {
  filterBuffer[bufferIndex] = input;
  bufferIndex = (bufferIndex + 1) % filterLength;
  float sum = 0.0;
  for (int i = 0; i < filterLength; i++) {
    int index = (bufferIndex - i + filterLength) % filterLength;
    sum += filterBuffer[index] * firCoeffs[i];
  }
  return sum;
}


//IIR Cutoff-3KHz LPF
//https://github.com/JR3XNW/pico-Program-Lab/blob/main/Filter_sweep_test_SpectrumDisplay_IIR.ino
// フィルターの係数 (2次のバターワースフィルター)
const int filterOrder = 2;
double b[filterOrder + 1] = { 0.03478604, 0.06957207, 0.03478604 }; // 係数b
double a[filterOrder + 1] = { 1.0, -1.40750534, 0.54664949 }; // 係数a

double filterBufferX[filterOrder + 1] = {0.0};
double filterBufferY[filterOrder + 1] = {0.0};
// IIRフィルターの実装
double iirFilter(double input) {
  // 入力をバッファに追加
  for (int i = filterOrder; i > 0; i--) {
    filterBufferX[i] = filterBufferX[i - 1];
    filterBufferY[i] = filterBufferY[i - 1];
  }
  filterBufferX[0] = input;

  // フィルター出力の計算
  double output = 0.0;
  for (int i = 0; i <= filterOrder; i++) {
    output += b[i] * filterBufferX[i];
    if (i > 0) {
      output -= a[i] * filterBufferY[i];
    }
  }
  filterBufferY[0] = output;

  return output;
}

//FIR.h Cutoff-3KHz LPF
 FIR<float, 31> fir_lp; //FIRローパスフィルタとタップ数指定
//FIRタップ数と係数
 float coef_lp[31] = {
   0.001695,0.001201,-0.000905,-0.004228,-0.005427,0.0,
   0.011365,0.018584,0.00825,-0.021236,-0.048939,-0.03959,
   0.029858,0.145107,0.254511,0.299507,0.254511,0.145107,
   0.029858,-0.03959,-0.048939,-0.021236,0.00825,0.018584,
   0.011365,0.0,-0.005427,-0.004228,-0.000905,0.001201,0.001695
  };

 
// IIR.h Cutoff-3KHz LPF
const double b_lp[] ={0.03478604, 0.06957207, 0.03478604};
const double a_lp[] ={1.0, -1.40750534, 0.54664949};

IIRFilter lp(b_lp, a_lp);


void setup(){  
  Wire.setSDA(12);  //SDA
  Wire.setSCL(13);  //SCL
  Wire.begin();
  Serial.begin(115200);  
 
  //ロータリーエンコーダとSTEP使用ピンの設定とプルアップ
  pinMode(ENC_A, INPUT_PULLUP);
  pinMode(ENC_B, INPUT_PULLUP);
  pinMode(SW_STEP, INPUT_PULLUP);  
  pinMode(25, OUTPUT);  // pico built-in LED    
  pinMode(SW1Pin, INPUT_PULLUP);  
  pinMode(SW2Pin, INPUT_PULLUP);
  //ロータリーエンコーダ割込み設定
  attachInterrupt(ENC_A, rotary, CHANGE);
  attachInterrupt(ENC_B, rotary, CHANGE);  

  pinMode(audio_Pin, OUTPUT);
  pinMode(adc_PinI, INPUT);
  pinMode(adc_PinQ, INPUT);

  analogReadResolution(12); //分解能0~4095
  analogWriteResolution(8); //分解能0~255
  analogWriteFreq(20000); //pwm 周波数20000Hz

 // OLED 初期表示
  u8g2.begin ();          // OLEDの初期化
  u8g2.setFlipMode(0);   // 画面の向きを設定、数字を変えると向きが変わる
  u8g2.clearBuffer();                 // バッファのクリア
  u8g2.setFont(u8g2_font_timB18_tf);
  u8g2.drawStr(0, 30, "PICO SDR");
  u8g2.sendBuffer();                    // バッファを転送して表示
  delay(2000);
  u8g2.clearBuffer();                 // バッファのクリア    
  Step_Disp();
  Freq_Disp(FREQ);
 
  //Si5351Aの初期化
  Si5351_init();      
  si5351();  
 
  //Set the coefficients 係数セット
  fir_lp.setFilterCoeffs(coef_lp);

}

void loop(){  
 if(digitalRead(SW_STEP) == LOW)Set_Step();
  if(FREQ != FREQ_OLD){ //周波数FREQが変わったら、Si5351Aの周波数を変更
    si5351();
    Freq_Disp(FREQ);
    FREQ_OLD = FREQ; //変更された周波数を保存
  }    
    digitalWrite(25, HIGH);  // Built-in LED lights up during sampling
 
  float adc_I, adc_Q;
  float demod = 0.0; // initialize

  adc_I = analogRead(adc_PinI);
  adc_Q = analogRead(adc_PinQ);
  adc_I = (adc_I / 2047.5) - 1.0; //adc信号変換 -1~1
  adc_Q = (adc_Q / 2047.5) - 1.0;


 
  if (digitalRead(SW1Pin) == LOW) {
    delay(50); // Debounce delay
    currentMode = static_cast<DemodulationMode>((currentMode + 1) % 5); //モード数
    while (digitalRead(SW1Pin) == LOW); // Wait for switch release
    delay(50); // Debounce delay
    Mode_Disp();
    Freq_Disp(FREQ);      
  }
 
  demod = adc_I - adc_Q; //SSB検波
     
  //モード処理  
  switch (currentMode) {
    case NON:      
      break;
    case FIR1:      
      demod = firFilter(demod);
      break;  
    case FIR2:        
      demod = fir_lp.processReading(demod);
      break;
    case IIR1:        
      demod = iirFilter(demod);
      break;
    case IIR2:        
      demod = lp.filter(demod);
      break;            
   }
         
    //復調信号出力設定    
    uint8_t pwm_audio = (uint8_t)((demod + 1.0) * 127.5) * gain; //audio信号変換 0~256
    analogWrite(audio_Pin, pwm_audio);
   
  }

0 件のコメント:

コメントを投稿

Pico SDR - FIR・IIRフィルタ 受信テスト

github.com/JR3XNW FIR・IIRフイルタ及びライブラリFIR・IIRフイルタを使用しRaspberry Pi Picoによる、Si5351A 7MHz VFO→直交ミキサ→ADC入力→SSB検波【demod = adc_I - adc_Q】→FIR・IIRフィル...