본문 바로가기
MCU/STM32

[STM32G474] 타이머 트리거 사용법: HAL API로 타이머 트리거 설정 및 코드 예제

by linuxgo 2025. 8. 19.
반응형

1. STM32G474 타이머 트리거 개요

STM32G474는 STMicroelectronics의 STM32G4 시리즈에 속하는 고성능 32비트 ARM Cortex-M4 마이크로컨트롤러로, 최대 170 MHz로 동작하며 다양한 타이머 모듈을 제공합니다. 타이머 트리거는 한 타이머의 이벤트를 사용하여 다른 타이머, ADC, DAC 등의 동작을 동기화하거나 시작하는 기능입니다. 이 문서에서는 STM32G474의 타이머 트리거를 HAL API를 사용하여 설정하고 사용하는 방법을 상세히 다룹니다. 모든 예제 코드는 STM32CubeMX로 생성된 완전한 코드로 구성되며, STM32CubeIDE에서 실행 가능합니다. 각 코드에는 상세한 주석이 포함되어 있습니다.

타이머 트리거의 주요 특징

  • 트리거 소스: 타이머 업데이트 이벤트, 출력 비교(OC), 입력 캡처(IC), 외부 이벤트(ETR) 등.
  • 트리거 대상: 다른 타이머, ADC, DAC, DMA 등.
  • 마스터/슬레이브 모드: 마스터 타이머가 트리거 신호를 생성하여 슬레이브 타이머를 동기화.
  • 모드: 원샷 모드, 반복 트리거, PWM 트리거 등.
  • 클럭 소스: 내부 클럭(APB1/APB2), 외부 클럭(ETR, TI1, TI2).
  • 인터럽트: 트리거 이벤트에 따른 인터럽트 지원.
  • 클럭: AHB2 및 APB1/APB2 클럭을 통해 동작하며, 저전력 모드 지원.

HAL API는 복잡한 레지스터 설정을 추상화하여 타이머 트리거 설정을 간소화합니다.

2. 주요 타이머 트리거 HAL API 함수

2.1. 타이머 초기화 및 트리거 설정 함수

  • HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)
    • 설명: 타이머 기본 설정(프리스케일러, 주기, 카운터 모드 등)을 초기화.
    • 매개변수: htim (타이머 핸들러 구조체).
  • HAL_TIM_ConfigClockSource(TIM_HandleTypeDef *htim, TIM_ClockConfigTypeDef *sClockSourceConfig)
    • 설명: 타이머 클럭 소스 설정(내부 클럭, 외부 트리거 등).
  • HAL_TIMEx_MasterConfigSynchronization(TIM_HandleTypeDef *htim, TIM_MasterConfigTypeDef *sMasterConfig)
    • 설명: 마스터 타이머의 트리거 출력(TRGO) 설정.
  • HAL_TIM_SlaveConfigSynchronization(TIM_HandleTypeDef *htim, TIM_SlaveConfigTypeDef *sSlaveConfig)
    • 설명: 슬레이브 타이머의 트리거 입력 설정.
  • HAL_TIM_OC_Init(TIM_HandleTypeDef *htim)
    • 설명: 출력 비교(Output Compare) 모드로 타이머 초기화(트리거 이벤트 생성 가능).
  • HAL_ADC_Start_IT(ADC_HandleTypeDef *hadc)
    • 설명: ADC를 인터럽트 모드로 시작(타이머 트리거로 동작 가능).
  • HAL_DAC_Start(DAC_HandleTypeDef *hdac, uint32_t Channel)
    • 설명: DAC 출력을 시작(타이머 트리거로 동작 가능).

2.2. 인터럽트 및 트리거 관련 함수

  • HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    • 설명: 타이머 업데이트 이벤트 발생 시 호출되는 사용자 정의 콜백 함수.
  • HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
    • 설명: ADC 변환 완료 시 호출되는 콜백 함수(타이머 트리거로 동작 시 사용).
  • HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac)
    • 설명: DAC 변환 완료 시 호출되는 콜백 함수(타이머 트리거로 동작 시 사용).
  • HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)
    • 설명: 인터럽트 우선순위 설정.
  • HAL_NVIC_EnableIRQ(IRQn_Type IRQn)
    • 설명: 인터럽트 활성화.

3. 타이머 트리거 설정 및 동작 원리

STM32G474의 타이머 트리거를 효과적으로 사용하려면 시스템 클럭, 타이머 마스터/슬레이브 설정, 트리거 대상(예: ADC, DAC, 타이머), 인터럽트 및 디버깅 절차를 정확히 구성해야 합니다. 아래는 STM32CubeMX와 STM32CubeIDE를 사용한 설정 절차와 동작 원리를 상세히 설명합니다.

3.1. STM32CubeMX 설정 절차

STM32CubeMX를 사용하여 타이머 트리거, 시스템 클럭, 디버깅 인터페이스를 설정하는 단계는 다음과 같습니다:

  1. 프로젝트 생성:
    •   STM32CubeMX를 열고 "New Project"를 선택.
    •   MCU 선택 창에서 "STM32G474"를 검색하고, 사용하는 패키지(예: LQFP64)를 선택.
    •   프로젝트 이름을 지정하고 저장 경로 설정.
  2. 시스템 클럭 설정 (최대 170 MHz, HSE 사용):
    •   Pinout & Configuration 탭에서 "System Core" > "RCC"를 선택.
    •   "HSE"를 "Crystal/Ceramic Resonator"로 설정 (외부 8 MHz 크리스털 가정).
    •   Clock Configuration 탭으로 이동.
      •   HSE를 8 MHz로 입력.
      •   PLL Source Mux를 "HSE"로 설정.
      •   PLL 설정: PLLM = 1, PLLN = 85, PLLR = 2, PLLR Enable 활성화.
      •   System Clock Mux를 "PLLCLK"로 설정.
      •   결과: SYSCLK = (8 MHz / 1) * 85 / 2 = 170 MHz.
      •   HCLK (AHB), PCLK1 (APB1), PCLK2 (APB2)를 170 MHz로 설정 (Divider = 1).
      •   Flash Latency를 4 WS로 설정 (170 MHz 동작 시 권장).
      •   클럭 트리에서 에러가 없음을 확인.
  3. 타이머 및 트리거 설정:
    •   Pinout & Configuration 탭에서 사용할 타이머, ADC, DAC 설정.
      •   예제 1 (타이머로 ADC 트리거): TIM1을 마스터 타이머로 설정, ADC1을 트리거 대상으로 설정.
        •   TIM1 설정:
          •   Clock Source: Internal Clock.
          •   Prescaler: 16999 (170 MHz / (16999+1) = 10 kHz).
          •   Counter Period: 9999 (10 kHz / (9999+1) = 1 Hz, 즉 1초).
          •   Master/Slave Mode: Enable, Trigger Output (TRGO) = Update Event.
        •   ADC1 설정:
          •   External Trigger Conversion Source: Timer 1 Trigger Out event.
          •   Continuous Conversion Mode: Disable.
          •   Channel 1 (PA0, 아날로그 모드) 선택, Sampling Time: 247.5 Cycles.
      • 예제 2 (타이머 간 트리거): TIM1을 마스터로, TIM2를 슬레이브로 설정.
        •   TIM1 설정:
          •   Clock Source: Internal Clock.
          •   Prescaler: 16999 (10 kHz).
          •   Counter Period: 9999 (1 Hz).
          •   Master/Slave Mode: Enable, Trigger Output (TRGO) = Update Event.
        •   TIM2 설정:
          •   Clock Source: Internal Clock.
          •   Slave Mode: Trigger Mode, Trigger Source: ITR0 (TIM1).
          •   Prescaler: 9999 (10 kHz / (9999+1) = 1 kHz).
          •   Counter Period: 999 (1 kHz / (999+1) = 1 Hz).
          •   GPIOA Pin 5 (LED)로 출력.
      •   예제 3 (타이머로 DAC 트리거): TIM1을 마스터 타이머로 설정, DAC1 채널 1을 트리거 대상으로 설정.
        •   TIM1 설정:
          •   Clock Source: Internal Clock.
          •   Prescaler: 16999 (10 kHz).
          •   Counter Period: 9999 (1 Hz).
          •   Master/Slave Mode: Enable, Trigger Output (TRGO) = Update Event.
        •   DAC1 설정:
          •   Channel 1 (PA4, 아날로그 모드) 활성화.
          •   Trigger: Timer 1 Trigger Out event.
          •   Output Buffer: Enable.
      •   예제 4 (다중 트리거): TIM1을 마스터로 설정, ADC1, DAC1, TIM2를 동시에 트리거.
        •   TIM1 설정:
          •   Clock Source: Internal Clock.
          •   Prescaler: 16999 (10 kHz).
          •   Counter Period: 9999 (1 Hz).
          •   Master/Slave Mode: Enable, Trigger Output (TRGO) = Update Event.
        •   ADC1 설정:
          •   External Trigger Conversion Source: Timer 1 Trigger Out event.
          •   Channel 1 (PA0, 아날로그 모드), Sampling Time: 247.5 Cycles.
        •   DAC1 설정:
          •   Channel 1 (PA4, 아날로그 모드).
          •   Trigger: Timer 1 Trigger Out event.
        •   TIM2 설정:
          •   Slave Mode: Trigger Mode, Trigger Source: ITR0 (TIM1).
          •   Prescaler: 9999 (1 kHz).
          •   Counter Period: 999 (1 Hz).
          •   GPIOA Pin 5 (LED)로 출력.
  4. 디버깅 인터페이스 설정:
    •   "System Core" > "SYS"에서 Debug를 "Serial Wire" (SWD)로 설정.
    •   SWD 핀 (PA13: SWDIO, PA14: SWCLK)이 자동으로 예약됨.
  5. 프로젝트 설정 및 코드 생성:
    •   Project Manager 탭에서:
      •   Project Name과 저장 경로 확인.
      •   Toolchain/IDE를 "STM32CubeIDE"로 설정.
      •   Code Generator 옵션에서 "Copy only the necessary library files" 선택.
    •   "Generate Code"를 클릭하여 프로젝트 파일 생성.

3.2. 타이머 트리거 동작 원리

  1. 시스템 초기화:
    •   시스템 클럭을 170 MHz로 설정하고, 타이머, ADC, DAC 클럭 활성화 (__HAL_RCC_TIMx_CLK_ENABLE(), __HAL_RCC_ADC12_CLK_ENABLE(), __HAL_RCC_DAC1_CLK_ENABLE()).
    •   HAL 라이브러리와 시스템 타이머(Systick) 초기화 (HAL_Init()).
  2. 타이머 설정:
    •   TIM_HandleTypeDef 구조체를 사용하여 마스터 타이머의 프리스케일러, 주기, TRGO 설정.
    •   슬레이브 타이머의 경우, HAL_TIM_SlaveConfigSynchronization으로 트리거 소스(ITR) 설정.
  3. 트리거 대상 설정:
    •   ADC: ADC_HandleTypeDef로 트리거 소스를 타이머(TRGO)로 설정.
    •   DAC: DAC_HandleTypeDef로 트리거 소스를 타이머(TRGO)로 설정.
    •   슬레이브 타이머: 트리거 모드와 소스(ITR0, ITR1 등) 설정.
  4. 인터럽트 설정:
    •   타이머 인터럽트(HAL_TIM_PeriodElapsedCallback), ADC 인터럽트(HAL_ADC_ConvCpltCallback), DAC 인터럽트(HAL_DAC_ConvCpltCallbackCh1) 활성화.
    •   NVIC 우선순위 설정 (HAL_NVIC_SetPriority, HAL_NVIC_EnableIRQ).
  5. 트리거 동작:
    •   마스터 타이머가 TRGO 이벤트를 생성하면, 슬레이브 타이머, ADC, 또는 DAC가 동작 시작.
    •   다중 트리거의 경우, 단일 TRGO 이벤트로 여러 대상 동기화.
  6. 디버깅:
    •   변수(예: ADC 값, DAC 출력, 타이머 카운터)를 STM32CubeIDE로 모니터링.

3.3. 디버깅 설정 절차 (STM32CubeIDE)

  1. 디버그 인터페이스 연결:
    •   STM32G474 보드와 ST-LINK 디버거를 연결 (SWDIO: PA13, SWCLK: PA14, GND, 3.3V).
    •   STM32CubeIDE에서 프로젝트를 열고, "Run" > "Debug Configurations" 선택.
  2. 디버그 구성:
    •   "STM32 Cortex-M MCU Debugging"에서 새 구성 생성.
    •   Debugger 탭에서:
      •   Debug Probe: ST-LINK (OpenOCD).
      •   Interface: Serial Wire (SWD).
      •   "Enable" 체크.
    • Startup 탭에서:
      •   "Load image"와 "Load symbols" 활성화.
      •   생성된 .elf 파일 경로 확인.
    •   "Apply" 후 "Debug" 클릭.
  3. 디버깅 기능 사용:
    •   브레이크포인트 설정: main.c에서 원하는 코드 라인에 더블 클릭.
    •   변수 모니터링: "Expressions" 또는 "Variables" 창에서 adcValue, dacValue, timerStatus 등 확인.
    •   디버깅 실행: "Resume" (F8), "Step Over" (F6), "Step Into" (F5)로 코드 실행 제어.

3.4. 프로그램 다운로드 절차 (STM32CubeIDE)

  1. 프로그램 빌드:
    •   STM32CubeIDE에서 프로젝트를 열고 "Project" > "Build Project" 클릭.
    •   빌드 오류가 없음을 확인 (Console 창 확인).
  2. 프로그램 다운로드:
    •   ST-LINK 디버거가 연결된 상태에서 "Run" > "Run Configurations" 선택.
    •   "STM32 Cortex-M MCU"에서 새 구성 생성.
    •   Main 탭에서 .elf 파일 선택 확인.
    •   "Run" 클릭하여 프로그램을 STM32G474에 다운로드.
    •   다운로드 완료 후, 보드가 자동으로 리셋되고 프로그램 실행.
  3. 검증:
    •   예제 1: ADC1이 TIM1 트리거로 1초마다 변환, adcValue에 저장.
    •   예제 2: TIM1의 트리거로 TIM2가 동작, LED가 1초마다 토글.
    •   예제 3: DAC1이 TIM1 트리거로 1초마다 출력 값 변경, PA4에서 확인.
    •   예제 4: TIM1 트리거로 ADC1, DAC1, TIM2가 동시에 동작, 각각의 출력 확인.

4. 타이머 트리거 예제 코드

아래는 STM32G474의 타이머 트리거를 HAL API로 제어하는 완전한 STM32CubeMX 생성 예제 코드입니다. 각 코드는 STM32CubeIDE에서 바로 실행 가능하며, 외부 HSE 클럭(8 MHz)을 사용하여 시스템 클럭을 170 MHz로 설정합니다. STM32CubeMX에서 생성된 주석은 그대로 유지되며, 추가로 상세한 한글 주석이 포함됩니다.

4.1. 예제 1: 타이머로 ADC 트리거

TIM1의 업데이트 이벤트를 사용하여 ADC1을 1초마다 트리거하고, GPIOA 핀 0에서 아날로그 값을 읽습니다.

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * STM32CubeMX-generated code for TIM1 triggering ADC1 on GPIOA Pin 0.
  * ADC conversion triggered every 1 second. System clock set to 170 MHz using HSE.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
TIM_HandleTypeDef htim1; // TIM1 핸들러 구조체 (마스터 타이머)
ADC_HandleTypeDef hadc1; // ADC1 핸들러 구조체
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define ADC_Pin GPIO_PIN_0 // GPIOA 핀 0를 ADC 입력으로 사용
#define ADC_GPIO_Port GPIOA // ADC가 연결된 GPIO 포트
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
volatile uint32_t timerStatus = 0; // 타이머 상태 변수: 0 (초기화 완료), 1 (동작 중), 0xFFFF (오류)
volatile uint16_t adcValue = 0; // ADC 변환 값 저장
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void); // 시스템 클럭 설정 함수 선언
static void MX_GPIO_Init(void); // GPIO 초기화 함수 선언
static void MX_TIM1_Init(void); // TIM1 초기화 함수 선언
static void MX_ADC1_Init(void); // ADC1 초기화 함수 선언

/* USER CODE BEGIN PFP */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc); // ADC 변환 완료 콜백 함수 선언
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
  if (hadc->Instance == ADC1) // ADC1 변환 완료 확인
  {
    adcValue = HAL_ADC_GetValue(hadc); // ADC 변환 값 읽기
    timerStatus = 1; // 동작 상태 업데이트
  }
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  HAL_Init(); // HAL 라이브러리 및 시스템 타이머(Systick) 초기화

  SystemClock_Config(); // 시스템 클럭을 170 MHz로 설정 (HSE 8 MHz 사용)

  MX_GPIO_Init(); // GPIOA 핀 0 초기화 (아날로그 모드)
  MX_TIM1_Init(); // TIM1 초기화 (마스터 타이머)
  MX_ADC1_Init(); // ADC1 초기화

  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start(&htim1); // TIM1 시작 (트리거 출력용)
  HAL_ADC_Start_IT(&hadc1); // ADC1 인터럽트 모드로 시작
  if (timerStatus != 1) // 초기화 성공 여부 확인
  {
    timerStatus = 0xFFFF; // 초기화 실패 시 오류 상태 설정
    while (1);
  }
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if (timerStatus == 0xFFFF) // 오류 상태 확인
    {
      // 오류 처리
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 85;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief TIM1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM1_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 16999; // 170 MHz / (16999+1) = 10 kHz
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 9999; // 10 kHz / (9999+1) = 1 Hz (1초 주기)
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; // TRGO를 업데이트 이벤트로 설정
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE; // 마스터 모드 활성화
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

  timerStatus = 1; // 타이머 초기화 성공 표시
}

/**
  * @brief ADC1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_ADC1_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};

  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc1.Init.ContinuousConvMode = ADC_CONTINUOUS_DISABLE; // 비연속 변환
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T1_TRGO; // TIM1 TRGO로 트리거
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; // 상승 에지 트리거
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_1; // GPIOA 핀 0
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  __HAL_RCC_GPIOA_CLK_ENABLE(); // GPIOA 클럭 활성화

  GPIO_InitStruct.Pin = ADC_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; // 아날로그 모드
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(ADC_GPIO_Port, &GPIO_InitStruct);
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  timerStatus = 0xFFFF;
  while (1)
  {
  }
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  while (1)
  {
  }
}
#endif /* USE_FULL_ASSERT */

설명:

  • 기능: TIM1의 업데이트 이벤트로 ADC1을 1초마다 트리거, GPIOA 핀 0에서 아날로그 값 읽기.
  • 설정:
    • TIM1: 프리스케일러 16999, 주기 9999 (1초 주기), TRGO = Update Event.
    • ADC1: TIM1 TRGO로 트리거, 12비트 해상도, 비연속 변환.
  • GPIO: GPIOA 핀 0 (아날로그 모드).
  • 출력: ADC 변환 값이 adcValue 변수에 저장.
  • 클럭: HSE 8 MHz, PLL로 170 MHz SYSCLK.

4.2. 예제 2: 타이머 간 트리거

TIM1을 마스터로 설정하여 TIM2를 트리거하고, TIM2의 업데이트 이벤트로 GPIOA 핀 5의 LED를 토글합니다.

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * STM32CubeMX-generated code for TIM1 triggering TIM2, toggling LED on GPIOA Pin 5.
  * System clock set to 170 MHz using HSE.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
TIM_HandleTypeDef htim1; // TIM1 핸들러 구조체 (마스터)
TIM_HandleTypeDef htim2; // TIM2 핸들러 구조체 (슬레이브)
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define LED_Pin GPIO_PIN_5 // GPIOA 핀 5를 LED로 사용
#define LED_GPIO_Port GPIOA // LED가 연결된 GPIO 포트
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
volatile uint32_t timerStatus = 0; // 타이머 상태 변수: 0 (초기화 완료), 1 (동작 중), 0xFFFF (오류)
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM1_Init(void);
static void MX_TIM2_Init(void);

/* USER CODE BEGIN PFP */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim); // 타이머 인터럽트 콜백 함수 선언
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == TIM2) // TIM2 업데이트 이벤트 확인
  {
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // LED 상태 토글
    timerStatus = 1; // 동작 상태 업데이트
  }
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();
  MX_TIM1_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start(&htim1); // TIM1 시작 (마스터)
  HAL_TIM_Base_Start_IT(&htim2); // TIM2 인터럽트 모드로 시작 (슬레이브)
  if (timerStatus != 1)
  {
    timerStatus = 0xFFFF;
    while (1);
  }
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if (timerStatus == 0xFFFF)
    {
      HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 85;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief TIM1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM1_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 16999; // 170 MHz / (16999+1) = 10 kHz
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 9999; // 10 kHz / (9999+1) = 1 Hz
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; // TRGO를 업데이트 이벤트로 설정
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE; // 마스터 모드 활성화
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

  timerStatus = 1; // 타이머 초기화 성공 표시
}

/**
  * @brief TIM2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM2_Init(void)
{
  TIM_SlaveConfigTypeDef sSlaveConfig = {0};
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 9999; // 10 kHz / (9999+1) = 1 kHz
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 999; // 1 kHz / (999+1) = 1 Hz
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER; // 트리거 모드
  sSlaveConfig.InputTrigger = TIM_TS_ITR0; // TIM1 TRGO를 트리거 소스로 사용
  if (HAL_TIM_SlaveConfigSynchronization(&htim2, &sSlaveConfig) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); // TIM2 인터럽트 우선순위 설정
  HAL_NVIC_EnableIRQ(TIM2_IRQn); // TIM2 인터럽트 활성화
}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  __HAL_RCC_GPIOA_CLK_ENABLE();

  HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);

  GPIO_InitStruct.Pin = LED_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
  HAL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct);
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  timerStatus = 0xFFFF;
  while (1)
  {
  }
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  while (1)
  {
  }
}
#endif /* USE_FULL_ASSERT */

설명:

  • 기능: TIM1의 업데이트 이벤트로 TIM2를 트리거, TIM2의 업데이트 이벤트로 GPIOA 핀 5의 LED 토글.
  • 설정:
    • TIM1: 프리스케일러 16999, 주기 9999 (1 Hz), TRGO = Update Event.
    • TIM2: 슬레이브 모드(트리거), ITR0(TIM1) 입력, 프리스케일러 9999, 주기 999 (1 Hz).
  • GPIO: GPIOA 핀 5 (LED, 푸시-풀 출력).
  • 출력: LED가 TIM2의 업데이트 이벤트에 따라 1초마다 토글.
  • 클럭: HSE 8 MHz, PLL로 170 MHz SYSCLK.

4.3. 예제 3: 타이머로 DAC 트리거

TIM1의 업데이트 이벤트를 사용하여 DAC1 채널 1을 1초마다 트리거하고, GPIOA 핀 4에서 아날로그 출력 값을 토글합니다.

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * STM32CubeMX-generated code for TIM1 triggering DAC1 on GPIOA Pin 4.
  * DAC output toggled every 1 second. System clock set to 170 MHz using HSE.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
TIM_HandleTypeDef htim1; // TIM1 핸들러 구조체 (마스터 타이머)
DAC_HandleTypeDef hdac1; // DAC1 핸들러 구조체
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define DAC_Pin GPIO_PIN_4 // GPIOA 핀 4를 DAC 출력으로 사용
#define DAC_GPIO_Port GPIOA // DAC가 연결된 GPIO 포트
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
volatile uint32_t timerStatus = 0; // 타이머 상태 변수: 0 (초기화 완료), 1 (동작 중), 0xFFFF (오류)
volatile uint32_t dacValue = 0; // DAC 출력 값 (0 또는 4095)
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void); // 시스템 클럭 설정 함수 선언
static void MX_GPIO_Init(void); // GPIO 초기화 함수 선언
static void MX_TIM1_Init(void); // TIM1 초기화 함수 선언
static void MX_DAC1_Init(void); // DAC1 초기화 함수 선언

/* USER CODE BEGIN PFP */
void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac); // DAC 변환 완료 콜백 함수 선언
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac)
{
  if (hdac->Instance == DAC1) // DAC1 변환 완료 확인
  {
    dacValue = (dacValue == 0) ? 4095 : 0; // DAC 값 토글 (0V 또는 3.3V)
    HAL_DAC_SetValue(hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, dacValue); // 새 DAC 값 설정
    timerStatus = 1; // 동작 상태 업데이트
  }
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  HAL_Init(); // HAL 라이브러리 및 시스템 타이머(Systick) 초기화

  SystemClock_Config(); // 시스템 클럭을 170 MHz로 설정 (HSE 8 MHz 사용)

  MX_GPIO_Init(); // GPIOA 핀 4 초기화 (아날로그 모드)
  MX_TIM1_Init(); // TIM1 초기화 (마스터 타이머)
  MX_DAC1_Init(); // DAC1 초기화

  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start(&htim1); // TIM1 시작 (트리거 출력용)
  HAL_DAC_Start(&hdac1, DAC_CHANNEL_1); // DAC1 채널 1 시작
  HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, dacValue); // 초기 DAC 값 설정
  if (timerStatus != 1) // 초기화 성공 여부 확인
  {
    timerStatus = 0xFFFF; // 초기화 실패 시 오류 상태 설정
    while (1);
  }
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if (timerStatus == 0xFFFF) // 오류 상태 확인
    {
      // 오류 처리
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 85;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief TIM1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM1_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 16999; // 170 MHz / (16999+1) = 10 kHz
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 9999; // 10 kHz / (9999+1) = 1 Hz (1초 주기)
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; // TRGO를 업데이트 이벤트로 설정
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE; // 마스터 모드 활성화
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

  timerStatus = 1; // 타이머 초기화 성공 표시
}

/**
  * @brief DAC1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_DAC1_Init(void)
{
  DAC_ChannelConfTypeDef sConfig = {0};

  hdac1.Instance = DAC1;
  if (HAL_DAC_Init(&hdac1) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.DAC_Trigger = DAC_TRIGGER_T1_TRGO; // TIM1 TRGO로 트리거
  sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE; // 출력 버퍼 활성화
  sConfig.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_DISABLE;
  sConfig.DAC_UserTrimming = DAC_TRIMMING_FACTORY;
  if (HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_NVIC_SetPriority(TIM1_TRG_COM_IRQn, 0, 0); // TIM1 트리거 인터럽트 우선순위 설정
  HAL_NVIC_EnableIRQ(TIM1_TRG_COM_IRQn); // TIM1 트리거 인터럽트 활성화
}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  __HAL_RCC_GPIOA_CLK_ENABLE(); // GPIOA 클럭 활성화

  GPIO_InitStruct.Pin = DAC_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; // 아날로그 모드
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(DAC_GPIO_Port, &GPIO_InitStruct);
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  timerStatus = 0xFFFF;
  while (1)
  {
  }
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  while (1)
  {
  }
}
#endif /* USE_FULL_ASSERT */

설명:

  • 기능: TIM1의 업데이트 이벤트로 DAC1 채널 1을 1초마다 트리거, GPIOA 핀 4에서 아날로그 출력 값(0V 또는 3.3V) 토글.
  • 설정:
    • TIM1: 프리스케일러 16999, 주기 9999 (1 Hz), TRGO = Update Event.
    • DAC1: TIM1 TRGO로 트리거, 12비트 출력, 출력 버퍼 활성화.
  • GPIO: GPIOA 핀 4 (아날로그 모드).
  • 출력: DAC 출력이 dacValue 값(0 또는 4095)에 따라 PA4에서 0V 또는 3.3V로 토글.
  • 클럭: HSE 8 MHz, PLL로 170 MHz SYSCLK.

4.4. 예제 4: 다중 트리거 (ADC, DAC, 타이머 동시 트리거)

TIM1의 업데이트 이벤트를 사용하여 ADC1, DAC1, TIM2를 동시에 트리거합니다. ADC1은 GPIOA 핀 0에서 값을 읽고, DAC1은 GPIOA 핀 4에서 출력 값을 토글하며, TIM2는 GPIOA 핀 5의 LED를 토글합니다.

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * STM32CubeMX-generated code for TIM1 triggering ADC1, DAC1, and TIM2 simultaneously.
  * ADC on PA0, DAC on PA4, LED toggle on PA5. System clock set to 170 MHz using HSE.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
TIM_HandleTypeDef htim1; // TIM1 핸들러 구조체 (마스터 타이머)
TIM_HandleTypeDef htim2; // TIM2 핸들러 구조체 (슬레이브 타이머)
ADC_HandleTypeDef hadc1; // ADC1 핸들러 구조체
DAC_HandleTypeDef hdac1; // DAC1 핸들러 구조체
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define ADC_Pin GPIO_PIN_0 // GPIOA 핀 0를 ADC 입력으로 사용
#define ADC_GPIO_Port GPIOA // ADC가 연결된 GPIO 포트
#define DAC_Pin GPIO_PIN_4 // GPIOA 핀 4를 DAC 출력으로 사용
#define DAC_GPIO_Port GPIOA // DAC가 연결된 GPIO 포트
#define LED_Pin GPIO_PIN_5 // GPIOA 핀 5를 LED로 사용
#define LED_GPIO_Port GPIOA // LED가 연결된 GPIO 포트
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
volatile uint32_t timerStatus = 0; // 타이머 상태 변수: 0 (초기화 완료), 1 (동작 중), 0xFFFF (오류)
volatile uint16_t adcValue = 0; // ADC 변환 값 저장
volatile uint32_t dacValue = 0; // DAC 출력 값 (0 또는 4095)
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void); // 시스템 클럭 설정 함수 선언
static void MX_GPIO_Init(void); // GPIO 초기화 함수 선언
static void MX_TIM1_Init(void); // TIM1 초기화 함수 선언
static void MX_TIM2_Init(void); // TIM2 초기화 함수 선언
static void MX_ADC1_Init(void); // ADC1 초기화 함수 선언
static void MX_DAC1_Init(void); // DAC1 초기화 함수 선언

/* USER CODE BEGIN PFP */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc); // ADC 변환 완료 콜백 함수 선언
void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac); // DAC 변환 완료 콜백 함수 선언
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim); // 타이머 인터럽트 콜백 함수 선언
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
  if (hadc->Instance == ADC1) // ADC1 변환 완료 확인
  {
    adcValue = HAL_ADC_GetValue(hadc); // ADC 변환 값 읽기
    timerStatus = 1; // 동작 상태 업데이트
  }
}

void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac)
{
  if (hdac->Instance == DAC1) // DAC1 변환 완료 확인
  {
    dacValue = (dacValue == 0) ? 4095 : 0; // DAC 값 토글 (0V 또는 3.3V)
    HAL_DAC_SetValue(hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, dacValue); // 새 DAC 값 설정
    timerStatus = 1; // 동작 상태 업데이트
  }
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == TIM2) // TIM2 업데이트 이벤트 확인
  {
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // LED 상태 토글
    timerStatus = 1; // 동작 상태 업데이트
  }
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  HAL_Init(); // HAL 라이브러리 및 시스템 타이머(Systick) 초기화

  SystemClock_Config(); // 시스템 클럭을 170 MHz로 설정 (HSE 8 MHz 사용)

  MX_GPIO_Init(); // GPIO 초기화 (PA0: ADC, PA4: DAC, PA5: LED)
  MX_TIM1_Init(); // TIM1 초기화 (마스터 타이머)
  MX_TIM2_Init(); // TIM2 초기화 (슬레이브 타이머)
  MX_ADC1_Init(); // ADC1 초기화
  MX_DAC1_Init(); // DAC1 초기화

  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start(&htim1); // TIM1 시작 (트리거 출력용)
  HAL_TIM_Base_Start_IT(&htim2); // TIM2 인터럽트 모드로 시작 (슬레이브)
  HAL_ADC_Start_IT(&hadc1); // ADC1 인터럽트 모드로 시작
  HAL_DAC_Start(&hdac1, DAC_CHANNEL_1); // DAC1 채널 1 시작
  HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, dacValue); // 초기 DAC 값 설정
  if (timerStatus != 1) // 초기화 성공 여부 확인
  {
    timerStatus = 0xFFFF; // 초기화 실패 시 오류 상태 설정
    while (1);
  }
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if (timerStatus == 0xFFFF) // 오류 상태 확인
    {
      HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); // 오류 시 LED 끄기
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 85;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief TIM1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM1_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 16999; // 170 MHz / (16999+1) = 10 kHz
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 9999; // 10 kHz / (9999+1) = 1 Hz (1초 주기)
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; // TRGO를 업데이트 이벤트로 설정
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE; // 마스터 모드 활성화
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

  timerStatus = 1; // 타이머 초기화 성공 표시
}

/**
  * @brief TIM2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM2_Init(void)
{
  TIM_SlaveConfigTypeDef sSlaveConfig = {0};
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 9999; // 10 kHz / (9999+1) = 1 kHz
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 999; // 1 kHz / (999+1) = 1 Hz
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER; // 트리거 모드
  sSlaveConfig.InputTrigger = TIM_TS_ITR0; // TIM1 TRGO를 트리거 소스로 사용
  if (HAL_TIM_SlaveConfigSynchronization(&htim2, &sSlaveConfig) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); // TIM2 인터럽트 우선순위 설정
  HAL_NVIC_EnableIRQ(TIM2_IRQn); // TIM2 인터럽트 활성화
}

/**
  * @brief ADC1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_ADC1_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};

  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc1.Init.ContinuousConvMode = ADC_CONTINUOUS_DISABLE; // 비연속 변환
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T1_TRGO; // TIM1 TRGO로 트리거
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; // 상승 에지 트리거
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_1; // GPIOA 핀 0
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief DAC1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_DAC1_Init(void)
{
  DAC_ChannelConfTypeDef sConfig = {0};

  hdac1.Instance = DAC1;
  if (HAL_DAC_Init(&hdac1) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.DAC_Trigger = DAC_TRIGGER_T1_TRGO; // TIM1 TRGO로 트리거
  sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE; // 출력 버퍼 활성화
  sConfig.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_DISABLE;
  sConfig.DAC_UserTrimming = DAC_TRIMMING_FACTORY;
  if (HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  __HAL_RCC_GPIOA_CLK_ENABLE(); // GPIOA 클럭 활성화

  HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); // LED 초기 상태 LOW

  GPIO_InitStruct.Pin = ADC_Pin; // ADC 핀 (PA0)
  GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; // 아날로그 모드
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(ADC_GPIO_Port, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = DAC_Pin; // DAC 핀 (PA4)
  GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; // 아날로그 모드
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(DAC_GPIO_Port, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = LED_Pin; // LED 핀 (PA5)
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 푸시-풀 출력
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
  HAL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct);
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  timerStatus = 0xFFFF;
  while (1)
  {
  }
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  while (1)
  {
  }
}
#endif /* USE_FULL_ASSERT */

설명:

  • 기능: TIM1의 업데이트 이벤트로 ADC1, DAC1, TIM2를 동시에 트리거. ADC1은 PA0에서 아날로그 값 읽기, DAC1은 PA4에서 출력 값 토글, TIM2는 PA5의 LED 토글.
  • 설정:
    •   TIM1: 프리스케일러 16999, 주기 9999 (1 Hz), TRGO = Update Event.
    •   ADC1: TIM1 TRGO로 트리거, 12비트 해상도, 비연속 변환.
    •   DAC1: TIM1 TRGO로 트리거, 12비트 출력, 출력 버퍼 활성화.
    •   TIM2: 슬레이브 모드(트리거), ITR0(TIM1) 입력, 프리스케일러 9999, 주기 999 (1 Hz).
  • GPIO: PA0 (ADC, 아날로그), PA4 (DAC, 아날로그), PA5 (LED, 푸시-풀 출력).
  • 출력:
    •   ADC 변환 값은 adcValue 변수에 저장.
    •   DAC 출력은 dacValue 값(0 또는 4095)에 따라 PA4에서 0V 또는 3.3V로 토글.
    •   LED는 TIM2의 업데이트 이벤트에 따라 1초마다 토글.
  • 클럭: HSE 8 MHz, PLL로 170 MHz SYSCLK.

5. 추가 고려 사항

  • 클럭 설정: STM32CubeMX로 HSE 8 MHz를 사용하여 170 MHz SYSCLK 구성. 실제 보드의 HSE 주파수에 따라 PLL 설정 조정 필요.
  • 타이머 선택: Advanced timers (TIM1, TIM8)은 복잡한 트리거 설정에 적합, General-purpose timers (TIM2~TIM5)는 간단한 트리거에 사용.
  • 트리거 소스: TRGO 설정은 Update Event, OC, Reset 등 다양. 대상 장치와 호환되는지 확인.
  • 인터럽트: 다중 트리거 사용 시 NVIC 우선순위 충돌 주의. 예제 4에서는 ADC, DAC, TIM2 인터럽트의 우선순위를 동일하게 설정했으나, 필요 시 조정.
  • 디버깅: SWD와 ST-LINK를 사용하여 adcValue, dacValue, 타이머 카운터 값 실시간 모니터링.
  • 저전력: 트리거 빈도를 최적화하여 전력 소모 감소.

키워드: STM32G474, 타이머 트리거, HAL API, STM32G4, 마이크로컨트롤러, STM32CubeIDE, STM32CubeMX, ADC 트리거, DAC 트리거, 타이머 동기화, 다중 트리거, HSE 클럭, 디버깅

반응형