« PICOのPWMをDMAで動作させる方法 | トップページ | pico_tnc 2200Hz(Space)でPhaseをずらす件の備忘録 »

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

 

« PICOのPWMをDMAで動作させる方法 | トップページ | pico_tnc 2200Hz(Space)でPhaseをずらす件の備忘録 »

ラズパイ日記」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

« PICOのPWMをDMAで動作させる方法 | トップページ | pico_tnc 2200Hz(Space)でPhaseをずらす件の備忘録 »