STM32F446ZET · 180MHz · Dual BLDC FOC · BLE (UART3) · 2025.07.05
100µs(=10kHz) 주기 안에서 누가 CPU를 쓰는지. ISR이 선점하면 나머지는 멈춤.
TIM1 CC4 → ADC Injected 완료 인터럽트. 전류 센싱 → 위치/속도 → 무게 로직 → FOC 전부 수행.
최고 우선순위. 이 안의 모든 연산이 모터 제어 품질을 결정.
1ms마다 등록된 태스크 실행. 50ms BLE Report, 10ms 무게 점진, 100ms Derating/LED.
ISR보다 낮은 우선순위 — ADC ISR이 오면 즉시 선점당함.
while(1). BLE 명령 파싱, 회생저항, LED 등. 모든 인터럽트에 선점됨.
전원 ON부터 운동 가능까지의 시간 흐름. 각 단계에서 ISR이 다른 로직을 실행.
Sector 0 (0x0800_0000, 16KB) · UART3/BLE 경유 · Y-Modem 프로토콜
~/HSW_Mac/STMWorkspace/STM32F446_iap/에 .git 없음.
백업: STM32F446_iap_checkpoint_241220/만 존재.
주소 0x0800_0000부터 0x0807_FFFF까지 512KB. IAP가 앞 16KB, 메인 FW가 나머지 496KB.
전원 ON 후 3초간 BLE UART를 감시. 아무것도 안 오면 메인 FW로 점프.
// 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 시작!
}
| 키 | 기능 | 설명 |
|---|---|---|
d | Flash Dump | 지정 주소 256B HEX 덤프 |
e | Erase | 섹터 단위 삭제 (1~7, A=전체) |
1/# | Download | Y-Modem FW 수신. #은 완료 후 자동 리셋 |
2 | Upload | Flash → Y-Modem 송신 (백업) |
3 | Jump | 유효성 검사 후 메인 FW로 |
[FF FF 02 FC 04 00] 시퀀스 수신 시 SystemReset. BLE 프로토콜 헤더와 동일 형식.
| 상수 | 값 | 의미 |
|---|---|---|
APPLICATION_ADDRESS | 0x0800_4000 | 메인 FW 시작 (Sector 1) |
WAIT_ON_STARTUP | 3000ms | 부팅 시 UART 대기 |
FLASH_CHECK_FW_MIN_SIZE | 64KB | 최소 유효 FW 크기 |
HW_VER | 3 | PCB 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() 진입부터 운동 가능 상태까지
각 단계의 실행 시간과 순서. 실제 시간은 하드웨어 상태에 따라 변동.
HAL_Init() → SystemClock_Config() // 180MHz HSE PLL
ADC1/2/3_InjectedStart_IT() // ← 이 순간부터 ISR 10kHz 시작!
TIM1/8 PWM Start // 모터 PWM (6채널)
TIM2/4 Encoder Start // 엔코더 카운터
MotorInitStart=0이라 Init_Encoder는 아직 안 함.Flash_Init() // EEPROM → RAM 복원
Initialize_WESPION() // 버전, 머신, 무게, 가동범위 파라미터
MotorInitStart = 1 // ← 이제 ISR에서 EncInit 시작!
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() // 연결 상태
// ...
}
HAL_ADCEx_InjectedConvCpltCallback · 10kHz · Priority 0
한 번의 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 (항상 실행)
}
}
WeightOnOff(), MotionAutoWeight(), ClampEccWeight()에서
UART3_Puts()와 Return_*()를 직접 호출.
UART_PutData() 내 NVIC_DisableIRQ/EnableIRQ로 FOC 제어 지터 유발.
PositionCalibrate()에서 200ms 블로킹. ISR 안에서 모터 제어 완전 정지.
scheduler.c · HAL_SYSTICK_Callback() · 1kHz
100ms 구간에서 각 태스크가 언제 실행되는지. 오프셋으로 겹침 방지.
| 슬롯 | 태스크 | 역할 | 등록 |
|---|---|---|---|
| 10ms | WeightBLEtoSetTask | BLE 무게 → 점진 적용 (0.025kg/step) | 동적 (BLE) |
| 50ms | Report_Task_v3 | BLE Report 17바이트 전송 | 동적 (START_REPORT) |
| 100ms | Task100ms | Derating + 전압 리포트 | 부팅 시 |
| 100ms | LED_Controller | WS2812B LED 제어 | 부팅 시 |
| 60s | Report_Vdc | DC 전압 주기 리포트 | 부팅 시 |
Task_Start_50ms(fn)으로 등록, Task_Stop_50ms()로 해제.
BLE Report와 무게 변경은 앱 명령에 의해 동적 시작/정지.
WESPION_App.c · WeightManager() · ISR 컨텍스트
케이블 위치(mm)에 따른 부하 변화. 위로 갈수록 케이블이 길어짐 = 사용자가 당김.
같은 위치에서 모드에 따라 부하가 어떻게 달라지는지.
| Mode | 이름 | 동작 | 상태 |
|---|---|---|---|
| 0 | Constant | 전 구간 일정 부하 (F_Load × OnOffScale) | ✅ 배포 |
| 1 | Negative | 속도 방향에 따라 적응. 내릴 때(이완) 무게 증가 | ✅ 배포 |
| 2 | Band | 위치 비례. 높이 당길수록 무거워짐 (탄성밴드 모사) | ✅ 배포 |
| 3+ | - | 미구현 | 추가 예정 |
속도 기반으로 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)으로 활성화 필요.
모터 응답성 보상. 부하 크기에 따라 3단계:
| F_Load | FfScale | 이유 |
|---|---|---|
| ≤1.5kg | 1.4 (Max) | 저부하에서 응답 보강 |
| 1.5~2.5kg | 0.75 | 중간 |
| >2.5kg | 0.1 (Min) | 고부하에서 진동 억제 |
H_shock 이하에서는 FfScale=0 (바닥 진동 방지)
WESPION_MC.c · ISR 컨텍스트 · 2812줄
| 상태 | 의미 | 동작 |
|---|---|---|
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.0 | Flash |
| 스풀 직경 | 60mm | Flash |
| Weight→Current | 0.5 A/kg | Flash |
| 마찰 전류 | 0.45A ≈ 0.9kg | Flash |
| 최대 전류 | 30A | |
| PWM 주기 | 10kHz | TIM1/8 |
| 교번 구동 | ON | L/R 번갈아 제어 (시간 부족 대응) |
protocol.c + WESPION_BLE.c · UART3 · 115200bps
| CMD | 이름 | 방향 | 동작 |
|---|---|---|---|
0x60 | MODE_CHANGE | App→FW | 무게 모드 변경 |
0x61 | SET_RANGE | App→FW | 현재 위치를 저점/고점 등록 |
0x62 | SET_RANGE_DIGIT | App→FW | 숫자로 저점/고점 직접 설정 |
0x63 | POS_CALIB | App→FW | 위치 영점 캘리 |
0x64 | SET_WEIGHT | App→FW | 무게 설정 (raw×0.5, ≥60 에러!) |
0x65 | WEIGHTONOFF | App→FW | 무게 ON/OFF |
0x66 | START_REPORT | App→FW | 50ms Report 시작 |
0x67 | STOP_REPORT | App→FW | Report 정지 |
0x68 | WEIGHT_ADJUST | App→FW | 무게 ±0.5kg |
0x69 | ECC_LEVEL | App→FW | 신장성 레벨 |
0x6A | AUTOWEIGHT_ACTIVE | App→FW | MotionAuto 활성화 |
0x6B | AUTOWEIGHT_STATUS | FW→App | 자동 On/Off 알림 |
0x70 | GET_VERSION | App→FW | FW 버전 응답 |
0x71 | GET_POSITION | App→FW | 현재 위치 응답 |
0x72 | GET_VOLTAGE | App→FW | DC 전압 응답 |
0x7F | WEIGHTPLUS | FW→App | 무게 변경 진행 알림 |
0x80 | GYM_RANGE_INIT | App→FW | 가동범위 초기화 |
0xFF | DEBUG | App→FW | 디버그 토글 |
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 컨텍스트
인크리멘탈 엔코더 원점 찾기. 모터를 약하게 회전시켜 Z펄스(EXTI) 감지.
| 항목 | 값 |
|---|---|
| 전류 | 0.75A → 1.0A (2단계) |
| 각도 탐색 | 0°~315° (8단계, 90° 간격 우선) |
| 회전 대기 | 1초, Z펄스 대기 5초 |
| 안전 | 역방향 360° 초과 → 다음 시도 |
| 좌우 | 독립 진행 |
케이블 완전 감기로 원점 확정. v1.0.1에서는 기본 스킵(Active_ForcedCalib=0).
| 항목 | 값 |
|---|---|
| 전류 | 0.4 → 0.5 → 0.9 → 1.1A (적응) |
| 완료 조건 | 0.75초 이상 정지 |
| 안전 장치 | 역방향 180°, 거리 3.5m, 과속 250RPM, 20초 타임아웃 |
~/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 ??줄 — 보안/인증
| 변경 | 내용 |
|---|---|
| 삭제 | WESPION_HIL.c/.h — HIL 테스트 모드 |
| 추가 | Init_Encoder_v3 / ForcedCalibration / Debug_UART3_printf |
| 변경 | PositionFine2(0.1mm) 확대 사용, 전향보상 리팩터, UART 토글 제어 |
리팩토링 대상 + 버그
| 함수 | UART3_Puts | Return_* | 합계 |
|---|---|---|---|
| WeightOnOff | 4 | 4 | 8 |
| MotionAutoWeight | 3 | 3 | 6 |
| ClampEccWeight | 0 | 1 | 1 |
해결안: 플래그만 세팅 → 스케줄러 태스크에서 전송
ISR 내 200ms 블로킹. 해결안: 타이머 카운터 비동기 대기
Position/PosFine/PosFine2 동일 기저값 ×1/×20/×10. 한 번 계산 후 곱하면 float 4회 절감.
Init_Encoder 20개 + ForcedCalib 15개. "배포시 제거" 주석. DebugingPrintActive=0 기본이라 실행 안 되지만 호출 오버헤드 남음.
raw*0.5 >= 60 → 정확히 60kg에서 에러. >여야 맞음. 앱에서 59kg으로 우회 중.
에러 시에도 ACK=1 반환.