2024년 11월 21일 목요일

Zephyr RTOS Programming

  지난 시간에 이어 Zephyr RTOS programming 얘기를 계속 이어가 보도록 하겠다. 특별히 이번 시간의 소주제는 BLE(Bluetooth Low Energy)이다. 😎

 

목차

5. Bluetooth Low Energy 이야기
6. ESP32 Board 기반의 BLE 프로그래밍
7. Gl-iNet Thread Dev Board 기반의 BLE 프로그래밍
8. nRF52840 USB dongle 기반의 BLE 프로그래밍
10. References



필자가 Bluetooth를 처음 접한 것은 아주 오래 전에 Android Smart Phone을 개발하던 시절인데, 그때 Bluetooth의 버젼은 2.1 EDR 이었다. 그 이후로 Bluetooth는 저전력을 모토로하는 Bluetooth Low Energy라는 전혀 다른 형태의 protocol로 발전하였고, 현재는 BLE Audio, BLE Mesh로까지 진화/발전한 상태((5.x version)이다. 경쟁 프로토콜로 ZigBee, Z-Wave, Thread 등이 있지만, 왜 BLE가 이들 기술에 비해 시장에서 우위를 차지하고 있는지, 이번 posting을 통해 낱낱이(?) 파헤쳐 보도록 하자.


5. Bluetooth Low Energy 이야기
이번 장에서는 Bluetooth Low Energy protocol에 관한 얘기를 해 볼까 한다. Bluetooth 스펙은 책 1-2권으로 정리되어야 할 만큼, 방대한 내용이 담겨 있다. 따라서 여기에서 모든 내용을 언급하는 것은 사실상 불가능하다(모든 내용을 알지도 못한다 😥). 따라서, 이번 posting에서는 Bluetooth Low Energy와 관련하여 zephyr programming을 하려는 개발자의 관점에서 반드시 숙지해야 할 내용(예를 들어, GAP와 GATT 프로파일의 개념)을 중심으로 간략히 짚고 넘어가 보고자 한다. 💢


📜 Key points 📜
1) BLE stack의 구조(구성 요소)
    - Application + Host(GAP/GATT/SM/L2CAP) + Controller(Link Layer + Physical layer)
2) RF(Radio Frequency)
3) 통신 방식
    - Connectionless 방식 - Broadcaster & Observer
    - Connection oriented 방식 - Central(Client) & Peripheral(Server)
4) Packet format
    - Advertising packet
    - Data packet
5) GAP, GATT(ATT), SM의 이해
    - GAP : BLE 기기간의 role(역할) 결정
    - GATT : connection된 기기간의 정보(Attribute) 교환(Client & Server 개념으로 동작)
    - SM : 안전한 암호통신(인증, 키교환, 암호 통신)


📜 BLE Stack 📜
BLE Stack은 아래와 같이 생겼다. 이중 Controller는 Link Layer와 물리적인 PHY로 구성된 계층이고, Host는 L2CAP, SM, ATT, GATT, GAP로 구성되어 있는 있는 s/w stack(or framework) 이다. 마지막으로 Apps(Application)은 BLE stack을 이용하여 작성한 사용자 프로그램을 말한다.


[그림 5.1] Bluetooth Low Energy Stack [출처 - 참고문헌 2]

📜 BLE H/W 구성 📜

BLE H/W(chip or module)를 사용하는 방식은 아래와 같이 크게 3가지 경우를 생각해 볼 수 있다.
  • Host 장치(Host stack 포함)에 module(Controller만 포함) 형태로 탑재
    • 예#1) Embedded Linux BlueZ Stack + HCI over UART or USB + BLE h/w module
    • 예#2)Android smart phone에 장착된 BLE h/w module
  • SoC 내에 BLE chip이 포함된 형태
    • 예#1) ESP32 board
    • 예#2) Zephyr가 탑재된 nRF52840 USB dongle
  • Host 장치(BLE stack 미 포함)에 SoC 형태의 BLE 모듈을 장착한 형태
    • 예#1) Linux + USB BLE dongle : AT 명령어로 BLE dongle 제어

[그림 5.2] Bluetooth H/W 구성 3가지 예 [출처 - 참고문헌 4]

📜 BLE RF 📜

BLE radio hardware는 ISM band 2.4 Ghz 스펙트럼에서 동작하도록 설계되어 있다. 이는 아래 그림에서 보는 것 처럼, 다시 2Mhz 단위로 세분화된 총 40개의 채널로 구성되어 있다. 40개의 채널 중, Primary advertising(방송) channel은 37, 38, 39번으로 이는 Wi-Fi에서 자주 사용되는 Ch 1, 6, 11과의 충돌을 피하기 위해서 선정되었다. 나머지 채널은 data 전송 용으로 사용된다(단, 37, 38, 39 채널을 통한 방송이 실패할 경우에는 나머지 채널이 사용될 수도 있음).
  • 40 band,  각각 2Mhz
    • 3개의 advertising channel, 37개의 data channel

[그림 5.3] BLE RF Channels [출처 - 참고문헌 2]

📜 Bluetooth 패킷 포맷 📜

BLE 기기간 무선 통신을 하기 위해서는 packet format이 정해져야 한다. BLE packet format을 하나로 압축해서 설명하는 것은 쉽지가 않다. (많은 추가 설명이 필요하지만) 개념적으로 이해하기 쉽도록 몇 개의 그림으로 요약해 보았으니, 한번 훑어 보시기 바란다. 💢

[그림 5.4] BLE packet format #1 [출처 - 참고문헌 2]

[그림 5.5] BLE packet format #2 [출처 - 참고문헌 6]


[그림 5.6] BLE Advertising packet format [출처 - 참고문헌 2]
📌 무선이든, 유선이든 통신 시에는 반드시 패킷 포맷이 있게 마련이고, 개발을 위해서는 이를 제대로 숙지할 필요가 있다.


📜 BLE 통신 방식 및 기기간 Role 결정 📜

Bluetooth 통신을 가만히 생각해 보면, 대략 아래와 같은 형태로 요약해 볼 수 있을 듯하다. (독자 여러분이) 자주 사용하는 Bluetooth 기기를 그동안 어떻게 사용했었는지 머릿속에 떠올려 보라~ 🌀


<Case #1>
1) 특정 Bluetooth 기기의 전원을 켠다. Bluetooth 기기는 전원이 켜진 후, 자신을 알리는 패킷을 내보낸다(Advertising).
2) 또 다른 bluetooth 기기에서는 Scan 버튼을 선택하여 현재 살아있는(advertising packet을 보내고 있는) bluetooth 기기를 알아낸다(Scanning).
3) 이후, (bluetooth packet 내용을 보고, 연결 가능한 경우라면) 2단계에서 찾은 bluetooth 기기 중 하나를 선택하여 연결을 시도한다(Connecting).
   --> 상호간의 role 결정(GAP).
4) 연결이 되고 나면, 해당 bluetooth 기기의 정보를 가져와 (화면 출력이 가능한 경우) 화면에 출력한다(GATT).
5) 이후, 해당 bluetooth 기기의 속성을 변경하거나, 특정 명령을 내린다(GATT).
   --> 상호간의 연결 후, 속성 data 송수신(client/server)
   --> Connection 상황, Central(client) & Peripheral(server), Bi-directional data transfer

그런데, Bluetooth 통신이 반드시 위와 같이 진행되는 것만은 아니다. 가령, 온/습도 센서 정보를 주기적으로 확인하고 싶은 경우를 예로 들어 보자.

<Case #2>
1) 온/습도 센서는 자신의 정보를 주기적으로 broadcast 한다(Broadcaster).
   --> 주의할 점은 이 경우는 중앙의 다른 장치로 부터의 연결을 허용하지 않는다는 것이다(Broadcaster의 대표적인 예: Beacon).
2) 다른 bluetooth 장치는 방송된 정보를 읽어서 온/습도 정보를 추출한다(Observer).
   --> Connectionless 상황, Broadcaster & Observer, Uni-directional data transfer



[그림 5.7] BLE Connectionless 통신 - Broadcaster and Observer [출처 - 참고문헌 4]
📌 Broadcaster의 대표적인 예로, Apple의 iBeacon과 Google의 Eddystone 등이 있다.


[그림 5.8] BLE Connection 통신 - Central and Peripheral [출처 - 참고문헌 4]

📌 BLE 기기 간에 4가지 역할을 결정하는데 사용되는 것이 GAP이다.

[그림 5.9] BLE Central and Peripheral role [출처 - 참고문헌 2]
📌 어떤 BLE 기기는 Central의 역할을 수행하다가, Peripheral의 역할을 수행하기도 한다.

📜 BLE Profiles 📜

GAP(Generic Access Profile)
GAP는 bluetooth 장치에서 장치 검색, 연결(connection), 광고(advertising) 등과 같은 서비스를 담당하는 프로파일로, 아래와 같이 Broadcaster, Observer, Peripheral, Central로 표현되는 device의 역할을 결정지어 준다.


[그림 5.10] GAP role과 Link Layer의 상호 작용  [출처 - 참고문헌 2]
📌 GAP에서 내려주는 role에 따라 Link Layer의 동작 방식(State Machine)이 결정된다. 

ATT(Attribute Protocol) and GATT(Generic Attribute Profile)
GATT는 ATT(client & server protocol)를 이용하여 bluetooth 장치 간에 데이터를 전송하는 방식을 결정하는 (일종의) 서비스 프레임워크이다. GATT에서 정의하는 data type을 특별히 Services, Characteristics, 그리고 Descriptors 라고 부른다.

[그림 5.11] ATT client & server protocol의 예  [출처 - 참고문헌 5]

GATT가 정의하는 Services, Characteristics, Descriptors를 그림(예제 포함)으로 표현하면 다음과 같다.


[그림 5.12] GATT Services, Characteristics and Descriptors  [출처 - 참고문헌 5]


[그림 5.13] GATT Services, Characteristics and Descriptors 예 [출처 - 참고문헌 5]
📌 GATT의 Services, Characteristics, Descriptors 부분의 개념을 파악하는 것이 매우 중요하다.


다시 말하지만, GATT는 ATT(client & server protocol)를 이용하여 bluetooth 장치 간에 데이터를 전송하는 방식을 말한다.
[그림 5.14] GATT client & server [출처 - 참고문헌 7]

마지막으로, BLE는 기기간 통신시 보안성(인증, 키 교환, 암호통신 관련)을 위해 Security Manager Protocol을 제공한다. BLE(v4.2 이후 버젼)는 키 교환을 위해서는 ECDH(Elliptic-curve Diffie-Hellman) 방식을 사용하며, 이를 통해 얻은 symmetric key와 128-bit AES 알고리즘을 이용하여 암호 통신을 한다.


[그림 5.15] BLE Security Manager Protocol Pairing & Bonding [출처 - 참고문헌 2]
📌 Central device와 Peripheral device간의 연결 상태를 Connection(Phase 1)이라고 하며, Pairing(Phase 2)은 ECDH 기반의 key 교환 및 암호 통신과정을 의미한다. 마지막으로 Bonding(Phase 3)은 optional phase이긴 한데, re-pairing 과정 없이 바로 연결하여 사용하기 위해, (pairing된 상태에서)암호에 사용할 키를 미리 교환하는 단계를 말한다.

_________________
이 밖에도 BLE 5.x에는 BLE Audio, BLE Mesh 등 새로운 profile이 추가되었다. 

[그림 5.16] BLE Audio Profiles [출처 - 참고문헌 19]
📌 LE Audio가 나오기 전까지는 Bluetooth classic version에서 이를 처리했다. LE Audio가 새로 정의되었다는 것은 그만큼 Bluetooth가 audio 용(예: headset)으로 많이 사용된다는 것을 말해준다.

[그림 5.17] BLE Audio Profiles [출처 - 참고문헌 19]


[그림 5.18] BLE Mesh Profiles [출처 - 참고문헌 20]
📌 Mesh는 ZigBee, Thread 등에서는 기본적으로 지원하는 사항인데, BLE도 이 시장(?)에 뛰어들겠다는 얘기다. 💣

_____________________
지금까지, 수박 겉 핥기 식으로 BLE의 이모저모를 확인해 보았다. 뭔가 구체적인 내용이 빠져 있는 느낌이지만, 그럼에도 불구하고 추후 BLE를 다시 분석할 상황이 올 경우, (지금의 분석이) 많은 도움이 될 것으로 믿어 의심치 않는다. 😂 

보다 자세한 내용이 필요한 분들은, Bluetooth 스펙 문서나 아래 도서를 참조해 주기 바란다.



[그림 5.19] BLE 추천도서 😍
📌 물론 필자는 이 책의 저자하고는 개인적으로 아무런 관계가 없다. ㅋ 


6. ESP32 Board 기반의 BLE 프로그래밍
집에 굴러다니는(?) ESP32 보드가 몇 개 보인다. 이번 장에서는 (맛뵈기 차원에서) ESP32 board를 가지고, Zephyr RTOS 기반의 BLE 예제를 돌려 보기로 하자. 😛

📌 아, 물론 ESP32 board는 Espressif의 ESP-IDF 환경에서 얼마든지 BLE programming이 가능하지만, 이번 시간에는 Zephyr가 그 역할을 대신할 것이다. 둘 중 어떤게 더 좋을까 ?

[그림 6.1] ESP32-WROOM-32D 보드 [출처 - 참고문헌 8]

[그림 6.2] ESP32-WROOM-32D 보드 pinout [출처 - 참고문헌 8]

별도로 download 가능한 회로도(HiLetgo 사에서 제작) 파일 대신 캡쳐된 사진이 있어, 여기에 첨부해 본다(위의 ESP32 보드는 HiLetgo 사에서 제작한 것을 구매한 것임).

[그림 6.3] ESP32-WROOM-32D 보드 schematic [출처 - 참고문헌 8]


<BLE 관련 esp32 library download 하기>
chyi@earth:~/zephyrproject/zephyr$ west blobs fetch hal_espressif
Fetching blob hal_espressif: /home/chyi/zephyrproject/modules/hal/espressif/zephyr/blobs/lib/esp32c3/libbtdm_app.a
Fetching blob hal_espressif: /home/chyi/zephyrproject/modules/hal/espressif/zephyr/blobs/lib/esp32s3/libbtdm_app.a
Fetching blob hal_espressif: /home/chyi/zephyrproject/modules/hal/espressif/zephyr/blobs/lib/esp32/libbtdm_app.a
Fetching blob hal_espressif: /home/chyi/zephyrproject/modules/hal/espressif/zephyr/blobs/lib/esp32/libsmartconfig.a
Fetching blob hal_espressif: /home/chyi/zephyrproject/modules/hal/espressif/zephyr/blobs/lib/esp32/libmesh.a
Fetching blob hal_espressif: 
...

6.1 Eddystone beacon 예제
chyi@earth:~/zephyrproject/zephyr$ west build -b esp32_devkitc_wroom/esp32/procpu samples/bluetooth/beacon --pristine
-- west build: making build dir /home/chyi/zephyrproject/zephyr/build pristine
-- west build: generating a build system
Loading Zephyr default modules (Zephyr base).
-- Application: /home/chyi/zephyrproject/zephyr/samples/bluetooth/beacon
-- CMake version: 3.22.1
-- Found Python3: /usr/bin/python3 (found suitable version "3.10.12", minimum required is "3.8") found components: Interpreter 
-- Cache files will be written to: /home/chyi/.cache/zephyr
-- Zephyr version: 3.7.0-rc2 (/home/chyi/zephyrproject/zephyr)
-- Found west (found suitable version "1.2.0", minimum required is "0.14.0")
-- Board: esp32_devkitc_wroom, qualifiers: esp32/procpu
-- ZEPHYR_TOOLCHAIN_VARIANT not set, trying to locate Zephyr SDK
-- Found host-tools: zephyr 0.16.8 (/home/chyi/zephyr-sdk-0.16.8)
-- Found toolchain: zephyr 0.16.8 (/home/chyi/zephyr-sdk-0.16.8)
-- Found Dtc: /home/chyi/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/bin/dtc (found suitable version "1.6.0", minimum required is "1.4.6") 
-- Found BOARD.dts: /home/chyi/zephyrproject/zephyr/boards/espressif/esp32_devkitc_wroom/esp32_devkitc_wroom_procpu.dts
-- Generated zephyr.dts: /home/chyi/zephyrproject/zephyr/build/zephyr/zephyr.dts
-- Generated devicetree_generated.h: /home/chyi/zephyrproject/zephyr/build/zephyr/include/generated/zephyr/devicetree_generated.h
-- Including generated dts.cmake file: /home/chyi/zephyrproject/zephyr/build/zephyr/dts.cmake
Parsing /home/chyi/zephyrproject/zephyr/Kconfig
Loaded configuration '/home/chyi/zephyrproject/zephyr/boards/espressif/esp32_devkitc_wroom/esp32_devkitc_wroom_procpu_defconfig'
Merged configuration '/home/chyi/zephyrproject/zephyr/samples/bluetooth/beacon/prj.conf'
Configuration saved to '/home/chyi/zephyrproject/zephyr/build/zephyr/.config'
Kconfig header saved to '/home/chyi/zephyrproject/zephyr/build/zephyr/include/generated/zephyr/autoconf.h'
-- Found GnuLd: /home/chyi/zephyr-sdk-0.16.8/xtensa-espressif_esp32_zephyr-elf/xtensa-espressif_esp32_zephyr-elf/bin/ld.bfd (found version "2.38") 
-- The C compiler identification is GNU 12.2.0
-- The CXX compiler identification is GNU 12.2.0
-- The ASM compiler identification is GNU
-- Found assembler: /home/chyi/zephyr-sdk-0.16.8/xtensa-espressif_esp32_zephyr-elf/bin/xtensa-espressif_esp32_zephyr-elf-gcc
esptool path: /home/chyi/zephyrproject/modules/hal/espressif/tools/esptool_py/esptool.py
-- Using ccache: /usr/bin/ccache
-- Configuring done
-- Generating done
-- Build files have been written to: /home/chyi/zephyrproject/zephyr/build
-- west build: building application
[3/239] Preparing syscall dependency handling

[4/239] Generating include/generated/zephyr/version.h
-- Zephyr version: 3.7.0-rc2 (/home/chyi/zephyrproject/zephyr), build: v3.7.0-rc2-417-g1e20f58c17c1
[239/239] Linking C executable zephyr/zephyr.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:      335156 B    4194048 B      7.99%
     iram0_0_seg:       85760 B       235 KB     35.64%
     dram0_0_seg:       71260 B     140452 B     50.74%
  dram0_shm0_seg:          0 GB         2 KB      0.00%
  dram0_sem0_seg:          0 GB          8 B      0.00%
     dram0_1_seg:          0 GB         0 GB
     irom0_0_seg:      138548 B      4092 KB      3.31%
     drom0_0_seg:         64 KB      4092 KB      1.56%
    rtc_iram_seg:          0 GB         8 KB      0.00%
    rtc_slow_seg:          0 GB         4 KB      0.00%
        IDT_LIST:          0 GB         8 KB      0.00%
Generating files from /home/chyi/zephyrproject/zephyr/build/zephyr/zephyr.elf for board: esp32_devkitc_wroom
esptool.py v4.7.0
Creating esp32 image...
Image has only RAM segments visible. ROM segments are hidden and SHA256 digest is not appended.
Merged 15 ELF sections
Successfully created esp32 image.


chyi@earth:~/zephyrproject/zephyr$ west flash
-- west flash: rebuilding
ninja: no work to do.
-- west flash: using runner esp32
-- runners.esp32: reset after flashing requested
-- runners.esp32: Flashing esp32 chip on None (921600bps)
esptool.py v4.7.0
Found 34 serial ports
Serial port /dev/ttyUSB1
Connecting....
Detecting chip type... Unsupported detection protocol, switching and trying again...
Connecting.....
Detecting chip type... ESP32
Chip is ESP32-D0WD (revision v1.0)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: 94:3c:c6:38:7e:60
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Changed.
Configuring flash size...
Auto-detected Flash size: 4MB
Flash will be erased from 0x00001000 to 0x00053fff...
Wrote 344064 bytes at 0x00001000 in 4.7 seconds (588.6 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

$ sudo minicom -D /dev/ttyUSB1 -b 115200
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3ffbdb60,len:9752
load:0x40080000,len:85552
1162 mmu set 00010000, pos 00010000
entry 0x4008f0b4
I (55) boot: ESP Simple boot
I (55) boot: compile time Nov 14 2024 12:39:07
W (55) boot: Unicore bootloader
I (55) spi_flash: detected chip: generic
I (58) spi_flash: flash io: dio
I (61) boot: chip revision: v1.0                                                
I (64) boot.esp32: SPI Speed      : 40MHz                                       
I (67) boot.esp32: SPI Mode       : DIO                                         
I (71) boot.esp32: SPI Flash Size : 4MB                                         
I (74) boot: Enabling RNG early entropy source...                               
[esp32] [INF] DRAM: lma 0x00001020 vma 0x3ffbdb60 len 0x2618   (9752)           
[esp32] [INF] IRAM: lma 0x00003640 vma 0x40080000 len 0x14e30  (85552)          
[esp32] [INF] padd: lma 0x00018488 vma 0x00000000 len 0x7b70   (31600)          
[esp32] [INF] IMAP: lma 0x00020000 vma 0x400d0000 len 0x21d34  (138548)         
[esp32] [INF] padd: lma 0x00041d3c vma 0x00000000 len 0xe2bc   (58044)          
[esp32] [INF] DMAP: lma 0x00050000 vma 0x3f400000 len 0x3b24   (15140)          
[esp32] [INF] Image with 6 segments                                             
[esp32] [INF] DROM segment: paddr=00050000h, vaddr=3f400000h, size=03B24h ( 151p
[esp32] [INF] IROM segment: paddr=00020000h, vaddr=400d0000h, size=21D34h (1385p
                                                                                      
*** Booting Zephyr OS build v3.7.0-rc2-417-g1e20f58c17c1 ***                    
Starting Beacon Demo                                                            
[00:00:00.169,000] <inf> esp32_bt_adapter: BT controller compile version [946b7]
[00:00:00.475,000] <inf> bt_hci_core: Identity: 3C:E9:0E:4C:83:0C (public)      
[00:00:00.475,000] <inf> bt_hci_core: HCI: version 4.2 (0x08) revision 0x030e, 0
[00:00:00.475,000] <inf> bt_hci_core: LMP: version 4.2 (0x08) subver 0x030e     
Bluetooth initialized                                                           
Beacon started, advertising as 3C:E9:0E:4C:83:0C (public)

BLE scanner app을 설치하면, Eddystone beacon이 잡히는 것을 확인할 수 있다.

[그림 6.4] BLE scanner app으로 Eddystone beacon을 capture한 모습

6.2 Peripheral 예제 (SmartPhone을 Central 기기로 하여 통신)
<Peripheral on ESP32#1 board>
chyi@earth:~/zephyrproject/zephyr$ west build -b esp32_devkitc_wroom/esp32/procpu samples/bluetooth/peripheral --pristine

chyi@earth:~/zephyrproject/zephyr$ west flash

$ sudo minicom -D /dev/ttyUSB1 -b 115200

[그림 6.5] peripheral app 실행 모습
📌 Central device인 SmartPhone에 해당 peripheral device가 보이고, passkey 값(예: 362970)을 입력할 경우 pairing도 된다.

6.3 Central and Peripheral(2대의 ESP32 보드 사용) 예제
<Central on ESP32#1 board>
$ west build -b esp32_devkitc_wroom/esp32/procpu samples/bluetooth/central_ht --pristine
$ west flash

[그림 6.6] ESP32 #1 보드 - central_ht app 실행 모습


<Peripheral on ESP32#2 board>
$ west build -b esp32_devkitc_wroom/esp32/procpu samples/bluetooth/peripheral_ht --pristine
$ west flash

[그림 6.7] ESP32 #2 보드 - peripheral_ht app 실행 모습

Peripheral(server 역할) 보드에서 (가상의) 온/습도 정보가 Central(client 역할) 보드로 전달되는 것이 보인다.

다음 장에서는 Nordic nRF52840 MCU가 탑재된 개발 board를 이용하여 BLE programming을 이어가 보도록 하겠다.


7. Gl-iNet Thread Dev Board 기반의 BLE 프로그래밍

[그림 7.1] Gi-iNet Thread Dev Board pinout

Gl-iNet Thread Dev Board의 MCU인 NRF52840 SoC ARM Cortex-M4, 1MB flash, 256KB RAM, BLE, Thread, ZigBee 및 다양한 주변장치 controller(UART, i2c, spi ..)등으로 구성되어 있다.

[그림 7.2] nRF52840 SoC 

7.1 J-Link(SWD) Debugger 연결하기
원래대로라면, 정품 J-link adapter를 이용하여 flash writing을 시도하는 것이 답이겠지만, 개인적으로 (값비싼) J-link adapter를 보유하지 못한 관계로, 아래와 같이 SWD 호환 debugger(배송비 빼고, 단돈 5000원이면 충분)를 이용해 보기로 한다. 

[그림 7.3] J-Link OB(On Board) V8 micro USB ARM용 SWD 호환 디버거 [SZH-EK205] [출처 - 참고문헌 11]

먼저, 아래와 같은 구성으로 pin 연결을 한 후, USB cable을 PC(notebook)에 연결한다.
[그림 7.4] J-Link OB V8 micro USB ARM용 SWD 호환 디버거 연결 개요도

<Target Board>                         <SWD debugger>
   VCC(3.3V)    --------------         VCC
     GND            --------------         GND
   SWDIO          --------------         SWDIO
  SWDCLK        --------------         SWDCLK

[그림 7.5] J-Link OB V8 micro USB ARM용 SWD 호환 디버거 연결 모습

다음으로, dmesg 명령으로 SWD debugger가 USB 장치로 제대로 인식되는지 확인한다.
sudo dmesg
[ 1396.261536] usb 1-9: new full-speed USB device number 8 using xhci_hcd
[ 1396.388703] usb 1-9: New USB device found, idVendor=1a86, idProduct=7523, bcdDevice=81.34
[ 1396.388713] usb 1-9: New USB device strings: Mfr=0, Product=2, SerialNumber=0
[ 1396.388717] usb 1-9: Product: USB Serial
[ 1396.391179] ch341 1-9:1.0: ch341-uart converter detected
[ 1396.391715] usb 1-9: ch341-uart converter now attached to ttyUSB1
[ 8708.931035] usb 1-9: USB disconnect, device number 8
[ 8708.931388] ch341-uart ttyUSB1: ch341-uart converter now disconnected from ttyUSB1
[ 8708.931419] ch341 1-9:1.0: device disconnected
[ 9267.124593] usb 1-7.1: new full-speed USB device number 9 using xhci_hcd
[ 9267.203694] usb 1-7.1: New USB device found, idVendor=1366, idProduct=0101, bcdDevice= 1.00
[ 9267.203706] usb 1-7.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 9267.203710] usb 1-7.1: Product: J-Link
[ 9267.203713] usb 1-7.1: Manufacturer: SEGGER
[ 9267.203716] usb 1-7.1: SerialNumber: 000000123456

그 다음, 아래 site에서 nrfjprog tool을 내려 받아 설치하도록 한다(west flash 명령을 시도해 보면, nrfjprog가 없다고 나옴).

sudo dpkg -i ./nrf-command-line-tools_10.24.2_amd64.deb
sudo apt install /opt/nrf-command-line-tools/share/JLink_Linux_V794e_x86_64.deb --fix-broken
📌 nrfjprog은 SEGGER J-Link programmer & debugger를 사용하여 노르딕 SoC에 flash writing(or debugging)을 할 때 사용하는 명령 도구이다. Openocd와 유사한 녀석으로 생각하면 될 듯하다.

<여기서 잠깐! Nordic nRF Connect SDK 설치 관련>
----------------------------------------------------------
앞서 설치했던 nrfjprog 및 j-link s/w pack을 포함하여 nRF Connect SDK(toolchain 및 source code)를 설치하고자 한다면, 아래 site 내용을 참조하기 바란다. nRF Connect SDK는 zephyr 환경에서 동작 가능하도록 구성되어 있다. 

<nRF Connect SDK 설치 관련 주요 내용>
a) nrf command line tool 설치
b) nRF util 설치
c) j-link s/w & document pack 설치
d) nRF Connect SDK toolchain 설치 
e) nRF Connect SDK code 설치 
----------------------------------------------------------

자, 준비가 끝났으니, BLE 예제를 돌려보도록 하자.

7.2 Eddystone beacon 예제
chyi@earth:~/gl-nrf-sdk/zephyr$ west build -b gl_nrf52840_dev_board samples/bluetooth/eddystone --pristine
-- west build: making build dir /home/chyi/gl-nrf-sdk/zephyr/build pristine
-- west build: generating a build system
Loading Zephyr default modules (Zephyr base).
-- Application: /home/chyi/gl-nrf-sdk/zephyr/samples/bluetooth/eddystone
-- Found Python3: /usr/bin/python3.10 (found suitable exact version "3.10.12") found components: Interpreter 
-- Cache files will be written to: /home/chyi/.cache/zephyr
-- Zephyr version: 3.2.99 (/home/chyi/gl-nrf-sdk/zephyr)
-- Found west (found suitable version "1.2.0", minimum required is "0.7.1")
-- Board: gl_nrf52840_dev_board
-- ZEPHYR_TOOLCHAIN_VARIANT not set, trying to locate Zephyr SDK
-- Found host-tools: zephyr 0.16.8 (/home/chyi/zephyr-sdk-0.16.8)
-- Found toolchain: zephyr 0.16.8 (/home/chyi/zephyr-sdk-0.16.8)
-- Found Dtc: /home/chyi/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/bin/dtc (found suitable version "1.6.0", minimum required is "1.4.6") 
-- Found BOARD.dts: /home/chyi/gl-nrf-sdk/nrf/boards/arm/gl_nrf52840_dev_board/gl_nrf52840_dev_board.dts
node '/soc/i2c@40003000/hx3203@44' compatible 'tianyihexin,hx3203' has unknown vendor prefix 'tianyihexin'
node '/soc/i2c@40003000/spl0601@77' compatible 'goertek,spl0601' has unknown vendor prefix 'goertek'
-- Generated zephyr.dts: /home/chyi/gl-nrf-sdk/zephyr/build/zephyr/zephyr.dts
-- Generated devicetree_generated.h: /home/chyi/gl-nrf-sdk/zephyr/build/zephyr/include/generated/devicetree_generated.h
-- Including generated dts.cmake file: /home/chyi/gl-nrf-sdk/zephyr/build/zephyr/dts.cmake
Parsing /home/chyi/gl-nrf-sdk/zephyr/Kconfig
Loaded configuration '/home/chyi/gl-nrf-sdk/nrf/boards/arm/gl_nrf52840_dev_board/gl_nrf52840_dev_board_defconfig'
Merged configuration '/home/chyi/gl-nrf-sdk/zephyr/samples/bluetooth/eddystone/prj.conf'
Configuration saved to '/home/chyi/gl-nrf-sdk/zephyr/build/zephyr/.config'
Kconfig header saved to '/home/chyi/gl-nrf-sdk/zephyr/build/zephyr/include/generated/autoconf.h'
-- The C compiler identification is GNU 12.2.0
-- The CXX compiler identification is GNU 12.2.0
-- The ASM compiler identification is GNU
-- Found assembler: /home/chyi/zephyr-sdk-0.16.8/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc
-- Configuring done
-- Generating done
-- Build files have been written to: /home/chyi/gl-nrf-sdk/zephyr/build
-- west build: building application
[1/224] Preparing syscall dependency handling

[2/224] Generating include/generated/version.h
-- Zephyr version: 3.2.99 (/home/chyi/gl-nrf-sdk/zephyr), build: v3.2.99-ncs1
[214/224] Linking C executable zephyr/zephyr_pre0.elf

[218/224] Linking C executable zephyr/zephyr_pre1.elf

[224/224] Linking C executable zephyr/zephyr.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:      162516 B         1 MB     15.50%
             RAM:       33532 B       256 KB     12.79%
        IDT_LIST:          0 GB         2 KB      0.00%

chyi@earth:~/gl-nrf-sdk/zephyr$ west flash --erase
-- west flash: rebuilding
ninja: no work to do.
-- west flash: using runner nrfjprog
-- runners.nrfjprog: mass erase requested
Using board 123456
-- runners.nrfjprog: Flashing file: /home/chyi/gl-nrf-sdk/zephyr/build/zephyr/zephyr.hex
ERROR: Serial number of connected device (20090928) does not match the expected serial number 123456.
ERROR: There is no debugger connected to the PC with the given serial number.
NOTE: For additional output, try running again with logging enabled (--log).
NOTE: Any generated log error messages will be displayed.
FATAL ERROR: command exited with status 40: nrfjprog --program /home/chyi/gl-nrf-sdk/zephyr/build/zephyr/zephyr.hex --chiperase --verify -f NRF52 --snr 123456

아, 그런데 Serial number가 문제가 있다는 식의 에러가 뜬다. 그렇다면, --snr 123456 option을 빼고, nrfjprog 명령을 직접 실행시켜 보자.

chyi@earth:~/gl-nrf-sdk/zephyr$ nrfjprog --program /home/chyi/gl-nrf-sdk/zephyr/build/zephyr/zephyr.hex --chiperase --verify -f NRF52


📌 참고로 nrfjprog 명령어의 사용법과 관련해서는 아래 site를 참조하도록 하자.

OK, 정상적으로 programming(flash writing) 되었다. 이후 minicom으로 확인해 보니 제대로 올라온다. Android app(BLE scanner)에서도 Eddystone packet이 보인다. 😎

$ sudo minicom -D /dev/ttyUSB1 -b 460800

[그림 7.6] Serial console 출력 모습 - BLE Eddystone 예제


[그림 7.7] Android BLE Scanner app으로 Eddystone beacon을 capture한 모습

7.3 Eddystone beacon 코드 분석 전 준비 사항
이제는 코드를 들여다 볼 차례이다. 그런데, 그 전에 먼저 알아야 할 내용이 몇가지 있다. 바로 Zephyr kernel service에 관한 것이다.

<Zephyr Kernel Services>
1) Threads(= Tasks), Scheduling
2) System Threads, Workqueue Threads
3) Interrupts
4) Timer
5) Polling API
6) Semaphore, Mutex, Spinlock, Condition Variables, Events ...
7) Data passing scheme - FIFO, LIFO, Stack, Message Queue, Mailbox, Pipe

뭔가 대단한 내용이 있을 것 처럼 보이지만, 너무 걱정할 필요는 없다. Zephyr API 내용이 조금 익숙하지 않을 수는 있겠으나, 기본 concept은 Linux kernel의 그것과 대동 소이하기 때문이다. 정말 그럴까 ? 😋

오늘은 이중에서 BLE 예제에 등장하는 자주 Workqueue Thread에 대한 내용을 먼저 살펴 보기로 하자.



[그림 7.8] Linux kernel의 workqueue 개념도 (개념적으로 비슷하여 그려 봄)

<Workqueue Thread 사용법>
Zephyr에는 (kernel에 기 정의되어 있는) system work queue(= k_sys_work_q)와 사용자가 새로 정의해서 사용하는 work queue가 존재한다. 보통은 system work queue를 사용하여 특정 work에 대한 지연 처리(delay work processing)를 하지만, 필요시 사용자 정의 work queue를 만들어 사용해야할 수도 있다. 먼저, 아래 내용은 사용자 정의 work queue를 만드는 방법을 보여준다.

#define MY_STACK_SIZE 512
#define MY_PRIORITY 5

K_THREAD_STACK_DEFINE(my_stack_area, MY_STACK_SIZE); //Thread를 사용하려면 stack을 할당해야 한다.

struct k_work_q my_work_q;   //이런 형태로 work queue 변수를 선언한다.

k_work_queue_init(&my_work_q);   //work queue를 초기화 한다.

k_work_queue_start(&my_work_q, my_stack_area,
                   K_THREAD_STACK_SIZEOF(my_stack_area), MY_PRIORITY,
                   NULL);  //work queue를 시작한다. parameter로 work queue변수와 사전에 할당해둔 stack 및 우선 순위 값을 넘긴다.

그럼, 실제 work queue를 사용하기 위해서는 어떻게 해야 할까 ? 내용 자체는 매우 단순하다. 먼저, 실제로 지연처리할 work(struct k_work 변수)을 선언한 후, work handler 함수(work이 처리될 때 실제로 호출되는 함수)와 함께 work을 초기화하도록 k_work_init( ) 함수를 호출해 주면 된다. 또한 이후 적당한 시점에 해당 work이 처리되도록 요청(submit)해 주도록 한다(submit 시점은 여러 곳이 될 수 있음).

아래 코드에서는 interrupt handler 내에서 k_work_submit( ) 함수를 사용하여 system work queue인 k_sys_work_q에 있는 work을 처리하도록 요청하고 있다. 만일, 앞서 사용자가 새로 생성한 work queue를 이용하고자 할 경우에는 k_work_submit_to_queue( ) 함수를 사용하고, 파라미터로  사용자 queue(&my_work_q)를 넘겨주기만 하면 된다.

struct device_info {
    struct k_work work;   //work queue에 넣을 work을 선언한다.
    char name[16]
} my_device;

void my_isr(void *arg)
{
    ...
    if (error detected) {
        k_work_submit(&my_device.work);   //k_sys_work_q(sysworkq)에 있는 work이 처리되도록 요청한다.
    }
    ...
}

void print_error(struct k_work *item)   //work handler 함수를 정의한다.
{
    struct device_info *the_device =
        CONTAINER_OF(item, struct device_info, work);
    printk("Got error on device %s\n", the_device->name);
}

/* initialize name info for a device */
strcpy(my_device.name, "FOO_dev");

/* initialize work item for printing device's error messages */
k_work_init(&my_device.work, print_error);  //work을 초기화한다. 이때 work변수와 work handler 함수를 파라미터로 넘긴다.

/* install my_isr() as interrupt handler for the device (not shown) */
...


7.4 Eddystone beacon 코드 분석
코드 분석에 앞서 Eddystone 프로토콜 스펙을 면밀히 살펴볼 필요가 있다. 💢


[그림 7.9] Eddystone beacon packet format [출처 - 참고문헌 17]
📌 Eddystone은 iBeacon과는 달리 4가지 type 즉, UUID, URL, TLM, EID 정보를 전달할 수 있는게 특징이다.

Eddystone application code는 다음 위치에 있는데, 주요 code 흐름을 살펴 보면 다음과 같다.

samples/bluetooth/eddystone/src/main.c

/* main routine */
void main(void)
{
   int err;

   
k_work_init_delayable(&idle_work, idle_timeout);

   /* Initialize the Bluetooth Subsystem */
   err =
bt_enable(bt_ready);
   if (err) {
       printk("Bluetooth init failed (err %d)\n", err);
   }
}

/* bt_enable에 넘기는 callback 함수 */
static void bt_ready(int err)
{
   char addr_s[BT_ADDR_LE_STR_LEN];
   struct bt_le_oob oob;

   if (err) {
       printk("Bluetooth init failed (err %d)\n", err);
       return;
   }

   printk("Bluetooth initialized\n");

   /* Start advertising */
   err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);  //BLE 광고 패킷 전송을 시작한다.
   if (err) {
       printk("Advertising failed to start (err %d)\n", err);
       return;
   }

   /* Restore connectable if slot */
   bt_le_oob_get_local(BT_ID_DEFAULT, &oob);
   bt_addr_le_to_str(&oob.addr, addr_s, sizeof(addr_s));
   printk("Initial advertising as %s\n", addr_s);

   k_work_schedule(&idle_work, EDS_IDLE_TIMEOUT);   //work queue에 있는 work을 처리하도록 요청한다.즉, 아래 idle_timeout( ) 함수가 실행되도록 요청한다.

   printk("Configuration mode: waiting connections...\n");
}

/* idle_work에 대한 work hander 함수 */
static void idle_timeout(struct k_work *work)   //work hander 함수
{
   if (eds_slots[eds_active_slot].type == EDS_TYPE_NONE) {
       printk("Switching to Beacon mode %u.\n", eds_active_slot);
       eds_slot_restart(&eds_slots[eds_active_slot], EDS_TYPE_URL);  //advertising packet을 다시 내보낸다.
   }
}

아, 그런데 위의 내용이 다이면 좋겠으나, 실제로는 GATT service & characteristics 초기화 관련하여 아래와 같은 조금은 복잡한 코드(macro)가 보인다. 😂

/* Eddystone service & characteristic을 선언해 주는 macro */
/* Eddystone Configuration Service Declaration */
BT_GATT_SERVICE_DEFINE
(eds_svc,
   BT_GATT_PRIMARY_SERVICE(&eds_uuid),
   /* Capabilities: Readable only when unlocked. Never writable. */
   BT_GATT_CHARACTERISTIC(&eds_caps_uuid.uuid, BT_GATT_CHRC_READ,
                  BT_GATT_PERM_READ, read_caps, NULL, &eds_caps),
   /* Active slot: Must be unlocked for both read and write. */
   BT_GATT_CHARACTERISTIC(&eds_slot_uuid.uuid,
                  BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
                  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
                  read_slot, write_slot, NULL),
   /* Advertising Interval: Must be unlocked for both read and write. */
   BT_GATT_CHARACTERISTIC(&eds_intv_uuid.uuid, BT_GATT_CHRC_READ,
                  BT_GATT_PERM_READ, read_interval, NULL, NULL),
   /* Radio TX Power: Must be unlocked for both read and write. */
   BT_GATT_CHARACTERISTIC(&eds_tx_uuid.uuid,
                  BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
                  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
                  read_tx_power, write_tx_power, NULL),
   /* Advertised TX Power: Must be unlocked for both read and write. */
   BT_GATT_CHARACTERISTIC(&eds_adv_tx_uuid.uuid,
                  BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
                  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
                  read_adv_tx_power, write_adv_tx_power, NULL),
    /* Lock State:
    * Readable in locked or unlocked state.
    * Writeable only in unlocked state.
    */
   BT_GATT_CHARACTERISTIC(&eds_lock_uuid.uuid,
                  BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
                  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
                  read_lock, write_lock, NULL),
   /* Unlock:
    * Readable only in locked state.
    * Writeable only in locked state.
    */
   BT_GATT_CHARACTERISTIC(&eds_unlock_uuid.uuid,
                  BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
                  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
                  read_unlock, write_unlock, NULL),
   /* Public ECDH Key: Readable only in unlocked state. Never writable. */
   BT_GATT_CHARACTERISTIC(&eds_ecdh_uuid.uuid, BT_GATT_CHRC_READ,
                  BT_GATT_PERM_READ, read_ecdh, NULL, &eds_ecdh),
   /* EID Identity Key:Readable only in unlocked state. Never writable. */
   BT_GATT_CHARACTERISTIC(&eds_eid_uuid.uuid, BT_GATT_CHRC_READ,
                  BT_GATT_PERM_READ, read_eid, NULL, eds_eid),
   /* ADV Slot Data: Must be unlocked for both read and write. */
   BT_GATT_CHARACTERISTIC(&eds_data_uuid.uuid,
                  BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
                  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
                  read_adv_data, write_adv_data, NULL),
    /* ADV Factory Reset: Must be unlocked for write. */
   BT_GATT_CHARACTERISTIC(&eds_reset_uuid.uuid,  BT_GATT_CHRC_WRITE,
                  BT_GATT_PERM_WRITE, NULL, write_reset, NULL),
   /* ADV Remain Connectable: Must be unlocked for write. */
   BT_GATT_CHARACTERISTIC(&eds_connectable_uuid.uuid,
                  BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
                  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
                  read_connectable, write_connectable, NULL),
);

/* BT_GATT_SERVICE_DEFINE macro */
#define BT_GATT_SERVICE_DEFINE(_name, ...)              \
   const struct bt_gatt_attr attr_##_name[] = { __VA_ARGS__ }; \
   const STRUCT_SECTION_ITERABLE(bt_gatt_service_static, _name) =  \   //GATT service 정보는 RAM의 특정 section(linker에 의해 지정됨)에 저장된다.
                   BT_GATT_SERVICE(attr_##_name)

위의 macro BT_GATT_SERVICE_DEFINE 내용으로 보아, GATT service & characteristics 정보는 linker에 의해 특정 영역(iterable section)에 배치되게 되고, BLE framework에서는 iterable section에 저장된 값을 이용하여 Eddystone packet을 구성한 후, packet read/write 시에 사용하는 것으로 보인다. 위의 macro의 끝 부분에는 packet read, write 관련 callback 함수(각 함수는 main.c 내에 정의되어 있음)가 전달되게  되는데, 적당한 시점에 이들 callback 함수가 호출되면서 상호간의 통신이 이루어지게 된다.
__________________________________________________________________________________________


7.5 추가 예제 코드 분석
추가로, broadcaster & observer code와 central_ht & peripheral_ht code도 분석한 내용을 정리해 보고자 했으나, 내용이 너무 길어지는 관계로 이정도 선에서 마무리하고자 한다. 😋 


8. nRF52840 USB dongle 기반의 BLE 프로그래밍
지난 주에 주문한 USB dongle이 오늘 도착했다~ 😎  이번 장에서는 Nordic nRF52840 dongle에 관한 얘기로 이번 posting을 마무리짓고자 한다.
[그림 8.1] Nordic nRF52840 USB Dongle [출처 - 참고문헌 22]


8.1 nRF52840 USB dongle(H/W)
The nRF52840 Dongle is a low-cost, versatile USB development dongle for Bluetooth® Low Energy, ANT™, 802.15.4, and user-proprietary 2.4 GHz applications using the nRF52840 SoC.

Key features:
    • nRF52840 flash-based ANT/ANT+™, Bluetooth Low Energy SoC solution
    • Button and LEDs for user interaction
    • 15 GPIO available on a castellated edge
    • Onboard USB bootloader with buttonless support
    • USB support
📌 굳이 번역을 안해도 의미 전달이 충분할 듯 하다. 😋

[그림 8.2] nRF52840 dongle(PCA10059) top/bottom [출처 - 참고문헌 21]


[그림 8.3] nRF52840 dongle block diagram [출처 - 참고문헌 22]


<Device Tree>
https://github.com/zephyrproject-rtos/zephyr/blob/main/boards/nordic/nrf52840dongle/nrf52840dongle_nrf52840.dts

8.2 nRF Connect SDK 설치하기
Noridc nRF series는 (zephyr를 기반으로 하는) 자체 SDK를 제공한다. 따라서 이를 먼저 설치해 보도록 하자.

📌 위의 link에서는 VSCode를 기준으로 환경설정하는 방법도 소개하고 있으나, 여기에서는 명령행 방법만을 소개하기로 하자.

아래 설치 환경은 (늘 그렇듯이) Ubuntu 22.04를 기준으로 한다.
[Step#1] nRF util download 하기
chmod 755 nrfutil
sudo cp ./nrfutil /usr/local/bin/

[Step#2] nRF command-line tool 설치

sudo dpkg -i ./nrf-command-line-tools_10.24.2_amd64.deb

[Step#3] segger j-link 설치
 -> https://www.segger.com/downloads/jlink/JLink_Linux_V810h_x86_64.deb
sudo apt install /opt/nrf-command-line-tools/share/JLink_Linux_V794e_x86_64.deb --fix-broken

[Step#4] nRF Connect SDK toolchain 설치
nrfutil install toolchain-manager
[00:00:05] ###### 100% [Install packages] Install packages

nrfutil toolchain-manager search
Version  Status
v2.8.0   Not installed
v2.7.0   Not installed
v2.6.2   Not installed
v2.6.1   Not installed
v2.6.0   Not installed
v2.5.3   Not installed
v2.5.2   Not installed
v2.5.1   Not installed
v2.5.0   Not installed
v2.4.4   Not installed
v2.4.3   Not installed
v2.4.2   Not installed
v2.4.1   Not installed
v2.4.0   Not installed
v2.3.0   Not installed
v2.2.0   Not installed
v2.1.3   Not installed
v2.1.2   Not installed
v2.1.1   Not installed
v2.1.0   Not installed
v2.0.2   Not installed
v2.0.1   Not installed
v2.0.0   Not installed

nrfutil toolchain-manager install --ncs-version v2.8.0
[00:02:44] ###### 100% [Download toolchain] Toolchain downloaded
[00:00:08] ###### 100% [Unpack toolchain] Toolchain unpacked to /home/chyi/ncs/tmp/.tmpnNFgjC
[00:00:00] ###### 100% [Install toolchain] Toolchain installed at /home/chyi/ncs/toolchains/b81a7cd864

[Step#5] nRF Connect SDK code download 하기
cd ~/ncs/
nrfutil toolchain-manager launch --shell
Initializing shell environment!

(v2.8.0) chyi@earth:~/ncs$ west init -m https://github.com/nrfconnect/sdk-nrf --mr v2.8.0
=== Initializing in /home/chyi/ncs
--- Cloning manifest repository from https://github.com/nrfconnect/sdk-nrf, rev. v2.8.0
Cloning into '/home/chyi/ncs/.west/manifest-tmp'...
remote: Enumerating objects: 275005, done.
remote: Counting objects: 100% (904/904), done.
remote: Compressing objects: 100% (624/624), done.
remote: Total 275005 (delta 436), reused 524 (delta 264), pack-reused 274101 (from 1)
Receiving objects: 100% (275005/275005), 163.36 MiB | 17.48 MiB/s, done.
Resolving deltas: 100% (205534/205534), done.
Note: switching to 'a2386bfc84016fa571f997ac871b25bd67ca481a'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

--- setting manifest.path to nrf
=== Initialized. Now run "west update" inside /home/chyi/ncs.

(v2.8.0) chyi@earth:~/ncs$ west update
=== updating zephyr (zephyr):
--- zephyr: initializing
Initialized empty Git repository in /home/chyi/ncs/zephyr/.git/
--- zephyr: fetching, need revision v3.7.99-ncs1
remote: Enumerating objects: 1155156, done.
remote: Counting objects: 100% (235/235), done.
remote: Compressing objects: 100% (130/130), done.
remote: Total 1155156 (delta 104), reused 149 (delta 93), pack-reused 1154921 (from 1)
Receiving objects: 100% (1155156/1155156), 620.32 MiB | 17.41 MiB/s, done.
Resolving deltas: 100% (872321/872321), done.
From https://github.com/nrfconnect/sdk-zephyr
 * tag                       v3.7.99-ncs1              -> FETCH_HEAD
 * [new tag]                 v1.13.99-ncs1             -> v1.13.99-ncs1

...
...

(v2.8.0) chyi@earth:~/ncs$ west zephyr-export
Zephyr (/home/chyi/ncs/zephyr/share/zephyr-package/cmake)
has been added to the user package registry in:
~/.cmake/packages/Zephyr

ZephyrUnittest (/home/chyi/ncs/zephyr/share/zephyrunittest-package/cmake)
has been added to the user package registry in:
~/.cmake/packages/ZephyrUnittest

(v2.8.0) chyi@earth:~/ncs$ ls -la
합계 52
drwxrwxr-x 13 chyi chyi 4096 11월 22 13:33 .
drwxr-x--- 60 chyi chyi 4096 11월 22 13:28 ..
drwxrwxr-x  2 chyi chyi 4096 11월 22 13:29 .west
drwxrwxr-x  3 chyi chyi 4096 11월 22 13:31 bootloader
drwxrwxr-x  2 chyi chyi 4096 11월 22 13:22 downloads
drwxrwxr-x 11 chyi chyi 4096 11월 22 13:33 modules
drwxrwxr-x 25 chyi chyi 4096 11월 22 13:29 nrf
drwxrwxr-x 22 chyi chyi 4096 11월 22 13:32 nrfxlib
drwxrwxr-x  3 chyi chyi 4096 11월 22 13:33 test
drwxrwxr-x  2 chyi chyi 4096 11월 22 13:25 tmp
drwxrwxr-x  3 chyi chyi 4096 11월 22 13:25 toolchains
drwxrwxr-x  5 chyi chyi 4096 11월 22 13:33 tools
drwxrwxr-x 23 chyi chyi 4096 11월 22 13:31 zephyr

[Step#6] command-line build environment setup하기
(v2.8.0) chyi@earth:~/ncs$ source zephyr/zephyr-env.sh

여기까지, nRF Connect SDK 설치가 모두 끝났다. (다시 얘기하지만) nRF Connect SDK는 zephyr를 기반으로 동작하도록 설계되어 있다.

8.3 BLE Eddystone 예제 build후 돌려 보기

(v2.8.0) chyi@earth:~/ncs/zephyr$ west build -b nrf52840dongle/nrf52840 samples/bluetooth/eddystone
-- west build: generating a build system
Loading Zephyr module(s) (Zephyr base): sysbuild_default
-- Found Python3: /home/chyi/ncs/toolchains/b81a7cd864/usr/local/bin/python3.12 (found suitable version "3.12.4", minimum required is "3.8") found components: Interpreter 
-- Cache files will be written to: /home/chyi/.cache/zephyr
-- Found west (found suitable version "1.2.0", minimum required is "0.14.0")
-- Board: nrf52840dongle, qualifiers: nrf52840
Parsing /home/chyi/ncs/zephyr/share/sysbuild/Kconfig
Loaded configuration '/home/chyi/ncs/zephyr/build/_sysbuild/empty.conf'
Merged configuration '/home/chyi/ncs/zephyr/build/_sysbuild/empty.conf'
Configuration saved to '/home/chyi/ncs/zephyr/build/zephyr/.config'
Kconfig header saved to '/home/chyi/ncs/zephyr/build/_sysbuild/autoconf.h'
-- 
   *******************************
   * Running CMake for eddystone *
   *******************************

Loading Zephyr default modules (Zephyr base).
-- Application: /home/chyi/ncs/zephyr/samples/bluetooth/eddystone
-- CMake version: 3.21.0
-- Using NCS Toolchain 2.8.20241106.790718371940 for building. (/home/chyi/ncs/toolchains/b81a7cd864/cmake)
-- Found Python3: /home/chyi/ncs/toolchains/b81a7cd864/usr/local/bin/python3 (found suitable version "3.12.4", minimum required is "3.8") found components: Interpreter 
-- Cache files will be written to: /home/chyi/.cache/zephyr
-- Zephyr version: 3.7.99 (/home/chyi/ncs/zephyr)
-- Found west (found suitable version "1.2.0", minimum required is "0.14.0")
-- Board: nrf52840dongle, qualifiers: nrf52840
-- Found host-tools: zephyr 0.16.8 (/home/chyi/ncs/toolchains/b81a7cd864/opt/zephyr-sdk)
-- Found toolchain: zephyr 0.16.8 (/home/chyi/ncs/toolchains/b81a7cd864/opt/zephyr-sdk)
-- Found Dtc: /home/chyi/ncs/toolchains/b81a7cd864/usr/local/bin/dtc (found suitable version "1.5.0", minimum required is "1.4.6") 
-- Found BOARD.dts: /home/chyi/ncs/zephyr/boards/nordic/nrf52840dongle/nrf52840dongle_nrf52840.dts
-- Generated zephyr.dts: /home/chyi/ncs/zephyr/build/eddystone/zephyr/zephyr.dts
-- Generated devicetree_generated.h: /home/chyi/ncs/zephyr/build/eddystone/zephyr/include/generated/zephyr/devicetree_generated.h
-- Including generated dts.cmake file: /home/chyi/ncs/zephyr/build/eddystone/zephyr/dts.cmake
Parsing /home/chyi/ncs/zephyr/Kconfig
Loaded configuration '/home/chyi/ncs/zephyr/boards/nordic/nrf52840dongle/nrf52840dongle_nrf52840_defconfig'
Merged configuration '/home/chyi/ncs/zephyr/samples/bluetooth/eddystone/prj.conf'
Merged configuration '/home/chyi/ncs/zephyr/build/eddystone/zephyr/.config.sysbuild'
Configuration saved to '/home/chyi/ncs/zephyr/build/eddystone/zephyr/.config'
Kconfig header saved to '/home/chyi/ncs/zephyr/build/eddystone/zephyr/include/generated/zephyr/autoconf.h'
-- Found GnuLd: /home/chyi/ncs/toolchains/b81a7cd864/opt/zephyr-sdk/arm-zephyr-eabi/arm-zephyr-eabi/bin/ld.bfd (found version "2.38") 
-- The C compiler identification is GNU 12.2.0
-- The CXX compiler identification is GNU 12.2.0
-- The ASM compiler identification is GNU
-- Found assembler: /home/chyi/ncs/toolchains/b81a7cd864/opt/zephyr-sdk/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc
CMake Warning at /home/chyi/ncs/zephyr/subsys/usb/device/CMakeLists.txt:22 (message):
  CONFIG_USB_DEVICE_VID has default value 0x2FE3.

  This value is only for testing and MUST be configured for USB products.


CMake Warning at /home/chyi/ncs/zephyr/subsys/usb/device/CMakeLists.txt:28 (message):
  CONFIG_USB_DEVICE_PID has default value 0x100.

  This value is only for testing and MUST be configured for USB products.


-- Setting build type to 'MinSizeRel' as none was specified.
-- Using ccache: /home/chyi/ncs/toolchains/b81a7cd864/usr/bin/ccache
-- Configuring done
-- Generating done
-- Build files have been written to: /home/chyi/ncs/zephyr/build/eddystone
-- Configuring done
-- Generating done
-- Build files have been written to: /home/chyi/ncs/zephyr/build
-- west build: building application
[5/10] Performing build step for 'eddystone'
[1/213] Preparing syscall dependency handling

[5/213] Generating include/generated/zephyr/version.h
-- Zephyr version: 3.7.99 (/home/chyi/ncs/zephyr), build: v3.7.99-ncs1
[213/213] Linking C executable zephyr/zephyr.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:      168848 B      1020 KB     16.17%
             RAM:       38332 B       256 KB     14.62%
        IDT_LIST:          0 GB        32 KB      0.00%
Generating files from /home/chyi/ncs/zephyr/build/eddystone/zephyr/zephyr.elf for board: nrf52840dongle
[10/10] Generating ../merged.hex


다음으로, USB dongle을 PC에서 연결해 보자.
sudo dmesg
...
[ 5865.936682] usb 1-7.1: new full-speed USB device number 8 using xhci_hcd
[ 5866.017643] usb 1-7.1: New USB device found, idVendor=1915, idProduct=521f, bcdDevice= 1.00
[ 5866.017653] usb 1-7.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 5866.017657] usb 1-7.1: Product: Open DFU Bootloader
[ 5866.017660] usb 1-7.1: Manufacturer: Nordic Semiconductor
[ 5866.017662] usb 1-7.1: SerialNumber: D65655357E68
[ 5866.053086] cdc_acm 1-7.1:1.0: ttyACM0: USB ACM device
[ 5866.053146] usbcore: registered new interface driver cdc_acm
[ 5866.053149] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters

USB dongle이 정상적으로 인식되었으니, 이제는 flash programming할 차례이다.

<nRF52840 dongle programming 방법>
  1. Using the built-in bootloader(1st bootloader 즉, ROM code를 말함) only => 1st bootloader 기반으로 DFU(Device Firmware Update) 방법

  2. Using MCUboot(2nd bootloader) in serial recovery mode => 2nd bootloader 기반으로 DFU하는 방법

  3. Using an external debug probe => SWD adapter를 사용하는 방법

(3번째 방법인) SWD programming(flash writing)을 하려면, 납땜이 필요하므로, 우선은 1번 방법(2번도 유사한 방법임)을 이용하여 위의 hex 파일을 write해 보자.

[그림 8.4] nRF52840 dongle pinmap [출처 - 참고문헌 22]

1, 2번 방법을 위해서는 nrfutil 명령을 사용해야하는데, nrfutil pkg generate option이 안 먹히므로, nrf5sdk-tools를 설치하도록 한다.

chyi@earth:~$ nrfutil list
Command            Version  Description
toolchain-manager  0.15.0   Manage and use toolchains for nRF Connect SDK

Found 1 installed command(s)
chyi@earth:~$ nrfutil search
Command           Installed Latest Status
91                          0.5.0  Not installed
ble-sniffer                 0.14.1 Not installed
completion                  1.5.0  Not installed
device                      2.7.6  Not installed
npm                         0.3.0  Not installed
nrf5sdk-tools               1.1.0  Not installed
suit                        0.8.4  Not installed
toolchain-manager 0.15.0    0.15.0 Installed
trace                       3.0.0  Not installed

Found 9 installable command(s)
chyi@earth:~$ nrfutil install nrf5sdk-tools
[00:00:11] ###### 100% [Install packages] Install packages

이어서 아래 명령을 이용하여 DFU용 zip file을 생성하도록 한다.
(v2.8.0) chyi@earth:~/ncs/zephyr$ nrfutil pkg generate --hw-version 52 --sd-req=0x00 --application build/merged.hex --application-version 1 eddystone.zip

|===============================================================|
|##      ##    ###    ########  ##    ## #### ##    ##  ######  |
|##  ##  ##   ## ##   ##     ## ###   ##  ##  ###   ## ##    ## |
|##  ##  ##  ##   ##  ##     ## ####  ##  ##  ####  ## ##       |
|##  ##  ## ##     ## ########  ## ## ##  ##  ## ## ## ##   ####|
|##  ##  ## ######### ##   ##   ##  ####  ##  ##  #### ##    ## |
|##  ##  ## ##     ## ##    ##  ##   ###  ##  ##   ### ##    ## |
| ###  ###  ##     ## ##     ## ##    ## #### ##    ##  ######  |
|===============================================================|
|You are not providing a signature key, which means the DFU     |
|files will not be signed, and are vulnerable to tampering.     |
|This is only compatible with a signature-less bootloader and is|
|not suitable for production environments.                      |
|===============================================================|

Zip created at eddystone.zip

뭔가, 서명이 필요하다는 warning message가 보이지만, 일단, 이 파일로 DFU를 시도해 보자.

먼저, reset 버튼을 눌러, 1st bootloader로 진입하도록 하자. 이후, 아래 명령을 실행해 보자.

(v2.8.0) chyi@earth:~/ncs/zephyr$ nrfutil dfu usb-serial -pkg eddystone.zip -p /dev/ttyACM0
  [####################################]  100%          
Device programmed.

성공한 듯 보이니, serial console로 정상 동작하는지 확인해 보자.
sudo minicom -D /dev/ttyACM0

[그림 8.5] Serial console로 확인한 모습

OK, Android BLE app에서도 Eddystone beacon이 정상적으로 잡힌다.

[그림 8.6] Android BLE app에서 capture한 모습

----------------------------------------------------------------------
<여기서 잠깐 - DFU zip file 생성시 서명을 하려면 !>

(v2.8.0) chyi@earth:~/ncs/zephyr$ nrfutil keys generate private.pem
Generated private key and stored it in: private.pem


(v2.8.0) chyi@earth:~/ncs/zephyr$ nrfutil pkg generate --hw-version 52 --sd-req=0x00 --application build/merged.hex --application-version 1 eddystone.zip --key-file ./private.pem
Zip created at eddystone.zip
----------------------------------------------------------------------


이상으로, 간단하게 나마 nRF52840 dongle을 사용하여 BLE 예제를 돌려 보았다.


__________
지금까지 Zephyr RTOS 환경에서 BLE programming하는 방법을 개략적으로 살펴 보았다. 부족한 부분은 후일을 기약(?)하며, 이번 posting을 마치도록 한다. 끝까지 읽어 주셔서 감사 드린다. 😎



9. TODO - WireGuard Porting for Zephyr


To be continued ...




10. References
[1] Intro to Bluetooth Energy v1.1 pdf,  Mohammad Afaneh, NovelBits.
[2] Intro to Bluetooth Low Energy 2nd edition, Mohammad Afaneh, NovelBits. *****

[3] https://novelbits.io/zephyr-getting-started-bluetooth-low-energy-development/
[4] Getting Started with Bluetooth Low Energy.pdf, Kevin Townsend, Carles Cufí,
Akiba, and Robert Davidson, Oreilly
[5] https://www.bluetooth.com/wp-content/uploads/2022/05/Bluetooth_LE_Primer_Paper.pdf
[6] Bluetooth: Technology and Applications, Yang Bo, CTTL‐SYS, CAICT 2017.10.31
[7] https://www.slideshare.net/slideshow/introduction-to-bluetooth-low-energy-77026798/77026798#26

[8] https://www.amazon.com/HiLetgo-ESP-WROOM-32-Development-Microcontroller-Integrated/dp/B0718T232Z

[9] https://www.embeddedrelated.com/showarticle/1505.php

[10] https://www.nordicsemi.com/Products/Development-hardware/nRF52840-Dongle
[11] https://www.devicemart.co.kr/goods/view?no=1360841&srsltid=AfmBOopMYhHHctSmebkqCsFrnyQIV4qp0h87Yfs1nBU5dAeDcBaODbJb
[12] https://www.nordicsemi.com/Products/Development-tools/nRF-Command-Line-Tools

[13] https://leevisual.tistory.com/99
[14] https://github.com/santansarah/ble-scanner - android ble app
[15] https://blog.msalt.net/247

[16] https://m.blog.naver.com/antplustech/220874291973

[17] https://os.mbed.com/teams/Bluetooth-Low-Energy/code/BLE_EddystoneBeacon_Service/shortlog/

[18] https://kontakt.io/blog/what-is-eddystone/

[19] https://www.bluetooth.com/learn-about-bluetooth/feature-enhancements/le-audio/le-audio-specifications/

[20] https://www.bluetooth.com/bluetooth-mesh-primer/


[21] https://docs.zephyrproject.org/latest/boards/nordic/nrf52840dongle/doc/index.html

[22] nRF52840_Dongle_User_Guide_v2.1.1.pdf, Nordic Semiconductor


[23] And, Google.


Slowboot