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. 구현 절차
- 하드웨어: DRV8825, NEMA 17, 리미트 스위치, 센서 연결.
- 타이머: TIM1(X), TIM2(Y), TIM3(S 커브).
- ADC: 전류/온도 감지.
- Modbus: FreeModbus 초기화.
- 기능: 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,