Raspberry Piでディジタル信号処理

Raspberry Pi 3上で151タップのFIRフィルタを実現する短いCプログラムです。オーディオ信号はIC-7410からUSBオーディオインターフェース経由で入力され、低域通過フィルタ処理されて、ボード上のアナログオーディオ端子から出力されます。

ウォーターフォール画面の上半分と下半分とは、それぞれ低域通過フィルタ前後の信号を示しています。

/* (c) JH1OOD, Mike */
/* % gcc -Wall -std=c99 test.c -o test -lm -lasound -lfftw3 */

#define NO_DEBUG
#define NO_MARKER
#define NFFT 4096
#define WINDOW_XSIZE 1320
#define WINDOW_YSIZE 500
#define AREA1_XSIZE 99
#define AREA1_YSIZE 50
#define WATERFALL_XSIZE 512
#define WATERFALL_YSIZE 768
#define WAVEFORM_LEN 128
#define BAUDRATE B19200
#define TIMEOUT_VALUE 100
#define END_OF_COMMAND 0xfd
#define M_PI 3.141592653589793

#include "asoundlib.h"
#include <fftw3.h>
#include <alloca.h>
#include <complex.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <math.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

static char myrig  [256]  = "/dev/ttyUSB0"; /* IC-7410 control */
static char device [256]  = "hw:1,0";       /* capture  device (IC-7410) */
static char device2[256]  = "hw:0,0";       /* playback device (Raspberry) */

static unsigned int myrate       [2] = { 32000,  32000};    /* stream rate */
static unsigned int mychannels   [2] = {     1,      2};    /* count of channels */
static unsigned int mybuffer_time[2] = {512000, 512000};    /* ring buffer length in us */
static unsigned int myperiod_time[2] = {128000, 128000};    /* ring buffer length in us */
static snd_pcm_sframes_t mybuffer_size[2];
static snd_pcm_sframes_t myperiod_size[2];

static int byte_per_sample       = 2; /* 16 bit format */

static double audio_signal      [NFFT];
static double audio_signal_ffted[NFFT];
static double fft_window        [NFFT];
static int nsamples;
static double bin_size;
static double amax = 14.0, amin = 7.0;

int    ntap = 151;
double lpf[151] = { 
1.247414986406200e-19 ,
4.334705494590657e-05 ,
8.850060712564477e-05 ,
1.361006393933032e-04 ,
1.866984580479588e-04 ,
2.406936513109861e-04 ,
2.982732904863955e-04 ,
3.593558051438401e-04 ,
4.235419118958102e-04 ,
4.900748511120966e-04 ,
5.578119888415469e-04 ,
6.252095759297119e-04 ,
6.903221284279323e-04 ,
7.508175103284513e-04 ,
8.040083705582066e-04 ,
8.469001216194019e-04 ,
8.762551590938831e-04 ,
8.886725221742926e-04 ,
8.806816987992936e-04 ,
8.488487984711850e-04 ,
7.898928649533107e-04 ,
7.008096928689311e-04 ,
5.790001590430827e-04 ,
4.223997924009963e-04 ,
2.296060950694531e-04 ,
1.269465717218602e-18 ,
-2.661421870477758e-04 ,
-5.675499571190165e-04 ,
-9.018763238075437e-04 ,
-1.265635303898488e-03 ,
-1.654162014855776e-03 ,
-2.061597828909119e-03 ,
-2.480902523911547e-03 ,
-2.903894804281316e-03 ,
-3.321321943257440e-03 ,
-3.722958634097960e-03 ,
-4.097734447801007e-03 ,
-4.433888594504296e-03 ,
-4.719149991620855e-03 ,
-4.940939970926038e-03 ,
-5.086594325967014e-03 ,
-5.143600826465778e-03 ,
-5.099847822969537e-03 ,
-4.943879146617302e-03 ,
-4.665150187537139e-03 ,
-4.254279820994538e-03 ,
-3.703292750564303e-03 ,
-3.005846857317347e-03 ,
-2.157440285595270e-03 ,
-1.155593258862735e-03 ,
-3.153189039542980e-18 ,
1.307353376967917e-03 ,
2.762108541214490e-03 ,
4.357495036116219e-03 ,
6.084332872893948e-03 ,
7.931078886295701e-03 ,
9.883917240592958e-03 ,
1.192689357713280e-02 ,
1.404209139127653e-02 ,
1.620984833667827e-02 ,
1.840900929837332e-02 ,
2.061721227239471e-02 ,
2.281120235698237e-02 ,
2.496716851584792e-02 ,
2.706109723255228e-02 ,
2.906913674980633e-02 ,
3.096796528888811e-02 ,
3.273515648009884e-02 ,
3.434953521002414e-02 ,
3.579151720701521e-02 ,
3.704342594131541e-02 ,
3.808978080607253e-02 ,
3.891755106250746e-02 ,
3.951637066630648e-02 ,
3.987870982977506e-02 ,
4.000000000000000e-02 ,
3.987870982977506e-02 ,
3.951637066630648e-02 ,
3.891755106250746e-02 ,
3.808978080607253e-02 ,
3.704342594131541e-02 ,
3.579151720701521e-02 ,
3.434953521002414e-02 ,
3.273515648009884e-02 ,
3.096796528888811e-02 ,
2.906913674980633e-02 ,
2.706109723255228e-02 ,
2.496716851584792e-02 ,
2.281120235698237e-02 ,
2.061721227239471e-02 ,
1.840900929837332e-02 ,
1.620984833667827e-02 ,
1.404209139127653e-02 ,
1.192689357713280e-02 ,
9.883917240592958e-03 ,
7.931078886295701e-03 ,
6.084332872893948e-03 ,
4.357495036116219e-03 ,
2.762108541214490e-03 ,
1.307353376967917e-03 ,
-3.153189039542980e-18 ,
-1.155593258862735e-03 ,
-2.157440285595270e-03 ,
-3.005846857317347e-03 ,
-3.703292750564303e-03 ,
-4.254279820994538e-03 ,
-4.665150187537139e-03 ,
-4.943879146617302e-03 ,
-5.099847822969537e-03 ,
-5.143600826465778e-03 ,
-5.086594325967014e-03 ,
-4.940939970926038e-03 ,
-4.719149991620855e-03 ,
-4.433888594504296e-03 ,
-4.097734447801007e-03 ,
-3.722958634097960e-03 ,
-3.321321943257440e-03 ,
-2.903894804281316e-03 ,
-2.480902523911547e-03 ,
-2.061597828909119e-03 ,
-1.654162014855776e-03 ,
-1.265635303898488e-03 ,
-9.018763238075437e-04 ,
-5.675499571190165e-04 ,
-2.661421870477758e-04 ,
1.269465717218602e-18 ,
2.296060950694531e-04 ,
4.223997924009963e-04 ,
5.790001590430827e-04 ,
7.008096928689311e-04 ,
7.898928649533107e-04 ,
8.488487984711850e-04 ,
8.806816987992936e-04 ,
8.886725221742926e-04 ,
8.762551590938831e-04 ,
8.469001216194019e-04 ,
8.040083705582066e-04 ,
7.508175103284513e-04 ,
6.903221284279323e-04 ,
6.252095759297119e-04 ,
5.578119888415469e-04 ,
4.900748511120966e-04 ,
4.235419118958102e-04 ,
3.593558051438401e-04 ,
2.982732904863955e-04 ,
2.406936513109861e-04 ,
1.866984580479588e-04 ,
1.361006393933032e-04 ,
8.850060712564477e-05 ,
4.334705494590657e-05 ,
1.247414986406200e-19
};

int fd = -1;
double *in;
fftw_complex *out;
fftw_plan p;

static signed short ringbuffer[16384]; // 0x4000
static int wpnt = 0;                   // 0x0000
static int rpnt = 16384 / 2;           // 0x2000

// ---------------------------------------------------------------------
static int myset_hwparams(snd_pcm_t *handle, snd_pcm_hw_params_t *params, int id) {
  snd_pcm_uframes_t size;
  int err, dir;

  fprintf(stderr, "myset_hwparams: id = %2d \n", id);

  /* choose all parameters */
  err = snd_pcm_hw_params_any(handle, params);
  if (err < 0) {
    printf("Broken configuration for playback: no configurations available: %s\n",
        snd_strerror(err));
    return err;
  }

  /* set hardware resampling */
  err = snd_pcm_hw_params_set_rate_resample(handle, params, 0); // 0: no resampling
  if (err < 0) {
    printf("Resampling setup failed for playback: %s\n", snd_strerror(err));
    return err;
  }

  /* set the interleaved read/write format */
  err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
  if (err < 0) {
    printf("Access type not available for playback: %s\n", snd_strerror(err));
    return err;
  }

  /* set the sample format */
  err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16);
  if (err < 0) {
    printf("Sample format not available for playback: %s\n", snd_strerror(err));
    return err;
  }

  /* set the count of channels */
  err = snd_pcm_hw_params_set_channels(handle, params, mychannels[id]);
  if (err < 0) {
    printf("Channels count (%i) not available for the device: %s\n", mychannels[id],
           snd_strerror(err));
    return err;
  }

  /* set the stream rate */
  unsigned int rrate;
  rrate = myrate[id];
  err   = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0);
  fprintf(stderr, "rate = %d, rrate = %d \n", myrate[id], rrate);
  if (err < 0) {
    printf("Rate %iHz not available for playback: %s\n", myrate[id], snd_strerror(err));
    return err;
  }
  if (rrate != myrate[id]) {
    printf("set_hwparams2: Rate doesn't match (requested %iHz, get %iHz)\n", myrate[id], err);
    return -EINVAL;
  }

  /* set the buffer time */
  err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &mybuffer_time[id], &dir);
  if (err < 0) {
    printf("Unable to set buffer time %i for the device: %s\n", mybuffer_time[id], snd_strerror(err));
    return err;
  }

  err = snd_pcm_hw_params_get_buffer_size(params, &size);
  if (err < 0) {
    printf("Unable to get buffer size for playback: %s\n", snd_strerror(err));
    return err;
  }
  mybuffer_size[id] = size;

  /* set the period time */
  err = snd_pcm_hw_params_set_period_time_near(handle, params, &myperiod_time[id], &dir);
  if (err < 0) {
    printf("Unable to set period time %i for playback: %s\n", myperiod_time[id], snd_strerror(err));
    return err;
  }

  err = snd_pcm_hw_params_get_period_size(params, &size, &dir);
  if (err < 0) {
    printf("Unable to get period size for playback: %s\n", snd_strerror(err));
    return err;
  }
  myperiod_size[id] = size;

  /* write the parameters to device */
  err = snd_pcm_hw_params(handle, params);
  if (err < 0) {
    printf("Unable to set hw params for playback: %s\n", snd_strerror(err));
    return err;
  }
  return 0;
}

// ---------------------------------------------------------------------
static int myset_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams, int id) {
  int err;
  fprintf(stderr, "myset_swparams: id = %2d \n", id);

  /* get the current swparams */
  err = snd_pcm_sw_params_current(handle, swparams);
  if (err < 0) {
    printf("Unable to determine current swparams for playback: %s\n",
           snd_strerror(err));
    return err;
  }

  /* start the transfer when the buffer is almost full: */

  err = snd_pcm_sw_params_set_start_threshold(
      handle, swparams, (mybuffer_size[id] / myperiod_size[id]) * myperiod_size[id]);
  if (err < 0) {
    printf("Unable to set start threshold mode for playback: %s\n",
           snd_strerror(err));
    return err;
  }

  /* allow the transfer when at least period_size samples can be processed */

  err = snd_pcm_sw_params_set_avail_min(handle, swparams, myperiod_size[id]);
  if (err < 0) {
    printf("Unable to set avail min for playback: %s\n", snd_strerror(err));
    return err;
  }

  /* write the parameters to the playback device */
  err = snd_pcm_sw_params(handle, swparams);
  if (err < 0) {
    printf("Unable to set sw params for playback: %s\n", snd_strerror(err));
    return err;
  }
  return 0;
}

// ---------------------------------------------------------------------
static void async_callback2(snd_async_handler_t *ahandler) {
  snd_pcm_t *handle     = snd_async_handler_get_pcm(ahandler);
  signed short *samples = snd_async_handler_get_callback_private(ahandler);
  snd_pcm_sframes_t avail;
  int err;
  static int icount = 0;

  avail = snd_pcm_avail_update(handle);

  while (avail >= myperiod_size[1]) {
    err = snd_pcm_writei(handle, samples, myperiod_size[1]);

    if (err < 0) {
      printf("Write error: %s\n", snd_strerror(err));
      exit(EXIT_FAILURE);
    }

    if (err != myperiod_size[1]) {
      printf("Write error: written %i expected %li\n", err, myperiod_size[1]);
      exit(EXIT_FAILURE);
    }

    fprintf( stderr, "async_callback2: icount = %8d, avail = %12ld, period_size = %12ld \n",
        icount++, avail, myperiod_size[1]);

    for (int i = 0; i < 4096; i++) {
    double val = 0.0;
    int index = 0;
    for (int i=0;i<ntap;i++) {
      index = rpnt - i;
      if (index < 0) index += 16384;
      val += ringbuffer[index] * lpf[i];
    }
      samples[2 * i]     = (signed short) val;
      samples[2 * i + 1] = (signed short) val;
      rpnt++;
      rpnt &= 0x3fff;
    }
    avail = snd_pcm_avail_update(handle);
  }
}

// ---------------------------------------------------------------------
static int async_loop2(snd_pcm_t *handle, signed short *samples) {
  snd_async_handler_t *ahandler;
  int err, count;

  err = snd_async_add_pcm_handler(&ahandler, handle, async_callback2, samples);
  if (err < 0) {
    printf("Unable to register async handler\n");
    exit(EXIT_FAILURE);
  }
  for (count = 0; count < 2; count++) {
    err = snd_pcm_writei(handle, samples, myperiod_size[1]);
    if (err < 0) {
      printf("Initial write error: %s\n", snd_strerror(err));
      exit(EXIT_FAILURE);
    }
    if (err != myperiod_size[1]) {
      printf("Initial write error: written %i expected %li\n", err,
             myperiod_size[1]);
      exit(EXIT_FAILURE);
    }
  }
  if (snd_pcm_state(handle) == SND_PCM_STATE_PREPARED) {
    err = snd_pcm_start(handle);
    if (err < 0) {
      printf("Start error: %s\n", snd_strerror(err));
      exit(EXIT_FAILURE);
    }
  }
  return 0;
}

// ---------------------------------------------------------------------
static void async_callback(snd_async_handler_t *ahandler) {
  snd_pcm_t *handle     = snd_async_handler_get_pcm(ahandler);
  signed short *samples = snd_async_handler_get_callback_private(ahandler);
  snd_pcm_sframes_t avail;
  int err;
  static int icount = 0;

  avail = snd_pcm_avail_update(handle);

  while (avail >= myperiod_size[0]) {
    err = snd_pcm_readi(handle, samples, myperiod_size[0]);
    if (err < 0) {
      fprintf(stderr, "Write error: %s\n", snd_strerror(err));
      exit(EXIT_FAILURE);
    }

    if (err != myperiod_size[0]) {
      fprintf(stderr, "Write error: written %i expected %li\n", err,
              myperiod_size[0]);
      exit(EXIT_FAILURE);
    }

    fprintf( stderr, "async_callback : icount = %8d, avail = %12ld, period_size = %12ld \n",
        icount++, avail, myperiod_size[0]);

    for (int i = 0; i < 4096; i++) {
      ringbuffer[wpnt++] = samples[i];
      wpnt &= 0x3fff;
    }

    for (int i = 0; i < NFFT; i++) { /* NFFT=period_size */
      audio_signal[i] = samples[i];
    }

    /* audio signal FFT */

    for (int i = 0; i < NFFT; i++) {
      in[i] = fft_window[i] * audio_signal[i];
    }

    fftw_execute(p);

    /* log10 and normalize */

    for (int i = 0; i < NFFT / 4; i++) {
      double val;
      val = out[i][0] * out[i][0] + out[i][1] * out[i][1];
      if (val < pow(10.0, amin)) {
        audio_signal_ffted[i] = 0.0;
      } else if (val > pow(10.0, amax)) {
        audio_signal_ffted[i] = 1.0;
      } else {
        audio_signal_ffted[i] = (log10(val) - amin) / (amax - amin);
      }
      //    if(i<512) fprintf(stdout, "%1d", (int) (10.0*audio_signal_ffted[i])
      //    ); // apple
    }
    //  fprintf(stdout, "\n"); // apple
    //  fflush(stdout);  // apple

    avail = snd_pcm_avail_update(handle);
  }
}

// ---------------------------------------------------------------------
static int async_loop(snd_pcm_t *handle, signed short *samples) {
  snd_async_handler_t *ahandler;
  int err;

  fprintf(stderr, "async_loop: period_size = %8ld, nsamples = %d \n",
          myperiod_size[0], nsamples);

  err = snd_async_add_pcm_handler(&ahandler, handle, async_callback, samples);
  if (err < 0) {
    fprintf(stderr, "Unable to register async handler\n");
    exit(EXIT_FAILURE);
  }

  if (snd_pcm_state(handle) == SND_PCM_STATE_PREPARED) {
    err = snd_pcm_start(handle);
    if (err < 0) {
      fprintf(stderr, "Start error: %s\n", snd_strerror(err));
      exit(EXIT_FAILURE);
    }
  }

  return 0;
}

// ---------------------------------------------------------------------
void serial_init(void) {
  struct termios tio;
  memset(&tio, 0, sizeof(tio));
  tio.c_cflag     = CS8 | CLOCAL | CREAD;
  tio.c_cc[VEOL]  = 0xfd; /* IC-7410 postamble */
  tio.c_lflag     = 0;    /* non canonical mode */
  tio.c_cc[VTIME] = 0;    /* non canonical mode */
  tio.c_cc[VMIN]  = 1;    /* non canonical mode */

  tio.c_iflag = IGNPAR | ICRNL;
  cfsetispeed(&tio, BAUDRATE);
  cfsetospeed(&tio, BAUDRATE);
  tcsetattr(fd, TCSANOW, &tio);
}

// ---------------------------------------------------------------------
int main(int argc, char *argv[]) {
  struct termios oldtio;
  snd_pcm_t *handle;
  snd_pcm_t *handle2;
  snd_pcm_hw_params_t *hwparams;
  snd_pcm_hw_params_t *hwparams2;
  snd_pcm_sw_params_t *swparams;
  snd_pcm_sw_params_t *swparams2;
  signed short *samples;
  signed short *mysamples;
  int err;
  int id;  /* 0: capture device, 1: playback device */

  for(int i=0;i<ntap;i++) fprintf(stderr, "%20.10f \n", lpf[i]);

  setvbuf(stdout, NULL, _IOFBF, 0);

  if (argc != 4) {
    fprintf(stderr, "Usage %s /dev/ttyUSB0 hw:1,0 hw:0,0 \n", argv[0]);
    fprintf(stderr,
            " try ls -l /dev/ttyUSB*; arecord -l; aplay -l to know "
            "these parameters.\n");
    return -1;
  }

  strcpy(myrig  , argv[1]);
  strcpy(device , argv[2]);
  strcpy(device2, argv[3]);
  fprintf(stderr, "serial        device is [%s] \n", myrig);
  fprintf(stderr, "audio capture device is [%s] \n", device);
  fprintf(stderr, "audio output  device is [%s] \n", device2);
  fprintf(stderr, "byte_per_sample = %d \n", byte_per_sample);

  bin_size = myrate[0] / (double)NFFT;
  for (int i = 0; i < NFFT; i++) {
    fft_window[i] = 0.54 - 0.46 * cos(2.0 * M_PI * i / (double)NFFT);
  }

  in  = malloc(sizeof(double) * NFFT);
  out = (fftw_complex *) fftw_malloc(sizeof(fftw_complex) * (NFFT / 2 + 1));
  p   = fftw_plan_dft_r2c_1d(NFFT, in, out, FFTW_MEASURE);

  snd_pcm_hw_params_alloca(&hwparams);
  snd_pcm_hw_params_alloca(&hwparams2);
  snd_pcm_sw_params_alloca(&swparams);
  snd_pcm_sw_params_alloca(&swparams2);

  if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
    fprintf(stderr, "Audio capture devic3 open error: %s\n", snd_strerror(err));
    return 0;
  }

  if ((err = snd_pcm_open(&handle2, device2, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
    fprintf(stderr, "Audio output devic open error: %s\n", snd_strerror(err));
    return 0;
  }

  id = 0; /* capture device */
  if ((err = myset_hwparams(handle, hwparams, id)) < 0) {
    fprintf(stderr, "Setting of hwparams failed: %s\n", snd_strerror(err));
    exit(EXIT_FAILURE);
  }
  if ((err = myset_swparams(handle, swparams, id)) < 0) {
    fprintf(stderr, "Setting of swparams failed: %s\n", snd_strerror(err));
    exit(EXIT_FAILURE);
  }

  id = 1; /* playback device */
  if ((err = myset_hwparams(handle2, hwparams2, id)) < 0) {
    printf("Setting of hwparams failed: %s\n", snd_strerror(err));
    exit(EXIT_FAILURE);
  }
  if ((err = myset_swparams(handle, swparams, id)) < 0) {
    fprintf(stderr, "Setting of swparams failed: %s\n", snd_strerror(err));
    exit(EXIT_FAILURE);
  }

  nsamples = myperiod_size[0] * mychannels[0] * byte_per_sample;
  fprintf(stderr, "main: period_size = %8ld, nsamples = %d \n", myperiod_size[0],
          nsamples);

  samples = malloc(nsamples);
  if (samples == NULL) {
    fprintf(stderr, "cannot malloc samples \n");
    exit(EXIT_FAILURE);
  }

  mysamples = malloc(32768);
  if (mysamples == NULL) {
    fprintf(stderr, "cannot malloc mysamples \n");
    exit(EXIT_FAILURE);
  }

  for (int i = 0; i < 4096; i++) {
    mysamples[2 * i + 1] = 8096.0 * sin(i * 2.0 * 3.14 / 128.0);
    mysamples[2 * i + 0] = 8096.0 * sin(i * 2.0 * 3.14 / 128.0);
  }

  if ((err = async_loop(handle, samples)) < 0) {
    fprintf(stderr, "async_loop set error: %s\n", snd_strerror(err));
  }

  if ((err = async_loop2(handle2, mysamples)) < 0) {
    fprintf(stderr, "async_loop2 set error: %s\n", snd_strerror(err));
  }

  fd = open(myrig, O_RDWR | O_NOCTTY);
  if (fd < 0) {
    fprintf(stderr, "Error: can not open %s \n", myrig);
    return (-1);
  }
  tcgetattr(fd, &oldtio);
  serial_init();

  while (1) {
    sleep(10000);
  }

  fftw_destroy_plan(p);
  fftw_free(in);
  fftw_free(out);

  return 0;
}

Raspberry PiとNode.jsとIC-7410

これも、動きますね。あなたは、あなたのRaspberry Piに、Node.jslibasound2-devFFTW3をインストールするだけです。

pi@raspberrypi:~/Znodejs $ uname -a
Linux raspberrypi 4.9.25-v7+ #994 SMP Fri Apr 28 16:56:00 BST 2017 armv7l GNU/Linux
pi@raspberrypi:~/Znodejs $ ./sprig_audio /dev/ttyUSB0 hw:1,0 | node index.js
serial device is [/dev/ttyUSB0], audio capture device is [hw:1,0] 

ソースコードは、IC-7410 Rig Control with http (7)を、見てください。

Raspberry Piと、Node.js

pi@raspberrypi:~/Znodejs $ node -v
v6.10.2
pi@raspberrypi:~/Znodejs $ npm -v
3.10.10
pi@raspberrypi:~/Znodejs $ node index.js
now listening to the port 3000..
serial port /dev/ttyUSB0 is opened.

動きますね!index.jsindex.htmlについては、私の以前の記事IC-7410 Rig Control with httpを参照して下さい。

Raspberry Pi 3 Model Bと、IC-7410

無線機を制御するには、単純なボードの方が良いかも知れません。

Mac-mini:~ $ ssh pi@192.168.0.102
pi@192.168.0.102's password: 

pi@raspberrypi:~ $ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       5.8G  4.0G  1.5G  74% /
devtmpfs        458M     0  458M   0% /dev
tmpfs           462M   23M  440M   5% /dev/shm
tmpfs           462M  6.4M  456M   2% /run
tmpfs           5.0M  4.0K  5.0M   1% /run/lock
tmpfs           462M     0  462M   0% /sys/fs/cgroup
/dev/mmcblk0p6   65M   21M   45M  33% /boot
tmpfs            93M     0   93M   0% /run/user/1000
/dev/mmcblk0p5   30M  398K   28M   2% /media/pi/SETTINGS

pi@raspberrypi:~ $ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: ALSA [bcm2835 ALSA], device 0: bcm2835 ALSA [bcm2835 ALSA]
  Subdevices: 8/8
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
  Subdevice #4: subdevice #4
  Subdevice #5: subdevice #5
  Subdevice #6: subdevice #6
  Subdevice #7: subdevice #7
card 0: ALSA [bcm2835 ALSA], device 1: bcm2835 ALSA [bcm2835 IEC958/HDMI]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: CODEC [USB Audio CODEC], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

pi@raspberrypi:~ $ arecord -l
**** List of CAPTURE Hardware Devices ****
card 1: CODEC [USB Audio CODEC], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

解析信号(2)

ソースコードは、テストのためだけです。

// test.c
#include <stdio.h>
#include <math.h>
#include <complex.h>
#include <stdlib.h>
#define  PI 3.1415926535

int main () {
	const int nhilbert = 51;
	const double normalization = 1.0028050845;
	double hilbert[51] = {
		-1.244274239685367e-02 ,	//  0
		+8.812508503481302e-16 ,	//  1
		-9.007745694645722e-03 ,	//  2
		-4.892921491956048e-16 ,	//  3
		-1.224827000343725e-02 ,	//  4
		-2.457355030119336e-16 ,	//  5
		-1.626770260654829e-02 ,	//  5
		-1.076580799233997e-15 ,	//  7
		-2.126259131785366e-02 ,	//  8
		+1.551646837953304e-15 ,	//  9
		-2.754013546165147e-02 ,	// 10
		-8.271548913440781e-16 ,	// 11
		-3.555144114893793e-02 ,	// 12
		+1.538194937222543e-16 ,	// 13
		-4.616069183085086e-02 ,	// 14
		-2.517547480270933e-16 ,	// 15
		-6.087912031697079e-02 ,	// 16
		+1.529522013663762e-16 ,	// 17
		-8.310832605502005e-02 ,	// 18
		-7.153673299041007e-16 ,	// 19
		-1.216347994939241e-01 ,	// 20
		-3.571474290448791e-17 ,	// 21
		-2.087518418318809e-01 ,	// 22
		-3.409056833722323e-16 ,	// 23
		-6.354638176591967e-01 };	// 24

	double wave0[1024], wave1[1024], wave2[1024];
	double complex cwave1[1024], cwave2[1024], cbfo[1024];
	double fsample = 32000.0;
	double fsignal =  1000.0;
	double fbfo    =  1010.0;
	double amp     = 1.0;
	double phase   = (45.0/360.0)*2.0*PI;

	hilbert[(nhilbert-1)/2] = 0.0;		// 25
	for (int i=((nhilbert-1)/2)+1;i<nhilbert;i++) {
		hilbert[i] = - hilbert[(nhilbert-1)-i];	// 26 <- -24
	}
	for (int i=0;i<nhilbert;i++) {
		printf("a+ %20.10f \n", hilbert[i]);
	}

	for (int i=0;i<1024;i++) {
		if( (i>=100) && (i<150) ) {
			amp = (i-100)/50.0;
		} else if ( (i>=120) && (i<800) ) {
			amp = 1.0;
		} else if ( (i>=800) && (i<850) ) {
			amp = 1.0 - (i-800)/50.0;
		} else {
			amp = 0.0;
		}
		cbfo [i] = cos(i*2.0*PI/(fsample/fbfo)+phase)
	             - I * sin(i*2.0*PI/(fsample/fbfo)+phase);
		wave0[i] = amp * sin(i*2.0*PI/(fsample/fsignal));
		wave1[i] = 0.0;
		wave2[i] = 0.0;
		printf("b+ %20.10f \n", wave0[i]);
	}

	for (int i=nhilbert;i<1024-nhilbert;i++) {
		wave1[i] = wave0[i-((nhilbert-1)/2)];
		printf("c+ %20.10f \n", wave1[i]);
	}

	for (int i=nhilbert;i<1024-nhilbert;i++) {
		for(int j=0;j<nhilbert;j++) {
			wave2[i] += wave0[i-j] * hilbert[j];
		}
		wave2[i] /= normalization;
		printf("d+ %20.10f \n", wave2[i]);
	}

	for (int i=0;i<1024;i++) {
		cwave1[i] = wave1[i] + I * wave2[i];
	}

	for (int i=0;i<1024;i++) {
		cwave2[i] = cwave1[i] * cbfo[i];
		printf("e+ %20.10f \n", creal(cwave2[i]));
	}
		
	for (int i=0;i<1024;i++) {
		printf("f+ %20.10f \n", cimag(cwave2[i]));
	}
		
	for (int i=0;i<1024;i++) {
		printf("g+ %20.10f \n", cabs(cwave2[i]));
	}
		
	return 0;
}

bash.sh

  grep a+ ttt | colrm 1 2 > Hilbert
  grep b+ ttt | colrm 1 2 > Waveform
  grep c+ ttt | colrm 1 2 > Re
  grep d+ ttt | colrm 1 2 > Im
  grep e+ ttt | colrm 1 2 > Sig-Re
  grep f+ ttt | colrm 1 2 > Sig-Im
  grep g+ ttt | colrm 1 2 > Sig-Abs
% gcc test.c -o test -lm && ./test > ttt && ./bash.sh
% gnuplot
gnuplot> plot "Re" w lp, "Sig-Re" w lp, "Sig-Im" w lp, "Sig-abs" w lp

解析信号

あたなたは、例えばI/Q出力を持たないような従来型の受信機から得られる実信号から、ヒルベルトフィルタを用いて解析信号を作り出すことができます。

一度、解析信号を得ることが出来れば周波数変換は直截な過程となります。図では、”Re”は10kHz近辺の入力実信号、”Im”は得られた解析信号の虚部、そして、”Sig”は700Hzにダウンコンバートされたバージョンの入力信号です。

あなたは、また(ほとんど)直流にまでダウンコンバートすることもできますが、その場合は、あなたはあなたのBFOの位相を制御しないのであれば、実部と虚部の両方を観測して信号の電力を計算する必要があります。

正弦波的、及び、ランダムな雑音の存在下では、私たちはあまり綺麗な結果は得ることができず、何らかのフィルタリングが必要になります。

IC-7410の制御をhttpで(8)

あなたはウォーターフォール上のトレースをクリックすることにより、その周波数に同調することができます。

index.html

var cwmarker1 = Math.floor(  cwpitch        / (32000 / 4096) );
var cwmarker2 = Math.floor( (cwpitch - 100) / (32000 / 4096) );
var cwmarker3 = Math.floor( (cwpitch + 100) / (32000 / 4096) );

function onClick(e) {
  var rect = e.target.getBoundingClientRect();
  var mx   = e.clientX - rect.left;
  var my   = e.clientY - rect.top;
  socket.emit('message7', mx);
}
canvas1.addEventListener('click', onClick, false);

index.js

  socket.on('message7', function(mx) {
    var newfreq = freqHz - (32000/4096 * mx - cwpitch);
    setfreq(newfreq);
  });

もちろん、新しい周波数を計算するには、自分がどちらのサイドバンドを聴いているのかを意識する必要があります。

IC-7410の制御をhttpで(7)

リモートにあるIC-7410のVFO周波数を、ブラウザのボタンをクリックすることで変更できます。

index.js

const bufa = new Buffer('fefe80e017', 'hex');              // preamble
const bufz = new Buffer('fd', 'hex');                      // postamble
const buf1 = new Buffer('fefe80e0174351fd', 'hex');        // CQ
const buf2 = new Buffer('fefe80e01751525a3ffd', 'hex');    // QRZ?
const buf3 = new Buffer('fefe80e003fd', 'hex');            // read freq
var buf4   = new Buffer('fefe80e0050000000000fd', 'hex');  // send freq

var SerialPort = require('serialport');
var serial     = new SerialPort(
    '/dev/ttyUSB0',
    {baudrate : 19200, parser : SerialPort.parsers.byteDelimiter([ 0xfd ])});

var http  = require('http');
var fs    = require('fs');
var index = fs.readFileSync(__dirname + '/index.html');
var app   = http.createServer(function(req, res) {
                res.writeHead(200, {'Content-Type' : 'text/html'});
                res.end(index);
              })
              .listen(3000);

var io     = require('socket.io').listen(app);
var freqHz = 7026000;

var ndata   = 512;  // data[512] + 0x0a
var pos     = 0;
var myarray = new Array();

// -- water fall --

var count = 0;
process.stdin.on('readable', function() {
  var buf = process.stdin.read();
  if (buf !== null) {
    for (var i = 0; i < buf.length; i++) {
      if (buf[i] == 0x0a) {
        if (pos >= 512) {
          io.emit('waterfall', myarray);
        }
        pos = 0;
      } else {
        myarray[pos++] = buf[i];
      }
    }
  }
});

// -- serial for IC-7410 --

serial.on('open',
          function() { console.log('serial port /dev/ttyUSB0 is opened.'); });

var count2 = 0;
serial.on('data', function(data) {
  //    console.log('received data: ', data.length, data);
  if (!(data[0] == 0xfe & data[1] == 0xfe)) {
    console.log('------------- received serial data error');
    }
  if (data[2] == 0xe0 & data[3] == 0x80 & data[4] == 0x03) {
    var f10   = data[5] >> 4 & 0x0f;
    var f1    = data[5] & 0x0f;
    var f1k   = data[6] >> 4 & 0x0f;
    var f100  = data[6] & 0x0f;
    var f100k = data[7] >> 4 & 0x0f;
    var f10k  = data[7] & 0x0f;
    var f10m  = data[8] >> 4 & 0x0f;
    var f1m   = data[8] & 0x0f;
    var freq  = f10m.toString() + f1m.toString() + "," + f100k.toString() +
               f10k.toString() + f1k.toString() + "." + f100.toString() +
               f10.toString() + f1 + " kHz";
    freqHz = f10m * 10000000 + f1m * 1000000 + f100k * 100000 + f10k * 10000 +
             f1k * 1000 + f100 * 100 + f10 * 10 + f1;
    //        console.log('count2 = ', count2++, 'freq: ' + freq, freqHz);
    io.emit('freqmsg', 'VFO A: ' + freq);
  }

});

serial
    .on('error', function(err) { console.log('Error: ', err.message); })

    // -- set frequency --

    function setfreq(f) {
      var f10m  = Math.floor(f / 10000000); f -= f10m  * 10000000;
      var f1m   = Math.floor(f / 1000000);  f -= f1m   * 1000000;
      var f100k = Math.floor(f / 100000);   f -= f100k * 100000;
      var f10k  = Math.floor(f / 10000);    f -= f10k  * 10000;
      var f1k   = Math.floor(f / 1000);     f -= f1k   * 1000;
      var f100  = Math.floor(f / 100);      f -= f100  * 100;
      var f10   = Math.floor(f / 10);       f -= f10   * 10;
      var f1    = Math.floor(f / 1);

      var data = new Array(
          [ 0xfe, 0xfe, 0x80, 0xe0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd ]);
      buf4[5] = f10   << 4 | f1;
      buf4[6] = f1k   << 4 | f100;
      buf4[7] = f100k << 4 | f10k;
      buf4[8] = f10m  << 4 | f1m;
      buf4[9] = 0;
      serial.write(buf4);
    }

// -- socket --

io.on('connection', function(socket) {

  socket.on('message1', function() { serial.write(buf1); });
  socket.on('message2', function() { serial.write(buf2); });

  socket.on('message3', function() {
    var newfreq = freqHz + 1000;
    setfreq(newfreq);
  });

  socket.on('message4', function() {
    var newfreq = freqHz + 100;
    setfreq(newfreq);
  });

  socket.on('message5', function() {
    var newfreq = freqHz - 100;
    setfreq(newfreq);
  });

  socket.on('message6', function() {
    var newfreq = freqHz - 1000;
    setfreq(newfreq);
  });

  socket.on('your message', function(msg) {
    serial.write(bufa);
    serial.write(msg);
    serial.write(bufz);
    io.emit('your message', msg);
  });

});

// -- request freq --

function sendTime() {
  serial.write(buf3);
  // console.log('Freq asked..');
}

setInterval(sendTime, 100);

// -- EOF --

index.html

<!doctype html>
<html>
<head>

<style>
* { margin: 0; padding: 0; box - sizing : border - box; }
body { font: 13px Helvetica, Arial; }
form { background: # 000; padding: 3px; position: fixed; bottom: 0; width: 100 % ; }
form input { border: 0; padding: 10px; width: 80 % ; margin - right : .5 % ; }
form button { width: 15 % ; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages{list - style - type : none; margin : 0; padding : 0; }
#messages li{padding : 5px 10px; }
#messages li : nth - child(odd){background : #eee; }
#messages {margin - bottom : 40px }
#messages {font - size : 2em }
#btn1 {position : fixed; top : 10px; left :  10px; font - size : 2em }
#btn2 {position : fixed; top : 10px; left :  70px; font - size : 2em }
#btn3 {position : fixed; top : 10px; left : 150px; font - size : 2em; width : 30px }
#btn4 {position : fixed; top : 10px; left : 190px; font - size : 2em; width : 30px }
#btn5 {position : fixed; top : 10px; left : 230px; font - size : 2em; width : 30px }
#btn6 {position : fixed; top : 10px; left : 270px; font - size : 2em; width : 30px }
#frequency {position : fixed; top :  10px; left : 310px; font - size : 2em }
#mycanvas  {position : fixed; top :  60px; left :  10px; font - size : 2em }
#mycanvas2 {position : fixed; top : 600px; left :  10px; font - size : 2em }
</style>

<script>
  window.addEventListener("load", init);
  function init(){}
</script>
</head>

 <body>
    <script src="https://code.jquery.com/jquery-1.11.1.js"></script>
    <script src='/socket.io/socket.io.js'></script>
    <script src="https://code.createjs.com/createjs-2015.11.26.min.js"></script>

    <button id="btn1"><font size="5">CQ</font></button>
    <button id="btn2"><font size="5">QRZ?</font></button>
    <button id="btn3"><font size="5">++</ font></button>
    <button id="btn4"><font size="5">+</font></button>
    <button id="btn5"><font size="5">-</font></button>
    <button id="btn6"><font size="5">--</font></button>

    <div id="frequency"></div>
    <div id="messages"></div>
    <form action="">
      <input id="m" autocomplete="off"/><button>Send</button>
    </form>
    <canvas id="mycanvas"  width="512" height="512" style="background:white;"></canvas>
    <canvas id="mycanvas2" width="512" height= "90" style="background:white;"></canvas>

<script>
var myarray = new Array();
var socket  = io();
var canvas;
var ctx;
var imgData;
var rgb = new Array(3);

var stage = new createjs.Stage("mycanvas2");
var t     = new createjs.Text("IC-7410", "26px serif", "DarkRed");
t.x       = 10;
t.y       = 10;
stage.addChild(t);
stage.update();

function colormap(charcode) {         // 0~9 or 0x30~0x39
  var tmp = (charcode - 0x30) * 0.1;  // 0.0~0.9
  var val;
  var r, g, b;
  if (tmp < 0.50) {
    r = 0.0;
  } else if (tmp > 0.75) {
    r = 1.0;
  } else {
    r = 4.0 * tmp - 2.0;
  }

  if (tmp < 0.25) {
    g = 4.0 * tmp;
  } else if (tmp > 0.75) {
    g = -4.0 * tmp + 4.0;
  } else {
    g = 1.0;
  }

  if (tmp < 0.25) {
    b = 1.0;
  } else if (tmp > 0.50) {
    b = 0.0;
  } else {
    b = -4.0 * tmp + 2.0;
  }

  rgb[1] = 255.0 * g;
  rgb[0] = 255.0 * r;
  rgb[2] = 255.0 * b;
}

function waterFall(myarray) {
  ctx.putImageData(imgData, 0, 1);
  imgData = ctx.getImageData(0, 0, 512, 512);
  for (j = 0; j < 512; j++) {
    colormap(myarray[j]);
    imgData.data[0 + j * 4] = rgb[0];
    imgData.data[1 + j * 4] = rgb[1];
    imgData.data[2 + j * 4] = rgb[2];
    imgData.data[3 + j * 4] = 255;
  }
}

canvas = document.getElementById('mycanvas');
ctx    = canvas.getContext('2d');

var mx, my;
function onClick(e) {
  var rect = e.target.getBoundingClientRect();
  mx       = e.clientX - rect.left;
  my       = e.clientY - rect.top;
  console.log('mouse clicked: ', mx, my);
}
canvas.addEventListener('click', onClick, false);

imgData = ctx.createImageData(512, 512);
for (i = 0; i < 512; i++) {
  for (j = 0; j < 512; j++) {
    imgData.data[0 + j * 4 + i * imgData.width * 4] = j % 256;
    imgData.data[1 + j * 4 + i * imgData.width * 4] = i % 256;
    imgData.data[2 + j * 4 + i * imgData.width * 4] = 128;
    imgData.data[3 + j * 4 + i * imgData.width * 4] = 255;
  }
}
ctx.putImageData(imgData, 0, 0);

var socket = io();

$('#btn1').click(function() { socket.emit('message1'); });
$('#btn2').click(function() { socket.emit('message2'); });
$('#btn3').click(function() { socket.emit('message3'); });
$('#btn4').click(function() { socket.emit('message4'); });
$('#btn5').click(function() { socket.emit('message5'); });
$('#btn6').click(function() { socket.emit('message6'); });

$('form').submit(function() {
  socket.emit('your message', $('#m').val());
  $('#m').val('');
  return false;
});

socket.on('your message', function(msg) {
  $('#messages').append($('<div>').text(msg));
  window.scrollTo(0, document.body.scrollHeight);
});

socket.on('waterfall', function(data) { waterFall(data); });

socket.on('freqmsg', function(msg) {
  t.text = msg;
  stage.update();
  document.getElementById("frequency").innerHTML = msg;
});

</script>

</body>
</html>

IC-7410の制御をhttpで(6)

これは本物のウォーターフォールです。

ところで、もしあなたがgccで、undefined reference to ‘alloca’というエラーメッセージを得るようであれば、以下の行が役に立つかも知れません。

#include <alloca.h>

もし、あなたが”stderr”ではなくて”stdout”を使うのであれば、あなたはファイルバッファリングのモードを指定する必要があるかも知れません。

% sprig_audio_only /dev/ttyUSB0 hw:2,0 |& node index.js
% sprig_audio_only /dev/ttyUSB0 hw:2,0 |  node index.js

sprig_audio_only.c

#include <stdio.h>
int main () {
 setvbuf(stdout, NULL, _IOLBF, 0); // line buffering mode
//
 for(int i=0;i<512<i++) {
  fprintf(stdout, "%1d", audio_signal_ffted[i]);
 }
 fprintf(stdout,"\n");
 fflush(stdout); // this will also do with full buffering mode
//
}

IC-7410の制御をhttpで(5)

それぞれのパーツを一緒にすると、こんな感じになります。ここで、ウォーターフォールのデータは、計算機で生成されたものであることに注意して下さい。

index.js

// file name = index20.js, reads index20.html

const bufa = new Buffer('fefe80e017'          , 'hex'); // preamble
const bufz = new Buffer('fd'                  , 'hex'); // postamble
const buf1 = new Buffer('fefe80e0174351fd'    , 'hex'); // CQ
const buf2 = new Buffer('fefe80e01751525a3ffd', 'hex'); // QRZ?
const buf3 = new Buffer('fefe80e003fd'        , 'hex'); // read freq

var SerialPort = require('serialport');
var serial = new SerialPort('/dev/ttyUSB0',{
    baudrate: 19200,
    parser: SerialPort.parsers.byteDelimiter([0xfd])
});

var http  = require('http');
var fs    = require('fs');
var index = fs.readFileSync(__dirname + '/index20.html');
var app = http.createServer(function(req, res) {
                res.writeHead(200, {'Content-Type' : 'text/html'});
                res.end(index);
              })
              .listen(3000);

var io = require('socket.io').listen(app);

var ndata   = 512;  // data[512] + 0x0a
var pos     = 0;
var myarray = new Array();

// -- water fall --

process.stdin.on('readable', function() {
  var buf = process.stdin.read();
  if (buf !== null) {
    for (var i = 0; i < buf.length; i++) {
      if (buf[i] == 0x0a) {
        pos = 0;
        io.emit('waterfall', myarray);
      } else {
        myarray[pos++] = buf[i];
      }
    }
  }
});

// -- serial for IC-7410 --

serial.on('open',function(){
    console.log('serial port /dev/ttyUSB0 is opened.');
});

serial.on('data',function(data){
    console.log('received data: ', data.length, data);
    if ( !(data[0] == 0xfe & data[1] == 0xfe) ) {
      console.log('** received serial data error **');
    }
    if (data[2] == 0xe0 & data[3] == 0x80 & data[4] == 0x03) {
        var f10   = data[5]>>4 & 0x0f;
        var f1    = data[5]    & 0x0f;
        var f1k   = data[6]>>4 & 0x0f;
        var f100  = data[6]    & 0x0f;
        var f100k = data[7]>>4 & 0x0f;
        var f10k  = data[7]    & 0x0f;
        var f10m  = data[8]>>4 & 0x0f;
        var f1m   = data[8]    & 0x0f;
        var freq  =  f10m.toString()+ f1m.toString()+","
                   +f100k.toString()+f10k.toString()+f1k.toString()+"."
                   + f100.toString()+ f10.toString()+f1+" kHz";
        console.log('freq: ' + freq);
        io.emit('freqmsg', 'VFO A: ' + freq);
    }

});

serial.on('error', function(err) {
  console.log('Error: ', err.message);
})

// -- socket --

io.on('connection', function(socket){

    socket.on('message1', function(){
    serial.write(buf1);
  });

    socket.on('message2', function(){
    serial.write(buf2);
  });

  socket.on('your message', function(msg){
    serial.write(bufa);
    serial.write(msg);
    serial.write(bufz);
    io.emit('your message', msg);
  });

});

// -- request freq --

function sendTime() {
    serial.write(buf3);
}

setInterval(sendTime, 100);

// -- EOF --