guiメモ

技術寄りの雑記(予定)

技術書典20に出展します【せ24】

技術書典20にサークル「guilab」として出展します。 techbookfest.org

開催概要

  • オンライン開催
    • 会期: 2026/4/11(土)~2026/4/26(日)
  • オフライン開催
    • 会期: 2026/4/12(日) 11:00~17:00
    • 会場: 池袋・サンシャインシティ展示ホールD (文化会館ビル2F)

配置は「せ24」になりました。会場の奥の方です。とても分かりやすい場所におりますので、ぜひお越しください。既刊のオペアンプ本もあるだけ持っていきます。

新刊としてデータ駆動型制御をテーマにした本を出します。イラストは前作に引き続き星守そらさんです。タブレットの筆圧が直って細い線が描けるようになったらしいです。(前回はどうしていたの…?)

オンラインマーケットのリンクは以下です。BOOTHでの頒布はオフライン会場での頒布後を予定しております。

techbookfest.org

既刊の「オペアンプを作って学ぶ トランジスタ増幅回路入門」もよろしくお願いします。

techbookfest.org

(2026/4/13追記) BOOTHへのリンクは下記になります。

guilab.booth.pm

guilab.booth.pm

ポータブルハイレゾUSB DACの試作 その2

概要

ポータブルハイレゾUSB DACの試作で紹介した、CH32V305FBP6を用いたシンプルなUSB DACの改良に成功したので報告します。サンプルプログラムを大幅に改造し、48kHz系の主要なレートでの再生に対応しました。また、フィードバックエンドポイントの追加により安定動作するようになりました。

ことのはじめとサンプルプログラムの改造

前回の記事で紹介したサンプルプログラムにはフィードバックエンドポイントが実装されておらず、ホストPCとマイコンクロックの微妙な差異により時間経過でストリーミングが破綻するという如何にも評価向けの実装でした。自力で改造しようと思っていたのですが、変更範囲が大きすぎて放置してしまっていました。つい先日、なんとなくClaude MAXプランを契約したタイミングでこの問題のことを思い出し、Opus4.6と「お話」を繰り返したところ、納得できるレベルの実装に追い込むことができました。抽象的な部分に集中できるVibe Codingはこのような時に便利ですね。


さて、変更点が多すぎるので本記事では処理概要だけ述べるに留めておきます。完全なプログラムについては何かしらの機会に出せればと思います。さて、元のサンプルプログラムでは50スロット用意されたNormalモードのDMAでUSBの受信バッファをそのままI2S向けに使用してデータ転送をしています。そのため、割込み処理としてはUSB割込みとDMA割込みがあり、各割込みルーチン内では次のような処理が走ります。

  • USB割込みルーチン
    • 受信バイト数の取得
    • DMAスロットの更新 (次に受信データのバッファリング先とするスロットを指定)
    • 受信データのスワップ、未消費バッファ数の加算
  • DMA割込みルーチン
    • DMAを停止
    • DMAスロットの更新 (次にI2Sへ転送するスロットを指定)
    • 未消費バッファ数が0より大きい場合に以下を実行
      • DMAの再起動
      • 未消費バッファ数の減算

ここでUSBから受信した32bitサンプルデータはメモリ上で[LSB 16bit] [MSB 16bit]の並びですが、I2Sの32bitモードは上位16bitから順にシフトアウトするため [MSB 16bit] [LSB 16bit]への並び替えが必要です。この隣接ハーフワードのスワップが地味に厄介で、変な実装をするとべらぼうに時間がかかってしまい、ストリーミングに影響が出てしまいます。また、安定動作させるためにはホストPC側へクロック数をフィードバックさせる必要があります。様々なアプローチを試した結果、USB受信用にダブルバッファを設け、I2S転送用のDMAをCircularモード(リングバッファ)で構成することで安定動作するようになりました。Circularモードにしたことで割り込みなしの連続転送が可能となり、割込みルーチンは次のようにUSB割込みのみとなります。

  • USB割込みルーチン
    • PID_OUT (パケット受信時)
      • ダブルバッファ切り替え
      • 受信データをスワップしながらリングバッファに格納
      • リングバッファ書き込み位置とバッファ充填率の更新
    • PID_IN (フィードバック送信時)
      • TIMから前回のPID_INからのクロック数を取得
      • 取得したクロック数+目標バッファ充填率による補正からフィードバック値を算出
      • フィードバック値転送設定

スロットによる管理からアクロバットにリングバッファへ書き込む動作に変更したため、実際のプログラムでは上記フローの要所毎にエラー処理を入れています。また、CH32V305FBP6のSRAMは32KBと少なめであり、バッファサイズの制約からもこの方式が必要でした。

ポータブルハイレゾUSB DAC基板の改版

実は前回の記事にClassAAバッファ回路を追加した新しい基板を同時期に用意していましたが、プログラム改造が難航していたため紹介できずにいました。以下のような回路の基板を起こしていました。チャージポンプ回路で負電圧を生成し、±5Vで動くオペアンプを動かせるようにしています。設計当時はオーディオ用オペアンプとしてNJM8830がリリースされたばかりであり、お試しできるようにSOP-8向けの基板としていました。NJM8830とMUSES8920を実装した2枚の基板を作っていたのですが、残念ながらNJM8830を実装していた基板は久々に電源をいれたところ煙を上げて何かが死んだので動かせなくなってしまいました……。

回路図(マイコン周辺)

回路図(バッファ)

基板サイズは当然FRISKのケースに収まるよう作っていたのですが、肝心のFRISKの樹脂ケースがディスコンになるという悲しい現実。小物電子工作はFRISKケースの中に入れなければならない、という呪いにも似た思想から解放されたことを喜ぶべきでしょうか。それはともかく、設計から2年が経過する前に思い通りに動かすことができるようになり感無量です。バッファ回路により平面磁界駆動型のヘッドホンもちゃんと鳴ってくれます。

ポータブルハイレゾUSB DAC

PCに接続すると設定画面からサンプルレートを指定できます。48kHz系の主要なレートが列挙されており、いずれも動作しました。

48kHz系の主要レートに対応した

まとめ

色々ありましたが、とりあえずCH32V305/307を使えば多少は使える48kHz系のDDCを作れることが分かりました。クロックツリー的に44.1kHz系は厳しく、また最近登場したハイスペックモデルであるCH32H417はSAIが付いているのにも関わらずオーディオ用クロック入力が無く、いい感じの逓倍率が設定できないという残念な状況のため、しっかりしたUSB DACを作りたい場合はDDC周辺を再検討しなければいけなさそうです。とはいえ、48kHz系だけで良ければCH32V305が価格的にも魅力的な選択肢なのは確かで、64fsでdutyが60%のMCLKも無理やり作ることもできるので、簡易的なUSB DACには持ってこいだと思います。多分。

技術書典19に出展します【あ15】

技術書典19にサークル「guilab」として出展します。

一般参加者向けサイトが公開されたので宣伝します。 techbookfest.org

開催概要

  • オンライン開催
    • 会期: 2025/11/15(土)~2025/11/30(日)
  • オフライン開催
    • 会期: 2025/11/16(日) 11:00~17:00
    • 会場: 池袋・サンシャインシティ展示ホールD (文化会館ビル2F)

配置は「あ15」になりました。下図のオレンジ色に塗りつぶされた場所です。そこそこ入口に近くてビビっています。


オペアンプの自作をテーマに、トランジスタの基本的な増幅回路について解説する本を出します。表紙イラストは星守そらさんです。電子版のサンプルは下記となります。

また、オフライン会場ではディスクリートヘッドホンアンプの組み立てキットも頒布します。トランジスタが計85個とボリューム満点のため、秋の夜長の工作にぴったりだと思います。はんだ付けの腕に自信のある方、いかがでしょうか?電源は付属しないため、別途DC24Vで200mA以上の出力が可能なACアダプタ等をご用意ください。

組み立てキットの内容

作例は下記となります。


書籍へのリンクは下記です。 techbookfest.org

組み立てキットは下記です。(オフライン会場のみの取り扱いとなります) techbookfest.org

(2026/3/28追記) BOOTHへのリンクは下記になります。イベント期間外は、こちらから紙の本と組み立てキットが購入できます。 guilab.booth.pm

guilab.booth.pm

よろしくお願いします。

XMOSの開発環境を構築して簡単なDDCを作る

はじめに

正攻法でUSB DDCを作る場合、CH32マイコンでは48kHz系のレートにしか対応できない(下記記事参照)ので思い切ってXMOSに入門して簡単なDDCを作ってみました。ネット上の日本語記事が片手でも余るぐらいの数しかなくて入門前は不安でしたが、ほぼコーディングせずに動いてしまい呆気に取られたので紹介します。

guiblo.hatenablog.com

前準備

XMOSへ入門するにあたり、最低限次のツールが必要になります。

  • 何らかの開発ボード
  • XTAG(開発ボードに搭載されている場合は不要)

オーディオに特化した開発ボードも公式から出ていますが、試食するには若干勇気の要るお値段がするので開発ボードを自作することにしました。

開発ボードの設計

手元にPCM5102Aのブレークアウトボードがあるので、これに合わせて作ることにします。また、コーディングを少しでも楽にするためにUSBマルチチャンネルオーディオの開発ボードを参考にピン配置などを決めます。この開発ボードの回路図は以下からダウンロードできます。その他、XMOSを動かす上で気を付けるべき点などは丁寧にデータシートに記載されているため、そちらも参考にします。 www.xmos.com

以下が自作XMOS開発ボードの回路図になります。回路図がスカスカなのは、当初は色々な実験ができるようにしようと思っていたためです。

自作XMOS開発ボードの回路図
自作XMOS開発ボード

実際に動かすと3.3Vと0.9Vのレギュレータが熱くなるので、ここの部分はリニアレギュレータにしない方が良さそうです。(そんなに電流を食わないだろうと侮っていました…)

XTC Toolsのセットアップ

基本的に下記の公式ガイドを見ながら進めれば初期セットアップは問題なくできるようになっています。以降、このガイドに沿ってセットアップを進めていきます。 www.xmos.com

まずは、下記の公式サイトからXTC Toolsをダウンロードします。なお、公式サイトからツールをダウンロードする際にユーザ登録が必要となります。 https://www.xmos.com/software-toolswww.xmos.com 以前はEclipseベースのIDEが提供されていたようですが、最新版ではCUI(+VScode)に移行したようです。Raspberry Pi PicoのC SDKよりは導入しやすかったので、身構える必要はないです。多分。

閑話休題Windowsの場合、XTC Toolsのインストール後にXTC Toolsのコマンドプロンプトを立ち上げ、XTAGを接続してからxrun -lすることでXTAGを認識するか確認することができます。

XTAGの動作確認

XTAGが正しく認識されることを確認したら、CMakeとGitをそれぞれインストールします。インストール時にPATHを追加することをお忘れなく。 cmake.org git-scm.com

VS Codeが入っていない人はVS Codeも入れます。宗教上の理由でVS Codeを入れることができない人は頑張ってよしなにしてください。 code.visualstudio.com

サンプルプログラムの導入と改変

サンプルプログラムも以下からダウンロードできます。USB Audio Softwareのzipファイルをダウンロードし、USB Audio User Guideを適宜見ながら改造を進めていきます。 www.xmos.com

ここでは開発ボードで参考にしたapp_usb_aud_xk_316_mcをベースに改造を進めていきます。

まず、sw_usb_audioフォルダを複製してuser_ddcにリネームし、app_usb_aud_xk_316_mcフォルダを除く全てのフォルダを削除します。また、app_usb_aud_xk_316_mcフォルダをapp_ddcに、user_ddc\app_ddc\src\core\xk-audio-316-mc.xnuser_ddc\app_ddc\src\core\ddc.xnにリネームします。

次に、user_ddcフォルダ直下にあるCMakeLists.txtを編集します。ここでは参照するフォルダとプロジェクト名を変えます。

cmake_minimum_required(VERSION 3.21)
include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake)
project(user_ddc)

add_subdirectory(app_ddc)

app_ddcフォルダ直下にあるCMakeLists.txtも編集します。サンプルのものは複数のハードウェア構成でビルドするように設定がされており初回コンパイル時に時間がかかるため、必要最小限の構成に書き換えます。

cmake_minimum_required(VERSION 3.21)
include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake)
project(app_ddc)

set(APP_HW_TARGET ddc.xn)
include(${CMAKE_CURRENT_LIST_DIR}/../deps.cmake)
set(APP_PCA_ENABLE ON)
set(SW_USB_AUDIO_FLAGS ${EXTRA_BUILD_FLAGS} -O3
                                            -report
                                            -lquadflash
                                            -g
                                            -fxscope
                                            -DUSB_TILE=tile[0]
                                            -DADAT_TX_USE_SHARED_BUFF=1
                                            -DXUA_QUAD_SPI_FLASH=1)

set(APP_COMPILER_FLAGS_ddc ${SW_USB_AUDIO_FLAGS})

include(${CMAKE_CURRENT_LIST_DIR}/configs_partial.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/configs_build.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/configs_test.cmake)

set(APP_INCLUDES src src/core src/extensions)
set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)

XMOS_REGISTER_APP()

以上でCMake周辺の改造は完了です。いよいよプログラム本体の改造に入っていきます。

XMOSでは、以下のようにファイルが分かれているようです。

  • プログラム本体: .xcファイル
  • ピン配置の設定: .xnファイル
  • ヘッダファイル: .hファイル

ここではまずuser_ddc\app_ddc\src\core\ddc.xnにあるピン配置から改造していきます。改造とはいっても公式開発ボードと同じほぼピン配置となるように開発ボードを設計したので、使用していないピンをコメントアウトする程度になります。(I2Cは今後使う予定があるため残しています)

<?xml version="1.0" encoding="UTF-8"?>
<Network xmlns="http://www.xmos.com"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://www.xmos.com http://www.xmos.com">
  <Type>Board</Type>
  <Name>xcore.ai MC Audio Board</Name>

  <Declarations>
    <Declaration>tileref tile[2]</Declaration>
  </Declarations>

  <Packages>
    <Package id="0" Type="XS3-UnA-1024-TQ128">
      <Nodes>
        <Node Id="0" InPackageId="0" Type="XS3-L16A-1024" Oscillator="24MHz" SystemFrequency="600MHz" ReferenceFrequency="100MHz">
          <Boot>
            <Source Location="bootFlash"/>
          </Boot>
          <Tile Number="0" Reference="tile[0]">
            <Port Location="XS1_PORT_1B" Name="PORT_SQI_CS"/>
            <Port Location="XS1_PORT_1C" Name="PORT_SQI_SCLK"/>
            <Port Location="XS1_PORT_4B" Name="PORT_SQI_SIO"/>

            <!-- Various ctrl signals -->
            <Port Location="XS1_PORT_8D"  Name="PORT_CTRL"/>

            <!-- I2C -->
            <Port Location="XS1_PORT_1L"  Name="PORT_I2C_SCL"/>
            <Port Location="XS1_PORT_1M"  Name="PORT_I2C_SDA"/>

            <!-- Clocking --> 
            <Port Location="XS1_PORT_16B" Name="PORT_MCLK_COUNT"/>
            <Port Location="XS1_PORT_1D"  Name="PORT_MCLK_IN_USB"/>
            <!-- Port Location="XS1_PORT_1A"  Name="PORT_PLL_REF"/ -->
            
            <!-- Audio Ports: Digital -->         
            <!-- Port Location="XS1_PORT_1O"  Name="PORT_ADAT_IN"/ -->   <!-- N: Coax O: Optical --> 
            <!-- Port Location="XS1_PORT_1N"  Name="PORT_SPDIF_IN"/ -->  <!-- N: Coax O: Optical --> 
            
          </Tile>
          <Tile Number="1" Reference="tile[1]">
            <!-- Audio Ports: I2S -->         
            <Port Location="XS1_PORT_1D"  Name="PORT_MCLK_IN"/>
            <!-- Port Location="XS1_PORT_16B" Name="PORT_MCLK_COUNT_2"/ -->
            <Port Location="XS1_PORT_1B"  Name="PORT_I2S_LRCLK"/>
            <Port Location="XS1_PORT_1C"  Name="PORT_I2S_BCLK"/>
            <Port Location="XS1_PORT_1P"  Name="PORT_I2S_DAC0"/>
            <!-- port Location="XS1_PORT_1O"  Name="PORT_I2S_DAC1"/ -->
            <!-- port Location="XS1_PORT_1N"  Name="PORT_I2S_DAC2"/ -->
            <!-- port Location="XS1_PORT_1M"  Name="PORT_I2S_DAC3"/ -->
            <!-- Port Location="XS1_PORT_1I"  Name="PORT_I2S_ADC0"/ -->
            <!-- Port Location="XS1_PORT_1J"  Name="PORT_I2S_ADC1"/ -->
            <!-- Port Location="XS1_PORT_1K"  Name="PORT_I2S_ADC2"/ -->
            <!-- Port Location="XS1_PORT_1L"  Name="PORT_I2S_ADC3"/ -->
            
            <!-- Audio Ports: Digital -->         
            <!-- Port Location="XS1_PORT_1G"  Name="PORT_ADAT_OUT"/ -->  <!-- A: Coax G: Optical --> 
            <!-- Port Location="XS1_PORT_1A"  Name="PORT_SPDIF_OUT"/ --> <!-- A: Coax G: Optical --> 

            <!-- MIDI -->
            <!-- Port Location="XS1_PORT_1F"  Name="PORT_MIDI_IN"/ -->
            <!-- Port Location="XS1_PORT_4C"  Name="PORT_MIDI_OUT"/ -->  <!-- bit[0] -->

          </Tile>
        </Node>
      </Nodes>
    </Package>
  </Packages>
  <Nodes>
    <Node Id="2" Type="device:" RoutingId="0x8000">
      <Service Id="0" Proto="xscope_host_data(chanend c);">
        <Chanend Identifier="c" end="3"/>
      </Service>
    </Node>
  </Nodes>
  <Links>
    <Link Encoding="2wire" Delays="5clk" Flags="XSCOPE">
      <LinkEndpoint NodeId="0" Link="XL0"/>
      <LinkEndpoint NodeId="2" Chanend="1"/>
    </Link>
  </Links>
  <ExternalDevices>
      <Device NodeId="0" Tile="0" Class="SQIFlash" Name="bootFlash" PageSize="256" SectorSize="4096" NumPages="16384">
      <Attribute Name="PORT_SQI_CS" Value="PORT_SQI_CS"/>
      <Attribute Name="PORT_SQI_SCLK"   Value="PORT_SQI_SCLK"/>
      <Attribute Name="PORT_SQI_SIO"  Value="PORT_SQI_SIO"/>
    </Device>
  </ExternalDevices>
  <JTAGChain>
    <JTAGDevice NodeId="0"/>
  </JTAGChain>

</Network>

次に同じフォルダにあるxua_conf.hを編集します。サンプルではMCLKの周波数が異なっていたり、最大24bit/192kHzまでしか対応しないため、該当箇所を追加・修正します。またアナログ入力は無効にし、出力も2チャンネル分に修正します。

// Copyright 2012-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
/**
 * @file       xua_conf.h
 * @brief      Defines relating to device configuration and customisation.
 *             For xCORE.ai Audio MC Board
 * @author     Ross Owen, XMOS Limited
 */
#ifndef _XUA_CONF_H_
#define _XUA_CONF_H_

#include "../../../shared/version.h"

/*
 * Device configuration option defines to override default defines found in lib_xua/api/xua_conf_default.h
 *
 * Build can be customised but changing and adding defines here
 *
 * Note, we check if they are already defined in Makefile
 */

/* Enable DFU functionality. */
#ifndef XUA_DFU_EN
#define XUA_DFU_EN         (1)
#endif

/*** Defines relating to basic functionality ***/
/* Enable/Disable MIDI - Default is MIDI off */
#ifndef MIDI
#define MIDI               (0)
#endif

/* Enable/Disable S/PDIF output - Default is S/PDIF off */
#ifndef XUA_SPDIF_TX_EN
#define XUA_SPDIF_TX_EN    (0)
#endif

/* Enable/Disable S/PDIF input - Default is S/PDIF off */
#ifndef XUA_SPDIF_RX_EN
#define XUA_SPDIF_RX_EN    (0)
#endif

/* Enable/Disable ADAT output - Default is ADAT off */
#ifndef XUA_ADAT_TX_EN
#define XUA_ADAT_TX_EN     (0)
#endif

/* Enable/Disable ADAT input - Default is ADAT off */
#ifndef XUA_ADAT_RX_EN
#define XUA_ADAT_RX_EN     (0)
#endif

/* Enable/Disable Mixing core(s) - Default is on */
#ifndef MIXER
#define MIXER              (1)
#endif

/* Set the number of mixes to perform - Default is 0 i.e mixing disabled */
#ifndef MAX_MIX_COUNT
#define MAX_MIX_COUNT      (0)
#endif

/* Audio Class version - Default is 2.0 */
#ifndef AUDIO_CLASS
#define AUDIO_CLASS        (2)
#endif

/*** Defines relating to channel counts ***/
/* Number of I2S channels to DACs*/
#ifndef I2S_CHANS_DAC
#define I2S_CHANS_DAC      (2)
#endif

/* Number of I2S channels from ADCs */
#ifndef I2S_CHANS_ADC
#define I2S_CHANS_ADC      (0)
#endif

/* Number of USB streaming channels - by default calculate by counting audio interfaces */
#ifndef NUM_USB_CHAN_IN
#define NUM_USB_CHAN_IN    (I2S_CHANS_ADC + 2*XUA_SPDIF_RX_EN + 8*XUA_ADAT_RX_EN)  /* Device to Host */
#endif

#ifndef NUM_USB_CHAN_OUT
#define NUM_USB_CHAN_OUT   (I2S_CHANS_DAC + 2*XUA_SPDIF_TX_EN + 8*XUA_ADAT_TX_EN)  /* Host to Device */
#endif

/*** Defines relating to channel arrangement/indices ***/
/* Channel index of S/PDIF Tx channels: separate channels after analogue channels (if they fit) */
#ifndef SPDIF_TX_INDEX
    #if (I2S_CHANS_DAC + 2*XUA_SPDIF_TX_EN) <= NUM_USB_CHAN_OUT
        #define SPDIF_TX_INDEX   (I2S_CHANS_DAC)
    #else
        #define SPDIF_TX_INDEX   (0)
    #endif
#endif

/* Channel index of S/PDIF Rx channels: separate channels after analogue channels */
#ifndef SPDIF_RX_INDEX
#define SPDIF_RX_INDEX     (I2S_CHANS_ADC)
#endif

/* Channel index of ADAT Tx channels: separate channels after S/PDIF channels (if they fit) */
#ifndef ADAT_TX_INDEX
    #define ADAT_TX_INDEX    (I2S_CHANS_DAC + 2*XUA_SPDIF_TX_EN)
#endif

/* Channel index of ADAT Rx channels: separate channels after S/PDIF channels */
#ifndef ADAT_RX_INDEX
#define ADAT_RX_INDEX      (I2S_CHANS_ADC + 2*XUA_SPDIF_RX_EN)
#endif

/*** Defines relating to audio frequencies ***/
/* Master clock defines (in Hz) */
#ifndef MCLK_441
#define MCLK_441           (1024*44100)   /* 44.1, 88.2 etc */
#endif

#ifndef MCLK_48
#define MCLK_48            (1024*48000)   /* 48, 96 etc */
#endif

/* Minumum sample frequency device runs at */
#ifndef MIN_FREQ
#define MIN_FREQ           (44100)
#endif

/* Maximum sample frequency device runs at */
#ifndef MAX_FREQ
#define MAX_FREQ           (768000)
#endif

/* Default device sample frequency */
#ifndef DEFAULT_FREQ
#define DEFAULT_FREQ             (384000)
#endif

/*** Defines relating to feature placement regarding tiles ***/
#define XUD_TILE           (0)
#define PLL_REF_TILE       (0)

#define AUDIO_IO_TILE      (1)
#define MIDI_TILE          (1)

/*** Defines relating to USB descriptor strings and ID's ***/
#define VENDOR_ID          (0x20B1) /* XMOS VID */
#ifndef PID_AUDIO_2
#define PID_AUDIO_2        (0x0016)
#endif
#ifndef PID_AUDIO_1
#define PID_AUDIO_1        (0x0017)
#endif

#ifndef DFU_PID
#if (AUDIO_CLASS == 1)
#define DFU_PID             (0xD000 + PID_AUDIO_1)
#else
#define DFU_PID             (0xD000 + PID_AUDIO_2)
#endif
#endif

#define PRODUCT_STR_A2     "XMOS xCORE.ai MC (UAC2.0)"
#define PRODUCT_STR_A1     "XMOS xCORE.ai MC (UAC1.0)"

/* Board power source - Default is bus-powered */
#ifndef XUA_POWERMODE
#define XUA_POWERMODE      XUA_POWERMODE_BUS
#endif

/* Enable/Disable example HID code - Default is off */
#ifndef HID_CONTROLS
#define HID_CONTROLS       (0)
#endif

/* Enable 32bit output */
#define OUTPUT_FORMAT_COUNT (3)

#include "user_main.h"

#endif

一つ上の階層に戻り、user_ddc\app_ddc\src\extensions\audiohw.xcを編集します。このファイルではUSBオーディオライブラリのweak属性で宣言された関数をオーバーライドしているため、ここでは適切なクロックに切り替える処理のみを実装します。サンプルプログラムではクロックを切り替えるためにI2Cの処理が入るため若干込み入ったコードになっていますが、重要な部分はライブラリ任せなので特別重要な処理はしていません。なので、このファイルで関数をオーバーライドしなくても動いたりします。(クロックの設定と再生レートが適切でない場合に再生速度が変わります)

// Copyright 2022-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include "xua.h"
#include <platform.h>
// #include "user_main.h"

#if (XUA_PCM_FORMAT == XUA_PCM_FORMAT_TDM) && (XUA_I2S_N_BITS != 32)
#warning ADC only supports TDM operation at 32 bits
#endif

/* TODO this is a key frequency and should be moved into lib_xua */
#if (XUA_SYNCMODE == XUA_SYNCMODE_SYNC)
    #define PLL_SYNC_FREQ           (500)
#else
    #if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
    /* Choose a frequency the xcore can easily generate internally */
    #define PLL_SYNC_FREQ           (300)
    #else
    #define PLL_SYNC_FREQ           (1000000)
    #endif
#endif

on tile[0]: out port p_ctrl = PORT_CTRL;

unsafe client interface mclk_switch_if mcksw_client;

void mclk_rec(server interface mclk_switch_if i)
{
    while(1)
    {
        select
        {
            case i.mclksw_switch(unsigned mClk):
                if(mClk == MCLK_441)
                {
                    p_ctrl <: 0x40;
                }
                else
                {
                    p_ctrl <: 0;
                }
                break;
        }
    }
}

void mclk_sen(client interface mclk_switch_if i, unsigned mClk)
{
    i.mclksw_switch(mClk);
}

/* Configures the external audio hardware at startup */
void AudioHwInit()
{
    unsafe
    {
        while(!(unsigned) mcksw_client);
        mclk_sen((client interface mclk_switch_if)mcksw_client, DEFAULT_MCLK);
    }

    delay_milliseconds(1);
}

/* Configures the external audio hardware for the required sample frequency */
void AudioHwConfig(unsigned samFreq, unsigned mClk, unsigned dsdMode, unsigned sampRes_DAC, unsigned sampRes_ADC)
{
    unsafe
    {
        while(!(unsigned) mcksw_client);
        mclk_sen((client interface mclk_switch_if)mcksw_client, mClk);
    }

    delay_milliseconds(1);
}

最後に同じフォルダにあるuser_main.hを編集します。ここではUSBオーディオライブラリ内で定義されているmain関数でユーザー関数を並列動作させるのに必要な宣言などを行っています。クロック設定を切り替えるピンが動いているタイルとUSBデバイスが動いているタイルが別なので、ここではインタフェースを用いて動かすように組んでいます。

// Copyright 2022-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#ifndef _USER_MAIN_H_
#define _USER_MAIN_H_

#ifdef __XC__

#include <platform.h>

typedef interface mclk_switch_if
{
    void mclksw_switch(unsigned mClk);
} mclk_switch_if;

extern unsafe client interface mclk_switch_if mcksw_client;
extern void mclk_rec(server interface mclk_switch_if i);

#define USER_MAIN_DECLARATIONS \
    interface mclk_switch_if mcksw;

#define USER_MAIN_CORES on tile[0]: {\
                                        mclk_rec(mcksw);\
                                    }\
                        on tile[1]: {\
                                        unsafe{ mcksw_client = mcksw; }\
                                    }
#endif

#endif

ここまでできたらコンパイルして書き込みます。XTC Toolsでuser_ddcフォルダに移動し、cmake -G "Unix Makefiles" -B buildxmake -C buildの順に実行します。

ビルドの様子

ビルドが無事完了すると書き込み用のファイルapp_ddc\bin\ddc\app_ddc_ddc.xeが生成されるため、xflash app_ddc\bin\ddc\app_ddc_ddc.xeによりXMOSに書き込みます。

書き込み時の様子

動かしてみる

開発ボード上のUSBコネクタからPCに接続すると、オーディオデバイスとして認識されるようになります。(画像では見切れていますが)ちゃんと32bit/384kHzまで選択できるようになっています。Macに接続すると32bit/768kHzまで選択できるようになりますが、開発ボードで使用しているPCM5102Aが384kHzまでの対応なのでこれで我慢します。

動作確認

感想

XMOSに対して漠然と高い敷居を感じていましたが、実際のところライブラリが優秀で簡単に入門することができました。CH32と比べるとどうしても高価になってしまいますが、性能や実装のお手軽さを考えると自作DDCで使う分には全然ありだなと思いました。ただ、(C++でお気持ちマルチスレッドプログラミングを齧った程度の自分からは)並列プログラミングの書き方がかなり特徴的に感じるので、本格的に細かい部分まで実装するとなると慣れるまで時間がかかりそうです。

ポータブルハイレゾUSB DACの試作

概要

CH32マイコンを用いてFRISKのケースに収まる大きさのUSB DAC開発ボードを作成しました。32bit/384kHzに(のみ)対応しています。

はじめに

CH32V305/307にはUSB-HS PHYが搭載されており、これを用いるとUSB周りの外付け部品なしでUAC2.0によりハイレゾ音源を再生できるようになります。今回は秋月電子で取り扱いが開始されたCH32V305FBP6を用いたシンプルなUSB DAC開発ボードを作成しました。

USB DAC開発ボードの作成

今回はプログラムの動作確認を行うために必要最小限の機能を備えることを目的とし、CH32V305FBP6にPCM5102Aを直接接続しただけのシンプルな構成を採用しました。

今回作成したUSB DAC開発ボードの回路図
あまりにもシンプルな回路なので、持ち運びしやすいように仕上げることとしました。具体的には、FRISKのケースに収まるように基板を設計しました。
完成した開発ボードの外観
蓋を開けたときの様子
開発ボード単体の写真
ケーブル類を装着したときの様子

プログラムの準備

公式githubにUSB Audioのサンプルは執筆時点で公開されていませんが、解説記事とともにサンプルプログラムを公開されている方がいるため、こちらを参考にプログラムを改変していきます。
今回作成した回路のクロックは16MHzであるため、まずはUSB周りのクロックの設定を変更します。こちらは先に示した記事にある通りにch32v30x_usbhs_device.c内にあるUSBHS_RCC_Init()関数の内容を次のように分周比が2となるように変更します。

RCC_USBHSPLLCLKConfig( RCC_HSBHSPLLCLKSource_HSE );
RCC_USBHSConfig( RCC_USBPLL_Div2 );
RCC_USBHSPLLCKREFCLKConfig( RCC_USBHSPLLCKREFCLK_8M );
RCC_USBCLK48MConfig( RCC_USBCLK48MCLKSource_USBPHY );
RCC_USBHSPHYPLLALIVEcmd( ENABLE );
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_USBHS, ENABLE );

次にI2S周りのクロックの設定を変更します。system_ch32v30x.c内にあるmyI2S_SetSysClock()関数の内容を次のように予め用意されてある16MHz向けのものに変更します。

#if 1
    //384K 32bit MCK DISABLE HSE 16M
    u32 temp=0;
    //HSE
    temp=RCC->CTLR;
    temp|=RCC_HSEON;
    RCC->CTLR = temp;
    while(RCC->CTLR & RCC_HSERDY==0);

    temp=RCC->CFGR0;
    temp&=(u32)~0x3FF0;//AHB APB2 APB1
    temp|=(u32)0x3D0002;//PLL-16 HSESource PLLSource
    RCC->CFGR0=temp;
    RCC->CFGR2=0x10000|0x40|0x0A00|0x04;//DIV2-5 PLL2-12 DIV1-5

    temp=RCC->CTLR;
    temp|=(1<<26);
    RCC->CTLR=temp;
    while( (RCC->CTLR & (1<<27)) == 0 );//PLL2READY

    temp=RCC->CTLR;
    temp |= (1 << 24);
    RCC->CTLR=temp;
    while( (RCC->CTLR & (1<<25)) == 0 );//PLLREADY
    while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08);
#endif

#if 0
    //384K 32bit MCK DISABLE HSE 24M
    u32 temp=0;
    //HSE
    temp=RCC->CTLR;
    temp|=RCC_HSEON;
    RCC->CTLR = temp;
    while(RCC->CTLR & RCC_HSERDY==0);

    temp=RCC->CFGR0;
    temp&=(u32)~0x3FF0;//AHB APB2 APB1
    temp|=(u32)0x3D0002;//PLL-16 HSESource PLLSource
    RCC->CFGR0=temp;
    RCC->CFGR2=0x10000|0x40|0x0600|0x04;//DIV2-5 PLL2-8 DIV1-5

    temp=RCC->CTLR;
    temp|=(1<<26);
    RCC->CTLR=temp;
    while( (RCC->CTLR & (1<<27)) == 0 );//PLL2READY

    temp=RCC->CTLR;
    temp |= (1 << 24);
    RCC->CTLR=temp;
    while( (RCC->CTLR & (1<<25)) == 0 );//PLLREADY
    while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08);
#endif

次にmain.c内にGPIOの設定を行う関数MyGPIO_Init()を作成します。

void MyGPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure={0};
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    GPIO_WriteBit(GPIOC, GPIO_Pin_6, Bit_RESET);
    GPIO_WriteBit(GPIOC, GPIO_Pin_7, Bit_RESET);
    GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_RESET);
    GPIO_WriteBit(GPIOC, GPIO_Pin_9, Bit_RESET);
}

このとき、同じくmain.c内にあるI2S_DMA_Init()関数にてI2S周りの設定が記述されていますが、今回はMCLKを出力しないため、該当する部分をコメントアウトします。

//    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
//    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
//    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//    GPIO_Init(GPIOC, &GPIO_InitStructure);

最後にmain.c内にあるmain関数内の内容を次のように書き換えてプログラムの準備は終了です。ここでは不意にプログラムがコケたときのために、無限ループ内に1秒周期でLEDが光るようにしています。

int main(void)
{
    NVIC_InitTypeDef  NVIC_InitStructure = {0};
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    Delay_Init();

    MyGPIO_Init();
    I2S_DMA_Init();

    USBHS_RCC_Init( );
    USBHS_Device_Init( ENABLE );

    NVIC_InitStructure.NVIC_IRQChannel = USBHS_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    SetVTFIRQ((u32)USBHS_IRQHandler,USBHS_IRQn,1,ENABLE);

    while(1)
    {
        Delay_Ms(500);
        GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_RESET);
        Delay_Ms(500);
        GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET);
    }
}

感想

基板を起こしたことを除くとサンプルプログラムをほぼそのまま動かしたような形になりましたが、あっさりとハイレゾ対応のUSB DACが作れてしまいました。主要部品が秋月電子で揃えられるという手軽さのインパクトを強烈に感じますがいかがでしょうか。
サンプルプログラムでは32bit/384kHzにのみ対応しており、また不意に不調となる場合があるため、USBの勉強をしながら適切な割り込み処理や様々なレートに対応できるようにしたいです。

補足

44.1kHz系のレートに対応できるか検討したところ逓倍率の上手い設定が無いらしく厳しいみたいです。USB-HS使用時には外部からそこそこ精度の良いクロックを供給しなければならず、これと両立させようとすると本稿で示した48kHz系のみとなってしまうようです。I2S専用のクロック入力を受け付けるようなモデルの登場を期待したいです。

秋月お楽しみ袋2023 6月

はじめに

秋月電子秋葉原店の2階店舗の開店と共にお楽しみ袋の販売がされたため、また懲りずに買ってしまいました。平日であったためか、人は休日よりも少なめで選びやすかったです。今回のお楽しみ袋は特別仕様になっており、お楽しみ袋を買うとトートバッグと使いきれない量のチャック袋が付いてきました。

秋月電子の名が刻まれたトートバッグ。お楽しみ袋を購入すると手に入る。(単品での購入も可)

大物

大物枠として恒例のACアダプタが入っていました。ACアダプタ以外ではROHMのマトリクスLED表示器のようなものも入っており、創作意欲を掻き立てらてます。この表示器のようなものの仕様はネット上を簡単に検索しても出てこなかったため、飼い慣らすのに苦労しそうです。また、中にはドリルビットも入っており、売価が1000円なのでこれだけでほとんど元を取れている計算になります。

ACアダプタ等の大物たち

小物

小物類はいつものお楽しみ袋と同様の傾向なので写真のみでお送りします。

オーディオ用コンデンサなど
ブレーキ抵抗(?)やケーブルなど
10円はんだとサーミスタやケーブルが生えてるコンデンサなど
テープ入りの部品と見覚えのある部品たち
SMDのICたち。ロジックICが多め。
想像以上に大量に入っていたヒューズ
ロープロファイルピンソケット。筆者愛用なのでテンション爆上がり。
ロープロファイルピンヘッダ
振動モータ
大量に在庫しても微妙に困る値のフィルムコンデンサたち
積層セラミックコンデンサバリスタのようなもの
前出の写真に写りきらなかった積層セラミックコンデンサたち
マーキングから値が読み取れなかったクリスタル
圧着端子たち。地味に嬉しい。
ICソケットやコネクタたち
いつもの電解コン
LEDのようなものとケーブル類
仕分けしている途中で出てきた既出の部品たち
ディスク型のセラコンたち。仕分ける気力がここで尽きた。

感想

いつも通りのお楽しみ袋でした。秋月の名が入ったトートバッグが良いですね。

オペアンプの聴き比べ

概要

いい感じのオペアンプを複数用意したので、以前作成したUSB-DACのメインオペアンプを差し替えて聴き比べしてみました。
※筆者はオーディオのオの字も知らないような人間なので、冗談半分で本稿をご覧ください。

試聴条件

今回は以下のオペアンプの聴き比べを行いました。聴き比べ時にはオーディオテクニカのATH-R70xを使用しました。

  • NJM8901E(秋月DIP化変換基板使用)
  • MUSES8820D
  • MUSES8920D
  • OPA1655DR(秋月2回路化モジュール)

聴き比べ

NJM8901E

NJM8901E
安価なオーディオ用オペアンプです。今回はこのオペアンプを基準として比較します。低音があっさり目で聴きやすい音が出ますが、以降のオペアンプの音を聴いてしまうと少し物足りない感じがします。ピアノソロなどに合う感じがします。

MUSES8820D

MUSES8820D
バイポーラ入力でMUSES02Dの廉価版です。NJM8901Eと比べて低音がものすごく良いです。内臓まで響く低音が出ます。夕食後に聴き比べを行ったのですが、夕食が戻りそうになりました。ただ高音はもう一歩というところでしょうか。MUSES02Dを試したくなります。

MUSES8920D

MUSES8920D
1年ほど使用しているオペアンプです。恐らく私の耳はこの子の色に染まっています。NJM8901Eと比べて低音・高音共に良好な感じです。MUSES8820Dと比べて低音の量は出てる感じがしますが、内臓まで響く感じではなく、やや団子状(?)になっている感じがします。低音部に限ればMUSES8820Dの方が一枚、いや二枚上手に感じます。しかし、全体的なバランスではMUSES8920Dの方が良いように感じるため、使いやすいオペアンプという印象です。が、秋月でディスコン扱いになっているのが残念です。型番が変わって再登場するという話を見かけますが、どうなることやら。

OPA1655DR

OPA1655DR
今日秋月に行って買ってきたオペアンプです。f特は今回使用したものの中でずば抜けて良い一品です。音の傾向はMUSES8920Dの低音を改善したような感じで、中々好印象です。NJM8901Eから差し替えると音の変化に驚きます。ただ、低音はやはりMUSES8820Dの方が一枚上手のようで、こちらも内蔵まで響く低音までは出てこないみたいです。また、データシートのボード線図を見ての通りユニティゲイン周波数付近で位相が暴れており、音量を絞らないと時々発振してしまうじゃじゃ馬なので、扱いやすさではMUSES8920Dの方が良い感じです。この点に関しては、とりあえず10dBぐらい稼げば解決しそうなので後日改造します。

まとめ

本稿では4つのオペアンプを用いて聴き比べを行いました。MUSES02Dが欲しいです。