pastaのアイコン画像
pasta 2022年09月25日作成 (2022年09月25日更新) © GPL-3.0+
製作品 製作品 閲覧数 691
pasta 2022年09月25日作成 (2022年09月25日更新) © GPL-3.0+ 製作品 製作品 閲覧数 691

spresenseを利用した超音波距離計

spresenseを利用した超音波距離計

spresenseを利用して同相フェーズドアレイ超音波距離計を作りました。
超音波を物体に向けて送信して、反射波が返ってくるまでの時間を計測するToF(Time of Flight)方式です。
(FMCW方式を試そうとしたところ、送信機と受信機のクロストークが酷かったため諦めました。)

距離はUSBシリアルから出力された波形から判別します。

構成

構成
フェーズドアレイの配置

使用した部品など(一部抜けがあります)

送信機

Arduino UNOを用いて4つの送信機を40kHz 10Vp-p矩形波パルスで駆動しています。
矩形波パルスの長さは2.5msです。
超音波送信機の共振スペクトルは鋭いため、送信される超音波は正弦波になり、パルス波形も鋭さを失います。
各送信機に別々のGPIOピンが割り当てられているので、位相はプログラマブルに制御することが可能です。

受信機

受信側アンプの回路図

キャプションを入力できます
1段目で100倍,2段目で10倍の増幅をしています。
入力インピーダンスは10 kΩです。

送信機のコード

#include <Arduino.h>

void setup()
{
  // put your setup code here, to run once:
  DDRD = 0b11111111;
  DDRC = 0b00000001;
  for (;;)
  {
    PORTC=0b00000001;
    for (int i = 0; i < 100; i++)
    {
      PORTD = 0b10101010;
      // PORTD = 0b00100000;
      _delay_us(12);
      PORTD = 0b01010101;
      // PORTD = 0b00010000;
      _delay_us(12);
    }
    PORTC=0b00000000;
    PORTD = 0;
    _delay_ms(100);
  }
}

SPRESENSEのソースコード

トリガーが信号が入ると、ADCを開始して、FIFOバッファーの値を一定ビット数分だけglobal配列にコピーして、内容をprintfします。
超音波スピーカー以外にもADCの測定テストに用いることができます。

#include <sdk/config.h>
#include <nuttx/arch.h>#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/boardctl.h>#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <debug.h>
#include <arch/chip/pin.h>
#include <arch/board/board.h>#ifdef CONFIG_CXD56_ADC
#include <arch/chip/scu.h>
#include <arch/chip/adc.h>
#endif/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/#ifndef CONFIG_EXAMPLES_ADC_MONITOR_DEVPATH
#define CONFIG_EXAMPLES_ADC_MONITOR_DEVPATH "/dev/hpadc0"
#endif#ifndef CONFIG_EXAMPLES_ADC_MONITOR_BUFSIZE
#define CONFIG_EXAMPLES_ADC_MONITOR_BUFSIZE 10000
#endif
bool signal_triggered = false;
/****************************************************************************
 * Name: myapp_main
 ****************************************************************************/
static int gpio_handler(int irq, FAR void *context, FAR void *arg)
{
  board_gpio_int(PIN_UART2_RXD, false);
  // printf("interrupt occured");
  fflush(stdout);
  signal_triggered = true;
  board_gpio_write(PIN_UART2_TXD, 1);
​
  return OK;
}
​
int max(int a, int b)
{
  return a > b ? a : b;
}
int min(int a, int b)
{
  return a < b ? a : b;
}
​
char wave_buffer[CONFIG_EXAMPLES_ADC_MONITOR_BUFSIZE];
int main(int argc, FAR char *argv[])
{
  board_gpio_config(PIN_UART2_TXD, 0, false, true, PIN_FLOAT);
  board_gpio_config(PIN_UART2_RXD, 0, true, false, PIN_PULLDOWN);
  board_gpio_intconfig(PIN_UART2_RXD, INT_RISING_EDGE, 0, gpio_handler);
  board_gpio_int(PIN_UART2_RXD, true);
  // printf("interrupt initialized");
  fflush(stdout);
  // board_gpio_int(PIN_UART2_RXD, true)
  int ret;
  int errval = 0;
  int fd;
  char *buftop = &wave_buffer;
​
  fd = open(CONFIG_EXAMPLES_ADC_MONITOR_DEVPATH, O_RDONLY);
  if (fd < 0)
  {
    printf("open %s failed: %d\n", CONFIG_EXAMPLES_ADC_MONITOR_DEVPATH, errno);
    errval = 4;
    goto errout;
  }
​
  /* SCU FIFO overwrite */
​
  ret = ioctl(fd, SCUIOC_SETFIFOMODE, 1);
  if (ret < 0)
  {
    errval = errno;
    printf("ioctl(SETFIFOMODE) failed: %d\n", errval);
    goto errout_with_dev;
  }
​
  while (!signal_triggered)
  {
    __asm__("nop");
  }
  /* Start A/D conversion */
  ret = ioctl(fd, ANIOC_CXD56_START, 0);
  if (ret < 0)
  {
    errval = errno;
    printf("ioctl(START) failed: %d\n", errval);
    goto errout_with_dev;
  }
​
  int total_bytes = 0;
  for (;;)
  {
    ssize_t nbytes;
    nbytes = read(fd, buftop + total_bytes, min(CONFIG_EXAMPLES_ADC_MONITOR_BUFSIZE - total_bytes, CONFIG_CXD56_HPADC0_FSIZE));
    if (nbytes < 0)
    {
      errval = errno;
      printf("read failed:%d\n", errval);
      goto errout_with_dev;
    }
    total_bytes += nbytes;
    if (total_bytes >= CONFIG_EXAMPLES_ADC_MONITOR_BUFSIZE)
    {
      break;
    }
  }
  /* Stop A/D conversion */
​
  ret = ioctl(fd, ANIOC_CXD56_STOP, 0);
  if (ret < 0)
  {
    int errcode = errno;
    printf("ioctl(STOP) failed: %d\n", errcode);
  }
​
  close(fd);
  char *start = buftop;
  char *end = buftop + CONFIG_EXAMPLES_ADC_MONITOR_BUFSIZE;
  while (1)
  {
    int16_t data = (int16_t)(*(uint16_t *)(start));
    start += sizeof(uint16_t);
    if (start >= end)
    {
      break;
    }
    printf("%d ", data);
  }
  printf("\n");
​
  board_gpio_write(PIN_UART2_TXD, 0);
  return OK;
​
  /* Error exits */
​
errout_with_dev:
  close(fd);
​
errout:
  printf("ADC monitor example terminating!\n");
  if (buftop)
  {
    free(buftop);
    buftop = NULL;
  }
  fflush(stdout);
  return errval;
}

SDK Configは"ADC"をベースに以下の部分を変更しました。
SDK Config

観測波形

サンプリング周波数は256 kHzです。
2m程度離れたガラス窓に向けて計測した波形です。
FIFOバッファからコピーする際に誤って0が挿入されています。
また、ADCの起動に20ms程度かかっているため、時間ずれが生じています。
キャプションを入力できます

今後の課題

  • フェーズドアレイの位相を制御して2次元スキャンを行う
  • 高電圧で超音波モジュールを駆動する
  • カメラの画像を用いて点群データにテクスチャマッピングを行う
  • microSDに深度画像を保存する
  • HPADC1に別の受信機をつないでダイバーシティ受信を実装する

感想

GPIOについて

  • GPIO割り込みの速度は超音波の計測には十分だった
  • GPIO出力に関して、位相制御を行うには遅延が大きすぎた
  • ASMPワーカーから呼び出せるのか呼び出せないのかドキュメントに明記されていなかった

ADCについて

  • read関数を呼ぶ間隔が大きすぎるとFIFOバッファーがオーバーフローしてデータが一部失われてしまう
ログインしてコメントを投稿する