Keil MDK STM32系列

配置 ADC

  • 模式: 如果只启用了一个ADC, 这里只能配置为Independent mode
  • 时钟分频: 这个选项是ADC的预分频器, 可设置为2/4/6/8, 决定了一个ADC时钟周期. 加入设置为2, 由于ADC是挂载在APB2总线(84M)上, 所以一个ADC时钟便是84 * M/2=42M
  • 分辨率: 最高为12位分辨率, 分辨率越高转换时间越长
  • 数据对齐方式: 如果选择12位分辨率, 右对齐, 得到的结果最大便是4096.
  • 扫描模式: 转换完一个通道会不会继续转换下一个通道
  • 连续转换模式: 使能的话转换将连续进行
  • 不连续转换模式: 当使能多个转换通道时, 可单独设置不连续转换通道.
  • DMA连续请求: 是否连续请求DMA.
  • EOC标志设置: 当有多个转换通道时, 是每转换完一个通道设置一次EOC标志还是所有通道都转换完设置一次EOC标志.
  • 转换的通道数:
  • 触发模式: 可选择软件触发, 外部触发或定时器事件触发
  • 秩序列表: 设置转换周期数和转换顺序
  • 注入通道设置
  • 窗口看门狗模式

配置 ADC 为主动请求模式

while (1)
{
/*##-1- Start the conversion process #######################################*/
HAL_ADC_Start(&hadc1); /*##-2- Wait for the end of conversion #####################################*/
/* Before starting a new conversion, you need to check the current state of
the peripheral; if it’s busy you need to wait for the end of current
conversion before starting a new one.
For simplicity reasons, this example is just waiting till the end of the
conversion, but application may perform other tasks while conversion
operation is ongoing. */
HAL_ADC_PollForConversion(&hadc1, 50); /* Check if the continous conversion of regular channel is finished */
if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1), HAL_ADC_STATE_REG_EOC))
{
/*##-3- Get the converted value of regular channel ######################*/
AD_Value = HAL_ADC_GetValue(&hadc1);
printf("MCU Temperature : %.1f¡æ\r\n",((AD_Value*3300/4096-760)/2.5+25));
}
HAL_Delay(1000);
}

配置 ADC 为多通道连续扫描DMA模式

  • 开ADC的IN0/IN1两个通道

    • 在Pinout图上, 将PA0和PA1设为ADC1_IN0和ADC2_IN1
  • 配置时钟

  • ADC1相关配置

  • ADCs_Common_Settings

    • Mode: Independent mode
  • ADC_Settings

    • Clock Prescaler: PCLK2 divided by 4 可以在时钟配置页看到PCLK2的值
    • Resolution: 12bits (15 ADC Clock cycles) 采样精度12bit, 此时每次采样需要15个时钟周期, 8bit对应11个时钟周期
    • Data Alignment: Right alignment
    • Scan Conversion Mode: Enabled
    • Continuous Conversion Mode: Enabled --> for DMA
    • Discontinuous Conversion Mode: Disabled
    • DMA Continuous Requests: Enabled
    • End Of Conversion Selection: EOC flag at the end of single channel conversion
  • ADC_Regular_ConversionMode

    • Number of Conversion: 2 --> 2 channels
    • External Trigger Conversion Source: Regular Conversion launched by software
    • External Trigger Conversion Edge: None
    • Rank: 1: Choose channel 0
    • Rank: 2: Choose channel 1
  • ADC_Injected_ConversionMode

    • Number of Conversions: 0
  • DMA相关配置

  • ADC1

    • Stream: DMA2 Stream 4
    • Direction: Peripheral To Memory
    • Priority: High
  • DMA Request Settings

    • Mode: Circular
    • Increment Address: Memory
    • Datawidth: Peripheral->Half Word, Memory->Half Word
  • NVIC Settings

    • ADC1 global interrupt: Enabled unchecked
    • DMA2 stream4 global interrupt: Enabled checked

ADC+DMA配置, 体现在代码上的变化

  1. stm32f4xx_hal_conf.h 去掉了ADC的注释
#define HAL_ADC_MODULE_ENABLED
  1. stm32f4xx_it.h 增加了方法声明
void DMA2_Stream4_IRQHandler(void);
  1. stm32f4xx_it.c 增加了对应的typeDef和方法定义
extern DMA_HandleTypeDef hdma_adc1;

void DMA2_Stream4_IRQHandler(void)
{
HAL_DMA_IRQHandler(&hdma_adc1);
}
  1. stm32f4xx_hal_msp.c
extern DMA_HandleTypeDef hdma_adc1;

void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(hadc->Instance==ADC1)
{
__HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE();
/**ADC1 GPIO Configuration
PA0-WKUP ------> ADC1_IN0
PA1 ------> ADC1_IN1
*/
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* ADC1 DMA Init */
/* ADC1 Init */
hdma_adc1.Instance = DMA2_Stream4;
hdma_adc1.Init.Channel = DMA_CHANNEL_0;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
{
Error_Handler();
} __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc1);
} } void HAL_ADC_MspDeInit(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance==ADC1)
{
/* Peripheral clock disable */
__HAL_RCC_ADC1_CLK_DISABLE(); /**ADC1 GPIO Configuration
PA0-WKUP ------> ADC1_IN0
PA1 ------> ADC1_IN1
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0|GPIO_PIN_1); /* ADC1 DMA DeInit */
HAL_DMA_DeInit(hadc->DMA_Handle);
} }
  1. main.c
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1; static void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0}; hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 2;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = 2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
} /**
* Enable DMA controller clock
*/
static void MX_DMA_Init(void)
{ __HAL_RCC_DMA2_CLK_ENABLE(); HAL_NVIC_SetPriority(DMA2_Stream4_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream4_IRQn);
}

最后, 在main.c中增加用于存储DMA数据的数组, 将数组地址传给HAL_ADC_Start_DMA()开启DMA传输就可以得到数据了.

DMA数组大小和中断的问题

数组的大小与HAL_ADC_Start_DMA()方法第三个参数length一致, 这里length代表的是数据的个数. 在设置这个大小时, 如果开启了DMAx_Streamx_IRQn的中断, 要考虑sConfig.SamplingTime指定的采样时间不能太短, 太短的话会一直卡在中断里(因为中断什么都不做也需要时间). 这个与SYSCLK大小无关, 在两个通道采样时

  • 如果这里指定的值为ADC_SAMPLETIME_3CYCLES, 这个数组大小至少为6, 如果等于4采样循环会卡住
  • 如果指定的值为ADC_SAMPLETIME_15CYCLES, 这个数组大小至少为4
  • 如果指定的值为ADC_SAMPLETIME_28CYCLES, 数组大小可以为2

如果不需要使用DMA中断, 可以在 MX_DMA_Init()方法中, 将HAL_NVIC_EnableIRQ(DMA2_Stream4_IRQn);这句注释掉或者改成HAL_NVIC_DisableIRQ(DMA2_Stream4_IRQn);指定禁用它, 这个数组就可以设到最小(和采样通道数一致)了.

uint16_t ADC_Value[6];

main(void) {
HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC_Value, 6); // Enable DMA transfer
while (1)
{
printf("%d %d %d %d\r\n",
ADC_Value[0], ADC_Value[1], ADC_Value[2], ADC_Value[3]);
HAL_Delay(100);
}
}

DMA中断处理回调

查看代码可以看到, 在stm32f4xxx_hal_dma.h中, 定义的 DMA_HandleTypeDef 类型中, 包含了几个对应中断的处理方法

typedef struct __DMA_HandleTypeDef
{
DMA_Stream_TypeDef *Instance; /*!< Register base address */
DMA_InitTypeDef Init; /*!< DMA communication parameters */
HAL_LockTypeDef Lock; /*!< DMA locking object */
__IO HAL_DMA_StateTypeDef State; /*!< DMA transfer state */
void *Parent; /*!< Parent object state */
void (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer complete callback */
void (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA Half transfer complete callback */
void (* XferM1CpltCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer complete Memory1 callback */
void (* XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer Half complete Memory1 callback */
void (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer error callback */
void (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA transfer Abort callback */
__IO uint32_t ErrorCode; /*!< DMA Error code */
uint32_t StreamBaseAddress; /*!< DMA Stream Base Address */
uint32_t StreamIndex; /*!< DMA Stream Index */
}DMA_HandleTypeDef;

其中处理接收完成的方法是 XferCpltCallback , 这个在 stm32f4xx_hal_adc.c 中, 被指定为相应的静态方法

stm32f4xx_hal_adc.c

HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length)
{
//...
/* Set the DMA transfer complete callback */
hadc->DMA_Handle->XferCpltCallback = ADC_DMAConvCplt;

对应不同外设, 指定的方法是不同的, 例如对于uart, stm32f4xx_hal_uart.c中指定的是

HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
//...
/* Set the UART DMA transfer complete callback */
huart->hdmatx->XferCpltCallback = UART_DMATransmitCplt;

对于ADC, 再进一步在ADC_DMAConvCplt()方法中定义了处理方法

static void ADC_DMAConvCplt(DMA_HandleTypeDef *hdma)
{
//...
/* Conversion complete callback */
#if (USE_HAL_ADC_REGISTER_CALLBACKS == 1)
hadc->ConvCpltCallback(hadc);
#else
HAL_ADC_ConvCpltCallback(hadc);
#endif /* USE_HAL_ADC_REGISTER_CALLBACKS */
}
else /* DMA and-or internal error occurred */
{
if ((hadc->State & HAL_ADC_STATE_ERROR_INTERNAL) != 0UL)
{
/* Call HAL ADC Error Callback function */
#if (USE_HAL_ADC_REGISTER_CALLBACKS == 1)
hadc->ErrorCallback(hadc);
#else
HAL_ADC_ErrorCallback(hadc);
#endif /* USE_HAL_ADC_REGISTER_CALLBACKS */
}
else
{
/* Call DMA error callback */
hadc->DMA_Handle->XferErrorCallback(hdma);
}
}
}

所以, 开发时只需要定义 HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) 和 HAL_ADC_ErrorCallback(ADC_HandleTypeDef* hadc)方法, 就能处理DMA传输完成的中断

参考

Keil MDK STM32系列(六) 基于抽象外设库HAL的ADC模数转换的更多相关文章

  1. Keil MDK STM32系列(四) 基于抽象外设库HAL的STM32F401开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  2. Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  3. Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  4. Keil MDK STM32系列(三) 基于标准外设库SPL的STM32F407开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  5. Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  6. Keil MDK STM32系列(七) STM32F4基于HAL的PWM和定时器

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  7. Keil MDK STM32系列(八) STM32F4基于HAL的PWM和定时器输出音频

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  8. Keil MDK STM32系列(五) 使用STM32CubeMX创建项目基础结构

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  9. AIR32F103(三) Linux环境基于标准外设库的项目模板

    目录 AIR32F103(一) 合宙AIR32F103CBT6开发板上手报告 AIR32F103(二) Linux环境和LibOpenCM3项目模板 AIR32F103(三) Linux环境基于标准外 ...

随机推荐

  1. Android工具-DDMS

    原创文章,如有转载,请注明出处:http://blog.csdn.net/yihui823/article/details/6686578 本文章的前提:已经安装了Eclipse和ADT.androi ...

  2. [BUUCTF]REVERSE——刮开有奖

    刮开有奖 附件 步骤: 例行检查,无壳,32位程序 32位ida载入,shift+f12检索程序里的字符串,看到了一个base64加密的特征字符串,猜想这题用到了base64加密 从main函数开始看 ...

  3. 初探Windows用户态调试机制

    我们在感叹Onlydbg强大与便利的同时,是否考虑过它实现的原理呢? 作为一个技术人员知其然必知其所以然,这才是我们追求的本心. 最近在学习张银奎老师的<软件调试>,获益良多.熟悉Wind ...

  4. 录入任务信息(Project)

    <Project2016 企业项目管理实践>张会斌 董方好 编著 日历设置好了,就该录入任务了.当然在录入任务之前还要对任务进行一下面分解,就是一个项目,要分几个大步完成,每个大步又分几个 ...

  5. CF975A Aramic script 题解

    Content 定义一个字符串的根为字符串中不同种类的字符按字典序非降序排列得到的字符串.例如 \(\texttt{aaa}\) 的词根为 \(\texttt{a}\),\(\texttt{babb} ...

  6. LuoguP7043 「MCOI-03」村国 题解

    Content 有 \(T\) 组询问,每组询问给定一个有 \(n\) 个节点的数,编号为 \(1\sim n\),每个节点一开始都有权值 \(a_i\).现有 \(m\) 次操作,每次操作选择树上所 ...

  7. Sublime Text3 Package Control Emmet插件安装

    https://www.cnblogs.com/carrie-hong/p/4995735.html https://www.cnblogs.com/tamato-jacob-wealllostcon ...

  8. nim_duilib之virtualListbox用法(22)

    概述 本文将介绍virtualListbox的用法. 更多请参考源码. 一个样式 样式丑了点,勿喷. 重写函数 使用virtualListbox, 需要一个派生类(继承自基类VirtualListIn ...

  9. lldb调试C++总结(3)

    note 本文将弥补之前的遗漏部分. continue 前面提到,当设置断点后,使用step和next和finish,程序会停下来,需要程序继续运行,键入continue, 程序可自动继续向下执行. ...

  10. 【LeetCode】424. 替换后的最长重复字符 Longest Repeating Character Replacement(Python)

    作者: 负雪明烛 id: fuxuemingzhu 公众号:每日算法题 本文关键词:LeetCode,力扣,算法,算法题,字符串,双指针,刷题群 目录 题目描述 题目大意 解题方法 双指针 代码 欢迎 ...