STM32F103RC单片机ADC1使用TIM1自动触发注入通道组的AD转换

注意:ADC外设最大允许的时钟频率为14MHz,打开ADC外设前必须先配置好分频系数!

72MHz / 6 = 12MHz,转换速率为每个1.17μs。

【程序】

#include <stdio.h>
#include <stm32f10x.h>

uint8_t injected_count = 0; // 转换的注入通道组数
uint8_t last_pos; // 定时器1停止时剩余规则通道个数(DMA位置)
uint16_t injected_result[4][5]; // 注入通道转换结果, 包括DMA位置

int fputc(int ch, FILE *fp)
{
	if (fp == stdout)
	{
		if (ch == '\n')
		{
			while ((USART1->SR & USART_SR_TXE) == 0);
			USART1->DR = '\r';
		}
		while ((USART1->SR & USART_SR_TXE) == 0);
		USART1->DR = ch;
	}
	return ch;
}

int main(void)
{
	uint8_t i, j;
	uint16_t result[10][16]; // 规则通道转换结果
	
	// 打开外设时钟
	RCC->CFGR |= RCC_CFGR_ADCPRE_1; // ADC时钟设为12MHz, 最大允许时钟为14MHz
	RCC->AHBENR = RCC_AHBENR_DMA1EN;
	RCC->APB2ENR = RCC_APB2ENR_ADC1EN | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_TIM1EN | RCC_APB2ENR_USART1EN;
	
	GPIOA->CRH = 0x444444bf; // 串口发送端设为复用推挽50MHz输出, TIM1输出比较端口1设为复用开漏50MHz输出
	GPIOA->CRL = 0x00000004; // ADC1通道1~7设为模拟
	GPIOB->CRL = 0x44444400; // ADC1通道8~9设为模拟
	
	TIM1->ARR = 15; // PWM周期: 16us
	TIM1->PSC = 71; // 计时频率: 1MHz
	TIM1->RCR = 3; // 产生4个上升沿信号
	TIM1->CR1 = TIM_CR1_OPM | TIM_CR1_URS; // 定时器自动停止; UG不置位UIF
	TIM1->EGR = TIM_EGR_UG; // 装入ARR, PSC和RCR寄存器的值
	
	TIM1->DIER = TIM_DIER_UIE; // 当定时器自动停止时触发中断
	NVIC_EnableIRQ(TIM1_UP_IRQn);
	
	TIM1->CR2 = TIM_CR2_MMS_2; // TRGO=OC1REF, 用于触发注入通道转换
	TIM1->BDTR = TIM_BDTR_MOE; // 打开输出总开关
	TIM1->CCR1 = 11; // 上升沿的产生位置
	TIM1->CCMR1 = TIM_CCMR1_OC1M; // PWM mode 2
	TIM1->CCER = TIM_CCER_CC1E; // 打开输出比较通道1
	
	// 规则通道和注入通道序列
	// SQ10: TS, SQ16: VREF
	ADC1->SQR1 = ADC_SQR1_L | (ADC_SQR1_SQ16_4 | ADC_SQR1_SQ16_0) | (ADC_SQR1_SQ15_2 | ADC_SQR1_SQ15_0) | ADC_SQR1_SQ14_2 | (ADC_SQR1_SQ13_1 | ADC_SQR1_SQ13_0);
	ADC1->SQR2 = ADC_SQR2_SQ12_1 | ADC_SQR2_SQ11_0 | ADC_SQR2_SQ10_4 | (ADC_SQR2_SQ9_3 | ADC_SQR2_SQ9_0) | ADC_SQR2_SQ8_3 | (ADC_SQR2_SQ7_2 | ADC_SQR2_SQ7_1 | ADC_SQR2_SQ7_0);
	ADC1->SQR3 = (ADC_SQR3_SQ6_2 | ADC_SQR3_SQ6_1) | (ADC_SQR3_SQ5_2 | ADC_SQR3_SQ5_0) | ADC_SQR3_SQ4_2 | (ADC_SQR3_SQ3_1 | ADC_SQR3_SQ3_0) | ADC_SQR3_SQ2_1 | ADC_SQR3_SQ1_0;
	ADC1->JSQR = ADC_JSQR_JL | ADC_JSQR_JSQ4_2 | (ADC_JSQR_JSQ3_1 | ADC_JSQR_JSQ3_0) | ADC_JSQR_JSQ2_1 | ADC_JSQR_JSQ1_0;
	
	ADC1->CR1 = ADC_CR1_SCAN | ADC_CR1_JEOCIE; // 规则通道组配置为多通道模式(必须用DMA收集数据); 当注入通道组转换完毕时触发中断
	ADC1->CR2 = ADC_CR2_TSVREFE | ADC_CR2_JEXTTRIG | ADC_CR2_DMA | ADC_CR2_CONT | ADC_CR2_ADON; // 打开16~17通道; 允许外部事件注入; 规则通道组循环转换; 打开ADC1
	NVIC_EnableIRQ(ADC1_2_IRQn);
	
	USART1->BRR = 0x271; // 波特率: 115200
	USART1->CR1 = USART_CR1_UE | USART_CR1_TE; // 允许发送
	
	// 配置ADC1_DMA
	DMA1_Channel1->CMAR = (uint32_t)result[0];
	DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR;
	DMA1_Channel1->CNDTR = 160;
	DMA1_Channel1->CCR = DMA_CCR1_MSIZE_0 | DMA_CCR1_PSIZE_0 | DMA_CCR1_MINC | DMA_CCR1_EN;
	
	ADC1->CR2 = ADC1->CR2; // 开始转换规则通道
	TIM1->CR1 |= TIM_CR1_CEN; // 开启定时器1
	while ((DMA1->ISR & DMA_ISR_TCIF1) == 0) // 若DMA正在搬运数据
	{
		if (DMA1_Channel1->CNDTR < 16 && (ADC1->CR2 & ADC_CR2_CONT)) // 转换最后一组时关闭CONT循环模式, 以免产生新数据使EOC不为0
			ADC1->CR2 &= ~ADC_CR2_CONT;
	}
	
	// 显示定时器1自动关闭时DMA的位置
	if ((TIM1->CR1 & TIM_CR1_CEN) == 0)
		printf("TIM1 stopped! CNDTR=%d\n", last_pos);
	
	// 显示规则通道的数据
	for (i = 0; i < 10; i++)
	{
		printf("[Regular %d]", i + 1);
		for (j = 0; j < 16; j++)
			printf(" %d", result[i][j]);
		printf("\n");
	}
	
	// 显示注入通道的数据
	for (i = 0; i < injected_count; i++)
	{
		printf("[Injected %d]", i + 1);
		for (j = 1; j <= 4; j++)
			printf(" %d", injected_result[i][j]);
		printf(" (CNDTR=%d)\n", injected_result[i][0]);
	}
	
	ADC1->SR &= ~ADC_SR_STRT;
	printf("ADC1->SR=0x%02x\n", ADC1->SR);
	
	// 等待USART1发送完毕并关闭外设
	while ((USART1->SR & USART_SR_TC) == 0);
	USART1->CR1 = 0;
	ADC1->CR2 = 0;
	
	// 进入Stop低功耗模式
	SCB->SCR = SCB_SCR_SLEEPDEEP;
	while (1)
		__WFI();
}

// 注入通道组转换完成时触发的中断
void ADC1_2_IRQHandler(void)
{
	ADC1->SR &= ~(ADC_SR_EOC | ADC_SR_JEOC | ADC_SR_JSTRT);
	injected_result[injected_count][0] = DMA1_Channel1->CNDTR;
	injected_result[injected_count][1] = ADC1->JDR1;
	injected_result[injected_count][2] = ADC1->JDR2;
	injected_result[injected_count][3] = ADC1->JDR3;
	injected_result[injected_count][4] = ADC1->JDR4;
	injected_count++;
}

void TIM1_UP_IRQHandler(void)
{
	TIM1->SR &= ~TIM_SR_UIF; // 清除中断标志
	last_pos = DMA1_Channel1->CNDTR; // 记录DMA的位置
	ADC1->CR2 &= ~ADC_CR2_JEXTTRIG; // 禁止外部触发注入通道组的转换
	TIM1->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_0; // 熄灭PA8上的LED灯
	// 不能在这里执行printf, 否则CONT位将无法及时清除而产生新数据
}
【运行结果】
TIM1 stopped! CNDTR=122
[Regular 1] 1638 1714 598 225 1566 3085 3572 1834 3469 1576 1636 1716 598 225 1565 1770
[Regular 2] 1639 1714 597 224 1568 3084 3569 1832 3468 2157 1638 1715 597 225 1564 1772
[Regular 3] 1638 1713 597 224 1567 3084 3570 1833 3469 2159 1638 1713 597 225 1567 1773
[Regular 4] 1637 1714 597 225 1567 3085 3571 1834 3469 2158 1639 1715 598 224 1566 1771
[Regular 5] 1638 1715 598 224 1566 3087 3568 1834 3470 2157 1638 1714 596 223 1569 1766
[Regular 6] 1638 1714 598 224 1567 3085 3570 1832 3470 2158 1638 1715 598 225 1567 1773
[Regular 7] 1638 1715 597 223 1565 3086 3569 1832 3469 2158 1637 1714 598 224 1566 1772
[Regular 8] 1638 1714 598 225 1565 3085 3568 1833 3469 2158 1638 1715 598 225 1566 1770
[Regular 9] 1638 1714 598 225 1568 3084 3571 1833 3470 2158 1638 1714 597 224 1565 1771
[Regular 10] 1638 1714 597 224 1566 3084 3569 1833 3469 2158 1638 1713 598 224 1568 1765
[Injected 1] 1636 1714 597 224 (CNDTR=151)
[Injected 2] 1637 1715 597 224 (CNDTR=142)
[Injected 3] 1636 1714 596 224 (CNDTR=133)
[Injected 4] 1638 1714 597 225 (CNDTR=124)
ADC1->SR=0x00

在上面的程序中,TIM1的计数方向为向上计数,每次比较匹配时都触发一次注入。事件源为TIM1_TRGO事件,JEXTSEL=000。

如果计数方向既向上又向下,则只有向上的那一次比较匹配才能触发注入,向下的那一次无效,因为只有TRGO=OCxREF信号的上升沿能够触发ADC注入。

对于TIMn_TRGO类事件,若TRGO=OCxREF,则只有OCxREF的上升沿才能触发注入,与CCxP的值无关。

对于TIMn_CCx类事件,若CCxP=0,则是OCxREF的上升沿触发注入;若CCxP=1,则是OCxREF的下降沿触发注入。对应的GPIO口输出的电平为OCxREF ^ CCxP。


当SCAN=0时,L和JL的值无效,ADC始终只转换序列中的第一个通道。

当SCAN=1时,ADC转换组中所有的通道,通道的个数由L或JL指定。整个通道组转换完成时EOC才置1,此时DR中的内容为最后一个通道的数据,读DR寄存器后EOC自动清零。所以只要SCAN=1,规则通道的数据就只能由DMA来采集。如果打开了EOCIE中断,则在中断中无需手动清除EOC标志。因为DMA读取最后一个通道的数据后,EOC会自动清零。

CONT(循环模式)只对规则通道有效,对注入通道无效。


如果JAUTO=1,则注入通道组的转换不需要由外部信号触发。规则通道组转换完成后将会自动转换注入通道组。

#include <stdio.h>
#include <stm32f10x.h>

uint8_t injected_count = 0; // 转换的注入通道组数
uint16_t injected_result[10][5]; // 注入通道转换结果, 包括DMA位置

void delay(void)
{
	uint32_t i;
	for (i = 0; i < 2000000; i++);
}

int fputc(int ch, FILE *fp)
{
	if (fp == stdout)
	{
		if (ch == '\n')
		{
			while ((USART1->SR & USART_SR_TXE) == 0);
			USART1->DR = '\r';
		}
		while ((USART1->SR & USART_SR_TXE) == 0);
		USART1->DR = ch;
	}
	return ch;
}

int main(void)
{
	uint8_t i, j;
	uint16_t result[10][16]; // 规则通道转换结果
	
	// 打开外设时钟
	RCC->CFGR |= RCC_CFGR_ADCPRE_1; // ADC时钟设为12MHz, 最大允许时钟为14MHz
	RCC->AHBENR = RCC_AHBENR_DMA1EN;
	RCC->APB1ENR = RCC_APB1ENR_PWREN;
	RCC->APB2ENR = RCC_APB2ENR_ADC1EN | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_USART1EN;
	
	GPIOA->CRH = 0x444444b4; // 串口发送端设为复用推挽50MHz输出
	GPIOA->CRL = 0x00000008; // ADC1通道1~7设为模拟, PA0待机唤醒按键设为带下拉电阻输入
	GPIOB->CRL = 0x44444400; // ADC1通道8~9设为模拟
	
	// 规则通道和注入通道序列
	// SQ10: TS, SQ16: VREF
	ADC1->SQR1 = ADC_SQR1_L | (ADC_SQR1_SQ16_4 | ADC_SQR1_SQ16_0) | (ADC_SQR1_SQ15_2 | ADC_SQR1_SQ15_0) | ADC_SQR1_SQ14_2 | (ADC_SQR1_SQ13_1 | ADC_SQR1_SQ13_0);
	ADC1->SQR2 = ADC_SQR2_SQ12_1 | ADC_SQR2_SQ11_0 | ADC_SQR2_SQ10_4 | (ADC_SQR2_SQ9_3 | ADC_SQR2_SQ9_0) | ADC_SQR2_SQ8_3 | (ADC_SQR2_SQ7_2 | ADC_SQR2_SQ7_1 | ADC_SQR2_SQ7_0);
	ADC1->SQR3 = (ADC_SQR3_SQ6_2 | ADC_SQR3_SQ6_1) | (ADC_SQR3_SQ5_2 | ADC_SQR3_SQ5_0) | ADC_SQR3_SQ4_2 | (ADC_SQR3_SQ3_1 | ADC_SQR3_SQ3_0) | ADC_SQR3_SQ2_1 | ADC_SQR3_SQ1_0;
	ADC1->JSQR = ADC_JSQR_JL | ADC_JSQR_JSQ4_2 | (ADC_JSQR_JSQ3_1 | ADC_JSQR_JSQ3_0) | ADC_JSQR_JSQ2_1 | ADC_JSQR_JSQ1_0;
	
	ADC1->CR1 = ADC_CR1_JAUTO | ADC_CR1_SCAN | ADC_CR1_JEOCIE; // 规则通道组转换完毕后自动开始转换注入通道组; 规则通道组配置为多通道模式(必须用DMA收集数据); 当注入通道组转换完毕时触发中断
	ADC1->CR2 = ADC_CR2_TSVREFE | ADC_CR2_DMA | ADC_CR2_CONT | ADC_CR2_ADON; // 打开16~17通道; 规则通道组循环转换; 打开ADC1
	NVIC_EnableIRQ(ADC1_2_IRQn);
	
	USART1->BRR = 0x271; // 波特率: 115200
	USART1->CR1 = USART_CR1_UE | USART_CR1_TE; // 允许发送
	
	// 配置ADC1_DMA
	DMA1_Channel1->CMAR = (uint32_t)result[0];
	DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR;
	DMA1_Channel1->CNDTR = 160;
	DMA1_Channel1->CCR = DMA_CCR1_MSIZE_0 | DMA_CCR1_PSIZE_0 | DMA_CCR1_MINC | DMA_CCR1_EN;
	
	ADC1->CR2 = ADC1->CR2; // 开始转换规则通道
	while ((DMA1->ISR & DMA_ISR_TCIF1) == 0) // 若DMA正在搬运数据
	{
		if (DMA1_Channel1->CNDTR < 16 && (ADC1->CR2 & ADC_CR2_CONT)) // 转换最后一组时关闭CONT循环模式, 以免产生新数据使EOC不为0
			ADC1->CR2 &= ~ADC_CR2_CONT;
	}
	
	// 显示规则通道的数据
	for (i = 0; i < 10; i++)
	{
		printf("[Regular %d]", i + 1);
		for (j = 0; j < 16; j++)
			printf(" %d", result[i][j]);
		printf("\n");
	}
	
	// 显示注入通道的数据
	for (i = 0; i < injected_count; i++)
	{
		printf("[Injected %d]", i + 1);
		for (j = 1; j <= 4; j++)
			printf(" %d", injected_result[i][j]);
		printf(" (CNDTR=%d)\n", injected_result[i][0]);
	}
	
	ADC1->SR &= ~ADC_SR_STRT;
	printf("ADC1->SR=0x%02x\n", ADC1->SR);
	
	// 进入待机模式
	while ((USART1->SR & USART_SR_TC) == 0); // 等待USART1发送完毕
	while (GPIOA->IDR & GPIO_IDR_IDR0)
		delay(); // WKUP按键消抖
	SCB->SCR = SCB_SCR_SLEEPDEEP;
	PWR->CR = PWR_CR_PDDS | PWR_CR_CWUF;
	PWR->CSR = PWR_CSR_EWUP;
	__WFI();
	
	return 0;
}

// 注入通道组转换完成时触发的中断
void ADC1_2_IRQHandler(void)
{
	ADC1->SR &= ~(ADC_SR_EOC | ADC_SR_JEOC | ADC_SR_JSTRT);
	injected_result[injected_count][0] = DMA1_Channel1->CNDTR;
	injected_result[injected_count][1] = ADC1->JDR1;
	injected_result[injected_count][2] = ADC1->JDR2;
	injected_result[injected_count][3] = ADC1->JDR3;
	injected_result[injected_count][4] = ADC1->JDR4;
	injected_count++;
}
【运行结果】
[Regular 1] 1636 1713 597 224 1565 3084 3570 1833 3471 2083 1638 1713 597 224 1565 1772
[Regular 2] 1637 1713 597 224 1566 3084 3569 1832 3470 2158 1637 1714 597 223 1566 1773
[Regular 3] 1639 1713 597 222 1568 3084 3572 1833 3467 2157 1636 1713 596 223 1564 1771
[Regular 4] 1637 1713 596 223 1565 3085 3569 1833 3470 2158 1638 1713 597 224 1566 1771
[Regular 5] 1637 1713 596 225 1566 3084 3570 1833 3470 2158 1637 1713 596 223 1567 1772
[Regular 6] 1637 1713 596 223 1567 3086 3569 1833 3469 2157 1637 1714 595 223 1565 1771
[Regular 7] 1637 1713 597 224 1565 3086 3568 1834 3470 2158 1637 1714 597 225 1566 1771
[Regular 8] 1637 1713 596 225 1565 3085 3569 1832 3470 2158 1637 1713 597 223 1567 1774
[Regular 9] 1637 1713 596 223 1567 3084 3569 1832 3470 2158 1638 1713 596 224 1565 1771
[Regular 10] 1637 1713 597 224 1564 3085 3569 1833 3470 2158 1637 1713 597 224 1565 1771
[Injected 1] 1638 1714 597 224 (CNDTR=144)
[Injected 2] 1637 1713 596 224 (CNDTR=128)
[Injected 3] 1637 1715 596 223 (CNDTR=112)
[Injected 4] 1638 1714 595 223 (CNDTR=96)
[Injected 5] 1637 1713 597 223 (CNDTR=80)
[Injected 6] 1637 1713 597 223 (CNDTR=64)
[Injected 7] 1638 1715 597 224 (CNDTR=48)
[Injected 8] 1638 1712 596 222 (CNDTR=32)
[Injected 9] 1637 1713 597 222 (CNDTR=16)
[Injected 10] 1637 1714 597 226 (CNDTR=0)
ADC1->SR=0x00

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是基于STM32CubeMX和HAL库的代码实现: 1. 配置TIM1ADC时钟 ``` /* TIM1 clock enable */ __HAL_RCC_TIM1_CLK_ENABLE(); /* ADC1 clock enable */ __HAL_RCC_ADC1_CLK_ENABLE(); ``` 2. 配置GPIO引脚和时钟 ``` /* Configure GPIO pin : PA8 */ GPIO_InitStruct.Pin = GPIO_PIN_8; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOA_CLK_ENABLE(); ``` 3. 配置TIM1为输入捕获模式,配置ADC为单通道转换模式 ``` /* TIM1 configuration */ htim1.Instance = TIM1; htim1.Init.Prescaler = 0; htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 65535; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; HAL_TIM_IC_Init(&htim1); /* Configure the Input Capture channel */ sConfig.ICPrescaler = TIM_ICPSC_DIV1; sConfig.ICFilter = 0; sConfig.ICPolarity = TIM_ICPOLARITY_RISING; sConfig.ICSelection = TIM_ICSELECTION_DIRECTTI; HAL_TIM_IC_ConfigChannel(&htim1, &sConfig, TIM_CHANNEL_1); /* ADC configuration */ hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1_CC1; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; HAL_ADC_Init(&hadc1); /* Configure Regular Channel */ sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5; HAL_ADC_ConfigChannel(&hadc1, &sConfig); ``` 4. 配置TIM1触发源和开启TIM1ADC ``` /* Configure the TIM1 trigger */ sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE; HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig); /* Start TIM1 */ HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1); /* Start ADC conversion */ HAL_ADC_Start(&hadc1); ``` 5. 在TIM1的输入捕获中断中进行ADC采样 ``` void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM1) { /* Start ADC conversion */ HAL_ADC_Start(&hadc1); } } ``` 6. 在ADC转换完成中断中获取采样值 ``` void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { /* Get ADC value */ uint16_t adc_value = HAL_ADC_GetValue(hadc); /* Do something with the ADC value */ ... } ``` 以上是基于STM32CubeMX和HAL库的代码实现,需要根据实际情况进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值