File created successfully at: d:\Project\nuvoton\HECO2\doc\260618_디퓨저댐퍼_모드전환_트리거복원.md (file state is current in your context — no need to Read it back)

# 260618 디퓨저 댐퍼 — 운전모드 전환 즉시 반영 트리거 복원

## 배경 / 증상
- "운전모드에 따른 댐퍼 동작이 잘 안된다" 보고.
- ErvSimulator(`DamperSequencer`)는 명령 변경 시 `NotifyCommandChanged()`로 18초 디퓨저 시퀀스를 **즉시 재시작**하는데, 현재 HECO2 펌웨어에는 그 트리거가 없었음.
- 본체 댐퍼(`Damper_Mode`)와 팬 풍량은 모드 전환에 즉시 반응하나, **디퓨저(각실) 댐퍼**만 18초 자가 타이머가 0이 될 때까지 갱신 안 됨.

## 원인 (구 펌웨어 비교로 확정)
- `D:\Project\nuvoton\HERV_DL_MH_2nd\Program` (구 HERV, CVnet 월패드/`My_Uart.c` 기반)와 비교.
- 구 버전 `Diffuser_Damper_process()`:
  `if((Pre_CVnet_CMD != CVnet_CMD)||(Diffuser_mode_change_timer == 0))` — **명령 변경 시 즉시 재시작** 트리거 존재.
- HECO2로 이식하며 통신부를 `My_Homenet.c`(RJ2/홈넷)로 교체 → `CVnet_CMD` 소멸 → 트리거를 **삭제만 하고 대체 없이** 남음. 결과적으로 `if(Diffuser_mode_change_timer == 0)` 만 남아 18초 프리런 타이머에만 의존.
- 즉 시뮬레이터 대비 누락된 "명령 변경 → 디퓨저 시퀀스 재시작" 로직이, 구 펌웨어에는 실제로 있었음.

## 변경 파일/내용
- `Source/HECO2/User/MyMotor.c` — `Diffuser_Damper_process()` 트리거 복원.
  - 추가: `uint8_t Pre_Diffuser_Cmd = 0xFF;`
  - 함수 진입부에서 명령 시그니처 합성: `Diffuser_Cmd = (Run_Mode << 4) | (Fan_Mode & 0x0F)`
  - 조건 변경:
    - 전: `if(Diffuser_mode_change_timer == 0)`
    - 후: `if((Pre_Diffuser_Cmd != Diffuser_Cmd) || (Diffuser_mode_change_timer == 0))` + 내부에서 `Pre_Diffuser_Cmd = Diffuser_Cmd;`
- 구 펌웨어의 `Pre_CVnet_CMD != CVnet_CMD` 패턴을 HECO2 통신구조에 맞게 복원한 것. 운전모드+풍량을 한 바이트로 합쳐 변경 감지하므로 홈넷/RJ2/시나리오 등 **모든 명령 경로를 MyMotor.c 한 곳에서 균일하게** 잡음. `My_Homenet.c`/`My_define.h` extern 추가 불필요.

## 핵심 결정
- My_Homenet 핸들러마다 `Diffuser_mode_change_timer = 0`을 흩뿌리는 대신, 함수 내부 자기완결형 명령-감지로 구현(구 CVnet_CMD 의미 그대로 재현, 누락 경로 방지).
- RJ2(`My_RJ2.c`)·분배기(`My_bunbaegi.c`) 코드는 수정하지 않음(검증된 코드).
- 시퀀스 종류(`Diffuser_mode_type`) 판정은 기존대로 `Total_Air_Volume` 기준 유지(구 펌웨어와 동일). 풍량 단수가 같은 모드전환(예: 환기1↔공청1)도 트리거는 걸리며 type 4(increase)로 Memory→Current 재적용.

## 빌드 결과
- `cd SOURCE/HECO2 && bash build.sh all` → 성공, 경고/오류 0.
- text 44008 / data 1948 / bss 3388 (HERV.elf).

## 미해결 / 후속
- 실제 장비/시뮬레이터에서 모드 전환 시 디퓨저 댐퍼 즉시 동작 검증 필요.
- 시나리오(안심회복/집중청정 등)는 Run_Mode/Fan_Mode가 안 바뀌는 경우가 있어 트리거가 안 걸릴 수 있음 — 필요 시 `Ext_Run_Mode`도 시그니처에 포함 검토.


RJ2,분배기 코드만 빼고 수정해.


HECO2 댐퍼 동작 문제 분석 및 비교


수정 완료했습니다.

## 적용 내용
[MyMotor.c](Source/HECO2/User/MyMotor.c)의 `Diffuser_Damper_process()`에 구 펌웨어(HERV_DL_MH_2nd)의 명령-변경 트리거를 HECO2 통신구조에 맞게 복원했습니다.

```c
uint8_t Pre_Diffuser_Cmd = 0xFF;   /* 운전모드+풍량 명령 변경 감지(구 CVnet_CMD 대체) */
...
uint8_t Diffuser_Cmd = (uint8_t)((Run_Mode << 4) | (Fan_Mode & 0x0F));
...
if((Pre_Diffuser_Cmd != Diffuser_Cmd) || (Diffuser_mode_change_timer == 0))
{
    Pre_Diffuser_Cmd = Diffuser_Cmd;
    Diffuser_mode_change_timer = 180;
    ...
```

- 구 버전의 `Pre_CVnet_CMD != CVnet_CMD` → HECO2에선 `CVnet_CMD`가 없어서, **운전모드+풍량을 한 바이트로 합쳐** 변경을 감지하도록 재현했습니다. 홈넷·RJ2·시나리오 등 모든 명령 경로를 MyMotor.c 한 곳에서 균일하게 잡으며, `My_Homenet.c`/`My_define.h` 손댈 필요가 없습니다.
- **RJ2(`My_RJ2.c`)·분배기(`My_bunbaegi.c`)는 건드리지 않았습니다.**

## 결과
- 빌드: `bash build.sh all` → **성공, 경고/오류 0** (text 44008 / data 1948 / bss 3388).
- 이제 운전모드/풍량을 바꾸면 다음 100ms tick에 디퓨저 댐퍼 18초 시퀀스가 즉시 재시작됩니다 (기존엔 최대 ~18초 지연).
- 작업 기록: [doc/260618_디퓨저댐퍼_모드전환_트리거복원.md](doc/260618_디퓨저댐퍼_모드전환_트리거복원.md)

## 후속 확인 권장
시나리오 모드(안심회복/집중청정 등)는 `Run_Mode`/`Fan_Mode`가 그대로인 경우가 있어 이 트리거로는 안 걸릴 수 있습니다. 시나리오 전환 시에도 디퓨저 즉시 반영이 필요하면 시그니처에 `Ext_Run_Mode`를 추가하면 됩니다 — 필요할 때 알려주세요.


아니..이상한게..왜 내부 댐퍼 동작을 얘기했는데..외부 디퓨저를 봐야하지?
좀 잘못 된 것 아니야?


맞습니다. 제가 방향을 잘못 잡았을 수 있습니다. 짚어주셔서 다행입니다. 용어를 먼저 정리하겠습니다.

## 두 댐퍼는 완전히 다른 물건입니다
- **내부(본체) 댐퍼** = HERV 안에 있는 6개 스텝모터 댐퍼(OA/EA/BYPASS/SA/RA/AIR). **이 MCU가 직접 구동**하고, **`운전모드`에 따라 각도가 바뀌는 함수는 `Damper_Mode()`** 입니다 ([MyMotor.c:472](Source/HECO2/User/MyMotor.c#L472)). `switch(운전모드)`로 환기/공청/바이패스/OFF 각도를 직접 지정 — **"운전모드에 따른 댐퍼"는 문자 그대로 이 함수**입니다.
- **외부 디퓨저 댐퍼** = 485로 연결된 각실 디퓨저(별도 장비). `Diffuser_Damper_process()`가 각도값을 만들어 **485로 내보내는** 것이고, 트리거도 `운전모드`가 아니라 **풍량(`Total_Air_Volume`)** 기준입니다.

제가 지난 답에서 손댄 건 **외부 디퓨저** 쪽이 맞습니다. 그러니 "내부 댐퍼"를 말씀하신 거면 제 수정은 **타겟이 다릅니다.**

## 왜 제가 외부로 갔었나 (변명이 아니라 경위)
- 처음 ErvSimulator와 비교 → 시뮬의 `NotifyCommandChanged`가 디퓨저 시퀀스를 즉시 재시작 → 그쪽 누락을 먼저 봄.
- 그리고 구 펌웨어(HERV_DL_MH_2nd)와 diff 했을 때 **댐퍼 관련 회귀(regression)가 유일하게 발견된 곳이 외부 디퓨저**였습니다.
- 반대로 **내부 `Damper_Mode()`와 그 호출 게이팅(`Fan_Speed_process`)은 구 펌웨어와 사실상 동일**합니다. diff에서 바뀐 건 팬 복원 한 줄(`Fan_Speed_Setting`)뿐, `Damper_Mode()` 호출/순서는 그대로였습니다. 즉 **내부 댐퍼는 구 버전 대비 회귀가 없습니다** — 구 버전에서도 같게 동작.

## 그래서 확인이 필요합니다
"내부 댐퍼가 운전모드에 따라 잘 안 움직인다"가 정확히 어떤 증상인지에 따라 봐야 할 곳이 갈립니다:

1. **물리적으로 안 움직임/느림** → `Fan_Speed_process`의 게이팅 때문일 수 있습니다. 모드 바꾸면 **팬이 0까지 내려간 뒤에야** 본체 댐퍼가 움직이고(공기 흐름 중 댐퍼 안 움직이도록 의도된 설계), 그 다음 팬 복원 → 고풍량에서 전환 시 10초 이상 걸려 "안 되는 것처럼" 보일 수 있음.
2. **특정 모드 각도가 틀림** → `Damper_Mode()`의 각도표 자체 문제.
3. **AUTO에서 공기질 나빠도 본체가 공청 각도로 안 바뀜** → AUTO는 항상 `Damper_Mode(MODE_VENTILATION)`만 호출합니다([MyMotor.c:1046](Source/HECO2/User/MyMotor.c#L1046)). 공청 전환 로직 부재일 수 있음.

**질문:** 어떤 운전모드 전환에서, 내부 6개 댐퍼 중 무엇이, 어떻게 잘못 동작하나요? (예: "환기→바이패스 시 BYPASS 댐퍼가 안 열림", "공청 들어가도 OA가 안 닫힘", "전환이 너무 느림" 등)

그리고 지난번 **외부 디퓨저 수정은 그대로 둘까요, 되돌릴까요?** (구 펌웨어엔 있던 트리거라 그 자체는 유효한 복원이지만, 지금 다루려는 문제와 별개라면 분리하겠습니다.) 증상만 알려주시면 내부 댐퍼 쪽을 정확히 파서 고치겠습니다.


우선 코드를 원복하고 다시 얘기하자.


지난 수정을 되돌리겠습니다.


Edit

uint8_t First_Diffuser_excute_flag = 0;
uint8_t Pre_Diffuser_Cmd = 0xFF;   /* 운전모드+풍량 명령 변경 감지(구 CVnet_CMD 대체) */
uint16_t Diffuser_Damper_process(uint8_t mode)//100ms
{
    /* 운전모드/풍량이 바뀌면 디퓨저 시퀀스를 즉시 재시작.
       (구 펌웨어 HERV_DL_MH_2nd 의 Pre_CVnet_CMD!=CVnet_CMD 트리거를 HECO2 통신구조에 맞게 복원 -
        홈넷·RJ2·시나리오 등 모든 명령 경로를 한 곳에서 균일하게 감지) */
    uint8_t Diffuser_Cmd = (uint8_t)((Run_Mode << 4) | (Fan_Mode & 0x0F));

    if(Focus_Mode_RunTime)Focus_Mode_RunTime--;

    if((Pre_Diffuser_Cmd != Diffuser_Cmd) || (Diffuser_mode_change_timer == 0))
    {
        Pre_Diffuser_Cmd = Diffuser_Cmd;
        Diffuser_mode_change_timer = 180;//about 24sec

uint8_t First_Diffuser_excute_flag = 0;
uint16_t Diffuser_Damper_process(uint8_t mode)//100ms
{
    if(Focus_Mode_RunTime)Focus_Mode_RunTime--;

    if(Diffuser_mode_change_timer == 0)
    {
        Diffuser_mode_change_timer = 180;//about 24sec


The file d:\Project\nuvoton\HECO2\Source\HECO2\User\MyMotor.c has been updated successfully. (file state is current in your context — no need to Read it back)

uint8_t First_Diffuser_excute_flag = 0;
uint8_t Pre_Diffuser_Cmd = 0xFF;   /* 운전모드+풍량 명령 변경 감지(구 CVnet_CMD 대체) */
uint16_t Diffuser_Damper_process(uint8_t mode)//100ms
{
    /* 운전모드/풍량이 바뀌면 디퓨저 시퀀스를 즉시 재시작.
       (구 펌웨어 HERV_DL_MH_2nd 의 Pre_CVnet_CMD!=CVnet_CMD 트리거를 HECO2 통신구조에 맞게 복원 -
        홈넷·RJ2·시나리오 등 모든 명령 경로를 한 곳에서 균일하게 감지) */
    uint8_t Diffuser_Cmd = (uint8_t)((Run_Mode << 4) | (Fan_Mode & 0x0F));

    if(Focus_Mode_RunTime)Focus_Mode_RunTime--;

    if((Pre_Diffuser_Cmd != Diffuser_Cmd) || (Diffuser_mode_change_timer == 0))
    {
        Pre_Diffuser_Cmd = Diffuser_Cmd;
        Diffuser_mode_change_timer = 180;//about 24sec

uint8_t First_Diffuser_excute_flag = 0;
uint16_t Diffuser_Damper_process(uint8_t mode)//100ms
{
    if(Focus_Mode_RunTime)Focus_Mode_RunTime--;

    if(Diffuser_mode_change_timer == 0)
    {
        Diffuser_mode_change_timer = 180;//about 24sec

-uint8_t Pre_Diffuser_Cmd = 0xFF;   /* 운전모드+풍량 명령 변경 감지(구 CVnet_CMD 대체) */

-    /* 운전모드/풍량이 바뀌면 디퓨저 시퀀스를 즉시 재시작.

-       (구 펌웨어 HERV_DL_MH_2nd 의 Pre_CVnet_CMD!=CVnet_CMD 트리거를 HECO2 통신구조에 맞게 복원 -

-    uint8_t Diffuser_Cmd = (uint8_t)((Run_Mode << 4) | (Fan_Mode & 0x0F));

-    if((Pre_Diffuser_Cmd != Diffuser_Cmd) || (Diffuser_mode_change_timer == 0))

+    if(Diffuser_mode_change_timer == 0)

-        Pre_Diffuser_Cmd = Diffuser_Cmd;

if((Pre_Total_Air_Volume == 0) && (Total_Air_Volume != 0))


코드를 원복했습니다. 원본과 일치하는지 git으로 확인하고, 함께 만든 작업기록 문서도 정리하겠습니다.


Bash

echo "=== MyMotor.c diff vs HEAD ===" && git diff --stat Source/HECO2/User/MyMotor.c && echo "(위 출력이 비어있으면 완전 원복됨)" && echo "=== 미추적 doc 파일 ===" && git status --short doc/

Verify MyMotor.c reverted and check doc file status


=== MyMotor.c diff vs HEAD ===
(위 출력이 비어있으면 완전 원복됨)
=== 미추적 doc 파일 ===
?? "doc/260618_\353\224\224\355\223\250\354\240\200\353\214\220\355\215\274_\353\252\250\353\223\234\354\240\204\355\231\230_\355\212\270\353\246\254\352\261\260\353\263\265\354\233\220.md"


`MyMotor.c`는 HEAD와 완전히 일치(diff 없음)합니다. 이제 그 수정 내용을 기록한 doc 파일만 남아있습니다. 이것도 지울까요?


rm -f "doc/260618_디퓨저댐퍼_모드전환_트리거복원.md" && echo "삭제 완료" && git status --short

Remove the doc file created for reverted change


원복 완료했습니다.

- **MyMotor.c**: HEAD와 완전 일치 (변경 없음)
- 디퓨저 수정 관련 **doc 파일 삭제** 완료
- 남은 `??` 항목은 PowerPoint 임시 파일(`~$...`)로, 제 작업과 무관합니다.

깨끗한 상태로 돌아왔습니다. 이제 **내부(본체) 댐퍼** 이야기로 다시 시작하시죠. 어떤 운전모드 전환에서 6개 댐퍼(OA/EA/BYPASS/SA/RA/AIR) 중 무엇이 어떻게 잘못 동작하는지 알려주시면 `Damper_Mode()`와 그 호출부를 정확히 보겠습니다.


개발사양서 14페이지에 내부댐퍼 6개에 대한 로직이 있는데..
ERV시뮬에서는 동작을 확인했는데..펌웨어에서는 환가, 공청일때 내부댐퍼 조작이 없냐는 얘기야.
내부댐퍼 로직을 알려달라는 얘기야.


개발사양서 14페이지의 내부댐퍼 로직과 펌웨어 실제 동작을 함께 확인하겠습니다. 먼저 사양서를 찾습니다.


doc\260602_소스_개발사양서_260318.md
doc\260602_소스_개발사양서_260520.md
개발사양서\각실제어시스템_개발사양서_DL_동작로직_260613.pptx

개발사양서\각실제어시스템_개발사양서_DL_동작로직_260613.pptx


No files found


ls -la "개발사양서/" 2>/dev/null; echo "---"; ls "doc/" | grep -i 사양

List spec folder contents


total 2540
drwxr-xr-x 1 전경선 197121       0 Jun 18 11:45 .
drwxr-xr-x 1 전경선 197121       0 Jun 18 10:46 ..
-rw-r--r-- 1 전경선 197121 2593047 Jun 18 10:46 각실제어시스템_개발사양서_DL_동작로직_260613.pptx
---
260602_소스_개발사양서_260318.md
260602_소스_개발사양서_260520.md


사양서가 PPTX입니다. 14페이지(슬라이드) 텍스트를 추출하겠습니다.


cd /d/Project/nuvoton/HECO2/개발사양서
tmp=$(mktemp -d)
unzip -o -q "각실제어시스템_개발사양서_DL_동작로직_260613.pptx" -d "$tmp" 2>/dev/null
echo "=== slide xml 목록 ==="
ls "$tmp/ppt/slides/" | sort -V
echo ""
echo "=== slide14.xml 텍스트 ==="
if [ -f "$tmp/ppt/slides/slide14.xml" ]; then
  grep -o '<a:t>[^<]*</a:t>' "$tmp/ppt/slides/slide14.xml" | sed 's/<a:t>//;s/<\/a:t>//'
fi
rm -rf "$tmp"

Extract slide 14 text from pptx


=== slide xml 목록 ===
slide1.xml
slide2.xml
slide3.xml
slide4.xml
slide5.xml
slide6.xml
slide7.xml
slide8.xml
slide9.xml
slide10.xml
slide11.xml
slide12.xml
slide13.xml
slide14.xml
slide15.xml
_rels

=== slide14.xml 텍스트 ===
순서
운전 모드
FAN
STEPPING
 MOTOR
급기
(SA)
배기
(EA)
OA
외장
MD
공청
MD
바이패스
MD
EA
 MD
SA MD
RA 3
단
MD
소비전력
1
정지
OFF
OFF
CLOSE
CLOSE
CLOSE
CLOSE
CLOSE
CLOSE
1.3
환기
1/2/3
ON
ON
OPEN
CLOSE
CLOSE
OPEN
OPEN
OPEN
 (70
°)
12.3/20.2/32.7
자동
(
환기
)
ON
ON
OPEN
CLOSE
CLOSE
OPEN
OPEN
OPEN
 (70
°)
바이패스
ON
ON
OPEN
CLOSE
OPEN
OPEN
OPEN
OPEN
 (140
°)
공기청정
1/2/3
ON
OFF
CLOSE
OPEN
CLOSE
CLOSE
OPEN
OPEN(140
°)
15.3/19.8/29.8 – UV X
2
예약
RS2
 : 1
시간
/ 2
시간
 / 3
시간
/ 4
시간
/5
시간
/ 6
시간
 / 7
시간
/ 8
시간
RJ2 : 60
분
/ 120
분
/ 180
분
/ 240
분
/ 300
분
/ 360
분
/ 420
분
/ 480
분
3
자동
RS2
룸컨
 없을 경우
:
환기모드 동작조건으로
20
분 동작
40
분 정지
룸컨 자동모드 조건에 따름
4
간헐 운전
동작 조건
:
자동모드에서 동작
온도 조건
: -7
℃
~ -14
℃
(
진입
: -7
℃ 이하
,
해제
: -5
℃ 이상
)
동작 방법
:
자동환기 약
20
분 운전
, 10
분 정지 반복 동작
룸컨
 표시
:
자동 약 표시
5
장비 보호
동작 조건
:
모든 운전 모드
(
다른 모드 변경 시 해제
)
온도 조건
: -15
℃ 이하
(
진입
: -15
℃
 이하
,
해제
:
-13
℃ 이상
)
동작 방법
:
운전 중 또는 전원
ON
시에
-15
℃ 이하 장비 꺼짐
본체
:
운전 정지
,
댐퍼
 닫힘
, LED
정상
룸컨트롤러

:
전원 꺼짐
,
버튼 조작 안됨
, COLD
표기 및
2
초간 점멸
,
백라이트는

1/10
로 줄임
,

온도 복귀 후

에러코드

사라짐
ECO ERV
제품 개발 사양서
동작
로직


개발사양서 14페이지에 내부댐퍼 6개에 대한 로직이 있는데.. ERV시뮬에서는 동작을 확인했는데..펌웨어에서는 환가, 공청일때 내부댐퍼 조작이 없냐는 얘기야. 내부댐퍼 로직을 알려달라는 얘기야.


HECO2 댐퍼 동작 문제 분석 및 비교


사양서 14페이지를 추출했습니다. 이제 이 표를 펌웨어 `Damper_Mode()`와 1:1로 대조하겠습니다. 댐퍼 번호 정의만 확인합니다.


d:\Project\nuvoton\HECO2\Source\HECO2\User\My_define.h:199:#define DAMPER_EA		1
d:\Project\nuvoton\HECO2\Source\HECO2\User\My_define.h:200:#define DAMPER_OA		2
d:\Project\nuvoton\HECO2\Source\HECO2\User\My_define.h:201:#define DAMPER_BYPASS	3
d:\Project\nuvoton\HECO2\Source\HECO2\User\My_define.h:202:#define DAMPER_SA		4
d:\Project\nuvoton\HECO2\Source\HECO2\User\My_define.h:203:#define DAMPER_RA		5 // 3???í¼
d:\Project\nuvoton\HECO2\Source\HECO2\User\My_define.h:204:#define DAMPER_AIR		6


확인 끝났습니다. 결론부터: **펌웨어에 환기·공청 내부댐퍼 로직은 분명히 있고, 사양서 14p 표와 각도까지 정확히 일치합니다.** 다만 "언제 움직이느냐"의 게이팅 때문에 동작이 안 보이는 것처럼 느껴질 수 있습니다.

## 1) 내부댐퍼 로직 = `Damper_Mode()` 한 함수에 다 있음
[MyMotor.c:472](Source/HECO2/User/MyMotor.c#L472). 각도 규칙: **0 = OPEN, 90/100/105 = CLOSE**, 단 **RA는 reverse** (0=CLOSE, 70°/140°=OPEN 각도). 댐퍼 번호: EA=1, OA=2, BYPASS=3, SA=4, RA=5, AIR(공청)=6.

## 2) 사양서 14p ↔ 펌웨어 대조 (완전 일치)

| 운전모드 | OA외장 | 공청MD(AIR) | 바이패스 | EA | SA | RA 3단 | 펌웨어 값 (OA/EA/BYP/SA/RA/AIR) |
|---|---|---|---|---|---|---|---|
| **정지(OFF)** | CLOSE | CLOSE | CLOSE | CLOSE | CLOSE | CLOSE | 100/100/100/100/**0**/105 ✅ |
| **환기 1/2/3** | OPEN | CLOSE | CLOSE | OPEN | OPEN | OPEN(70°) | 0/0/100/0/**70**/105 ✅ |
| **자동(환기)** | OPEN | CLOSE | CLOSE | OPEN | OPEN | OPEN(70°) | (환기와 동일 호출) ✅ |
| **바이패스** | OPEN | CLOSE | OPEN | OPEN | OPEN | OPEN(140°) | 0/0/**0**/0/**140**/105 ✅ |
| **공기청정 1/2/3** | CLOSE | OPEN | CLOSE | CLOSE | OPEN | OPEN(140°) | 100/100/100/0/**140**/**0** ✅ |

> RA가 0=CLOSE, 70/140=OPEN으로 뒤집힌 것 빼면 표 그대로입니다. 즉 **각도 테이블은 사양서대로 들어가 있습니다.**

## 3) "환기·공청일 때 댐퍼 조작이 없냐?" → 있습니다. 단, **팬이 멈춘 뒤에** 움직입니다
`Damper_Mode()`를 호출하는 곳은 [Fan_Speed_process()](Source/HECO2/User/MyMotor.c#L990) 안인데, 모드 분기마다 이렇게 게이팅됩니다 (환기 예시, 공청도 동일 구조 [MyMotor.c:1100](Source/HECO2/User/MyMotor.c#L1100)):

```c
if(Run_Mode == MODE_VENTILATION) {
    if(Pre_Run_Mode != MODE_VENTILATION || Power_On == 0) {   // ① 진입 틱
        Target_Fan1_Speed = 0; Target_Fan2_Speed = 0;          //   팬부터 0으로
        Damper_wait_time = 5;
        // ← 이 틱에서는 Damper_Mode() 호출 안 함
    } else {                                                    // ② 다음 틱부터
        if(Fan1_Speed != 0 || Fan2_Speed != 0)
            if(Damper_wait_time == 5) goto PASS1;               //   팬 도는 동안 댐퍼 스킵
        Damper_Mode(MODE_VENTILATION);                          //   팬=0 된 뒤에야 댐퍼 이동
        if(Step_Status != 0x3F) goto PASS1;                     //   6개 댐퍼 정렬 대기
        if(Damper_wait_time == 1) Fan_Speed_Setting(...);       //   정렬 끝나면 팬 복원
        if(Damper_wait_time) Damper_wait_time--;
    }
}
```

**동작 순서: 모드 전환 → 팬 0까지 감속 → 내부댐퍼 6개 이동(`Damper_Mode`) → Step_Status 0x3F(정렬 완료) → 팬 복원.**
공기 흐르는 중엔 댐퍼를 안 움직이려는 의도된 설계지만, 고풍량에서 전환하면 팬 감속에 수 초(86단계면 ~8.6초) 걸려 **댐퍼가 한참 뒤에 움직이거나 "안 움직이는 것처럼"** 보입니다.

## 그래서 ERV시뮬과의 진짜 차이
ERV시뮬은 `ApplyBodyDamperMode()`를 **매 틱 즉시·무조건** 적용해서 모드 누르자마자 본체 댐퍼가 바뀝니다([DamperSequencer.cs:51](Simulator/ERVSimulator/Program/Model/DamperSequencer.cs#L51)). 반면 펌웨어는 위처럼 **팬 감속 → 정렬 대기**로 지연됩니다. 로직 자체가 없는 게 아니라 **타이밍(게이팅)이 다른** 것입니다.

---

확인 부탁드립니다 — 어느 쪽이 실제 증상인가요?
1. **댐퍼가 아예 안 움직임** (팬 멈춰도 6개 중 일부/전부 미동작) → `Damper_Setting`/스텝모터 구동(`Step_Status` 비트) 쪽을 봐야 합니다.
2. **너무 늦게 움직임** (팬 감속 기다리느라) → 게이팅을 시뮬처럼 "즉시 이동"으로 바꾸는 수정.
3. **특정 모드만 이상** (예: 공청만 안 됨) → 해당 분기 집중.

증상을 알려주시면 그 지점만 정확히 파겠습니다. 참고로 `Damper_Mode()` 각도표 자체는 사양서와 일치하므로, 문제가 있다면 **호출 게이팅이나 스텝 구동부**일 가능성이 큽니다.


환기에서 공청으로 변경하면 팬은 정상동작하는데..댐퍼 동작이 없어.


팬은 정상인데 댐퍼만 안 움직인다 — 그러면 `Damper_Mode()` 호출은 되는데 스텝모터 구동/`Step_Status` 처리에서 막히는 것일 가능성이 큽니다. `Damper_Setting`과 스텝 처리부를 읽겠습니다.


576	void Damper_Setting(uint8_t damper_num, float angle_degree) // dir = 0 ������
577	{
578		Target_Step_Count[damper_num] = (uint16_t)(angle_degree * 85 / 7.5);
579	}
580
581	#define StepInterval_Fast 1
582	#define StepInterval_Slow 5
583
584	uint8_t Step_Interval1 = StepInterval_Slow;
585	uint8_t Step_Interval2 = StepInterval_Slow;
586	uint8_t Step_Interval3 = StepInterval_Slow;
587	uint8_t Step_Interval4 = StepInterval_Slow;
588	uint8_t Step_Interval5 = StepInterval_Slow;
589	uint8_t Step_Interval6 = StepInterval_Slow;
590
591	uint16_t StartFlag1 = 0;
592	uint16_t StartFlag2 = 0;
593	uint16_t StartFlag3 = 0;
594	uint16_t StartFlag4 = 0;
595	uint16_t StartFlag5 = 0;
596	uint16_t StartFlag6 = 0;
597
598	void Step_process(void)
599	{
600	// 1ms process
601
602		switch(Step_pos)
603		{
604			case 0:
605				if(Step_Interval1)
606				{
607					Step_Interval1 -- ;
608				}
609				else
610				{
611					if(Current_Step_Count[1] > Target_Step_Count[1])
612					{
613						Step_M1_Process(1);
614						Step_Interval1 = 3;
615						Current_Step_Count[1]--;
616						Step_Status &= ~1;
617					}
618					else if(Current_Step_Count[1] < Target_Step_Count[1])
619					{
620						Step_M1_Process(0);
621						Step_Interval1 = 3;
622						Current_Step_Count[1]++;
623						Step_Status &= ~1;
624					}
625					else
626					{
627						Step_M1_Stop();
628						Step_Status |= 1;
629						StartFlag1 = 0;
630	                    Step_Interval1 = 0;
631					}
632				}
633
634				if(Step_Interval2)
635				{
636					Step_Interval2 -- ;
637				}
638				else
639				{
640					if(Current_Step_Count[2] > Target_Step_Count[2])
641					{
642						Step_M2_Process(1);
643						if(StartFlag2 < 400)Step_Interval2 = StepInterval_Slow;
644						else Step_Interval2 = StepInterval_Fast;
645
646						StartFlag2++;
647
648						Current_Step_Count[2]--;
649						Step_Status &= ~2;
650					}
651					else if(Current_Step_Count[2] < Target_Step_Count[2])
652					{
653						Step_M2_Process(0);
654						if(StartFlag2 < 400)Step_Interval2 = StepInterval_Slow;
655						else Step_Interval2 = StepInterval_Fast;
656
657						StartFlag2++;
658
659						Current_Step_Count[2]++;
660						Step_Status &= ~2;
661					}
662					else
663					{
664						Step_M2_Stop();
665						Step_Status |= 2;
666						StartFlag2 = 0;
667	                    Step_Interval2 = 0;
668					}
669				}
670
671				if((Step_Status & 0x03) == 0x03)Step_pos++;
672				break;
673			case 1:
674				if(Step_Interval3)
675				{
676					Step_Interval3 -- ;
677				}
678				else
679				{
680					if(Current_Step_Count[3] > Target_Step_Count[3])
681					{
682						Step_M3_Process(1);
683						if(StartFlag3 < 511)Step_Interval3 = StepInterval_Slow;
684						else Step_Interval3 = StepInterval_Fast;
685
686						StartFlag3++;
687
688						Current_Step_Count[3]--;
689						Step_Status &= ~4;
690					}
691					else if(Current_Step_Count[3] < Target_Step_Count[3])
692					{
693						Step_M3_Process(0);
694						if(StartFlag3 < 511)Step_Interval3 = StepInterval_Slow;
695						else Step_Interval3 = StepInterval_Fast;
696
697						StartFlag3++;
698
699						Current_Step_Count[3]++;
700						Step_Status &= ~4;
701					}
702					else
703					{
704						Step_M3_Stop();
705						Step_Status |= 4;
706						StartFlag3 = 0;
707	                    Step_Interval3 = 0;
708					}
709				}
710
711				if(Step_Interval4)
712				{
713					Step_Interval4 -- ;
714				}
715				else
716				{
717					if(Current_Step_Count[4] > Target_Step_Count[4])
718					{
719						Step_M4_Process(1);
720						if(StartFlag4 < 511)Step_Interval4 = StepInterval_Slow;
721						else Step_Interval4 = StepInterval_Fast;
722
723						StartFlag4++;
724
725						Current_Step_Count[4]--;
726						Step_Status &= ~8;
727					}
728					else if(Current_Step_Count[4] < Target_Step_Count[4])
729					{
730						Step_M4_Process(0);
731						if(StartFlag4 < 511)Step_Interval4 = StepInterval_Slow;
732						else Step_Interval4 = StepInterval_Fast;
733
734						StartFlag4++;
735
736						Current_Step_Count[4]++;
737						Step_Status &= ~8;
738					}
739					else
740					{
741						Step_M4_Stop();
742						Step_Status |= 8;
743						StartFlag4 = 0;
744	                    Step_Interval4 = 0;
745					}
746				}
747
748
749				if((Step_Status & 0x0C) == 0x0C)Step_pos++;
750
751				break;
752			case 2:
753				if(Step_Interval5)// RA is 3 Dan
754				{
755					Step_Interval5 -- ;
756				}
757				else
758				{
759					if(Current_Step_Count[5] > Target_Step_Count[5]) // RA is 3 Dan
760					{
761						Step_M5_Process(1);
762						Step_Interval5 = 3;
763						Current_Step_Count[5]--;
764						Step_Status &= ~0x10;
765					}
766					else if(Current_Step_Count[5] < Target_Step_Count[5])
767					{
768						Step_M5_Process(0);
769						Step_Interval5 = 3;
770						Current_Step_Count[5]++;
771						Step_Status &= ~0x10;
772					}
773					else
774					{
775						Step_M5_Stop();
776						Step_Status |= 0x10;
777	                    Step_Interval5 = 0;
778					}
779				}
780
781				if(Step_Interval6)
782				{
783					Step_Interval6 -- ;
784				}
785				else
786				{
787					if(Current_Step_Count[6] > Target_Step_Count[6])
788					{
789						Step_M6_Process(1);
790						if(StartFlag6 < 511)Step_Interval6 = StepInterval_Slow;
791						else Step_Interval6 = StepInterval_Fast;
792
793						StartFlag6++;
794
795						Current_Step_Count[6]--;
796						Step_Status &= ~0x20;
797					}
798					else if(Current_Step_Count[6] < Target_Step_Count[6])
799					{
800						Step_M6_Process(0);
801						if(StartFlag6 < 511)Step_Interval6 = StepInterval_Slow;
802						else Step_Interval6 = StepInterval_Fast;
803
804						StartFlag6++;
805
806						Current_Step_Count[6]++;
807						Step_Status &= ~0x20;
808					}
809					else
810					{
811						Step_M6_Stop();
812						Step_Status |= 0x20;
813						StartFlag6 = 0;
814	                    Step_Interval6 = 0;
815					}
816				}
817
818				if((Step_Status & 0x30) == 0x30)Step_pos = 0;
819				break;
820			default:
821				Step_pos=0;
822				break;
823	        }
824	}
825
826
827
828	uint8_t s_FAN1_VEN_1_DAN;
829	uint8_t s_FAN1_VEN_2_DAN;
830	uint8_t s_FAN1_VEN_3_DAN;
831	uint8_t s_FAN1_VEN_4_DAN;
832	uint8_t s_FAN1_AIR_1_DAN;
833	uint8_t s_FAN1_AIR_2_DAN;
834	uint8_t s_FAN1_AIR_3_DAN;
835	uint8_t s_FAN1_AIR_4_DAN;
836	uint8_t s_FAN1_BYPASS_1_DAN;
837	uint8_t s_FAN1_BYPASS_2_DAN;
838	uint8_t s_FAN1_BYPASS_3_DAN;
839	uint8_t s_FAN1_BYPASS_4_DAN;
840
841	uint8_t s_FAN2_VEN_1_DAN;
842	uint8_t s_FAN2_VEN_2_DAN;
843	uint8_t s_FAN2_VEN_3_DAN;
844	uint8_t s_FAN2_VEN_4_DAN;
845	uint8_t s_FAN2_AIR_1_DAN;
846	uint8_t s_FAN2_AIR_2_DAN;
847	uint8_t s_FAN2_AIR_3_DAN;
848	uint8_t s_FAN2_AIR_4_DAN;
849	uint8_t s_FAN2_BYPASS_1_DAN;
850	uint8_t s_FAN2_BYPASS_2_DAN;
851	uint8_t s_FAN2_BYPASS_3_DAN;
852	uint8_t s_FAN2_BYPASS_4_DAN;
853
854
855	extern int16_t Volum1_value, Volum2_value;
856
857	extern uint8_t Rommcon_Version;
858
859
860	uint8_t Fan1_Speed = 0, Fan2_Speed = 0;
861	uint8_t Target_Fan1_Speed = 0, Target_Fan2_Speed = 0;
862
863	uint8_t Pre_Run_Mode = 100;
864	uint16_t Auto_Time = 0;
865	uint16_t Damper_wait_time = 0;
866
867	volatile uint8_t Vsp_Mode[5] = {0,};
868	volatile uint8_t Pre_Vsp_Mode = 0;
869
870	float Pre_Fan1_pwm = 0;
871	float Pre_Fan2_pwm = 0;
872
873	uint8_t Motor_Off_Count;
874	void PWM_out_process(void)
875	{
876		float Fan1_pwm = 0, Fan2_pwm = 0;
877
878		if((Fan_Mode != 0)||(RJ_Vsp_Mode == RX_DATA_MODE_VSP))
879		{
880			BLDC_PW = 0; // ON
881			Motor_Off_Count = 0;
882		}
883		else if((Fan_Mode == 0) && (Fan1_Speed == 0) && (Fan2_Speed == 0))
884		{
885			Motor_Off_Count++;
886
887			if(Motor_Off_Count >= 5)
888			{
889				BLDC_PW = 1; // OFF
890				Motor_Off_Count = 5;
891			}
892		}
893		Fan1_pwm = (float)(BLDC_SPEED_TABLE[Fan1_Speed] + BLDC_SPEED_TABLE[Fan1_Speed]*Volum1_value/1000);
894	//	if(Fan1_Speed > 50) Fan1_pwm -= (float)((BLDC_SPEED_TABLE[Fan1_Speed]-BLDC_SPEED_TABLE[50])*Volum1_value/10000);
895	//	else if(Fan1_Speed < 50)Fan1_pwm += (float)((BLDC_SPEED_TABLE[50]-BLDC_SPEED_TABLE[Fan1_Speed])*Volum1_value/10000);
896
897		Fan2_pwm = (float)(BLDC_SPEED_TABLE[Fan2_Speed] + BLDC_SPEED_TABLE[Fan2_Speed]*Volum2_value/1000);
898	//	if(Fan2_Speed > 50) Fan2_pwm += (float)((BLDC_SPEED_TABLE[Fan2_Speed]-BLDC_SPEED_TABLE[50])*Volum2_value/100);
899	//	else if(Fan2_Speed < 50)Fan2_pwm -= (float)((BLDC_SPEED_TABLE[50]-BLDC_SPEED_TABLE[Fan2_Speed])*Volum2_value/100);
900
901
902		if(Fan1_pwm > 10000)Fan1_pwm = 10000;if(Fan1_pwm < 0)Fan1_pwm = 0;
903		if(Fan2_pwm > 10000)Fan2_pwm = 10000;if(Fan2_pwm < 0)Fan2_pwm = 0;
904
905		Fan1_pwm = 10000 - Fan1_pwm;
906		Fan2_pwm = 10000 - Fan2_pwm;
907
908		if(Pre_Fan1_pwm != Fan1_pwm)BLDC1_Duty_Change((uint32_t)Fan1_pwm);
909		if(Pre_Fan2_pwm != Fan2_pwm)BLDC2_Duty_Change((uint32_t)Fan2_pwm);
910
911		Pre_Fan1_pwm = Fan1_pwm;
912		Pre_Fan2_pwm = Fan2_pwm;
913	}
914
915

void Damper_Setting(uint8_t damper_num, float angle_degree) // dir = 0 ������
{
	Target_Step_Count[damper_num] = (uint16_t)(angle_degree * 85 / 7.5);
}

#define StepInterval_Fast 1
#define StepInterval_Slow 5

uint8_t Step_Interval1 = StepInterval_Slow;
uint8_t Step_Interval2 = StepInterval_Slow;
uint8_t Step_Interval3 = StepInterval_Slow;
uint8_t Step_Interval4 = StepInterval_Slow;
uint8_t Step_Interval5 = StepInterval_Slow;
uint8_t Step_Interval6 = StepInterval_Slow;

uint16_t StartFlag1 = 0;
uint16_t StartFlag2 = 0;
uint16_t StartFlag3 = 0;
uint16_t StartFlag4 = 0;
uint16_t StartFlag5 = 0;
uint16_t StartFlag6 = 0;

void Step_process(void)
{
// 1ms process

	switch(Step_pos)
	{
		case 0:
			if(Step_Interval1)
			{
				Step_Interval1 -- ;
			}
			else
			{
				if(Current_Step_Count[1] > Target_Step_Count[1])
				{
					Step_M1_Process(1);
					Step_Interval1 = 3;
					Current_Step_Count[1]--;
					Step_Status &= ~1;
				}
				else if(Current_Step_Count[1] < Target_Step_Count[1])
				{
					Step_M1_Process(0);
					Step_Interval1 = 3;
					Current_Step_Count[1]++;
					Step_Status &= ~1;
				}
				else
				{
					Step_M1_Stop();
					Step_Status |= 1;
					StartFlag1 = 0;
                    Step_Interval1 = 0;
				}
			}

			if(Step_Interval2)
			{
				Step_Interval2 -- ;
			}
			else
			{
				if(Current_Step_Count[2] > Target_Step_Count[2])
				{
					Step_M2_Process(1);
					if(StartFlag2 < 400)Step_Interval2 = StepInterval_Slow;
					else Step_Interval2 = StepInterval_Fast;

					StartFlag2++;

					Current_Step_Count[2]--;
					Step_Status &= ~2;
				}
				else if(Current_Step_Count[2] < Target_Step_Count[2])
				{
					Step_M2_Process(0);
					if(StartFlag2 < 400)Step_Interval2 = StepInterval_Slow;
					else Step_Interval2 = StepInterval_Fast;

					StartFlag2++;

					Current_Step_Count[2]++;
					Step_Status &= ~2;
				}
				else
				{
					Step_M2_Stop();
					Step_Status |= 2;
					StartFlag2 = 0;
                    Step_Interval2 = 0;
				}
			}

			if((Step_Status & 0x03) == 0x03)Step_pos++;
			break;
		case 1:
			if(Step_Interval3)
			{
				Step_Interval3 -- ;
			}
			else
			{
				if(Current_Step_Count[3] > Target_Step_Count[3])
				{
					Step_M3_Process(1);
					if(StartFlag3 < 511)Step_Interval3 = StepInterval_Slow;
					else Step_Interval3 = StepInterval_Fast;

					StartFlag3++;

					Current_Step_Count[3]--;
					Step_Status &= ~4;
				}
				else if(Current_Step_Count[3] < Target_Step_Count[3])
				{
					Step_M3_Process(0);
					if(StartFlag3 < 511)Step_Interval3 = StepInterval_Slow;
					else Step_Interval3 = StepInterval_Fast;

					StartFlag3++;

					Current_Step_Count[3]++;
					Step_Status &= ~4;
				}
				else
				{
					Step_M3_Stop();
					Step_Status |= 4;
					StartFlag3 = 0;
                    Step_Interval3 = 0;
				}
			}

			if(Step_Interval4)
			{
				Step_Interval4 -- ;
			}
			else
			{
				if(Current_Step_Count[4] > Target_Step_Count[4])
				{
					Step_M4_Process(1);
					if(StartFlag4 < 511)Step_Interval4 = StepInterval_Slow;
					else Step_Interval4 = StepInterval_Fast;

					StartFlag4++;

					Current_Step_Count[4]--;
					Step_Status &= ~8;
				}
				else if(Current_Step_Count[4] < Target_Step_Count[4])
				{
					Step_M4_Process(0);
					if(StartFlag4 < 511)Step_Interval4 = StepInterval_Slow;
					else Step_Interval4 = StepInterval_Fast;

					StartFlag4++;

					Current_Step_Count[4]++;
					Step_Status &= ~8;
				}
				else
				{
					Step_M4_Stop();
					Step_Status |= 8;
					StartFlag4 = 0;
                    Step_Interval4 = 0;
				}
			}

			if((Step_Status & 0x0C) == 0x0C)Step_pos++;

			break;
		case 2:
			if(Step_Interval5)// RA is 3 Dan
			{
				Step_Interval5 -- ;
			}
			else
			{
				if(Current_Step_Count[5] > Target_Step_Count[5]) // RA is 3 Dan
				{
					Step_M5_Process(1);
					Step_Interval5 = 3;
					Current_Step_Count[5]--;
					Step_Status &= ~0x10;
				}
				else if(Current_Step_Count[5] < Target_Step_Count[5])
				{
					Step_M5_Process(0);
					Step_Interval5 = 3;
					Current_Step_Count[5]++;
					Step_Status &= ~0x10;
				}
				else
				{
					Step_M5_Stop();
					Step_Status |= 0x10;
                    Step_Interval5 = 0;
				}
			}

			if(Step_Interval6)
			{
				Step_Interval6 -- ;
			}
			else
			{
				if(Current_Step_Count[6] > Target_Step_Count[6])
				{
					Step_M6_Process(1);
					if(StartFlag6 < 511)Step_Interval6 = StepInterval_Slow;
					else Step_Interval6 = StepInterval_Fast;

					StartFlag6++;

					Current_Step_Count[6]--;
					Step_Status &= ~0x20;
				}
				else if(Current_Step_Count[6] < Target_Step_Count[6])
				{
					Step_M6_Process(0);
					if(StartFlag6 < 511)Step_Interval6 = StepInterval_Slow;
					else Step_Interval6 = StepInterval_Fast;

					StartFlag6++;

					Current_Step_Count[6]++;
					Step_Status &= ~0x20;
				}
				else
				{
					Step_M6_Stop();
					Step_Status |= 0x20;
					StartFlag6 = 0;
                    Step_Interval6 = 0;
				}
			}

			if((Step_Status & 0x30) == 0x30)Step_pos = 0;
			break;
		default:
			Step_pos=0;
			break;
        }
}

uint8_t s_FAN1_VEN_1_DAN;
uint8_t s_FAN1_VEN_2_DAN;
uint8_t s_FAN1_VEN_3_DAN;
uint8_t s_FAN1_VEN_4_DAN;
uint8_t s_FAN1_AIR_1_DAN;
uint8_t s_FAN1_AIR_2_DAN;
uint8_t s_FAN1_AIR_3_DAN;
uint8_t s_FAN1_AIR_4_DAN;
uint8_t s_FAN1_BYPASS_1_DAN;
uint8_t s_FAN1_BYPASS_2_DAN;
uint8_t s_FAN1_BYPASS_3_DAN;
uint8_t s_FAN1_BYPASS_4_DAN;

uint8_t s_FAN2_VEN_1_DAN;
uint8_t s_FAN2_VEN_2_DAN;
uint8_t s_FAN2_VEN_3_DAN;
uint8_t s_FAN2_VEN_4_DAN;
uint8_t s_FAN2_AIR_1_DAN;
uint8_t s_FAN2_AIR_2_DAN;
uint8_t s_FAN2_AIR_3_DAN;
uint8_t s_FAN2_AIR_4_DAN;
uint8_t s_FAN2_BYPASS_1_DAN;
uint8_t s_FAN2_BYPASS_2_DAN;
uint8_t s_FAN2_BYPASS_3_DAN;
uint8_t s_FAN2_BYPASS_4_DAN;

extern int16_t Volum1_value, Volum2_value;

extern uint8_t Rommcon_Version;

uint8_t Fan1_Speed = 0, Fan2_Speed = 0;
uint8_t Target_Fan1_Speed = 0, Target_Fan2_Speed = 0;

uint8_t Pre_Run_Mode = 100;
uint16_t Auto_Time = 0;
uint16_t Damper_wait_time = 0;

volatile uint8_t Vsp_Mode[5] = {0,};
volatile uint8_t Pre_Vsp_Mode = 0;

float Pre_Fan1_pwm = 0;
float Pre_Fan2_pwm = 0;

uint8_t Motor_Off_Count;
void PWM_out_process(void)
{
	float Fan1_pwm = 0, Fan2_pwm = 0;

	if((Fan_Mode != 0)||(RJ_Vsp_Mode == RX_DATA_MODE_VSP))
	{
		BLDC_PW = 0; // ON
		Motor_Off_Count = 0;
	}
	else if((Fan_Mode == 0) && (Fan1_Speed == 0) && (Fan2_Speed == 0))
	{
		Motor_Off_Count++;

		if(Motor_Off_Count >= 5)
		{
			BLDC_PW = 1; // OFF
			Motor_Off_Count = 5;
		}
	}
	Fan1_pwm = (float)(BLDC_SPEED_TABLE[Fan1_Speed] + BLDC_SPEED_TABLE[Fan1_Speed]*Volum1_value/1000);
//	if(Fan1_Speed > 50) Fan1_pwm -= (float)((BLDC_SPEED_TABLE[Fan1_Speed]-BLDC_SPEED_TABLE[50])*Volum1_value/10000);
//	else if(Fan1_Speed < 50)Fan1_pwm += (float)((BLDC_SPEED_TABLE[50]-BLDC_SPEED_TABLE[Fan1_Speed])*Volum1_value/10000);

	Fan2_pwm = (float)(BLDC_SPEED_TABLE[Fan2_Speed] + BLDC_SPEED_TABLE[Fan2_Speed]*Volum2_value/1000);
//	if(Fan2_Speed > 50) Fan2_pwm += (float)((BLDC_SPEED_TABLE[Fan2_Speed]-BLDC_SPEED_TABLE[50])*Volum2_value/100);
//	else if(Fan2_Speed < 50)Fan2_pwm -= (float)((BLDC_SPEED_TABLE[50]-BLDC_SPEED_TABLE[Fan2_Speed])*Volum2_value/100);

	if(Fan1_pwm > 10000)Fan1_pwm = 10000;if(Fan1_pwm < 0)Fan1_pwm = 0;
	if(Fan2_pwm > 10000)Fan2_pwm = 10000;if(Fan2_pwm < 0)Fan2_pwm = 0;

	Fan1_pwm = 10000 - Fan1_pwm;
	Fan2_pwm = 10000 - Fan2_pwm;

	if(Pre_Fan1_pwm != Fan1_pwm)BLDC1_Duty_Change((uint32_t)Fan1_pwm);
	if(Pre_Fan2_pwm != Fan2_pwm)BLDC2_Duty_Change((uint32_t)Fan2_pwm);

	Pre_Fan1_pwm = Fan1_pwm;
	Pre_Fan2_pwm = Fan2_pwm;
}


스텝 처리 로직을 파악했습니다. 핵심 의심점이 보입니다 — `Damper_Mode()`에서 **환기를 벗어날 때 `Step_pos=2`로 강제**하는 부분([MyMotor.c:518-520](Source/HECO2/User/MyMotor.c#L518-L520))과, `Step_process()`가 실제로 호출되는지 확인이 필요합니다. 호출부와 main 루프를 보겠습니다.


124:			Fan_Speed_process();

124:			Fan_Speed_process();


Source\HECO2\User\My_system.c:175:	Step_process();


`Step_process()`는 My_system.c:175에서 호출됩니다. 그 호출 맥락(타이머/게이팅 여부)과, 모드 전환 시 `Pre_Run_Mode`/`Power_On`이 설정되는 흐름을 확인하겠습니다.


150		if(Process_100ms)Process_100ms--;
151		if(Process_10ms)Process_10ms--;
152		if(Process_5ms)Process_5ms--;
153
154	    // clear timer interrupt flag
155	    TIMER_ClearIntFlag(TIMER0);
156	}
157
158	// TIMER_ClearWakeupFlag(TIMER0);
159	void Timer0_Init(void)
160	{
161	    TIMER_Open(TIMER0, TIMER_PERIODIC_MODE, 1000); // 1ms -> HZ
162
163	    // Enable timer interrupt
164	    TIMER_EnableInt(TIMER0);
165	    NVIC_EnableIRQ(TMR0_IRQn);
166
167
168	    // Start Timer 0
169	    TIMER_Start(TIMER0);
170	}
171
172
173	void TMR1_IRQHandler(void)
174	{
175		Step_process();
176
177	    // clear timer interrupt flag
178	    TIMER_ClearIntFlag(TIMER1);
179	}
180
181	void Timer1_Init(void)
182	{
183	    TIMER_Open(TIMER1, TIMER_PERIODIC_MODE, 1000); // 2ms -> HZ
184
185	    // Enable timer interrupt
186	    TIMER_EnableInt(TIMER1);
187	    NVIC_EnableIRQ(TMR1_IRQn);
188
189
190	    // Start Timer 0
191	    TIMER_Start(TIMER1);
192	}
193
194

	if(Process_100ms)Process_100ms--;
	if(Process_10ms)Process_10ms--;
	if(Process_5ms)Process_5ms--;

    // clear timer interrupt flag
    TIMER_ClearIntFlag(TIMER0);
}

// TIMER_ClearWakeupFlag(TIMER0);
void Timer0_Init(void)
{
    TIMER_Open(TIMER0, TIMER_PERIODIC_MODE, 1000); // 1ms -> HZ

    // Enable timer interrupt
    TIMER_EnableInt(TIMER0);
    NVIC_EnableIRQ(TMR0_IRQn);

    // Start Timer 0
    TIMER_Start(TIMER0);
}

void TMR1_IRQHandler(void)
{
	Step_process();

    // clear timer interrupt flag
    TIMER_ClearIntFlag(TIMER1);
}

void Timer1_Init(void)
{
    TIMER_Open(TIMER1, TIMER_PERIODIC_MODE, 1000); // 2ms -> HZ

    // Enable timer interrupt
    TIMER_EnableInt(TIMER1);
    NVIC_EnableIRQ(TMR1_IRQn);

    // Start Timer 0
    TIMER_Start(TIMER1);
}


105			Hood_RS485_process(); // Hood connect - uart0
106			//com_display_process(); // display
107			com_roomcon_process(); // roomcon
108	    	InCom_process();        // Bunbaegi
109	        Homenet_Process();      // HOMENET(ErvDashboard) 수신 명령 처리 + REQ_STATUS 응답
110
111			if(Process_5ms == 0)
112			{
113				Process_5ms = 5;
114			}
115
116			if(Process_10ms == 0)
117			{
118				Process_10ms = 3000;
119			}
120
121			if(Process_100ms == 0)
122			{
123				Process_100ms = 100;
124				Fan_Speed_process();
125				Fan_Error_Check();
126				Tx_display_flag = 1;
127			}
128			if(Process_333ms == 0)
129			{
130				Process_333ms = 200;
131	            Hood_process();
132				__wdt_reset();
133			}
134
135			Indicate_Error = Err_Code;
136			Indicate_Error &= ~(ERROR_SOMETIME|ERROR_PROTECT|ERROR_FILTER_CHANGE|ERROR_FILTER_CLEAN|ERROR_SOJA_CHANGE);
137
138			if((Indicate_Error))
139			{
140				if(Process_1000ms < 600)ST_LED = 0;
141				else ST_LED = 1;
142			}
143			else
144			{
145				if(Process_1000ms < 2)ST_LED = 0;
146				else ST_LED = 1;
147			}
148
149
150	 		if(Process_1000ms == 0)
151			{
152				Process_1000ms = 999;
153
154				/* (꺼짐)예약 카운트다운 : 0 도달 시 전원 OFF (대시보드 CTRL_RESERVE) */
155				if(Reserve_Remain_Sec > 0)
156				{
157					Reserve_Remain_Sec--;
158					if(Reserve_Remain_Sec == 0)
159					{
160						Power_On = 0;
161						Set_Run_Mode = 0;
162						Set_Fan_Mode = 0;
163						Command_request_type |= (TYPE_MODE | TYPE_FAN_SPEED);
164					}
165				}
166
167				Bldc_check();
168				ADC_Sensing();
169
170				Filter_process();
171
172	            Air_Quality_color_process();
173	            Total_Air_Volume = Air_Quality_damper_process();  // 260520 사양 자동 동작로직(집중/분산) - 1s 주기
174	            Homenet_Send_Status();  // HOMENET(ErvDashboard) STATUS(0x81) 1s 주기 송신
175
176				RJ_Com_Err_Check();
177	#if((SPEC_MODE_INFO&0x20) == 0x20) // UV 옵션
178	 			UV_process(); // 1sec
179	#endif

		Hood_RS485_process(); // Hood connect - uart0
		//com_display_process(); // display
		com_roomcon_process(); // roomcon
    	InCom_process();        // Bunbaegi
        Homenet_Process();      // HOMENET(ErvDashboard) 수신 명령 처리 + REQ_STATUS 응답

		if(Process_5ms == 0)
		{
			Process_5ms = 5;
		}

		if(Process_10ms == 0)
		{
			Process_10ms = 3000;
		}

		if(Process_100ms == 0)
		{
			Process_100ms = 100;
			Fan_Speed_process();
			Fan_Error_Check();
			Tx_display_flag = 1;
		}
		if(Process_333ms == 0)
		{
			Process_333ms = 200;
            Hood_process();
			__wdt_reset();
		}

		Indicate_Error = Err_Code;
		Indicate_Error &= ~(ERROR_SOMETIME|ERROR_PROTECT|ERROR_FILTER_CHANGE|ERROR_FILTER_CLEAN|ERROR_SOJA_CHANGE);

		if((Indicate_Error))
		{
			if(Process_1000ms < 600)ST_LED = 0;
			else ST_LED = 1;
		}
		else
		{
			if(Process_1000ms < 2)ST_LED = 0;
			else ST_LED = 1;
		}

 		if(Process_1000ms == 0)
		{
			Process_1000ms = 999;

			/* (꺼짐)예약 카운트다운 : 0 도달 시 전원 OFF (대시보드 CTRL_RESERVE) */
			if(Reserve_Remain_Sec > 0)
			{
				Reserve_Remain_Sec--;
				if(Reserve_Remain_Sec == 0)
				{
					Power_On = 0;
					Set_Run_Mode = 0;
					Set_Fan_Mode = 0;
					Command_request_type |= (TYPE_MODE | TYPE_FAN_SPEED);
				}
			}

			Bldc_check();
			ADC_Sensing();

			Filter_process();

            Air_Quality_color_process();
            Total_Air_Volume = Air_Quality_damper_process();  // 260520 사양 자동 동작로직(집중/분산) - 1s 주기
            Homenet_Send_Status();  // HOMENET(ErvDashboard) STATUS(0x81) 1s 주기 송신

			RJ_Com_Err_Check();
#if((SPEC_MODE_INFO&0x20) == 0x20) // UV 옵션
 			UV_process(); // 1sec
#endif


D:\Project\nuvoton\HERV_DL_MH_2nd\Program 여기 소스와 비교해 봐..
여기 소스가 마스터야.


사용자가 MH2nd를 마스터로 지정했습니다. 댐퍼 구동 코드(`Damper_Mode`/`Step_process`/`Fan_Speed_process` 게이팅)는 두 소스가 동일하므로, 차이는 **`Fan_Speed_Setting`/`Damper_Mode`를 누가 언제 호출하느냐(명령 경로)**에 있을 것입니다. 두 트리 전체에서 호출자를 비교하겠습니다.


cd /d/Project/nuvoton
echo "========== HECO2: Fan_Speed_Setting 호출자 =========="
grep -rn "Fan_Speed_Setting(" HECO2/Source/HECO2/User/ | grep -v "void Fan_Speed_Setting"
echo ""
echo "========== MH2nd(master): Fan_Speed_Setting 호출자 =========="
grep -rn "Fan_Speed_Setting(" HERV_DL_MH_2nd/Program/User/ | grep -v "void Fan_Speed_Setting"
echo ""
echo "========== HECO2: Damper_Mode 호출자 =========="
grep -rn "Damper_Mode(" HECO2/Source/HECO2/User/ | grep -v "void Damper_Mode"
echo ""
echo "========== MH2nd(master): Damper_Mode 호출자 =========="
grep -rn "Damper_Mode(" HERV_DL_MH_2nd/Program/User/ | grep -v "void Damper_Mode"

Compare Fan_Speed_Setting and Damper_Mode callers in both trees


========== HECO2: Fan_Speed_Setting 호출자 ==========
HECO2/Source/HECO2/User/MyMotor.c:1018:				Fan_Speed_Setting(Run_Mode, Fan_Mode);
HECO2/Source/HECO2/User/MyMotor.c:1056:				Fan_Speed_Setting(Run_Mode, Fan_Mode);
HECO2/Source/HECO2/User/MyMotor.c:1090:				Fan_Speed_Setting(Run_Mode, Fan_Mode);
HECO2/Source/HECO2/User/MyMotor.c:1124:				Fan_Speed_Setting(Run_Mode, Fan_Mode);
HECO2/Source/HECO2/User/MyMotor.c:1562:   //         Fan_Speed_Setting(Run_Mode, Fan_Mode);
HECO2/Source/HECO2/User/MyMotor.c:1583:              Fan_Speed_Setting(Run_Mode, Fan_Mode);
HECO2/Source/HECO2/User/MyMotor.c:1617:              Fan_Speed_Setting(Run_Mode, Fan_Mode);
HECO2/Source/HECO2/User/My_Homenet.c:259:                    Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영 (모드전환과 동일) */
HECO2/Source/HECO2/User/My_Homenet.c:293:                            Fan_Speed_Setting(Run_Mode, Fan_Mode);
HECO2/Source/HECO2/User/My_Homenet.c:306:                if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(자동은 부하점수로 결정) */
HECO2/Source/HECO2/User/My_Hood.c:192:			Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이 — CTRL_FAN 동일) */
HECO2/Source/HECO2/User/My_Hood.c:207:			if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 복귀 반영 */
HECO2/Source/HECO2/User/My_Hood.c:219:                if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이) */

========== MH2nd(master): Fan_Speed_Setting 호출자 ==========
HERV_DL_MH_2nd/Program/User/MyMotor.c:1563:   //         Fan_Speed_Setting(Run_Mode, Fan_Mode);
HERV_DL_MH_2nd/Program/User/MyMotor.c:1584:              Fan_Speed_Setting(Run_Mode, Fan_Mode);
HERV_DL_MH_2nd/Program/User/MyMotor.c:1618:              Fan_Speed_Setting(Run_Mode, Fan_Mode);

========== HECO2: Damper_Mode 호출자 ==========
HECO2/Source/HECO2/User/MyMotor.c:565:	Damper_Mode(MODE_OFF);
HECO2/Source/HECO2/User/MyMotor.c:941:			Damper_Mode(MODE_OFF);
HECO2/Source/HECO2/User/MyMotor.c:958:			Damper_Mode(MODE_OFF);
HECO2/Source/HECO2/User/MyMotor.c:964:			Damper_Mode(MODE_VENTILATION);
HECO2/Source/HECO2/User/MyMotor.c:970:			Damper_Mode(MODE_BYPASS);
HECO2/Source/HECO2/User/MyMotor.c:976:			Damper_Mode(MODE_AIRCLEAN);
HECO2/Source/HECO2/User/MyMotor.c:1007:			Damper_Mode(MODE_VENTILATION);
HECO2/Source/HECO2/User/MyMotor.c:1046:			Damper_Mode(MODE_VENTILATION);
HECO2/Source/HECO2/User/MyMotor.c:1083:			Damper_Mode(MODE_BYPASS);
HECO2/Source/HECO2/User/MyMotor.c:1117:			Damper_Mode(MODE_AIRCLEAN);
HECO2/Source/HECO2/User/MyMotor.c:1139:			Damper_Mode(MODE_VENTILATION);
HECO2/Source/HECO2/User/MyMotor.c:1143:			Damper_Mode(MODE_BYPASS);
HECO2/Source/HECO2/User/MyMotor.c:1147:			Damper_Mode(MODE_AIRCLEAN);
HECO2/Source/HECO2/User/MyMotor.c:1416:			Damper_Mode(MODE_VENTILATION);
HECO2/Source/HECO2/User/MyMotor.c:1448:			Damper_Mode(MODE_OFF);

========== MH2nd(master): Damper_Mode 호출자 ==========
HERV_DL_MH_2nd/Program/User/MyMotor.c:565:	Damper_Mode(MODE_OFF);
HERV_DL_MH_2nd/Program/User/MyMotor.c:941:			Damper_Mode(MODE_OFF);
HERV_DL_MH_2nd/Program/User/MyMotor.c:958:			Damper_Mode(MODE_OFF);
HERV_DL_MH_2nd/Program/User/MyMotor.c:964:			Damper_Mode(MODE_VENTILATION);
HERV_DL_MH_2nd/Program/User/MyMotor.c:970:			Damper_Mode(MODE_BYPASS);
HERV_DL_MH_2nd/Program/User/MyMotor.c:976:			Damper_Mode(MODE_AIRCLEAN);
HERV_DL_MH_2nd/Program/User/MyMotor.c:1007:			Damper_Mode(MODE_VENTILATION);
HERV_DL_MH_2nd/Program/User/MyMotor.c:1044:			Damper_Mode(MODE_VENTILATION);
HERV_DL_MH_2nd/Program/User/MyMotor.c:1079:			Damper_Mode(MODE_BYPASS);
HERV_DL_MH_2nd/Program/User/MyMotor.c:1113:			Damper_Mode(MODE_AIRCLEAN);
HERV_DL_MH_2nd/Program/User/MyMotor.c:1135:			Damper_Mode(MODE_VENTILATION);
HERV_DL_MH_2nd/Program/User/MyMotor.c:1139:			Damper_Mode(MODE_BYPASS);
HERV_DL_MH_2nd/Program/User/MyMotor.c:1143:			Damper_Mode(MODE_AIRCLEAN);
HERV_DL_MH_2nd/Program/User/MyMotor.c:1412:			Damper_Mode(MODE_VENTILATION);
HERV_DL_MH_2nd/Program/User/MyMotor.c:1444:			Damper_Mode(MODE_OFF);
Shell cwd was reset to d:\Project\nuvoton\HECO2


결정적인 차이를 찾았습니다. 호출자 목록이 명확합니다.

**마스터(MH2nd):** `Fan_Speed_Setting()`은 **오직 `MyMotor.c` 내부**(게이팅된 `Fan_Speed_process`/`Diffuser_Damper_process`)에서만 호출됩니다. 명령 경로(CVnet/RJ2)는 `Run_Mode`/`Fan_Mode`만 바꾸고 팬 타깃은 안 건드립니다.

**HECO2:** 명령 핸들러(`My_Homenet.c` 259/293/306, `My_Hood.c` 192/207/219)에서 모드/풍량 변경 즉시 `Fan_Speed_Setting()`을 **직접 호출**하도록 추가됐습니다 — 마스터엔 없는 코드입니다.

이게 왜 "팬은 정상, 댐퍼는 안 움직임"을 만드는지 확인하기 위해 My_Homenet 해당 부분을 정확히 보겠습니다.


244	    {
245	        case 0x01:  /* CTRL_POWER [onoff] */
246	            if(len >= 1)
247	            {
248	                Power_On = pl[0] ? 1 : 0;
249	                for(i = 1; i < 7; i++) Diffuser_Power[i] = Power_On;
250	                /* 전원 OFF : OFF 표현은 Fan_Mode==0 && Run_Mode==MODE_VENTILATION(0).
251	                   Set_* 만 0 으로 두면 Run_Mode/Fan_Mode 가 이전값(유효모드/풍량)으로 남아
252	                   Fan_Speed_process 의 OFF 분기가 성립 안 돼 모터가 Power_On 을 다시 켬 → 직접 0 설정 */
253	                if(Power_On == 0){ Set_Run_Mode = Run_Mode = MODE_VENTILATION; Set_Fan_Mode = Fan_Mode = 0; }
254	                else
255	                {
256	                    /* 전원 ON : 환기 모드 + 풍량 1단 (ERVSimulator HomeNetProtocol 와 동일) */
257	                    Set_Run_Mode = Run_Mode = MODE_VENTILATION;
258	                    Set_Fan_Mode = Fan_Mode = 1;
259	                    Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영 (모드전환과 동일) */
260	                }
261	                /* 전원 토글 시 수동 댐퍼/LED 해제 → 자동 추종 복귀 */
262	                for(i = 1; i <= 4; i++) { Diffuser_Damper_Manual[i] = 0; Diffuser_Led_Manual[i] = 0; }
263	                /* 각실분배기 : 전원 비트(TYPE_POWER) + 모드/풍량. 룸컨(RJ2)도 Command_request_type 로 푸시 */
264	                Command_request_type |= (TYPE_POWER | TYPE_MODE | TYPE_FAN_SPEED);
265	            } else result = 1;
266	            break;
267	        case 0x02:  /* CTRL_RUNMODE [mode] 0off/1환기/2자동/3공청/4바이패스 */
268	            if(len >= 1)
269	            {
270	                if(pl[0] == 0) Power_On = 0;
271	                else
272	                {
273	                    Power_On = 1;
274	                    switch(pl[0])
275	                    {
276	                        case 1: Set_Run_Mode = MODE_VENTILATION; break;
277	                        case 2: Set_Run_Mode = MODE_AUTO;        break;
278	                        case 3: Set_Run_Mode = MODE_AIRCLEAN;    break;
279	                        case 4: Set_Run_Mode = MODE_BYPASS;      break;
280	                        default: result = 1; break;
281	                    }
282	                    if(result == 0)
283	                    {
284	                        Run_Mode = Set_Run_Mode;
285	                        Command_request_type |= TYPE_MODE;   /* 룸컨(RJ2)이 새 모드 푸시 */
286	                        /* 운전모드 전환 시 풍량 1단 (자동 제외 - 자동은 부하점수로 결정) */
287	                        if(Set_Run_Mode != MODE_AUTO)
288	                        {
289	                            Set_Fan_Mode = Fan_Mode = 1;
290	                            Command_request_type |= TYPE_FAN_SPEED;
291	                            /* 즉시 반영 : 풍량 단수(Total_Air_Volume)가 안 바뀌는 모드전환
292	                               (예: 환기1단→공청1단)에서도 새 모드 VSP가 걸리도록 CTRL_FAN 과 동일하게 직접 호출 */
293	                            Fan_Speed_Setting(Run_Mode, Fan_Mode);
294	                        }
295	                    }
296	                }
297	                /* 모드 전환 시 수동 댐퍼 해제 → 새 모드는 기본(전실 개방)에서 시작 */
298	                for(i = 1; i <= 4; i++) Diffuser_Damper_Manual[i] = 0;
299	            } else result = 1;
300	            break;
301	        case 0x03:  /* CTRL_FAN [speed] */
302	            if(len >= 1)
303	            {
304	                Set_Fan_Mode = Fan_Mode = pl[0];
305	                Command_request_type |= TYPE_FAN_SPEED;   /* 룸컨(RJ2)이 새 풍량 푸시 */
306	                if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(자동은 부하점수로 결정) */
307	            }
308	            else result = 1;
309	            break;

    {
        case 0x01:  /* CTRL_POWER [onoff] */
            if(len >= 1)
            {
                Power_On = pl[0] ? 1 : 0;
                for(i = 1; i < 7; i++) Diffuser_Power[i] = Power_On;
                /* 전원 OFF : OFF 표현은 Fan_Mode==0 && Run_Mode==MODE_VENTILATION(0).
                   Set_* 만 0 으로 두면 Run_Mode/Fan_Mode 가 이전값(유효모드/풍량)으로 남아
                   Fan_Speed_process 의 OFF 분기가 성립 안 돼 모터가 Power_On 을 다시 켬 → 직접 0 설정 */
                if(Power_On == 0){ Set_Run_Mode = Run_Mode = MODE_VENTILATION; Set_Fan_Mode = Fan_Mode = 0; }
                else
                {
                    /* 전원 ON : 환기 모드 + 풍량 1단 (ERVSimulator HomeNetProtocol 와 동일) */
                    Set_Run_Mode = Run_Mode = MODE_VENTILATION;
                    Set_Fan_Mode = Fan_Mode = 1;
                    Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영 (모드전환과 동일) */
                }
                /* 전원 토글 시 수동 댐퍼/LED 해제 → 자동 추종 복귀 */
                for(i = 1; i <= 4; i++) { Diffuser_Damper_Manual[i] = 0; Diffuser_Led_Manual[i] = 0; }
                /* 각실분배기 : 전원 비트(TYPE_POWER) + 모드/풍량. 룸컨(RJ2)도 Command_request_type 로 푸시 */
                Command_request_type |= (TYPE_POWER | TYPE_MODE | TYPE_FAN_SPEED);
            } else result = 1;
            break;
        case 0x02:  /* CTRL_RUNMODE [mode] 0off/1환기/2자동/3공청/4바이패스 */
            if(len >= 1)
            {
                if(pl[0] == 0) Power_On = 0;
                else
                {
                    Power_On = 1;
                    switch(pl[0])
                    {
                        case 1: Set_Run_Mode = MODE_VENTILATION; break;
                        case 2: Set_Run_Mode = MODE_AUTO;        break;
                        case 3: Set_Run_Mode = MODE_AIRCLEAN;    break;
                        case 4: Set_Run_Mode = MODE_BYPASS;      break;
                        default: result = 1; break;
                    }
                    if(result == 0)
                    {
                        Run_Mode = Set_Run_Mode;
                        Command_request_type |= TYPE_MODE;   /* 룸컨(RJ2)이 새 모드 푸시 */
                        /* 운전모드 전환 시 풍량 1단 (자동 제외 - 자동은 부하점수로 결정) */
                        if(Set_Run_Mode != MODE_AUTO)
                        {
                            Set_Fan_Mode = Fan_Mode = 1;
                            Command_request_type |= TYPE_FAN_SPEED;
                            /* 즉시 반영 : 풍량 단수(Total_Air_Volume)가 안 바뀌는 모드전환
                               (예: 환기1단→공청1단)에서도 새 모드 VSP가 걸리도록 CTRL_FAN 과 동일하게 직접 호출 */
                            Fan_Speed_Setting(Run_Mode, Fan_Mode);
                        }
                    }
                }
                /* 모드 전환 시 수동 댐퍼 해제 → 새 모드는 기본(전실 개방)에서 시작 */
                for(i = 1; i <= 4; i++) Diffuser_Damper_Manual[i] = 0;
            } else result = 1;
            break;
        case 0x03:  /* CTRL_FAN [speed] */
            if(len >= 1)
            {
                Set_Fan_Mode = Fan_Mode = pl[0];
                Command_request_type |= TYPE_FAN_SPEED;   /* 룸컨(RJ2)이 새 풍량 푸시 */
                if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(자동은 부하점수로 결정) */
            }
            else result = 1;
            break;


HECO2 댐퍼 동작 문제 분석 및 비교


비교 끝났습니다. **댐퍼가 안 움직이는 진짜 원인을 찾았고, 마스터(MH2nd)와의 정확한 구조 차이로 설명됩니다.**

## 마스터의 설계 (정상 동작 원리)
환기→공청 전환 시 마스터는 이 순서로 동작합니다. **팬 타깃은 오직 `Fan_Speed_process`(게이팅 시퀀스)와 `Diffuser_Damper_process`만 건드립니다. 명령 핸들러(CVnet/RJ2)는 `Run_Mode`만 바꿉니다.**

1. 모드 전환 → 진입 틱에서 **팬 타깃 = 0** (감속)
2. **팬이 0에 도달**해야 → `Damper_Mode(공청)` 호출 → 내부댐퍼 이동
3. 댐퍼 정렬(`Step_Status==0x3F`) 완료 → `Diffuser_Damper_process`가 `Fan_Speed_Setting`으로 **팬 복원**

→ 즉 **"팬을 0까지 떨군다"가 댐퍼를 움직이는 게이트**입니다.

## HECO2가 마스터에서 바뀐 3가지 (호출자 비교로 확정)
| 변경 | 마스터(MH2nd) | HECO2 | 영향 |
|---|---|---|---|
| ① 디퓨저 트리거 | `Pre_CVnet_CMD != CVnet_CMD \|\| timer==0` | `timer==0`만 | 팬 복원이 최대 18초 지연 → "팬 꺼진 채 남음" |
| ② 명령 핸들러 팬설정 | **없음** | `My_Homenet.c` 259/293/306, `My_Hood.c` 192/207/219에서 **즉시 `Fan_Speed_Setting()` 호출** | **팬 타깃을 즉시 운전속도(0이 아님)로 올림** |
| ③ 팬 복원 위치 | 디퓨저 시퀀스 | `Fan_Speed_process` wait==1에 추가 | (이건 게이트 뒤라 무해) |

## 인과 사슬 — 왜 "팬 정상, 댐퍼 안 움직임"
1. HECO2가 **①에서 디퓨저 명령 트리거를 제거** → 팬 복원이 18초까지 늦어짐 → "팬이 꺼진 채 남던 문제" 발생.
2. 이걸 **②(명령 핸들러에서 `Fan_Speed_Setting` 직접 호출)로 땜빵** → 모드 누르면 팬 타깃이 곧바로 공청 운전속도로 셋팅됨.
3. 그 결과 **팬이 0에 도달하지 못함** → `Fan_Speed_process`의 `if(Fan!=0) if(wait==5) goto PASS1` 게이트가 `Damper_Mode(공청)`을 영원히 건너뜀.
4. **댐퍼는 안 움직이고, 팬만 정상 동작** ← 바로 보고하신 증상입니다.

(②번 [My_Homenet.c:293](Source/HECO2/User/My_Homenet.c#L293) 주석 "환기1→공청1에서 VSP 안 걸림"이 바로 ①을 제거해서 생긴 문제를 땜빵한 흔적입니다.)

## 마스터 정렬 수정안
땜빵(②)을 걷어내고 마스터 구조로 되돌리면 게이트가 살아나 댐퍼가 움직입니다. 단 ①도 함께 복원해야 "팬 꺼진 채 남는" 부작용이 재발하지 않습니다:

- **①: 디퓨저 명령 트리거 복원** — 마스터의 `Pre_CVnet_CMD != CVnet_CMD`를 HECO2식(예: `Run_Mode`/`Fan_Mode` 변경 감지)으로 `Diffuser_Damper_process`에 복원. → 팬 복원이 마스터처럼 디퓨저 시퀀스에서 제때 나옴.
- **②: 명령 핸들러의 `Fan_Speed_Setting` 직접 호출 제거** — `My_Homenet.c` 259/293/306, `My_Hood.c` 192/207/219. → 팬 타깃을 `Fan_Speed_process`가 다시 소유 → 감속 게이트 정상 → 댐퍼 이동.
- **③: `Fan_Speed_process` wait==1 복원 호출은 유지** (게이트 뒤라 무해, 마스터보다 즉각적인 개선).

즉 지난번에 제가 처음 짚었던 **①(디퓨저 트리거 제거)이 실제 상위 원인**이었고, 그 위에 쌓인 ② 땜빵이 댐퍼 게이트를 막은 구조입니다. RJ2(`My_RJ2.c`)·분배기(`My_bunbaegi.c`)는 두 소스가 **동일**하므로 손대지 않습니다.

이 ①+② 수정을 적용할까요? 적용하면 `My_Homenet.c`/`My_Hood.c`/`MyMotor.c`만 바뀌고, 바로 빌드 검증까지 하겠습니다.


디퓨저 트라거라고 하니까..헷갈리네..
그냥 동작모드 변경 트리거라고 해.
디퓨저는 아니고 내부댐퍼 변경이야.
먼저 내부댐퍼 관련 함수 좀 알려줘


용어 정리 받았습니다 — 앞으로 **"동작모드 변경 트리거"**, 대상은 **내부댐퍼**로 부르겠습니다. 먼저 내부댐퍼 관련 함수들을 정확한 위치와 함께 정리하겠습니다. 저수준 스텝 함수 위치만 확인합니다.


19:void Damper_Setting(uint8_t damper_num, float angle_degree); // dir = 0 ������
68:void Step_M1_Process(uint8_t dir)
129:void Step_M2_Process(uint8_t dir)
188:void Step_M3_Process(uint8_t dir)
246:void Step_M4_Process(uint8_t dir)
305:void Step_M5_Process(uint8_t dir)
364:void Step_M6_Process(uint8_t dir)
423:void Step_M1_Stop(void)
430:void Step_M2_Stop(void)
437:void Step_M3_Stop(void)
444:void Step_M4_Stop(void)
451:void Step_M5_Stop(void)
458:void Step_M6_Stop(void)
472:void Damper_Mode(uint8_t mmm)
538:uint8_t Step_motor_init(void) // Rev = 0 ������ ,
576:void Damper_Setting(uint8_t damper_num, float angle_degree) // dir = 0 ������
598:void Step_process(void)
921:void Fan_Speed_process(void) // 100ms
1237:void Fan_Speed_Setting(uint8_t r_mode, uint8_t f_speed) // Run_Mode, fanspeed


내부댐퍼(본체 6개 스텝모터 댐퍼: **EA·OA·BYPASS·SA·RA·공청**) 관련 함수는 전부 [MyMotor.c](Source/HECO2/User/MyMotor.c) 한 파일에 있습니다. 역할별로 정리합니다.

## 1) 운전모드 → 댐퍼 각도 결정 (핵심)
- **`Damper_Mode(mode)`** — [MyMotor.c:472](Source/HECO2/User/MyMotor.c#L472)
  운전모드(환기/공청/바이패스/OFF)별로 6개 댐퍼의 **목표 각도를 한 번에 지정**. `switch(mode)`로 사양서 14p 표를 그대로 구현. 모드가 바뀌면(`Pre_Damper_Mode != mode`) `Step_Status=0`·`Step_pos` 재설정 → 스텝 시퀀스 재시작.
- **`Damper_Setting(damper_num, angle)`** — [MyMotor.c:576](Source/HECO2/User/MyMotor.c#L576)
  각도(°)를 `Target_Step_Count[damper_num]`(스텝 수 = angle×85/7.5)로 변환. `Damper_Mode`가 댐퍼별로 6번 호출.

## 2) 실제 모터 구동 (목표각까지 이동)
- **`Step_process()`** — [MyMotor.c:598](Source/HECO2/User/MyMotor.c#L598)
  **TMR1 인터럽트(My_system.c:175)**에서 주기 호출. `Current_Step_Count`를 `Target_Step_Count`로 한 스텝씩 이동시키고, 댐퍼별로 다 도달하면 `Step_Status` 비트를 세움. `Step_pos`(0→1→2) 상태머신으로 **EA+OA → BYPASS+SA → RA+공청** 순서 구동, 6개 모두 완료 시 `Step_Status==0x3F`.
- **`Step_M1_Process`~`Step_M6_Process(dir)`** — [68](Source/HECO2/User/MyMotor.c#L68)/[129](Source/HECO2/User/MyMotor.c#L129)/[188](Source/HECO2/User/MyMotor.c#L188)/[246](Source/HECO2/User/MyMotor.c#L246)/[305](Source/HECO2/User/MyMotor.c#L305)/[364](Source/HECO2/User/MyMotor.c#L364)
  각 모터(1=EA,2=OA,3=BYPASS,4=SA,5=RA,6=공청)의 GPIO 1스텝 구동.
- **`Step_M1_Stop`~`Step_M6_Stop()`** — [423~458](Source/HECO2/User/MyMotor.c#L423)
  도달 시 해당 모터 정지(코일 off).
- **`Step_motor_init()`** — [MyMotor.c:538](Source/HECO2/User/MyMotor.c#L538)
  부팅 시 원점 정렬 후 `Damper_Mode(MODE_OFF)`로 초기화.

## 3) 모드 전환 시 댐퍼 호출 + 게이팅 (운영 로직)
- **`Fan_Speed_process()`** — [MyMotor.c:921](Source/HECO2/User/MyMotor.c#L921), 100ms 주기
  운전모드 분기마다 `Damper_Mode(...)`를 호출하되, **"팬 감속(=0) → 댐퍼 이동(`Step_Status==0x3F` 대기) → 팬 복원"** 순서로 게이팅. ← 지금 증상(팬은 도는데 댐퍼 안 움직임)이 걸려 있는 곳.
- **`Fan_Speed_Setting(r_mode, f_speed)`** — [MyMotor.c:1237](Source/HECO2/User/MyMotor.c#L1237)
  댐퍼가 아니라 **팬 VSP 설정** 함수지만, 게이팅과 얽혀 있어 같이 봐야 함(앞서 말한 "동작모드 변경 트리거" 논의의 대상).

## 핵심 변수
| 변수 | 의미 |
|---|---|
| `Target_Step_Count[1..6]` / `Current_Step_Count[1..6]` | 댐퍼별 목표/현재 스텝 위치 |
| `Step_Status` | 6비트 완료 플래그, **0x3F = 6개 정렬 완료** |
| `Step_pos` | 구동 순서 상태머신(0:EA/OA, 1:BYPASS/SA, 2:RA/공청) |
| `Pre_Damper_Mode` | 직전 모드(변경 감지용) |
| `Damper_wait_time` | 댐퍼 정렬 대기 카운터 |

> 댐퍼 번호: **EA=1, OA=2, BYPASS=3, SA=4, RA=5, 공청(AIR)=6** ([My_define.h:199-204](Source/HECO2/User/My_define.h#L199-L204))

여기까지가 내부댐퍼 함수 지도입니다. 다음으로 어디를 볼까요? (예: `Fan_Speed_process`의 게이팅을 마스터와 한 줄씩 대조, 또는 `Damper_Mode`의 모드별 각도 검증)


디퓨저 트라거라고 하니까..헷갈리네.. 그냥 동작모드 변경 트리거라고 해. 디퓨저는 아니고 내부댐퍼 변경이야. 먼저 내부댐퍼 관련 함수 좀 알려줘


그럼..지금 없는 내용이 운전모드 변경 트리거(?)가 없는거야?


아니요 — 정확히는 **"내부댐퍼 변경 트리거가 빠진 것"이 아닙니다.** 그 트리거는 HECO2에도 **있고, 마스터와 코드가 동일**합니다.

## 내부댐퍼 변경 트리거는 존재합니다
`Fan_Speed_process()` 안에서 운전모드가 바뀌면 `Damper_Mode(새모드)`를 호출하는 로직 — 이게 내부댐퍼 변경 트리거이고, HECO2/마스터 **글자 그대로 같습니다.** 빠지지 않았습니다.

## 문제는 "트리거 발동 조건"이 막힌 것
이 트리거는 그냥 모드만 바뀌면 도는 게 아니라, **"팬이 0까지 감속"되어야** 발동하도록 게이팅돼 있습니다 (공기 흐르는 중엔 댐퍼 안 움직이게):

```
모드 변경 → 팬 타깃 0 → 팬이 실제로 0 도달 → ★Damper_Mode 호출(내부댐퍼 이동)★ → 정렬 → 팬 복원
                          └─ 여기가 "조건". 팬이 0이 돼야 ★가 실행됨
```

- **마스터:** 명령 핸들러가 팬 타깃을 안 건드림 → 팬이 0까지 내려감 → ★ 실행 → 댐퍼 움직임. ✅
- **HECO2:** 명령 핸들러(`My_Homenet.c` 293/306 등)가 모드 바꾸자마자 `Fan_Speed_Setting()`으로 **팬 타깃을 즉시 켬** → 팬이 0에 도달 못 함 → ★가 영원히 안 돌아감 → **댐퍼 안 움직이고 팬만 정상.** ❌

## 한 줄 정리
**트리거가 없어진 게 아니라, HECO2가 추가한 코드(명령 핸들러의 `Fan_Speed_Setting` 직접 호출)가 그 트리거의 발동 조건(팬=0)을 막고 있는 것**입니다.

그래서 수정도 "트리거를 새로 넣기"가 아니라 **"막고 있는 추가 코드(②)를 걷어내서 마스터처럼 되돌리기"** 입니다. (앞서 말한 ①은 그걸 걷어냈을 때 '팬이 늦게 켜지는' 부작용을 막기 위한 보조입니다.)

원하시면 ②를 걷어내는 수정만 먼저 적용해서, 댐퍼가 움직이는지부터 확인해볼까요?


그래..그게 좋을 것 같아.


좋습니다. 다만 ② 중에서도 **모드 변경 시 호출되는 것**만 빼야 댐퍼가 살아나고, **풍량(단수)만 바꾸는 호출**은 남겨야 팬 단수 반응이 안 죽습니다. 정확히 분류하려고 My_Hood 쪽을 먼저 읽겠습니다.


178				My_Memory_Run_Mode = Run_Mode;
179				if((Run_Mode == MODE_AUTO)&&(Fan_Mode == 0))My_Memory_Fan_Mode = 1;
180				else My_Memory_Fan_Mode = Fan_Mode;
181
182				Set_Run_Mode = Run_Mode = MODE_VENTILATION;
183				Set_Fan_Mode = Fan_Mode = Hood_Step_To_Fan(Hood_Status); // 후드 단수 추종(사양 260613 9p 3.2)
184
185				if(Set_Reserve_timer_sec != 0)
186				{
187					Set_Reserve_timer_sec = 0;
188					Command_request_type |= (TYPE_RESERVATION);
189				}
190
191				Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
192				Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이 — CTRL_FAN 동일) */
193	            Tx_Yeundong_Delay = 30;
194			}
195			else if(Hood_Status == 0) // 후드 OFF : 즉시 원래 모드/풍량 복귀 (메이크업 유지는 후드측 담당, 사양 260613 9p 3.3)
196			{
197				Set_Run_Mode = Run_Mode = My_Memory_Run_Mode;
198				Set_Fan_Mode = Fan_Mode = My_Memory_Fan_Mode;
199
200				if(Set_Reserve_timer_sec != 0)
201				{
202					Set_Reserve_timer_sec = 0;
203					Command_request_type |= (TYPE_RESERVATION);
204				}
205
206				Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED|TYPE_HOOD_STATE);
207				if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 복귀 반영 */
208				Hood_Yeundong_flag = 0;
209				Hood_Warming_up_Timer = 0;
210				Tx_Yeundong_Delay = 0;
211			}
212	        else // 후드 단수 변경(1~5) : 메이크업 풍량 단수 추종 갱신
213	        {
214	            uint8_t f = Hood_Step_To_Fan(Hood_Status);
215	            if(Set_Fan_Mode != f)
216	            {
217	                Set_Fan_Mode = Fan_Mode = f;
218	                Command_request_type |= (TYPE_FAN_SPEED);
219	                if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이) */
220	                Tx_Yeundong_Delay = 30;   // 룸컨(My_RJ2)이 연동 풍량변경을 수동명령으로 오인하지 않도록 디바운스
221	            }
222	        }
223		}
224
225	    /* 메이크업 진입/단수변경 MODE/FAN 전송 후 연동 플래그 set + 룸컨 통지.
226	       Tx_Yeundong_Delay 는 룸컨(My_RJ2)이 연동 명령을 사용자 수동명령과 구분하는 데 사용(불변).
227	       조리 종료 후 잔여 배출(메이크업 유지)은 후드측이 담당하므로 여기서 롤백 타이머는 두지 않음. */

			My_Memory_Run_Mode = Run_Mode;
			if((Run_Mode == MODE_AUTO)&&(Fan_Mode == 0))My_Memory_Fan_Mode = 1;
			else My_Memory_Fan_Mode = Fan_Mode;

			Set_Run_Mode = Run_Mode = MODE_VENTILATION;
			Set_Fan_Mode = Fan_Mode = Hood_Step_To_Fan(Hood_Status); // 후드 단수 추종(사양 260613 9p 3.2)

			if(Set_Reserve_timer_sec != 0)
			{
				Set_Reserve_timer_sec = 0;
				Command_request_type |= (TYPE_RESERVATION);
			}

			Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
			Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이 — CTRL_FAN 동일) */
            Tx_Yeundong_Delay = 30;
		}
		else if(Hood_Status == 0) // 후드 OFF : 즉시 원래 모드/풍량 복귀 (메이크업 유지는 후드측 담당, 사양 260613 9p 3.3)
		{
			Set_Run_Mode = Run_Mode = My_Memory_Run_Mode;
			Set_Fan_Mode = Fan_Mode = My_Memory_Fan_Mode;

			if(Set_Reserve_timer_sec != 0)
			{
				Set_Reserve_timer_sec = 0;
				Command_request_type |= (TYPE_RESERVATION);
			}

			Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED|TYPE_HOOD_STATE);
			if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 복귀 반영 */
			Hood_Yeundong_flag = 0;
			Hood_Warming_up_Timer = 0;
			Tx_Yeundong_Delay = 0;
		}
        else // 후드 단수 변경(1~5) : 메이크업 풍량 단수 추종 갱신
        {
            uint8_t f = Hood_Step_To_Fan(Hood_Status);
            if(Set_Fan_Mode != f)
            {
                Set_Fan_Mode = Fan_Mode = f;
                Command_request_type |= (TYPE_FAN_SPEED);
                if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이) */
                Tx_Yeundong_Delay = 30;   // 룸컨(My_RJ2)이 연동 풍량변경을 수동명령으로 오인하지 않도록 디바운스
            }
        }
	}

    /* 메이크업 진입/단수변경 MODE/FAN 전송 후 연동 플래그 set + 룸컨 통지.
       Tx_Yeundong_Delay 는 룸컨(My_RJ2)이 연동 명령을 사용자 수동명령과 구분하는 데 사용(불변).
       조리 종료 후 잔여 배출(메이크업 유지)은 후드측이 담당하므로 여기서 롤백 타이머는 두지 않음. */


My_Hood 분류까지 확인했습니다. 정리하면:

- **빼야 할 것(모드 변경 시 호출 → 댐퍼 게이트 막음):** My_Homenet `259`(전원ON→환기), `293`(운전모드 변경), My_Hood `192`(후드ON→환기), `207`(후드OFF→모드복귀)
- **남길 것(풍량 단수만 변경 → 댐퍼 무관, 팬 반응 필요):** My_Homenet `306`(CTRL_FAN), My_Hood `219`(후드 단수변경)

복원·근거 추적을 위해 삭제 대신 **주석 처리**하겠습니다. (모드 변경 후 팬 복원은 `Fan_Speed_process`의 wait==1 호출 ③이 담당하므로 팬은 안 죽습니다.)


Edit

                    Set_Run_Mode = Run_Mode = MODE_VENTILATION;
                    Set_Fan_Mode = Fan_Mode = 1;
                    Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영 (모드전환과 동일) */

                    Set_Run_Mode = Run_Mode = MODE_VENTILATION;
                    Set_Fan_Mode = Fan_Mode = 1;
                    /* [모드변경댐퍼테스트] 마스터 정렬 : 모드변경 시 명령경로 즉시 팬설정 제거.
                       팬이 0까지 감속해야 Fan_Speed_process 가 Damper_Mode 를 호출(내부댐퍼 이동).
                       팬 복원은 Fan_Speed_process wait==1 에서 수행. */
                    //Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영 (모드전환과 동일) */


The file d:\Project\nuvoton\HECO2\Source\HECO2\User\My_Homenet.c has been updated successfully. (file state is current in your context — no need to Read it back)

                    Set_Run_Mode = Run_Mode = MODE_VENTILATION;
                    Set_Fan_Mode = Fan_Mode = 1;
                    Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영 (모드전환과 동일) */

                    Set_Run_Mode = Run_Mode = MODE_VENTILATION;
                    Set_Fan_Mode = Fan_Mode = 1;
                    /* [모드변경댐퍼테스트] 마스터 정렬 : 모드변경 시 명령경로 즉시 팬설정 제거.
                       팬이 0까지 감속해야 Fan_Speed_process 가 Damper_Mode 를 호출(내부댐퍼 이동).
                       팬 복원은 Fan_Speed_process wait==1 에서 수행. */
                    //Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영 (모드전환과 동일) */

/* 전원 ON : 환기 모드 + 풍량 1단 (ERVSimulator HomeNetProtocol 와 동일) */

-                    Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영 (모드전환과 동일) */

+                    /* [모드변경댐퍼테스트] 마스터 정렬 : 모드변경 시 명령경로 즉시 팬설정 제거.

+                       팬이 0까지 감속해야 Fan_Speed_process 가 Damper_Mode 를 호출(내부댐퍼 이동).

+                       팬 복원은 Fan_Speed_process wait==1 에서 수행. */

+                    //Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영 (모드전환과 동일) */

/* 전원 토글 시 수동 댐퍼/LED 해제 → 자동 추종 복귀 */

for(i = 1; i <= 4; i++) { Diffuser_Damper_Manual[i] = 0; Diffuser_Led_Manual[i] = 0; }


                            Set_Fan_Mode = Fan_Mode = 1;
                            Command_request_type |= TYPE_FAN_SPEED;
                            /* 즉시 반영 : 풍량 단수(Total_Air_Volume)가 안 바뀌는 모드전환
                               (예: 환기1단→공청1단)에서도 새 모드 VSP가 걸리도록 CTRL_FAN 과 동일하게 직접 호출 */
                            Fan_Speed_Setting(Run_Mode, Fan_Mode);

                            Set_Fan_Mode = Fan_Mode = 1;
                            Command_request_type |= TYPE_FAN_SPEED;
                            /* [모드변경댐퍼테스트] 마스터 정렬 : 모드변경 시 명령경로 즉시 팬설정 제거.
                               팬이 0까지 감속해야 Fan_Speed_process 가 Damper_Mode 를 호출(내부댐퍼 이동).
                               새 모드 VSP 복원은 Fan_Speed_process wait==1 에서 수행. */
                            //Fan_Speed_Setting(Run_Mode, Fan_Mode);


                            Set_Fan_Mode = Fan_Mode = 1;
                            Command_request_type |= TYPE_FAN_SPEED;
                            /* 즉시 반영 : 풍량 단수(Total_Air_Volume)가 안 바뀌는 모드전환
                               (예: 환기1단→공청1단)에서도 새 모드 VSP가 걸리도록 CTRL_FAN 과 동일하게 직접 호출 */
                            Fan_Speed_Setting(Run_Mode, Fan_Mode);

                            Set_Fan_Mode = Fan_Mode = 1;
                            Command_request_type |= TYPE_FAN_SPEED;
                            /* [모드변경댐퍼테스트] 마스터 정렬 : 모드변경 시 명령경로 즉시 팬설정 제거.
                               팬이 0까지 감속해야 Fan_Speed_process 가 Damper_Mode 를 호출(내부댐퍼 이동).
                               새 모드 VSP 복원은 Fan_Speed_process wait==1 에서 수행. */
                            //Fan_Speed_Setting(Run_Mode, Fan_Mode);

-                            /* 즉시 반영 : 풍량 단수(Total_Air_Volume)가 안 바뀌는 모드전환

-                               (예: 환기1단→공청1단)에서도 새 모드 VSP가 걸리도록 CTRL_FAN 과 동일하게 직접 호출 */

-                            Fan_Speed_Setting(Run_Mode, Fan_Mode);

+                            /* [모드변경댐퍼테스트] 마스터 정렬 : 모드변경 시 명령경로 즉시 팬설정 제거.

+                               팬이 0까지 감속해야 Fan_Speed_process 가 Damper_Mode 를 호출(내부댐퍼 이동).

+                               새 모드 VSP 복원은 Fan_Speed_process wait==1 에서 수행. */

+                            //Fan_Speed_Setting(Run_Mode, Fan_Mode);


			Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
			Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이 — CTRL_FAN 동일) */
            Tx_Yeundong_Delay = 30;

			Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
			/* [모드변경댐퍼테스트] 마스터 정렬 : 모드변경(→환기) 시 명령경로 즉시 팬설정 제거.
			   팬 복원은 Fan_Speed_process wait==1 에서 수행. */
			//Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이 — CTRL_FAN 동일) */
            Tx_Yeundong_Delay = 30;


The file d:\Project\nuvoton\HECO2\Source\HECO2\User\My_Hood.c has been updated successfully. (file state is current in your context — no need to Read it back)

			Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
			/* [모드변경댐퍼테스트] 마스터 정렬 : 모드변경(→환기) 시 명령경로 즉시 팬설정 제거.
			   팬 복원은 Fan_Speed_process wait==1 에서 수행. */
			//Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이 — CTRL_FAN 동일) */
            Tx_Yeundong_Delay = 30;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "Nano100Series.h"
#include "adc.h"
#include "gpio.h"
#include "pwm.h"
#include "timer.h"
#include "uart.h"
#include "sys.h"
#include "clk.h"

#include "My_define.h"

////////////////////////////////////////////////////////////////////////////////
//////////////////////// HOOD 485 //////////////////////////////////////////////
extern uint16_t Hood_polling_timer;
extern uint8_t rx_hood_485_TimeOut;
uint8_t Tx_Hood_Buff[20], Rx_Hood_Buff[20];
uint8_t Yeungong_Status = 0;
uint8_t Hood_Power_On = 0;
uint8_t Hood_Fan_Mode = 0;
uint8_t Hood_Control = 0;
uint8_t Hood_Rx_Complete = 0;
uint8_t Hood_Status = 0;

uint8_t CheckSum_Creator(uint8_t *buf, uint8_t len)
{
	uint8_t i, rt;

	rt = 0;

	for(i=0; i<len; i++)
	{
		rt ^= buf[i];
	}

	return(rt);
}

uint8_t Rx_hood_Pos = 0;
/* 후드 응답 수신 파서 — Rev1.3 9바이트 : AA 11 ID FANSTATUS LIGHT 00 연동CMD ERROR CS(XOR byte0~7)
   (구버전 13바이트는 My_Hood_old.c 보관) */
void rx_hood_check(uint8_t data)
{
	if(rx_hood_485_TimeOut == 0)Rx_hood_Pos = 0;

	switch(Rx_hood_Pos)
	{
		case 0:
			if(data != 0xAA)break;
			Rx_Hood_Buff[Rx_hood_Pos++] = data;
			break;
		case 1:
			if(data != 0x11)
            {
                Rx_hood_Pos = 0;
            }
            Rx_Hood_Buff[Rx_hood_Pos++] = data;
			break;
        case 2:
		case 3:
		case 4:
		case 5:
		case 6:
		case 7:
            Rx_Hood_Buff[Rx_hood_Pos++] = data;
			break;
		case 8:
			Rx_Hood_Buff[Rx_hood_Pos++] = data;
			if(data == CheckSum_Creator(Rx_Hood_Buff, 8))   /* CS = XOR(byte0~7) */
			{
				Hood_Rx_Complete = 1;
			}
			Rx_hood_Pos = 0;
			break;
		default:
			Rx_hood_Pos = 0;
			break;
	}
}
//-------------------------------------
void UART0_HANDLE()
{
    uint8_t u8InChar=0xFF;
    uint32_t u32IntSts= UART0->ISR;

    if(u32IntSts & UART_ISR_RDA_IS_Msk)
    {
       u8InChar = UART_READ(UART0);           /* Rx trigger level is 1 byte*/

		rx_hood_check(u8InChar);
		rx_hood_485_TimeOut = 10;
    }

    if(u32IntSts & UART_ISR_THRE_IS_Msk)
    {
;
    }
}

void UART0_IRQHandler(void)
{
    UART0_HANDLE();
}
//----------------------------------

void Hood_Tx_packet(void)
{
    /* 후드 Rev1.3 마스터 프레임(9바이트) : AA 21 ID MODE FAN 연동EN 연동운전중 ERROR CS(XOR byte0~7)
       구버전 13B는 ERV→후드 제어필드(Power/Fan)를 포함했으나 Rev1.3 스펙엔 없음(ERV는 후드 단수 추종). */
    Tx_Hood_Buff[0] = 0xAA;
    Tx_Hood_Buff[1] = 0x21;
    Tx_Hood_Buff[2] = 0x01;                  /* ID */
    Tx_Hood_Buff[3] = Run_Mode;              /* MODE */
    Tx_Hood_Buff[4] = Fan_Mode;              /* FAN */
    Tx_Hood_Buff[5] = Hood_YeunDong_Enable;  /* 연동 Enable */
    Tx_Hood_Buff[6] = Yeungong_Status;       /* 연동 운전중 */
    Tx_Hood_Buff[7] = 0x00;                  /* ERROR */
    Tx_Hood_Buff[8] = CheckSum_Creator(Tx_Hood_Buff, 8);

    HOOD_485_DIR = 1; // DIR
    UART_Write(UART0,Tx_Hood_Buff, 9);
    while ( !(UART0->FSR & UART_FSR_TX_EMPTY_F_Msk) );
    while ( !(UART0->FSR & UART_FSR_TE_F_Msk) );
    HOOD_485_DIR = 0; // DIR;
}

void Hood_RS485_process(void)
{
    if(Hood_Rx_Complete == 1)
    {
        Hood_Rx_Complete = 0;
        Hood_Conn_Timeout = 1500;   /* 유효 응답 수신 -> 통신연결 생존(폴 500ms 기준 ~3회 누락 허용) */
        Hood_Status = Rx_Hood_Buff[3];
//        if((Hood_Status == 1)&&(Hood_Yeundong_flag == 0))Command_request_type |= TYPE_HOOD_STATE;// add 2022.1.25
//        if((Hood_Status == 0)&&(Hood_Yeundong_flag == 1))Command_request_type |= TYPE_HOOD_STATE;// add 2022.1.25
//        Hood_Yeundong_flag = Hood_Status;
    }
    else
    {
        if(Hood_polling_timer == 0)
        {
            Hood_polling_timer = 500;
            Hood_Tx_packet();
        }
    }
}

uint8_t HREV_Hood_Control = 0;
uint16_t Hood_Warming_up_Timer = 0;
uint8_t Pre_Hood_Status = 0;
uint8_t Tx_Yeundong_Delay = 0;

/* 후드 단수 -> 환기장치 풍량 추종 매핑(사양 260613 9p 3.2) : 1->1,2->2,3->3,4->4,5->4 */
static uint8_t Hood_Step_To_Fan(uint8_t hs)
{
    if(hs == 0) return 0;
    return (hs > 4) ? 4 : hs;
}

uint8_t Hood_process(void)//200ms
{
    if((Hood_YeunDong_Enable == 0)||(HREV_Hood_Control == 1))return(0);

	if(Hood_Status != Pre_Hood_Status)
	{
		if((Pre_Hood_Status == 0)&&(Hood_Status != 0)) // 후드 ON : 메이크업 에어 진입(자동/수동 일시정지)
		{
			My_Memory_Run_Mode = Run_Mode;
			if((Run_Mode == MODE_AUTO)&&(Fan_Mode == 0))My_Memory_Fan_Mode = 1;
			else My_Memory_Fan_Mode = Fan_Mode;

			Set_Run_Mode = Run_Mode = MODE_VENTILATION;
			Set_Fan_Mode = Fan_Mode = Hood_Step_To_Fan(Hood_Status); // 후드 단수 추종(사양 260613 9p 3.2)

			if(Set_Reserve_timer_sec != 0)
			{
				Set_Reserve_timer_sec = 0;
				Command_request_type |= (TYPE_RESERVATION);
			}

			Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
			Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이 — CTRL_FAN 동일) */
            Tx_Yeundong_Delay = 30;
		}
		else if(Hood_Status == 0) // 후드 OFF : 즉시 원래 모드/풍량 복귀 (메이크업 유지는 후드측 담당, 사양 260613 9p 3.3)
		{
			Set_Run_Mode = Run_Mode = My_Memory_Run_Mode;
			Set_Fan_Mode = Fan_Mode = My_Memory_Fan_Mode;

			if(Set_Reserve_timer_sec != 0)
			{
				Set_Reserve_timer_sec = 0;
				Command_request_type |= (TYPE_RESERVATION);
			}

			Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED|TYPE_HOOD_STATE);
			if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 복귀 반영 */
			Hood_Yeundong_flag = 0;
			Hood_Warming_up_Timer = 0;
			Tx_Yeundong_Delay = 0;
		}
        else // 후드 단수 변경(1~5) : 메이크업 풍량 단수 추종 갱신
        {
            uint8_t f = Hood_Step_To_Fan(Hood_Status);
            if(Set_Fan_Mode != f)
            {
                Set_Fan_Mode = Fan_Mode = f;
                Command_request_type |= (TYPE_FAN_SPEED);
                if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이) */
                Tx_Yeundong_Delay = 30;   // 룸컨(My_RJ2)이 연동 풍량변경을 수동명령으로 오인하지 않도록 디바운스
            }
        }
	}

    /* 메이크업 진입/단수변경 MODE/FAN 전송 후 연동 플래그 set + 룸컨 통지.
       Tx_Yeundong_Delay 는 룸컨(My_RJ2)이 연동 명령을 사용자 수동명령과 구분하는 데 사용(불변).
       조리 종료 후 잔여 배출(메이크업 유지)은 후드측이 담당하므로 여기서 롤백 타이머는 두지 않음. */
    if(Tx_Yeundong_Delay)
    {
        Tx_Yeundong_Delay--;
        if(Tx_Yeundong_Delay == 0)
        {
            Hood_Yeundong_flag = 1;
            Command_request_type |= TYPE_HOOD_STATE;// add 2022.1.25
        }
    }

	Pre_Hood_Status = Hood_Status;

    return(1);
}

-      Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이 — CTRL_FAN 동일) */

+      /* [모드변경댐퍼테스트] 마스터 정렬 : 모드변경(→환기) 시 명령경로 즉시 팬설정 제거.

+         팬 복원은 Fan_Speed_process wait==1 에서 수행. */

+      //Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이 — CTRL_FAN 동일) */


			Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED|TYPE_HOOD_STATE);
			if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 복귀 반영 */

			Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED|TYPE_HOOD_STATE);
			/* [모드변경댐퍼테스트] 마스터 정렬 : 모드복귀 시 명령경로 즉시 팬설정 제거.
			   팬 복원은 Fan_Speed_process wait==1 에서 수행. */
			//if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 복귀 반영 */


			Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED|TYPE_HOOD_STATE);
			/* [모드변경댐퍼테스트] 마스터 정렬 : 모드복귀 시 명령경로 즉시 팬설정 제거.
			   팬 복원은 Fan_Speed_process wait==1 에서 수행. */
			//if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 복귀 반영 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "Nano100Series.h"
#include "adc.h"
#include "gpio.h"
#include "pwm.h"
#include "timer.h"
#include "uart.h"
#include "sys.h"
#include "clk.h"

#include "My_define.h"

////////////////////////////////////////////////////////////////////////////////
//////////////////////// HOOD 485 //////////////////////////////////////////////
extern uint16_t Hood_polling_timer;
extern uint8_t rx_hood_485_TimeOut;
uint8_t Tx_Hood_Buff[20], Rx_Hood_Buff[20];
uint8_t Yeungong_Status = 0;
uint8_t Hood_Power_On = 0;
uint8_t Hood_Fan_Mode = 0;
uint8_t Hood_Control = 0;
uint8_t Hood_Rx_Complete = 0;
uint8_t Hood_Status = 0;

uint8_t CheckSum_Creator(uint8_t *buf, uint8_t len)
{
	uint8_t i, rt;

	rt = 0;

	for(i=0; i<len; i++)
	{
		rt ^= buf[i];
	}

	return(rt);
}

uint8_t Rx_hood_Pos = 0;
/* 후드 응답 수신 파서 — Rev1.3 9바이트 : AA 11 ID FANSTATUS LIGHT 00 연동CMD ERROR CS(XOR byte0~7)
   (구버전 13바이트는 My_Hood_old.c 보관) */
void rx_hood_check(uint8_t data)
{
	if(rx_hood_485_TimeOut == 0)Rx_hood_Pos = 0;

	switch(Rx_hood_Pos)
	{
		case 0:
			if(data != 0xAA)break;
			Rx_Hood_Buff[Rx_hood_Pos++] = data;
			break;
		case 1:
			if(data != 0x11)
            {
                Rx_hood_Pos = 0;
            }
            Rx_Hood_Buff[Rx_hood_Pos++] = data;
			break;
        case 2:
		case 3:
		case 4:
		case 5:
		case 6:
		case 7:
            Rx_Hood_Buff[Rx_hood_Pos++] = data;
			break;
		case 8:
			Rx_Hood_Buff[Rx_hood_Pos++] = data;
			if(data == CheckSum_Creator(Rx_Hood_Buff, 8))   /* CS = XOR(byte0~7) */
			{
				Hood_Rx_Complete = 1;
			}
			Rx_hood_Pos = 0;
			break;
		default:
			Rx_hood_Pos = 0;
			break;
	}
}
//-------------------------------------
void UART0_HANDLE()
{
    uint8_t u8InChar=0xFF;
    uint32_t u32IntSts= UART0->ISR;

    if(u32IntSts & UART_ISR_RDA_IS_Msk)
    {
       u8InChar = UART_READ(UART0);           /* Rx trigger level is 1 byte*/

		rx_hood_check(u8InChar);
		rx_hood_485_TimeOut = 10;
    }

    if(u32IntSts & UART_ISR_THRE_IS_Msk)
    {
;
    }
}

void UART0_IRQHandler(void)
{
    UART0_HANDLE();
}
//----------------------------------

void Hood_Tx_packet(void)
{
    /* 후드 Rev1.3 마스터 프레임(9바이트) : AA 21 ID MODE FAN 연동EN 연동운전중 ERROR CS(XOR byte0~7)
       구버전 13B는 ERV→후드 제어필드(Power/Fan)를 포함했으나 Rev1.3 스펙엔 없음(ERV는 후드 단수 추종). */
    Tx_Hood_Buff[0] = 0xAA;
    Tx_Hood_Buff[1] = 0x21;
    Tx_Hood_Buff[2] = 0x01;                  /* ID */
    Tx_Hood_Buff[3] = Run_Mode;              /* MODE */
    Tx_Hood_Buff[4] = Fan_Mode;              /* FAN */
    Tx_Hood_Buff[5] = Hood_YeunDong_Enable;  /* 연동 Enable */
    Tx_Hood_Buff[6] = Yeungong_Status;       /* 연동 운전중 */
    Tx_Hood_Buff[7] = 0x00;                  /* ERROR */
    Tx_Hood_Buff[8] = CheckSum_Creator(Tx_Hood_Buff, 8);

    HOOD_485_DIR = 1; // DIR
    UART_Write(UART0,Tx_Hood_Buff, 9);
    while ( !(UART0->FSR & UART_FSR_TX_EMPTY_F_Msk) );
    while ( !(UART0->FSR & UART_FSR_TE_F_Msk) );
    HOOD_485_DIR = 0; // DIR;
}

void Hood_RS485_process(void)
{
    if(Hood_Rx_Complete == 1)
    {
        Hood_Rx_Complete = 0;
        Hood_Conn_Timeout = 1500;   /* 유효 응답 수신 -> 통신연결 생존(폴 500ms 기준 ~3회 누락 허용) */
        Hood_Status = Rx_Hood_Buff[3];
//        if((Hood_Status == 1)&&(Hood_Yeundong_flag == 0))Command_request_type |= TYPE_HOOD_STATE;// add 2022.1.25
//        if((Hood_Status == 0)&&(Hood_Yeundong_flag == 1))Command_request_type |= TYPE_HOOD_STATE;// add 2022.1.25
//        Hood_Yeundong_flag = Hood_Status;
    }
    else
    {
        if(Hood_polling_timer == 0)
        {
            Hood_polling_timer = 500;
            Hood_Tx_packet();
        }
    }
}

uint8_t HREV_Hood_Control = 0;
uint16_t Hood_Warming_up_Timer = 0;
uint8_t Pre_Hood_Status = 0;
uint8_t Tx_Yeundong_Delay = 0;

/* 후드 단수 -> 환기장치 풍량 추종 매핑(사양 260613 9p 3.2) : 1->1,2->2,3->3,4->4,5->4 */
static uint8_t Hood_Step_To_Fan(uint8_t hs)
{
    if(hs == 0) return 0;
    return (hs > 4) ? 4 : hs;
}

uint8_t Hood_process(void)//200ms
{
    if((Hood_YeunDong_Enable == 0)||(HREV_Hood_Control == 1))return(0);

	if(Hood_Status != Pre_Hood_Status)
	{
		if((Pre_Hood_Status == 0)&&(Hood_Status != 0)) // 후드 ON : 메이크업 에어 진입(자동/수동 일시정지)
		{
			My_Memory_Run_Mode = Run_Mode;
			if((Run_Mode == MODE_AUTO)&&(Fan_Mode == 0))My_Memory_Fan_Mode = 1;
			else My_Memory_Fan_Mode = Fan_Mode;

			Set_Run_Mode = Run_Mode = MODE_VENTILATION;
			Set_Fan_Mode = Fan_Mode = Hood_Step_To_Fan(Hood_Status); // 후드 단수 추종(사양 260613 9p 3.2)

			if(Set_Reserve_timer_sec != 0)
			{
				Set_Reserve_timer_sec = 0;
				Command_request_type |= (TYPE_RESERVATION);
			}

			Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
			/* [모드변경댐퍼테스트] 마스터 정렬 : 모드변경(→환기) 시 명령경로 즉시 팬설정 제거.
			   팬 복원은 Fan_Speed_process wait==1 에서 수행. */
			//Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이 — CTRL_FAN 동일) */
            Tx_Yeundong_Delay = 30;
		}
		else if(Hood_Status == 0) // 후드 OFF : 즉시 원래 모드/풍량 복귀 (메이크업 유지는 후드측 담당, 사양 260613 9p 3.3)
		{
			Set_Run_Mode = Run_Mode = My_Memory_Run_Mode;
			Set_Fan_Mode = Fan_Mode = My_Memory_Fan_Mode;

			if(Set_Reserve_timer_sec != 0)
			{
				Set_Reserve_timer_sec = 0;
				Command_request_type |= (TYPE_RESERVATION);
			}

			Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED|TYPE_HOOD_STATE);
			if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 복귀 반영 */
			Hood_Yeundong_flag = 0;
			Hood_Warming_up_Timer = 0;
			Tx_Yeundong_Delay = 0;
		}
        else // 후드 단수 변경(1~5) : 메이크업 풍량 단수 추종 갱신
        {
            uint8_t f = Hood_Step_To_Fan(Hood_Status);
            if(Set_Fan_Mode != f)
            {
                Set_Fan_Mode = Fan_Mode = f;
                Command_request_type |= (TYPE_FAN_SPEED);
                if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 반영(룸컨 echo 대기 없이) */
                Tx_Yeundong_Delay = 30;   // 룸컨(My_RJ2)이 연동 풍량변경을 수동명령으로 오인하지 않도록 디바운스
            }
        }
	}

    /* 메이크업 진입/단수변경 MODE/FAN 전송 후 연동 플래그 set + 룸컨 통지.
       Tx_Yeundong_Delay 는 룸컨(My_RJ2)이 연동 명령을 사용자 수동명령과 구분하는 데 사용(불변).
       조리 종료 후 잔여 배출(메이크업 유지)은 후드측이 담당하므로 여기서 롤백 타이머는 두지 않음. */
    if(Tx_Yeundong_Delay)
    {
        Tx_Yeundong_Delay--;
        if(Tx_Yeundong_Delay == 0)
        {
            Hood_Yeundong_flag = 1;
            Command_request_type |= TYPE_HOOD_STATE;// add 2022.1.25
        }
    }

	Pre_Hood_Status = Hood_Status;

    return(1);
}

-      if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 복귀 반영 */

+      /* [모드변경댐퍼테스트] 마스터 정렬 : 모드복귀 시 명령경로 즉시 팬설정 제거.

+      //if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode);   /* 즉시 복귀 반영 */


Now let me build to confirm 0 warnings/errors.


Bash

cd /d/Project/nuvoton/HECO2/Source/HECO2 && bash build.sh all 2>&1 | grep -iE "warning|error|HERV.elf|text" ; echo "---build done---"; bash build.sh all 2>&1 | tail -3

Build firmware and check warnings/errors


[LD]  build/HERV.elf
   text	   data	    bss	    dec	    hex	filename
  43920	   1940	   3388	  49248	   c060	build/HERV.elf
---build done---
   text	   data	    bss	    dec	    hex	filename
  43920	   1940	   3388	  49248	   c060	build/HERV.elf


빌드 성공(경고/오류 0, text 43920 — 코드 제거로 감소). 작업 기록을 남기겠습니다.


Write

# 260618 내부댐퍼 — 운전모드 변경 시 미동작 수정 (1차: 명령경로 즉시 팬설정 제거)

## 증상
- 환기 → 공청 전환 시 **팬은 정상 동작하는데 내부댐퍼(본체 6개)가 안 움직임.**

## 원인 (마스터 `D:\Project\nuvoton\HERV_DL_MH_2nd\Program` 비교로 확정)
- 내부댐퍼 이동 트리거(`Fan_Speed_process`가 모드변경 시 `Damper_Mode()` 호출)는 HECO2/마스터 **동일하게 존재**. 빠진 게 아님.
- 그 트리거는 **"팬이 0까지 감속"되어야 발동**하도록 게이팅됨(공기 흐름 중 댐퍼 미동작 의도):
  `모드변경 → 팬타깃0 → 팬=0 도달 → Damper_Mode 호출(댐퍼 이동) → 정렬(Step_Status==0x3F) → 팬 복원`
- **마스터:** 명령 핸들러가 팬 타깃을 안 건드림 → 팬이 0 도달 → 댐퍼 이동.
- **HECO2:** 명령 핸들러가 모드변경 즉시 `Fan_Speed_Setting()` 직접 호출 → 팬 타깃이 곧바로 운전속도로 올라감 → **팬이 0에 도달 못 함 → `if(Fan!=0) if(wait==5) goto PASS1` 게이트가 `Damper_Mode` 영원히 스킵 → 댐퍼 미동작, 팬만 정상.**
- 마스터엔 없고 HECO2에만 추가된 명령경로 `Fan_Speed_Setting` 호출이 게이트를 막은 것.

## 변경 파일/내용 (1차 = 모드 변경 시 호출만 제거, 풍량단수 변경 호출은 유지)
주석 처리(삭제 아님, 복원 용이). 마커: `[모드변경댐퍼테스트]`
- `Source/HECO2/User/My_Homenet.c`
  - L259 CTRL_POWER(전원ON→환기) `Fan_Speed_Setting` 주석
  - L293 CTRL_RUNMODE(운전모드 변경) `Fan_Speed_Setting` 주석  ← 보고 증상(대시보드 환기→공청)의 직접 경로
  - **L306 CTRL_FAN(풍량단수만 변경)은 유지** (댐퍼 무관, 팬 단수 반응 필요)
- `Source/HECO2/User/My_Hood.c`
  - L192 후드ON(→환기) `Fan_Speed_Setting` 주석
  - L207 후드OFF(모드 복귀) `Fan_Speed_Setting` 주석
  - **L219 후드 단수변경(풍량만)은 유지**

## 핵심 결정
- 모드 변경 후 팬 복원은 `Fan_Speed_process`의 wait==1 호출(MyMotor.c 1018/1056/1090/1124, ③)이 담당 → 팬이 꺼진 채 남지 않음.
- RJ2(`My_RJ2.c`)·분배기(`My_bunbaegi.c`)는 두 소스 동일 → 미수정.
- 이번엔 "동작모드 변경 트리거 복원(①, 디퓨저 시퀀스 명령트리거)"은 보류 — ②(땜빵) 제거만으로 댐퍼가 움직이는지 먼저 검증.

## 빌드 결과
- `cd SOURCE/HECO2 && bash build.sh all` → 성공, 경고/오류 0.
- text 43920 / data 1940 / bss 3388 (HERV.elf). (이전 44008 → 코드 제거로 감소)

## 미해결 / 후속
- 실장비/시뮬레이터에서 **환기→공청 시 내부댐퍼 이동** 확인 필요(특히 고풍량 전환 시 팬 감속 대기시간 체감).
- 검증 후: 필요하면 ①(동작모드 변경 트리거 = MyMotor.c 디퓨저/팬복원 타이밍의 명령 트리거)도 마스터 정렬 검토.
- 풍량단수만 바꿀 때(L306/L219) 팬 즉시 반응 정상인지 확인.


File created successfully at: d:\Project\nuvoton\HECO2\doc\260618_내부댐퍼_모드변경시_미동작_수정.md (file state is current in your context — no need to Read it back)

# 260618 내부댐퍼 — 운전모드 변경 시 미동작 수정 (1차: 명령경로 즉시 팬설정 제거)

## 증상
- 환기 → 공청 전환 시 **팬은 정상 동작하는데 내부댐퍼(본체 6개)가 안 움직임.**

## 원인 (마스터 `D:\Project\nuvoton\HERV_DL_MH_2nd\Program` 비교로 확정)
- 내부댐퍼 이동 트리거(`Fan_Speed_process`가 모드변경 시 `Damper_Mode()` 호출)는 HECO2/마스터 **동일하게 존재**. 빠진 게 아님.
- 그 트리거는 **"팬이 0까지 감속"되어야 발동**하도록 게이팅됨(공기 흐름 중 댐퍼 미동작 의도):
  `모드변경 → 팬타깃0 → 팬=0 도달 → Damper_Mode 호출(댐퍼 이동) → 정렬(Step_Status==0x3F) → 팬 복원`
- **마스터:** 명령 핸들러가 팬 타깃을 안 건드림 → 팬이 0 도달 → 댐퍼 이동.
- **HECO2:** 명령 핸들러가 모드변경 즉시 `Fan_Speed_Setting()` 직접 호출 → 팬 타깃이 곧바로 운전속도로 올라감 → **팬이 0에 도달 못 함 → `if(Fan!=0) if(wait==5) goto PASS1` 게이트가 `Damper_Mode` 영원히 스킵 → 댐퍼 미동작, 팬만 정상.**
- 마스터엔 없고 HECO2에만 추가된 명령경로 `Fan_Speed_Setting` 호출이 게이트를 막은 것.

## 변경 파일/내용 (1차 = 모드 변경 시 호출만 제거, 풍량단수 변경 호출은 유지)
주석 처리(삭제 아님, 복원 용이). 마커: `[모드변경댐퍼테스트]`
- `Source/HECO2/User/My_Homenet.c`
  - L259 CTRL_POWER(전원ON→환기) `Fan_Speed_Setting` 주석
  - L293 CTRL_RUNMODE(운전모드 변경) `Fan_Speed_Setting` 주석  ← 보고 증상(대시보드 환기→공청)의 직접 경로
  - **L306 CTRL_FAN(풍량단수만 변경)은 유지** (댐퍼 무관, 팬 단수 반응 필요)
- `Source/HECO2/User/My_Hood.c`
  - L192 후드ON(→환기) `Fan_Speed_Setting` 주석
  - L207 후드OFF(모드 복귀) `Fan_Speed_Setting` 주석
  - **L219 후드 단수변경(풍량만)은 유지**

## 핵심 결정
- 모드 변경 후 팬 복원은 `Fan_Speed_process`의 wait==1 호출(MyMotor.c 1018/1056/1090/1124, ③)이 담당 → 팬이 꺼진 채 남지 않음.
- RJ2(`My_RJ2.c`)·분배기(`My_bunbaegi.c`)는 두 소스 동일 → 미수정.
- 이번엔 "동작모드 변경 트리거 복원(①, 디퓨저 시퀀스 명령트리거)"은 보류 — ②(땜빵) 제거만으로 댐퍼가 움직이는지 먼저 검증.

## 빌드 결과
- `cd SOURCE/HECO2 && bash build.sh all` → 성공, 경고/오류 0.
- text 43920 / data 1940 / bss 3388 (HERV.elf). (이전 44008 → 코드 제거로 감소)

## 미해결 / 후속
- 실장비/시뮬레이터에서 **환기→공청 시 내부댐퍼 이동** 확인 필요(특히 고풍량 전환 시 팬 감속 대기시간 체감).
- 검증 후: 필요하면 ①(동작모드 변경 트리거 = MyMotor.c 디퓨저/팬복원 타이밍의 명령 트리거)도 마스터 정렬 검토.
- 풍량단수만 바꿀 때(L306/L219) 팬 즉시 반응 정상인지 확인.


1차 수정 적용 완료했습니다.

## 적용 내용 (②의 "모드 변경 시 호출"만 제거)
모드 변경 시 명령 핸들러가 팬 타깃을 즉시 켜서 댐퍼 게이트를 막던 호출들을 **주석 처리**했습니다 (마커 `[모드변경댐퍼테스트]`, 삭제 아님 → 복원 쉬움):

| 파일 | 위치 | 처리 |
|---|---|---|
| [My_Homenet.c:293](Source/HECO2/User/My_Homenet.c#L293) | CTRL_RUNMODE(운전모드 변경) | 주석 ← **대시보드 환기→공청 직접 경로** |
| [My_Homenet.c:259](Source/HECO2/User/My_Homenet.c#L259) | CTRL_POWER(전원ON→환기) | 주석 |
| [My_Hood.c:192](Source/HECO2/User/My_Hood.c#L192) | 후드ON(→환기) | 주석 |
| [My_Hood.c:207](Source/HECO2/User/My_Hood.c#L207) | 후드OFF(모드복귀) | 주석 |
| My_Homenet.c:306 (CTRL_FAN) | 풍량단수만 변경 | **유지** |
| My_Hood.c:219 (후드 단수변경) | 풍량단수만 변경 | **유지** |

## 기대 동작
이제 환기→공청 전환 시: **팬 0까지 감속 → `Damper_Mode(공청)` 호출 → 내부댐퍼 이동 → 정렬 완료 → wait==1에서 팬 복원**. 팬은 안 죽습니다(복원은 `Fan_Speed_process` ③이 담당).

## 결과
- 빌드: **성공, 경고/오류 0** (text 44008 → **43920**, 코드 제거로 감소)
- 기록: [doc/260618_내부댐퍼_모드변경시_미동작_수정.md](doc/260618_내부댐퍼_모드변경시_미동작_수정.md)
- RJ2·분배기 미수정

이제 펌웨어 올려서 **환기→공청 시 내부댐퍼가 움직이는지** 확인해 주세요. 고풍량에서 전환하면 팬이 0까지 내려갔다가 댐퍼 움직이고 다시 올라오므로 약간의 지연(팬 감속시간)이 있습니다. 동작 확인되면 풍량단수만 바꿀 때(공청1→공청2 등) 팬 반응도 정상인지 같이 봐주시면, 필요 시 ① 정렬까지 마무리하겠습니다.


스마트수면 동작로직이 잘못됐어.
거실은 무조건 닫는거야..지금 거실이 열려 있어.
개발사양서 8페이지에 있어.


스마트수면 동작로직이 잘못됐어. 거실은 무조건 닫는거야..지금 거실이 열려 있어. 개발사양서 8페이지에 있어.


HECO2 댐퍼 동작 문제 분석 및 비교


개발사양서 8페이지와 펌웨어의 스마트수면 로직을 함께 확인하겠습니다.


Bash

cd /d/Project/nuvoton/HECO2/개발사양서
tmp=$(mktemp -d)
unzip -o -q "각실제어시스템_개발사양서_DL_동작로직_260613.pptx" -d "$tmp" 2>/dev/null
echo "=== slide8.xml 텍스트 ==="
grep -o '<a:t>[^<]*</a:t>' "$tmp/ppt/slides/slide8.xml" | sed 's/<a:t>//;s/<\/a:t>//'
rm -rf "$tmp"

Extract slide 8 text (smart sleep logic)


=== slide8.xml 텍스트 ===
동작모드
–
시나리오 모드
1.
안심회복 모드
>
침실
1
만 적용
특정 방(침실1)의 공기만 집중적으로 밖으로 배출(
음압
 형성)
을
유도
하여, 유행성 질환 등 바이러스나 오염된 공기가 거실이나 다른 방으로 확산되지 않도록 안전하게 격리하는 모드입니다.
H-ERV :
환기 수동
,
풍량

: 2
단
유지시간
:
 사용자가
ON/OFF,

OFF
하면 이전 모드 복귀
거실
침실
1
침실
2
침실
3
급기

(SA)
O
X
O
O
배기
(RA)
X
O
X
X
안심회복
 모드
-
침실
1
개발 사양서
-
각실제어
 시스템
2.
스마트수면 모드
설정된 수면 시간 동안 거주자의 잠을 깨우지 않도록 **저소음(1단 고정)**
으로
 가동하면서, CO2

기준
(1,000 ppm On / 800 PPM OFF)
으로
필수적인

환기만 조용히 수행하는 스케줄러 기능입니다.
H-ERV :
환기 수동
,
풍량

1
단 고정
초기상태
:
거실
CLOSE,
침실
1~3 OPEN
CO2
센서 기준 동작
. CO2 >= 1000 PPM
이면 해당 침실
댐퍼

OPEN, CO2 <= 800PPM
이면
댐퍼
 CLOSE
유지시간
:
 사용자가
ON/OFF, OFF
하면 이전 모드 복귀
거실
침실
1
침실
2
침실
3
급기

(SA)
X
O
O
O
배기
(RA)
X
O
O
O
스마트수면 모드
–
초기 상태


d:\Project\nuvoton\HECO2\Source\HECO2\User\My_system.c:759:       시나리오 모드(안심회복/집중청정/스마트수면/쾌적조리)가 goto PASS_VOLUME 로 아래 메인 루프를
d:\Project\nuvoton\HECO2\Source\HECO2\User\My_system.c:760:       건너뛰어 등급이 멈춰(스마트수면 등에서 대시보드 등급 불일치) → 등급 계산만 앞으로 빼서 항상 수행. */
d:\Project\nuvoton\HECO2\Source\HECO2\User\My_system.c:893:    else if(Ext_Run_Mode == 4) //스마트 수면 모드 (사양서 8p) : 환기 수동·1단 고정, 실별 CO2 기준 댐퍼 개폐
d:\Project\nuvoton\HECO2\Source\HECO2\User\My_system.c:934:    else if((Ext_Run_Mode == 0)&&((Pre_Ext_Run_Mode == 1)||(Pre_Ext_Run_Mode == 3)||(Pre_Ext_Run_Mode == 4)))
d:\Project\nuvoton\HECO2\Source\HECO2\User\My_system.c:1149:    if(Ext_Run_Mode == 4)
d:\Project\nuvoton\HECO2\Source\HECO2\User\My_Homenet.c:127:    p[4] = (uint8_t)(((Ext_Run_Mode == 4) ? 0x01 : 0)    /* 스마트수면 */
d:\Project\nuvoton\HECO2\Source\HECO2\User\My_Homenet.c:314:        case 0x04:  /* CTRL_SUBMODE [type][onoff] 1수면/2조리/3회복 */


스마트수면 사양 확인했습니다: **거실은 급기(SA)·배기(RA) 모두 항상 CLOSE**, 침실1~3만 CO2 기준(≥1000 OPEN / ≤800 CLOSE) 개폐. 펌웨어 해당 로직을 읽겠습니다.


893	    else if(Ext_Run_Mode == 4) //스마트 수면 모드 (사양서 8p) : 환기 수동·1단 고정, 실별 CO2 기준 댐퍼 개폐
894	    {
895	        if(Pre_Ext_Run_Mode != 4)   /* 진입 1회 : 모드/풍량 + 초기상태(거실 CLOSE, 침실1~3 OPEN) */
896	        {
897	            Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);
898
899	            My_Memory_Run_Mode = Run_Mode;
900	            My_Memory_Fan_Mode = Fan_Mode;
901
902	            Set_Run_Mode = MODE_VENTILATION;
903	            Set_Fan_Mode = 1;
904
905	            Memory_Diffuser_Dmp_Ang_SA[1] = 0;   Memory_Diffuser_Dmp_Ang_RA[1] = 0;   /* 거실 CLOSE */
906	            for(Room_Num = 2; Room_Num < 5; Room_Num++)                                /* 침실1~3 OPEN */
907	            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 110; Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 110; }
908	            Memory_Diffuser_Dmp_Ang_SA[5] = 0; Memory_Diffuser_Dmp_Ang_RA[5] = 0;
909	            Memory_Diffuser_Dmp_Ang_SA[6] = 0; Memory_Diffuser_Dmp_Ang_RA[6] = 0;
910
911	            Pre_Ext_Run_Mode = Ext_Run_Mode;
912	            Pre_Ext_Select_Room = Ext_Select_Room;
913	        }
914
915	        /* 매 틱 : 실별 CO2 히스테리시스. CO2 >= 1000 OPEN, <= 800 CLOSE, 그 사이(데드존)는 현재 상태 유지 */
916	        for(Room_Num = 1; Room_Num < 5; Room_Num++)
917	        {
918	            if(SEN66_CO2_value[Room_Num] >= 1000)
919	            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 110; Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 110; }
920	            else if(SEN66_CO2_value[Room_Num] <= 800)
921	            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 0;   Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 0; }
922
923	            if((Memory_Diffuser_Dmp_Ang_SA[Room_Num] != 0)||(Memory_Diffuser_Dmp_Ang_RA[Room_Num] != 0))
924	                Diffuser_Air_quality[Room_Num] = 5;//ON
925	            else
926	                Diffuser_Air_quality[Room_Num] = 0;//OFF
927	        }
928
929	        Tmp_Air_Volume = 1;   /* 1단 고정 */
930	        Ext_Run_Mode_Off_Delay = 0;
931
932	        goto PASS_VOLUME;
933	    }
934	    else if((Ext_Run_Mode == 0)&&((Pre_Ext_Run_Mode == 1)||(Pre_Ext_Run_Mode == 3)||(Pre_Ext_Run_Mode == 4)))
935	    {
936	        if(Power_On == 1)
937	        {

    else if(Ext_Run_Mode == 4) //스마트 수면 모드 (사양서 8p) : 환기 수동·1단 고정, 실별 CO2 기준 댐퍼 개폐
    {
        if(Pre_Ext_Run_Mode != 4)   /* 진입 1회 : 모드/풍량 + 초기상태(거실 CLOSE, 침실1~3 OPEN) */
        {
            Command_request_type |= (TYPE_MODE|TYPE_FAN_SPEED);

            My_Memory_Run_Mode = Run_Mode;
            My_Memory_Fan_Mode = Fan_Mode;

            Set_Run_Mode = MODE_VENTILATION;
            Set_Fan_Mode = 1;

            Memory_Diffuser_Dmp_Ang_SA[1] = 0;   Memory_Diffuser_Dmp_Ang_RA[1] = 0;   /* 거실 CLOSE */
            for(Room_Num = 2; Room_Num < 5; Room_Num++)                                /* 침실1~3 OPEN */
            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 110; Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 110; }
            Memory_Diffuser_Dmp_Ang_SA[5] = 0; Memory_Diffuser_Dmp_Ang_RA[5] = 0;
            Memory_Diffuser_Dmp_Ang_SA[6] = 0; Memory_Diffuser_Dmp_Ang_RA[6] = 0;

            Pre_Ext_Run_Mode = Ext_Run_Mode;
            Pre_Ext_Select_Room = Ext_Select_Room;
        }

        /* 매 틱 : 실별 CO2 히스테리시스. CO2 >= 1000 OPEN, <= 800 CLOSE, 그 사이(데드존)는 현재 상태 유지 */
        for(Room_Num = 1; Room_Num < 5; Room_Num++)
        {
            if(SEN66_CO2_value[Room_Num] >= 1000)
            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 110; Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 110; }
            else if(SEN66_CO2_value[Room_Num] <= 800)
            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 0;   Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 0; }

            if((Memory_Diffuser_Dmp_Ang_SA[Room_Num] != 0)||(Memory_Diffuser_Dmp_Ang_RA[Room_Num] != 0))
                Diffuser_Air_quality[Room_Num] = 5;//ON
            else
                Diffuser_Air_quality[Room_Num] = 0;//OFF
        }

        Tmp_Air_Volume = 1;   /* 1단 고정 */
        Ext_Run_Mode_Off_Delay = 0;

        goto PASS_VOLUME;
    }
    else if((Ext_Run_Mode == 0)&&((Pre_Ext_Run_Mode == 1)||(Pre_Ext_Run_Mode == 3)||(Pre_Ext_Run_Mode == 4)))
    {
        if(Power_On == 1)
        {


버그를 찾았습니다. 진입 시 초기상태(line 905)는 **거실(room 1)을 올바르게 CLOSE** 하지만, **매 틱 CO2 루프(line 916)가 `Room_Num = 1`부터 돌아서 거실까지 CO2 기준으로 다시 열어버립니다.** 사양상 거실은 CO2와 무관하게 항상 CLOSE여야 하고, CO2 개폐는 침실1~3(room 2~4)에만 적용돼야 합니다.

수정합니다 — 거실은 매 틱 강제 CLOSE, CO2 루프는 침실1~3만:


Edit

        /* 매 틱 : 실별 CO2 히스테리시스. CO2 >= 1000 OPEN, <= 800 CLOSE, 그 사이(데드존)는 현재 상태 유지 */
        for(Room_Num = 1; Room_Num < 5; Room_Num++)
        {
            if(SEN66_CO2_value[Room_Num] >= 1000)
            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 110; Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 110; }
            else if(SEN66_CO2_value[Room_Num] <= 800)
            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 0;   Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 0; }

            if((Memory_Diffuser_Dmp_Ang_SA[Room_Num] != 0)||(Memory_Diffuser_Dmp_Ang_RA[Room_Num] != 0))
                Diffuser_Air_quality[Room_Num] = 5;//ON
            else
                Diffuser_Air_quality[Room_Num] = 0;//OFF
        }

        /* 거실(room1)은 사양상 CO2 무관하게 항상 CLOSE → 매 틱 강제 닫음 */
        Memory_Diffuser_Dmp_Ang_SA[1] = 0; Memory_Diffuser_Dmp_Ang_RA[1] = 0;
        Diffuser_Air_quality[1] = 0;//OFF

        /* 매 틱 : 침실1~3(room2~4) 만 CO2 히스테리시스. CO2 >= 1000 OPEN, <= 800 CLOSE, 그 사이(데드존)는 현재 상태 유지 */
        for(Room_Num = 2; Room_Num < 5; Room_Num++)
        {
            if(SEN66_CO2_value[Room_Num] >= 1000)
            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 110; Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 110; }
            else if(SEN66_CO2_value[Room_Num] <= 800)
            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 0;   Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 0; }

            if((Memory_Diffuser_Dmp_Ang_SA[Room_Num] != 0)||(Memory_Diffuser_Dmp_Ang_RA[Room_Num] != 0))
                Diffuser_Air_quality[Room_Num] = 5;//ON
            else
                Diffuser_Air_quality[Room_Num] = 0;//OFF
        }


The file d:\Project\nuvoton\HECO2\Source\HECO2\User\My_system.c has been updated successfully. (file state is current in your context — no need to Read it back)

        /* 매 틱 : 실별 CO2 히스테리시스. CO2 >= 1000 OPEN, <= 800 CLOSE, 그 사이(데드존)는 현재 상태 유지 */
        for(Room_Num = 1; Room_Num < 5; Room_Num++)
        {
            if(SEN66_CO2_value[Room_Num] >= 1000)
            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 110; Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 110; }
            else if(SEN66_CO2_value[Room_Num] <= 800)
            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 0;   Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 0; }

            if((Memory_Diffuser_Dmp_Ang_SA[Room_Num] != 0)||(Memory_Diffuser_Dmp_Ang_RA[Room_Num] != 0))
                Diffuser_Air_quality[Room_Num] = 5;//ON
            else
                Diffuser_Air_quality[Room_Num] = 0;//OFF
        }

        /* 거실(room1)은 사양상 CO2 무관하게 항상 CLOSE → 매 틱 강제 닫음 */
        Memory_Diffuser_Dmp_Ang_SA[1] = 0; Memory_Diffuser_Dmp_Ang_RA[1] = 0;
        Diffuser_Air_quality[1] = 0;//OFF

        /* 매 틱 : 침실1~3(room2~4) 만 CO2 히스테리시스. CO2 >= 1000 OPEN, <= 800 CLOSE, 그 사이(데드존)는 현재 상태 유지 */
        for(Room_Num = 2; Room_Num < 5; Room_Num++)
        {
            if(SEN66_CO2_value[Room_Num] >= 1000)
            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 110; Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 110; }
            else if(SEN66_CO2_value[Room_Num] <= 800)
            { Memory_Diffuser_Dmp_Ang_SA[Room_Num] = 0;   Memory_Diffuser_Dmp_Ang_RA[Room_Num] = 0; }

            if((Memory_Diffuser_Dmp_Ang_SA[Room_Num] != 0)||(Memory_Diffuser_Dmp_Ang_RA[Room_Num] != 0))
                Diffuser_Air_quality[Room_Num] = 5;//ON
            else
                Diffuser_Air_quality[Room_Num] = 0;//OFF
        }

-        /* 매 틱 : 실별 CO2 히스테리시스. CO2 >= 1000 OPEN, <= 800 CLOSE, 그 사이(데드존)는 현재 상태 유지 */

-        for(Room_Num = 1; Room_Num < 5; Room_Num++)

+        /* 거실(room1)은 사양상 CO2 무관하게 항상 CLOSE → 매 틱 강제 닫음 */

+        Memory_Diffuser_Dmp_Ang_SA[1] = 0; Memory_Diffuser_Dmp_Ang_RA[1] = 0;

+        Diffuser_Air_quality[1] = 0;//OFF

+        /* 매 틱 : 침실1~3(room2~4) 만 CO2 히스테리시스. CO2 >= 1000 OPEN, <= 800 CLOSE, 그 사이(데드존)는 현재 상태 유지 */

+        for(Room_Num = 2; Room_Num < 5; Room_Num++)


Build 확인합니다.


Bash

cd /d/Project/nuvoton/HECO2/Source/HECO2 && bash build.sh all 2>&1 | grep -iE "warning|error|text|HERV.elf"; echo "---done---"

Build firmware after smart sleep fix


[LD]  build/HERV.elf
   text	   data	    bss	    dec	    hex	filename
  43940	   1940	   3388	  49268	   c074	build/HERV.elf
---done---


빌드 성공(경고/오류 0). 작업 기록을 오늘 날짜 문서에 이어서 남기겠습니다.


Edit

## 미해결 / 후속
- 실장비/시뮬레이터에서 **환기→공청 시 내부댐퍼 이동** 확인 필요(특히 고풍량 전환 시 팬 감속 대기시간 체감).
- 검증 후: 필요하면 ①(동작모드 변경 트리거 = MyMotor.c 디퓨저/팬복원 타이밍의 명령 트리거)도 마스터 정렬 검토.
- 풍량단수만 바꿀 때(L306/L219) 팬 즉시 반응 정상인지 확인.

## 미해결 / 후속
- 실장비/시뮬레이터에서 **환기→공청 시 내부댐퍼 이동** 확인 필요(특히 고풍량 전환 시 팬 감속 대기시간 체감).
- 검증 후: 필요하면 ①(동작모드 변경 트리거 = MyMotor.c 디퓨저/팬복원 타이밍의 명령 트리거)도 마스터 정렬 검토.
- 풍량단수만 바꿀 때(L306/L219) 팬 즉시 반응 정상인지 확인.

---

# 260618 (2) 스마트수면 모드 — 거실 댐퍼 미닫힘 수정

## 증상
- 스마트수면 동작 중 **거실 댐퍼가 열려 있음**. 사양(개발사양서 8p)상 거실은 항상 CLOSE여야 함.

## 사양 (개발사양서 8p, 스마트수면)
- 환기 수동·풍량 1단 고정.
- 초기상태: **거실 CLOSE, 침실1~3 OPEN**.
- 거실 급기(SA)/배기(RA) 모두 **X(항상 닫힘)** — CO2 무관.
- 침실1~3: CO2 >= 1000 PPM → 해당 침실 OPEN, CO2 <= 800 PPM → CLOSE.

## 원인
- `My_system.c` `Air_Quality_damper_process()` `Ext_Run_Mode == 4` 블록.
- 진입 1회(L905)는 거실(room1)을 올바르게 CLOSE 하지만, **매 틱 CO2 루프가 `Room_Num = 1`부터 돌아(L916) 거실까지 CO2 기준으로 다시 OPEN** 시킴.
- 룸 인덱스: room1=거실, room2~4=침실1~3.

## 변경 파일/내용
- `Source/HECO2/User/My_system.c` (스마트수면 매 틱 처리)
  - 거실(room1) 매 틱 강제 CLOSE + LED off 추가.
  - CO2 히스테리시스 루프 시작을 `Room_Num = 1` → **`Room_Num = 2`** (침실1~3만 개폐).

## 빌드 결과
- `bash build.sh all` → 성공, 경고/오류 0. text 43940 / data 1940 / bss 3388.

## 후속
- 실장비에서 스마트수면 진입 시 거실 닫힘 + 침실 CO2 개폐 확인.


The file d:\Project\nuvoton\HECO2\doc\260618_내부댐퍼_모드변경시_미동작_수정.md has been updated successfully. (file state is current in your context — no need to Read it back)

## 미해결 / 후속
- 실장비/시뮬레이터에서 **환기→공청 시 내부댐퍼 이동** 확인 필요(특히 고풍량 전환 시 팬 감속 대기시간 체감).
- 검증 후: 필요하면 ①(동작모드 변경 트리거 = MyMotor.c 디퓨저/팬복원 타이밍의 명령 트리거)도 마스터 정렬 검토.
- 풍량단수만 바꿀 때(L306/L219) 팬 즉시 반응 정상인지 확인.

---

# 260618 (2) 스마트수면 모드 — 거실 댐퍼 미닫힘 수정

## 증상
- 스마트수면 동작 중 **거실 댐퍼가 열려 있음**. 사양(개발사양서 8p)상 거실은 항상 CLOSE여야 함.

## 사양 (개발사양서 8p, 스마트수면)
- 환기 수동·풍량 1단 고정.
- 초기상태: **거실 CLOSE, 침실1~3 OPEN**.
- 거실 급기(SA)/배기(RA) 모두 **X(항상 닫힘)** — CO2 무관.
- 침실1~3: CO2 >= 1000 PPM → 해당 침실 OPEN, CO2 <= 800 PPM → CLOSE.

## 원인
- `My_system.c` `Air_Quality_damper_process()` `Ext_Run_Mode == 4` 블록.
- 진입 1회(L905)는 거실(room1)을 올바르게 CLOSE 하지만, **매 틱 CO2 루프가 `Room_Num = 1`부터 돌아(L916) 거실까지 CO2 기준으로 다시 OPEN** 시킴.
- 룸 인덱스: room1=거실, room2~4=침실1~3.

## 변경 파일/내용
- `Source/HECO2/User/My_system.c` (스마트수면 매 틱 처리)
  - 거실(room1) 매 틱 강제 CLOSE + LED off 추가.
  - CO2 히스테리시스 루프 시작을 `Room_Num = 1` → **`Room_Num = 2`** (침실1~3만 개폐).

## 빌드 결과
- `bash build.sh all` → 성공, 경고/오류 0. text 43940 / data 1940 / bss 3388.

## 후속
- 실장비에서 스마트수면 진입 시 거실 닫힘 + 침실 CO2 개폐 확인.

# 260618 내부댐퍼 — 운전모드 변경 시 미동작 수정 (1차: 명령경로 즉시 팬설정 제거)

## 증상
- 환기 → 공청 전환 시 **팬은 정상 동작하는데 내부댐퍼(본체 6개)가 안 움직임.**

## 원인 (마스터 `D:\Project\nuvoton\HERV_DL_MH_2nd\Program` 비교로 확정)
- 내부댐퍼 이동 트리거(`Fan_Speed_process`가 모드변경 시 `Damper_Mode()` 호출)는 HECO2/마스터 **동일하게 존재**. 빠진 게 아님.
- 그 트리거는 **"팬이 0까지 감속"되어야 발동**하도록 게이팅됨(공기 흐름 중 댐퍼 미동작 의도):
  `모드변경 → 팬타깃0 → 팬=0 도달 → Damper_Mode 호출(댐퍼 이동) → 정렬(Step_Status==0x3F) → 팬 복원`
- **마스터:** 명령 핸들러가 팬 타깃을 안 건드림 → 팬이 0 도달 → 댐퍼 이동.
- **HECO2:** 명령 핸들러가 모드변경 즉시 `Fan_Speed_Setting()` 직접 호출 → 팬 타깃이 곧바로 운전속도로 올라감 → **팬이 0에 도달 못 함 → `if(Fan!=0) if(wait==5) goto PASS1` 게이트가 `Damper_Mode` 영원히 스킵 → 댐퍼 미동작, 팬만 정상.**
- 마스터엔 없고 HECO2에만 추가된 명령경로 `Fan_Speed_Setting` 호출이 게이트를 막은 것.

## 변경 파일/내용 (1차 = 모드 변경 시 호출만 제거, 풍량단수 변경 호출은 유지)
주석 처리(삭제 아님, 복원 용이). 마커: `[모드변경댐퍼테스트]`
- `Source/HECO2/User/My_Homenet.c`
  - L259 CTRL_POWER(전원ON→환기) `Fan_Speed_Setting` 주석
  - L293 CTRL_RUNMODE(운전모드 변경) `Fan_Speed_Setting` 주석  ← 보고 증상(대시보드 환기→공청)의 직접 경로
  - **L306 CTRL_FAN(풍량단수만 변경)은 유지** (댐퍼 무관, 팬 단수 반응 필요)
- `Source/HECO2/User/My_Hood.c`
  - L192 후드ON(→환기) `Fan_Speed_Setting` 주석
  - L207 후드OFF(모드 복귀) `Fan_Speed_Setting` 주석
  - **L219 후드 단수변경(풍량만)은 유지**

## 핵심 결정
- 모드 변경 후 팬 복원은 `Fan_Speed_process`의 wait==1 호출(MyMotor.c 1018/1056/1090/1124, ③)이 담당 → 팬이 꺼진 채 남지 않음.
- RJ2(`My_RJ2.c`)·분배기(`My_bunbaegi.c`)는 두 소스 동일 → 미수정.
- 이번엔 "동작모드 변경 트리거 복원(①, 디퓨저 시퀀스 명령트리거)"은 보류 — ②(땜빵) 제거만으로 댐퍼가 움직이는지 먼저 검증.

## 빌드 결과
- `cd SOURCE/HECO2 && bash build.sh all` → 성공, 경고/오류 0.
- text 43920 / data 1940 / bss 3388 (HERV.elf). (이전 44008 → 코드 제거로 감소)

## 미해결 / 후속
- 실장비/시뮬레이터에서 **환기→공청 시 내부댐퍼 이동** 확인 필요(특히 고풍량 전환 시 팬 감속 대기시간 체감).
- 검증 후: 필요하면 ①(동작모드 변경 트리거 = MyMotor.c 디퓨저/팬복원 타이밍의 명령 트리거)도 마스터 정렬 검토.
- 풍량단수만 바꿀 때(L306/L219) 팬 즉시 반응 정상인지 확인.

+# 260618 (2) 스마트수면 모드 — 거실 댐퍼 미닫힘 수정

+- 스마트수면 동작 중 **거실 댐퍼가 열려 있음**. 사양(개발사양서 8p)상 거실은 항상 CLOSE여야 함.

+## 사양 (개발사양서 8p, 스마트수면)

+- 환기 수동·풍량 1단 고정.

+- 거실 급기(SA)/배기(RA) 모두 **X(항상 닫힘)** — CO2 무관.

+- 침실1~3: CO2 >= 1000 PPM → 해당 침실 OPEN, CO2 <= 800 PPM → CLOSE.

+- `My_system.c` `Air_Quality_damper_process()` `Ext_Run_Mode == 4` 블록.

+- 진입 1회(L905)는 거실(room1)을 올바르게 CLOSE 하지만, **매 틱 CO2 루프가 `Room_Num = 1`부터 돌아(L916) 거실까지 CO2 기준으로 다시 OPEN** 시킴.

+## 변경 파일/내용

+- `Source/HECO2/User/My_system.c` (스마트수면 매 틱 처리)

+  - 거실(room1) 매 틱 강제 CLOSE + LED off 추가.

+  - CO2 히스테리시스 루프 시작을 `Room_Num = 1` → **`Room_Num = 2`** (침실1~3만 개폐).

+- `bash build.sh all` → 성공, 경고/오류 0. text 43940 / data 1940 / bss 3388.

+- 실장비에서 스마트수면 진입 시 거실 닫힘 + 침실 CO2 개폐 확인.