RoomFit FW v1.0.1 Architecture

STM32F446ZET · 180MHz · Dual BLDC FOC · BLE (UART3) · 2025.07.05

시간축으로 본 CPU 점유

100µs(=10kHz) 주기 안에서 누가 CPU를 쓰는지. ISR이 선점하면 나머지는 멈춤.

🔴 ADC ISR ~35µs (선점 불가) 🟢 SysTick ~3-20µs (ISR에 선점됨) 🔵 Main Loop 나머지 (모두에 선점됨) ⬜ idle

3개 실행 컨텍스트

🔴 ADC ISR 10kHz · Priority 0 · ~35µs

TIM1 CC4 → ADC Injected 완료 인터럽트. 전류 센싱 → 위치/속도 → 무게 로직 → FOC 전부 수행.

최고 우선순위. 이 안의 모든 연산이 모터 제어 품질을 결정.

🟢 SysTick Scheduler 1kHz · 시분할

1ms마다 등록된 태스크 실행. 50ms BLE Report, 10ms 무게 점진, 100ms Derating/LED.

ISR보다 낮은 우선순위 — ADC ISR이 오면 즉시 선점당함.

🔵 Main Loop 비결정적

while(1). BLE 명령 파싱, 회생저항, LED 등. 모든 인터럽트에 선점됨.

Drive Status 상태 전이

전원 ON부터 운동 가능까지의 시간 흐름. 각 단계에서 ISR이 다른 로직을 실행.

전체 부팅 플로우

🔑 IAP 부트로더

Sector 0 (0x0800_0000, 16KB) · UART3/BLE 경유 · Y-Modem 프로토콜

⚠️ Git 미등록! ~/HSW_Mac/STMWorkspace/STM32F446_iap/에 .git 없음. 백업: STM32F446_iap_checkpoint_241220/만 존재.

Flash 메모리 레이아웃 (주소축)

주소 0x0800_0000부터 0x0807_FFFF까지 512KB. IAP가 앞 16KB, 메인 FW가 나머지 496KB.

부팅 판단 (시간축 3초)

전원 ON 후 3초간 BLE UART를 감시. 아무것도 안 오면 메인 FW로 점프.

Jump to Application 절차

// 1. SP 유효성: 0x08004000의 값이 SRAM(0x20020000) 범위인지
if ((*(__IO uint32_t*)0x08004000 & 0x2FFE0000) == 0x20020000) {
    HAL_RCC_DeInit();                              // 2. 클럭 초기화
    JumpAddress = *(uint32_t*)(0x08004000 + 4);    // 3. Reset Handler 읽기
    __set_MSP(*(uint32_t*)0x08004000);             // 4. Stack Pointer 설정
    JumpToApplication();                            // 5. 메인 FW 시작!
}

IAP 메뉴

기능설명
dFlash Dump지정 주소 256B HEX 덤프
eErase섹터 단위 삭제 (1~7, A=전체)
1/#DownloadY-Modem FW 수신. #은 완료 후 자동 리셋
2UploadFlash → Y-Modem 송신 (백업)
3Jump유효성 검사 후 메인 FW로
숨겨진 리부팅: [FF FF 02 FC 04 00] 시퀀스 수신 시 SystemReset. BLE 프로토콜 헤더와 동일 형식.

핵심 상수

상수의미
APPLICATION_ADDRESS0x0800_4000메인 FW 시작 (Sector 1)
WAIT_ON_STARTUP3000ms부팅 시 UART 대기
FLASH_CHECK_FW_MIN_SIZE64KB최소 유효 FW 크기
HW_VER3PCB v3+ (UART3: PD8/PD9)

파일 구조

STM32F446_iap/Core/Src/
├── main.c      245줄 — 부팅 판단 + Jump
├── menu.c      311줄 — IAP 메뉴 + Download/Upload
├── ymodem.c    507줄 — Y-Modem 프로토콜
├── flash_if.c  253줄 — Flash Erase/Write/Check
└── common.c    109줄 — UART 유틸

🚀 부팅 시퀀스

main() 진입부터 운동 가능 상태까지

시간축 부팅 타이밍

각 단계의 실행 시간과 순서. 실제 시간은 하드웨어 상태에 따라 변동.

부팅 단계 상세

1️⃣ HAL 초기화 (~10ms)
HAL_Init() → SystemClock_Config()  // 180MHz HSE PLL
2️⃣ 페리페럴 시작 (~5ms)
ADC1/2/3_InjectedStart_IT()  // ← 이 순간부터 ISR 10kHz 시작!
TIM1/8 PWM Start             // 모터 PWM (6채널)
TIM2/4 Encoder Start         // 엔코더 카운터
TIM1 Start 이후 ISR이 돌지만, MotorInitStart=0이라 Init_Encoder는 아직 안 함.
3️⃣ WESPION 초기화 (~200ms)
Flash_Init()           // EEPROM → RAM 복원
Initialize_WESPION()   // 버전, 머신, 무게, 가동범위 파라미터
MotorInitStart = 1     // ← 이제 ISR에서 EncInit 시작!
4️⃣ 스케줄러 등록 + while(1)
Task_Start_100ms(Task100ms)       // Derating
Task_Start_100ms(LED_Controller)  // LED
Task_Start_60s(Report_Vdc)        // 전압

while(1) {
  Protocol_Receive_Task_Uart3()   // BLE 명령 파싱
  Regen_Resistor_Control_HSW()    // 회생저항 보호
  Check_BLE_Status()              // 연결 상태
  // ...
}

⚡ ISR 체인

HAL_ADCEx_InjectedConvCpltCallback · 10kHz · Priority 0

100µs 주기 내 실행 흐름 (시간축)

한 번의 ISR 호출 동안 무엇이 어떤 순서로 얼마나 걸리는지.

콜백 내부 코드

void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef* hadc) {
  if (hadc == &hadc1) {
    ADC_Inject[0..2][0..3] = InjectedGetValue(...)  // ① ADC 결과
    WP_EncPos(MOTOR1/2, &Motor_Ang)                  // ② 엔코더 SPI
    ConvAng2Pos()                                     // ③ 위치/속도

    switch (Drive_Status) {                           // ④ 상태별 분기
      case RunGym:      WeightManager();      break;  //    정상 운동
      case EncInit:     Init_Encoder_v3();    break;  //    Z펄스 탐색
      case Calibration: ForcedCalibration();  break;  //    강제 감기
    }

    WESPION_MotorControl();  // ⑤ FOC (항상 실행)
  }
}
🔴 ISR 내 UART 호출 문제
WeightOnOff(), MotionAutoWeight(), ClampEccWeight()에서 UART3_Puts()Return_*()를 직접 호출. UART_PutData()NVIC_DisableIRQ/EnableIRQ로 FOC 제어 지터 유발.
🔴 ForcedCalibration 내 delay_msec(200)
PositionCalibrate()에서 200ms 블로킹. ISR 안에서 모터 제어 완전 정지.

⏱️ SysTick 스케줄러

scheduler.c · HAL_SYSTICK_Callback() · 1kHz

시분할 타이밍 (시간축)

100ms 구간에서 각 태스크가 언제 실행되는지. 오프셋으로 겹침 방지.

등록된 태스크

슬롯태스크역할등록
10msWeightBLEtoSetTaskBLE 무게 → 점진 적용 (0.025kg/step)동적 (BLE)
50msReport_Task_v3BLE Report 17바이트 전송동적 (START_REPORT)
100msTask100msDerating + 전압 리포트부팅 시
100msLED_ControllerWS2812B LED 제어부팅 시
60sReport_VdcDC 전압 주기 리포트부팅 시
동적 등록 패턴: Task_Start_50ms(fn)으로 등록, Task_Stop_50ms()로 해제. BLE Report와 무게 변경은 앱 명령에 의해 동적 시작/정지.

🏋️ 무게 제어

WESPION_App.c · WeightManager() · ISR 컨텍스트

위치축 Region 맵

케이블 위치(mm)에 따른 부하 변화. 위로 갈수록 케이블이 길어짐 = 사용자가 당김.

무게 모드별 부하 곡선 (위치축)

같은 위치에서 모드에 따라 부하가 어떻게 달라지는지.

모드 상세

Mode이름동작상태
0Constant전 구간 일정 부하 (F_Load × OnOffScale)✅ 배포
1Negative속도 방향에 따라 적응. 내릴 때(이완) 무게 증가✅ 배포
2Band위치 비례. 높이 당길수록 무거워짐 (탄성밴드 모사)✅ 배포
3+-미구현추가 예정

MotionAutoWeight (자동 On/Off)

속도 기반으로 3가지 자동 전환 (ISR 내에서 판단):

조건임계값유지 시간결과
FastPullOn양쪽 ≥520mm/s~100ms (1000 cycle)자동 ON
DropRelease한쪽 ≤-720mm/s~150ms (1500 cycle)자동 OFF
AutoRest양쪽 ±35mm/s 이내~5초 (50000 cycle)자동 OFF

MotionAutoActive=0 기본 — BLE 명령(0x6A)으로 활성화 필요.

FeedForward Scale

모터 응답성 보상. 부하 크기에 따라 3단계:

F_LoadFfScale이유
≤1.5kg1.4 (Max)저부하에서 응답 보강
1.5~2.5kg0.75중간
>2.5kg0.1 (Min)고부하에서 진동 억제

H_shock 이하에서는 FfScale=0 (바닥 진동 방지)

⚙️ 모터 제어 (FOC)

WESPION_MC.c · ISR 컨텍스트 · 2812줄

FOC 파이프라인 (시간축, ISR 내)

모터 제어 상태 머신

상태의미동작
Inv_Off초기ADC 오프셋 캘리 → Inv_Init
Inv_Init초기화오프셋 완료 → Ready
Inv_Ready대기외부 모드 변경 대기
Inv_TorqueCtrl토크 제어Iq PI → PWM (메인 모드)
Inv_IbyFCtrl주파수 기반EncInit용 (강제 회전)

핵심 파라미터

파라미터비고
모터BS-105-10, 10-pole (P=5)
센서 기어비3.0Flash
스풀 직경60mmFlash
Weight→Current0.5 A/kgFlash
마찰 전류0.45A ≈ 0.9kgFlash
최대 전류30A
PWM 주기10kHzTIM1/8
교번 구동ONL/R 번갈아 제어 (시간 부족 대응)

📡 BLE 프로토콜

protocol.c + WESPION_BLE.c · UART3 · 115200bps

패킷 구조

명령 맵 (18개)

CMD이름방향동작
0x60MODE_CHANGEApp→FW무게 모드 변경
0x61SET_RANGEApp→FW현재 위치를 저점/고점 등록
0x62SET_RANGE_DIGITApp→FW숫자로 저점/고점 직접 설정
0x63POS_CALIBApp→FW위치 영점 캘리
0x64SET_WEIGHTApp→FW무게 설정 (raw×0.5, ≥60 에러!)
0x65WEIGHTONOFFApp→FW무게 ON/OFF
0x66START_REPORTApp→FW50ms Report 시작
0x67STOP_REPORTApp→FWReport 정지
0x68WEIGHT_ADJUSTApp→FW무게 ±0.5kg
0x69ECC_LEVELApp→FW신장성 레벨
0x6AAUTOWEIGHT_ACTIVEApp→FWMotionAuto 활성화
0x6BAUTOWEIGHT_STATUSFW→App자동 On/Off 알림
0x70GET_VERSIONApp→FWFW 버전 응답
0x71GET_POSITIONApp→FW현재 위치 응답
0x72GET_VOLTAGEApp→FWDC 전압 응답
0x7FWEIGHTPLUSFW→App무게 변경 진행 알림
0x80GYM_RANGE_INITApp→FW가동범위 초기화
0xFFDEBUGApp→FW디버그 토글

BLE Report v3 (50ms)

17 bytes: [FF FF] [0E] [66] [cnt_H cnt_L]
  [posL_H posL_L] [posR_H posR_L]        // 0.05mm 단위
  [forceL_H forceL_L] [forceR_H forceR_L] // Icmd.q × C2W × 100
  [vdc_H vdc_L]                            // Vdc × 100
  [checksum] [0D 0A]

🔧 초기화 시퀀스

EncInit + ForcedCalibration · ISR 컨텍스트

Init_Encoder_v3 (Z펄스 탐색)

인크리멘탈 엔코더 원점 찾기. 모터를 약하게 회전시켜 Z펄스(EXTI) 감지.

항목
전류0.75A → 1.0A (2단계)
각도 탐색0°~315° (8단계, 90° 간격 우선)
회전 대기1초, Z펄스 대기 5초
안전역방향 360° 초과 → 다음 시도
좌우독립 진행

ForcedCalibration (강제 감기)

케이블 완전 감기로 원점 확정. v1.0.1에서는 기본 스킵(Active_ForcedCalib=0).

항목
전류0.4 → 0.5 → 0.9 → 1.1A (적응)
완료 조건0.75초 이상 정지
안전 장치역방향 180°, 거리 3.5m, 과속 250RPM, 20초 타임아웃
⚠️ delay_msec(200) in ISR — PositionCalibrate() 내부. 200ms간 모터 제어 + 모든 인터럽트 정지.

📁 파일 맵

~/HSW_Mac/STMWorkspace/RoomFitMCU_v1/ · 총 13,945줄

Core/Src/
├── WESPION_MC.c       2,812줄 — FOC 모터 제어 (sinTab 2048 포함)
├── WESPION_App.c      1,537줄 — 무게 로직 + 엔코더 초기화 + 강제캘리
├── protocol.c         1,427줄 — BLE 프로토콜 파싱 + Report
├── tim.c              1,033줄 — 타이머 설정 (CubeMX)
├── led.c                907줄 — WS2812B LED 제어
├── adc.c                905줄 — ADC 설정 + ISR 콜백 (핵심 진입점!)
├── usart.c              771줄 — UART 드라이버 (TX 큐 + RX 버퍼)
├── main.c               645줄 — 부팅 + while(1) 루프
├── stm32f4xx_it.c       535줄 — 인터럽트 벡터
├── flash.c              437줄 — EEPROM 에뮬레이션
├── gpio.c               372줄 — GPIO (CubeMX)
├── spi.c                298줄 — SPI (엔코더 통신)
├── WESPION.c            253줄 — 전역 초기화
├── scheduler.c          235줄 — SysTick 스케줄러
├── WESPION_BLE.c        129줄 — BLE 상태 체크
└── WSPN_Security.c       ??줄 — 보안/인증

v1.0.0 대비 변경

변경내용
삭제WESPION_HIL.c/.h — HIL 테스트 모드
추가Init_Encoder_v3 / ForcedCalibration / Debug_UART3_printf
변경PositionFine2(0.1mm) 확대 사용, 전향보상 리팩터, UART 토글 제어

🔴 이슈 & TODO

리팩토링 대상 + 버그

ISR 비효율

🔴 ISR 내 UART/BLE 전송 (15회)
함수UART3_PutsReturn_*합계
WeightOnOff448
MotionAutoWeight336
ClampEccWeight011

해결안: 플래그만 세팅 → 스케줄러 태스크에서 전송

🔴 ForcedCalib delay_msec(200)

ISR 내 200ms 블로킹. 해결안: 타이머 카운터 비동기 대기

🟡 ConvAng2Pos 중복 연산

Position/PosFine/PosFine2 동일 기저값 ×1/×20/×10. 한 번 계산 후 곱하면 float 4회 절감.

🟡 Debug_UART3_printf (35개)

Init_Encoder 20개 + ForcedCalib 15개. "배포시 제거" 주석. DebugingPrintActive=0 기본이라 실행 안 되지만 호출 오버헤드 남음.

버그

🐛 SET_WEIGHT 60kg = 에러

raw*0.5 >= 60 → 정확히 60kg에서 에러. >여야 맞음. 앱에서 59kg으로 우회 중.

🐛 SET_RANGE_DIGIT ACK 항상 1

에러 시에도 ACK=1 반환.