적외선 비접촉 호출과 방향성 SCAN 배차 알고리즘을 적용한 4층 엘리베이터 제어 시스템입니다.
양손에 짐을 든 사용자나 위생상 버튼 접촉을 꺼리는 상황에서도 엘리베이터를 호출할 수 있도록, 적외선 센서를 이용한 비접촉 호출 인터페이스를 구현한 프로젝트입니다.
단순히 "버튼을 누르면 그 층으로 이동"하는 수준을 넘어서, 실제 엘리베이터가 사용하는 방향성 SCAN 배차 알고리즘을 구현했습니다. 상승 호출과 하강 호출을 별도 큐로 관리하고, 진행 방향에 남은 호출을 우선 처리한 뒤 방향을 전환합니다. 전체 제어는 RTOS 없이 HAL_GetTick() 기반 Non-Blocking 상태 머신으로 구성하여, 스텝모터가 이동하는 중에도 버튼 스캔과 서보 구동이 멈추지 않도록 설계했습니다.
| 항목 | 내용 |
|---|---|
| 프로젝트 형태 | 팀 프로젝트 (3인) · 온디바이스 AI 3회차 Project 2 |
| 담당 범위 | 스텝모터 층간 이동 제어, FND 층 표시, 하드웨어 제작, 발표 |
| MCU | STM32F411RE (NUCLEO-F411RE) |
| Language | C |
| Development | STM32CubeIDE, STM32CubeMX, STM32 HAL |
| 제어 구조 | Bare-metal Non-Blocking FSM (RTOS 미사용) |
| 층 구성 | 4개 층 (1F ~ 4F) |
| 주요 인터페이스 | GPIO, TIM Interrupt, PWM |
| 개발 기간 | 2026.05.22 ~ 2026.05.29 (8일) |
| 기능 | 구현 내용 |
|---|---|
| 비접촉 호출 | 적외선 센서 출력을 외부 호출 입력으로 처리, 버튼과 동일 경로로 디바운스 |
| SCAN Dispatch | 상승·하강·내부 호출을 3개 큐로 분리하고 진행 방향 우선 처리 |
| Non-Blocking FSM | 5개 상태를 HAL_GetTick() 비교로 전이, Delay 기반 대기 제거 |
| Interrupt Stepper | TIM 인터럽트에서 Half-step 시퀀스를 1스텝씩 출력해 이동 중에도 CPU 점유 없음 |
| Homing Sequence | 부팅 시 포토인터럽터까지 하강하여 1층 기준점 확보 |
| Gradual Servo | 10 ms 주기로 PWM을 점진 증감시켜 문이 튀지 않고 부드럽게 개폐 |
| Toggle / Edge 분리 | 층 호출은 Toggle, 열림·닫힘 버튼은 Edge 1회성으로 구분 처리 |
| 상태 시각화 | FND 현재 층, ARR LED 방향 애니메이션, 호출 LED 점등 |
기존 엘리베이터의 물리 버튼 방식이 가진 한계에서 출발했습니다.
| 문제 | 내용 |
|---|---|
| 조작 불가 상황 | 양손에 짐을 든 경우 버튼을 누르기 어려움 |
| 위생 우려 | 다중 이용 시설의 공용 버튼 접촉에 대한 감염 우려 |
| 접근성 저하 | 고령자·교통약자의 이용 불편 |
자동차의 스마트 트렁크(사용자 동작을 감지해 자동 개방)에서 착안하여, 손이나 발 동작만으로 호출이 가능한 구조를 적용했습니다.
펌웨어 관점에서 IR 센서 모듈은 디지털 레벨을 출력하므로, 버튼과 IR을 구분하지 않고 동일한 GPIO 디바운스 스캔 경로로 처리합니다. 덕분에 상위 배차 로직은 입력 장치 종류를 알 필요가 없으며, 물리 버튼과 비접촉 호출이 완전히 동등하게 동작합니다.
[ 입력 ] [ STM32F411RE ] [ 출력 ]
IR Sensor ──┐
├── GPIO ──▶ buttonScan() ┌──▶ Step Motor (ULN2003)
External Btn ──┤ 10 ms Debounce │ 층간 이동
│ Toggle / Edge 분리 │
Internal Btn ──┘ │ ├──▶ Servo Motor (TIM3 PWM)
▼ │ 문 개폐
Photo ── GPIO ──▶ ElevatorSystem_Run() ────────┤
Interrupter · 호출 큐 등록 │──▶ FND
(Homing) · SCAN 방향 판단 │ 현재 층 표시
· 5-State FSM 전이 │
│ └──▶ ARR LED × 8
▼ 방향 애니메이션
Stepper_MoveToFloor()
│
TIM Interrupt
Half-step 출력
buttonScan()이 모든 입력을 10 ms 디바운스로 스캔하고 Toggle/Edge 상태를 갱신합니다.ElevatorSystem_Run()이 Toggle 상태를 읽어callUp[],callDown[],callInternal[]큐에 호출을 등록하고 해당 LED를 점등합니다.- 현재 FSM 상태와 진행 방향에 따라 SCAN 우선순위를 평가하여 다음 목표 층을 결정합니다.
Stepper_MoveToFloor()가 필요한 스텝 수를 계산하고 TIM 인터럽트를 시작합니다.- TIM 인터럽트가 매 주기 Half-step을 1스텝씩 출력하며, 메인 루프는 그동안에도 버튼 스캔과 서보 갱신을 계속 수행합니다.
- 목표 층 도달 시 정지 조건을 평가하여 문을 열거나, 조건 미충족 시 다음 층으로 재명령합니다.
┌──────────────────────────────────────────────────────────┐
│ │
▼ │
┌────────────────┐ 호출 등록 ┌──────────────┐ 목표 층 도달 │
│ IDLE_CLOSED │─────────────▶│ MOVING │──────────────┐ │
│ 정지 · 문 닫힘 │ SCAN 우선순위 │ 스텝모터 구동 │ │ │
└────────────────┘ └──────────────┘ │ │
▲ │ 정지 조건 미충족 │ │
│ │ 다음 층 재명령 ◀─────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ DOOR_OPENING │ │
│ └──────────────┘ │
│ │ 서보 구동 완료 │
│ ▼ │
│ ┌──────────────┐ │
│ │ DOOR_OPEN │ 3 s 유지 │
│ └──────────────┘ │
│ │ Timeout / 닫힘 버튼 │
│ ▼ │
│ ┌──────────────┐ │
└───────────────────────│ DOOR_CLOSING │──────────────────┘
문 닫힘 완료 └──────────────┘
│ 열림 버튼 → DOOR_OPENING 복귀
| State | 역할 | 전이 조건 |
|---|---|---|
STATE_IDLE_CLOSED |
대기, SCAN 우선순위 재평가 | 호출 존재 시 MOVING 또는 DOOR_OPENING |
STATE_MOVING |
스텝모터 층간 이동 | Stepper_IsBusy() 해제 시 정지 조건 평가 |
STATE_DOOR_OPENING |
서보 개방 진행 | IsServoBusy() 해제 시 DOOR_OPEN |
STATE_DOOR_OPEN |
개방 유지 (3 s) | Timeout 또는 닫힘 버튼 시 DOOR_CLOSING |
STATE_DOOR_CLOSING |
서보 폐쇄 진행 | 완료 시 IDLE_CLOSED, 열림 버튼 시 재개방 |
문 닫힘 완료 후 IDLE_CLOSED로 복귀시켜 우선순위를 매번 재평가하도록 설계했습니다. 문이 닫히는 동안 새로 들어온 호출도 즉시 반영됩니다.
이 프로젝트의 핵심입니다. 호출을 단일 큐로 처리하면 "3층으로 올라가던 중 2층 하강 호출을 받아 되돌아가는" 비효율이 발생합니다. 이를 막기 위해 호출을 방향별로 분리했습니다.
static bool callUp[5]; // 각 층의 상승 방향 외부 호출
static bool callDown[5]; // 각 층의 하강 방향 외부 호출
static bool callInternal[5]; // 각 층의 내부(목적지) 버튼 호출STATE_IDLE_CLOSED에서 매 루프 아래 순서로 평가합니다.
1. 진행 방향 유지
└─ 현재 방향에 남은 호출이 있으면 그 방향으로 1개 층 이동
2. 현재 층 호출 처리
└─ 현재 층에 호출이 있으면 방향을 확정하고 즉시 문 개방
3. 반대 방향 전환
└─ 진행 방향에 호출이 없고 반대 방향에 있으면 방향 전환 후 이동
4. 완전 대기 상태
└─ 방향이 없으면 상승 → 하강 순으로 탐색하여 방향 결정
CheckCallsAhead()가 진행 방향의 남은 층을 스캔하여 호출 유무를 반환하며, 이 함수 하나로 위 4단계 우선순위가 모두 구성됩니다.
이동 중 각 층에 도달할 때마다 정지 여부를 판단합니다.
| 조건 | 내용 |
|---|---|
| 조건 1 | 해당 층의 내부 버튼이 눌렸는가 (방향 무관) |
| 조건 2 | 진행 방향과 같은 방향의 외부 버튼이 눌렸는가 |
| 조건 3 | 진행 방향에 더 이상 호출이 없고(종점) 현재 층에 반대 방향 호출이 있는가 → 방향 전환 후 정지 |
세 조건 중 하나라도 만족하면 문을 열고, 아니면 정지하지 않고 다음 층으로 재명령합니다. 이 구조 덕분에 "3층 하강 호출자를 태우러 가는 중 2층 상승 호출을 지나쳐도 되돌아가지 않는" 실제 엘리베이터의 동작이 재현됩니다.
도착 시 호출을 해제할 때도 방향을 구분합니다.
- 내부 버튼: 방향과 무관하게 해당 층 호출 해제
- 외부 버튼: 현재 진행 방향과 일치하는 호출만 해제
- 1층은 하강 호출이, 4층은 상승 호출이 존재할 수 없으므로 종단층에서는 예외 처리
즉, 3층에서 상승 중 정지했다면 3층의 상승 호출만 꺼지고 하강 호출은 그대로 유지되어, 나중에 하강할 때 다시 정지합니다.
28BYJ-48 스텝모터를 ULN2003 드라이버로 구동하며, 토크와 분해능 확보를 위해 8상 Half-step 시퀀스를 사용합니다.
static const uint8_t HALF_STEP_SEQ[8][4] = {
{1,0,0,0}, {1,1,0,0}, {0,1,0,0}, {0,1,1,0},
{0,0,1,0}, {0,0,1,1}, {0,0,0,1}, {1,0,0,1}
};while 루프로 스텝을 돌리면 이동 중 다른 동작이 모두 멈춥니다. 이를 피하기 위해 TIM 인터럽트에서 1스텝씩만 출력하고, 남은 스텝 수를 감소시키는 방식으로 구현했습니다.
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (remainingSteps > 0) {
currentStep = (motorDirection == STEPPER_DIR_UP)
? (currentStep + 1) % 8
: (currentStep == 0 ? 7 : currentStep - 1);
Stepper_Write(currentStep);
remainingSteps--;
} else {
HAL_TIM_Base_Stop_IT(stepper_htim);
Stepper_AllOff(); // 정지 시 코일 전류 차단 (발열 방지)
currentFloor = targetFloor;
motorBusy = 0;
}
}| Parameter | Value | Description |
|---|---|---|
STEPS_PER_FLOOR |
3500 | 1개 층 이동에 필요한 스텝 수 (실측 보정값) |
STEPPER_SPEED_HZ |
550 | 스텝 출력 주파수 |
STEPPER_TIMER_HZ |
1,000,000 | 타이머 기준 클럭 (1 MHz) |
| Prescaler | 100 − 1 | 타이머 분주 설정 |
| Auto-Reload | 1000000 / 550 − 1 |
스텝 주기 결정 |
전원 인가 시점의 실제 위치를 알 수 없으므로, 부팅 직후 포토인터럽터가 감지될 때까지 하강시켜 물리적 기준점(1층) 을 확보합니다.
while (HAL_GPIO_ReadPin(PhotoIt_PORT, PhotoIt_PIN) == GPIO_PIN_RESET)
{
currentStep = (currentStep == 0) ? 7 : currentStep - 1;
Stepper_Write(currentStep);
HAL_Delay(1);
}
Stepper_AllOff();
currentFloor = 1;Homing은 부팅 시 1회만 수행되며, 이후 층 위치는 스텝 카운트로 관리합니다.
서보에 목표 각도를 한 번에 쓰면 문이 순간적으로 튀듯이 움직입니다. 이를 막기 위해 10 ms마다 PWM 값을 조금씩 증감시키는 점진 구동 방식을 사용했습니다.
pwmStep = (PWM_DOOR_OPENED - PWM_DOOR_CLOSED)
/ (SERVO_MOVE_TIME_MS / SERVO_UPDATE_INTERVAL);
// ServoStateUpdate() 내부 (10 ms 주기)
if (ServoState == DOOR_OPENING) {
currentPwm += pwmStep;
if (currentPwm >= PWM_DOOR_OPENED) {
currentPwm = PWM_DOOR_OPENED;
ServoState = DOOR_OPENED;
doorOpenStartTime = currentTick;
}
__HAL_TIM_SET_COMPARE(SERVO_TIM, SERVO_CHANNEL, (uint32_t)currentPwm);
}| Parameter | Value | Description |
|---|---|---|
PWM_DOOR_CLOSED |
100 | 문 닫힘 위치 CCR 값 |
PWM_DOOR_OPENED |
200 | 문 열림 위치 CCR 값 |
SERVO_MOVE_TIME_MS |
1000 | 개폐 총 소요 시간 |
SERVO_UPDATE_INTERVAL |
10 | PWM 갱신 주기 |
DOOR_HOLD_TIME_MS |
3000 | 완전 개방 후 유지 시간 |
문 상태 전이에 조건을 두어 물리적으로 불가능한 명령을 차단했습니다.
OpenDoor():DOOR_CLOSED또는DOOR_CLOSING상태에서만 허용 → 닫히는 중에도 즉시 재개방 가능CloseDoor():DOOR_OPENED상태에서만 허용 → 열리는 도중의 닫기 명령은 무시
10 ms 디바운스를 적용하고, 입력 성격에 따라 Toggle 방식과 Edge 방식을 분리했습니다.
if ((currentTime - button[i].lastDebounceTime) > DEBOUNCE_DELAY)
{
if (currentState != button[i].debouncedState)
{
button[i].debouncedState = currentState;
if (button[i].debouncedState == button[i].onState)
{
isEdgeDetected[i] = true; // Edge: 1회성
if (i != BTN_INT_OPEN && i != BTN_INT_CLOSE)
toggleState[i] = !toggleState[i]; // Toggle: 상태 유지
}
}
}| Type | 대상 | 동작 |
|---|---|---|
| Toggle | 층 호출 버튼 (외부 6 / 내부 4) | 눌리면 상태 유지, 도착 시 buttonSetToggleState()로 명시적 해제 |
| Edge | 열림 / 닫힘 버튼 | buttonWasPressed() 호출 시 1회만 true 반환 후 플래그 소거 |
호출 버튼을 Toggle로 둔 이유는, 배차 로직이 매 루프 "지금 이 층에 호출이 걸려 있는가"를 조회하는 구조이기 때문입니다. 도착 처리 시점에만 해제되므로 호출 상태와 LED 점등이 자연스럽게 일치합니다.
또한 buttonInit()에서 부팅 직후 실제 핀 상태를 읽어 초기값으로 설정하여, 풀업 저항 안정화 전의 오검출을 방지했습니다.
| 표시 장치 | 구현 방식 |
|---|---|
| FND | 층 도달 시점에 FND_DisplayNumber()로 현재 층 갱신, 부팅 시 8을 표시해 세그먼트 전체 점등 확인 |
| ARR LED × 8 | 100 ms 주기로 점등 위치를 이동시키는 체이싱 애니메이션, 상승은 정방향·하강은 역방향, 정지 시 전체 소등 |
| Call LED | 호출 등록 시 점등, 해당 방향으로 도착 처리될 때 소등 |
ARR LED는 이동 중(STATE_MOVING)에만 동작하며, 문이 열리거나 대기 중일 때는 ARR_DIR_STOP으로 전체 소등되어 현재 상태가 직관적으로 구분됩니다.
| Peripheral | Pin | Usage |
|---|---|---|
| Step Motor IN1 ~ IN4 | PB13 / PB14 / PB15 / PB1 | ULN2003 경유 28BYJ-48 구동 |
| Photo Interrupter | PB7 | 부팅 시 1층 Homing 기준점 |
| Servo Motor | TIM3 CH2 | 문 개폐 PWM |
| Step Timer | TIM (Base Interrupt) | Half-step 시퀀스 출력 주기 |
| Button | Pin |
|---|---|
| 1F Up | PC8 |
| 2F Up / 2F Down | PC6 / PC5 |
| 3F Up / 3F Down | PC9 / PB8 |
| 4F Down | PB9 |
| Button | Pin |
|---|---|
| 1F / 2F / 3F / 4F | PA5 / PA6 / PA7 / PC7 |
| Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| Pin | PA8 | PA9 | PC13 | PD2 | PA15 | PB10 | PC4 | PB12 |
전체 핀 배치와 클럭 설정은
project_elevater.ioc를 STM32CubeMX로 열어 확인할 수 있습니다.
| Problem | Cause | Applied Solution |
|---|---|---|
| 층간 이동 거리 오차 | 구조물 하중·마찰로 이론 스텝 수와 실제 이동거리 불일치 | 반복 실측으로 STEPS_PER_FLOOR를 3500으로 재조정 |
| 이동 중 버튼 입력 무시 | while 루프 기반 스텝 구동이 메인 루프를 Blocking |
TIM 인터럽트에서 1스텝씩 출력하도록 변경 |
| 문이 튀듯이 급하게 개폐 | 목표 PWM 값을 한 번에 인가 | 10 ms 주기로 PWM 점진 증감, 1초에 걸쳐 이동 |
| 닫히는 중 재개방 불가 | 문 상태 전이 조건이 DOOR_CLOSED로만 제한 |
DOOR_CLOSING 상태에서도 OpenDoor() 허용 |
| 정지 후 모터 발열 | 정지 상태에서도 코일에 전류 유지 | 이동 완료 시 Stepper_AllOff()로 전 상 차단 |
| 전원 인가 시 위치 불명 | 스텝모터는 절대 위치를 알 수 없음 | 포토인터럽터까지 하강하는 Homing 시퀀스 추가 |
| 부팅 직후 버튼 오검출 | 구조체 초기화 시 실제 핀 상태와 불일치 | buttonInit()에서 실제 핀 상태를 읽어 초기값 설정 |
| IR 센서 오인식 | 주변 환경·사용자 각도에 민감하게 반응 | 감지 거리·각도 재조정 및 임계값 튜닝 |
| 다중 모듈 구동 시 오동작 | MCU 단독 전원으로 전류 부족, 전압 강하 발생 | MB-102 외부 전원 모듈로 전원 분리 공급 |
| GPIO 핀 부족 | FND·LED·버튼·센서 등 입출력 장치 과다 | 핀 우선순위 재정리 및 미사용 GPIO 재활용 |
| 신호 전달 불안정 | 브레드보드·점퍼선 접촉 저항 | 배선 재정리, 불량 점퍼선 교체, GND 안정화 |
| 항목 | 검증 방법 | 테스트 환경 | 결과 |
|---|---|---|---|
| 층 이동 정확도 | 반복 호출 및 도착 위치 오차 측정 | 4층 모형 트랙 (1F~4F) | PASS |
| 비접촉 호출 기능 | IR 센서 인식 거리·각도 시험 | 실내 조명, 5~10 cm | PASS |
| FND 출력 | 현재 층 수치 표시 육안 검수 | 시리얼 모니터 병행 비교 | PASS |
| 서보모터 문 제어 | 개폐 각도·속도 반복 시험 | 5 V 외부 전원, 20회 반복 | PASS |
| ARR LED 방향 표시 | 상승/하강 점등 패턴 확인 | 일상 조도 환경 | PASS |
| 다중 입력 처리 | 이동 중 호출 입력 큐잉 확인 | 동시 다중 호출 | PASS |
6 / 6 항목 정상 동작 검증 완료
Touch-Free-Elevator/
├── assets/
│ └── elevator_prototype.png # 완성 프로토타입 이미지
│
├── STM32CubeIDE/
│ ├── Application/
│ │ ├── Startup/
│ │ │ └── startup_stm32f411retx.s
│ │ └── User/ # 핵심 애플리케이션 코드
│ │ ├── elevator_ctrl.c / .h # SCAN 배차 알고리즘 및 5-State FSM
│ │ ├── stepper.c / .h # Half-step 구동, Homing, 층 위치 관리
│ │ ├── servo.c / .h # 점진 PWM 문 개폐 제어
│ │ ├── button.c / .h # 디바운스, Toggle/Edge 입력 처리
│ │ ├── led.c / .h # 호출 상태 LED
│ │ ├── arrLED.c / .h # 방향 표시 체이싱 애니메이션
│ │ ├── fnd.c / .h # 7-Segment 현재 층 표시
│ │ └── syscalls.c / sysmem.c
│ ├── STM32F411RETX_FLASH.ld
│ ├── STM32F411RETX_RAM.ld
│ ├── .cproject / .project # STM32CubeIDE 프로젝트 설정
│ └── .settings/
│
├── Drivers/ # STM32 HAL / CMSIS (CubeMX 생성)
├── Inc/ # CubeMX 생성 Header
├── Src/ # CubeMX 생성 Source (main, gpio, tim, usart)
├── project_elevater.ioc # STM32CubeMX 핀·클럭 설정
├── .mxproject
└── README.md
Inc/·Src/가STM32CubeIDE/Application/User/와 분리된 구조는 CubeMX 기본 생성 형태입니다..cproject의 Include Path가../../Inc,../../Drivers/...상대 경로를 참조하므로 폴더를 옮기면 빌드가 깨집니다. 원본 경로를 그대로 유지했습니다.
| File | Description |
|---|---|
Application/User/elevator_ctrl.c |
방향별 호출 큐, SCAN 우선순위 판단, 5-State FSM 전이 |
Application/User/stepper.c |
Half-step 시퀀스, TIM 인터럽트 구동, Homing, 층 위치 추적 |
Application/User/servo.c |
10 ms 주기 점진 PWM 개폐 및 상태 전이 가드 |
Application/User/button.c |
10 ms 디바운스, Toggle / Edge 분리 처리 |
Application/User/arrLED.c |
8구간 방향 표시 체이싱 애니메이션 |
Application/User/fnd.c |
7-Segment 현재 층 출력 |
- 적외선 비접촉 호출과 물리 버튼을 동일 경로로 처리하는 통합 입력 구조 구현
- 상승·하강·내부 호출을 분리한 방향성 SCAN 배차 알고리즘 구현
- 스텝모터 이동 중에도 입력·서보·표시가 멈추지 않는 Non-Blocking 제어 구조 확보
- Homing 시퀀스로 전원 인가 시점의 위치 불확실성 해소
- 층 이동 정확도, 비접촉 호출, FND 출력, 문 제어, 방향 표시, 다중 입력 처리 6개 항목 전체 정상 동작 검증
- 스텝모터의 이론적 스텝 수와 실제 이동거리는 하중·마찰로 달라지며, 실측 기반 보정이 필수라는 점
- Blocking 코드를 인터럽트와 Tick 비교로 전환했을 때 시스템 응답성이 근본적으로 달라진다는 점
- 상태 머신에 전이 가드 조건을 두면 물리적으로 불가능한 명령을 코드 레벨에서 차단할 수 있다는 점
- 입력의 성격(지속 상태 / 순간 이벤트)에 따라 Toggle과 Edge를 나누어야 상위 로직이 단순해진다는 점
- 실제 엘리베이터의 배차가 단순 FIFO가 아니라 방향성을 가진 SCAN 구조라는 점과 그 구현 방법
- 센서 인식 정확도 개선: 거리·각도별 감도 보정 및 사용자 동작 오인식 최소화
- 다양한 사용자 환경 대응: 사용자 키·동작 차이와 조도 변화에 따른 인식 안정화
- 열림 / 닫힘 버튼 활성화: 제어 로직은 구현되어 있으나, 후보 핀(PB12·PA8·PA9)이 ARR LED와 충돌하고 PB6은 RESET과 겹쳐 현재 미할당 상태 (핀 재배치 또는 I/O 확장 필요)
- 층 이동 시 가속·감속 프로파일 적용으로 진동 저감
- 과부하 감지 및 도어 끼임 방지 센서 추가
- 장시간 연속 동작 시 스텝 누적 오차 보정을 위한 주기적 Re-homing
본 저장소는 포트폴리오 코드 리뷰를 목적으로 정리한 STM32CubeIDE 프로젝트입니다. 빌드 산출물(Debug/, *.o, *.su 등)은 제외했으며, 소스와 프로젝트 설정 파일은 원본 경로를 그대로 유지했습니다. 재현 시에는 project_elevater.ioc를 STM32CubeMX로 열어 주변장치와 핀 설정을 확인하시면 됩니다.
Embedded Firmware · Non-Blocking FSM · Motor Control · Dispatch Algorithm
GitHub: @kimdk1005-collab
