STM32使用TIM定时触发ADC通过HAL库实现采样 模拟ADC使用DMA数据传输时发生OVR溢出和恢复DMA

目录

ADC相关配置

TIM的配置

ADC的配置

DMA的配置

ADC上溢模拟

SPI的配置

程序编写

ADC上溢恢复


主芯片:STM32G0B1RETx G0系列芯片(内核:Cortex®-M0+)

TPCLK:64MHz

PCLK:64MHz

相关资料:https://www.st.com/resource/zh/reference_manual/dm00371828-stm32g0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdfhttps://www.st.com/resource/zh/reference_manual/dm00371828-stm32g0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf

ADC相关配置

TIM的配置

本文使用TIM6,周期100us,IOC配置如下:

|-------------------------|--------------|
| Prescaler | 3 |
| Counter Mode | Up |
| Counter Period | 1599 |
| auto-reload preload | Disable |
| Trigger Event Selection | Update Event |

ADC的配置

使用ADC引脚PA3、PA5、PA6、PA7,IOC配置如下:

|------------------------------------|---------------------------------------------------------------------------------------------------|
| Clock Prescaler | Synchronous clock mode divided by 2 |
| Resolution | ADC 12-bit resolution |
| Data Alignment | Right alignment |
| Sequencer | Sequencer set to not fully configurable (固定序列) (ADC_CFGR1寄存器 位21 CHSELRMOD) |
| Scan Conversion Mode | Forward (ADC_CFGR1寄存器 位 2 SCANDIR) |
| DMA Continuous Requests | Enabled |
| End Of Conversion Selection | End of single conversion |
| Overrun behaviour | Overrun data preserved (ADC_CFGR1寄存器 位 12 OVRMOD 用于配置数据溢出的管理方式 此处配置了,如果检测到溢出, ADC_DR 寄存器会保留原有数据。) |
| 其余设置为Disabled ||
| SamplingTime Common 1 | 1.5 Cycles |
| External Trigger Conversion Source | Timer 6 Trigger Out event |
| External Trigger Conversion Edge | Trigger detection on the rising edge |
| Trigger Frequency | High frequency |

ADC周期:

IOC设置中,ADC时钟频率分频2,所以ADC_CLK = 64MHz/2 = 32MHz

设置了采样周期为1.5个周期,采样四个通道(多少个通道就需要乘多少倍),没有配置过采样(如果有过采样需要再乘以过采样倍数),且从下列表格可见12位AD的固定需要12.5个周期

因此采样一次需要时间=(1/(32M))*(1.5+12.5)*4=1.75us

DMA的配置

这个勾是自动选上的

选择模式Circular

ADC上溢模拟

++这个方法有点邪修,但还是供给作为参考,毕竟确实发生OVR上溢错误了++

方法:其他外设使用DMA通道改为ADC目前使用的DMA通道,占用了ADC使用的DMA通道,就可以让ADC发生上溢OVR错误

ADC 溢出:

如果转换后的数据未由 CPU 或 DMA 及时读取,在新转换生成数据之前,会由溢出标志 (OVR) 指示数据溢出事件。
如果因 DMA 无法及时处理 DMA 传输请求而发生溢出 (OVR=1), ADC 会停止生成 DMA 请求,新转换对应的数据不会通过 DMA 进行传输。这意味着可将传输到 RAM 的所有数据都视为有效数据。

SPI的配置

首先将SPI配置使用DMA,配置方式见下,只要配置占用DMA通道即可,发送使用DMA通道2

NUCLEO-G0B1RE STM32G0B1RET6的学习(1)------STM32CubeIDE的安装、新建工程和配置硬件SPI-CSDN博客

程序编写

spi.c中增加:用以更换Channel通道

cpp 复制代码
void SPI_ChannelChange_M7()
{
    hdma_spi1_tx.Instance = DMA1_Channel7;

    if (HAL_DMA_Init(&hdma_spi1_tx) != HAL_OK)
    {
      Error_Handler();
    }
}

void SPI_ChannelChange_M2()
{
    hdma_spi1_tx.Instance = DMA1_Channel2;//

    if (HAL_DMA_Init(&hdma_spi1_tx) != HAL_OK)
    {
      Error_Handler();
    }
}

adc.c中增加,并在main函数的while(1)循环中调用ADC_Test();,pastTickMs只是用于延时(不是死等的延时,是通过其他定时器的计数),可以替换为其他延时函数。

ADC_Test中有对GPIO的电平变换,只是为了波形显示,可以去掉。程序中的PB15显示SPI发送通道变化的时刻,PB9显示何时发生溢出错误。

在上电后的1.5s,将会发生SPI发送的DMA通道从DMA1 Channel2变更到DMA1 Channel7,再从DMA1 Channel7变更回DMA1 Channel2。

cpp 复制代码
static uint16_t adValue[4] = {0};

static uint32_t pastTickMs(uint32_t lastTick)
{
	uint32_t temp2 = 0;
	uint32_t nowTick = 0;

	nowTick = GetTick();
	if(nowTick >= lastTick)
	{
		temp2 = nowTick - lastTick;
	}
	else
	{
		temp2 = 0xffff - lastTick + nowTick ;
	}
	return temp2;
}

void ADC_Test(void)
{
	static uint8_t step = 0;
	static uint32_t lastTick = 0;

	if(step == 0)
	{
		step = 1;
        HAL_TIM_Base_Start_IT(&htim6);
		HAL_ADC_Start_DMA(&hadc1,(uint32_t*)adValue,4);

		lastTick = GetTick();
	}


	if((step == 1)&&(pastTickMs(lastTick) >= 1500))//delay 2500ms
	{
		HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_15);//PB15
		SPI_ChannelChange_M7();//2转7
		SPI_ChannelChange_M2();//7转2
		HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_15);//PB15
		step = 2;
	}

	//监控OVR标志位
	if(__HAL_ADC_GET_FLAG(&hadc1, ADC_FLAG_OVR))
	{
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9,GPIO_PIN_SET);//PB9
	}
	else
	{
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9,GPIO_PIN_RESET);//PB9
	}
}

在stm32g0xx_it.c函数中DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQHandler函数增加PB12的电平翻转,这也是为了波形显示,可以去掉。程序中的PB12显示何时发生DMA中断。

stm32g0xx_it.c文件也是有IOC配置自动编写程序的,当然也可以看出外设配置了哪个通道就会在发生哪个通道的DMA中断时,运行对应通道的DMA IRQ中断函数,比如SPI发送使用DMA1 Channel2,若DMA发生中断就会执行DMA1_Channel2_3_IRQHandler,ADC转换使用DMA1_Channel7,若DMA发生中断就会执行 DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQHandler。

因此,若只是通过在程序调用hdma_spi1_tx.Instance = DMA1_Channel7;和HAL_DMA_Init(&hdma_spi1_tx); 只能更改传输通道,不能在发生中断后调用更改后对应通道的IRQ函数。

比如以下程序,在IOC中,配置了SPI发送的通道为DMA1 Channel2,那么在程序生成后,HAL_DMA_IRQHandler(&hdma_spi1_tx);就会被放在DMA1_Channel2_3_IRQHandler(void)函数的定义中,一旦SPI发送触发中断,那就是对应DMA1 Channel2的中断,就会调用DMA1_Channel2_3_IRQHandler,调用HAL_DMA_IRQHandler(&hdma_spi1_tx);。

而后,如果在其他位置执行了hdma_spi1_tx.Instance = DMA1_Channel7;和HAL_DMA_Init(&hdma_spi1_tx);,只会把SPI发送的通道为DMA1 Channel7,那么在SPI发送触发中断,那就是对应DMA1 Channel7的中断,就会调用DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQHandler,调用HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_12);和HAL_DMA_IRQHandler(&hdma_adc1);。

而本文中只是更换通道,也没有调用SPI的发送接收函数,所以正常来说不会SPI触发DMA中断,若发生令PB12发生电平翻转,只会是ADC触发调用了DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQHandler函数

cpp 复制代码
/**
  * @brief This function handles DMA1 channel 1 interrupt.
  */
void DMA1_Channel1_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel1_IRQn 0 */

  /* USER CODE END DMA1_Channel1_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_spi1_rx);
  /* USER CODE BEGIN DMA1_Channel1_IRQn 1 */

  /* USER CODE END DMA1_Channel1_IRQn 1 */
}

/**
  * @brief This function handles DMA1 channel 2 and channel 3 interrupts.
  */
void DMA1_Channel2_3_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel2_3_IRQn 0 */

  /* USER CODE END DMA1_Channel2_3_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_spi1_tx);
  /* USER CODE BEGIN DMA1_Channel2_3_IRQn 1 */

  /* USER CODE END DMA1_Channel2_3_IRQn 1 */
}

/**
  * @brief This function handles DMA1 Ch4 to Ch7, DMA2 Ch1 to Ch5 and DMAMUX1 Overrun Interrupts.
  */
void DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 0 */
  HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_12);//进入中断
  /* USER CODE END DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_adc1);
  /* USER CODE BEGIN DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 1 */

  /* USER CODE END DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 1 */
}

在逻辑分析仪上也可以看到:

在上电后的1.5s,将会发生SPI发送的DMA通道从DMA1 Channel2变更到DMA1 Channel7,再从DMA1 Channel7变更到DMA1 Channel2。同时在这个时间点,也发生了ADC OVR溢出异常。

0-1.5s,正常发生中断:每个周期触发ADC会发生两次中断,第二次才是DMA传输完成中断,周期为100us,符合TIM6设定

把1.5s这个时间点放大去看:

在ADC发生OVR溢出后,改变PA3引脚的输入电压,adValue数组的值不再变化,也是符合参考手册所描述的,但查看ADC 数据寄存器 (ADC_DR)的值是有在变化的,即ADC还是在周期触发采样,但不再通过DMA传输出去了,只停留在寄存器上。

ADC上溢恢复

仅通过直接调用 __HAL_ADC_CLEAR_FLAG(&hadc1, ADC_FLAG_OVR); 去清除错误是无法重新启动DMA传输的,清除了也会再被置位,或者仅调用HAL_ADC_Stop_DMA(&hadc1);和HAL_ADC_Start_DMA(&hadc1,(uint32_t*)adValue,4);也是如此。需要将DMA反初始化再初始化,相当于重置了DMA才可以。而调用HAL_ADC_DeInit(&hadc1);和MX_ADC1_Init();实际上这两个函数在定义上也有对DMA进行操作,因此恢复程序如下:

cpp 复制代码
if(__HAL_ADC_GET_FLAG(&hadc1, ADC_FLAG_OVR))
{
	__HAL_ADC_CLEAR_FLAG(&hadc1, ADC_FLAG_OVR);
	HAL_ADC_Stop_DMA(&hadc1);
	HAL_ADC_DeInit(&hadc1);
	MX_ADC1_Init();
	HAL_ADC_Start_DMA(&hadc1,(uint32_t*)adValue,4);
}
相关推荐
挽天java1 小时前
Arduino/EIDE/Platform IO/Linux/MRS/Keil/STM32Cubemx配置
linux·运维·stm32
@good_good_study2 小时前
STM32 TIM通道捕获配置函数及实验
stm32·单片机
偶像你挑的噻11 小时前
15-Linux驱动开发-PWM子系统
linux·驱动开发·stm32·嵌入式硬件
Java小白,一起学习15 小时前
STM32新建工程(标准库官网下载)
stm32·单片机·嵌入式硬件
小尧嵌入式17 小时前
C++基础语法总结
开发语言·c++·stm32·单片机·嵌入式硬件·算法
2401_8534482317 小时前
STM32F103C8T6---OLED显示屏
stm32·单片机·oled
沐欣工作室_lvyiyi17 小时前
基于单片机的居家智能音箱系统(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·智能音箱
Meraki.Zhang1 天前
【STM32实践篇】:STM32CubeMX 的使用
stm32·单片机·嵌入式软件
番茄灭世神1 天前
OTA远程升级STM32固件
stm32