Sensirion製温湿度センサーの湿度が100%になる?2016年05月09日 07時34分56秒

自作のWeatherSensorShield

現象

Weatherduinoという製品のサンプルライブラリをベースにセンシリオン製のSHT-11という温湿度センサーからのデータ取得を行っておりました。

ソースコードを何度確認してもデータシート通りの演算のように見えるのに、観測結果の湿度がいとも簡単に頻繁に100%に達してしまうのです。

Weatherduino用のライブラリはこちらから入手可能です。

このセンサーの入手はストロベリーリナックスが比較的安くて良いでしょう。

Weatherduinoのサンプルプログラムのバグ

さて、本題です。 上記の菅工房のサンプルライブラリG7Weather3.cppのSHT-11の処理では以下の演算部分があります。

// SHT11 温度(℃)・湿度(%)取得
void G7Weather::get_sht11 (float *temp, float *humi) {
       unsigned short sot, sorh;
       float t, rhl, rht;
 
       // 温度読込み
       sht_tsseq();
       sht_write(0x03);
       SHT_SDA_H;
       while (SHT_SDA); // 変換終了待機
       sot = (unsigned short)sht_read(1) << 8;
       sot |= sht_read(1);
       sht_read(0);

       // 湿度読込み
       sht_tsseq();
       sht_write(0x05);
       SHT_SDA_H;
       while (SHT_SDA); // 変換終了待機
       sorh = (unsigned short)sht_read(1) << 8;
       sorh |= sht_read(1);
       sht_read(0);

       // 温度補正 14bit (3.3V)
       sot &= 0x3fff;
       t = -39.66 + 0.01 * (float)sot;
       *temp = t;

       // 湿度補正 12bit
       sorh &= 0x0fff;
       // rhl = -4.0 + 0.0405 * sorh + (-2.8 * 0.000001) * (sorh * sorh); // Ver.3
       rhl = -2.0468 + 0.0367 * (float)sorh + (-1.5955 / 1000000.0) * (float)(sorh * sorh); // Ver.4
       rht = (t - 25.0) * (0.01 + 0.00008 * (float)sorh) + rhl;
       if (rht > 99.0) rht = 100.0;
       *humi = rht;
}

この処理の中でのバグは以下です。

(float)(sorh * sorh)

この部分だけですね。問題があるのは。 sorhという変数はunsigned shortで有効なデータは12ビット分です。単独で使う場合は16ビットで十分ですが2乗すると演算結果は16ビットでは足りなくなります。

バグ修正

なので、以下のように修正の必要があります。

(float)((long)sorh * (long)sorh)

観測データは12ビットなので最大値は4095となります。 それを2乗すると16769025となり16ビットの最大値である65535を 超えてしまっていたわけです。

一方、符号付のlongの正の最大値は2147483647ですので十分に格納可能になります。 この修正により、湿度が簡単に100%になる問題は解消しました。 原因解明に3年かかりました。

ちなみに、写真の基板はWeatherduinoではなく自作品です。 ArduinoUnoとの組み合わせで使っています。

復刻:キーワードガイダンス第18回「仮想化」2014年05月24日 06時36分31秒

初出:技術評論社「組込みプレスVol.20」(平成22(2010)年11月6日発売)
※この号を最後に予告なく「休刊」

●インターネットと仮想
インターネットコミュニティ上の「仮想世界」、仮想世界で本物のお金と同じように機能する「仮想通貨」、仮想世界での自分の分身である「アバター」などが「仮想」に関連した言葉として一般的にも浸透しています。

「クラウドコンピューティング」という言葉も流行っています。
こちらもサーバ、ストレージ、ネットワークなどのハードウェア資源をインターネット環境で仮想的に扱うための概念です。

仮想化の特徴は以下になります。

(1) 仮想化する前の実体(リソース)が既知である
(2) 仮想化された後の実体は仮想化される前の実体と同じように見える

●仮想化の分類
このようにコンピュータの世界での「仮想」とは物理的な何かをシステム上置き換えた「代替物」であるということが出来ます。
そして、「仮想化」は「仮想」を扱うための仕組みです。

ここでは「仮想化」を以下のように分類します。

(a) ユーザインターフェースの仮想化
(b) 言語処理系の実装表現としての仮想化
(c) ハードウェアリソースの仮想化

インターネットコミュニティ上の「仮想化」はユーザインターフェースの可視化のためにアバターを定義して、その仮想世界の提供者の収益を上げるために仮想通貨を定義しているに過ぎません。

言語処理系としてJava言語に対するJavaVM(Java Virtual Machine)は「仮想」という言葉が使われています。
しかし、JavaVMは実装的視点では単なるインタープリタやコンパイラによるコード翻訳の一つの形態と見なすことが出来ます。
Javaバイトコードは独自に定義されたコードであり、他の既存のCPUの命令コードを置き換えたもの(エミュレートしたもの)ではありません。

●仮想化の実装方式
ハードウェアリソースの仮想化の例として「ハイパーバイザ(hypervisor)」という技術があります。

この技術ではJavaVMと同じように「仮想マシン(Virtual Machine)」という言葉が使われます。
ハイパーバイザはCPUを含むハードウェアリソース一式を仮想化して仮想マシンを定義します。

仮想マシンは仮想マシン上で動作するソフトウェアから見るとハードウェア実体そのものに見えます。
仮想マシンは独立していてそれぞれが別々のOSを動作させることが出来ます。

この時、実体としてのハードウェアは一つで構いません。
ハイパーバイザはハードウェアと仮想マシンの間に介在してエミュレーション動作することになります。

「仮想化」には以下の種類があることになります。

(A) 可視化の手法としての仮想化
(B) 変換・翻訳機能(トランスレータ)
(C) 既知の何かに見せかける機能(エミュレータ)

●組込みシステムと仮想化技術
組込みシステムの開発を中心に考えた時、有効となる仮想化技術はトランスレータ(translator)とエミュレータ(emulator)です。
仮想化階層を利用する側から見ると従来から実在するハードウェアやソフトウェアに対するインターフェースと同等に機能することが必要です。

実際には仮想化階層の先にある物理インターフェースとの機能差の程度によってトランスレータとエミュレータは使い分けられることになります。
単純な手順の変換だけで済む場合はトランスレータで完結することになりますが、複雑な機能の場合はエミュレータとして実装することになります。

ターゲットとなる実機のソフトウェア構造にこのような仮想化の仕組みが組込まれていればハードウェアリソースに対するアクセス処理は共通に記述することが出来るためハードウェアの物理的な属性や拡張に依存しません。

ハードウェアの物理的属性に依存する部分は仮想化技術階層部分で吸収することができます。
結果として開発効率、再利用性が上がり、品質向上にも寄与します。

また、実機をエミュレートする仮想的な実機がPC上で動作すれば実機なしに開発およびデバッグを行うことも可能です。

一方で仮想化階層が介在することによるオーバーヘッドを考慮する必要があります。

復刻:キーワードガイダンス第17回「仕向けと地域化」2014年05月24日 06時00分59秒

初出:技術評論社「組込みプレスVol.19」(平成22(2010)年5月6日発売)

●海外製品とOEM製品
日本で発売される製品と同じ外観、同じデザインのものが海外で販売されているのを目にすることも多いと思います。
ですが日本で売られているものと細かいところで異なる点もあります。

(a) 型番や製品名
(b) 機能の増減
(c) 表示される言語の違い
(d) 電圧などの違い
(e) 物理的な配置の違い

自社のブランドをつけた市販品と他のメーカに製品を供給して供給先のブランドとして販売されるようなOEM製品という扱いもあります。
これは日本のメーカでは「仕向け」という言葉で表現されます。

本来は単なる「送り先」というような意味ですが、基準となる製品の派生製品を指す場合に使われる言葉です。

●部品の共通化
メーカでは派生製品を製造する場合には出来るだけ部品を共通化することが行われます。
同じ部品を数多く使う方が製造原価が下がるためです。

但し、ここでいう「共通部品」というのはハードウェアについてのことであり、多くの場合ソフトウェアは含まれていません。
このため、ソフトウェアの共通化を視野に入れて設計を行う要求仕様そのものが発生しません。

共通化を最初に視野に入れるかどうかで派生製品の開発のやり方が全く異なってしまうのですが、共通化するための設計および実装工数は配慮されないままに開発が行われることになります。

製造原価という視点では開発工数が増加するかどうかはあまり問題視されないからです。

●国際化と地域化
PCで動作させるソフトウェアでは国際化(internationalization)と地域化(localization)という思想でソフトウェアが組み上げられることが一般的になっています。
主に日本語や中国語のようなマルチバイト言語の切り替えに対する配慮が共通部品として提供されているような仕組みです。

共通部品化されたソフトウェアをベースにアプリケーションを開発すれば、言語切り替えの仕組みに対する切り替え処理と定義データの差し替えを行うだけで、ソフトウェアの処理ロジック部分には手を加えることはなく、製品を開発することができます。

このような考え方は開発工数の削減には非常に有効なものです。
このように、地域化は国際化を前提とした対になる表現ですが、「仕向け」はソフトウェア上の「共通部品化」を前提には考えられていません。

そして、組込みソフトウェア開発以外では「仕向け」という言葉が使われることはほとんどなく、汎用的なOSを使わないような組込みソフトウェア開発では
「ローカライズする」というような言葉が使われることもほとんどありません。

●仕向け対応の実際
このような状況で、汎用的なOSを使わない組込みソフトウェアの開発では以下のような開発が現実には行われることになります。

(1) 基準製品の開発時の開発予算には共通部品化の予算は含まれていない
(2) 仕向け対応はその製品単位の要求仕様を満たすものだけが行われる
(3) 仕向けの切り替えは動作中には行われないものとして設計される
(4) ソースプログラムに複数の仕向け用の記述を網羅してコンパイルの
  段階で切り替えられるようにする

(4)の対応で充分にソフトウェア開発上の問題は解決しているように見える場合はもちろんありますが、実際には仕向けの組み合わせによってタイミングが異なることもあり、正常動作していたはずのソースプログラムにまで手直しが必要な場合もあります。

例えば、A機能、B機能、C機能の処理ブロックがある場合、A機能だけの仕向け製品、B機能だけの仕向け製品、C機能だけの仕向け製品というような場合には正常に動作していても、A機能とC機能を組み合わせた仕向け製品では正常に動作しないということがあるわけです。

また、A機能、B機能、C機能を全て有効にするような仕向けが発生した場合、プログラムを格納するROM領域が足りなくなったり、使用するRAM領域が足りなくなるようなことが考えられます。

単純にハードウェア部品としてROMやRAMのサイズを増やせるような場合はソフトウェアに対する影響は少ないものになりますが、製造原価に直接響くような対応は後回しにされます。

このような場合はROMやRAMのサイズを増やさないでも納まるようにソフトウェア全体を見直して作り変えることが第一に検討されます。

復刻:キーワードガイダンス第16回「コールバック」2014年05月18日 22時52分26秒

初出:技術評論社「組込みプレスVol.18」(平成22(2010)年2月26日発売)

●通知と代行
自分のやることを忘れないために以下のような方法があります。

(1) 実行を忘れないように実行時期を誰かに教えてもらう
(2) 実行そのものを誰かに代行してもらう

ここで、クリーニングの受け取りについて考えて見ます。

Aさんはクリーニング屋さんにクリーニング完了のタイミングでメールや電話で連絡してもらえるサービスを利用しています。
クリーニング屋さんからの連絡後、自分の空き時間でクリーニング屋さんに取りに行くことになります。

Bさんはクリーニング屋さんと提携している代行業者にクリーニングの受け取りを依頼しています。
業者にはクリーニングされた衣類をクリーニング屋さんからの受け取り後に自宅の宅配ボックスや勤務先に届けてもらいます。
届け先や完了時の連絡方法などはオプションでその都度変更が可能です。

Aさんの場合は連絡してもらうだけで、実行は常に自分の責任で行います。
面倒ですが自分で状態やスケジュールを調整して受け取りを実行するので、行き違いは発生しません。
Bさんの場合、自分の都合で自ら実行するような場合や複数の業者を使い分けるような場合には自分と業者や業者同士の調整をするのに却って手間がかかってしまいます。

●イベント通知の手法と開発効率
コールバック(callback)はコールバック関数(callback function)の省略形として使われる場合やコールバックルーチン(callback routine)の省略形で使われる場合があります。
本来は電話をかけた相手から依頼した結果を折り返し連絡をもらうような場合に使われる用語です。

開発実装上は以下のような使われ方をします。

(a) 依頼先のイベント通知処理に対して関数ポインタを引数として渡すことにより自分で作った関数を依頼先に登録できる
(b) イベント通知の種類毎に自分で作った関数を使い分けることができる
(c) イベントの種類を一つのコールバック関数の引数でもらえる仕組みもある
(d) 非同期処理の場合にのみ使われる

多くの場合、コールバックの仕組みはOSが提供する場合やミドルウェアの特定のSDK(Software Development Kit)としてのAPI(Application Program Interface)が提供する場合があります。

自分が作りたいプログラム(アプリケーション)と汎用的なSDKが提供する機能を分離することが主な目的となります。
このような機能の分離はソフトウェアの可搬性や開発効率を高めます。

●組込みとPC用OSとの違い
WindowsなどのPC用のOS上で動作するアプリケーションを開発する場合、明確な開発に関する作法がSDKやOSにより決まっています。

自分が作っているアプリケーションは特に意識しない限り、言語処理系とSDKの連携で比較的簡単に矛盾のないアプリケーション開発が可能になっています。

中身の仕組みは別としても自分(アプリケーション)の責任範囲が明確な開発が出来るようになっています。
実行コンテキストについての配慮や資源同期の配慮も不要です。
アプリケーション開発者視点で考えるとAさんのパターンと同様です。

ところが組込みシステムで採用するようなコールバックの仕組みはまちまちです。
「コールバック」という用語が一人歩きするだけで実装の仕組みは決まっていません。
開発する会社やチームの違いによっても実装の違いがあり得ます。

これはBさんと業者の関係に似ています。

以下の留意が必要です。

・非同期イベント検出の起点は割り込み処理
・繰り返し状態を見るようなポーリング処理でイベントを検出する場合もある
・イベント検出後のコールバック関数の実行は割り込み処理の場合や自分以外の他のタスクである場合が多い
・SDKとして提供されてソースコードが見えない場合はコールバック関数の実行コンテキストについて把握しておく必要がある
・アプリケーションとコールバックの実行コンテキストが異なる場合は資源の排他制御が必要

このように、アプリケーション開発者が一定の基準でコールバックを捉えていたとしても実行コンテキストや資源同期の仕方が同じであるということが保証できない場合があります。

これはOSを使わない組込みシステム、μITRONを使った組込みシステムでの開発特有の問題です。

復刻:キーワードガイダンス第15回「インスタンス」2014年05月13日 21時01分20秒

初出:技術評論社「組込みプレスVol.17」(平成21(2009)年11月13日発売)

●モノの実体としてのインスタンス

お弁当屋さんでお弁当を買うことを考えて見ましょう。

メニューには幕の内弁当、から揚げ弁当、コロッケ弁当があるとします。
Aさんは幕の内弁当、Bさんはから揚げ弁当、Cさんはコロッケ弁当を購入しました。
それぞれ持ち帰ることが出来るお弁当の実体は別々に存在します。

一方、幕の内弁当にはから揚げとコロッケが含まれます。
から揚げ弁当にはから揚げ、コロッケ弁当はコロッケがそれぞれお弁当のおかずの主役です。
幕の内弁当に入っているコロッケとコロッケ弁当に入っているコロッケは同じ種類のコロッケで別々に実体が存在します。

「お弁当」という枠組みでの実体とお弁当の中身の「おかず」としての実体はどちらもインスタンスの具体例となります。
また、インスタンスの捕らえ方の範囲について、お弁当という中身の詳細を問わない概念、おかずの組み合わせに着目した概念、お弁当のおかず単体に
着目するような概念など段階があることも分かります。
この概念的な段階を「抽象度」とここでは定義しておきます。

●オブジェクト指向から独立したインスタンス

以上のようなたとえ話はインスタンスという言葉とともにオブジェクト指向の説明として語られることが多いようです。

また、言語処理系、例えばC++やJavaなどに依存して語られることも一般的です。
ですが、インスタンスという言葉をオブジェクト指向とは無関係に使うこともできます。
例えば、リアルタイムOS上のタスクとオブジェクト指向のオブジェクトは概念も実装方式も異なりますが、タスクの実体を指すときにインスタンスという言葉を使った方が大雑把な概念的な理解のきっかけとなります。

「タスクはメソッドだけを実体化したインスタンス」

というようなオブジェクト指向風に大胆に簡略化した説明を行った方が、大筋では会話が成立してしまいます。
本当はリアルタイムOSの仕組み上のTCB(タスク・コントロール・ブロック)の説明とレジスタ退避、スケジューラによるコンテキストスイッチなどの正確な説明を行う必要があるのですが、全体的な設計上の概要把握だけが目的の場合はOSの仕組みまでは理解していなくても話を進められます。

つまり、オブジェクト指向用語が他の実装や設計に対しての「たとえ話」に使えるようになってきたということです。
また、このようなアプローチをすると、リアルタイムOSのタスク生成における「同一機能タスクの複数生成」という概念も説明しやすくなります。
なお、お弁当の例は振る舞いのないデータだけのインスタンスのたとえ話でしたが、タスクの例は振る舞いだけのインスタンスのたとえ話になります。
リアルタイムOSでは振る舞いのインスタンスとデータのインスタンスは別々に管理されています。

●組込みシステム特有の問題

これまでのたとえ話のインスタンスをソフトウェア実装の視点で見れば全てメモリ領域に独立して存在するデータの集合と言うことが出来ます。
おかずを表す変数データとおかずやご飯を格納する「容器」は全てメモリ上のデータやデータの並びになります。
オブジェクト指向言語仕様上はこのメモリ確保と管理の仕組みをどのように表現するかということに重きが置かれています。
ですが、どのメモリにインスタンスを配置するかということはあまり議論されません。
インスタンスを高速なSRAMに配置するのか、普通のDRAMに配置するのか、もしくは静的にインスタンスを生成した上でROMに配置するのか、などの議論です。

これらの議論は組込みシステムでは必要な場合があります。
組込みシステムではお弁当の容器を選択したい場合があるからです。
プラスチック容器、紙の容器、竹の容器、持ち込みの自分用の容器など出来合いのお弁当屋さんの既定容器以外の容器が使える必要があります。
つまり、メモリ配置について意図的に指定する必要があるということです。
単なるクラス全体のメモリ配置の問題だけなのであれば、メモリ配置のためのコンパイラの拡張書式を使えば解決します。
通常、組込みシステム用のコンパイラにはこのような目的の拡張書式があります。

但し、クラスや機能全体ではなく一部の特定部分だけが性能上の問題になる場合には、抽象度を上げた実装をわざわざ解体し、抽象度を低くした上でメモリ配置を変える必要があるかもしれません。