2024년 11월 13일 수요일

Zephyr RTOS Programming

이번 시간에는 오랫 만에 리소스 제한(부족) 장치(resource contrained device)를 가지고, 실제로 Zephyr RTOS programming을 어떻게 하는지 소개해 볼까 한다. 좀 거창하지만, 실전 Zephyr RTOS programming 시간이 되겠다~ 😎 


v4.0.0
안정성을 넘어서 이제는 대세로~


목차
1. Zephyr Kernel 부팅 Flow
2. Zephyr Device Driver Model
3. STM32 Nucleo L432KC Board - GPIO LED
4. STM32 Nucleo L432KC Board  - I2C Device
5. BLE application - TODO
6. References


어느덧 Zephyr가 출시(2016년 2월에 1.0 release)된지도 벌써 8~9년의 시간이 흘렀고, 국내에서도 Zephyr 개발자를 찾을 정도로 Zephyr의 인기는 하늘 높은 줄 모르고 치솟고 있다. 아닌가 ? ㅋ 😋 Zephyr는 Linux의 brother OS로 알려져 있는 만큼, 많은 부분에서 linux와 유사한 점이 있다. kernel 디렉토리로 대표되는 task management 관련 코드(물론 OS의 출발지는 전혀 다름)는 물론이고, 각종 device driver를 위한 drivers 디렉토리 내용도 linux의 그것과 흡사하다. 이외에도 표면상 보이는 Kconfig, device tree 사용 등도 zephyr가 왜 linux의 brother OS인지를 설명하는데 일조하고 있다.


1. Zephyr Kernel 부팅 Flow
Zephyr RTOS device driver의 초기화 부분을 제대로 이해하기 위해서는, application main( ) 함수가 실행되기까지의 booting flow를 이해할 필요가 있다. 즉, 부팅 중, 각종 device driver에 대한 초기화가 진행되는데, 이 부분을 이해하는 것이 실제 device driver 작업시 특정 API(예: device_is_ready())의 배경 & 의미를 이해하는데 도움을 줄 것으로 보인다.


Zephyr RTOS의 bootling flow와 관련해서는 특별히 아래 문서의 내용이 아주 훌륭하여, 여기에 옮겨 보았다. 😍
📌 이 글의 원저자이신 David Leach 님에게 경의를 표한다. 👍

참고로, 아래 내용은 필자가 3년전에 분석했던 내용으로, 함께 참조해 주시기 바란다. 😓
[그림 1.1] Zephyr booting flow - 대략적인 코드 흐름 분석


먼저, 전체 초기화 과정을 간략히 압축하여 표현하면 다음과 같다.

[그림 1.2] Zephyr booting flow(단순화 버젼) [출처 - 참고문헌 2]

다음 그림은, Early startup 부분에 대한 내용이다.

[그림 1.3] Zephyr booting flow - Early Startup 흐름 분석 [출처 - 참고문헌 2]

다음 그림은 (Assembly code에서) C code로 전환되면서, 진행되는 내용을 표현하고 있다. 이 부분에서 device driver 초기화가 일부 진행된다.

[그림 1.4] Zephyr booting flow - C Start 흐름 분석 [출처 - 참고문헌 2]

다음 그림은 task scheduler 관련 흐름도이다.

[그림 1.5] Zephyr booting flow - Task Scheduler init 흐름 분석 [출처 - 참고문헌 2]

마지막으로, 아래 그림은 bg_thread_main( ) 함수에 대한 내용으로, 최종적으로는 application main() 함수를 호출하여 사용자가 작성한 application이 시작되기 전까지의 과정을 보여주고 있다.

[그림 1.6] Zephyr booting flow - bg_thread_main 흐름 분석 [출처 - 참고문헌 2]

이상의 내용을 모두 통합하여, 하나로 정리해 보면 다음과 같다. 캬~ 😍

[그림 1.7] Zephyr booting flow 전체 [출처 - 참고문헌 2]

위의 내용을 참조하여, kernel startup & initialization code를 (독자 여러분이) 직접 분석해 볼 것을 추천드린다. 🌷


2. Zephyr Device Driver Model
이번 posting의 주제인 리소스 부족 장치(resource contrained device)를 이용하여 zephyr programming을 제대로 수행하기 위해서는, zephyr RTOS 관련하여 몇가지 사전 지식이 필요하다.

1) device tree 개념
2) device driver가 초기화되는 시점 및 방법 - 1장에서 소개
3) zepyhr device driver model

위의 내용 중, 1번 항목과 관련해서는 부족하지만, 이전 blog posting을 통해 한차례 소개한 바가 있다.


따라서 이번 시간에는 3번 항목을 중심으로 zephyr device driver programming을 위한 기초 지식을 좀 더 파헤쳐 보고자 한다.

2.1 Linux kernel의 device driver model
먼저, Zephyr RTOS의 device driver model을 설명하기 전에, 비교 차원에서 linux kernel의 그것과 booting flow를 가볍게 짚고 넘어가기로 하자. 이 대목에서 굳이 linux kernel에 대한 내용을 언급하는 이유는, 많은 부분에서 유사한 concept이 적용되어 있기 때문이다.

<device model 관련 용어 정리>

  • device: bus에 연결되어 있는 물리적인 혹은 가상의 객체. controller device or consumer device를 말함.
  • driver: 특정 device에 대한 동작을 담당하는 software. 동작 시 probe 과정이 필요함.
  • bus: CPU와 device 들간의 통신 channel.

Linux device driver model은 아래와 같이 3가지 data structure로 구성되어 있다. 즉,
  • struct bus_type : USB, PCI, I2C, SPI, UART 등과 같은 bus 중의 하나를 표현하는 구조체(bus라는 h/w device를 표현하는 data structure)
  • struct device_driver : 특정 bus에 붙어 있는 device(adapter or slave devices)를 처리하는 device driver를 표현하는 구조체
  • struct device : 특정 bus에 연결되어 있는 하나의 device를 표현하는 구조체(controller device 및 일반 consumer device를 말함)

이를 하나의 그림으로 표현해 보면 다음과 같다.

[그림 2.1] Linux device driver model [출처 - 참고문헌 11]

📌 linux device driver model은 kernel v2.6에서 지금과 같은 모습으로 정립되었다.

Bus는 cpu와 device 간의 channel이므로, 각종 device는 해당 bus에 연결되어야 한다. 한편 Bus core driver는 struct bus_type 구조체를 할당하고, bus_register() 함수를 이용하여 bus에 등록하게 되는데, bus core driver의 주요 역할은 다음과 같다.

<bus core driver의 역할>
  • controller(adapter) driver에 대한 등록 허용
  • 각종 device driver에 대한 등록 허용
  • 각종 device와 device driver를 matching 시켜주는 일을 함.

아래 내용은 usb device를 예로하여, bus core driver, controller 및 consumer device가 동작하는 과정을 간략히 보여준다. 먼저, 각종 device driver(controller 및 consumer device)는 driver_register() 함수를 이용하여 bus core driver에 등록을 하게 된다.

[그림 2.2] USB driver_register 과정 [출처 - 참고문헌 9]
📌 편의상 USB bus를 기준으로 내용 정리를 하였다.

이후, 새로운 device가 (controller에 의해) 감지된 경우, bus core driver는 이에 matching되는 device driver를 찾게 되고, 이렇게 해서 찾은 driver 내의 probe() 함수가 호출되면서 해당 device의 동작이 시작되게 된다. probe() 함수는 device의 각종 h/w 정보를 가져오기 위해 Device Tree를 이용한다.

[그림 2.3] USB driver detection 및 probe 과정 [출처 - 참고문헌 9]

이상의 과정을 통해 최종적으로 연결된 bus core(예: USB core), controller(예: USB adapter) 및 device(예: USB device) 간의 관계를 하나의 그림으로 표현하면 다음과 같다.

[그림 2.4] USB bus core/adapter/device 관계도 [출처 - 참고문헌 9]

이와 같은 각종 device(controller & consumer devices)는 부팅 시에 initialization level에 의거하여 device list에 추가되고, 초기화되는 과정을 거치게 된다.

[그림 2.5] Linux kernel initialization sequence
📌 실제로 이 부분도 할말이 아주 많은 부분이나, (서운하지만) 그림 하나로 퉁치고 넘어가기로 한다. 😋


2.2 Zephyr device driver model
한편, Zephyr는 resource가 제한되어 있는 환경을 고려하여, linux 처럼 복잡한 구조 보다는 아래와 같은 단순한 data structure를 이용하여 device driver를 구현한다.

struct device { /** Name of the device instance */ const char *name; /** Address of device instance config information */ const void *config; /** Address of the API structure exposed by the device instance */ const void *api; //application code가 사용하는 API 함수 /** Address of the common device state */ struct device_state *state; /** Address of the device instance private data */ void *data;
}

위의 struct device의 각 필드는 (드라이버 개발자에 의해) 아래와 같은 내용으로 채워지게 된다.
struct your_dev_config {
    // memory mapped IO 주소, IRQ line number, device 관련 물리적인 정보 등이 포함됨.
}

static struct subsystem_api your_api_funcs = {
    // application에서 driver 정보를 추출하고자 할 때 사용하는 API
    .do_this = my_driver_do_this,
    .do_that = my_driver_do_that
};

struct your_dev_data {
    // device 관련 data 정보를 임시로 담아두고자 할때 사용하는 buffer 등을 정의
}

실제 device driver에는 driver code를 시작하는 init( ) 함수가 있으며, driver registration(list에 등록을 의미함)을 하기 위해서는 아래와 같은 macro를 사용한다. 아래 macro 호출 시, 앞서 정의한  config, api, data pointer 정보도 함께 전달된다.

📌 주요 device driver code를 살펴 보면, 코드 마지막 부분에 위와 같은 macro를 사용하는 내용이 보인다.


각각의 device를 표현하는 struct device 내용은 initialization level(level, prio 값)에 입각하여 RAM section에 적절히 배치되고, 실제 동작 시 활용된다.


[그림 2.6] Zephyr device driver model [출처 - 참고문헌 1]


끝으로, device tree 관련 내용은 아래과 같은 절차를 거쳐 compile 과정에서 C #define 문으로 자동 전환되게 되며, device driver는 이 내용(혹은 이를 이용한 macro)을 사용하여 driver code를 완성하게 된다.
zephyr/build/zephyr/include/generated/zephyr/devicetree_generated.h

^
^
[그림 2.7] device tree로 부터 C header file 생성 과정 [출처 - 참고문헌 13]


지금까지, zephyr RTOS의 device driver model에 관하여 간략히 살펴 보았다.

1장과 2장을 통해 (필자가) 궁극적으로 말하고자 하는 바는, device model(device tree 포함)과 device driver 초기화 과정에 대한 이해가 선행되어야만, 제대로된 device driver 및 application code를 만들 수 있다는 것이다. 이어지는 장에서는 실제 target board와 device를 이용하여 실습을 진행해 보도록 하자. 폭탄 투하 시작~ ㅋ 💣💣



3. STM32 Nucleo L432KC Board - GPIO LED
이번 장에서는 STM32 Nucleo L432KC 보드를 이용하여 zephyr programming을 하는 방법을 본격적으로 소개해 보고자 한다.
Zephyr 환경 설정과 관련해서는 (좀 오래되기는 했으나) 이전 blog posting을 확인해 주기 바란다.



[그림 3.1] STM32 Nucleo-L432KC board [출처 - 참고문헌 4]

3.1 hello world program 올리기
chyi@earth:~/zephyrproject/zephyr$ west build -b nucleo_l432kc samples/hello_world -t menuconfig

[그림 3.2] zephyr menuconfig

chyi@earth:~/zephyrproject/zephyr$ west build -b nucleo_l432kc samples/hello_world --pristine
📌 --pristine option은 build 하기 전에 이전 build 디렉토리를 통째로 날린다.
-- 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/hello_world
-- 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: nucleo_l432kc, qualifiers: stm32l432xx
-- 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/st/nucleo_l432kc/nucleo_l432kc.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
/home/chyi/zephyrproject/zephyr/build/zephyr/zephyr.dts:473.10-480.5: Warning (simple_bus_reg): /soc/clocks: missing or empty reg/ranges property
Parsing /home/chyi/zephyrproject/zephyr/Kconfig
Loaded configuration '/home/chyi/zephyrproject/zephyr/boards/st/nucleo_l432kc/nucleo_l432kc_defconfig'
Merged configuration '/home/chyi/zephyrproject/zephyr/samples/hello_world/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/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/zephyr-sdk-0.16.8/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc
-- Using ccache: /usr/bin/ccache
-- Configuring done
-- Generating done
-- Build files have been written to: /home/chyi/zephyrproject/zephyr/build
-- west build: building application
[1/131] Preparing syscall dependency handling

[2/131] Generating include/generated/zephyr/version.h
-- Zephyr version: 3.7.0-rc2 (/home/chyi/zephyrproject/zephyr), build: v3.7.0-rc2-417-g1e20f58c17c1
[131/131] Linking C executable zephyr/zephyr.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       14076 B       256 KB      5.37%
             RAM:        4416 B        64 KB      6.74%
        IDT_LIST:          0 GB        32 KB      0.00%
Generating files from /home/chyi/zephyrproject/zephyr/build/zephyr/zephyr.elf for board: nucleo_l432kc

chyi@earth:~/zephyrproject/zephyr$ west flash
-- west flash: rebuilding
ninja: no work to do.
-- west flash: using runner openocd
-- runners.openocd: Flashing file: /home/chyi/zephyrproject/zephyr/build/zephyr/zephyr.hex
Open On-Chip Debugger 0.11.0+dev-00728-gb6f95a16c (2024-05-29-12:13)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
srst_only separate srst_nogate srst_open_drain connect_deassert_srst

Info : clock speed 500 kHz
Info : STLINK V2J31M21 (API v2) VID:PID 0483:374B
Info : Target voltage: 3.248262
Info : [stm32l4x.cpu] Cortex-M4 r0p1 processor detected
Info : [stm32l4x.cpu] target has 6 breakpoints, 4 watchpoints
Info : starting gdb server for stm32l4x.cpu on 3333
Info : Listening on port 3333 for gdb connections
    TargetName         Type       Endian TapName            State       
--  ------------------ ---------- ------ ------------------ ------------
 0* stm32l4x.cpu       hla_target little stm32l4x.cpu       running

Info : Unable to match requested speed 500 kHz, using 480 kHz
Info : Unable to match requested speed 500 kHz, using 480 kHz
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x0800058c msp: 0x20001100
Info : device idcode = 0x10016435 (STM32L43/L44xx - Rev Z : 0x1001)
Info : RDP level 0 (0xAA)
Info : flash size = 256kbytes
Info : flash mode : single-bank
Info : Padding image section 0 at 0x080036fc with 4 bytes (bank write end alignment)
Warn : Adding extra erase range, 0x08003700 .. 0x080037ff
auto erase enabled
wrote 14080 bytes from file /home/chyi/zephyrproject/zephyr/build/zephyr/zephyr.hex in 0.426400s (32.247 KiB/s)

Info : Unable to match requested speed 500 kHz, using 480 kHz
Info : Unable to match requested speed 500 kHz, using 480 kHz
shutdown command invoked

$ sudo minicom -D /dev/ttyACM0 -b 115200
[그림 3.3] minicom serial console 화면


[그림 3.4] STM32 Nucleo-L432KC board 구동 모습

3.2 LED blinky program(GPIO 예제) 돌려 보기
2번째로 돌려 볼 program은 (가장 만만한 😂) 녹색 LED를 점멸(blinking)하는 예제이다. 이를 위해 STM32 Nucleo-L432KC board의 pinmap을 확인할 필요가 있다.


[그림 3.5] STM32 Nucleo-L432KC board top layout [출처 - 참고문헌 5]


[그림 3.6] STM32 Nucleo-L432KC board bottom layout [출처 - 참고문헌 5]

아래 표와 회로도는 LD3 green LED의 pinmap을 보여준다.
CN4 -> D13 -> PB3 -> LD3(green LED, 그림 3.5 좌측 상단)

[그림 3.7] STM32 Nucleo-L432KC pinout [출처 - 참고문헌 6]

[그림 3.8] STM32 Nucleo-L432KC schematic에서 발췌 [출처 - 참고문헌 7]

자 green LED에 대한 pinmap이 확인되었으니, 이 쯤에서 코드를 build & 실행해 보도록 하자. 백견이 불여일타 ~

$ cd ~/zephyrproject/zephyr
$ west build -b nucleo_l432kc samples/basic/blinky -t menuconfig --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/basic/blinky
-- 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: nucleo_l432kc, qualifiers: stm32l432xx
-- 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/st/nucleo_l432kc/nucleo_l432kc.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
/home/chyi/zephyrproject/zephyr/build/zephyr/zephyr.dts:473.10-480.5: Warning (simple_bus_reg): /soc/clocks: missing or empty reg/ranges property
Parsing /home/chyi/zephyrproject/zephyr/Kconfig
Loaded configuration '/home/chyi/zephyrproject/zephyr/boards/st/nucleo_l432kc/nucleo_l432kc_defconfig'
Merged configuration '/home/chyi/zephyrproject/zephyr/samples/basic/blinky/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/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/zephyr-sdk-0.16.8/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc
-- Using ccache: /usr/bin/ccache
-- Configuring done
-- Generating done
-- Build files have been written to: /home/chyi/zephyrproject/zephyr/build
-- west build: running target menuconfig
[0/1] cd /home/chyi/zephyrproject/zephyr/build/...nfig.py /home/chyi/zephyrproject/zephyr/Kconfig
Loaded configuration '/home/chyi/zephyrproject/zephyr/build/zephyr/.config'
Configuration (/home/chyi/zephyrproject/zephyr/build/zephyr/.config) was not saved

$
west build -b nucleo_l432kc samples/basic/blinky
$ west flash

(별도의 화면으로 capture하지는 않았지만) LED가 1초 간격으로 점멸하는 것을 알 수 있다. 이제 부터는 코드를 들여다 볼 차례이다.

$ vi zephyr/samples/basic/blinky/src/main.c
___________________________________________________________

/*
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <stdio.h>
#include <zephyr/kernel.h>  
#include <zephyr/drivers/gpio.h>
          
/* 1000 msec = 1 sec */
#define SLEEP_TIME_MS   1000

/* The devicetree node identifier for the "led0" alias. */
#define LED0_NODE
DT_ALIAS(led0)   //device tree alias 설정 내용 중, led0에 해당하는 node identifier를 구함

/*   
* A build error on this line means your board is unsupported.
* See the sample documentation for information on how to fix this.
*/  
static const struct gpio_dt_spec led =
GPIO_DT_SPEC_GET(LED0_NODE, gpios);  //led0 node의 gpios 속성 정보를 추출

int main(void)  
{
   int ret;
   bool led_state = true;

   if (!
gpio_is_ready_dt(&led)) {  // ............................................... (A)
       return 0;
   }

   ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);  //gpiob 3번 pin(PB3)을 output mode로 설정(active High)
   if (ret < 0) {
       return 0;
   }

   while (1) {
       ret = gpio_pin_toggle_dt(&led);  //gpiob 3번 pin 설정을 toggle 시켜준다.
       if (ret < 0) {
           return 0;
       }

       led_state = !led_state;
       printf("LED state: %s\n", led_state ? "ON" : "OFF");
       k_msleep(SLEEP_TIME_MS);
   }
    return 0;
}
___________________________________________________________

main.c를 제대로 이해하기 위해서는 stm32_l432kc board의 device tree 내용을 함께 들여다 볼 필요가 있다.

$ vi boards/st/nucleo_l432kc/nucleo_l432kc.dts
___________________________________________________________

/*
* Copyright (c) 2017 Linaro Limited
*
* SPDX-License-Identifier: Apache-2.0
*/

/dts-v1/;
#include <st/l4/stm32l432Xc.dtsi>
#include <st/l4/stm32l432k(b-c)ux-pinctrl.dtsi>

/ {
   model = "STMicroelectronics STM32L432KC-NUCLEO board";
   compatible = "st,stm32l432kc-nucleo";

   chosen {
       zephyr,console = &usart2;
       zephyr,shell-uart = &usart2;
       zephyr,sram = &sram0;
       zephyr,flash = &flash0;
       zephyr,canbus = &can1;
   };

    leds: leds {
       compatible = "gpio-leds";
       green_led: led_0 {
           gpios = <&gpiob 3 GPIO_ACTIVE_HIGH>;   //PB3을 가리키고 있음.
           label = "User LD3";
       };
   };

   aliases {
       led0 = &green_led;   //DT_ALIAS(led0) macro로 green_led node를 찾는데 활용
   };
};

...
___________________________________________________________

<(A) code에 대한 보충 설명>

(아래의 코드 흐름에 따라) gpio_is_ready_dt() 함수, 다시 말해, device_is_ready( ) 함수를 호출하는 것은, 앞서 1-2장에서 설명한 내용에 의거하여 (gpio 관련) device가 제대로 초기화 되어 있는지를 확인하는 절차로 설명될 수 있다.
📌 사실 이 부분을 설명하기 위해 1-2장의 내용을 추가했다고 보아도 무방하다. 😂

___________________________________________________________

static inline bool gpio_is_ready_dt(const struct gpio_dt_spec *spec)
{
    /* Validate port is ready */
    return device_is_ready(spec->port);
}

===>

/* build/zephyr/include/generated/zephyr/syscalls/device.h => 이 파일은 build 과정에서 자동 생성된다. */
__pinned_func
static inline bool device_is_ready(const struct device * dev)
{
#ifdef CONFIG_USERSPACE
    if (z_syscall_trap()) {
        union { uintptr_t x; const struct device * val; } parm0 = { .val = dev };
        return (bool) arch_syscall_invoke1(parm0.x, K_SYSCALL_DEVICE_IS_READY);
    }
#endif
    compiler_barrier();
    return z_impl_device_is_ready(dev);
}

===>

/* zephyr/kernel/device.c */
bool z_impl_device_is_ready(const struct device *dev)
{
    /*
     * if an invalid device pointer is passed as argument, this call
     * reports the `device` as not ready for usage.
     */
    if (dev == NULL) {
        return false;
    }

    return dev->state->initialized && (dev->state->init_res == 0U);   //zephyr/drivers/gpio/gpio_stm32.c GPIO_DEVICE_INIT_STM32 macro에 의해 초기화. 1-2장에서 설명한 device driver 초기화 부분과 관련된 내용임.
}
___________________________________________________________

다음 장에서는 target board에 i2c device(adxl345 accelerometer)를 하나 붙여 보고, 이를 인식시키는 과정을 소개해 보도록 하자.



4. STM32 Nucleo L432KC Board - I2C Device
이번에는 ADXL345 3축 가속도(Accelerometer) 센서용 i2c device driver와 이를 사용하는 application을 구현하는 방법을 소개해 보고자 한다.


ADXL345 3축 가속도 센서를 인식하는 device driver 및 application 코드를 작성하는 순서는 대략적으로 다음과 같다.

1) H/W 연결(i2c pin 연결)
2) Driver model을 기초로, device driver code 작성
3) application code 작성
   3-1) project 파일 생성
   3-2) device tree 수정 - dt overlay 파일 추가
   3-3) proj.conf 수정
   3-4) src/main.c 파일 구현 - i2c device로 부터 sensor 정보 추출하는 코드 구현
4) build & run & debugging

4.1 ADXL345 3축 가속도 센서 연결(i2c)
먼저 아래 내용을 참조하여 ADXL345 breakout board를 target board에 연결해 보자.

[그림 4.1] ADXL345 3축 가속도(Accelerometer) 센서 브레이크아웃 보드

📌 주의: ADXL345 보드는 i2c와 spi 겸용으로 설계되어 있다.


[그림 4.2] ADXL345 I2C connection  [출처 - 참고문헌 18]

📌 주의: ADXL345를 i2c mode로 사용하려면 CS pin을 Vcc(3.3V)와 연결해 주어야만 한다.


[그림 4.3] STM32 Nucleo-L432KC CN4 pinout [출처 - 참고문헌 6]

📌 주의: (매뉴얼을 보니) A4, A5 pin이 solder bridge SB16, SB18에 의해 PB7, PB6로 연결되어 있다.


아래 회로도를 보면, MCU의 i2c1 pin(PA5: SDA, PA6: SCL)은 아두이노 확장 커넥터의 A4와 A5에 연결되어 있는데, 다시 우측의 Solder Bridge SB18, SB16에 의해 PB7, PB6와 연결되어 있음을 알 수 있다.

[그림 4.4] STM32 Nucleo-L432KC Schematic - extension connector  [출처 - 참고문헌 6]

실제로 STM32 Nucleo-L432KC dts 파일을 보면, PA5, PA6 pin이 아니라, PB7, PB6 pin을 기준으로 내용이 기술되어 있음을 알 수 있다.

[그림 4.5] STM32 Nucleo-L432KC dts file - i2c1

따라서, STM32 Nucleo-L432KC 보드와 ADXL345 breakout board는 아래와 같이 연결되어야 한다.

    <Nucleo-L432KC 보드>                               <ADXL345>
  • CN4 2번 pin - GND                                ------- GND
  • CN4 14번 pin - Vcc(3.3V)                      ------- VCC(3.3V)
  • CN4 7번 pin(PA6) - I2C1 SCL                 ------- SCL
  • CN4 8번 pin(PA5) - I2C1 SDA                ------- SDA
  • CN3 4번 pin - GND                                ------- SDO (ALT Address)
  • 다른 Board Vcc(3.3V)                             ------- CS

📌 처음에는 PB7(SDA), PB6(SCL)에 연결해야 하나 생각했으나, 테스트 결과, 원래 pin 즉, PA5, PA6에 연결하는 것이 맞았다.

📌 Target board에 ADXL345 CS pin을 연결할 여분의 VCC(3.3V) 확장 pin이 없으므로, 다른 board를 하나 준비한 후, 그 board에 연결해야 할 것 같다. 😱


[그림 4.6] STM32 Nucleo-L432KC 보드(우측 하단)와 ADXL345 보드(상단) 연결

📌 좌측 하단의 보드는 Vcc(3.3V)용 연결 목적으로만 사용된다.

📌 (참고) 나중에 다시 확인해 보니, CS pin을 굳이 VCC(3.3V)에 연결하지 않아도 동작한다. 😓


4.2 device driver 작성
먼저 device tree 파일에 adxl345 sensor를 위한 node 정보를 추가해 보도록 하자(실제로 이 내용은 나중에 overlay 파일로 분리될 것임).

&i2c1 {
   pinctrl-0 = <&i2c1_scl_pb6 &i2c1_sda_pb7>;
   pinctrl-names = "default";
   clock-frequency = <I2C_BITRATE_FAST>;
   status = "okay";
   adxl345: adxl345@53 {
         compatible = "adi,adxl345";
         reg = <0x53>;
         status = "okay";
   };
};

다음으로, 실제 device driver code를 작성하기에 앞서, 2장에서도 언급한 것 처럼, device driver를 작성하는데 있어 필요한 내용(skeleton)을 먼저 정리해 보면 다음과 같다.


/* my_driver.c */
#include <zephyr/drivers/some_api.h>

/* Define data (RAM) and configuration (ROM) structures: */
struct my_dev_data {
     /* per-device values to store in RAM */
};
struct my_dev_cfg {
     uint32_t freq; /* Just an example: initial clock frequency in Hz */
     /* other configuration to store in ROM */
};

/* Implement driver API functions (drivers/some_api.h callbacks): */
static int my_driver_api_func1(const struct device *dev, uint32_t *foo) { /* ... */ }
static int my_driver_api_func2(const struct device *dev, uint64_t bar) { /* ... */ }
static struct some_api my_api_funcs = {
     .func1 = my_driver_api_func1,
     .func2 = my_driver_api_func2,
};

위의 내용을 기초로하여, adxl345 device driver code를 대략적으로 작성해 보면 다음과 같다.

struct adxl345_dev_config {
    const union adxl345_bus bus;
    adxl345_bus_is_ready_fn bus_is_ready;
    adxl345_reg_access_fn reg_access;
};

static const struct sensor_driver_api adxl345_api_funcs = {
    .sample_fetch = adxl345_sample_fetch,
    .channel_get = adxl345_channel_get,
};

struct adxl345_dev_data {
    unsigned int sample_number;

    int16_t bufx[ADXL345_MAX_FIFO_SIZE];
    int16_t bufy[ADXL345_MAX_FIFO_SIZE];
    int16_t bufz[ADXL345_MAX_FIFO_SIZE];
};

#define DT_DRV_COMPAT adi_adxl345

📌 device tree의 compatible = "adi,adxl345"에 해당하는 내용이다. (주의)#define 문에서는 ,(comma) 대신에 _(underscore)로 교체되어 표기한다.


...
static bool adxl345_bus_is_ready_i2c(const union adxl345_bus *bus)
{
    return device_is_ready(bus->i2c.bus);
}

static int adxl345_reg_access_i2c(const struct device *dev, uint8_t cmd, uint8_t reg_addr,
                  uint8_t *data, size_t length)
{
    const struct adxl345_dev_config *cfg = dev->config;

    if (cmd == ADXL345_READ_CMD) {
        return i2c_burst_read_dt(&cfg->bus.i2c, reg_addr, data, length);
    } else {
        return i2c_burst_write_dt(&cfg->bus.i2c, reg_addr, data, length);
    }                
}

📌 i2c read/write 함수를 정의한다.


static int adxl345_sample_fetch(const struct device *dev,
                enum sensor_channel chan)
{
    ...
}

static int adxl345_channel_get(const struct device *dev,
                   enum sensor_channel chan,
                   struct sensor_value *val)
{
    ...
}

static const struct sensor_driver_api adxl345_api_funcs = {
    .sample_fetch = adxl345_sample_fetch,
    .channel_get = adxl345_channel_get,
};

📌 sample_fetch()와 channel_get() 함수는 application을 위한 창구(API) 역할을 한다.


static int adxl345_init(const struct device *dev)
{
    struct adxl345_dev_data *data = dev->data;

    if (!adxl345_bus_is_ready(dev)) {
        LOG_ERR("bus not ready");
        return -ENODEV;
    }

    rc = adxl345_reg_read_byte(dev, ADXL345_DEVICE_ID_REG, &dev_id);
    rc = adxl345_reg_write_byte(dev, ADXL345_FIFO_CTL_REG, ADXL345_FIFO_STREAM_MODE);
    rc = adxl345_reg_write_byte(dev, ADXL345_DATA_FORMAT_REG, ADXL345_RANGE_16G);
    rc = adxl345_reg_write_byte(dev, ADXL345_RATE_REG, ADXL345_RATE_25HZ);
    rc = adxl345_reg_write_byte(dev, ADXL345_POWER_CTL_REG, ADXL345_ENABLE_MEASURE_BIT);
    return 0;
}

#define ADXL345_CONFIG_I2C(inst)                \
    {                           \
        .bus = {.i2c = I2C_DT_SPEC_INST_GET(inst)}, \
        .bus_is_ready = adxl345_bus_is_ready_i2c,   \
        .reg_access = adxl345_reg_access_i2c,       \
    }

#define ADXL345_DEFINE(inst)                                \
    static struct adxl345_dev_data adxl345_data_##inst;             \
                                            \
    static const struct adxl345_dev_config adxl345_config_##inst =                  \
        COND_CODE_1(DT_INST_ON_BUS(inst, spi), (ADXL345_CONFIG_SPI(inst)),      \
                (ADXL345_CONFIG_I2C(inst)));                                \
                                                                                        \
    SENSOR_DEVICE_DT_INST_DEFINE(inst, adxl345_init, NULL,              \
                  &adxl345_data_##inst, &adxl345_config_##inst, POST_KERNEL,\
                  CONFIG_SENSOR_INIT_PRIORITY, &adxl345_api_funcs);     \

DT_INST_FOREACH_STATUS_OKAY(ADXL345_DEFINE)

📌 중요: compatible DT_DRV_COMPAT(예: adi_adxl345) 값과 일치하는 device tree 내의 node(단, status="okay"인 경우만 해당)에 대해 device(struct device)를 초기화(instance화)하는 역할을 담당하는 macro이다.


지금까지 소개한 adxl345 device driver code는 아래 위치에 존재한다. 헐~ 😋

4.3 application code 작성
자, device driver가 준비되었으니, 이제 부터는 이를 이용하는 application code를 작성할 차례이다.

a) project 파일 생성
samples/sensor
$ cp -r lsm303dlhc adxl345

📌 적당한 예제 코드를 찾아 복사하거나, 일일이 새로 만든다.


<project 파일 구성>
project
|
+-- CMakeLists.txt
+-- README.rst
+-- prj.conf
+-- sample.yaml
+-- app.overlay
+-- src
       |
       + -- main.c
       + -- ... 
______________________________________

<CMakeLists.txt>
#
# Copyright (c) 2024, Chunghan Yi <chunghan.yi@gmail.com>
#
# SPDX-License-Identifier: Apache-2.0
#

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(adxl345)

FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

b) device overlay 파일 추가
Nucleo-L432KC 보드의 device tree 내용을 살펴 보면 다음과 같다.


...
...
[그림 4.7] STM32 Nucleo-L432KC device tree -boards/st/nucleo_l432kc/nucleo_l432kc.dts

이 내용 중 i2c1 controller의 하위 device로 adxl345를 추가하면 다음과 같다. 실제로 application code를 작성할 경우에는 직접 dts 파일을 수정하지 않고, overlay 형태로 작업을 하는게 관행이다.


<app.overlay>
/*
 * Copyright (c) 2024 Chunghan Yi<chunghan.yi@gmail.com>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

&i2c1 {
    status = "okay";
    adxl345: adxl345@53 {
        compatible = "adi,adxl345";
        reg = <0x53>;
        status = "okay";
    };
};

📌 adxl345의 i2c slave address가 0x53인 이유는 아래 posting(2장)을 참고하도록 하자.


c) prj.conf 파일 수정

<prj.conf>
CONFIG_STDOUT_CONSOLE=y
CONFIG_SENSOR=y
CONFIG_ADXL345=y

📌 CONFIG_I2C=y는 굳이 추가하지 않아도 자동으로 zephyr/build/zephyr/.config 파일에 추가된다.


d) main code 작성
application main code는 다음과 같다.

[그림 4.8] adxl345 application main.c code

(별 내용은 없지만) Source code는 아래 위치에 올려 두었다. 😘


4.4 build & run & debugging

지금까지 만든 내용을 실제로 target board에서 돌려 보도록 하자.

chyi@earth:~/zephyrproject/zephyr$ west build -b nucleo_l432kc samples/sensor/adxl345 --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/sensor/adxl345
-- 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: nucleo_l432kc, qualifiers: stm32l432xx
-- 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/st/nucleo_l432kc/nucleo_l432kc.dts
-- Found devicetree overlay: /home/chyi/zephyrproject/zephyr/samples/sensor/adxl345/app.overlay
-- 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
/home/chyi/zephyrproject/zephyr/build/zephyr/zephyr.dts:478.10-485.5: Warning (simple_bus_reg): /soc/clocks: missing or empty reg/ranges property
Parsing /home/chyi/zephyrproject/zephyr/Kconfig
Loaded configuration '/home/chyi/zephyrproject/zephyr/boards/st/nucleo_l432kc/nucleo_l432kc_defconfig'
Merged configuration '/home/chyi/zephyrproject/zephyr/samples/sensor/adxl345/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/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/zephyr-sdk-0.16.8/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc
-- Using ccache: /usr/bin/ccache
-- Configuring done
-- Generating done
-- Build files have been written to: /home/chyi/zephyrproject/zephyr/build
-- west build: building application
[1/138] Preparing syscall dependency handling

[2/138] Generating include/generated/zephyr/version.h
-- Zephyr version: 3.7.0-rc2 (/home/chyi/zephyrproject/zephyr), build: v3.7.0-rc2-417-g1e20f58c17c1
[138/138] Linking C executable zephyr/zephyr.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       20532 B       256 KB      7.83%
             RAM:        4672 B        64 KB      7.13%
        IDT_LIST:          0 GB        32 KB      0.00%
Generating files from /home/chyi/zephyrproject/zephyr/build/zephyr/zephyr.elf for board: nucleo_l432kc

chyi@earth:~/zephyrproject/zephyr$ west flash
-- west flash: rebuilding
ninja: no work to do.
-- west flash: using runner openocd
-- runners.openocd: Flashing file: /home/chyi/zephyrproject/zephyr/build/zephyr/zephyr.hex
Open On-Chip Debugger 0.11.0+dev-00728-gb6f95a16c (2024-05-29-12:13)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
srst_only separate srst_nogate srst_open_drain connect_deassert_srst

Info : clock speed 500 kHz
Info : STLINK V2J31M21 (API v2) VID:PID 0483:374B
Info : Target voltage: 3.245669
Warn : target stm32l4x.cpu examination failed
Error: jtag status contains invalid mode value - communication failure
Polling target stm32l4x.cpu failed, trying to reexamine
Info : [stm32l4x.cpu] Cortex-M4 r0p1 processor detected
Info : [stm32l4x.cpu] target has 0 breakpoints, 0 watchpoints
Error: The 'read_memory' command must be used after 'init'.
Error executing event examine-end on target stm32l4x.cpu:
/home/chyi/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/share/openocd/scripts/mem_helper.tcl:5: Error: 
in procedure 'mmw' called at file "/home/chyi/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/share/openocd/scripts/target/stm32l4x.cfg", line 97
in procedure 'mrw' called at file "/home/chyi/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/share/openocd/scripts/mem_helper.tcl", line 30
at file "/home/chyi/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/share/openocd/scripts/mem_helper.tcl", line 5
Info : Previous state query failed, trying to reconnect
Error: jtag status contains invalid mode value - communication failure
Polling target stm32l4x.cpu failed, trying to reexamine
Examination failed, GDB will be halted. Polling again in 100ms
Info : Previous state query failed, trying to reconnect
Error: jtag status contains invalid mode value - communication failure
Polling target stm32l4x.cpu failed, trying to reexamine
Examination failed, GDB will be halted. Polling again in 300ms
Info : starting gdb server for stm32l4x.cpu on 3333
Info : Listening on port 3333 for gdb connections
    TargetName         Type       Endian TapName            State       
--  ------------------ ---------- ------ ------------------ ------------
 0* stm32l4x.cpu       hla_target little stm32l4x.cpu       unknown

Info : Unable to match requested speed 500 kHz, using 480 kHz
Info : Unable to match requested speed 500 kHz, using 480 kHz
Error: read_memory: read at 0xe0042004 with width=32 and count=1 failed
Error executing event examine-end on target stm32l4x.cpu:
/home/chyi/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/share/openocd/scripts/mem_helper.tcl:5: Error: read_memory: failed to read memory
in procedure 'ocd_process_reset' 
in procedure 'ocd_process_reset_inner' called at file "embedded:startup.tcl", line 876
in procedure 'mmw' called at file "/home/chyi/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/share/openocd/scripts/target/stm32l4x.cfg", line 97
in procedure 'mrw' called at file "/home/chyi/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/share/openocd/scripts/mem_helper.tcl", line 30
at file "/home/chyi/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/share/openocd/scripts/mem_helper.tcl", line 5
Info : Previous state query failed, trying to reconnect
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x080011f8 msp: 0x20001200
Polling target stm32l4x.cpu failed, trying to reexamine
Info : [stm32l4x.cpu] Cortex-M4 r0p1 processor detected
Info : [stm32l4x.cpu] target has 6 breakpoints, 4 watchpoints
Info : device idcode = 0x10016435 (STM32L43/L44xx - Rev Z : 0x1001)
Info : RDP level 0 (0xAA)
Info : flash size = 256kbytes
Info : flash mode : single-bank
Info : Padding image section 0 at 0x08005034 with 4 bytes (bank write end alignment)
Warn : Adding extra erase range, 0x08005038 .. 0x080057ff
auto erase enabled
wrote 20536 bytes from file /home/chyi/zephyrproject/zephyr/build/zephyr/zephyr.hex in 0.604989s (33.149 KiB/s)

Info : Unable to match requested speed 500 kHz, using 480 kHz
Info : Unable to match requested speed 500 kHz, using 480 kHz
shutdown command invoked

Console 출력을 확인해 보니, 뭔가 값이 올라온다. 😙

[그림 4.9] minicom serial console 화면

📌 정확한 3축 센서 값을 출력해야 하나, driver 구동 과정에 대한 설명이 목적인 만큼 일단 pass~ 😋


지금까지 STM32 board, ADXL345 accelerometer 등을 가지고, zephyr RTOS programming하는 방법을 자세히 알아 보았다. 이어지는 내용은 (별도의 target board를 이용한) BLE에 관한 부분이다. 내용이 길어지는 관계로, 별도의 posting을 통해 내용 정리를 하는 것으로 한다. 😎


5. BLE Application - TODO
이번 장에서는 nRF52840 dongle board를 기반으로 zephyr BLE application을 만드는 과정을 소개하고자 한다.

To be continued ...





6. References
<zephyr>
[1] https://docs.zephyrproject.org/latest/
[2] Zephyr Project: RTOS Startup and Initialization Flow, David Leach, NXP Semiconductors ELC 2021 *****
[3] Zephyr RTOS Embedded C Programming Using Embedded RTOS POSIX AP, Apress, Andrew Eliasz


<stm32>
[4] https://www.st.com/en/evaluation-tools/nucleo-l432kc.html
[5] UM1956 STM32 Nucleo-32 boards (MB1180), STMicroelectronics
[6] STM32 Nucleo-32 boards(MB1180) User manual, STMicroelectronics
[7] https://www.st.com/en/evaluation-tools/nucleo-l432kc.html#cad-resources -> STM32 Nucleo (32 pins) schematics, STMicroelectronics

<linux>
[8] https://bootlin.com/pub/conferences/2012/lsm/arm-kernel-consolidation/arm-kernel-consolidation.pdf
[9] linux-kernel-slides.pdf, Bootlin
[10] https://dreamlog.tistory.com/336
[11] Linux Driver Development for Embedded Processors, Alberto Liberal de los Rios

<zephyr applications>
[12] https://www.we-online.com/components/media/o758413v410%20ANR034_Zephyr_Sensor_Driver.pdf
[13] https://static.linaro.org/connect/yvr18/presentations/yvr18-115.pdf
[14] https://blog.golioth.io/how-to-write-a-zephyr-device-driver-with-a-custom-api/
[15] https://bootlin.com/blog/zephyr-implementing-a-device-driver-for-a-sensor/ *****
[16] https://www.marcusfolkesson.se/blog/write-a-device-driver-for-zephyr-part1/


<adxl345 sensor>
[17] https://www.analog.com/en/products/adxl345.html#documentation
[18] https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf

[19] And, Google~


Slowboot