Category (Click)
개발보드 덕질하기

[Rack Out Of Dust] 3. 펌웨어 구현 및 Z2M 연결

 차완기 - @4/28/2024, 9:50:00 PM
Rack Out Of Dust 프로젝트
시끄러워 발코니에 격리한 허브랙에 공기청정기 환기 시스템을 달아주는, 그야말로 병주고 약주고죠
#HW #3D Design #3D Printing #PCB Design #FW #nRF Connect SDK #ZigBee #HomeAssistant #Zigbee2MQTT
PCB 설계 및 완성 TODO

회로 프로토타이핑

nRF52840 DK 개발보드에 앞서 선정했던 센서와 모듈들을 연결했습니다.
센서 중 온습도 센서는 선정했던 SHT45가 품절로 취소되는 바람에(...) 이전에 사용했던 AM2320센서를 사용했습니다.
이외에도 상태 표시용 LED와 지그비 리셋용 스위치를 하나씩 달아주었습니다.
사실 포스팅을 작성하기 전에 사진을 한번 찍었는데, 너무 더러워서(?) 새로 배선한게 위 사진입니다 ㅋㅋㅋ.. 새로 찍은것도 그다지 깔끔하지 않기는 하네요.
드러운 배선

디바이스트리 작성

nRF Connect SDK는 Zephyr를 기반으로 하고 있습니다.
Zephyr는 linux에서 사용하는 디바이스트리를 채용해 SoC 내부의 peripherals를 관리하는데요, 이때문에 펌웨어 작성에 앞서서 우선 디바이스트리를 작성해 주어야 했습니다.
디바이스트리를 작성하기 위해 SoC에서 사용하는 peripherals와 연결된 포트를 정리했습니다.
pwm0
ch0: P0.04 - 쿨링팬 PWM 소스 (25 KHz freq)
ch1: P0.30 - 상태 PWM LED (주파수 상관없음 → ch0 따라감)
GPIO
P0.03: 쿨링팬 타코미터 펄스 입력
P0.31: 버튼 입력
UART0: 디버깅 로그 출력
UART1: PMS7003 (Zephyr 드라이버 사용)
I2C0: AM2320 (0x5c, 커스텀 드라이버 사용), SHT45 (0x44, Zephyr 드라이버 사용)
IEEE 802.15.4: ZigBee 드라이버
Nordic의 가이드를 참고해 커스텀 보드를 생성한 후 DTS를 작성했습니다.
(DTS 펼쳐서 열기)
오랜만에 nRF Connect SDK VSCode Extension을 켰더니 peripherals 뷰도 생겼네요. experimental 딱지도 떨어진 것을 보니 이제 정식적으로 지원을 하는가 봅니다.

펌웨어 작성

크게 app, hw, mw로 나누어 펌웨어를 구성하였습니다.
STM32에서 항상 베어메탈로 코드를 작성하다가 RTOS가 달리고 네트워크가 달리고 하다 보니 어떻게 구조를 가져가야 할지 항상 고민하고는 했는데요, 이제는 조금 정리가 된 것 같습니다.
App: 기능 구현을 위한 코드
Task: RTOS Task (Thread)
Handler: HW나 MW에서 발생하는 이벤트 처리
HW: 하드웨어 관련 코드(센서 및 모듈 드라이버 등)
MW: 미들웨어 관련 코드(ZigBee, Wi-Fi, BLE 등)

Main Thread → ZigBee

Main Thread에서는 일정 시간마다 모든 센서 데이터를 읽어 ZigBee로 데이터를 던져주도록 했습니다. 이를 위해 ZigBee 모듈의 인터페이스 함수를 만들어주었습니다.
main/mw/zigbee/zigbee.h
인터페이스 함수 zigbee_fz_set_callback()는 enum 형태의 데이터 flag와 실제 센서 데이터 두 가지를 입력받습니다. 센서 데이터는 포인터로 받는게 이상적이지만, 가장 크기가 큰 데이터가 32 bit(float)라 변수를 복사하도록 했습니다.
main/app/task/main_thread/main_thread.c
Main Thread의 일부는 위와 같습니다. 센서 데이터를 읽고 ZigBee로 데이터를 던져주죠.

ZigBee → ZigBee Event Handler

만약 ZigBee의 특정 Cluster의 Attribute를 변경하게 되면 ZigBee MW에서 Event를 발생시키고 Handler를 호출합니다.
main/mw/zigbee/zigbee.h
역시나 ZigBee 모듈에 Handler 등록을 위한 인터페이스 함수를 만들고,
main/app/handler/zigbee_fz_handler/zigbee_fz_handler.c
ZigBee Handler 모듈 Init 과정에서 Handler 함수의 포인터를 전달합니다.
main/app/handler/zigbee_fz_handler/zigbee_fz_handler.c
그 후 Event가 발생하면 위와 같이 처리합니다.

ZigBee 리셋 버튼

순전히 취미용이기 때문에 네트워크 탈퇴는 Flash를 밀어버리면 되지만..그래도 구현해봤습니다.
main/hw/driver/zbrstbtn/zbrstbtn.c
버튼을 하나 만들어 인터럽트를 등록한 후, 클릭한 횟수를 카운트 해 마지막 버튼 입력으로부터 0.5초 이상 경과하면 콜백을 실행하도록 했습니다.
main/app/handler/reset_button_handler/reset_button_handler.c
ZigBee 네트워크 탈퇴를 위한 zigbee_reset() 함수는 ISR에서 사용이 불가능해 System WorkQueue에서 실행해주었습니다.
main/mw/zigbee/zigbee.c
Nordic에 따르면 ZigBee 리셋은 Flash를 밀어버리는 방법보다는 zb_bdb_reset_via_local_action() 함수를 사용하는것을 추천한다고 합니다.

Zigbee2MQTT 컨버터 작성

이 상태로 디바이스를 Z2M에 추가하게 되면 등록되지 않은 장치라는 오류가 발생합니다.
Z2M의 가이드와 zigbee-herdsman-converters의 여러 레퍼런스를 참조해 장치 정의 파일을 만들었습니다.

연결 및 테스트

완성까지 얼마 남지 않았네요..!
PCB 제작이 완료되면 돌아오겠습니다