2020年05月30日

Win環境でのRust試食

安全性、速度、並行性にフォーカスしたシステムプログラミング言語としてRustが昨今、話題です。今回は手持ちのSTM32F042のNucleoボードを用いて、Windows環境でRust組み込みプログラミングをしてみました。Rust+STM32組み込み系の場合、MacやUbuntuを使った例が多いですが、今回はWindows環境でopenocdを使用せずに行いました。Rust版STM32HALライブラリを使用してLチカをやってみました。


@Rust環境構築
Rust公式サイトからインストーラーをダウンロードしてインストールします。CLIベースなので最初は戸惑うかもしれませんが、コマンドラインに従ってインストールを行います。インストール後、コマンドラインからcargoと打って下記の様なメッセージが出ればインストール成功です。

cargo.jpg


Aクロスコンパイル環境構築
Windows環境でArmバイナリを生成するためのクロスコンパイル環境のパッケージをインストールします。使用するマイコンによって選択する必要があります。今回はSTM32F042でM0系なので、thumbv6m-none-eabiを使用しました。下記のコマンドを打ってパッケージをインストールします。

rustup target add thumbv6m-none-eabi

なお、環境によって選択するパッケージは下記の通りです。

ARM Cortex-M0 and Cortex-M0+thumbv6m-none-eabi
ARM Cortex-M3thumbv7m-none-eabi
ARM Cortex-M4 and Cortex-M7 (FPUなし) thumbv7em-none-eabi
ARM Cortex-M4F and Cortex-M7F(FPUあり) thumbv7em-none-eabihf


BArm Embedded Toolchainインストール
Rustで生成されるバイナリをbinファイルに変換したり、バイナリデータを確認する際に使用します。Armサイトからインストーラーをダウンロードしてインストールします。インストール設定のpath追加にチェックを入れてコマンドラインからアクセスできるようにします。

gcc-arm-none-eabi-*-20**-q*-major-win32.exe

CRust版HALライブラリ
Rust版HALライブラリをgithubサイトからダウンロードします。Zipファイルなので解凍します。

Dライブラリ書き換え
今回はLチカを行うため、stm32f0xx-hal-master\examples内のblinky.rsを使用します。デフォルトはPA1を点滅させるコードとなっているため、NucleoF042に搭載されているLED、LD3のポートPB3に書き換えます。blinky.rsをテキストエディタで開いて、17行目付近を下記のように書き換えます。

let gpiob = p.GPIOB.split(&mut rcc);
// (Re-)configure PB3 as output
let mut led = cortex_m::interrupt::free(|cs| gpiob.pb3.into_push_pull_output(cs));

Eコードコンパイル
初回は関連パッケージのダウロードとビルドが行われるため、時間が多少多くかかります。下記のコマンドでblinkyサンプルコードをコンパイルします。

cargo build --example blinky --release --features=stm32f042

Fバイナリデータ変換
コンパイルされたバイナリデータは下記のフォルダに生成されます。フォルダ内の拡張子がないblinkyというファイルがRustバイナリデータです。
\stm32f0xx-hal-master\target\thumbv6m-none-eabi\release\examples

下記のコマンドを打って、Rustバイナリデータをbinファイルに変換します。

arm-none-eabi-objcopy -O binary blinky blinky.bin

Gbinファイル書き込み
ST-LINK UtilityやCubeProgrammer等でbinファイルを書き込みます。LD3が点滅すれば成功です。for文の回数等を変更して点滅周期が変えたりしてみてください。

blink.jpg


今回はWindows環境でRust版STM32HALライブラリを使用してサンプルコードのLチカを行いました。Rustは安全性、速度、並行性に優れているということはよく言われますが、それ以外にメリットとして、組み込み系にもパッケージ管理が使えるようになったことです。パッケージ管理はLinuxやnodejs等では当たり前ですが、組み込み系では足りないファイルを追加したり、手動で管理する必要がありました。Rustの場合はパッケージ管理がデフォルトで有効になっているため、必要な記述を事前にしておけば、ビルド時に足りないファイルが自動的に取得されます。組み込み系で頻繁にパッケージのバージョンが変わるor変えることはあまりないかもしれませんが、足りないファイルが自動的に取得できるのはパッケージ管理の大きなメリットだと思いました。Rust版STM32HALライブラリは完成度も高いので、今後、ST公式でもサポートされればさらに面白くなると思いました。

posted by Crescent at 00:00| Comment(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする

2020年05月09日

外部高速ADC接続方法検討

アナログ信号をマイコンに取り込む場合、内蔵ADCを使用することが多いですが、入力電圧範囲、分解能、精度、絶縁、同時サンプリングといった制約でADCを外付けすることが必要な場合があります。低速な外部ADCの場合はI2C(~1MHz)やSPI(~数10MHz)の通信帯域で十分ですが、マルチチャンネルで高速なADCの場合はSPIでも帯域が足りないため、パラレルバスを採用したADCが多くなります。

一方、AnalogDevicesのLTC2358、LTC2458といった高速ADCではSPIと互換性を保ちつつ、パラレルバス接続するインターフェースを採用しています。具体的には一般的なSPI接続のCS、CLK、MOSI、MISOに加えて、MISO2、MISO3といった具合にMISOがパラレルになっています。MISOをパラレルにして増やすことで1回の通信時間を短くし、高速サンプリングを実現しています。今回はMISOがパラレルになっている外付けADCをどうSTM32マイコンと接続するか検討してみました。

検討に際してざっと思いつく通信方式を並べてみました。

方法@ QSPIを利用
方法A FMC/FSMCを利用
方法B GPIOで個別制御(bit-bang)
方法C 内蔵ADCでSDO読み込み 
方法D SPIを複数接続


方法@ QSPIを利用
STM32マイコンの一部にはQSPIインターフェスがあります。QSPIは主にNOR Flash接続するインタフェースとして使われています。QSPIは通信ラインが双方向です。一方、今回のインタフェースは通信方向は常に一定で決まっており、双方向ではありません。QSPIは次にデータを書き込むのか読み込むのかマスターからコマンドを送って通信方向を決定します。QSPIを個別にレジスタで制御して通信方向を決定できれば実現可能かもしれませんが、STM32マイコンのQSPI HALライブラリがNOR Flash用になっているため、あまり適切ではなさそうです。

方法A FMC/FSMCを利用
LCDや外付けRAM、パラレルバスとしてFMC/FSMCインタフェースがあります。FMCとFSMCの違いはFSMCにSDRAMリフレッシュ機能等をつけたのがFMCでFMCの方が高機能です。LCDのように一方的にマイコンからLCDに書き込む場合やメモリのようにリードライトを切り替えて処理する場合にFMC/FSMCが最適です。今回の場合はパラレルインタフェースでありながら、ADCに変換指令を送信しつつ、ADC変換結果を受信するため、同時に送受信を行う必要があります。FMC/FSMCがパラレルバスの同時送受信に対応しているか確認が必要です。また、既にバスラインをSDRAMで使用する予定があるため、この方法も厳しいと判断しました。


方法B GPIOで個別制御(bit-bang)
GPIOを個別に制御してソフトウェアSPIを実現する方法です。この方法であれば、MISOがパラレルになっている外付けADCでも対応可能です。ただ、課題として個別にGPIOを制御するために時間がかかるため、数MHzは容易に実現できそうですが、数10MHz以上のクロックを実現できるか分かりません。STからパラレルバス用のDMA転送ライブラリが提供されており、DMA転送もできなくはなさそうです。GPIOの個別制御は最終手段として取っておきたいと思います。


方法C 内蔵ADCでSDO読み込み 
SPIでCS、CLK、MOSI、MISOの通信に加えて、それ以外のMISO2、MISO3といったバスをADCで同時に取り込んでデジタル化する方法です。邪道な方法ですが、STM32マイコンのリソースを活用して実現するという意味では実現できそうです。問題点として取り込んだアナログ値をデジタル化するために処理が必要で連続で取り込むためにはRAMの制約を大きく受けます。ADCで取り込むことはできなくはなさそうですが、マイコンのリソース制約を大きく受けるため非現実的だと思いました。

方法D SPIを複数接続
STM32マイコンではSPIが複数利用できます。1つのSPIを送受信マスタとしてADCと接続し、それ以外を受信専用スレーブとしてのADCのSDOxと接続します。具体的には下記のような配線になります。複数SPIを同時に利用するためにDMA転送は必須です。事前に受信専用スレーブをDMA受信設定してから、マスタSPIから送信することで同時通信を実現します。

また、MISOがパラレルになっている外付けADCで今回使用予定のLTC2458はSDOのレーン数を常に8ライン使用する必要はなく、2レーン、4レーン選択できるようになっています。SPIが8つなくても、2つ、4つであれば、中規模以上のSTM32マイコンであれば搭載されているため、十分実現できそうです。

SPIの機能をそのまま使用しているため、通常はSPIを1レーンで使用し、帯域が必要な場合にのみ複数レーンを使用するといった切り分けが容易に実現できます。

ADC.jpg



実際に方法D SPIを複数接続で検討してみました。下記のようにDMA転送のPriorityを設定しました。また、SPIはNSS Signal TypeのHardwareを選択しました。

adc_dma.jpg


処理をprintfで出力すると下記のようになりました。STM32F411で検証した結果、Priority設定にもよりますが、SPI2が先に呼び出される前にSPI4、3が呼び出されました。

Init OK
Rx Cplt Callback:SPI4
Rx Cplt Callback:SPI3
RxTx Cplt Callback:SPI2
Rx Cplt Callback:SPI5

LTC2458は下記のように各SDOから順番循環して各チャンネルのAD変換結果が出力されます。1レーンで使用する場合はSDO0から1x8回データ取得し、2レーンの場合はSDO0とSDO4から2x4回データ取得、4レーンの場合はSDO0、SDO2、SDO4、SDO6の4x2回データ取得する感じです。

SDO0:[CH0]、[CH1]、[CH2]、[CH3]、[CH4]、[CH5]、[CH6]、[CH7]...
SDO1:[CH1]、[CH2]、[CH3]、[CH4]、[CH5]、[CH6]、[CH7]、[CH0]...
SDO2:[CH2]、[CH3]、[CH4]、[CH5]、[CH6]、[CH7]、[CH0]、[CH1]...
.
.
.

8回分のAD変換結果を書き出した結果が下記の通りになり、同じ値が循環して取得できていることから複数のSDOを読み込めていることが確認できました。

SDO0: 10459, 55041, 8220, 1782, 133, 64062, 62606, 61580,
SDO2: 8220, 1782, 133, 64062, 62606, 61580, 10459, 55041,
SDO4: 133, 64062, 62606, 61580, 10459, 55041, 8220, 1782,
SDO6: 62606, 61580, 10459, 55041, 8220, 1782, 133, 64062,


海外サイトでも同様の議論があり、実際に動いたかどうかまで書かれていませんでしたが、方法D SPIを複数接続で実現できることが確認できました。SPIの機能をそのまま利用しており、DMA転送や数10MHz程度のクロックを容易に実現できるため、DMA転送を利用した方法D SPIを複数接続は外部高速ADC接続方法として良いと思いました。

posted by Crescent at 00:00| Comment(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする

2020年04月25日

STM32 FMCを用いたSDRAMの追加設定

今回はSTM32マイコンのFMCを用いたSDRAM設定を紹介します。基本的な設定はCubeMXで自動で生成されるため、最低限設定は完了しています。一方で実際にSDRAMを使いこなすためには追加設定が必要です。

追加設定の中でもメモリマップモードで使用する方法について紹介します。メモリマップモードの場合、変数宣言時に領域を指定することで変数をSDRAM上に配置することが可能です。SDRAMと内蔵RAMの差を意識せずに取り扱うことができます。

今回はSDRAMがボード上に既に実装されているSTM32F7 Discoveryを使用しました。


@リンカスクリプト追加
SDRAMの領域をリンカに追加します。プロジェクトファイル直下の***_FLASH.ldファイルをテキストエディタで編集します。Eclipseでそのまま開くと文字コードが異なるため、テキストエディタで編集します。Eclipseで編集する場合はEclipseのファイルプロパティからUTF-8を選択してから編集してください。

61行目付近に赤字の文字列を追加します。STM32F7 Discoveryは128Mbitのメモリを搭載していますが、データ線が16bit分しか配線されていないため、半分の64Mbit、つまり8MByteが使用できます。


/* Specify the memory areas */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K
SDRAM(rw) : ORIGIN = 0xC0000000, LENGTH = 8M
}


160行目付近に赤字の文字列を追加します。
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM

.sdram (NOLOAD) :
{
. = ALIGN(4);
*(.sdram)
*(.sdram.*)
. = ALIGN(4);
} >SDRAM

Aメモリ初期化コード追加
メモリ初期化コードを追加します。今回はmain.cのPrivate user code部分に下記のdefineと初期化関数を追加しました。下記のコードはSTM32CubeMXのファーム、STM32Cube\Repository\STM32Cube_FW_F7_V***\Drivers\BSP\STM32746G-Discovery内、stm32746g_discovery_sdram.c、stm32746g_discovery_sdram.hを参考にしています。

/* Private user code ---------------------------------------------------------*/

#define SDRAM_DEVICE_ADDR ((uint32_t)0xC0000000)
#define SDRAM_DEVICE_SIZE ((uint32_t)0x800000) /* SDRAM device size in MBytes */
#define SDRAM_MODEREG_BURST_LENGTH_1 ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_LENGTH_2 ((uint16_t)0x0001)
#define SDRAM_MODEREG_BURST_LENGTH_4 ((uint16_t)0x0002)
#define SDRAM_MODEREG_BURST_LENGTH_8 ((uint16_t)0x0004)
#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED ((uint16_t)0x0008)
#define SDRAM_MODEREG_CAS_LATENCY_2 ((uint16_t)0x0020)
#define SDRAM_MODEREG_CAS_LATENCY_3 ((uint16_t)0x0030)
#define SDRAM_MODEREG_OPERATING_MODE_STANDARD ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE ((uint16_t)0x0200)
#define REFRESH_COUNT ((uint32_t)0x0603) /* SDRAM refresh counter (100Mhz SD clock) */
#define SDRAM_TIMEOUT ((uint32_t)0xFFFF)


void SDRAM_Initialization_sequence(SDRAM_HandleTypeDef *sdramHandle, uint32_t RefreshCount)
{

  __IO uint32_t tmpmrd = 0;
  FMC_SDRAM_CommandTypeDef Command;

  /* Configure a clock configuration enable command */
  Command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;
  Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber = 1;
  Command.ModeRegisterDefinition = 0;
  /* Send the command */
  HAL_SDRAM_SendCommand(sdramHandle, &Command, SDRAM_TIMEOUT);

  /* Insert 100 us minimum delay */
  /* Inserted delay is equal to 1 ms due to systick time base unit (ms) */
  HAL_Delay(1);

  /* Configure a PALL (precharge all) command */
  Command.CommandMode = FMC_SDRAM_CMD_PALL;
  Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber = 1;
  Command.ModeRegisterDefinition = 0;
  /* Send the command */
  HAL_SDRAM_SendCommand(sdramHandle, &Command, SDRAM_TIMEOUT);

  /* Configure an Auto Refresh command */
  Command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
  Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber = 8;
  Command.ModeRegisterDefinition = 0;
  /* Send the command */
  HAL_SDRAM_SendCommand(sdramHandle, &Command, SDRAM_TIMEOUT);

  /* Program the external memory mode register */
  tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 |\
  SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |\
  SDRAM_MODEREG_CAS_LATENCY_2 |\
  SDRAM_MODEREG_OPERATING_MODE_STANDARD |\
  SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
  Command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE;
  Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber = 1;
  Command.ModeRegisterDefinition = tmpmrd;
  /* Send the command */
  HAL_SDRAM_SendCommand(sdramHandle, &Command, SDRAM_TIMEOUT);

  /* Set the refresh rate counter */
  /* Set the device refresh rate */
  HAL_SDRAM_ProgramRefreshRate(sdramHandle, RefreshCount);

}

/* USER CODE END 0 */


Bメモリ初期化関数追加
先ほど、追加したメモリ初期化関数をメモリ関連のペリフェラル初期化後に実行します。STM32CubeMXではFMCを有効化するとペリフェラル初期化コードが自動で生成されます。main.c内のmain関数内、MX_FMC_Init()がメモリ関連のペリフェラル初期化関数に該当します。MX_FMC_Init()内ではメモリタイミング等のFMC関連の設定がHAL_SDRAM_Init()で行われます。FMC関連の設定後に先ほどのメモリ初期化関数を追加します。ユーザーコードを記入する箇所、「USER CODE BEGIN FMC_Init 2」の中に下記のように関数を追加します。

 /* USER CODE BEGIN FMC_Init 2 */
  SDRAM_Initialization_sequence(&hsdram1, REFRESH_COUNT);
 /* USER CODE END FMC_Init 2 */

Cグローバル変数追加
attributeを追加することでSDRAMに変数を配置します。コンパイラがarm-gccの場合は下記のようにattributeを指定します。

uint32_t sdram_data[85400]__attribute__((section(".sdram")));

arm-gccに限定されない場合はコンパイラに応じて対応できるようにifdefを使用して下記のように変数を定義します。

#if defined ( __ICCARM__ ) // !< IAR Compiler
#pragma location=0xC0000000
 static __no_init uint32_t sdram_data[85400];
#elif defined ( __CC_ARM ) //!< Keil Compiler
 uint32_t sdram_data[85400] __attribute__((at(0xC0000000)));
#elif defined ( __GNUC__ ) // !< GNU Compiler
 uint32_t sdram_data[85400]__attribute__((section(".sdram")));
#endif


以上の設定で通常の内蔵RAM上の変数と同じように使用することができます。基本的な設定はCubeMXで自動で生成されるため、最低限設定は完了していますが、メモリマップモードといったシームレスにSDRAMを使うためには追加の設定が必要です。なお、SDRAMの初期化タイミング等の設定についてはこちらのサイトで丁寧に説明がされています。また、前回のメモリ領域空き確認方法を設定することでSDRAMの使用率も可視化することができます。合わせて確認してみてください。
posted by Crescent at 00:00| Comment(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする

2020年03月07日

Node-Redを用いたRTCの設定と読み込み

前回紹介したNode-RedからのI2Cデバイス制御する方法に続いて、応用例を紹介します。今回はDS1307搭載のRTCモジュールUSB-I2C変換アダプタを使用して、RTCの読み込みと設定をしてみました。


Arduino等でRTCモジュールを使用するプロジェクトとして時計やちょっとしたデータロガーなどが考えられます。このような用途では主要機能は時刻を読み込むだけなので、RTCモジュールに時刻を設定する方法が悩みどころです。パソコンから時刻をUARTで送信するか、ディスプレイとスイッチをつけるなど少し作りこみが必要です。個人レベルであれば、ベタでコードに時刻を書いて、Arduino等から書き込むといったことが多いと思います。

そこでUSB-I2C変換アダプタを使えば、PCから簡単にRTCモジュールに時刻を設定することができます。Node-RedがPC上で動いている特徴を生かして、時刻を書き込む際にはPC上の時刻をjavascriptで取得してRTCに書き込み仕様にしています。その都度、時刻を書き換えてRTCモジュールに書き込む必要がありません。


Node-Red_DS1307.jpg

読み込みはRTCから取得した情報をpayloadに格納してDebugに表示する仕様です。今回のサンプルコードはこちらで公開しています。活用してみてください。

なお、今回はDS1307を搭載したRTCモジュールを使用しましたが、個人的にDS1307を推奨しません。DS1307は時刻精度が悪く、数か月で数分以上時刻がずれます。また、チップのESD対策が悪く、コネクタ未接続状態、特に電池を外した状態で扱うと静電気ですぐに破損or動作不良になります。気づいたら時刻が読み込めない、時刻が書き込めない、変な値が読み出されるといった症状に悩まされます。
 DS1307の代わりに温度補償水晶発振器(TCXO)を内蔵したDS3231を推奨します。ピン配置、パッケージが異なるものの、DS1307とコマンド互換があるため、置き換え可能です。DS3231であれば数か月で数秒程度のずれに収まり、DS1307に比べてESD対策もされている感じです。

posted by Crescent at 00:00| Comment(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする

2020年02月29日

Node-Redを用いたEEPROM読み込みと書き出し

前回紹介したNode-RedからのI2Cデバイス制御する方法に続いて、応用例を紹介します。

Node-Redはノードを結び付けてフローを作成してプログラミングできる他にファイルの読み込み、書き出しをすることが可能です。今回はMicrochip製のI2C接続EEPROM 24LC512USB-I2C変換アダプタを使用してメモリデータのファイルへの読み出しとファイルからの書き出しをしてみました。プログラムのforループを書き直すことで24LC64, 256にも対応可能です。また、アドレスをバンク毎で書き換えれば24LC1024にも対応できます。なお、アドレスセレクトのA0、A1、A2はすべてGNDに接続しています。


下記のようにNode-Redでコードを書いてました。

full_read.jpg

16byteずつ読み込んでcsvファイルに出力します。NodejsPortableの場合はNodeJSPortable\Dataのフォルダにmem_read.csvが生成されます。

read_csv.jpg


処理のラグが多く、24LC512の場合はすべて読み出すまでに10分程度要しました。



csvファイルから読み込んで書き込むツールも同様に作成してみました。16byte毎に0~255の数字を書き込んだcsvファイル(mem_write.csv)を用意し、Node-Redから読み込んで24LC512に書き込みます。


full_write.jpg

読み込み同様に16byte毎のcsvファイル(mem_write.csv)から8byte毎に書き込みを実行します。NodejsPortableの場合はNodeJSPortable\Dataのフォルダにcsvファイル(mem_write.csv)を配置します。シリアルI2C変換SC18IM700のバッファサイズが16byteのため、開始や終了コマンドを含めると最大10byte毎の書き込みとなりますが、キリが悪いので8byte毎に書き込みを実行しました。

write_csv.jpg

読み込み同様に書き込みには10分ほど要しました。一般的には書き込みの方が時間を要しますが、今回のシステム構成の場合は書き込みの方が若干早く処理が終わりました。この理由として読み込みの場合、Node-Redのシリアル送信処理をしてからシリアル受信するまで待機する必要があります。一方、書き込みの場合は一方的にシリアル送信処理のみなのでシリアル受信するまで待機する必要ありません。シリアル受信はOS側のFIFOやNode-Red側の割込み発生周期などが影響し、シリアル受信してもすぐに受信処理が実行されません。現在のコードは受信処理が16byte毎に発生しますが、少し大きなサイズでまとめて受信すれば、受信処理の待機時間の回数を減らせるため、全体の処理時間を短くできると思われます。


正直、実用的なバックアップ、リストアツールではありませんが、ちょっとデータを保存しておきたいといった場合や複製したいという場合には有効だと思いました。I2Cアドレスやサイズを変更すれば24LC512以外でもI2CのEEPROMであれば利用できます。今回のサンプルコードはこちらで公開しています。活用してみてください。

posted by Crescent at 00:00| Comment(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする