S12SD紫外线传感器模块在立创开发板上的ADC驱动与数据采集实战
最近在做一个户外环境监测的小项目,需要用到紫外线传感器。选来选去,发现S12SD这个模块性价比挺高,而且输出是模拟电压信号,可以直接用单片机的ADC来读取,非常适合咱们嵌入式开发者自己折腾。不过网上关于它的具体驱动资料不多,尤其是和具体开发板对接的教程更少。
今天我就以立创的开发板为例,手把手带大家走一遍完整的流程:从硬件怎么接线,到软件怎么配置ADC,再到最后怎么把ADC读到的数字转换成我们能看懂的紫外线强度等级。整个过程我会把原理讲清楚,代码一行行解释,顺便分享几个调试时容易踩的坑。如果你也在用或者打算用S12SD模块,这篇教程应该能帮你省下不少时间。
1. 认识S12SD紫外线传感器模块
在动手写代码之前,咱们先得搞清楚手头的“兵器”到底是个啥。S12SD模块的核心是一个对紫外线特别敏感的光电二极管,它能把接收到的紫外线强度转换成微弱的电流信号。
这个电流信号太小了,单片机根本“听”不见。所以模块内部用了一颗LM358运算放大器,把这个微弱的信号放大,变成一个单片机ADC引脚能轻松读取的电压信号。模块上的所有元器件都用了1%精度的电阻电容,保证了输出信号的稳定和准确。
它的用途很广,比如做紫外线测试仪、带紫外线检测的户外运动手表、手机上的紫外线指数提醒功能等等。
模块关键参数:
| 参数 | 数值 | 说明 |
|---|---|---|
| 工作电压 | 2.7V - 5V | 直接用开发板的3.3V或5V供电都行 |
| 工作电流 | 约 1mA | 非常省电,适合电池供电设备 |
| 检测角度 | 130度 | 视野比较广,不用精确对准光源 |
| 温度系数 | 0.08%/°C | 温度变化对精度影响很小 |
| 检测波长 | 240nm ~ 370nm | 覆盖UVA和UVB波段,正是我们需要监测的 |
| 输出方式 | 模拟电压 (ADC) | 输出信号和紫外线强度成正比 |
| 引脚数量 | 3 Pin (VCC, GND, SIG) | 接线非常简单 |
注意:模块检测的是紫外线强度,输出的是一个0到VCC之间的模拟电压。紫外线越强,输出电压越高。我们的任务就是用单片机的ADC把这个电压值“读”出来。
2. 硬件连接与开发板引脚选择
硬件连接是第一步,也是最简单的一步。S12SD模块就三根线,千万别接错了。
接线对照表:
| 传感器模块引脚 | 立创开发板引脚 | 说明 |
|---|---|---|
| VCC | 3V3 | 接3.3V电源,模块工作电压范围是2.7-5V,接3.3V最稳妥。 |
| GND | GND | 接地,和开发板共地。 |
| SIG | P000 | 这是关键!模块的信号输出线,必须接到开发板带ADC功能的引脚上。我选择的是P000引脚,它对应着ADC通道0。 |
为什么选P000?因为立创开发板的图形化配置工具里,ADC通道0默认就是映射到P000引脚的,用起来最方便。当然,你也可以根据自己项目的布线情况,选择其他支持ADC的引脚,比如P001、P002等,但记得在软件配置里也要相应修改。
接好线之后,硬件部分就搞定了。接下来进入重头戏——软件配置和驱动编写。
3. 软件工程配置(使用立创图形化工具)
立创的开发环境提供了图形化配置工具,能帮我们快速完成外设的初始化,非常方便。这里咱们主要配置ADC模块。
- 打开图形化界面:在你的工程里,找到并打开FSP(Flexible Software Package)配置界面。
- 配置ADC模块:
- 在“Stacks”标签页下,点击“New Stack” -> “Analog” -> “ADC (r_adc)”。这会为工程添加一个ADC驱动栈。
- 在ADC的属性窗口中,进行关键配置:
- Mode(模式):选择
Scan Mode。因为我们要持续扫描读取一个通道的值。 - Resolution(分辨率):选择
12 Bits。这样ADC值范围是0-4095,精度足够。 - Alignment(对齐方式):选择
Right Aligned,数据右对齐方便处理。 - 其他参数如时钟源、采样时间等,可以先保持默认。
- Mode(模式):选择
- 配置ADC通道:
- 在“Pins”标签页下,找到
P000引脚。 - 将其功能
Mode设置为Analog。这一步很重要,将引脚切换到模拟输入模式,才能正确读取电压。 - 在下面的“Analog”设置中,将它分配到我们刚刚创建的ADC单元(比如
ADC0)的通道0(Channel 0)。
- 在“Pins”标签页下,找到
- 保存并生成代码:
- 按
Ctrl + S保存所有配置。 - 点击右上角的
Generate Project Content按钮。工具会自动根据你的配置,生成底层硬件初始化的代码(在hal_data.c和hal_data.h中)。这样我们就不用自己手动写一大堆寄存器配置了。
- 按
图形化配置完成后,基础的ADC外设初始化代码就有了。接下来,我们需要自己编写应用层的驱动函数,来读取数据并处理。
4. 编写紫外线传感器驱动代码
为了代码结构清晰,我们新建两个文件:bsp_ultraviolet.c和bsp_ultraviolet.h,把S12SD相关的操作都放在里面。记得把bsp_ultraviolet.h的头文件路径添加到你的编译器包含目录中。
4.1 头文件定义 (bsp_ultraviolet.h)
头文件里主要做三件事:包含必要的库、定义一些好用的宏、声明我们要用到的函数。
#ifndef BSP_CODE_BSP_ULTRAVIOLET_H_ #define BSP_CODE_BSP_ULTRAVIOLET_H_ #include "hal_data.h" #include <stdio.h> // 延时宏定义,方便使用 #ifndef delay_ms #define delay_ms(x) R_BSP_SoftwareDelay(x, BSP_DELAY_UNITS_MILLISECONDS) #endif #ifndef delay_us #define delay_us(x) R_BSP_SoftwareDelay(x, BSP_DELAY_UNITS_MICROSECONDS) #endif // 类型定义简写 #ifndef u8 #define u8 uint8_t #endif #ifndef u16 #define u16 uint16_t #endif // 定义我们使用的ADC通道(对应P000) #define BSP_ADC_CHANNEL ADC_CHANNEL_0 // 定义采样次数,用于求平均值滤波 #define SAMPLES 5 // 函数声明 void S12SD_UV_GPIO_Init(void); // 初始化函数 int Get_ADC_Value(void); // 获取ADC原始值(已平均) char Get_Ultraviolet_Intensity(uint16_t value); // 将ADC值转换为紫外线强度等级 #endif /* BSP_CODE_BSP_ULTRAVIOLET_H_ */4.2 初始化函数 (S12SD_UV_GPIO_Init)
这个函数负责打开和配置ADC硬件。它调用了FSP库提供的API。
void S12SD_UV_GPIO_Init(void) { fsp_err_t err = FSP_SUCCESS; // 用于检查函数执行状态 // 1. 打开ADC设备。g_adc0_ctrl和g_adc0_cfg是图形化工具自动生成的变量。 err = R_ADC_Open(&g_adc0_ctrl, &g_adc0_cfg); if (FSP_SUCCESS != err) { printf("ADC Open Failed! \n"); return; // 初始化失败,直接返回 } // 2. 配置ADC扫描。这里告诉ADC我们要扫描哪个通道。 err = R_ADC_ScanCfg(&g_adc0_ctrl, &g_adc0_channel_cfg); if (FSP_SUCCESS != err) { printf("ADC Scan Config Failed! \n"); R_ADC_Close(&g_adc0_ctrl); // 如果配置失败,记得关闭之前打开的ADC return; } // 初始化成功,ADC就准备好了 }4.3 核心数据读取与处理函数
这里包含了三个层层调用的函数,是驱动的心脏部分。
首先,是单次读取ADC的底层函数ADC_GET:
// ADC扫描完成标志位,在回调函数里设置 volatile bool adc_flag = false; // ADC扫描完成的中断回调函数 void adc_callback(adc_callback_args_t *p_args) { FSP_PARAMETER_NOT_USED(p_args); // 防止编译器警告 adc_flag = true; // 扫描完成,设置标志 } static int ADC_GET(void) { int gAdcResult = 0; int timeOut = 1000; // 超时计数器,防止卡死 fsp_err_t err = FSP_SUCCESS; // 1. 启动一次ADC扫描 err = R_ADC_ScanStart(&g_adc0_ctrl); if (FSP_SUCCESS != err) { printf("ADC扫描启动失败! \n"); return 9999; // 返回一个错误值 } // 2. 等待扫描完成。这里用循环查询标志位的方式。 while(!adc_flag && timeOut--) { delay_us(1); // 稍微延时一下,避免纯空转消耗CPU } // 3. 清除标志位,为下一次读取做准备 adc_flag = false; // 4. 检查是否超时 if(!timeOut) { printf("ADC_GET 超时!!!\r\n"); return 0; } // 5. 读取ADC转换结果 err = R_ADC_Read(&g_adc0_ctrl, BSP_ADC_CHANNEL, (uint16_t *)&gAdcResult); if (FSP_SUCCESS != err) { printf("ADC Read Failed!\n"); return 9999; } return gAdcResult; // 返回12位的ADC原始值(0-4095) }提示:这里用了“阻塞式+超时”的方式等待ADC完成。在实际产品中,如果对实时性要求高,可以考虑用中断方式,让ADC转换完成后主动通知CPU。
然后,是带滤波的平均值函数Get_ADC_Value:传感器读数可能会有微小波动,连续采样几次求平均,可以让数据更稳定。
int Get_ADC_Value(void) { int Data = 0; int i; // 循环采样 SAMPLES 次(前面定义为5次) for(i = 0; i < SAMPLES; i++) { Data += ADC_GET(); // 累加每次的采样值 delay_ms(5); // 每次采样间隔5ms } Data = Data / SAMPLES; // 计算平均值 return Data; }最后,是最关键的映射函数Get_Ultraviolet_Intensity:这个函数把ADC原始值(0-4095)映射到0-11级的紫外线强度等级。这里的阈值是根据模块特性实测出来的,是代码的核心逻辑。
char Get_Ultraviolet_Intensity(uint16_t value) { char ret = 0; // 根据ADC值,判断所处的紫外线强度等级区间 if( value < 227 ) //紫外线强度0级 { ret = 0; } if( value >= 227 && value < 318 ) //紫外线强度1级 { ret = 1; } if( value >= 318 && value < 408 ) //紫外线强度2级 { ret = 2; } if( value >= 408 && value < 503 ) //紫外线强度3级 { ret = 3; } if( value >= 503 && value < 606 ) //紫外线强度4级 { ret = 4; } if( value >= 606 && value < 696 ) //紫外线强度5级 { ret = 5; } if( value >= 696 && value < 795 ) //紫外线强度6级 { ret = 6; } if( value >= 795 && value < 881 ) //紫外线强度7级 { ret = 7; } if( value >= 881 && value < 976 ) //紫外线强度8级 { ret = 8; } if( value >= 976 && value < 1079 ) //紫外线强度9级 { ret = 9; } if( value >= 1079 && value < 1170 ) //紫外线强度10级 { ret = 10; } if( value >= 1170 ) //紫外线强度11级(最高) { ret = 11; } return ret; // 返回强度等级 0-11 }重要:这些阈值(227, 318, 408...)是特定于S12SD模块在3.3V供电下的表现。如果你的供电电压不同,或者模块批次有差异,这个映射关系可能需要重新校准。校准方法很简单:在已知紫外线强度(用专业仪器)下,读取对应的ADC值即可。
5. 在主程序中调用与测试
驱动函数写好了,最后就是在主函数里调用它们。通常我们会在app.c或main.c的某个主循环函数里操作。
#include "app.h" #include "stdio.h" #include "bsp_uart.h" // 串口调试用 #include "bsp_ultraviolet.h" // 我们刚写的驱动 void Run(void) // 假设这是你的主应用函数 { // 1. 初始化调试串口,方便打印数据 UART0_Debug_Init(); printf("\r\n=== S12SD紫外线传感器测试程序 ===\r\n"); // 2. 初始化紫外线传感器(实质是初始化ADC) S12SD_UV_GPIO_Init(); printf("ADC通道0 (P000) 已用于检测紫外线强度\r\n"); // 3. 主循环,持续读取并打印 while(1) { // 获取ADC原始值(已平均) int adc_raw_value = Get_ADC_Value(); // 将原始值转换为紫外线强度等级 char uv_level = Get_Ultraviolet_Intensity(adc_raw_value); // 通过串口打印结果 printf("ADC原始值 = [ %d ]\r\n", adc_raw_value); printf("紫外线强度等级 = [ %d ]\r\n", uv_level); // 延时500ms再读下一次 delay_ms(500); } }把代码编译下载到开发板,打开串口调试助手(波特率根据你的UART0_Debug_Init配置而定),你应该能看到类似这样的输出:
ADC原始值 = [ 450 ] 紫外线强度等级 = [ 3 ]把传感器拿到阳光下和阴影处,数值应该会有明显变化。数值越大,等级越高,代表紫外线越强。
调试小贴士:
- 如果读到的值一直是0或4095:检查硬件接线,尤其是SIG线是否接到了正确的ADC引脚,以及图形化配置中该引脚是否已设置为模拟模式。
- 如果数值跳动很厉害:可以尝试增加
SAMPLES的平均次数(比如改成10),或者适当增加delay_ms(5)的延时时间。 - 关于等级划分:0-11级的划分是模块资料提供的。你可以根据这个等级,在你的项目中触发不同的动作,比如等级大于5时让蜂鸣器报警,或者在屏幕上显示“强紫外线,注意防护”。