カテゴリー「Arduino」の記事

Intel GalileoでCoreMark

Intel Galileoで、以前LPCXpressoで動かしたCoreMark(CPUコアのベンチマークテスト)を試してみました。比較として、Raspberry Piでも。さて、結果はいかに。

IMG_0349


GalileoへのCoreMarkの移植

GalileoのArduino形式スケッチにCoreMarkを移植する際に工夫した点は以下です:

  • 表示に使うprintfは、下地で動いているるLinuxのprintfがリンクできることを発見(773mbarさんのIntel Galileoでprintfを使うを参照)
  • CoreMark本体(Cのコード)はArduinoのLibraryとしてリンク

スケッチのイメージは以下の通りです:

#include <stdio.h>
#include <coremark.h>

void setup() {
  stdout = freopen("/dev/ttyGS0", "w", stdout);
}

void loop() {
  printf("Run CoreMark\n");
  mainCoreMark();
  printf("CoreMark End \n\n");    
}

5行目のコードで、printfの出力をUSBで接続したPCのIDEのシリアルモニターに出力できます。CoreMarkの本体をloopの中で呼んでいるのは、CPU使用率を100%張り付きにして温度変化を調べるためです。CoreMarkの実行結果は以下の通り:

2K performance run parameters for coremark.
CoreMark Size    : 666
Total ticks      : 112070000
Total time (secs): 112.070000
Iterations/Sec   : 446.149728
Iterations       : 50000
Compiler version : 4.7.2
Compiler flags   : #pragma O3, Otime
Memory location  : STACK
seedcrc          : 0xe9f5
[0]crclist       : 0xe714
[0]crcmatrix     : 0x1fd7
[0]crcstate      : 0x8e3a
[0]crcfinal      : 0xa14c
Correct operation validated. See readme.txt for run and reporting rules.
CoreMark 1.0 : 446.149728 / 4.7.2 #pragma O3, Otime / STACK

スコアは、446で以外と伸びていません。LPC1769 120MHzで191出ていましたので、Pentiumクラス400MHz動作のQuark X1000ならもっと数字が伸びてもよいと思うのですが。CoreMarkの設定方法が正しくないのか・・

CoreMarkを連続して動作させると、以下の通りCPU使用率は100%に張り付き、スケッチのCPU使用率が98%になります。

CPU:  98% usr   1% sys   0% nic   0% idle   0% io   0% irq   0% sirq
Load average: 1.00 1.00 0.87 2/44 1416
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
 1333  1330 root     R    18300   8%  98% /sketch/sketch.elf /dev/pts/0 /dev/ttyS0
 1416  1385 root     R     1268   1%   1% top
 1383  1304 root     S     3316   1%   1% {sshd} sshd: root@pts/1
  200     2 root     SW       0   0%   0% [kworker/0:1]
 1304     1 root     S     3256   1%   0% /usr/sbin/sshd
 1385  1383 root     S     1968   1%   0% -sh
 1328     1 root     S     1852   1%   0% {clloader.sh} /bin/sh /etc/init.d/clloader.
 1296     1 messageb S     1464   1%   0% /usr/bin/dbus-daemon --system
 1319     1 root     S     1264   1%   0% /sbin/syslogd -n -O /var/log/messages
 1325     1 root     S     1264   1%   0% /sbin/getty 115200 ttyS1
 1326     1 root     S     1264   1%   0% /sbin/getty 38400 tty1
 1276     1 root     S     1264   1%   0% udhcpc -R -n -p /var/run/udhcpc.eth0.pid -i
 1322     1 root     S     1260   1%   0% /sbin/klogd -n
 1330  1328 root     S      836   0%   0% /opt/cln/galileo/clloader --escape --binary
    1     0 root     S      796   0%   0% init [5]
 1327     1 root     S      768   0%   0% /opt/cln/galileo/galileo_sketch_reset -v
    6     2 root     SW       0   0%   0% [kworker/u:0]
  936     2 root     SW<      0   0%   0% [loop0]
    3     2 root     SW       0   0%   0% [ksoftirqd/0]
  529     2 root     SW       0   0%   0% [mmcqd/0]
  752     2 root     SW<      0   0%   0% [kworker/0:1H]
   10     2 root     SW       0   0%   0% [kdevtmpfs]
   12     2 root     SW       0   0%   0% [kworker/u:1]
    5     2 root     SW<      0   0%   0% [kworker/0:0H]
   11     2 root     SW<      0   0%   0% [netns]
  119     2 root     SW       0   0%   0% [bdi-default]
  121     2 root     SW<      0   0%   0% [kblockd]
    2     0 root     SW       0   0%   0% [kthreadd]
    4     2 root     SW       0   0%   0% [kworker/0:0]
  384     2 root     SW       0   0%   0% [fsnotify_mark]
  396     2 root     SW<      0   0%   0% [crypto]

このときのCPU温度は72℃で、Lチカ(CPU使用率2%)の時と変わりません。ということは、GalileoのCPUはアイドル状態でも全く電力制御が動いていないということになります。今一な動作ですが、下地のLinuxカーネルのチューニングの問題でしょうか。Raspberry Piだと、変化は少ないですが、アイドル状態からフル負荷に上げると、CPU温度が44℃程度から50℃台に上昇するため、こちらは電力制御が若干行われていると思われます。


Raspberry PiのCoreMark

おまけとして、Raspberry PiのCoreMark結果を以下に示します:

2K performance run parameters for coremark.
CoreMark Size    : 666
Total ticks      : 36340000
Total time (secs): 36.340000
Iterations/Sec   : 1375.894331
Iterations       : 50000
Compiler version : 4.6.3
Compiler flags   : #pragma O3, Otime
Memory location  : STACK
seedcrc          : 0xe9f5
[0]crclist       : 0xe714
[0]crcmatrix     : 0x1fd7
[0]crcstate      : 0x8e3a
[0]crcfinal      : 0xa14c
Correct operation validated. See readme.txt for run and reporting rules.
CoreMark 1.0 : 1375.894331 / 4.6.3 #pragma O3, Otime / STACK

Raspberry PiはCoreMark値 1375とGalileoのトリプルスコアで圧勝。クロック周波数差(700MHz vs 400MHz)を考慮しても、Raspberry PiのARMコアの方がスコアが高いです。使ったコードは同じで、両方ともLinux配下で動かしていますから条件は同じだと思います。


まとめ

こうして比較してみると、(GalileoでのCoreMarkの動かし方の是非がありますが)GalileoのQuark X1000 CPUの性能は今一な感じです。

  CPU clock CoreMark値
Intel Galileo 400 MHz 446.14
Raspberry Pi 700 MHz 1375.89


参考文献

Intel Galileoのインプレッション

Blog名更新後の初エントリ(久しぶり・・)は、Intel Galileoです。昨年スイッチサイエンスさんに予約して、1月15日に無事届きました。ArduinoとLinuxの共存やIntelの組み込み(IoT)向けプロセッサQuark SoC X1000搭載など、なかなか興味深い製品で、発売即ゲットしてしまいました。

まだ、LチカとLinux環境にちょっと触った程度ですが、気がついたことを書いてみます。

 

パッケージ構成

本体とACアダプタ(各国対応のプラグ付き)というシンプルな構成。

量産品発売前のレビュー記事(マイナビさんとか)だと、マスコット人形・箱を開けたときのオルゴール・USBケーブル・基板スタンド用アダプタが添付とありましたが、量産版では削除されています。USBケーブルや基板スタンドは手持ち品があるので、Arduino(DIY)の精神としては余計なものは付けないほうが私的には好ましいです。でも、オルゴールは聴いてみたかった・・・ 電源は、5V/2AのACアダプターで(USBからの給電は不可。ボードが壊れると、恐ろしいことがマニュアルに書いてあります)、2Aの手持ち品もあるのですが、こちらは添付の純正品を使っています。

IMG_0326

(左側はMFTのIntelブースでもらったポストイット)

 

ハードウェア構成

Arduinoシールド互換とするために、5V GPIO/PWM, ADCが乗っていますが、Quark SoC X1000の機能は使わず、I2C経由の外付けのチップで対応しています。GPIOはQuarkにも搭載されているようにデータシート上見えますが、敢えて外付けチップ経由になっています。恐らく、5V/PWM対応がQuark内臓のGPIOではできないためと思われますが、GPIOが100KHz動作のI2C経由なので、GPIOのトグル周期は230Hz程度とオリジナルのArduinoより遅そうです(比べた訳ではないですが)。そのため、表示用のLCDも従来の4bitパラレルよりI2Cで直接接続した方が効率がよいかも知れません。

CPUが熱くなるとマイナビさんのレビュー記事にありますが、Linux環境を起動して温度センサーの値を調べてみるとこんな感じ:

□CPU温度の確認
# cat /sys/class/thermal/thermal_zone0/temp

  • Quark SoC X1000 : 69℃~72℃程度
  • RaspberryPi : 44℃程度

確かに、この手の組み込みチップとしては熱いと思います。マイナビさんのレビューでは、電源のチップ周りが熱くなるという記述もありましたが、自分の固体はCPU以外は殆ど熱くならないので量産版では改善されたのかも。

オンボードに8MB Flashが搭載されており、通常はこのFlashからLinuxをブートしてLinux上でArduinoスケッチを動かします。スケッチ用には、Quark SoCオンチップの512KB SRAMの約半分が割り当てられているみたい。512KB SRAM以外にもLinuxを動かす256MB DDR3 DRAMが搭載されています。


ソフトウェア構成

専用IDEを使ってスケッチを作成します。Macにインストールする際の注意点としてアプリケーションフォルダーにDLしたファイルを置くだけですが、appファイル名にスペースを入れてはいけません。私は、当初Arduino用のIDEと区別したかったため、”Arduino Galileo”とスペースありの名前で登録したのですが、一番最初に行うファームウェアのアップデートでエラーが発生し、はまりました(エラーメッセージでググったところ、Intelのサポートフォーラムで解決策が見つかった次第)。

オンボードのFlashから起動した場合、電源を落とすとスケッチも消えてしまい、再アップロードが必要です(スケッチはSRAMに直接書き込まれる模様)。もう一つの起動方法として、MicroSDカードにIntelが提供しているLinuxイメージを書き込んでブートすることができます。この場合、スケッチはMicroSDカードの/sketch/sketch.elfに書き込まれるため、電源を切っても再投入時自動実行されます。

オンボードFlashからブートした場合、Linuxのコマンド操作を行うためにはピンジャックのシリアルケーブルをPCに接続する必要があるようです。このピンジャック3.3V TTLでなく、RS232C (±12V?)で信号が出ているため、直接FT232等のUSB-シリアル変換に接続できません。自作も面倒なので、スイッチサイエンスさんからアクセサリとして接続ケーブルが発売されるのを待ちます。MicroSDカードからLinuxをブートした場合は、宅内ルーターからDHCP経由で割り当てられたIPアドレスを調べることによってssh経由でLinuxにアクセスできるため(ひとりブログさんの記事を参照)、この方法でLinuxに触っています。

MicroSDからブートした場合、Arduinoのスケッチ(sketch.elf)はLinuxの一プロセスとして実行されているように見えます。(下記ps出力の41行目)

root@clanton:/# ps
  PID USER       VSZ STAT COMMAND
    1 root       796 S    init [5]
    2 root         0 SW   [kthreadd]
    3 root         0 SW   [ksoftirqd/0]
    4 root         0 SW   [kworker/0:0]
    5 root         0 SW<  [kworker/0:0H]
    6 root         0 SW   [kworker/u:0]
    7 root         0 SW<  [kworker/u:0H]
    8 root         0 SW<  [cpuset]
    9 root         0 SW<  [khelper]
   10 root         0 SW   [kdevtmpfs]
   11 root         0 SW<  [netns]
   12 root         0 SW   [kworker/u:1]
  119 root         0 SW   [bdi-default]
  121 root         0 SW<  [kblockd]
  200 root         0 SW   [kworker/0:1]
  329 root         0 SW   [kswapd0]
  384 root         0 SW   [fsnotify_mark]
  396 root         0 SW<  [crypto]
  526 root         0 SW<  [deferwq]
  529 root         0 SW   [mmcqd/0]
  777 root         0 SW<  [kworker/0:1H]
  937 root         0 SW<  [loop0]
  938 root         0 SW   [kjournald]
 1002 root         0 SW   [khubd]
 1058 root         0 SW   [irq/61-0-0020]
 1100 root         0 SW   [spi0]
 1103 root         0 SW   [spi1]
 1133 root         0 SW<  [cfg80211]
 1277 root      1264 S    udhcpc -R -n -p /var/run/udhcpc.eth0.pid -i eth0
 1297 messageb  1464 S    /usr/bin/dbus-daemon --system
 1305 root      3256 S    /usr/sbin/sshd
 1320 root      1264 S    /sbin/syslogd -n -O /var/log/messages
 1322 root      1260 S    /sbin/klogd -n
 1326 root      1264 S    /sbin/getty 115200 ttyS1
 1327 root      1264 S    /sbin/getty 38400 tty1
 1328 root       768 S    /opt/cln/galileo/galileo_sketch_reset -v
 1329 root      1856 S    {clloader.sh} /bin/sh /etc/init.d/clloader.sh
 1443 root       876 S    /opt/cln/galileo/clloader --escape --binary --zmodem --di
 1452 root     18292 S    /sketch/sketch.elf /dev/pts/0 /dev/ttyS0
 1455 root      3316 R    {sshd} sshd: root@pts/1
 1457 root      1944 S    -sh
 1463 root      1264 R    ps

MicroSD用に提供されているLinuxの機能はミニマムです。コマンドファイルは以下のようにBusyBoxを使って小型化されており最小限。gccやパッケージアップデートの機能もありません。そのため、RaspberryPiのようにLinux環境での開発は難しく、当面はArduino環境での開発が主体になりそうです。

root@clanton:/# ls -la /sbin
drwxr-sr-x    2 root     root          2048 Sep 30  2013 .
drwxr-xr-x   18 root     root          1024 Jan  1 00:57 ..
lrwxrwxrwx    1 root     root            12 Sep 30  2013 acpid -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 arp -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 blkid -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 blockdev -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 bootchartd -> /bin/busybox
-rwxr-xr-x    1 root     root         12668 Sep 30  2013 bootlogd
lrwxrwxrwx    1 root     root            12 Sep 30  2013 depmod -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 fdisk -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 findfs -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 fsck -> /bin/busybox
-rwxr-xr-x    1 root     root          3944 Sep 30  2013 fstab-decode
lrwxrwxrwx    1 root     root            12 Sep 30  2013 getty -> /bin/busybox
lrwxrwxrwx    1 root     root            19 Sep 30  2013 halt -> /sbin/halt.sysvinit
-rwxr-xr-x    1 root     root         12168 Sep 30  2013 halt.sysvinit
lrwxrwxrwx    1 root     root            12 Sep 30  2013 hwclock -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 ifconfig -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 ifdown -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 ifup -> /bin/busybox
lrwxrwxrwx    1 root     root            19 Sep 30  2013 init -> /sbin/init.sysvinit
-rwxr-xr-x    1 root     root         31268 Sep 30  2013 init.sysvinit
lrwxrwxrwx    1 root     root            12 Sep 30  2013 insmod -> /bin/busybox
-rwxr-xr-x    1 root     root         78556 Sep 30  2013 iwconfig
lrwxrwxrwx    1 root     root             8 Sep 30  2013 iwgetid -> iwconfig
lrwxrwxrwx    1 root     root             8 Sep 30  2013 iwlist -> iwconfig
lrwxrwxrwx    1 root     root             8 Sep 30  2013 iwpriv -> iwconfig
lrwxrwxrwx    1 root     root             8 Sep 30  2013 iwspy -> iwconfig
-rwxr-xr-x    1 root     root         14972 Sep 30  2013 killall5
lrwxrwxrwx    1 root     root            12 Sep 30  2013 klogd -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 loadkmap -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 logread -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 losetup -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 lsmod -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 mdev -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 modprobe -> /bin/busybox
-rwxr-xr-x    1 root     root          3456 Sep 30  2013 nologin
lrwxrwxrwx    1 root     root            12 Sep 30  2013 pivot_root -> /bin/busybox
lrwxrwxrwx    1 root     root            23 Sep 30  2013 poweroff -> /sbin/poweroff.sysvinit
lrwxrwxrwx    1 root     root            13 Sep 30  2013 poweroff.sysvinit -> halt.sysvinit
lrwxrwxrwx    1 root     root            21 Sep 30  2013 reboot -> /sbin/reboot.sysvinit
lrwxrwxrwx    1 root     root            13 Sep 30  2013 reboot.sysvinit -> halt.sysvinit
lrwxrwxrwx    1 root     root            12 Sep 30  2013 rmmod -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 route -> /bin/busybox
lrwxrwxrwx    1 root     root            23 Sep 30  2013 runlevel -> /sbin/runlevel.sysvinit
-rwxr-xr-x    1 root     root          3472 Sep 30  2013 runlevel.sysvinit
lrwxrwxrwx    1 root     root            12 Sep 30  2013 setconsole -> /bin/busybox
lrwxrwxrwx    1 root     root            23 Sep 30  2013 shutdown -> /sbin/shutdown.sysvinit
-rwxr-xr-x    1 root     root         18260 Sep 30  2013 shutdown.sysvinit
lrwxrwxrwx    1 root     root            12 Sep 30  2013 start-stop-daemon -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 sulogin -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 switch_root -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 sysctl -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 syslogd -> /bin/busybox
lrwxrwxrwx    1 root     root             4 Sep 30  2013 telinit -> init
lrwxrwxrwx    1 root     root            12 Sep 30  2013 udhcpc -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Sep 30  2013 vconfig -> /bin/busybox
lrwxrwxrwx    1 root     root            17 Sep 30  2013 vigr -> /sbin/vigr.shadow
lrwxrwxrwx    1 root     root            11 Sep 30  2013 vigr.shadow -> vipw.shadow
lrwxrwxrwx    1 root     root            17 Sep 30  2013 vipw -> /sbin/vipw.shadow
-rwxr-xr-x    1 root     root         39808 Sep 30  2013 vipw.shadow
lrwxrwxrwx    1 root     root            12 Sep 30  2013 zcip -> /bin/busybox


CPU発熱の考察

Arduinoのスケッチはloopの部分で無限ループを廻すので、そのためCPU使用率が高いのではないかと思い、調べてみました。Lチカスケッチを動かした状態でtopコマンドを動かすとCPU使用率は以下の通り。全体のCPU使用率が3%で、sketch.elfのCPU使用率は0%です。ですので、スケッチの無限ループによるCPU使用率の高騰はないです。

Mem: 42132K used, 191680K free, 0K shrd, 1868K buff, 27688K cached
CPU:   0% usr   1% sys   0% nic  97% idle   0% io   0% irq   0% sirq
Load average: 0.00 0.02 0.05 2/45 1478
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
 1478  1464 root     R     1268   1%   1% top
 1455  1305 root     S     3316   1%   1% {sshd} sshd: root@pts/1
  200     2 root     SW       0   0%   0% [kworker/0:1]
 1452  1443 root     S    18292   8%   0% /sketch/sketch.elf /dev/pts/0 /dev/ttyS0
 1305     1 root     S     3256   1%   0% /usr/sbin/sshd
 1464  1457 root     S     1944   1%   0% bash
 1457  1455 root     S     1944   1%   0% -sh
 1329     1 root     S     1856   1%   0% {clloader.sh} /bin/sh /etc/init.d/clloader.
 1297     1 messageb S     1464   1%   0% /usr/bin/dbus-daemon --system
 1320     1 root     S     1264   1%   0% /sbin/syslogd -n -O /var/log/messages
 1326     1 root     S     1264   1%   0% /sbin/getty 115200 ttyS1
 1327     1 root     S     1264   1%   0% /sbin/getty 38400 tty1
 1277     1 root     S     1264   1%   0% udhcpc -R -n -p /var/run/udhcpc.eth0.pid -i
 1322     1 root     S     1260   1%   0% /sbin/klogd -n
 1443  1329 root     S      876   0%   0% /opt/cln/galileo/clloader --escape --binary
    1     0 root     S      796   0%   0% init [5]
 1328     1 root     S      768   0%   0% /opt/cln/galileo/galileo_sketch_reset -v
  937     2 root     SW<      0   0%   0% [loop0]
    6     2 root     SW       0   0%   0% [kworker/u:0]
    3     2 root     SW       0   0%   0% [ksoftirqd/0]
  529     2 root     SW       0   0%   0% [mmcqd/0]
  777     2 root     SW<      0   0%   0% [kworker/0:1H]
   10     2 root     SW       0   0%   0% [kdevtmpfs]
   12     2 root     SW       0   0%   0% [kworker/u:1]
  938     2 root     SW       0   0%   0% [kjournald]
    2     0 root     SW       0   0%   0% [kthreadd]
    5     2 root     SW<      0   0%   0% [kworker/0:0H]
  119     2 root     SW       0   0%   0% [bdi-default]
  121     2 root     SW<      0   0%   0% [kblockd]
    4     2 root     SW       0   0%   0% [kworker/0:0]
  396     2 root     SW<      0   0%   0% [crypto]

じゃあなぜ熱いのかと言うと、パワー・マネジメント機能がちゃんと動いていない、もともと消費電力が高めということかと思います。

Quark SoC X1000のデータシートを見ると、Power Management Stateとして以下をサポートしています:

  • S0: Full On
  • S3: Suspend to RAM (Standby)
  • S4: S4 - Suspend to Disk (Hibernate)

S3以降はCPUが完全に停止してしまうためスケッチ実行中に遷移することはないと思います。ですので、通常S0ステートで動き続ける訳ですが、AtomのようにS0を細分化してS0内に省電力モードを設けるような機能はQuarkにはありません。

Sステート以外に、ACPIアイドルステートして以下があります:

  • C0: Active mode, processor executing code
  • C1: AutoHALT state
  • C2: Stop Grant state

PCのACPIでは、アイドル時OSがHALT命令を発行してCPUをC1ステートに落とすようなことやってませんでしたっけ?だとすると、GalileoのLinuxはACPIが動いておらず、アイドル時もCPUクロック周りがフル回転していることになります。

70℃程度でCPUが壊れることはないと思いますが、消費電力や発熱的には組み込みやIoT向けには今一な感じがします。

 

参考文献

Arduino Ethernet Shieldのパワーオンリセット

ひさびさにArduinoネタを一つ。私のブログにもかつて書いたのですが、ArduinoにEthernet shieldを載せて電源ONした際にリンクアップしないケースが結構あります。この場合、リセットボタンを押すと正常に起動してくれますが、いちいちマニュアルリセットするのは面倒です。この問題の対策について質問をいただきましたので、自分が行っている対策を記載します。

対策には、Ethernet shield側に部品の追加(半田付け)が必要です。電源ピン周りの配線を行いますので、配線を間違うと電源をショートして本体を破損する可能性もあります。そのため、作業は慎重に&自己責任でお願いします。


なぜ電源オン時に起動しないのか - パワーオンリセットの必要性 -

マイコンチップなどは、電源(パワー)オン時に内部状態の初期化を行うために、リセット信号の入力を要求する場合があります。この動作をパワーオンリセットと呼びます。Ethernet shieldでTCP/IPの処理を行っているW5100チップは、以下に示すパワーオンリセット信号を要求します(データーシートの抜粋)。

W5100_RestTiming

上記のように、電源オン後、2μsの期間リセット信号をLowレベルに落とす必要があります。冒頭の問題が発生する原因は、Arduinoがこの2μsのリセット信号を生成していないためです。

以下にArduino Duemilanoveのリセット信号まわりの回路を示します。

ArduinoRestCircuit

赤で囲った部分に注目してください。MCU(ATmega)のRESETピンを10KΩの抵抗でプルアップしてあるだけです。このRESET信号はヘッダコネクタを介してEthernet shieldに繋がっており、W5100チップのRESET信号になっているのですが、この回路だと、電源オンと同時にRESET信号がHigh状態になってしまい、2μsのリセット期間Low状態を保持することができないため、W5100チップが正しく初期化できません。このため、リンクアップしないという異常動作が発生します。リセットSWを押すことで、スイッチを押している間RESET信号がLowレベルに落ちるため、この動作でやっとW5100が初期化され正常動作がスタートします。

じゃあ、MCU (ATmega)はなぜこのリセット回路で問題がないかと言うと、ATmegaは電源オンを検出した際に内部でリセットパルスを生成する機能を持っているためです。言い換えると、W5100はこの機能を持っていないため、外部からリセット信号を入力する必要があります。


RESET信号の作り方

RESET信号を作る方法としては、GLEDシールドの制作で使ったように専用のリセットICを使う方法があります。この方法は一番確実なのですが、Arduino本体もしくはEthernet shieldにリセットICを載せる場所が必要となり実装が困難です。そのため、簡易的なコンデンサー追加の方法を紹介します。

以下の回路図に示すように、リセット回路にコンデンサ(C)を追加します。コンデンサを加えることによって、電源オン(VCC印加)時、コンデンサーを充電する時間分リセットピンの電圧上昇を遅延させることによってリセット状態を作ることができます。

EtherSheildRest

リセットピンの電圧上昇速度は、C(コンデンサ容量) x R(充電電流の制限抵抗)に比例します。このCRを時定数と呼び、1 CR単位時間でコンデンサーをで3.2V(63.2%)まで充電、4 CR単位時間で4.9V(98.2%)まで充電できます。CR単位時間と充電量の関係は以下のグラフとなります。このあたりの詳細はこちらを参照ください。

CR_Curve

W5100チップはリセット端子の電圧が0.8V(VIL)以下をリセット状態とみなすため、0.2CR単位時間までがリセット入力状態となります。今回はC = 0.047uF, R = 10KΩですので、0.047E-6 x 10E3 x 0.2 x1000 = 0.09msとなります。

すなわちCR回路によって、電源ONから90μsの間、W5100チップのREST信号を有効にすることができます。この値は、W5100のリセット信号入力時間である2μsを十分満足します。仮に、もっと長いリセット時間を作りたい場合は、CRの値を大きくすればよいことになります。

最後に、なぜダイオードが入っているかについて説明します。リセット回路に追加したコンデンサーは電源OFF直後は電荷を蓄えており、RESETピンがHighの状態になっています。コンデンサが1μF x 抵抗100KΩ程度のリセット回路の場合、数秒間コンデンサの電荷が抜けずRESETピンがHighのままになります。そのため、コンデンサの電荷が残っている状態で電源を再度ONにした場合、RESETピンがLowに落ちずリセットがかからない場合が発生します。ダイオードを入れることによって、電源OFFの際に、コンデンサーの電荷を電源ライン側にすばやく放電させることによってこの問題を防止できます。

今回の回路では、コンデンサーの容量が小さいため実質的に問題はないと思うのですが、念のためにダイオードも追加しました。私は手持ちの小信号用ダイオード(1S133)を使っていますが、電流容量が小さいため(100mA)、整流用のもっと容量があるダイオードを使った方がよいと思います。


部品の取り付け

コンデンサとダイオードは、Ethernet shieldの端子部分に半田付けします。

先ずは、コンデンサーをEthernet shieldのRESETピンとGNDピンに半田付けします。基板上にスルーホールがあるため、コンデンサーの足がスルーホールに接触しないよう、熱収縮チューブで絶縁します。コンデンサー取り付け後の写真を以下に示します。

CapAdd


次に、RESETと5V(VCC)ピンの間にダイオードを取り付けます。ダイオードの極性には十分注してください。カソードをVCC側に繋ぐ形になります。この極性を間違うと、リセットボタンを押した際に電源が短絡してしまいます。

CapDiodeAdd


効果と制約事項

コンデンサーを追加することによって、私の環境では、Arduino側の外部電源ジャックを抜き差しすることによる電源ONでは、Ethernet shieldが問題なく起動します。何回か外部電源ジャックの抜き差しを繰り返していますが、いまのところ起動できなかったケースはありません。

しかしながら、ACアダプターをACコンセントから抜いて再度コンセント挿入した場合、依然としてEthernet shieldが正しく起動しません(リンクアップしない)。原因としては、ACアダプター出力電圧の立ち上がりがゆっくりで、CR回路によるリセット信号が作れないのだと思います。このケースを救済するためには、自作のGLCDシールドで行ったように、電源電圧を監視して一定レベルに電圧が上がった時点からリセットパルスを作ってくれるリセットICを使う必要があります(GLCDシールドでは、AC側の抜き差しでも確実に起動してくれます)。自分は、AC側の抜き差しはめったに行わないため、純正Ethernet shieldに関しては今回のコンデンサー追加による対策でよしとしています。

Arduino RSSリーダー

これまでに準備した、GLCDへの漢字表示やRSSサイトのXML解析などのパーツをまとめてRSSリーダーを作りました。最低限の機能しか実装してありませんが、Yahoo天気情報を読み込んで1週間分の天気予報を表示するリーダーです。

ハード構成

自作のGLCDシールドをベースに、RSSの記事選択等に使うタクトスイッチを加えて、以下のハード構成としました。

Glcd_shield3v2_2

スケッチ

ArduinoでXMLを解析する」で使用したNunniMCAX XMLパーサーを使用しました。このライブラリはXMLパーサーとしては非常にシンプル・コンパクトですが、Arduino向けとしてはメモリー使用量が多くATMega328ではSRAM容量が不足してしまい、Arduino Megaが必要となります。今回は、ハードリソース的にもディジタルピンの必要本数が328では足りず、Arduino Megaが必要となっているためよしとしました。

スケッチのソースを以下に示します。
  Weather11pub.zip」をダウンロード

スケッチが起動すると、Yahoo天気情報のRSSサイトにアクセスして今日の天気をGLCDに表示します。対象地域は神奈川県東部(私のすみか)固定です、、

Rssreader

RSS情報の解析

Yahoo天気情報は、以下のソース情報抜粋に示すように、RSS 2.0の形式で配信されます。RSS 2.0はXML文書の一種です。

<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
  <channel>
    <title>Yahoo!天気情報 - 東部(横浜)の天気</title>
    <link>http://rd.yahoo.co.jp/rss...............</link>
    <description>Yahoo! JAPANの天気情報に掲載されている最新の情報を提供しています。</description>
    <language>ja</language>
    <copyright>Copyright (C) 2009 Yahoo Japan Corporation. All Rights Reserved.</copyright>
    <lastBuildDate>Sun, 06 Sep 2009 09:51:29 +0900</lastBuildDate>
    <item>
      <title>【 6日(日) 東部(横浜) 】 晴時々曇 - 27℃/22℃ - Yahoo!天気情報</title>
      <link>http://rd.yahoo.co.jp/rss..............</link>
      <description>晴時々曇 - 27℃/22℃</description>
      <pubDate>Sun, 06 Sep 2009 05:00:00 +0900</pubDate>
    </item>
    <item>
      <title>【 7日(月) 東部(横浜) 】 曇時々晴 - 26℃/22℃ - Yahoo!天気情報</title>
      <link>http://rd.yahoo.co.jp/rss..............</link>
      <description>曇時々晴 - 26℃/22℃</description>
      <pubDate>Sun, 06 Sep 2009 05:00:00 +0900</pubDate>
     </item>

       ~ 中略 ~

   </channel>
</rss>

XML情報解析のプログラム部分は、「ArduinoでXMLを解析する」に示したサンプルスケッチと同様です。<item>~</item>タグに囲まれた部分(以後"item"要素と呼びます)が、日毎の天気情報(記事)になります。そのため、"item"要素の開始を見つけた時点で、ItemCount変数をインクリメントし、n番目のitem(デフォルトは1)内にある、"title"要素と"pubDate"要素を抜き取ってGLCDへの表示情報とします。

タクトスイッチを操作することで、翌日・前日の予報に移動します。表示対象日を切り替えた場合はページ全体をリロードして新たな対象日の予報を表示します。item毎の情報をメモリーに保持した方がレスポンスが上がるのですが、現状は手抜きをしています。

RSS 2.0を使用したサイトなら、この方法で記事のタイトル・概要(description)を取り出すことができると思います。RSSには1.0という別の形式がありこちらはフォーマットが全く異なります(RSS 1.0の方が複雑です)。今回は使用していませんが、NunniMCAXにはアトリビュート情報を取り出す機能もあるため、rssタグ(<rss version="2.0">)からversionアトリビュートを取り出して2.0 or 1.0の判定を行うことによって、RSS 2.0と1.0の両方に対応したRSSリーダーを作ることも可能です。

使用したライブラリ

XML解析パーサーとして使用したNunniMCAXとSPI Flash memory(AT45DB161D)ライブラリ以外は、Arduino - Librariesから入手できます。今回のプロジェクト用に変更を行った箇所は以下です:

1) ks0108 GLCD Library
最新のV2をベースに、「Arduinoで漢字表示(4)」で示した漢字表示のコードを追加しています。今回さらに、画面幅を超える長い文字列を表示する際に、自動的に改行を行う機能を追加しました。また、画面の最終行(12ドットフォントの場合、Y座標52~63にかけて)に表示ができない問題を見つけたため修正を盛り込みました。

2) Ethernet Library
WIZ812MJ(W5100搭載モジュール)とAT45DB161D(SPIフラシュメモリー)を同時使用するための変更を実施。

3) String Library
このライブラリも結構メモリーを喰いますが、文字列操作が楽ちんなので使用。メモリーリークバグの修正を盛り込んでいます。

今回使用したライブラリ一式(Arduinoサイトから入手できるライブラリは変更を行ったファイルのみ)を以下に示します。
 「LibraryForWeather11.zip」をダウンロード

余談

ks0108ライブラリの最終行が表示できない問題の解決には結構時間を取られました。GLCD制御レジスタ(ピクセル位置の指定)の操作、書き込むフォントデーターのビット操作が少々複雑で、私の頭の中ではプログラムの動作がイメージし切れませんでした。だいぶ前に、優秀なプログラマーは、部屋数が20位あるお屋敷の総模様替えを頭の中で出来る人、即ちプログラムの実行ステップに従って刻々と変化する変数やハードの状態を頭の中で追える人だとありましたが、その通りだと思いました。

EclipseやVisualStudioではビジュアルなデバック機能に助けられるのですが(上記の状態変化を追いかける仕事の多くを実行・補助してくれる)、Arduinoのデバック環境が貧弱な点も苦戦要素の一つでした(言い訳ですが)。 昔ならもう少し脳内トレースもできたと思うのですが、歳のせいもありそうです。

今回見つけたks0108の問題の修正イメージをArduino Forumにアップしたところ、ライブラリの作者さんから速攻でより美しい修正イメージが提示されました。悩むよりさっさとバグレポートした方が早かったような、、

2010/3/28更新:
WIZ812MJのSCKピン番号を誤記修正

SPIデバイスの混在とSS信号の初期状態

自作のGLCDシールドにて突然一部の漢字が表示できなくなる事象に何回か遭遇しました。漢字フォントを格納したSPI Flash memory(AT45DB161D)の内容をダンプするとページ単位でall-FFに初期化されています(データーが残っているページもあります)。なにやら、ページ・セクター単位で消去が動いたような状態です。この問題の原因と対策について考えてみました。

問題の発生トリガー

時間の関係で事象を再現させる試験はできていませんが、以下の状況で消去が発生しているように見えます:

  • Ethernet shield互換のWIZ812MJとSPI Flash memory双方を基板に搭載して通電している(以下、ここではWIZ812MJをEthernet shieldと記載)
  • Ethernet shieldのライブラリのみ使用して、Ethernet関連の試験を行った(SPI Flashは初期化を行っていない)
  • SPI Flash memoryのSS(CS)信号入力は、以下の回路図に示す通り、汎用I/OのPL0(PORTL bit0)を使用している

Glcd_shield2

すなわち、MCUに対してPL0の制御レジスタ設定を行っていない(リセット時の初期状態のままにしている)ため、SPI Flashから見るとSS(CS)信号が有効に見えてしまうことを疑っています。想定される問題の発生メカニズムは以下です:

  • Ethernet shieldへのデーター書き込みを行うと、SCK/MOSIが駆動される
  • このとき、SPI FlashのSS(CS)がLowレベルに見えてしまう
  • Ethernet shieldとSPI Flashの両方が選択されてしまい、Ethernet shieldへのコマンドがSPI Flashへのページ・セクタ消去コマンドと誤認識された(こんな偶然の一致があるのだろうかと思いますが)

上記の仮定が成り立つかは、SS信号ピンの初期状態に依存します。

AVRにおけるSS信号ピンの初期状態

データーシートには以下の記載があります:

  1. SPIを有効にすると、SS/MOSI/MISO/SCKの入出力方向をSPIモード用に設定する
  2. MCUをMasterに設定すると、SS信号の入出力方向はユーザーのDDRBレジスタ設定に従う

1項の「SPIを有効にする」とは、SPCRレジスタのSPE(bit6)を1にすることだと思います。リセット時の初期状態ではSPEは0(SPI無効)となります。この場合のSS信号ピンの方向性や出力レベルはどうなるのでしょうか。SS信号を収容するPORTBは汎用I/Oとして機能すると考えられます。汎用I/Oの初期状態は入力ポートでハイインピーダンス状態になります。

以下の試験スケッチを使って、リセット時のSS信号ピン(PB2)電圧を測りました(測定器はテスターを使用)。MCUはATmega328(Arduino nano)を使用。

1) SPCRを設定しない(SPI無効): SS = 0.4V
 → PORTBは入力ポート(Hi-z)として動作

2) SPE=1, MSTR=1(マスター): SS = 0.4V
 → PB2のDDRを設定していない→ 入力として動いていると思われる

3) SPE=1, MSTR=1, DDRB=4, PORTB=4:SS = 4.7V(HIGH)
 → PB2を明示的に、出力・HIGHに設定

上記の1)はSPIライブラリの初期化ルーチンを実行していない状態に相当し、この場合MCUのSSピンに接続されたSPIデバイスはLOW入力(選択状態)と等価になると思われます。冒頭の回路図に示したSPI FlashのSS(CS)入力は汎用I/Oで制御しているため、リセット直後は入力ポート状態となり上記1)と同様になります。このためライブラリの初期化ルーチンを実行していない状態では問題の発生条件が成立すると言えます。

対策

Ethernet shieldに加えてSPI Flash memoryもライブラリ経由で初期化を行えば、SS信号ピンにHighを出力するため問題が発生することはありません。Ethernetのみを使用する場合でも必ずSPI Flash memory(AT45DB161D)の初期化を行うようにすればよのですが、忘れてしまいそうです。

そのため、SS信号を5Vにプルアップしました。Ethernet shiedのみを動作させる試験を少し行った範囲では、Flash memoryの誤消去は発生しておらずうまく動いているようです。ハード・ソフトのどちらの責任で初期設定を行うかを設計仕様として決める必要があることを意味し、なかなか奥が深いです。大規模開発では重要なポイントになります。このプロジェクトは個人レベルの日曜大工なので、行き当たりばったりでやっていますが、、、

ArduinoでXMLを解析する

GLCDで漢字表示ができるようになりましたが、Arduino RSSリーダーを作るためには、RSSサイトから配信されるXMLデーターの解析を行う必要があります。この解析を行うライブラリを自作するか、フリーライブラリを移植するか迷っていたのですが、Arduinoでも動くライブラリが見つかったので試してみました。

XML構文解析を行うパーサー(Parser)の種類

標準ベースの実装としては以下の2つがあります:

1) DOM(Document Object Model)

W3Cが勧告しているXML文書を操作するためのAPI。XML文書全体をメモリーに読み込み、XML文書のツリー構造をメモリー上に展開した上で各要素へのアクセスを行います。データー全体のツリー構造を保持することからランダムに要素の取り出しを行う用途に向いていますが、反面メモリー消費が多いのが欠点です。Arduino/AVRのようにメモリーリソースが限られた組み込み系では使いづらいと思います。

2) SAX(Simple API for XML)

SAXはXML文書全体を読み込まず、データーを読み込みながら逐次処理を行います。SAXでは、開始タグ・終了タグを見つけるとイベントが発生し、call back関数が呼び出されます。このcall back関数に必要な処理を記述することで特定の要素を抽出するなのど操作が可能となります。DOMのようにランダムアクセス用途には向きませんが、メモリー消費が少ないため、組込用途には向いています。SAXの概要はここを参考にしました

当初、XMLパーサーはDOMしかない(もしくは、.Net FramewarkのXmlTextReaderのような独自の実装)と思っていたため、Arduinoで使うことをためらっていましたが、XML解析について調べていくうちにSAXを発見しました。RSSリーダーとしての用途なら、データーの先頭から逐次データーを読みながら1パスで処理することで十分なため、SAXなら使えそうです。

フリーのSAXライブラリ

例によってフリーのライブラリを物色。NunniMCAXというライブラリを見つけました。Arduinoと同じMade In Italyなのですが、読み方分かりません、、

オリジナルのライブラリは、FILE *fileを引数としてパーサーを呼び出す形式になっています。パーサー本体の中では、fgetc(file)を使ってテキストファイルから文字を読み込みながら逐次処理を行っています。この部分だけを変更して、関数ポインタを渡すようにしました。Ethernet shieldから1文字読み込む関数を作ってこの関数のポインタを渡してやることでwebサーバーから取得したXMLデーターの解析ができます。

Arduino用に変更を盛り込んだライブラリソースは以下です:
「NunniMCAX-1.4.1_arduino.zip」をダウンロード

使い方ですが、IDEのライブラリフォルダー配下に「NunniMCAX」というフォルダーを作って解凍したファイルを格納して下さい。

XML文書を解析する過程でイベントが発生すると以下の関数がcall backされますので、ユーザープログラムの中で実体を記述します。

  1. startDocument: XML文書を見つけた
  2. startElement: 要素ノードを見つけた → 開始タグを見つけた
  3. characters: タグに挟まれたテキスト情報を見つけた → 文字単位にイベントが発生するようです
  4. endElement: 要素の終了→ 終了タグを見つけた
  5. endDocument:

例えば、次のXMLデーターを入力した際の発生イベントを示します。
<?xml version="1.0" encoding="utf-8"?>
<Hello>
   <World>こんにちはみなさん!</World>
</Hello>④⑤

丸付き数字が各イベントが発生するポイントになります。③のchractorsイベントは、「こんにちはみなさん!」の各文字単位に発生します。

サンプルスケッチ1

Yahoo JAPANのお天気情報RSS(http://rss.weather.yahoo.co.jp/rss/days/4610.xml)を読み込んで解析を行うサンプルスケッチを以下に示します。

このスケッチでは、①~⑤のイベント毎にシリアルポートに結果の出力を行います。③はcharactorsイベント発生毎に検出した文字をバッファに蓄積し、④のイベント発生時に文字列として出力しています。Attribute(タグの中に書き込まれた属性)情報を取得するAPIもあるのですが、ここでは割愛します。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include <NunniMCAX.h>
#include <Ethernet.h>

#define MAXLEN 256
static char m_characters[MAXLEN];
struct NunniMCAXContentHandler handler;
bool RootDetected = false;

byte mac[] = { xx, xx, xx, xx, xx, xx };
byte ip[] = { 192, 168, 0, 111 };
byte server[] = { 192, 168, 0, 10 };
Client client(server, 80);

void setup()
{
  delay(1000);
  Ethernet.begin(mac, ip);
  Serial.begin(115200);

  /* set up the paser handler functions */
  handler.startDocument = startDocument;
  handler.startElement = startElement;
  handler.characters = characters;
  handler.endElement = endElement;
  handler.endDocument = endDocument;
}

void loop()
{
  Serial.println("##connecting...");
  if (client.connect()) {
    Serial.println("##connected");
    client.write("GET /ansi.xml HTTP/1.0\r\n\r\n");
  } else {
    Serial.println("##connection failed");
    delay(5000);
    return;
  }

  // パーサーの呼び出し 
  NunniMCAXparse( &getcFunc, &handler );

  Serial.println("##disconnecting.");
  Serial.println("");
  client.stop();
  RootDetected = false;
  delay(5000);
  return;
}

// Ethernetからの1文字読み取るコールバック関数
// 最初の'<'(タグの開始)まで読み飛ばし
//  → HTTPサーバーレスポンス文字を読み飛ばす
int getcFunc()
{
  while(!RootDetected)
  {
    if (getChar() == '<')
    {
      RootDetected = true;
      return '<';
    }
  }

  return getChar();
}

int getChar()
{
  if (client.available()) 
    return client.read();

  if (!client.connected()) 
    return EOF;
}

/* SAXパーサーのイベントハンドラー */
int startDocument(void) 
{
  Serial.println( "startDocument" );
  return 0;
}

int startElement( const char *tagname, struct NunniHashtable *args ) 
{
  const int size = NunniHashtableSize( args );
  char ** keys;
  int i, ret;
  const char *name, *value;
  keys = (char**)calloc( size, sizeof( char * ) );
  Serial.print("start element: ");
  Serial.println(tagname);
  ret = NunniHashtableKeys( args, keys );
  for ( i = 0; i < size; ++i ) {
    name = keys[i];
    value = NunniHashtableGet( args, name );
    Serial.print("  attrName: ");
    Serial.print(name);
    Serial.print("   attrValue: ");
    Serial.println(value);
  }
  memset( m_characters, 0, MAXLEN );
  return 0;
}

int characters( char ch[], int start, int length ) 
{
  int i = strlen( m_characters );
  if ( i == MAXLEN )
    return -1;
  strncat( m_characters, &(ch[start]), length );
  return 0;
}

int endElement( const char *tagname ) 
{
  int len;
  char *data = m_characters;
  while( isspace( *data ) ) {
    ++data;
  }
  len = strlen( data );
  while( isspace( data[--len] ) ) {
    data[len] = 0;
  }
  if ( data != NULL && strncmp( data, "", 1 ) )
  {
    Serial.print("text: ");
    Serial.println(data);
  }
  memset( m_characters, 0, MAXLEN );
  Serial.print("end element: ");
  Serial.println(tagname);

  return 0;
}

int endDocument(void) 
{
  Serial.println( "endDocument" );
  return 0;
}

実行結果は以下の通りです。IDEのSerial Monitorは漢字表示に対応していないため、UTF-8に対応したターミナルソフト(UTF-8 TeraTerm Proなど)に表示させます。

##connecting...
##connected
startDocument
start element: rss
start element: channel
start element: title
text: Yahoo!天気情報 - 東部(横浜)の天気
end element: title
start element: link
text: http://rd.yahoo.co.jp/rss/l/weather/days/*http://weather.yahoo.co.jp/weather/jp/14/4610.html
end element: link
start element: description
text: Yahoo! JAPANの天気情報に掲載されている最新の情報を提供しています。
end element: description
start element: language
text: ja
end element: language
start element: copyright
text: Copyright (C) 2009 Yahoo Japan Corporation. All Rights Reserved.
end element: copyright
start element: lastBuildDate
text: Mon, 24 Aug 2009 23:55:16 +0900
end element: lastBuildDate
start element: item
start element: title
text: 【 24日(月) 東部(横浜) 】 雨後曇 - 29℃/23℃ - Yahoo!天気情報
end element: title
start element: link
text: http://rd.yahoo.co.jp/rss/l/weather/days/*http://weather.yahoo.co.jp/weather/jp/14/4610.html?d=20090824
end element: link
start element: description
text: 雨後曇 - 29℃/23℃
end element: description
start element: pubDate
text: Mon, 24 Aug 2009 17:00:00 +0900
end element: pubDate
end element: item
start element: item
start element: title
text: 【 25日(火) 東部(横浜) 】 曇時々晴 - 28℃/22℃ - Yahoo!天気情報

end element: title
start element: link
text: http://rd.yahoo.co.jp/rss/l/weather/days/*http://weather.yahoo.co.jp/weather/jp/14/4610.html?d=20090825
end element: link
start element: description
text: 曇時々晴 - 28℃/22℃
end element: description
start element: pubDate
text: Mon, 24 Aug 2009 17:00:00 +0900
end element: pubDate
end element: item
~中略~
end element: channel
end element: rss
endDocument
##disconnecting.

上記のように、RSS情報を、要素名(タグ名)やTextに分解して取得できます。

サンプルスケッチ2

サンプルスケッチ1では、サイト情報全体を表示していますが、例えば上記の例で、1番目の"item"要素→"title"要素→text情報を抜き出すと、今日の天気を取得できます。このスケッチを以下に示します。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include <NunniMCAX.h>
#include <Ethernet.h>

#define MAXLEN 256
static char m_characters[MAXLEN];
struct NunniMCAXContentHandler handler;
bool RootDetected = false;
int ItemCount = 0;
int ItemToGet = 1;

byte mac[] = { XX, XX, XX, XX, XX, XX };
byte ip[] = { 192, 168, 0, 111 };
byte server[] = { 124, 83, 139, 175 };    // rss.weather.yahoo.co.jp
Client client(server, 80);

void setup()
{
  delay(1000);
  Ethernet.begin(mac, ip);
  Serial.begin(115200);

  /* set up the paser handler functions */
  handler.startDocument = startDocument;
  handler.startElement = startElement;
  handler.characters = characters;
  handler.endElement = endElement;
  handler.endDocument = endDocument;
}

void loop()
{
  Serial.println("##connecting...");
  if (client.connect()) {
    Serial.println("##connected");
    client.write("GET /rss/days/4610.xml HTTP/1.0\r\n\r\n");
  } else {
    Serial.println("##connection failed");
    delay(5000);
    return;
  }

  // パーサーの呼び出し
  NunniMCAXparse( &getcFunc, &handler );

  Serial.println("##disconnecting.");
  Serial.println("");
  client.stop();
  for (;;);    // stop
}

// Ethernetからの1文字読み取るコールバック関数
// 最初の'<'(タグの開始)まで読み飛ばす
//  → HTTPサーバーレスポンス文字を読み飛ばす
int getcFunc()
{
  while(!RootDetected)
  {
    if (getChar() == '<')
    {
       RootDetected = true;
       return '<';
    }
  }

  return getChar();
}

int getChar()
{   
  if (client.available())
    return client.read();

  if (!client.connected())
    return EOF;   
}

/* SAXパーサーのイベントハンドラー */
int startDocument(void)
{   
  return 0;
}

int startElement( const char *tagname, struct NunniHashtable *args )
{   
  if (strcmp(tagname, "item") == 0)
    ItemCount++;
  memset( m_characters, 0, MAXLEN );
  return 0;
}

int characters( char ch[], int start, int length )
{
  int i = strlen( m_characters );
  if ( i == MAXLEN )
    return -1;
  strncat( m_characters, &(ch[start]), length );
  return 0;
}

int endElement( const char *tagname )
{
  int len;
  char *data = m_characters;
  while( isspace( *data ) ) {
    ++data;
  }
  len = strlen( data );
  while( isspace( data[--len] ) ) {
    data[len] = 0;
  }

  if ( data != NULL && strncmp( data, "", 1 ) )
    if (ItemCount == ItemToGet && strcmp(tagname, "title")  == 0)
    {
      Serial.println(data);
    }
  memset( m_characters, 0, MAXLEN );

  return 0;
}

int endDocument(void)
{
  return 0;
}

実行結果を以下に示します。

##connecting...
##connected
【 24日(月) 東部(横浜) 】 雨後曇 - 29℃/23℃ - Yahoo!天気情報
##disconnecting.

イベントハンドラー部分を書き替えることで抜き出す情報を制御できることが分かります。

メモリー使用量

気になるサンプルスケッチ2のメモリー使用量は以下です:

  • プログラムサイズ(ROM占有量):20944bytes
  • データーサイズ(SRAM占有量):3021bytes

データーサイズは、「Arduinoメールチェッカー(その3)」に記載した、avr-objdump -hコマンドで調べました。SRAMを3KB程度占有している点が大きく、Arduino Megaでないと実行できないサイズになっています。ATmega 328でも動かせる程度のメモリー量に収めたいところですが、NunniMCAXライブラリーはなかなかよく出来ており捨てがたいです。

Arduino Ethernet Shieldと他SPIデバイスの混在

GLCDシールドの制作にてArduino上でSPIデバイスの混在を行う場合の考慮点を記載しましたが、Official Ethernet Shieldではさらに制約があることが分かりました。

MISO信号のハイインピーダンス制御

複数のSPIデバイスを使用する場合MISO信号をバス状に配線しますが、MCUと通信できるデバイスは常に1つのみです。そのため、選択されていないデバイスはMISOをハイインピーダンス状態にして、バスから電気的に切り離した状態にする必要があります。

GLCDシールドの制作にて使用したFlash Memory(AT45DB161D)はSS信号をInactiveにするとMISOをハイインピーダンス状態にする仕様になっています。普通こういう設計だろと思うのですが、W5100(Ethernet ShieldのTCP/IP処理チップ)は動作が異なると以下のArduino Forumスレッドに記載がありました:
  Arduino + Ethernet Shield + Another SPI device

W5100チップのMISOハイインピーダンス制御

Forumのスレッドによると、W5100 チップにてMISOをハイインピーダンスにするためには、SS(W5100ではSCS)を制御するのではなく、SEN(SPI enable)信号をLowレベルに落としてやる必要があるとのことです(データーシートには記載がなし)。Official Ethernet ShieldではSENをプルアップしているのみのため、SENをLowに落としてMISOをハイインピーダンスにすることができません。すなわち、このままでは他のSPIデバイスと混在して使用することができません。

対策としては、上記のスレッドに記載があったのですが、「PROG」とシルク印刷されたジャンパーピン用のスルーホールを使うことです。このピンはSENにつながっているため、ここにMCUのI/OピンをつないでEthernet Shieldを使用しない期間はSENをLowレベルに落します。やり方としては、以下の2つが考えられます:

  1. SSはActive Low, SENはActive Highと論理が逆になっています。そのため、Ethernet Shieldに入力するSSを反転してSENに入力する。この場合、インバーター回路を追加する必要があり、Ethernet Shieldに部品や配線を追加することになるため難しいです
  2. もう一つのSPIデバイス用のSS信号をSENに接続すると、もう一つのSPIデバイスを選択(SS = Low)した際にSENがLowに落ちます。この方法だと、PROGピンに配線を追加するだけですので1)より簡単です。ただし、1)に比べてSENをLowに落とすタイミングが遅れると思われ、タイミング的に問題がないかの検証が必要と思われます。

GLCDシールドの制作ではWIZ812MJとAT45DB161DをSPIバス上で混在動作させることができました。WIZ812MJはEthernet Shieldと互換性がありますが、SS信号をモ ジュール内で反転してSENに入力しているため、1)相当の動作をしていることが今回分かりました。GLCDシールドの製作ではこの点はまったく意識していなかったのですが、結果オーライでした。

NKC ElectronicsさんのEthernt ShieldはWIZ812MJを使用しているため、他のSPIデバイスとの混在が前提の場合、こちらの製品を使用するという選択肢もあると思います(この製品はキットのため部品の半田付けが必要です)。

2009/8/17追記:
hamayanさんからW5100 MISO/SEN信号の動作は、SPI Guide for W3150A+ and W5100SPI application note for W5100 and W3150A+に記載がある点をご指摘いただきました。データーシートだけでなく、関連ドキュメントにも目を通さないといかんですね。

Arduino 0017

Arduino 0017が公開されました。色々改善されています。使ってみて気がついたことを記載します。

改善点

1. LiquidCrystal libraryのアップデート

LiquidCrystal(LCD)ライブラリの初期化コード」と同等の初期化コードが追加されました。電源ON時にLCD表示が出ない問題が解消しています。カーソル表示のON/OFF、左右のスクロールなど機能が追加されました。

2. スケッチ毎にライブラリをコンパイル・リンクするようになった

Board Typeを変更した際の「ArduinoライブラリのMCUタイプ依存性」が解消しました。また、ライブラリのコードをいじった際に、ライブラリフォルダー内の.oファイルをいちいち削除する必要がなくなりました。

3. 操作性の改善

  • 複数行を指定してタブ位置の左右移動ができるようになった
  • 複数行の一括コメントアウト・コメント削除
  • File Menuからのスケッチオープンは新規ウインドウが開く(マルチウインドウ対応)
  • シリアル・モニターが別ウインドウになり視認性が向上

4. ユーザーライブラリフォルダーの新設

スケッチフォルダー配下に「libraries」フォルダを作成し、web等で公開されているサードパティーライブラリを格納できるようになりました。例えば;
 スケッチフォルダー\libraries\ks0108GLCD
といった感じです。これまでサードパティーライブラリは、オフィシャルライブラリと同じ「IDEインストールフォルダ\hardware\libraries」に格納する必要がありました。私はIDEを更新した際、旧環境を残すためにIDEインストールフォルダ名前を変えており、サードパティーライブラリをコピーする必要があったのですが、この作業が不要になります。

注意点

1. LiquidCrystal libraryのデフォルト行数が1行表示になりました

Arduino 0016以前はLCDの初期化の中で2行モードに設定していました。以下に0016のモード設定コードを示します。
  command(0x28);  // function set: 4 bits, 1 line, 5x8 dots
 →コメントは1 lineとありますが、bit 3を1にしているため2 lineを指定しています
  command(0x0C);  // display control: turn display on, cursor off, no blinking
  command(0x06);  // entry mode set: increment automatically, display shift, right shift

Arduino 0017に移行後2行表示を行うためには、setupの中でlcd.begin(16, 2)を指定する必要があります。beginメソッドの第2引数が行数の指定です。そのため、既存スケッチの変更が必要になります。以下のようにライブラリのコードを書き替えれば既存スケッチの変更は不要になると思います(試してはいません)。

void LiquidCrystal::init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable,
             uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
             uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7)
{
  _rs_pin = rs;
  _rw_pin = rw;
  _enable_pin = enable;
 
  _data_pins[0] = d0;
  _data_pins[1] = d1;
  _data_pins[2] = d2;
  _data_pins[3] = d3;
  _data_pins[4] = d4;
  _data_pins[5] = d5;
  _data_pins[6] = d6;
  _data_pins[7] = d7;

  pinMode(_rs_pin, OUTPUT);
  // we can save 1 pin by not using RW. Indicate by passing -1 instead of pin#
  if (_rw_pin != -1) {
    pinMode(_rw_pin, OUTPUT);
  }
  pinMode(_enable_pin, OUTPUT);
 
  if (fourbitmode)
    _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
  else
    _displayfunction = LCD_8BITMODE | LCD_1LINE | LCD_5x8DOTS;
 
  begin(16, 1); // → begin(16, 2)に変更
}

問題点

1. IDE起動時に前回終了時のウインドウ位置・サイズ・スケッチが復元されない

Arduino 0016以前のバージョンでは、IDE起動時に、前回終了時のWindow位置・サイズ・使用していたスケッチを復元してくれたのですが、Arduino 0017ではこの機能がうまく動きません。

Arduino 0017では、Processing 1.0.3のコードを取り込んだとありますが、Processingのバグのようです。(確かにProcessingのIDEでも同様の現象になります)。

私はいつもWindow右上の×アイコンをクリックしてIDEを終了しますが、この場合、必ず問題が発生します。回避策としては、File Menu→Quitもしくは Ctrl-QでIDEを終了すると、前回の状態を復元してくれます。ただ、File Menu→Quitで終了した場合でも何かの拍子にデフォルト状態で起動してしまうことがあります。

2. タスクバー・タイトルバーのアイコンがJAVAになる

Arduino 0016以前はArduinoのアイコンが表示されていましたが、Arduino 0017ではJAVAアプリのデフォルトアイコンになってしまいました。私はWindows Vista環境で使用していますが、Arduino Forumの投稿を見るとXP環境でも同様の問題があるようです。

その他

Windows用のIDEにはJava VMが添付されていますが、SunのJava VMをインストールしている場合、IDE添付のVMを削除可能です。

2009/8/17追記:
問題点2を追加。

2009/8/18追記:
注意点の記載を変更(Arduino 0016では、LCDへの初期値として2行表示を指定していることに気がつきました)。

Arduino Nano 3.0 Get

Arduino 3.0 Nanoを買ってしまいました。手持ちのArduinoファミリーと並べると小ささがよく分かります。

Arduinonano

左から順にNano, Duemilanove, Mega, REDUINO-SP328(マイクロファンさん)です。

発売前にスイッチサイエンスさんにて予約の案内が出ていたのですが、私がwebを見た際は「売り切れ」中になっていました。ひょっとすると初回ロットの割当が少なく、次の入荷まで時間がかかるのでは思い、本家のGarvitechさんのWebを見ると先行予約(割引あり)かつ発売日に発送可能でした。そのため、購入はGarvitechさんから直接行いました。

価格は先行予約価格で$29.74+送料$6.65でした。発売日が7/27に対して、7/30に発送のメールが届き、商品が届いたのが8/8でした。発送はUSPS(アメリカ合衆国郵便公社)郵便小包でしたので、配送に一週間程度かかるのは致し方ないと思います。ちなみに、封筒に切手相当のシールが貼ってあり郵便料金が$1.44の表示がありました。う~ん、$5.21がGarvitechさんの手数料なのですね。

私がGarvitechさんに予約を行った直後(発売前)にスイッチサイエンスさんでも予約ができるようになり、8/3に発送したとありましたので、結果的にはスイッチサイエンスさんに注文した方が少し早く届いたと思います。

NanoはAnalog入力(ADC)が8chと、Duemilanoveの6chに対して2ch多い仕様になっています。DuemilanoveのAnalog 入力はDigital I/Oとしても使用できるため、NanoではAnalog入力8ch全てをDigital I/Oとして使用できると期待してNano購入に走ったのですが、この点はハズレでした。事前にATMega328のデーターシートを見れば分かったことですが、ADC0~ADC5の6chはPort CのDigital I/Oとしても使用できますが、Nanoのプラス2chはADC専用でした。

Nano 3.0を使用する場合、IDE-0016ではBoard typeに「Duemilanove w/ ATmega328」を指定します。IDE-0017では「Arduino Duemilanove or Nano w/ ATmega328」を指定します。IDE-0016でArduino Nanoを指定するとスケッチのアップロードができません(Arduino NanoはATmega168のため)。Duemilanove w/ ATmega328の指定で、プラス2chのADCは問題なく使用できました。

Duemilanove/NanoはDigital I/Oが14 + 6 pinありますが(Analog入力をDigital I/Oとして使用した場合)、次に示すように汎用I/Oポートの8bitを連続して使えない点が不満です。Port Dはdigital pin 0~7として8bit全てが配線されていますが、0, 1 pinはシリアル通信用のため実質的にはdigital pin 2~7の6 pinしか使えません。Port Bは水晶発振子をつなぐために2 pinつぶれており、digital pin 8~13の6 pinしか使えません。

一方Arduino MegaはDitigal I/Oが54pinもあるのですが、こんなにあっても使い切れないです。個人的な理想は以下のPin構成です;

  • Digital 16pin(汎用I/Oポート2本の8 bitを分断なしで割り当てる)
  • 別のpin 8本に、シリアル2ch(PCとの通信用に加えて、もう1chハードウェアシリアルがあると便利)、SPI/I2Cの通信関係pin
  • Analog入力8ch

Sanguinoがこの構成に近いので、Pin数を要するスケッチならSanguinoが使いやすそうに思います。どこかでコレクションが増えるか、、、

SPIの基本動作とArduinoでの使い方

漢字表示とGLCDシールド作成でSPIをお勉強しました。SPIの動作やArduinoでの基本的な使い方について分かったことを記載します。

SPIのブロック構成

SPIはボード内のデバイス間通信規格です。2つのデバイスがSPIを使って通信する場合、通信を主導する側がMaster、受動的に動作する側がSlaveとなります。余談ですが、私が以前関わったシステム開発ではMaster(主人)・Slave(奴隷)という用語は差別的な響きがあるためよろしくないと言われて、Main・Subordinary(主・従)という用語を使ったことがありました。

図1にSPIの機能ブロックとMaster~ Slave間の接続構成を示します。Arduinoの場合、AVRがMasterになります。

Spi_block_diagram

図1に示す通り、Master・SlaveデバイスのShift Registerをぐるっとループ接続した形になっており、MasterのShift Registerに設定したデーターがSPI Clockに同期してShift outされSlave側のShift Registerに書き込まれます。

AVR~Slaveデバイス間のデーター送受信

AVRにはSPI専用のShift RegisterとしてSPDR(SPI Data Register)と呼ばれる8bit長のレジスターが存在します。AVRがMasterになる場合の動作概要は以下のとおりです:

  1. SPDRに送信データーを書き込む
  2. SPIクロックがスタートし、データー転送が始まる
  3. 8bitのデーター転送が終了するとSPIクロックが停止し、SPSR(SPI Status Register)のSPIFフラグをセットする

上記のように、SPIは1byteをサイクルとしたデーター転送になります。複数バイトのデーター転送を行う場合は、SPIFフラグセットを確認した後に後続のbyteをSPDRに書き込みます。

SPIには、以下の組み合わせによって、4つのモード(Mode 0~3)があります。

  • クロック極性(CLPOL):クロック停止(アイドル)状態状態にてクロック信号がLow/Highのどちらを取るか
  • クロック位相(CPHA):入出力データーの取り込み・送出をクロックの立ち上がり・立ち下がりのどちらで行うか

Ethernet ShieldやFlash Memoryライブラリで使用しているmode 0は以下の動作となります(SPCRレジスタを以下に設定):

  • CPOL = 0:アイドル時SCKをLowレベルに保持(正パルスのクロックを生成)
  • CPHA = 0:クロックの立ち上がりでMISO(入力)をサンプリング、クロックの立ち下がりでMOSI(出力)を次のデーターに切り替える

データー送信のビット・オーダーはMSB First or LSB FirstをSPCR(SPI Control Register)の設定で指定することができます。ライブラリではMSB Firstになっています。

図2・図3にMasver(AVR) - Slaveデバイス間のデーター送受信イメージを示します。
Spi_datatransferppt

1) Master(AVR)→ Slaveへの送信(図2)
 ①SPDRにデーターをセット
 ②SPIクロックがスタートしデーターを送信(例ではMSB First)
 ③同時にSlave側のShift Registerから送られたデーターを受信する

2) Slave→ Master(AVR)への送信 (図3)
SPIはMaster主導で通信を開始するため、MasterからSlaveに対してSPI Clockを送る必要があります。そのため以下のシーケンスになります;
 ①AVRのSPDRにダミーデーターを設定する(値は任意)
 ②SPI Clockがスタートし、Slaveがデーターを送信する
 ③Slaveから受信したデーターがSPDRに格納される

上記以外に、AVR側のプログラムにてSS(Slave Select)信号を制御する必要があります(ハード自律制御機能はありません)。即ち、データー転送サイクル開始前にSS信号をLowに落として、転送終了後にHighに戻す必要があります。

実際のSPI制御イメージ

GLCDシールドの制作で使用したSPI Flash Memory(AT45DB161Dライブラリ)を例に、SPIの制御イメージを示します。

Flash MemoryからManufacturer・Device IDを読み出してみます。Arduinoのスケッチは以下となります。

#include <at45db161d.h>
ATD45DB161D dataflash;

void setup()
{   
  /* Set baud rate for serial communication */
  Serial.begin(115200);

  uint8_t  status;
  ATD45DB161D::ID id;      // ID情報を保持する構造体
 
  /* Let's wait 1 second, allowing use to press the serial monitor button :p */
  delay(1000);
 
  /* Initialize dataflash */
  dataflash.Init();
  Serial.println("\nDataflash Init");
      
  delay(10);
 
  /* Read status register */
  status = dataflash.ReadStatusRegister();
  Serial.println("Status Register read");

  /* Display status register */
  Serial.print("Status register :");
  Serial.print(status, BIN);
  Serial.print('\n');
 
  /* Read manufacturer and device ID */
  dataflash.ReadManufacturerAndDeviceID(&id);

  /* Read manufacturer and device ID */
  Serial.print("Manufacturer ID :");  // Should be 00011111 (1FH)
  Serial.print(id.manufacturer, HEX);
  Serial.print('\n');

  Serial.print("Device ID (part 1) :"); // Should be 00100110 (26H)
  Serial.print(id.device[0], HEX);
  Serial.print('\n');

  Serial.print("Device ID (part 2) :"); // Should be 00000000
  Serial.print(id.device[1], HEX);
  Serial.print('\n');

  Serial.print("Extended Device Information String Length  :"); // 00000000
  Serial.print(id.extendedInfoLength, HEX);
  Serial.print('\n');

  dataflash.EndAndWait();                //  終了処理(SSをLowに落とす)
}

void loop()
{
}

dataflash.ReadManufacturerAndDeviceID(&id);にてManufacturer・Device IDを読み出します。ReadManufacturerAndDeviceIDメソッドはライブラリ上では、以下のコーディングになっています。

void ATD45DB161D::ReadManufacturerAndDeviceID(struct ATD45DB161D::ID *id)
{
        DF_CS_inactive;    /* Make sure to toggle CS signal in order */
        DF_CS_active;      /* to reset Dataflash command decoder     */
 
       /* Send command */
       spi_transfer(AT45DB161D_READ_MANUFACTURER_AND_DEVICE_ID);

        /* Read Manufacturer ID */
        id->manufacturer = spi_transfer(0x00);
        /* Read Device ID (part 1) */
        id->device[0] = spi_transfer(0x00);
        /* Read Device ID (part 2) */
        id->device[1] = spi_transfer(0x00);
        /* Read Extended Device Information String Length */
        id->extendedInfoLength = spi_transfer(0x00);
}

spi_transfer()関数を呼び出すことによって、ID読み出しのコマンド(0x9F)をSlave(Flash Memory)に送信します。

コマンドを送信した後で、ID情報として4byteを受信する必要があるのですが、図3に示したシーケンスを起こすために、ダミーの0x00を引数としたspi_transfer()の呼び出しを4回行っています。この際の戻り値が受信データーとなります。

spi_transfer関数の定義は以下となっています;

inline uint8_t spi_transfer(uint8_t data)
{
        SPDR = data;
        while(!(SPSR & (1 << SPIF))) ;
        return SPDR;
}

SPDR(SPI Data Register)に送信dataを設定することでデーター転送を起動し、SPSR(SPI Status Register)にSPIFフラグが立ったら受信データー(SPDRの格納値)を返しています。

SPIの特長として、ID読み出しコマンド送信のようにSlaveデバイスからの受信データーを期待しないケースでも、何らかの情報がSlaveから返ってきます。この値がどうなっているのか興味があったため、spi_transfer関数に以下のprint文を入れて実行してみました。

inline uint8_t spi_transfer(uint8_t data)
{
        SPDR = data;
        Serial.print("Send data="); Serial.println(data, HEX);
        while(!(SPSR & (1 << SPIF))) ;
        Serial.print("SPDRrx="); Serial.println(SPDR, HEX);
        return SPDR;
}

<Manufacturer・Device ID読み出しスケッチの実行結果>
(青字がデバック用のprint文による出力です)

Dataflash Init
Send data=D7
SPDRrx=FF
Send data=0
SPDRrx=AC

Status Register read
Status register :10101100

Send data=9F  → ID読み出しコマンド
SPDRrx=FF   → Flash MemoryからはFFが返っている
Send data=0  → ID取得のためのダミーデーター送信
SPDRrx=1F   → Flash Memoryが送信したID情報
Send data=0
SPDRrx=26
Send data=0
SPDRrx=0
Send data=0
SPDRrx=0

Manufacturer ID :1F
Device ID (part 1) :26
Device ID (part 2) :0
Extended Device Information String Length  :0

Send data=D7  → Status要求コマンド
SPDRrx=FF
Send data=0   → Status取得のためのダミーデーター送信
SPDRrx=AC    → 取得したStatus値

上記の結果に示すように、コマンド送信時にFlash Memoryから戻ってくる値は0xFFでした。Flash Memoryにて、コマンド受信開始時点でのShift Register値が送られると思いますが、送ったコマンドがそのまま返ってくるということはshift registerの構造的にないということです。

SPIを使ったブロック転送

I2Cを使用したEEPROMでは、ライブラリを使用して指定したアドレスから連続したデーターをブロック転送することができました。SPIを使用したFlash Memoryでも同様にブロック転送が可能です。AT45DB161Dライブラリにはブロック転送のメソッドがないため、コマンドを発行した後でspi_transfer()を連続して呼び出すことによってブロック転送を実現します。例として、漢字表示のライブラリでAT45DB161Dから1文字分のフォントデーターをブロック読み出しするためのコードを示します。

    dataflash.ContinuousArrayRead(dfAddress[0], dfAddress[1], 1);  // コマンド発行
    for (i = 0; i < dataLength; i++)        // ブロック転送
        fontBuf[i] = spi_transfer(0xff);
    dataflash.EndAndWait();                  // 終了処理(SSをLowに落とす)

Flash Memoryのアドレスは、ContinuousArrayRead()メソッドにて転送開始アドレスを指定し、後続データー取得時はFlash Memory側でアドレスを自動インクリメントしてくれるため、オーバーヘッドは最小限です。

一方Ethernetライブラリでは、W5100(TCP/IP処理チップ)からのデーター読み出しは以下となっています。(読みやすさのために、ダイレクトバス接続のコードを削除し、SPI接続のコードのみを抜粋)

/**
@brief    This function reads into W5100 memory(Buffer)
*/
uint16 wiz_read_buf(uint16 addr, uint8* buf, uint16 len)
{
    uint16 idx = 0;
     IINCHIP_ISR_DISABLE();

    IINCHIP_SpiInit();
   
 for (idx=0; idx<len; idx++)
        {
        IINCHIP_CSoff();                             // CS=0, SPI start

        IINCHIP_SpiSendData(0x0F);        // W5100へのReadコマンド発行
        IINCHIP_SpiSendData(((addr+idx) & 0xFF00) >> 8);   // 読み出しアドレス送信
        IINCHIP_SpiSendData((addr+idx) & 0x00FF);


        IINCHIP_SpiSendData(0);             // ダミーデーターの送信
        buf[idx] = IINCHIP_SpiRecvData();

        IINCHIP_CSon();                             // CS=1, SPI end       
       }

       IINCHIP_ISR_ENABLE();
    return len;
}

なんと、forループの内側で毎回読み出しアドレス 2byteを指定しています(リストの青字部分)。W5100のデーターシートを見ると、Readコマンド(0x0F)+アドレス2 byte+データー1byteの32bitが、1byteのデーターをW5100から読み出すための基本サイクルだと書いてあります。W5100では、SPI接続でのブロック転送モードという概念はないということになります。ArduinoのEthernet接続でスループットを要求することはないでしょうからまぁ許せるのですがオーバーヘッドがでかくてもったいないです。W5100では、8bit data busと15bit address busを使用したMCUとのダイレクト接続モードがあるため、高速処理を行いたい場合はこちらを使えということなのだと思います(address busを2bit接続に縮退したIndirect Busという接続構成もあります)。

SS信号(コードではCS)のLow/High制御もループの内側で1byte受信毎に行っているためオーバーヘッドが多いように思います。他のSPIデバイスと混在した際に、W5100からパケットデーターの途中まで受信したところで処理を中断して、もう一方のSPIデバイスから優先的に受信を行いたいようなケースを加味してこのようなコードになっていると思われます。(他の割り込み処理を考えなければ、関数の出入り口で一回だけSSのON/OFFを行えばよいです)。

AT45DB161DはSPIインタフェース専用のため、ブロック転送の考慮がされています。AT45DB161Dのデーターシートを見ると、SPIクロックは最大66MHz(High Frequency Mode)まで許容しており結構な高速動作が可能です。また、最大SPIクロックが33MHzのLow Frequency Modeが存在します。High Frequency Modeでは、address指定の後にダミーの1byteを追加転送する必要があります(Flash Memoryがデーターを準備する時間を確保するためのwait代わりなのだと思います)。AVRと接続する場合、AVRのクロック÷2が最大SPIクロック周波数のため(SPIの倍速化を参照)、余計なダミーbyte送信が不要となるLow Frequency Modeが最適です。

SPI接続のFlash MemoryはPCのBIOS格納用に使用されているらしく、そのため高クロック化が進んでいるようです。PC起動時には真っ先にBIOSが動いて、POSTや各デバイスの初期化を行います。BIOSが起動するためにはSPI FlashからDRAMにBIOSイメージを展開してBIOSの開始アドレスにJumpする必要がありますが、誰がこの処理を行っているのか(ひょっとしてチップセット?)など、新たな疑問がわいてきます。

その他

SPIのクロック周波数を調べていくうち、I2Cのクロックはどうなっているのか興味が出てきたのでArduinoのライブラリを調べてみました。答えは、100KHzです(以下に示すwireライブラリのコードより)。

  #define CPU_FREQ 16000000L
  #define TWI_FREQ 100000L

// initialize twi prescaler and bit rate
  cbi(TWSR, TWPS0);        // TWI(I2C)のクロック分周比を1:1にセット
  cbi(TWSR, TWPS1);
  TWBR = ((CPU_FREQ / TWI_FREQ) - 16) / 2;   // TWI_FREQにするための調整値をセット

  /* twi bit rate formula from atmega128 manual pg 204
  SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
  note: TWBR should be 10 or higher for master mode
  It is 72 for a 16mhz Wiring board with 100kHz TWI */

以前に、漢字フォントの格納先として「I2C/SPI Memoryのどちらが早いか?」と書いた事があったのですが、圧倒的にSPIが早いです。

余談

漢字表示が一段落したため、次のRSSリーダーをどうするか考えているのですが、今ひとつ進んでいません。XMLの構文解析が重そうです。

RSSなら構文(タグの名称など)はある程度決め打ちにできるため、超簡単なタグ解析処理を自分で作るか、TinyXMLのようなXML Parserを移植するのがよいか(そこそこプログラム規模があるXML ParserがArduinoに乗るか)を考えているのですが、自分で作るのも大変そう(作っても中途半端なものしか出来ない)、一方で出来合いのライブラリを持ってくるのもおもしろくなく、今ひとつモチベーションが上がらない状態です。

2017年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        
無料ブログはココログ