부트로더: 펌웨어가 스스로 업데이트하는 구조
Flash를 부트로더와 앱으로 나누고, 새 펌웨어를 받아 검증한 뒤 앱으로 점프합니다. 점프 순서와 실패 대비.
메모리 분할 예
| 영역 | 주소 | 크기 |
|---|---|---|
| 부트로더 | 0x0800_0000 | 16KB |
| 앱 | 0x0800_4000 | 나머지 |
| (선택) 새 앱 임시 영역 | 뒤쪽 | 앱과 같은 크기 |
부트로더 흐름
- 리셋 → 부트로더 실행
- 업데이트 요청(버튼, 플래그, 통신 명령)이 있으면 UART/CAN/USB로 새 이미지 수신
- 앱 영역을 지우고 쓰기, CRC나 서명 검증
- 앱이 유효하면 앱으로 점프
앱으로 점프하는 순서
uint32_t sp = *(uint32_t *)APP_ADDR, pc = *(uint32_t *)(APP_ADDR + 4);
__disable_irq();
// 사용한 주변장치와 SysTick 끄기, 펜딩 인터럽트 지우기
SCB->VTOR = APP_ADDR;
__set_MSP(sp);
((void (*)(void))pc)();
앱은 링커 스크립트의 FLASH 시작 주소를 0x0800_4000으로 바꿔 빌드합니다.
실패 대비
- 쓰는 도중 전원이 꺼져도 부트로더는 남아 있어야 합니다. 부트로더 영역은 쓰기 보호합니다.
- 앱 끝에 "유효" 표시를 마지막에 쓰고, 표시가 없으면 부트로더에 머뭅니다.
- 이미지 두 개를 번갈아 쓰는 A/B 방식이면 새 앱이 부팅에 실패할 때 이전 앱으로 되돌릴 수 있습니다.
STM32에는 ROM에 내장된 시스템 부트로더(BOOT0 핀)도 있어서 공장 초기 굽기에 씁니다.
그림으로 보기: Flash를 나눠 쓰기
부트로더는 작고 단순해야 합니다. 부트로더 자체에 버그가 있으면 고칠 방법이 없기 때문입니다.
부트로더가 하는 판단
- 업데이트 요청이 있는가? (버튼, 플래그, 통신으로 들어온 명령)
- 응용 프로그램이 온전한가? (CRC 검사)
- 온전하면 응용으로 점프, 아니면 업데이트 대기
int main(void) {
if (!update_requested() && app_crc_ok()) jump_to_app();
run_update_protocol(); // 여기서 새 펌웨어를 받는다
}
응용으로 점프하기
응용 프로그램의 벡터 테이블 첫 두 칸에는 초기 스택 포인터와 리셋 핸들러 주소가 들어 있습니다.
#define APP_BASE 0x08004000u
void jump_to_app(void) {
uint32_t sp = *(uint32_t *)(APP_BASE + 0);
uint32_t reset = *(uint32_t *)(APP_BASE + 4);
__disable_irq();
/* 부트로더가 켠 주변장치와 인터럽트를 모두 끄고, 대기 중인 요청을 지운다 */
SCB->VTOR = APP_BASE; // 벡터 테이블을 응용 것으로
__set_MSP(sp);
((void (*)(void))reset)();
}
응용 프로그램 쪽에서는 링커 스크립트의 Flash 시작 주소를 0x08004000으로 옮겨야 합니다. 이를 빠뜨리면 응용이 부트로더 자리에 맞춰 빌드되어 점프 직후 죽습니다.
Flash에 쓰는 규칙
- 지워야 쓸 수 있다: Flash는 1을 0으로만 바꿀 수 있습니다. 다시 1로 만들려면 페이지나 섹터 단위로 지워야 합니다.
- 지우는 단위가 크다: 수 KB 단위입니다. 한 바이트만 고칠 수 없습니다.
- 횟수 제한: 보통 1만~10만 번입니다. 자주 바뀌는 값을 같은 자리에 계속 쓰면 닳습니다.
- 쓰는 동안 조심: 쓰기 중 전원이 끊기면 그 페이지는 반쯤 쓰인 상태가 됩니다.
전원이 끊겨도 벽돌이 되지 않게
| 방법 | 원리 | 대가 |
|---|---|---|
| CRC 확인 후 점프 | 응용이 깨졌으면 부트로더에 머묾 | 가장 기본, 반드시 넣기 |
| 유효 플래그를 마지막에 기록 | 전체를 쓰고 검증한 뒤에만 "완료" 표시 | 플래그 저장 공간 |
| 슬롯 두 개 (A/B) | 새 버전을 빈 슬롯에 쓰고 전환 | Flash가 두 배 필요 |
| 시험 부팅 후 확정 | 새 버전이 정상 동작을 알려야 확정, 아니면 되돌림 | 구현 복잡 |
적어도 앞의 두 가지는 넣습니다. 업데이트 도중 전원이 꺼져도 부트로더가 살아 있으면 다시 시도할 수 있습니다.
업데이트 프로토콜에 들어갈 것
- 전체 크기와 버전을 먼저 보낸다.
- 일정 크기 블록마다 번호와 CRC를 붙이고, 받는 쪽이 응답한다.
- 응답이 없으면 같은 블록을 다시 보낸다.
- 마지막에 전체 CRC로 한 번 더 확인한다.
현업에서 자주 터지는 문제
- 점프 후 인터럽트가 이상함: 부트로더가 켠 타이머나 UART 인터럽트를 끄지 않고 넘어갔습니다.
- 워치독: 부트로더에서 워치독을 켰다면 응용에서도 계속 먹이를 줘야 합니다. 한 번 켜면 끌 수 없는 워치독이 많습니다.
- 다른 제품의 펌웨어를 받아들임: 헤더에 제품 식별자를 넣어 확인합니다.
- 보안: 서명 검증이 없으면 누구든 임의의 펌웨어를 넣을 수 있습니다. 제품에는 서명 확인과 읽기 보호를 검토합니다.
영상으로 이어 보기회로와 하드웨어 시리즈 8편전자제품 속 회로 소자는 어떻게 동작할까? · 저항 · 커패시터 · 인덕터 완벽 정리PROJECT COURSE레지스터 설정을 실제 보드에서 해 보고 싶다면밸런싱 로봇 하나를 요구사항, 회로와 PCB, 펌웨어, 앱까지 실무 순서대로 만드는 강의입니다. 10파트 165강.강의 살펴보기 →FREE PDF무료 전자책 3권 받기이 주제를 한 권으로 이어서 읽고 싶다면. BLDC 모터 제어, 직렬통신, 센서를 책 분량으로 묶었습니다. 이메일만 남기면 바로 받습니다.무료로 받기 →