冰箱坚称自己有255°C
一句话总结
传感器发来的两个字节还不是温度。只有能解释字节顺序、符号和单位,才能相信这个数字。
为什么需要它
冰箱坚称自己是 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,依次检查正数的全部范围、负数的全部范围、两种模式以及错误输入。也请先解决编译器警告,不要只把表中的几个值硬编码。