作品タイトル:GR-KURUMIの機能を使う Ethernet編
表示名:@chobichan
コンセプト・作品説明 |
---|
GR-KURUMIのEthernetライブラリを使います |
GR-KURUMIでもEthernet
GR-KURUMIのスケッチリファレンスでは永久に「編集中」となっていますが、実際には使えます。
標準はArduinoのEther Shieldではなく、Wiznet社のW5500をコントローラチップにしたWIZ550ioです。
知っている人は少ないと思いますが。
http://www.wiznet.co.kr/product-item/wiz550io/
コントローラーチップのW5500は、Ether Shieldで使用していたW5100の後継の後継にあたり、ハードウエアバグが修正されたりソケットの数が増えています。
今回はこのW5500を使ってみます。
えーっと、リンク先の基板と写真が違う?
この基板はオリジナル設計の基板です。WalNetと呼んでいます。WIZ550ioとの違いはそんなに有りません。
早速動かしてみます。サンプルとしてはArduinoの参考例をWalNetに合わせたものです。
https://www.arduino.cc/en/Tutorial/WebClient
参考コードを掲載して置きます。
/* ------------------------------------------------------------------------ */ /* W5500を使ってみる */ /* designed by hamayan */ /* Copyright(C) hamayan */ /* since 2004 - 2016 */ /* ------------------------------------------------------------------------ */ //https://www.arduino.cc/en/Tutorial/WebClient #include#include #define RSTn 4 byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; char server[] = "www.google.com"; IPAddress ip(192, 168, 100, 177); EthernetClient client; void setup() { Serial.begin(9600); Serial.println("W5500 Ethernet controller chip test."); pinMode( RSTn, OUTPUT ); //w5500 hardware reset digitalWrite( RSTn, LOW ); delay( 10UL ); digitalWrite( RSTn, HIGH ); // start the Ethernet connection: if(Ethernet.begin(mac) == 0) { Serial.println("Failed to configure Ethernet using DHCP"); // try to congifure using IP address instead of DHCP: Ethernet.begin(mac, ip); } // give the Ethernet shield a second to initialize: delay(1000); Serial.println("connecting..."); if( client.connect(server, 80) ) { Serial.println("connected"); // Make a HTTP request: client.println("GET /search?q=GR-KURUMI HTTP/1.1"); client.println("Host: www.google.com"); client.println("Connection: close"); client.println(); } else { // if you didn't get a connection to the server: Serial.println("connection failed"); } } void loop() { // if there are incoming bytes available // from the server, read them and print them: if( client.available() ) { char c = client.read(); Serial.print(c); } // if the server's disconnected, stop the client: if( !client.connected() ) { Serial.println(); Serial.println("disconnecting."); client.stop(); // do nothing forevermore: while (true) {} } }
応答を見てみると、「そのドキュメントはここに引っ越したぞ!」
となっていてあれですが、無事HTTPでGoogleに接続できた様です。
すでにDHCPもDNSも動いているのがとても楽ですね。
WalNetを使うに当たってちょっとだけライブラリの一部に
修正が必要となります。gr_common/RLduino78/libraries/Ethernet/utility
以下のw5100.hのWIZ550io_WITH_MACADDRESSを
コメントアウトするか別の名前に定義してしまうかで無効
としてください。
WIZ550ioは基板上にMAC Addressが格納されたEEPROM
が搭載されていますが、WalNetは有りません。
インターネットラジオを作ってみる
SDカードで音楽を再生した「SDカード編」ではMP3デコーダーICを使用して音楽の再生を行いました。
インターネット上にも音楽を配信しているサイトが有ります。
一般的にインターネットラジオと呼ばれるサービスです。
WalNetのネットワーク機能と、SDカード編の基板(WalSound)を組み合わせてインターネットラジオ局にアクセスし、音楽を再生してみましょう。ある意味これはインターネットに接続された「物」なので、IoTとも言えるかもしれません。
どうやってインターネットラジオに接続するか?
PC上で動くWinampなどのアプリケーションは、ネット上のインターネットラジオ局を探し出してくれ、利用者は特に苦労せずに音楽配信を聞くことができますが、GR-KURUMIで作る装置ではインターネットラジオ局を自ら探すところから始めなくてはなりません。
とは言え本当にGR-KURUMIにインターネット上を検索させるのは大変なので、あらかじめラジオ局を見つけて置く事とします。
インターネットラジオ局は沢山あると思いますが、今回はこのサイトで情報を入手します。
http://www.shoutcast.com
サイトを開くとジャンル別の表形式で表され、Bit Rateや形式(MP3とかAACとか)が記載されています。
ダウンロードマークのアイコンをクリックするとWinamp、M3U、XSPFのいずれかを選択できます。
ここではWinampを選んでみます。
ダウンロードすると拡張子が.PLSのファイルとなります。
これを適当なテキストエディタで開いてみます。
[playlist]
numberofentries=1
File1=http://167.114.131.90:5344/stream
Title1=(#1 - 0/1000) ebrandradio
Length1=-1
Version=2
音楽の配信サーバーのグローバルIPアドレス:167.114.131.90
サーバーのポート番号:5344
ファイル名:stream
が判りました。
※グローバルIPアドレスの代わりにドメイン名が書かれている物もあります。
このアドレス、ポート番号に対してファイルをGETするHTTP文を送信すると音楽データ、このサイトならMP3形式をどんどん送って来ますので、それをMP3デコーダーICにちぎっては投げ、ちぎっては投げします。
なおVS1063AはMP3形式以外にAACもサポートしていますので、ほとんどの配信サービスに対応できると思います。
※対応形式はVS1063Aの資料を読んでください。
高いBit Rateの対応
SDカードから音楽を再生した場合は、192kbpsの曲でも問題無く再生できました。しかしこのインターネットラジオでは32kbpsくらいが限界です。インターネット側の効率に問題がありそうです。
こう言ったネットワーク上の問題が発生した時は、パケットモニタを使うと問題が見えてくることがよくあります。
使用しているのはレイヤーのPacMonです。
http://www.layer.co.jp
日本語対応で読みやすく、とても便利です。
青の反転表示がサーバーからのパケットです。
これを見るとデータは1024byteと細切れに、さらに間隔も数msの時も有れば、170ms位の時もあります。
プロトコルであるTCPでは相手の受信バッファの空き容量に応じて送信データサイズを調整するフロー制御が働きます。
それはサーバーがデータを送ってくる直前のWalNetからのパケットの内容を見てみるとフロー制御がどのように動いているのかが判ります。
反転表示した「ウインドウ」が2048byteとなっています。
この小さなウインドウサイズにサーバーが遠慮して大きなデータを送って来ない事が判りました。
ではどうすれば良いのでしょう?
もちろん受信バッファのサイズを大きくすればイイのです。
コントローラチップのW5500は8個のソケットを持つ事ができます。それぞれのソケットで使用する送受信バッファは、32kbyteのRAMの中からいくつかのパターンで切り分けする事ができます。
複数のソケットを同時に使い倒すようなアプリケーションではなく、今回のような使い方であればソケットは1つで済んでしまいます。
なので、32kbyteをソケット0が送信16kbyte、受信16kbyteと使うように設定してしまいます。
この変更はgr_common/RLduino78/libraries/Ethernet/utility以下のw5500.cppで行います。
初期化関数initの中でfor文で各ソケットのバッファサイズを一律に2kbyteで決めていますが、ソケット0のみ16にし、それ以外のソケットは0を設定します。
コンパイルして動かしてみました。WalNetの受信バッファの空き容量が16kbyteとなった事が判ります。
サーバーから送られてくるデータのサイズが格段に増えました。
効率の悪いprint文をやめる
HTTP文を送る間だけなので我慢できると言えばできるのですが、あまりにも気持ち悪いのでやっぱりなんとかする事とします。
例によってパケットモニタの画面を見てください。
HTTP文を送るところで大量の1byteのパケットがWalNetから送信されています。
HTTP文は全部で96文字ありますから、96回この効率の悪いバカげたパケットが送られます。
本来なら1回の送信で済むものを。
この原因はもちろんclient.printにあります。もともと1byte単位で送信する事が前提のシリアル向けのキャラクタ関数ですから、Ethernetでもそれをやってしまっているだけです。
しかしEthernetはデータをブロック処理できますのでこのやり方はネットワーク上を無駄に混雑させるだけで百害有って一利無しです。
こんなのがIoTとか言ってインターネットで輻輳を起こしていたらと思うと。
もちろん対策はclient.writeでいっぺんに!です。
こう言うのは実際にLANケーブル上を流れているパケットをパケットモニタで見ないとなかなか判らないですね。
参考のコードを掲載しておきます。
/* ------------------------------------------------------------------------ */ /* W5500でインターネットラジオ */ /* designed by hamayan */ /* Copyright(C) hamayan */ /* since 2004 - 2016 */ /* ------------------------------------------------------------------------ */ #include#include #include #include //for vs1063a #define RSTn 4 extern "C" { #include "string.h" } #if !defined( _MULTITASK_H_ ) #define dly_tsk(tim) delay(tim) #define rot_rdq() #endif /*_MULTITASK_H_ */ void VS1011e_Wait_DREQ( void ); void vs1011eInit( void ); void VS1011e_SCI_Write( unsigned char adr, unsigned short data ); unsigned short VS1011e_SCI_Read( unsigned char adr ); void VS1011e_SDI_Write( const char *data, unsigned long size ); void setVolum( unsigned short vol ); bool isDREQ( void ); byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; IPAddress ip(192, 168, 100, 177); struct INTERNET_RADIO { const char *name; //station name const char *domain; //server domain unsigned short port; //server port const char *fn; //file name int rate; //bit rate //const char *type; //mp3 or aac } station[] = { {"ABC Lounge","listen.radionomy.com",80,"/ABC-Lounge",128}, {"SmoothJazz.com Global Radio","75.126.221.202",8000,"/",128}, {"TheJazzGroove.com","199.180.72.2",8015,"/",128}, {"ALL SMOOTH JAZZ","listen.radionomy.com",80,"/all-smooth-jazz",128}, {"ABC Jazz","listen.radionomy.com",80,"/ABC-Jazz",128}, {"KHNY Honey 103","listen.shoutcast.com",80,"/Honey103",128}, {"181.fm - The Breeze","108.61.73.120",8004,"/",128}, {"Smooth Jazz 247","listen.radionomy.com",80,"/Smoothjazz247",128}, {"SwissGroove","184.154.202.243",8053,"/",128}, {"Instrumentals Forever","listen.radionomy.com",80,"/Instrumentals-Forever",128}, {"SmoothJazz.com Global Radio","173.244.199.248",80,"/",128}, {"Smooth Jazz Florida","162.244.80.41",8802,"/",128}, {"BEST SMOOTH JAZZ - UK (LONDON)","64.95.243.43",8002,"/",128}, {"JAZZ.FM91 (CJRT-FM)","206.223.188.170",8000,"/",96}, }; int stationNumber = 0; EthernetClient client; /*DREQ:D3(p16),XRESET:D5(p15),XCS:D6(p10),XDCS:D9(p13) */ #define VS_DRQ (P1.BIT.bit6 == 1) #define VS_RST_IS_0 P1.BIT.bit5 = 0 #define VS_RST_IS_1 P1.BIT.bit5 = 1 #define VS_XDCS_IS_0 P1.BIT.bit3 = 0 #define VS_XDCS_IS_1 P1.BIT.bit3 = 1 #define VS_XCS_IS_0 P1.BIT.bit0 = 0 #define VS_XCS_IS_1 P1.BIT.bit0 = 1 #define VS_SCI_OPE_WRITE 0x02 #define VS_SCI_OPE_READ 0x03 enum VS_REGISTERS { VS_MODE, VS_STATUS, VS_BASS, VS_CLOCKF, VS_DECODE_TIME, VS_AUDATA, VS_WRAM, VS_WRAMADDR, VS_HDAT0, VS_HDAT1, VS_AIADDR, VS_VOL, VS_AICTRL0, VS_AICTRL1, VS_AICTRL2, VS_AICTRL3 }; void setup() { Serial.begin(230400); Serial.println("W5500 internet radio."); pinMode( RSTn, OUTPUT ); //w5500 hardware reset digitalWrite( RSTn, LOW ); delay( 10UL ); digitalWrite( RSTn, HIGH ); SPI.begin(); SPI.setClockDivider(SPI_CLOCK_DIV8); //2mhz SPI.setDataMode(SPI_MODE0); vs1011eInit(); setVolum( 0x2020 ); //上位と下位に分かれている。数字が大きくなると音が小さくなる。 SPI.setClockDivider(SPI_CLOCK_DIV2); //8mhz // start the Ethernet connection: if(Ethernet.begin(mac) == 0) { Serial.println("Failed to configure Ethernet using DHCP"); // try to congifure using IP address instead of DHCP: Ethernet.begin(mac, ip); } // give the Ethernet shield a second to initialize: delay(1000); Serial.print("connect to "); Serial.println(station[stationNumber].domain); while( true ) { if( client.connect(station[stationNumber].domain, station[stationNumber].port) ) { Serial.print(station[stationNumber].name); Serial.println(" connected."); // Make a HTTP request: String str = "GET "; str += station[stationNumber].fn; str += " HTTP/1.1\r\n"; str += "Host: "; str += station[stationNumber].domain; str += "\r\n"; str += "Accept: */*\r\n"; str += "User-Agent: GR-KURUMI\r\n"; str += "Connection: Close\r\n\r\n"; client.write((const unsigned char*)&str[0],str.length()); Serial.print( str ); unsigned long lastTime = millis(); #define TIMEOUT 5000UL #define RCV_BUFFER_LIMIT 2048 int rcvSz; byte *mp3Data; while( true ) { rcvSz = client.available(); if(rcvSz > 0) { Serial.print("rcvSz="); Serial.println(rcvSz,DEC); rcvSz = (rcvSz > RCV_BUFFER_LIMIT) ? RCV_BUFFER_LIMIT : rcvSz; mp3Data = new byte[rcvSz]; rcvSz = client.read( mp3Data, rcvSz ); SPI.setClockDivider(SPI_CLOCK_DIV8); VS1011e_SDI_Write( (const char *)mp3Data, (unsigned long)rcvSz ); SPI.setClockDivider(SPI_CLOCK_DIV2); delete[] mp3Data; lastTime = millis(); } else { if( (millis() - lastTime) > TIMEOUT ) { Serial.println("TIMEOUT!"); client.stop(); if( ++stationNumber >= (sizeof(station) / sizeof(station[0])) ) stationNumber = 0; break; } } if(!client.connected()) { Serial.println("DISCONNECTED!"); client.stop(); if( ++stationNumber >= (sizeof(station) / sizeof(station[0])) ) stationNumber = 0; break; } } } else { // if you didn't get a connection to the server: Serial.println("connection failed"); delay( 60 * 1000UL ); } } } void loop() { } /****************************************************************************/ /* vs1011e初期化 */ /****************************************************************************/ void vs1011eInit( void ) { int i; char dummy; unsigned short loud; /*PORTの設定*/ PM1.BIT.bit6 = 1; //dreq input mode PIM1.BIT.bit6 = 1; //ttl input PM1.BIT.bit5 = 0; //xreset output mode POM1.BIT.bit5 = 0; //cmos PM1.BIT.bit3 = 0; //xdcs output mode POM1.BIT.bit3 = 0; //cmos PM1.BIT.bit0 = 0; //xcs output mode POM1.BIT.bit0 = 0; //cmos /*XRESET*/ VS_RST_IS_0; dly_tsk(10); /*XRESETの解除*/ VS_RST_IS_1; /*ソフトウエアリセット*/ VS1011e_SCI_Write( VS_MODE, 0x0804 ); VS1011e_SCI_Write( VS_MODE, 0x0800 ); /*モード設定完了*/ VS1011e_SCI_Write( VS_CLOCKF, 0xa000 + 1072 ); /*クロック設定 x4 0x8000:x3.5*/ VS1011e_SCI_Write( VS_VOL, 0x2020 ); /*音量設定 夜、寝ている時にいい感じ*/ //VS1011e_SCI_Write( VS_VOL, 0x1010 ); /*音量設定*/ loud = VS1011e_SCI_Read( VS_VOL ); dly_tsk(100); /*バッファを0で埋める*/ for( i = 0, dummy = 0; i < 2048; i++ ) VS1011e_SDI_Write( &dummy, 1 ); } /****************************************************************************/ /* VS1011eのDREQがネゲートされるのを待つ */ /****************************************************************************/ void VS1011e_Wait_DREQ( void ) { while( VS_DRQ == false ) rot_rdq(); } /****************************************************************************/ /* SCIの書き込み */ /****************************************************************************/ void VS1011e_SCI_Write( unsigned char adr, unsigned short data ) { /*DREQのチェック*/ if( VS_DRQ == false ) VS1011e_Wait_DREQ(); /*XCSのアサート*/ VS_XCS_IS_0; /*オペレーションコードの書き込み*/ SPI.transfer(VS_SCI_OPE_WRITE); /*アドレスの書き込み*/ SPI.transfer(adr); /*データの書き込み*/ SPI.transfer(data >> 8); SPI.transfer(data >> 0); /*XCSのネゲート*/ VS_XCS_IS_1; } /****************************************************************************/ /* SCIの読み込み */ /****************************************************************************/ unsigned short VS1011e_SCI_Read( unsigned char adr ) { int i; unsigned char ope; unsigned short data = 0; /*DREQのチェック*/ if( VS_DRQ == false ) VS1011e_Wait_DREQ(); /*XCSのアサート*/ VS_XCS_IS_0; /*オペレーションコードの書き込み*/ SPI.transfer(VS_SCI_OPE_READ); /*アドレスの書き込み*/ SPI.transfer(adr); /*データの読み込み*/ data = SPI.transfer(0xff) << 8; data |= SPI.transfer(0xff) << 0; /*XCSのネゲート*/ VS_XCS_IS_1; return data; } /****************************************************************************/ /* SDIの読み込み */ /****************************************************************************/ void VS1011e_SDI_Write( const char *data, unsigned long size ) { /*XDCSのアサート*/ VS_XDCS_IS_0; for( ;size > 0; size-- ) { /*書き込めるまで待ちを入れる*/ while( size > 32 && VS_DRQ == false ) rot_rdq(); //while( size > 32 && digitalRead(dreq) == LOW ) rot_rdq(); /*データの書き込み*/ SPI.transfer(*data++); } /*XDCSのネゲート*/ VS_XDCS_IS_1; } /****************************************************************************/ /* volume control */ /* 上位と下位に分かれている。数字が大きくなると音が小さくなる。 */ /****************************************************************************/ void setVolum( unsigned short vol ) { VS1011e_SCI_Write( VS_VOL, vol ); /*音量設定*/ } /****************************************************************************/ /* what is state of dreq pin ? */ /****************************************************************************/ bool isDREQ( void ) { return VS_DRQ; }
極一般のハードウエアエンジニア。たまに雑誌記事を書いています。
twitterアカウント:https://twitter.com/chobichan