STM32F103和STM32F401的ADC多通道采集DMA输出
使用STM32F103和STM32F401CCU6对双轴摇杆(两个电压通道)进行ADC采样并通过DMA读取数值
STM32 ADC(模数转换)工作模式
单次转换模式
In Single Conversion mode, the ADC does one conversion. This mode is started either by setting the ADON bit in the ADC_CR2 register (for a regular channel only) or by an external trigger (for a regular or injected channel), while the CONT bit is 0. Once the conversion of the selected channel is complete:
If a regular channel was converted:
– The converted data is stored in the 16-bit ADC_DR register
– The EOC (End Of Conversion) flag is set
– and an interrupt is generated if the EOCIE is set.
If an injected channel was converted:
– The converted data is stored in the 16-bit ADC_DRJ1 register
– The JEOC (End Of Conversion Injected) flag is set
– and an interrupt is generated if the JEOCIE bit is set.
The ADC is then stopped.
连续转换模式
In continuous conversion mode, ADC starts another conversion as soon as it finishes one. This mode is started either by an external trigger or by setting the ADON bit in the ADC_CR2 register, while the CONT bit is 1. After each conversion:
If a regular channel was converted:
– The converted data is stored in the 16-bit ADC_DR register
– The EOC (End Of Conversion) flag is set
– An interrupt is generated if the EOCIE is set.
If an injected channel was converted:
– The converted data is stored in the 16-bit ADC_DRJ1 register
– The JEOC (End Of Conversion Injected) flag is set
– An interrupt is generated if the JEOCIE bit is set.
扫描模式
This mode is used to scan a group of analog channels. A single conversion is performed for each channel of the group. After each end of conversion, the next channel of the group is converted automatically. If the CONT bit is set, conversion does not stop at the last selected group channel but continues again from the first selected group channel.
When using scan mode, DMA bit must be set and the direct memory access controller is used to transfer the converted data of regular group channels to SRAM after each update of the ADC_DR register. The injected channel converted data is always stored in the ADC_JDRx registers.
https://electronics.stackexchange.com/questions/504118/stm32-why-cant-i-use-scan-mode-in-interrupt-driven-adc 与 F4 系列不同, F1 系列中的 ADC, 只会在整个扫描结束后才产生一个中断, 所以在扫描模式中, 必须使用DMA.
非连续模式
This mode is enabled by setting the DISCEN bit in the ADC_CR1 register. It can be used to convert a short sequence of n conversions (n <=8) which is a part of the sequence of conversions selected in the ADC_SQRx registers. The value of n is specified by writing to the DISCNUM[2:0] bits in the ADC_CR1 register.
When an external trigger occurs, it starts the next n conversions selected in the ADC_SQRx registers until all the conversions in the sequence are done. The total sequence length is defined by the L[3:0] bits in the ADC_SQR1 register.
读取ADC结果的几种方式
The Polling Method
It’s the easiest way in code in order to perform an analog to digital conversion using the ADC on an analog input channel. However, it’s not an efficient way in all cases as it’s considered to be a blocking way of using the ADC. As in this way, we start the A/D conversion and wait for the ADC until it completes the conversion so the CPU can resume processing the main code.
中断模式
The interrupt method is an efficient way to do ADC conversion in a non-blocking manner, so the CPU can resume executing the main code routine until the ADC completes the conversion and fires an interrupt signal so the CPU can switch to the ISR context and save the conversion results for further processing.
However, when you’re dealing with multiple channels in a circular mode or so, you’ll have periodic interrupts from the ADC that are too much for the CPU to handle. This will introduce jitter injection and interrupt latency and all sorts of timing issues to the system. This can be avoided by using DMA.
DMA方式
Lastly, the DMA method is the most efficient way of converting multiple ADC channels at very high rates and still transfers the results to the memory without CPU intervention which is so cool and time-saving technique.
STM32F103C8T6的代码实现
管脚与ADC的映射关系
- PA0:7 ADC1_IN0:7
- PB0 ADC1_IN8
- PB1 ADC1_IN9
实现两个通道电压采集到DMA
- 确定要采集的信号通道数量, 每个信号通道要保留的采样数, 比如下面的例子中是2个通道, 每个通道4个采样
- 根据上面的数量得到
ARRAYSIZE, 声明用于DMA的内存变量__IO uint16_t ADCConvertedValue[ARRAYSIZE] - 初始化时钟: ADC1, GPIOA, DMA1
- 初始化GPIOA用于采集的两个pin
- 初始化ADC1
- 初始化DMA1
代码
#include <stdio.h>
#include "timer.h"
#include "usart.h"
#define ARRAYSIZE 2*4
__IO uint16_t ADCConvertedValue[ARRAYSIZE];
void RCC_Configuration(void)
{
/* ADCCLK = PCLK2/4 */
RCC_ADCCLKConfig(RCC_PCLK2_Div4);
/* Enable peripheral clocks ------------------------------------------------*/
/* Enable DMA1 clock */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/* Enable ADC1 and GPIOC clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
}
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure PA.00 (ADC Channel0) as analog input -------------------------*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
int main(void)
{
SystemInit();
Systick_Init();
USART_Configuration();
/* System clocks configuration ---------------------------------------------*/
RCC_Configuration();
/* GPIO configuration ------------------------------------------------------*/
GPIO_Configuration();
/* ADC1 configuration ------------------------------------------------------*/
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
//We will convert multiple channels
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
//right 12-bit data alignment in ADC data register
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// Set it to the number of channels
ADC_InitStructure.ADC_NbrOfChannel = 2;
ADC_Init(ADC1, &ADC_InitStructure);
/* ADC1 regular channel0 configuration, rank decides the order in ADCConvertedValue, start from 1 */
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_41Cycles5);
/* ADC1 regular channel1 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_41Cycles5);
/* Enable ADC1 DMA */
ADC_DMACmd(ADC1, ENABLE);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
/* Enable ADC1 reset calibration register */
ADC_ResetCalibration(ADC1);
/* Check the end of ADC1 reset calibration register */
while(ADC_GetResetCalibrationStatus(ADC1));
/* Start ADC1 calibration */
ADC_StartCalibration(ADC1);
/* Check the end of ADC1 calibration */
while(ADC_GetCalibrationStatus(ADC1));
/* Start ADC1 Software Conversion */
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
/* DMA1 channel1 configuration ----------------------------------------------*/
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(DMA1_Channel1);
// ADC1_DR_Address ((uint32_t)0x4001244C)
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(ADC1->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADCConvertedValue;
/* Direction:
DMA_DIR_PeripheralSRC:from peripheral,
DMA_DIR_PeripheralDST:to peripheral
*/
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
/* Specifies the buffer size, in data unit, of the specified Stream.
The data unit is equal to the configuration set in DMA_PeripheralDataSize
or DMA_MemoryDataSize members depending in the transfer direction.
Set it to the number of channels
*/
DMA_InitStructure.DMA_BufferSize = ARRAYSIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// Specifies whether the memory address register should be incremented or not
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// Priority among DMA channels
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// From Memory to Memory
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
/* Enable DMA1 channel1 */
DMA_Cmd(DMA1_Channel1, ENABLE);
while(1) {
for (u8 i = 0; i < ARRAYSIZE; i++) {
printf("%d ", *(ADCConvertedValue + i));
}
printf("\r\n");
Systick_Delay_ms(500);
}
}
void ADC1_IRQHandler(void)
{
ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}
STM32F401CCU6的代码实现
只有1个16通道ADC. One 12-bit analog-to-digital converter is embedded and shares up to 16 external channels, performing conversions in the single-shot or scan mode. In scan mode, automatic conversion is performed on a selected group of analog inputs. The ADC can be served by the DMA controller. An analog watchdog feature allows very precise monitoring of the converted voltage of one, some or all selected channels. An interrupt is generated when the converted voltage is outside the programmed thresholds.
To synchronize A/D conversion and timers, the ADCs could be triggered by any of TIM1, TIM2, TIM3, TIM4 or TIM5 timer.
管脚与ADC的映射关系
- PA0:7 ADC1_IN0:7
- PB0 ADC1_IN8
- PB1 ADC1_IN9
- PC0:5 ADC1_IN10:15
因为F401CCU6的PC口只有PC13,PC14,PC15, 所以可以用的ADC只有ADC1_IN0 - IN9
STM32F4的ADC1与DMA的映射
根据STM32F2/F4/F7的DMA参考手册, 这个系列的芯片中DMA1与DMA2各有8个Stream(Stream0 - Stream7), 分别对应着不同的外设, 其中ADC1对应的是DMA2的Stream0和Stream4, 在代码中必须使用这两个, 否则DMA不起作用
实现两个通道电压采集到DMA的代码
#include <stdio.h>
#include "config.h"
#include "led.h"
#include "timer.h"
#include "uart.h"
#define ARRAYSIZE 2*4
__IO uint16_t ADCConvertedValue[ARRAYSIZE];
void RCC_Configuration(void)
{
/* Enable ADCx, DMA and GPIO clocks ****************************************/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1, DISABLE);
}
void GPIO_Configuration(void)
{
/* Configure ADC1 Channel0,1 pin as analog input ******************************/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
int main(void)
{
Systick_Init();
USART1_Init();
LED_Init();
/* System clocks configuration ---------------------------------------------*/
RCC_Configuration();
/* GPIO configuration ------------------------------------------------------*/
GPIO_Configuration();
/* DMA2 Stream0 channel0 configuration **************************************/
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_Channel = DMA_Channel_0;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADCConvertedValue;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_BufferSize = ARRAYSIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream0, &DMA_InitStructure);
DMA_Cmd(DMA2_Stream0, ENABLE);
/* ADC Common Init **********************************************************/
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
// 预分频4分频, ADCCLK=PCLK2/4=84/4=21Mhz,ADC时钟最好不要超过36Mhz
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
// DMA使能 (DMA传输下要设置使能)
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
//两个采样阶段之间的延迟x个时钟
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
ADC_CommonInit(&ADC_CommonInitStructure);
/* ADC1 Init ****************************************************************/
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
//ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfConversion = 2;
ADC_Init(ADC1, &ADC_InitStructure);
/* ADC1 regular channel0,1 configuration **************************************/
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_56Cycles);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_56Cycles);
/* Enable DMA request after last transfer (Single-ADC mode) */
ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
/* Enable ADC1 DMA */
ADC_DMACmd(ADC1, ENABLE);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
ADC_SoftwareStartConv(ADC1);
while(1) {
LED_On();
for (u8 i = 0; i < ARRAYSIZE; i++) {
float a = (*(ADCConvertedValue + i) - 2048) * 512 /2048;
printf("% 5d ", (int)a);
}
printf("\r\n");
LED_Off();
Systick_Delay_ms(200);
}
}
参考
- 这篇写得很详细 https://deepbluembedded.com/stm32-adc-tutorial-complete-guide-with-examples/
- 可能有用的代码 https://github.com/Itachihi/stm32-adc/blob/master/readvb.c
- 可能有用的代码 ContinuousConvMode https://github.com/mnectnky/STM32F103-One-ADC-MultiChannel-Analog-Read/blob/main/main.c
- 可能有用的代码 ContinuousConvMode https://github.com/klesogor/ADC/blob/master/ADC.c
- 有用的代码 DMA, adc部分在main.c https://github.com/daedaleanai/stm32f103_adc2serial/blob/main/main.c
- 有用的代码 DMA, adc部分在main.c https://github.com/MrLuuuu/STM32F103ZET6_UartPrint/blob/master/USER/main.c
- 用stm32f103做的电容电感测试仪, 注入型, adc部分在main.c https://github.com/MilanGb/STM32F103-LC-R-meter/blob/main/main.c
- https://community.st.com/s/question/0D50X00009XkZ4F/adc-with-multiple-channels-settings
- This one is helpful https://embedds.com/multichannel-adc-using-dma-on-stm32/
- 这篇 https://blog.csdn.net/weixin_45456099/article/details/110669752
- 直接中断输出到串口 https://www.it610.com/article/1296571998083817472.htm
- STM32F2/F4/F7 DMA参考 https://www.st.com/resource/en/application_note/dm00046011-using-the-stm32f2-stm32f4-and-stm32f7-series-dma-controller-stmicroelectronics.pdf
STM32F103和STM32F401的ADC多通道采集DMA输出的更多相关文章
- STM32 ADC多通道转换DMA模式与非DMA模式两种方法(HAL库)
一.非DMA模式(转) 说明:这个是自己刚做的时候百度出来的,不是我自己做出来的,因为感觉有用就保存下来做学习用,原文链接:https://blog.csdn.net/qq_24815615/arti ...
- STM32L0开发——ADC多通道采集,IDE和IAR开发注意事项
keil开发L0系列是免费的,官方提供许可的.因此建议Keil开发,L011F3由于flash只有8K,因此不建议HAL库,建议使用cubemx+LL(或snippets库).0.起初,可以参考官方库 ...
- STM32—ADC多通道采集电压
文章目录 ADC详解 程序说明 函数主体 引脚配置 ADC和DMA配置 主函数 ADC详解 前面的博客中详细介绍了STM32中ADC的相关信息,这篇博客是对ADC内容的一个总结提升,ADC的详细介绍: ...
- ADC多通道采样DMA传输模板
void MyADC_Init(void){ ADC_InitTypeDef ADC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; DMA_I ...
- GD32F330 | ADC实例 基于DMA方式
GD32F330 | ADC实例 基于DMA方式 简单记录一下 ADC多通道转换 DMA搬运 的使用,以 GD32F330G8U6 为例: 一.ADC 基础知识 12位ADC是一种采用逐次逼近方式的模 ...
- 【STM32H7教程】第46章 STM32H7的ADC应用之DMA方式多通道采样
完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第46章 STM32H7的ADC应用之DMA方式多 ...
- STM32L15x——ADC采集DMA数据只第一次正确(已解决)
前提:我用的芯片是STM32L系列,可能对其它STM32系列不完全适用,仅供参考! 一.问题描述 我在使用DMA方式读取单ADC单通道采集的数据时,发现只能正确的采集一次数据,后来的就一直与第一次的相 ...
- 第30章 ADC—电压采集—零死角玩转STM32-F429系列
第30章 ADC—电压采集 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/fireg ...
- STM32 ADC多通道转换
描述:用ADC连续采集11路模拟信号,并由DMA传输到内存.ADC配置为扫描并且连续转换模式,ADC的时钟配置为12MHZ.在每次转换结束后,由DMA循环将转换的数据传输到内存中.ADC可以连续采集N ...
- 案例 stm32单片机,adc的双通道+dma 内部温度
可以这样理解 先配置adc :有几个通道就配置几个通道. 然后配置dma,dma是针对adc的,而不是针对通道的. 一开始我以为一个adc通道对应一个dma通道.(这里是错的,其实是我想复杂了) 一个 ...
随机推荐
- ORA-65140: 无效的通用配置文件名称
1.问题 CREATE PROFILE PM_Profile LIMIT SESSIONS_PER_USER 100 PASSWORD_LIFE_TIME 90; 在创建概要文件时,报错:ORA-65 ...
- 【特别的骚气】asp.net core运行时注入服务,实现类库热插拔
引言 很久之前在群里有看到说asp.net core能不能在运行时注入程序,当时并没有太在意,刚才在某个群里又看到有人再问,core能不能在运行时注入服务,闲来无事,我就研究了一下,其实也比较简单,在 ...
- [转帖]etcd raft模块解析
https://www.cnblogs.com/luohaixian/p/16641100.html 1. Raft简介 raft是一个管理复制式日志的共识算法,它是通过复制日志的方式来保持状态机里的 ...
- [转帖]解决jmeter请求响应结果乱码的问题
如下图所示,请求百度接口的时候,发现返回的信息里面中文是乱码 这个时候我们只需要改一下jmeter里的配置文件,设置响应结果的字符编码为UTF-8就行了. 进入jmeter安装目录/bin中,找到jm ...
- vue3中beforeRouteEnter 的使用和注意点
beforeRouteEnter 在vue3中的使用 有些时候,我们需要在知道是从哪一个页面过来的. 然后做一些逻辑处理 比如说:从A->B,B页面需要调用接口,回填B页面中的数据 B--> ...
- mysql系列14---mysql数据库还原与备份
一.Liunx服务器下数据库定时备份 1.编写mysql在docker容器中备份的shell脚本: #!/bin/bash# 2020-11-15#docker启动的mysql备份mysql_use ...
- 从零开始配置vim(21)——lsp简介与treesitter 配置
截止到上一篇文章,我们配置了neovim的很多内容了.具备了一些编辑器的常用功能了,而且可以胜任日常的文档编辑工作了.但是想作为一个可靠的代码编辑器还缺少重要的一环,即代码语法部分的支持. 在过去的v ...
- 从嘉手札<2023-12-09>
大雪时节 有种风雪欲来的静谧 如同飘摇的浮舟 人们常说上岸 可对于常年生活在水里的鱼儿来说 哪里是岸边呢 我不知道未来 但唯一可以确定的是 无论你过的怎么样 你都需要给自己一个交待 哪怕风雪兼程 哪怕 ...
- iOS测试包的安装方法
iOS测试包根据要安装的机器类型可以分为2种: .app模拟器测试包 .ipa真机测试包 .app模拟器测试包的安装方式 方式一:Xcode生成安装包 1.Xcode运行项目,生成app包 2.将AP ...
- css 修改复选框的样式
效果图: 实现代码如下: /* 选中input标签类型为复选框的 */ input[type="checkbox"] { width: 16px; height: 16px; ve ...