目录
主芯片:STM32G0B1RETx G0系列芯片(内核:Cortex®-M0+)
TPCLK:64MHz
PCLK:64MHz
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);
}