ESP32-S3 AF信号処理ボード(SSB復調)

24kHzのSSB信号を復調する処理を組み込んでみた.
構成は下図のとおり.

96kHzサンプリングで入力した信号をダウンコンバートしたあとダウンサンプリングしているので,計算量はESP32でもまだ余裕があるくらいに抑えることができている.

コードは以下のとおり.
ただし各フィルタの係数に関してはまだ検討の余地があるため値を設定する処理は含めていない.
(雛形,参考用として開示しますので詳細な動作の説明やプログラムの追加,修正を求めることはご遠慮願います.)

// 24kHz SSB RX on ESP32-S3
// T.Uebo  October 17 , 2022

// I2S Master MODE 96kHz/32bit
//
//   Mclk  GPIO 39
//   Bclk  GPIO 42
//   LRclk GPIO 2
//   Dout  GPIO 41
//   Din   GPIO 1

#include <esp_dsp.h>
#include <driver/i2s.h>
#define fsample 96000
#define DOWN_SAMPLE 6
#define BLOCK_SAMPLES (DOWN_SAMPLE*16)

//buffers
int rxbuf[BLOCK_SAMPLES*2], txbuf[BLOCK_SAMPLES*2];
float Lch_in[BLOCK_SAMPLES], Rch_in[BLOCK_SAMPLES];
float Lch_out[BLOCK_SAMPLES], Rch_out[BLOCK_SAMPLES];

// Decimation Filter
#define Ndec 512
float dec_fir_coeff[Ndec];   // フィルタ係数
float z_decL[Ndec], z_decR[Ndec];
fir_f32_t sfir_decL, sfir_decR;

// Complex coeff. Filter
#define Ncmplx 256
float CF_Re[Ncmplx], CF_Im[Ncmplx];  // フィルタ係数
float z_firRe[Ncmplx], z_firIm[Ncmplx];
fir_f32_t sfirRe, sfirIm;

// Anti-aliasing Filter
float IIR_coeff0[5], IIR_coeff1[5], IIR_coeff2[5], IIR_coeff3[5];  // フィルタ係数
float z_IIR0[2], z_IIR1[2], z_IIR2[2], z_IIR3[2];

// 24kHz Local OSC.
#define N_LO 4 
float LO_I[N_LO]={1.0f, 0, -1.0f, 0};
float LO_Q[N_LO]={0, -1.0f, 0, 1.0f};
int pt_LO = 0;

// for FFT
#define Nfft 4096
float dat[Nfft*2];
float wf[Nfft];
float pow_sp[Nfft];

//Ring Buffer
#define Ring_Buf_Size (Nfft+256)
volatile float d_Lch[Ring_Buf_Size];
volatile float d_Rch[Ring_Buf_Size];
volatile int pt = 0;


/*-----------------------------------------------------------------------------------------------
  Setup
-------------------------------------------------------------------------------------------------*/
void setup(void) {
  Serial.begin(115200);
  delay(50);

  digitalWrite(48, LOW);
  pinMode(48, OUTPUT);
  digitalWrite(48, LOW);
 

  // I2S setup (Lables are defined in i2s_types.h, esp_intr_alloc.h)----------------------------------------
  i2s_config_t i2s_config = {
    .mode =  (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX  | I2S_MODE_RX),
    .sample_rate = fsample,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
    .communication_format = I2S_COMM_FORMAT_STAND_I2S,
    .intr_alloc_flags = ESP_INTR_FLAG_LOWMED,
    .dma_buf_count = 2,
    .dma_buf_len = BLOCK_SAMPLES*4, //*2
    .use_apll = false,
    .tx_desc_auto_clear = true,
    .fixed_mclk = 0,
    .mclk_multiple = I2S_MCLK_MULTIPLE_256,
  };
  i2s_driver_install( I2S_NUM_0, &i2s_config, 0, NULL);

  i2s_pin_config_t pin_config = {
        .mck_io_num = 39,
        .bck_io_num = 42,
        .ws_io_num = 2,
        .data_out_num = 41,
        .data_in_num = 1                                                       
    };
  i2s_set_pin( I2S_NUM_0, &pin_config);

  // set up FIR --------------------------------------------------------
  // Decimation filter
  dsps_fird_init_f32(&sfir_decL, dec_fir_coeff, z_decL, Ndec, DOWN_SAMPLE, 0);
  dsps_fird_init_f32(&sfir_decR, dec_fir_coeff, z_decR, Ndec, DOWN_SAMPLE, 0);
  // Complex FIR Filter
  dsps_fir_init_f32(&sfirRe, CF_Re, z_firRe, Ncmplx);
  dsps_fir_init_f32(&sfirIm, CF_Im, z_firIm, Ncmplx);
}


/*-----------------------------------------------------------------------------------------------
  Signal Process Loop
-------------------------------------------------------------------------------------------------*/
void loop(void) {
  size_t readsize = 0;

  //Input from I2S codec
  esp_err_t rxfb = i2s_read(I2S_NUM_0, &rxbuf[0], BLOCK_SAMPLES*2*4, &readsize, portMAX_DELAY);
  if (rxfb == ESP_OK && readsize==BLOCK_SAMPLES*2*4) {

    digitalWrite(48, HIGH);

    int j=0;
    for (int i=0; i<BLOCK_SAMPLES; i++) {
      Lch_in[i] = (float) rxbuf[j];
      Rch_in[i] = (float) rxbuf[j+1];
      j+=2;

      d_Lch[pt]=Lch_in[i];
      d_Rch[pt]=Rch_in[i];
      pt++; if( pt == Ring_Buf_Size ) pt=0;
    }
    
    //-------Signal process -------------------------------
    
    // for (int i=0; i<BLOCK_SAMPLES; i++) {
    //   Lch_out[i] = Lch_in[i];
    //   Rch_out[i] = Rch_in[i];    
    // }

    // Down conversion,  24kHz to 0
    for(int i=0; i<BLOCK_SAMPLES; i++){
      Lch_out[i] = Lch_in[i]*LO_I[pt_LO] - Rch_in[i]*LO_Q[pt_LO];
      Rch_out[i] = Rch_in[i]*LO_I[pt_LO] + Lch_in[i]*LO_Q[pt_LO];
      pt_LO++; if(pt_LO==N_LO) pt_LO=0;
    }
    
    // Decimation (Down sampling) 
    dsps_fird_f32(&sfir_decL, Lch_out, Lch_in, BLOCK_SAMPLES);
    dsps_fird_f32(&sfir_decR, Rch_out, Rch_in, BLOCK_SAMPLES);
  
    // Demod.  -----------------------------------------------------------------------
    // SSB/CW
    // Complex coeff. Filter
    dsps_fir_f32(&sfirRe, Lch_in, Lch_out, BLOCK_SAMPLES/DOWN_SAMPLE);
    dsps_fir_f32(&sfirIm, Rch_in, Rch_out, BLOCK_SAMPLES/DOWN_SAMPLE);

    for(int i=0; i<BLOCK_SAMPLES/DOWN_SAMPLE; i++){
      Lch_in[i] = Lch_out[i] - Rch_out[i];
      //Lch_in[i] = Lch_out[i] + Rch_out[i]; 
    }
    // Demod. output : Lch_in[]


    // UP sampling (Interpolation)---------------------------------------
    for(int i=BLOCK_SAMPLES/DOWN_SAMPLE-1; i>=0;  i--){
      Lch_out[i*DOWN_SAMPLE]=Lch_in[i];
      for(int j=1; j<DOWN_SAMPLE; j++){
        Lch_out[i*DOWN_SAMPLE+j]=0;
      }
    }
    // Anti-aliasing Filter
    dsps_biquad_f32(Lch_out, Lch_in, BLOCK_SAMPLES, IIR_coeff0, z_IIR0);
    dsps_biquad_f32(Lch_in, Lch_out, BLOCK_SAMPLES, IIR_coeff1, z_IIR1);
    dsps_biquad_f32(Lch_out, Lch_in, BLOCK_SAMPLES, IIR_coeff2, z_IIR2);
    dsps_biquad_f32(Lch_in, Lch_out, BLOCK_SAMPLES, IIR_coeff3, z_IIR3);

    //Output to I2S codec
    j=0;
    for (int i=0; i<BLOCK_SAMPLES; i++) {
      txbuf[j]   = (int) Lch_out[i];
      txbuf[j+1] = (int) Lch_out[i];
      j+=2;

      // d_Lch[pt]=Lch_out[i];
      // d_Rch[pt]=Rch_out[i];
      // pt++; if( pt == Ring_Buf_Size ) pt=0;
    }

    i2s_write( I2S_NUM_0, &txbuf[0], BLOCK_SAMPLES*2*4, &readsize, portMAX_DELAY);

  }
  digitalWrite(48, LOW);
}

とりあえず問題なく動作しているので,今後詳細に動作を確認していくことにする.
特にADCに内蔵の入力フィルタの切れがあまりよくなさそうで,48kHz以上が折り返してきて56kHzくらいでようやく見えなくなるのが気になる.