STM32H7的ADC定时器触发配合定时器的DMA双缓冲
仅供个人学习
ADC基本简介
ADC_INP[0:19]和 ADC_INN[0:19]
INP 是差分正向输入,INN 是差分反向输入。
ADC_INP[0:5]和 ADC_INN[0:5]是快速通道。
ADC_INP[6:19]和 ADC_INN[6:19]是慢速通道。
adc_ext_trg[20:0]
共有 21 路触发用于规则通道,ADC1 和 ADC2 共用的,而 ADC3 是独立的。
adc_jext_trg[20:0]
共有 21 路触发用于注入通道,ADC1 和 ADC2 共用的,而 ADC3 是独立的。
adc_awd1,adc_awd2 和 adc_awd3
每个 ADC 都支持三个模拟看门狗。
adc_it
ADC 中断。
adc_hclk
ADC 的 AHB 时钟。
adc_ker_ck
ADC 的内核时钟。
adc_dma
用于 ADC 的 DMA 请求。
dac_out1,dac_out2,Vsense,Vrefint 和 Vbat
五条专用的内部通道,内部参考电压 VrefInt,内部温度传感器和 VBAT 监测通道 VBAT/4 都是连
接到 ADC3。另外内部 DAC 通道 1 和通道 2,连接到 ADC2。
ADC时钟选择
ADC 有两种时钟源可供选择,可以使用来自 AHB 总线的系统时钟(属于同步时钟,对应下面框图的adc_hclk),也可以使用PLL2,PLL3,HSE,HSI或者CSI时钟(属于异步时钟,对应下面框图的adc_ker_ck)。
ADC1,ADC2 和 ADC3 共用选择的时钟。
ADC 的时钟源使用 AHB 时钟,且使用注入模式,那么在 16bit,14bit,12bit 或者 10bit 分辨率时,ADC 的时钟不能超过 AHB 时钟的四分之一。8bit 模式时,不能超过 AHB 时钟的三分之一。
选择 AHB 时钟的话,ADC 的配置中提供了不分频,二分频和四分频。如果选择了不分频,那么配置
AHB 的时钟输出时也不可以设置分频,即 RCC 的 CFGR 寄存器配置不可分频。
如果使用 PLL 时钟,运行期间要一直开启,不可关闭
最后特别注意一点,如果 STM32H7 工作在 400MHz,ADC 使用 AHB 做时钟源,超频是不可避免的。ADC1 和 ADC2 位于 200MHz 的 AHB1 总线时钟,而 ADC3 位于 200MHz 的 AHB4 下。根据上面的框图,ADCx_CCR 寄存器的 CKMODE 最高可以选择 4 分频,那么就是 50MHz,而 ADC 数据手册限制最高是 36MHz,也就是说已经超频了。
使用 AHB 作为时钟源的好处就是定时器等外部触发方式的效果好。
ADC采样时间和转换时间
STM32H7 的 ADC 采样速度,即转换时间 = 采样时间 + 逐次逼近时间。
采样时间是可配置的,通过 ADCx_SMPR1 和 ADCx_SMPR2 寄存器中的 SMP[2:0] 位就可以编程
所有 ADC 通道,可选采样时间值如下:
SMP = 000: 1.5 个 ADC 时钟周期
SMP = 001: 2.5 个 ADC 时钟周期
SMP = 010: 8.5 个 ADC 时钟周期
SMP = 011: 16.5 个 ADC 时钟周期
SMP = 100: 32.5 个 ADC 时钟周期
SMP = 101: 64.5 个 ADC 时钟周期
SMP = 110: 387.5 个 ADC 时钟周期
SMP = 111: 810.5 个 ADC 时钟周期
不同 ADC 分辨率对应的逐次逼近时间不同,具体数值如下:
比如配置 SMP = 110,采用 16 位分辨率,那么:
ADC 的转换时间 =采样时间 + 逐次逼近时间
= 387.5 个 ADC 时钟周期 + 8.5 个 ADC 时钟周期
= 396 个 ADC 时钟周期。
ADC单次转换和连续转换
STM32H7 的 ADC 支持单次转换和连续转换。
单次转换
在单次转换模式下,ADC 会将通道的所有转换执行一次。
连续转换
该模式仅适用于常规通道。
在连续转换模式下,如果发生软件或硬件触发,ADC 会执行所有常规通道的转换,随后会自动重启并继续执行每个通道的转换。
ADC 的外部触发
STM32H7 既可以选择软件触发也可以选择外部硬件触发,并且可以设置触发边沿。
ADC多通道连接方式
ADC1,ADC2 和 ADC3 均支持 20 条通道扫描采样(注意,部分引脚是多个 ADC 共用的):
◆ 6 路快速模拟输入 (ADCx_INP[0]/INN[0] 到 ADCx_INP[5]/INN[5])
◆ 14 路慢速模拟输入 (ADCx_INP[6]/INN[6] 到 ADCx_INP[19]/INN[19])
◆ ADC 连接至 5 路内部模拟输入:
ADC配合定时器触发和DMA双缓冲配置
ADC 转换既可以选择外部触发也可以选择软件触发。定时器属于外部触发方式,使用定时器触发的好处是可以设置任何 ADC 能够支持的转换频率。
对于 ADC1,ADC2,ADC3 来说,规则通道支持的外部触发源如下:
#define ADC_EXTERNALTRIG_T1_CC1 ((uint32_t)0x00000000)
#define ADC_EXTERNALTRIG_T1_CC2 ((uint32_t)ADC_CFGR_EXTSEL_0)
#define ADC_EXTERNALTRIG_T1_CC3 ((uint32_t)ADC_CFGR_EXTSEL_1)
#define ADC_EXTERNALTRIG_T2_CC2 ((uint32_t)(ADC_CFGR_EXTSEL_1 | ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_T3_TRGO ((uint32_t)ADC_CFGR_EXTSEL_2)
#define ADC_EXTERNALTRIG_T4_CC4 ((uint32_t)(ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_EXT_IT11 ((uint32_t)(ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_1))
#define ADC_EXTERNALTRIG_T8_TRGO ((uint32_t)(ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_1 |
ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_T8_TRGO2 ((uint32_t) ADC_CFGR_EXTSEL_3)
#define ADC_EXTERNALTRIG_T1_TRGO ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_T1_TRGO2 ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_1))
#define ADC_EXTERNALTRIG_T2_TRGO ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_1 |
ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_T4_TRGO ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_2))
#define ADC_EXTERNALTRIG_T6_TRGO ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_2 |
ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_T15_TRGO ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_2 |
ADC_CFGR_EXTSEL_1))
#define ADC_EXTERNALTRIG_T3_CC4 ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_2 |
ADC_CFGR_EXTSEL_1 | ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_HR1_ADCTRG1 ((uint32_t) ADC_CFGR_EXTSEL_4)
#define ADC_EXTERNALTRIG_HR1_ADCTRG3 ((uint32_t) (ADC_CFGR_EXTSEL_4 | ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_LPTIM1_OUT ((uint32_t) (ADC_CFGR_EXTSEL_4 | ADC_CFGR_EXTSEL_1))
#define ADC_EXTERNALTRIG_LPTIM2_OUT ((uint32_t) (ADC_CFGR_EXTSEL_4 | ADC_CFGR_EXTSEL_1|
ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_LPTIM3_OUT ((uint32_t) (ADC_CFGR_EXTSEL_4 | ADC_CFGR_EXTSEL_2))
srt文件配置
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00200000 { ; load region size_region
ER_IROM1 0x08000000 0x00200000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00020000 { ; RW data - 128KB DTCM
.ANY (+RW +ZI)
}
RW_IRAM2 0x24000000 0x00080000 { ; RW data - 512KB AXI SRAM
*(.RAM_D1)
}
RW_IRAM3 0x30000000 0x00048000 { ; RW data - 128KB SRAM1(0x30000000) + 128KB SRAM2(0x3002 0000) + 32KB SRAM3(0x30040000)
*(.RAM_D2)
}
RW_IRAM4 0x38000000 0x00010000 { ; RW data - 64KB SRAM4(0x38000000)
*(.RAM_D3)
}
}
cache数据做32位对齐
ALIGN_32BYTES(__attribute__((section (".RAM_D3"))) uint16_t ADCxValues[128]);
初始化TIM1
static void TIM1_Config(void)
{
TIM_HandleTypeDef htim ={0};
TIM_OC_InitTypeDef sConfig = {0};
/* 使能时钟 */
__HAL_RCC_TIM1_CLK_ENABLE();
/*-----------------------------------------------------------------------
bsp.c 文件中 void SystemClock_Config(void) 函数对时钟的配置如下:
System Clock source = PLL (HSE)
SYSCLK(Hz) = 400000000 (CPU Clock)
HCLK(Hz) = 200000000 (AXI and AHBs Clock)
AHB Prescaler = 2
D1 APB3 Prescaler = 2 (APB3 Clock 100MHz)
D2 APB1 Prescaler = 2 (APB1 Clock 100MHz)
D2 APB2 Prescaler = 2 (APB2 Clock 100MHz)
D3 APB4 Prescaler = 2 (APB4 Clock 100MHz)
因为APB1 prescaler != 1, 所以 APB1上的TIMxCLK = APB1 x 2 = 200MHz;
因为APB2 prescaler != 1, 所以 APB2上的TIMxCLK = APB2 x 2 = 200MHz;
APB4上面的TIMxCLK没有分频,所以就是100MHz;
APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14,LPTIM1
APB2 定时器有 TIM1, TIM8 , TIM15, TIM16,TIM17
APB4 定时器有 LPTIM2,LPTIM3,LPTIM4,LPTIM5
TIM12CLK = 200MHz/(Period + 1) / (Prescaler + 1) = 200MHz / 2000 / 1 = 100KHz
----------------------------------------------------------------------- */
HAL_TIM_Base_DeInit(&htim);
htim.Instance = TIM1;
htim.Init.Period = 1999;
htim.Init.Prescaler = 0;
htim.Init.ClockDivision = 0;
htim.Init.CounterMode = TIM_COUNTERMODE_UP;
htim.Init.RepetitionCounter = 0;
HAL_TIM_Base_Init(&htim);
sConfig.OCMode = TIM_OCMODE_PWM1;
sConfig.OCPolarity = TIM_OCPOLARITY_LOW;
/* 占空比50% */
sConfig.Pulse = 1000;
if(HAL_TIM_OC_ConfigChannel(&htim, &sConfig, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
/* 启动OC1 */
if(HAL_TIM_OC_Start(&htim, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
}
初始化adc和dma
void bsp_InitADC(void)
{
ADC_HandleTypeDef AdcHandle = {0};
ADC_ChannelConfTypeDef sConfig = {0};
GPIO_InitTypeDef GPIO_InitStruct;
/* ## - 1 - 配置ADC采样的时钟 ####################################### */
#if defined (ADC_CLOCK_SOURCE_PLL)
/* 配置PLL2时钟为的72MHz,方便分频产生ADC最高时钟36MHz */
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_ADC;
PeriphClkInitStruct.PLL2.PLL2M = 25;
PeriphClkInitStruct.PLL2.PLL2N = 504;
PeriphClkInitStruct.PLL2.PLL2P = 7;
PeriphClkInitStruct.PLL2.PLL2Q = 7;
PeriphClkInitStruct.PLL2.PLL2R = 7;
PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_0;
PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE;
PeriphClkInitStruct.PLL2.PLL2FRACN = 0;
PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
#elif defined (ADC_CLOCK_SOURCE_AHB)
/* 使用AHB时钟的话,无需配置,默认选择*/
#endif
/* ## - 2 - 配置ADC采样使用的引脚 ####################################### */
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/* ## - 3 - 配置ADC采样使用的时钟 ####################################### */
__HAL_RCC_DMA1_CLK_ENABLE();
DmaHandle.Instance = DMA1_Stream1; /* 使用的DMA1 Stream1 */
DmaHandle.Init.Request = DMA_REQUEST_ADC1; /* 请求类型采用DMA_REQUEST_ADC1 */
DmaHandle.Init.Direction = DMA_PERIPH_TO_MEMORY; /* 传输方向是从外设到存储器 */
DmaHandle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设地址自增禁止 */
DmaHandle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器地址自增使能 */
DmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; /* 外设数据传输位宽选择半字,即16bit */
DmaHandle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; /* 存储器数据传输位宽选择半字,即16bit */
DmaHandle.Init.Mode = DMA_CIRCULAR; /* 循环模式 */
DmaHandle.Init.Priority = DMA_PRIORITY_LOW; /* 优先级低 */
DmaHandle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; /* 禁止FIFO*/
DmaHandle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* 禁止FIFO此位不起作用,用于设置阀值 */
DmaHandle.Init.MemBurst = DMA_MBURST_SINGLE; /* 禁止FIFO此位不起作用,用于存储器突发 */
DmaHandle.Init.PeriphBurst = DMA_PBURST_SINGLE; /* 禁止FIFO此位不起作用,用于外设突发 */
/* 初始化DMA */
if(HAL_DMA_Init(&DmaHandle) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
/* 开启DMA1 Stream1的中断 */
HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);
/* 关联ADC句柄和DMA句柄 */
__HAL_LINKDMA(&AdcHandle, DMA_Handle, DmaHandle);
/* ## - 4 - 配置ADC ########################################################### */
__HAL_RCC_ADC12_CLK_ENABLE();
AdcHandle.Instance = ADC1;
#if defined (ADC_CLOCK_SOURCE_PLL)
AdcHandle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2; /* 采用PLL异步时钟,2分频,即72MHz/2 = 36MHz */
#elif defined (ADC_CLOCK_SOURCE_AHB)
AdcHandle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; /* 采用AHB同步时钟,4分频,即200MHz/4 = 50MHz */
#endif
AdcHandle.Init.Resolution = ADC_RESOLUTION_16B; /* 16位分辨率 */
AdcHandle.Init.ScanConvMode = ADC_SCAN_DISABLE; /* 禁止扫描,因为仅开了一个通道 */
AdcHandle.Init.EOCSelection = ADC_EOC_SINGLE_CONV; /* EOC转换结束标志 */
AdcHandle.Init.LowPowerAutoWait = DISABLE; /* 禁止低功耗自动延迟特性 */
AdcHandle.Init.ContinuousConvMode = DISABLE; /* 禁止自动转换,采用的定时器触发转换 */
AdcHandle.Init.NbrOfConversion = 1; /* 使用了1个转换通道 */
AdcHandle.Init.DiscontinuousConvMode = DISABLE; /* 禁止不连续模式 */
AdcHandle.Init.NbrOfDiscConversion = 1; /* 禁止不连续模式后,此参数忽略,此位是用来配置不连续子组中通道数 */
AdcHandle.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T1_CC1; /* 定时器1的CC1触发 */
AdcHandle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; /* 上升沿触发 */
AdcHandle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR; /* DMA循环模式接收ADC转换的数据 */
AdcHandle.Init.BoostMode = ENABLE; /* ADC时钟超过20MHz的话,使能boost */
AdcHandle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; /* ADC转换溢出的话,覆盖ADC的数据寄存器 */
AdcHandle.Init.OversamplingMode = DISABLE; /* 禁止过采样 */
/* 初始化ADC */
if (HAL_ADC_Init(&AdcHandle) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
/* 校准ADC,采用偏移校准 */
if (HAL_ADCEx_Calibration_Start(&AdcHandle, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
/* 配置ADC通道 */
sConfig.Channel = ADC_CHANNEL_10; /* 配置使用的ADC通道 */
sConfig.Rank = ADC_REGULAR_RANK_1; /* 采样序列里的第1个 */
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5; /* 采样周期 */
sConfig.SingleDiff = ADC_SINGLE_ENDED; /* 单端输入 */
sConfig.OffsetNumber = ADC_OFFSET_NONE; /* 无偏移 */
sConfig.Offset = 0; /* 无偏移的情况下,此参数忽略 */
if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
/* ## - 5 - 配置ADC的定时器触发 ####################################### */
TIM1_Config();
/* ## - 6 - 启动ADC的DMA方式传输 ####################################### */
if (HAL_ADC_Start_DMA(&AdcHandle, (uint32_t *)ADCxValues, 128) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
}
配置DMA全满和半满中断失效cache读取数据
void DMA1_Stream1_IRQHandler(void)
{
/* 传输完成中断 */
if((DMA1->LISR & DMA_FLAG_TCIF1_5) != RESET)
{
HC574_TogglePin(GPIO_PIN_23);
/*
1、使用此函数要特别注意,第1个参数地址要32字节对齐,第2个参数要是32字节的整数倍。
2、进入传输完成中断,当前DMA正在使用缓冲区的前半部分,用户可以操作后半部分。
*/
SCB_InvalidateDCache_by_Addr((uint32_t *)(&ADCxValues[64]), 128);
s_DmaFlag = 2;
/* 清除标志 */
DMA1->LIFCR = DMA_FLAG_TCIF1_5;
}
/* 半传输完成中断 */
if((DMA1->LISR & DMA_FLAG_HTIF1_5) != RESET)
{
/*
1、使用此函数要特别注意,第1个参数地址要32字节对齐,第2个参数要是32字节的整数倍。
2、进入半传输完成中断,当前DMA正在使用缓冲区的后半部分,用户可以操作前半部分。
*/
SCB_InvalidateDCache_by_Addr((uint32_t *)(&ADCxValues[0]), 128);
s_DmaFlag = 1;
/* 清除标志 */
DMA1->LIFCR = DMA_FLAG_HTIF1_5;
}
/* 传输错误中断 */
if((DMA1->LISR & DMA_FLAG_TEIF1_5) != RESET)
{
/* 清除标志 */
DMA1->LIFCR = DMA_FLAG_TEIF1_5;
}
/* 直接模式错误中断 */
if((DMA1->LISR & DMA_FLAG_DMEIF1_5) != RESET)
{
/* 清除标志 */
DMA1->LIFCR = DMA_FLAG_DMEIF1_5;
}
}
放入循环中获取数据
uint32_t values;
float temp;
void bsp_GetAdcValues(void)
{
/* 当前DMA操作是后半个缓冲,读取前半个缓冲的前4个数值求平均 */
if(s_DmaFlag == 1)
{
DISABLE_INT();
s_DmaFlag = 0;
values = (ADCxValues[0] + ADCxValues[1] + ADCxValues[2] + ADCxValues[3])/4;
ENABLE_INT();
}
/* 当前DMA操作是后前个缓冲,读取后半个缓冲的前4个数值求平均 */
else if(s_DmaFlag == 2)
{
DISABLE_INT();
s_DmaFlag = 0;
values = (ADCxValues[64] + ADCxValues[65] + ADCxValues[66] + ADCxValues[67])/4;
ENABLE_INT();
}
/* 打印读出的串口值 */
temp = values *3.3 / 65536;
printf("ADCxValues = %d, %5.3f\r\n", values, temp);
}