본문 바로가기
Power Electronics/모터제어(MotorControl)

STM32 + DRV8825 예제 | Modbus RTU 기반 2축(X,Y) 스테이지 모터 제어 방법

by linuxgo 2025. 8. 21.
반응형

1. 개요

이 글에서는 STM32F401RE 보드DRV8825 모터 드라이버를 활용하여 Modbus RTU 기반 2축(X,Y) 스테이지 모터 제어 시스템을 구현하는 방법을 설명합니다. 정/역 방향, 속도/위치 제어 ,S-커브 가속/감속, 동기화, RUN/Stop,  원점 복귀(Home), 과전류 및 과열 보호 기능을 적용해 실제 CNC 스테이지 제어와 유사한 동작을 재현합니다. X,Y 스테이지는 CNC, 3D 프린터, 로봇 팔 등에서 사용하는 2축 선형 이동 시스템입니다. 

키워드(Keywords): STM32,모드버스RTU,STM32F401RE , DRV8825 모터 드라이버, Modbus RTU 기반 2축(X,Y) 스테이지 모터 제어 시스템

하드웨어 구성

  • STM32 보드: STM32F401RE(Nucleo-F401RE).
  • DRV8825 드라이버 2개: X축(모터 1), Y축(모터 2).
  • 스텝 모터 2개: NEMA 17 (1.8°/스텝, 200 스텝/회전, 1/16 마이크로스테핑: 3200 스텝/회전).
  • RS-485 트랜시버: MAX485.
  • 리미트 스위치: X축(PA5), Y축(PA6).
  • 센서: ACS712(전류, PA7), NTC 서미스터(온도, PA8).
  • 연결:
    • X축: STEP1(PA8, TIM1_CH1), DIR1(PA9), ENABLE1(PA10).
    • Y축: STEP2(PA0, TIM2_CH1), DIR2(PA1), ENABLE2(PA2).
    • RS-485: USART2(TX: PA2, RX: PA3), DE/RE(PA4).
  • 전원: DRV8825 VMOT(12~24V), STM32(3.3V/5V), GND 공유.

소프트웨어 구성

  • 개발 환경: STM32CubeIDE, STM32 HAL.
  • Modbus: FreeModbus, 19200bps, 8비트, Even Parity, 1 정지 비트.
  • 타이머: TIM1(X축 PWM), TIM2(Y축 PWM), TIM3(S 커브/타이밍).
  • ADC: 전류/온도 감지.
  • 최적화: LUT(S 커브), DMA(UART), 인터럽트 우선순위.

2. Modbus RTU 레지스터 매핑

Modbus RTU는 STM32가 마스터의 명령을 처리하는 산업용 프로토콜입니다. 아래 테이블은 X,Y 스테이지 제어를 위한 레지스터 매핑을 정리합니다.

Holding Register (읽기/쓰기)

주소 설명 범위 단위 비고
40001 X축 속도 100~2000 Hz 최대 주파수
40002 X축 목표 스텝 수 0~65535 스텝 예: 3200 = 1회전
40003 X축 S 커브 활성화 0~1 - 0: 비활성화, 1: 활성화
40004 X축 S 커브 시간 500~2000 ms 가속/감속 시간
40005 X축 원점 복귀 속도 100~500 Hz 낮은 속도로 안정성 확보
40006 Y축 속도 100~2000 Hz 최대 주파수
40007 Y축 목표 스텝 수 0~65535 스텝 -
40008 Y축 S 커브 활성화 0~1 - 0: 비활성화, 1: 활성화
40009 Y축 S 커브 시간 500~2000 ms -
40010 Y축 원점 복귀 속도 100~500 Hz -
40011 동기화 플래그 0~1 - 0: 독립, 1: 동기화
40012 보호 기능 활성화 0~1 - 0: 비활성화, 1: 활성화

Coil (읽기/쓰기)

주소 설명 비고
00001 X축 방향 0: 역방향, 1: 정방향 -
00002 X축 활성화 0: 비활성화, 1: 활성화 ENABLE 핀 제어
00003 X축 RUN/Stop 0: Stop, 1: Run -
00004 X축 원점 복귀 0: 대기, 1: 실행 -
00005 Y축 방향 0: 역방향, 1: 정방향 -
00006 Y축 활성화 0: 비활성화, 1: 활성화 -
00007 Y축 RUN/Stop 0: Stop, 1: Run -
00008 Y축 원점 복귀 0: 대기, 1: 실행 -

Input Register (읽기 전용)

주소 설명 범위 단위 비고
30001 X축 현재 스텝 수 0~65535 스텝 -
30002 X축 상태 0~3 - 0: 정지, 1: 동작, 2: 원점 복귀, 3: 보호
30003 Y축 현재 스텝 수 0~65535 스텝 -
30004 Y축 상태 0~3 - -
30005 전류 값 0~5000 mA ACS712 측정
30006 온도 값 0~100 °C NTC 서미스터

3. 기능 구현 설명

3.1 스텝 모터 제어

TIM1, TIM2로 PWM 생성, DRV8825의 STEP(PA8, PA0), DIR(PA9, PA1), ENABLE(PA10, PA2) 핀 제어. 1/16 마이크로스테핑(3200 스텝/회전).

3.2 RUN/Stop 기능

Coil 00003(X축), 00007(Y축)으로 RUN(1)/Stop(0). Stop 시 PWM 중지, 스텝 카운트 유지, ENABLE 비활성화. Run 시 PWM 재개.

3.3 원점 복귀 기능

Coil 00004(X축), 00008(Y축)으로 트리거. 리미트 스위치(PA5, PA6) 감지까지 역방향 이동(속도: 40005, 40010). 스위치 감지 시 스텝 카운트 0.

3.4 보호 기능

ACS712(PA7, 과전류: 2A), NTC 서미스터(PA8, 과열: 70°C)로 모터 보호. 임계값 초과 시 정지.

3.5 S 커브 가속/감속

S 커브는 부드러운 가속/감속을 위해 시그모이드 함수 사용:

\[ v(t) = v_{\text{min}} + \frac{v_{\text{max}} - v_{\text{min}}}{1 + e^{-k(t - t_0)}} \]

  • \( v_{\text{max}} \): Holding Register 40001, 40006.
  • \( v_{\text{min}} \): 100Hz.
  • \( k \): 0.01.
  • \( t_0 \): 0.25(가속), 0.75(감속).
  • 최적화: 100단계 LUT, TIM3(10ms)으로 주파수 업데이트.

3.6 동기화

진행률 (\( \frac{\text{현재 스텝}}{\text{목표 스텝}} \)) 비교, 느린 축 속도 10% 증가. Holding Register 40011로 활성화.

3.7 Modbus RTU 통신

19200bps, 3.5 문자 타임아웃 (~1.75ms). DMA(UART RX/TX), FreeModbus(슬레이브 주소 0x01).

4. 최적화 전략

  • S 커브: LUT로 부동소수점 연산 제거, CPU 부하 50% 감소.
  • DMA: UART RX/TX에 DMA1 Stream5/6.
  • 인터럽트: TIM1/TIM2(스텝, 최고), TIM3(S 커브, 중간), UART/DMA/ADC(낮음).
  • 메모리: 정적 할당, 스택 최적화.

5. 구현 절차

  1. 하드웨어: DRV8825, NEMA 17, 리미트 스위치, 센서 연결.
  2. 타이머: TIM1(X), TIM2(Y), TIM3(S 커브).
  3. ADC: 전류/온도 감지.
  4. Modbus: FreeModbus 초기화.
  5. 기능: RUN/Stop, 원점 복귀, 보호, 동기화.

6. 테스트 방법

QModbus, ModPoll 사용. 예시 명령어:

  • X축 이동 (1000Hz, 3200 스텝, S 커브):
    Write Coil 00001: 1
    Write Holding 40001: 1000
    Write Holding 40002: 3200
    Write Holding 40003: 1
    Write Coil 00003: 1
    Write Coil 00002: 1
  • X축 정지: Write Coil 00003: 0
  • X축 원점 복귀: Write Holding 40005: 200, Write Coil 00004: 1
  • 보호 확인: Read Input 30005, 30006
  • 동기화: Write Holding 40011: 1

7. 코드

아래는 STM32 X,Y 스테이지 제어를 위한 C 코드로, 상세 주석과 함께 제공됩니다.

/*
 * xy_stage_modbus_rtu.c
 * STM32F401RE 기반 X,Y 스테이지 제어 (DRV8825 x2, Modbus RTU)
 * 기능: 정/역 방향, 속도/위치 제어, S 커브, 동기화, RUN/Stop, 원점 복귀, 보호
 * 최적화: S 커브 LUT, DMA(UART), 인터럽트 우선순위
 * 작성: 2025-08-21
 */
#include "main.h"
#include "mb.h"         // FreeModbus 라이브러리
#include "mbport.h"     // FreeModbus 포트
#include <string.h>
#include <stdio.h>

/* 타이머, UART, ADC 핸들 선언 */
TIM_HandleTypeDef htim1, htim2, htim3; // TIM1: X축 PWM, TIM2: Y축 PWM, TIM3: S 커브 및 타이밍
UART_HandleTypeDef huart2;             // USART2: Modbus RTU 통신
DMA_HandleTypeDef hdma_usart2_rx, hdma_usart2_tx; // DMA: UART RX/TX 최적화
ADC_HandleTypeDef hadc1;               // ADC: 전류/온도 감지

/* X축 상태 변수 */
uint32_t stepCount1 = 0;          // 현재 스텝 수 (최대 65535)
uint32_t targetSteps1 = 0;        // 목표 스텝 수 (Modbus 40002)
float maxFrequency1 = 2000.0f;    // 최대 주파수 (Hz, 40001)
float minFrequency1 = 100.0f;     // 최소 주파수 (Hz)
float homeFrequency1 = 200.0f;    // 원점 복귀 속도 (Hz, 40005)
float k1 = 0.01f;                 // S 커브 시그모이드 경사
uint32_t totalTime1 = 1000;       // S 커브 전체 시간 (ms, 40004)
uint32_t currentTime1 = 0;        // S 커브 현재 시간
uint8_t sCurveEnabled1 = 0;       // S 커브 활성화 (0: 비활성화, 1: 활성화, 40003)
uint8_t motorState1 = 0;          // 상태: 0(정지), 1(동작), 2(원점 복귀), 3(보호)
uint8_t runStop1 = 0;             // RUN/Stop: 0(Stop), 1(Run, 00003)
uint8_t homeTrigger1 = 0;         // 원점 복귀 트리거 (00004)

/* Y축 상태 변수 */
uint32_t stepCount2 = 0;          // Y축 현재 스텝 수
uint32_t targetSteps2 = 0;        // Y축 목표 스텝 수 (40007)
float maxFrequency2 = 2000.0f;    // Y축 최대 주파수 (40006)
float minFrequency2 = 100.0f;     // Y축 최소 주파수
float homeFrequency2 = 200.0f;    // Y축 원점 복귀 속도 (40010)
float k2 = 0.01f;                 // Y축 S 커브 경사
uint32_t totalTime2 = 1000;       // Y축 S 커브 시간 (40009)
uint32_t currentTime2 = 0;        // Y축 S 커브 현재 시간
uint8_t sCurveEnabled2 = 0;       // Y축 S 커브 활성화 (40008)
uint8_t motorState2 = 0;          // Y축 상태
uint8_t runStop2 = 0;             // Y축 RUN/Stop (00007)
uint8_t homeTrigger2 = 0;         // Y축 원점 복귀 트리거 (00008)

/* 동기화 및 보호 변수 */
uint8_t syncMode = 0;             // 동기화: 0(독립), 1(동기화, 40011)
uint8_t protectionEnabled = 1;    // 보호 기능: 0(비활성화), 1(활성화, 40012)
float currentValue = 0.0f;        // 전류 값 (mA, 30005)
float temperatureValue = 0.0f;    // 온도 값 (°C, 30006)
#define CURRENT_LIMIT 2000.0f     // 과전류 임계값 (mA)
#define TEMP_LIMIT 70.0f          // 과열 임계값 (°C)

/* S 커브 룩업 테이블 (LUT) */
#define LUT_SIZE 100              // LUT 크기 (100단계)
float sCurveLUT[LUT_SIZE];        // S 커브 주파수 값 저장

/* 함수 선언 */
void SystemClock_Config(void);        // 시스템 클럭 설정 (72MHz)
void MX_GPIO_Init(void);              // GPIO 초기화 (STEP, DIR, ENABLE, 리미트 스위치)
void MX_TIM1_Init(void);              // TIM1: X축 STEP PWM
void MX_TIM2_Init(void);              // TIM2: Y축 STEP PWM
void MX_TIM3_Init(void);              // TIM3: S 커브 및 타이밍
void MX_UART2_Init(void);             // UART2: Modbus RTU
void MX_DMA_Init(void);               // DMA: UART RX/TX
void MX_ADC1_Init(void);              // ADC: 전류/온도
void initSCurveLUT(void);             // S 커브 LUT 초기화
void setMotorDirection(uint8_t motor, uint8_t direction); // 모터 방향 설정
void setMotorFrequency(uint8_t motor, float frequency);   // 모터 주파수 설정
void moveWithSCurve(uint8_t motor, uint32_t steps, uint8_t direction); // S 커브 이동
void moveLinear(uint8_t motor, uint32_t steps, uint8_t direction, uint32_t frequency); // 선형 이동
void stopMotor(uint8_t motor);        // 모터 정지
void resumeMotor(uint8_t motor);      // 모터 재개
void homeMotor(uint8_t motor);        // 원점 복귀
void syncMotors(void);                // X,Y축 동기화
void checkProtection(void);           // 과전류/과열 보호 점검

/* 메인 함수 */
int main(void) {
    HAL_Init();                       // HAL 라이브러리 초기화
    SystemClock_Config();             // 시스템 클럭 설정
    MX_DMA_Init();                    // DMA 초기화 (UART 통신 최적화)
    MX_GPIO_Init();                   // GPIO 초기화
    MX_TIM1_Init();                   // X축 PWM 타이머
    MX_TIM2_Init();                   // Y축 PWM 타이머
    MX_TIM3_Init();                   // S 커브 타이머
    MX_UART2_Init();                  // Modbus RTU UART
    MX_ADC1_Init();                   // ADC 초기화 (보호)

    initSCurveLUT();                  // S 커브 LUT 생성
    eMBInit(MB_RTU, 0x01, 0, 19200, MB_PAR_EVEN); // Modbus RTU 초기화 (주소 0x01, 19200bps)
    eMBEnable();                      // Modbus 활성화

    // 인터럽트 우선순위 설정 (NVIC)
    HAL_NVIC_SetPriority(TIM1_UP_TIM10_IRQn, 0, 0); // X축 스텝 (최고)
    HAL_NVIC_SetPriority(TIM2_IRQn, 0, 1);         // Y축 스텝
    HAL_NVIC_SetPriority(TIM3_IRQn, 1, 0);         // S 커브 타이밍
    HAL_NVIC_SetPriority(USART2_IRQn, 2, 0);       // Modbus 통신
    HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 2, 1); // UART RX
    HAL_NVIC_SetPriority(DMA1_Stream6_IRQn, 2, 2); // UART TX
    HAL_NVIC_SetPriority(ADC_IRQn, 1, 1);          // ADC (보호)

    HAL_ADC_Start_IT(&hadc1);         // ADC 인터럽트 시작 (전류/온도)

    while (1) {
        eMBPoll();                    // Modbus 요청 처리
        if (syncMode) syncMotors();   // 동기화 활성화 시 진행률 조정
        if (protectionEnabled) checkProtection(); // 보호 기능 점검
    }
}

/* 시스템 클럭 설정 (72MHz) */
void SystemClock_Config(void) {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    __HAL_RCC_PWR_CLK_ENABLE();       // 전원 관리 클럭 활성화
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); // 전압 스케일 1

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // HSE 오실레이터
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;                   // HSE 활성화
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;               // PLL 활성화
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;       // PLL 소스: HSE
    RCC_OscInitStruct.PLL.PLLM = 8;                            // PLL M 분주
    RCC_OscInitStruct.PLL.PLLN = 72;                           // PLL N 곱셈
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;                // PLL P 분주
    RCC_OscInitStruct.PLL.PLLQ = 3;                            // PLL Q 분주
    HAL_RCC_OscConfig(&RCC_OscInitStruct);                     // 오실레이터 설정 적용

    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
                                | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 클럭 타입
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 시스템 클럭: PLL
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;        // AHB 분주
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;         // APB1 분주
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;         // APB2 분주
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2); // 클럭 설정 적용
}

/* GPIO 초기화 */
void MX_GPIO_Init(void) {
    __HAL_RCC_GPIOA_CLK_ENABLE();     // GPIOA 클럭 활성화
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // DIR1(PA9), ENABLE1(PA10), DIR2(PA1), ENABLE2(PA2), DE/RE(PA4) 설정
    GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 출력 모드
    GPIO_InitStruct.Pull = GPIO_NOPULL;        // 풀업/풀다운 없음
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 저속
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // STEP1(PA8, TIM1_CH1), STEP2(PA0, TIM2_CH1) 설정
    GPIO_InitStruct.Pin = GPIO_PIN_8;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;    // 대체 기능 (PWM)
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM1; // TIM1 대체 기능
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Alternate = GPIO_AF2_TIM2; // TIM2 대체 기능
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 리미트 스위치 (PA5: X축, PA6: Y축) 설정
    GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 하강 엣지 인터럽트
    GPIO_InitStruct.Pull = GPIO_PULLUP;         // 내부 풀업 저항
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    HAL_NVIC_SetPriority(EXTI9_5_IRQn, 1, 2);   // EXTI 인터럽트 우선순위
    HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);           // EXTI 인터럽트 활성화
}

/* TIM1 초기화 (X축 STEP PWM) */
void MX_TIM1_Init(void) {
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_OC_InitTypeDef sConfigOC = {0};

    htim1.Instance = TIM1;                    // TIM1 선택
    htim1.Init.Prescaler = 72 - 1;            // 72MHz -> 1MHz
    htim1.Init.Period = 10000 - 1;            // 초기 주파수 100Hz
    htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 클럭 분주 없음
    HAL_TIM_Base_Init(&htim1);                // 타이머 기본 초기화
    HAL_TIM_PWM_Init(&htim1);                 // PWM 모드 초기화

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; // 내부 클럭
    HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig);

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; // 트리거 리셋
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; // 슬레이브 모드 비활성화
    HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);

    sConfigOC.OCMode = TIM_OCMODE_PWM1;       // PWM 모드 1
    sConfigOC.Pulse = 5000;                   // 50% 듀티 사이클
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; // 고전압 출력
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; // 빠른 모드 비활성화
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1); // 채널 1 설정
}

/* TIM2 초기화 (Y축 STEP PWM) */
void MX_TIM2_Init(void) {
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_OC_InitTypeDef sConfigOC = {0};

    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 72 - 1;
    htim2.Init.Period = 10000 - 1;
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&htim2);
    HAL_TIM_PWM_Init(&htim2);

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);

    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 5000;
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
}

/* TIM3 초기화 (S 커브 및 타이밍) */
void MX_TIM3_Init(void) {
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};

    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 72 - 1;            // 1MHz
    htim3.Init.Period = 10000 - 1;            // 10ms 주기
    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&htim3);

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig);
}

/* DMA 초기화 (UART RX/TX) */
void MX_DMA_Init(void) {
    __HAL_RCC_DMA1_CLK_ENABLE();              // DMA1 클럭 활성화

    // UART RX DMA 설정
    hdma_usart2_rx.Instance = DMA1_Stream5;
    hdma_usart2_rx.Init.Channel = DMA_CHANNEL_4;
    hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; // 주변장치 -> 메모리
    hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;    // 주변장치 주소 고정
    hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;        // 메모리 주소 증가
    hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 바이트 단위
    hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart2_rx.Init.Mode = DMA_NORMAL;               // 일반 모드
    hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW;     // 낮은 우선순위
    HAL_DMA_Init(&hdma_usart2_rx);
    __HAL_LINKDMA(&huart2, hdmarx, hdma_usart2_rx);     // UART RX와 DMA 연결

    // UART TX DMA 설정
    hdma_usart2_tx.Instance = DMA1_Stream6;
    hdma_usart2_tx.Init.Channel = DMA_CHANNEL_4;
    hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; // 메모리 -> 주변장치
    hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart2_tx.Init.Mode = DMA_NORMAL;
    hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW;
    HAL_DMA_Init(&hdma_usart2_tx);
    __HAL_LINKDMA(&huart2, hdmatx, hdma_usart2_tx);

    HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 2, 1); // RX 인터럽트
    HAL_NVIC_SetPriority(DMA1_Stream6_IRQn, 2, 2); // TX 인터럽트
}

/* UART 초기화 (Modbus RTU) */
void MX_UART2_Init(void) {
    huart2.Instance = USART2;
    huart2.Init.BaudRate = 19200;             // Modbus RTU 속도
    huart2.Init.WordLength = UART_WORDLENGTH_9B; // 9비트 (패리티 포함)
    huart2.Init.StopBits = UART_STOPBITS_1;   // 1 정지 비트
    huart2.Init.Parity = UART_PARITY_EVEN;    // 짝수 패리티
    huart2.Init.Mode = UART_MODE_TX_RX;       // 송수신 모드
    huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 하드웨어 흐름 제어 비활성화
    HAL_UART_Init(&huart2);
}

/* ADC 초기화 (전류/온도) */
void MX_ADC1_Init(void) {
    __HAL_RCC_ADC1_CLK_ENABLE();              // ADC1 클럭 활성화
    ADC_ChannelConfTypeDef sConfig = {0};

    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2; // ADC 클럭 분주
    hadc1.Init.Resolution = ADC_RESOLUTION_12B; // 12비트 해상도
    hadc1.Init.ScanConvMode = ENABLE;         // 스캔 모드 활성화
    hadc1.Init.ContinuousConvMode = ENABLE;   // 연속 변환
    hadc1.Init.DiscontinuousConvMode = DISABLE; // 불연속 변환 비활성화
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 데이터 오른쪽 정렬
    HAL_ADC_Init(&hadc1);

    sConfig.Channel = ADC_CHANNEL_7;           // PA7: 전류 (ACS712)
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES; // 샘플링 시간
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    sConfig.Channel = ADC_CHANNEL_8;           // PA8: 온도 (NTC)
    sConfig.Rank = 2;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}

/* S 커브 룩업 테이블 초기화 */
void initSCurveLUT(void) {
    // 시그모이드 함수 기반 100단계 LUT 생성
    // 수식: v(t) = v_min + (v_max - v_min) / (1 + e^(-k(t - t_0)))
    for (int i = 0; i < LUT_SIZE; i++) {
        float t = (float)i / (LUT_SIZE - 1);  // 0~1 정규화
        if (t < 0.5f) {
            // 가속 구간 (t_0 = 0.25)
            sCurveLUT[i] = minFrequency1 + (maxFrequency1 - minFrequency1) /
                           (1.0f + exp(-k1 * (t - 0.25f) * LUT_SIZE));
        } else {
            // 감속 구간 (t_0 = 0.75)
            sCurveLUT[i] = minFrequency1 + (maxFrequency1 - minFrequency1) /
                           (1.0f + exp(k1 * (t - 0.75f) * LUT_SIZE));
        }
    }
}

/* 모터 방향 설정 */
void setMotorDirection(uint8_t motor, uint8_t direction) {
    if (motor == 1) {
        // X축 DIR 핀 (PA9): 1(정방향), 0(역방향)
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, direction ? GPIO_PIN_SET : GPIO_PIN_RESET);
    } else {
        // Y축 DIR 핀 (PA1)
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, direction ? GPIO_PIN_SET : GPIO_PIN_RESET);
    }
}

/* 모터 주파수 설정 */
void setMotorFrequency(uint8_t motor, float frequency) {
    TIM_HandleTypeDef *htim = (motor == 1) ? &htim1 : &htim2; // 타이머 선택
    float minFreq = (motor == 1) ? minFrequency1 : minFrequency2; // 최소 주파수
    float maxFreq = (motor == 1) ? maxFrequency1 : maxFrequency2; // 최대 주파수
    if (frequency < minFreq) frequency = minFreq; // 하한 제한
    if (frequency > maxFreq) frequency = maxFreq; // 상한 제한
    uint32_t arr = (SystemCoreClock / (htim->Init.Prescaler + 1)) / frequency - 1; // ARR 계산
    __HAL_TIM_SET_AUTORELOAD(htim, arr); // 타이머 주기 설정
    __HAL_TIM_SET_COMPARE(htim, TIM_CHANNEL_1, arr / 2); // 50% 듀티 사이클
}

/* S 커브 이동 */
void moveWithSCurve(uint8_t motor, uint32_t steps, uint8_t direction) {
    if (!(motorState1 == 3 || motorState2 == 3)) { // 보호 상태 확인
        setMotorDirection(motor, direction); // 방향 설정
        if (motor == 1) {
            stepCount1 = 0;               // 스텝 카운트 초기화
            targetSteps1 = steps;         // 목표 스텝 설정
            currentTime1 = 0;             // S 커브 시간 초기화
            motorState1 = 1;              // 동작 상태
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_RESET); // X축 ENABLE 활성화
            HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1); // PWM 시작
            HAL_TIM_Base_Start_IT(&htim3); // S 커브 타이머 시작
        } else {
            stepCount2 = 0;
            targetSteps2 = steps;
            currentTime2 = 0;
            motorState2 = 1;
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); // Y축 ENABLE
            HAL_TIM_PWM_Start_IT(&htim2, TIM_CHANNEL_1);
            HAL_TIM_Base_Start_IT(&htim3);
        }
    }
}

/* 선형 이동 */
void moveLinear(uint8_t motor, uint32_t steps, uint8_t direction, uint32_t frequency) {
    if (!(motorState1 == 3 || motorState2 == 3)) {
        setMotorDirection(motor, direction);
        setMotorFrequency(motor, frequency);
        if (motor == 1) {
            stepCount1 = 0;
            targetSteps1 = steps;
            motorState1 = 1;
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_RESET);
            HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1);
        } else {
            stepCount2 = 0;
            targetSteps2 = steps;
            motorState2 = 1;
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET);
            HAL_TIM_PWM_Start_IT(&htim2, TIM_CHANNEL_1);
        }
    }
}

/* 모터 정지 */
void stopMotor(uint8_t motor) {
    if (motor == 1) {
        if (motorState1 == 1) {       // 동작 중일 때만
            HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1); // PWM 중지
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_SET); // ENABLE 비활성화
            motorState1 = 0;          // 정지 상태
            runStop1 = 0;             // RUN/Stop 플래그 리셋
        }
    } else {
        if (motorState2 == 1) {
            HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET);
            motorState2 = 0;
            runStop2 = 0;
        }
    }
}

/* 모터 재개 */
void resumeMotor(uint8_t motor) {
    if (!(motorState1 == 3 || motorState2 == 3)) { // 보호 상태가 아니면
        if (motor == 1 && runStop1 && motorState1 == 0) {
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_RESET); // ENABLE 활성화
            if (sCurveEnabled1) {     // S 커브 모드
                HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1);
                HAL_TIM_Base_Start_IT(&htim3);
            } else {                  // 선형 모드
                setMotorFrequency(1, maxFrequency1);
                HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1);
            }
            motorState1 = 1;          // 동작 상태
        } else if (motor == 2 && runStop2 && motorState2 == 0) {
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET);
            if (sCurveEnabled2) {
                HAL_TIM_PWM_Start_IT(&htim2, TIM_CHANNEL_1);
                HAL_TIM_Base_Start_IT(&htim3);
            } else {
                setMotorFrequency(2, maxFrequency2);
                HAL_TIM_PWM_Start_IT(&htim2, TIM_CHANNEL_1);
            }
            motorState2 = 1;
        }
    }
}

/* 원점 복귀 */
void homeMotor(uint8_t motor) {
    if (!(motorState1 == 3 || motorState2 == 3)) {
        if (motor == 1) {
            setMotorDirection(1, 0);  // 역방향
            setMotorFrequency(1, homeFrequency1); // 원점 복귀 속도
            stepCount1 = 0;           // 스텝 카운트 초기화
            motorState1 = 2;          // 원점 복귀 상태
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_RESET);
            HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1);
        } else {
            setMotorDirection(2, 0);
            setMotorFrequency(2, homeFrequency2);
            stepCount2 = 0;
            motorState2 = 2;
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET);
            HAL_TIM_PWM_Start_IT(&htim2, TIM_CHANNEL_1);
        }
    }
}

/* 동기화 */
void syncMotors(void) {
    if (syncMode && motorState1 == 1 && motorState2 == 1) {
        // 진행률 비교: 현재 스텝 / 목표 스텝
        float progress1 = (float)stepCount1 / targetSteps1;
        float progress2 = (float)stepCount2 / targetSteps2;
        if (progress1 > progress2) {
            setMotorFrequency(2, maxFrequency2 * 1.1f); // Y축 속도 10% 증가
        } else if (progress2 > progress1) {
            setMotorFrequency(1, maxFrequency1 * 1.1f); // X축 속도 10% 증가
        }
    }
}

/* 보호 기능 점검 */
void checkProtection(void) {
    if (currentValue > CURRENT_LIMIT || temperatureValue > TEMP_LIMIT) {
        stopMotor(1);                 // X축 정지
        stopMotor(2);                 // Y축 정지
        motorState1 = 3;              // X축 보호 상태
        motorState2 = 3;              // Y축 보호 상태
    }
}

/* TIM3 인터럽트 (S 커브 주파수 업데이트) */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM3) {
        if (sCurveEnabled1 && motorState1 == 1) {
            currentTime1++;           // 시간 증가
            if (currentTime1 < LUT_SIZE) {
                setMotorFrequency(1, sCurveLUT[currentTime1]); // LUT 기반 주파수 설정
            }
        }
        if (sCurveEnabled2 && motorState2 == 1) {
            currentTime2++;
            if (currentTime2 < LUT_SIZE) {
                setMotorFrequency(2, sCurveLUT[currentTime2]);
            }
        }
    }
}

/* PWM 펄스 완료 인터럽트 */
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM1 && motorState1 == 1) {
        stepCount1++;             // X축 스텝 증가
        if (stepCount1 >= targetSteps1) {
            stopMotor(1);         // 목표 도달 시 정지
        }
    }
    if (htim->Instance == TIM2 && motorState2 == 1) {
        stepCount2++;
        if (stepCount2 >= targetSteps2) {
            stopMotor(2);
        }
    }
}

/* 리미트 스위치 인터럽트 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == GPIO_PIN_5 && motorState1 == 2) { // X축 리미트 스위치
        HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1); // PWM 중지
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_SET); // ENABLE 비활성화
        stepCount1 = 0;           // 원점으로 스텝 초기화
        motorState1 = 0;          // 정지 상태
        homeTrigger1 = 0;         // 원점 복귀 완료
    }
    if (GPIO_Pin == GPIO_PIN_6 && motorState2 == 2) { // Y축 리미트 스위치
        HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET);
        stepCount2 = 0;
        motorState2 = 0;
        homeTrigger2 = 0;
    }
}

/* ADC 인터럽트 (전류/온도 측정) */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
    if (hadc->Instance == ADC1) {
        uint32_t adcValue1 = HAL_ADC_GetValue(hadc); // 전류 값 (PA7)
        uint32_t adcValue2 = HAL_ADC_GetValue(hadc); // 온도 값 (PA8)
        currentValue = (adcValue1 * 5000.0f) / 4096.0f; // ACS712: 5V/12비트
        temperatureValue = (adcValue2 * 100.0f) / 4096.0f; // NTC 단순 변환
        HAL_ADC_Start_IT(hadc);   // 다음 변환 시작
    }
}

/* Modbus Holding Register 콜백 */
eMBErrorCode eMBRegHoldingCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode) {
    eMBErrorCode eStatus = MB_ENOERR;
    if (eMode == MB_REG_READ) {
        switch (usAddress) {
            case 1: pucRegBuffer[0] = (uint16_t)maxFrequency1; break; // X축 속도
            case 2: pucRegBuffer[0] = (uint16_t)targetSteps1; break; // X축 목표 스텝
            case 3: pucRegBuffer[0] = sCurveEnabled1; break; // X축 S 커브
            case 4: pucRegBuffer[0] = (uint16_t)totalTime1; break; // X축 S 커브 시간
            case 5: pucRegBuffer[0] = (uint16_t)homeFrequency1; break; // X축 원점 속도
            case 6: pucRegBuffer[0] = (uint16_t)maxFrequency2; break; // Y축 속도
            case 7: pucRegBuffer[0] = (uint16_t)targetSteps2; break; // Y축 목표 스텝
            case 8: pucRegBuffer[0] = sCurveEnabled2; break; // Y축 S 커브
            case 9: pucRegBuffer[0] = (uint16_t)totalTime2; break; // Y축 S 커브 시간
            case 10: pucRegBuffer[0] = (uint16_t)homeFrequency2; break; // Y축 원점 속도
            case 11: pucRegBuffer[0] = syncMode; break; // 동기화
            case 12: pucRegBuffer[0] = protectionEnabled; break; // 보호 기능
            default: eStatus = MB_ENOREG; // 지원하지 않는 레지스터
        }
    } else if (eMode == MB_REG_WRITE) {
        switch (usAddress) {
            case 1: maxFrequency1 = pucRegBuffer[0]; break;
            case 2: // X축 목표 스텝 설정 및 이동 시작
                if (sCurveEnabled1) moveWithSCurve(1, pucRegBuffer[0], HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9));
                else moveLinear(1, pucRegBuffer[0], HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9), maxFrequency1);
                break;
            case 3: sCurveEnabled1 = pucRegBuffer[0]; break;
            case 4: totalTime1 = pucRegBuffer[0]; initSCurveLUT(); break; // S 커브 시간 갱신
            case 5: homeFrequency1 = pucRegBuffer[0]; break;
            case 6: maxFrequency2 = pucRegBuffer[0]; break;
            case 7: // Y축 목표 스텝
                if (sCurveEnabled2) moveWithSCurve(2, pucRegBuffer[0], HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1));
                else moveLinear(2, pucRegBuffer[0], HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1), maxFrequency2);
                break;
            case 8: sCurveEnabled2 = pucRegBuffer[0]; break;
            case 9: totalTime2 = pucRegBuffer[0]; initSCurveLUT(); break;
            case 10: homeFrequency2 = pucRegBuffer[0]; break;
            case 11: syncMode = pucRegBuffer[0]; break;
            case 12: protectionEnabled = pucRegBuffer[0]; break;
            default: eStatus = MB_ENOREG;
        }
    }
    return eStatus;
}

/* Modbus Coil 콜백 */
eMBErrorCode eMBRegCoilsCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode) {
    eMBErrorCode eStatus = MB_ENOERR;
    if (eMode == MB_REG_WRITE) {
        switch (usAddress) {
            case 1: setMotorDirection(1, pucRegBuffer[0]); break; // X축 방향
            case 2: HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, pucRegBuffer[0] ? GPIO_PIN_RESET : GPIO_PIN_SET); break; // X축 ENABLE
            case 3: runStop1 = pucRegBuffer[0]; if (pucRegBuffer[0]) resumeMotor(1); else stopMotor(1); break; // X축 RUN/Stop
            case 4: homeTrigger1 = pucRegBuffer[0]; if (pucRegBuffer[0]) homeMotor(1); break; // X축 원점 복귀
            case 5: setMotorDirection(2, pucRegBuffer[0]); break; // Y축 방향
            case 6: HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, pucRegBuffer[0] ? GPIO_PIN_RESET : GPIO_PIN_SET); break; // Y축 ENABLE
            case 7: runStop2 = pucRegBuffer[0]; if (pucRegBuffer[0]) resumeMotor(2); else stopMotor(2); break; // Y축 RUN/Stop
            case 8: homeTrigger2 = pucRegBuffer[0]; if (pucRegBuffer[0]) homeMotor(2); break; // Y축 원점 복귀
            default: eStatus = MB_ENOREG;
        }
    }
    return eStatus;
}

/* Modbus Input Register 콜백 */
eMBErrorCode eMBRegInputCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs) {
    eMBErrorCode eStatus = MB_ENOERR;
    switch (usAddress) {
        case 1: pucRegBuffer[0] = (uint16_t)stepCount1; break; // X축 현재 스텝
        case 2: pucRegBuffer[0] = motorState1; break; // X축 상태
        case 3: pucRegBuffer[0] = (uint16_t)stepCount2; break; // Y축 현재 스텝
        case 4: pucRegBuffer[0] = motorState2; break; // Y축 상태
        case 5: pucRegBuffer[0] = (uint16_t)currentValue; break; // 전류
        case 6: pucRegBuffer[0] = (uint16_t)temperatureValue; break; // 온도
        default: eStatus = MB_ENOREG;
    }
    return eStatus;
}
        

8. 주의사항

  • Modbus 타이밍: 3.5 문자 타임아웃 (~1.75ms at 19200bps).
  • DRV8825 VREF: 모터 사양에 맞게 전류 제한.
  • 리미트 스위치: 풀업 저항 필수.
  • 보호 센서: ACS712, NTC 서미스터 캘리브레이션 필요.
  • CPU: STM32F4 이상 권장.

9. 테스트 및 디버깅

  • 소프트웨어: QModbus, ModPoll.
  • 환경: 슬레이브 주소 0x01, 19200bps, Even Parity.
  • 디버깅:
    •   리미트 스위치: EXTI 인터럽트 확인.
    •   ADC: 전류/온도 값 로깅.
    •   Modbus: 테이블 기반 레지스터 테스트.

10. 결론

이 가이드는 STM32 X,Y 스테이지 제어를 위한 기본 솔루션으로, Modbus RTU 통신 인터페이스 및,  S 커브 가속, RUN/Stop, 원점 복귀, 보호 기능을 구현했습니다. CNC, 3D 프린터, 로봇 공학에 활용 가능합니다.

키워드: STM32 X,Y 스테이지, Modbus RTU , S 커브 가속, DRV8825, 원점 복귀, 보호 기능, STM32CubeIDE, 

 

반응형