# 펌웨어 구현하면서 한 일 정리 (개인용 메모)

readme.md는 그대로 두고, 코드 작업하면서 실제로 뭘 했고 뭘 확인했는지를 여기 따로 적는다. 어려운 말 최소화.

---

## 1. 뭘 만들었나 (간단 설명)

보드에 LED 9개가 물려 있고, 각 LED는 "이번 판에는 몇 번 깜빡일지"가 정해져 있다. 예를 들어 1번 LED는 3번, 2번 LED는 5번, 이런 식으로 9개가 각자 다른 횟수만큼 깜빡인다.

동작은 이렇게 반복된다:

1. 8초짜리 한 판이 시작
2. 처음 6초 동안 9개가 각자 정해진 횟수만큼 깜빡임 (다 끝난 애는 먼저 꺼지고 기다림)
3. 나머지 2초는 전부 꺼짐 — 이 "다 같이 꺼진 구간"이 다음 판이 시작하는 기준점 역할을 함
4. 다음 판에서는 깜빡이는 횟수가 전부 1씩 밀림 (1번이 3번이었으면 다음 판엔 4번)
5. 이걸 계속 반복

그리고 판이 끝날 때마다 컴퓨터(시리얼 포트)로 "이번 판 정답은 1번=3번, 2번=5번, ..." 하고 로그를 찍어준다. 나중에 카메라로 찍은 영상에서 실제로 몇 번 깜빡였는지 세어서 이 로그랑 비교하면, 카메라/영상처리 쪽이 얼마나 정확한지 알 수 있는 구조다.

숫자를 랜덤이 아니라 "1씩 밀리는" 규칙으로 정한 이유: 무작위로 하면 어쩌다 맞았는지 진짜 맞은 건지 구분이 안 된다. 규칙적으로 밀리면 "특정 LED만 계속 틀리네" 같은 패턴이 바로 보인다.

---

## 2. 원래 코드에서 발견한 문제 2가지

CubeMX(보드 핀 설정을 자동으로 코드로 만들어주는 툴)가 만들어둔 코드를 열어보니, 설계 문서 내용이랑 안 맞는 부분이 두 군데 있었다.

### 문제 1: 전원 켜자마자 LED가 다 켜져 있음

LED를 켜는 핀들은 "오픈 드레인"이라는 방식으로 동작하는데, 이 방식에서는 핀을 Low로 두면 불이 켜지고 High로 두면 꺼진다 (일반적인 상식과 반대). 그런데 자동 생성된 코드는 시작할 때 이 핀들을 전부 Low로 초기화하고 있었다. 즉 리셋 버튼을 누르는 순간 9개 LED가 전부 켜진 채로 시작하는 상태였다.

→ 시작값을 High로 바꿔서, 전원 켜졌을 때 전부 꺼진 상태에서 시작하도록 고쳤다.

### 문제 2: 자리 하나가 비어 있었음 (9개가 아니라 8개만 동작)

LED 9개를 붙일 핀 9개(PC0~PC8)가 연속으로 나란히 있어야 코드가 한 번에 9개를 동시에 켜고 끌 수 있다. 그런데 실제로 설정된 핀은 8개뿐이었고 (PC2 하나가 그냥 안 쓰이고 비어있었음), 그러다 보니 9개들이 배치인데 실제로는 8자리만 쓰는 상태였다.

→ 비어있던 PC2를 9번째 자리로 채워서 9개가 전부 동작하도록 고쳤다. 기존에 이미 배정돼 있던 1~8번 자리는 그대로 두고, 마지막 9번째만 새로 채운 것이라 기존 배선을 바꿀 필요는 없다.

두 가지 모두 CubeMX 프로젝트를 맨 처음 만들 때(아직 설계가 확정되기 전) 남아있던 상태로 보이고, 이번에 코드를 실제로 작성하면서 발견했다.

---

## 3. 컴파일이 되는지 확인

먼저 컴퓨터에 있는 컴파일러로 빌드를 시도했는데 실패했다. 원인은 그 컴파일러(Homebrew로 설치된 것)에 표준 라이브러리 파일 일부가 빠져 있었기 때문. ST에서 따로 제공하는 컴파일러(STM32CubeCLT에 포함된 것)로 바꿔서 다시 빌드하니 문제없이 성공했다.

```
사용 메모리: RAM 2224B / 20KB (10.9%), FLASH 17232B / 192KB (8.8%)
```

여유가 충분해서 메모리 문제로 막힐 일은 없다.

---

## 4. 동작 검증 과정

**실제 보드(NUCLEO 보드 + LED + 카메라)는 아직 연결 안 해봤다.** 그래서 "진짜 불이 깜빡이는지, 카메라로 잘 보이는지"는 이번에 확인한 범위 밖이다 — 이건 readme.md 10장에 있는 순서대로 나중에 직접 해봐야 하는 부분.

대신, MCU에 올리기 전에 **로직 자체가 맞게 짜였는지**는 컴퓨터에서 미리 확인할 수 있다. 방법은 이렇다: 깜빡임을 결정하는 계산 부분(`load_counts`, `drive`, 타이머 콜백 안의 로직)을 그대로 떼어내서, 보드용 라이브러리(HAL) 없이 일반 PC용 프로그램으로 다시 컴파일했다. 그리고 "실제로 몇 번 깜빡였는지"를 프로그램이 스스로 세어서, 원래 의도한 횟수랑 맞는지 비교하게 만들었다.

파일 위치: [`doc/verify/sim_check.c`](verify/sim_check.c) — 순수 C코드라 아무 컴퓨터에서나 아래처럼 바로 돌려볼 수 있다.

```
gcc -Wall -Wextra -o sim_check doc/verify/sim_check.c
./sim_check
```

### 확인한 것 4가지

**① 한 판이 정확히 8초(40틱)인지**
→ 40틱 정확히 맞음.

**② 216판(약 8시간 분량)을 계속 돌려서, "이번 판 목표 횟수"와 "실제로 깜빡인 횟수"가 매번 정확히 같은지**
→ 216판 전부 정확히 일치. 단 한 번도 어긋나지 않음.

```
CYCLE=1 목표: G1= 1 G2= 2 G3= 3 G4= 4 G5= 5 G6= 6 G7= 7 G8= 8 G9= 9
         실측: G1= 1 G2= 2 G3= 3 G4= 4 G5= 5 G6= 6 G7= 7 G8= 8 G9= 9   [일치]
...
CYCLE=216 목표: G1= 6 G2= 7 G3= 8 G4= 9 G5=10 G6=11 G7=12 G8=13 G9=14
         실측: G1= 6 G2= 7 G3= 8 G4= 9 G5=10 G6=11 G7=12 G8=13 G9=14   [일치]
```

**③ "0번 깜빡임"이 절대 안 나오는지**
설계상 0번 깜빡임은 쓰면 안 되는 값이다 (0번 깜빡인 건지, 고장나서 안 켜진 건지 구분이 안 되기 때문). 200판 넘게 돌려봐도 0이 나온 적이 없는지 확인함.
→ 한 번도 안 나옴. 정상.

**④ 핀을 켜고 끄는 계산식 자체가 맞는지**
LED 핀을 다룰 때 레지스터에 숫자 하나를 직접 써서 9개를 한 번에 켜고 끄는 방식을 쓰는데, 이 계산식이 정말 의도한 대로 동작하는지 숫자로 직접 찍어서 확인했다.

```
1번 LED만 켜려고 할 때 -> 계산 결과 0x000101FE
  1번 핀 쪽: 켜짐(불이 들어오는 방향)으로 확인됨 (기대한 대로)
  2번 핀 쪽: 꺼짐(불이 안 들어오는 방향)으로 확인됨 (기대한 대로)
```

→ 맞게 계산됨.

### 결론

깜빡이는 횟수를 정하고, 실제로 몇 번 깜빡이게 만들고, 그 신호를 핀에 내보내는 계산까지 — **로직상으로는 문제없이 의도한 대로 동작한다**는 걸 확인했다. 다만 이건 "코드 안의 계산이 맞다"는 확인이지 "실제 LED가 카메라에 잘 잡히는지"까지 확인한 건 아니다. 그건 실물을 연결해봐야 안다.

---

## 5. 아직 안 해본 것 (다음 할 일)

- **배선 자체를 아직 안 함 — 아래 6장 보고 먼저 연결할 것**
- 실제 보드에 펌웨어 올려서 LED가 진짜 깜빡이는지 눈으로 확인
- 시리얼 로그가 8초마다 한 줄씩 잘 찍히는지 확인
- 950nm 필터 낀 카메라로 9개가 겹치지 않고 잘 분리돼서 찍히는지 확인
- 노출 시간 등 카메라 설정 튜닝

→ 순서는 readme.md의 "10. 브링업 절차"에 이미 정리돼 있어서 그대로 따라가면 됨.

---

## 6. 배선 연결도 (현재 미배선 — 이대로 연결하면 됨)

### 준비물

- LD274 (950nm IR LED) × 9
- 330Ω 저항 × 9  (연결하고 밝기가 너무 약하면 220Ω으로 교체)
- 점퍼선 여러 개, 브레드보드 또는 만능기판
- NUCLEO-L073RZ 보드

### 한 채널 기본 회로 (이걸 9번 반복)

LED는 극성이 있다 — 방향을 반대로 꽂으면 안 켜진다.

- **긴 다리 = +(애노드)**
- **짧은 다리 = -(캐소드)**, 다리를 이미 잘랐다면 LED 몸통 테두리에 살짝 평평하게 깎인 부분이 있는 쪽이 캐소드

```
NUCLEO 보드의 3V3 핀
        │
        │  (9개 LED가 전부 여기 한 곳에 같이 연결됨 — 공통)
        │
     LED 애노드(+, 긴 다리)
        │
     LED 캐소드(-, 짧은 다리)
        │
      330Ω 저항
        │
     MCU 핀 (아래 표에서 해당 LED에 배정된 핀)
```

전류가 3V3 → LED → 저항 → MCU 핀 순서로 흐르고 MCU 안에서 빠지는 구조라 별도로 GND를 연결할 필요는 없다 (보드가 USB로 컴퓨터에 물려 있으면 그걸로 충분).

### 어떤 LED를 어떤 핀에 연결할지 (9개 전부)

| LED 번호 | 연결할 MCU 핀 | 저항 |
|---|---|---|
| G1 | PC0 | 330Ω |
| G2 | PC1 | 330Ω |
| G3 | PC3 | 330Ω |
| G4 | PC4 | 330Ω |
| G5 | PC5 | 330Ω |
| G6 | PC6 | 330Ω |
| G7 | PC7 | 330Ω |
| G8 | PC8 | 330Ω |
| G9 | PC2 | 330Ω |

핀 이름(PC0, PC1 ...)은 NUCLEO 보드의 커넥터(모포 헤더, CN10 쪽)에 실크스크린으로 그대로 인쇄되어 있어서 이름으로 찾으면 된다. 3V3 핀도 마찬가지로 보드에 "3V3"라고 인쇄돼 있는 핀을 쓰면 된다.

> G9가 PC2인 게 좀 이상해 보일 수 있는데, G1~G8을 먼저 PC0~PC1, PC3~PC8에 배정해놓은 상태에서 마지막으로 남는 자리가 PC2였기 때문이다 (자세한 사연은 readme.md 6.7절). 표 그대로 연결하면 문제없다.

### 카메라 쪽에서 봤을 때 실제 배치 (3×3 격자, 간격 10mm)

패널에 LED를 심을 때 이 배치를 따른다 — 카메라가 찍는 화면 기준으로 자리를 잡은 것이다.

```
        좌            중            우
      ┌──────────┬──────────┬──────────┐
 상단  │    G1    │    G2    │    G3    │
      │  (PC0)   │  (PC1)   │  (PC3)   │
      ├──────────┼──────────┼──────────┤
 중단  │    G4    │    G5    │    G6    │
      │  (PC4)   │  (PC5)   │  (PC6)   │
      ├──────────┼──────────┼──────────┤
 하단  │    G7    │    G8    │    G9    │
      │  (PC7)   │  (PC8)   │  (PC2)   │
      └──────────┴──────────┴──────────┘

   LED 중심 간 간격 10mm, LED 몸통 지름 5.1mm
```

### 연결 순서 (체크리스트)

1. [ ] LED 9개 + 저항 9개 극성/자리 맞춰서 위 표대로 하나씩 연결
2. [ ] LED 애노드(+) 9개를 전부 한 군데로 모아서 3V3 핀 하나에 연결
3. [ ] 저항 반대쪽 끝을 표에 있는 MCU 핀에 각각 연결
4. [ ] 전부 연결했으면 다시 한번 표랑 대조 — 특히 G9가 PC2로 갔는지 확인
5. [ ] readme.md 10장 "브링업 절차"대로, 처음엔 LED 1개(G1/PC0)만 켜보고 정상이면 나머지 연결

**주의할 것:**
- LED 극성 반대로 끼우면 안 켜짐 (고장 아니니 방향만 바꿔서 다시 확인)
- 저항 없이 LED를 직접 핀에 연결하면 과전류로 LED나 핀이 망가질 수 있음 — 저항 절대 빼먹지 말 것
- 9개 중 하나라도 다른 핀에 잘못 꽂으면, 코드 로그(G1=.. G2=..)랑 실제 불빛 위치가 안 맞게 되니 표 대조를 꼼꼼히

### 정답 로그(시리얼)는 배선 필요 없음

LED와 달리 UART 로그(USART2, PA2/PA3)는 **추가로 연결할 게 없다.** NUCLEO 보드 안에서 PA2/PA3가 보드에 내장된 ST-LINK로 이미 연결돼 있어서, 지금 펌웨어 업로드용으로 꽂는 USB 케이블 하나가 곧 가상 COM 포트(VCP) 역할도 한다.

- PC에서 터미널 프로그램(PuTTY, screen, CoolTerm, VS Code 시리얼 모니터 등) 실행
- 보드가 잡힌 COM 포트를 **115200 8N1**로 열기
- 8초마다 `CYCLE=.. G1=.. G2=.. ... G9=..` 한 줄씩 찍히면 정상

### CoolTerm에 로그가 안 뜬 문제 — 해결됨 (원인은 보드 배선, 펌웨어는 정상이었음)

**→ SB13, SB14 브리지하고 나서 CoolTerm에 로그 정상적으로 뜨는 거 확인함 (2026-08-22).**

2026-08-22, CoolTerm으로 열었는데 아무것도 안 뜬다는 얘기가 나와서 ST-LINK로 보드에 직접 붙어 확인했다. 순서대로:

1. 최신 펌웨어를 다시 플래시하고 리셋 → CoolTerm 대신 터미널에서 시리얼 포트를 직접 열어서 20초 넘게 지켜봐도 **0바이트**. CoolTerm 설정 문제가 아니라 보드에서 아무것도 안 나오고 있다는 뜻.
2. ST-LINK로 MCU에 붙어서 **보드가 정지 없이 실제로 돌아가는 상태 그대로(Hot Plug 모드)** 레지스터를 직접 읽어봄:
   - GPIOA/C/H 클럭 켜짐, TIM6·USART2 클럭 켜짐 — 전부 코드에서 의도한 그대로
   - PA2/PA3가 USART2용 핀 모드(AF4)로 정확히 설정돼 있음
   - PC0~PC8(G1~G9, PC2 포함) 전부 출력모드로 정확히 설정돼 있음 — 6.7절에서 고친 내용이 실제로 반영돼서 동작 중
   - USART2 레지스터 확인 결과 **송신 기능이 켜져서 실제로 활성 상태**(TEACK=1), 보레이트 값도 계산했던 값(139)과 정확히 일치
   - RAM에 있는 카운트 값도 우리가 만든 공식과 한 자리도 안 틀리고 일치
   - 반대로 USART2 수신 쪽에는 "깨진 데이터를 받았다"는 에러 플래그가 떠 있었음 — 그 핀이 뭔가에 연결 안 된 채 붕 떠서 잡음을 줍고 있다는 신호

→ **MCU 내부에서는 데이터를 제대로 내보내고 있는데, 그게 컴퓨터까지 한 바이트도 안 온다.** 펌웨어/설정 쪽은 이걸로 결백이 증명된 셈이고, 남은 용의선상은 **PA2/PA3와 ST-LINK를 이어주는 보드 위의 연결(솔더 브리지)이 끊겨 있는 경우**뿐이다.

**정확한 솔더 브리지 번호 (ST 공식 UM1724 매뉴얼, "5.7 USART communication" / "Table 9. Solder bridges" 확인함):**

NUCLEO-64 보드(L073RZ 포함)는 PA2/PA3를 어디로 보낼지 솔더 브리지 4개로 정한다.

| 브리지 | 정상(VCP 사용) 상태 | 역할 |
|---|---|---|
| **SB13, SB14** (ST-LINK-USART) | **ON (이어져 있어야 함)** | PA2/PA3 ↔ ST-LINK MCU의 USART를 연결 — 이게 켜져 있어야 시리얼 로그가 PC로 나옴 |
| **SB62, SB63** (USART) | **OFF (끊겨 있어야 함)** | PA2/PA3를 대신 Arduino 커넥터(CN9의 D1/D0)나 Morpho 커넥터(CN10) 쪽으로 돌림. 이게 켜져 있으면 ST-LINK 쪽 연결이 끊긴다 |

즉 **SB13·SB14가 열려있거나(OFF), SB62·SB63가 닫혀있으면(ON)** 지금 겪는 증상(펌웨어는 정상인데 로그가 안 뜸)과 정확히 일치한다.

**확인 방법:**
1. 보드에서 SB13, SB14, SB62, SB63 실크스크린 표시를 찾는다 (ST-LINK 쪽 회로와 CN9/CN10 커넥터 사이 어딘가에 작은 두 패드로 표시돼 있음)
2. 멀티미터 연속성(continuity) 모드로 SB13, SB14 각각의 두 패드를 찍어본다 → **삑 소리(연결됨)가 나야 정상.** 안 나면 납땜으로 이어줘야 함
3. SB62, SB63도 같은 방식으로 찍어본다 → **삑 소리가 안 나야(끊겨 있어야) 정상.** 소리가 나면 커터로 끊어야 함
4. 넷 다 정상 상태로 맞춘 뒤 다시 CoolTerm으로 확인

**급하면:** 별도 USB-TTL 시리얼 어댑터를 PA2(TX)/PA3(RX)/GND에 직접 물려서 ST-LINK VCP를 아예 안 거치고 확인. 아니면 다른 NUCLEO-L073RZ 보드로 교체 — 펌웨어는 이미 검증됐으니 보드만 바꾸면 바로 될 가능성이 큼.

출처: [UM1724 User manual - STM32 Nucleo-64 boards (MB1136)](https://www.st.com/resource/en/user_manual/um1724-stm32-nucleo64-boards-mb1136-stmicroelectronics.pdf), §5.7 USART communication / Table 9 Solder bridges (DocID025833 Rev 8)

이걸로 카메라 쪽 판독 결과랑 대조하면 된다. 안 찍히면 보드레이트(115200)부터 다시 확인.

---

## 7. LED가 잘 안 보임 — 저항/노출 얼마까지 밀어붙여도 되나

2026-08-22, 노출 20ms + 330Ω→220Ω로 바꿨는데도 LED가 협각(±10°) 때문에 잘 안 보인다는 얘기가 나와서, STM32L073 공식 데이터시트(DS10685)로 실제 한계를 확인했다.

### 저항 — 220Ω에서 더 낮추지 말 것

데이터시트 Table 24(절대최대정격) 기준:

| 항목 | 절대최대값 |
|---|---|
| 핀 1개가 sink하는 전류 | 16 mA |
| **9개 핀 합계로 sink하는 전류** | **90 mA** |

Table 61(출력전압특성)을 보면 15mA 지점에서 V_OL이 **최대 1.3V**까지 올라간다(8mA 기준으로 잡았던 0.4V가 아님). 즉 저항을 낮춰도 핀 자체의 전압강하가 커져서 기대만큼 전류가 안 오를 수 있다.

**더 중요한 건 합계 90mA 쪽이다.** 사이클 시작 직후엔 설계상 9개가 동시에 켜지는 순간이 항상 있는데, 지금 220Ω(약 8mA×9≈72mA)만 해도 이미 90mA 한도에 꽤 붙어 있다. 여기서 더 낮추면 그 동시 점등 순간에 절대정격을 넘길 수 있다.

→ **220Ω보다 낮추지 말 것.** 180Ω 정도가 실질적 하한선.

### 노출 — 60ms 근처까지는 늘려도 됨

지금 20ms는 30fps 프레임 주기(33ms) 안에 들어가 안전. 5.4절에서 이미 "카메라가 15fps로 떨어져도 상태당 최소 3프레임 확보"를 설계 마진으로 잡아뒀으므로, **15fps로 떨어지는 걸 감수하면 노출을 60ms 근처까지 늘려도 이 마진 안에 있음.** 그 이상(10fps대)은 5.4절 표에서 이미 "위험"으로 표시해둔 구간이라 피할 것.

전류보다 노출을 늘리는 쪽이 안전하고 효과도 큼 — 3.4절에서 이미 권장했던 방향과 같음.

### 그래도 안 보이면: 협각 자체가 원인일 가능성

±10° 협각은 전류·노출로 완전히 못 고친다. 카메라가 LED 패널 정면 중앙에 잘 정렬돼 있는지(각 LED가 카메라를 정확히 향하고 있는지) 먼저 확인. 정렬이 어긋나 있으면 아무리 밝게/오래 노출해도 카메라가 빔 가장자리(광량 30~50% 이하 구간)만 보게 되므로 근본 해결이 안 됨.

출처: [Datasheet - STM32L073x8 STM32L073xB STM32L073xZ (DS10685)](https://www.st.com/resource/en/datasheet/stm32l073v8.pdf), §6.2 Table 24 Current characteristics / §6.3.13 Table 61 Output voltage characteristics
