TT Lab
开始
学习 学习路径 课程

冰箱坚称自己有255°C

编写识别错误温度的C驱动

在 TT Lab 中继续学习

目标

用 C 实现两个字节的转换,以及 HAL 的错误与期限约定。会运行真实的 C 程序,但不涉及真实的 MCU、I2C 波形和电压测量。

为什么重要

即使输出了数字,只要符号或单位错了,就不是正常的测量。部分读取可能污染最后一个正常值,无限重试会阻塞后续任务。把错误、值、时间放在一起验证,就能构建可复现的驱动测试。需要具备 C 函数、指针、结构体和 shell 的基础。

步骤

  1. 把字节变成温度 — 实现 sensor_decode(hi, lo, out)。对于正常模式下有效的正数和 0 输入,先合并高位字节,再把去掉低 4 位的计数值乘以 62,500,以 int32_t 微摄氏度存储。成功返回 SENSOR_OK。对象是 0–2047 的全部计数值以及小数值。
  2. 找出 255 这个数字背后的负数 — 支持正常模式下包含负数的全部 4096 个计数值。还原 12 位二进制补码的符号,-0.0625°C 存为 -62,500。保持之前的正数转换不变。
  3. 区分扩展模式与损坏的输入 — lo 的 bit 0 为 1 时是扩展模式:13 位计数值,去掉低 3 位,保留位 bit 2、1 必须为 0。正常模式要求 lo 的低 4 位为 0。违反保留位返回 SENSOR_FORMAT,NULL 输出返回 SENSOR_ARG。出错时不要改变 *out。检查两种模式下全部 65,536 种字节组合。
  4. 连接地址与组合传输 — 在 sensor_read(bus, address7, budget_ms, out) 中实现正常路径。用 now_ms(ctx) 读取开始和完成时间,并调用一次 transfer(ctx,address7,&pointer,1,bytes,2,start+budget_ms)。pointer 为 0x00,address7 保持 0x48–0x4b 不变。把转换结果和完成确认时间存入 out->micro_c、observed_ms,并返回 SENSOR_OK。正常模式和扩展模式都要支持。
  5. 防止部分失败覆盖正常样本 — 遇到 NACK、SHORT、FORMAT 错误时原样返回,不要重传或恢复。出错时 out 的两个字段保持进入函数时的值。transfer 失败时 bytes 的一部分可能已被改变,所以要使用单独的工作缓冲区和局部转换值。
  6. 只恢复一次并重新读取 — 只有第一次传输返回 SENSOR_BUS 时,才调用一次 recover(ctx,deadline)。恢复成功后,用相同的地址、指针、长度、deadline 重试一次传输。恢复错误原样返回,第二次传输的 BUS 不再恢复。保持对其他错误和输出保留的约定。
  7. 迟到的成功也要回答为失败 — 让传输、恢复、重试共享开始时求出的同一个 deadline。每次 transfer、recover 之后,如果 uint32_t 的经过时间达到或超过 budget_ms,就返回 SENSOR_TIMEOUT。即使 HAL 返回了迟到的成功,也不更新样本。预算在这一步生效,恰好在期限时到达也属于超时。
  8. 连时钟边界和错误调用也要防御 — 在 I/O 之前检查 bus、out、transfer、now_ms、recover 是否为 NULL,地址是否在 0x48–0x4b,预算是否在 1–INT32_MAX,不合法就返回 SENSOR_ARG。ctx 本身可以为 NULL。参数错误时也要保持输出和 I/O 状态不变。对于 uint32_t 时钟回绕一次的传输,以及“正常→部分失败→正常”的连续调用,也要保持前面各步骤的约定。完成确认时间并不是 ADC 测量时间。

参考

所有实现都保存在 /root/sensor-driver/sensor.c 一个文件中。用 mkdir -p /root/sensor-driver 创建文件夹,示例框架只需在第一次保存。提供的头文件是 /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 函数由检查器提供。不要向标准输出添加调试日志。不需要安装额外的库,也不需要联网。提交文件须是 64 KiB 以下的普通 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 位二进制补码的符号,-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。检查两种模式下全部 65,536 种字节组合。

每种模式的计数值位宽和移位数都不同。先确认有效性,只在最后才输出。

连接地址与组合传输

在 sensor_read(bus, address7, budget_ms, out) 中实现正常路径。用 now_ms(ctx) 读取开始和完成时间,并调用一次 transfer(ctx,address7,&pointer,1,bytes,2,start+budget_ms)。pointer 为 0x00,address7 保持 0x48–0x4b 不变。把转换结果和完成确认时间存入 out->micro_c、observed_ms,并返回 SENSOR_OK。正常模式和扩展模式都要支持。

函数指针和 ctx 的用法请看 sensor.h。不要给地址附加 R/W 位。失败策略在下一步添加。

防止部分失败覆盖正常样本

遇到 NACK、SHORT、FORMAT 错误时原样返回,不要重传或恢复。出错时 out 的两个字段保持进入函数时的值。transfer 失败时 bytes 的一部分可能已被改变,所以要使用单独的工作缓冲区和局部转换值。

先把输出初始化为 0,同样是在清除已有的正常值。确认成功之后,再同时更新两个字段。

只恢复一次并重新读取

只有第一次传输返回 SENSOR_BUS 时,才调用一次 recover(ctx,deadline)。恢复成功后,用相同的地址、指针、长度、deadline 重试一次传输。恢复错误原样返回,第二次传输的 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 时钟回绕一次的传输,以及“正常→部分失败→正常”的连续调用,也要保持前面各步骤的约定。完成确认时间并不是 ADC 测量时间。

不要让解引用先于指针检查。绝对时间的比较会在回绕之前失败,而保留错误并不意味着要阻止下一次正常更新。