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

冰箱坚称自己有255°C

冰箱坚称自己有255°C

在 TT Lab 中继续学习

一句话总结

传感器发来的两个字节还不是温度。只有能解释字节顺序、符号和单位,才能相信这个数字。

为什么需要它

冰箱坚称自己是 255°C。既不能立刻断定它真的过热,也不能当作显示缺陷而不理会。这个故事是一个合成的故障场景。先把原始字节、转换约定、输出单位分开并复现,练习找出错误的值是在哪个边界产生的,这并不是设计真实冰箱控制与安全装置的过程。

开始之前,请先掌握 C 函数、指针、结构体的基础。函数不会把结果全部放进一个返回值里。返回值表示是否成功,温度则写入输出指针。失败时即使还留着上一次的值,也不代表这次调用成功。调用方必须把状态码和值一起处理。

工作原理

本次的 TMP102 温度寄存器转换先接收高位字节。正常模式是 12 位,扩展模式是 13 位二进制补码,一个计数值对应 0.0625°C。用第二个字节的 bit 0 区分模式。详细布局请查看官方数据手册中的表 6-8、6-9。

实验中不用浮点数,而是使用以微摄氏度为单位的整数。1°C 是 1,000,000,一个计数值是 62,500。如果先舍去小数点再换算单位,较小的负数和小数值就会消失。应当先精确求出计数值,最后再换算单位。

正常模式下的 0x19 0x00 是 25°C,0xff 0xf0 是 -0.0625°C。把 0xff 直接显示为无符号温度 255,就是混淆了字节与物理量的例子。对于 12 位数字,如果符号位被置位,从按正整数读出的计数值中减去 4096 即可。扩展模式的位宽则有所不同。之所以不依赖负数的右移,而是显式地还原,是为了减少 C 语言实现之间的差异。

对于保留位被置位的输入,本任务以 SENSOR_FORMAT 拒绝,并保持输出不变。这是本任务的有效输入策略,并不是所有传感器的通用规则。请先阅读头文件 sensor.h 中的函数声明和状态枚举。main 函数由检查器提供,所以提交文件中只需实现两个函数。

手工换算并为代码建立假设

下表是依据转换约定得出的计算示例,而不是实测值。请先在纸上求出计数值,再与最后一列比较。微摄氏度一列是带单位的整数,省略了千位分隔逗号。

两个字节 模式 还原符号后的计数值 微摄氏度
0x19 0x00 正常 400 25000000
0x0a 0x10 正常 161 10062500
0xff 0x50 正常 -11 -687500
0xff 0xf0 正常 -1 -62500
0x4b 0x01 扩展 2400 150000000

我们来拆解第三行。把两个字节合并成 0xff50,去掉低四位得到 0xff5,也就是 4085。这个值不小于 12 位符号边界 2048,所以 4085−4096=−11。再乘以 62,500 得到 −687,500,换算成摄氏度就是 −0.6875°C。如果只读前一个字节,或者先换算成摄氏度整数再相除,这个很小的负数就会消失。第五行的低位 bit 0 为 1,所以要去掉三位。如果只检查模式位而保持移位宽度不变,同样的字节就会被解释为另一个温度。

在 C 中,符号还原是一个包含运算所用数据类型在内的约定。移位后的非负计数值最大为 8191,可以放进 int32_t。在这个范围内先转存到有符号变量,再减去 4096 或 8192,就能显式得到负数。反过来,如果在 unsigned 状态下先做减法,就会变成很大的正数。不要指望之后再把结果转换为 signed。完成后的计数值绝对值最大为 4096,所以乘以 62,500 的结果也在 int32_t 范围内。中间计算和最终存储两处都要检查范围,这个习惯很重要。

现在来构造反例。把表中的 0x0a 0x10 反过来读,会得到 0x100a,这也违反了本次正常模式的保留位策略。而 0x00 0x00 即使颠倒顺序也不会改变。因此,“结果为 0”的测试与“顺序正确”的测试并不相同。请同时写明选择这个正常输入的理由,以及该输入无法区分的缺陷。

在现场相遇的样子

复现报告中不仅要留下屏幕上的数字,还要留下原始字节、模式和输出单位。字节顺序错误的代码,在某些值上也会碰巧正确。只测试 0,就分不出调换顺序的代码与正确的代码。所以要把正数、0、较小的负数、边界和保留位分别测一遍。

可表示的数与物理上的工作范围也是两回事。即使扩展编码中 150°C 的计算正确,也不能保证真实的 TMP102 在该温度下正常工作。分辨率精细,同样不意味着测量精度也那么好。

接下来要确认什么

紧接着的测验会确认计算顺序和反例。之后阅读失败与时间约定,再进入最后一个模块的累积 C 实验。在实验的第 1–3 步中实现 sensor_decode,依次检查正数的全部范围、负数的全部范围、两种模式以及错误输入。也请先解决编译器警告,不要只把表中的几个值硬编码。