STM32CubeMX 시리즈 05 - ADC(폴링, 인터럽트, DMA)

====>>> 기사 요약(코드 요약 포함) <<<====

1. 하드웨어 사용

지키는 아톰 미니 보드, 메인 제어 STM32F103RCT6.

사용된 주변기기:

  1. 직렬 포트 1(PA9, PA10)
  2. 임의 개수의 GPIO 포트(여기서는 PA1, PA2 및 PA3가 사용되며 ADC 채널 1, 2 및 3에 해당함).

2. 프로젝트 생성

2.1 프로젝트 선택 마스터 생성

여기에 이미지 설명 삽입

2.2 시스템 구성

클록 소스 구성
여기에 이미지 설명 삽입
디버그 모드 구성(ST-Link 다운로드 및 디버그가 필요한 경우 확인할 수 있음)
여기에 이미지 설명 삽입
클록 트리 구성(HCLK에 직접 72를 입력한 다음 Enter 키를 눌러 자동 구성 가능)
여기에 이미지 설명 삽입

마지막 ADC 클록에 주목하십시오. 최대 클록 주파수는 14MHZ이므로 여기에서 6으로 나눈 주파수는 14보다 약간 작습니다.

2.3 구성 프로젝트 디렉토리

여기에 이미지 설명 삽입여기에 이미지 설명 삽입

2.4 구성에 사용되는 주변 장치

직렬 포트 1 구성(출력 결과용)
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입

3. ADC 구성(4개 중 1개 선택)

다음과 같은 상황이 있습니다.

  1. 단일 채널 폴링
  2. 단일 채널 인터럽트
  3. 다중 채널 폴링
  4. DMA 모드(단일 채널, 다중 채널 사용 가능)

설정 지침:

  • ADC_설정:
    • 데이터 정렬:
      • Right alignment: 변환 결과 데이터는 오른쪽 정렬이며 일반적으로 오른쪽 정렬 모드를 선택합니다.
      • 왼쪽 정렬 변환 결과 데이터는 왼쪽 정렬됩니다.
    • 스캔 변환 모드:
      • 비활성화됨 스캔 모드를 비활성화합니다. 단일 채널 AD 변환인 경우 DISABLE을 사용합니다.
      • Enabled 开启扫描模式. 다중 채널 AD 변환인 경우 ENABLE을 사용하십시오.
    • 연속 변환 모드:
      • Disabled 单次转换. 변환 후 중지하려면 변환을 다시 시작하기 위해 수동 제어가 필요합니다.
      • 자동 연속 변환을 활성화했습니다.
    • 불연속 전환 모드:
      • Disabled 禁止间断模式. 전력 소모를 고려해야 하는 제품, 즉 특정 이벤트가 발생하면 변환이 켜지는 제품에 필요합니다.
      • 불연속 모드를 활성화하려면 활성화합니다.
  • ADC_Regular_ConversionMode:
    • Enable Regular Conversions규칙 변환을 활성화할지 여부입니다.
    • Number Of ConversionADC 변환 채널의 개수는 있는 만큼 쓰시면 됩니다.
    • External Trigger Conversion Source외부 트리거 선택. 이에 대한 여러 옵션이 있으며 일반적으로 소프트웨어 트리거가 사용됩니다.
  • 계급:
    • ChannelADC 변환 채널
    • Sampling Time샘플링 기간 선택, 샘플링 기간이 짧을수록 ADC 변환 데이터 출력 기간은 짧아지지만 데이터 정확도가 낮을수록 샘플링 기간이 길어지고 ADC 변환 데이터 출력 기간이 길어지고 데이터 정확도가 높아집니다.
  • ADC_Injected_ConversionMode:
    Enable Injected Conversions 주입 변환 활성화 여부. 주입 채널은 일반 채널이 있는 경우에만 나타납니다.
  • WatchDog: Enable Analog WatchDog Mode 아날로그 감시 인터럽트를 활성화할지 여부. ADC에 의해 변환된 아날로그 전압이 낮은 임계값 아래로 떨어지거나 높은 임계값 위로 떨어지면 인터럽트가 발생합니다.

3.1 단일 채널 폴링

1단계: ADC 구성
여기에 이미지 설명 삽입
2단계: 코드 생성 클릭

세 번째 단계: 직렬 포트 리디렉션 , usart.c에 다음 코드를 추가합니다. 자세한 내용은 직렬 포트 사용 에 대한 이전 기사를 참조하십시오.

// 需要调用stdio.h文件
#include <stdio.h>
//取消ARM的半主机工作模式
#pragma import(__use_no_semihosting)//标准库需要的支持函数                 
struct __FILE 
{
    
     
	int handle; 
}; 
FILE __stdout;       
void _sys_exit(int x) //定义_sys_exit()以避免使用半主机模式
{
    
     
	x = x;
} 

int fputc(int ch, FILE *f)
{
    
      
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
	return ch;
}

4단계: main.c 코드 작성, 다른 것은 변경할 필요 없음

  while (1)
  {
    
    
		// 开启ADC
		HAL_ADC_Start(&hadc1);
		// 开始轮询转换
		HAL_ADC_PollForConversion(&hadc1,100);
		// 存储转换的值
		float value = 0;
		// 查询ADC状态
		uint32_t state = HAL_ADC_GetState(&hadc1);
		if (( state & HAL_ADC_STATE_REG_EOC) == HAL_ADC_STATE_REG_EOC)
		{
    
    
			// 获取ADC转换结果
			value = HAL_ADC_GetValue(&hadc1);
			printf("adc value:%f \r\n",value/4096.0*3.3);
		}
		else
		{
    
    
			printf("adc state %d \r\n",state);
		}
		// 关闭ADC
		HAL_ADC_Stop(&hadc1);
		HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

효과 검증
여기에 이미지 설명 삽입

단일 채널 폴링은 변환이 완료될 때까지 차단됩니다.

3.2 단일 채널 인터럽트

1단계: 구성: "단일 채널 폴링" 구성을 기반으로 ADC 글로벌 인터럽트를 활성화합니다.
여기에 이미지 설명 삽입
2단계: 코드 생성 클릭

세 번째 단계: 직렬 포트 리디렉션 , usart.c에 다음 코드를 추가합니다. 자세한 내용은 직렬 포트 사용 에 대한 이전 기사를 참조하십시오.

// 需要调用stdio.h文件
#include <stdio.h>
//取消ARM的半主机工作模式
#pragma import(__use_no_semihosting)//标准库需要的支持函数                 
struct __FILE 
{
    
     
	int handle; 
}; 
FILE __stdout;       
void _sys_exit(int x) //定义_sys_exit()以避免使用半主机模式
{
    
     
	x = x;
} 

int fputc(int ch, FILE *f)
{
    
      
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
	return ch;
}

4단계: main.c 코드 작성

생성 후 코드를 확인하십시오. stm32f1xx_it.c파일에 ADC1 채널 2의 인터럽트 기능이 있습니다 ADC1_2_IRQHandler. 이 인터럽트 기능이 다시 호출됩니다.HAL_ADC_IRQHandler(&hadc1);

/**
  * @brief This function handles ADC1 and ADC2 global interrupts.
  */
void ADC1_2_IRQHandler(void)
{
    
    
  /* USER CODE BEGIN ADC1_2_IRQn 0 */

  /* USER CODE END ADC1_2_IRQn 0 */
  HAL_ADC_IRQHandler(&hadc1);
  /* USER CODE BEGIN ADC1_2_IRQn 1 */

  /* USER CODE END ADC1_2_IRQn 1 */
}

HAL_ADC_IRQHandler(&hadc1);함수 에서 stm32f1xx_hal_adc.c이 함수는 호출되는 많은 상황을 고려하거나 HAL_ADC_ConvCpltCallback(hadc);동일한 파일에서 이것은 약한 함수입니다. 번역에 따르면 이해하기 쉽고 이 방법을 직접 재정의할 수 있습니다.

/**
  * @brief  Conversion complete callback in non blocking mode 
  * @param  hadc: ADC handle
  * @retval None
  */
__weak void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    
    
  /* Prevent unused argument(s) compilation warning */
  UNUSED(hadc);
  /* NOTE : This function should not be modified. When the callback is needed,
            function HAL_ADC_ConvCpltCallback must be implemented in the user file.
   */
}

main.c

/* USER CODE BEGIN PFP */
// 重定义ADC转换完成回调函数
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    
    
	if(hadc == &hadc1)
	{
    
    
		uint16_t adc_value = HAL_ADC_GetValue(hadc);
		printf("refresh adc value:%f \r\n", adc_value/4096.0*3.3);
		// 重新开启ADC中断
		HAL_ADC_Start_IT(&hadc1);
	}
}
/* USER CODE END PFP */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    
    
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  
  /* USER CODE BEGIN WHILE */
	// 开启ADC中断
	HAL_ADC_Start_IT(&hadc1);
  while (1)
  {
    
    
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

효과는 폴링과 동일하지만 항상 실행되며 매우 빠르게 실행됩니다.

실제로 여기에서 단일 변환이 설정되므로 인터럽트가 한 번만 트리거되며 인터럽트를 다시 활성화하려면 HAL_ADC_Start_IT를 사용해야 합니다. 실시간 변환이 필요한 경우 변환을 연속 모드로 설정할 수 있으므로 ADC 변환기가 실시간으로 계속 변환하여 많은 CPU를 소비하므로 메인이 정상적으로 실행될 수 없습니다(만약 샘플링 시간이 너무 짧습니다) .

인터럽트가 활성화된 후 일반적으로 HAL_ADC_ConvCpltCallback 함수를 구현해야 하며 콜백의 GetValue는 폴링과 같은 프로그램의 다른 위치에서 ADC 상태를 판단한 다음 GetValue를 사용할 수도 있습니다.

3.3 다채널 폴링

1단계: ADC 구성
채널이 여러 개인 경우 스캔 모드가 자동으로 켜집니다. "불연속 변환 모드"를 활성화하려면.
여기에 이미지 설명 삽입
2단계: 코드 생성 클릭

세 번째 단계: 직렬 포트 리디렉션 , usart.c에 다음 코드를 추가합니다. 자세한 내용은 직렬 포트 사용 에 대한 이전 기사를 참조하십시오.

// 需要调用stdio.h文件
#include <stdio.h>
//取消ARM的半主机工作模式
#pragma import(__use_no_semihosting)//标准库需要的支持函数                 
struct __FILE 
{
    
     
	int handle; 
}; 
FILE __stdout;       
void _sys_exit(int x) //定义_sys_exit()以避免使用半主机模式
{
    
     
	x = x;
} 

int fputc(int ch, FILE *f)
{
    
      
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
	return ch;
}

4단계: main.c 코드 작성

HAL_ADCEx_Calibration_Start(&hadc1);
const uint8_t kNbrOfPin = 3;
  while (1)
  {
    
    
	for(int i = 0; i < kNbrOfPin; i++)
	{
    
    
		HAL_ADC_Start(&hadc1);
		HAL_ADC_PollForConversion(&hadc1, 100);
		float value = 0;
		uint32_t state = HAL_ADC_GetState(&hadc1);
		if (( state & HAL_ADC_STATE_REG_EOC) == HAL_ADC_STATE_REG_EOC)
		{
    
    
			value = HAL_ADC_GetValue(&hadc1);
			printf("adc value [%d]:%f\r\n", i,value/4096.0*3.3);
		}
		else
		{
    
    
			printf("adc state[%d]:%d\r\n", i, state);
		}
	}
	HAL_ADC_Stop(&hadc1);
	HAL_Delay(200);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

효과 검증
여기에 이미지 설명 삽입

3.4 DMA 모드

1단계: ADC 구성
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입
2단계: 코드 생성 클릭

세 번째 단계: 직렬 포트 리디렉션 , usart.c에 다음 코드를 추가합니다. 자세한 내용은 직렬 포트 사용 에 대한 이전 기사를 참조하십시오.

// 需要调用stdio.h文件
#include <stdio.h>
//取消ARM的半主机工作模式
#pragma import(__use_no_semihosting)//标准库需要的支持函数                 
struct __FILE 
{
    
     
	int handle; 
}; 
FILE __stdout;       
void _sys_exit(int x) //定义_sys_exit()以避免使用半主机模式
{
    
     
	x = x;
} 

int fputc(int ch, FILE *f)
{
    
      
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
	return ch;
}

4단계: main.c 코드 작성

/* USER CODE BEGIN PFP */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    
    
	if(hadc == &hadc1)
	{
    
    
		// 使用DMA其实也会运行到这里,也可以将结果在这里输出。
		// 当然此函数也可以不写。
	}
}
/* USER CODE END PFP */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    
    
  /* USER CODE BEGIN 1 */
	uint16_t adc_value[3] = {
    
    0};
  /* USER CODE END 1 */

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  
  /* Configure the system clock */
  SystemClock_Config();
  
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  
  /* USER CODE BEGIN WHILE */
	HAL_ADCEx_Calibration_Start(&hadc1);
	// enable DMA通道
	// 参数:ADC1、目标缓冲区地址、从ADC外围设备传输到内存的数据长度
	/*
	 * 此处有个大坑,经过测试,DMA中断非常容易进(具体的不知道)
	 *
	 * 如果ADC采样周期短的话,一直在执行中断,
	 * 导致无法执行主程序,因此会卡死在这个函数里面出不去。
	 *
	 * 因此,ADC的采用周期需要长一点。
	 */
	HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_value, 3);
	
  while (1)
  {
    
    
	printf("-------------------- \r\n");
	printf("adc value[0]:%f \r\n", adc_value[0]/4096.0*3.3);
	printf("adc value[1]:%f \r\n", adc_value[1]/4096.0*3.3);
	printf("adc value[2]:%f \r\n", adc_value[2]/4096.0*3.3);
	HAL_Delay(1000);
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

참고
DMA를 사용하는 주변 장치의 경우 MX_DMA_Init();는 여기에서 MX_ADC1_Init();와 같은 주변 장치 초기화 이전에 있어야 합니다.

효과 검증
여기에 이미지 설명 삽입

다채널 DMA와 단일채널 DMA의 구성은 기본적으로 동일하며 AD 변환 결과를 저장하는 배열만 주의하면 된다.채널이 2개이고 배열의 길이가 2라면 각 채널의 값은 배열의 비트; 배열의 길이가 10과 같이 2의 정수 배수인 경우 배열의 [0] [2] [4] [6] [8] 값은 다음 중 하나의 AD 값에 해당합니다. 채널, 즉 5회 연속 획득한 AD 값이 저장되어 있어 여러 AD 값을 평균화하여 사용할 수 있습니다.

おすすめ

転載: blog.csdn.net/weixin_46253745/article/details/127818727