2025年4月2日水曜日

Pico SDR - Si5351 AM放送 受信テスト

 Raspberry Pi Pico SDR を74HC74フリップフロップ回路を使って90度位相シフトによるAM放送の受信テストを行いました。 
ZEPエンジニアリング株式会社公開【5_PicoStackSDR_program.zipPico SDR IQ.c - AM検波関数を使用し受信。 







動画



参考サイト


回路図
















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



Si5351A クロックジェネレータ





90度位相シフト基板
Si5351からAM放送帯の90度位相シフトができないので(同相)、74HC74フリップフロップ回路を使って90度位相シフトしました。基板はブレットボードに差込できるようにしました。
●ZEPエンジニアリング株式会社
●Tj Lab【Si5351で直交信号







ロジックアナライザ

求める周波数の4倍クロック(X4)を入力して90度位相シフトクロック0、1を出力。






オシロスコープ
上ーGPIO0 下ーGPIO1 (531kHz)




















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

//pico sdk libraries
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "hardware/gpio.h"
#include "hardware/timer.h"
#include "hardware/irq.h"
#include "hardware/adc.h"
#include "hardware/pwm.h"

int audio_slice; //PWM slice for audio
int audio_gain = 3; //audio gain
const int pushSwitchPin = 22; //Reception mode change push switch (LSB→USB→CW→AM)
float result;

//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設定
#define SCREEN_WIDTH 128  // OLED 幅指定
#define SCREEN_HEIGHT 64  // OLED 高さ指定(高さ32のものを使用する場合は32)
#define OLED_RESET -1     // リセット端子(未使用-1)
// I2Cに接続されたSSD1306用「display」の宣言
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire1, OLED_RESET);

//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 = 10000; //STEP 初期値

//VFO関係の定義
const uint32_t LOW_FREQ = 2120000; //下限周波数 = 530000 * 4
const uint32_t HI_FREQ = 6400000;  //上限周波数 = 1600000 * 4
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)
{
Wire1.beginTransmission(Si5351A_ADDR);
Wire1.write(Reg);
Wire1.write(Data);
Wire1.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/4);
  String fH = freqt.substring(0,1);
  String fM = freqt.substring(1,4);
  String fL = freqt.substring(4,6);
  display.clearDisplay();
  display.setCursor(10,00);
  display.setTextSize(2);    
  if (STEP == 10)display.print("10Hz");
  if (STEP == 100)display.print("100Hz");
  if (STEP == 1000)display.print("1KHz");
  if (STEP == 10000)display.print("10KHz");
  Mode_Disp();  
//MHzの表示
  display.setTextSize(3);
  display.setCursor(10,30);
  display.print(fH);    
  fH_old = fH;
//KHzの表示    
  display.setCursor(30,30);
  display.print(fM);    
  fM_old = fM;
//Hzの表示  
  display.setCursor(90,30);
  display.print(fL);  
  display.display();  
  fL_old = fL;  
}

//STEP表示
void Step_Disp(){
  display.clearDisplay();
  display.setCursor(10,00);
  display.setTextSize(2);
  if (STEP == 10)display.print("10Hz");
  if (STEP == 100)display.print("100Hz");
  if (STEP == 1000)display.print("1KHz");
  if (STEP == 10000)display.print("10KHz");  
  display.display();  
  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 { LSB, USB, CW, AM };
DemodulationMode currentMode = LSB;

//モード表示
void Mode_Disp(){
  display.setCursor(90,00);
  display.setTextSize(2);
 if (currentMode == 0){
  display.print("LSB");
 }
 if (currentMode == 1){
  display.print("USB");
 }
 if (currentMode == 2){
  display.print("CW");
 }
 if (currentMode == 3){
  display.print("AM");
}
}
void setup(){  
  Wire1.setSDA(18);  //SDA 1
  Wire1.setSCL(19);  //SCL 1  
  Wire1.begin();
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306:0 allocation failed"));
    for (;;);
  }
  display.setTextColor(SSD1306_WHITE);
  //ロータリーエンコーダとSTEP使用ピンの設定とプルアップ
  pinMode(ENC_A, INPUT_PULLUP);
  pinMode(ENC_B, INPUT_PULLUP);
  pinMode(SW_STEP, INPUT_PULLUP);  
  pinMode(25, OUTPUT);  // pico built-in LED  
  pinMode(pushSwitchPin, INPUT_PULLUP);
 
  //ロータリーエンコーダ割込み設定
  attachInterrupt(ENC_A, rotary, CHANGE);
  attachInterrupt(ENC_B, rotary, CHANGE);  
  display.begin(SSD1306_SWITCHCAPVCC, 0x78>>1);
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.print("VFO_Si5351");
  display.setCursor(0,30);
  display.setTextSize(3);  
  display.print("PicoSDR");  
  display.display();
  delay(2000);
  display.clearDisplay();
  Step_Disp();
  Freq_Disp(FREQ);
 
  //Si5351Aの初期化
  Si5351_init();  
  si5351();  

//ADC init
  adc_init();
  adc_gpio_init(26); //GPIO 26
  adc_select_input(0); //GPIO26 is ADC channel 0    
  adc_set_clkdiv(0); //ADC clock: 48MHz    
  adc_gpio_init(27); //GPIO 27
  adc_select_input(1); //GPIO27 is ADC channel 1
  adc_set_clkdiv(0); //ADC clock: 48MHz
 
  //PWM init for Audio (GPIO16, PWMチャネル0A)
  gpio_set_function(16, GPIO_FUNC_PWM); //GPIO16をPWM出力設定
  audio_slice = pwm_gpio_to_slice_num(16); //GPIO16 PWMスライス宣言
  pwm_set_enabled(audio_slice, true); //PWM出力有効
  pwm_set_wrap(audio_slice, 255); //PWMの分解能力を255に設定
  pwm_set_chan_level(audio_slice, PWM_CHAN_A, 128);//GPIO16-0ACh Duty設定  
}

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

  //read ADC (In-phase channel)
    adc_select_input(0);
    int adc_data_I = adc_read(); //0 to 4095    
    //read ADC (Quadrature channel)
    adc_select_input(1);
    int adc_data_Q = adc_read(); //0 to 4095    
    //remove the offset and normalize the values
    float I_signal = (float)(adc_data_I-2048)/2048.0f; //-1.0f to 1.0f
    float Q_signal = (float)(adc_data_Q-2048)/2048.0f; //-1.0f to 1.0f  
   
  if (digitalRead(pushSwitchPin) == LOW) {
    delay(50); // Debounce delay
    currentMode = static_cast<DemodulationMode>((currentMode + 1) % 4);
    while (digitalRead(pushSwitchPin) == LOW); // Wait for switch release
    delay(50); // Debounce delay
    Mode_Disp();
    Freq_Disp(FREQ);      
  }
//モード処理
switch (currentMode) {
    case LSB:        
      result = I_signal - Q_signal;
      break;
    case USB:      
      result = I_signal + Q_signal;
      break;
    case CW:      
      result = I_signal * Q_signal;
      break;
    case AM:
      result = sqrt(I_signal*I_signal + Q_signal*Q_signal);
      break;
  }
    //復調信号出力設定
    pwm_set_chan_level(audio_slice, PWM_CHAN_A, (int)(256.0f * result * (float)audio_gain * 0.1f) );
    //int PWM = (int)(256.0f * result * (float)audio_gain * 0.1f);  
    //Serial.println(PWM); //シリアルプロッタ
  }

0 件のコメント:

コメントを投稿

USBロジックアナライザ - PWM・UART・I2C測定

USB ロジックアナライザをセットアップしてPWM(波形)・シリアル通信(データ)・I2C(データ)の測定を行いました。 ●PC接続 8ch 24MHz 超コンパクト USB ロジックアナライザ  【 https://www.amazon.co.jp/gp/product/B0D...