So-net無料ブログ作成
検索選択
プログラム三昧 ブログトップ
前の20件 | 次の20件

HTML文書作成用コード変換器 [プログラム三昧]このエントリーを含むはてなブックマーク#

WS000262.png

ソースコードがたっぷり入った BLOG を書く時には、 HTML 文書向けに記号を変換して書く必要があります。 毎度毎度、テキスト・エディタで置換をするのも手間なので、変換装置を作ってみました。

HTML文書 : escapeHTML.html

一般に HTML の記号として認識されないように変換する作業を escape と呼んでいます。 ここで使用している手法は、prototype.jsで使われていた方法です。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<!-- $Id: escapeHTML.html,v 1.1.1.1 2009/06/11 13:36:21 noritan Exp $ -->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" >
<head profile="http://www.w3.org/2005/10/profile">
<title>HTML escape converter</title>
</head>
<body>
<h1>HTML escape converter</h1>
<form action="#">
  <p>
    <textarea name="source" id="source" rows="10" cols="80"></textarea>
  </p>
  <p>
    <input type="button" value="Convert" onclick="convert_message();" />
  </p>
  <p>
    <textarea name="destination" id="destination" rows="10" cols="80"></textarea>
  </p>
</form>

<script type="text/javascript">

function escapeHTML(s) {
  var div         = document.createElement('div');
  var text        = document.createTextNode(s);
  div.appendChild(text);
  return div.innerHTML;
}
function convert_message() {
  var source      = document.getElementById("source");
  var destination = document.getElementById("destination");
  destination.value = escapeHTML(source.value);
}

</script>
</body>
</html>

上の TextBox に入れた文書が HTML に埋め込んでも問題の無い文書になって、下の TextBox に入ります。

問題点もある

この変換装置は、 Firefox では正しく働きますが、 InternetExplorer では改行の処理が異なっているために、狙い通りの出力になりません。 InternetExplorer でも使えるようにするためには、現在使用中のブラウザを判別して、別の "escapeHTML()" 関数を利用します。

function escapeHTML(s) {
    return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}

これでは、簡単に変換装置が書けるというメリットがなくなってしまいますので、今回はあえて対応しないことにしました。 どうしてもという場合には、素直に prototype.js を使ったほうが賢明だと思います。

参考サイト

escapeHTML.html
今回作成した WEB ページは、ここにあります。
prototype.js
JavaScript を使い倒すためのユーティリティ・プログラムです。 このサイズのスクリプトには、ちょっと大げさな気がするので、私は使っていません。

Python CGI で 掲示板みたいなものを作る~Ajax編~ [プログラム三昧]このエントリーを含むはてなブックマーク#

WS000261.png

これまで、 HTML 文書から直接 CGI を呼び出すことで、掲示板みたいなものを実現してきました。 今回は、 JavaScript ファイルを追加して、いわゆる "Ajax" に挑戦してみます。

HTML : visitor_world6.html

今回のメッセージ送信画面は、今までとちょっと違います。 メッセージを記入する "TextBox" と "Submit" "Clear" ボタンが並んでいる所までは同じなのですが、今回は、その下にメッセージのリストが表示されています。

"TextBox" にメッセージを記入して、 "Submit" ボタンが押したら、今までは CGI を直接呼び出していました。 今回は、一旦 "JavaScript" が "Submit" ボタンのクリックを受けとめて、そこから改めて CGI を呼び出して、メッセージの追加とメッセージ一覧の取り寄せを行います。 つまり、 "JavaScript" がサーバにデータを送信し結果を受信します。 画面上のメッセージのリストは、取り寄せたメッセージ一覧を元に動的に書き換えられます。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http$#58;//www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<!-- $Id: visitor_world6.html,v 1.2 2009/06/11 12:50:27 noritan Exp $ -->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" >
<head profile="http://www.w3.org/2005/10/profile">
<title>VISITOR WORLD 6</title>
</head>
<body>
<h1>VISITOR WORLD 6</h1>
<form action="#">
<div>
<textarea name="message" id="message" rows="4" cols="40"></textarea>
</div>
<p>
<input type="button" value="Submit" onclick="submit_message();" />
<input type="reset" value="Clear" />
</p>
</form>
<div id="table"> </div>
<script src="xmlhttprequest.js" type="text/javascript"></script>
<script src="visitor_world6.js" type="text/javascript"></script>
</body>
</html>
2009-06-11 21:45
"onClick=" を "onclick=" に修正
「<form>」 を 「<form action="#">」に修正

HTML文書自体は、この程度です。 面倒な処理は、この HTML 文書から呼び出している、二つの "JavaScript" ファイルに押し込めてあります。

JavaScript : xmlhttprequest.js

一つ目のファイル、 "xmlhttprequest.js" は、 "InternetExplorer" で動作させるための仕掛けです。 この記述そのものは、短期集中連載●今こそAjaxに強くなる - ITproから貰ってきました。 したがって、詳しい情報は、こちらの記事をご参照ください。

ちなみに、当方自宅の InternetExplorer では、 ActiveX オブジェクトの生成がなぜかうまくいかず、動作確認が出来ませんでした。 他にも動かないという方がいらっしゃったら、お知らせください。 何とかする気になるかもしれません。

JavaScript : visitor_world6.js

このファイルには、 HTML 記述とサーバ上の CGI との仲介を行うための記述が記載されています。 凝る必要性を感じなかったので、オブジェクト指向を無視した、ベタな記述になっていますので、ご勘弁を。

//
//   BBS using Ajax technique
//
// $Id: visitor_world6.js,v 1.3 2009/06/11 12:41:10 noritan Exp $

//
//   Submit a message to the server
//
function submit_message() {
  var element = document.getElementById('message');
  var query = 'message='+encodeURIComponent(element.value);
  xmlhttp = new XMLHttpRequest();
  xmlhttp.open('POST', 'visitor_world6.cgi', true);
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
      update_table(xmlhttp.responseXML);
    }
  }
  xmlhttp.setRequestHeader(
    'Content-Type', 'application/x-www-form-urlencoded;charset=utf-8'
  );
  xmlhttp.setRequestHeader("Content-Length", query.length);
  xmlhttp.send(query);
}

//
//   Get a table of messages.
//
function get_table() {
  xmlhttp = new XMLHttpRequest();
  xmlhttp.open('GET', 'visitor_world6.cgi', true);
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
      update_table(xmlhttp.responseXML);
    }
  }
  xmlhttp.send(null);
}

//
//   Escape a string with entity references
//
function escapeHTML(str) {
  str = str.split("&").join("&amp;");
  str = str.split("<").join("&lt;");
  str = str.split(">").join("&gt;");
  str = str.split('"').join("&quot;");
  str = str.split("{").join("&#123;");
  str = str.split("}").join("&#125;");
  str = str.split("'").join("&#039;");
  return str;
}

//
//   Update a table of message with a received XML
//
function update_table(doc) {
  var str = "";
  var element   = document.getElementById('table');
  var topnode   = doc.getElementsByTagName("visitor-memo")[0];
  var mes_list  = topnode.getElementsByTagName("message");
  
  str += '<dl>\n'
  for (var i = 0; i < mes_list.length; i++) {
    var date     = escapeHTML(mes_list[i].getAttribute("date"));
    var message  = escapeHTML(mes_list[i].firstChild.nodeValue);
    str += "<dt>" + date + "</dt>\n";
    str += "<dd>" + message + "</dd>\n";
  }
  str += "</dl>\n"
  element.innerHTML = str;
}

//
//   Initialize the table visualization
//
get_table()
2009-06-11 08:50
"mes_list[i]textContent" を "mes_list[i].firstChild.nodeValue" に修正

問い合わせは、 "XMLHttpRequest" オブジェクトを利用して "GET" または "POST" を発行しています。 "POST" を送信する具体的な方法については、AJAXでXMLHttpRequestのopenをPOST、sendに内容を送っても送信できない時 - ウィリアムのいたずらの開発日記を参考にさせてもらいました。

問い合わせをサーバの CGI に行った結果は、 XML フォーマットで返ってきます。 このため、返ってきた DOM オブジェクトから必要な情報を取り出す操作を行っています。

XMLに含まれるメッセージを画面上に表示する時には、クロス・サイト・スクリプティング等への警戒から、ある特定の記号を実体参照に変換しています。 文字列を実体参照に変換する関数は、文字の実体参照化からいただきました。

CGI : visitor_world6.cgi

サーバに配置した CGI の機能そのものは、今までとほぼ同じですが、出力が HTML 文書になっているか、 XML データになっているかという違いがあります。 出力は、 W3CMarkup Validation で正しい XML 文書であるとお墨付きをもらうことができる、完全を目指した XML 文書が得られるようになっています。

#!/usr/local/bin/python
# $Id: visitor_world6.cgi,v 1.1 2009/06/10 09:57:59 noritan Exp $

import os
import sys
import cgi
import urllib
import time
import cgitb

# Show error as a page description.
sys.stderr = sys.stdout
cgitb.enable()

# Database releated information
db_file = "visitor.sqlite"
table_name = "visitor"
command = "/usr/local/bin/sqlite3 %s" % (db_file)

# Get a POST data.
form = cgi.FieldStorage()

# Get Current time
now = time.time()

# Get and escape a MESSAGE
message_key = 'message'
if message_key in form:
    message = urllib.quote(cgi.escape(str(form.getvalue(message_key))))
else:
    message = ""

# Compose a SQL
sql = "SELECT time, description FROM %(table)s ORDER BY time DESC;"
if len(message) > 0:
    sql = \
      "INSERT INTO %(table)s VALUES (%(now)f, \"%(message)s\");\n" \
      + sql

# Show HTML header
print """Content-type: text/xml; charset="utf-8"

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE visitor-memo [
<!ELEMENT visitor-memo (message)* >
<!ELEMENT message (#PCDATA) >
<!ATTLIST message date CDATA #REQUIRED >
]>
"""

# Access to the DATABSE
(pipe_out, pipe_in) = os.popen2(command)
pipe_out.write(
  sql % {"table":table_name, "now":now, "message":message}
)
pipe_out.close()

# Show a list of visitor record
print """<visitor-memo>
"""

# Make a list of messages
try:
    for line in pipe_in:
        field = line.split("|")
        asctime = time.strftime(
          "%Y-%m-%d (%A) %H:%M:%S",
          time.localtime(float(field[0]))
        )
        message = urllib.unquote(field[1])
        print "<message date=\"%s\">%s</message>\n" % (asctime, message)
finally:
    pipe_in.close()


# Show footer
print """
</visitor-memo>
"""

2009-06-11 08:50 追記

自宅の Internet Explorer では、あいかわらず、 "Msxml2.XMLHTTP" オブジェクトを生成してくれません。 別のPCで試してみたところ、オブジェクトの生成はしてくれましたが、 XML オブジェクトからメッセージ文字列を取り出す部分に「ブラウザ依存」があったようで、エラーが発生しました。 responseXMLではまった - 文系大学的IT系の悲哀を参考に "mes_list[i]textContent" を "mes_list[i].firstChild.nodeValue" に変更しました。

参考サイト

VISITOR WORLD 6
"FORM"入力ページは、ここにありますので、お試しください。
今からでも遅くない Ajax基本のキ - ITpro
短期集中連載●今こそAjaxに強くなる - ITpro
Web開発の新手法Ajax - ITpro
文字の実体参照化 - SimpleBoxes
Python の場合、ライブラリに変換関数があるので、とうぜん、 JavaScript にも同等関数があるのだろうと思っていました。 ところが、いざ、探してみると、どこにもありません。 どうやら必要な人が個々に関数を記述するという非効率な状態になっているようです。
AJAXでXMLHttpRequestのopenをPOST、sendに内容を送っても送信できない時 - ウィリアムのいたずらの開発日記
"POST" でデータを送るには、 "Content-Type" を書かなくてはならないそうだ。
responseXMLではまった - 文系大学的IT系の悲哀
どうも、すべてのブラウザに対応するのは、並大抵の努力ではすまないようで。

Python CGI で 掲示板みたいなものを作る~FieldStorage編~ [プログラム三昧]このエントリーを含むはてなブックマーク#

WS000257.png

前回の記事Python CGI で 掲示板みたいなものを作る~POST編~では、 "cgi.parse_qs()" を使って query を取り出す方法を使いました。 今回は、こんどはPOSTを受ける方法。で紹介されていた "cgi.FieldStorage()" を使用してみます。 もしかして、こっちが、本流なのかな?

FORMの構成

HTML文書は、タイトルと呼び出すCGIが変わっただけです。 もう、説明はいらないよね。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<!-- $Id: visitor_world5.html,v 1.1 2009/06/06 05:07:43 noritan Exp $ -->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" >
<head profile="http://www.w3.org/2005/10/profile">
<title>VISITOR WORLD 5</title>
</head>
<body>
<h1>VISITOR WORLD 5</h1>
<form action="./visitor_world5.cgi" method="post">
<div>
<textarea name="message" rows="4" cols="40"></textarea>
</div>
<p>
<input type="submit" value="Submit" />
<input type="reset" value="Clear" />
</p>
</form>
</body>
</html>

query データベースの作り方と使い方

前回は query のデータベースを "cgi.parse_qs()" メソッドで作成していましたが、今回は、 "cgi.FieldStorage()" オブジェクトとして生成します。

  • BEFORE
    # Get a POST data.
    content_length = int(os.environ['CONTENT_LENGTH'])
    query = cgi.parse_qs(sys.stdin.read(content_length))
    
  • AFTER
    # Get a POST data.
    form = cgi.FieldStorage()
    

今まで苦労してたのが、ウソみたいだ。 出来上がったデータベースは、 "cgi.parse_qs" で作成した方は、 Python のデータベースオブジェクトそのものでしたが、 "cgi.FieldStorage()" の方は再帰的な木構造を持っています。 そのため、目的の要素を取り出す方法も異なっています。

  • BEFORE
    # Get and escape a MESSAGE
    message_key = 'message'
    if message_key in query:
        message = urllib.quote(cgi.escape(query[message_key][0]))
    else:
        message = ""
    
  • AFTER
    # Get and escape a MESSAGE
    message_key = 'message'
    if message_key in form:
        message = urllib.quote(cgi.escape(str(form.getvalue(message_key))))
    else:
        message = ""
    

"FieldStorage" から要素を取り出すには、色々なメソッドがあります。 今回は、手ごろなところで、 "FieldStorage.getvalue()" を使いました。

先に書いたように、 FieldStorage クラスは木構造を持っています。 したがって、 "FieldStorage.getvalue()" メソッドで取り出した値が文字列であるという保証はどこにもありません。 その正体不明の要素を確実に文字列にするため、 "str()" 関数を使っています。 これで、確実に何らかの文字列が得られます。

CGIの出来上がり

という訳で、できあがりました。

#!/usr/local/bin/python
# $Id: visitor_world5.cgi,v 1.1 2009/06/06 05:07:43 noritan Exp $

import os
import sys
import cgi
import urllib
import time
import cgitb

# Show error as a page description.
sys.stderr = sys.stdout
cgitb.enable()

# Database releated information
db_file = "visitor.sqlite"
table_name = "visitor"
command = "/usr/local/bin/sqlite3 %s" % (db_file)

# Get a POST data.
form = cgi.FieldStorage()

# Get Current time
now = time.time()

# Get and escape a MESSAGE
message_key = 'message'
if message_key in form:
    message = urllib.quote(cgi.escape(str(form.getvalue(message_key))))
else:
    message = ""

# Compose a SQL
sql = "SELECT time, description FROM %(table)s ORDER BY time DESC;"
if len(message) > 0:
    sql = \
      "INSERT INTO %(table)s VALUES (%(now)f, \"%(message)s\");\n" \
      + sql

# Show HTML header
print """Content-type: text/html

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
<head profile="http://www.w3.org/2005/10/profile">
<link rel="icon" href="/favicon.png" type="image/png" />
<title>VISITOR WORLD 5</title>
</head>
<body>
<h1>VISITOR WORLD 5</h1>
"""

# Access to the DATABSE
(pipe_out, pipe_in) = os.popen2(command)
pipe_out.write(
  sql % {"table":table_name, "now":now, "message":message}
)
pipe_out.close()

# Show a list of visitor record
print """<dl>
"""

# Make a list of messages
try:
    for line in pipe_in:
        field = line.split("|")
        asctime = time.strftime(
          "%Y-%m-%d (%A) %H:%M:%S",
          time.localtime(float(field[0]))
        )
        message = urllib.unquote(field[1])
        print "<dt>%s</dt><dd>%s</dd>\n" % (asctime, message)
finally:
    pipe_in.close()


# Show footer
print """
</dl>
</body>
</html>
"""

参考サイト

VISITOR WORLD 5
"FORM"入力ページは、ここにありますので、お試しください。
Python v2.6.2 documentation
python.org にあるオンラインドキュメントです。 現在、私が使っているサーバにインストールされている python は、 version 2.4.5 なのですが、ここには、原稿執筆時点で version 2.6.2 が置いてあります。 ちょっと、注意しながら使わなくては。
21.2. cgi — Common Gateway Interface support.
cgi パッケージ関連のマニュアルです。 FieldStorage クラスの解説は、これだけなのかな?

参考文献

Learning Python 3e (Learning)

Learning Python 3e (Learning)

  • 作者: M Lutz
  • 出版社/メーカー: Pragma
  • 発売日: 2007/11/06
  • メディア: ペーパーバック
初めてのPython 第3版

初めてのPython 第3版

  • 作者: Mark Lutz
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2009/02/26
  • メディア: 大型本

Python CGI で 掲示板みたいなものを作る~POST編~ [プログラム三昧]このエントリーを含むはてなブックマーク#

WS000256.png

もう、かれこれ3ヶ月も前になりますが、Python CGI で 掲示板みたいなものを作るで”GET”メソッド使った「掲示板みたいなもの」を作りました。

そして、先日の事です。 某BLOGより私の記事にリンクが張られていることに気が付きました。 そのBLOGのとある記事では、Python CGIで"POST"メソッドを使う方法を探しておられるらしい。 これは、私が以前見つけた方法と違うみたいですね。

FORMの構成

FORMを配置したHTML文書は、こうなりました。 実は、"GET"メソッドを使う場合とほとんどおなじなんです。

  • タイトルを"VISITOR WORLD 3"から"VISITOR WORLD 4"に変えました。
  • FORMから呼び出すCGIファイルを"visitor_world3.cgi"から"visitor_world4.cgi"に変えました。
  • FORMで使用するメソッドを"get"から"post"に変えました。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<!-- $Id: visitor_world4.html,v 1.1 2009/06/05 13:37:46 noritan Exp $ -->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" >
<head profile="http://www.w3.org/2005/10/profile">
<title>VISITOR WORLD 4</title>
</head>
<body>
<h1>VISITOR WORLD 4</h1>
<form action="./visitor_world4.cgi" method="post">
<div>
<textarea name="message" rows="4" cols="40"></textarea>
</div>
<p>
<input type="submit" value="Submit" />
<input type="reset" value="Clear" />
</p>
</form>
</body>
</html>

queryを受け取る方法

CGI ファイルの方も大きく変更はしていません。 "GET" メソッドの場合には、環境変数 "QUERY_STRING" から query を引っ張り出していましたが、 "POST" メソッドの場合には、標準入力 "stdin" から query を引っ張り出します。

  • GET
    # Get and parse a query string
    query_string_key = 'QUERY_STRING'
    if query_string_key in os.environ:
        query = cgi.parse_qs(os.environ[query_string_key])
    else:
        query = {}
    
  • POST
    # Get a POST data.
    content_length = int(os.environ['CONTENT_LENGTH'])
    query = cgi.parse_qs(sys.stdin.read(content_length))
    

タイトルを変更した以外の違いは、これだけです。 "GET" でも "POST" でも、最終的に query を作るのは、 "cgi.parse_qs()" メソッドです。

CGIの出来上がり

という訳で、今回も以前と同じデータベースファイルに記録を残していきます。

#!/usr/local/bin/python
# $Id: visitor_world4.cgi,v 1.1 2009/06/05 13:37:46 noritan Exp $

import os
import sys
import cgi
import urllib
import time

# Show error as a page description.
sys.stderr = sys.stdout

# Database releated information
db_file = "visitor.sqlite"
table_name = "visitor"
command = "/usr/local/bin/sqlite3 %s" % (db_file)

# Get a POST data.
content_length = int(os.environ['CONTENT_LENGTH'])
query = cgi.parse_qs(sys.stdin.read(content_length))

# Get Current time
now = time.time()

# Get and escape a MESSAGE
message_key = 'message'
if message_key in query:
    message = urllib.quote(cgi.escape(query[message_key][0]))
else:
    message = ""

# Compose a SQL
sql = "SELECT time, description FROM %(table)s ORDER BY time DESC;"
if len(message) > 0:
    sql = \
      "INSERT INTO %(table)s VALUES (%(now)f, \"%(message)s\");\n" \
      + sql

# Show HTML header
print """Content-type: text/html

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
<head profile="http://www.w3.org/2005/10/profile">
<link rel="icon" href="/favicon.png" type="image/png" />
<title>VISITOR WORLD 4</title>
</head>
<body>
<h1>VISITOR WORLD 4</h1>
"""

# Access to the DATABSE
(pipe_out, pipe_in) = os.popen2(command)
pipe_out.write(
  sql % {"table":table_name, "now":now, "message":message}
)
pipe_out.close()

# Show a list of visitor record
print """<dl>
"""

# Make a list of messages
try:
    for line in pipe_in:
        field = line.split("|")
        asctime = time.strftime(
          "%Y-%m-%d (%A) %H:%M:%S",
          time.localtime(float(field[0]))
        )
        message = urllib.unquote(field[1])
        print "<dt>%s</dt><dd>%s</dd>\n" % (asctime, message)
finally:
    pipe_in.close()


# Show footer
print """
</dl>
</body>
</html>
"""

"cgi.FieldStorage()" という手も、あるらしいので、後で調べてみます。

参考サイト

VISITOR WORLD 4
"FORM"入力ページは、ここにありますので、お試しください。

参考文献

確か、この本に書いてあったのだと記憶しているのですが。

Learning Python 3e (Learning)

Learning Python 3e (Learning)

  • 作者: M Lutz
  • 出版社/メーカー: Pragma
  • 発売日: 2007/11/06
  • メディア: ペーパーバック
初めてのPython 第3版

初めてのPython 第3版

  • 作者: Mark Lutz
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2009/02/26
  • メディア: 大型本

この本には、CGIの話は出てこないみたいです。

Python Cookbook 2e

Python Cookbook 2e

  • 作者: A Martelli
  • 出版社/メーカー: Pragma
  • 発売日: 2005/03/24
  • メディア: ペーパーバック

SCI送信用リングバッファをMC908QB8に移植した [プログラム三昧]このエントリーを含むはてなブックマーク#

HCS08 Unleashedに掲載されていたSCI送信用リングバッファをMC908QB8移植してみました。

某所で「リングバッファのサンプル・プログラムが無いか」と質問がありました。 「HCS08 Unleashed にあるよ」と、書こうとしてターゲットがMC908QB8であることに気が付きました。 そのままでは、使えないので、MC908QB8向けに移植します。

使用するハードウェアは、ほこりをかぶっていたDEMO908QB8デモボードです。 家中、ジャンパを探し回って、ようやく動かすことができました。

リングバッファの移植に必要なのは、HCS08MC908QB8のレジスタ・ビットの対照表です。 移植するために書き換えたレジスタ・ビットは、以下のとおりです。

HCS08MC908QB8概要
SCI1C2_TIESCC2_SCTIE割り込み許可ビット
SCI1S1_TDRESCS1_SCTE送信データ書き込み可能フラグ
SCI1DSCDR送信データレジスタ

リング・バッファ以外の部分、たとえば、ボーレートの設定やSCIの起動などは、参考サイトにある文章を参考にさせていただきました。 って、これは自分で書いた文章だよ。

デバッガから起動すると動くのに、スタンドアローンでは様子が変わるという、ありがちな問題も発生しました。 私の場合、「Watch Dog (COP) に与える餌が間に合わなかった」のと「一部のポートがフラフラしていた」のが原因でした。

COPはディセーブル、全ポートにプルアップ、で解決です。

というわけで、プログラムの完成です。 内容は、デモボード上のSW1/SW2を定期的に見に行って、押されていたらSCIにメッセージを表示するというものです。 このプログラム自身は、リングバッファを使っている恩恵は全く受けないのですが、サンプルとしては十分でしょう。

#include <hidef.h> /* for EnableInterrupts macro */
#include "derivative.h" /* include peripheral declarations */

#define     TX_BUF_SIZE     (32)

//--------------------------------------------------------------
//  Variables.
//--------------------------------------------------------------
#pragma DATA_SEG __SHORT_SEG MY_ZEROPAGE
byte tx_buf_read_pointer;
byte tx_buf_write_pointer;
struct {
  char tx_buf_empty:1;
} flags;

#pragma DATA_SEG DEFAULT
byte tx_buffer[TX_BUF_SIZE];

//--------------------------------------------------------------
//  initOSC() : void
//  Initialize the Oscillator Module (OSC)
//  The BUSCLK is adjusted to 3.2MHz.
//  CGMXCLK (BUSCLKx4) is used by ESCI.
//  See p100 of MC68HC908QB8 Data Sheet, Rev.1
//--------------------------------------------------------------
void initOSC() {
  // load trim value to adjust BUSCLK to 3.2MHz
  OSCTRIM = Optional;   // Adjust trim for internal OSC
  OSCSC_ICFS = 0b10;    // Select 12.8MHz as internal OSC
}

//--------------------------------------------------------------
//  initBaudRate() : void
//  Initialize the baud rate of ESCI to 1200bps.
//  The CGMXCLK is assumed as a 12.8MHz clock.
//  DETAIL:
//    SCP  =    01 for BPD=3
//    SCR  =   011 for BD=8
//    PDS  =   101 for PD=6
//    PSSB = 11110 for PDFA=0.9375
//    Baud Rate = 12.8MHz / (64 * BPD * BD * (PD + PDFA))
//              = 1201 baud
//  See p130 of MC68HC908QB8 Data Sheet, Rev.1
//--------------------------------------------------------------
void initBaudRate() {
  SCBR_SCP   =    0b01;  // BPD  = 3
  SCBR_SCR   =   0b011;  // BD   = 8
  SCPSC_PDS  =   0b101;  // PD   = 6
  SCPSC_PSSB = 0b11110;  // PDFA = 0.9375
}

//--------------------------------------------------------------
//  initESCI() : void
//  Initialize the Enhanced Serial Communication Interface (ESCI)
//--------------------------------------------------------------
void initESCI() {
  SCC1_ENSCI = 1;       // Enables ESCI module
  SCC2_TE = 1;          // Enables transmitter
}

//--------------------------------------------------------------
//  isr_sctx() : void
//  Interrupt sevice routine for SCI-TX
//--------------------------------------------------------------
void __interrupt VectorNumber_SCITransmit isr_sctx(void) {
  if (tx_buf_read_pointer == tx_buf_write_pointer) {
    SCC2_SCTIE = 0;             // Disable the interrupt
    flags.tx_buf_empty = 1;     // Set empty flag
  } else {
    if (SCS1_SCTE) {
      SCDR = tx_buffer[tx_buf_read_pointer];
      tx_buf_read_pointer++;
      tx_buf_read_pointer &= TX_BUF_SIZE - 1;
    }
  }
}

//--------------------------------------------------------------
//  sci_putc(byte) : void
//  Put a character into the ring buffer.
//--------------------------------------------------------------
void sci_putc(const byte data) {
  SCC2_SCTIE = 0;               // Disable the interrupt  
  flags.tx_buf_empty = 0;
  tx_buffer[tx_buf_write_pointer] = data;
  tx_buf_write_pointer++;
  tx_buf_write_pointer &= TX_BUF_SIZE - 1;
  SCC2_SCTIE = 1;               // Enable the interrupt  
}

//--------------------------------------------------------------
//  sci_puts(byte *) : void
//  Put a string into the ring buffer.
//--------------------------------------------------------------
void sci_puts(const byte *s) {
  while (*s) {
    sci_putc(*s++);
  }
}

//--------------------------------------------------------------
//  initQueue() : void
//  Initialize the queue related variable.
//--------------------------------------------------------------
void initQueue(void) {
  tx_buf_read_pointer = 0;      // Initialize READ pointer
  tx_buf_write_pointer = 0;     // Initialize WRITE pointer
  flags.tx_buf_empty = 1;       // Set empty flag
}

//--------------------------------------------------------------
//  main() : void
//--------------------------------------------------------------
void main(void) {
  word i;

  CONFIG1 = 0x01;       // Disable COP
  PTAPUE = 0x3F;        // Enable all port pull-up
  PTBPUE = 0xFF;        // Enable all port pull-up
  
  initOSC();            // Initialize OSC module
  initBaudRate();       // Initialize baud rate
  initESCI();           // Initialize ESCI module
  initQueue();          // Prepare QUEUE

  EnableInterrupts; /* enable interrupts */
  /* include your code here */

  for(;;) {
    if (!PTA_PTA5) {
      sci_puts("SW1 PUSHED\r\n");
    }
    if (!PTA_PTA4) {
      sci_puts("SW2 PUSHED\r\n");
    }
    // a delay
    for (i = 0; i < 30000; i++) {
    }
    __RESET_WATCHDOG(); /* feeds the dog */
  } /* loop forever */
  /* please make sure that you never leave main */
}

おっと、忘れてた。 Project.prmを変更して"RAM"領域を広げておかないと、バッファが配置できなかったのでした。

/* This is a linker parameter file for the mc68hc908qb8 */

NAMES END /* CodeWarrior will pass all the needed files to the linker by command line. But here you may add your own files too. */

SEGMENTS /* Here all RAM/ROM areas of the device are listed. Used in PLACEMENT below. */
    Z_RAM                    =  READ_WRITE   0x0040 TO 0x007F;
    RAM                      =  READ_WRITE   0x0080 TO 0x013F;
    ROM                      =  READ_ONLY    0xDE00 TO 0xFDFF;
    ROM1                     =  READ_ONLY    0xFFB0 TO 0xFFBD;
    ROM2                     =  READ_ONLY    0xFFC2 TO 0xFFCF;
 /* INTVECTS                 =  READ_ONLY    0xFFDE TO 0xFFFF; Reserved for Interrupt Vectors */
END

PLACEMENT /* Here all predefined and user segments are placed into the SEGMENTS defined above. */
    DEFAULT_RAM,                        /* non-zero page variables */
                                        INTO  RAM;

    _PRESTART,                          /* startup code */
    STARTUP,                            /* startup data structures */
    ROM_VAR,                            /* constant variables */
    STRINGS,                            /* string literals */
    VIRTUAL_TABLE_SEGMENT,              /* C++ virtual table segment */
    DEFAULT_ROM,
    COPY                                /* copy down information: how to initialize variables */
                                        INTO  ROM; /* ,ROM1,ROM2: To use "ROM1,ROM2" as well, pass the option -OnB=b to the compiler */

    _DATA_ZEROPAGE,                     /* zero page variables */
    MY_ZEROPAGE                         INTO  Z_RAM;
END

STACKSIZE 0x30

VECTOR 0 _Startup /* Reset vector: this is the default entry point for an application. */

参考サイト

003A QB8でもシリアル通信
ESCIの使い方なんて、すっかり忘れていました。 一番参考になったのは、その昔自分で書いた文章だったというオチ。
ScTec
HCS08 Unleashedの筆者のサイトです。 日本語でもなく、英語でもなく、スペイン語(だったっけ?)なのが辛いところです。 書籍のエラッタ、書籍に掲載されているソースコードも入手できます。

参考文献

HCS08 Unleashed: Designer's Guide to the HCS08 Microcontrollers

HCS08 Unleashed: Designer's Guide to the HCS08 Microcontrollers

  • 作者: Fabio Pereira
  • 出版社/メーカー: Booksurge Llc
  • 発売日: 2007/11/13
  • メディア: ペーパーバック

Quartus II V9.0 出たらしい [プログラム三昧]このエントリーを含むはてなブックマーク#

2434525

Alteraさんからメールが届きました。 どうやら、Quartus II の新バージョン V9.0 が出たようです。 今回は急ぐ必要もないので、DVDが届くのを気長に待つつもりでいます。 あ、写真は、 V8.2 の DVD ですので、誤解のないように。

参考サイト

Quartus II Web Edition Software
おなじみ無償バージョン(Web edition)のページです。 本日現在、右の方に"Request Software DVD"というリンクがあるので、ここから DVD を請求することができます。 なお、 DVD の発送は3月30日以降となっているようです。

Python CGI で 掲示板みたいなものを作る [プログラム三昧]このエントリーを含むはてなブックマーク#

WS000339.png

これまで、CGI内部で生成した文字列をデータベースに登録していく仕掛けを作ってきました。 今回は、HTML文書の "FORM" から文字列を入力して登録していく、掲示板みたいなものを作成してみました。

FORM の構成

HTML文書には、"TEXTAREA" を持った "FORM" が一つ埋め込んであります。 "Submit" ボタンを押すと、 CGI に "FORM" の情報が送信されるという仕組みになっています。 今回、送信に使用したのは、 "GET" という手法です。 これは、情報を URL に埋め込んで送信します。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<!-- $Id: visitor_world3.html,v 1.1 2009/03/01 07:26:41 noritan Exp $ -->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" >
<head profile="http://www.w3.org/2005/10/profile">
<title>VISITOR WORLD 3</title>
</head>
<body>
<h1>VISITOR WORLD 3</h1>
<form action="./visitor_world3.cgi" method="get">
<div>
<textarea name="message" rows="4" cols="40"></textarea>
</div>
<p>
<input type="submit" value="Submit" />
<input type="reset" value="Clear" />
</p>
</form>
</body>
</html>

CGI の悩み

"FORM" からは、URLの一部として "TEXTAREA" の情報が送られてきます。 この情報自身は、URLの流儀に従って、エンコードされているので、Pythonでは、 cgi.parse_qs というメソッドを使って簡単に文字列として取り出すことができます。 問題は、この文字列をどうやってデータベースに格納するかです。

通常、データベースには、文字列を格納することができます。 文字列を格納するときには、しかるべきライブラリによって、適切なエンコードが行われます。 ところが、さくらのレンタルサーバ・ライトプランでは、 SQLite をコマンドラインで使わざるをえません。 そのため、エンコード部分も自分で考えなくてはなりません。

そこで、Pythonの標準ライブラリを探し回って、二つほど解決策を考えました。

  • urllib.quote / urllib.unquote メソッドを使う

    このメソッドは、特殊文字を含んでいた場合に % を使った十六進表記で置き換えます。 良く URL の表記に使われている方法です。

    >>> urllib.quote("<p>\nHELLO\n</p>")
    '%3Cp%3E%0AHELLO%0A%3C/p%3E'
    
  • base64.b64encode / base64.b64decode メソッドを使う

    base64というのは、バイナリのデータを64種類の文字で置き換えるエンコード方法です。 良く、ファイルをメールに添付するときに使われます。

    >>> base64.b64encode("<p>\nHELLO\n</p>")
    'PHA+CkhFTExPCjwvcD4='
    

これらを見比べた結果、あとから人間が見たときによりわかりやすいと思われる、 "urllib.quote" を使うことにしました。

CGIの出来上がり

”FORM" 情報を受け取って、データベースに登録し、データベースの内容を表示する CGI に仕立てました。 使用したデータベースファイルは、前回の "VISITOR WORLD 2" で作成した "visitor.sqlite" をそのまま利用しました。

#!/usr/local/bin/python
# $Id: visitor_world3.cgi,v 1.1 2009/03/01 07:26:41 noritan Exp $

import os
import sys
import cgi
import urllib
import time

# Show error as a page description.
sys.stderr = sys.stdout

# Database releated information
db_file = "visitor.sqlite"
table_name = "visitor"
command = "/usr/local/bin/sqlite3 %s" % (db_file)

# Get and parse a query string
query_string_key = 'QUERY_STRING'
if query_string_key in os.environ:
    query = cgi.parse_qs(os.environ[query_string_key])
else:
    query = {}

# Get Current time
now = time.time()

# Get and escape a MESSAGE
message_key = 'message'
if message_key in query:
    message = urllib.quote(cgi.escape(query[message_key][0]))
else:
    message = ""

# Compose a SQL
sql = "SELECT time, description FROM %(table)s ORDER BY time DESC;"
if len(message) > 0:
    sql = \
      "INSERT INTO %(table)s VALUES (%(now)f, \"%(message)s\");\n" \
      + sql

# Show HTML header
print """Content-type: text/html

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
<head profile="http://www.w3.org/2005/10/profile">
<link rel="icon" href="/favicon.png" type="image/png" />
<title>VISITOR WORLD 3</title>
</head>
<body>
<h1>VISITOR WORLD 3</h1>
"""

# Access to the DATABSE
(pipe_out, pipe_in) = os.popen2(command)
pipe_out.write(
  sql % {"table":table_name, "now":now, "message":message}
)
pipe_out.close()

# Show a list of visitor record
print """<dl>
"""

# Make a list of messages
try:
    for line in pipe_in:
        field = line.split("|")
        asctime = time.strftime(
          "%Y-%m-%d (%A) %H:%M:%S",
          time.localtime(float(field[0]))
        )
        message = urllib.unquote(field[1])
        print "<dt>%s</dt><dd>%s</dd>\n" % (asctime, message)
finally:
    pipe_in.close()


# Show footer
print """
</dl>
</body>
</html>
"""

参考サイト

VISITOR WORLD 3
"FORM"入力ページは、ここにありますので、お試しください。 いちおう、"XHTML 1.1"認証済みです。

参考文献

Python Cookbook (Cookbook)

Python Cookbook (Cookbook)

  • 作者:
  • 出版社/メーカー: Oreilly & Associates Inc
  • 発売日: 2005/05/05
  • メディア: ペーパーバック
Python クックブック 第2版

Python クックブック 第2版

  • 作者: Alex Martelli
  • 出版社/メーカー: オライリー・ジャパン
  • 発売日: 2007/06/26
  • メディア: 大型本

ActivePython に同梱された PythonWin がクラッシュするお話 [プログラム三昧]このエントリーを含むはてなブックマーク#

PCでデバッグを行うために、ActiveStateActivePythonを使用しています。 Windows の再インストールに伴い、インストールしたのですが…

ActivePython はインストールしたけれど

ActivePythonを使っている理由は、エディタまでついてくるお手軽構成だからです。 インストールして、いざエディタである Pythonwin を起動してポチポチ…

あれ? 固まっちゃったよ?

あれ? 落っこちた!

何度も起動しなおして試してみましたが、一行も実行される気配がありません。 再インストールも試みましたが、どうにもこうにも、動きません。

Shell なら問題なし

GUIに問題があるかもしれないと思い、 "Python Interactive Shell" を起動してタイプしてみましたが、こちらはちゃんと動きました。 どうやら、問題があるのは Pythonwin のようです。

掲示板に解決策があった

そこで、 google さんにお願いして、どこかで問題になっていないか探してもらいました。 キーワード「pythonwin crash」で探すと、ありました。 「ActiveState Community Site」とまさにぴったんこです。

ActivePython 2.6 Pythonwin Crashes

最初に報告があったのが、2008年11月とそんなに古くもありません。 スレッドを紐解くと「MFC90.DLLの問題じゃないか」「ユーザのレジストリのせいだ」「特権を与えればいい」と、さまざまな解決策が出てきましたが、どれも決定的じゃないみたい。 本当に解決したのか心配になっていた所、2009年2月20日の投稿がありました。

問題は、解決した!

最新版の "scintilla.dll" ファイルが "www.dlldll.com/scintilla.dll_download.html" からダウンロードできて、 "pythonwin.exe" と同じ場所にある古いものと入れ替えることができる。

"pythonwin.exe" は、デフォルトでは、「C:\Python26\Lib\site-packages\pythonwin」にあります。

さっそく、ダウンロードしてみました。 古いファイルが 408,576バイトなのに対して、新しいファイルが 134,144バイトです。 ずいぶん、ダイエットしましたね。 本当に更新版なの?

半信半疑で入れ替えてみると、みごと動きました。

それぞれのファイルのバージョン情報を調べると、「新しい」"2.2.0.146"は"Editor control used by Pythonwin 1995-1998"とあり、「古い」"1.7.5.0"は"A Source Editing Component 1998-2007"とあります。 いったい、何があったんでしょうね。

これで、やっとプログラムを書き始められるわい。

参考文献

ActivePython は、この本で紹介されていたディストリビューションのひとつだったはずです。

初めてのPython 第2版

初めてのPython 第2版

  • 作者: マーク ルッツ
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2004/11
  • メディア: 単行本

Python CGI で SQLite を使ってみようか [プログラム三昧]このエントリーを含むはてなブックマーク#

Python CGI で来客記録をつけるでは、Pythonで記述したCGIを利用してテキストファイルに来客記録を付けました。 ただ、排他制御をしていなかったので、かなり確率は低いと思いますが、複数の来客があった場合にはファイルが破壊されてしまう可能性があります。 そこで、データベースを使ってみようと考えました。

さくらのレンタルサーバで使えるデータベースは

さくらのレンタルサーバには、いくつかのコースが設定してあります。 私が利用しているのは、一番安い「ライト」プランです。 機能比較ページを見るとわかるのですが、「ライト」プランはかなり制限があります。 それでも、データベースとして「SQLite が使える」と書いてありましたので、どうやって使うものか調べてみました。

まず、SQLiteというのは、他のデータベースと違って、常駐プロセスが存在しないらしいことがわかりました。 つまり、CGIで呼び出されたプログラムがその場でデータベース操作プロセスを立ち上げて使用するようです。 データベースは、ファイルに存在します。 もっと調べると、FirefoxでもSQLiteデータベースを使っているらしいこともわかりました。 へ~、知らんかった。

さくらインターネットが、「データベース使えます」とうたっているので、「どうやって使うんですか?」と聞いたところ、「Perlのモジュールがあります。使い方は、勝手に調べてね。」という返事がありました。 Perlですか。

意地でもPythonで使っちゃる

そこでPythonSQLiteを使う話題を探してみました。 どうも、pysqliteというモジュールが存在していて、Pythonバージョン2.5以降では標準装備されているようです。 ところが、レンタルサーバには、Python2.4.5がインストールされています。 しかも、「ライト」プランでは、telnetsshの使用が許可されていないので、モジュールをインストールすることもできません。 困ったな。

そんな時、こんな記事を見つけました。

さくらのレンタルサーバーの sqlite3 のバージョンを確認

この記事によると、さくらのレンタルサーバには、"/usr/local/bin/sqlite3"にSQLiteの実行ファイルが存在していて、インタラクティブに使えるようです。 では、ひとつCGIに実行させてみよう。

print os.system("/usr/local/bin/sqlite3 -help 2>&1").read()

これをPythonで書いたCGIファイルに埋め込むと実行結果が返ってきました。

Usage: /usr/local/bin/sqlite3 [OPTIONS] FILENAME [SQL]
FILENAME is the name of an SQLite database. A new database is created
if the file does not previously exist.
OPTIONS include:
   -init filename       read/process named file
   -echo                print commands before execution
   -[no]header          turn headers on or off
   -bail                stop after hitting an error
   -interactive         force interactive I/O
   -batch               force batch I/O
   -column              set output mode to 'column'
   -csv                 set output mode to 'csv'
   -html                set output mode to HTML
   -line                set output mode to 'line'
   -list                set output mode to 'list'
   -separator 'x'       set output field separator (|)
   -nullvalue 'text'    set text string for NULL values
   -version             show SQLite version

なんだ、SQLを与える事ができるんだったら、自分でQUERYを書いちゃえばいいんだ。 と、いうわけで、この記事ではPythonのモジュールを使わずにコマンドを使ってデータベースを使います。 「じゃあ、Pythonでなくても、いいじゃん。」というのは、言いっこなし。

CGIでデータベースを構築する

来客記録システムでは、あらかじめデータベースを構築しておいて、CGIで来客記録を追加していきます。 とはいっても、「ライト」プランでは、telnetが使えないので、データベースを構築するためのコマンドを打つことも出来ません。 仕方が無いので、データベースの構築もCGIを介して行います。

#!/usr/local/bin/python
# $Id: visitor_db_init.cgi,v 1.2 2009/02/24 13:19:57 noritan Exp $

import sys
import os

db_file = "visitor.sqlite"
table_name = "visitor"
sql = "CREATE TABLE %s (time NUMBER, description TEXT)" % table_name
command = "/usr/local/bin/sqlite3 %s \"%s\"" % (db_file, sql)

# Show error as a page description.
sys.stderr = sys.stdout

# Execute command
print """Content-type: text/plain

STATUS=%s
""" % (os.system(command))

データベースには、テーブル(visitor)を一つ作ります。 一つ目のカラム(time)には、来訪時刻を秒数で表した数値を入れます。 テーブルのソーティングに使います。 二つ目のカラム(description)には、一覧表の内容を入れます。 今の所は、来訪時刻を示す文字列を入れる入れる予定しかありません。

実行すると、「STATUS=0」が表示され、正常に実行されたことがわかります。 来客記録システムの運用中は、このファイルがうっかり実行されないように鍵をかけてしまっておきましょう。

CGIでデータベースに来客記録をつける

WS000244.png

出来上がったデータベースに来客を記録していくCGIを作成します。 やることは、ファイルに書いていた時とおなじです。 来客時刻をデータベースに追加して、データベース全体をHTML文書のテーブルとして表示します。

#!/usr/local/bin/python
# $Id: visitor_world2.cgi,v 1.2 2009/02/24 13:35:32 noritan Exp $

import os
import sys
import time

db_file = "visitor.sqlite"
table_name = "visitor"
command = "/usr/local/bin/sqlite3 %s" % (db_file)

# Show error as a page description.
sys.stderr = sys.stdout

# Get Current time
now = time.time()
asctime = time.strftime("%Y-%m-%d (%A) %H:%M:%S", time.localtime(now))

# Show header
print """Content-type: text/html

<html>
<head>
<title>VISITOR WORLD 2</title>
</head>
<body>
<h1>VISITOR WORLD 2</h1>
"""

# Append a record of this visit
(pipe_out, pipe_in) = os.popen2(command)
pipe_out.write(
"""INSERT INTO %(table)s VALUES (%(now)d, "%(asctime)s");
SELECT description FROM %(table)s ORDER BY time DESC;""" \
 % {"table":table_name, "now":now, "asctime":asctime}
)
pipe_out.close()

# Show a list of visitor record
print """<table border="1" cellpadding="3">
"""

# Make a table contents
try:
    for line in pipe_in:
        print "<tr><td>%s</td></tr>\n" % line
finally:
    pipe_in.close()


# Show footer
print """
</table>
</body>
</html>
"""

CGIの中では、SQLiteは、一回しか呼び出していません。 その一回の呼び出しの中で来客記録の追加()INSERTとテーブルの書き出し(SELECT)の二つのSQL文を連続して処理しています。 また、テーブルは、SQL文で新しい順にソーティングしているので、新たな記録は表の上に追加されています。

参考サイト

さくらのレンタルサーバ
レンタルサーバは、様々なプランがありますので、用途とお財布によって使い分けることが出来ます。
SQLite
データベースのソフトウェアは、ここからダウンロードできます。 今回は、レンタルサーバ上のプログラムを使用しているので、何もダウンロードしていません。
pydoc.org: Python Documentation Online
Pythonのオンライン・マニュアルは、ここから参照できます。 まだ、使い慣れておりません。
Python
Pythonは、ここで入手できますが、GUIなどは付いてきません。
VISITOR WORLD 2
今回のプログラムの動作は、ここから見ることができます。

参考文献

Learning Python (Learning)

Learning Python (Learning)

  • 作者: Mark Lutz
  • 出版社/メーカー: Oreilly & Associates Inc
  • 発売日: 2007/10
  • メディア: ペーパーバック
Python Cookbook (Cookbook)

Python Cookbook (Cookbook)

  • 作者:
  • 出版社/メーカー: Oreilly & Associates Inc
  • 発売日: 2005/05/05
  • メディア: ペーパーバック

週末時計ブログパーツ [プログラム三昧]このエントリーを含むはてなブックマーク#

週末まで

終末まで

3月末まで

ご希望にお応えして、「週末時計」ブログパーツを作りました。 IEでも動くと思う。たぶん。

週末時計の使い方

このブログパーツは、JavaScriptファイルをあらかじめ呼び出しておいて、別のJavaScriptコマンドから呼び出します。

<script type="text/javascript" src="http://noritan.org/js/RemainTimer.js"></script>
<p>週末まで <script type="text/javascript">
  new org_noritan_WeekendTimer(5, 17, 45);
</script> 秒</p>

呼び出すときに三つのパラメータを指定します。 最初は、就業最終曜日です。 週末がいつになるか人によって様々であるケースを考えて、以下の値を指定します。

the day of the week就業最終曜日
0日曜日
1月曜日
2火曜日
3水曜日
4木曜日
5金曜日
6土曜日

二つ目と三つ目が、就業終了時間の時と分を表します。 このパラメータも人によって様々だと思います。 24時間制であらわしてください。 この例だと、「金曜日17:45」までの残り時間を教えてくれます。

タイマが切れる時間は、このスクリプトが実行されたときに設定されます。 週に一度は、リロードして更新してください。

終末時計もリニューアル

前回紹介した「終末時計」もリニューアルしました。 呼び出し方は、以前のものとは互換性がありません。

<script type="text/javascript" src="http://noritan.org/js/RemainTimer.js"></script>
<p>終末まで <script type="text/javascript">
  new org_noritan_WorldendTimer();
</script> 秒</p>

呼び出すJavaScriptファイルは、共通です。 そのため、一つのページに一回だけ書いてやればよいはずです。 逆に二回呼び出したらどうなっちゃうんだろ?

スーパークラス、残り時間時計

実は、上の二つの時計には、スーパークラスがいます。 それが、RemainTimerです。

<script type="text/javascript" src="http://noritan.org/js/RemainTimer.js"></script>
<p>3月末まで <script type="text/javascript">
  new org_noritan_RemainTimer(Date.parse("31 Mar 2009 23:59:59") / 1000);
</script> 秒</p>

与えるパラメータは、UNIX紀元の秒数です。 このスクリプトでは、「31 Mar 2009 23:59:59」までの残り秒数を表示してくれます。

補足

この記事のために、当初作成していたプログラムを Micro$oftInternetExplorer で表示させるとあまりにもひどい結果となってしまいました。 あまりのひどさ加減に InternetExplorer でもエラーが発生しないように変更してしまったほどです。

JavaScript ファイルのコメント箇所を見てもらうとわかるのですが、 InternetExplorer では、 Window.setInterval メソッドの第二書式をサポートしていません。 そのため、クラス名ベタ書きのかなり美しくないプログラムになってしまいました。 このぐらい、サポートして欲しいよな。

参考文献

Javascript: The Definitive Guide (Definitive Guide)

Javascript: The Definitive Guide (Definitive Guide)

  • 作者: David Flanagan
  • 出版社/メーカー: Oreilly & Associates Inc
  • 発売日: 2006/08
  • メディア: ペーパーバック

JavaScriptのプログラミングは、本当に久々なので、この本とにらめっこになりました。

Javascript: The Definitive Guide

Javascript: The Definitive Guide

  • 作者: David Flanagan
  • 出版社/メーカー: Oreilly & Associates Inc
  • 発売日: 1998/07/15
  • メディア: ペーパーバック

私が持っているのは、 JavaScript 1.2 対応の第3版(1998年発行)です。

JavaScript 第5版

JavaScript 第5版

  • 作者: David Flanagan
  • 出版社/メーカー: オライリー・ジャパン
  • 発売日: 2007/08/14
  • メディア: 大型本

日本語版でさえ、第5版になってる。


終末時計ブログパーツ [プログラム三昧]このエントリーを含むはてなブックマーク#

F/TさんのUNIXTIMEを表示するページから「終末時計」部分を抜き出してブログパーツ風にしてみました。

こんな風に呼び出します。 単なる<span>タグが出てくるので、アレンジは、お好みに合わせて。

<div
  align="center"
  style="margin:0.1em; border:thick groove; padding:0.1em; font-size:large;">
<p><span title="UNIX時計"><u>世界</u></span>の終わりまで</p>
<p>あと・・・</p>
<p><script
  type="text/javascript"
  src="http://noritan.org/js/remaintime.js"></script> 秒</p>
</div>

当ブログにも「ブログパーツ」として並べてみました。

参考サイト

今日はUNIX TIMEの1234567890到達記念日ですね

Python CGI で来客記録をつける [プログラム三昧]このエントリーを含むはてなブックマーク#

今回のPythonで作る CGI では、来客記録をとってみます。

履歴をファイルに記録する

CGIの前半では、来客記録を更新します。 来客記録は、サーバ上のファイルに記録していきます。 フォーマットは、一行に一件という簡単なものです。 Pythonには、ファイルというオブジェクトがあり、これを使うことでファイルの読み書きを行うことが出来ます。 来客があるたびにファイルの最後に"HOGE HOGE"と記録させるためには、以下のように書きます。

# Append a record of this visit
f = open(record_file,"a+")
try:
    f.write("HOGE HOGE\n")
finally:
    f.close()

これだけでは、芸が無いので"HOGE HOGE"の代わりに来訪時刻を書かせるようにします。 これには、日付と時刻を扱う"datetime"というモジュールを使います。 "datetime"モジュールの"datetime"オブジェクトの"now()"メソッドを呼び出すと現在時刻を表す"datetime"オブジェクトを得ることができます。 このオブジェクトからお好みのフォーマットの文字列を得る"strftime()"メソッドを使って、現在時刻を表す行を作成します。

    f.write(datetime.datetime.now().
        strftime("%Y-%m-%d (%A) %H:%M:%S\n"))

これで、例えば、"2009-02-04 (Wednesday) 22:44:43"という一行が追加されます。

来客記録を表にする

CGIの後半では、出来上がった来客記録ファイルを使って、来客表の入ったHTML文書を作成させます。 ファイルの内容を読み出すためにも、ファイルオブジェクトを使用します。

# Make a table contents
f = open(record_file,"r")
try:
    for line in f:
        print "<tr><td>%s</td></tr>\n" % line
finally:
    f.close()

この前後にHTML文書のヘッダとフッタをそろえて、CGIは、完成です。

できあがり

完成したCGIは、こうなりました。 都合により、来訪記録を書き込んだ具体的なファイル名は伏せてあります。 http://noritan.org/cgi/visitor_world.cgi に配置してありますので、よろしければ、お試しください。

#!/usr/local/bin/python
# $Id: visitor_time.cgi,v 1.2 2009/02/04 13:46:27 noritan Exp $

import os
import sys
import datetime

record_file = "XXXXXXXXXX"

# Show error as a page description.
sys.stderr = sys.stdout

# Append a record of this visit
f = open(record_file,"a+")
try:
    f.write(datetime.datetime.now().
        strftime("%Y-%m-%d (%A) %H:%M:%S\n"))
finally:
    f.close()

# Show a list of visitor record
print """Content-type: text/html

<html>
<head>
<title>VISITOR WORLD</title>
</head>
<body>
<h1>VISITOR WORLD</h1>
<table border="1" cellpadding="3">
"""

# Make a table contents
f = open(record_file,"r")
try:
    for line in f:
        print "<tr><td>%s</td></tr>\n" % line
finally:
    f.close()


print """
</table>
</body>
</html>
"""

このCGIでは、複数の来客があった場合、同じファイルに同時にアクセスが発生します。 そのため、ファイルが壊れる危険性をもっています。 本来は、ロック機構を取り入れるべきなので、あとで考えてみます。

参考文献

図書館で借りました。

初めてのPython 第2版

初めてのPython 第2版

  • 作者: マーク ルッツ
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2004/11
  • メディア: 単行本

英語版は、第3版が出ています。

Learning Python (Learning)

Learning Python (Learning)

  • 作者: Mark Lutz
  • 出版社/メーカー: Oreilly & Associates Inc
  • 発売日: 2007/10
  • メディア: ペーパーバック

参考サイト

http://python.org/
マニュアルなどは、ここで参照しています。 Pythonは、そのバージョンによって刻々と変遷している言語なので、サーバに入っているバージョンを確認したうえでプログラムを書く必要があります。

Python CGI でqueryを受け取る [プログラム三昧]このエントリーを含むはてなブックマーク#

前回Python CGIでは、決まったHTML文書を表示するだけでした。 今回は、QUERYを介してメッセージを表示させます。

QUERYって何だ?

HTML文書を要求する時、文書名の後にパラメータを付けることがあります。 例えば、こんな感じ。

http://noritan.org/cgi/echo_world.cgi?message=GOOD%20MORNING&time=06:00

この疑問符から後の部分をQUERYと呼び、CGIがその構文を解析してパラメータと値を適切に処理します。 「構文解析、めんどうジャン」と、思ったあなた。 そんな方のために、Pythonは、cgiという名のモジュールを用意してくれています。

今日のCGI - ECHO WORLD

今日のCGIは、QUERYから"message"というパラメータを見つけ出して、HTML文書として表示するプログラムです。

#!/usr/local/bin/python
# $Id: echo_world.cgi,v 1.2 2009/02/02 12:52:37 noritan Exp $

import sys
import os
import cgi


# Show error as a page description.
sys.stderr = sys.stdout


# Get and parse a query string
query_string_key = 'QUERY_STRING'
if query_string_key in os.environ:
    query = cgi.parse_qs(os.environ[query_string_key])
else:
    query = {}


# Get and escape a MESSAGE
message_key = 'message'
if message_key in query:
    message = cgi.escape(query[message_key][0])
else:
    message = "ECHO WORLD"


# Show MESSAGE
print """Content-type: text/html

<html>
<head>
<title>%(message)s</title>
</head>
<body>
<h1>%(message)s</h1>
</body>
</html>
""" % {message_key:message}

与えられたQUERYは、環境変数"QUERY_STRING"で引き渡されます。 環境変数連想リスト"os.environ"からQUERY全体を引き出します。 そして、"cgi.parse_qs"で構文解析を行い連想リスト"query"を作成します。 このように、難しい処理は、すべて"cgi"モジュールがやってくれます。

次は、"query"連想リストからパラーメタ"message"を抜き出します。 ここでも"cgi"のメソッドである"cgi.escape"が使用されています。 このメソッドは、HTML文書で使われる < などの特殊記号を &lt; のようにエスケープしてくれるメソッドです。 もし、この処理が無かった場合には、「クロス・サイト・スクリプティング」と呼ばれる怪しい行為に利用されるかもしれません。 たとえば、

http://noritan.org/cgi/echo_world.cgi?message=%3Cscript%3Ealert(%27hello%27)%3C/script%3E

などと、意図しないスクリプトを実行させられてしまう可能性があるのです。 そのため、「エスケープの必要性」があるという事に注意を払いましょう。 この処理もモジュールがやってくれるので、ラクチンです。

HTML文書の構成は、前回と同じです。 単純にタイトルと第一レベルヘッダを表示しています。

また、ここに配置しておきましたのでお試しください。

http://noritan.org/cgi/echo_world.cgi

被参照サイト


Python CGI で HELLO WORLD [プログラム三昧]このエントリーを含むはてなブックマーク#

さあ、いよいよ話題が発散を始めたぞ。

リンククラブからさくらインターネットに脱出して、思わずレンタル・サーバを持つことになってしまいました。 せっかくだから、CGIでも作ってみますか。

初めてのCGI

さくらインターネットの場合、拡張子に".cgi"が付いて、実行権が適切に設定されていれば、そのファイルはCGIとして認識されます。 ファイルの置き場所に制限はありません。 最初のCGIは、やっぱりこれでしょう。

#!/usr/local/bin/python
#$Id: hello_world.cgi,v 1.1 2009/01/31 09:25:01 noritan Exp $

print """Content-type: text/html

<html>
<head>
<title>HELLO WORLD</title>
</head>
<body>
<h1>HELLO WORLD</h1>
</body>
</html>
"""

CGIファイルが実行されたら、pythonのプログラムが HTML 文書としての情報を返します。 返答をウェブ・ブラウザが HTML 文書として解釈して画面に表示します。

現時点で、ここに配置しています。 他に何の芸もございませんが、良かったら、お試しください。


scilabで遊ぼう (13) [プログラム三昧]このエントリーを含むはてなブックマーク#

WS000226.png

だんだん、scilabから離れてきたぞ。

scilabで遊ぼう (11)では、LTspiceで周波数特性のシミュレーションを行いました。 ところが、理論的に考えていたのとは違って、途中にへこみのある周波数特性が出てきてしまいました。 これは、入出力インピーダンスを無視したムリな設計に原因があるのだろうと予想しました。 そこで、今回は、「理想的な状態のモデル」を作って入出力インピーダンスの影響を確認します。

理想的なフィルタ

いうまでも無く、どんなものをつないでも確実に動作するフィルタというのは、入力インピーダンスが無限大で、出力インピーダンスがゼロの系です。 これは、高周波の世界では違うらしいのですが、オーディオ帯域の範囲ではそうなると思います。

WS000302.png

もちろん、そんなものは現実の世界には無いので、実現できるのはシミュレーションの世界だけです。 で、そんな理想的な特性を持つ素子を取り入れたのが、この回路です。

入力インピーダンスが無限大で出力インピーダンスがゼロになる「電圧制御電圧源」を使って、PID制御器と被制御システムを分離しました。 これで、それぞれのシステムの相互干渉はなくなるはずです。

入出力インピーダンスの影響を確認する

WS000303.png

モデルが出来たので、シミュレーションにかけてみます。 従来の直結回路と理想的システムを同時にシミュレーションして比較しています。 これによると、理想的なシステム(赤いグラフ)の方は、まっすぐ素直にゲインが下がっていきます。

一方、直結回路(緑のグラフ)の方は、200Hz付近にへこみのある特性になってしまいました。 このことから、両システムの入出力インピーダンスを無視していたことが妙なへこみの原因になっているらしい事が確認されました。

で、どうする?

直結回路のループ・ゲイン特性も、scilabで遊ぼう (11)のグラフと比べるとかなり異なっています。 どうも、ループ・ゲイン測定用に電圧源を挿入した位置によっても結果が異なっているようです。 シミュレーションによって確認する限りは、どうしようもないんですかね。

解決法としては、系の出力インピーダンスを下げるためにバッファを入れるぐらいしか思いつかないのですが、そうすると移相が回ってしまうのではないかという別の心配もあります。 なんとも、悩ましいところです。

付録 : 「scilab で遊ぼう」索引


scilabで遊ぼう (11) [プログラム三昧]このエントリーを含むはてなブックマーク#

WS000226.png

scilabで遊ぼうの11回目には、scilabは登場しません。 今回は、scilabの成果をLTspiceで確認してみます。

被制御システムを決める

前回の記事で取り上げた被制御システムは、参考文献(1)から持ってきました。

G(s) = 0.1 / (s + 0.5)3

これを現実の回路で表現すると、RCによる一次ローパスフィルタを三段連ねたものになります。 この時の時定数は、

&omega; = 1 / (C * R) = 0.5

となります。 適当に R=1k&Omega; と定めると、 C=2000µF となります。

CR_triple.png

ところが、これだけコンデンサの容量が大きくなるとシミュレータが収束してくれなくなりました。 仕方なく、二桁落として、 C=20µF としました。 時定数は、 C×R=20msec です。 全体の伝達関数は、

G(s) = 1.25 × 105 / (s + 50)3

と、オリジナルとは少し異なりますが、あとはPID制御器のゲイン調整でカバーします。

実際にCRを三段重ねにすると、入出力インピーダンスの不整合から、伝達関数は変形します。 ですが、今日の記事では、入出力のインピーダンスはあえて無視しています。 作ってみて、出たとこ勝負で調整していこうと考えてのことです。

P制御器

P_factor.png

次は、P制御器を接続して、安定して発振するポイントを探します。 P制御器は、エラー検出機能込みの増幅器をOPアンプで構成しました。 電源電圧は、3Vにしています。

ゲインは、パラメータ"Kc"で与えられます。 "Rs"を 10kΩ として、"Rf"をゲインから計算しています。

電源電圧の半分の電圧に仮想グランド(VSS)を作るために、二個目のOPアンプが使われています。 ここでは、8ピンDIPにOPアンプが二個搭載されているLM358を想定しています。

全体の構成

WS000264.png

全体の構成は、このようになります。 フィードバック・ループには、ループ・ゲインを測定するために電圧源を挿入してあります。 これは、参考文献(2)で使われていた方法です。

この回路図を見て、気づいた方もいらっしゃるかと思いますが、これは、「移相形発振器」そのものです。 ループ・ゲインを上げると発振してあたりまえの回路です。

トランジェント・シミュレーション

この回路図で、トランジェント(過渡)シミュレーションをかけます。 シミュレーション時間2秒、最大ステップ時間0.2m秒としました。

.tran 0 2 0 0.2m
WS000266.png

すると、 Kc=32 の時に振幅の安定した発振波形が現れました。 これが、限界感度法で求めるべきゲインです。

また、この波形から発振周期も求めることが出来ます。 周期は、Tc=49.90m秒でした。

ACシミュレーション

次にコントロール・カードを入れ替えて、ACシミュレーションを行います。

.ac dec 100 0.1 1meg
WS000265.png

ここで、興味があるのは、ループゲイン(V(vy)/V(vx))です。 カーソルを当てて、ループゲインの利得(実線)が0dBになる周波数と位相(破線)が0°になる周波数を求めます。 すると、どちらも20.5Hz付近であることがわかりました。 二つの条件が揃った周波数で安定して発振が起こっているというわけです。

このボード線図によると、100Hz付近でゲインの落ち込みがあり、位相が回っています。 これは、どこかに共振回路ができてしまったことを意味するのですが、その原因が何処にあるのか判断できませんでした。 おそらく、OPアンプのモデルと外部回路の相互作用によるものであろうと推測しています。

PID制御器を定義する

シミュレーションで求めた、 Kc と Tc から Kp, Ti, Td を計算します。

Kp = Kc * 0.6
Ti = Tc / 2
Td = Tc / 8 
WS000272.png

次は、この三つのパラメータを実現するPID制御器を作ります。 "Rs"と並列に"Cs"を、"Rf"と直列に"Cf"を追加しました。 伝達関数は、こうなります。

C(s) = (Rf + (1/sCf)) / (Rs // (1/sCs))
C(s) = (Rf + (1/sCf)) * (Rs + (1/sCs)) / (Rs * (1/sCs))
C(s) = (Rf / Rs) * (1 + (1/sCfRf)) * (1 + sCsRs)
C(s) = (Rf / Rs) * ((1 + CsRs/CfRf) + (1/sCfRf) + sCsRs)

Kp = (Rf / Rs) * (1 + CsRs/CfRf)
Ti = CfRs * Kp = Tc / 2
Td = CsRf / Kp = Tc / 8

これらの方程式から、各抵抗およびコンデンサの値を導きます。 方程式を解くとこうなりました。

Rf = (Kp * Rs) / 2
Cs = Tc / (4 * Rs)
Cf = Tc / (2 * Kp * Rs)

再び、トランジェント・シミュレーション

P制御によるシミュレーション結果から、各パラメータは以下のように求まります。

.param Kc=32
.param Tc=49.90m
.param Kp=Kc*0.6=19.2
.param Rs=10k
.param Rf=Kp*Rs/2=96k
.param Cs=Tc/(4*Rs)=1.25u
.param Cf=Tc/(2*Kp*Rs)=0.130u
WS000273.png

パラメータが決まったので、トランジェント・シミュレーションにかけてみます。 すると、オーバーシュートはありますが、かなり目標値に追従した出力が得られるのがわかります。

さらにACシミュレーション

WS000274.png

次は、周波数特性を見てみます。 まあ、発振こそしませんが、位相余裕(フェーズ・マージン)は、22.5°しかありません。 おまけに、870Hz付近に妙なピークがあります。 そこで、被制御システムとPID制御器の部分について周波数特性をとりました。

WS000275.png

その結果、PID制御器(赤いグラフ)が870Hzにピークを持っていることがわかりました。 これは、微分制御部分でゲインが上がりすぎたためにできたピークです。


WS000277.png

そこで、ピークをやわらげるために、コンデンサ"Rs"に直列に抵抗を付けました。 値は、試行錯誤で100Ωとしました。

WS000276.png

これで、ちょっとは安心できます。

オーバーシュートを減らしたい

このシステムは、目標にたどりつくまでに、行きつ戻りつを繰り返しています。 これをオーバーシュートと呼んでいます。 次は、オーバーシュートを減らしてみます。

.param Kc=32
.param Tc=49.90m
.param Kp=Kc*0.6*1.2
.param Rs=10k
.param Rf=Kp*Rs/2
.param Cs=Tc/(4*Rs)*3
.param Cf=Tc/(2*Kp*Rs)*3
WS000278.png

積分定数と微分定数を3倍に調整して、不足気味のゲインを1.2倍に増やしてみました。 まだ、オーバーシュートは残っていますが、なかなか、いい感じです。

結論

このように、LTspiceでPID制御のシミュレーションを行うことが出来ましたが、 なぜか、周波数特性にへこみが出来てしまい、何が原因なのか探り当てる所までいきませんでした。 おそらく、インピーダンスの計算をさぼったためであろうとは思っていますが、まだまだ、修行が足りんな。

付録 : 「scilab で遊ぼう」索引

参考文献

(1)
Scilabで学ぶシステム制御の基礎

Scilabで学ぶシステム制御の基礎

  • 作者: 橋本 洋志
  • 出版社/メーカー: オーム社
  • 発売日: 2007/04
  • メディア: 単行本
(2)
トランジスタ技術 (Transistor Gijutsu) 2009年 02月号 [雑誌]

トランジスタ技術 (Transistor Gijutsu) 2009年 02月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2009/01/10
  • メディア: 雑誌

scilabで遊ぼう (10) [プログラム三昧]このエントリーを含むはてなブックマーク#

WS000226.png

scilabの本を貸してもらって読みました。 そこで、名前だけは聞いたことのある、「PID制御」に挑んでみました。

比例、積分、微分で、PID

pidblock.png

PID制御というのは、制御の目的となるシステムを、P(比例)I(積分)D(微分)の三つの成分からなる制御装置で思い通りに動かそうという仕組みです。 PID制御装置の伝達関数は、こんな風に記述されます。

C(s) = KP * (1 + 1 / (TI * s) + TD * s)

うまいこと、これらの係数を決めてやれば、制御装置の出来上がりです。 という事は、三つもパラメータを決めなきゃいけないんですか。 大変じゃん。

と、思っていたのですが、どうやら「だまって座ればピタリと当たる」解法があるらしいのです。 ほうほう、「限界感度法」というんですね。 いっちょ、scilabさんで試してみますか。

被制御システムを決める

制御装置は、被制御システムを制御するのが目的です。 そのため、最初に被制御システムを決めます。 ここでは、参考文献で使われていたシステムをそのまま使います。

G(s) = 0.1 / (s + 0.5)3

このシステムの出力と制御目標との差をC(s)の入力として、G(s)を制御します。 scilab式に書くとこうなります。

s=poly(0,'s');
G=0.1/((s+0.5)^3);

安定して発振する条件を探す

最初のステップは、P制御だけを使って、安定して発振するゲイン KC を探すことです。 発振しているのに「安定して」というのは、変な話ですが、一定の振幅を保って発振するという意味のようです。 ゲインを探すには、無骨ですが、こんな記述を書いて、発振の様子を観測しました。

KC=10.0;

C=syslin('c',KC*(1/(1+s*0)));
BETA=1+0*s;

t=[0:0.1:50];
y=csim('step',t,G*C/.BETA);
scf(1);clf;plot(t,y);ax1=gca();ax1.grid=[4,4];

"BETA" は、フィードバックのフィルタです。 ここでは、入力をそのまま出力に出すフィルタを使っています。

WS000235.png

KC=9.8 にすると、振幅がしだいに小さくなっていきます。


WS000236.png

KC=10.0 にすると、振幅が安定します。


WS000237.png

KC=10.2 にすると、振幅がしだいに大きくなっていきます。


これらの結果から、 KC=10.0 が求めるゲインであることがわかりました。 ここでは、同時に発振周期も求めておきます。 KC=10.0 の時のグラフによれば、時刻4から48の間に6サイクルの波が入っています。 1サイクルあたりの時間を TC として定義しておきます。

KC=10.0;
TC=(48-4)/6;

以上で、第一ステップは、終了です。 ところで、このときのループ・ゲインは、どうなっているんでしょうね。

WS000238.png

これが、ループ・ゲインの周波数特性です。 発振周波数である、 f=0.102Hz (T=9.80) の時にゲインが0dB、位相が-180°になっているのがわかります。

PID制御装置を定義する

KC と TC を見つけたら、これらの値を元にPID制御装置のパラメータを計算します。 計算式は、このようにしごく簡単です。

KP=0.6*KC;
TI=0.5*TC;
TD=0.125*TC;
C=syslin('c',KP*(1+1/(TI*s)+TD*s));

理屈は、よくわかりませんが、このパラメータを使うと、ちょうどいいらしいです。 周波数特性は、こんな風になりました。

WS000243.png

左の斜めに下がっていく部分がI特性、中央の平坦になっている部分がP特性、右側の斜めに上がっていく部分がD特性になっています。

t=[0:0.1:50];
y=csim('step',t,G*C/.BETA);
scf(1);clf;plot(t,y);ax1=gca();ax1.grid=[4,4];
WS000240.png

ステップ応答を求めてみました。 立ち上がりが俊敏で、リンギングもおとなしめです。


ループ・ゲインの周波数特性は、どうなっているんでしょうね。

WS000241.png

位相余裕が約30°、利得余裕が約70dBと、安定したシステムになっています。 下が、閉ループ・ゲインです。

WS000242.png

と、シミュレーションだと綺麗に出来上がるのですが、実践するとどうなんでしょうね。

付録 : 「scilab で遊ぼう」索引

参考文献

Scilabで学ぶシステム制御の基礎

Scilabで学ぶシステム制御の基礎

Scilab/Scicosで学ぶシミュレーションの基礎―自然・社会現象から、経済・金融、システム制御まで

Scilab/Scicosで学ぶシミュレーションの基礎―自然・社会現象から、経済・金融、システム制御まで

  • 作者: 橋本 洋志
  • 出版社/メーカー: オーム社
  • 発売日: 2008/01
  • メディア: 単行本

scilabで遊ぼう (9) [プログラム三昧]このエントリーを含むはてなブックマーク#

WS000226.png

今回は、フィードバック・ループを作成し、全体の特性を調べます。 トランジスタ技術、2008年7月号と9月号に掲載されている記事「dsPICで作るDC-DCコンバータのソフトウェア」もご参照ください。

あれ? いつのまにか、scilab-5.0.3の方が立ち上がっていたみたいだ。

ここまで作成してきた部品

前回までで必要な部品はほとんどそろいました。 まずは、これまでのあらすじから。

L1=39d-6;
C1=680d-6;
w0_lc=1.0/sqrt(L1*C1);
Q=0.8;
s=poly(0,'s');
lc_cont=syslin('c',w0_lc^2/(s^2+s*(w0_lc/Q)+w0_lc^2));

Ts=50d-6;
lc_filter=ss2tf(dscr(tf2ss(lc_cont),Ts));

Ap=3.87;
Fc=100;
z=poly(0,'z');
p_factor=syslin(Ts,Ap/(1+z*0));
i_factor=syslin(Ts,2*%pi*Fc*Ts*(z/(z-1)));
PI=p_factor*(1+i_factor);
変数概要
Tsサンプリング周期
lc_filter離散時間表現のLCフィルタ
p_factor比例制御フィルタ
i_factor積分制御フィルタ
PIPI制御フィルタ

フィードバック制御を作成する

フィードバック制御の基本にしたがって、入力から出力までの伝達関数を"A"、出力から入力に戻ってくるフィードバックの伝達関数を"beta"(β)と定義します。

-->A=PI*lc_filter;
 
-->beta=syslin(Ts,1/(2+z*0));

"A"は、PI制御とLCフィルタの合成です。 また、"beta"は、記事にあったものと同じ1/2分圧回路です。 "z*0"は、"z"の関数とするためのおまじないです。

-->open_loop=A*beta
 open_loop  =
 
                                         2    
    - 0.1406813 - 0.0148216z + 0.1649467z     
    ---------------------------------------   
                                      2    3  
  - 1.3625494 + 4.569748z - 5.2071985z + 2z   

-->scf(1);clf;bode(open_loop,1,3000);
WS000229.png

"open_loop"は、フィードバック・ループを一周したときの伝達関数です。 このループ・ゲインからシステムの安定性を検証します。

-->[G,freqGM]=g_margin(open_loop)
 freqGM  =
 
    0.  
 G  =
 
  - 272.57409  

マニュアルによれば、"g_margin"でゲイン・マージンを計算してくれることになっているのですが、思ったようには動作してくれませんでした。 ボード線図を見たところ、2.6kHzで13dBぐらいのマージンがあるようです。

-->[P,freqPM]=p_margin(open_loop)
 freqPM  =
 
    1351.9202  
 P  =
 
    45.890072  

一方、フェーズ・マージンは、計算してくれました。 1352Hzで45.9°のフェーズ・マージンになっています。 これは、ボード線図から得られる値と同じです。

閉ループ特性を調べる

閉ループ特性は、"A"と"beta"を用いて簡単に計算させることが出来ます。

-->closed_loop=A/.beta
 closed_loop  =
 
                                         2     
    - 0.2813627 - 0.0296433z + 0.3298935z      
    ---------------------------------------    
                                       2      
  - 1.5032308 + 4.5549263z - 5.0422518z       
           3                                   
       + 2z                                    
 
-->scf(1);clf;bode(closed_loop,1,3000);
WS000228.png

これは、出力電圧に対する基準電源の影響を示します。 DC領域では、6dBのゲインがあるので、入力の二倍の出力電圧が得られます。 また、3kHzを超えると利得が急激に落ちるので、基準電源にゆらぎがあっても出力には影響しないはずです。 1.5kHz付近に利得のピークがあるのだけど、問題にならないのかな?

付録 : 「scilab で遊ぼう」索引

参考文献

トランジスタ技術 (Transistor Gijutsu) 2008年 09月号 [雑誌]

トランジスタ技術 (Transistor Gijutsu) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/08/09
  • メディア: 雑誌

scilabで遊ぼう (8) [プログラム三昧]このエントリーを含むはてなブックマーク#

WS000199.png

いよいよ、PI制御を作成します。

scilabで、P優先型PI制御を作る

ここで作成するのは、トランジスタ技術、2008年7月号と9月号に掲載されていた「dsPICで作るDC-DCコンバータのソフトウェア」という記事からの完全な焼き直しです。 100Hzを境に低周波側で積分特性、高周波側で比例特性をもつフィルタを作ります。 サンプリング周期は、50&micro;秒(20kHz)です。

-->Ts=50d-6;
 
-->A=3.87;
 
-->Fc=100;

まず、各種定数を定義しておきます。 ここで定義するのは、サンプリング周期(Ts)、比例制御ゲイン(A)、P/I制御の交点周波数(Fc)の三つです。

-->z=poly(0,'z');
 
-->p_factor=syslin(Ts,A/(1+z*0))
 p_factor  =
 
    3.87   
    ----   
     1     
 

"p_factor"は、比例制御部分の伝達関数です。 第二引数は、本来は定数なのですが、"z"の関数しか受け付けられないので、"z*0"というおまじないを入れてあります。

-->i_factor=syslin(Ts,2*%pi*Fc*Ts*(z/(z-1)))
 i_factor  =
 
    0.0314159z   
    ----------   
    - 1 + z      
 

"i_factor"は、積分制御部分の伝達関数です。 記事の「実用的なディジタル積分器」をそのまま使っています。

-->PI=p_factor*(1+i_factor)
 PI  =
 
  - 3.87 + 3.9915796z   
    -----------------   
        - 1 + z         

"p_factor"と"i_factor"を記事の「ブロック図」を参考にしてPI制御を構成します。 これで、完成です。

PI制御の周波数特性を確認する

出来上がったPI制御の周波数特性をボード線図で確認します。

-->scf(1);clf;bode(p_factor,1,1e4);
WS000223.png

最初は、比例制御部分単体の特性です。 あたりまえですが、平坦な周波数特性になっています。

-->scf(1);clf;bode(i_factor,1,1e4);
WS000224.png

二つ目は、積分制御部分単体の特性です。 ゲインが直線的に落ちていくのがわかります。 100Hzの時のゲインがちょうど0dBになっています。

-->scf(1);clf;bode(PI,1,1e4);
WS000225.png

最後は、PI制御全体の特性です。 100Hz付近を境に積分制御と比例制御が切り替わっているのがわかります。

部品がそろったので、次回はフィードバック・ループを組み、ループ・ゲインを確認します。

付録 : 「scilab で遊ぼう」索引

参考文献

トランジスタ技術 (Transistor Gijutsu) 2008年 09月号 [雑誌]

トランジスタ技術 (Transistor Gijutsu) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/08/09
  • メディア: 雑誌

scilabで遊ぼう (7) [プログラム三昧]このエントリーを含むはてなブックマーク#

WS000199.png

前回、連続時間伝達関数を離散時間伝達関数に変換した際に伝達関数にサンプリング周期の情報が入ったことがわかりました。 すると、後からサンプリング周期情報を入れることが出来るのかな?

離散時間伝達関数にサンプリング周期情報を入れる

scilabのオンラインマニュアルを探し回ったところ、"syslin"の第一引数にサンプリング周期情報を与えられることがわかりました。

-->z=poly(0,'z');

-->sys=syslin(50d-6,(1+z^(-1)+z^(-2)+z^(-3)+z^(-4))/5);

-->sys("dt")
 ans  =

    0.00005

指定したサンプリング周期は、"dt"というインデックスで参照することが出来ます。 この状態でボード線図を描くと、絶対周波数で表示されるようになります。

-->scf(1);clf;bode(sys,1,1e4);
WS000222.png

また、サンプリング周期を後から変更することもできちゃいます。

-->sys("dt")=1e-3;

これにて、一件落着。

付録 : 「scilab で遊ぼう」索引


前の20件 | 次の20件 プログラム三昧 ブログトップ

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