誤った温度を見抜くCドライバー
目標
2バイトの変換と、HALのエラー・期限の契約を、Cで実装します。実際のCプログラムを実行しますが、実物のMCU・I2Cの波形・電圧の計測は行いません。
なぜ重要なのか
数字が出力されても、符号や単位が間違っていれば、正常な測定ではありません。部分読み取りは最後の正常な値を汚染することがあり、無限のリトライは次の作業を妨げます。エラー・値・時間を一緒に検証して、再現できるドライバーの試験を作ります。C言語の関数・ポインター・構造体と、シェルの基礎が必要です。
ステップ
- バイトを温度に変換する: sensor_decode(hi, lo, out)を実装してください。通常モードの有効な正の数・0の入力を、上位バイトを先にして結合し、下位4ビットを除いたカウントに62,500を掛けて、int32_tのマイクロ摂氏として保存します。成功はSENSOR_OKです。0–2047カウント全体と、小数の値が対象です。
- 255という数字の裏にある負の数を見つける: 通常モードの負の数を含む、4096個のカウント全体に対応してください。12ビットの2の補数の符号を復元し、-0.0625°Cは-62,500として保存します。以前の正の数の変換は維持してください。
- 拡張モードと壊れた入力を区別する: loのbit 0が1なら拡張モードで、13ビットのカウントとし、下位3ビットを除き、予約のbit 2・1は0でなければなりません。通常モードは、loの下位4ビットが0でなければなりません。予約ビットの違反はSENSOR_FORMAT、NULLの出力はSENSOR_ARGです。エラーのときは*outを変えないでください。2つのモードの全65,536通りのバイトの組み合わせを検査します。
- アドレスと結合転送をつなぐ: sensor_read(bus, address7, budget_ms, out)に、正常パスを実装してください。now_ms(ctx)で開始と完了の時刻を読み、transfer(ctx,address7,&pointer,1,bytes,2,start+budget_ms)を1回呼び出します。pointerは0x00、address7は0x48–0x4bそのままです。変換結果と完了を確認した時刻を、out->micro_cとobserved_msに保存し、SENSOR_OKを返します。通常モードと拡張モードの両方に対応します。
- 部分的な失敗が正常なサンプルを上書きしないようにする: NACK・SHORT・FORMATのエラーをそのまま返し、再転送や復旧はしないでください。エラーのとき、outの2つのフィールドは、進入時の値のまま維持します。transferが失敗したときにbytesの一部が変わることがあるので、別の作業用バッファーと、ローカルの変換値を使ってください。
- 1回だけ復旧して読み直す: 最初の転送がSENSOR_BUSのときだけ、recover(ctx,deadline)を1回呼び出してください。復旧が成功したら、同じアドレス・ポインター・長さ・deadlineで、転送を1回リトライします。復旧のエラーはそのまま返し、2回目の転送のBUSには、それ以上復旧しません。他のエラーと、出力保存の契約は維持してください。
- 遅れた成功にも失敗と答える: 転送・復旧・リトライが、開始時に求めた単一のdeadlineを共有するようにしてください。各transfer・recoverの直後に、uint32_tの経過時間がbudget_ms以上なら、SENSOR_TIMEOUTです。HALが遅れた成功を返しても、サンプルを更新しません。バジェットはこのステップで有効であり、ちょうど期限に到着してもタイムアウトです。
- 時計の境界と不正な呼び出しまで防御する: I/Oの前に、bus・out・transfer・now_ms・recoverがNULLかどうか、アドレスが0x48–0x4bかどうか、バジェットが1–INT32_MAXかどうかを検査し、不正ならSENSOR_ARGを返してください。ctx自体はNULLでもかまいません。引数エラーでも、出力とI/Oの状態は維持します。uint32_tの時計が1回ラップアラウンドする転送と、正常→部分的な失敗→正常の連続呼び出しまで、前のステップの契約を維持してください。完了を確認した時刻は、ADCの測定時刻ではありません。
参考
すべての実装は、/root/sensor-driver/sensor.cの1つに保存します。mkdir -p /root/sensor-driverでフォルダーを作り、例のひな形は最初の1回だけ保存してください。提供されるヘッダーは、/opt/lab/sensor_driver/sensor.hです。catコマンドで、関数とステータスコードを確認してください。自分でのコンパイル確認は、cc -std=c11 -Wall -Wextra -Werror -Wconversion -pedantic -I /opt/lab/sensor_driver -c /root/sensor-driver/sensor.c -o /tmp/sensor.oです。main関数は検査ツールが提供します。標準出力にデバッグログを追加しないでください。追加のライブラリのインストールやインターネットは必要ありません。提出ファイルは64KiB以下の通常のCファイルで、採点のコンパイル3秒・実行2秒の制限は、学習時間の制限とは別です。検査ツールは前のステップも再確認します。途中のステップの実装は、まだ全体の契約を満たしていません。必要ならセッションの+時間で延長し、セッションが終了する前に、ソースと記録を別に保管してください。終了後のファイルは保持されません。
バイトを温度に変換する
sensor_decode(hi, lo, out)を実装してください。通常モードの有効な正の数・0の入力を、上位バイトを先にして結合し、下位4ビットを除いたカウントに62,500を掛けて、int32_tのマイクロ摂氏として保存します。成功はSENSOR_OKです。0–2047カウント全体と、小数の値が対象です。
0x19 0x00は400カウントです。floatではなく整数の単位を使い、バイトを結合する前にuint32_tに広げてください。
255という数字の裏にある負の数を見つける
通常モードの負の数を含む、4096個のカウント全体に対応してください。12ビットの2の補数の符号を復元し、-0.0625°Cは-62,500として保存します。以前の正の数の変換は維持してください。
符号ビットが立っている値から、2の12乗を引いてみてください。負の数を先に整数の摂氏で割ると、小さな値が消えます。
拡張モードと壊れた入力を区別する
loのbit 0が1なら拡張モードで、13ビットのカウントとし、下位3ビットを除き、予約のbit 2・1は0でなければなりません。通常モードは、loの下位4ビットが0でなければなりません。予約ビットの違反はSENSOR_FORMAT、NULLの出力はSENSOR_ARGです。エラーのときは*outを変えないでください。2つのモードの全65,536通りのバイトの組み合わせを検査します。
モードごとに、カウントの幅とシフト数が一緒に変わります。有効性を先に確認し、最後にだけ出力してください。
アドレスと結合転送をつなぐ
sensor_read(bus, address7, budget_ms, out)に、正常パスを実装してください。now_ms(ctx)で開始と完了の時刻を読み、transfer(ctx,address7,&pointer,1,bytes,2,start+budget_ms)を1回呼び出します。pointerは0x00、address7は0x48–0x4bそのままです。変換結果と完了を確認した時刻を、out->micro_cとobserved_msに保存し、SENSOR_OKを返します。通常モードと拡張モードの両方に対応します。
関数ポインターとctxの使い方は、sensor.hを見てください。アドレスにR/Wビットを付けません。失敗のポリシーは、次のステップで追加します。
部分的な失敗が正常なサンプルを上書きしないようにする
NACK・SHORT・FORMATのエラーをそのまま返し、再転送や復旧はしないでください。エラーのとき、outの2つのフィールドは、進入時の値のまま維持します。transferが失敗したときにbytesの一部が変わることがあるので、別の作業用バッファーと、ローカルの変換値を使ってください。
出力を先に0で初期化するのも、既存の正常な値を消す動作です。成功を確認してから、2つのフィールドを一緒に更新してください。
1回だけ復旧して読み直す
最初の転送がSENSOR_BUSのときだけ、recover(ctx,deadline)を1回呼び出してください。復旧が成功したら、同じアドレス・ポインター・長さ・deadlineで、転送を1回リトライします。復旧のエラーはそのまま返し、2回目の転送のBUSには、それ以上復旧しません。他のエラーと、出力保存の契約は維持してください。
復旧せずに繰り返し呼び出すと、モデルの引っかかった状態が残ります。試行の回数とエラーの種類を、一緒に確認してください。
遅れた成功にも失敗と答える
転送・復旧・リトライが、開始時に求めた単一のdeadlineを共有するようにしてください。各transfer・recoverの直後に、uint32_tの経過時間がbudget_ms以上なら、SENSOR_TIMEOUTです。HALが遅れた成功を返しても、サンプルを更新しません。バジェットはこのステップで有効であり、ちょうど期限に到着してもタイムアウトです。
現在時刻から開始時刻をunsignedで引いた値と、バジェットを比べてください。リトライのたびにstartを読み直すと、全体のバジェットを延ばすことになります。
時計の境界と不正な呼び出しまで防御する
I/Oの前に、bus・out・transfer・now_ms・recoverがNULLかどうか、アドレスが0x48–0x4bかどうか、バジェットが1–INT32_MAXかどうかを検査し、不正ならSENSOR_ARGを返してください。ctx自体はNULLでもかまいません。引数エラーでも、出力とI/Oの状態は維持します。uint32_tの時計が1回ラップアラウンドする転送と、正常→部分的な失敗→正常の連続呼び出しまで、前のステップの契約を維持してください。完了を確認した時刻は、ADCの測定時刻ではありません。
ポインターの検査より先にデリファレンスが来ないようにしてください。絶対時刻の比較はラップアラウンドの手前で失敗し、エラーの保存は、次の正常な更新を妨げるという意味ではありません。