STM32 HAL库 ADC+TIM+DMA 3路 1S采样一次电压
一、引言
在很多嵌入式系统应用中,需要对多路模拟信号进行周期性采样,例如在工业控制、环境监测等领域。STM32F407 是一款高性能的微控制器,其丰富的外设资源可以方便地实现这样的功能。通过结合 ADC(模拟 - 数字转换器)、TIM(定时器)和 DMA(直接内存访问),可以高效、稳定地完成多路模拟信号的周期性采样。
二、硬件连接
在使用 STM32F407 进行 ADC 采样时,需要将外部模拟信号连接到对应的 ADC 通道引脚。以下是一个常见的硬件连接示例:
| 信号 | STM32F407 引脚 | ADC 通道 |
|---|---|---|
| 模拟信号 1 | PA0 | ADC1 通道 0 |
| 模拟信号 2 | PA1 | ADC1 通道 1 |
| 模拟信号 3 | PA2 | ADC1 通道 2 |
三、原理分析
1. ADC(模拟 - 数字转换器)
ADC 的作用是将模拟信号转换为数字信号。STM32F407 的 ADC 具有 12 位分辨率,可对输入的模拟电压进行转换,转换结果范围为 0 - 4095(2^12 - 1)。在本应用中,我们使用 ADC1 来对 3 路模拟信号进行采样。
2. TIM(定时器)
定时器用于产生周期性的触发信号,控制 ADC 的采样频率。在本应用中,我们希望每 1S 采样一次,因此需要配置定时器的周期为 1S。
3. DMA(直接内存访问)
DMA 可以在不占用 CPU 的情况下,将 ADC 转换结果直接传输到内存中。这样可以提高系统的效率,避免 CPU 频繁参与数据传输。
四、代码实现
1. 初始化代码
#include "stm32f4xx_hal.h"// 定义全局变量
// hadc1 用于存储 ADC1 的句柄,通过这个句柄可以操作 ADC1 外设
ADC_HandleTypeDef hadc1;
// hdma_adc1 用于存储 DMA2 流 0 的句柄,用于控制 ADC1 数据的 DMA 传输
DMA_HandleTypeDef hdma_adc1;
// htim2 用于存储定时器 2 的句柄,用于产生 ADC 转换的触发信号
TIM_HandleTypeDef htim2;// 采样数据数组,用于存储 ADC 转换后的 3 路数据
uint16_t adc_values[3];// 定时器初始化函数,用于配置定时器 2 产生 1S 的定时信号
void TIM2_Init(void)
{// 定义定时器时钟源配置结构体,用于配置定时器的时钟源TIM_ClockConfigTypeDef sClockSourceConfig = {0};// 定义定时器主模式配置结构体,用于配置定时器的主模式输出触发等TIM_MasterConfigTypeDef sMasterConfig = {0};// 指定定时器 2 作为操作对象htim2.Instance = TIM2;// 定时器预分频器,将定时器时钟频率进行分频// 8400 - 1 表示将定时器时钟频率分频为原来的 1/8400htim2.Init.Prescaler = 8400 - 1; // 定时器计数模式设置为向上计数,即从 0 开始递增计数htim2.Init.CounterMode = TIM_COUNTERMODE_UP;// 定时器周期,当计数器计数到这个值时产生更新事件// 10000 - 1 结合预分频器实现 1S 的定时htim2.Init.Period = 10000 - 1; // 定时器时钟分频,这里设置为不分频htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;// 自动重载预装载功能禁用htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;// 调用 HAL 库函数初始化定时器 2,如果初始化失败则调用错误处理函数if (HAL_TIM_Base_Init(&htim2) != HAL_OK){Error_Handler();}// 配置定时器的时钟源为内部时钟sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;// 应用时钟源配置,如果配置失败则调用错误处理函数if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK){Error_Handler();}// 配置定时器的主模式输出触发为更新事件,即定时器更新时产生触发信号sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;// 禁用主从模式sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;// 应用主模式配置,如果配置失败则调用错误处理函数if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK){Error_Handler();}
}// DMA 初始化函数,用于配置 DMA2 流 0 进行 ADC 数据的传输
void DMA_Init(void)
{// 使能 DMA2 时钟,为 DMA2 外设提供时钟信号__HAL_RCC_DMA2_CLK_ENABLE();// 指定 DMA2 流 0 作为操作对象hdma_adc1.Instance = DMA2_Stream0;// 指定 DMA 通道为通道 0hdma_adc1.Init.Channel = DMA_CHANNEL_0;// 设置 DMA 传输方向为从外设(ADC)到内存hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;// 外设地址不递增,因为 ADC 数据寄存器地址固定hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;// 内存地址递增,因为要将数据依次存储到数组中hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;// 外设数据对齐方式为半字(16 位)hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;// 内存数据对齐方式为半字(16 位)hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;// 设置 DMA 工作模式为循环模式,数据传输完成后自动重新开始hdma_adc1.Init.Mode = DMA_CIRCULAR;// 设置 DMA 传输优先级为低hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;// 禁用 DMA FIFO 模式hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;// 调用 HAL 库函数初始化 DMA,如果初始化失败则调用错误处理函数if (HAL_DMA_Init(&hdma_adc1) != HAL_OK){Error_Handler();}// 将 DMA 句柄与 ADC 句柄关联起来,使 DMA 可以为 ADC 服务__HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
}// ADC 初始化函数,用于配置 ADC1 进行 3 路模拟信号的转换
void ADC1_Init(void)
{// 定义 ADC 通道配置结构体,用于配置 ADC 通道的参数ADC_ChannelConfTypeDef sConfig = {0};// 使能 ADC1 时钟,为 ADC1 外设提供时钟信号__HAL_RCC_ADC1_CLK_ENABLE();// 指定 ADC1 作为操作对象hadc1.Instance = ADC1;// ADC 时钟预分频,将 ADC 时钟频率进行分频hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;// 设置 ADC 分辨率为 12 位hadc1.Init.Resolution = ADC_RESOLUTION_12B;// 使能 ADC 扫描模式,用于多通道转换hadc1.Init.ScanConvMode = ENABLE;// 禁用连续转换模式,每次转换完成后等待下一次触发hadc1.Init.ContinuousConvMode = DISABLE;// 禁用不连续转换模式hadc1.Init.DiscontinuousConvMode = DISABLE;// 设置外部触发转换的边沿为上升沿hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;// 设置外部触发源为定时器 2 的 TRGO 信号hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;// 设置 ADC 数据对齐方式为右对齐hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;// 设置要转换的通道数量为 3hadc1.Init.NbrOfConversion = 3;// 使能 DMA 连续请求模式,确保 DMA 可以持续传输数据hadc1.Init.DMAContinuousRequests = ENABLE;// 设置 EOC 选择为序列转换结束标志hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;// 调用 HAL 库函数初始化 ADC1,如果初始化失败则调用错误处理函数if (HAL_ADC_Init(&hadc1) != HAL_OK){Error_Handler();}// 配置 ADC 通道 0sConfig.Channel = ADC_CHANNEL_0;// 设置通道 0 的转换顺序为第 1 个sConfig.Rank = 1;// 设置通道 0 的采样时间为 3 个 ADC 时钟周期sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;// 应用通道 0 的配置,如果配置失败则调用错误处理函数if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK){Error_Handler();}// 配置 ADC 通道 1sConfig.Channel = ADC_CHANNEL_1;// 设置通道 1 的转换顺序为第 2 个sConfig.Rank = 2;// 应用通道 1 的配置,如果配置失败则调用错误处理函数if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK){Error_Handler();}// 配置 ADC 通道 2sConfig.Channel = ADC_CHANNEL_2;// 设置通道 2 的转换顺序为第 3 个sConfig.Rank = 3;// 应用通道 2 的配置,如果配置失败则调用错误处理函数if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK){Error_Handler();}
}// GPIO 初始化函数,用于配置 GPIO 引脚作为 ADC 的模拟输入引脚
void GPIO_Init(void)
{// 使能 GPIOA 时钟,为 GPIOA 外设提供时钟信号__HAL_RCC_GPIOA_CLK_ENABLE();// 定义 GPIO 初始化结构体,用于配置 GPIO 引脚的参数GPIO_InitTypeDef GPIO_InitStruct = {0};// 指定要配置的引脚为 PA0、PA1 和 PA2GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2;// 设置引脚模式为模拟输入模式GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;// 不使用上拉或下拉电阻GPIO_InitStruct.Pull = GPIO_NOPULL;// 应用 GPIO 引脚配置HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}// 系统初始化函数,用于初始化整个系统的外设
void System_Init(void)
{// 初始化 HAL 库HAL_Init();// 初始化 GPIO 引脚GPIO_Init();// 初始化定时器 2TIM2_Init();// 初始化 DMA2 流 0DMA_Init();// 初始化 ADC1ADC1_Init();
}
2. 主函数
int main(void)
{System_Init();// 启动定时器HAL_TIM_Base_Start(&htim2);// 启动ADC DMA传输HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_values, 3);while (1){// 可以在这里处理采样数据// 例如将采样数据通过串口发送出去}
}
3. 错误处理函数
void Error_Handler(void)
{while (1){// 可以在这里添加错误处理代码,例如点亮LED灯提示错误}
}
五、代码解释
1. 定时器初始化
在TIM2_Init函数中,我们配置了定时器 2 的预分频器和周期,使其产生 1S 的定时信号。定时器的更新事件(TRGO)将作为 ADC 的外部触发信号。
2. DMA 初始化
在DMA_Init函数中,我们配置了 DMA2 的 Stream0,使其将 ADC 的转换结果从外设(ADC)传输到内存(adc_values数组)。使用循环模式可以实现连续的数据传输。
3. ADC 初始化
在ADC1_Init函数中,我们配置了 ADC1 的时钟、分辨率、扫描模式等参数。使用外部触发模式,由定时器 2 的 TRGO 信号触发 ADC 转换。配置了 3 个转换通道,分别对应 PA0、PA1 和 PA2 引脚。
4. GPIO 初始化
在GPIO_Init函数中,我们将 PA0、PA1 和 PA2 引脚配置为模拟输入模式,用于连接外部模拟信号。
5. 主函数
在main函数中,我们首先调用System_Init函数进行系统初始化。然后启动定时器和 ADC 的 DMA 传输。在主循环中,可以对采样数据进行处理,例如将数据通过串口发送出去。
六、调试与测试
在完成代码编写后,可以使用调试工具(如 ST-Link)对程序进行调试。可以在主循环中添加打印语句,将采样数据通过串口输出到电脑上,观察采样结果是否正确。
七、注意事项
- 时钟配置:确保系统时钟和外设时钟配置正确,否则定时器和 ADC 可能无法正常工作。
- DMA 配置:DMA 的配置需要与 ADC 的配置相匹配,例如数据对齐方式、传输方向等。
- 电源和接地:确保模拟信号的电源和接地良好,避免引入噪声干扰。
八、总结
通过结合 STM32F407 的 ADC、TIM 和 DMA 外设,我们可以高效、稳定地实现 3 路模拟信号的周期性采样。定时器用于控制采样频率,DMA 用于数据传输,提高了系统的效率。在实际应用中,可以根据需要对采样频率、通道数量等参数进行调整。
相关文章:
STM32 HAL库 ADC+TIM+DMA 3路 1S采样一次电压
一、引言 在很多嵌入式系统应用中,需要对多路模拟信号进行周期性采样,例如在工业控制、环境监测等领域。STM32F407 是一款高性能的微控制器,其丰富的外设资源可以方便地实现这样的功能。通过结合 ADC(模拟 - 数字转换器ÿ…...
汉诺塔问题——用贪心算法解决
目录 一:起源 二:问题描述 三:规律 三:解决方案 递归算法 四:代码实现 复杂度分析 一:起源 汉诺塔(Tower of Hanoi)问题起源于一个印度的古老传说。在世界中心贝拿勒斯&#…...
【Python爬虫】简单介绍
目录 一、基本概念 1.1 什么是爬虫 1.2 Python为什么适合爬虫 1.3 Python爬虫应用领域 (1)数据采集与分析 市场调研 学术研究 (2)内容聚合与推荐 新闻聚合 视频内容聚合 (3)金融领域 股票数据获…...
使用MCP服务通过自然语言操作数据库(vscode+cline版本)
使用MCP服务操纵数据库(vscodecline版本) 本文主要介绍,在vscode中使用cline插件调用deepseek模型,通过MCP服务器 使用自然语言去操作指定数据库。本文使用的是以己经创建号的珠海航展数据库。 理解MCP服务: MCP(Model Context…...
Vue 3 + TypeScript 实现一个多语言国际化组件(支持语言切换与内容加载)
文章目录 一、项目背景与功能概览二、项目技术架构与依赖安装2.1 技术栈2.2 安装依赖 三、国际化组件实现3.1 创建 i18n 实例3.2 配置 i18n 到 Vue 应用3.3 在组件中使用国际化内容3.4 支持语言切换 四、支持类型安全4.1 添加类型支持4.2 自动加载语言文件 一、项目背景与功能概…...
PhalApi 2.x:让PHP接口开发从“简单”到“极简”的开源框架
—— 专为高效开发而生,助你轻松构建高可用API接口 一、为什么选择PhalApi 2.x? 1.轻量高效,性能卓越 PhalApi 2.x 是一款专为接口开发设计的轻量级PHP框架,其核心代码精简但功能强大。根据开发者实测,在2核2G服务器…...
库magnet使用指南
Magnet 多线程控制库使用指南 目录 库功能概述环境配置核心类与接口基础使用示例代码生成工具高级功能与改进建议完整示例代码常见问题解答 https://blink.csdn.net/details/1872803?spm1001.2014.3001.5501 1. 库功能概述 Magnet 库提供以下核心功能: 多线程…...
Oracle数据库数据编程SQL<9.3 数据库逻辑备份和迁移Data Pump (EXPDP/IMPDP) 导出、导入补充>
Oracle Data Pump 是 Oracle 10g 引入的高效数据迁移工具,相比传统的 EXP/IMP 工具,它提供了更强大的功能和显著的性能提升。以下是对 EXPDP 和 IMPDP 工具的全面讲解。 目录 一、高级功能扩展 1. 数据过滤与转换 2. 加密与安全 二、性能调优进阶 1. 并行处理优化 2. …...
Java 企业级应用:SOA 与微服务的对比与选择
企业级应用开发中,架构设计是决定系统可扩展性、可维护性和性能的关键因素。SOA(面向服务的架构)和微服务架构是两种主流的架构模式,它们各自有着独特的和设计理念适用场景。本文将深入探讨 SOA 和微服务架构的对比,并…...
Linux LED驱动(设备树)
Linux LED驱动(设备树) 之前的LED驱动直接在驱动文件中定义有关寄存器物理地址,然后使用io_remap函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对GPIO的初始化。 但也可以先在设备树文件中创…...
Zookeeper的典型应用场景?
大家好,我是锋哥。今天分享关于【Zookeeper的典型应用场景?】面试题。希望对大家有帮助; Zookeeper的典型应用场景? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 ZooKeeper 是一个开源的分布式协调服务,主要用于管理和协调大…...
数据分析不只是跑个SQL!
数据分析不只是跑个SQL! 数据分析五大闭环,你做到哪一步了?闭环一:认识现状闭环二:原因分析闭环三:优化表现闭环四:预测走势闭环五:主动解读数据 数据思维:WHY-WHAT-HOW模…...
面试篇 - GPT-3(Generative Pre-trained Transformer 3)模型
GPT-3(Generative Pre-trained Transformer 3)模型 模型结构 与GPT-2一样,但是应用了Sparse attention: Dense attention:每个token之间两两计算attention,复杂度为O(n2)。 Sparse attention:…...
Dify智能体平台源码二次开发笔记(4) - 多租户的SAAS版实现
前言 Dify 的多租户功能是其商业版的标准功能,我们应当尊重其盈利模式。只有保持良性的商业运作,Dify 才能持续发展,并为用户提供更优质的功能。因此,此功能仅限学习使用。 我们的需求是:实现类似 SaaS 版的账号隔离&a…...
C# 13新特性 - .NET 9
转载: C# 13 中的新增功能 | Microsoft Learn C# 13 包括以下新增功能。 可以使用最新的 Visual Studio 2022 版本或 .NET 9 SDK 尝试这些功能:Introduced in Visual Studio 2022 Version 17.12 and newer when using C# 13 C# 13 中的新增功能 | Micr…...
【Code】《代码整洁之道》笔记-Chapter9-单元测试
第9章 单元测试 过去十年以来,编程专业领域进步很大。1997年时,没人听说过测试驱动开发。对于我们之中的大多数人来说,单元测试是那种用来确保程序“可运行”的用过即扔的短代码。我们辛勤地编写类和方法,再弄出一些特殊代码来测…...
java -jar 如何持久化运行
在 Linux 中,直接通过 java -jar 启动服务后关闭 SSH 客户端(如 Xshell)会导致服务终止,因为进程默认与当前终端会话绑定。以下是几种解决方案,确保服务在后台持久运行: (1)使用nohup命令,让进程忽略挂断信号,并在后台运行。 ps -ef | grep xxx.jar 或者 ps -ef …...
layui中transfer两个table展示不同的数据列
在项目的任务开发中需要达到transfer右侧table需要有下拉框可选择状态,左侧table不变 使用的layui版本为2.4.5,该版本没有对transfer可自定义数据列的配置,所以改动transfer.js中的源码 以下为transfer.js部分源码 也是transfer.js去render的…...
如何通过Radius认证服务器实现虚拟云桌面安全登录认证:安当ASP身份认证系统解决方案
引言:虚拟化时代的安全挑战 随着云计算和远程办公的普及,虚拟云桌面(如VMware Horizon、Citrix)已成为企业数字化办公的核心基础设施。然而,传统的用户名密码认证方式暴露了诸多安全隐患:弱密码易被暴力破…...
如何用DeepSeek大模型提升MySQL DBA工作效率?实战案例解析
如何用DeepSeek大模型提升MySQL DBA工作效率?实战案例解析 MySQL DBA(数据库管理员)的工作涉及数据库监控、SQL优化、故障排查、备份恢复等复杂任务,传统方式依赖手动操作和经验判断,效率较低。而DeepSeek大模型可以结…...
【机器学习】机器学习笔记
1 机器学习定义 计算机程序从经验E中学习,解决某一任务T,进行某一性能P,通过P测定在T上的表现因经验E而提高。 eg:跳棋程序 E: 程序自身下的上万盘棋局 T: 下跳棋 P: 与新对手下跳棋时赢的概率…...
CFD中的动量方程非守恒形式详解
在计算流体力学(CFD)中,动量方程可以写成守恒形式和非守恒形式,两者在数学上等价,但推导方式和应用场景不同。以下是对非守恒形式的详细解释: 1. 动量方程的守恒形式 首先回顾守恒形式的动量方程ÿ…...
如何在本地修改 Git 项目的远程仓库地址
✅ 场景说明 你当前的 Git 项目地址是: http://192.168.0.16/xxx.git你希望把它改成: http://192.168.0.22:8099/xxx.git🧩 操作步骤 步骤 ①:进入项目所在目录 你已经在正确路径下了: cd C:\Develop\xxx确认这个…...
clickhouse中的窗口函数
窗口函数 边界核心参数 窗口边界通过 ROWS、RANGE 或 GROUPS 模式定义,语法为: ROWS BETWEEN AND 基于 物理行位置 定义窗口,与排序键的实际值无关,适用于精确控制窗口行数 – 或 RANGE BETWEEN AND 基于 排序键的数值范围 定义窗口,适用于时间序列或连续数值的场景(…...
如何从项目目标到成功标准:构建可量化、可落地的项目评估体系
引言 在项目管理领域,"项目成功"的定义往往比表面看起来更复杂。根据PMI的行业报告,67%的项目失败源于目标与成功标准的不匹配。当项目团队仅关注"按时交付"或"预算达标"时,常会忽视真正的价值创造。本文将通…...
fbx/obj/glb/gltf/b3dm等通用格式批量转换成osgb
fbx/obj/glb/gltf/b3dm等通用格式批量转换成osgb fbx/obj/glb/gltf/b3dm等通用格式批量转换成osgb...
STM32 BOOT设置,bootloader,死锁使用方法
目录 BOOT0 BOOT1的配置含义 bootloader使用方法 芯片死锁解决方法开发调试过程中,由于某种原因导致内部Flash锁死,无法连接SWD以及JTAG调试,无法读到设备,可以通过修改BOOT模式重新刷写代码。修改为BOOT01,BOOT10…...
vue2 设置ant-table和el-table隔行变色
vue2 设置ant-table和el-table隔行变色 ant-table /* 奇数行 */ ::v-deep .ant-table-tbody > tr:nth-child(odd) {background-color: transparent; } /* 偶数行 */ ::v-deep .ant-table-tbody > tr:nth-child(even) {background-color: rgba(15, 166, 255, 0.26); }el…...
【Redis】string类型
目录 1、介绍2、底层实现【1】SDS【2】int编码【3】embstr编码【4】raw编码【5】embstr和raw的区别 3、常用指令【1】字符串基本操作:【2】批量操作【3】计数器【4】过期时间【5】不存在就插入 4、使用场景 1、介绍 string是redis中最简单的键值对形式,…...
《解锁分布式软总线:构建智能设备统一管理平台》
智能设备的数量呈爆发式增长,从智能家居里的各类电器,到智能办公中的电脑、打印机,再到工业领域的各种自动化设备,不一而足。如何对这些纷繁复杂的智能设备进行有效管理,成为摆在我们面前的一道难题。分布式软总线技术…...
