1. 背景:真的准了吗
在之前的文章里,我们介绍了在APM32F402 EVB平台如何使用 HC-SR04 测量距离,并通过卡尔曼滤波等手段让数据变得平稳。然而别忘了,声速并不是“恒速奔跑”,它会随着环境温度、湿度的变化而有所波动。
- 温度越高,空气分子运动就越激烈,声速就越快;
- 湿度增加,声速也会稍微提高,因为水蒸气分子质量比空气分子更小;
- 压强的影响则相对有限,可以在大多数情况下忽略。
也就是说,如果你只是在室温环境下随便测测,可能还好;但如果追求更高精度,或者实地环境温湿度较为极端,就要正儿八经地考虑温湿度补偿了!
2. 温湿度对声音的影响:别让声波了迷路
参考https://www.buildenvi.com/zhuanti/abc/sygs/141111c2#,声音在理想气体中的传播速度可由公式 : v = √(k·R·T) 来估算。其中 k 为气体绝热指数,R 为气体常数,T 为绝对温度(K)。虽说公式挺复杂,但我们大可以用现成的“声音在不同温湿度情况下的速度对照表”:

那么,这些公式或表格有什么实战意义?举个例子:本来在 20°C、湿度 60% 时声音速度可能是 343 m/s,但若环境升温到 30°C,又是一个潮湿闷热的地方,声速可能就到了 350 m/s。别看这区区几 m/s,相当于测 2 米时就能带来好几厘米的误差呢!
3. 成本王者 DHT11:小巧又实惠
要想搞定温湿度测量,就得有传感器。DHT11 行业内非常吃香:便宜、易上手、占空间小。精度虽不算“炸裂”,但足以应付日常需求。更何况它有点“萌萌哒”外形,一看就让人有安全感。

图片来源:https://images.theengineeringprojects.com/image/webp/2019/02/dht11.jpg.webp?ssl=1
下面是 DHT11 的部分驱动代码,核心逻辑就是通过单总线协议与 MCU 通信,一收一发间便获取到温度与湿度:
static void DHT11_IO_OUT(void)
{
GPIO_Config_T GPIO_ConfigStruct;
/* Enable GPIO and AFIO clock */
RCM_EnableAPB2PeriphClock(DHT11_GPIO_CLK);
/* Configure DHT11 pin as push-pull output */
GPIO_ConfigStruct.pin = DHT11_PIN;
GPIO_ConfigStruct.mode = GPIO_MODE_OUT_PP;
GPIO_ConfigStruct.speed = GPIO_SPEED_50MHz;
GPIO_Config(DHT11_GPIO_PORT, &GPIO_ConfigStruct);
}
static void DHT11_IO_IN(void)
{
GPIO_Config_T GPIO_ConfigStruct;
/* Enable GPIO and AFIO clock */
RCM_EnableAPB2PeriphClock(DHT11_GPIO_CLK);
/* Configure DHT11 pin as floating input */
GPIO_ConfigStruct.pin = DHT11_PIN;
GPIO_ConfigStruct.mode = GPIO_MODE_IN_FLOATING;
GPIO_ConfigStruct.speed = GPIO_SPEED_50MHz; // For input mode, speed usually has no real effect
GPIO_Config(DHT11_GPIO_PORT, &GPIO_ConfigStruct);
}
uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi)
{
uint8_t buf[5];
uint8_t i;
/* Reset signal */
DHT11_Rst();
/* Check if DHT11 responds */
if (DHT11_Check() == 0)
{
for (i = 0; i < 5; i++)
{
buf[i] = DHT11_Read_Byte();
}
/* Verify checksum */
if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
{
*humi = buf[0];
*temp = buf[2];
}
else
{
return 1; // Checksum error
}
}
else
{
return 1; // Not responding
}
return 0; // Success
}
uint8_t DHT11_Init(void)
{
/* Send reset signal */
DHT11_Rst();
/* Check if DHT11 is ready */
return DHT11_Check(); // 0 => success; 1 => fail
}
就这么几段,你已经能搞定 DHT11 的温湿度测量。理论很简单:拉低、等待、检测响应、读出数据,然后校验和即可。
4. 温湿度补偿:查表法 or 公式?
接下来是超声测距的“终极大招”——温湿度补偿。大致流程是:
- 用 DHT11 读出实时温湿度;
- 根据温湿度找到对应的声速;
- 将 echo 回波时间乘以该声速即可得到实际距离。
4.1 为什么选择“查表法”?
- 速度快:大部分MCU 做大规模浮点运算效率较低,不如直接索引查表再做微小插值来得痛快。
- 稳定性好:在一定范围内,表格能给出稳定输出,不会因为浮点误差惊扰软件。
- 简单可控:如果后续对更多温湿度点有需求,也可以继续扩展表格。
当然,APM32F402 内置 FPU(Floating Point Unit),也能飙浮点运算,用公式也行,但查表依旧是个常用、简洁的方案。以下为示例代码:
/* 在 ultrasonic_th_compensation.c 中 */
// Temperature array (°C)
static const float tempPoints[7] = {0.0f, 5.0f, 10.0f, 15.0f, 20.0f, 25.0f, 30.0f};
// Humidity array (%)
static const float humPoints[9] = {10.0f, 20.0f, 30.0f, 40.0f, 50.0f,
60.0f, 70.0f, 80.0f, 90.0f};
/* speedTable[row for temp][column for humidity] => mm/us (one-way) */
static const float speedTable[7][9] =
{
/* T=0 */ {0.16575f, 0.16575f, 0.16575f, 0.16580f, 0.16580f, 0.16580f, 0.16585f, 0.16585f, 0.16585f},
/* T=5 */ {0.16725f, 0.16730f, 0.16730f, 0.16735f, 0.16735f, 0.16735f, 0.16740f, 0.16740f, 0.16745f},
/* T=10 */ {0.16875f, 0.16880f, 0.16885f, 0.16885f, 0.16890f, 0.16895f, 0.16895f, 0.16900f, 0.16900f},
/* T=15 */ {0.17025f, 0.17030f, 0.17035f, 0.17040f, 0.17045f, 0.17050f, 0.17055f, 0.17060f, 0.17060f},
/* T=20 */ {0.17175f, 0.17180f, 0.17185f, 0.17195f, 0.17200f, 0.17205f, 0.17210f, 0.17220f, 0.17225f},
/* T=25 */ {0.17320f, 0.17330f, 0.17340f, 0.17350f, 0.17355f, 0.17365f, 0.17375f, 0.17380f, 0.17390f},
/* T=30 */ {0.17470f, 0.17480f, 0.17495f, 0.17505f, 0.17515f, 0.17525f, 0.17540f, 0.17550f, 0.17560f}
};
static int getNearestIndex(const float* arr, int arrSize, float value)
{
if(value <= arr[0]) return 0;
if(value >= arr[arrSize - 1]) return arrSize - 1;
int nearestIdx = 0;
float minDiff = fabsf(value - arr[0]);
for(int i = 1; i < arrSize; i++)
{
float diff = fabsf(value - arr[i]);
if(diff < minDiff)
{
minDiff = diff;
nearestIdx = i;
}
}
return nearestIdx;
}
float CalcDistanceWithTHCompensation(uint32_t echoWidth_us, float temperature, float humidity)
{
// 1) 索引
int tempIndex = getNearestIndex(tempPoints, 7, temperature);
int humIndex = getNearestIndex(humPoints, 9, humidity);
// 2) 查表得到单程声速因子(mm/us)
float speedFactor = speedTable[tempIndex][humIndex];
// 3) 计算距离(回波时间 × speedFactor)
float distance_mm = echoWidth_us * speedFactor;
// 4) 超过4m做无效处理
if (distance_mm > 4000.0f)
{
distance_mm = 0.0f;
}
return distance_mm;
}
实际对比:

5. 结语
声速听起来神秘,却又真实地影响着我们的超声测距结果。大部分室内测量可以忽略温湿度差异,但在极寒、高温、高湿或极干环境中,差异不容小觑。
本文给大家简单介绍了如何把 DHT11 测得的温湿度值与 HC-SR04 的回波时间配合使用
附件:APM32F402_403_SDK_V1.0.1_HC_SR04_DHT11.zip。文中使用了查表法补偿。当然,你也可以直接使用浮点公式来计算,一样能得到不错的效果——凡事任何时候都要权衡精度、速度和可维护性。
通过温湿度补偿,提高HC-SR04超声波测距模块的精准度。