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

[nRF52840, Zephyr] nRF Connect SDK 입문, AM2320 I2C 읽어보기 (blocking)

 차완기 - @2/4/2024, 5:03:00 PM
“My First nRF52840 Project”
nRF Connect SDK에 입문하며 공부 겸 진행해보는 간단한 프로젝트입니다. AM2320 온습도센서를 붙여 Home Assistant에 Z2M으로 연결하는것이 목표입니다.
잘못된 내용이 있을 확률이 매우 높으니 주의해주시고, 부디 둥근 지적 부탁드립니다
#FW #nRF Connect SDK #HomeAssistant #Z2M #ZigBee

이게... Nordic??

nRF52840 DK를 구매하기는 했는데, 무엇부터 해야할지 감이 잡히지 않아 검색하던 중 DevAcademy를 찾게 되었습니다.
Nordic에서 공식적으로 만든 가이드라기에 FreeRTOSArm CMSIS의 Docs 정도를 기대하고 들어갔는데..
DevAcademy - nRF Connect SDK Fundamentals 중 일부 [링크]
오이잉??
아주 깔끔한 페이지가 열러 깜짝 놀랐습니다.
아니아니.. 깔끔한 UI는 페이크이고 내용이 부실할 수도 있죠. 조금 더 읽어보았는데요,
DevAcademy - nRF Connect SDK Fundamentals 중 일부 [링크]
퀴...즈...?
Nordic에서 공식적으로 만든 강의 코스였습니다. 맙소사..
덕분에 쉽게쉽게 첫 코드를 작성해볼 수 있었습니다.

프로젝트 on

튜토리얼을 통해 LED 켜보고 스위치 입력받았으면 기본은 다 했다고 보고(?) 자세한건 삽질을 하며 배우기로 했습니다. 떠먹여주는건 재미가 없죠 ㅋ
작년 즈음 ESP32-C6를 이용해 ZigBee로 Z2M(Home Assistant)에 연결해 보았는데요, 동일하게 ZigBee를 지원하는 SoC인 만큼 비슷한 프로젝트를 해보면 재미있겠다는 생각이 들었습니다.
고로 “ZigBee를 이용해 Home Assistant Z2M에 달라붙는 IoT 디바이스 만들기”를 진행해보기로 했습니다.

부품 선정

나름 프로젝트이니 부품을 정해야겠죠.
마침 DevAcademy의 I2C 파트를 읽던 차라 I2C를 지원하는 센서, 그 중에서도 당장 눈앞에서 발견된 AM2320를 써먹어보기로 했습니다.
개발보드: nRF52840 DK
센서: AM2320
간단한 센서이니 이정도면 충분하겠죠.

AM2320 읽기

RTOS를 사용한다면 DMA와 인터럽트를 활용해 비동기적으로 데이터를 읽어내는게 효율적이죠. 그러나, 아직 I2C를 써보지 않았기에 그런 부분은 모두 무시하고 우선 읽기만 해보기로 했습니다.

Device Tree 세팅

nRF Connect SDK의 기반이 되는 Zephyr(제퍼)를 알아보며 가장 신기했던 개념이 바로 Device Tree였습니다. Linux의 그것과 유사한 Device Tree를 이용해 SoC(MCU)와의 의존성을 SDK 차원에서 죽여버리는 것이였죠.
엥?? DeviceTree같은건 모르는데요?
그런 당신과 저을 위해 준비했습니다. Devicetree Visual Editor!!
STM의 CubeMX와 유사한 GUI 에디터를 제공하고 있었죠. 이 에디터를 이용해 하드웨어를 정의하는 dts 파일을 수정하거나, 프로젝트에 따라 디바이스트리를 덮어쓰도록 도와주는 오버레이를 편집할 수 있었습니다.
물론 위 사진에서 열려있는건 SDK에서 제공하는 보드 파일이라 수정하게 되면 모든 프로젝트에 전역적으로 반영되어 영좋지 못한 일이 발생할 수 있습니다. 따라서 이를 덮어쓰는 오버레이 파일을 만들어야 했습니다.
빌드 과정에서 프로젝트 최상위 폴더의 *.overlay 파일을 참조한다고 하니 “nrf52840dk_nrf52840.overlay” 라는 이름으로 디바이스트리 오버레이 파일을 만들어준 후 GUI 에디터로 열었습니다.
&arduino_i2c { am2320: am2320@5c { compatible = "i2c-device"; reg = <0x5c>; label = "AM2320"; status = "okay"; }; };
Plain Text
복사
위와 같이 디바이스를 추가했습니다.

Kconfig (prj.conf)

아래 설정을 해주었습니다.
CONFIG_LOG: 로깅
CONFIG_I2C: SDK I2C 드라이버
CONFIG_FPU: 부동소수점 출력 (log)
CONFIG_LOG=y CONFIG_I2C=y CONFIG_FPU=y
Plain Text
복사

드라이버 코드 작성

여기서 이야기하는 드라이버는 펌웨어 계층화를 위한 센서의 드라이버입니다. SDK에서 제공하는 peripheral 드라이버와 이름이 겹치는데, 다른 어떤 이름으로 부르는게 좋을까 고민중입니다
데이터시트와 Adafruit의 아두이노 예제를 참고해 코드를 작성했습니다.
헤더 파일은 이렇습니다.
main/hw/driver/am2320/am2320.h
딱히 뭔가 설정하고 할 거리가 없음으로 init, read 두 가지 함수를 만들었습니다.

bool am2320_init(void) : 센서 init

main/hw/driver/am2320/am2320.c 중 일부
우선 앞에서 Device Tree Overlay로 am2320 라벨을 사용했으니, 위와 같이 DT 구조체를 받아왔습니다.
main/hw/driver/am2320/am2320.c 중 일부
init 함수에서는 디바이스 트리의 유효성 정도만 검사하도록 해두었습니다. 나중에 시간이 되면 모델 정보를 가져와 센서의 유효성을 검사하도록 해보려 합니다.

bool am2320_read(float *, float *) : 온습도 데이터 취득

AM2320 센서는 기본적으로 절전 모드를 유지합니다. 따라서 센서를 읽기 전, 센서를 깨워줘야 하는데요, 이를 위해 쓰기 요청을 전송해야 합니다.
AM2320 Datasheet 일부 [링크]
데이터시트 상에서는 시작 비트 + 7-bit 주소 + W 를 보낸 후 0.8 ms 뒤에 정지 신호를 전송해라 하는데요, 제가 못찾은건지 Zephyr I2C 드라이버 API에서는 이걸 해결할 수 있는 방법이 없는것 같았습니다.
main/hw/driver/am2320/am2320.c 중 일부
대신 빈 데이터 하나를 전송하고 1 ms를 기다려주게 하였습니다.
ACK가 돌아오지 않기 때문에 이 과정에서 오류가 발생합니다.
센서를 깨웠으면, 데이터를 읽어야죠.
AM2320 Datasheet 일부 [링크]
데이터시트에 따르면 센서에서 데이터를 읽어오기 위해서는 7-bit 주소 + W 비트 + 0x03 + 읽기 시작 레지스터 주소 + 길이 로 데이터를 던져주면 된다고 합니다.
앞쪽의 7-bit 주소 + W 비트 부분은 I2C 드라이버에서 모두 해주니 뒷 부분만 해주면 되겠죠.
AM2320 Datasheet 일부 [링크]
읽어야할 온도, 습도 데이터는 레지스터 주소로 0x00~0x03으로, 총 4bit입니다. 따라서 아래와 같이 데이터를 전송하도록 했습니다.
0x03 0x00 0x04
main/hw/driver/am2320/am2320.c 중 일부
AM2320 Datasheet 일부 [링크]
데이터를 전송한 후에는 1.5 ms 이상 대기한 후 다시 읽어야 합니다.
main/hw/driver/am2320/am2320.c 중 일부
AM2320 Datasheet 일부 [링크]
이때 반환되는 데이터는 명령 종류인 읽기 코드(0x03) + 길이(0x04) + 읽은 데이터(len=4) + CRC low + CRC high 순입니다.
main/hw/driver/am2320/am2320.c 중 일부
main/hw/driver/am2320/am2320.c 중 일부
데이터시트의 예시대로 CRC 체크 코드를 작성하고,
반으로 갈라진 온도, 습도 데이터를 비트 연산해 float 타입으로 이어붙여 함수 호출 과정에서 입력받는 데이터 버퍼에 넣어주도록 했습니다.
전체 소스코드는 위 리포지토리에서 확인해보세요.

테스트

온도, 습도가 잘 출력됩니다.
앞에서도 언급했듯이 센서를 깨우는 과정에서 오류가 발생하는데요, Kconfig의 CONFIG_I2C_LOG_LEVEL_OFF를 켜주면 오류가 무시되기는 합니다.

마무리

이번 포스팅에서는 SDK의 I2C 드라이버를 이용해 I2C 온습도 센서인 AM2320을 사용해 보았습니다.
데이터를 읽는 Thread를 만들어 Non-Blocking 방식으로 데이터를 읽어보고 본격적으로 ZigBee를 사용해보겠습니다.