SSブログ

「オープンソースハードウェアセミナーVol1」レポート Arduino Ether Shield de Web Server 改 [ATmarquino Arduino]

chip1 Stopからいらした方、Arduino関連カテゴリをお試しください。http://hamayan.blog.so-net.ne.jp/archive/c2300498101-1

※サンプルwebサーバーはhttp://hamayan.ddo.jp:8888/でアクセスできると思います。
※ああ勿論地球温暖化防止の為(笑)、電源電圧3.3V仕様で運用中です。

Img_1427.jpgまあオリジナルのサンプルWEBサーバーのスケッチがかなーり微妙なので、せめてRequest Methodの解析とURLの判定位はした方が今後の為にも良いでしょうと言う話です。
※オリジナルは、どんなURLのりクエストでも全部同じ内容を表示してしまう。解析は空行の検出のみの機能なので、「WEBサーバー動いた!Arduinoってすごーい!、、、」と言った後が続かない。だって利用できるところが殆ど無いからね。

それの前段階としてまたまたEthernetライブラリをちょっといじって、プログラムメモリ上の静的コンテンツを送信できるようにしました。
これが出来るとプログラムメモリ上に埋め込んだ画像とかも、このサーバーから送れるようになります。
例えばfaviconとかね。

まずはClient.cppに関数を追加します。今度はオーバーライドできませんでした。

まあ見て判る様に、一旦プログラムメモリ上のコンテンツを256byteのSRAM上のバッファに転送して、それからTCPで送信します。コンテンツのサイズが256byteより大きい時は分割します。
※多分もう少し大き目にHEAPから領域を取得できると思うんだけれど、今後のSRAMの使い方が不明なので程々にしています。

うーん、AVRはプログラムメモリの空間とワーク用のSRAM空間が一致しておらず、その為こう言ったところが物凄く面倒なんだよね。こう言ったアーキテクチャに強烈に依存するところが有るので、決してArduino=簡単とは言えないと思うんだけれど。
※またはsocketライブラリ自体もいじって、プログラムメモリ上のコンテンツも直接送信できるようにするとか。
#define  TEMP_HEAP_SIZE  256
void Client::write_P( PGM_VOID_P buf, int size )
{
  uint8_t *temp = (uint8_t *)malloc( TEMP_HEAP_SIZE );
  int sz;
  PGM_P ptr = (PGM_P)buf;

  for( ; size > 0; )
  {
    sz = ( size < TEMP_HEAP_SIZE ) ? size : TEMP_HEAP_SIZE;
    memcpy_P( temp, ptr, sz );
    send( _sock, (const uint8_t *)temp, sz );
    size -= sz;
    ptr += sz;
  }

  free( temp );
}


Client.hには先の関数のプロトタイプ宣言を追加しておきます。ここは割愛。
あと、Ethernet.hに以下の記述を追加して置いて下さい。
#include <avr/pgmspace.h>


改造後のWEBサーバーのスケッチは以下の様になります。

prog_char PROGMEMで型指定されたところがプログラムメモリに埋め込まれます。先にも書いたようにSRAMとは別の空間にありますので、それ専用のアクセス方法が必要となります。

PROGMEMを付けないで書くと恐ろしい事が起きます(笑)。いくらconstを付けてもSRAM上に展開されてしまいますので、気が付かない内にメモリ不足に陥っています。まあ、確かにconst修飾子は値を変更しない事を(コンパイラに)宣言しているだけですから、決してこれを「定数領域に入れろ!」とは言っていない、、、のかもね。
/*
 * Web Server
 *
 * A simple web server that shows the value of the analog input pins.
 */

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

#define  LINE_STRING_SIZE  128

char *line;
char meth[10],url[30],ver[20];
long access_count;

const byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
const byte ip[] = { 192, 168, 1, 177 };
const byte gateway[] = { 192, 168, 1, 1 };

/*contents*/
prog_char PROGMEM http_head[] =
  "HTTP/1.0 200 OK\r\n" \
  "Server: Arduino with Ether Shield/ ver.0.1\r\n" \
  "Content-Type: text/html; charset=UTF-8\r\n\r\n" \
  "<html lang=\"ja\"><head>\r\n" \
  "<meta HTTP-EQUIV=\"Content-type\" CONTENT=\"text/html; charset=UTF-8\">\r\n" \
  "<title>Arduino WEB Server 0.1</title></head>\r\n" \
  "<body bgcolor=\"#ccffcc\">\r\n" \
  "<h3>designed by hamayan</h3>\r\n";

prog_char PROGMEM http_foot[] =
  "</body></html>\r\n\r\n";

prog_char PROGMEM thanks[] =
  "アクセス有難うございます。このページはArduino+Ether Shieldで表示しています。<br>\r\n" \
  "現在はリクエストのパーサーを作成しています。<br>\r\n";

prog_char PROGMEM links[] =
  "hamayan blog <a href=\"http://hamayan.blog.so-net.ne.jp/\">http://hamayan.blog.so-net.ne.jp/</a><br>\r\n" \
  "chip 1 stop <a href=\"http://www.chip1stop.com/\">http://www.chip1stop.com/</a><br>\r\n" \
  "オープンソースハードウェアセミナーのページ <a href=\"http://www.chip1stop.com/knowledge/Arduino/\">http://www.chip1stop.com/knowledge/Arduino/</a><br>\r\n" \
  "Make:Japan <a href=\"http://jp.makezine.com/blog/\">http://jp.makezine.com/blog/</a><br><br>\r\n";

prog_char PROGMEM banner_01[] =
  "<p><a href=\"http://www.chip1stop.com/knowledge/Arduino/\">" \
  "<img src=\"http://www.chip1stop.com/img/link_Arduino.gif\" width=\"468\" height=\"60\" alt=\"Arduinoモニタープログラム参加中\" /></a><br>" \
  "<a href=\"http://www.chip1stop.com/\" title=\"電子部品・半導体の通販サイト - チップワンストップ\">電子部品・半導体の通販サイト - チップワンストップ</a></p><br><br>\r\n";

prog_char PROGMEM img_src[] =
  "<p><img src=\"./image2.jpg\" width=\"200\" height=\"140\" align=\"center\"></p><br><br>\r\n";

prog_char PROGMEM jpeg_head[] =
  "HTTP/1.0 200 OK\r\n" \
  "Server: Arduino with Ether Shield/ ver.0.1\r\n" \
  "Content-Type: image/JPEG\r\n\r\n";

Server server = Server( 8888 );

void setup()
{
  Ethernet.begin( (uint8_t *)mac, (uint8_t *)ip, (uint8_t *)gateway );
  server.begin();
  line = (char *)malloc( LINE_STRING_SIZE );
}

void loop()
{
  Client client = server.available();

  if( client )
  {
    while( client.connected() )
    {
      char *dst = HTTPGets( &client, line, LINE_STRING_SIZE );
      char *argv[ 10 ];
      int div_num = split( dst, argv, sizeof(argv) / sizeof(argv[0]) );  /*文字列分割*/
      strcpy( meth, argv[ 0 ] );
      strcpy( url, argv[ 1 ] );
      strcpy( ver, argv[ 2 ] );
      break;
    }

    while( client.connected() )
    {
      char *dst = HTTPGets( &client, line, LINE_STRING_SIZE );
      if( dst != NULL && *dst == '\0' )  /*改行のみの行を検出*/
      {
        index_html( &client );
        break;
      }
    }

    delay( 1 );
    client.stop();
  }
}

void index_html( Client *client )
{
  //httpヘッダーの返信
  client->write_P( http_head, sizeof( http_head ) - 1 );

  //サンキューメッセージ
  client->write_P( thanks, sizeof( thanks ) - 1 );

  //リンク
  client->write_P( (const uint8_t *)links, sizeof( links ) - 1 );

  //アクセスカウント
  sprintf( line, "COUNT=%d <br>\r\n", ++access_count );
  client->write( (const uint8_t *)line, strlen( line ) );

  //バナー
  client->write_P( (const uint8_t *)banner_01, sizeof( banner_01 ) - 1 );

  //Footer
  client->write_P( (const uint8_t *)http_foot, sizeof( http_foot ) - 1 );
}

/*
HTTPプロトコルでは行末はCR、LFであると言う前提がある
*/
static char *HTTPGets( Client *client, char *dst, int size )
{
  int loop;
  char c,*ptr,*limit;

  ptr = dst;
  limit = dst + size;

  for( loop = 10; loop > 0; loop-- )
  {
    if( ptr == limit ) return (char *)0;  /*上限の確認*/

    if( client->available() )
    {
      loop = 10;  /*タイムアウト延長*/

      c = client->read();  /*一文字取得*/

      if( c == '\r' )  /*CRはNULLに変換*/
      {
        *ptr++ = '\0';
      }
      else if( c == '\n' )  /*LFは終端文字*/
      {
        *ptr = '\0';
        return dst;
      }
      else  /*文字の取得*/
      {
        *ptr++ = c;
      }
    }
    else
    {
      delay( 100 );  /*残りのデータが遅れて来る可能性があるので、ここで待ちを入れる*/
    }
  }

  return (char *)0;
}

/*
文字列分割
*/
static int split( char *str , char *argv[], int sz )
{
  int argc = 0;

  while( *str != '\0' && argc < sz )
  {
    if( isgraph( *str ) != 0 )
    {
      argv[ argc++ ] = str;
      while( *str != '\0' && isgraph( *str ) != 0 ) str++;
    }
    else *str++ = '\0';
  }

  return argc;
}


Arduinoモニタープログラム参加中
電子部品・半導体の通販サイト - チップワンストップ




Arduinoをはじめよう

Arduinoをはじめよう

  • 作者: Massimo Banzi
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2009/03/27
  • メディア: 単行本(ソフトカバー)



マスタリングTCP/IP 入門編 第4版

マスタリングTCP/IP 入門編 第4版

  • 作者: 竹下 隆史
  • 出版社/メーカー: オーム社
  • 発売日: 2007/02/24
  • メディア: 大型本



詳解TCP/IP〈Vol.1〉プロトコル

詳解TCP/IP〈Vol.1〉プロトコル

  • 作者: W.リチャード スティーヴンス
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2000/12
  • メディア: 単行本



nice!(0)  コメント(3)  トラックバック(0) 

nice! 0

コメント 3

noritan

Clientクラスを継承し、
void ClientX::write( PGM_VOID_P buf, int size )
を実装したクラスを定義するという案はいかがでしょう。

//サンキューメッセージ
client->write( thanks, sizeof( thanks ) - 1 );

とメソッド名を気にすることなくプログラムできます。


文字列定数配列の宣言は、

const char * const XXX[];

でいけそうだけど、これは、そういう問題じゃないんだろうな。きっと。

by noritan (2009-06-03 09:12) 

hamayan

なるほど、関数のオーバーライドを使うのではなく、新たにClassを派生させるのですね、、、って、C++は勉強不足でよく判っていませんが。

> 文字列定数配列の宣言は、
> const char * const XXX[];

PROGMEMなんて使わずもっとスマートな方法があれば良いのですが、、、有るのかなぁ?。
私はAVRはアセンブラから始めたから、面倒だけれど仕方が無い!で済むのですけれど、汎用OS上のC言語から入った人は「なんのこっちゃ?」ですよね。

アーキテクチャ的にはもっと素直なマイコンが有っただろうに。例えばHCS08なんかはアキュムレータータイプで教科書に載っているそのままで理解し易いし、変なところも無い。

by hamayan (2009-06-03 09:34) 

hamayan

トホホ。結局クラスの継承判っていません。

by hamayan (2009-06-03 23:32) 

コメントを書く

お名前:[必須]
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

※ブログオーナーが承認したコメントのみ表示されます。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。