diff --git a/build.sh b/build.sh index ef4343a..c4d557b 100644 --- a/build.sh +++ b/build.sh @@ -15,6 +15,9 @@ nRF_boards=( thinknode_m6 rak_wismesh_tag ikoka_nano_30dbm + ikoka_stick_22dbm + ikoka_stick_30dbm + ikoka_stick_33dbm sensecap_solar xiao_nrf52840 lilygo_techo diff --git a/zephcore/CMakeLists.txt b/zephcore/CMakeLists.txt index 4ddeb1f..c60845c 100644 --- a/zephcore/CMakeLists.txt +++ b/zephcore/CMakeLists.txt @@ -11,117 +11,117 @@ cmake_minimum_required(VERSION 3.20.0) # Idempotent: stamp file tracks (patch hashes + target HEAD). Conflicts are fatal. # function(zephcore_apply_patches PATCH_DIR TARGET_DIR LABEL) - file(GLOB PATCH_FILES "${PATCH_DIR}/*.patch") - list(SORT PATCH_FILES) - if(NOT PATCH_FILES) - return() - endif() - # Stamp = hash(patch contents + target HEAD). Skip if unchanged. - execute_process( + file(GLOB PATCH_FILES "${PATCH_DIR}/*.patch") + list(SORT PATCH_FILES) + if(NOT PATCH_FILES) + return() + endif() + # Stamp = hash(patch contents + target HEAD). Skip if unchanged. + execute_process( COMMAND git rev-parse HEAD WORKING_DIRECTORY "${TARGET_DIR}" OUTPUT_VARIABLE _target_head OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET ) - set(_hash_input "${_target_head}") - foreach(_pf ${PATCH_FILES}) - file(MD5 "${_pf}" _h) - string(APPEND _hash_input "${_h}") - endforeach() - string(MD5 _stamp_hash "${_hash_input}") - set(_stamp "${TARGET_DIR}/.zephcore_${LABEL}.stamp") - if(EXISTS "${_stamp}") - file(READ "${_stamp}" _existing_hash) - string(STRIP "${_existing_hash}" _existing_hash) - if(_existing_hash STREQUAL _stamp_hash) - message(STATUS " [${LABEL}] Patches already applied, skipping.") - return() - endif() - message(STATUS " [${LABEL}] Patch set changed, re-applying...") + set(_hash_input "${_target_head}") + foreach(_pf ${PATCH_FILES}) + file(MD5 "${_pf}" _h) + string(APPEND _hash_input "${_h}") + endforeach() + string(MD5 _stamp_hash "${_hash_input}") + set(_stamp "${TARGET_DIR}/.zephcore_${LABEL}.stamp") + if(EXISTS "${_stamp}") + file(READ "${_stamp}" _existing_hash) + string(STRIP "${_existing_hash}" _existing_hash) + if(_existing_hash STREQUAL _stamp_hash) + message(STATUS " [${LABEL}] Patches already applied, skipping.") + return() endif() - foreach(PATCH_FILE ${PATCH_FILES}) - get_filename_component(PATCH_NAME ${PATCH_FILE} NAME) - set(PATCH_FILE_TO_APPLY "${PATCH_FILE}") - # Prepare LF/CRLF patch variants for robust matching across clones. - file(READ "${PATCH_FILE}" _patch_content) - string(REPLACE "\r\n" "\n" _patch_lf "${_patch_content}") - set(_patch_crlf "${_patch_lf}") - string(REPLACE "\n" "\r\n" _patch_crlf "${_patch_crlf}") - set(_tmp_patch_dir "${CMAKE_BINARY_DIR}/zephcore_patch_tmp/${LABEL}") - file(MAKE_DIRECTORY "${_tmp_patch_dir}") - set(_tmp_patch_lf "${_tmp_patch_dir}/${PATCH_NAME}.lf") - set(_tmp_patch_crlf "${_tmp_patch_dir}/${PATCH_NAME}.crlf") - file(WRITE "${_tmp_patch_lf}" "${_patch_lf}") - file(WRITE "${_tmp_patch_crlf}" "${_patch_crlf}") - # Dry-run check - execute_process( + message(STATUS " [${LABEL}] Patch set changed, re-applying...") + endif() + foreach(PATCH_FILE ${PATCH_FILES}) + get_filename_component(PATCH_NAME ${PATCH_FILE} NAME) + set(PATCH_FILE_TO_APPLY "${PATCH_FILE}") + # Prepare LF/CRLF patch variants for robust matching across clones. + file(READ "${PATCH_FILE}" _patch_content) + string(REPLACE "\r\n" "\n" _patch_lf "${_patch_content}") + set(_patch_crlf "${_patch_lf}") + string(REPLACE "\n" "\r\n" _patch_crlf "${_patch_crlf}") + set(_tmp_patch_dir "${CMAKE_BINARY_DIR}/zephcore_patch_tmp/${LABEL}") + file(MAKE_DIRECTORY "${_tmp_patch_dir}") + set(_tmp_patch_lf "${_tmp_patch_dir}/${PATCH_NAME}.lf") + set(_tmp_patch_crlf "${_tmp_patch_dir}/${PATCH_NAME}.crlf") + file(WRITE "${_tmp_patch_lf}" "${_patch_lf}") + file(WRITE "${_tmp_patch_crlf}" "${_patch_crlf}") + # Dry-run check + execute_process( COMMAND git apply --check "${PATCH_FILE_TO_APPLY}" WORKING_DIRECTORY "${TARGET_DIR}" RESULT_VARIABLE PATCH_CHECK ERROR_VARIABLE PATCH_ERR ) - # Retry check with explicit LF/CRLF variants to avoid EOL drift issues. - if(NOT PATCH_CHECK EQUAL 0) - execute_process( + # Retry check with explicit LF/CRLF variants to avoid EOL drift issues. + if(NOT PATCH_CHECK EQUAL 0) + execute_process( COMMAND git apply --check "${_tmp_patch_lf}" WORKING_DIRECTORY "${TARGET_DIR}" RESULT_VARIABLE PATCH_CHECK_LF ERROR_VARIABLE PATCH_ERR_LF ) - if(PATCH_CHECK_LF EQUAL 0) - set(PATCH_FILE_TO_APPLY "${_tmp_patch_lf}") - set(PATCH_CHECK 0) - else() - execute_process( + if(PATCH_CHECK_LF EQUAL 0) + set(PATCH_FILE_TO_APPLY "${_tmp_patch_lf}") + set(PATCH_CHECK 0) + else() + execute_process( COMMAND git apply --check "${_tmp_patch_crlf}" WORKING_DIRECTORY "${TARGET_DIR}" RESULT_VARIABLE PATCH_CHECK_CRLF ERROR_VARIABLE PATCH_ERR_CRLF ) - if(PATCH_CHECK_CRLF EQUAL 0) - set(PATCH_FILE_TO_APPLY "${_tmp_patch_crlf}") - set(PATCH_CHECK 0) - else() - # Keep the most relevant error from the last attempted variant. - set(PATCH_ERR "${PATCH_ERR_CRLF}") - endif() - endif() + if(PATCH_CHECK_CRLF EQUAL 0) + set(PATCH_FILE_TO_APPLY "${_tmp_patch_crlf}") + set(PATCH_CHECK 0) + else() + # Keep the most relevant error from the last attempted variant. + set(PATCH_ERR "${PATCH_ERR_CRLF}") endif() - # Stale patches from previous build: reset affected files, retry - if(NOT PATCH_CHECK EQUAL 0) - # Extract affected file paths from numstat - execute_process( + endif() + endif() + # Stale patches from previous build: reset affected files, retry + if(NOT PATCH_CHECK EQUAL 0) + # Extract affected file paths from numstat + execute_process( COMMAND git apply --numstat "${PATCH_FILE_TO_APPLY}" WORKING_DIRECTORY "${TARGET_DIR}" OUTPUT_VARIABLE PATCH_NUMSTAT ERROR_QUIET ) - # numstat format: "adds\tdels\tpath" - string(REGEX MATCHALL "[^\t\n]+\t[^\t\n]+\t[^\t\n]+" NUMSTAT_LINES "${PATCH_NUMSTAT}") - set(PATCH_PATHS "") - foreach(_line ${NUMSTAT_LINES}) - string(REGEX REPLACE "^[^\t]+\t[^\t]+\t" "" _path "${_line}") - list(APPEND PATCH_PATHS "${_path}") - endforeach() - if(PATCH_PATHS) - message(STATUS " [${LABEL}] Resetting stale files for: ${PATCH_NAME}") - execute_process( + # numstat format: "adds\tdels\tpath" + string(REGEX MATCHALL "[^\t\n]+\t[^\t\n]+\t[^\t\n]+" NUMSTAT_LINES "${PATCH_NUMSTAT}") + set(PATCH_PATHS "") + foreach(_line ${NUMSTAT_LINES}) + string(REGEX REPLACE "^[^\t]+\t[^\t]+\t" "" _path "${_line}") + list(APPEND PATCH_PATHS "${_path}") + endforeach() + if(PATCH_PATHS) + message(STATUS " [${LABEL}] Resetting stale files for: ${PATCH_NAME}") + execute_process( COMMAND git checkout -- ${PATCH_PATHS} WORKING_DIRECTORY "${TARGET_DIR}" ERROR_QUIET ) - # Retry dry-run - execute_process( + # Retry dry-run + execute_process( COMMAND git apply --check "${PATCH_FILE_TO_APPLY}" WORKING_DIRECTORY "${TARGET_DIR}" RESULT_VARIABLE PATCH_CHECK ERROR_VARIABLE PATCH_ERR ) - endif() - endif() - if(NOT PATCH_CHECK EQUAL 0) - message(FATAL_ERROR + endif() + endif() + if(NOT PATCH_CHECK EQUAL 0) + message(FATAL_ERROR "ZephCore patch FAILED to apply: ${PATCH_NAME}\n" "Target tree: ${TARGET_DIR}\n" "Error:\n${PATCH_ERR}\n" @@ -130,20 +130,20 @@ function(zephcore_apply_patches PATCH_DIR TARGET_DIR LABEL) " git diff -- # inspect current upstream\n" " # Regenerate: git diff -- > ${PATCH_FILE}\n" ) - endif() - # Apply - execute_process( + endif() + # Apply + execute_process( COMMAND git apply "${PATCH_FILE_TO_APPLY}" WORKING_DIRECTORY "${TARGET_DIR}" RESULT_VARIABLE APPLY_RESULT ERROR_VARIABLE APPLY_ERR ) - if(NOT APPLY_RESULT EQUAL 0) - message(FATAL_ERROR "git apply failed unexpectedly: ${PATCH_NAME}\n${APPLY_ERR}") - endif() - message(STATUS " [${LABEL}] Applied: ${PATCH_NAME}") - endforeach() - file(WRITE "${_stamp}" "${_stamp_hash}") + if(NOT APPLY_RESULT EQUAL 0) + message(FATAL_ERROR "git apply failed unexpectedly: ${PATCH_NAME}\n${APPLY_ERR}") + endif() + message(STATUS " [${LABEL}] Applied: ${PATCH_NAME}") + endforeach() + file(WRITE "${_stamp}" "${_stamp_hash}") endfunction() # Resolve target directories before find_package(Zephyr) @@ -152,8 +152,8 @@ get_filename_component(MODULES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../modules ABSOLU # Apply patches if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/patches/zephyr) - message(STATUS "Applying ZephCore patches to Zephyr...") - zephcore_apply_patches( + message(STATUS "Applying ZephCore patches to Zephyr...") + zephcore_apply_patches( "${CMAKE_CURRENT_SOURCE_DIR}/patches/zephyr" "${ZEPHYR_DIR}" "zephyr" @@ -162,8 +162,8 @@ endif() # Apply patches to loramac-node module if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/patches/modules/loramac-node) - message(STATUS "Applying ZephCore patches to loramac-node...") - zephcore_apply_patches( + message(STATUS "Applying ZephCore patches to loramac-node...") + zephcore_apply_patches( "${CMAKE_CURRENT_SOURCE_DIR}/patches/modules/loramac-node" "${MODULES_DIR}/lib/loramac-node" "loramac-node" @@ -172,15 +172,15 @@ endif() # Copy new files into Zephyr tree if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/patches/zephyr-new) - file(GLOB_RECURSE ZEPHCORE_NEW_FILES + file(GLOB_RECURSE ZEPHCORE_NEW_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/patches/zephyr-new ${CMAKE_CURRENT_SOURCE_DIR}/patches/zephyr-new/*) - foreach(REL_PATH ${ZEPHCORE_NEW_FILES}) - set(SRC_FILE ${CMAKE_CURRENT_SOURCE_DIR}/patches/zephyr-new/${REL_PATH}) - set(DST_FILE ${ZEPHYR_DIR}/${REL_PATH}) - configure_file(${SRC_FILE} ${DST_FILE} COPYONLY) - message(STATUS " [zephyr-new] ${REL_PATH}") - endforeach() + foreach(REL_PATH ${ZEPHCORE_NEW_FILES}) + set(SRC_FILE ${CMAKE_CURRENT_SOURCE_DIR}/patches/zephyr-new/${REL_PATH}) + set(DST_FILE ${ZEPHYR_DIR}/${REL_PATH}) + configure_file(${SRC_FILE} ${DST_FILE} COPYONLY) + message(STATUS " [zephyr-new] ${REL_PATH}") + endforeach() endif() # Custom board root: boards/// discovered via BOARD_ROOT @@ -194,18 +194,18 @@ list(APPEND BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) # Usage: zephcore_auto_pair_overlay("path/to/config.conf") # function(zephcore_auto_pair_overlay CONF_FILE) - if(NOT CONF_FILE OR NOT EXISTS "${CONF_FILE}") - return() - endif() - string(REPLACE ".conf" ".overlay" OVERLAY_FILE "${CONF_FILE}") - if(EXISTS "${OVERLAY_FILE}") - if(EXTRA_DTC_OVERLAY_FILE) - set(EXTRA_DTC_OVERLAY_FILE "${EXTRA_DTC_OVERLAY_FILE};${OVERLAY_FILE}" PARENT_SCOPE) - else() - set(EXTRA_DTC_OVERLAY_FILE "${OVERLAY_FILE}" PARENT_SCOPE) - endif() - message(STATUS " [auto-pair] ${CONF_FILE} → ${OVERLAY_FILE}") + if(NOT CONF_FILE OR NOT EXISTS "${CONF_FILE}") + return() + endif() + string(REPLACE ".conf" ".overlay" OVERLAY_FILE "${CONF_FILE}") + if(EXISTS "${OVERLAY_FILE}") + if(EXTRA_DTC_OVERLAY_FILE) + set(EXTRA_DTC_OVERLAY_FILE "${EXTRA_DTC_OVERLAY_FILE};${OVERLAY_FILE}" PARENT_SCOPE) + else() + set(EXTRA_DTC_OVERLAY_FILE "${OVERLAY_FILE}" PARENT_SCOPE) endif() + message(STATUS " [auto-pair] ${CONF_FILE} → ${OVERLAY_FILE}") + endif() endfunction() # ========== Board Configuration Hierarchy ========== @@ -213,29 +213,29 @@ endfunction() # Recover BOARD and EXTRA_CONF_FILE from sysbuild cache (needed before find_package) if(NOT BOARD AND DEFINED SYSBUILD_CACHE AND EXISTS "${SYSBUILD_CACHE}") - file(STRINGS "${SYSBUILD_CACHE}" _sysbuild_strings ENCODING UTF-8) - foreach(_str ${_sysbuild_strings}) - if(_str MATCHES "^BOARD:STRING=(.+)") - set(BOARD "${CMAKE_MATCH_1}") - endif() - # User-specified EXTRA_CONF_FILE (e.g., repeater.conf) is passed to sysbuild, - # not the app. Recover it so our auto-include logic can detect it. - if(_str MATCHES "^EXTRA_CONF_FILE:UNINITIALIZED=(.+)") - set(_SYSBUILD_USER_CONF "${CMAKE_MATCH_1}") - endif() - endforeach() - if(BOARD) - message(STATUS "Sysbuild: BOARD=${BOARD}") + file(STRINGS "${SYSBUILD_CACHE}" _sysbuild_strings ENCODING UTF-8) + foreach(_str ${_sysbuild_strings}) + if(_str MATCHES "^BOARD:STRING=(.+)") + set(BOARD "${CMAKE_MATCH_1}") endif() - if(_SYSBUILD_USER_CONF) - # Merge user extras into EXTRA_CONF_FILE so downstream checks see them - if(EXTRA_CONF_FILE) - set(EXTRA_CONF_FILE "${EXTRA_CONF_FILE};${_SYSBUILD_USER_CONF}") - else() - set(EXTRA_CONF_FILE "${_SYSBUILD_USER_CONF}") - endif() - message(STATUS "Sysbuild: user EXTRA_CONF_FILE=${_SYSBUILD_USER_CONF}") + # User-specified EXTRA_CONF_FILE (e.g., repeater.conf) is passed to sysbuild, + # not the app. Recover it so our auto-include logic can detect it. + if(_str MATCHES "^EXTRA_CONF_FILE:UNINITIALIZED=(.+)") + set(_SYSBUILD_USER_CONF "${CMAKE_MATCH_1}") endif() + endforeach() + if(BOARD) + message(STATUS "Sysbuild: BOARD=${BOARD}") + endif() + if(_SYSBUILD_USER_CONF) + # Merge user extras into EXTRA_CONF_FILE so downstream checks see them + if(EXTRA_CONF_FILE) + set(EXTRA_CONF_FILE "${EXTRA_CONF_FILE};${_SYSBUILD_USER_CONF}") + else() + set(EXTRA_CONF_FILE "${_SYSBUILD_USER_CONF}") + endif() + message(STATUS "Sysbuild: user EXTRA_CONF_FILE=${_SYSBUILD_USER_CONF}") + endif() endif() # Build common config file list based on detected platform @@ -244,24 +244,24 @@ set(ZEPHCORE_COMMON_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/zephcore_com # Detect platform from board name and add platform-specific common config # For boards with qualifiers like "wio_tracker_l1/nrf52840", check both the full BOARD # string and the BOARD_QUALIFIERS which contains just the qualifier (e.g., "nrf52840") -if(BOARD MATCHES ".*nrf52.*" OR BOARD MATCHES "rak4631" OR BOARD MATCHES "rak_wismesh_tag" OR BOARD MATCHES "rak3401_1watt" OR BOARD MATCHES "wio_tracker" OR BOARD MATCHES "ikoka_nano" OR BOARD MATCHES "t1000_e" OR BOARD MATCHES "thinknode_m1" OR BOARD MATCHES "thinknode_m3" OR BOARD MATCHES "thinknode_m6" OR BOARD MATCHES "promicro_lr2021" OR BOARD MATCHES "promicro_sx1262" OR BOARD MATCHES "sensecap_solar" OR BOARD MATCHES "xiao_nrf52840" OR BOARD MATCHES "lilygo_techo" OR BOARD MATCHES "heltec_t114") - set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf52_common.conf") +if(BOARD MATCHES ".*nrf52.*" OR BOARD MATCHES "rak4631" OR BOARD MATCHES "rak_wismesh_tag" OR BOARD MATCHES "rak3401_1watt" OR BOARD MATCHES "wio_tracker" OR BOARD MATCHES "ikoka_nano" OR BOARD MATCHES "ikoka_stick" OR BOARD MATCHES "t1000_e" OR BOARD MATCHES "thinknode_m1" OR BOARD MATCHES "thinknode_m3" OR BOARD MATCHES "thinknode_m6" OR BOARD MATCHES "promicro_lr2021" OR BOARD MATCHES "promicro_sx1262" OR BOARD MATCHES "sensecap_solar" OR BOARD MATCHES "xiao_nrf52840" OR BOARD MATCHES "lilygo_techo" OR BOARD MATCHES "heltec_t114") + set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf52_common.conf") elseif(DEFINED BOARD_QUALIFIERS AND BOARD_QUALIFIERS MATCHES ".*nrf52.*") - set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf52_common.conf") + set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf52_common.conf") elseif(BOARD MATCHES ".*esp32.*" OR BOARD MATCHES "station_g2" OR BOARD MATCHES "lilygo_tlora_c6" OR BOARD MATCHES "heltec_wifi_lora32_v3" OR BOARD MATCHES "heltec_wifi_lora32_v4") - set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/esp32_common.conf") + set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/esp32_common.conf") elseif(DEFINED BOARD_QUALIFIERS AND BOARD_QUALIFIERS MATCHES ".*esp32.*") - set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/esp32_common.conf") + set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/esp32_common.conf") elseif(BOARD MATCHES ".*nrf54l.*") - set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf54l_common.conf") + set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf54l_common.conf") elseif(DEFINED BOARD_QUALIFIERS AND BOARD_QUALIFIERS MATCHES ".*nrf54l.*") - set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf54l_common.conf") + set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf54l_common.conf") elseif(BOARD MATCHES ".*mg24.*" OR BOARD MATCHES ".*efr32.*") - set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/mg24_common.conf") + set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/mg24_common.conf") elseif(DEFINED BOARD_QUALIFIERS AND BOARD_QUALIFIERS MATCHES ".*mg24.*") - set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/mg24_common.conf") + set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/mg24_common.conf") else() - set(ZEPHCORE_PLATFORM_CONF "") + set(ZEPHCORE_PLATFORM_CONF "") endif() # Check for board-specific config in new folder structure @@ -271,49 +271,49 @@ endif() # For Zephyr boards with qualifiers (e.g., rak4631/nrf52840), BOARD contains base/qualifier # We extract just the base board name for folder lookup if(BOARD) - string(REPLACE "/" ";" BOARD_PARTS "${BOARD}") - list(GET BOARD_PARTS 0 BOARD_BASE) + string(REPLACE "/" ";" BOARD_PARTS "${BOARD}") + list(GET BOARD_PARTS 0 BOARD_BASE) else() - set(BOARD_BASE "") + set(BOARD_BASE "") endif() # Search for board.conf in possible locations set(ZEPHCORE_BOARD_CONF "") file(GLOB_RECURSE BOARD_CONF_CANDIDATES "${CMAKE_CURRENT_SOURCE_DIR}/boards/*/${BOARD_BASE}/board.conf") if(BOARD_CONF_CANDIDATES) - list(GET BOARD_CONF_CANDIDATES 0 ZEPHCORE_BOARD_CONF) + list(GET BOARD_CONF_CANDIDATES 0 ZEPHCORE_BOARD_CONF) endif() # Fallback: direct path without vendor subdirectory if(NOT ZEPHCORE_BOARD_CONF AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_BASE}/board.conf") - set(ZEPHCORE_BOARD_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_BASE}/board.conf") + set(ZEPHCORE_BOARD_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_BASE}/board.conf") endif() # Build the EXTRA_CONF_FILE list (append to any user-provided extras) # Auto-pair each .conf with a same-named .overlay if it exists. set(ZEPHCORE_CONF_FILES "") if(EXISTS ${ZEPHCORE_COMMON_CONF}) - list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_COMMON_CONF}) - zephcore_auto_pair_overlay("${ZEPHCORE_COMMON_CONF}") + list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_COMMON_CONF}) + zephcore_auto_pair_overlay("${ZEPHCORE_COMMON_CONF}") endif() if(ZEPHCORE_PLATFORM_CONF AND EXISTS ${ZEPHCORE_PLATFORM_CONF}) - list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_PLATFORM_CONF}) - zephcore_auto_pair_overlay("${ZEPHCORE_PLATFORM_CONF}") + list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_PLATFORM_CONF}) + zephcore_auto_pair_overlay("${ZEPHCORE_PLATFORM_CONF}") endif() if(ZEPHCORE_BOARD_CONF AND EXISTS ${ZEPHCORE_BOARD_CONF}) - list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_BOARD_CONF}) - zephcore_auto_pair_overlay("${ZEPHCORE_BOARD_CONF}") + list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_BOARD_CONF}) + zephcore_auto_pair_overlay("${ZEPHCORE_BOARD_CONF}") endif() # Include logging tuning only for debug builds (when prod.conf is NOT active). # This avoids Kconfig warnings from log settings that depend on CONFIG_LOG. set(ZEPHCORE_LOGGING_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/logging.conf") if(EXTRA_CONF_FILE MATCHES "prod\\.conf") - message(STATUS " Production build — skipping logging.conf") + message(STATUS " Production build — skipping logging.conf") else() - if(EXISTS ${ZEPHCORE_LOGGING_CONF}) - list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_LOGGING_CONF}) - zephcore_auto_pair_overlay("${ZEPHCORE_LOGGING_CONF}") - endif() + if(EXISTS ${ZEPHCORE_LOGGING_CONF}) + list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_LOGGING_CONF}) + zephcore_auto_pair_overlay("${ZEPHCORE_LOGGING_CONF}") + endif() endif() # Auto-include WiFi OTA for ESP32 repeater builds. @@ -321,31 +321,31 @@ endif() # Requires --sysbuild to build MCUboot alongside the app. # Non-ESP32 builds and companion builds are unaffected. if(ZEPHCORE_PLATFORM_CONF MATCHES "esp32_common" AND EXTRA_CONF_FILE MATCHES "repeater") - set(ZEPHCORE_WIFI_OTA_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/wifi_ota.conf") - if(EXISTS ${ZEPHCORE_WIFI_OTA_CONF}) - list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_WIFI_OTA_CONF}) - zephcore_auto_pair_overlay("${ZEPHCORE_WIFI_OTA_CONF}") - message(STATUS " WiFi OTA: auto-enabled (ESP32 repeater)") - endif() + set(ZEPHCORE_WIFI_OTA_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/wifi_ota.conf") + if(EXISTS ${ZEPHCORE_WIFI_OTA_CONF}) + list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_WIFI_OTA_CONF}) + zephcore_auto_pair_overlay("${ZEPHCORE_WIFI_OTA_CONF}") + message(STATUS " WiFi OTA: auto-enabled (ESP32 repeater)") + endif() endif() # Append to EXTRA_CONF_FILE — auto-generated configs first, user extras last. # User-specified extras (repeater.conf, prod.conf) MUST come after the auto chain # so they can override settings (e.g. CONFIG_BT=n in repeater.conf). if(ZEPHCORE_CONF_FILES) - if(EXTRA_CONF_FILE) - set(EXTRA_CONF_FILE "${ZEPHCORE_CONF_FILES};${EXTRA_CONF_FILE}" CACHE STRING "" FORCE) - else() - set(EXTRA_CONF_FILE "${ZEPHCORE_CONF_FILES}" CACHE STRING "" FORCE) - endif() + if(EXTRA_CONF_FILE) + set(EXTRA_CONF_FILE "${ZEPHCORE_CONF_FILES};${EXTRA_CONF_FILE}" CACHE STRING "" FORCE) + else() + set(EXTRA_CONF_FILE "${ZEPHCORE_CONF_FILES}" CACHE STRING "" FORCE) + endif() endif() # Auto-pair user-supplied EXTRA_CONF_FILE entries with same-named .overlay files. # This allows repeater.conf + repeater.overlay, prod.conf + prod.overlay, etc. if(EXTRA_CONF_FILE) - foreach(_conf IN LISTS EXTRA_CONF_FILE) - zephcore_auto_pair_overlay("${_conf}") - endforeach() + foreach(_conf IN LISTS EXTRA_CONF_FILE) + zephcore_auto_pair_overlay("${_conf}") + endforeach() endif() # ========== DTC Overlay Hierarchy ========== @@ -353,20 +353,20 @@ endif() set(ZEPHCORE_BOARD_OVERLAY "") file(GLOB_RECURSE BOARD_OVERLAY_CANDIDATES "${CMAKE_CURRENT_SOURCE_DIR}/boards/*/${BOARD_BASE}/board.overlay") if(BOARD_OVERLAY_CANDIDATES) - list(GET BOARD_OVERLAY_CANDIDATES 0 ZEPHCORE_BOARD_OVERLAY) + list(GET BOARD_OVERLAY_CANDIDATES 0 ZEPHCORE_BOARD_OVERLAY) endif() # Fallback: direct path without vendor subdirectory if(NOT ZEPHCORE_BOARD_OVERLAY AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_BASE}/board.overlay") - set(ZEPHCORE_BOARD_OVERLAY "${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_BASE}/board.overlay") + set(ZEPHCORE_BOARD_OVERLAY "${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_BASE}/board.overlay") endif() # Append to EXTRA_DTC_OVERLAY_FILE (preserves any user-specified extras) if(ZEPHCORE_BOARD_OVERLAY AND EXISTS ${ZEPHCORE_BOARD_OVERLAY}) - if(EXTRA_DTC_OVERLAY_FILE) - set(EXTRA_DTC_OVERLAY_FILE "${EXTRA_DTC_OVERLAY_FILE};${ZEPHCORE_BOARD_OVERLAY}" CACHE STRING "" FORCE) - else() - set(EXTRA_DTC_OVERLAY_FILE "${ZEPHCORE_BOARD_OVERLAY}" CACHE STRING "" FORCE) - endif() + if(EXTRA_DTC_OVERLAY_FILE) + set(EXTRA_DTC_OVERLAY_FILE "${EXTRA_DTC_OVERLAY_FILE};${ZEPHCORE_BOARD_OVERLAY}" CACHE STRING "" FORCE) + else() + set(EXTRA_DTC_OVERLAY_FILE "${ZEPHCORE_BOARD_OVERLAY}" CACHE STRING "" FORCE) + endif() endif() diff --git a/zephcore/boards/nrf52840/ikoka_stick_22dbm/Kconfig.ikoka_stick_22dbm b/zephcore/boards/nrf52840/ikoka_stick_22dbm/Kconfig.ikoka_stick_22dbm new file mode 100644 index 0000000..31d3bf8 --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_22dbm/Kconfig.ikoka_stick_22dbm @@ -0,0 +1,5 @@ +# Ikoka Stick 22dBm board configuration +# DC/DC converter enabled via DTS: ®1 { regulator-initial-mode = ; } + +config BOARD_IKOKA_STICK_22DBM + select SOC_NRF52840_QIAA diff --git a/zephcore/boards/nrf52840/ikoka_stick_22dbm/board.conf b/zephcore/boards/nrf52840/ikoka_stick_22dbm/board.conf new file mode 100644 index 0000000..d2b93ed --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_22dbm/board.conf @@ -0,0 +1,37 @@ +# Ikoka Stick 22dBm (XIAO nRF52840 + E22-900M22S) +# Board-specific configuration +# +# Hardware: +# - XIAO nRF52840 base board (socketed via pin headers) +# - E22-900M22S LoRa module (bare SX1262, no external PA, 22 dBm output) +# - User button on D0 (P0.02) +# - Optional SSD1306 0.96" OLED @ 0x3C on I2C1 (auto-detected) +# - Battery ADC on AIN7 (P0.31), enable on P0.14 +# - LEDs: RED=P0.26, BLUE=P0.06, GREEN=P0.30 +# +# Pin Mapping (XIAO D-pins to nRF52840 GPIOs): +# D0 = P0.02 (User button) D4 = P0.04 (SX1262 NSS) D8 = P1.13 (SCK) +# D1 = P0.03 (SX1262 DIO1) D5 = P0.05 (PA RXEN) D9 = P1.14 (MISO) +# D2 = P0.28 (SX1262 RESET) D6 = P1.11 (I2C SDA) D10 = P1.15 (MOSI) +# D3 = P0.29 (SX1262 BUSY) D7 = P1.12 (I2C SCL) +# +# LoRa Module E22-900M22S Notes: +# - Bare SX1262. No external PA stage; SX1262 drives the antenna directly +# up to +22 dBm. No input-power restriction beyond the SX1262's own spec. +# - DIO2 drives the internal TX/RX antenna switch (dio2-tx-enable). +# - RXEN (P0.05) is still wired on the PCB (shared trace across all three +# Stick variants) but has no external-PA function on this module — the +# line toggles are harmless. + +# Board identification (matches Arduino variant name) +CONFIG_ZEPHCORE_BOARD_NAME="Ikoka Stick-E22-22dBm (Xiao_nrf52)" + +# Device Information Service model name +CONFIG_BT_DIS_MODEL_NUMBER_STR="Ikoka Stick 22dBm" + +# SoftDevice firmware ID (XIAO nRF52840 uses S140 v7.3.0) +CONFIG_ZEPHCORE_SD_FWID=0x0123 + +# SX1262 TX power caps — full rated output (22 dBm) is safe on the bare module. +CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=22 +CONFIG_ZEPHCORE_MAX_TX_POWER_DBM=22 diff --git a/zephcore/boards/nrf52840/ikoka_stick_22dbm/board.yml b/zephcore/boards/nrf52840/ikoka_stick_22dbm/board.yml new file mode 100644 index 0000000..9414865 --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_22dbm/board.yml @@ -0,0 +1,6 @@ +board: + name: ikoka_stick_22dbm + full_name: Ikoka Stick 22dBm (XIAO nRF52840 + E22-900M22S) + vendor: ikoka + socs: + - name: nrf52840 diff --git a/zephcore/boards/nrf52840/ikoka_stick_22dbm/ikoka_stick_22dbm-pinctrl.dtsi b/zephcore/boards/nrf52840/ikoka_stick_22dbm/ikoka_stick_22dbm-pinctrl.dtsi new file mode 100644 index 0000000..89bb3d0 --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_22dbm/ikoka_stick_22dbm-pinctrl.dtsi @@ -0,0 +1,99 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Ikoka Stick 22dBm pin control - based on XIAO nRF52840 + * + * Pin Mapping (XIAO D-pins to nRF52840 GPIOs): + * D0 = P0.02 (User button) D4 = P0.04 (SX1262 NSS) D8 = P1.13 (SCK) + * D1 = P0.03 (SX1262 DIO1) D5 = P0.05 (PA RXEN) D9 = P1.14 (MISO) + * D2 = P0.28 (SX1262 RESET) D6 = P1.11 (I2C SDA) D10 = P1.15 (MOSI) + * D3 = P0.29 (SX1262 BUSY) D7 = P1.12 (I2C SCL) + * + * UART0 is NOT wired on this board (console is USB-CDC). The pinctrl + * nodes below are kept so that upstream nRF52840 SoC defaults that + * reference &uart0_default/&uart0_sleep still resolve. &uart0 itself + * is disabled in the .dts to prevent the driver from driving pins. + * Dummy pins P0.08 / P0.09 are not brought out to a connector on XIAO. + */ + +&pinctrl { + /* UART0 placeholder — driver is disabled, pins are not brought out */ + uart0_default: uart0_default { + group1 { + psels = ; + }; + group2 { + psels = ; + bias-pull-up; + }; + }; + + uart0_sleep: uart0_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + + /* I2C1 on D6/D7 for optional SSD1306 OLED and any external sensors */ + i2c1_default: i2c1_default { + group1 { + psels = , /* D6 = P1.11 */ + ; /* D7 = P1.12 */ + }; + }; + + i2c1_sleep: i2c1_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + + /* SPI2 for LoRa on D8/D9/D10 */ + spi2_default: spi2_default { + group1 { + psels = , /* D8 = P1.13 */ + , /* D10 = P1.15 */ + ; /* D9 = P1.14 */ + }; + }; + + spi2_sleep: spi2_sleep { + group1 { + psels = , + , + ; + low-power-enable; + }; + }; + + /* QSPI for onboard flash (disabled on XIAO nRF52840 — kept for symmetry) */ + qspi_default: qspi_default { + group1 { + psels = , + , + , + , + , + ; + }; + }; + + qspi_sleep: qspi_sleep { + group1 { + psels = , + , + , + , + ; + low-power-enable; + }; + group2 { + psels = ; + low-power-enable; + bias-pull-up; + }; + }; +}; diff --git a/zephcore/boards/nrf52840/ikoka_stick_22dbm/ikoka_stick_22dbm.dts b/zephcore/boards/nrf52840/ikoka_stick_22dbm/ikoka_stick_22dbm.dts new file mode 100644 index 0000000..99afa06 --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_22dbm/ikoka_stick_22dbm.dts @@ -0,0 +1,258 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Ikoka Stick 22dBm - XIAO nRF52840 + E22-900M22S LoRa module + * + * Reference: https://ndoo.sg/projects:amateur_radio:meshtastic:diy_devices:ikoka_stick + * + * E22-900M22S LoRa Module: + * - Bare SX1262 (no external PA) — up to 22 dBm directly from the chip. + * - RXEN (P0.05 / D5) is wired on the PCB (shared trace with the 30/33 dBm + * variants) but has no PA-switching function on this module; toggling + * it is harmless. + * - DIO2 as RF switch, TCXO 1.8V + * + * Pin Mapping (XIAO D-pins to nRF52840 GPIOs): + * D0 = P0.02 (User button) D4 = P0.04 (SX1262 NSS) D8 = P1.13 (SCK) + * D1 = P0.03 (SX1262 DIO1) D5 = P0.05 (PA RXEN) D9 = P1.14 (MISO) + * D2 = P0.28 (SX1262 RESET) D6 = P1.11 (I2C SDA) D10 = P1.15 (MOSI) + * D3 = P0.29 (SX1262 BUSY) D7 = P1.12 (I2C SCL) + * + * Peripherals: + * - User button on D0 with long-press (deep sleep) + multi-tap actions + * - Optional SSD1306 0.96" OLED @ 0x3C on I2C1 (auto-detected at boot) + * - Battery ADC on AIN7 (P0.31), enable on P0.14 + * - LEDs: RED=P0.26/D11, BLUE=P0.06/D12, GREEN=P0.30/D13 + */ + +/dts-v1/; +#include +#include "ikoka_stick_22dbm-pinctrl.dtsi" +#include +#include + +/ { + model = "Ikoka Stick 22dBm"; + compatible = "ikoka,stick-22dbm"; + + chosen { + zephyr,sram = &sram0; + zephyr,flash = &flash0; + zephyr,code-partition = &code_partition; + zephyr,console = &cdc_acm_uart; + zephyr,shell-uart = &cdc_acm_uart; + zephyr,display = &ssd1306; + }; + + aliases { + lora0 = &lora; + led0 = &led_red; + led1 = &led_green; + led2 = &led_blue; + watchdog0 = &wdt0; + }; + + /* Status LEDs - active LOW on XIAO */ + leds { + compatible = "gpio-leds"; + led_red: led_0 { + gpios = <&gpio0 26 GPIO_ACTIVE_LOW>; + label = "Red LED"; + }; + led_green: led_1 { + gpios = <&gpio0 30 GPIO_ACTIVE_LOW>; + label = "Green LED"; + }; + led_blue: led_2 { + gpios = <&gpio0 6 GPIO_ACTIVE_LOW>; + label = "Blue LED"; + }; + }; + + /* User button on D0 (P0.02), active LOW with internal pull-up. + * Matches the pinout of the Seeed XIAO nRF52840 base board. */ + buttons: buttons { + compatible = "gpio-keys"; + + user_button: button_0 { + gpios = <&gpio0 2 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + zephyr,code = ; + label = "User Button"; + }; + }; + + /* Long-press = deep sleep (≥1 s), short = feeds into multi-tap */ + user_btn_longpress { + compatible = "zephyr,input-longpress"; + input = <&buttons>; + input-codes = ; + short-codes = ; + long-codes = ; + long-delay-ms = <1000>; + }; + + /* Multi-tap — standard ZephCore mapping: + * 1 tap → page next 2 taps → flood advert + * 3 taps → buzzer mute 4 taps → GPS toggle + * 5 taps → LED heartbeat */ + user_btn_multitap { + compatible = "zephcore,input-multi-tap"; + input-codes = ; + tap-codes = ; + tap-delay-ms = <400>; + }; + + /* Battery ADC enable - P0.14 must be LOW to read battery */ + vbat_enable: vbat-enable { + compatible = "regulator-fixed"; + regulator-name = "vbat-enable"; + enable-gpios = <&gpio0 14 GPIO_ACTIVE_LOW>; + regulator-boot-on; + }; + + /* Battery ADC channel */ + zephyr,user { + io-channels = <&adc 7>; + vbat-mv-multiplier = <10650>; /* 1M/510k divider (2.96x), 3.6V ref */ + }; +}; + +®0 { + status = "okay"; +}; + +®1 { + regulator-initial-mode = ; +}; + +&adc { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@7 { + reg = <7>; + zephyr,gain = "ADC_GAIN_1_6"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + zephyr,resolution = <12>; + }; +}; + +&uicr { + gpio-as-nreset; +}; + +&gpiote { + status = "okay"; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +/* UART0 is not wired on this board — console/shell use USB-CDC */ +&uart0 { + status = "disabled"; +}; + +/* I2C1 on D6/D7 for optional SSD1306 OLED and any external sensors. + * + * Uses TWIM (not TWI) so the driver can issue SSD1306 page writes + * (1 cmd + 128 data = 129 bytes) in a single transaction — the + * concat-buf-size of 256 covers the full page. + */ +&i2c1 { + compatible = "nordic,nrf-twim"; + status = "okay"; + pinctrl-0 = <&i2c1_default>; + pinctrl-1 = <&i2c1_sleep>; + pinctrl-names = "default", "sleep"; + zephyr,concat-buf-size = <256>; + + /* Optional SSD1306 0.96" OLED at 0x3C. + * + * The Stick's OLED is optional per the ndoo.sg design. If the + * physical part is absent, zephcore's display helper returns + * -ENODEV and the firmware continues without a display — + * leaving the node declared unconditionally is safe. */ + ssd1306: ssd1306@3c { + compatible = "solomon,ssd1306"; + reg = <0x3c>; + width = <128>; + height = <64>; + segment-offset = <0>; + page-offset = <0>; + display-offset = <0>; + multiplex-ratio = <63>; + segment-remap; + com-invdir; + inversion-on; + prechargep = <0x22>; + }; + + /* All supported environment & power sensors — auto-detected at runtime */ + #include "../../common/sensors-i2c.dtsi" +}; + +/* SPI2 for LoRa module */ +&spi2 { + compatible = "nordic,nrf-spi"; + status = "okay"; + pinctrl-0 = <&spi2_default>; + pinctrl-1 = <&spi2_sleep>; + pinctrl-names = "default", "sleep"; + cs-gpios = <&gpio0 4 GPIO_ACTIVE_LOW>; /* D4 = P0.04 NSS */ + + lora: lora@0 { + compatible = "semtech,sx1262"; + reg = <0>; + spi-max-frequency = <8000000>; + + /* LoRa control pins */ + reset-gpios = <&gpio0 28 GPIO_ACTIVE_LOW>; /* D2 = P0.28 */ + busy-gpios = <&gpio0 29 GPIO_ACTIVE_HIGH>; /* D3 = P0.29 */ + dio1-gpios = <&gpio0 3 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; /* D1 = P0.03 */ + + /* RXEN trace (no PA on this module; line is harmless to toggle) */ + rx-enable-gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>; /* D5 = P0.05 RXEN */ + + /* Use DIO2 to drive TX enable on the internal RF switch */ + dio2-tx-enable; + + /* TCXO voltage 1.8V */ + dio3-tcxo-voltage = ; + tcxo-power-startup-delay-ms = <10>; + + /* Enable RX boosted mode for better sensitivity */ + rx-boosted; + }; +}; + +/* QSPI Flash - disabled, matching the 30 dBm sibling. XIAO nRF52840's + * onboard P25Q16H is not used in this firmware; the internal nRF52840 + * flash provides storage. Re-enable with status = "okay" if needed. */ +&qspi { + status = "disabled"; +}; + +/* USB CDC for console/CLI */ +zephyr_udc0: &usbd { + compatible = "nordic,nrf-usbd"; + status = "okay"; + + cdc_acm_uart: cdc_acm_uart { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +/* Arduino MeshCore compatible partition layout (SoftDevice v7) */ +#include "../../common/nrf52_partitions_sdv7.dtsi" + +/* Mark the 'buttons' node as a System OFF wakeup source. + * Only valid when the board has a `buttons` label (i.e. a user button). */ +#include "../../common/nrf52_wakeup.dtsi" diff --git a/zephcore/boards/nrf52840/ikoka_stick_22dbm/ikoka_stick_22dbm_defconfig b/zephcore/boards/nrf52840/ikoka_stick_22dbm/ikoka_stick_22dbm_defconfig new file mode 100644 index 0000000..c53d57b --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_22dbm/ikoka_stick_22dbm_defconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: Apache-2.0 +# Ikoka Stick 22dBm default configuration + +# Enable MPU +CONFIG_ARM_MPU=y +CONFIG_HW_STACK_PROTECTION=y + +# Enable GPIO +CONFIG_GPIO=y + +# Enable console on USB CDC (new stack) +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y +CONFIG_SERIAL=y +CONFIG_UART_LINE_CTRL=y + +# Enable flash/NVS storage +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_NVS=y + +# Pinctrl +CONFIG_PINCTRL=y + +# SPI for LoRa +CONFIG_SPI=y + +# ADC for battery +CONFIG_ADC=y diff --git a/zephcore/boards/nrf52840/ikoka_stick_30dbm/Kconfig.ikoka_stick_30dbm b/zephcore/boards/nrf52840/ikoka_stick_30dbm/Kconfig.ikoka_stick_30dbm new file mode 100644 index 0000000..51b72a4 --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_30dbm/Kconfig.ikoka_stick_30dbm @@ -0,0 +1,5 @@ +# Ikoka Stick 30dBm board configuration +# DC/DC converter enabled via DTS: ®1 { regulator-initial-mode = ; } + +config BOARD_IKOKA_STICK_30DBM + select SOC_NRF52840_QIAA diff --git a/zephcore/boards/nrf52840/ikoka_stick_30dbm/board.conf b/zephcore/boards/nrf52840/ikoka_stick_30dbm/board.conf new file mode 100644 index 0000000..76f938c --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_30dbm/board.conf @@ -0,0 +1,36 @@ +# Ikoka Stick 30dBm (XIAO nRF52840 + E22-900M30S) +# Board-specific configuration +# +# Hardware: +# - XIAO nRF52840 base board (socketed via pin headers) +# - E22-900M30S LoRa module (SX1262 + external PA, 30 dBm / 1 W output) +# - User button on D0 (P0.02) +# - Optional SSD1306 0.96" OLED @ 0x3C on I2C1 (auto-detected) +# - Battery ADC on AIN7 (P0.31), enable on P0.14 +# - LEDs: RED=P0.26, BLUE=P0.06, GREEN=P0.30 +# +# Pin Mapping (XIAO D-pins to nRF52840 GPIOs): +# D0 = P0.02 (User button) D4 = P0.04 (SX1262 NSS) D8 = P1.13 (SCK) +# D1 = P0.03 (SX1262 DIO1) D5 = P0.05 (PA RXEN) D9 = P1.14 (MISO) +# D2 = P0.28 (SX1262 RESET) D6 = P1.11 (I2C SDA) D10 = P1.15 (MOSI) +# D3 = P0.29 (SX1262 BUSY) D7 = P1.12 (I2C SCL) +# +# LoRa Module E22-900M30S Notes: +# - Built-in ~10 dB PA with external RXEN control on P0.05 (HIGH=RX, LOW=TX). +# - DIO2 drives the internal TX/RX antenna switch (dio2-tx-enable). +# - SX1262 input power should not exceed 20 dBm (datasheet-typical drive +# for 30 dBm output). The caps below enforce this in firmware. + +# Board identification (matches Arduino variant name) +CONFIG_ZEPHCORE_BOARD_NAME="Ikoka Stick-E22-30dBm (Xiao_nrf52)" + +# Device Information Service model name +CONFIG_BT_DIS_MODEL_NUMBER_STR="Ikoka Stick 30dBm" + +# SoftDevice firmware ID (XIAO nRF52840 uses S140 v7.3.0) +CONFIG_ZEPHCORE_SD_FWID=0x0123 + +# SX1262 TX power caps — 20 dBm input yields ~30 dBm output through the +# E22-900M30S PA. +CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=20 +CONFIG_ZEPHCORE_MAX_TX_POWER_DBM=20 diff --git a/zephcore/boards/nrf52840/ikoka_stick_30dbm/board.yml b/zephcore/boards/nrf52840/ikoka_stick_30dbm/board.yml new file mode 100644 index 0000000..a81bd1d --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_30dbm/board.yml @@ -0,0 +1,6 @@ +board: + name: ikoka_stick_30dbm + full_name: Ikoka Stick 30dBm (XIAO nRF52840 + E22-900M30S) + vendor: ikoka + socs: + - name: nrf52840 diff --git a/zephcore/boards/nrf52840/ikoka_stick_30dbm/ikoka_stick_30dbm-pinctrl.dtsi b/zephcore/boards/nrf52840/ikoka_stick_30dbm/ikoka_stick_30dbm-pinctrl.dtsi new file mode 100644 index 0000000..3abea95 --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_30dbm/ikoka_stick_30dbm-pinctrl.dtsi @@ -0,0 +1,99 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Ikoka Stick 30dBm pin control - based on XIAO nRF52840 + * + * Pin Mapping (XIAO D-pins to nRF52840 GPIOs): + * D0 = P0.02 (User button) D4 = P0.04 (SX1262 NSS) D8 = P1.13 (SCK) + * D1 = P0.03 (SX1262 DIO1) D5 = P0.05 (PA RXEN) D9 = P1.14 (MISO) + * D2 = P0.28 (SX1262 RESET) D6 = P1.11 (I2C SDA) D10 = P1.15 (MOSI) + * D3 = P0.29 (SX1262 BUSY) D7 = P1.12 (I2C SCL) + * + * UART0 is NOT wired on this board (console is USB-CDC). The pinctrl + * nodes below are kept so that upstream nRF52840 SoC defaults that + * reference &uart0_default/&uart0_sleep still resolve. &uart0 itself + * is disabled in the .dts to prevent the driver from driving pins. + * Dummy pins P0.08 / P0.09 are not brought out to a connector on XIAO. + */ + +&pinctrl { + /* UART0 placeholder — driver is disabled, pins are not brought out */ + uart0_default: uart0_default { + group1 { + psels = ; + }; + group2 { + psels = ; + bias-pull-up; + }; + }; + + uart0_sleep: uart0_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + + /* I2C1 on D6/D7 for optional SSD1306 OLED and any external sensors */ + i2c1_default: i2c1_default { + group1 { + psels = , /* D6 = P1.11 */ + ; /* D7 = P1.12 */ + }; + }; + + i2c1_sleep: i2c1_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + + /* SPI2 for LoRa on D8/D9/D10 */ + spi2_default: spi2_default { + group1 { + psels = , /* D8 = P1.13 */ + , /* D10 = P1.15 */ + ; /* D9 = P1.14 */ + }; + }; + + spi2_sleep: spi2_sleep { + group1 { + psels = , + , + ; + low-power-enable; + }; + }; + + /* QSPI for onboard flash (disabled on XIAO nRF52840 — kept for symmetry) */ + qspi_default: qspi_default { + group1 { + psels = , + , + , + , + , + ; + }; + }; + + qspi_sleep: qspi_sleep { + group1 { + psels = , + , + , + , + ; + low-power-enable; + }; + group2 { + psels = ; + low-power-enable; + bias-pull-up; + }; + }; +}; diff --git a/zephcore/boards/nrf52840/ikoka_stick_30dbm/ikoka_stick_30dbm.dts b/zephcore/boards/nrf52840/ikoka_stick_30dbm/ikoka_stick_30dbm.dts new file mode 100644 index 0000000..476275d --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_30dbm/ikoka_stick_30dbm.dts @@ -0,0 +1,257 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Ikoka Stick 30dBm - XIAO nRF52840 + E22-900M30S LoRa module + * + * Reference: https://ndoo.sg/projects:amateur_radio:meshtastic:diy_devices:ikoka_stick + * + * E22-900M30S LoRa Module: + * - SX1262 with external ~10 dB PA, 30 dBm (1 W) output + * - RXEN (P0.05 / D5) control: HIGH for RX, LOW for TX + * - SX1262 input limited to 20 dBm (datasheet-typical drive for 30 dBm out) + * - DIO2 as RF switch, TCXO 1.8V + * + * Pin Mapping (XIAO D-pins to nRF52840 GPIOs): + * D0 = P0.02 (User button) D4 = P0.04 (SX1262 NSS) D8 = P1.13 (SCK) + * D1 = P0.03 (SX1262 DIO1) D5 = P0.05 (PA RXEN) D9 = P1.14 (MISO) + * D2 = P0.28 (SX1262 RESET) D6 = P1.11 (I2C SDA) D10 = P1.15 (MOSI) + * D3 = P0.29 (SX1262 BUSY) D7 = P1.12 (I2C SCL) + * + * Peripherals: + * - User button on D0 with long-press (deep sleep) + multi-tap actions + * - Optional SSD1306 0.96" OLED @ 0x3C on I2C1 (auto-detected at boot) + * - Battery ADC on AIN7 (P0.31), enable on P0.14 + * - LEDs: RED=P0.26/D11, BLUE=P0.06/D12, GREEN=P0.30/D13 + */ + +/dts-v1/; +#include +#include "ikoka_stick_30dbm-pinctrl.dtsi" +#include +#include + +/ { + model = "Ikoka Stick 30dBm"; + compatible = "ikoka,stick-30dbm"; + + chosen { + zephyr,sram = &sram0; + zephyr,flash = &flash0; + zephyr,code-partition = &code_partition; + zephyr,console = &cdc_acm_uart; + zephyr,shell-uart = &cdc_acm_uart; + zephyr,display = &ssd1306; + }; + + aliases { + lora0 = &lora; + led0 = &led_red; + led1 = &led_green; + led2 = &led_blue; + watchdog0 = &wdt0; + }; + + /* Status LEDs - active LOW on XIAO */ + leds { + compatible = "gpio-leds"; + led_red: led_0 { + gpios = <&gpio0 26 GPIO_ACTIVE_LOW>; + label = "Red LED"; + }; + led_green: led_1 { + gpios = <&gpio0 30 GPIO_ACTIVE_LOW>; + label = "Green LED"; + }; + led_blue: led_2 { + gpios = <&gpio0 6 GPIO_ACTIVE_LOW>; + label = "Blue LED"; + }; + }; + + /* User button on D0 (P0.02), active LOW with internal pull-up. + * Matches the pinout of the Seeed XIAO nRF52840 base board. */ + buttons: buttons { + compatible = "gpio-keys"; + + user_button: button_0 { + gpios = <&gpio0 2 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + zephyr,code = ; + label = "User Button"; + }; + }; + + /* Long-press = deep sleep (≥1 s), short = feeds into multi-tap */ + user_btn_longpress { + compatible = "zephyr,input-longpress"; + input = <&buttons>; + input-codes = ; + short-codes = ; + long-codes = ; + long-delay-ms = <1000>; + }; + + /* Multi-tap — standard ZephCore mapping: + * 1 tap → page next 2 taps → flood advert + * 3 taps → buzzer mute 4 taps → GPS toggle + * 5 taps → LED heartbeat */ + user_btn_multitap { + compatible = "zephcore,input-multi-tap"; + input-codes = ; + tap-codes = ; + tap-delay-ms = <400>; + }; + + /* Battery ADC enable - P0.14 must be LOW to read battery */ + vbat_enable: vbat-enable { + compatible = "regulator-fixed"; + regulator-name = "vbat-enable"; + enable-gpios = <&gpio0 14 GPIO_ACTIVE_LOW>; + regulator-boot-on; + }; + + /* Battery ADC channel */ + zephyr,user { + io-channels = <&adc 7>; + vbat-mv-multiplier = <10650>; /* 1M/510k divider (2.96x), 3.6V ref */ + }; +}; + +®0 { + status = "okay"; +}; + +®1 { + regulator-initial-mode = ; +}; + +&adc { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@7 { + reg = <7>; + zephyr,gain = "ADC_GAIN_1_6"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + zephyr,resolution = <12>; + }; +}; + +&uicr { + gpio-as-nreset; +}; + +&gpiote { + status = "okay"; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +/* UART0 is not wired on this board — console/shell use USB-CDC */ +&uart0 { + status = "disabled"; +}; + +/* I2C1 on D6/D7 for optional SSD1306 OLED and any external sensors. + * + * Uses TWIM (not TWI) so the driver can issue SSD1306 page writes + * (1 cmd + 128 data = 129 bytes) in a single transaction — the + * concat-buf-size of 256 covers the full page. + */ +&i2c1 { + compatible = "nordic,nrf-twim"; + status = "okay"; + pinctrl-0 = <&i2c1_default>; + pinctrl-1 = <&i2c1_sleep>; + pinctrl-names = "default", "sleep"; + zephyr,concat-buf-size = <256>; + + /* Optional SSD1306 0.96" OLED at 0x3C. + * + * The Stick's OLED is optional per the ndoo.sg design. If the + * physical part is absent, zephcore's display helper returns + * -ENODEV and the firmware continues without a display — + * leaving the node declared unconditionally is safe. */ + ssd1306: ssd1306@3c { + compatible = "solomon,ssd1306"; + reg = <0x3c>; + width = <128>; + height = <64>; + segment-offset = <0>; + page-offset = <0>; + display-offset = <0>; + multiplex-ratio = <63>; + segment-remap; + com-invdir; + inversion-on; + prechargep = <0x22>; + }; + + /* All supported environment & power sensors — auto-detected at runtime */ + #include "../../common/sensors-i2c.dtsi" +}; + +/* SPI2 for LoRa module */ +&spi2 { + compatible = "nordic,nrf-spi"; + status = "okay"; + pinctrl-0 = <&spi2_default>; + pinctrl-1 = <&spi2_sleep>; + pinctrl-names = "default", "sleep"; + cs-gpios = <&gpio0 4 GPIO_ACTIVE_LOW>; /* D4 = P0.04 NSS */ + + lora: lora@0 { + compatible = "semtech,sx1262"; + reg = <0>; + spi-max-frequency = <8000000>; + + /* LoRa control pins */ + reset-gpios = <&gpio0 28 GPIO_ACTIVE_LOW>; /* D2 = P0.28 */ + busy-gpios = <&gpio0 29 GPIO_ACTIVE_HIGH>; /* D3 = P0.29 */ + dio1-gpios = <&gpio0 3 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; /* D1 = P0.03 */ + + /* E22-900M30S PA control: HIGH = RX, LOW = TX */ + rx-enable-gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>; /* D5 = P0.05 RXEN */ + + /* Use DIO2 to drive TX enable on the internal RF switch */ + dio2-tx-enable; + + /* TCXO voltage 1.8V */ + dio3-tcxo-voltage = ; + tcxo-power-startup-delay-ms = <10>; + + /* Enable RX boosted mode for better sensitivity */ + rx-boosted; + }; +}; + +/* QSPI Flash - disabled, matching the 30 dBm sibling. XIAO nRF52840's + * onboard P25Q16H is not used in this firmware; the internal nRF52840 + * flash provides storage. Re-enable with status = "okay" if needed. */ +&qspi { + status = "disabled"; +}; + +/* USB CDC for console/CLI */ +zephyr_udc0: &usbd { + compatible = "nordic,nrf-usbd"; + status = "okay"; + + cdc_acm_uart: cdc_acm_uart { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +/* Arduino MeshCore compatible partition layout (SoftDevice v7) */ +#include "../../common/nrf52_partitions_sdv7.dtsi" + +/* Mark the 'buttons' node as a System OFF wakeup source. + * Only valid when the board has a `buttons` label (i.e. a user button). */ +#include "../../common/nrf52_wakeup.dtsi" diff --git a/zephcore/boards/nrf52840/ikoka_stick_30dbm/ikoka_stick_30dbm_defconfig b/zephcore/boards/nrf52840/ikoka_stick_30dbm/ikoka_stick_30dbm_defconfig new file mode 100644 index 0000000..bc2215f --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_30dbm/ikoka_stick_30dbm_defconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: Apache-2.0 +# Ikoka Stick 30dBm default configuration + +# Enable MPU +CONFIG_ARM_MPU=y +CONFIG_HW_STACK_PROTECTION=y + +# Enable GPIO +CONFIG_GPIO=y + +# Enable console on USB CDC (new stack) +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y +CONFIG_SERIAL=y +CONFIG_UART_LINE_CTRL=y + +# Enable flash/NVS storage +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_NVS=y + +# Pinctrl +CONFIG_PINCTRL=y + +# SPI for LoRa +CONFIG_SPI=y + +# ADC for battery +CONFIG_ADC=y diff --git a/zephcore/boards/nrf52840/ikoka_stick_33dbm/Kconfig.ikoka_stick_33dbm b/zephcore/boards/nrf52840/ikoka_stick_33dbm/Kconfig.ikoka_stick_33dbm new file mode 100644 index 0000000..b0d5421 --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_33dbm/Kconfig.ikoka_stick_33dbm @@ -0,0 +1,5 @@ +# Ikoka Stick 33dBm board configuration +# DC/DC converter enabled via DTS: ®1 { regulator-initial-mode = ; } + +config BOARD_IKOKA_STICK_33DBM + select SOC_NRF52840_QIAA diff --git a/zephcore/boards/nrf52840/ikoka_stick_33dbm/board.conf b/zephcore/boards/nrf52840/ikoka_stick_33dbm/board.conf new file mode 100644 index 0000000..8f159ad --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_33dbm/board.conf @@ -0,0 +1,36 @@ +# Ikoka Stick 33dBm (XIAO nRF52840 + E22-900M33S) +# Board-specific configuration +# +# Hardware: +# - XIAO nRF52840 base board (socketed via pin headers) +# - E22-900M33S LoRa module (SX1262 + PA, 33 dBm / 2 W output) +# - User button on D0 (P0.02) +# - Optional SSD1306 0.96" OLED @ 0x3C on I2C1 (auto-detected) +# - Battery ADC on AIN7 (P0.31), enable on P0.14 +# - LEDs: RED=P0.26, BLUE=P0.06, GREEN=P0.30 +# +# Pin Mapping (XIAO D-pins to nRF52840 GPIOs): +# D0 = P0.02 (User button) D4 = P0.04 (SX1262 NSS) D8 = P1.13 (SCK) +# D1 = P0.03 (SX1262 DIO1) D5 = P0.05 (PA RXEN) D9 = P1.14 (MISO) +# D2 = P0.28 (SX1262 RESET) D6 = P1.11 (I2C SDA) D10 = P1.15 (MOSI) +# D3 = P0.29 (SX1262 BUSY) D7 = P1.12 (I2C SCL) +# +# LoRa Module E22-900M33S Notes: +# - Built-in 33 dBm PA with external RXEN control on P0.05 (HIGH=RX, LOW=TX). +# - DIO2 drives the internal TX/RX antenna switch (dio2-tx-enable). +# - SX1262 input power MUST NEVER exceed 9 dBm — higher values will damage +# the module's RF frontend. The caps below enforce this in firmware. + +# Board identification (matches Arduino variant name) +CONFIG_ZEPHCORE_BOARD_NAME="Ikoka Stick-E22-33dBm (Xiao_nrf52)" + +# Device Information Service model name +CONFIG_BT_DIS_MODEL_NUMBER_STR="Ikoka Stick 33dBm" + +# SoftDevice firmware ID (XIAO nRF52840 uses S140 v7.3.0) +CONFIG_ZEPHCORE_SD_FWID=0x0123 + +# SX1262 TX power caps — 9 dBm input yields ~33 dBm output through the +# E22-900M33S PA. Exceeding 9 dBm risks permanent damage to the module. +CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=9 +CONFIG_ZEPHCORE_MAX_TX_POWER_DBM=9 diff --git a/zephcore/boards/nrf52840/ikoka_stick_33dbm/board.yml b/zephcore/boards/nrf52840/ikoka_stick_33dbm/board.yml new file mode 100644 index 0000000..6f95473 --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_33dbm/board.yml @@ -0,0 +1,6 @@ +board: + name: ikoka_stick_33dbm + full_name: Ikoka Stick 33dBm (XIAO nRF52840 + E22-900M33S) + vendor: ikoka + socs: + - name: nrf52840 diff --git a/zephcore/boards/nrf52840/ikoka_stick_33dbm/ikoka_stick_33dbm-pinctrl.dtsi b/zephcore/boards/nrf52840/ikoka_stick_33dbm/ikoka_stick_33dbm-pinctrl.dtsi new file mode 100644 index 0000000..9abaaca --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_33dbm/ikoka_stick_33dbm-pinctrl.dtsi @@ -0,0 +1,99 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Ikoka Stick 33dBm pin control - based on XIAO nRF52840 + * + * Pin Mapping (XIAO D-pins to nRF52840 GPIOs): + * D0 = P0.02 (User button) D4 = P0.04 (SX1262 NSS) D8 = P1.13 (SCK) + * D1 = P0.03 (SX1262 DIO1) D5 = P0.05 (PA RXEN) D9 = P1.14 (MISO) + * D2 = P0.28 (SX1262 RESET) D6 = P1.11 (I2C SDA) D10 = P1.15 (MOSI) + * D3 = P0.29 (SX1262 BUSY) D7 = P1.12 (I2C SCL) + * + * UART0 is NOT wired on this board (console is USB-CDC). The pinctrl + * nodes below are kept so that upstream nRF52840 SoC defaults that + * reference &uart0_default/&uart0_sleep still resolve. &uart0 itself + * is disabled in the .dts to prevent the driver from driving pins. + * Dummy pins P0.08 / P0.09 are not brought out to a connector on XIAO. + */ + +&pinctrl { + /* UART0 placeholder — driver is disabled, pins are not brought out */ + uart0_default: uart0_default { + group1 { + psels = ; + }; + group2 { + psels = ; + bias-pull-up; + }; + }; + + uart0_sleep: uart0_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + + /* I2C1 on D6/D7 for optional SSD1306 OLED and any external sensors */ + i2c1_default: i2c1_default { + group1 { + psels = , /* D6 = P1.11 */ + ; /* D7 = P1.12 */ + }; + }; + + i2c1_sleep: i2c1_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + + /* SPI2 for LoRa on D8/D9/D10 */ + spi2_default: spi2_default { + group1 { + psels = , /* D8 = P1.13 */ + , /* D10 = P1.15 */ + ; /* D9 = P1.14 */ + }; + }; + + spi2_sleep: spi2_sleep { + group1 { + psels = , + , + ; + low-power-enable; + }; + }; + + /* QSPI for onboard flash (disabled on XIAO nRF52840 — kept for symmetry) */ + qspi_default: qspi_default { + group1 { + psels = , + , + , + , + , + ; + }; + }; + + qspi_sleep: qspi_sleep { + group1 { + psels = , + , + , + , + ; + low-power-enable; + }; + group2 { + psels = ; + low-power-enable; + bias-pull-up; + }; + }; +}; diff --git a/zephcore/boards/nrf52840/ikoka_stick_33dbm/ikoka_stick_33dbm.dts b/zephcore/boards/nrf52840/ikoka_stick_33dbm/ikoka_stick_33dbm.dts new file mode 100644 index 0000000..30a73b5 --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_33dbm/ikoka_stick_33dbm.dts @@ -0,0 +1,258 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Ikoka Stick 33dBm - XIAO nRF52840 + E22-900M33S LoRa module + * + * Reference: https://ndoo.sg/projects:amateur_radio:meshtastic:diy_devices:ikoka_stick + * + * E22-900M33S LoRa Module: + * - SX1262 with integrated 33 dBm (2 W) PA + * - RXEN (P0.05 / D5) control: HIGH for RX, LOW for TX + * - SX1262 input power limited to 9 dBm (33 dBm output) — higher values + * will permanently damage the module's RF frontend. + * - DIO2 as RF switch, TCXO 1.8V + * + * Pin Mapping (XIAO D-pins to nRF52840 GPIOs): + * D0 = P0.02 (User button) D4 = P0.04 (SX1262 NSS) D8 = P1.13 (SCK) + * D1 = P0.03 (SX1262 DIO1) D5 = P0.05 (PA RXEN) D9 = P1.14 (MISO) + * D2 = P0.28 (SX1262 RESET) D6 = P1.11 (I2C SDA) D10 = P1.15 (MOSI) + * D3 = P0.29 (SX1262 BUSY) D7 = P1.12 (I2C SCL) + * + * Peripherals: + * - User button on D0 with long-press (deep sleep) + multi-tap actions + * - Optional SSD1306 0.96" OLED @ 0x3C on I2C1 (auto-detected at boot) + * - Battery ADC on AIN7 (P0.31), enable on P0.14 + * - LEDs: RED=P0.26/D11, BLUE=P0.06/D12, GREEN=P0.30/D13 + */ + +/dts-v1/; +#include +#include "ikoka_stick_33dbm-pinctrl.dtsi" +#include +#include + +/ { + model = "Ikoka Stick 33dBm"; + compatible = "ikoka,stick-33dbm"; + + chosen { + zephyr,sram = &sram0; + zephyr,flash = &flash0; + zephyr,code-partition = &code_partition; + zephyr,console = &cdc_acm_uart; + zephyr,shell-uart = &cdc_acm_uart; + zephyr,display = &ssd1306; + }; + + aliases { + lora0 = &lora; + led0 = &led_red; + led1 = &led_green; + led2 = &led_blue; + watchdog0 = &wdt0; + }; + + /* Status LEDs - active LOW on XIAO */ + leds { + compatible = "gpio-leds"; + led_red: led_0 { + gpios = <&gpio0 26 GPIO_ACTIVE_LOW>; + label = "Red LED"; + }; + led_green: led_1 { + gpios = <&gpio0 30 GPIO_ACTIVE_LOW>; + label = "Green LED"; + }; + led_blue: led_2 { + gpios = <&gpio0 6 GPIO_ACTIVE_LOW>; + label = "Blue LED"; + }; + }; + + /* User button on D0 (P0.02), active LOW with internal pull-up. + * Matches the pinout of the Seeed XIAO nRF52840 base board. */ + buttons: buttons { + compatible = "gpio-keys"; + + user_button: button_0 { + gpios = <&gpio0 2 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + zephyr,code = ; + label = "User Button"; + }; + }; + + /* Long-press = deep sleep (≥1 s), short = feeds into multi-tap */ + user_btn_longpress { + compatible = "zephyr,input-longpress"; + input = <&buttons>; + input-codes = ; + short-codes = ; + long-codes = ; + long-delay-ms = <1000>; + }; + + /* Multi-tap — standard ZephCore mapping: + * 1 tap → page next 2 taps → flood advert + * 3 taps → buzzer mute 4 taps → GPS toggle + * 5 taps → LED heartbeat */ + user_btn_multitap { + compatible = "zephcore,input-multi-tap"; + input-codes = ; + tap-codes = ; + tap-delay-ms = <400>; + }; + + /* Battery ADC enable - P0.14 must be LOW to read battery */ + vbat_enable: vbat-enable { + compatible = "regulator-fixed"; + regulator-name = "vbat-enable"; + enable-gpios = <&gpio0 14 GPIO_ACTIVE_LOW>; + regulator-boot-on; + }; + + /* Battery ADC channel */ + zephyr,user { + io-channels = <&adc 7>; + vbat-mv-multiplier = <10650>; /* 1M/510k divider (2.96x), 3.6V ref */ + }; +}; + +®0 { + status = "okay"; +}; + +®1 { + regulator-initial-mode = ; +}; + +&adc { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@7 { + reg = <7>; + zephyr,gain = "ADC_GAIN_1_6"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + zephyr,resolution = <12>; + }; +}; + +&uicr { + gpio-as-nreset; +}; + +&gpiote { + status = "okay"; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +/* UART0 is not wired on this board — console/shell use USB-CDC */ +&uart0 { + status = "disabled"; +}; + +/* I2C1 on D6/D7 for optional SSD1306 OLED and any external sensors. + * + * Uses TWIM (not TWI) so the driver can issue SSD1306 page writes + * (1 cmd + 128 data = 129 bytes) in a single transaction — the + * concat-buf-size of 256 covers the full page. + */ +&i2c1 { + compatible = "nordic,nrf-twim"; + status = "okay"; + pinctrl-0 = <&i2c1_default>; + pinctrl-1 = <&i2c1_sleep>; + pinctrl-names = "default", "sleep"; + zephyr,concat-buf-size = <256>; + + /* Optional SSD1306 0.96" OLED at 0x3C. + * + * The Stick's OLED is optional per the ndoo.sg design. If the + * physical part is absent, zephcore's display helper returns + * -ENODEV and the firmware continues without a display — + * leaving the node declared unconditionally is safe. */ + ssd1306: ssd1306@3c { + compatible = "solomon,ssd1306"; + reg = <0x3c>; + width = <128>; + height = <64>; + segment-offset = <0>; + page-offset = <0>; + display-offset = <0>; + multiplex-ratio = <63>; + segment-remap; + com-invdir; + inversion-on; + prechargep = <0x22>; + }; + + /* All supported environment & power sensors — auto-detected at runtime */ + #include "../../common/sensors-i2c.dtsi" +}; + +/* SPI2 for LoRa module */ +&spi2 { + compatible = "nordic,nrf-spi"; + status = "okay"; + pinctrl-0 = <&spi2_default>; + pinctrl-1 = <&spi2_sleep>; + pinctrl-names = "default", "sleep"; + cs-gpios = <&gpio0 4 GPIO_ACTIVE_LOW>; /* D4 = P0.04 NSS */ + + lora: lora@0 { + compatible = "semtech,sx1262"; + reg = <0>; + spi-max-frequency = <8000000>; + + /* LoRa control pins */ + reset-gpios = <&gpio0 28 GPIO_ACTIVE_LOW>; /* D2 = P0.28 */ + busy-gpios = <&gpio0 29 GPIO_ACTIVE_HIGH>; /* D3 = P0.29 */ + dio1-gpios = <&gpio0 3 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; /* D1 = P0.03 */ + + /* E22-900M33S PA control: HIGH = RX, LOW = TX */ + rx-enable-gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>; /* D5 = P0.05 RXEN */ + + /* Use DIO2 to drive TX enable on the internal RF switch */ + dio2-tx-enable; + + /* TCXO voltage 1.8V */ + dio3-tcxo-voltage = ; + tcxo-power-startup-delay-ms = <10>; + + /* Enable RX boosted mode for better sensitivity */ + rx-boosted; + }; +}; + +/* QSPI Flash - disabled, matching the 30 dBm sibling. XIAO nRF52840's + * onboard P25Q16H is not used in this firmware; the internal nRF52840 + * flash provides storage. Re-enable with status = "okay" if needed. */ +&qspi { + status = "disabled"; +}; + +/* USB CDC for console/CLI */ +zephyr_udc0: &usbd { + compatible = "nordic,nrf-usbd"; + status = "okay"; + + cdc_acm_uart: cdc_acm_uart { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +/* Arduino MeshCore compatible partition layout (SoftDevice v7) */ +#include "../../common/nrf52_partitions_sdv7.dtsi" + +/* Mark the 'buttons' node as a System OFF wakeup source. + * Only valid when the board has a `buttons` label (i.e. a user button). */ +#include "../../common/nrf52_wakeup.dtsi" diff --git a/zephcore/boards/nrf52840/ikoka_stick_33dbm/ikoka_stick_33dbm_defconfig b/zephcore/boards/nrf52840/ikoka_stick_33dbm/ikoka_stick_33dbm_defconfig new file mode 100644 index 0000000..61a69ff --- /dev/null +++ b/zephcore/boards/nrf52840/ikoka_stick_33dbm/ikoka_stick_33dbm_defconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: Apache-2.0 +# Ikoka Stick 33dBm default configuration + +# Enable MPU +CONFIG_ARM_MPU=y +CONFIG_HW_STACK_PROTECTION=y + +# Enable GPIO +CONFIG_GPIO=y + +# Enable console on USB CDC (new stack) +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y +CONFIG_SERIAL=y +CONFIG_UART_LINE_CTRL=y + +# Enable flash/NVS storage +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_NVS=y + +# Pinctrl +CONFIG_PINCTRL=y + +# SPI for LoRa +CONFIG_SPI=y + +# ADC for battery +CONFIG_ADC=y diff --git a/zephcore/boards/supported_boards.md b/zephcore/boards/supported_boards.md index b931cb8..3b2a03b 100644 --- a/zephcore/boards/supported_boards.md +++ b/zephcore/boards/supported_boards.md @@ -14,6 +14,9 @@ thinknode_m3 thinknode_m6 rak_wismesh_tag ikoka_nano_30dbm +ikoka_stick_22dbm +ikoka_stick_30dbm +ikoka_stick_33dbm sensecap_solar xiao_nrf52840 lilygo_techo