« 2022年11月 | トップページ | 2023年1月 »

2022年12月

2022年12月31日 (土)

pico_tnc 2200Hz(Space)でPhaseをずらす件の備忘録

GitHubからダウンロードしたpico_tncのsend.cのsend_byte()についての考察記録

なぜ、mark_tabのサイズは121なのか,、そしてspace_tabのサイズは96なのか。

wave_table.h
#define PHASE_CYCLE 6
#define MARK_TAB_LEN 121 // 66 * 5/6 + 66
#define SPACE_TAB_LEN 96 // 36 * 5/6 + 66
mark_tab[MARK_TAB_LEN]={.....  1200Hz正弦波
space_tab[SPACE_TAB_LEN]={....     2200Hz正弦波
phase_tabは、spaceは6サンプリング周期ごと、markは11サンプリングごとに区切られ、6区画で1波形となる。
つまり、1波形を6等分したもの=60度分。
1200baudでは66サンプリングで1サイクル=360度となる。
static const uint32_t *phase_tab[2][PHASE_CYCLE] = {
{ &space_tab[0], &space_tab[6], &space_tab[12], &space_tab[18], &space_tab[24], &space_tab[30], },
{ &mark_tab[0], &mark_tab[11], &mark_tab[22], &mark_tab[33], &mark_tab[44], &mark_tab[55], },
};
phase_tabには1波形分のサンプリングを6等分(60度分)した数の波形データが入れられており、最終エントリーの先頭から66サンプリング分(360度=1DMA分)が設定されている。つまり、どのエントリーからDMAを始めても1DMA分のデータは揃っていることになる。
send.c
dma_handler()
....
データの1bit単位で波形を1サイクル分設定する
phase_tabから取り出すポインターから66サンプリング分(360度分)がdma_blockに格納される。
   tp->dma_blocks[next][idx++] = phase_tab[level][phase]; // 0: space, 1:mark
仮にlevel=1, phase=5の場合、phase_tab[1][5], つまり mark_tab[55](300度)から66サンプリング(6phase分=360度分)が取り出されることになる。
したがってサンプリングテーブル(mark_tab)はその長さ分(66x5/6=300度)が1波形分(66=360度)よりも余分に必要となる(合計 66x5/6 + 66=121)。
level=0, つまりspaceだったら次回の生成波形は60度(6サンプリング分)波形を前にずらす。
   if (!level) { // need adjust phase if space (2200Hz)
      if (--phase < 0) phase = PHASE_CYCLE - 1;
   }
減算する前のphaseが0、つまり先頭だったら最後尾にする。
なぜspace (2200Hz)だと1波形ごとに6サンプリング分前にずらすのか。
1200Hzで1サイクルが完結する時間スロットでは2200Hzでは2200/1200=11/6サイクル発生することになる。
つまり、サイクル完結に1/6(60度)足りない。仮にこの時間スロットの開始点で2200Nz波形を連続して生成する場合、その波形は前回の開始点波形にたいして1/6サイクル分(60度)後戻りした時点の波形から始まらないと連続性が保てない。
1200Hz波形は2200Hz波形を11/6倍だけ時間方向に引き伸ばしたものと考えることができる。よって2200Hzの後に1200Hz波形を同じタイムスロットで連続性を保って生成するには11サンプル分(60度)後戻りした時点の波形から始めると波形の連続性が保てることになる。
この様子を図示すると以下となる。Markが1サイクルする時間間隔ではSpaceは出力ゼロになるまで60度足りない。次の波形を連続させるには次の波形を―60度ポイントから波形生成を始める必要があるわけだ。
Photo_20221231131001
そもそも、1200Hzと2200Hzでのビット表現となるとどうしても波形は不連続になるが、その事は問題なのだろうか。
この波形不連続は、1200Hzと2200Hz以外の非常に多くの周波数成分を発生してしまうようだ。このノイズ成分により、信号復調率が落ちるのだろう(多分)。

2022年12月29日 (木)

PICOのPIOを使った指定周期DMAによるシリアラル信号出力プログラムについて

PICOのPIOを使ったPWM信号出力をDMAで動作させる方法について備忘録

注:PIOを使ったPICOのPWM機能をDMAで動作させる方法はいまいちわからなかった。(というか、PIOのTX FIFOにデータを送った場合、PIOはGPIOピンに0/1信号しか出せず、PWMレジスターに値を書き込めない。そもそもDMAには自己完結型のDREQインターバル設定ができるので、DMAでPWMレジスターに値を書き込むだけなら、PIOは特に必要ないかも。。。)

ここではPIOに32ビットデータを1ビットずつ設定した周期でGPIO出力させることでPWM信号を作る方法について備忘録する。

  • DMAはDREQによってDMA転送を1データ単位(32ビット)ごとに実行する。
  • DMAは自らのDMAインターバルによってDREQを自動発生することもできる。
    • channel_config_set_dreq(&pwm_dma_chan_config, DREQ_PWM_WRAP0 + led_pwm_slice_num)
  • DMA ch構成では、転送ソースアドレス、転送ターゲットアドレス、転送カウントを設定する。
    • dma_channel_configure(
        dma_chan,     // DMAチャネル
        &dma_chan_config,    // DMAチャネル構成情報
        &pio0_hw->txf[state_machine_id],  // PIOのTX FIFO
        // &pwm_hw->slice[led_pwm_slice_num].cc, // PWMレジスタの場合
        data_table,      // データ元
          256,                              // 転送カウント
        false                             // スタートタイミング(即時スタートの場合はtrue)
      );
  • DMAは設定した転送カウント分の転送が完了するとIRQを発生する。
  • このIRQを使って次回DMA転送するデータを設定する。

 

  • PIOはTX FIFOの1データ単位(32ビット)を取り出す。
  • 32ビットを1ビット取り出すにはshiftを使う
    • channel_config_set_transfer_data_size(&dma_chan_config, DMA_SIZE_32);
      新しくTX FIFOからデータを取り出すまで1ビットずつシフトし1ビットずつとりだす動作を32ビット行う
  • PIOのTX FIFOが空になるとDREQを発生させる。
    • channel_config_set_dreq(&pwm_dma_chan_config, DREQ_PIO0_TX0);
    • 上記と以下は等価
      channel_config_set_dreq(&pwm_dma_chan_config, pio_get_dreq(pio, state_machine_id, true));
  • 対象とするDMA chにこのDREQを紐付けることでTX FIFOが空になるごとにDMAを行うようにする

======= Program 1 DMAだけ、IRQなし =======

Main C Program

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "hardware/pio.h"
#include "hardware/clocks.h"
#include <hello.pio.h>

int main()
{
   PIO pio = pio0;
   uint state_machine_id = 0;
   uint offset = pio_add_program(pio, &pio_dac_program);
   pio_dac_program_init(pio, state_machine_id, offset, PICO_DEFAULT_LED_PIN, 2000.f);

   int pwm_dma_chan = dma_claim_unused_channel(true);

   dma_channel_config dma_chan_config = dma_channel_get_default_config(pwm_dma_chan);

   channel_config_set_transfer_data_size(&dma_chan_config, DMA_SIZE_32);
   channel_config_set_read_increment(&dma_chan_config, true);
   channel_config_set_write_increment(&dma_chan_config, false);
   channel_config_set_dreq(&dma_chan_config, pio_get_dreq(pio, state_machine_id, true)); // pio sm, TX

   dma_channel_configure(
      dma_chan,
      &dma_chan_config,
      &pio0_hw->txf[state_machine_id],
      data_table, 
      256, 
      false 
   );

   dma_channel_set_read_addr(dma_chan, mark_tab, true);   // DMAをスタートする

   while(true) {
      tight_loop_contents(); 
   }
}

PIO Program

.program pio_dac

.wrap_target
   out pins, 1
.wrap

% c-sdk {

static inline void pio_dac_program_init(PIO pio, uint sm, uint offset, uint pin, float fs) {
   pio_sm_config c = pio_dac_program_get_default_config(offset);

   sm_config_set_out_shift(&c, true, true, 32);
   sm_config_set_out_pins(&c, pin, 1);
   // sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); これはなくても動作は同じ
   sm_config_set_clkdiv(&c, (float)clock_get_hz(clk_sys) / fs);

   pio_gpio_init(pio, pin);

   pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
   pio_sm_init(pio, sm, offset, &c);
   pio_sm_set_enabled(pio, sm, true);
}
%}

======= Program 2  DMAとIRQの両方 =======

Main C Program

int dma_chan;
static bool mark_signal=true;
// DMA転送完了(指定データ数の転送完了)のIREQの度に送出するシグナルを交互に変える。
void dma_handler() {
//  指定されたデータテーブルのDMA転送を即時実行する。なのでDREQを待つことなしにDMAが開始される。
   if(mark_signal){
      dma_channel_set_read_addr(dma_chan, mark_tab, true);  // mark_tabの転送を即時実行
      mark_signal = false;
   }else{
      dma_channel_set_read_addr(dma_chan, space_tab, true);  // space_tabの転送を即時実行 
      mark_signal = true;
   }
   // IRQをクリア
   dma_hw->ints0 = 1u << dma_chan;
}

int main()
{
   PIO pio = pio0;
   uint state_machine_id = 0;
   uint offset = pio_add_program(pio, &pio_dac_program);
   pio_dac_program_init(pio, state_machine_id, offset, PICO_DEFAULT_LED_PIN, 2000.f);
   dma_chan = dma_claim_unused_channel(true);

   dma_channel_config dma_chan_config = dma_channel_get_default_config(dma_chan);

   channel_config_set_transfer_data_size(&dma_chan_config, DMA_SIZE_32);
   channel_config_set_read_increment(&dma_chan_config, true);
   channel_config_set_write_increment(&dma_chan_config, false);

   channel_config_set_dreq(&dma_chan_config, pio_get_dreq(pio, state_machine_id, true)); // pio sm, TX

   // Setup the channel and set it going
   dma_channel_configure(
      dma_chan,
      &dma_chan_config,
      &pio0_hw->txf[state_machine_id],
      mark_tab,    // 初期データテーブル
      66,                       // 転送データ(ワード)数
      false                     // トリガーされるまで待機
   );

   dma_channel_set_irq0_enabled(dma_chan, true);
   irq_set_exclusive_handler(DMA_IRQ_0, dma_handler);  // IRQハンドラーとしてdma_handlerを設定
   irq_set_enabled(DMA_IRQ_0, true);

   dma_handler(); // DMAを即時実行。DMA開始後はPIOが発生するDREQのペースでDMA転送される
   // 上記実行以降はDREQとIREQによってPIOの実行ペースでデータ転送が実行される。
   //  そのおかげでメインプログラムはデータ転送を気にすることなく他のタスクが実行可能となる
   while(true) {
      tight_loop_contents(); 
   }
}
SDKドキュメント。dma_channel_set_read_addr()の第三パラメータは即時実行指定。

◆ dma_channel_set_read_addr()




static void dma_channel_set_read_addr ( uint  channel,
    const volatile void *  read_addr,
    bool  trigger 
  )    
inlinestatic

Set the DMA initial read address.

Parameters
channel DMA channel
read_addr Initial read address of transfer.
trigger True to start the transfer immediately
ビルド環境
親DIR
   + led_fade (ソースコードDIR)
   + build (ビルドDIR)
親DIRのCMakeLists.txt

cmake_minimum_required(VERSION 3.16)    //使っている環境が3.16だからとりあえず3.16に設定

include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
project(led_fade C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_subdirectory(led_fade)

ソースコードDIRのCMakeLists.txt

add_executable(led_fade
   led_fade.c
)

pico_generate_pio_header(led_fade ${CMAKE_CURRENT_LIST_DIR}/hello.pio)

target_link_libraries(led_fade
   pico_stdlib
   hardware_pio
   hardware_dma
)

pico_add_extra_outputs(led_fade)

 

======= Program 3 文字列を波形出力する =======

送出する文字列に沿って波形を出してみたくなったので、その実験コードを書いてみた。
uint8_t xfer_data[] = "HIJKLMNO"; // 波形送出する文字列
int xfer_length;
const uint32_t *dma_signal[80];  // up to 10 data
int dma_signal_count, dma_count;

void dma_handler()
{
   if (dma_count >= dma_signal_count) // no more data to DMA
      dma_count = 0;              // repeater forever
   // DMAする順にWaveform Tableのポインターを取り出しDMAをキックする。
   dma_channel_set_read_addr(dma_chan, dma_signal[dma_count], true);

   // Clear the interrupt request.
   dma_hw->ints0 = 1u << dma_chan;

   dma_count++; // prepare for next DMA transfer
}
送り出すビットが1か0によって、DMA単位ごとのwaveform(1200Hz/2200Hz)を設定する
void data_set()
{  
   int i, j, k = 0;
   uint8_t dma_data;

   xfer_length = strlen(xfer_data);
   for (i = 0; i < xfer_length; i++){
      dma_data = xfer_data[i];   // 送出するCharactorを取り出す
      for (j = 0; j < 8; j++)    {      // LSBから順にWaveformを割り当てる
         if ((dma_data >> j) & 1)  //  BitごとにWaveform Tableポインターを設定する
            dma_signal[k] = mark_tab;  
         else
            dma_signal[k] = space_tab;
         k++;
      }
   }
   dma_signal_count = k;
   dma_count = 0;   
}
main()の中で、dma_handler()を呼ぶ前(DMAをキックする前)にdata_set()をコールする。
   data_set(); // set wave table to DMA
   dma_handler(); // trigger DMA

 

2022年12月28日 (水)

PICOのPWMをDMAで動作させる方法

Raspberry Pi PICOのPWMをDMAで動作させる方法について備忘録。
以下、動作検証を行ったサンプルプログラム。
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "hardware/pwm.h"
#include "hardware/clocks.h"
int main()
{
// オンボードLEDがつながっているPINをPWMに設定する
   gpio_set_function(PICO_DEFAULT_LED_PIN, GPIO_FUNC_PWM);

//  このPINのSlice番号を得る
   int led_pwm_slice_num = pwm_gpio_to_slice_num(PICO_DEFAULT_LED_PIN);
//  PWM構成情報を作成する
   pwm_config config = pwm_get_default_config();
//  PWM構成情報にデータ要求周期のクロック分割を設定する。
//  ここではLED点灯変化が目視できるようにクロック数を1/800に設定している。
//  なお、800.fのfはfloatを表している。
   pwm_config_set_clkdiv(&config, 800.f);
//  このPWM構成情報でPWMを初期化する
   pwm_init(led_pwm_slice_num, &config, true);

//   PWM用に、空いているDMAチャンネル番号を取得する
   int pwm_dma_chan = dma_claim_unused_channel(true);
//  取得したDMAチャンネルの構成情報を取得する
   dma_channel_config pwm_dma_chan_config = dma_channel_get_default_config(pwm_dma_chan);
//  DMA転送単位を32ビットに設定する
   channel_config_set_transfer_data_size(&pwm_dma_chan_config, DMA_SIZE_32);
//  DMA読み出し元(データテーブル)アドレスを自動インクリメント設定する
   channel_config_set_read_increment(&pwm_dma_chan_config, true);

//  DMA書き込み先(PWMレジスタ)アドレスは固定にする
   channel_config_set_write_increment(&pwm_dma_chan_config, false);
//  PWM(取得したスライス番号)のデータ要求によってDMA REQが発生するよう指定する
   channel_config_set_dreq(&pwm_dma_chan_config, DREQ_PWM_WRAP0 + led_pwm_slice_num);

//  DMAチャンネル構成情報を設定する
   dma_channel_configure(
      pwm_dma_chan,
      &pwm_dma_chan_config,
      &pwm_hw->slice[led_pwm_slice_num].cc, // 書き込み先PWM counter compare
      data_table,               // 読み出し元
      256,                                                              // データテーブルからの読み出しデータ数、これを読みだしたらDMA停止
      true                   // DMA即時開始
   );

   while(true) {
      tight_loop_contents();
   }
}
上記のコードはPWMの実行周期を設定し、その周期でDMA REQを発生させることで、data_tableからPWMレジスタにデータをDMA転送させている。
なお、data_tableはuint32_t data_table[256]で、0x00000000から0xFFFFFFFFまでの値でLED輝度変化が目視できるテーブルにする。

検証にあたってはこのブログを参考にした。

2022年12月27日 (火)

PICOのPIOについて

Raspberry Pi PicoのPIOについて調べてみたので備忘録。

Raspberry Pi Picoはメインプログラムとは別にアセンブラレベルコードを実行することができる。これはメインプログラムとは別に(並行して)動作するプログラムのようだ。基本アセンブラレベルのコードを書くことになる。これはかなり強力な機能で、メインプログラムでタイミングを意識することなく、決まったサイクルでオペレーションを実行できることになる。

その1からその2まで記録。。。

その1ーーー

開発手順

ここではオンボードLEDを点滅するPIOを書いてみる。

  1. hello.pioを書く。この中にアセンブラレベルのコードを書く。このhello.pioはCヘッダーファイルに変換され、Cプログラムから参照される。
  2. Cプログラムを書く。
  3. buildする。

hello.pioディレクトリに以下を配置
   + CMakeLists.txt
   + hello-pio 
          + hello.pio
          + hello-pio.c
          + CMakeLists.txt
   + build

buildディレクトリにて以下を実行してbuildする。
$ cmake ..
$ make

以下、実際に書いたコード。

参考になった情報1情報2。ただし、情報1の通りコードを書いてもLEDはブリンクしない(サイクルが短すぎて点灯しっぱなしに見える)。

hello.pio

.program hello
loop:
;.wrap_target 
   set pins, 1 [19] ; Turn LED on and wait another 19 cycles
   nop [19] ; Wait 20 cycles
   nop [19] ; Wait 20 cycles
   nop [19] ; Wait 20 cycles
   nop [19] ; Wait 20 cycles
   set pins, 0 [19] ; Turn LED off and wait another 19 cycles
   nop [19] ; Wait 20 cycles
   nop [19] ; Wait 20 cycles
   nop [19] ; Wait 20 cycles
   nop [19] ; Wait 20 cycles
;.wrap
jmp loop

//  上記loopのwrapも同じ結果になる

% c-sdk {
static inline void hello_program_init(PIO pio, uint sm, uint offset, uint pin) {
   //  Configオブジェクトを定義する 
   pio_sm_config config = hello_program_get_default_config(offset);
   //    Configオブジェクトにgpioピンpinを割り当てる
   pio_gpio_init(pio, pin);
   sm_config_set_set_pins(&config, pin, 1);  //  pinから1ピンを連続割当、ここでは1ピンのみの割当。
   //  pinから1ピンを出力に設定。ここでは1ピンのみを設定。
   pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);

   // クロック分割数を決める。divの分だけsysclkがマスクされる。これをやらないとLED ON/OFFが早すぎてLED点灯しっぱなしに見える。
   static const float pio_freq = 2000;
   float div = (float)clock_get_hz(clk_sys) / pio_freq;
   sm_config_set_clkdiv(&config, div);

   // 初期化とイネーブ
   pio_sm_init(pio, sm, offset, &config);
   pio_sm_set_enabled(pio, sm, true);
}
%}

hello-pio.c

#include <stdio.h>
#include <stdbool.h>
#include <pico/stdlib.h>
#include <hardware/pio.h>
#include "hardware/clocks.h"  //  hello.pioでclk_sysを参照しているので必要
#include <hello.pio.h>     // hello.pioから生成されるヘッダーファイルをincludeする

#define LED_BUILTIN 25     // オンボードLEDのGPIO

int main() {

   stdio_init_all();

   PIO pio = pio0;
   uint state_machine_id = 0;
   // hello.pioで定義したhelloはhello_programと自動的に命名される。
   uint offset = pio_add_program(pio, &hello_program);
   hello_program_init(pio, state_machine_id, offset, LED_BUILTIN);
   while(1) {
      // Cプログラムで何かをやっていたとしても、これとは並行してpioが動作している
   }
}

CMakeLists.txt(hello.pioディレクトリ)

cmake_minimum_required(VERSION 3.12)

include(pico_sdk_import.cmake)

#project(pico_examples C CXX ASM)
project(hello-pio C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

pico_sdk_init()

add_subdirectory(hello-pio)

CMakeLists.txt(hello-pioディレクトリ) 

add_executable(hello-pio
   hello-pio.c
)
#  CMAKE_CURRENT_LIST_DIRはCMAKEによって自動設定される

pico_generate_pio_header(hello-pio ${CMAKE_CURRENT_LIST_DIR}/hello.pio)

target_link_libraries(hello-pio
   pico_stdlib
   hardware_pio
)
# hexファイルやuf2ファイルを生成する
pico_add_extra_outputs(hello-pio)

hello.pio.h

CMakeLists.txtのpico_generate_pio_header()によって以下のヘッダーファイルが生成される。

// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //

#pragma once

#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif

// ----- //
// hello //
// ----- //

#define hello_wrap_target 0
#define hello_wrap 10

static const uint16_t hello_program_instructions[] = {
// .wrap_target
   0xf301, // 0: set pins, 1 [19]
   0xb342, // 1: nop [19]
   0xb342, // 2: nop [19]
   0xb342, // 3: nop [19]
   0xb342, // 4: nop [19]
   0xf300, // 5: set pins, 0 [19]
   0xb342, // 6: nop [19]
   0xb342, // 7: nop [19]
   0xb342, // 8: nop [19]
   0xb342, // 9: nop [19]
   0x0000, // 10: jmp 0
// .wrap
};

#if !PICO_NO_HARDWARE
static const struct pio_program hello_program = {
   .instructions = hello_program_instructions,
   .length = 11,
   .origin = -1,
};

static inline pio_sm_config hello_program_get_default_config(uint offset) {
   pio_sm_config c = pio_get_default_sm_config();
   sm_config_set_wrap(&c, offset + hello_wrap_target, offset + hello_wrap);
   return c;
}

static inline void hello_program_init(PIO pio, uint sm, uint offset, uint pin) {

   pio_sm_config config = hello_program_get_default_config(offset);

   pio_gpio_init(pio, pin);
   sm_config_set_set_pins(&config, pin, 1);
   pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);

   static const float pio_freq = 2000;
   float div = (float)clock_get_hz(clk_sys) / pio_freq;
   sm_config_set_clkdiv(&config, div);

   pio_sm_init(pio, sm, offset, &config);
   pio_sm_set_enabled(pio, sm, true);
}

#endif

その2ーーー

sm_config_set_out_shiftについて

.program hello

.wrap_target
   out pins, 1   ;  結果的に1ワード分のデータを右シフトで1ビットずつ出力される。
.wrap

% c-sdk {

static inline void hello1_program_init(PIO pio, uint sm, uint offset, uint pin, float fs) {
   pio_sm_config c = pio_dac_program_get_default_config(offset);

//  TX FIFOからデータをpullするまでのビットシフト実行指定、右向きに32ビットまでシフトする。
//  つまり32ビット全部をpinに順次送り出し、全て送り出したらTX FIFOから自動で新しいデータをpullする。
   sm_config_set_out_shift(&c, true, true, 32);

//  pinから1ピンをoutに設定する
   sm_config_set_out_pins(&c, pin, 1);

//  クロックマスク周期を設定する(動作速度を調整する)
   sm_config_set_clkdiv(&c, (float)clock_get_hz(clk_sys) / fs);

SDKドキュメントは以下のとおり。

◆ sm_config_set_out_shift()

static void sm_config_set_out_shift ( pio_sm_config  c,
    bool  shift_right,
    bool  autopull,
    uint  pull_threshold 
  )    
inlinestatic

Setup 'out' shifting parameters in a state machine configuration.

Parameters
c Pointer to the configuration structure to modify
shift_right true to shift OSR to right, false to shift OSR to left
autopull whether autopull is enabled
pull_threshold threshold in bits to shift out before auto/conditional re-pulling of the OSR

2022年12月19日 (月)

Google Driveへのパソコンフォルダーの同期方法

Google DriveにPC上の同期対象フォルダーの指定にちと手間取ったので備忘録。

スタートメニューにあるGoogle Driveを右クリックして、その他 -> 管理者として実行 を選択する。

Googledrive

するとGoogle Drive設定ダイヤログが表示されるので、そこでPC上の同期対象フォルダーを選べばよい。

 

2022年12月17日 (土)

PICO TNCno製作 その3

KENWOODハンディトランシーバー対応。トランシーバーはTH-K20、ちょっと(かなり)古い。

KENWOOD対応のヘッドセットのケーブル・ジャックを活用しようとして驚いた。ケーブルを切ったのはこのヘッドセット。
Img03220_small

まぁ、幾分細いワイヤーがはいっているんだろうと思ったら、、、なんと超極細エナメル線を編んだワイヤーが4本入っていた。これはどうしたものやら。とりあえずカッターナイフで極細エナメル線の被覆を削り落としてクリップを付けて配線を確認した。
Img03218_small

3芯ケーブルに半田付けし、熱収縮チューブでカバーした。
Img03222_small

KENWOOD ハンディのスピーカーマイク配線は以下のとおり。
Kenwoodpinlayout

つまりMICラインとPTTラインは分けないといけない。ここがYaesuと違う所。そこで、ビーコン発生機のジャック端子の真ん中(Ring)にPTT信号を割り当てる改造を実施。

35mmjack

この結果、ハンディのスピーカーマイク端子とビーコン発生機に差し込むジャックとの配線は以下のようになった。マイク信号線はマイク端子(Tip)へ、MIC/PTT共通信号線はPTT端子(Ring)へ、PTT/GND信号線はGND端子(Sleeve)へ接続した。
Kenwood

これでとりあえず動くようになった。

Img03228_small
しかし、トランシーバーから電波を出すとPicoが誤動作し、PTTが切れない。つまり電波が出っぱなしになる。この状態で今回作ったケーブルを手で覆い隠すと誤動作(PTTオン)が止まる。つまり、トランシーバーからの電波に対してビーコン発生機をシールドする必要がある。

Picoが誤動作している可能性があると思い、ケーブルのビーコン発生機ジャック根元部分にコアを入れてみたが効果なし。
Img03229_small

今度はトランシーバー側にコアを入れてみた。こちらは効果があり、安定的に動作するようになった。ちなみにコアはFT-114-43、バラン用に購入してあったもの。
Img03230_small

以上でKENWOOD Handy対応も目処がたった。

追加のノイズ対策

今回の誤動作はPTTがオンになりっ放しになるというもの。スピーカーマイクケーブルのトランシーバー側にコアを入れて改善したことから、このケーブルを介してノイズがビーコン発生機に入ってきていることが想像できる。そして、ビーコン発生機のPTT信号出力トランジスターを誤動作させているということだろうか。であれば、トランジスター自体にノイズ対策を施すことは効果がありそうだ。

それで以下の対策を施した。
Emitterreg
Q1のベース・エミッター間にR3 10KΩ抵抗を入れた。この抵抗がない状態ではベースに入ってきたノイズ電流によってQ1が誤動作する場合が有り得るが、そのノイズ電流をGNDに流す役割をするのがR3になる。仮に0.01mAのノイズ電流がベースに入ってきたしても、10KΩに流れたとして電圧は0.1Vとなりベース・エミッター間飽和電圧(大体0.8V)よりも十分小さいのでQ1を動作させることはないがノイズ電流はGNDに流すことができる。

試しにコアを取り外してみたが、やっぱりコアが無いとPTTがオンになったままになる。ということで、上記R3の追加は決定的な対策にはならないことが判明。

2022年12月12日 (月)

PICO TNCの製作 その2

PICO TNC製作の続編その2。

プロトタイプが完成した。
Img03140_small

事前設定したインターバルで、ハンディ―トランシーバーのマイク端子経由にてMic-EフォーマットでGPSデータを送信することができるようになった。

PICO TNC コマンドの使い方

  • mycall JA0WBT または JA0WBT-7  :  SSID有無どちらでもよい。mycallを設定しないとBeaconは出ない。
  • unproto JA0WBT V WIDE1-1   :  Vの前はDestination Addressだが、ソフトウエア内部でMic-E形式のDestinationに上書きするようにした。なのでどんな文字列でも構わない。Vの後はデジパス1でNewパラダイムに従ってWIDE1-1を指定する。
  • digi ON  : デジパスを指定しているのでONを指定する。
  • gps $GNGGA   : プロトタイプに接続しているGPSモジュールのGGAフォーマットは$GNGGAと頭に付けてくるのでそれを指定する。これ以外のフォーマットタイプは今はサポートしていない。
  • btext HelloWorld  : BeaconのInformationに付加する自由テキスト。
  • beacon every n  : Beacon発生インターバル。n=1で1/6秒。最大360=60分。
  • PERM  : パラメータを設定したら実行。これにより設定したパラメータ値がFlashメモリーに保存され、パワーサイクルでも消えない。

以下がdispコマンド実行結果。MYALIASは設定しなくてもよい。

disp

ECHO ON
TXDELAY 100
GPS $GNGGA
TRace OFF
MONitor ALL
DIGIpeater ON
BEACON On EVERY 6
UNPROTO SUTPW8 V WIDE1-1
MYCALL JA0WBT-7
MYALIAS JA0WBT
BTEXT HelloWorld

OK
cmd:

送信されるパケットデータ

PICOの出力(トランシーバーのマイク入力)を入力(トランシーバーのスピーカ出力)にループさせてPICO TNCにて自分の出力を自分でデコードさせた。さらに実際にトランシーバー(FT-70D)のマイク入力に接続し、電波送信してI-Gateにて受信した。その受信した結果が以下:

17:43:53R JA0WBT-7>SUTPW8,WIDE1-1 Port=1 <UI C Len=24>:
`AB'l l[/`"9L}HelloWorld
17:43:53R JA0WBT-7>SUTPW8,WIDE1-1 Port=2 <UI C Len=24>:
`AB'l l[/`"9L}HelloWorld
17:43:55R JA0WBT-7>SUTPW8,WIDE1-1* Port=1 <UI C Len=24>:
`AB'l l[/`"9L}HelloWorld
17:43:55R JA0WBT-7>SUTPW8,WIDE1-1* Port=2 <UI C Len=24>:
`AB'l l[/`"9L}HelloWorld

I-Gateは2ポート設定しているので(1ポートにする方法がわからん)、同じ電波信号をPort1とPort2で2回取り込んでいる。最初のペアがPICO TNCが生成したフレームで、次のペアがPICO TNC内でループバックして再送信した信号になる。ループバックした信号はデジピートしたことになるのでWIDE1-1の後ろに*が付加されている。

Digipeaterとしての機能

Digipeaterとしの機能を確認する。FT3Dからビーコンを送信し、それをFT-70Dで受信。FT-70DのSpeakerをPICO TNCの入力に接続し、MicをPICO TNCの出力に接続した。

Img03160_hdr_small

Beaconrepeat

結果は、FT3Dからのビーコン(上図①)はFT-70Dでビーコン再送(上図④)された。そのビーコンはFT3Dで受信することができた(以下写真)。一方、UI-VIEW32はFT3Dからのビーコン(上図①)は受信表示したが、FT-70Dからのビーコン再送(上図④)は受信表示されなかった。
Img03161_small

考察として、UI-VIEW32はオリジナル信号を受信した後に、そのデジピート信号を受信した場合、それは受信表示しないのではないかと思われる。もしそれを許したら、同じ信号をデジピートの分だけ何度もI-Gateしてしまうから。一方、FT3Dは自分が発信した信号のデジピートであっても自分以外の送信機からの信号なのでそのまま表示したのだと思う。

UI-VIEW32のTerminalで確認したところ、上記の通りと判断できる。以下がTerminalの表示内容。3つ目のWIDE1-1に*が付いているのでデジピートされた信号と判断される。デジピートしたのはPico TNCになるので、Pico TNCはちゃんとデジピートしているのだけれど、それをUI-VIEW32がInternetには送り出さなかったということだ。

19:34:21R JA0WBT-7>SUTPW7,WIDE1-1 Port=1 <UI R Len=17>:
`AB(l!m[/`"9l}_0
19:34:21R JA0WBT-7>SUTPW7,WIDE1-1 Port=2 <UI R Len=17>:
`AB(l!m[/`"9l}_0
19:34:22R JA0WBT-7>SUTPW7,WIDE1-1* Port=2 <UI R Len=17>:
`AB(l!m[/`"9l}_0

 

乾電池駆動について

この実験に際して、PICO TNCを単四乾電池2本で駆動した。5分インターバルで一晩ビーコンを出し、朝停波。その状態で実験を続けていたところ午後3時ころには電池がなくなった(Watchdog Timer Failure発生)。結構電力を消費している。

基板への実装とケースへの取り付け

ブレッドボードに作った回路を

Img03170_small

この回路をユニバーサル基板に配置する。

Img03163_hdr_small

実装が終わった状態。RS232CでPCでモニターする。
Img03175_small

乾電池(単三2本)での動作確認。
Img03171_small

ダイソーの3個100円のケースにいれる。RS232Cのボードもユニバーサル基板に実装した。
Img03228_small

完成した状態。
Img03187_small

この状態で持ち運んで軌跡確認を行った。
Img03210_small

実験結果はまずまず良好で、基本機能が動作することを確認できた。

DIAMOND GS-3000の修理の件

ヤフオク!で落としたDiamond GS-3000 13.8V MAX30A電源が届いた。

動作させたところ電圧は13.78V、しかし電流は4A程度で電流制限が働いで電源OFFになる。

Img03149_small

良くある故障モードとして可変抵抗器の劣化が考えられる。

GS-3000の故障に関する情報から、VR1が電流制限調整VRであることが判明した。

Img03152_small

VR1を拡大する。この写真では8時方向をむいているが、入手時点では12時方向を向いていた。
Img03152_smallzoom

とりあえず、VR1を何度か回してまずは、3時方向にセットし負荷を繋いでみた。4Aよりも小さな負荷で電流制限が発生。そこで8時方向まで回してみたところ、8Aの負荷でも電流制限は作動しなかった。30A負荷を与えることができればVR1の位置は追い込めるが、そのような負荷がないので、とりあえずこれで様子を見てみる。

考察:

この電源、カバーを外してみると結構きれいな感じだったが、基板の隅の方を見るとシルク印刷が隠れるほど埃がこびりついている。つまり、この電源の出品に当たって掃除をしたということだろうと思う。ひょっとしたらその際にVR1の位置が変わってしまったのかもしれない。いずれにせよVR1の調整だけで問題は解決したように見える。

考察のやり直し:

電流が出ないというか過電流保護が働いて出力電流制限されている理由が分かった。4つあるパワートランジスタの内3つのエミッターに繋がるワイヤーが断線していた。半田にクラックが入って取れたように見えた。VR1を変化させて制限電流値が増えたのは残っていた1つのパワートランジスターの最大電流(0.1Ω抵抗に流れる電流量)を増やしたため、ということのようだ。

断線していたワイヤー部分(黄丸)。
Img03192_small

エミッター端子の半田面が割れて剥がれたような半田断面にみえる。
Img031911
こちらも同様。
Img031912

本来は4個のパワートランジスターで出力しているところを1個のパワートランジスターで制御したため、過電流制御が働いてしまったようだ。エミッターへケーブルを半田付けしなおした(黄丸)。
Img03200_small

この結果、電流制御用のVRを元の位置に戻しても、問題なく動作するようになった。また、電流計は剥がれがケーブルの先に繋がっていたので当初は電流表示が出来なかったがケーブルを半田付けしなおすことで、電流計も動作するようになった。

考察

もうちょっとちゃんと構造を調べて見たくて、今一度理解を深めるためにJH7LUC局のブログなど先人の知恵を参考に自分で回路図を書いてみた。

Gs3000circuit

ダウンロード - gs3000.ce3

構成はダーリントン接続のトランジスターが3段になっていて、ツェナーダイオードで定電圧としている。リセットIC TA8505Pの入力にサーモスタットにかかる電圧を接続し、ある程度の抵抗値(温度)になったらリセットICがリセット信号を出力し、その信号によってファンモーターnの駆動制御を行っている。TA8505PはVCC=5Vを必要とするので三端子レギュレータTA78006PでVCCを作っている。過電圧(ツェナー電圧)となるとツェナー電流が流れリレーが働いてパワートランジスタへの入力をカットする。

定電圧の原理は以下の通りと理解した。Q2のエミッター電圧Veq2はZDのツェナー電圧Vzdで決まる。Q2のベース電圧は、ベース・エミッター間電圧をVbeq2とするとVzd+Vbeq2となる。この電圧はVout(R2/(R1+R2))であたえられるので、Vout=(Vzd+Vbeq2)((R1+R2)/R1)となるようにQ1が帰還制御される。

Photo_20221217143201

よくわからなかったのが過電流制御部分。ここは以下の通り解釈した。

Photo_20221217144001

VEQ5=VEQ3-R2xIOUT  : IOUTが大きくなるとR2による電圧降下が大きくなるのでVEQ5(Q5のエミッター電圧)は小さくなる。
VBQ5はVRの値によって変化する。
VBQ5-VEQ5>VBE となればQ5はONになる。つまりIOUTが大きくなるとQ5はONになる。その閾値はVRで変化する。

Q1のベース電圧からみると、Q3のエミッター電圧はQ1からQ3のベース・エミッター間電圧の合計分低いことになる。Q5がオンになるとその電圧差がなくなるので、Q1からQ3へのベース・エミッター間に電流が流れなくなる。

解釈が正しいかイマイチ不安な部分があるけれども、出力電流による0.1Ω抵抗の電圧降下とVR1の値のバランスでQ5がONされることには違いない。

2022年12月11日 (日)

トランシーバのマイクとPTTが一本の線にまとめられている件

FT-70DとかFT3Dとかのハンディトランシーバーではマイク端子とPTT端子が一つにまとめられている。

MICとPTTが一本の線にまとめられているってどういう事なんだろうってことで調べてみた。

Yaesu スピーカーマイクSSM-17Aのピンジャックの信号配列は以下のようになっているようだ。他の資料などを見るとSPとMIC/PTTの間の電極はDATA/Cloneとなっているが、このスピーカーマイクでは未使用だと思う。
Ssm17apin

MIC/PTTとGNDの間の抵抗値をテスターで測ってみた。PTTを押さない場合は絶縁状態、PTTを押すと633Ωとなった。
Img03143_burst01_small

いろいろな資料を見てみて、この仕組みを回路にまとめると以下のようだ。テスターで測定した633Ωは回路図のR01にあたる。つまり、PTTを押すとMICラインが633Ωで接地されるわけだ。トランシーバー側はこのMICラインがPNPトランジスタのベースに繋がっているようで、PTTが押されるとトランジスタがオンになる。これがトランシーバー内でSEND信号になるようだ。一方MICはコンデンサーCを介してAC成分だけがMIC信号として取り出される。ここでR01の抵抗値はトランシーバー内のR02との関係で適切な値に設定し、ベース電流が流れるようにしないといけない。実験ではR01が2KΩではPTTが効いたが、10KΩではPTTが効かなかった。
Micptt0

とりあえずR01はスピーカーマイクでの測定値に近い680Ω位に設定すれば安心なんだろうと思う。

スピーカーマイクの代わりにTNCを取り付けた場合、PTT信号を使って以下の回路を構成すればよい。この回路ではPTTオンがHIGHを前提としている。
Micptt2

実験の結果680Ωでこの回路でも動作を確認した。

2022年12月 5日 (月)

GPSトラックログデータをGoogle Mapに表示する

GPSトラックログファイル(GPXファイル)をGoogle Map上に表示する方法の備忘録。

GPSトラックログファイルはヤマレコの山行記録からダウンロードした。対象とする山行記録を選んでマップ昨日からGPXファイルをダウンロードを選ぶ。
Photo_20221205121401

ダウンロードされるGPXファイルはこんな感じになっている。

<?xml version="1.0" encoding="UTF-8"?>
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/0" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" creator="Yamareco Android 6.2.2 - www.yamareco.com">
<trk><name>track</name><number>1</number><trkseg>
<trkpt lat="35.5110116" lon="137.6645703"><ele>1040</ele><time>2022-11-17T23:46:07Z</time></trkpt>
<trkpt lat="35.5109365" lon="137.6644874"><ele>1041</ele><time>2022-11-17T23:46:17Z</time></trkpt>
<trkpt lat="35.5107415" lon="137.6643537"><ele>1045</ele><time>2022-11-17T23:46:30Z</time></trkpt>
<trkpt lat="35.5106972" lon="137.6642274"><ele>1048</ele><time>2022-11-17T23:47:24Z</time></trkpt>
<trkpt lat="35.5092595" lon="137.6552615"><ele>1284</ele><time>2022-11-18T00:28:01Z</time></trkpt>
<trkpt lat="35.509086" lon="137.6537141"><ele>1300</ele><time>2022-11-18T00:31:16Z</time></trkpt>

LatitudeとLongitudeがUTC付きで記録されている。

次にGoogle Mapを開き、自分のGoogleアカウントでログインする。その後メニューをだどって地図を作成を実行。
メニュー -> マイプレイス -> マイマップ -> 地図を作成

無題のレイヤのインポートを選択。
Gm1_20221205121401

ここにGPXファイルをドラッグ&ドロップする。
Gm2_20221205121401

すると、GPXファイルのエリアで軌跡が表示される。
Gm3

以上。

Yaesu FT3D APRSビーコン パケットデータの解析

FT3Dが送信しているAPRSパケットデータの解析をしたので、その記録。

以下がUI_VIEW32のTerminal出力に表示されたFT3DのAPRSビーコンデータ:

JA0WBT-7>SUTPW9,WIDE1-1 Port=1 <UI R Len=17>:`AB(l T[/`"9a}_0

FT3DはMic-E形式でAPRSパケットを送出している。FT3DのMic-EはDestination AddressとInformationにGPS座標情報(Latitude/Logitude)、高度情報(Altitude)、無線機形式(Radio Type code)で構成されている。

まずはDestination Address(以下の赤字部分)の分析から。ちなみにFT3DのGPS座標は北緯35度40.79分、東経137度38.12分。

JA0WBT-7>SUTPW9

Byte Latitude Msg A/B/C N/S Longitude offset E/W
S 3 A=1      
U 5 B=1      
T 4 C=1      
P 0   N    
W 7     +100  
9 9       E

SUTPW9は1バイト目からAPRSスペックの44ページに掲載される以下の表でデコードできる。1バイト目からこの表を見ながらその意味を解釈するとデータ並びのASCII Char順に以下となる。

  • S: Lat Digit Byte 1=3, Message A=1
  • U: Lat Digit Byte 2=5, Message B=1
  • T: Lat Digit Byte 3=4, Message C=1
  • P: Lat Digit Byte 4=0, N/S = North
  • W: Lat Digit Byte 5=7, Long Offset = +100
  • 9: Lat Digit Byte 6=9, W/E = East

Mice_destination_20221205081901

ちなみにMessage A/B/C = 111はスペックの45ページの表(以下)Off Duty(無線機の前に運用者は不在)という意味になる。

Msgtype
FT5DのマニュアルにあるMessageの解釈は以下の通り。

Micemessage

次にInformationに記載されたデータの解釈を行う。

`AB(l T[/`"9a}_0

結論を先に書くと、これは以下の3つのパート(青、赤、緑)で構成されている。

`AB(l T[/`"9a}_0

`AB(l T[/ : Logitude + Speed/Course + Symbol Code

Information Fieldのフォーマットはスペックの46ページに掲載されている。
Informationfield

この表に従ってデータを解釈する。

  • `  : Current GPS Data
  • A  : Ascii code=65, d+28 = 65 -> 137 (以下のMic-E Longitude Degree Encodingのd+28=65から値を引くが、先に解釈したDestination AddressのByte5でLong Offset=+100となっているので表の+100の欄の値を引く)。
  • B  : Ascii code=66, m+28 = 66 -> 38 (以下のMic-E Longitude Minutes Encodineのm+28=66から値を引く)
  • (  : Ascii code=40, h+28 =40  単純に40から28を引いた値がh。h=40-28=12
    以上よりLongitude=137度38.40分
  • l (small L) : Ascii code=108, SP+28=108 -> Speed Knot 0-9 (100の桁0、10の桁0、1の桁0~9
  • space  : Ascii code=32 , DC+28=32 -> Speed Knot 0 (1の桁0)、Couse 0-99 degree (100の桁0)
  • T  : Ascii code=84, SE+28=84 -> Couse 56 degree (10と1の桁)
    以上より、速度=0ノット、進路56度
  • [  : Symbol Lookup Table参照
  • /  : Symbol Lookup Table参照
    [/でRUNNERシンボル

LonitudedegreeLongitudeminutes

Speed and Course
Speedandcourse

SpeedencodingSpeedcourseencodingCourseencoding

 

`"9a} : Altitude

  • `  : Current GPS data
  • "  : Ascii code=34  34-33= 1       1*91*91=8,281
  • 9  : Ascii code=57  57-33=24          24*91=2,184
  • a  : Ascii code=97  97-33=64                   = 64        合計10,529   -> 海抜529m
  • }  : 終端記号

Altitudeは最も深い海の底(10,000メートル)からの標高で表現する。なので海抜に10,000メートルを加算する。この標高を基数91で表現する。例えば標高61メートルだと、10,061メートルとなるので、
   10,061 / (91*91) = 1  あまり1,780
          1,780 / 91 = 19, あまり51
つまり基数91表現で119あまり51となる。これに33を加算し3バイトで表現すると、Byte1=1+33=34、Byte2=19+33=42、Byte3=51+33=84となる。受信したビーコンはこの逆の方法で計算する。

_0 : Radio Type code

  • _0

Mic-E Type Codeによると_0はYaesu FT3Dのモデルコード。

 

Beacon Textについて

FT3DのBeacon ステータステキストにHello WorldをセットしてBeaconを送信してみた。以下、ピンクの所に挿入された。つまり。モデルコードの前。モデルコードがビーコン情報の最後に位置するようだ。言い換えるとAPRSスペック1.01に記載されているフォーマットに付加される形でモデルコードが追加されたことがわかる。

06:07:04R JA0WBT-7>SUTPW9,WIDE1-1 Port=1 <UI R Len=28>:`AB(l-=[/`"9N}Hello World_0

 

結果

以上より、FT3Dから発信されているビーコン情報は以下のとおりとなる。
北緯 35度40.79分
東経 137度38.12分
速度 0ノット
進路 56度
シンボル RUNNER
海抜 529m
無線機コード  Yaesu FT3D

以上

« 2022年11月 | トップページ | 2023年1月 »