TT Lab
Get started
Learn Learning paths Courses

The Fridge Insists It Is 255°C

Build a C driver that catches false temperatures

Continue in TT Lab

Goal

Implement the two-byte conversion and the HAL error and deadline contract in C. A real C program runs, but no real MCU, I2C waveform, or voltage is measured.

Why it matters

Even if a number is printed, it is not a normal measurement when the sign or unit is wrong. A partial read can contaminate the last good value, and endless retries block the next job. You verify the error, the value, and the time together to build a reproducible driver test. You need the basics of C functions, pointers, structs, and the shell.

Steps

  1. Convert bytes to a temperature — Implement sensor_decode(hi, lo, out). For valid positive and zero inputs in normal mode, combine the upper byte first, multiply the count with the low 4 bits dropped by 62,500, and store it as int32_t micro-degrees Celsius. Success is SENSOR_OK. The target is every count from 0 to 2047 and the fractional values.
  2. Find the negative behind the number 255 — Support all 4096 counts of normal mode, including negatives. Restore the 12-bit two's complement sign, and store -0.0625°C as -62,500. Keep the earlier positive conversion working.
  3. Tell extended mode from broken input — If bit 0 of lo is 1, it is extended mode: a 13-bit count, dropping the low 3 bits, and the reserved bits 2 and 1 must be 0. In normal mode, the low 4 bits of lo must be 0. A reserved-bit violation is SENSOR_FORMAT, and a NULL output is SENSOR_ARG. Do not change *out on an error. The check covers all 65,536 byte combinations of both modes.
  4. Connect the address and the combined transfer — Implement the normal path in sensor_read(bus, address7, budget_ms, out). Read the start and completion times with now_ms(ctx), and call transfer(ctx,address7,&pointer,1,bytes,2,start+budget_ms) once. The pointer is 0x00, and address7 is 0x48 to 0x4b as is. Store the conversion result and the completion-confirmed time in out->micro_c and observed_ms, and return SENSOR_OK. Support both normal and extended modes.
  5. Keep a partial failure from overwriting a good sample — Return NACK, SHORT, and FORMAT errors as they are, and do not retransfer or recover. On an error, the two fields of out stay exactly as they were on entry. A failed transfer may change part of bytes, so use a separate working buffer and a local converted value.
  6. Recover only once and read again — Call recover(ctx,deadline) once, only when the first transfer is SENSOR_BUS. If the recovery succeeds, retry the transfer once with the same address, pointer, length, and deadline. Return a recovery error as it is, and do not recover again on a BUS from the second transfer. Keep the contract for other errors and for output preservation.
  7. Answer failure even for a late success — Make the transfer, recovery, and retry share the single deadline computed at the start. If the uint32_t elapsed time right after each transfer or recover is at or above budget_ms, return SENSOR_TIMEOUT. Even if the HAL returns a late success, do not update the sample. The budget is valid in this step, and arriving exactly at the deadline is still a timeout.
  8. Defend against clock boundaries and bad calls too — Before any I/O, check whether bus, out, transfer, now_ms, and recover are NULL, whether the address is 0x48–0x4b, and whether the budget is 1–INT32_MAX, and return SENSOR_ARG if any is wrong. ctx itself may be NULL. Even on an argument error, keep the output and the I/O state. Keep the contracts of the earlier steps, including a transfer where the uint32_t clock wraps once and consecutive good → partial failure → good calls. The completion-confirmed time is not the ADC measurement time.

Notes

Save the whole implementation in a single file, /root/sensor-driver/sensor.c. Create the folder with mkdir -p /root/sensor-driver and save the sample skeleton only the first time. The supplied header is /opt/lab/sensor_driver/sensor.h. Check the functions and status codes with the cat command. To check that it compiles yourself: cc -std=c11 -Wall -Wextra -Werror -Wconversion -pedantic -I /opt/lab/sensor_driver -c /root/sensor-driver/sensor.c -o /tmp/sensor.o. The main function is supplied by the checker. Do not add debug logs to standard output. No extra library installation or internet is needed. The submission file is a regular C file of at most 64 KiB, and the grading limits of 3 seconds for compilation and 2 seconds for execution are different from the study time limit. The checker rechecks the earlier steps too. An intermediate-step implementation does not yet satisfy the whole contract. If needed, extend the session with +time, and keep the source and your records separately before the session ends. Files are not kept after it ends.

Convert bytes to a temperature

Implement sensor_decode(hi, lo, out). For valid positive and zero inputs in normal mode, combine the upper byte first, multiply the count with the low 4 bits dropped by 62,500, and store it as int32_t micro-degrees Celsius. Success is SENSOR_OK. The target is every count from 0 to 2047 and the fractional values.

0x19 0x00 is 400 counts. Use integer units rather than float, and widen the bytes to uint32_t before combining them.

Find the negative behind the number 255

Support all 4096 counts of normal mode, including negatives. Restore the 12-bit two's complement sign, and store -0.0625°C as -62,500. Keep the earlier positive conversion working.

For a value with the sign bit set, try subtracting 2 to the 12th power. If you divide a negative number down to integer degrees Celsius first, the small value disappears.

Tell extended mode from broken input

If bit 0 of lo is 1, it is extended mode: a 13-bit count, dropping the low 3 bits, and the reserved bits 2 and 1 must be 0. In normal mode, the low 4 bits of lo must be 0. A reserved-bit violation is SENSOR_FORMAT, and a NULL output is SENSOR_ARG. Do not change *out on an error. The check covers all 65,536 byte combinations of both modes.

In each mode the count width and the shift amount change together. Check validity first, and write the output only at the end.

Connect the address and the combined transfer

Implement the normal path in sensor_read(bus, address7, budget_ms, out). Read the start and completion times with now_ms(ctx), and call transfer(ctx,address7,&pointer,1,bytes,2,start+budget_ms) once. The pointer is 0x00, and address7 is 0x48 to 0x4b as is. Store the conversion result and the completion-confirmed time in out->micro_c and observed_ms, and return SENSOR_OK. Support both normal and extended modes.

See sensor.h for how to use the function pointers and ctx. Do not attach the R/W bit to the address. The failure policy is added in the next step.

Keep a partial failure from overwriting a good sample

Return NACK, SHORT, and FORMAT errors as they are, and do not retransfer or recover. On an error, the two fields of out stay exactly as they were on entry. A failed transfer may change part of bytes, so use a separate working buffer and a local converted value.

Initializing the output to 0 first is also an action that erases the existing good value. After you confirm success, update the two fields together.

Recover only once and read again

Call recover(ctx,deadline) once, only when the first transfer is SENSOR_BUS. If the recovery succeeds, retry the transfer once with the same address, pointer, length, and deadline. Return a recovery error as it is, and do not recover again on a BUS from the second transfer. Keep the contract for other errors and for output preservation.

If you call repeatedly without recovery, the model's stuck state remains. Check the attempt count and the error kind together.

Answer failure even for a late success

Make the transfer, recovery, and retry share the single deadline computed at the start. If the uint32_t elapsed time right after each transfer or recover is at or above budget_ms, return SENSOR_TIMEOUT. Even if the HAL returns a late success, do not update the sample. The budget is valid in this step, and arriving exactly at the deadline is still a timeout.

Compare the budget with the value you get by subtracting the start time from the current time in unsigned arithmetic. If you re-read start on every retry, you enlarge the overall budget.

Defend against clock boundaries and bad calls too

Before any I/O, check whether bus, out, transfer, now_ms, and recover are NULL, whether the address is 0x48–0x4b, and whether the budget is 1–INT32_MAX, and return SENSOR_ARG if any is wrong. ctx itself may be NULL. Even on an argument error, keep the output and the I/O state. Keep the contracts of the earlier steps, including a transfer where the uint32_t clock wraps once and consecutive good → partial failure → good calls. The completion-confirmed time is not the ADC measurement time.

Make sure a dereference never comes before the pointer check. An absolute time comparison fails just before a wrap, and preserving on error does not mean blocking the next good update.