2025年4月1日火曜日

Pi Pico PIO 90度位相差クロック - AM放送 受信

 Pi Pico Rx - 公開プログラムnco_pioを使い、Raspberry Pi Pico (RP2040) PIO制御による90度位相差クロック530~1600KHzの局発I Qを直交ミキサに入力、出力IQ信号をZEPエンジニアリング株式会社公開5_PicoStackSDR_program.zipPico SDR IQ.c -AM検波処理によるAM放送を受信しました。
※周波数ステップ100Hz以降は周波数表示・出力周波数共にステップしますが周波数表示と出力周波数に誤差が生じます。AM放送受信には影響が少ないです(通常1KHzでステップ操作)




動画


参考サイト
●Raspberry Pi PicoとMOSFET ③ PWMのAPI



回路図











直交ミキサ基板
uSDX FST3253の回路図を基に製作
●JA2GQP’s Blog uSDX FST3253/CBT3253




オーディオアンプボード
アマゾン購入
●アンプモジュール LM386 DC3-12V
https://www.amazon.co.jp/dp/B07ND6X469?ref=ppx_yo2ov_dt_b_fed_asin_title



90度位相差クロック測定
上ーGPIO0 下ーGPIO1 (530KHz)





プログラム  Arduino IDE【ボード:Raspberry Pi Pico】
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <hardware/pio.h>
#include "nco.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
float result;

// 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, &Wire, OLED_RESET);
//Rotary endoder関係の定義
#define ENC_A 13
#define ENC_B 14
volatile uint8_t old_value = 0x11;
volatile uint8_t value = 0;
volatile uint8_t D;
volatile int8_t count = 0;

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

//VFO関係の定義
const uint32_t LOW_FREQ = 530000; //下限周波数
const uint32_t HI_FREQ = 1600000; //上限周波数
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; //STEP周波数
    count = 0;
  }else if(count <= -4){    
    FREQ -= STEP; //STEP周波数
    count = 0;
  }
  FREQ = constrain(FREQ,LOW_FREQ,HI_FREQ); //VFOの下限と上限を超えないように
  old_value = value;
  }
}

//周波数表示
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);
  display.setRotation(2); //上下反転
  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");
//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);
  }
}




// clock関連
float divider;
float adjusted_frequency;
float system_clock_frequency = 133000000;//システムクロック

void setup() {
  Wire.setSDA(16);  //SDA
  Wire.setSCL(17);  //SCL  
  Wire.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);  
  //ロータリーエンコーダ割込み設定
  attachInterrupt(ENC_A, rotary, CHANGE);
  attachInterrupt(ENC_B, rotary, CHANGE);  
  display.begin(SSD1306_SWITCHCAPVCC, 0x78>>1);
  display.setRotation(2); //上下反転
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.print("Pico PIO");
  display.setCursor(0,30);
  display.setTextSize(3);  
  display.print("VFO");  
  display.display();
  delay(2000);
  display.clearDisplay();
  Step_Disp();
  Freq_Disp(FREQ);  
   
  PIO pio = pio0;
  adjusted_frequency = FREQ;  
  uint sm;
  uint offset = pio_add_program(pio, &nco_program); //offset変数 ← アセンブラのアドレス
  nco_program_init(pio, sm, offset);  //PIOの初期化      
  divider = system_clock_frequency/(4*adjusted_frequency); //クロック周波数分周      
  pio_sm_set_clkdiv(pio, sm, divider); //クロック分割を設定する    
  delay(50);
 
  //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(10, GPIO_FUNC_PWM); //GP10をPWM出力設定
  audio_slice = pwm_gpio_to_slice_num(10); //GP10 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);//GP10-5ACh Duty設定
 
}
void loop() {  
  if(digitalRead(SW_STEP) == LOW)Set_Step();
  if(FREQ != FREQ_OLD){ //周波数FREQが変わったら、周波数を変更    
    Freq_Disp(FREQ);
    FREQ_OLD = FREQ; //変更された周波数を保存        
    adjusted_frequency = FREQ;    
    divider = system_clock_frequency/(4*adjusted_frequency); //クロック周波数分周    
    PIO pio = pio0;
    uint sm;    
    pio_sm_set_clkdiv(pio, sm, divider); //クロック分周を設定する    
    delay(50);
  }  
  //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  
    result = sqrt(I_signal*I_signal + Q_signal*Q_signal); //AM検波
    //復調信号出力設定
    pwm_set_chan_level(audio_slice, PWM_CHAN_A, (int)(256.0f * result * (float)audio_gain * 0.1f) );
    }


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 件のコメント:

コメントを投稿

Pi Pico PIO 90度位相差クロック - AM放送 受信

 Pi Pico Rx - 公開プログラムnco_pioを使い、Raspberry Pi Pico (RP2040) PIO制御による90度位相差クロック530~1600KHzの局発I Qを直交ミキサに入力、出力IQ信号をZEPエンジニアリング株式会社公開 【 5_PicoSta...