一、项目背景与原理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 输入通道。通道 12PC2光敏电阻分压采集通道。通道 11PC1可调分压采集通道。此张表格信息来自高博士ppt或哔哩哔哩网课以上图片的解释来自AI。若是大家仍然有不理解的地方可以参考以下链接的课程内容。[13-02]STM32的ADC外设详解_哔哩哔哩_bilibili串口连接STM32 的USART1_TXPA9接 USB 转 TTL 模块的RXSTM32 的USART1_RXPA10接 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通道配置Rank1ADC1_IN11 PC1 电位器Rank2ADC1_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: ADC816 | 0.66V | 2.49kΩ 大致在这个范围内波动光敏电阻手指遮挡的情况LIGHT: ADC2734 | 2.20V | 20.09kΩ 大致在这个范围内波动光敏电阻特性光线越强 → 电阻越小光线越暗 → 电阻越大数据算是符合物理规律相关的操作视频请各位小伙伴关注我的哔哩哔哩视频动态实验有问题的话可以在博客的评论区或者视频的评论区留言感谢各位小伙伴的支撑大家一起共同进步不会c的小谢的个人空间-不会c的小谢个人主页-哔哩哔哩视频3.7 基于AI的实验总结