前置介绍
回顾
通过 ADC 多通道 + DMA 采集 空气, 烟雾传感器的 ADC 数据.
引出
但是 ADC 数据只是一个电压值, 该怎么得到空气和烟雾的浓度呢?
空气, 烟雾传感器公式换算

ADC 采集到的电压值是一个 "中间量" , 并不能直接反映气体浓度.
例: 读取电压为 1V, 但是却并不知道具体的浓度.
模块手册 - 烟雾传感器 - 程序流程


原理图 - 烟雾传感器

MQ2 - 数据手册与函数拟合 Excel 表

这里的预热时间对于我们做程序实验来说, 不需要满足预热 48 小时, 一分钟即可进行简单测量.

项目配置
代码部分
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h> // 使用 printf 函数
#include <math.h> // 使用 pow(浮点幂运算)
#include <string.h> // 使用 strncmp
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* ===================== MQ135 参数 ===================== */
#define RL_MQ135 1.0f /* 根据硬件原理图可知:RL = 1k */
#define R0_MQ135 2.0f /* MQ135在洁净空气中的阻值 */
#define VC_MQ135 5.0f /* MQ135供电电压,根据实际供电修改 */
#define A_MQ135 4.17f /* y=ax^b 的 a */
#define B_MQ135 -2.28f /* y=ax^b 的 b */
/* ===================== MQ2 参数 ===================== */
#define RL_MQ2 10.0f /* 根据硬件原理图可知:RL = 10k */
#define R0_MQ2 20.0f /* MQ2在洁净空气中的阻值 */
#define VC_MQ2 5.0f /* MQ2供电电压,根据实际供电修改 */
#define A_MQ2 43.03f /* y=ax^b 的 a */
#define B_MQ2 -1.66f /* y=ax^b 的 b */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint16_t adc_val[2]; // 存储传感器 ADC 数值
float voltage[2]; // 存储传感器 ADC 电压
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
// ADC 校准
HAL_ADCEx_Calibration_Start(&hadc1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// 启动 ADC + DMA, adc_val 是数组(地址), 在 CubeMX 中配置了内存地址递增, 所以这里只需要给这个地址就可以了.
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_val, 2);
// 每 500ms 转换一次.
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE BEGIN 4 */
/**
* @brief 根据ADC值计算指定气体传感器的ppm
* @param adc_value : ADC读取的原始值(0~4095)
* @param RL : 负载电阻(kΩ)
* @param R0 : 传感器在洁净空气中的电阻(kΩ)
* @param VC : 传感器供电电压(V)
* @param A : 拟合公式系数 a
* @param B : 拟合公式指数 b
* @retval ppm
* @note y = A * (Rs/R0)^B
*/
float Get_PPM(uint16_t adc_value, float RL, float R0, float VC, float A, float B)
{
float vrl; /* AO输出的模拟电压 */
float Rs; /* 当前传感器电阻 */
float ppm; /* 气体平均浓度 */
/* 读取AO输出电压 */
vrl = (float)adc_value / 4095.0f * VC;
/* 换算Rs电阻 */
Rs = (VC - vrl) * RL / vrl;
/* y=ax^b,x为Rs/R0 */
ppm = A * pow(Rs / R0, B);
return ppm;
}
/**
* @brief ADC + DMA 转换完成回调函数
* @param hadc 指向 ADC 句柄的指针
* @retval 无
* @note 当 ADC1 的规则通道转换完成并且DMA传输完成后被自动调用,用于计算电压值并打印显示
*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
if (hadc -> Instance == ADC1)
{
// 将 ADC 数值转换为电压(假设参考电压为 3.3V,12位精度)
voltage[0] = (float)adc_val[0] / 4095 * 3.3f;
voltage[1] = (float)adc_val[1] / 4095 * 3.3f;
printf("\r\n空气(IN4): %.3f V,烟雾(IN5): %.3f V\r\n",
voltage[0],voltage[1]);
// 打印80个*的分隔线
printf("\r\n/*");
for (uint32_t i = 0; i < 80; i++) printf("*");
printf("*/");
// MQ135空气质量计算,基于传感器电阻与经验参数
float ppm_air = Get_PPM(adc_val[0], RL_MQ135, R0_MQ135, VC_MQ135, A_MQ135, B_MQ135);
if (ppm_air < 10)
printf("\r\n空气质量:低于检测范围!");
else if (ppm_air > 1000)
printf("\r\n空气质量:高于检测范围!");
else
printf("\r\n空气质量:%.2f ppm!", ppm_air);
// MQ2烟雾浓度计算
float ppm_smoke = Get_PPM(adc_val[1], RL_MQ2, R0_MQ2, VC_MQ2, A_MQ2, B_MQ2);
if (ppm_smoke < 300)
printf("\r\n烟雾浓度:低于检测范围!");
else if (ppm_smoke > 10000)
printf("\r\n烟雾浓度:高于检测范围!");
else
printf("\r\n烟雾浓度:%.2f ppm!", ppm_smoke);
// 结尾分隔线
printf("\r\n/*");
for (uint32_t i = 0; i < 80; i++) printf("*");
printf("*/\r\n");
}
}
/**
* @brief 重定向 printf 的输出到串口
* @param ch 要发送的字符
* @param f 文件指针(标准库要求的参数,一般不使用)
* @retval 返回发送的字符
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
/* USER CODE END 4 */
程序现象

使用普通打火机, 轻按(不点火) 将丁烷朝烟雾或空气质量传感器释放, 可以检测到空气质量和烟雾浓度达到检测范围, 并显示 ppm 数值.