« 2010年8月 | トップページ | 2010年10月 »

2010年9月の記事

.NET Micro FrameworkでSPIを使う

NETMFのSPIを使って、ATMELのAT45DB161D Data Flashを動かしてみました。NETMFのSPIライブラリの使い方がよく分からず苦戦したのですが、FEZ Domino用のドライバをベースに手直しを行うことでやっと動作に成功しましたので、今回の試行錯誤から分かったことを記載します。

コードはFEZ Domino用に作ってありますが(GHIのコードをベースにしているため)、ちょっと手直しするだけで、Netduinoでも動作可能です。


NETMFでのSPIコマンドの発行方法

SPIデバイスとの通信は、コマンドを発行してレスポンスを受け取る形態が一般的です。また、ArduinoやmbedのSPIライブラリでは、1 Frame(8bitとか)のデーターをSPIデバイスと送受信する基本的なメソッドが用意されているのみです。SPIは仕組み上データーを受信するためには何か適当な値(ダミーデーター)をデバイスに送り込む必要があります。Arduinoやmbedでは、SPIライブラリのメソッドを何回も呼び出して、コマンド送信+ダミーデーター送信(レスポンスを受信するため)をframe単位にくり返します。

最初は(Netdunoで開始)、NETMFのSPIライブラリを上記Arduino/mbedの作法で使っていたのですが、NetduinoではSPI Flashのステータスレジスタが読めないという事象に悩まされました。すなわちNetduinoでは、コマンドを送信するメソッド呼び出しと、レスポンス(ステータス値)を受信するためのメソッド呼び出しを2回に分けると、正しくデーターが受信できませんでした。なぜか(今でも原因はよく分からないのですが)、FEZ Dominoだと上記のコードが正しく動作するため、NETMFではライブラリ呼び出しの作法が異なることに気がつけませんでした。

自力で問題が解決できずNetduinoのフォーラムに質問したら、FEZ DominoにAT45DB161Dを使ったFlash Shieldがあるので、そのドライバーが使えるのではというコメントをいただき、試してみたら一発で動きました。ドライバ自体は後で示す問題があったのですが、コードを読んでみると、NETMFではコマンド・レスポンスに対応した一本のバイト列を作って、1回のSPIメソッド呼び出しで処理を済ませています。以下の図に、NETMFでの処理イメージを示します。

SpiDataFormat

今回のように、受信データーが比較的でかい場合はRAMの消費量が多くなりますが、SPIメソッドの呼び出し回数を減らすというのがNETMFの設計思想(お作法)のようです。勝手な想像ですが、マネージドコードのオーバーヘッドが大きいNETMF故のパフォーマンス対策なのかもしれません。ひょっとすると、コマンド送信・レスポンス受信で処理を分けた最初のコードがFEZ Dominoで動作するがNetduinoでは動作しなかった原因は、MCUの処理性能差なのかもです。

 
ドライバコードとサンプルアプリ

今回使用したAT45DB161Dドライバのコードを以下に示します。AT45DB161Dへの書き込みは、1)SRAMの一次バッファへの書き込み、2)バッファからフラッシュメモリセルへの書き込みという2段階で行う必要があるのですが、GHIのオリジナルコードは1)のバッファ書き込みしか行っていませんでした。読み出しはその逆で、メモリセルからバッファに読み出し、バッファからMCUに転送する必要があるのですが、やはりバッファを読んでいるのみでした。GHI提供のサンプルアプリは同じページを読み書きしているだけなので、見た目動いているように見えるという、おいおいなヤツでした。

そのため、上記のバッファを介した読み書き部分を修正しました。Arduino RSSリーダーでAT45DB161Dを使った経験が生きたということで、、

/*
Copyright 2010 GHI Electronics LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 

Modofied by todotani 2010/9/28 
*/

using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.Hardware;

namespace GHIElectronics.NETMF.FEZ
{
    public static partial class FEZ_Shields
    {
        public static class FlashShield
        {
            const byte Status_Register_Read = 0xD7;
            const byte Continuous_Array_Read = 0x03;    // Continuous Array Read (Low Frequency). Changed from Legacy command (0xE8)
            const byte Buffer_1_Read = 0xD1;            // Buffer 1 Read (Low Frequency)
            const byte Buffer_1_Write = 0x84;           // Buffer 1 Write
            const byte DB_BUF1_PAGE_ERASE_PGM = 0x83;   // Buffer to Main Memory Page Program with Built-in Erase
            const byte AT45DB161D_density = 0x2C;
            static private uint pageSize = 0;
            static private SPI Spi = null;

            static public void Initialize()
            {

                SPI.Configuration SPICfig = new SPI.Configuration((Cpu.Pin)FEZ_Pin.Digital.Di10, 
                    false, 0, 0, false, true, 16000, SPI.SPI_module.SPI1);
                Spi = new SPI(SPICfig);
                byte status = ReadStatusRegister();
                if ((status & 0x3C) != AT45DB161D_density)
                    throw new Exception("Not Supported Device or DataFlash");
                if ((status & 0x01) == 0x01)
                {
                    pageSize = 512;
                }
                else
                {
                    pageSize = 528;
                }
            }

            static public uint PageSize
            {
                get
                {
                    return pageSize;
                }
            }

            static private void SendCommandAndData(byte[] command, byte[] data)
            {
                if (command != null && data != null)
                {
                    byte[] temp = new byte[command.Length + data.Length];

                    Array.Copy(command, temp, command.Length);
                    Array.Copy(data, 0, temp, command.Length, data.Length);
                    Spi.WriteRead(temp, temp);
                    Array.Copy(temp, command, command.Length);
                    Array.Copy(temp, command.Length, data, 0, data.Length);

                    //for (int i = 0; i < command.Length; i++)
                    //{
                    //    temp[i] = command[i];
                    //}
                    //for (int i = 0; i < data.Length; i++)
                    //{
                    //    temp[i + command.Length] = data[i];
                    //}
                    //Spi.WriteRead(temp, temp);
                    //for (int i = 0; i < command.Length; i++)
                    //{
                    //    command[i] = temp[i];
                    //}
                    //for (int i = 0; i < data.Length; i++)
                    //{
                    //    data[i] = temp[i + command.Length];
                    //}
                }
                else
                {
                    if (command != null)
                        Spi.WriteRead(command, command);
                    if (data != null)
                        Spi.WriteRead(data, data);
                }
            }

            static private byte ReadStatusRegister()
            {
                byte[] cmd = new byte[2] { Status_Register_Read, 0 };
                SendCommandAndData(cmd, null);
                return cmd[1];
            }

            static public void ReadPage(uint PageNumber, byte[] buffer)
            {
                if (buffer.Length < PageSize)
                    throw new Exception("Buffer Size is smaller than Page Size");

                byte[] cmd = new byte[4];

                cmd[0] = Continuous_Array_Read;
                /*
                 * 3 address bytes consist of :
                 *  - 2 don't care bits
                 *  - 12 page address bits (PA11 - PA0) that specify the page in 
                 *    the main memory to be written
                 *  - 10 buffer address bits (BA9 - BA0) --> Allways set to 0
                 */
                cmd[1] = (byte)(PageNumber >> 6);
                cmd[2] = (byte)(PageNumber << 2);
                cmd[3] = 0;
                SendCommandAndData(cmd, buffer);
                for (int timeout = 0; timeout < 100; timeout++)
                    if ((ReadStatusRegister() & 0x80) > 0)
                        return;
                throw new Exception("Error reading data");

            }

            static public void WritePage(uint PageNumber, byte[] buffer)
            {
                if (buffer.Length < PageSize)
                    throw new Exception("Buffer Size is smaller than Page Size");
                byte[] cmd = new byte[4];

                // Write data to buffer 1
                cmd[0] = Buffer_1_Write;
                /*
                 * 3 address bytes consist of :
                 *  - 14 don't care bits
                 *  - 10 buffer address bits (BA9 - BA0) --> Allways set to 0
                 */
                cmd[1] = 0;
                cmd[2] = 0;
                cmd[3] = 0;
                SendCommandAndData(cmd, buffer);

                cmd[0] = DB_BUF1_PAGE_ERASE_PGM;
                /*
                 * 3 address bytes consist of :
                 *  - 2 don't care bits
                 *  - 12 page address bits (PA11 - PA0) that specify the page in 
                 *    the main memory to be written
                 *  - 10 don’t care bits
                 */
                cmd[1] = (byte)(PageNumber >> 6);
                cmd[2] = (byte)(PageNumber << 2);
                cmd[3] = 0;
                SendCommandAndData(cmd, null);
                for (int timeout = 0; timeout < 100; timeout++)
                    if ((ReadStatusRegister() & 0x80) > 0)
                        return;
                throw new Exception("Error writeing data");
            }
        }
    }
}

AT45DB161Dはバッファを2面持っており、ダブルバッファにすることで読み書きを高速化することができますが、このコードではバッファは1面のみを使用しています。また、読み出しはバッファを使用せずメモリーセルのページアドレスを指定して1ページ分のデーターを直接MCUに転送するContinuous Array Readという機能を使っています。この機能を使えば数百KBあるようなでかいデーターを一気に読み出すような用途にも使えます(FEZ DominoやNetduioには読んだデーターを格納できるRAMがありませんが、、)。

ライブラリを使用したサンプルアプリを以下に示します。

using System;
using System.Threading;
using Microsoft.SPOT;
using GHIElectronics.NETMF.FEZ;

/* --------------------------------------------------------
 * Pin assignment FEZ Domio -- AT45DB161D
 *            CS    D10     -- 4 (Pull-up with 10k ohm R)
 *            RESET RESET   -- 3 (Pull-down with 10k ohm R)
 *            MOSI  D11     -- 1
 *            MISO  D12     -- 8
 *            SCK   D13     -- 2
-----------------------------------------------------------*/

public class Program
{
    public static void Main()
    {
        // Initialize DataFlash
        FEZ_Shields.FlashShield.Initialize();
        Debug.Print("Page size is " + FEZ_Shields.FlashShield.PageSize);
        byte[] write_buffer = new byte[FEZ_Shields.FlashShield.PageSize];
        byte[] read_buffer = new byte[FEZ_Shields.FlashShield.PageSize];

        // Test storing data
        for (uint page = 0; page < 16; page++)
        {
            Debug.Print("Page " + page.ToString() + " Write");
            for (int i = 0; i < FEZ_Shields.FlashShield.PageSize; i++)
            {
                write_buffer[i] = (byte)page;
            }
            FEZ_Shields.FlashShield.WritePage(page, write_buffer);
        }

        // Test retrieving data
        for (uint page = 0; page < 16; page++)
        {
            Debug.Print("Page " + page.ToString() + " Read");
            FEZ_Shields.FlashShield.ReadPage(page, read_buffer);
            for (int i = 0; i < 16; i++)
            {
                Debug.Print(read_buffer[i].ToString());
            }
        }
    }
}

おわりに

今回はNetduinoのフォーラムメンバーに助けられました。最初、FEZで動くのにNetduinoで動かない事象に悩んだ際は、AT45DB161Dはあまりポピュラーなデバイスではなく使用例が少ないと思ったため、質問するにしてももう少し事象を切り分けてからと思ったのですが、結果的には(既に実装例があり)もっと早く聞いておくのが正解でした。

またまた、GHIの成果物をパクッてしまいましたが、オリジナルコードの問題点をFEZ Dominoのフォーラムにフィードバックしておきましたのでご容赦をお願いします・・・GHIのドライバは当初、GHI製品でのみ使えるという論争があったみたいですが、最終的にApacheライセンスになり改変・再配布が自由になったので、Netduinoでの利用も許されるのかと思います。

mbedのSPIライブラリはデーターのフレーム長を4~16bitの間で1bit単位に指定できますが、NETMFは8 or 16bitの2択になります。そのため、Nokia液晶のようにフレーム長が9bitのデバイスは使えません。Netduinoのフォーラムでも議論が上がっていましたが、今のところサポート予定はないようです。Nokia液晶を使ってみたかったのですがこの点は残念です。

参考文献


2010/10/2更新:

SendCommandAndData()メソッドで配列のコピーをforループで行っていたのですが、Array.Copy()メソッドの呼び出しに変更しました(FEZ Dominoフォーラムのご指摘により)。Array.Copyの方が劇的に早くなります(Netduinoで要素数500の配列コピーを1000回繰り返す処理で比較すると200倍近い差が出ます)。

« 2010年8月 | トップページ | 2010年10月 »

2018年10月
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
無料ブログはココログ