news 2026/8/27 1:35:26

梁山派GD32F470光敏电阻传感器驱动移植实战:ADC与DMA采集光照强度

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
梁山派GD32F470光敏电阻传感器驱动移植实战:ADC与DMA采集光照强度

梁山派GD32F470光敏电阻传感器驱动移植实战:ADC与DMA采集光照强度

最近在梁山派GD32F470开发板上做环境监测项目,需要检测光照强度,就用到了最常见的光敏电阻模块。很多刚开始接触嵌入式传感器开发的朋友,可能会觉得ADC采集、DMA传输这些概念有点复杂,配置起来也容易出错。今天我就结合这个光敏电阻模块,手把手带大家走一遍完整的驱动移植流程,从硬件原理分析到代码编写,最后实现光照强度的百分比显示。整个过程我会尽量讲得详细,把容易踩的坑也指出来,保证你跟着做一遍就能完全掌握。

1. 光敏电阻模块与硬件连接

咱们先来认识一下今天的主角——光敏电阻传感器模块。

1.1 模块工作原理

光敏电阻,简单来说就是一种“见光变怂”的电阻。它内部用的是硫化镉或硒化镉这类半导体材料,其核心特性是:光照越强,电阻值越小;完全黑暗时,电阻值非常大

这背后的原理叫做“内光电效应”。当有光线照射时,半导体材料内部会产生可以自由移动的电荷(载流子),这些电荷在外部电压的驱动下定向移动,就形成了电流。电荷多了,导电能力就强了,电阻自然就小了。

我们用的这个模块型号是5516,根据它的资料,在明亮环境下电阻大约在8-20KΩ,而在黑暗环境中,电阻可以高达1MΩ左右。

1.2 模块接口与引脚

这个模块非常小巧,尺寸是31.1mm x 14.1mm,采用4Pin(2.54mm间距)排针供电和通信。工作电压是3.3V-5V,和咱们的梁山派开发板(3.3V系统)完美匹配。

模块上有两个关键输出引脚,功能完全不同:

引脚标识类型功能说明
VCC电源接3.3V或5V
GND电源接地
AO模拟输出输出连续变化的电压信号,需要接MCU的ADC引脚
DO数字输出输出高低电平,需要接MCU的GPIO输入引脚

AO引脚是模拟量输出。它直接是光敏电阻和一个固定电阻(原理图里的R2和R3)分压后的电压点。光线变化导致光敏电阻阻值变化,分压点的电压也就连续变化。这个电压值被送到单片机的ADC引脚,转换成数字值,我们就能精确知道当前的光照强度。

DO引脚是数字量输出。它连接了一个电压比较器芯片(LM393)。比较器会持续比较AO引脚的电压和一个可调参考电压(通过电位器R4设置)。当AO电压高于参考电压(比如光线变暗),DO输出高电平;反之输出低电平。这个引脚适合做简单的“亮/暗”阈值判断,比如天黑自动开灯。

注意:对于需要量化光照强度的应用,我们必须使用AO引脚配合ADC进行采集。DO引脚只能做二值判断。

1.3 与梁山派开发板连接

我们的目标是采集AO引脚的模拟电压。首先得在梁山派GD32F470上找一个带ADC功能的引脚。

查阅GD32F470的数据手册(具体是GD32F450xx_Datasheet_Rev2.2.pdf第28页的引脚功能定义图),可以看到很多引脚都支持ADC。这里我选择使用PC1引脚,它复用了ADC0的第11输入通道。连接非常简单:

  1. 模块VCC-> 开发板3.3V
  2. 模块GND-> 开发板GND
  3. 模块AO-> 开发板PC1
  4. 模块DO-> 开发板任意GPIO输入引脚(例如PA1

2. 驱动程序设计:ADC与DMA配置

接下来是核心部分——编写驱动程序。为了让ADC采集不占用CPU时间,我们采用“ADC + DMA”的方案。ADC负责把电压变成数字,DMA负责悄无声息地把这个数字搬运到我们指定的内存数组中,CPU完全不用管。

2.1 头文件定义 (bsp_illume.h)

头文件里主要做宏定义,把引脚、外设通道等配置信息固定下来,这样程序看起来清晰,修改也方便。

#ifndef _BSP_ILLUME_H_ #define _BSP_ILLUME_H_ #include "gd32f4xx.h" // 时钟定义 #define RCU_ILLUME_GPIO_AO RCU_GPIOC // AO引脚(PC1)的GPIO时钟 #define RCU_ILLUME_GPIO_DO RCU_GPIOA // DO引脚(PA1)的GPIO时钟 #define RCU_ILLUME_ADC RCU_ADC0 // ADC0时钟 #define RCU_ILLUME_DMA RCU_DMA1 // DMA1时钟 // DMA与ADC外设定义 #define PORT_DMA DMA1 // 使用DMA1 #define CHANNEL_DMA DMA_CH0 // 使用DMA1的通道0 #define PORT_ADC ADC0 // 使用ADC0 #define CHANNEL_ADC ADC_CHANNEL_11 // 使用ADC0的第11通道(对应PC1) // GPIO引脚定义 #define PORT_ILLUME_AO GPIOC // AO引脚端口 #define GPIO_ILLUME_AO GPIO_PIN_1 // AO引脚号(PC1) #define PORT_ILLUME_DO GPIOA // DO引脚端口 #define GPIO_ILLUME_DO GPIO_PIN_1 // DO引脚号(PA1) // 读取DO引脚电平的快捷宏 #define GET_DO_IN gpio_input_bit_get(PORT_ILLUME_DO, GPIO_ILLUME_DO) // 采样参数定义 #define SAMPLES 30 // 每个通道采样30次,用于后续求平均滤波 #define CHANNEL_NUM 1 // 总共采样1个ADC通道 // 声明DMA使用的全局缓冲区数组 extern uint16_t gt_adc_val[SAMPLES][CHANNEL_NUM]; // 函数声明 void Illume_GPIO_Init(void); unsigned int Get_Adc_Dma_Value(char CHx); unsigned int Get_illume_Percentage_value(void); char Get_DO_In(void); #endif

2.2 初始化函数详解 (bsp_illume.c)

初始化函数Illume_GPIO_Init是整个驱动的核心,它完成了从时钟使能到启动ADC转换的所有配置。咱们一步步拆解来看。

第一步:开启所有需要的时钟就像用电器要先通电一样,使用任何外设前都要先打开它的时钟。

rcu_periph_clock_enable(RCU_ILLUME_GPIO_AO); // 打开GPIOC时钟 rcu_periph_clock_enable(RCU_ILLUME_GPIO_DO); // 打开GPIOA时钟 rcu_periph_clock_enable(RCU_ILLUME_ADC); // 打开ADC0时钟 rcu_periph_clock_enable(RCU_ILLUME_DMA); // 打开DMA1时钟

第二步:配置ADC时钟和GPIO模式ADC工作需要基准时钟,这里设置为APB2时钟(PCLK2)的4分频。GPIO模式是关键,AO引脚必须设置为模拟输入模式。

adc_clock_config(ADC_ADCCK_PCLK2_DIV4); // 配置ADC时钟源 // DO引脚(PA1)配置为上拉/下拉无效的输入模式,用于读取高低电平 gpio_mode_set(PORT_ILLUME_DO, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_ILLUME_DO); // AO引脚(PC1)必须配置为模拟输入模式,这是ADC采集的正确姿势 gpio_mode_set(PORT_ILLUME_AO, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_ILLUME_AO);

第三步:配置ADC工作模式这里配置了ADC以独立、连续、扫描模式工作,数据右对齐,并开启了DMA请求。

adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT); // ADC独立模式 adc_special_function_config(PORT_ADC, ADC_CONTINUOUS_MODE, ENABLE); // 使能连续转换 adc_special_function_config(PORT_ADC, ADC_SCAN_MODE, ENABLE); // 使能扫描模式(多通道时才需) adc_data_alignment_config(PORT_ADC, ADC_DATAALIGN_RIGHT); // 数据右对齐(方便读取) adc_resolution_config(PORT_ADC, ADC_RESOLUTION_12B); // 12位分辨率(0-4095) // 设置规则组通道数量(我们只用1个通道)和具体通道参数 adc_channel_length_config(PORT_ADC, ADC_REGULAR_CHANNEL, CHANNEL_NUM); // 配置规则组第0个转换序列为ADC通道11,采样时间15个周期 adc_regular_channel_config(PORT_ADC, 0, CHANNEL_ADC, ADC_SAMPLETIME_15); // 禁用外部触发,使用软件触发启动转换 adc_external_trigger_config(PORT_ADC, ADC_REGULAR_CHANNEL, EXTERNAL_TRIGGER_DISABLE); // 使能每次规则组转换完成后产生DMA请求 adc_dma_request_after_last_enable(PORT_ADC); adc_dma_mode_enable(PORT_ADC); // 使能ADC的DMA模式

第四步:ADC校准ADC模块内部有微小误差,上电后进行一次校准可以大大提高采集精度,这是很重要但容易被忽略的一步。

adc_enable(PORT_ADC); // 先使能ADC delay_1ms(1); // 等待至少1ms让ADC稳定 adc_calibration_enable(PORT_ADC); // 开始自校准

第五步:配置DMA搬运工DMA的配置是重点,它决定了数据从哪里搬到哪里,怎么搬。

dma_deinit(PORT_DMA, CHANNEL_DMA); // 先清除DMA通道之前的配置 dma_single_data_parameter_struct dma_single_data_parameter; // 设置源地址:ADC的数据寄存器地址,数据从这里被搬出 dma_single_data_parameter.periph_addr = (uint32_t)(&ADC_RDATA(PORT_ADC)); dma_single_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 外设地址固定,不自增 // 设置目标地址:我们定义的全局数组 gt_adc_val 的首地址 dma_single_data_parameter.memory0_addr = (uint32_t)(gt_adc_val); dma_single_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 内存地址自增,存到数组下一个位置 // 设置传输参数 dma_single_data_parameter.periph_memory_width = DMA_PERIPH_WIDTH_16BIT; // 每次传输16位(ADC数据是12位,用16位变量存) dma_single_data_parameter.direction = DMA_PERIPH_TO_MEMORY; // 传输方向:从外设到内存 dma_single_data_parameter.number = SAMPLES * CHANNEL_NUM; // 总传输次数 = 30次 * 1通道 dma_single_data_parameter.priority = DMA_PRIORITY_HIGH; // 高优先级 // 初始化DMA通道 dma_single_data_mode_init(PORT_DMA, CHANNEL_DMA, &dma_single_data_parameter);

第六步:选择DMA通道映射并启动GD32的DMA通道需要和外设(这里是ADC0)绑定。

// 根据数据手册,ADC0对应DMA_SUBPERI0 dma_channel_subperipheral_select(PORT_DMA, CHANNEL_DMA, DMA_SUBPERI0); dma_circulation_enable(PORT_DMA, CHANNEL_DMA); // 使能循环模式,传输完自动从头开始 dma_channel_enable(PORT_DMA, CHANNEL_DMA); // 使能DMA通道 // 最后,软件触发ADC开始转换。一旦开始,ADC会连续转换,DMA会连续搬运,完全自动运行。 adc_software_trigger_enable(PORT_ADC, ADC_REGULAR_CHANNEL);

初始化完成后,ADC就会以设定的速率持续采样PC1引脚的电压,并通过DMA自动将结果存放到gt_adc_val数组中,整个过程无需CPU干预。

2.3 数据读取与处理函数

数据已经在后台自动采集好了,我们只需要从数组中读取并处理。

获取ADC原始值并求平均为了滤除偶然干扰,我们采集了30个样本,求取平均值作为最终结果。

unsigned int Get_Adc_Dma_Value(char CHx) { unsigned char i = 0; unsigned int AdcValue = 0; // 累加 SAMPLES 次采样的数据 for(i=0; i< SAMPLES; i++) { AdcValue += gt_adc_val[i][CHx]; // CHx为通道索引,我们只有0通道 } // 求平均值 AdcValue = AdcValue / SAMPLES; return AdcValue; // 返回值范围:0 ~ 4095 }

将ADC值转换为光照百分比这是将物理量转换为直观数值的关键一步。注意,光敏电阻的特性是光照越强,电阻越小,分压后AO引脚电压越低,ADC值越小。所以我们需要用“1 - 比值”来反映正相关。

unsigned int Get_illume_Percentage_value(void) { int adc_max = 4095; // 12位ADC的最大值 int adc_new = 0; int Percentage_value = 0; adc_new = Get_Adc_Dma_Value(0); // 获取当前ADC平均值 // 核心转换公式:百分比 = ( 1 - (当前值 / 最大值) ) * 100 // 当 adc_new=0(最亮),百分比为100%;当 adc_new=4095(最暗),百分比为0% Percentage_value = ( 1 - ( (float)adc_new / adc_max ) ) * 100; return Percentage_value; // 返回0~100之间的整数 }

读取DO引脚状态这个函数很简单,就是读取连接DO引脚的GPIO电平。

char Get_DO_In(void) { if( GET_DO_IN == 1 ) // 使用头文件定义的宏读取引脚 { return 1; // 返回1表示光线低于阈值(可能过暗) } return 0; // 返回0表示光线高于阈值 }

3. 在main函数中测试验证

驱动写好了,最后就是在主函数里调用测试了。

#include "gd32f4xx.h" #include "systick.h" #include "bsp_usart.h" #include "bsp_illume.h" #include "stdio.h" int main(void) { // 系统初始化 nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 设置中断优先级分组 systick_config(); // 初始化系统滴答定时器,用于delay usart_gpio_config(115200U); // 初始化串口,用于打印数据 Illume_GPIO_Init(); // 初始化光敏电阻模块 printf("ADC+DMA Light Sensor Demo Start\r\n"); while(1) { // 打印ADC原始值(0-4095) printf("ADC Value: %d\r\n", Get_Adc_Dma_Value(0)); // 打印光照强度百分比(0%-100%) printf("Light Intensity: %d%%\r\n", Get_illume_Percentage_value()); // 打印DO引脚状态 printf("DO Pin State: %s\r\n", Get_DO_In() ? "Dark" : "Bright"); delay_1ms(1000); // 每隔1秒打印一次 } }

将代码编译下载到梁山派开发板,打开串口助手(波特率115200),你就能看到实时的光照数据输出了。用手遮住光敏电阻,或者用手电筒照它,观察ADC值和百分比的变化。当光线变化时,DO引脚的状态也会在某个阈值点发生翻转。

4. 常见问题与调试心得

  1. ADC采样值跳动大?

    • 原因:可能是电源噪声或信号干扰。
    • 解决:代码里已经做了软件平均滤波(采样30次求平均),能有效平滑数据。如果还不行,可以在AO引脚和GND之间加一个0.1uF的滤波电容。
  2. 百分比计算不准确?

    • 注意公式逻辑:我们的公式(1 - (adc/4095)) * 100是基于该模块电路(光敏电阻在上拉位置)的。如果你的模块电路不同(比如光敏电阻在下拉位置),公式可能要改成(adc/4095) * 100。一定要根据实际电路的分压关系来定。
  3. DMA数据不更新?

    • 检查顺序:务必确保初始化顺序是ADC校准 -> DMA配置 -> 启动DMA -> 软件触发ADC。顺序错了DMA可能不工作。
    • 检查缓冲区:确保gt_adc_val数组被正确定义为全局变量,且长度足够(SAMPLES * CHANNEL_NUM)。
  4. DO引脚阈值怎么调?

    • 模块上通常有一个蓝色的电位器(就是原理图里的R4),用螺丝刀旋转它,就可以改变LM393比较器的参考电压,从而改变光线触发阈值。调到合适位置,就能实现“天暗到一定程度才输出高电平”的效果。

好了,整个光敏电阻的ADC+DMA驱动移植过程就完成了。最关键的是理解“ADC负责转换,DMA负责搬运”这个协作流程。一旦掌握了这个模式,其他类似的传感器(比如土壤湿度、气体浓度等)的驱动开发,就都是类似的思路了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/14 17:00:36

March7thAssistant智能辅助:重新定义星穹铁道游戏体验

March7thAssistant智能辅助&#xff1a;重新定义星穹铁道游戏体验 【免费下载链接】March7thAssistant &#x1f389; 崩坏&#xff1a;星穹铁道全自动 Honkai Star Rail &#x1f389; 项目地址: https://gitcode.com/gh_mirrors/ma/March7thAssistant 在快节奏的现代生…

作者头像 李华
网站建设 2026/7/14 17:00:34

开源可部署的文生图利器:Meixiong Niannian画图引擎GitHub源码编译部署指南

开源可部署的文生图利器&#xff1a;Meixiong Niannian画图引擎GitHub源码编译部署指南 想自己搭建一个速度快、效果好、还不怎么吃显存的AI画图工具吗&#xff1f;今天给大家介绍一个宝藏项目——Meixiong Niannian画图引擎。这是一个专门为个人电脑GPU设计的轻量化文生图系统…

作者头像 李华
网站建设 2026/7/14 17:00:38

FLUX小红书极致真实V2在Linux系统下的性能调优指南

FLUX小红书极致真实V2在Linux系统下的性能调优指南 1. 引言 如果你正在Linux系统上运行FLUX小红书极致真实V2模型&#xff0c;可能会遇到这样的困扰&#xff1a;生成一张高质量图片需要等待很长时间&#xff0c;或者同时处理多个任务时系统变得异常卡顿。这其实不是模型本身的…

作者头像 李华
网站建设 2026/7/14 17:00:38

DEIM实战:基于Transformer的自定义SAR目标检测模型调优与部署

1. 从零开始&#xff1a;为什么选择DEIM来处理我的SAR数据&#xff1f; 大家好&#xff0c;我是老张&#xff0c;在AI和遥感图像处理这块摸爬滚打了十来年。最近几年&#xff0c;Transformer在视觉领域火得一塌糊涂&#xff0c;从ViT到DETR&#xff0c;各种模型层出不穷。但说实…

作者头像 李华
网站建设 2026/7/14 17:00:49

免费语音识别方案:Qwen3-ASR-0.6B镜像部署与使用教程

免费语音识别方案&#xff1a;Qwen3-ASR-0.6B镜像部署与使用教程 你是否遇到过这样的场景&#xff1a;一段重要的会议录音需要整理成文字&#xff0c;手动听写耗时费力&#xff1b;或者想为一段外语视频添加字幕&#xff0c;却苦于语言不通&#xff1f;传统的语音识别方案要么…

作者头像 李华