基于GD32E230的MS1100甲醛传感器驱动移植与室内空气质量监测实战最近在做一个智能家居的小项目需要检测室内的甲醛浓度。找了一圈传感器发现MS1100这款VOC气体传感器性价比很高价格便宜灵敏度也不错特别适合咱们这种DIY项目或者小家电产品。今天我就来手把手教大家怎么在立创的GD32E230C8T6开发板上把MS1100传感器驱动起来实现一个简单的空气质量监测节点。整个过程我会拆解成几个步骤先带你认识一下MS1100传感器然后讲解GD32E230的ADC和GPIO怎么配置接着把驱动代码移植到你的工程里最后教你如何采集数据并转换成我们能看懂的电压值。即使你是嵌入式新手跟着做下来也能搞定。1. 认识我们的“鼻子”MS1100传感器在写代码之前咱们得先了解要驱动的对象。MS1100是一个半导体式的气体传感器模块常被做成CJMCU-1100这种小模块。它的主要任务是检测空气中的挥发性有机化合物VOC比如甲醛、甲苯、苯这些装修后常见的“坏家伙”。这个模块有几个关键特点咱们得记一下检测对象甲醛、甲苯、苯等多种VOC气体。灵敏度很高能检测到0.1ppm百万分之一以上的气体浓度。输出信号它有两路输出这是核心AOUT模拟输出这是一个电压信号电压值会随着检测到的气体浓度变化。浓度越高电压一般也越高。我们需要用单片机的ADC模数转换器来读取这个电压。DOUT数字输出这是一个简单的数字信号只有0或1低电平或高电平。模块上有个可调电阻设定一个阈值电压。当AOUT的电压超过这个阈值时DOUT就输出高电平通常伴随一个LED灯亮起告诉你“气体超标啦”反之则输出低电平。工作电压5V。注意GD32E230的IO口电压一般是3.3V但模块输出AOUT的电压范围是在其工作电压下的我们读取时按比例计算即可。预热传感器通电后需要3-5分钟预热读数才会稳定。刚上电就测数据是不准的。简单来说AOUT给我们一个连续的“浓度计”而DOUT则是一个简单的“超标报警器”。2. 硬件连接与引脚规划咱们用的是立创GD32E230C8T6开发板传感器模块就4个引脚VCC、GND、AOUT、DOUT。连接非常简单VCC- 开发板的5V引脚。GND- 开发板的GND。AOUT- 开发板的PA1引脚。为什么是PA1因为GD32E230的ADC通道1对应着PA1引脚这样我们才能用ADC去读取模拟电压。DOUT- 开发板的PB1引脚。这里选PB1是随意的任何一个具有普通GPIO输入功能的引脚都可以比如PC13、PB0等等看你的板子哪个方便。为了方便后续编程我们在代码里用宏定义把这些引脚关系固定下来。后面写代码时就直接用GPIO_MS1100_AO这样的名字而不是具体的GPIO_PIN_1这样程序更清晰以后换引脚也只需改一个地方。3. 驱动代码移植与解析接下来就是核心部分了——写代码。咱们分两个文件bsp_ms1100.h头文件放宏定义和函数声明和bsp_ms1100.c源文件放函数实现。3.1 头文件定义 (bsp_ms1100.h)头文件主要是做“预告”和“命名”。#ifndef _BSP_MS1100_H_ #define _BSP_MS1100_H_ #include gd32e23x.h // 包含GD32E230的固件库头文件 // 1. 定义AO引脚ADC引脚相关的宏 #define RCU_MS1100_AO RCU_GPIOA // 时钟PA口属于GPIOA #define PORT_MS1100_AO GPIOA // 端口 #define GPIO_MS1100_AO GPIO_PIN_1 // 具体引脚PA1 // 2. 定义DO引脚数字输入引脚相关的宏 #define RCU_MS1100_DO RCU_GPIOB // 时钟PB口属于GPIOB #define PORT_MS1100_DO GPIOB // 端口 #define GPIO_MS1100_DO GPIO_PIN_1 // 具体引脚PB1 // 3. 定义ADC相关的宏 #define RCU_MS1100_ADC RCU_ADC // ADC时钟 #define PORT_MS1100_ADC ADC // ADC外设 #define CHANNEL_MS1100_ADC ADC_CHANNEL_1 // 使用ADC通道1对应PA1 // 采样通道数我们只用了一个通道CH1 #define CHANNEL_NUM 1 // 4. 定义一个读取DO引脚状态的快捷方式 // gpio_input_bit_get是库函数用于读取指定引脚的电平 #define MS1100_DO gpio_input_bit_get(PORT_MS1100_DO, GPIO_MS1100_DO) // 5. 声明三个对外公开的函数 void MS1100_GPIO_Init(void); // 初始化函数 unsigned int Get_ADC_Value(unsigned int num); // 读取AO的ADC值 unsigned char Get_DO_Num(void); // 读取DO的状态 #endif3.2 初始化函数详解 (bsp_ms1100.c)初始化函数MS1100_GPIO_Init()是最复杂的一步它要完成GPIO和ADC的配置。咱们一步步拆解来看。#include bsp_ms1100.h #include systick.h // 用于延时函数 #include bsp_usart.h // 用于串口打印调试信息 #include stdio.h void MS1100_GPIO_Init(void) { /* 第一步打开时钟开关 */ // 单片机任何外设要工作必须先给它供“电”时钟 rcu_periph_clock_enable(RCU_MS1100_AO); // 使能GPIOA时钟PA1 rcu_periph_clock_enable(RCU_MS1100_DO); // 使能GPIOB时钟PB1 rcu_periph_clock_enable(RCU_MS1100_ADC); // 使能ADC时钟 // 配置ADC时钟源和分频这里选择APB2时钟4分频。具体频率需根据系统主频计算保证ADC时钟不超过14MHz。 rcu_adc_clock_config(RCU_ADCCK_APB2_DIV4); /* 第二步配置GPIO引脚模式 */ // 配置DO引脚(PB1)为上拉输入模式。上拉是为了引脚悬空时有一个确定的高电平。 gpio_mode_set(PORT_MS1100_DO, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO_MS1100_DO); // 配置AO引脚(PA1)为模拟输入模式。这是ADC引脚必须设置的模式。 gpio_mode_set(PORT_MS1100_AO, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_MS1100_AO); /* 第三步配置ADC工作参数 */ // 使能连续转换模式ADC转换一次完成后自动开始下一次转换。 adc_special_function_config(ADC_CONTINUOUS_MODE, ENABLE); // 使能扫描模式虽然我们只有一个通道但开启扫描模式是标准流程。 adc_special_function_config(ADC_SCAN_MODE, ENABLE); // 设置ADC分辨率为12位。12位精度意味着ADC值范围是0~4095。 adc_resolution_config(ADC_RESOLUTION_12B); // 配置ADC触发源为软件触发。意思是需要我们用代码命令ADC开始转换。 adc_external_trigger_source_config(ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_NONE); // 禁用外部触发只使用软件触发。 adc_external_trigger_config(ADC_REGULAR_CHANNEL, ENABLE); // 设置数据对齐方式为右对齐。这样读取的数值就是正常的0-4095。 adc_data_alignment_config(ADC_DATAALIGN_RIGHT); // 配置规则组通道长度我们用了1个通道 adc_channel_length_config(ADC_REGULAR_CHANNEL, CHANNEL_NUM); // 配置规则组的第0个序列也是唯一一个为通道1PA1采样时间设为13.5个周期 adc_regular_channel_config(0, CHANNEL_MS1100_ADC, ADC_SAMPLETIME_13POINT5); /* 第四步使能ADC并校准 */ adc_enable(); // 使能ADC外设 delay_1ms(1); // 稍作延时等待ADC稳定 adc_calibration_enable(); // 执行ADC自校准消除内部误差这一步很重要 adc_software_trigger_enable(ADC_REGULAR_CHANNEL); // 启动软件触发开始转换 }注意ADC采样时间ADC_SAMPLETIME_13POINT5需要根据信号源阻抗调整。传感器模块输出阻抗不高13.5个周期通常足够。如果发现读数跳动大可以适当增加采样时间。3.3 数据读取函数初始化完成后我们就能读取数据了。这里有两个函数一个读模拟量一个读数字量。// 函数Get_ADC_Value // 作用读取AO引脚的ADC值并进行多次采样取平均提高稳定性。 // 参数num - 平均采样的次数 // 返回平均后的ADC值0~4095 unsigned int Get_ADC_Value(unsigned int num) { unsigned int Data 0; int i 0; for(i 0; i num; i) { // adc_regular_data_read() 读取规则组数据寄存器的值 Data adc_regular_data_read(); delay_1ms(1); // 每次读取间隔1ms避免过于密集 } Data Data / num; // 计算平均值 return Data; } // 函数Get_DO_Num // 作用读取DO引脚的数字状态判断是否超过阈值。 // 返回1 - 气体浓度正常DOUT为高电平0 - 气体浓度超标DOUT为低电平 // 注意模块上可调电阻可以改变这个判断的阈值。 unsigned char Get_DO_Num(void) { if(MS1100_DO 1) // 使用头文件里定义的宏读取PB1电平 { return 1; // 正常 } else { return 0; // 超标 } }4. 在主函数中测试驱动写好了最后就是在主函数里调用它们看看效果。我们通过串口把采集到的电压值打印出来。#include gd32e23x.h #include systick.h #include bsp_usart.h #include stdio.h #include bsp_ms1100.h // 包含我们刚写的驱动头文件 int main(void) { float voltage 0; systick_config(); // 初始化系统滴答定时器用于延时函数 MS1100_GPIO_Init(); // 初始化MS1100传感器包括ADC usart_gpio_config(115200U); // 初始化串口波特率115200用于打印数据 printf(MS1100 Sensor Test Start!\r\n); delay_1ms(5000); // 上电后等待5秒让传感器充分预热 while(1) { // 1. 采集30次ADC值并取平均然后转换为电压值 // 公式电压 (ADC值 / 4095) * 3.3V (GD32的工作电压) voltage (Get_ADC_Value(30) / 4095.0) * 3.3; printf(AO Voltage %.2f V, , voltage); // 2. 读取数字输出状态 if(Get_DO_Num() 1) { printf(DO State: Normal\r\n); } else { printf(DO State: Exceeded!!\r\n); } delay_1ms(1000); // 每隔1秒采集一次 } }把代码编译下载到开发板打开串口助手比如Putty、XCOM设置好波特率115200。你就能看到每秒输出一行数据显示当前的电压值和超标状态。验证方法对着MS1100传感器轻轻吹一口气人体呼出的气体含有CO2和VOC观察串口输出的电压值应该会有一个明显的上升。同时模块上的LED灯可能会亮起DOUT输出低电平打印出“Exceeded!!”的提示。5. 从电压到浓度下一步怎么做现在我们已经能稳定地读取传感器输出的电压了。你可能会问“这个电压值到底对应多少ppm的甲醛啊”这是一个关键问题。MS1100的数据手册通常不会给出一个精确的、通用的电压-浓度公式因为半导体气体传感器的读数受温度、湿度、其他干扰气体影响很大。在实际项目中我们通常需要校准。一个常见的做法是建立基准在已知的“清洁空气”或零气环境中记录传感器的输出电压V0。获取灵敏度曲线查阅传感器手册或相关资料找到其灵敏度特性曲线。通常电阻比Rs/R0或电压比与气体浓度存在某种对数或幂函数关系。其中Rs是传感器在目标气体中的电阻正比于电压R0是清洁空气中的电阻。应用公式根据曲线拟合出一个公式。例如对于许多VOC传感器浓度C(ppm) 与电压比V/V0的关系近似为C a * (V/V0)^b其中a和b是常数需要通过实验标定。温湿度补偿更精确的系统还需要接入温湿度传感器如SHT30根据温湿度对读数进行补偿。对于要求不高的定性或阈值报警应用比如判断“空气变差了”直接使用我们上面得到的电压值voltage或者ADC原始值进行判断就已经非常有效了。你可以设定一个电压阈值当超过该阈值时就触发报警或启动风扇。好了整个从硬件连接到软件驱动、从数据采集到初步应用的流程就讲完了。代码我都验证过你可以直接拿去用在你的立创GD32E230开发板上。遇到问题多检查一下接线和引脚定义记得给传感器足够的预热时间。祝你DIY顺利
基于GD32E230的MS1100甲醛传感器驱动移植与室内空气质量监测实战
基于GD32E230的MS1100甲醛传感器驱动移植与室内空气质量监测实战最近在做一个智能家居的小项目需要检测室内的甲醛浓度。找了一圈传感器发现MS1100这款VOC气体传感器性价比很高价格便宜灵敏度也不错特别适合咱们这种DIY项目或者小家电产品。今天我就来手把手教大家怎么在立创的GD32E230C8T6开发板上把MS1100传感器驱动起来实现一个简单的空气质量监测节点。整个过程我会拆解成几个步骤先带你认识一下MS1100传感器然后讲解GD32E230的ADC和GPIO怎么配置接着把驱动代码移植到你的工程里最后教你如何采集数据并转换成我们能看懂的电压值。即使你是嵌入式新手跟着做下来也能搞定。1. 认识我们的“鼻子”MS1100传感器在写代码之前咱们得先了解要驱动的对象。MS1100是一个半导体式的气体传感器模块常被做成CJMCU-1100这种小模块。它的主要任务是检测空气中的挥发性有机化合物VOC比如甲醛、甲苯、苯这些装修后常见的“坏家伙”。这个模块有几个关键特点咱们得记一下检测对象甲醛、甲苯、苯等多种VOC气体。灵敏度很高能检测到0.1ppm百万分之一以上的气体浓度。输出信号它有两路输出这是核心AOUT模拟输出这是一个电压信号电压值会随着检测到的气体浓度变化。浓度越高电压一般也越高。我们需要用单片机的ADC模数转换器来读取这个电压。DOUT数字输出这是一个简单的数字信号只有0或1低电平或高电平。模块上有个可调电阻设定一个阈值电压。当AOUT的电压超过这个阈值时DOUT就输出高电平通常伴随一个LED灯亮起告诉你“气体超标啦”反之则输出低电平。工作电压5V。注意GD32E230的IO口电压一般是3.3V但模块输出AOUT的电压范围是在其工作电压下的我们读取时按比例计算即可。预热传感器通电后需要3-5分钟预热读数才会稳定。刚上电就测数据是不准的。简单来说AOUT给我们一个连续的“浓度计”而DOUT则是一个简单的“超标报警器”。2. 硬件连接与引脚规划咱们用的是立创GD32E230C8T6开发板传感器模块就4个引脚VCC、GND、AOUT、DOUT。连接非常简单VCC- 开发板的5V引脚。GND- 开发板的GND。AOUT- 开发板的PA1引脚。为什么是PA1因为GD32E230的ADC通道1对应着PA1引脚这样我们才能用ADC去读取模拟电压。DOUT- 开发板的PB1引脚。这里选PB1是随意的任何一个具有普通GPIO输入功能的引脚都可以比如PC13、PB0等等看你的板子哪个方便。为了方便后续编程我们在代码里用宏定义把这些引脚关系固定下来。后面写代码时就直接用GPIO_MS1100_AO这样的名字而不是具体的GPIO_PIN_1这样程序更清晰以后换引脚也只需改一个地方。3. 驱动代码移植与解析接下来就是核心部分了——写代码。咱们分两个文件bsp_ms1100.h头文件放宏定义和函数声明和bsp_ms1100.c源文件放函数实现。3.1 头文件定义 (bsp_ms1100.h)头文件主要是做“预告”和“命名”。#ifndef _BSP_MS1100_H_ #define _BSP_MS1100_H_ #include gd32e23x.h // 包含GD32E230的固件库头文件 // 1. 定义AO引脚ADC引脚相关的宏 #define RCU_MS1100_AO RCU_GPIOA // 时钟PA口属于GPIOA #define PORT_MS1100_AO GPIOA // 端口 #define GPIO_MS1100_AO GPIO_PIN_1 // 具体引脚PA1 // 2. 定义DO引脚数字输入引脚相关的宏 #define RCU_MS1100_DO RCU_GPIOB // 时钟PB口属于GPIOB #define PORT_MS1100_DO GPIOB // 端口 #define GPIO_MS1100_DO GPIO_PIN_1 // 具体引脚PB1 // 3. 定义ADC相关的宏 #define RCU_MS1100_ADC RCU_ADC // ADC时钟 #define PORT_MS1100_ADC ADC // ADC外设 #define CHANNEL_MS1100_ADC ADC_CHANNEL_1 // 使用ADC通道1对应PA1 // 采样通道数我们只用了一个通道CH1 #define CHANNEL_NUM 1 // 4. 定义一个读取DO引脚状态的快捷方式 // gpio_input_bit_get是库函数用于读取指定引脚的电平 #define MS1100_DO gpio_input_bit_get(PORT_MS1100_DO, GPIO_MS1100_DO) // 5. 声明三个对外公开的函数 void MS1100_GPIO_Init(void); // 初始化函数 unsigned int Get_ADC_Value(unsigned int num); // 读取AO的ADC值 unsigned char Get_DO_Num(void); // 读取DO的状态 #endif3.2 初始化函数详解 (bsp_ms1100.c)初始化函数MS1100_GPIO_Init()是最复杂的一步它要完成GPIO和ADC的配置。咱们一步步拆解来看。#include bsp_ms1100.h #include systick.h // 用于延时函数 #include bsp_usart.h // 用于串口打印调试信息 #include stdio.h void MS1100_GPIO_Init(void) { /* 第一步打开时钟开关 */ // 单片机任何外设要工作必须先给它供“电”时钟 rcu_periph_clock_enable(RCU_MS1100_AO); // 使能GPIOA时钟PA1 rcu_periph_clock_enable(RCU_MS1100_DO); // 使能GPIOB时钟PB1 rcu_periph_clock_enable(RCU_MS1100_ADC); // 使能ADC时钟 // 配置ADC时钟源和分频这里选择APB2时钟4分频。具体频率需根据系统主频计算保证ADC时钟不超过14MHz。 rcu_adc_clock_config(RCU_ADCCK_APB2_DIV4); /* 第二步配置GPIO引脚模式 */ // 配置DO引脚(PB1)为上拉输入模式。上拉是为了引脚悬空时有一个确定的高电平。 gpio_mode_set(PORT_MS1100_DO, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO_MS1100_DO); // 配置AO引脚(PA1)为模拟输入模式。这是ADC引脚必须设置的模式。 gpio_mode_set(PORT_MS1100_AO, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_MS1100_AO); /* 第三步配置ADC工作参数 */ // 使能连续转换模式ADC转换一次完成后自动开始下一次转换。 adc_special_function_config(ADC_CONTINUOUS_MODE, ENABLE); // 使能扫描模式虽然我们只有一个通道但开启扫描模式是标准流程。 adc_special_function_config(ADC_SCAN_MODE, ENABLE); // 设置ADC分辨率为12位。12位精度意味着ADC值范围是0~4095。 adc_resolution_config(ADC_RESOLUTION_12B); // 配置ADC触发源为软件触发。意思是需要我们用代码命令ADC开始转换。 adc_external_trigger_source_config(ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_NONE); // 禁用外部触发只使用软件触发。 adc_external_trigger_config(ADC_REGULAR_CHANNEL, ENABLE); // 设置数据对齐方式为右对齐。这样读取的数值就是正常的0-4095。 adc_data_alignment_config(ADC_DATAALIGN_RIGHT); // 配置规则组通道长度我们用了1个通道 adc_channel_length_config(ADC_REGULAR_CHANNEL, CHANNEL_NUM); // 配置规则组的第0个序列也是唯一一个为通道1PA1采样时间设为13.5个周期 adc_regular_channel_config(0, CHANNEL_MS1100_ADC, ADC_SAMPLETIME_13POINT5); /* 第四步使能ADC并校准 */ adc_enable(); // 使能ADC外设 delay_1ms(1); // 稍作延时等待ADC稳定 adc_calibration_enable(); // 执行ADC自校准消除内部误差这一步很重要 adc_software_trigger_enable(ADC_REGULAR_CHANNEL); // 启动软件触发开始转换 }注意ADC采样时间ADC_SAMPLETIME_13POINT5需要根据信号源阻抗调整。传感器模块输出阻抗不高13.5个周期通常足够。如果发现读数跳动大可以适当增加采样时间。3.3 数据读取函数初始化完成后我们就能读取数据了。这里有两个函数一个读模拟量一个读数字量。// 函数Get_ADC_Value // 作用读取AO引脚的ADC值并进行多次采样取平均提高稳定性。 // 参数num - 平均采样的次数 // 返回平均后的ADC值0~4095 unsigned int Get_ADC_Value(unsigned int num) { unsigned int Data 0; int i 0; for(i 0; i num; i) { // adc_regular_data_read() 读取规则组数据寄存器的值 Data adc_regular_data_read(); delay_1ms(1); // 每次读取间隔1ms避免过于密集 } Data Data / num; // 计算平均值 return Data; } // 函数Get_DO_Num // 作用读取DO引脚的数字状态判断是否超过阈值。 // 返回1 - 气体浓度正常DOUT为高电平0 - 气体浓度超标DOUT为低电平 // 注意模块上可调电阻可以改变这个判断的阈值。 unsigned char Get_DO_Num(void) { if(MS1100_DO 1) // 使用头文件里定义的宏读取PB1电平 { return 1; // 正常 } else { return 0; // 超标 } }4. 在主函数中测试驱动写好了最后就是在主函数里调用它们看看效果。我们通过串口把采集到的电压值打印出来。#include gd32e23x.h #include systick.h #include bsp_usart.h #include stdio.h #include bsp_ms1100.h // 包含我们刚写的驱动头文件 int main(void) { float voltage 0; systick_config(); // 初始化系统滴答定时器用于延时函数 MS1100_GPIO_Init(); // 初始化MS1100传感器包括ADC usart_gpio_config(115200U); // 初始化串口波特率115200用于打印数据 printf(MS1100 Sensor Test Start!\r\n); delay_1ms(5000); // 上电后等待5秒让传感器充分预热 while(1) { // 1. 采集30次ADC值并取平均然后转换为电压值 // 公式电压 (ADC值 / 4095) * 3.3V (GD32的工作电压) voltage (Get_ADC_Value(30) / 4095.0) * 3.3; printf(AO Voltage %.2f V, , voltage); // 2. 读取数字输出状态 if(Get_DO_Num() 1) { printf(DO State: Normal\r\n); } else { printf(DO State: Exceeded!!\r\n); } delay_1ms(1000); // 每隔1秒采集一次 } }把代码编译下载到开发板打开串口助手比如Putty、XCOM设置好波特率115200。你就能看到每秒输出一行数据显示当前的电压值和超标状态。验证方法对着MS1100传感器轻轻吹一口气人体呼出的气体含有CO2和VOC观察串口输出的电压值应该会有一个明显的上升。同时模块上的LED灯可能会亮起DOUT输出低电平打印出“Exceeded!!”的提示。5. 从电压到浓度下一步怎么做现在我们已经能稳定地读取传感器输出的电压了。你可能会问“这个电压值到底对应多少ppm的甲醛啊”这是一个关键问题。MS1100的数据手册通常不会给出一个精确的、通用的电压-浓度公式因为半导体气体传感器的读数受温度、湿度、其他干扰气体影响很大。在实际项目中我们通常需要校准。一个常见的做法是建立基准在已知的“清洁空气”或零气环境中记录传感器的输出电压V0。获取灵敏度曲线查阅传感器手册或相关资料找到其灵敏度特性曲线。通常电阻比Rs/R0或电压比与气体浓度存在某种对数或幂函数关系。其中Rs是传感器在目标气体中的电阻正比于电压R0是清洁空气中的电阻。应用公式根据曲线拟合出一个公式。例如对于许多VOC传感器浓度C(ppm) 与电压比V/V0的关系近似为C a * (V/V0)^b其中a和b是常数需要通过实验标定。温湿度补偿更精确的系统还需要接入温湿度传感器如SHT30根据温湿度对读数进行补偿。对于要求不高的定性或阈值报警应用比如判断“空气变差了”直接使用我们上面得到的电压值voltage或者ADC原始值进行判断就已经非常有效了。你可以设定一个电压阈值当超过该阈值时就触发报警或启动风扇。好了整个从硬件连接到软件驱动、从数据采集到初步应用的流程就讲完了。代码我都验证过你可以直接拿去用在你的立创GD32E230开发板上。遇到问题多检查一下接线和引脚定义记得给传感器足够的预热时间。祝你DIY顺利