土木本科生的STM32探索之旅:ADC+DMA采集光敏电阻,usart串口发送到电脑串口助手
一、项目背景与原理
1.1 项目目标
本项目的核心目标是:
-
用 STM32 自带的 ADC 读取光敏电阻的模拟电压值
-
利用 DMA 实现数据的自动传输,解放 CPU 资源
-
将采集到的数据通过 USART 串口发送到电脑,在串口助手上实时显示
1.2 关键技术原理
-
光敏电阻:光照越强,电阻值越小。通过分压电路将电阻变化转化为电压变化,再由 ADC 采集电压值。
-
ADC(模数转换器):将光敏电阻输出的连续模拟电压,转换为单片机能处理的数字信号(12 位精度,数值范围 0~4095)。
-
DMA(直接存储器访问):让数据从 ADC 外设直接传输到内存,无需 CPU 干预,实现高效连续采样。
-
USART 串口通信:将采集到的数字数据,通过串口发送到电脑端的串口助手进行显示。
二、硬件连接
我使用的是 STM32F103VET6 开发板,硬件连接非常简单:

-
光敏电阻电路:由于是集成在STM32F103VET6 开发板上的,不用进行硬件的连接,下面我就放上它的原理图。

我对原理图的认识如下(可能存在错误,若有理解错误的地方请大家谅解):电路分为两路独立的信号采集通道,最终都通过排针J3连接到 STM32 的PC1和PC2引脚(ADC1 输入通道)。
通道 12(PC2):光敏电阻分压采集通道。
通道 11(PC1):可调分压采集通道。

此张表格信息来自高博士ppt或哔哩哔哩网课




以上图片的解释来自AI。若是大家仍然有不理解的地方,可以参考以下链接的课程内容。
[13-02]STM32的ADC外设详解_哔哩哔哩_bilibili
-
串口连接:
-
STM32 的
USART1_TX(PA9)接 USB 转 TTL 模块的RX -
STM32 的
USART1_RX(PA10)接 USB 转 TTL 模块的TX -
开发板和 USB 转 TTL 模块的
GND共地。
-

三、CubeMX 配置(硬核操作过程)
3.1 基础配置
-
新建工程:ADC_DMA_USART_2,选择对应的 STM32F103VET6 芯片。
-
配置 RCC 时钟,使用外部晶振,将系统时钟配置为 72MHz。
-
配置 SYS,选择 Serial Wire 调试模式。



3.2 ADC1配置
-
模式选择:Scan Conversion Mode 扫描模式 关闭
-
Continuous Conversion Mode 连续转换 开启
-
转换通道数:Number Of Conversion 设置为 2
-
通道配置:
-
Rank1:ADC1_IN11 (PC1 电位器)
-
Rank2:ADC1_IN12 (PC2 光敏电阻)
-
-
采样时间都设为:55.5 Cycles(数值更稳定)

这个adc1选择了11和12通道,时钟会跳出来一个红色的警告

我把右下角这里调整了一下,红色的警告就消失了


3.3 DMA 配置
在 ADC1 设置中,开启 DMA请求。

3.4 USART 配置
-
打开
USART1,模式选择异步通信。 -
波特率设置为
115200,数据位 8,无校验位,停止位 1。

配置完成后,点击菜单栏上面的小齿轮生成初始化代码。

进入到main.c代码,增加用户代码以及计算的公式。

借助AI工具,它提供了一定的代码给我们(软件自己会生成一定代码,会有点代码重复)
#include <stdio.h> //标准输入输出头文件,支持printf、scanf
#include <string.h> // 字符串处理头文件,支持strlen、strcmp、strcpy等
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
UART_HandleTypeDef huart1;

代码在软件中粘贴后的情况

#define ADC_DMA_BATCH_LEN 2 //ADC DMA一次搬运数据个数
uint16_t adc_dma_buffer[ADC_DMA_BATCH_LEN]; //ADC DMA接收缓冲区,存放ADC采样原始值
uint8_t adc_dma_cplt_flg = 0; // ADC DMA传输完成标志位:0未完成,1完成
#define ADC_VREF_PLUS 3300.0f //ADC参考电压 3.3V,单位mV
#define ADC_RESOLUTION 4095.0f //ADC分辨率 12位 最大数值 0~4095

代码在软件中粘贴后的情况
由于写本篇博客文章,中间有断开,写文章的时间不是连续,不是一气呵成的。中间可能就有一点问题,且代码是基于AI辅助做成的,为了大家方便,我这里就把main.c代码直接放在了下方。
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
******************************************************************************
*/
/* USER CODE END Header */
#include "main.h"
#include <stdio.h>
#include <string.h>
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
UART_HandleTypeDef huart1;
/* USER CODE BEGIN PV */
#define ADC_DMA_BATCH_LEN 2
uint16_t adc_dma_buffer[ADC_DMA_BATCH_LEN];
uint8_t adc_dma_cplt_flg = 0;
#define ADC_VREF_PLUS 3300.0f
#define ADC_RESOLUTION 4095.0f
/* USER CODE END PV */
/* USER CODE BEGIN 0 */
float ADC_Value_To_Volt(uint16_t adc_value)
{
return (float)adc_value * ADC_VREF_PLUS / ADC_RESOLUTION / 1000.0f;
}
float Volt_To_LightRes(float volt)
{
return (volt * 10.0f) / (3.3f - volt);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
if (hadc->Instance == ADC1)
{
HAL_ADC_Stop_DMA(&hadc1);
adc_dma_cplt_flg = 1;
}
}
/* USER CODE END 0 */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_ADC1_Init(void);
static void MX_USART1_UART_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_dma_buffer, ADC_DMA_BATCH_LEN);
/* USER CODE END 2 */
while (1)
{
if (adc_dma_cplt_flg)
{
char buf[64];
float light_volt = ADC_Value_To_Volt(adc_dma_buffer[1]);
float light_res = Volt_To_LightRes(light_volt);
sprintf(buf, "LIGHT: ADC=%d | %.2fV | %.2fkΩ\r\n",
adc_dma_buffer[1], light_volt, light_res);
HAL_UART_Transmit(&huart1, (uint8_t *)buf, strlen(buf), 100);
adc_dma_buffer[0] = 0;
adc_dma_buffer[1] = 0;
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_dma_buffer, ADC_DMA_BATCH_LEN);
adc_dma_cplt_flg = 0;
}
HAL_Delay(200);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
static void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 2;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_11;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_12;
sConfig.Rank = ADC_REGULAR_RANK_2;
sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
static void MX_DMA_Init(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}
static void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
大家可以对比一下,借助ai分析解析一下代码内容
3.5 编译无误以后将代码烧录进单片机
3.6 打开串口助手,观察代码运行后的实验情况
串口助手有网页版也有电脑端的,大家可以自行选择。
为了方便,我这里就用了波特律动网页版串口助手,网址如下:

光敏电阻无遮挡的情况(LIGHT: ADC=816 | 0.66V | 2.49kΩ 大致在这个范围内波动)

光敏电阻手指遮挡的情况(LIGHT: ADC=2734 | 2.20V | 20.09kΩ 大致在这个范围内波动)
光敏电阻特性:
- 光线越强 → 电阻越小
- 光线越暗 → 电阻越大
数据算是符合物理规律
相关的操作视频请各位小伙伴,关注我的哔哩哔哩视频动态,实验有问题的话可以在博客的评论区或者视频的评论区留言,感谢各位小伙伴的支撑,大家一起共同进步!!
3.7 基于AI的实验总结








更多推荐
所有评论(0)