commit f62db776e164ededf86b8e8237db9f142de03773 Author: Kevin Thomas Date: Mon Jul 6 14:32:12 2026 -0400 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..30fc821 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +*.c linguist-detectable=true +*.cmake linguist-detectable=false +*.py linguist-detectable=false +*.txt linguist-detectable=false +*.json linguist-detectable=false +*.ps1 linguist-detectable=false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1430494 --- /dev/null +++ b/.gitignore @@ -0,0 +1,64 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb +*.rep +*.gpr + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# Build output +build/ +target/ + +# Node.js and PDF generation script +node_modules/ +package-lock.json +package.json +svg-to-pdf.ps1 diff --git a/0x0001_hello-world/.gitignore b/0x0001_hello-world/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x0001_hello-world/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x0001_hello-world/.vscode/c_cpp_properties.json b/0x0001_hello-world/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..e80461d --- /dev/null +++ b/0x0001_hello-world/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h", + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x0001_hello-world/.vscode/cmake-kits.json b/0x0001_hello-world/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x0001_hello-world/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x0001_hello-world/.vscode/extensions.json b/0x0001_hello-world/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x0001_hello-world/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x0001_hello-world/.vscode/launch.json b/0x0001_hello-world/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x0001_hello-world/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x0001_hello-world/.vscode/settings.json b/0x0001_hello-world/.vscode/settings.json new file mode 100644 index 0000000..688748a --- /dev/null +++ b/0x0001_hello-world/.vscode/settings.json @@ -0,0 +1,43 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja", + "files.associations": { + "stdlib.h": "c" + } +} diff --git a/0x0001_hello-world/.vscode/tasks.json b/0x0001_hello-world/.vscode/tasks.json new file mode 100644 index 0000000..f427bf0 --- /dev/null +++ b/0x0001_hello-world/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Risc-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x0001_hello-world/0x0001_hello-world.c b/0x0001_hello-world/0x0001_hello-world.c new file mode 100644 index 0000000..7f7b062 --- /dev/null +++ b/0x0001_hello-world/0x0001_hello-world.c @@ -0,0 +1,46 @@ +/** + * @file 0x0001_hello-world.c + * @brief Hello World: print a greeting over UART in an infinite loop + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Demonstrates the minimal "hello, world" program on the Raspberry Pi Pico 2. + * Initializes stdio over UART and prints a greeting in an infinite loop. + * + * Wiring: + * No external wiring required (USB serial). + */ + +#include +#include "pico/stdlib.h" + +int main(void) { + stdio_init_all(); + while (true) { + printf("hello, world\r\n"); + } +} diff --git a/0x0001_hello-world/CMakeLists.txt b/0x0001_hello-world/CMakeLists.txt new file mode 100644 index 0000000..31d8fc5 --- /dev/null +++ b/0x0001_hello-world/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x0001_hello-world C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x0001_hello-world 0x0001_hello-world.c ) + +pico_set_program_name(0x0001_hello-world "0x0001_hello-world") +pico_set_program_version(0x0001_hello-world "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x0001_hello-world 1) +pico_enable_stdio_usb(0x0001_hello-world 0) + +# Add the standard library to the build +target_link_libraries(0x0001_hello-world + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x0001_hello-world PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x0001_hello-world) + diff --git a/0x0001_hello-world/pico_sdk_import.cmake b/0x0001_hello-world/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x0001_hello-world/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x0005_intro-to-variables/.gitignore b/0x0005_intro-to-variables/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x0005_intro-to-variables/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x0005_intro-to-variables/.vscode/c_cpp_properties.json b/0x0005_intro-to-variables/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..e80461d --- /dev/null +++ b/0x0005_intro-to-variables/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h", + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x0005_intro-to-variables/.vscode/cmake-kits.json b/0x0005_intro-to-variables/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x0005_intro-to-variables/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x0005_intro-to-variables/.vscode/extensions.json b/0x0005_intro-to-variables/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x0005_intro-to-variables/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x0005_intro-to-variables/.vscode/launch.json b/0x0005_intro-to-variables/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x0005_intro-to-variables/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x0005_intro-to-variables/.vscode/settings.json b/0x0005_intro-to-variables/.vscode/settings.json new file mode 100644 index 0000000..cdb8e61 --- /dev/null +++ b/0x0005_intro-to-variables/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x0005_intro-to-variables/.vscode/tasks.json b/0x0005_intro-to-variables/.vscode/tasks.json new file mode 100644 index 0000000..f427bf0 --- /dev/null +++ b/0x0005_intro-to-variables/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Risc-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x0005_intro-to-variables/0x0005_intro-to-variables.c b/0x0005_intro-to-variables/0x0005_intro-to-variables.c new file mode 100644 index 0000000..29f6b2d --- /dev/null +++ b/0x0005_intro-to-variables/0x0005_intro-to-variables.c @@ -0,0 +1,48 @@ +/** + * @file 0x0005_intro-to-variables.c + * @brief Introduction to variables: declare, assign, and print a uint8_t + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Demonstrates declaring, initializing, and reassigning a uint8_t variable. + * Prints the variable value over UART in an infinite loop. + * + * Wiring: + * No external wiring required (USB serial). + */ + +#include +#include "pico/stdlib.h" + +int main(void) { + uint8_t age = 42; + age = 43; + stdio_init_all(); + while (true) { + printf("age: %d\r\n", age); + } +} diff --git a/0x0005_intro-to-variables/CMakeLists.txt b/0x0005_intro-to-variables/CMakeLists.txt new file mode 100644 index 0000000..673ff61 --- /dev/null +++ b/0x0005_intro-to-variables/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x0005_intro-to-variables C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x0005_intro-to-variables 0x0005_intro-to-variables.c ) + +pico_set_program_name(0x0005_intro-to-variables "0x0005_intro-to-variables") +pico_set_program_version(0x0005_intro-to-variables "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x0005_intro-to-variables 1) +pico_enable_stdio_usb(0x0005_intro-to-variables 0) + +# Add the standard library to the build +target_link_libraries(0x0005_intro-to-variables + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x0005_intro-to-variables PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x0005_intro-to-variables) + diff --git a/0x0005_intro-to-variables/pico_sdk_import.cmake b/0x0005_intro-to-variables/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x0005_intro-to-variables/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x0008_uninitialized-variables-a/.gitignore b/0x0008_uninitialized-variables-a/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x0008_uninitialized-variables-a/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x0008_uninitialized-variables-a/.vscode/c_cpp_properties.json b/0x0008_uninitialized-variables-a/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..27d83bd --- /dev/null +++ b/0x0008_uninitialized-variables-a/.vscode/c_cpp_properties.json @@ -0,0 +1,21 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x0008_uninitialized-variables-a/.vscode/cmake-kits.json b/0x0008_uninitialized-variables-a/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x0008_uninitialized-variables-a/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x0008_uninitialized-variables-a/.vscode/extensions.json b/0x0008_uninitialized-variables-a/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x0008_uninitialized-variables-a/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x0008_uninitialized-variables-a/.vscode/launch.json b/0x0008_uninitialized-variables-a/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x0008_uninitialized-variables-a/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x0008_uninitialized-variables-a/.vscode/settings.json b/0x0008_uninitialized-variables-a/.vscode/settings.json new file mode 100644 index 0000000..cdb8e61 --- /dev/null +++ b/0x0008_uninitialized-variables-a/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x0008_uninitialized-variables-a/.vscode/tasks.json b/0x0008_uninitialized-variables-a/.vscode/tasks.json new file mode 100644 index 0000000..f427bf0 --- /dev/null +++ b/0x0008_uninitialized-variables-a/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Risc-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x0008_uninitialized-variables-a/0x0008_uninitialized-variables-a.c b/0x0008_uninitialized-variables-a/0x0008_uninitialized-variables-a.c new file mode 100644 index 0000000..9d2ddb3 --- /dev/null +++ b/0x0008_uninitialized-variables-a/0x0008_uninitialized-variables-a.c @@ -0,0 +1,53 @@ +/** + * @file 0x0008_uninitialized-variables-a.c + * @brief Blink LED using SDK gpio functions (no UART) + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Blinks an LED on GPIO16 using the standard Pico SDK gpio_init / gpio_put + * functions. No UART output; pure GPIO blink demonstration. + * + * Wiring: + * GPIO16 -> LED anode (with current-limiting resistor to GND) + */ + +#include +#include "pico/stdlib.h" + +/** @brief GPIO pin number for the LED */ +#define LED_PIN 16 + +int main(void) { + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); + while (true) { + gpio_put(LED_PIN, 1); + sleep_ms(500); + gpio_put(LED_PIN, 0); + sleep_ms(500); + } +} diff --git a/0x0008_uninitialized-variables-a/CMakeLists.txt b/0x0008_uninitialized-variables-a/CMakeLists.txt new file mode 100644 index 0000000..347375d --- /dev/null +++ b/0x0008_uninitialized-variables-a/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x0008_uninitialized-variables-a C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x0008_uninitialized-variables-a 0x0008_uninitialized-variables-a.c ) + +pico_set_program_name(0x0008_uninitialized-variables-a "0x0008_uninitialized-variables-a") +pico_set_program_version(0x0008_uninitialized-variables-a "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x0008_uninitialized-variables-a 0) +pico_enable_stdio_usb(0x0008_uninitialized-variables-a 0) + +# Add the standard library to the build +target_link_libraries(0x0008_uninitialized-variables-a + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x0008_uninitialized-variables-a PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x0008_uninitialized-variables-a) + diff --git a/0x0008_uninitialized-variables-a/pico_sdk_import.cmake b/0x0008_uninitialized-variables-a/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x0008_uninitialized-variables-a/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x0008_uninitialized-variables-b/.gitignore b/0x0008_uninitialized-variables-b/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x0008_uninitialized-variables-b/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x0008_uninitialized-variables-b/.vscode/c_cpp_properties.json b/0x0008_uninitialized-variables-b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..e80461d --- /dev/null +++ b/0x0008_uninitialized-variables-b/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h", + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x0008_uninitialized-variables-b/.vscode/cmake-kits.json b/0x0008_uninitialized-variables-b/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x0008_uninitialized-variables-b/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x0008_uninitialized-variables-b/.vscode/extensions.json b/0x0008_uninitialized-variables-b/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x0008_uninitialized-variables-b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x0008_uninitialized-variables-b/.vscode/launch.json b/0x0008_uninitialized-variables-b/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x0008_uninitialized-variables-b/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x0008_uninitialized-variables-b/.vscode/settings.json b/0x0008_uninitialized-variables-b/.vscode/settings.json new file mode 100644 index 0000000..cdb8e61 --- /dev/null +++ b/0x0008_uninitialized-variables-b/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x0008_uninitialized-variables-b/.vscode/tasks.json b/0x0008_uninitialized-variables-b/.vscode/tasks.json new file mode 100644 index 0000000..f427bf0 --- /dev/null +++ b/0x0008_uninitialized-variables-b/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Risc-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x0008_uninitialized-variables-b/0x0008_uninitialized-variables-b.c b/0x0008_uninitialized-variables-b/0x0008_uninitialized-variables-b.c new file mode 100644 index 0000000..b42c544 --- /dev/null +++ b/0x0008_uninitialized-variables-b/0x0008_uninitialized-variables-b.c @@ -0,0 +1,80 @@ +/** + * @file 0x0008_uninitialized-variables-b.c + * @brief Blink LED using gpioc coprocessor bit-level functions + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Blinks an LED on GPIO16 using the RP2350 gpioc coprocessor bit-level + * functions (gpioc_bit_oe_put / gpioc_bit_out_put) instead of the standard + * gpio_init / gpio_put SDK calls. + * + * Wiring: + * GPIO16 -> LED anode (with current-limiting resistor to GND) + */ + +#include +#include "pico/stdlib.h" + +/** @brief GPIO pin number for the LED */ +#define LED_PIN 16 + +/** + * @brief Initialize GPIO16 for output using coprocessor bit functions + * + * @details Configures the pad for SIO, sets direction to input first, + * clears the output, selects SIO function, then enables output. + * + * @retval None + */ +static void gpio_coprocessor_init(void) { + gpio_set_dir(LED_PIN, GPIO_IN); + gpio_put(LED_PIN, 0); + gpio_set_function(LED_PIN, GPIO_FUNC_SIO); + gpioc_bit_oe_put(LED_PIN, GPIO_OUT); +} + +/** + * @brief Blink LED using coprocessor bit output functions + * + * @details Toggles the LED on and off with 500ms delays using + * gpioc_bit_out_put and sleep_us. + * + * @retval None + */ +static void blink_cycle(void) { + gpioc_bit_out_put(LED_PIN, 1); + sleep_us(500 * 1000ull); + gpioc_bit_out_put(LED_PIN, 0); + sleep_us(500 * 1000ull); +} + +int main(void) { + gpio_coprocessor_init(); + while (true) { + blink_cycle(); + } +} diff --git a/0x0008_uninitialized-variables-b/CMakeLists.txt b/0x0008_uninitialized-variables-b/CMakeLists.txt new file mode 100644 index 0000000..24d559f --- /dev/null +++ b/0x0008_uninitialized-variables-b/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x0008_uninitialized-variables-b C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x0008_uninitialized-variables-b 0x0008_uninitialized-variables-b.c ) + +pico_set_program_name(0x0008_uninitialized-variables-b "0x0008_uninitialized-variables-b") +pico_set_program_version(0x0008_uninitialized-variables-b "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x0008_uninitialized-variables-b 0) +pico_enable_stdio_usb(0x0008_uninitialized-variables-b 0) + +# Add the standard library to the build +target_link_libraries(0x0008_uninitialized-variables-b + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x0008_uninitialized-variables-b PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x0008_uninitialized-variables-b) + diff --git a/0x0008_uninitialized-variables-b/pico_sdk_import.cmake b/0x0008_uninitialized-variables-b/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x0008_uninitialized-variables-b/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x0008_uninitialized-variables-c/.gitignore b/0x0008_uninitialized-variables-c/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x0008_uninitialized-variables-c/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x0008_uninitialized-variables-c/.vscode/c_cpp_properties.json b/0x0008_uninitialized-variables-c/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..e80461d --- /dev/null +++ b/0x0008_uninitialized-variables-c/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h", + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x0008_uninitialized-variables-c/.vscode/cmake-kits.json b/0x0008_uninitialized-variables-c/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x0008_uninitialized-variables-c/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x0008_uninitialized-variables-c/.vscode/extensions.json b/0x0008_uninitialized-variables-c/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x0008_uninitialized-variables-c/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x0008_uninitialized-variables-c/.vscode/launch.json b/0x0008_uninitialized-variables-c/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x0008_uninitialized-variables-c/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x0008_uninitialized-variables-c/.vscode/settings.json b/0x0008_uninitialized-variables-c/.vscode/settings.json new file mode 100644 index 0000000..cdb8e61 --- /dev/null +++ b/0x0008_uninitialized-variables-c/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x0008_uninitialized-variables-c/.vscode/tasks.json b/0x0008_uninitialized-variables-c/.vscode/tasks.json new file mode 100644 index 0000000..f427bf0 --- /dev/null +++ b/0x0008_uninitialized-variables-c/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Risc-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x0008_uninitialized-variables-c/0x0008_uninitialized-variables-c.c b/0x0008_uninitialized-variables-c/0x0008_uninitialized-variables-c.c new file mode 100644 index 0000000..40771de --- /dev/null +++ b/0x0008_uninitialized-variables-c/0x0008_uninitialized-variables-c.c @@ -0,0 +1,85 @@ +/** + * @file 0x0008_uninitialized-variables-c.c + * @brief Blink LED using direct register-level pad and IO bank configuration + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Blinks an LED on GPIO16 using direct register writes to PADS_BANK0 and + * IO_BANK0 for pad configuration, plus gpioc coprocessor bit-level functions + * for output enable and data. Demonstrates the register-level equivalent of + * gpio_init / gpio_set_function. + * + * Wiring: + * GPIO16 -> LED anode (with current-limiting resistor to GND) + */ + +#include +#include "pico/stdlib.h" + +/** @brief GPIO pin number for the LED */ +#define LED_PIN 16 + +/** + * @brief Configure pad and IO bank registers for GPIO16 as SIO output + * + * @details Sets PADS_BANK0 IE/OD bits, assigns FUNCSEL to SIO in IO_BANK0, + * clears the ISO bit, and enables output via the coprocessor. + * + * @retval None + */ +static void configure_pad_and_iobank(void) { + gpioc_bit_oe_put(LED_PIN, GPIO_OUT); + gpioc_bit_out_put(LED_PIN, 0); + hw_write_masked(&pads_bank0_hw->io[LED_PIN], + PADS_BANK0_GPIO0_IE_BITS, + PADS_BANK0_GPIO0_IE_BITS | PADS_BANK0_GPIO0_OD_BITS); + io_bank0_hw->io[LED_PIN].ctrl = GPIO_FUNC_SIO << IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB; + hw_clear_bits(&pads_bank0_hw->io[LED_PIN], PADS_BANK0_GPIO0_ISO_BITS); + gpioc_bit_oe_put(LED_PIN, GPIO_OUT); +} + +/** + * @brief Blink LED using coprocessor bit output functions + * + * @details Toggles the LED on and off with 500ms delays using + * gpioc_bit_out_put and sleep_us. + * + * @retval None + */ +static void blink_cycle(void) { + gpioc_bit_out_put(LED_PIN, 1); + sleep_us(500 * 1000ull); + gpioc_bit_out_put(LED_PIN, 0); + sleep_us(500 * 1000ull); +} + +int main(void) { + configure_pad_and_iobank(); + while (true) { + blink_cycle(); + } +} diff --git a/0x0008_uninitialized-variables-c/CMakeLists.txt b/0x0008_uninitialized-variables-c/CMakeLists.txt new file mode 100644 index 0000000..b7e1991 --- /dev/null +++ b/0x0008_uninitialized-variables-c/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x0008_uninitialized-variables-c C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x0008_uninitialized-variables-c 0x0008_uninitialized-variables-c.c ) + +pico_set_program_name(0x0008_uninitialized-variables-c "0x0008_uninitialized-variables-c") +pico_set_program_version(0x0008_uninitialized-variables-c "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x0008_uninitialized-variables-c 0) +pico_enable_stdio_usb(0x0008_uninitialized-variables-c 0) + +# Add the standard library to the build +target_link_libraries(0x0008_uninitialized-variables-c + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x0008_uninitialized-variables-c PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x0008_uninitialized-variables-c) + diff --git a/0x0008_uninitialized-variables-c/pico_sdk_import.cmake b/0x0008_uninitialized-variables-c/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x0008_uninitialized-variables-c/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x0008_uninitialized-variables-d/.gitignore b/0x0008_uninitialized-variables-d/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x0008_uninitialized-variables-d/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x0008_uninitialized-variables-d/.vscode/c_cpp_properties.json b/0x0008_uninitialized-variables-d/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..e80461d --- /dev/null +++ b/0x0008_uninitialized-variables-d/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h", + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x0008_uninitialized-variables-d/.vscode/cmake-kits.json b/0x0008_uninitialized-variables-d/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x0008_uninitialized-variables-d/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x0008_uninitialized-variables-d/.vscode/extensions.json b/0x0008_uninitialized-variables-d/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x0008_uninitialized-variables-d/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x0008_uninitialized-variables-d/.vscode/launch.json b/0x0008_uninitialized-variables-d/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x0008_uninitialized-variables-d/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x0008_uninitialized-variables-d/.vscode/settings.json b/0x0008_uninitialized-variables-d/.vscode/settings.json new file mode 100644 index 0000000..cdb8e61 --- /dev/null +++ b/0x0008_uninitialized-variables-d/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x0008_uninitialized-variables-d/.vscode/tasks.json b/0x0008_uninitialized-variables-d/.vscode/tasks.json new file mode 100644 index 0000000..f427bf0 --- /dev/null +++ b/0x0008_uninitialized-variables-d/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Risc-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x0008_uninitialized-variables-d/0x0008_uninitialized-variables-d.c b/0x0008_uninitialized-variables-d/0x0008_uninitialized-variables-d.c new file mode 100644 index 0000000..b834115 --- /dev/null +++ b/0x0008_uninitialized-variables-d/0x0008_uninitialized-variables-d.c @@ -0,0 +1,144 @@ +/** + * @file 0x0008_uninitialized-variables-d.c + * @brief Blink LED using pico_default_asm_volatile coprocessor instructions + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Blinks an LED on GPIO16 using pico_default_asm_volatile to emit mcrr + * coprocessor instructions for GPIO output enable and data, plus inline + * assembly for pad configuration (hw_write_masked, IO_BANK0 FUNCSEL, + * hw_clear_bits equivalents). + * + * Wiring: + * GPIO16 -> LED anode (with current-limiting resistor to GND) + */ + +#include +#include "pico/stdlib.h" + +/** @brief GPIO pin number for the LED */ +#define LED_PIN 16 + +/** + * @brief Enable output and set initial coprocessor OE/OUT via mcrr + * + * @details Issues two mcrr p0 instructions: one to enable output direction, + * one to set the initial output state. + * + * @retval None + */ +static void asm_set_oe_and_out(void) { + pico_default_asm_volatile ("mcrr p0, #4, %0, %1, c4" : : "r" (LED_PIN), "r" (GPIO_OUT)); + pico_default_asm_volatile ("mcrr p0, #4, %0, %1, c4" : : "r" (LED_PIN), "r" (GPIO_OUT)); +} + +/** + * @brief Configure PADS_BANK0 IE/OD bits via inline assembly hw_xor_bits + * + * @details Performs a read-modify-write on the pad register to set IE + * and clear OD, replicating hw_write_masked behavior. + * + * @retval None + */ +static void asm_configure_pad(void) { + pico_default_asm_volatile ( + "ldr r2, [%0]\n" // load current pad register + "eor r2, r2, %1\n" // xor with IE bit + "and r2, r2, %2\n" // mask with (IE|OD) + "eor r2, r2, %1\n" // recombine (hw_xor_bits logic) + "str r2, [%0]\n" // write back + : + : "r" (&pads_bank0_hw->io[LED_PIN]), + "r" (PADS_BANK0_GPIO0_IE_BITS), + "r" (PADS_BANK0_GPIO0_IE_BITS | PADS_BANK0_GPIO0_OD_BITS) + : "r2", "memory" + ); +} + +/** + * @brief Set IO_BANK0 FUNCSEL to SIO via inline assembly store + * + * @details Writes the SIO function select value directly to the + * IO_BANK0 GPIO control register. + * + * @retval None + */ +static void asm_set_funcsel(void) { + pico_default_asm_volatile ( + "str %1, [%0]\n" + : + : "r" (&io_bank0_hw->io[LED_PIN].ctrl), + "r" (GPIO_FUNC_SIO << IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB) + : "memory" + ); +} + +/** + * @brief Clear ISO bits in PADS_BANK0 via inline assembly + * + * @details Performs a read-modify-write to clear the pad isolation + * bit, un-isolating the pad for normal operation. + * + * @retval None + */ +static void asm_clear_iso(void) { + pico_default_asm_volatile ( + "ldr r2, [%0]\n" // load current register value + "bic r2, r2, %1\n" // clear the ISO bits (bit clear) + "str r2, [%0]\n" // write back + : + : "r" (&pads_bank0_hw->io[LED_PIN]), + "r" (PADS_BANK0_GPIO0_ISO_BITS) + : "r2", "memory" + ); +} + +/** + * @brief Blink LED using coprocessor mcrr output instructions + * + * @details Toggles the LED on and off with 500ms delays using + * pico_default_asm_volatile mcrr instructions. + * + * @retval None + */ +static void asm_blink_cycle(void) { + pico_default_asm_volatile ("mcrr p0, #4, %0, %1, c0" : : "r" (LED_PIN), "r" (1)); + sleep_us(500 * 1000ull); + pico_default_asm_volatile ("mcrr p0, #4, %0, %1, c0" : : "r" (LED_PIN), "r" (0)); + sleep_us(500 * 1000ull); +} + +int main(void) { + asm_set_oe_and_out(); + asm_configure_pad(); + asm_set_funcsel(); + asm_clear_iso(); + pico_default_asm_volatile ("mcrr p0, #4, %0, %1, c4" : : "r" (LED_PIN), "r" (GPIO_OUT)); + while (true) { + asm_blink_cycle(); + } +} diff --git a/0x0008_uninitialized-variables-d/CMakeLists.txt b/0x0008_uninitialized-variables-d/CMakeLists.txt new file mode 100644 index 0000000..9e88754 --- /dev/null +++ b/0x0008_uninitialized-variables-d/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x0008_uninitialized-variables-d C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x0008_uninitialized-variables-d 0x0008_uninitialized-variables-d.c ) + +pico_set_program_name(0x0008_uninitialized-variables-d "0x0008_uninitialized-variables-d") +pico_set_program_version(0x0008_uninitialized-variables-d "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x0008_uninitialized-variables-d 0) +pico_enable_stdio_usb(0x0008_uninitialized-variables-d 0) + +# Add the standard library to the build +target_link_libraries(0x0008_uninitialized-variables-d + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x0008_uninitialized-variables-d PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x0008_uninitialized-variables-d) + diff --git a/0x0008_uninitialized-variables-d/pico_sdk_import.cmake b/0x0008_uninitialized-variables-d/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x0008_uninitialized-variables-d/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x0008_uninitialized-variables-e/.gitignore b/0x0008_uninitialized-variables-e/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x0008_uninitialized-variables-e/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x0008_uninitialized-variables-e/.vscode/c_cpp_properties.json b/0x0008_uninitialized-variables-e/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..e80461d --- /dev/null +++ b/0x0008_uninitialized-variables-e/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h", + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x0008_uninitialized-variables-e/.vscode/cmake-kits.json b/0x0008_uninitialized-variables-e/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x0008_uninitialized-variables-e/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x0008_uninitialized-variables-e/.vscode/extensions.json b/0x0008_uninitialized-variables-e/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x0008_uninitialized-variables-e/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x0008_uninitialized-variables-e/.vscode/launch.json b/0x0008_uninitialized-variables-e/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x0008_uninitialized-variables-e/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x0008_uninitialized-variables-e/.vscode/settings.json b/0x0008_uninitialized-variables-e/.vscode/settings.json new file mode 100644 index 0000000..cdb8e61 --- /dev/null +++ b/0x0008_uninitialized-variables-e/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x0008_uninitialized-variables-e/.vscode/tasks.json b/0x0008_uninitialized-variables-e/.vscode/tasks.json new file mode 100644 index 0000000..f427bf0 --- /dev/null +++ b/0x0008_uninitialized-variables-e/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Risc-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x0008_uninitialized-variables-e/0x0008_uninitialized-variables-e.c b/0x0008_uninitialized-variables-e/0x0008_uninitialized-variables-e.c new file mode 100644 index 0000000..ac572c8 --- /dev/null +++ b/0x0008_uninitialized-variables-e/0x0008_uninitialized-variables-e.c @@ -0,0 +1,107 @@ +/** + * @file 0x0008_uninitialized-variables-e.c + * @brief Blink LED using pure inline ARM assembly with direct register addresses + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Blinks an LED on GPIO16 using pure inline ARM assembly. No SDK calls, no + * includes. Configures PADS_BANK0, IO_BANK0, and GPIO coprocessor via direct + * register addresses and mcrr instructions, with a busy-wait delay loop. + * + * Wiring: + * GPIO16 -> LED anode (with current-limiting resistor to GND) + */ + +int main(void) { + __asm__ volatile ( + // gpio_init(LED_PIN); + /// gpio_set_dir(LED_PIN, GPIO_IN); + //// gpioc_bit_oe_put(LED_PIN, GPIO_OUT); + "movs r4, #0x10\n" // GPIO16 + "movs r5, #0x01\n" // bit 1; used for OUT/OE writes + "mcrr p0, #4, r4, r5, c4\n" // gpioc_bit_oe_put(16, 1); p102 + + // gpio_set_function(LED_PIN, GPIO_FUNC_SIO); + /// hw_write_masked(&pads_bank0_hw->io[LED_PIN], + /// PADS_BANK0_GPIO0_IE_BITS, + /// PADS_BANK0_GPIO0_IE_BITS | PADS_BANK0_GPIO0_OD_BITS + /// ); + //// hw_xor_bits(addr, (*addr ^ values) & write_mask); + "ldr r3, =0x40038044\n" // &pads_bank0_hw->io[16]; p785, p796 + "ldr r2, [r3]\n" // load current config + "bic r2, r2, #0x80\n" // clear OD; output disable + "orr r2, r2, #0x40\n" // set IE; enable input buffer + "str r2, [r3]\n" // store updated config + /// io_bank0_hw->io[LED_PIN].ctrl = GPIO_FUNC_SIO << IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB; + "ldr r3, =0x40028084\n" // &io_bank0_hw->io[16].ctrl; p603, p637 + "ldr r2, [r3]\n" // load current config + "bic r2, r2, #0x1f\n" // clear FUNCSEL bits [4:0] + "orr r2, r2, #5\n" // set FUNCSEL = 5 (SIO) + "str r2, [r3]\n" // store updated config + /// hw_clear_bits(&pads_bank0_hw->io[gpio], PADS_BANK0_GPIO0_ISO_BITS); + "ldr r3, =0x40038044\n" // &pads_bank0_hw->io[16]; p785, p796 + "ldr r2, [r3]\n" // load current config + "bic r2, r2, #0x100\n" // clear ISO bit (bit 8) un‑isolate pad + "str r2, [r3]\n" // store updated config + + // gpio_set_dir(LED_PIN, GPIO_OUT); + /// gpioc_bit_oe_put(LED_PIN, GPIO_OUT); + "movs r4, #0x10\n" // GPIO16 + "movs r5, #0x01\n" // bit 1; used for OUT/OE writes + "mcrr p0, #4, r4, r5, c4\n" // gpioc_bit_oe_put(16, 1); p102 + + // while (true) + "1:\n" // loop start + + // gpio_put(LED_PIN, 1); + /// gpioc_bit_out_put(LED_PIN, 1); + "movs r4, #0x10\n" // GPIO16 + "movs r5, #0x01\n" // bit 1; used for OUT/OE writes + "mcrr p0, #4, r4, r5, c0\n" // gpioc_bit_out_put(16, 1) + // sleep_ms(500); + /// sleep_us(500 * 1000ull); + "ldr r2, =0x17D7840\n" // r2 = ~8.4M cycles + "2:\n" // delay loop + "subs r2, r2, #1\n" // decrement counter + "bne 2b\n" // repeat until zero + + // gpio_put(LED_PIN, 1); + /// gpioc_bit_out_put(LED_PIN, 1); + "movs r4, #0x10\n" // GPIO16 + "movs r5, #0x00\n" // bit 0; used for OUT/OE writes + "mcrr p0, #4, r4, r5, c0\n" // gpioc_bit_out_put(16, 0) + // sleep_ms(500); + /// sleep_us(500 * 1000ull); + "ldr r2, =0x17D7840\n" // r2 = ~8.4M cycles + "3:\n" // delay loop + "subs r2, r2, #1\n" // decrement counter + "bne 3b\n" // repeat until zero + + // jmp + "b 1b\n" // repeat forever + ); +} diff --git a/0x0008_uninitialized-variables-e/CMakeLists.txt b/0x0008_uninitialized-variables-e/CMakeLists.txt new file mode 100644 index 0000000..bfc1c28 --- /dev/null +++ b/0x0008_uninitialized-variables-e/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x0008_uninitialized-variables-e C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x0008_uninitialized-variables-e 0x0008_uninitialized-variables-e.c ) + +pico_set_program_name(0x0008_uninitialized-variables-e "0x0008_uninitialized-variables-e") +pico_set_program_version(0x0008_uninitialized-variables-e "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x0008_uninitialized-variables-e 0) +pico_enable_stdio_usb(0x0008_uninitialized-variables-e 0) + +# Add the standard library to the build +target_link_libraries(0x0008_uninitialized-variables-e + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x0008_uninitialized-variables-e PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x0008_uninitialized-variables-e) + diff --git a/0x0008_uninitialized-variables-e/pico_sdk_import.cmake b/0x0008_uninitialized-variables-e/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x0008_uninitialized-variables-e/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x0008_uninitialized-variables/.gitignore b/0x0008_uninitialized-variables/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x0008_uninitialized-variables/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x0008_uninitialized-variables/.vscode/c_cpp_properties.json b/0x0008_uninitialized-variables/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..e80461d --- /dev/null +++ b/0x0008_uninitialized-variables/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h", + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x0008_uninitialized-variables/.vscode/cmake-kits.json b/0x0008_uninitialized-variables/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x0008_uninitialized-variables/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x0008_uninitialized-variables/.vscode/extensions.json b/0x0008_uninitialized-variables/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x0008_uninitialized-variables/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x0008_uninitialized-variables/.vscode/launch.json b/0x0008_uninitialized-variables/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x0008_uninitialized-variables/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x0008_uninitialized-variables/.vscode/settings.json b/0x0008_uninitialized-variables/.vscode/settings.json new file mode 100644 index 0000000..4e5d7b6 --- /dev/null +++ b/0x0008_uninitialized-variables/.vscode/settings.json @@ -0,0 +1,45 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja", + "files.associations": { + "addressmap.h": "c", + "resets.h": "c", + "pads_bank0.h": "c" + } +} diff --git a/0x0008_uninitialized-variables/.vscode/tasks.json b/0x0008_uninitialized-variables/.vscode/tasks.json new file mode 100644 index 0000000..f427bf0 --- /dev/null +++ b/0x0008_uninitialized-variables/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Risc-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x0008_uninitialized-variables/0x0008_uninitialized-variables.c b/0x0008_uninitialized-variables/0x0008_uninitialized-variables.c new file mode 100644 index 0000000..8e9dc42 --- /dev/null +++ b/0x0008_uninitialized-variables/0x0008_uninitialized-variables.c @@ -0,0 +1,68 @@ +/** + * @file 0x0008_uninitialized-variables.c + * @brief Uninitialized variables: demonstrate undefined behavior with printf + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Demonstrates the danger of uninitialized variables. Prints the value of an + * uninitialized uint8_t over UART while blinking an LED on GPIO16. + * + * Wiring: + * GPIO16 -> LED anode (with current-limiting resistor to GND) + */ + +#include +#include "pico/stdlib.h" + +/** @brief GPIO pin number for the LED */ +#define LED_PIN 16 + +/** + * @brief Toggle LED and print uninitialized variable value + * + * @details Blinks the LED on and off with a 500ms delay between each + * transition and prints the age variable each cycle. + * + * @param age value to print (uninitialized in this demo) + */ +static void blink_and_print(uint8_t age) { + printf("age: %d\r\n", age); + gpio_put(LED_PIN, 1); + sleep_ms(500); + gpio_put(LED_PIN, 0); + sleep_ms(500); +} + +int main(void) { + uint8_t age; + stdio_init_all(); + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); + while (true) { + blink_and_print(age); + } +} diff --git a/0x0008_uninitialized-variables/CMakeLists.txt b/0x0008_uninitialized-variables/CMakeLists.txt new file mode 100644 index 0000000..c2ac591 --- /dev/null +++ b/0x0008_uninitialized-variables/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x0008_uninitialized-variables C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x0008_uninitialized-variables 0x0008_uninitialized-variables.c ) + +pico_set_program_name(0x0008_uninitialized-variables "0x0008_uninitialized-variables") +pico_set_program_version(0x0008_uninitialized-variables "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x0008_uninitialized-variables 1) +pico_enable_stdio_usb(0x0008_uninitialized-variables 0) + +# Add the standard library to the build +target_link_libraries(0x0008_uninitialized-variables + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x0008_uninitialized-variables PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x0008_uninitialized-variables) + diff --git a/0x0008_uninitialized-variables/pico_sdk_import.cmake b/0x0008_uninitialized-variables/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x0008_uninitialized-variables/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x000b_integer-data-type/.gitignore b/0x000b_integer-data-type/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x000b_integer-data-type/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x000b_integer-data-type/.vscode/c_cpp_properties.json b/0x000b_integer-data-type/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..e80461d --- /dev/null +++ b/0x000b_integer-data-type/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h", + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x000b_integer-data-type/.vscode/cmake-kits.json b/0x000b_integer-data-type/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x000b_integer-data-type/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x000b_integer-data-type/.vscode/extensions.json b/0x000b_integer-data-type/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x000b_integer-data-type/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x000b_integer-data-type/.vscode/launch.json b/0x000b_integer-data-type/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x000b_integer-data-type/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x000b_integer-data-type/.vscode/settings.json b/0x000b_integer-data-type/.vscode/settings.json new file mode 100644 index 0000000..cdb8e61 --- /dev/null +++ b/0x000b_integer-data-type/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x000b_integer-data-type/.vscode/tasks.json b/0x000b_integer-data-type/.vscode/tasks.json new file mode 100644 index 0000000..f427bf0 --- /dev/null +++ b/0x000b_integer-data-type/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Risc-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x000b_integer-data-type/0x000b_integer-data-type.c b/0x000b_integer-data-type/0x000b_integer-data-type.c new file mode 100644 index 0000000..c923449 --- /dev/null +++ b/0x000b_integer-data-type/0x000b_integer-data-type.c @@ -0,0 +1,132 @@ +/** + * @file 0x000b_integer-data-type.c + * @brief Integer data types: cycle LEDs on GPIO16-18 with asm, print int values + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Demonstrates uint8_t and int8_t integer data types. Initializes GPIO16-18 + * via inline assembly (PADS_BANK0, IO_BANK0, coprocessor OE), then cycles + * LEDs using coprocessor mcrr instructions while printing integer values. + * + * Wiring: + * GPIO16 -> LED1 anode (with current-limiting resistor to GND) + * GPIO17 -> LED2 anode (with current-limiting resistor to GND) + * GPIO18 -> LED3 anode (with current-limiting resistor to GND) + */ + +#include +#include "pico/stdlib.h" + +/** + * @brief Initialize GPIO16-18 as SIO outputs via inline assembly loop + * + * @details Configures PADS_BANK0 (clear OD+ISO, set IE), IO_BANK0 + * (FUNCSEL=5 SIO), and coprocessor OE for pins 16-18. + * + * @retval None + */ +static void asm_init_gpio_range(void) { + __asm volatile ( + "ldr r3, =0x40038000\n" // address of PADS_BANK0_BASE + "ldr r2, =0x40028004\n" // address of IO_BANK0 GPIO0.ctrl + "movs r0, #16\n" // GPIO16 (start pin) + "init_loop:\n" // loop start + "lsls r1, r0, #2\n" // pin * 4 (pad offset) + "adds r4, r3, r1\n" // PADS base + offset + "ldr r5, [r4]\n" // load current config + "bic r5, r5, #0x180\n" // clear OD+ISO + "orr r5, r5, #0x40\n" // set IE + "str r5, [r4]\n" // store updated config + "lsls r1, r0, #3\n" // pin * 8 (ctrl offset) + "adds r4, r2, r1\n" // IO_BANK0 base + offset + "ldr r5, [r4]\n" // load current config + "bic r5, r5, #0x1f\n" // clear FUNCSEL bits [4:0] + "orr r5, r5, #5\n" // set FUNCSEL = 5 (SIO) + "str r5, [r4]\n" // store updated config + "mov r4, r0\n" // pin + "movs r5, #1\n" // bit 1; used for OUT/OE writes + "mcrr p0, #4, r4, r5, c4\n" // gpioc_bit_oe_put(pin,1) + "adds r0, r0, #1\n" // increment pin + "cmp r0, #20\n" // stop after pin 18 + "blt init_loop\n" // loop until r0 == 20 + ); +} + +/** + * @brief Blink a single pin using coprocessor mcrr instructions + * + * @details Turns the specified pin on for 500ms, then off for 500ms + * using inline asm mcrr coprocessor output instructions. + * + * @param pin GPIO pin number to blink + * @retval None + */ +static void asm_blink_pin(uint8_t pin) { + __asm volatile ( + "mov r4, %0\n" + "movs r5, #0x01\n" + "mcrr p0, #4, r4, r5, c0\n" + : : "r"(pin) : "r4", "r5" + ); + sleep_ms(500); + __asm volatile ( + "mov r4, %0\n" + "movs r5, #0\n" + "mcrr p0, #4, r4, r5, c0\n" + : : "r"(pin) : "r4", "r5" + ); + sleep_ms(500); +} + +/** + * @brief Blink LED, advance pin, and print age/range + * + * @details Blinks the current pin, wraps pin 16-18, + * and prints both integer variables. + * + * @param pin pointer to the current GPIO pin number + * @param age unsigned 8-bit age value + * @param range signed 8-bit range value + * @retval None + */ +static void blink_and_print(uint8_t *pin, uint8_t age, int8_t range) { + asm_blink_pin(*pin); + *pin = (*pin > 18) ? 16 : *pin + 1; + printf("age: %d\r\n", age); + printf("range: %d\r\n", range); +} + +int main(void) { + uint8_t age = 43; + int8_t range = -42; + stdio_init_all(); + asm_init_gpio_range(); + uint8_t pin = 16; + while (1) { + blink_and_print(&pin, age, range); + } +} diff --git a/0x000b_integer-data-type/CMakeLists.txt b/0x000b_integer-data-type/CMakeLists.txt new file mode 100644 index 0000000..6178a2a --- /dev/null +++ b/0x000b_integer-data-type/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x000b_integer-data-type C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x000b_integer-data-type 0x000b_integer-data-type.c ) + +pico_set_program_name(0x000b_integer-data-type "0x000b_integer-data-type") +pico_set_program_version(0x000b_integer-data-type "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x000b_integer-data-type 1) +pico_enable_stdio_usb(0x000b_integer-data-type 0) + +# Add the standard library to the build +target_link_libraries(0x000b_integer-data-type + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x000b_integer-data-type PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x000b_integer-data-type) + diff --git a/0x000b_integer-data-type/pico_sdk_import.cmake b/0x000b_integer-data-type/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x000b_integer-data-type/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x000e_floating-point-data-type/.gitignore b/0x000e_floating-point-data-type/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x000e_floating-point-data-type/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x000e_floating-point-data-type/.vscode/c_cpp_properties.json b/0x000e_floating-point-data-type/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..e80461d --- /dev/null +++ b/0x000e_floating-point-data-type/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h", + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x000e_floating-point-data-type/.vscode/cmake-kits.json b/0x000e_floating-point-data-type/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x000e_floating-point-data-type/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x000e_floating-point-data-type/.vscode/extensions.json b/0x000e_floating-point-data-type/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x000e_floating-point-data-type/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x000e_floating-point-data-type/.vscode/launch.json b/0x000e_floating-point-data-type/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x000e_floating-point-data-type/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x000e_floating-point-data-type/.vscode/settings.json b/0x000e_floating-point-data-type/.vscode/settings.json new file mode 100644 index 0000000..cdb8e61 --- /dev/null +++ b/0x000e_floating-point-data-type/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x000e_floating-point-data-type/.vscode/tasks.json b/0x000e_floating-point-data-type/.vscode/tasks.json new file mode 100644 index 0000000..f427bf0 --- /dev/null +++ b/0x000e_floating-point-data-type/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Risc-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x000e_floating-point-data-type/0x000e_floating-point-data-type.c b/0x000e_floating-point-data-type/0x000e_floating-point-data-type.c new file mode 100644 index 0000000..f0a3bac --- /dev/null +++ b/0x000e_floating-point-data-type/0x000e_floating-point-data-type.c @@ -0,0 +1,47 @@ +/** + * @file 0x000e_floating-point-data-type.c + * @brief Floating-point data type: print a float value over UART + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Demonstrates the float data type on the Raspberry Pi Pico 2. Initializes + * a float variable and prints its value over UART in an infinite loop. + * + * Wiring: + * No external wiring required (USB serial). + */ + +#include +#include "pico/stdlib.h" + +int main(void) { + float fav_num = 42.5; + stdio_init_all(); + while (true) { + printf("fav_num: %f\r\n", fav_num); + } +} diff --git a/0x000e_floating-point-data-type/CMakeLists.txt b/0x000e_floating-point-data-type/CMakeLists.txt new file mode 100644 index 0000000..72041a0 --- /dev/null +++ b/0x000e_floating-point-data-type/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x000e_floating-point-data-type C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x000e_floating-point-data-type 0x000e_floating-point-data-type.c ) + +pico_set_program_name(0x000e_floating-point-data-type "0x000e_floating-point-data-type") +pico_set_program_version(0x000e_floating-point-data-type "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x000e_floating-point-data-type 1) +pico_enable_stdio_usb(0x000e_floating-point-data-type 0) + +# Add the standard library to the build +target_link_libraries(0x000e_floating-point-data-type + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x000e_floating-point-data-type PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x000e_floating-point-data-type) + diff --git a/0x000e_floating-point-data-type/pico_sdk_import.cmake b/0x000e_floating-point-data-type/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x000e_floating-point-data-type/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x0011_double-floating-point-data-type/.gitignore b/0x0011_double-floating-point-data-type/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x0011_double-floating-point-data-type/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x0011_double-floating-point-data-type/.vscode/c_cpp_properties.json b/0x0011_double-floating-point-data-type/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..c429d6d --- /dev/null +++ b/0x0011_double-floating-point-data-type/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h", + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x0011_double-floating-point-data-type/.vscode/cmake-kits.json b/0x0011_double-floating-point-data-type/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x0011_double-floating-point-data-type/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x0011_double-floating-point-data-type/.vscode/extensions.json b/0x0011_double-floating-point-data-type/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x0011_double-floating-point-data-type/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x0011_double-floating-point-data-type/.vscode/launch.json b/0x0011_double-floating-point-data-type/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x0011_double-floating-point-data-type/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x0011_double-floating-point-data-type/.vscode/settings.json b/0x0011_double-floating-point-data-type/.vscode/settings.json new file mode 100644 index 0000000..95be83b --- /dev/null +++ b/0x0011_double-floating-point-data-type/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x0011_double-floating-point-data-type/.vscode/tasks.json b/0x0011_double-floating-point-data-type/.vscode/tasks.json new file mode 100644 index 0000000..2359f7f --- /dev/null +++ b/0x0011_double-floating-point-data-type/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Risc-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x0011_double-floating-point-data-type/0x0011_double-floating-point-data-type.c b/0x0011_double-floating-point-data-type/0x0011_double-floating-point-data-type.c new file mode 100644 index 0000000..753f51d --- /dev/null +++ b/0x0011_double-floating-point-data-type/0x0011_double-floating-point-data-type.c @@ -0,0 +1,47 @@ +/** + * @file 0x0011_double-floating-point-data-type.c + * @brief Double floating-point data type: print a double value over UART + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Demonstrates the double data type on the Raspberry Pi Pico 2. Initializes + * a double variable and prints its value over UART in an infinite loop. + * + * Wiring: + * No external wiring required (USB serial). + */ + +#include +#include "pico/stdlib.h" + +int main(void) { + double fav_num = 42.52525; + stdio_init_all(); + while (true) { + printf("fav_num: %lf\r\n", fav_num); + } +} diff --git a/0x0011_double-floating-point-data-type/CMakeLists.txt b/0x0011_double-floating-point-data-type/CMakeLists.txt new file mode 100644 index 0000000..035b92e --- /dev/null +++ b/0x0011_double-floating-point-data-type/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0-a4) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x0011_double-floating-point-data-type C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x0011_double-floating-point-data-type 0x0011_double-floating-point-data-type.c ) + +pico_set_program_name(0x0011_double-floating-point-data-type "0x0011_double-floating-point-data-type") +pico_set_program_version(0x0011_double-floating-point-data-type "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x0011_double-floating-point-data-type 1) +pico_enable_stdio_usb(0x0011_double-floating-point-data-type 0) + +# Add the standard library to the build +target_link_libraries(0x0011_double-floating-point-data-type + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x0011_double-floating-point-data-type PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x0011_double-floating-point-data-type) + diff --git a/0x0011_double-floating-point-data-type/pico_sdk_import.cmake b/0x0011_double-floating-point-data-type/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x0011_double-floating-point-data-type/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x0014_static-variables/.gitignore b/0x0014_static-variables/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x0014_static-variables/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x0014_static-variables/.vscode/c_cpp_properties.json b/0x0014_static-variables/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..c429d6d --- /dev/null +++ b/0x0014_static-variables/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h", + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x0014_static-variables/.vscode/cmake-kits.json b/0x0014_static-variables/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x0014_static-variables/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x0014_static-variables/.vscode/extensions.json b/0x0014_static-variables/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x0014_static-variables/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x0014_static-variables/.vscode/launch.json b/0x0014_static-variables/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x0014_static-variables/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x0014_static-variables/.vscode/settings.json b/0x0014_static-variables/.vscode/settings.json new file mode 100644 index 0000000..95be83b --- /dev/null +++ b/0x0014_static-variables/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x0014_static-variables/.vscode/tasks.json b/0x0014_static-variables/.vscode/tasks.json new file mode 100644 index 0000000..d1b3193 --- /dev/null +++ b/0x0014_static-variables/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "RISC-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x0014_static-variables/0x0014_static-variables.c b/0x0014_static-variables/0x0014_static-variables.c new file mode 100644 index 0000000..ff5fd32 --- /dev/null +++ b/0x0014_static-variables/0x0014_static-variables.c @@ -0,0 +1,90 @@ +/** + * @file 0x0014_static-variables.c + * @brief Static variables: compare regular vs static local variable persistence + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Demonstrates the difference between regular and static local variables. + * A regular variable resets to 42 each iteration while a static variable + * persists and increments across loop iterations. Also reads a button on + * GPIO15 and mirrors its state to an LED on GPIO16. + * + * Wiring: + * GPIO15 -> Button (with pull-up, active low) + * GPIO16 -> LED anode (with current-limiting resistor to GND) + */ + +#include +#include "pico/stdlib.h" + +/** @brief GPIO pin number for the button input */ +#define BUTTON_GPIO 15 +/** @brief GPIO pin number for the LED output */ +#define LED_GPIO 16 + +/** + * @brief Initialize button and LED GPIO pins + * + * @details Configures the button pin as input with pull-up and the + * LED pin as output. + * + * @retval None + */ +static void init_gpio(void) { + gpio_init(BUTTON_GPIO); + gpio_set_dir(BUTTON_GPIO, GPIO_IN); + gpio_pull_up(BUTTON_GPIO); + gpio_init(LED_GPIO); + gpio_set_dir(LED_GPIO, GPIO_OUT); +} + +/** + * @brief Print and increment regular vs static variables, update LED + * + * @details Prints both variable values, increments them, reads the + * button state and drives the LED accordingly. + * + * @retval None + */ +static void demo_static_variable(void) { + uint8_t regular_fav_num = 42; + static uint8_t static_fav_num = 42; + printf("regular_fav_num: %d\r\n", regular_fav_num); + printf("static_fav_num: %d\r\n", static_fav_num); + regular_fav_num++; + static_fav_num++; + bool pressed = gpio_get(BUTTON_GPIO); + gpio_put(LED_GPIO, pressed ? 0 : 1); +} + +int main(void) { + stdio_init_all(); + init_gpio(); + while (true) { + demo_static_variable(); + } +} diff --git a/0x0014_static-variables/CMakeLists.txt b/0x0014_static-variables/CMakeLists.txt new file mode 100644 index 0000000..63d1f6b --- /dev/null +++ b/0x0014_static-variables/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0-a4) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x0014_static-variables C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x0014_static-variables 0x0014_static-variables.c ) + +pico_set_program_name(0x0014_static-variables "0x0014_static-variables") +pico_set_program_version(0x0014_static-variables "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x0014_static-variables 1) +pico_enable_stdio_usb(0x0014_static-variables 0) + +# Add the standard library to the build +target_link_libraries(0x0014_static-variables + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x0014_static-variables PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x0014_static-variables) + diff --git a/0x0014_static-variables/pico_sdk_import.cmake b/0x0014_static-variables/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x0014_static-variables/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x0017_constants/.gitignore b/0x0017_constants/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x0017_constants/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x0017_constants/.vscode/c_cpp_properties.json b/0x0017_constants/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..c429d6d --- /dev/null +++ b/0x0017_constants/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h", + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x0017_constants/.vscode/cmake-kits.json b/0x0017_constants/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x0017_constants/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x0017_constants/.vscode/extensions.json b/0x0017_constants/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x0017_constants/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x0017_constants/.vscode/launch.json b/0x0017_constants/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x0017_constants/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x0017_constants/.vscode/settings.json b/0x0017_constants/.vscode/settings.json new file mode 100644 index 0000000..8aecbe6 --- /dev/null +++ b/0x0017_constants/.vscode/settings.json @@ -0,0 +1,43 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja", + "files.associations": { + "stdlib.h": "c" + } +} diff --git a/0x0017_constants/.vscode/tasks.json b/0x0017_constants/.vscode/tasks.json new file mode 100644 index 0000000..d1b3193 --- /dev/null +++ b/0x0017_constants/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "RISC-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x0017_constants/0x0017_constants.c b/0x0017_constants/0x0017_constants.c new file mode 100644 index 0000000..f3b5182 --- /dev/null +++ b/0x0017_constants/0x0017_constants.c @@ -0,0 +1,100 @@ +/** + * @file 0x0017_constants.c + * @brief Constants: demonstrate #define and const with LCD1602 I2C display + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Demonstrates #define macro constants and const-qualified variables. + * Initializes an LCD1602 display over I2C and prints constant values + * over UART in an infinite loop. + * + * Wiring: + * GPIO2 (SDA) -> LCD1602 PCF8574 SDA + * GPIO3 (SCL) -> LCD1602 PCF8574 SCL + * 3V3 -> LCD1602 VCC + * GND -> LCD1602 GND + */ + +#include +#include +#include "pico/stdlib.h" +#include "hardware/i2c.h" +#include "lcd_1602.h" + +/** @brief Macro constant for favorite number */ +#define FAV_NUM 42 +/** @brief I2C peripheral instance */ +#define I2C_PORT i2c1 +/** @brief GPIO pin for I2C SDA */ +#define I2C_SDA_PIN 2 +/** @brief GPIO pin for I2C SCL */ +#define I2C_SCL_PIN 3 + +/** @brief Const-qualified favorite number */ +const int OTHER_FAV_NUM = 1337; + +/** + * @brief Initialize I2C bus and LCD1602 display + * + * @details Configures I2C1 at 100kHz on GPIO2/3, initializes the + * LCD via PCF8574 at address 0x27, and writes two lines. + * + * @retval None + */ +static void init_i2c_and_lcd(void) { + i2c_init(I2C_PORT, 100000); + gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(I2C_SDA_PIN); + gpio_pull_up(I2C_SCL_PIN); + lcd_i2c_init(I2C_PORT, 0x27, 4, 0x08); +} + +/** + * @brief Write greeting text to the LCD display + * + * @details Sets cursor to line 0 and writes "Reverse", then sets + * cursor to line 1 and writes "Engineering". + * + * @retval None + */ +static void write_lcd_greeting(void) { + lcd_set_cursor(0, 0); + lcd_puts("Reverse"); + lcd_set_cursor(1, 0); + lcd_puts("Engineering"); +} + +int main(void) { + stdio_init_all(); + init_i2c_and_lcd(); + write_lcd_greeting(); + while (true) { + printf("FAV_NUM: %d\r\n", FAV_NUM); + printf("OTHER_FAV_NUM: %d\r\n", OTHER_FAV_NUM); + } +} diff --git a/0x0017_constants/CMakeLists.txt b/0x0017_constants/CMakeLists.txt new file mode 100644 index 0000000..4c75aa3 --- /dev/null +++ b/0x0017_constants/CMakeLists.txt @@ -0,0 +1,59 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0-a4) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x0017_constants C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x0017_constants 0x0017_constants.c lcd_1602.c) + +pico_set_program_name(0x0017_constants "0x0017_constants") +pico_set_program_version(0x0017_constants "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x0017_constants 1) +pico_enable_stdio_usb(0x0017_constants 0) + +# Add the standard library to the build +target_link_libraries(0x0017_constants + pico_stdlib + hardware_i2c + hardware_gpio) + +# Add the standard include files to the build +target_include_directories(0x0017_constants PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x0017_constants) + diff --git a/0x0017_constants/lcd_1602.c b/0x0017_constants/lcd_1602.c new file mode 100644 index 0000000..80054cc --- /dev/null +++ b/0x0017_constants/lcd_1602.c @@ -0,0 +1,160 @@ +/** + * @file lcd_1602.c + * @brief Implementation of PCF8574-backed HD44780 (16x2) LCD driver + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "lcd_1602.h" +#include +#include + +/** @brief I2C instance pointer for the LCD */ +static i2c_inst_t *lcd_i2c = NULL; +/** @brief I2C address of the PCF8574 backpack */ +static uint8_t lcd_addr = 0x27; +/** @brief Bit shift for 4-bit nibble position */ +static int lcd_nibble_shift = 4; +/** @brief PCF8574 bit mask controlling the backlight */ +static uint8_t lcd_backlight_mask = 0x08; + +/** @brief PCF8574 bit mask for Register Select */ +#define PIN_RS 0x01 +/** @brief PCF8574 bit mask for Read/Write */ +#define PIN_RW 0x02 +/** @brief PCF8574 bit mask for Enable */ +#define PIN_EN 0x04 + +/** + * @brief Write one raw byte to the PCF8574 expander over I2C + * + * @param data Output byte to send to the expander + */ +static void pcf_write_byte(uint8_t data) { + if (!lcd_i2c) return; + i2c_write_blocking(lcd_i2c, lcd_addr, &data, 1, false); +} + +/** + * @brief Toggle EN to latch a nibble into the LCD controller + * + * @param data Current control/data bus byte (with RS and backlight already set) + */ +static void pcf_pulse_enable(uint8_t data) { + pcf_write_byte(data | PIN_EN); + sleep_us(1); + pcf_write_byte(data & ~PIN_EN); + sleep_us(50); +} + +/** + * @brief Write one 4-bit nibble to the LCD + * + * @param nibble Lower 4 bits to write + * @param mode 0 for command, non-zero for character data + */ +static void lcd_write4(uint8_t nibble, uint8_t mode) { + uint8_t data = (nibble & 0x0F) << lcd_nibble_shift; + data |= mode ? PIN_RS : 0; + data |= lcd_backlight_mask; + pcf_pulse_enable(data); +} + +/** + * @brief Send one full 8-bit command/data value as two nibbles + * + * @param value Byte to send to the LCD + * @param mode 0 for command, non-zero for character data + */ +static void lcd_send(uint8_t value, uint8_t mode) { + lcd_write4((value >> 4) & 0x0F, mode); + lcd_write4(value & 0x0F, mode); +} + +/** + * @brief Store LCD driver configuration in module-level state + * + * @param i2c Pointer to the I2C instance + * @param pcf_addr 7-bit PCF8574 address + * @param nibble_shift Bit shift for 4-bit nibbles + * @param backlight_mask Backlight control bit mask + */ +static void lcd_store_config(i2c_inst_t *i2c, uint8_t pcf_addr, + int nibble_shift, uint8_t backlight_mask) { + lcd_i2c = i2c; + lcd_addr = pcf_addr; + lcd_nibble_shift = nibble_shift; + lcd_backlight_mask = backlight_mask; +} + +/** + * @brief Execute the HD44780 4-bit mode power-on reset sequence + */ +static void lcd_hd44780_reset(void) { + lcd_write4(0x03, 0); + sleep_ms(5); + lcd_write4(0x03, 0); + sleep_us(150); + lcd_write4(0x03, 0); + sleep_us(150); + lcd_write4(0x02, 0); + sleep_us(150); +} + +/** + * @brief Send post-reset configuration commands to the HD44780 + * + * Sets 4-bit mode with 2 display lines, turns the display on with + * cursor hidden, clears the screen, and selects left-to-right entry mode. + */ +static void lcd_hd44780_configure(void) { + lcd_send(0x28, 0); + lcd_send(0x0C, 0); + lcd_send(0x01, 0); + sleep_ms(2); + lcd_send(0x06, 0); +} + +void lcd_i2c_init(i2c_inst_t *i2c, uint8_t pcf_addr, int nibble_shift, uint8_t backlight_mask) { + lcd_store_config(i2c, pcf_addr, nibble_shift, backlight_mask); + lcd_hd44780_reset(); + lcd_hd44780_configure(); +} + +void lcd_clear(void) { + lcd_send(0x01, 0); + sleep_ms(2); +} + +void lcd_set_cursor(int line, int position) { + const uint8_t row_offsets[] = {0x00, 0x40}; + if (line > 1) line = 1; + lcd_send(0x80 | (position + row_offsets[line]), 0); +} + +void lcd_puts(const char *s) { + while (*s) + lcd_send((uint8_t)*s++, 1); +} diff --git a/0x0017_constants/lcd_1602.h b/0x0017_constants/lcd_1602.h new file mode 100644 index 0000000..072ae13 --- /dev/null +++ b/0x0017_constants/lcd_1602.h @@ -0,0 +1,74 @@ +/** + * @file lcd_1602.h + * @brief Header for PCF8574-backed HD44780 (16x2) LCD driver + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef LCD_1602_H +#define LCD_1602_H + +#include +#include "pico/stdlib.h" +#include "hardware/i2c.h" + +/** + * @brief Initialize the LCD driver over I2C + * + * Configures the internal driver state and performs the HD44780 initialization + * sequence. The driver does not configure I2C pins or call i2c_init; that + * must be done by the caller prior to calling this function. + * + * @param i2c Pointer to the I2C instance (e.g. i2c0 or i2c1) + * @param pcf_addr PCF8574 I2C address (commonly 0x27 or 0x3F) + * @param nibble_shift Bit shift applied to 4-bit nibbles (commonly 4 or 0) + * @param backlight_mask PCF8574 bit mask that controls the backlight + */ +void lcd_i2c_init(i2c_inst_t *i2c, uint8_t pcf_addr, int nibble_shift, uint8_t backlight_mask); + +/** + * @brief Clear the LCD display + * + * Clears the display and returns the cursor to the home position. This + * call blocks for the duration required by the HD44780 controller. + */ +void lcd_clear(void); + +/** + * @brief Set the cursor position + * + * @param line Line number (0 or 1) + * @param position Column (0..15) + */ +void lcd_set_cursor(int line, int position); + +/** + * @brief Write a null-terminated string to the display + * + * @param s The string to write (ASCII) + */ +void lcd_puts(const char *s); + +#endif // LCD_1602_H diff --git a/0x0017_constants/pico_sdk_import.cmake b/0x0017_constants/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x0017_constants/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x001a_operators/.gitignore b/0x001a_operators/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x001a_operators/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x001a_operators/.vscode/c_cpp_properties.json b/0x001a_operators/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..c429d6d --- /dev/null +++ b/0x001a_operators/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h", + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x001a_operators/.vscode/cmake-kits.json b/0x001a_operators/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x001a_operators/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x001a_operators/.vscode/extensions.json b/0x001a_operators/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x001a_operators/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x001a_operators/.vscode/launch.json b/0x001a_operators/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x001a_operators/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x001a_operators/.vscode/settings.json b/0x001a_operators/.vscode/settings.json new file mode 100644 index 0000000..95be83b --- /dev/null +++ b/0x001a_operators/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x001a_operators/.vscode/tasks.json b/0x001a_operators/.vscode/tasks.json new file mode 100644 index 0000000..d1b3193 --- /dev/null +++ b/0x001a_operators/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "RISC-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x001a_operators/0x001a_operators.c b/0x001a_operators/0x001a_operators.c new file mode 100644 index 0000000..1eecc97 --- /dev/null +++ b/0x001a_operators/0x001a_operators.c @@ -0,0 +1,126 @@ +/** + * @file 0x001a_operators.c + * @brief Operators: demonstrate C operators with DHT11 temperature sensor + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Demonstrates arithmetic, increment, relational, logical, bitwise, and + * assignment operators in C. Also reads humidity and temperature from a + * DHT11 sensor on GPIO4. + * + * Wiring: + * GPIO4 -> DHT11 data pin (with 10k pull-up to 3V3) + * 3V3 -> DHT11 VCC + * GND -> DHT11 GND + */ + +#include +#include "pico/stdlib.h" +#include "dht11.h" + +/** + * @brief Print pre-computed operator results + * + * @details Prints all six operator demonstration values over UART. + * + * @param arith arithmetic operator result + * @param inc increment operator result + * @param rel relational operator result + * @param logic logical operator result + * @param bitw bitwise operator result + * @param assign assignment operator result + * @retval None + */ +static void print_operator_results(int arith, int inc, bool rel, + bool logic, int bitw, int assign) { + printf("arithmetic_operator: %d\r\n", arith); + printf("increment_operator: %d\r\n", inc); + printf("relational_operator: %d\r\n", rel); + printf("logical_operator: %d\r\n", logic); + printf("bitwise_operator: %d\r\n", bitw); + printf("assignment_operator: %d\r\n", assign); +} + +/** + * @brief Compute arithmetic and increment operator results + * + * @param arith pointer to store multiplication result + * @param incr pointer to store post-increment result + * @param x left operand + * @param y right operand + * @retval None + */ +static void compute_arithmetic_ops(int *arith, int *incr, int x, int y) { + *arith = (x * y); + *incr = x++; +} + +/** + * @brief Compute all operator values and print results + * + * @details Performs arithmetic, relational, logical, bitwise, + * and assignment operations, then prints each result. + * + * @retval None + */ +static void compute_operators(void) { + int x = 5, y = 10; + int arith, incr; + compute_arithmetic_ops(&arith, &incr, x, y); + bool rel = (x > y); + bool logic = (x > y) && (y > x); + int bits = (x << 1); + int assign = (x += 5); + print_operator_results(arith, incr, rel, logic, bits, assign); +} + +/** + * @brief Read and print DHT11 humidity and temperature + * + * @details Attempts a DHT11 read; on success prints humidity and + * temperature, on failure prints an error message. + * + * @retval None + */ +static void print_dht11_reading(void) { + float hum, temp; + if (dht11_read(&hum, &temp)) { + printf("Humidity: %.1f%%, Temperature: %.1f°C\r\n", hum, temp); + } else { + printf("DHT11 read failed\r\n"); + } +} + +int main(void) { + stdio_init_all(); + dht11_init(4); + while (true) { + compute_operators(); + print_dht11_reading(); + sleep_ms(2000); + } +} \ No newline at end of file diff --git a/0x001a_operators/CMakeLists.txt b/0x001a_operators/CMakeLists.txt new file mode 100644 index 0000000..f6ef427 --- /dev/null +++ b/0x001a_operators/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0-a4) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x001a_operators C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x001a_operators 0x001a_operators.c dht11.c ) + +pico_set_program_name(0x001a_operators "0x001a_operators") +pico_set_program_version(0x001a_operators "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x001a_operators 1) +pico_enable_stdio_usb(0x001a_operators 0) + +# Add the standard library to the build +target_link_libraries(0x001a_operators + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x001a_operators PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x001a_operators) + diff --git a/0x001a_operators/dht11.c b/0x001a_operators/dht11.c new file mode 100644 index 0000000..28f147f --- /dev/null +++ b/0x001a_operators/dht11.c @@ -0,0 +1,140 @@ +/** + * @file dht11.c + * @brief Implementation of DHT11 temperature and humidity sensor driver + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "dht11.h" +#include "hardware/gpio.h" +#include "pico/time.h" + +/** @brief GPIO pin connected to the DHT11 sensor */ +static uint dht_pin; + +/** + * @brief Send the DHT11 start signal on the data pin + * + * Drives the pin LOW for 18 ms then HIGH for 40 us before switching + * the pin to input mode to listen for the sensor response. + */ +static void send_start_signal(void) { + gpio_set_dir(dht_pin, GPIO_OUT); + gpio_put(dht_pin, 0); + sleep_ms(18); + gpio_put(dht_pin, 1); + sleep_us(40); + gpio_set_dir(dht_pin, GPIO_IN); +} + +/** + * @brief Wait for the pin to leave a given logic level + * + * Spins until the pin no longer reads the specified level, returning + * false if a timeout of 10 000 iterations is exceeded. + * + * @param level Logic level to wait through (0 or 1) + * @return bool true once the level changed, false on timeout + */ +static bool wait_for_level(int level) { + uint32_t timeout = 10000; + while (gpio_get(dht_pin) == level) + if (--timeout == 0) return false; + return true; +} + +/** + * @brief Wait for the DHT11 response after the start signal + * + * The sensor pulls LOW then HIGH then LOW again; each transition + * is awaited with a timeout. + * + * @return bool true if the full response was received, false on timeout + */ +static bool wait_response(void) { + if (!wait_for_level(1)) return false; + if (!wait_for_level(0)) return false; + if (!wait_for_level(1)) return false; + return true; +} + +/** + * @brief Read a single bit from the DHT11 data stream + * + * Waits for the low-period to end, measures the high-period duration, + * and shifts the result into the appropriate byte of the data array. + * + * @param data 5-byte array accumulating the received bits + * @param i Bit index (0-39) + * @return bool true on success, false on timeout + */ +static bool read_bit(uint8_t *data, int i) { + if (!wait_for_level(0)) return false; + uint32_t start = time_us_32(); + if (!wait_for_level(1)) return false; + uint32_t duration = time_us_32() - start; + data[i / 8] <<= 1; + if (duration > 40) data[i / 8] |= 1; + return true; +} + +/** + * @brief Read all 40 data bits from the DHT11 + * + * @param data 5-byte array filled with the received data + * @return bool true if all 40 bits were read, false on timeout + */ +static bool read_40_bits(uint8_t *data) { + for (int i = 0; i < 40; i++) + if (!read_bit(data, i)) return false; + return true; +} + +/** + * @brief Verify the DHT11 checksum byte + * + * @param data 5-byte received data (bytes 0-3 plus checksum in byte 4) + * @return bool true if the checksum matches, false otherwise + */ +static bool validate_checksum(const uint8_t *data) { + return data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF); +} + +void dht11_init(uint8_t pin) { + dht_pin = pin; + gpio_init(pin); + gpio_pull_up(pin); +} + +bool dht11_read(float *humidity, float *temperature) { + uint8_t data[5] = {0}; + send_start_signal(); + if (!wait_response()) return false; + if (!read_40_bits(data)) return false; + if (!validate_checksum(data)) return false; + *humidity = data[0] + data[1] * 0.1f; + *temperature = data[2] + data[3] * 0.1f; + return true; +} diff --git a/0x001a_operators/dht11.h b/0x001a_operators/dht11.h new file mode 100644 index 0000000..30e298c --- /dev/null +++ b/0x001a_operators/dht11.h @@ -0,0 +1,57 @@ +/** + * @file dht11.h + * @brief Header for DHT11 temperature and humidity sensor driver + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef DHT11_H +#define DHT11_H + +#include +#include + +/** + * @brief Initialize the DHT11 driver + * + * Configures the GPIO pin for the DHT11 sensor. This must be called before + * using dht11_read(). + * + * @param pin GPIO pin number connected to DHT11 signal + */ +void dht11_init(uint8_t pin); + +/** + * @brief Read temperature and humidity from DHT11 sensor + * + * Performs the DHT11 communication protocol to read sensor data. + * + * @param humidity Pointer to store humidity value (0-100%) + * @param temperature Pointer to store temperature value in Celsius + * @return true if read successful, false on error or timeout + */ +bool dht11_read(float *humidity, float *temperature); + +#endif // DHT11_H diff --git a/0x001a_operators/hack-temp.py b/0x001a_operators/hack-temp.py new file mode 100644 index 0000000..8973073 --- /dev/null +++ b/0x001a_operators/hack-temp.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 + +""" +FILE: hack-temp.py + +DESCRIPTION: +Incremental firmware patcher with minimal, conservative steps to bias readings. + +BRIEF: +Provides three modes: +- pool_only: change only the literal pool constant (scaling effect; least invasive) +- temp_only_vadd: convert temperature path to vadd.f32 and set a small negative pool +- both_vadd: convert both paths to vadd.f32 and set a small negative pool + +Intended to let you test changes incrementally, keeping humidity stable while +you evaluate temperature bias first. + +AUTHOR: Kevin Thomas +CREATION DATE: November 1, 2025 +UPDATE DATE: November 1, 2025 +""" + +import sys +import os +import struct + +BIN_PATH = "build/0x001a_operators.bin" + +# Offsets +OFF_410 = 0x410 +OFF_414 = 0x414 +OFF_POOL = 0x42C + +# Original encodings (for visibility only) +ORIG_410 = bytes.fromhex("a6ee257a") # vfma.f32 s14, s12, s11 +ORIG_414 = bytes.fromhex("e6eea57a") # vfma.f32 s15, s13, s11 +ORIG_POOL = bytes.fromhex("cccccc3d") # 0.1f + +# Encodings for vadd.f32 s14, s14, s11 and vadd.f32 s15, s15, s11 +VADD_S14_S11 = struct.pack(" 1 else "pool_only").strip().lower() + if mode == "pool_only": + patch_pool_only(new_pool_float=-1.0) + elif mode == "temp_only_vadd": + patch_temp_only_vadd(new_pool_float=-2.0) + elif mode == "both_vadd": + patch_both_vadd(new_pool_float=-2.0) + else: + print(f"Unknown mode: {mode}") + print("Use: pool_only | temp_only_vadd | both_vadd") + sys.exit(2) + + +if __name__ == "__main__": + main() + print("Patch complete. Convert to UF2 and flash. Test each mode incrementally.") diff --git a/0x001a_operators/pico_sdk_import.cmake b/0x001a_operators/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x001a_operators/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x001d_static-conditionals/.gitignore b/0x001d_static-conditionals/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x001d_static-conditionals/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x001d_static-conditionals/.vscode/c_cpp_properties.json b/0x001d_static-conditionals/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..c429d6d --- /dev/null +++ b/0x001d_static-conditionals/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h", + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x001d_static-conditionals/.vscode/cmake-kits.json b/0x001d_static-conditionals/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x001d_static-conditionals/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x001d_static-conditionals/.vscode/extensions.json b/0x001d_static-conditionals/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x001d_static-conditionals/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x001d_static-conditionals/.vscode/launch.json b/0x001d_static-conditionals/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x001d_static-conditionals/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x001d_static-conditionals/.vscode/settings.json b/0x001d_static-conditionals/.vscode/settings.json new file mode 100644 index 0000000..95be83b --- /dev/null +++ b/0x001d_static-conditionals/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x001d_static-conditionals/.vscode/tasks.json b/0x001d_static-conditionals/.vscode/tasks.json new file mode 100644 index 0000000..d1b3193 --- /dev/null +++ b/0x001d_static-conditionals/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "RISC-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x001d_static-conditionals/0x001d_static-conditionals.c b/0x001d_static-conditionals/0x001d_static-conditionals.c new file mode 100644 index 0000000..6db9a5e --- /dev/null +++ b/0x001d_static-conditionals/0x001d_static-conditionals.c @@ -0,0 +1,106 @@ +/** + * @file 0x001d_static-conditionals.c + * @brief Static conditionals: if/else and switch with servo sweep + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Demonstrates static (compile-time known) conditionals using if/else if/else + * and switch/case statements. Also sweeps a servo on GPIO6 between 0 and + * 180 degrees each iteration. + * + * Wiring: + * GPIO6 -> Servo signal wire (orange/white) + * 5V -> Servo VCC (red) + * GND -> Servo GND (brown/black) + */ + +#include +#include "pico/stdlib.h" +#include "servo.h" + +/** @brief GPIO pin number for the servo */ +#define SERVO_GPIO 6 + +/** + * @brief Print choice value using if/else if/else conditional + * + * @details Prints "1", "2", or "?" depending on the choice value. + * + * @param choice integer value to evaluate + * @retval None + */ +static void print_if_else(int choice) { + if (choice == 1) { + printf("1\r\n"); + } else if (choice == 2) { + printf("2\r\n"); + } else { + printf("?\r\n"); + } +} + +/** + * @brief Print choice value using switch/case conditional + * + * @details Prints "one", "two", or "??" depending on the choice value. + * + * @param choice integer value to evaluate + * @retval None + */ +static void print_switch(int choice) { + switch (choice) { + case 1: printf("one\r\n"); break; + case 2: printf("two\r\n"); break; + default: printf("??\r\n"); + } +} + +/** + * @brief Sweep servo between 0 and 180 degrees + * + * @details Sets the servo to 0 degrees, waits 500ms, then sets + * to 180 degrees and waits 500ms. + * + * @retval None + */ +static void sweep_servo(void) { + servo_set_angle(0.0f); + sleep_ms(500); + servo_set_angle(180.0f); + sleep_ms(500); +} + +int main(void) { + stdio_init_all(); + int choice = 1; + servo_init(SERVO_GPIO); + while (true) { + print_if_else(choice); + print_switch(choice); + sweep_servo(); + } +} \ No newline at end of file diff --git a/0x001d_static-conditionals/CMakeLists.txt b/0x001d_static-conditionals/CMakeLists.txt new file mode 100644 index 0000000..7b03b00 --- /dev/null +++ b/0x001d_static-conditionals/CMakeLists.txt @@ -0,0 +1,59 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0-a4) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x001d_static-conditionals C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x001d_static-conditionals 0x001d_static-conditionals.c servo.c) + +pico_set_program_name(0x001d_static-conditionals "0x001d_static-conditionals") +pico_set_program_version(0x001d_static-conditionals "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x001d_static-conditionals 1) +pico_enable_stdio_usb(0x001d_static-conditionals 0) + +# Add the standard library to the build +target_link_libraries(0x001d_static-conditionals + pico_stdlib + hardware_pwm + hardware_clocks) + +# Add the standard include files to the build +target_include_directories(0x001d_static-conditionals PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x001d_static-conditionals) + diff --git a/0x001d_static-conditionals/pico_sdk_import.cmake b/0x001d_static-conditionals/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x001d_static-conditionals/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x001d_static-conditionals/servo.c b/0x001d_static-conditionals/servo.c new file mode 100644 index 0000000..d0fefbd --- /dev/null +++ b/0x001d_static-conditionals/servo.c @@ -0,0 +1,107 @@ +/** + * @file servo.c + * @brief Implementation of a simple SG90 servo driver using PWM (50Hz) + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "servo.h" +#include "pico/stdlib.h" +#include "hardware/pwm.h" +#include "hardware/clocks.h" + +/** @brief Default minimum pulse width in microseconds */ +static const uint16_t SERVO_DEFAULT_MIN_US = 1000; +/** @brief Default maximum pulse width in microseconds */ +static const uint16_t SERVO_DEFAULT_MAX_US = 2000; + +/** @brief GPIO pin assigned to the servo */ +static uint8_t servo_pin = 0; +/** @brief PWM hardware slice for the servo pin */ +static uint servo_slice = 0; +/** @brief PWM channel within the servo slice */ +static uint servo_chan = 0; +/** @brief PWM counter wrap value for 50 Hz servo */ +static uint32_t servo_wrap = 20000 - 1; +/** @brief Servo PWM frequency in Hz */ +static float servo_hz = 50.0f; +/** @brief Flag indicating servo has been initialized */ +static bool servo_initialized = false; + +/** + * @brief Convert a pulse width in microseconds to a PWM counter level + * + * Uses the configured PWM wrap and servo frequency to map pulse time + * into the channel compare value expected by the PWM hardware. + * + * @param pulse_us Pulse width in microseconds + * @return uint32_t PWM level suitable for pwm_set_chan_level() + */ +static uint32_t pulse_us_to_level(uint32_t pulse_us) { + const float period_us = 1000000.0f / servo_hz; + float counts_per_us = (servo_wrap + 1) / period_us; + return (uint32_t)(pulse_us * counts_per_us + 0.5f); +} + +/** + * @brief Build and apply the PWM slice configuration for 50 Hz servo + * + * Computes the clock divider from the system clock to achieve the + * target servo frequency with the chosen wrap value, then starts + * the PWM slice. + */ +static void apply_servo_config(void) { + pwm_config config = pwm_get_default_config(); + const uint32_t sys_clock_hz = clock_get_hz(clk_sys); + float clock_div = (float)sys_clock_hz / (servo_hz * (servo_wrap + 1)); + pwm_config_set_clkdiv(&config, clock_div); + pwm_config_set_wrap(&config, servo_wrap); + pwm_init(servo_slice, &config, true); +} + +void servo_init(uint8_t pin) { + servo_pin = pin; + gpio_set_function(servo_pin, GPIO_FUNC_PWM); + servo_slice = pwm_gpio_to_slice_num(servo_pin); + servo_chan = pwm_gpio_to_channel(servo_pin); + apply_servo_config(); + servo_initialized = true; +} + +void servo_set_pulse_us(uint16_t pulse_us) { + if (!servo_initialized) return; + if (pulse_us < SERVO_DEFAULT_MIN_US) pulse_us = SERVO_DEFAULT_MIN_US; + if (pulse_us > SERVO_DEFAULT_MAX_US) pulse_us = SERVO_DEFAULT_MAX_US; + uint32_t level = pulse_us_to_level(pulse_us); + pwm_set_chan_level(servo_slice, servo_chan, level); +} + +void servo_set_angle(float degrees) { + if (degrees < 0.0f) degrees = 0.0f; + if (degrees > 180.0f) degrees = 180.0f; + float ratio = degrees / 180.0f; + uint16_t pulse = (uint16_t)(SERVO_DEFAULT_MIN_US + ratio * (SERVO_DEFAULT_MAX_US - SERVO_DEFAULT_MIN_US) + 0.5f); + servo_set_pulse_us(pulse); +} \ No newline at end of file diff --git a/0x001d_static-conditionals/servo.h b/0x001d_static-conditionals/servo.h new file mode 100644 index 0000000..878d029 --- /dev/null +++ b/0x001d_static-conditionals/servo.h @@ -0,0 +1,62 @@ +/** + * @file servo.h + * @brief Header for SG90 servo driver (PWM) + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SERVO_H +#define SERVO_H + +#include +#include + +/** + * @brief Initialize servo driver on a given GPIO pin + * + * Configures PWM for a 50 Hz servo (SG90). Call once before using other API. + * + * @param pin GPIO pin number to use for servo PWM + */ +void servo_init(uint8_t pin); + +/** + * @brief Set servo pulse width in microseconds (typical 1000-2000) + * + * @param pulse_us Pulse width in microseconds + */ +void servo_set_pulse_us(uint16_t pulse_us); + +/** + * @brief Set servo angle in degrees (0 to 180) + * + * Maps 0..180 degrees to the configured pulse range (default 1000..2000 us). + * Values outside [0,180] will be clamped. + * + * @param degrees Angle in degrees + */ +void servo_set_angle(float degrees); + +#endif // SERVO_H diff --git a/0x0020_dynamic-conditionals/.gitignore b/0x0020_dynamic-conditionals/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x0020_dynamic-conditionals/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x0020_dynamic-conditionals/.vscode/c_cpp_properties.json b/0x0020_dynamic-conditionals/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..c429d6d --- /dev/null +++ b/0x0020_dynamic-conditionals/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h", + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x0020_dynamic-conditionals/.vscode/cmake-kits.json b/0x0020_dynamic-conditionals/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x0020_dynamic-conditionals/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x0020_dynamic-conditionals/.vscode/extensions.json b/0x0020_dynamic-conditionals/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x0020_dynamic-conditionals/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x0020_dynamic-conditionals/.vscode/launch.json b/0x0020_dynamic-conditionals/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x0020_dynamic-conditionals/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x0020_dynamic-conditionals/.vscode/settings.json b/0x0020_dynamic-conditionals/.vscode/settings.json new file mode 100644 index 0000000..95be83b --- /dev/null +++ b/0x0020_dynamic-conditionals/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x0020_dynamic-conditionals/.vscode/tasks.json b/0x0020_dynamic-conditionals/.vscode/tasks.json new file mode 100644 index 0000000..d1b3193 --- /dev/null +++ b/0x0020_dynamic-conditionals/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "RISC-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x0020_dynamic-conditionals/0x0020_dynamic-conditionals.c b/0x0020_dynamic-conditionals/0x0020_dynamic-conditionals.c new file mode 100644 index 0000000..028fcd4 --- /dev/null +++ b/0x0020_dynamic-conditionals/0x0020_dynamic-conditionals.c @@ -0,0 +1,111 @@ +/** + * @file 0x0020_dynamic-conditionals.c + * @brief Dynamic conditionals: UART input drives if/else and switch with servo + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Demonstrates dynamic (runtime) conditionals using UART input via getchar(). + * Reads a character, evaluates it with if/else and switch/case, and controls + * a servo on GPIO6 based on the input ('1' or '2'). + * + * Wiring: + * GPIO6 -> Servo signal wire (orange/white) + * 5V -> Servo VCC (red) + * GND -> Servo GND (brown/black) + */ + +#include +#include "pico/stdlib.h" +#include "servo.h" + +/** @brief GPIO pin number for the servo */ +#define SERVO_GPIO 6 + +/** + * @brief Evaluate choice with if/else conditional and print result + * + * @details Compares against hex ASCII values 0x31 ('1') and 0x32 ('2'). + * + * @param choice character value received from UART + * @retval None + */ +static void eval_if_else(uint8_t choice) { + if (choice == 0x31) { + printf("1\r\n"); + } else if (choice == 0x32) { + printf("2\r\n"); + } else { + printf("??\r\n"); + } +} + +/** + * @brief Sweep servo from start angle to end angle with a pause + * + * @details Sets the servo to start, waits 500 ms, sets to end, + * waits 500 ms. + * + * @param label text to print before sweeping + * @param start starting angle in degrees + * @param end ending angle in degrees + * @retval None + */ +static void sweep_servo(const char *label, float start, float end) { + printf("%s\r\n", label); + servo_set_angle(start); + sleep_ms(500); + servo_set_angle(end); + sleep_ms(500); +} + +/** + * @brief Process choice with switch/case and drive servo accordingly + * + * @details On '1', sweeps servo 0->180; on '2', sweeps 180->0; + * otherwise prints unknown. + * + * @param choice character value received from UART + * @retval None + */ +static void process_servo_command(uint8_t choice) { + switch (choice) { + case '1': sweep_servo("one", 0.0f, 180.0f); break; + case '2': sweep_servo("two", 180.0f, 0.0f); break; + default: printf("??\r\n"); + } +} + +int main(void) { + stdio_init_all(); + uint8_t choice = 0; + servo_init(SERVO_GPIO); + while (true) { + choice = getchar(); + eval_if_else(choice); + process_servo_command(choice); + } +} diff --git a/0x0020_dynamic-conditionals/CMakeLists.txt b/0x0020_dynamic-conditionals/CMakeLists.txt new file mode 100644 index 0000000..4859d4e --- /dev/null +++ b/0x0020_dynamic-conditionals/CMakeLists.txt @@ -0,0 +1,59 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0-a4) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x0020_dynamic-conditionals C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x0020_dynamic-conditionals 0x0020_dynamic-conditionals.c servo.c) + +pico_set_program_name(0x0020_dynamic-conditionals "0x0020_dynamic-conditionals") +pico_set_program_version(0x0020_dynamic-conditionals "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x0020_dynamic-conditionals 1) +pico_enable_stdio_usb(0x0020_dynamic-conditionals 0) + +# Add the standard library to the build +target_link_libraries(0x0020_dynamic-conditionals + pico_stdlib + hardware_pwm + hardware_clocks) + +# Add the standard include files to the build +target_include_directories(0x0020_dynamic-conditionals PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x0020_dynamic-conditionals) + diff --git a/0x0020_dynamic-conditionals/pico_sdk_import.cmake b/0x0020_dynamic-conditionals/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x0020_dynamic-conditionals/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x0020_dynamic-conditionals/servo.c b/0x0020_dynamic-conditionals/servo.c new file mode 100644 index 0000000..d0fefbd --- /dev/null +++ b/0x0020_dynamic-conditionals/servo.c @@ -0,0 +1,107 @@ +/** + * @file servo.c + * @brief Implementation of a simple SG90 servo driver using PWM (50Hz) + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "servo.h" +#include "pico/stdlib.h" +#include "hardware/pwm.h" +#include "hardware/clocks.h" + +/** @brief Default minimum pulse width in microseconds */ +static const uint16_t SERVO_DEFAULT_MIN_US = 1000; +/** @brief Default maximum pulse width in microseconds */ +static const uint16_t SERVO_DEFAULT_MAX_US = 2000; + +/** @brief GPIO pin assigned to the servo */ +static uint8_t servo_pin = 0; +/** @brief PWM hardware slice for the servo pin */ +static uint servo_slice = 0; +/** @brief PWM channel within the servo slice */ +static uint servo_chan = 0; +/** @brief PWM counter wrap value for 50 Hz servo */ +static uint32_t servo_wrap = 20000 - 1; +/** @brief Servo PWM frequency in Hz */ +static float servo_hz = 50.0f; +/** @brief Flag indicating servo has been initialized */ +static bool servo_initialized = false; + +/** + * @brief Convert a pulse width in microseconds to a PWM counter level + * + * Uses the configured PWM wrap and servo frequency to map pulse time + * into the channel compare value expected by the PWM hardware. + * + * @param pulse_us Pulse width in microseconds + * @return uint32_t PWM level suitable for pwm_set_chan_level() + */ +static uint32_t pulse_us_to_level(uint32_t pulse_us) { + const float period_us = 1000000.0f / servo_hz; + float counts_per_us = (servo_wrap + 1) / period_us; + return (uint32_t)(pulse_us * counts_per_us + 0.5f); +} + +/** + * @brief Build and apply the PWM slice configuration for 50 Hz servo + * + * Computes the clock divider from the system clock to achieve the + * target servo frequency with the chosen wrap value, then starts + * the PWM slice. + */ +static void apply_servo_config(void) { + pwm_config config = pwm_get_default_config(); + const uint32_t sys_clock_hz = clock_get_hz(clk_sys); + float clock_div = (float)sys_clock_hz / (servo_hz * (servo_wrap + 1)); + pwm_config_set_clkdiv(&config, clock_div); + pwm_config_set_wrap(&config, servo_wrap); + pwm_init(servo_slice, &config, true); +} + +void servo_init(uint8_t pin) { + servo_pin = pin; + gpio_set_function(servo_pin, GPIO_FUNC_PWM); + servo_slice = pwm_gpio_to_slice_num(servo_pin); + servo_chan = pwm_gpio_to_channel(servo_pin); + apply_servo_config(); + servo_initialized = true; +} + +void servo_set_pulse_us(uint16_t pulse_us) { + if (!servo_initialized) return; + if (pulse_us < SERVO_DEFAULT_MIN_US) pulse_us = SERVO_DEFAULT_MIN_US; + if (pulse_us > SERVO_DEFAULT_MAX_US) pulse_us = SERVO_DEFAULT_MAX_US; + uint32_t level = pulse_us_to_level(pulse_us); + pwm_set_chan_level(servo_slice, servo_chan, level); +} + +void servo_set_angle(float degrees) { + if (degrees < 0.0f) degrees = 0.0f; + if (degrees > 180.0f) degrees = 180.0f; + float ratio = degrees / 180.0f; + uint16_t pulse = (uint16_t)(SERVO_DEFAULT_MIN_US + ratio * (SERVO_DEFAULT_MAX_US - SERVO_DEFAULT_MIN_US) + 0.5f); + servo_set_pulse_us(pulse); +} \ No newline at end of file diff --git a/0x0020_dynamic-conditionals/servo.h b/0x0020_dynamic-conditionals/servo.h new file mode 100644 index 0000000..878d029 --- /dev/null +++ b/0x0020_dynamic-conditionals/servo.h @@ -0,0 +1,62 @@ +/** + * @file servo.h + * @brief Header for SG90 servo driver (PWM) + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SERVO_H +#define SERVO_H + +#include +#include + +/** + * @brief Initialize servo driver on a given GPIO pin + * + * Configures PWM for a 50 Hz servo (SG90). Call once before using other API. + * + * @param pin GPIO pin number to use for servo PWM + */ +void servo_init(uint8_t pin); + +/** + * @brief Set servo pulse width in microseconds (typical 1000-2000) + * + * @param pulse_us Pulse width in microseconds + */ +void servo_set_pulse_us(uint16_t pulse_us); + +/** + * @brief Set servo angle in degrees (0 to 180) + * + * Maps 0..180 degrees to the configured pulse range (default 1000..2000 us). + * Values outside [0,180] will be clamped. + * + * @param degrees Angle in degrees + */ +void servo_set_angle(float degrees); + +#endif // SERVO_H diff --git a/0x0023_structures/.gitignore b/0x0023_structures/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x0023_structures/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x0023_structures/.vscode/c_cpp_properties.json b/0x0023_structures/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..c429d6d --- /dev/null +++ b/0x0023_structures/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h", + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x0023_structures/.vscode/cmake-kits.json b/0x0023_structures/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x0023_structures/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x0023_structures/.vscode/extensions.json b/0x0023_structures/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x0023_structures/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x0023_structures/.vscode/launch.json b/0x0023_structures/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x0023_structures/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x0023_structures/.vscode/settings.json b/0x0023_structures/.vscode/settings.json new file mode 100644 index 0000000..95be83b --- /dev/null +++ b/0x0023_structures/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x0023_structures/.vscode/tasks.json b/0x0023_structures/.vscode/tasks.json new file mode 100644 index 0000000..d1b3193 --- /dev/null +++ b/0x0023_structures/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "RISC-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x0023_structures/0x0023_structures.c b/0x0023_structures/0x0023_structures.c new file mode 100644 index 0000000..657dc5f --- /dev/null +++ b/0x0023_structures/0x0023_structures.c @@ -0,0 +1,144 @@ +/** + * @file 0x0023_structures.c + * @brief Structures: IR remote controls LEDs via a struct-based controller + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Demonstrates C structures by defining a simple_led_ctrl_t to manage three + * LEDs. An IR receiver on GPIO5 decodes NEC commands and activates the + * corresponding LED (0x0C -> GPIO16, 0x18 -> GPIO17, 0x5E -> GPIO18). + * + * Wiring: + * GPIO5 -> IR receiver OUT + * GPIO16 -> LED1 anode (with current-limiting resistor to GND) + * GPIO17 -> LED2 anode (with current-limiting resistor to GND) + * GPIO18 -> LED3 anode (with current-limiting resistor to GND) + */ + +#include +#include +#include "pico/stdlib.h" +#include "ir.h" + +/** @brief GPIO pin number for the IR receiver */ +#define IR_PIN 5 + +typedef struct { + uint8_t led1_pin; + uint8_t led2_pin; + uint8_t led3_pin; + bool led1_state; + bool led2_state; + bool led3_state; +} simple_led_ctrl_t; + +/** + * @brief Initialize all three LED GPIO pins as outputs + * + * @details Configures led1_pin, led2_pin, and led3_pin from the + * structure as GPIO outputs. + * + * @param leds pointer to the LED controller structure + * @retval None + */ +static void init_led_gpios(simple_led_ctrl_t *leds) { + gpio_init(leds->led1_pin); + gpio_set_dir(leds->led1_pin, GPIO_OUT); + gpio_init(leds->led2_pin); + gpio_set_dir(leds->led2_pin, GPIO_OUT); + gpio_init(leds->led3_pin); + gpio_set_dir(leds->led3_pin, GPIO_OUT); +} + +/** + * @brief Process an NEC IR key and update LED states + * + * @details Maps NEC command codes to LEDs: 0x0C -> LED1, 0x18 -> LED2, + * 0x5E -> LED3. Turns off all LEDs first, then activates the match. + * + * @param leds pointer to the LED controller structure + * @param key NEC command code from IR receiver + * @retval None + */ +static void process_ir_key(simple_led_ctrl_t *leds, int key) { + printf("NEC command: 0x%02X\n", key); + leds->led1_state = (key == 0x0C); + leds->led2_state = (key == 0x18); + leds->led3_state = (key == 0x5E); + gpio_put(leds->led1_pin, leds->led1_state); + gpio_put(leds->led2_pin, leds->led2_state); + gpio_put(leds->led3_pin, leds->led3_state); + sleep_ms(10); +} + +/** + * @brief Poll IR receiver and dispatch key to handler + * + * @details Reads one IR key; if valid, processes it; otherwise + * yields with a short sleep. + * + * @param leds pointer to the LED controller structure + * @retval None + */ +static void poll_ir(simple_led_ctrl_t *leds) { + int key = ir_getkey(); + if (key >= 0) { + process_ir_key(leds, key); + } else { + sleep_ms(1); + } +} + +/** + * @brief Build the default LED controller structure + * + * @details Initializes pins 16-18 with all LEDs off. + * + * @return simple_led_ctrl_t initialized structure + */ +static simple_led_ctrl_t make_default_leds(void) { + simple_led_ctrl_t leds = { + .led1_pin = 16, + .led2_pin = 17, + .led3_pin = 18, + .led1_state = false, + .led2_state = false, + .led3_state = false + }; + return leds; +} + +int main(void) { + stdio_init_all(); + simple_led_ctrl_t leds = make_default_leds(); + init_led_gpios(&leds); + ir_init(IR_PIN); + printf("IR receiver on GPIO %d ready\n", IR_PIN); + while (true) { + poll_ir(&leds); + } +} diff --git a/0x0023_structures/CMakeLists.txt b/0x0023_structures/CMakeLists.txt new file mode 100644 index 0000000..d171bb7 --- /dev/null +++ b/0x0023_structures/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0-a4) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x0023_structures C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x0023_structures 0x0023_structures.c ir.c) + +pico_set_program_name(0x0023_structures "0x0023_structures") +pico_set_program_version(0x0023_structures "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x0023_structures 1) +pico_enable_stdio_usb(0x0023_structures 0) + +# Add the standard library to the build +target_link_libraries(0x0023_structures + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x0023_structures PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x0023_structures) + diff --git a/0x0023_structures/ir.c b/0x0023_structures/ir.c new file mode 100644 index 0000000..9d5b42a --- /dev/null +++ b/0x0023_structures/ir.c @@ -0,0 +1,130 @@ +/** + * @file ir.c + * @brief Implementation of NEC IR receiver (decoder) + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "ir.h" +#include "pico/stdlib.h" +#include "pico/time.h" +#include "hardware/gpio.h" + +/** @brief GPIO pin connected to the IR receiver */ +static unsigned int ir_pin = 0; + +/** + * @brief Wait for a GPIO pin to reach a given logic level + * + * Spins until the pin matches the requested level or a microsecond timeout + * is exceeded. Returns the elapsed time in microseconds, or -1 on timeout. + * + * @param gpio GPIO pin to monitor + * @param level Desired logic level (true = HIGH, false = LOW) + * @param timeout_us Maximum wait in microseconds + * @return int64_t Elapsed microseconds, or -1 on timeout + */ +static int64_t wait_for_level(unsigned int gpio, bool level, uint32_t timeout_us) { + absolute_time_t start = get_absolute_time(); + while (gpio_get(gpio) != level) { + if (absolute_time_diff_us(start, get_absolute_time()) > (int64_t)timeout_us) + return -1; + } + return absolute_time_diff_us(start, get_absolute_time()); +} + +/** + * @brief Wait for the NEC 9 ms leader pulse and 4.5 ms space + * + * @return bool true if a valid leader was detected, false on timeout + */ +static bool wait_leader(void) { + if (wait_for_level(ir_pin, 0, 150000) < 0) return false; + int64_t t = wait_for_level(ir_pin, 1, 12000); + if (t < 8000 || t > 10000) return false; + t = wait_for_level(ir_pin, 0, 7000); + if (t < 3500 || t > 5000) return false; + return true; +} + +/** + * @brief Read a single NEC-encoded bit from the IR receiver + * + * Measures the mark/space timing and shifts the result into the + * appropriate byte of the data array. + * + * @param data 4-byte array accumulating received bits + * @param i Bit index (0-31) + * @return bool true on success, false on timeout or protocol error + */ +static bool read_nec_bit(uint8_t *data, int i) { + if (wait_for_level(ir_pin, 1, 1000) < 0) return false; + int64_t t = wait_for_level(ir_pin, 0, 2500); + if (t < 200) return false; + int byte_idx = i / 8; + int bit_idx = i % 8; + if (t > 1200) data[byte_idx] |= (1 << bit_idx); + return true; +} + +/** + * @brief Read all 32 data bits of an NEC frame + * + * @param data 4-byte array filled with the received address and command + * @return bool true if all 32 bits were read, false on timeout + */ +static bool read_32_bits(uint8_t *data) { + for (int i = 0; i < 32; ++i) + if (!read_nec_bit(data, i)) return false; + return true; +} + +/** + * @brief Validate an NEC frame and extract the command byte + * + * Checks that the address and command pairs are bitwise-inverted. + * + * @param data 4-byte NEC frame (addr, ~addr, cmd, ~cmd) + * @return int Command byte (0-255) on success, -1 on validation failure + */ +static int validate_nec_frame(const uint8_t *data) { + if ((uint8_t)(data[0] + data[1]) == 0xFF && (uint8_t)(data[2] + data[3]) == 0xFF) + return data[2]; + return -1; +} + +void ir_init(uint8_t pin) { + ir_pin = pin; + gpio_init(pin); + gpio_set_dir(pin, GPIO_IN); + gpio_pull_up(pin); +} + +int ir_getkey(void) { + if (!wait_leader()) return -1; + uint8_t data[4] = {0, 0, 0, 0}; + if (!read_32_bits(data)) return -1; + return validate_nec_frame(data); +} diff --git a/0x0023_structures/ir.h b/0x0023_structures/ir.h new file mode 100644 index 0000000..84aa67f --- /dev/null +++ b/0x0023_structures/ir.h @@ -0,0 +1,63 @@ +/** + * @file ir.h + * @brief Header for NEC IR receiver (decoder) API + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef IR_H +#define IR_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize the IR receiver GPIO + * + * Configures the given `pin` as an input with pull-up for the NEC IR receiver. + * Call this once during board initialization before calling `ir_getkey()`. + * + * @param pin GPIO pin number connected to IR receiver output + */ +void ir_init(uint8_t pin); + +/** + * @brief Blocking NEC IR decoder + * + * Blocks while waiting for a complete NEC frame. On success returns the + * decoded command byte (0..255). On timeout or protocol error returns -1. + * + * @return decoded command byte (0..255) or -1 on failure + */ +int ir_getkey(void); + +#ifdef __cplusplus +} +#endif + +#endif // IR_H diff --git a/0x0023_structures/pico_sdk_import.cmake b/0x0023_structures/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x0023_structures/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/0x0026_functions/.gitignore b/0x0026_functions/.gitignore new file mode 100644 index 0000000..2435c20 --- /dev/null +++ b/0x0026_functions/.gitignore @@ -0,0 +1,2 @@ +build +!.vscode/* diff --git a/0x0026_functions/.vscode/c_cpp_properties.json b/0x0026_functions/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..c429d6d --- /dev/null +++ b/0x0026_functions/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${userHome}/.pico-sdk/sdk/2.2.0/**" + ], + "forcedInclude": [ + "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h", + "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h" + ], + "defines": [], + "compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm" + } + ], + "version": 4 +} diff --git a/0x0026_functions/.vscode/cmake-kits.json b/0x0026_functions/.vscode/cmake-kits.json new file mode 100644 index 0000000..b0f3815 --- /dev/null +++ b/0x0026_functions/.vscode/cmake-kits.json @@ -0,0 +1,15 @@ +[ + { + "name": "Pico", + "compilers": { + "C": "${command:raspberry-pi-pico.getCompilerPath}", + "CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}" + }, + "environmentVariables": { + "PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}" + }, + "cmakeSettings": { + "Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}" + } + } +] \ No newline at end of file diff --git a/0x0026_functions/.vscode/extensions.json b/0x0026_functions/.vscode/extensions.json new file mode 100644 index 0000000..a940d7c --- /dev/null +++ b/0x0026_functions/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/0x0026_functions/.vscode/launch.json b/0x0026_functions/.vscode/launch.json new file mode 100644 index 0000000..424aa71 --- /dev/null +++ b/0x0026_functions/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/${command:raspberry-pi-pico.getTarget}.cfg" + ], + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ], + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceRoot}", + "executable": "${command:raspberry-pi-pico.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "${command:raspberry-pi-pico.getGDBPath}", + "device": "${command:raspberry-pi-pico.getChipUppercase}", + "svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd", + "runToEntryPoint": "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + "overrideLaunchCommands": [ + "monitor reset init", + "load \"${command:raspberry-pi-pico.launchTargetPath}\"" + ] + }, + ] +} diff --git a/0x0026_functions/.vscode/settings.json b/0x0026_functions/.vscode/settings.json new file mode 100644 index 0000000..95be83b --- /dev/null +++ b/0x0026_functions/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "cmake.showSystemKits": false, + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "hidden" + }, + "launch": { + "statusBarVisibility": "hidden" + }, + "debug": { + "statusBarVisibility": "hidden" + } + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + "PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + "Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "terminal.integrated.env.linux": { + "PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0", + "PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + "PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}" + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja" +} diff --git a/0x0026_functions/.vscode/tasks.json b/0x0026_functions/.vscode/tasks.json new file mode 100644 index 0000000..d1b3193 --- /dev/null +++ b/0x0026_functions/.vscode/tasks.json @@ -0,0 +1,102 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "${userHome}/.pico-sdk/ninja/v1.12.1/ninja", + "args": ["-C", "${workspaceFolder}/build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$gcc", + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1/ninja.exe" + } + }, + { + "label": "Run Project", + "type": "process", + "command": "${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool", + "args": [ + "load", + "${command:raspberry-pi-pico.launchTargetPath}", + "-fx" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool.exe" + } + }, + { + "label": "Flash", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getTarget}.cfg", + "-c", + "adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "Rescue Reset", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/${command:raspberry-pi-pico.getChip}-rescue.cfg", + "-c", + "adapter speed 5000; reset halt; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + }, + { + "label": "RISC-V Reset (RP2350)", + "type": "process", + "command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + "args": [ + "-s", + "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts", + "-c", + "set USE_CORE { rv0 rv1 cm0 cm1 }", + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2350.cfg", + "-c", + "adapter speed 5000; init;", + "-c", + "write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];", + "-c", + "reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit" + ], + "problemMatcher": [], + "windows": { + "command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe", + } + } + ] +} diff --git a/0x0026_functions/0x0026_functions.c b/0x0026_functions/0x0026_functions.c new file mode 100644 index 0000000..210c7cc --- /dev/null +++ b/0x0026_functions/0x0026_functions.c @@ -0,0 +1,237 @@ +/** + * @file 0x0026_functions.c + * @brief Functions: IR remote controls LEDs with helper function decomposition + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Demonstrates function decomposition by breaking IR-controlled LED logic + * into small, focused helper functions. An IR receiver on GPIO5 decodes + * NEC commands and activates the corresponding LED with a blink effect. + * + * Wiring: + * GPIO5 -> IR receiver OUT + * GPIO16 -> LED1 anode (with current-limiting resistor to GND) + * GPIO17 -> LED2 anode (with current-limiting resistor to GND) + * GPIO18 -> LED3 anode (with current-limiting resistor to GND) + */ + +#include +#include +#include "pico/stdlib.h" +#include "ir.h" + +/** @brief GPIO pin number for the IR receiver */ +#define IR_PIN 5 + +typedef struct { + uint8_t led1_pin; + uint8_t led2_pin; + uint8_t led3_pin; + bool led1_state; + bool led2_state; + bool led3_state; +} simple_led_ctrl_t; + +/** + * @brief Map NEC IR command code to LED number + * + * @details Translates a received NEC IR command code to a logical + * LED number. Supports three button mappings. + * + * @param ir_command NEC command code from IR receiver + * @retval 1-3 for matched LED, 0 if no match + */ +static int ir_to_led_number(int ir_command) { + if (ir_command == 0x0C) return 1; + if (ir_command == 0x18) return 2; + if (ir_command == 0x5E) return 3; + return 0; +} + +/** + * @brief Get GPIO pin number for a given LED number + * + * @details Retrieves the GPIO pin associated with a logical LED + * number from the LED controller structure. + * + * @param leds pointer to LED controller structure + * @param led_num LED number (1-3) + * @retval GPIO pin number or 0 if invalid + */ +static uint8_t get_led_pin(simple_led_ctrl_t *leds, int led_num) { + if (led_num == 1) return leds->led1_pin; + if (led_num == 2) return leds->led2_pin; + if (led_num == 3) return leds->led3_pin; + return 0; +} + +/** + * @brief Turn off all LEDs in the controller + * + * @details Sets all three LED GPIO outputs to low. + * + * @param leds pointer to LED controller structure + * @retval None + */ +static void leds_all_off(simple_led_ctrl_t *leds) { + gpio_put(leds->led1_pin, false); + gpio_put(leds->led2_pin, false); + gpio_put(leds->led3_pin, false); +} + +/** + * @brief Blink an LED pin a specified number of times + * + * @details Toggles the specified GPIO pin on and off for the given + * count, with configurable delay between transitions. + * + * @param pin GPIO pin number to blink + * @param count number of blink cycles + * @param delay_ms delay in milliseconds for on/off periods + * @retval None + */ +static void blink_led(uint8_t pin, uint8_t count, uint32_t delay_ms) { + for (uint8_t i = 0; i < count; i++) { + gpio_put(pin, true); + sleep_ms(delay_ms); + gpio_put(pin, false); + sleep_ms(delay_ms); + } +} + +/** + * @brief Process IR command and activate corresponding LED + * + * @details Turns off all LEDs, maps the command to an LED number, + * blinks it, then holds it steady. + * + * @param ir_command NEC command code from IR receiver + * @param leds pointer to LED controller structure + * @param blink_count number of blinks before steady state + * @retval LED number activated (1-3), 0 if none, -1 if invalid + */ +static int process_ir_led_command(int ir_command, simple_led_ctrl_t *leds, uint8_t blink_count) { + if (!leds || ir_command < 0) return -1; + leds_all_off(leds); + int led_num = ir_to_led_number(ir_command); + if (led_num == 0) return 0; + uint8_t pin = get_led_pin(leds, led_num); + blink_led(pin, blink_count, 50); + gpio_put(pin, true); + return led_num; +} + +/** + * @brief Initialize all three LED GPIO pins as outputs + * + * @details Configures led1_pin, led2_pin, and led3_pin from the + * structure as GPIO outputs. + * + * @param leds pointer to the LED controller structure + * @retval None + */ +static void init_led_gpios(simple_led_ctrl_t *leds) { + gpio_init(leds->led1_pin); + gpio_set_dir(leds->led1_pin, GPIO_OUT); + gpio_init(leds->led2_pin); + gpio_set_dir(leds->led2_pin, GPIO_OUT); + gpio_init(leds->led3_pin); + gpio_set_dir(leds->led3_pin, GPIO_OUT); +} + +/** + * @brief Poll IR and handle a single received key + * + * @details Reads an IR key, processes the command with 3 blinks, + * and prints the result. + * + * @param leds pointer to the LED controller structure + * @retval None + */ +/** + * @brief Handle a valid IR key press + * + * @details Prints the NEC command, processes it with 3 blinks, + * and reports the activated LED. + * + * @param leds pointer to the LED controller structure + * @param key NEC command code + * @retval None + */ +static void handle_ir_key(simple_led_ctrl_t *leds, int key) { + printf("NEC command: 0x%02X\n", key); + int activated_led = process_ir_led_command(key, leds, 3); + if (activated_led > 0) { + printf("LED %d activated on GPIO %d\n", activated_led, + get_led_pin(leds, activated_led)); + } + sleep_ms(10); +} + +/** + * @brief Poll IR and handle a single received key + * + * @details Reads an IR key; if valid dispatches to handler, + * otherwise yields with a short sleep. + * + * @param leds pointer to the LED controller structure + * @retval None + */ +static void poll_and_handle_ir(simple_led_ctrl_t *leds) { + int key = ir_getkey(); + if (key >= 0) { + handle_ir_key(leds, key); + } else { + sleep_ms(1); + } +} + +/** + * @brief Build the default LED controller structure + * + * @details Initializes pins 16-18 with all LEDs off. + * + * @return simple_led_ctrl_t initialized structure + */ +static simple_led_ctrl_t make_default_leds(void) { + simple_led_ctrl_t leds = { + .led1_pin = 16, .led2_pin = 17, .led3_pin = 18, + .led1_state = false, .led2_state = false, .led3_state = false + }; + return leds; +} + +int main(void) { + stdio_init_all(); + simple_led_ctrl_t leds = make_default_leds(); + init_led_gpios(&leds); + ir_init(IR_PIN); + printf("IR receiver on GPIO %d ready\n", IR_PIN); + while (true) { + poll_and_handle_ir(&leds); + } +} diff --git a/0x0026_functions/CMakeLists.txt b/0x0026_functions/CMakeLists.txt new file mode 100644 index 0000000..b6ea0f9 --- /dev/null +++ b/0x0026_functions/CMakeLists.txt @@ -0,0 +1,57 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0-a4) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(0x0026_functions C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(0x0026_functions 0x0026_functions.c ir.c) + +pico_set_program_name(0x0026_functions "0x0026_functions") +pico_set_program_version(0x0026_functions "0.1") + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(0x0026_functions 1) +pico_enable_stdio_usb(0x0026_functions 0) + +# Add the standard library to the build +target_link_libraries(0x0026_functions + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(0x0026_functions PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) + +pico_add_extra_outputs(0x0026_functions) + diff --git a/0x0026_functions/ir.c b/0x0026_functions/ir.c new file mode 100644 index 0000000..9d5b42a --- /dev/null +++ b/0x0026_functions/ir.c @@ -0,0 +1,130 @@ +/** + * @file ir.c + * @brief Implementation of NEC IR receiver (decoder) + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "ir.h" +#include "pico/stdlib.h" +#include "pico/time.h" +#include "hardware/gpio.h" + +/** @brief GPIO pin connected to the IR receiver */ +static unsigned int ir_pin = 0; + +/** + * @brief Wait for a GPIO pin to reach a given logic level + * + * Spins until the pin matches the requested level or a microsecond timeout + * is exceeded. Returns the elapsed time in microseconds, or -1 on timeout. + * + * @param gpio GPIO pin to monitor + * @param level Desired logic level (true = HIGH, false = LOW) + * @param timeout_us Maximum wait in microseconds + * @return int64_t Elapsed microseconds, or -1 on timeout + */ +static int64_t wait_for_level(unsigned int gpio, bool level, uint32_t timeout_us) { + absolute_time_t start = get_absolute_time(); + while (gpio_get(gpio) != level) { + if (absolute_time_diff_us(start, get_absolute_time()) > (int64_t)timeout_us) + return -1; + } + return absolute_time_diff_us(start, get_absolute_time()); +} + +/** + * @brief Wait for the NEC 9 ms leader pulse and 4.5 ms space + * + * @return bool true if a valid leader was detected, false on timeout + */ +static bool wait_leader(void) { + if (wait_for_level(ir_pin, 0, 150000) < 0) return false; + int64_t t = wait_for_level(ir_pin, 1, 12000); + if (t < 8000 || t > 10000) return false; + t = wait_for_level(ir_pin, 0, 7000); + if (t < 3500 || t > 5000) return false; + return true; +} + +/** + * @brief Read a single NEC-encoded bit from the IR receiver + * + * Measures the mark/space timing and shifts the result into the + * appropriate byte of the data array. + * + * @param data 4-byte array accumulating received bits + * @param i Bit index (0-31) + * @return bool true on success, false on timeout or protocol error + */ +static bool read_nec_bit(uint8_t *data, int i) { + if (wait_for_level(ir_pin, 1, 1000) < 0) return false; + int64_t t = wait_for_level(ir_pin, 0, 2500); + if (t < 200) return false; + int byte_idx = i / 8; + int bit_idx = i % 8; + if (t > 1200) data[byte_idx] |= (1 << bit_idx); + return true; +} + +/** + * @brief Read all 32 data bits of an NEC frame + * + * @param data 4-byte array filled with the received address and command + * @return bool true if all 32 bits were read, false on timeout + */ +static bool read_32_bits(uint8_t *data) { + for (int i = 0; i < 32; ++i) + if (!read_nec_bit(data, i)) return false; + return true; +} + +/** + * @brief Validate an NEC frame and extract the command byte + * + * Checks that the address and command pairs are bitwise-inverted. + * + * @param data 4-byte NEC frame (addr, ~addr, cmd, ~cmd) + * @return int Command byte (0-255) on success, -1 on validation failure + */ +static int validate_nec_frame(const uint8_t *data) { + if ((uint8_t)(data[0] + data[1]) == 0xFF && (uint8_t)(data[2] + data[3]) == 0xFF) + return data[2]; + return -1; +} + +void ir_init(uint8_t pin) { + ir_pin = pin; + gpio_init(pin); + gpio_set_dir(pin, GPIO_IN); + gpio_pull_up(pin); +} + +int ir_getkey(void) { + if (!wait_leader()) return -1; + uint8_t data[4] = {0, 0, 0, 0}; + if (!read_32_bits(data)) return -1; + return validate_nec_frame(data); +} diff --git a/0x0026_functions/ir.h b/0x0026_functions/ir.h new file mode 100644 index 0000000..84aa67f --- /dev/null +++ b/0x0026_functions/ir.h @@ -0,0 +1,63 @@ +/** + * @file ir.h + * @brief Header for NEC IR receiver (decoder) API + * @author Kevin Thomas + * @date 2025 + * + * MIT License + * + * Copyright (c) 2025 Kevin Thomas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef IR_H +#define IR_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize the IR receiver GPIO + * + * Configures the given `pin` as an input with pull-up for the NEC IR receiver. + * Call this once during board initialization before calling `ir_getkey()`. + * + * @param pin GPIO pin number connected to IR receiver output + */ +void ir_init(uint8_t pin); + +/** + * @brief Blocking NEC IR decoder + * + * Blocks while waiting for a complete NEC frame. On success returns the + * decoded command byte (0..255). On timeout or protocol error returns -1. + * + * @return decoded command byte (0..255) or -1 on failure + */ +int ir_getkey(void); + +#ifdef __cplusplus +} +#endif + +#endif // IR_H diff --git a/0x0026_functions/pico_sdk_import.cmake b/0x0026_functions/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/0x0026_functions/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/DDI0553B_y_armv8m_arm.pdf b/DDI0553B_y_armv8m_arm.pdf new file mode 100644 index 0000000..653e7c4 Binary files /dev/null and b/DDI0553B_y_armv8m_arm.pdf differ diff --git a/EHP2.fzz b/EHP2.fzz new file mode 100644 index 0000000..cf56c6d Binary files /dev/null and b/EHP2.fzz differ diff --git a/EHP2_bb.png b/EHP2_bb.png new file mode 100644 index 0000000..be809a8 Binary files /dev/null and b/EHP2_bb.png differ diff --git a/Embedded Hacking.png b/Embedded Hacking.png new file mode 100644 index 0000000..81c55fd Binary files /dev/null and b/Embedded Hacking.png differ diff --git a/Embedded-Hacking.pdf b/Embedded-Hacking.pdf new file mode 100644 index 0000000..af0b640 Binary files /dev/null and b/Embedded-Hacking.pdf differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/PARTS.md b/PARTS.md new file mode 100644 index 0000000..6e635b6 --- /dev/null +++ b/PARTS.md @@ -0,0 +1,20 @@ +# Embedded Hacking - Hardware Parts and Products + +Here is a comprehensive list of hardware parts, products, and components referenced in the repository for the Embedded Hacking course: + +## Specific Components & Sensors +The following specific components are used throughout the course experiments and drivers (many of which are included in the component kit above): +- [1x Full-size Breadboard (Long)](https://www.amazon.com/s?k=full+size+breadboard) +- [1x Assorted Jumper Wires (Male-to-Male, Male-to-Female, Female-to-Female)](https://www.amazon.com/s?k=breadboard+jumper+wires+assortment) +- [1x Raspberry Pi Pico 2 w/ Header](https://www.amazon.com/s?k=raspberry+pi+pico+2+with+pre-soldered+header) +- [1x Raspberry Pi Pico Debug Probe](https://www.amazon.com/s?k=raspberry+pi+debug+probe) +- [2x USB A-Male to USB Micro-B Cables (1 for Pico 2, 1 for Debug Probe)](https://www.amazon.com/s?k=micro+usb+cable+2+pack) +- [3x 5mm LEDs (1 Red, 1 Green, 1 Yellow)](https://www.amazon.com/s?k=5mm+led+kit) +- [3x 100, 220 or 330 Ohm Resistors (for LEDs)](https://www.amazon.com/s?k=resistor+assortment+kit) +- [1x Push Button (Tactile switch)](https://www.amazon.com/s?k=tactile+push+button+assortment) +- [1x 1602 LCD (with PCF8574 I2C backpack)](https://www.amazon.com/s?k=1602+lcd+i2c+module) +- [1x DHT11 Temperature & Humidity Sensor](https://www.amazon.com/s?k=dht11+temperature+and+humidity+sensor) +- [1x SG90 Servo Motor](https://www.amazon.com/s?k=sg90+micro+servo+motor) +- [1x 1000uF 25V Capacitor (for Servo power stabilization)](https://www.amazon.com/Cionyce-Capacitor-Electrolytic-CapacitorsMicrowave/dp/B0B63CCQ2N) +- [1x Infrared (IR) Receiver (VS1838B)](https://www.amazon.com/s?k=vs1838b+ir+receiver+module) +- [1x Infrared (IR) Remote Controller (NEC-compatible)](https://www.amazon.com/s?k=arduino+ir+remote+control) diff --git a/README.md b/README.md new file mode 100644 index 0000000..7980218 --- /dev/null +++ b/README.md @@ -0,0 +1,391 @@ +![image](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded%20Hacking.png?raw=true) + +## FREE Reverse Engineering Self-Study Course [HERE](https://github.com/mytechnotalent/Reverse-Engineering-Tutorial) + +
+ +# Today's Tutorial [July 5, 2026] +## Lesson 236: Go Hacking Course (Chapter 3: Hacking Hello Distributed System World) +This chapter covers hacking of the basic hello world style Go program for the x64 architecture. + +-> Click [HERE](https://github.com/mytechnotalent/Go-Hacking/blob/main/Go%20Hacking.pdf) to read the FREE pdf book. + +
+ +# Embedded Hacking +A FREE comprehensive step-by-step embedded hacking course covering Embedded Software Development to Reverse Engineering. + +VIDEO PROMO [HERE](https://www.youtube.com/watch?v=aD7X9sXirF8) + +
+ +# FREE Book [Download](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) + +
+ +# Required Skills +Students must have a working understanding of the following items: + +- **Intermediate C Programming:** In particular, you must understand how pointers reference memory addresses which we will use to perform live variable hijacking. + - Training Link [HERE](https://www.learn-c.org) +- **Bare-Metal Embedded Systems Assembly Basics:** You must have experience with bare-metal assembly as we will be using this extensively throughout the entire course specifically with GDB and Ghidra. + - Training Link [HERE](https://azeria-labs.com/writing-arm-assembly-part-1) +- **Computer Architecture Basics (Registers & Stack):** You need a working mental model of how a CPU/MCU uses registers and the stack to follow the boot process and function calls. + - Training Link [HERE](https://www.geeksforgeeks.org/computer-science-fundamentals/what-is-register-memory) + - Training Link [HERE](https://www.geeksforgeeks.org/computer-organization-architecture/memory-stack-organization-in-computer-architecture) +- **Command Line Interface (CLI) Proficiency:** You must be comfortable navigating directories and executing commands in a terminal to run the necessary OpenOCD and GDB tools. + - Training Link [HERE](https://www.freecodecamp.org/news/command-line-commands-cli-tutorial) +- **Breadboarding Proficiency:** You must be comfortable demonstrating breadboard proficiency by efficiently prototyping circuits and integrating diverse peripherals, sensors, and microcontrollers. + - Training Video [HERE](https://www.youtube.com/watch?v=fq6U5Y14oM4) + +
+ +# Hardware [View Full Parts List](PARTS.md) +## Raspberry Pi Pico 2 w/ Header [BUY](https://www.pishop.us/product/raspberry-pi-pico-2-with-header) +## USB A-Male to USB Micro-B Cable [BUY](https://www.pishop.us/product/usb-a-male-to-usb-micro-b-cable-6-inches) +## Raspberry Pi Pico Debug Probe [BUY](https://www.pishop.us/product/raspberry-pi-debug-probe) +## Complete Component Kit for Raspberry Pi [BUY](https://www.pishop.us/product/complete-component-kit-for-raspberry-pi) +## 10pc 25v 1000uF Capacitor [BUY](https://www.amazon.com/Cionyce-Capacitor-Electrolytic-CapacitorsMicrowave/dp/B0B63CCQ2N?th=1) +### 10% PiShop DISCOUNT CODE - KVPE_HS320548_10PC + +

+ +# Breadboard Design +![image](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/EHP2_bb.png?raw=true) + +
+ +# Syllabus + +## Week 1 +Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts + +### Week 1 Slides [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01-SLIDES.pdf) + +### Week 1 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK01/WEEK01.md) + + +### Chapter 1: hello, world +This chapter covers the basics of setting up a dev environment and basic template firmware for the Pico 2 MCU in addition to printing hello, world. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 2: Debugging hello, world +This chapter covers the debugging of our firmware for the Pico 2 MCU hello, world program. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +## Week 2 +Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2 + +### Week 2 Slides [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02-SLIDES.pdf) + +### Week 2 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK02/WEEK02.md) + + +### Chapter 3: Hacking hello, world +This chapter covers the hacking of our firmware for the Pico 2 MCU hello, world program. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +## Week 3 +Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis + +### Week 3 Slides [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-SLIDES.pdf) + +### Week 3 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03.md) + + +### Chapter 4: Embedded System Analysis +This chapter covers a comprehensive embedded system analysis reviewing parts of the RP2350 datasheet and helpful firmware analysis tools. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +## Week 4 +Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics + +### Week 4 Slides [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-SLIDES.pdf) + +### Week 4 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04.md) + + +### Chapter 5: Intro To Variables +This chapter covers an introduction to variables as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 6: Debugging Intro To Variables +This chapter covers debugging an introduction to variables as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 7: Hacking Intro To Variables +This chapter covers hacking an introduction to variables as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 8: Uninitialized Variables +This chapter covers uninitialized variables as well as an intro to GPIO outputs as we blink an LED as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 9: Debugging Uninitialized Variables +This chapter covers debugging uninitialized variables as well as an intro to GPIO outputs as we blink an LED as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 10: Hacking Uninitialized Variables +This chapter covers hacking uninitialized variables as well as an intro to GPIO outputs as we blink an LED as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +## Week 5 +Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis + +### Week 5 Slides [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05-SLIDES.pdf) + +### Week 5 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK05/WEEK05.md) + + +### Chapter 11: Integer Data Type +This chapter covers the integer data type in addition to a deeper assembler dive into GPIO outputs as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 12: Debugging Integer Data Type +This chapter covers debugging the integer data type in addition to a deeper assembler dive into GPIO outputs as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 13: Hacking Integer Data Type +This chapter covers hacking the integer data type in addition to a deeper assembler dive into GPIO outputs as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 14: Floating-Point Data Type +This chapter covers the floating-point data type as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 15: Debugging Floating-Point Data Type +This chapter covers debugging the floating-point data type as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 16: Hacking Floating-Point Data Type +This chapter covers hacking the floating-point data type as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 17: Double Floating-Point Data Type +This chapter covers the double floating-point data type as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 18: Debugging Double Floating-Point Data Type +This chapter covers debugging the double floating-point data type as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 19: Hacking Double Floating-Point Data Type +This chapter covers hacking the double floating-point data type as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +## Week 6 +Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics + +### Week 6 Slides [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06-SLIDES.pdf) + +### Week 6 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK06/WEEK06.md) + + +### Chapter 20: Static Variables +This chapter covers static variables as well as an intro to GPIO inputs as we work with push buttons as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 21: Debugging Static Variables +This chapter covers debugging static variables as well as an intro to GPIO inputs as we work with push buttons as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 22: Hacking Static Variables +This chapter covers hacking static variables as well as an intro to GPIO inputs as we work with push buttons as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +## Week 7 +Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics + +### Week 7 Slides [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07-SLIDES.pdf) + +### Week 7 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK07/WEEK07.md) + + +### Chapter 23: Constants +This chapter covers constants as well as an intro to I2C as we work a 1602 LCD as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 24: Debugging Constants +This chapter covers debugging constants as well as an intro to I2C as we work a 1602 LCD as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 25: Hacking Constants +This chapter covers hacking constants as well as an intro to I2C as we work a 1602 LCD as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +## Week 8 +### Midterm Exam + +## Week 9 +Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics + +### Week 9 Slides [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09-SLIDES.pdf) + +### Week 9 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK09/WEEK09.md) + + +### Chapter 26: Operators +This chapter covers operators as well as an intro to single-wire protocol as we work a DHT11 temperature and humidity sensor as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 27: Debugging Operators +This chapter covers debugging operators as well as an intro to single-wire protocol as we work a DHT11 temperature and humidity sensor as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 28: Hacking Operators +This chapter covers hacking operators as well as an intro to single-wire protocol as we work a DHT11 temperature and humidity sensor as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +## Week 10 +Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics + +### Week 10 Slides [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10-SLIDES.pdf) + +### Week 10 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK10/WEEK10.md) + + +### Chapter 29: Static Conditionals +This chapter covers static conditionals as well as an intro to PWM as we work a SG90 servo motor as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 30: Debugging Static Conditionals +This chapter covers debugging static conditionals as well as an intro to PWM as we work a SG90 servo motor as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 31: Hacking Static Conditionals +This chapter covers hacking static conditionals as well as an intro to PWM as we work a SG90 servo motor as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 32: Dynamic Conditionals +This chapter covers dynamic conditionals as well as additional PWM examples as we work a SG90 servo motor as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 33: Debugging Dynamic Conditionals +This chapter covers debugging dynamic conditionals as well as additional PWM examples as we work a SG90 servo motor as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 34: Hacking Dynamic Conditionals +This chapter covers hacking dynamic conditionals as well as additional PWM examples as we work a SG90 servo motor as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +## Week 11 +Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics + +### Week 11 Slides [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11-SLIDES.pdf) + +### Week 11 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK11/WEEK11.md) + + +### Chapter 35: Structures +This chapter covers structures as well as an intro to infrared basics as we work a infrared receiver and infrared remote controller as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 36: Debugging Structures +This chapter covers debugging structures as well as an intro to infrared basics as we work a infrared receiver and infrared remote controller as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 37: Hacking Structures +This chapter covers hacking structures as well as an intro to infrared basics as we work a infrared receiver and infrared remote controller as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 38: Functions, w/ Param, w/ Return +This chapter covers functions, w/ params and w/ a return value as well as additional infrared examples as we work a infrared receiver and infrared remote controller it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 39: Debugging Functions, w/ Param, w/ Return +This chapter covers debugging functions, w/ params and w/ a return value as well as additional infrared examples as we work a infrared receiver and infrared remote controller as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +### Chapter 40: Hacking Functions, w/ Param, w/ Return +This chapter covers hacking functions, w/ params and w/ a return value as it relates to embedded development on the Pico 2. + +-> Click [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/Embedded-Hacking.pdf) to read the FREE pdf book. + +## Week 12 +Unknown Firmware Debugging and Hacking + +## Week 13 +Final Review – Embedded Debugging and Hacking Techniques w/ Advanced Firmware Analysis Q&A + +## Week 14 +### Final +### Final Project Option 1: The InfuSafe Pro Incident +In the aftermath of a catastrophic medical device failure, you are thrust into the role of an FDA forensic investigator facing an impossible crisis: 23 patients dead, 100 million recalled insulin pumps sitting in warehouses worldwide, and 2.3 million lives hanging in the balance all while the only evidence remaining is raw binary firmware after a rogue engineer destroyed every line of source code before fleeing to Montenegro. Armed only with GDB, Ghidra, and the reverse engineering skills honed over the first seven weeks of this course, you must excavate the truth from machine code, identify the lethal bugs spawned by an AI code generator called "OopsieGPT," and determine whether these devices can be salvaged to save millions in underserved communities or if $4.7 billion in humanitarian medical technology must be incinerated. This is not a simulation; this is triage at the intersection of embedded systems security and human survival. + +### Final Project Option 2: Operation Dark Eclipse +Forty-two stories beneath frozen tundra, a shadow intelligence alliance called Dark Eyes operates centrifuges enriching weapons-grade material for a first strike against Washington, D.C. and Agent NIGHTINGALE gave her life to extract the single firmware file that now sits before you. Conventional warfare cannot reach this fortress buried beneath rock and concrete, but you can: as the architect of a precision cyber weapon in the tradition of Stuxnet, you must reverse engineer the RP2350-based centrifuge controller, craft binary patches that double the spin speed while falsifying every sensor readout to show nominal operation, and execute the sabotage that will cascade-destroy their enrichment program and set their nuclear ambitions back a decade. Every skill from the entire semester ARM assembly, Ghidra analysis, IEEE-754 floating-point manipulation, branch modification, log desynchronization converges in this final mission. Agent NIGHTINGALE's seven-year-old daughter still watches the driveway, waiting for a mother who will never return. Honor that sacrifice. Complete the mission. Do not fail. + + +
+ +# Rust Drivers +Rust ports of the C drivers above using `rp235x-hal`. Each Rust driver folder contains a thin demo (`main.rs`) and a reusable library module with full Rust doc comments. + +### Testing + +```bash +# Run unit tests on the host +cargo test --lib --target x86_64-pc-windows-msvc +``` + +| Driver | Description | Key Crates | +| -------------------------------------------------- | ------------------------------------------ | ------------------------------- | +| [0x01_uart_rust](drivers/0x01_uart_rust) | Raw UART transmit / receive | `rp235x-hal`, `embedded-hal-nb` | +| [0x02_blink_rust](drivers/0x02_blink_rust) | GPIO output LED blink | `rp235x-hal`, `embedded-hal` | +| [0x03_button_rust](drivers/0x03_button_rust) | Push-button input with debounce | `rp235x-hal`, `embedded-hal` | +| [0x04_pwm_rust](drivers/0x04_pwm_rust) | Generic PWM output | `rp235x-hal`, `embedded-hal` | +| [0x05_servo_rust](drivers/0x05_servo_rust) | SG90 servo motor control | `rp235x-hal`, `embedded-hal` | +| [0x06_adc_rust](drivers/0x06_adc_rust) | 12-bit ADC voltage + temperature | `rp235x-hal`, `embedded-hal` | +| [0x07_i2c_rust](drivers/0x07_i2c_rust) | I2C bus scanner (7-bit addresses) | `rp235x-hal`, `embedded-hal` | +| [0x08_lcd1602_rust](drivers/0x08_lcd1602_rust) | HD44780 16x2 LCD (PCF8574 I2C) | `rp235x-hal`, `embedded-hal` | +| [0x09_dht11_rust](drivers/0x09_dht11_rust) | DHT11 temperature & humidity (single-wire) | `rp235x-hal`, `fugit` | +| [0x0a_ir_rust](drivers/0x0a_ir_rust) | IR remote NEC protocol decoder | `rp235x-hal`, `fugit` | +| [0x0b_spi_rust](drivers/0x0b_spi_rust) | SPI bus init & bidirectional transfer | `rp235x-hal`, `embedded-hal` | +| [0x0c_multicore_rust](drivers/0x0c_multicore_rust) | Dual-core launch & FIFO messaging | `rp235x-hal` | +| [0x0d_timer_rust](drivers/0x0d_timer_rust) | Repeating timer callbacks | `rp235x-hal`, `fugit` | +| [0x0e_watchdog_rust](drivers/0x0e_watchdog_rust) | Watchdog enable, feed & reboot detection | `rp235x-hal` | +| [0x0f_flash_rust](drivers/0x0f_flash_rust) | On-board flash erase, write & read | `rp235x-hal` | + +
+ +# License +[Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) diff --git a/WEEK01/WEEK01-SLIDES.pdf b/WEEK01/WEEK01-SLIDES.pdf new file mode 100644 index 0000000..5fb1421 Binary files /dev/null and b/WEEK01/WEEK01-SLIDES.pdf differ diff --git a/WEEK01/WEEK01.md b/WEEK01/WEEK01.md new file mode 100644 index 0000000..893bfe6 --- /dev/null +++ b/WEEK01/WEEK01.md @@ -0,0 +1,727 @@ +# Week 1: Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts + +## What You'll Learn This Week + +By the end of this week, you will be able to: +- Understand what a microcontroller is and how it works +- Know the basic registers of the ARM Cortex-M33 processor +- Understand memory layout (Flash vs RAM) and why it matters +- Understand how the stack works in embedded systems +- Set up and connect GDB to your Pico 2 for debugging +- Use Ghidra for static analysis of your binary +- Read basic ARM assembly instructions and understand what they do + +--- + +## Part 1: Understanding the Basics + +### What is a Microcontroller? + +Think of a microcontroller as a tiny computer on a single chip. Just like your laptop has a processor, memory, and storage, a microcontroller has all of these packed into one small chip. The **RP2350** is the microcontroller chip that powers the **Raspberry Pi Pico 2**. + +### What is the ARM Cortex-M33? + +The RP2350 has two "brains" inside it - we call these **cores**. One brain uses ARM Cortex-M33 instructions, and the other can use RISC-V instructions. In this course, we'll focus on the **ARM Cortex-M33** core because it's more commonly used in the industry. + +### What is Reverse Engineering? + +Reverse engineering is like being a detective for code. Instead of writing code and compiling it, we take compiled code (the 1s and 0s that the computer actually runs) and figure out what it does. This is useful for: +- Understanding how things work +- Finding bugs or security issues +- Learning how software interacts with hardware + +--- + +## Part 2: Understanding Processor Registers + +### What is a Register? + +A **register** is like a tiny, super-fast storage box inside the processor. The processor uses registers to hold numbers while it's doing calculations. Think of them like the short-term memory your brain uses when doing math in your head. + +### The ARM Cortex-M33 Registers + +The ARM Cortex-M33 has several important registers: + +| Register | Also Called | Purpose | +| ------------ | -------------------- | ------------------------------------------- | +| `r0` - `r12` | General Purpose | Store numbers, pass data between functions | +| `r13` | SP (Stack Pointer) | Keeps track of where we are in the stack | +| `r14` | LR (Link Register) | Remembers where to go back after a function | +| `r15` | PC (Program Counter) | Points to the next instruction to run | + +##### General Purpose Registers (`r0` - `r12`) + +These 13 registers are your "scratch paper." When the processor needs to add two numbers, subtract, or do any calculation, it uses these registers to hold the values. + +**Example:** If you want to add 5 + 3: +1. Put 5 in `r0` +2. Put 3 in `r1` +3. Add them and store the result (8) in `r2` + +##### The Stack Pointer (`r13` / SP) + +The **stack** is a special area of memory that works like a stack of plates: +- When you add something, you put it on top (called a **PUSH**) +- When you remove something, you take it from the top (called a **POP**) + +The Stack Pointer always points to the top of this stack. On ARM systems, the stack **grows downward** in memory. This means when you push something onto the stack, the address number gets smaller! + +The two Arm ABI documents we verified give the formal proof for these rules. In AAPCS32, page 17 defines the core register roles used by the base procedure call standard: `r13` is `SP`, `r14` is `LR`, `r15` is `PC`, `r0`-`r3` are argument and scratch registers, and `r4`-`r11` are the longer-lived variable registers. In Advisory Note 132, page 7 states that `SP` must be aligned to a multiple of 8 at every conforming call site and must already be 8-byte aligned when control first enters conforming code. That is why compiler-generated prologues often push an even number of registers, such as `push {r3, lr}`, to preserve both saved state and the required ABI stack alignment. + +``` +Higher Memory Address (0x20082000) ++------------------+ +| | ↠Stack starts here (empty) ++------------------+ +| Pushed Item 1 | ↠SP points here after 1 push ++------------------+ +| Pushed Item 2 | ↠SP points here after 2 pushes ++------------------+ +Lower Memory Address (0x20081FF8) +``` + +##### The Link Register (`r14` / LR) + +When you call a function, the processor needs to remember where to come back to. The Link Register stores this "return address." + +**Example:** +``` +main() calls print_hello() + ↓ +LR = address right after the call in main() + ↓ +print_hello() runs + ↓ +print_hello() finishes, looks at LR + ↓ +Jumps back to main() at the address stored in LR +``` + +##### The Program Counter (`r15` / PC) + +The Program Counter always points to the **next instruction** the processor will execute. It's like your finger following along as you read a book - it always points to where you are. + +--- + +## Part 3: Understanding Memory Layout + +### XIP - Execute In Place + +The RP2350 uses something called **XIP (Execute In Place)**. This means the processor can run code directly from the flash memory (where your program is stored) without copying it to RAM first. + +**Key Memory Address:** `0x10000000` + +This is where your program code starts in flash memory. Remember this address - we'll use it a lot! + +### Memory Map Overview + +``` ++-------------------------------------+ +| Flash Memory (XIP) | +| Starts at: 0x10000000 | +| Contains: Your program code | ++-------------------------------------+ +| RAM | +| Starts at: 0x20000000 | +| Contains: Stack, Heap, Variables | ++-------------------------------------+ +``` + +### Stack vs Heap + +| Stack | Heap | +| ---------------------------------------- | ---------------------------------- | +| Automatic memory management | Manual memory management | +| Fast | Slower | +| Limited size | More flexible size | +| Used for function calls, local variables | Used for dynamic memory allocation | +| Grows downward | Grows upward | + +--- + +## Part 3.5: Reviewing Our Hello World Code + +Before we start debugging, let's understand the code we'll be working with. Here's our `0x0001_hello-world.c` program: + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + stdio_init_all(); + + while (true) + printf("hello, world\r\n"); +} +``` + +### Breaking Down the Code + +##### The Includes + +```c +#include +#include "pico/stdlib.h" +``` + +- **``** - This is the standard input/output library. It gives us access to the `printf()` function that lets us print text. +- **`"pico/stdlib.h"`** - This is the Pico SDK's standard library. It provides essential functions for working with the Raspberry Pi Pico hardware. + +##### The Main Function + +```c +int main(void) { +``` + +Every C program starts running from the `main()` function. The `void` means it takes no arguments, and `int` means it returns an integer (though our program never actually returns). + +##### Initializing Standard I/O + +```c +stdio_init_all(); +``` + +This function initializes all the standard I/O (input/output) for the Pico. It sets up: +- **USB CDC** (so you can see output when connected to a computer via USB) +- **UART** (serial communication pins) + +Without this line, `printf()` wouldn't have anywhere to send its output! + +##### The Infinite Loop + +```c +while (true) + printf("hello, world\r\n"); +``` + +- **`while (true)`** - This creates an infinite loop. The program will keep running forever (or until you reset/power off the Pico). +- **`printf("hello, world\r\n")`** - This prints the text "hello, world" followed by a carriage return (`\r`) and newline (`\n`). + +> Tip: **Why `\r\n` instead of just `\n`?** +> +> In embedded systems, we often use both carriage return (`\r`) and newline (`\n`) together. The `\r` moves the cursor back to the beginning of the line, and `\n` moves to the next line. This ensures proper display across different terminal programs. + +### What Happens When This Runs? + +1. **Power on** - The Pico boots up and starts executing code from flash memory +2. **`stdio_init_all()`** - Sets up USB and/or UART for communication +3. **Infinite loop begins** - The program enters the `while(true)` loop +4. **Print forever** - "hello, world" is sent over and over as fast as possible + +### Why This Code is Perfect for Learning + +This simple program is ideal for reverse engineering practice because: +- It has a clear, recognizable function call (`printf`) +- It has an infinite loop we can observe +- It's small enough to understand completely +- It demonstrates real hardware interaction (USB/UART output) + +When we debug this code, we'll be able to see how the C code translates to ARM assembly instructions! + +### Compiling and Flashing to the Pico 2 + +Now that we understand the code, let's get it running on our hardware: + +##### Step 1: Compile the Code + +In VS Code, look for the **Compile** button in the status bar at the bottom of the window. This is provided by the Raspberry Pi Pico extension. Click it to compile your project. + +The extension will run CMake and build your code, creating a `.uf2` file that can be loaded onto the Pico 2. + +##### Step 2: Put the Pico 2 in Flash Loading Mode + +To flash new code to your Pico 2, you need to put it into **BOOTSEL mode**: + +1. **Press and hold** the right-most button on your breadboard (the BOOTSEL button) +2. **While holding BOOTSEL**, press the white **Reset** button +3. **Release the Reset button** first +4. **Then release the BOOTSEL button** + +When done correctly, your Pico 2 will appear as a USB mass storage device (like a flash drive) on your computer. This means it's ready to receive new firmware! + +> Tip: **Tip:** You'll see a drive called "RP2350" appear in your file explorer when the Pico 2 is in flash loading mode. + +##### Step 3: Flash and Run + +Back in VS Code, click the **Run** button in the status bar. The extension will: +1. Copy the compiled `.uf2` file to the Pico 2 +2. The Pico 2 will automatically reboot and start running your code + +Once flashed, your Pico 2 will immediately start executing the hello-world program, printing "hello, world" continuously when we open PuTTY! + +--- + +## Part 4: Dynamic Analysis with GDB + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. GDB (GNU Debugger) installed +3. OpenOCD or another debug probe connection +4. The sample "hello-world" binary loaded on your Pico 2 + +### Connecting to Your Pico 2 with OpenOCD + +Open a terminal and start OpenOCD: + +```powershell +openocd -s "$env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev\scripts" -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000" +``` + +### Connecting to Your Pico 2 with GDB + +Open another terminal and start GDB with your binary: + +```cmd +arm-none-eabi-gdb build\0x0001_hello-world.elf +``` + +Connect to your target: + +```cmd +(gdb) target extended-remote :3333 +(gdb) monitor reset halt +``` + +### Basic GDB Commands: Your First Steps + +Now that we're connected, let's learn three essential GDB commands that you'll use constantly in embedded reverse engineering. + +##### Setting a Breakpoint with `b main` + +A **breakpoint** tells the debugger to pause execution when it reaches a specific point. Let's set one at our `main` function: + +```gdb +(gdb) b main +Breakpoint 1 at 0x10000234: file ../0x0001_hello-world.c, line 5. +``` + +**What this tells us:** +- GDB found our `main` function +- It's located at address `0x10000234` in flash memory +- The source file and line number are shown (because we have debug symbols) + +Now let's run to that breakpoint: + +```gdb +(gdb) c +Continuing. + +Breakpoint 1, main () at ../0x0001_hello-world.c:5 +5 stdio_init_all(); +``` + +The program has stopped right at the beginning of `main`! + +##### Disassembling with `disas` + +The `disas` (disassemble) command shows us the assembly instructions for the current function: + +```gdb +(gdb) disas +Dump of assembler code for function main: +=> 0x10000234 <+0>: push {r3, lr} + 0x10000236 <+2>: bl 0x1000156c + 0x1000023a <+6>: ldr r0, [pc, #8] @ (0x10000244 ) + 0x1000023c <+8>: bl 0x100015fc <__wrap_puts> + 0x10000240 <+12>: b.n 0x1000023a + 0x10000242 <+14>: nop + 0x10000244 <+16>: adds r4, r1, r7 + 0x10000246 <+18>: asrs r0, r0, #32 +End of assembler dump. +``` + +**Understanding the output:** +- The `=>` arrow shows where we're currently stopped +- Each line shows: `address : instruction operands` +- We can see the calls to `stdio_init_all` and `__wrap_puts` (printf was optimized to puts) +- The `b.n 0x1000023a` at the end is our infinite loop - it jumps back to reload the string! + +##### Viewing ELF Sections with `info files` and `maintenance info sections` + +To see how the ELF is laid out in memory, use: + +```gdb +(gdb) info files +Symbols from "C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0001_hello-world\build\0x0001_hello-world.elf". +Extended remote target using gdb-specific protocol: + `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0001_hello-world\build\0x0001_hello-world.elf', file type elf32-littlearm. + Entry point: 0x1000014c + 0x10000000 - 0x100019cc is .text + 0x100019cc - 0x10001b18 is .rodata + 0x10001b18 - 0x10001b20 is .ARM.exidx + 0x10001b20 - 0x10001b4c is .binary_info + 0x20000000 - 0x20000110 is .ram_vector_table + 0x20000110 - 0x200002ac is .data + 0x200002ac - 0x200002ac is .tdata + 0x200002ac - 0x200002ac is .tbss + 0x200002b0 - 0x200004d8 is .bss + 0x200004d8 - 0x20000cd8 is .heap + 0x20081000 - 0x20081800 is .stack_dummy + 0x10001ce8 - 0x10001cfc is .flash_end + While running this, GDB does not access memory from... +Local exec file: + `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0001_hello-world\build\0x0001_hello-world.elf', file type elf32-littlearm. + Entry point: 0x1000014c + 0x10000000 - 0x100019cc is .text + 0x100019cc - 0x10001b18 is .rodata + 0x10001b18 - 0x10001b20 is .ARM.exidx + 0x10001b20 - 0x10001b4c is .binary_info + 0x20000000 - 0x20000110 is .ram_vector_table + 0x20000110 - 0x200002ac is .data + 0x200002ac - 0x200002ac is .tdata + 0x200002ac - 0x200002ac is .tbss + 0x200002b0 - 0x200004d8 is .bss + 0x200004d8 - 0x20000cd8 is .heap + 0x20081000 - 0x20081800 is .stack_dummy + 0x10001ce8 - 0x10001cfc is .flash_end +(gdb) maintenance info sections +Exec file: `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0001_hello-world\build\0x0001_hello-world.elf', file type elf32-littlearm. + [0] 0x10000000->0x100019cc at 0x00001000: .text ALLOC LOAD READONLY CODE HAS_CONTENTS + [1] 0x100019cc->0x10001b18 at 0x000029cc: .rodata ALLOC LOAD READONLY DATA HAS_CONTENTS + [2] 0x10001b18->0x10001b20 at 0x00002b18: .ARM.exidx ALLOC LOAD READONLY DATA HAS_CONTENTS + [3] 0x10001b20->0x10001b4c at 0x00002b20: .binary_info ALLOC LOAD READONLY DATA HAS_CONTENTS + [4] 0x20000000->0x20000110 at 0x00004000: .ram_vector_table ALLOC + [5] 0x20000110->0x20000110 at 0x00003cfc: .uninitialized_data HAS_CONTENTS + [6] 0x20000110->0x200002ac at 0x00003110: .data ALLOC LOAD READONLY CODE HAS_CONTENTS + [7] 0x200002ac->0x200002ac at 0x00003cfc: .tdata ALLOC LOAD DATA HAS_CONTENTS + [8] 0x200002ac->0x200002ac at 0x00000000: .tbss ALLOC + [9] 0x200002b0->0x200004d8 at 0x000042b0: .bss ALLOC + [10] 0x200004d8->0x20000cd8 at 0x000044d8: .heap ALLOC READONLY + [11] 0x20080000->0x20080000 at 0x00003cfc: .scratch_x HAS_CONTENTS + [12] 0x20081000->0x20081000 at 0x00003cfc: .scratch_y HAS_CONTENTS + [13] 0x20081000->0x20081800 at 0x00004000: .stack_dummy ALLOC READONLY + [14] 0x10001ce8->0x10001cfc at 0x00003ce8: .flash_end ALLOC LOAD READONLY DATA HAS_CONTENTS + [15] 0x0000->0x0034 at 0x00003cfc: .ARM.attributes READONLY HAS_CONTENTS + [16] 0x0000->0x0045 at 0x00003d30: .comment READONLY HAS_CONTENTS + [17] 0x0000->0x2069a at 0x00003d75: .debug_info READONLY HAS_CONTENTS + [18] 0x0000->0x54ff at 0x0002440f: .debug_abbrev READONLY HAS_CONTENTS + [19] 0x0000->0x0af0 at 0x00029910: .debug_aranges READONLY HAS_CONTENTS + [20] 0x0000->0x2f86 at 0x0002a400: .debug_rnglists READONLY HAS_CONTENTS + [21] 0x0000->0x15526 at 0x0002d386: .debug_line READONLY HAS_CONTENTS + [22] 0x0000->0x56a7 at 0x000428ac: .debug_str READONLY HAS_CONTENTS + [23] 0x0000->0x1ed4 at 0x00047f54: .debug_frame READONLY HAS_CONTENTS + [24] 0x0000->0xffd1 at 0x00049e28: .debug_loclists READONLY HAS_CONTENTS + [25] 0x0000->0x0178 at 0x00059df9: .debug_line_str READONLY HAS_CONTENTS +``` + +**What each section means:** + +| Section | Purpose | +| ------- | ------- | +| `.text` | Executable machine code (your functions/instructions). | +| `.rodata` | Read-only constants (strings like `"hello, world"`, lookup tables, const data). | +| `.ARM.exidx` | ARM exception unwind index used for stack unwinding/backtraces. | +| `.binary_info` | Pico metadata used by tools (program identity/build information). | +| `.ram_vector_table` | Interrupt vector table copied/placed in RAM for runtime use. | +| `.uninitialized_data` | Deliberately non-zeroed RAM region that can survive certain reset paths. | +| `.data` | Initialized global/static variables in RAM (initial values come from flash). | +| `.tdata` | Initialized thread-local storage data (often empty in simple bare-metal apps). | +| `.tbss` | Zero-initialized thread-local storage (often empty). | +| `.bss` | Zero-initialized global/static variables in RAM. | +| `.heap` | Heap allocation region (`malloc/new`) reserved in RAM. | +| `.scratch_x` | RP2 scratch RAM bank X section (core-local/low-contention placement). | +| `.scratch_y` | RP2 scratch RAM bank Y section (core-local/low-contention placement). | +| `.stack_dummy` | Linker-reserved stack range marker used to size/place the stack. | +| `.flash_end` | Marker/metadata near the logical end of flash image region. | +| `.ARM.attributes` | ARM build attributes (ABI/architecture metadata for tools/linkers). | +| `.comment` | Compiler/build comment strings (toolchain identification). | +| `.debug_info` | Main DWARF debug database (types, variables, symbols, scopes). | +| `.debug_abbrev` | Abbreviation table referenced by `.debug_info`. | +| `.debug_aranges` | Address-to-compilation-unit lookup acceleration data. | +| `.debug_rnglists` | DWARF range lists for non-contiguous code/data ranges. | +| `.debug_line` | Address-to-source-line mapping used for stepping/breakpoints. | +| `.debug_str` | Shared string pool used by DWARF debug sections. | +| `.debug_frame` | Call frame information used for unwinding stack frames. | +| `.debug_loclists` | Variable location lists (where variables live over PC ranges). | +| `.debug_line_str` | Extra string pool used by `.debug_line` data. | + +> Tip: **Practical rule:** For reverse engineering runtime behavior, focus first on `.text`, `.rodata`, `.data`, `.bss`, heap/stack regions, and the vector table. Debug sections are for source-level mapping and symbol intelligence. + +**Fast interpretation checklist (use this every time):** + +1. **Find where code executes**: Verify `.text` starts at `0x10000000` (XIP flash) and note its end. +2. **Find immutable constants**: Use `.rodata` for strings/tables; cross-reference these addresses in disassembly. +3. **Find initialized RAM state**: `.data` lives in RAM at runtime, but its initial bytes come from flash. +4. **Find zeroed runtime state**: `.bss` is RAM that startup code clears to zero before `main`. +5. **Find interrupt control point**: Confirm `.ram_vector_table` location for exception/IRQ handler mapping. +6. **Bound dynamic memory**: Note `.heap` range so you can classify allocator activity vs static data. +7. **Bound call-stack activity**: Use `.stack_dummy` as linker stack reservation, then track live stack with `$sp`. +8. **Separate runtime vs debug-only sections**: `.debug_*`, `.comment`, and `.ARM.attributes` help tooling, not execution. +9. **Correlate any suspicious address quickly**: Flash/XIP (`0x100...`) usually code/const; SRAM (`0x200...`) usually data/stack/heap. +10. **Validate in memory**: After identifying a section, inspect it with `x` in GDB to confirm actual bytes/instructions. + +##### Viewing Registers with `i r` + +The `i r` (info registers) command shows the current state of all CPU registers: + +```gdb +(gdb) i r +r0 0x0 0 +r1 0x10000235 268436021 +r2 0x80808080 -2139062144 +r3 0xe000ed08 -536810232 +r4 0x100001d0 268435920 +r5 0x88526891 -2007865199 +r6 0x4f54710 83183376 +r7 0x400e0014 1074659348 +r8 0x43280035 1126694965 +r9 0x0 0 +r10 0x10000000 268435456 +r11 0x62707361 1651536737 +r12 0xed07f600 -318245376 +sp 0x20082000 0x20082000 +lr 0x1000018f 268435855 +pc 0x10000234 0x10000234
+xpsr 0x69000000 1761607680 +``` + +**Key registers to watch:** +| Register | Value | Meaning | +| -------- | ------------ | ----------------------------------------------- | +| `pc` | `0x10000234` | Program Counter - we're at the start of `main` | +| `sp` | `0x20081fc8` | Stack Pointer - top of our stack in RAM | +| `lr` | `0x100002d5` | Link Register - where we return after `main` | +| `r0-r3` | Various | Will hold function arguments and return values | + +> Tip: **Tip:** You can also use `i r pc sp lr` to show only specific registers you care about. + +### Quick Reference: Essential GDB Commands + +| Command | Short Form | What It Does | +| --------------------- | ---------- | ------------------------------------ | +| `break main` | `b main` | Set a breakpoint at main | +| `continue` | `c` | Continue execution until breakpoint | +| `disassemble` | `disas` | Show assembly for current function | +| `info registers` | `i r` | Show all register values | +| `stepi` | `si` | Execute one assembly instruction | +| `nexti` | `ni` | Execute one instruction (skip calls) | +| `x/10i $pc` | | Examine 10 instructions at PC | +| `monitor reset halt` | | Reset the target and halt | + +### Watching the Stack Change After `push {r3, lr}` + +The first instruction in `main` is `push {r3, lr}`. Before we step it, `SP` is `0x20082000`. After a single `si`, `SP` becomes `0x20081ff8`, which tells us the processor reserved 8 bytes on the stack for two 32-bit values. The first word at the new top of stack is `0xe000ed08`, which is the old value of `r3`, and the second word is `0x1000018f`, which is the saved `lr` return address. This matches the ABI rule we discussed earlier: the compiler pushes an even number of registers so the stack stays 8-byte aligned at the next call site before `stdio_init_all()` runs. + +Notice the difference between inspecting memory at `$sp` and inspecting `$lr`. `x/4x $sp` is enough here to show the relevant stack words in RAM, while `x/x $lr` shows the instruction word stored at the flash address held in the link register. In other words, `$sp` points to saved data on the stack, but `$lr` points to code that execution will return to later. + +```gdb +(gdb) x/x $sp +0x20082000: 0x00000000 +(gdb) si +0x10000236 5 stdio_init_all(); + +(gdb) x/x $sp +0x20081ff8: 0xe000ed08 +(gdb) x/4x $sp +0x20081ff8: 0xe000ed08 0x1000018f 0x00000000 0x00000000 +(gdb) x/x $lr +0x1000018f : 0x00478849 +``` + +> Tip: **What's Next?** In Week 2, we'll put these GDB commands to work with hands-on debugging exercises! We'll step through code, examine the stack, watch registers change, and ultimately use these skills to modify a running program. The commands you learned here are the foundation for everything that follows. + +--- + +## Part 5: Static Analysis with Ghidra + +### Setting Up Your First Ghidra Project + +Before we dive into GDB debugging, let's set up Ghidra to analyze our hello-world binary. Ghidra is a powerful reverse engineering tool that will help us visualize the disassembly and decompiled code. + +##### Step 1: Create a New Project + +1. Launch Ghidra +2. A window will appear - select **File -> New Project** +3. Choose **Non-Shared Project** and click **Next** +4. Enter the Project Name: `0x0001_hello-world` +5. Click **Finish** + +##### Step 2: Import the Binary + +1. Open your file explorer and navigate to the `Embedded-Hacking` folder +2. **Drag and drop** the `0x0001_hello-world.elf` file into the folder panel within the Ghidra application + +##### Step 3: Understand the Import Dialog + +In the small window that appears, you will see the file identified as an **ELF** (Executable and Linkable Format). + +> Tip: **What is an ELF file?** +> +> ELF stands for **Executable and Linkable Format**. This format includes **symbols** - human-readable names for functions and variables. These symbols make reverse engineering much easier because you can see function names like `main` and `printf` instead of just memory addresses. +> +> In future weeks, we will work with **stripped binaries** (`.bin` files) that do not contain these symbols. This is more realistic for real-world reverse engineering scenarios where symbols have been removed to make analysis harder. + +3. Click **Ok** to import the file +4. **Double-click** on the file within the project window to open it in the CodeBrowser + +##### Step 4: Auto-Analyze the Binary + +When prompted, click **Yes** to auto-analyze the binary. Accept the default analysis options and click **Analyze**. + +Ghidra will now process the binary, identifying functions, strings, and cross-references. This may take a moment. + +### Reviewing the Main Function in Ghidra + +Once analysis is complete, let's find our `main` function: + +1. In the **Symbol Tree** panel on the left, expand **Functions** +2. Look for `main` in the list (you can also use **Search -> For Address or Label** and type "main") +3. Click on `main` to navigate to it + +##### What You'll See + +Ghidra shows you two views of the code: + +**Listing View (Center Panel)** - The disassembled ARM assembly: +``` + ************************************************************* + * FUNCTION + ************************************************************* + int main (void ) + assume LRset = 0x0 + assume TMode = 0x1 + int r0:4 + main XREF[3]: Entry Point (*) , + _reset_handler:1000018c (c) , + .debug_frame::00000018 (*) + 0x0001_hello-world.c:4 (2) + 0x0001_hello-world.c:5 (2) + 10000234 08 b5 push {r3,lr} + 0x0001_hello-world.c:5 (4) + 10000236 01 f0 99 f9 bl stdio_init_all _Bool stdio_init_all(void) + LAB_1000023a XREF[1]: 10000240 (j) + 0x0001_hello-world.c:7 (6) + 0x0001_hello-world.c:8 (6) + 1000023a 02 48 ldr r0=>__EH_FRAME_BEGIN__ ,[DAT_10000244 ] = "hello, world\r" + = 100019CCh + 1000023c 01 f0 de f9 bl __wrap_puts int __wrap_puts(char * s) + 0x0001_hello-world.c:7 (8) + 10000240 fb e7 b LAB_1000023a + 10000242 00 ?? 00h + 10000243 bf ?? BFh + DAT_10000244 XREF[1]: main:1000023a (R) + 10000244 cc 19 00 10 undefine 100019CCh ? -> 100019cc + +``` + +**Decompile View (Right Panel)** - The reconstructed C code: +```c +int main(void) + +{ + stdio_init_all(); + do { + __wrap_puts("hello, world\r"); + } while( true ); +} +``` + +> **Notice how Ghidra reconstructed our original C code!** The decompiler recognized the infinite loop and the `puts` call (the compiler optimized `printf` to `puts` since we're just printing a simple string). + +##### Why We Start with .elf Files + +We're using the `.elf` file because it contains symbols that help us learn: +- Function names are visible (`main`, `stdio_init_all`, `puts`) +- Variable names may be preserved +- The structure of the code is easier to understand + +In future weeks, we'll work with `.bin` files that have been stripped of symbols. This will teach you how to identify functions and understand code when you don't have these helpful hints! + +--- + +## Part 6: Summary and Review + +### What We Learned + +1. **Registers**: The ARM Cortex-M33 has 13 general-purpose registers (`r0`-`r12`), plus special registers for the stack pointer (`r13`/SP), link register (`r14`/LR), and program counter (`r15`/PC). + +2. **The Stack**: + - Grows downward in memory + - PUSH adds items (SP decreases) + - POP removes items (SP increases) + - Used to save return addresses and register values + +3. **Memory Layout**: + - Code lives in flash memory starting at `0x10000000` + - Stack lives in RAM around `0x20080000` + +4. **GDB Basics**: We learned the essential commands for connecting to hardware and examining code: + +| Command | What It Does | +| --------------------- | -------------------------------------- | +| `target extended-remote :3333` | Connect to OpenOCD debug server | +| `monitor reset halt` | Reset and halt the processor | +| `b main` | Set breakpoint at main function | +| `c` | Continue running until breakpoint | +| `disas` | Disassemble current function | +| `i r` | Show all register values | + +5. **Ghidra Static Analysis**: We set up a Ghidra project and analyzed our binary: + - Imported the ELF file with symbols + - Found the `main` function + - Saw the decompiled C code + - Understood how assembly maps to C + +6. **Little-Endian**: The RP2350 stores multi-byte values with the least significant byte at the lowest address, making them appear "backwards" when viewed as a single value. + +### The Program Flow + +``` ++-----------------------------------------------------+ +| 1. push {r3, lr} | +| Save registers to stack | ++-----------------------------------------------------+ +| 2. bl stdio_init_all | +| Initialize standard I/O | ++-----------------------------------------------------+ +| 3. ldr r0, [pc, #8] ----------------+ | +| Load address of "hello, world" into r0| | ++-----------------------------------------------------+ +| 4. bl __wrap_puts | | +| Print the string | | ++-----------------------------------------------------+ +| 5. b.n (back to step 3) ----------------+ | +| Infinite loop! | ++-----------------------------------------------------+ +``` + +--- + +> **Note:** The detailed hands-on GDB debugging (stepping through code, watching the stack, examining memory) will be covered in Week 2! + +--- + +## Key Takeaways + +1. **Reverse engineering combines static and dynamic analysis** - we look at the code (static with Ghidra) and run it to see what happens (dynamic with GDB). + +2. **The stack is fundamental** - understanding how push/pop work is essential for following function calls. + +3. **GDB and Ghidra work together** - Ghidra helps you understand the big picture, GDB lets you watch it happen live. + +4. **Assembly isn't scary** - each instruction does one simple thing. Put them together and you understand the whole program! + +5. **Everything is just numbers** - whether it's code, data, or addresses, it's all stored as numbers in memory. + +--- + +## Glossary + +| Term | Definition | +| ------------------- | --------------------------------------------------------- | +| **Assembly** | Human-readable representation of machine code | +| **Breakpoint** | A marker that tells the debugger to pause execution | +| **GDB** | GNU Debugger - a tool for examining running programs | +| **Hex/Hexadecimal** | Base-16 number system (0-9, A-F) | +| **Little-Endian** | Storing the least significant byte at the lowest address | +| **Microcontroller** | A small computer on a single chip | +| **Program Counter** | Register that points to the next instruction | +| **Register** | Fast storage inside the processor | +| **Stack** | Memory region for temporary storage during function calls | +| **Stack Pointer** | Register that points to the top of the stack | +| **XIP** | Execute In Place - running code directly from flash | + + + diff --git a/WEEK01/slides/WEEK01-IMG00.svg b/WEEK01/slides/WEEK01-IMG00.svg new file mode 100644 index 0000000..e666d03 --- /dev/null +++ b/WEEK01/slides/WEEK01-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 01 + + +Introduction and Overview of +Embedded Reverse Engineering: +Ethics, Scoping, and Basic Concepts + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEK01/slides/WEEK01-IMG01.svg b/WEEK01/slides/WEEK01-IMG01.svg new file mode 100644 index 0000000..58888b0 --- /dev/null +++ b/WEEK01/slides/WEEK01-IMG01.svg @@ -0,0 +1,112 @@ + + + + + +ARM Cortex-M33 Regs +ARM Architecture & Registers + + + + Key Registers + + + + + r0 + Arg 1 / Return + + + + r1 + Arg 2 + + + + r2 + Arg 3 + + + + r3 + Arg 4 + + + + r0-r3 Caller-saved + r4-r11 Callee-saved + + + + SP (r13) + Stack Ptr + + + + LR (r14) + Return Addr + + + + PC (r15) + Next Instr + + + + xPSR + Status Flags + + + + Function Call Flow + + + + + main() + + + + + BL + + + + func() + + + + + BX LR + + + + + + How It Works + + r0 = first argument + puts(r0) passes the + string address in r0 + + + + LR saves return addr + BL: PC+4 stored in LR + BX LR: jump back + + + + All registers: 32 bits wide + \ No newline at end of file diff --git a/WEEK01/slides/WEEK01-IMG02.svg b/WEEK01/slides/WEEK01-IMG02.svg new file mode 100644 index 0000000..3345c63 --- /dev/null +++ b/WEEK01/slides/WEEK01-IMG02.svg @@ -0,0 +1,101 @@ + + + + + +Stack Growth Direction +ARM Stack Mechanics + + + + Before PUSH + + + 0x20082000 + SP here + + + (empty) + + + (empty) + + + (empty) + + + (empty) + + 0x20080000 + + + Grows DOWN + + + + + + After PUSH + + + 0x20082000 + + + LR value + + + r3 value + + + (empty) + + + (empty) + + SP here now = 0x20081FF8 + + SP moved down + by 8 bytes + + 0x20080000 + + + + Key Concepts + + + Full Descending + SP points to the + last pushed item + + + + PUSH: SP -= 4 + Each 32-bit val + drops SP by 4 + Two vals = -8 + + + + POP: SP += 4 + Restores values + SP moves back up + + + + Initial SP + Set by vector + table at 0x00 + StackTop=0x20082000 + \ No newline at end of file diff --git a/WEEK01/slides/WEEK01-IMG03.svg b/WEEK01/slides/WEEK01-IMG03.svg new file mode 100644 index 0000000..b684520 --- /dev/null +++ b/WEEK01/slides/WEEK01-IMG03.svg @@ -0,0 +1,98 @@ + + + + + +RP2350 Memory Map +RP2350 Address Space + + + + Address Space + + + + + ROM Boot + 0x0000_0000 + + + + XIP Flash + 16MB max + 0x1000_0000 + + + + SRAM + 520KB total + 0x2000_0000 + + + + APB Periph + 0x4000_0000 + + + + AHB Periph + 0x5000_0000 + + + + SIO + 0xD000_0000 + + + + PPB Cortex + 0xE000_0000 + + + addr+ + + + + Key Details + + + + XIP Flash + Code runs directly + from flash via cache + Execute-In-Place + + + + + SRAM Banks + SRAM0-7: 8x64KB + SRAM8-9: 2x4KB + Stack + Heap here + + + + + Peripherals + GPIO, UART, SPI + I2C, PWM, ADC + Memory-mapped I/O + + + + + SIO + PPB + Single-cycle I/O + Debug + NVIC + \ No newline at end of file diff --git a/WEEK01/slides/WEEK01-IMG04.svg b/WEEK01/slides/WEEK01-IMG04.svg new file mode 100644 index 0000000..d6c7ad5 --- /dev/null +++ b/WEEK01/slides/WEEK01-IMG04.svg @@ -0,0 +1,90 @@ + + + + + +Stack vs Heap in RAM +RP2350 SRAM: Data, BSS, Heap, Stack + + + + SRAM Layout + + + + 0x2008_2000 (top) + + + + STACK + Grows DOWN + SP = top of stack + + + + + + + + FREE SPACE + + + + + + + + HEAP + Grows UP + heap_base = __end__ + + + + .data + .bss + + + 0x2000_0000 (base) + + + + How To Read Sizes Quickly + + + + 1) .data size + __data_end__ - __data_start__ + init non-zero globals/statics + + + + 2) .bss size + __bss_end__ - __bss_start__ + zero/uninit globals/statics + + + + 3) Heap starts at + __end__ (aka __HeapBase) + + + + 4) Stack starts at + vector_table[0] (initial SP) + typical: 0x20082000 on RP2350 + + + + 5) Safety rule + Heap must never collide with stack + \ No newline at end of file diff --git a/WEEK01/slides/WEEK01-IMG05.svg b/WEEK01/slides/WEEK01-IMG05.svg new file mode 100644 index 0000000..7941c08 --- /dev/null +++ b/WEEK01/slides/WEEK01-IMG05.svg @@ -0,0 +1,86 @@ + + + + + +Link Register & Return +ARM Function Calls + + + + BL Call Flow + + + + Step 1: caller + + main: BL add + + + + + + + Step 2: hardware saves LR + + LR = return addr + + + + + + + Step 3: run function + + add: ADD r0, r1 + + + + + + + Step 4: return to caller + + BX LR (jump back) + + + + Key Concepts + + + BL instruction + Branch with Link + Saves return addr + in LR (r14) + + + + BX LR + Branch to addr + stored in LR + Returns to caller + + + + Nested Calls + Must PUSH LR first + PUSH {r3, lr} + POP {r3, pc} + POP into PC = return + + + + r14 = LR + Always check LR in GDB + \ No newline at end of file diff --git a/WEEK01/slides/WEEK01-IMG06.svg b/WEEK01/slides/WEEK01-IMG06.svg new file mode 100644 index 0000000..3d6d502 --- /dev/null +++ b/WEEK01/slides/WEEK01-IMG06.svg @@ -0,0 +1,101 @@ + + + + + +Program Counter Flow +Instruction Execution + + + + PC Execution + + + + ADDR + INSTRUCTION + + + + 0x1000 + MOV r0, #5 + PC + + + + 0x1002 + MOV r1, #3 + + + + 0x1004 + ADD r0, r1 + + + + 0x1006 + BL func + + + + 0x1010 + func: PUSH {lr} + + + + 0x1012 + SUB r0, #1 + + + + 0x1014 + POP {pc} + + + + + BL = jump to function target + POP {pc} = return to caller + + + + Key Concepts + + + r15 = PC + Points to current + instruction + 4 + Prefetch pipeline + + + + Sequential + Thumb next = +2 or +4 + depends on instruction encoding + Cortex-M33 executes Thumb only + + + + Branch + B = unconditional + BL = save LR, jump + BX = branch reg + BEQ = branch if Z=1 + not linear +2 stepping + + + + GDB tip + stepi = step 1 instr + \ No newline at end of file diff --git a/WEEK01/slides/WEEK01-IMG07.svg b/WEEK01/slides/WEEK01-IMG07.svg new file mode 100644 index 0000000..0acc44a --- /dev/null +++ b/WEEK01/slides/WEEK01-IMG07.svg @@ -0,0 +1,110 @@ + + + + + +Little-Endian Bytes +Byte Ordering + + + + Byte Ordering + + + + 0xDEADBEEF + + + Big-Endian + MSB first + + + DE + + + AD + + + BE + + + EF + + + +0 + +1 + +2 + +3 + + + vs + + + Little-Endian + LSB first + + + EF + + + BE + + + AD + + + DE + + + +0 + +1 + +2 + +3 + + + Bytes are reversed! + Lowest address holds + least significant byte + ARM uses little-endian + + + + Key Concepts + + + ARM = Little-Endian + Cortex-M33 uses LE + by default + Also: x86, RISC-V + + + + Why It Matters + Memory dumps show + raw byte order + Must mentally flip + to get true value + + + + GDB Example + x/4xb 0x2000 + EF BE AD DE + = 0xDEADBEEF + + + + x/xw = word view + GDB auto-corrects + \ No newline at end of file diff --git a/WEEK01/slides/WEEK01-IMG08.svg b/WEEK01/slides/WEEK01-IMG08.svg new file mode 100644 index 0000000..6b7faad --- /dev/null +++ b/WEEK01/slides/WEEK01-IMG08.svg @@ -0,0 +1,90 @@ + + + + + +XIP Flash Model +Execute-in-Place + + + + Execute-In-Place + + + + + QSPI Flash + External 16MB + + + + + + + + QSPI Controller + + + + + + + + XIP Cache + 16KB cache + + + + + + + + Cortex-M33 CPU + Fetches via PC + + + Mapped at + 0x1000_0000 + + + + Key Details + + + What is XIP? + Code stays in flash + CPU reads it as if + it were normal memory + No copy to SRAM needed + + + + QSPI Interface + 4 data lines + Fast serial flash + CLK, CS, IO0-IO3 + + + + Cache Behavior + Used for XIP flash reads + (code + const data at 0x1000....) + Not used for SRAM/peripherals + or during flash program/erase + + + + RE Insight + Dump flash via SWD + \ No newline at end of file diff --git a/WEEK01/slides/WEEK01-IMG09.svg b/WEEK01/slides/WEEK01-IMG09.svg new file mode 100644 index 0000000..7fc15e0 --- /dev/null +++ b/WEEK01/slides/WEEK01-IMG09.svg @@ -0,0 +1,92 @@ + + + + + +ELF File Structure +Binary Format + + + + ELF File Layout (Offsets) + + Higher file offsets + + + + Section Headers + + + + .symtab + + + + .bss + + + + .data + + + + .rodata + + + + .text + Machine code + + + + Program Headers + + + + ELF Header + Magic: 7f 45 4c 46 (off 0x0000) + + + + Section Details + Mapping + + + ELF Header + Arch: ARM 32-bit + Entry point addr + Type: executable + + + + .text = code + All instructions + Maps to XIP flash + Disassemble this! + + + + .data / .rodata + .data = initialized + .rodata = constants + .bss = zeroed vars + + + + .symtab = symbols + Function names + ELF header is file metadata + not runtime address 0x1000.... + Use section VMA/LMA for memory map + \ No newline at end of file diff --git a/WEEK01/slides/WEEK01-IMG10.svg b/WEEK01/slides/WEEK01-IMG10.svg new file mode 100644 index 0000000..ecffd4b --- /dev/null +++ b/WEEK01/slides/WEEK01-IMG10.svg @@ -0,0 +1,94 @@ + + + + + +GDB-OpenOCD Chain +Debug Toolchain + + + + Debug Toolchain + + + + + GDB + Client + + + + + TCP + :3333 + + + + OpenOCD + Server + + + + + USB + + + + CMSIS-DAP + Probe + + + + + SWD + + + + RP2350 + Target + + + + GDB Commands + + + target remote :3333 + Connect to OpenOCD + + monitor reset halt + Reset + stop at entry + + break main + Set breakpoint + + info registers + Dump all regs + + + + SWD Protocol + + + 2 wires only + SWCLK = clock + SWDIO = data + + + + Capabilities + Read/write memory + Read/write regs + Set breakpoints + Full chip control + \ No newline at end of file diff --git a/WEEK02/WEEK02-SLIDES.pdf b/WEEK02/WEEK02-SLIDES.pdf new file mode 100644 index 0000000..be346bc Binary files /dev/null and b/WEEK02/WEEK02-SLIDES.pdf differ diff --git a/WEEK02/WEEK02.md b/WEEK02/WEEK02.md new file mode 100644 index 0000000..05bb942 --- /dev/null +++ b/WEEK02/WEEK02.md @@ -0,0 +1,1597 @@ +# Week 2: Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2 + +## What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Connect to a live embedded system using OpenOCD and GDB +- Step through code instruction by instruction and watch the stack change +- Examine memory, registers, and decode little-endian values +- Set strategic breakpoints to pause execution at key moments +- Understand why direct string assignment fails in bare-metal systems +- Write custom data directly to SRAM memory +- Hijack register values to redirect program behavior +- Modify a running program's output in real-time + +## Review from Week 1 +This week builds directly on Week 1 concepts. You should already be comfortable with: +- **Registers** (`r0`-`r12`, SP, LR, PC) - We'll watch them change and manipulate `r0` to change program behavior +- **Memory Layout** (Flash at `0x10000000`, RAM at `0x20000000`) - Critical for understanding where we can write +- **The Stack** and how `push`/`pop` work - We'll watch this in action +- **Little-Endian** byte ordering - We'll decode values live +- **GDB basics** from Week 1's dynamic analysis section +- **Ghidra basics** from Week 1's static analysis section + +--- + +## Part 1: Understanding Live Hacking + +#### What is Live Hacking? + +**Live hacking** means modifying a program *while it's running* on real hardware. Instead of changing the source code and recompiling, we intercept the program mid-execution and change what it does on the fly. + +Think of it like this: imagine a train is heading to New York City. Live hacking is like switching the tracks while the train is moving so it goes to Los Angeles instead! + +#### Why is This Important? + +Live hacking techniques are used for: +- **Security Research**: Finding vulnerabilities in embedded systems +- **Penetration Testing**: Testing if systems can be compromised +- **Malware Analysis**: Understanding how malicious code works +- **Debugging**: Fixing bugs in systems that can't be easily reprogrammed + +#### Real-World Application + +> **"With great power comes great responsibility!"** + +Imagine you're a security researcher testing an industrial control system at a power plant. You need to verify that an attacker couldn't: +1. Change the values being displayed to engineers +2. Make dangerous equipment appear safe +3. Hide malicious activity from monitoring systems + +The techniques you'll learn today are *exactly* how this would be done. Understanding these attacks helps us build better defenses! + +--- + +## Part 2: Review - Memory Layout (from Week 1) + +> **REVIEW:** In Week 1, we learned about the RP2350's memory layout. This knowledge is essential for our hack! + +Before we hack, let's remember where things live in memory on the RP2350: + +#### The Code We're Hacking + +Remember our `0x0001_hello-world.c` program from Week 1: + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + stdio_init_all(); + + while (true) + printf("hello, world\r\n"); +} +``` + +This simple program: +1. Initializes I/O with `stdio_init_all()` +2. Enters an infinite `while(true)` loop +3. Prints `"hello, world\r\n"` forever + +Our goal: **Make it print something else WITHOUT changing the source code!** + +#### Memory Map + +``` ++-----------------------------------------------------+ +| Flash Memory (XIP) - READ ONLY | +| Starts at: 0x10000000 | +| Contains: Program code, constant strings | +| NOTE: We CANNOT write to flash during runtime! | ++-----------------------------------------------------+ +| SRAM - READ/WRITE | +| Starts at: 0x20000000 | +| Contains: Stack, Heap, Variables | +| NOTE: We CAN write to SRAM during runtime! | ++-----------------------------------------------------+ +``` + +> **REVIEW:** In Week 1, we saw SP values in the `0x20081xxx` range (for example `0x20081fc8`) - that's in the SRAM region. In this run you may see values like `0x20081ff8` depending on where execution is paused. The stack "grows downward" from the top of SRAM. + +#### Why This Matters for Our Hack + +The string `"hello, world"` is stored in **flash memory** (around `0x100019cc`). Flash memory is **read-only** during normal operation - we can't just overwrite it. + +But SRAM (starting at `0x20000000`) is **read-write**! This is where we'll create our hacked string. + +--- + +## Part 3: The Attack Plan + +Here's our step-by-step attack strategy: + +``` ++-----------------------------------------------------+ +| STEP 1: Start the debug server (OpenOCD) | ++-----------------------------------------------------+ +| STEP 2: Connect with GDB and halt the program | ++-----------------------------------------------------+ +| STEP 3: Set a breakpoint right before puts() | ++-----------------------------------------------------+ +| STEP 4: When we hit the breakpoint, r0 contains | +| the address of "hello, world" | ++-----------------------------------------------------+ +| STEP 5: Create our malicious string in SRAM | ++-----------------------------------------------------+ +| STEP 6: Change r0 to point to OUR string | ++-----------------------------------------------------+ +| STEP 7: Continue execution - HACKED! | ++-----------------------------------------------------+ +``` + +--- + +## Part 4: Setting Up Your Environment + +#### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board with debug probe connected +2. OpenOCD installed and configured +3. GDB (arm-none-eabi-gdb) installed +4. A serial monitor application (like PuTTY, minicom, or screen) +5. The "hello-world" binary loaded on your Pico 2 + +#### What You'll Need Open + +You will need **THREE** terminal windows: +1. **Terminal 1**: Running OpenOCD (the debug server) +2. **Terminal 2**: Running GDB (where we do the hacking) +3. **PuTTY**: Running your serial monitor (to see output) + +--- + +## Part 5: GDB Deep Dive - Exploring the Binary + +Before we start hacking, let's use GDB to thoroughly understand our program. This hands-on tutorial will teach you to examine memory, step through code, and watch the stack in action. + +#### Starting the Debug Environment + +##### Step 0a: Start OpenOCD (Terminal 1) + +OpenOCD is the bridge between your computer and the Pico 2's debug interface. It creates a server that GDB can connect to. + +**Open Terminal 1 and type:** + +```powershell +openocd -s "$env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev\scripts" -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000" +``` + +**What this command means:** +- `openocd` = the OpenOCD program +- `-s ...` = path to OpenOCD scripts folder +- `-f interface/cmsis-dap.cfg` = use the CMSIS-DAP debug probe configuration +- `-f target/rp2350.cfg` = configure for the RP2350 chip +- `-c "adapter speed 5000"` = set the debug speed to 5000 kHz + +**You should see output like:** + +``` +Open On-Chip Debugger 0.12.0 +Licensed under GNU GPL v2 +Info : Listening on port 3333 for gdb connections +Info : CMSIS-DAP: SWD supported +Info : CMSIS-DAP: Interface ready +``` + +**Important:** Leave this terminal running! Don't close it. + +##### Step 0b: Start Your Serial Monitor - PuTTY (Terminal 2) + +PuTTY will show us the output from our Pico 2. When we hack the program, we'll see the results here! + +**To set up PuTTY:** + +1. Open **PuTTY** +2. In the **Session** category: + - **Connection type**: Select **Serial** + - **Serial line**: Enter your COM port (e.g., `COM3` - check Device Manager to find yours) + - **Speed**: Enter `115200` +3. Click **Open** + +> Tip: **Finding your COM port:** Open Device Manager -> Ports (COM & LPT) -> Look for "USB Serial Device" or "Pico" - note the COM number. + +**You should see:** + +``` +hello, world +hello, world +hello, world +hello, world +... +``` + +The program is running and printing `"hello, world"` in an infinite loop! + +**Important:** Leave PuTTY running! We'll watch it change when we hack the system. + +##### Step 0c: Start GDB (Terminal 3) + +**Open Terminal 3** and start GDB with your binary: + +```cmd +arm-none-eabi-gdb build\0x0001_hello-world.elf +``` + +**What this command means:** +- `arm-none-eabi-gdb` = the ARM version of GDB +- `build\0x0001_hello-world.elf` = our compiled program with debug symbols + +**You should see:** + +``` +GNU gdb (Arm GNU Toolchain 13.2) 13.2 +Reading symbols from build\0x0001_hello-world.elf... +(gdb) +``` + +The `(gdb)` prompt means GDB is ready for commands! + +##### Step 0d: Connect GDB to OpenOCD + +Now we need to connect GDB to OpenOCD. OpenOCD is listening on port `3333`. + +**Type this command:** + +```gdb +(gdb) target extended-remote :3333 +``` + +**You should see:** + +``` +Remote debugging using :3333 +main () + at C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0001_hello-world/0x0001_hello-world.c:5 +5 stdio_init_all(); +``` + +We're connected! GDB shows us the program is currently in the `main` function. + +##### Step 0e: Halt the Running Program + +The program is still running (you can see "hello, world" still printing in PuTTY). Let's stop it: + +**Type this command:** + +```gdb +(gdb) monitor reset halt +``` + +**What this command means:** +- `monitor` = send a command to OpenOCD (not GDB) +- `reset` = reset the processor +- `halt` = stop execution immediately + +**You should see:** + +``` +[rp2350.cm0] halted due to debug-request, current mode: Thread +xPSR: 0xf9000000 pc: 0x00000088 msp: 0xf0000000 +[rp2350.cm1] halted due to debug-request, current mode: Thread +xPSR: 0xf9000000 pc: 0x00000088 msp: 0xf0000000 +``` + +**Check PuTTY:** The "hello, world" messages should have stopped! The processor is now frozen, waiting for our commands. + +--- + +#### Exploring the Binary + +Now that we're connected and the processor is halted, let's explore! + +##### Step 1: Examine Memory Starting at XIP Base Address + +Our program code starts at address `0x10000000`. Let's look at the first 1000 instructions to find our `main` function. + +**Type this command in GDB:** + +```gdb +(gdb) x/1000i 0x10000000 +``` + +**What this command means:** +- `x` = examine memory +- `/1000i` = show 1000 instructions +- `0x10000000` = starting address + +**What you're looking for:** + +Scroll through the output and look for something like this: + +``` +0x10000234
: push {r3, lr} +``` + +The `
` label tells us we found our main function! The address `0x10000234` is where main starts. + +##### Step 2: Examine the Main Function in Detail + +Now let's look at just the main function. We'll examine 5 instructions starting at the main function address: + +**Type this command:** + +```gdb +(gdb) x/5i 0x10000234 +``` + +**You should see:** + +``` +(gdb) x/5i 0x10000234 +=> 0x10000234
: push {r3, lr} + 0x10000236 : bl 0x1000156c + 0x1000023a : ldr r0, [pc, #8] @ (0x10000244 ) + 0x1000023c : bl 0x100015fc <__wrap_puts> + 0x10000240 : b.n 0x1000023a +``` + +**Let's understand each instruction:** + +| Address | Instruction | What It Does | +| ------------ | -------------------------------- | --------------------------------------------------- | +| `0x10000234` | `push {r3, lr}` | Save `r3` and the return address onto the stack | +| `0x10000236` | `bl 0x1000156c ` | Call the `stdio_init_all` function | +| `0x1000023a` | `ldr r0, [pc, #8]` | Load the address of our string into `r0` | +| `0x1000023c` | `bl 0x100015fc <__wrap_puts>` | Call `puts` to print our string | +| `0x10000240` | `b.n 0x1000023a` | Jump back to the `ldr` instruction (infinite loop!) | + +##### Step 3: Set a Breakpoint at Main + +A **breakpoint** is like a stop sign for your program. When the processor reaches that address, it will pause and let us examine things. + +**Type this command:** + +```gdb +(gdb) b *0x10000234 +``` + +**You should see:** + +``` +Breakpoint 1 at 0x10000234: file C:/Users/.../0x0001_hello-world.c, line 5. +Note: automatically using hardware breakpoints for read-only addresses. +``` + +**What this means:** +- `b` = set breakpoint +- `*0x10000234` = at this exact memory address +- GDB confirms the breakpoint is set and even tells us which line of C code this corresponds to! + +##### Step 4: Continue Execution Until Breakpoint + +Now let's run the program until it hits our breakpoint: + +**Type this command:** + +```gdb +(gdb) c +``` + +**You should see:** + +``` +Continuing. + +Thread 1 "rp2350.cm0" hit Breakpoint 1, main () + at C:/Users/.../0x0001_hello-world.c:5 +5 stdio_init_all(); +``` + +**What happened:** +- The processor ran until it reached address `0x10000234` +- It stopped right before executing the instruction at that address +- GDB shows us we're at line 5 of our C source code + +##### Step 5: Examine Instructions with Arrow + +Let's look at our instructions again: + +**Type this command:** + +```gdb +(gdb) x/5i 0x10000234 +``` + +**You should see:** + +``` +(gdb) x/5i 0x10000234 +=> 0x10000234
: push {r3, lr} + 0x10000236 : bl 0x1000156c + 0x1000023a : ldr r0, [pc, #8] @ (0x10000244 ) + 0x1000023c : bl 0x100015fc <__wrap_puts> + 0x10000240 : b.n 0x1000023a +``` + +**Notice the arrow `=>`!** This arrow shows which instruction we're about to execute. We haven't executed it yet - we're paused right before it. + +--- + +#### Understanding the Stack in Action + +##### Step 6: Examine the Stack Before Push + +Before we execute the `push` instruction, let's see what's on the stack: + +**Type this command:** + +```gdb +(gdb) x/10x $sp +``` + +**What this command means:** +- `x` = examine memory +- `/10x` = show 10 values in hexadecimal +- `$sp` = starting at the stack pointer address + +**You should see:** + +``` +0x20082000: 0x00000000 0x00000000 0x00000000 0x00000000 +0x20082010: 0x00000000 0x00000000 0x00000000 0x00000000 +0x20082020: 0x00000000 0x00000000 +``` + +**What this shows:** +- The stack pointer is at address `0x20082000` +- The stack is empty (all zeros) +- This is the "top" of our stack in RAM + +**Verify where this value came from (the Vector Table):** + +Now let's trace back where this initial `0x20082000` value came from. It comes from the first entry in the vector table at `0x10000000`: + +```gdb +(gdb) x/x $sp +0x20082000: 0x00000000 +(gdb) x/x 0x10000000 +0x10000000 <__vectors>: 0x20082000 +``` + +**What this shows:** +- The first command `x/x $sp` reads one word at the stack pointer (currently `0x00000000`) +- The second command `x/x 0x10000000` reads the **first entry in the vector table** at address `0x10000000` +- That vector table entry contains `0x20082000` - this is the **initial stack pointer value**! +- The Boot ROM reads this value and puts it into the SP register when the chip starts +- This is why our SP register already has `0x20082000` before `main()` runs + +##### Step 7: Execute One Instruction (Step Into) + +Now let's execute just ONE assembly instruction: + +**Type this command:** + +```gdb +(gdb) si +``` + +**What this command means:** +- `si` = step instruction (execute one assembly instruction) + +**You should see:** + +``` +0x10000236 5 stdio_init_all(); +``` + +Let's verify where we are: + +**Type this command:** + +```gdb +(gdb) x/5i 0x10000234 +``` + +**You should see:** + +``` +(gdb) x/5i 0x10000234 + 0x10000234
: push {r3, lr} +=> 0x10000236 : bl 0x1000156c + 0x1000023a : ldr r0, [pc, #8] @ (0x10000244 ) + 0x1000023c : bl 0x100015fc <__wrap_puts> + 0x10000240 : b.n 0x1000023a +``` + +**Notice:** The arrow `=>` has moved! We've executed the `push` instruction and are now about to execute the `bl` (branch with link) instruction. + +##### Step 8: Examine the Stack After Push + +Now let's see what the push instruction did to our stack: + +**Type this command:** + +```gdb +(gdb) x/10x $sp +``` + +**You should see:** + +``` +0x20081ff8: 0xe000ed08 0x1000018f 0x00000000 0x00000000 +0x20082008: 0x00000000 0x00000000 0x00000000 0x00000000 +0x20082018: 0x00000000 0x00000000 +``` + +**What changed:** +- The stack pointer moved from `0x20082000` to `0x20081ff8` +- That's 8 bytes lower (2 * 4-byte values) +- Two new values appeared: `0xe000ed08` and `0x1000018f` + +##### Step 9: Verify What Was Pushed + +Let's prove that these values came from `r3` and `lr`: + +**Check `r3`:** + +```gdb +(gdb) x/x $r3 +``` + +**You should see:** + +``` +0xe000ed08: Cannot access memory at address 0xe000ed08 +``` + +This error is expected! The value `0xe000ed08` is in `r3`, and when we try to examine it as an address, that memory location isn't accessible. But we can see the value matches what's on the stack! + +**Check `lr` (Link Register):** + +```gdb +(gdb) x/x $lr +``` + +**You should see:** + +``` +0x1000018f : 0x00478849 +``` + +The value `0x1000018f` is in `lr` - this is the return address! This matches the second value on our stack. + +##### Step 10: Verify Stack Layout + +Let's look at each pushed value individually: + +**First pushed value (`r3`):** + +```gdb +(gdb) x/x $sp +``` + +**You should see:** + +``` +0x20081ff8: 0xe000ed08 +``` + +This is the value from `r3`, pushed first. + +**Second pushed value (`lr`):** + +```gdb +(gdb) x/x $sp+4 +``` + +**You should see:** + +``` +0x20081ffc: 0x1000018f +``` + +This is the value from `lr` (the return address), pushed second. + +### Understanding the Stack Diagram + +``` +Before push {r3, lr}: After push {r3, lr}: + +Address Value Address Value +--------------------- --------------------- +0x20082000 (empty) ↠SP 0x20082000 (old SP location) + 0x20081ffc 0x1000018f (lr) + 0x20081ff8 0xe000ed08 (r3) ↠SP +``` + +**Key Points:** +1. The stack grows DOWNWARD (addresses get smaller) +2. The SP always points to the last item pushed +3. `r3` was pushed first, then `lr` was pushed on top of it + +--- + +#### Continuing Through the Program + +##### Step 11: Step Over the stdio_init_all Function + +We don't need to examine every instruction inside `stdio_init_all` - it's just setup code. Let's "step over" it: + +**First, verify where we are:** + +```gdb +(gdb) x/5i 0x10000234 +``` + +**You should see:** + +```gdb +(gdb) x/5i 0x10000234 + 0x10000234
: push {r3, lr} +=> 0x10000236 : bl 0x1000156c + 0x1000023a : ldr r0, [pc, #8] @ (0x10000244 ) + 0x1000023c : bl 0x100015fc <__wrap_puts> + 0x10000240 : b.n 0x1000023a +``` + +**Now step over the function call:** + +```gdb +(gdb) n +``` + +**What this command means:** +- `n` = next (step over function calls, don't go inside them) + +**You should see:** + +``` +8 printf("hello, world\r\n"); +``` + +**Verify where we are now:** + +```gdb +(gdb) x/5i 0x10000234 +``` + +**You should see:** + +```gdb +(gdb) x/5i 0x10000234 + 0x10000234
: push {r3, lr} + 0x10000236 : bl 0x1000156c +=> 0x1000023a : ldr r0, [pc, #8] @ (0x10000244 ) + 0x1000023c : bl 0x100015fc <__wrap_puts> + 0x10000240 : b.n 0x1000023a +``` + +The arrow has moved past the function call! + +#### Understanding the LDR Instruction + +We're now at: +``` +ldr r0, [pc, #8] @ (0x10000244 ) +``` + +**What does this instruction do?** + +1. Take the current Program Counter (PC) value +2. Add 8 to it +3. Go to that memory address (`0x10000244`) +4. Load the value stored there into `r0` + +This is loading a **pointer** - the address of our "hello, world" string! + +##### Step 13: Execute the LDR and Examine `r0` + +**Execute one instruction:** + +```gdb +(gdb) si +``` + +**You should see:** + +``` +0x1000023c 8 printf("hello, world\r\n"); +``` + +**Now examine what's in `r0`:** + +```gdb +(gdb) x/x $r0 +``` + +**You should see:** + +``` +0x100019cc: 0x6c6c6568 +``` + +##### Step 14: Decoding the Mystery Value + +The value `0x6c6c6568` looks strange, but it's actually ASCII characters! Let's decode it: + +**ASCII Table Reference:** +| Hex | Character | +| ------ | --------- | +| `0x68` | h | +| `0x65` | e | +| `0x6c` | l | +| `0x6c` | l | + +So `0x6c6c6568` = "lleh" backwards! + +**Why is it backwards?** + +This is called **little-endian** byte order. The RP2350 stores bytes in reverse order in memory. When we read them as a 32-bit value, they appear reversed. + +##### Step 15: View the Full String + +Let's tell GDB to show this as a string instead of a hex number: + +```gdb +(gdb) x/s $r0 +``` + +**What this command means:** +- `x` = examine memory +- `/s` = show as a string +- `$r0` = at the address stored in `r0` + +**You should see:** + +``` +0x100019cc: "hello, world\r" +``` + +There's our string! The `\r` is a carriage return character (part of `\r\n`). + +> **Key Discovery:** The string `"hello, world"` is stored at address `0x100019cc` in flash memory. This is the value that gets loaded into `r0` before calling `puts()`. We'll use this knowledge in our hack! + +--- + +## Part 6: Continuing the Debug Session for the Hack + +You're right where you need to be from Part 5, so we **do not restart** OpenOCD or GDB here. + +Use this as a quick checkpoint before the hack: + +- OpenOCD is still running and listening on `:3333` +- GDB is still connected (`target extended-remote :3333` already done) +- The target is halted at a known point (or easy to re-halt) + +If your session is still live, continue directly to Part 7. + +If you got disconnected, run only this minimal recovery in GDB: + +```gdb +(gdb) target extended-remote :3333 +(gdb) monitor reset halt +``` + +After that, continue to the analysis steps below. + +--- + +## Part 7: Analyzing the Target + +> **REVIEW:** We're using the same GDB commands we learned earlier. The `x` command examines memory, and `/5i` shows 5 instructions. + +##### Step 6: Examine the Main Function + +Let's look at the main function to understand what we're dealing with: + +**Type this command:** + +```gdb +(gdb) x/5i 0x10000234 +``` + +**What this command means:** +- `x` = examine memory (Week 1 review!) +- `/5i` = show 5 instructions +- `0x10000234` = the address of main (we found this in Week 1!) + +**You should see:** + +```gdb +(gdb) x/5i 0x10000234 + 0x10000234
: push {r3, lr} + 0x10000236 : bl 0x1000156c + 0x1000023a : ldr r0, [pc, #8] @ (0x10000244 ) +=> 0x1000023c : bl 0x100015fc <__wrap_puts> + 0x10000240 : b.n 0x1000023a +``` + +> **REVIEW:** This is the same disassembly we analyzed in Week 1! Remember: +> - `push {r3, lr}` saves registers to the stack +> - `bl` is "branch with link" - it calls a function and saves the return address in LR +> - `b.n` is the infinite loop that jumps back to the `ldr` instruction + +#### Understanding the Code Flow + +> **REVIEW:** In Week 1, we learned that `r0`-`r3` are used to pass arguments to functions. The first argument always goes in `r0`! + +Let's break down what happens each time through the loop: + +| Address | Instruction | What Happens | +| ------------ | ------------------ | ------------------------------------------------------ | +| `0x1000023a` | `ldr r0, [pc, #8]` | Load the address of `"hello, world"` into `r0` | +| `0x1000023c` | `bl __wrap_puts` | Call `puts()` - it reads the string address from `r0`! | +| `0x10000240` | `b.n 0x1000023a` | Jump back to the `ldr` instruction (loop forever) | + +#### How This Maps to Our C Code + +```c +while (true) + printf("hello, world\r\n"); // The compiler optimized this to puts() +``` + +The compiler: +1. Loads the string address into `r0` (first argument) +2. Calls `puts()` (optimized from `printf()` since we're just printing a string) +3. Loops back forever with `b.n` + +**The Key Insight:** Right before the `bl __wrap_puts` instruction (at `0x1000023c`), the register `r0` contains the address of the string to print! + +If we can change what `r0` points to, we can make it print **anything we want**! + +--- + +## Part 8: Setting the Trap + +> **REVIEW:** In Week 1, we used `b main` and `b *0x10000234` to set breakpoints. Now we'll use the same technique at a more strategic location! + +##### Step 7: Set a Strategic Breakpoint + +We want to stop the program RIGHT BEFORE it calls `puts()`. That's at address `0x1000023c`. + +**Type this command:** + +```gdb +(gdb) b *0x1000023c +``` + +**What this command means:** +- `b` = set a breakpoint (same as Week 1!) +- `*0x1000023c` = at this exact memory address (the asterisk means "address") + +**You should see:** + +``` +Breakpoint 2 at 0x1000023c: file C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0001_hello-world/0x0001_hello-world.c, line 8 +``` + +**What does "hardware breakpoints" mean?** + +Because our code is in flash memory (read-only), GDB can't insert a software breakpoint by modifying the code. Instead, it uses a special feature of the ARM processor called a **hardware breakpoint**. The processor has a limited number of these (usually 4-8), but they work on any memory type. + +##### Step 8: Continue Execution and Hit the Breakpoint + +Now let's run the program until it hits our breakpoint: + +**Type this command:** + +```gdb +(gdb) c +``` + +**What this command means:** +- `c` = continue (run until something stops us) + +**You should see:** + +```gdb +Continuing. + +Thread 1 "rp2350.cm0" hit Breakpoint 2, 0x1000023c in main () + at C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0001_hello-world/0x0001_hello-world.c:8 +8 printf("hello, world\r\n"); +``` + +The program has stopped RIGHT BEFORE calling `puts()`! The string address is loaded into `r0`, but the function hasn't been called yet. + +##### Step 9: Verify Our Position with Disassembly + +Let's double-check where we are using the `disas` command: + +**Type this command:** + +```gdb +(gdb) disas +``` + +**What this command means:** +- `disas` = disassemble the current function + +**You should see:** + +```gdb +(gdb) disas +Dump of assembler code for function main: + 0x10000234 <+0>: push {r3, lr} + 0x10000236 <+2>: bl 0x1000156c + 0x1000023a <+6>: ldr r0, [pc, #8] @ (0x10000244 ) +=> 0x1000023c <+8>: bl 0x100015fc <__wrap_puts> + 0x10000240 <+12>: b.n 0x1000023a + 0x10000242 <+14>: nop + 0x10000244 <+16>: adds r4, r1, r7 + 0x10000246 <+18>: asrs r0, r0, #32 +``` + +**Notice the arrow `=>`** pointing to `0x1000023c`! This confirms we're about to execute the `bl __wrap_puts` instruction. Perfect! + +--- + +## Part 9: Examining the Current State + +> **REVIEW:** In Week 1, we used `x/s $r0` to view the "hello, world" string. We also learned about **little-endian** byte ordering - remember how `0x6c6c6568` spelled "lleh" backwards? + +##### Step 10: Examine What's in r0 + +Let's see what string `r0` is currently pointing to: + +**Type this command:** + +```gdb +(gdb) x/s $r0 +``` + +**What this command means:** +- `x` = examine memory (Week 1 review!) +- `/s` = display as a string +- `$r0` = the address stored in register `r0` + +**You should see:** + +```gdb +0x100019cc: "hello, world\r" +``` + +There it is! The register `r0` contains `0x100019cc`, which is the address of our `"hello, world"` string in flash memory. + +> **REVIEW:** This is the same address `0x100019cc` we discovered in Week 1, Step 15 when we used `x/s $r0` after executing the `ldr` instruction! + +--- + +## Part 10: The Failed Hack Attempt (Learning Why) + +##### Step 11: Try to Directly Change the String (This Will Fail!) + +Your first instinct might be to just assign a new string to `r0`. Let's try it and see what happens: + +**Type this command:** + +```gdb +(gdb) set $r0 = "hacky, world\r" +``` + +**You should see an error:** + +``` +evaluation of this expression requires the program to have a function "malloc". +``` + +**Oh no! It didn't work!** + +#### Why Did This Fail? + +This is a very important lesson! Here's what happened: + +1. When you type `"hacky, world\r"` in GDB, GDB interprets this as: "Create a new string and give me its address" + +2. To create a new string at runtime, GDB tries to allocate target memory using `malloc()`. + +3. If the target program exposed a working `malloc()` that GDB could call, this style of assignment can work. + +4. In our case, this is a bare-metal firmware image and there is no usable `malloc()` path for GDB expression evaluation, so GDB cannot create temporary storage for that string literal. + +5. That is why this command fails here, and why we switch to writing bytes directly into SRAM ourselves. + +**Let's verify nothing changed:** + +```gdb +(gdb) x/s $r0 +``` + +**You should see:** + +``` +0x100019cc: "hello, world\r" +``` + +The original string is still there. Our hack attempt failed... but we're not giving up! + +--- + +## Part 11: The Real Hack - Writing to SRAM + +##### Step 12: Understanding the Solution + +Since we can't use `malloc()`, we need to manually create our string somewhere in memory. Remember our memory map? + +- Flash (`0x10000000`): **Read-only** - can't write here +- SRAM (`0x20000000`): **Read-write** - we CAN write here! + +> **REVIEW:** In Week 1 we observed SP in the `0x20081xxx` range (for example `0x20081fc8`). In this run, after `push {r3, lr}`, you are seeing `0x20081ff8`, which is also correct and still near the top of SRAM. We'll write our string at a safer SRAM address (`0x20040000`) to avoid vector-table and stack conflicts. + +We'll write our malicious string directly to SRAM, then point `r0` to it. + +##### Step 13: Create Our Malicious String in SRAM + +We need to write 13 bytes (12 characters + null terminator) to SRAM: + +| Character | ASCII Hex | +| --------- | --------- | +| h | - | +| a | - | +| c | - | +| k | - | +| y | - | +| , | - | +| (space) | - | +| w | - | +| o | - | +| r | - | +| l | - | +| d | - | +| \r | - | +| \0 | - | + +**Type this command:** + +```gdb +(gdb) set {char[13]} 0x20040000 = "hacky, world" +``` + +**What this command means:** +- `set` = modify memory +- `{char[13]}` = treat the target as an array of 13 characters +- `0x20040000` = the address where we're writing (safe SRAM offset) +- `= "hacky, world"` = the string bytes to write + +**No output means success!** + +##### Step 14: Verify Our String Was Written + +Let's confirm our malicious string is in SRAM: + +**Type this command:** + +```gdb +(gdb) x/s 0x20040000 +``` + +**You should see:** + +```gdb +0x20040000: "hacky, world" +``` + +**OUR STRING IS IN MEMORY!** + +The important thing is our string is in writable SRAM at a safe offset and ready to use. + +--- + +## Part 12: Hijacking the Register + +> **REVIEW:** In Week 1, we learned that `r0` holds the first argument to a function. When `puts()` is called, it expects `r0` to contain a pointer to the string it should print. By changing `r0`, we change what gets printed! + +##### Step 15: Change r0 to Point to Our String + +Now for the magic moment! We'll change `r0` from pointing to the original string to pointing to OUR string: + +**Type this command:** + +```gdb +(gdb) set $r0 = 0x20040000 +``` + +**What this command means:** +- `set` = modify a value +- `$r0` = the `r0` register +- `= 0x20040000` = change it to this address (where our string is) + +**No output means success!** + +##### Step 16: Verify the Register Was Changed + +Let's confirm `r0` now points to our malicious string: + +**First, check one byte (explicit byte view):** + +```gdb +(gdb) x/bx $r0 +``` + +**You should see:** + +``` +0x20040000: 0x68 +``` + +The value `0x68` is ASCII `'h'`, the first byte of `"hacky, world"`. + +**Now check one 32-bit word (explicit word view):** + +```gdb +(gdb) x/wx $r0 +``` + +**You should see:** + +``` +0x20040000: 0x6b636168 +``` + +This is the first 4 bytes (`h a c k`) packed into one 32-bit little-endian word. + +**Now check it as a string:** + +```gdb +(gdb) x/s $r0 +``` + +**You should see:** + +``` +0x20040000: "hacky, world" +``` + +**THE HIJACK IS COMPLETE!** When `puts()` runs, it will read the string address from `r0` - which now points to our malicious string! + +--- + +## Part 13: Executing the Hack + +##### Step 17: Continue Execution + +This is the moment of truth! Let's continue the program and watch our hack take effect: + +**Type this command:** + +```gdb +(gdb) c +``` + +**You should see:** + +```gdb +Continuing. + +Thread 1 "rp2350.cm0" hit Breakpoint 2, 0x1000023c in main () + at C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0001_hello-world/0x0001_hello-world.c:8 +8 printf("hello, world\r\n"); +``` + +The program ran through one loop iteration and hit our breakpoint again. + +##### Step 18: Check Your Serial Monitor! + +**Look at Terminal 2 (your serial monitor)!** + +**You should see:** + +``` +hello, world +hello, world +hello, world +hacky, world <-- OUR HACK! +``` + + **BOOM! WE DID IT!** + +You just modified a running program on real hardware! The processor executed code that was supposed to print "hello, world" but instead printed "hacky, world" because we hijacked the data it was using! + +--- + +## Part 14: Static Analysis with Ghidra - Understanding the Hack + +Now that we've performed the hack dynamically with GDB, let's use Ghidra to understand the same concepts through static analysis. This shows how you could plan such an attack without even connecting to the hardware! + +#### Opening the Project in Ghidra + +If you haven't already set up the Ghidra project from Week 1: + +1. Launch Ghidra +2. Select **File -> New Project** -> **Non-Shared Project** +3. Name it `0x0001_hello-world` +4. Drag and drop `0x0001_hello-world.elf` into the project +5. Double-click to open in CodeBrowser +6. Click **Yes** to auto-analyze + +##### Step 1: Navigate to Main + +1. In the **Symbol Tree** panel (left side), expand **Functions** +2. Find and click on `main` + +**What you'll see in the Listing View:** + +``` + ************************************************************* + * FUNCTION + ************************************************************* + int main (void ) + assume LRset = 0x0 + assume TMode = 0x1 + int r0:4 + main XREF[3]: Entry Point (*) , + _reset_handler:1000018c (c) , + .debug_frame::00000018 (*) + 0x0001_hello-world.c:4 (2) + 0x0001_hello-world.c:5 (2) + 10000234 08 b5 push {r3,lr} + 0x0001_hello-world.c:5 (4) + 10000236 01 f0 99 f9 bl stdio_init_all _Bool stdio_init_all(void) + LAB_1000023a XREF[1]: 10000240 (j) + 0x0001_hello-world.c:7 (6) + 0x0001_hello-world.c:8 (6) + 1000023a 02 48 ldr r0=>__EH_FRAME_BEGIN__ ,[DAT_10000244 ] = "hello, world\r" + = 100019CCh + 1000023c 01 f0 de f9 bl __wrap_puts int __wrap_puts(char * s) + 0x0001_hello-world.c:7 (8) + 10000240 fb e7 b LAB_1000023a + 10000242 00 ?? 00h + 10000243 bf ?? BFh + DAT_10000244 XREF[1]: main:1000023a (R) + 10000244 cc 19 00 10 undefine 100019CCh ? -> 100019cc +``` + +**What you'll see in the Decompile View:** + +```c +int main(void) + +{ + stdio_init_all(); + do { + __wrap_puts("hello, world\r"); + } while( true ); +} +``` + +##### Step 2: Identify the Attack Point + +In our GDB hack, we set a breakpoint at `0x1000023c` - right before `bl __wrap_puts`. Let's understand why this was the perfect attack point: + +**Click on address `0x1000023c` in the Listing view.** + +Notice: +- The instruction is `bl __wrap_puts` - a function call +- The previous instruction at `0x1000023a` loaded `r0` with the string address +- Ghidra shows `= "hello, world\r"` right in the listing! + +> **Key Insight:** Ghidra already tells us the string value! In the Listing, you can see `= "hello, world\r"` and `= 100019CCh`. This is the exact address we discovered through GDB! + +##### Step 3: Find the String in Memory + +Let's trace where the string actually lives: + +1. In the Listing view, look at address `0x1000023a`: + ``` + LAB_1000023a XREF[1]: 10000240 (j) + 0x0001_hello-world.c:7 (6) + 0x0001_hello-world.c:8 (6) + 1000023a 02 48 ldr r0=>__EH_FRAME_BEGIN__ ,[DAT_10000244 ] = "hello, world\r" + = 100019CCh + ``` + +2. **Double-click on `DAT_10000244`** to go to the data reference + +3. You'll see: + ``` + DAT_10000244 XREF[1]: main:1000023a (R) + 10000244 cc 19 00 10 undefine 100019CCh ? -> 100019cc + ``` + +4. **Double-click on `100019CCh`** to navigate to the actual string + +**You'll arrive at the string data:** + +``` + // + // .rodata + // SHT_PROGBITS [0x100019cc - 0x10001b17] + // ram:100019cc-ram:10001b17 + // + __init_array_end XREF[5]: Entry Point (*) , + __boot2_start__ frame_dummy:10000218 (*) , + __boot2_end__ main:1000023a (*) , + __EH_FRAME_BEGIN__ runtime_init:1000138a (R) , + _elfSectionHeaders::0000005c (*) + 100019cc 68 65 6c ds "hello, world\r" + 6c 6f 2c + 20 77 6f +``` + +##### Step 4: Understand Why We Needed SRAM + +Look at the string address: `0x100019cc` + +This starts with `0x10...` which means it's in **Flash memory (XIP region)**! + +| Address Range | Memory Type | Writable? | +| ------------- | ----------- | --------- | +| `0x10000000`+ | Flash (XIP) | **NO** - Read Only | +| `0x20000000`+ | SRAM | **YES** - Read/Write | + +> **This is why our direct string modification failed in GDB!** The string lives in flash memory, which is read-only at runtime. We had to create our malicious string in SRAM at a safe address (`0x20040000`) instead. + +##### Step 5: Examine Cross-References + +Ghidra's cross-reference feature shows everywhere a value is used: + +1. Navigate back to `main` (press **G**, type `main`, press Enter) +2. Click on `__wrap_puts` at address `0x1000023c` +3. Right-click and select **References -> Show References to __wrap_puts** + +This shows every place that calls `puts()`. In a larger program, you could find ALL the print statements and potentially modify any of them! + +##### Step 6: Use the Decompiler to Plan Attacks + +The Decompile view makes attack planning easy: + +```c +int main(void) + +{ + stdio_init_all(); + do { + __wrap_puts("hello, world\r"); + } while( true ); +} +``` + +From this view, you can immediately see: +- The program loops forever (`do { } while (true)`) +- It calls `__wrap_puts()` with a string argument +- To change the output, you need to change what's passed to `puts()` + +##### Step 7: Viewing the String in the .rodata Section + +When you navigate to the string address `0x100019cc`, you'll see the string stored in the `.rodata` (read-only data) section: + +``` + // + // .rodata + // SHT_PROGBITS [0x100019cc - 0x10001b17] + // ram:100019cc-ram:10001b17 + // + __init_array_end XREF[5]: Entry Point (*) , + __boot2_start__ frame_dummy:10000218 (*) , + __boot2_end__ main:1000023a (*) , + __EH_FRAME_BEGIN__ runtime_init:1000138a (R) , + _elfSectionHeaders::0000005c (*) + 100019cc 68 65 6c ds "hello, world\r" + 6c 6f 2c + 20 77 6f +``` + +This shows the raw bytes of our string: `68 65 6c 6c 6f 2c 20 77 6f...` which spell out "hello, world\r" in ASCII. + +##### Step 8: Patching Data in Ghidra (Preview) + +Ghidra allows you to modify data directly in the binary! Here's how to patch the string: + +1. **Navigate to the string** at address `0x100019cc` +2. **Right-click** on the string `"hello, world\r"` in the Listing view +3. **Select** **Patch Data** from the context menu +4. **Type** your new string: `"hacky, world\r"` +5. **Press Enter** to apply the patch + +> **Important:** The new string must be the **same length or shorter** than the original! If your new string is longer, it will overwrite adjacent data and likely crash the program. + +| Original String | Patched String | Result | +| --------------- | -------------- | ------ | +| `hello, world\r` (14 bytes) | `hacky, world\r` (14 bytes) | Works perfectly | +| `hello, world\r` (14 bytes) | `PWNED!\r` (7 bytes) | Works (shorter is OK) | +| `hello, world\r` (14 bytes) | `this is a much longer string\r` | Overwrites other data! | + +After patching, you'll see the change reflected in the Listing view: + +``` + 100019cc 68 61 63 ds "hacky, world\r" + 6b 79 2c + 20 77 6f +``` + +Notice how the bytes changed: `68 65 6c 6c 6f` ("hello") became `68 61 63 6b 79` ("hacky")! + +#### Looking Ahead: Persistent Binary Patching + +> **Coming in Future Lessons:** What we've done in Ghidra so far is just a **preview** of the patch - it modifies the data in Ghidra's view, but doesn't save it back to the actual binary file. + +In future lessons, we will learn how to: + +1. **Export the patched binary** from Ghidra to create a modified `.elf` or `.bin` file +2. **Flash the patched binary** to the Pico 2, making the hack **persistent** + +The key difference: + +| Technique | Persistence | When It's Useful | +| --------- | ----------- | ---------------- | +| **GDB Live Hacking** (this week) | Temporary - lost on reset | Testing, debugging, quick exploitation | +| **Ghidra Patch Preview** (this step) | None - just visualization | Planning and verifying patches | +| **Binary Patching** (future lessons) | **Permanent** - survives reboot | Persistent backdoors, firmware mods | + +This step helps you understand the mechanics of modifying binary data. Once you're comfortable with this concept, you'll be ready to create truly persistent modifications! + +#### Comparing GDB and Ghidra Approaches + +| Task | GDB (Dynamic) | Ghidra (Static) | +| ---- | ------------- | --------------- | +| Find main address | `x/1000i 0x10000000` + search | Symbol Tree -> Functions -> main | +| Find string address | Step through `ldr`, examine `$r0` | Click on `ldr` - shows `= 100019CCh` | +| See string content | `x/s $r0` | Double-click address -> see `ds "hello, world"` | +| Identify attack point | Set breakpoints, step, observe | Read decompiled code, find function calls | +| Verify memory type | Know address ranges | Check address prefix (`0x10...` vs `0x20...`) | + +#### Why Use Both Tools? + +- **Ghidra** helps you **plan** the attack by understanding code structure +- **GDB** lets you **execute** the attack and modify live values +- Together, they form a complete reverse engineering workflow! + +#### Ghidra Tips for Attack Planning + +1. **Use the Decompiler** - It shows you the high-level logic without decoding assembly +2. **Follow Cross-References** - Find all places a function or variable is used +3. **Check Address Ranges** - Quickly identify Flash vs SRAM locations +4. **Add Comments** - Press `;` to annotate what you discover for later +5. **Rename Variables** - Right-click -> Rename to give meaningful names + +--- + +## Part 15: Summary and Review + +#### What We Accomplished + +We successfully performed a **live memory injection attack**: + +1. **Connected** to a running embedded system using OpenOCD and GDB +2. **Analyzed** the program flow to find the perfect attack point +3. **Set a breakpoint** right before the critical function call +4. **Discovered** that direct string assignment doesn't work without `malloc()` +5. **Wrote** our malicious data directly to SRAM +6. **Hijacked** the `r0` register to point to our data +7. **Executed** the hack and watched the output change! + +#### Week 1 Concepts We Applied + +| Week 1 Concept | How We Used It This Week | +| -------------- | ------------------------ | +| Memory Layout (Flash vs RAM) | We knew flash is read-only, so we wrote to SRAM | +| Registers (`r0`) | We hijacked `r0` to point to our malicious string | +| GDB `x` command | We examined memory and verified our injected string | +| GDB breakpoints (`b`) | We set a strategic breakpoint before `puts()` | +| Disassembly (`disas`) | We found the exact instruction to target | +| Little-endian | We understood how our string bytes are stored | + +#### The Attack Flow Diagram + +``` +BEFORE OUR HACK: ++-----------------+ +------------------------------+ +| r0 = 0x100019cc| ---> | Flash: "hello, world\r" | ++-----------------+ +------------------------------+ + | + ▼ + puts() prints "hello, world" + +AFTER OUR HACK: ++-----------------+ +------------------------------+ +| r0 = 0x20040000| ---> | SRAM: "hacky, world" | ++-----------------+ +------------------------------+ + | + ▼ + puts() prints "hacky, world" +``` + +#### New GDB Commands We Learned + +| Command | What It Does | New/Review | +| ---------------------------- | ----------------------------------- | ---------- | +| `target extended-remote :3333` | Connect to OpenOCD debug server | **New** | +| `monitor reset halt` | Reset and halt the processor | **New** | +| `disas` | Disassemble the current function | Review | +| `x/Ni ADDRESS` | Examine N instructions at ADDRESS | Review | +| `x/s ADDRESS` | Examine memory as a string | Review | +| `b *ADDRESS` | Set breakpoint at exact address | Review | +| `c` | Continue execution | Review | +| `set $r0 = VALUE` | Change a register's value | **New** | +| `set {char[N]} ADDR = {...}` | Write characters directly to memory | **New** | + +#### Key Memory Addresses + +| Address | What's There | Read/Write? | +| ------------ | -------------------------------- | ----------- | +| `0x10000234` | Start of `main()` function | Read-only | +| `0x1000023c` | The `bl __wrap_puts` call | Read-only | +| `0x100019cc` | Original `"hello, world"` string | Read-only | +| `0x20040000` | Safe SRAM location (hack target) | Read-Write | + +--- + +--- + +## Key Takeaways + +#### Building on Week 1 + +1. **Memory layout knowledge is power** - Understanding that flash is read-only and SRAM is read-write was essential for our hack. This directly built on Week 1's memory map lesson. + +2. **Registers control everything** - In Week 1, we watched registers change during execution. This week, we CHANGED them ourselves to alter program behavior. + +3. **GDB is a hacking tool** - The same commands we used for learning (`x`, `b`, `c`, `disas`) are the same commands used for exploitation. + +#### New Concepts + +4. **Flash is Read-Only at Runtime** - You can't modify code or constant strings in flash memory while the program runs. You must use SRAM. + +5. **Bare-Metal Means No Runtime** - Without an operating system, there's no `malloc()`, no dynamic memory allocation. You have to manage memory manually. + +6. **Registers Are the Key** - Function arguments are passed in registers (`r0`, `r1`, etc.). By changing these registers at the right moment, you can change what functions do. + +7. **Timing is Everything** - We had to set our breakpoint at exactly the right instruction. One instruction earlier, and `r0` wouldn't be loaded yet. One instruction later, and `puts()` would already have the wrong address. + +8. **This is Real Hacking** - The techniques you learned today are used by security researchers, penetration testers, and yes, attackers. Understanding these attacks helps us build more secure systems. + +--- + +## Security Implications + +#### How Would This Work in the Real World? + +Imagine an attacker with physical access to an industrial control system: + +| Scenario | Attack | +| ---------------------- | ----------------------------------------------------------------- | +| **Nuclear Centrifuge** | Change the displayed RPM from dangerous (15,000) to safe (10,000) | +| **Medical Device** | Modify dosage readings to hide an overdose | +| **Vehicle ECU** | Alter speedometer reading while car actually speeds | +| **Smart Lock** | Change the "locked" status to "unlocked" | + +#### How Do We Defend Against This? + +1. **Disable Debug Ports** - Production devices should have JTAG/SWD disabled +2. **Secure Boot** - Verify firmware hasn't been tampered with +3. **Memory Protection** - Use ARM's MPU to restrict memory access +4. **Tamper Detection** - Hardware that detects physical intrusion +5. **Encryption** - Keep sensitive data encrypted in memory + +--- + +## Glossary + +#### New Terms This Week + +| Term | Definition | +| ------------------------- | ---------------------------------------------------------------------- | +| **Bare-Metal** | Programming directly on hardware without an operating system | +| **CMSIS-DAP** | A standard debug interface protocol for ARM processors | +| **Hijack** | Taking control of a value or flow that was intended for something else | +| **Hardware Breakpoint** | A breakpoint implemented in CPU hardware, works on any memory | +| **Memory Injection** | Writing attacker-controlled data into a program's memory space | +| **OpenOCD** | Open On-Chip Debugger - software that interfaces with debug hardware | +| **Register Manipulation** | Changing the values stored in CPU registers | +| **SRAM** | Static Random Access Memory - fast, volatile, read-write memory | +| **Software Breakpoint** | A breakpoint implemented by modifying code (requires writable memory) | + +#### Review Terms from Week 1 + +| Term | Definition | Where We Used It | +| ------------------- | --------------------------------------------------------- | ---------------- | +| **Breakpoint** | A marker that pauses program execution at a specific location | Part 7 - Setting the trap | +| **Register** | Fast storage inside the processor | Part 11 - Hijacking `r0` | +| **Stack Pointer** | Register that points to the top of the stack | Part 2 - Memory layout | +| **XIP** | Execute In Place - running code directly from flash | Part 2 - Why we can't write to flash | +| **Little-Endian** | Storing the least significant byte at the lowest address | Part 10 - String storage | + + + diff --git a/WEEK02/slides/WEEK02-IMG00.svg b/WEEK02/slides/WEEK02-IMG00.svg new file mode 100644 index 0000000..44adb6f --- /dev/null +++ b/WEEK02/slides/WEEK02-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 02 + + +Hello, World - Debugging and +Hacking Basics: Debugging and Hacking +a Basic Program for the Pico 2 + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEK02/slides/WEEK02-IMG01.svg b/WEEK02/slides/WEEK02-IMG01.svg new file mode 100644 index 0000000..c48c43a --- /dev/null +++ b/WEEK02/slides/WEEK02-IMG01.svg @@ -0,0 +1,73 @@ + + + + + +Live Hacking Overview +Introduction to Live Hacking + + + + What Is Live Hacking? + + + Modify a program + WHILE it is running + on real hardware + + + + The Train Analogy + Train heading to NYC + Switch the tracks + while it moves + Now it goes to LA! + + + + Why It Matters + Security research + Penetration testing + Malware analysis + Hardware debugging + + No recompile needed! + + + + This Week's Goal + + + Target Program + hello-world.c + Prints "hello, world" + in infinite loop + + + + Our Mission + Make it print + something ELSE + without changing + the source code + + + + Tools Used + GDB = live debug + OpenOCD = HW bridge + Ghidra = analysis + + Hack the running binary + \ No newline at end of file diff --git a/WEEK02/slides/WEEK02-IMG02.svg b/WEEK02/slides/WEEK02-IMG02.svg new file mode 100644 index 0000000..718b3de --- /dev/null +++ b/WEEK02/slides/WEEK02-IMG02.svg @@ -0,0 +1,85 @@ + + + + + +GDB Debug Session +GDB Fundamentals + + + + Setup Steps + + + + Step 1: Start OpenOCD + + openocd -s <scripts> + -f interface/cmsis-dap.cfg + -f target/rp2350.cfg + -c "adapter speed 5000" + + + Step 2: Launch GDB + + arm-none-eabi-gdb + build\0x0001_hello-world.elf + + + Step 3: Connect to target + + target extended-remote :3333 + + + Step 4: Reset + halt + + monitor reset halt + + + Step 5: Set breakpoint + + break main + + Then: continue (c) + + + + What Each Does + + + openocd + Loads probe + chip + config files + Then listens on :3333 + + + + arm-none-eabi-gdb + ARM debugger from + the embedded toolchain + + + + target extended-remote + GDB connects to + OpenOCD server + + + + monitor reset halt + Reset chip + stop + at very first instr + Clean starting state + \ No newline at end of file diff --git a/WEEK02/slides/WEEK02-IMG03.svg b/WEEK02/slides/WEEK02-IMG03.svg new file mode 100644 index 0000000..3932205 --- /dev/null +++ b/WEEK02/slides/WEEK02-IMG03.svg @@ -0,0 +1,88 @@ + + + + + +Breakpoints +GDB Breakpoint Types + + + + How They Work + + + + Normal Execution + + + MOV r0, #5 + + + MOV r1, #3 + + + BL printf + + + With Breakpoint + + + MOV r0, #5 + + + MOV r1, #3 + STOP + + + BL printf + paused + + CPU halts BEFORE + executing breakpoint + instruction + + Now you can inspect + + + + GDB Breakpoints + + + break main + Stop at function + By symbol name + + + + break *0x10000340 + Stop at exact addr + By hex address + + + + info break + List all active + breakpoints + + + + continue (c) + Resume running + until next break + + + + delete 1 + Remove breakpoint #1 + \ No newline at end of file diff --git a/WEEK02/slides/WEEK02-IMG04.svg b/WEEK02/slides/WEEK02-IMG04.svg new file mode 100644 index 0000000..494fdda --- /dev/null +++ b/WEEK02/slides/WEEK02-IMG04.svg @@ -0,0 +1,102 @@ + + + + + +Stack in Action +Runtime Stack Analysis + + + + Before Call + + + 0x20082000 + SP here + + + empty (0x20081FFC) + + + empty (0x20081FF8) + + + free stack space + + + unused lower space + + 0x20080000 + + Grows DOWN + + + + + + After PUSH + + + 0x20082000 + PUSH {r4, lr} + + + saved LR + + + saved r4 + + + free stack space + + + free stack space + + SP now = 0x20081FF8 + + SP moved down + by 8 bytes + GDB: x/4xw $sp + saved regs are now visible + + + + Key Points + + + PUSH saves + Preserves regs + before function + body runs + + + + POP restores + Puts values + back when func + returns + + + + Watch in GDB + x/4xw $sp + See stack data + + + + stepi + Step 1 instr + watch stack + change live + \ No newline at end of file diff --git a/WEEK02/slides/WEEK02-IMG05.svg b/WEEK02/slides/WEEK02-IMG05.svg new file mode 100644 index 0000000..83ce46a --- /dev/null +++ b/WEEK02/slides/WEEK02-IMG05.svg @@ -0,0 +1,79 @@ + + + + + +LDR Instruction +ARM Load Instructions + + + + How LDR Works + + + + Instruction: + + LDR r0, [pc, #12] + + + Step 1: Calculate addr + + addr = PC + 12 + + + Step 2: Read memory + + value = *(addr) + + + Step 3: Load into reg + + r0 = value + + + r0 now holds the + address of our + "hello, world" string + + + + Why It Matters + + + String Loading + printf needs addr + of string in r0 + r0 = first argument + + + + PC-Relative + Address computed + relative to current + PC position + Works from any addr + + + + The Attack Point + If we change r0 + AFTER the LDR + printf prints OUR + string instead! + + + + This is the hack! + \ No newline at end of file diff --git a/WEEK02/slides/WEEK02-IMG06.svg b/WEEK02/slides/WEEK02-IMG06.svg new file mode 100644 index 0000000..52029b8 --- /dev/null +++ b/WEEK02/slides/WEEK02-IMG06.svg @@ -0,0 +1,93 @@ + + + + + +The Attack Plan +Exploit Strategy + + + + Attack Flow (4 Steps) + + + + + 1. Break at + printf call + + + + + + + + 2. Write new + string to SRAM + + + + + + + + 3. Set r0 to + SRAM addr + + + + + + + + 4. Continue + execution + + printf reads r0, prints "hacky, world"! + + + + Normal Flow + + + + LDR r0, ="hello" + + + BL printf + + Output: + "hello, world" + + Prints original string + + + + Hacked Flow + + + + LDR r0, ="hello" + + + r0 = 0x20040000 + + + BL printf + + Output: + "hacky, world" + + Prints our string + \ No newline at end of file diff --git a/WEEK02/slides/WEEK02-IMG07.svg b/WEEK02/slides/WEEK02-IMG07.svg new file mode 100644 index 0000000..984e441 --- /dev/null +++ b/WEEK02/slides/WEEK02-IMG07.svg @@ -0,0 +1,80 @@ + + + + + +Failed vs Real Hack +Attack Methodology + + + + Failed Attempt + + + The Bad Idea + Set r0 to point + at a string literal + like "hacky" + + + + Why It Fails + r0 only holds a + 32-bit number + Not a string itself! + + + + set $r0 = "HACK" + GDB interprets this + as an address value + pointing to garbage + + + + Result: CRASH + or prints garbage + + + + Real Hack + + + The Right Way + 1. Write string + bytes to SRAM + 2. Point r0 to + that SRAM addr + + + + GDB Commands + + + set {char[13]}0x20040000 + + + = "hacky, world" + + + set $r0 = 0x20040000 + + + + String exists in + writable SRAM + r0 points to it + + "hacky, world" printed! + \ No newline at end of file diff --git a/WEEK02/slides/WEEK02-IMG08.svg b/WEEK02/slides/WEEK02-IMG08.svg new file mode 100644 index 0000000..f9626eb --- /dev/null +++ b/WEEK02/slides/WEEK02-IMG08.svg @@ -0,0 +1,83 @@ + + + + + +Writing to SRAM +Memory Manipulation + + + + SRAM at 0x20040000 + + + + Before (empty) + + + 00 00 00 00 00 00 00 00 + + + 00 00 00 00 00 00 00 00 + + + After writing + + + 68 61 63 6b 79 2c 20 77 + + h a c k y , w + + + GDB Command: + + + set {char[13]} + 0x20040000 = "hacky, world" + + + Verify with: + + x/s 0x20040000 + + + + Why SRAM? + + + SRAM = writable + RAM at 0x20000000 + We can write any + data here via GDB + + + + Flash = read-only + XIP at 0x10000000 + Cannot write to it + during execution + That's why we use RAM + + + + Choosing Address + 0x20040000 is safe + Far from stack + and heap regions + + + + Null terminator + \0 ends the string + \ No newline at end of file diff --git a/WEEK02/slides/WEEK02-IMG09.svg b/WEEK02/slides/WEEK02-IMG09.svg new file mode 100644 index 0000000..4cf346e --- /dev/null +++ b/WEEK02/slides/WEEK02-IMG09.svg @@ -0,0 +1,77 @@ + + + + + +Register Hijack +Control Flow Attack + + + + Before Hijack + + + r0 loaded by LDR: + + + r0 = 0x10001234 + + Points to flash: + + + "hello, world\r\n" + + printf will read r0 + and print that string + + + + The Hijack Command + + + set $r0 = 0x20040000 + + Now r0 points to + OUR string in SRAM + instead of flash + + + + After Hijack + + + r0 now contains: + + + r0 = 0x20040000 + + Points to SRAM: + + + "hacky, world" + + + + Then: continue + printf reads r0 + Follows pointer + to 0x20040000 + Finds "hacky, world" + Prints it! + + + + Output changed + without touching code + \ No newline at end of file diff --git a/WEEK02/slides/WEEK02-IMG10.svg b/WEEK02/slides/WEEK02-IMG10.svg new file mode 100644 index 0000000..3582678 --- /dev/null +++ b/WEEK02/slides/WEEK02-IMG10.svg @@ -0,0 +1,75 @@ + + + + + +GDB vs Ghidra +Static vs Dynamic Analysis + + + + GDB (Dynamic) + + + Live analysis + Program is running + on real hardware + + + + Capabilities + Set breakpoints + Read/write memory + Modify registers + Step instructions + Watch values change + + + + Best For + Live modification + Runtime behavior + Testing exploits + Verifying attacks + + Needs running target + + + + Ghidra (Static) + + + Offline analysis + Just the binary file + No hardware needed + + + + Capabilities + Disassembly view + Decompile to C + Find functions + Cross-references + String search + + + + Best For + Planning attacks + Understanding code + Finding targets + Mapping functions + + Works with just ELF + \ No newline at end of file diff --git a/WEEK03/WEEK03-SLIDES.pdf b/WEEK03/WEEK03-SLIDES.pdf new file mode 100644 index 0000000..3d7e59a Binary files /dev/null and b/WEEK03/WEEK03-SLIDES.pdf differ diff --git a/WEEK03/WEEK03.md b/WEEK03/WEEK03.md new file mode 100644 index 0000000..e8a67c7 --- /dev/null +++ b/WEEK03/WEEK03.md @@ -0,0 +1,1545 @@ +# Week 3: Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis + +## What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand how the RP2350 boots from the on-chip bootrom +- Know what the vector table is and why it's important +- Trace the complete boot sequence from power-on to `main()` +- Understand XIP (Execute In Place) and how code runs from flash +- Read and analyze the startup assembly code (`crt0.S`) +- Use GDB to examine the boot process step by step +- Use Ghidra to statically analyze the boot sequence +- Understand the difference between Thumb mode addressing and actual addresses + +## Review from Weeks 1-2 +This week builds on your GDB and Ghidra skills from previous weeks: +- **GDB Commands** (`x`, `b`, `c`, `si`, `disas`, `i r`) - We'll use all of these to trace the boot process +- **Memory Layout** (Flash at `0x10000000`, RAM at `0x20000000`) - Understanding where code and data live +- **Registers** (`r0`-`r12`, SP, LR, PC) - We'll watch how they're initialized during boot +- **Ghidra Analysis** - Decompiling and understanding assembly in a visual tool +- **Thumb Mode** - Remember addresses with LSB=1 indicate Thumb code + +--- + +## The Code We're Analyzing + +Throughout this week, we'll continue working with our `0x0001_hello-world.c` program: + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + stdio_init_all(); + + while (true) + printf("hello, world\r\n"); +} +``` + +But this week, we're going **deeper** - we'll understand everything that happens BEFORE `main()` even runs! How does the chip know where `main()` is? How does the stack get initialized? Let's find out! + +--- + +## Part 1: Understanding the Boot Process + +### What Happens When You Power On? + +When you plug in your Raspberry Pi Pico 2, a lot happens before your `main()` function runs! Think of it like waking up in the morning: + +1. **First, your alarm goes off** (Power is applied to the chip) +2. **You open your eyes** (The bootrom starts running) +3. **You check your phone** (The bootrom looks for valid code in flash) +4. **You get out of bed** (The bootrom jumps to your program) +5. **You brush your teeth, get dressed** (Startup code initializes everything) +6. **Finally, you start your day** (Your `main()` function runs!) + +Each of these steps has a corresponding piece of code. Let's explore them all! + +### The RP2350 Boot Sequence Overview + +``` ++-----------------------------------------------------------------+ +| STEP 1: Power On | +| - The Cortex-M33 core wakes up | +| - Execution begins at address 0x00000000 (Bootrom) | ++-----------------------------------------------------------------+ + ↓ ++-----------------------------------------------------------------+ +| STEP 2: Bootrom Executes (32KB on-chip ROM) | +| - This code is burned into the chip - can't be changed! | +| - It looks for valid firmware in flash memory | +| - It scans the first 4 kB of the image for a valid IMAGE_DEF | +| (Datasheet §4.1, p. 338: 32KB ROM; §5.9.5, p. 429: IMAGE_DEF) | ++-----------------------------------------------------------------+ + ↓ ++-----------------------------------------------------------------+ +| STEP 3: Flash XIP Setup (bootrom-managed) | +| - The bootrom configures the flash interface automatically | +| - Sets up XIP (Execute In Place) mode | +| - NOTE: Unlike RP2040, there is NO separate boot2 in flash! | +| (Datasheet §5.2, p. 375: "removal of a boot2 in the first | +| 256 bytes of the image") | ++-----------------------------------------------------------------+ + ↓ ++-----------------------------------------------------------------+ +| STEP 4: Vector Table & Reset Handler | +| - Bootrom reads the vector table at 0x10000000 | +| - Gets the initial stack pointer from offset 0x00 | +| - Gets the reset handler address from offset 0x04 | +| - Jumps to the reset handler! | ++-----------------------------------------------------------------+ + ↓ ++-----------------------------------------------------------------+ +| STEP 5: C Runtime Startup (crt0.S) | +| - Copies initialized data from flash to RAM | +| - Zeros out the BSS section | +| - Calls runtime_init() | +| - Finally calls main()! | ++-----------------------------------------------------------------+ +``` + +--- + +## Part 2: The Bootrom - Where It All Begins + +### What is the Bootrom? + +The **bootrom** is a 32KB piece of code that is permanently burned into the RP2350 chip at the factory. You cannot change it - it's "mask ROM" (Read Only Memory). + +Think of the bootrom like the BIOS in your computer - it's the first thing that runs and is responsible for finding and loading your actual program. + +### Key Bootrom Facts + +| Property | Value | Description | +| ----------- | ------------- | ---------------------------------- | +| Size | 32 KB | Small but powerful | +| Location | `0x00000000` | The very first address in memory | +| Modifiable? | **NO** | Burned into silicon at the factory | +| Purpose | Boot the chip | Find and load your firmware | + +### What Does the Bootrom Do? + +1. **Initialize Hardware**: Sets up clocks, resets peripherals +2. **Check Boot Sources (Discovery)**: Scans configured boot sources (for this course: flash) to find a candidate firmware image region. +3. **Validate Firmware (Validation)**: Verifies that candidate by finding IMAGE_DEF start/end markers and parsing the block. +4. **Configure Flash**: Sets up the XIP interface +5. **Jump to Your Code**: Reads the vector table and jumps to your reset handler + +### The IMAGE_DEF Structure + +The bootrom looks for a special marker in your firmware called **IMAGE_DEF**. This tells the bootrom "Hey, there's valid code here!" + +Here's what it looks like in the Pico SDK: + +```assembly +.section .picobin_block, "a" // placed in flash +.word 0xffffded3 // PICOBIN_BLOCK_MARKER_START ↠ROM looks for this! +.byte 0x42 // PICOBIN_BLOCK_ITEM_1BS_IMAGE_TYPE +.byte 0x1 // item is 1 word in size +.hword 0b0001000000100001 // SECURE mode (0x1021) +.byte 0xff // PICOBIN_BLOCK_ITEM_2BS_LAST +.hword 0x0001 // item is 1 word in size +.byte 0x0 // pad +.word 0x0 // relative pointer to next block (0 = loop to self) +.word 0xab123579 // PICOBIN_BLOCK_MARKER_END +``` + +**The magic numbers:** +- `0xffffded3` = Start marker ("I'm a valid Pico binary!") +- `0xab123579` = End marker ("End of the header block") + +### See This Exact Block in Your ELF (Commands + Real Output) + +Use these commands to view the IMAGE_DEF bytes directly in the ELF: + +```cmd +arm-none-eabi-objdump -s --start-address=0x1000013c --stop-address=0x10000150 build/0x0001_hello-world.elf +arm-none-eabi-objdump -s --start-address=0x10000130 --stop-address=0x10000154 build/0x0001_hello-world.elf +arm-none-eabi-gdb build/0x0001_hello-world.elf -ex "x/20bx 0x1000013c" -ex quit +``` + +Actual output from this lesson build: + +```text +build/0x0001_hello-world.elf: file format elf32-littlearm + +Contents of section .text: + 1000013c 42012110 ff010000 b01b0000 793512ab B.!.........y5.. + 1000014c 4ff00000 O... + +build/0x0001_hello-world.elf: file format elf32-littlearm + +Contents of section .text: + 10000130 a0010010 90a31ae7 d3deffff 42012110 ............B.!. + 10000140 ff010000 b01b0000 793512ab 4ff00000 ........y5..O... + 10000150 1e490860 .I.` +``` + +Command 1 explained (`--start-address=0x1000013c --stop-address=0x10000150`): +- Starts at `0x1000013c`, so it does **not** include the start marker at `0x10000138` (`d3deffff`). +- Shows IMAGE_DEF body fields and the end marker: + - `42012110` = `42 01 21 10` (item type/size + secure mode field) + - `ff010000` = last-item marker + size + pad + - `b01b0000` = next word in the block payload for this build + - `793512ab` = `PICOBIN_BLOCK_MARKER_END` (`0xab123579` in little-endian) +- `4ff00000` at `0x1000014c` is already the next instruction word after IMAGE_DEF. + +Command 2 explained (`--start-address=0x10000130 --stop-address=0x10000154`): +- Starts earlier, so it captures context **and** both IMAGE_DEF markers. +- `a0010010 90a31ae7` = binary-info context before IMAGE_DEF. +- `d3deffff` at `0x10000138` = `PICOBIN_BLOCK_MARKER_START`. +- `793512ab` at `0x10000148` = `PICOBIN_BLOCK_MARKER_END`. +- `4ff00000 1e490860` = code words after the IMAGE_DEF block. +- This command proves the full block location for this build: `0x10000138` to `0x1000014b`. + +Important: IMAGE_DEF offset can vary by build. In this build, the start marker `d3deffff` +is at `0x10000138` (not `0x1000013c`), so always search for the marker bytes instead of +assuming a fixed address. + +--- + +## Part 3: Understanding XIP (Execute In Place) + +> **REVIEW:** In Week 1, we learned that our code lives at `0x10000000` in flash memory. We used `x/1000i 0x10000000` to find our `main` function. Now we'll understand WHY code is at this address! + +### What is XIP? + +**XIP (Execute In Place)** means the processor can run code directly from flash memory without copying it to RAM first. + +Think of it like reading a book: +- **Without XIP**: You photocopy every page into a notebook, then read from the notebook +- **With XIP**: You just read directly from the book! + +### Why Use XIP? + +| Advantage | Explanation | +| ----------- | ------------------------------------------- | +| Saves RAM | Code stays in flash, RAM is free for data | +| Faster Boot | No need to copy entire program to RAM first | +| Simpler | Less memory management needed | + +### XIP Memory Address + +The XIP flash region starts at address `0x10000000`. This is where your compiled code lives! + +``` ++-----------------------------------------------------+ +| Address: 0x10000000 (XIP Base) | +| +-------------------------------------------------+| +| | Vector Table (first thing here!) || +| | - Stack Pointer at offset 0x00 || +| | - Reset Handler at offset 0x04 || +| | - Other exception handlers... || +| +-------------------------------------------------+| +| | Your Code || +| | - Reset handler || +| | - main() function || +| | - Other functions || +| +-------------------------------------------------+| +| | Read-Only Data || +| | - Strings like "hello, world" || +| | - Constant values || +| +-------------------------------------------------+| ++-----------------------------------------------------+ +``` + +--- + +## Part 4: The Vector Table - The CPU's Instruction Manual + +### What is the Vector Table? + +The **vector table** is a list of addresses at the very beginning of your program. It tells the CPU: +1. Where to set the stack pointer +2. Where to start executing code (reset handler) +3. Where to go when errors or interrupts happen + +Think of it like the table of contents in a book - it tells you where to find everything! + +### Vector Table Layout + +The vector table lives at `0x10000000` and looks like this: + +| Offset | Address | Content | Description | +| ------ | ------------ | ------------ | --------------------------- | +| `0x00` | `0x10000000` | `0x20082000` | Initial Stack Pointer (SP) | +| `0x04` | `0x10000004` | `0x1000015d` | Reset Handler (entry point) | +| `0x08` | `0x10000008` | `0x1000011b` | NMI Handler | +| `0x0C` | `0x1000000C` | `0x1000011d` | HardFault Handler | + +### Understanding Thumb Mode Addressing + +**Important Concept Alert!** + +Look at the reset handler address: `0x1000015d`. Notice it ends in `d` (an odd number)? + +On ARM Cortex-M processors, all code runs in **Thumb mode**. The processor uses the **least significant bit (LSB)** of an address to indicate this: + +| LSB | Mode | Meaning | +| ---------- | ----- | ----------------------------------------- | +| `1` (odd) | Thumb | "This is Thumb code" | +| `0` (even) | ARM | "This is ARM code" (not used on Cortex-M) | + +So `0x1000015d` means: +- The actual code is at `0x1000015c` (even address) +- The `+1` tells the processor "use Thumb mode" + +**GDB vs Ghidra:** +- GDB shows `0x1000015d` (with Thumb bit) +- Ghidra shows `0x1000015c` (actual instruction address) +- Both are correct! They're just displaying it differently. + +--- + +## Part 5: The Linker Script - Memory Mapping + +### What is a Linker Script? + +The **linker script** tells the compiler where to put different parts of your program in memory. It's like an architect's blueprint for memory! + +### Finding the Linker Script + +On Windows with the Pico SDK 2.2.0, you'll find it at: +``` +C:\Users\\.pico-sdk\sdk\2.2.0\src\rp2_common\pico_crt0\rp2350\memmap_default.ld +``` + +### Key Parts of the Linker Script + +```ld +MEMORY +{ + INCLUDE "pico_flash_region.ld" + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 512k + SCRATCH_X(rwx) : ORIGIN = 0x20080000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20081000, LENGTH = 4k +} +``` + +**What this means:** + +| Region | Start Address | Size | Purpose | +| --------- | ------------- | -------- | --------------------- | +| Flash | `0x10000000` | (varies) | Your code (XIP) | +| RAM | `0x20000000` | 512 KB | Main RAM | +| SCRATCH_X | `0x20080000` | 4 KB | Core 0 scratch memory | +| SCRATCH_Y | `0x20081000` | 4 KB | Core 0 stack | + +### Where Does the Stack Come From? + +The linker script calculates the initial stack pointer: + +```ld +__StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); +``` + +Let's do the math: +- `ORIGIN(SCRATCH_Y)` = `0x20081000` +- `LENGTH(SCRATCH_Y)` = `0x1000` (4 KB) +- `__StackTop` = `0x20081000` + `0x1000` = **`0x20082000`** + +This value (`0x20082000`) is what we see at offset `0x00` in the vector table! + +--- + +## Part 6: Setting Up Your Environment (GDB - Dynamic Analysis) + +> **REVIEW:** This setup is identical to Weeks 1-2. If you need a refresher on OpenOCD and GDB connection, refer back to Week 1 Part 4 or Week 2 Part 5. + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board with debug probe connected +2. OpenOCD installed and configured +3. GDB (`arm-none-eabi-gdb`) installed +4. The "hello-world" binary loaded on your Pico 2 +5. Access to the Pico SDK source files (for reference) + +### Starting the Debug Session + +**Terminal 1 - Start OpenOCD:** + +```powershell +openocd -s "$env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev\scripts" -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000" +``` + +**Terminal 2 - Start GDB:** + +```cmd +arm-none-eabi-gdb build\0x0001_hello-world.elf +``` + +**Connect to target:** + +```gdb +(gdb) target extended-remote :3333 +(gdb) monitor reset halt +``` + +--- + +## Part 7: Hands-On GDB Tutorial - Examining the Vector Table + +> **REVIEW:** We're using the same `x` (examine) command from Week 1. Remember: `x/Nx` shows N hex values, `x/Ni` shows N instructions, `x/s` shows strings. + +### Step 1: Examine the Vector Table + +Let's look at the first 4 entries of the vector table at `0x10000000`: + +**Type this command:** + +```gdb +(gdb) x/4x 0x10000000 +``` + +**What this command means:** +- `x` = examine memory (Week 1 review!) +- `/4x` = show 4 values in hexadecimal +- `0x10000000` = the address of the vector table + +**You should see:** + +``` +0x10000000 <__vectors>: 0x20082000 0x1000015d 0x1000011b 0x1000011d +``` + +### Step 2: Understanding What We See + +> **REVIEW:** In Weeks 1-2, we saw both `sp = 0x20082000` at the clean breakpoint at `main` and lower values like `0x20081fc8` or `0x20081ff8` after additional stack activity. Here we are looking at the *initial* stack pointer from the vector table before any code runs. + +Let's decode each value: + +| Address | Value | Meaning | +| ------------ | ------------ | ---------------------------------------- | +| `0x10000000` | `0x20082000` | Initial Stack Pointer - top of SCRATCH_Y | +| `0x10000004` | `0x1000015d` | Reset Handler + 1 (Thumb bit) | +| `0x10000008` | `0x1000011b` | NMI Handler + 1 (Thumb bit) | +| `0x1000000C` | `0x1000011d` | HardFault Handler + 1 (Thumb bit) | + +**Key Insight:** The stack pointer (`0x20082000`) is exactly what the linker script calculated! And all the handler addresses have their LSB set to `1` for Thumb mode. + +### Step 3: Verify the Stack Pointer Calculation + +Let's confirm our math by examining what's at `0x10000000`: + +**Type this command:** + +```gdb +(gdb) x/x 0x10000000 +``` + +**You should see:** + +``` +0x10000000 <__vectors>: 0x20082000 +``` + +This matches: +- `SCRATCH_Y` starts at `0x20081000` +- `SCRATCH_Y` is 4 KB (`0x1000` bytes) +- `0x20081000` + `0x1000` = `0x20082000` + +--- + +## Part 8: Examining the Reset Handler + +> **REVIEW:** We used `x/5i` extensively in Weeks 1-2 to examine our `main` function. Now we'll use the same technique to examine the code that runs BEFORE `main`! + +### Step 4: Disassemble the Reset Handler + +The reset handler is where execution begins after the bootrom hands off control. Let's look at it: + +**Type this command:** + +```gdb +(gdb) x/3i 0x1000015c +``` + +**Note:** We use `0x1000015c` (even) not `0x1000015d` (odd) because we want to see the actual instructions! + +**You should see:** + +``` + 0x1000015c <_reset_handler>: mov.w r0, #3489660928 @ 0xd0000000 + 0x10000160 <_reset_handler+4>: ldr r0, [r0, #0] + 0x10000162 <_reset_handler+6>: + cbz r0, 0x1000016a +``` + +### Step 5: Understanding the Reset Handler + +Let's break down what these first three instructions do: + +**Instruction 1: `mov.w r0, #0xd0000000`** + +This loads the address `0xd0000000` into register `r0`. But what's at that address? + +That's the **SIO (Single-cycle I/O) base address**! The SIO block contains a special register called **CPUID** that tells us which core we're running on. + +**Instruction 2: `ldr r0, [r0, #0]`** + +This reads the value at address `0xd0000000` (the CPUID register) into `r0`. + +| Core | CPUID Value | +| ------ | ----------- | +| Core 0 | `0` | +| Core 1 | `1` | + +**Instruction 3: `cbz r0, 0x1000016a`** + +This is "Compare and Branch if Zero". If `r0` is `0` (meaning we're on Core 0), branch to `0x1000016a` to continue with startup. Otherwise, we're on Core 1 and need to handle that differently. + +### Why Check Which Core We're On? + +The RP2350 has **two cores**, but only **Core 0** should run the startup code! If both cores tried to initialize the same memory and peripherals, chaos would ensue. + +So the reset handler checks: +- **Core 0?** -> Continue with startup +- **Core 1?** -> Go back to the bootrom and wait + +--- + +## Part 9: The Complete Reset Handler Flow + +### Step 6: Examine More of the Reset Handler + +Let's look at more instructions to see the full picture: + +**Type this command:** + +```gdb +(gdb) x/20i 0x1000015c +``` + +**You should see:** + +``` + 0x1000015c <_reset_handler>: mov.w r0, #3489660928 @ 0xd0000000 + 0x10000160 <_reset_handler+4>: ldr r0, [r0, #0] + 0x10000162 <_reset_handler+6>: + cbz r0, 0x1000016a + 0x10000164 : mov.w r0, #0 + 0x10000168 : + b.n 0x10000150 <_enter_vtable_in_r0> + 0x1000016a : + add r4, pc, #52 @ (adr r4, 0x100001a0 ) + 0x1000016c : ldmia r4!, {r1, r2, r3} + 0x1000016e : cmp r1, #0 + 0x10000170 : + beq.n 0x10000178 + 0x10000172 : + bl 0x1000019a + 0x10000176 : + b.n 0x1000016c + 0x10000178 : + ldr r1, [pc, #84] @ (0x100001d0 ) + 0x1000017a : + ldr r2, [pc, #88] @ (0x100001d4 ) + 0x1000017c : movs r0, #0 + 0x1000017e : + b.n 0x10000182 + 0x10000180 : stmia r1!, {r0} + 0x10000182 : cmp r1, r2 + 0x10000184 : bne.n 0x10000180 + 0x10000186 : + ldr r1, [pc, #80] @ (0x100001d8 ) + 0x10000188 : blx r1 +``` + +### Step 7: Understanding the Startup Phases + +The reset handler performs several phases: + +``` ++-----------------------------------------------------------------+ +| PHASE 1: Core Check (0x1000015c - 0x10000168) | +| - Check CPUID to see which core we're on | +| - If not Core 0, go back to bootrom | ++-----------------------------------------------------------------+ + ↓ ++-----------------------------------------------------------------+ +| PHASE 2: Data Copy Setup & Loop (0x1000016a - 0x10000176) | +| - Set up the data_cpy_table pointer and load each copy triplet | +| - Copy initialized variables from flash to RAM | ++-----------------------------------------------------------------+ + ↓ ++-----------------------------------------------------------------+ +| PHASE 3: BSS Setup & Clear (0x10000178 - 0x10000184) | +| - Load the BSS start/end addresses into r1 and r2 | +| - GDB labels those literals as `data_cpy_table+48/+52` | +| - Zero out all uninitialized global variables | ++-----------------------------------------------------------------+ + ↓ ++-----------------------------------------------------------------+ +| PHASE 4: Platform Entry Begins (0x10000186 - 0x10000188 shown) | +| - Load the runtime_init() pointer from the table | +| - Branch to runtime_init() with `blx r1` | +| - `main()` and `exit()` appear a few instructions later | ++-----------------------------------------------------------------+ +``` + +--- + +## Part 10: Understanding the Data Copy Phase + +### What is the Data Copy Phase? + +> **REVIEW:** In Week 2, we learned that flash is read-only and SRAM is read-write. That's why the startup code must COPY initialized variables from flash to RAM - they can't be modified in flash! + +When you write C code like this: + +```c +int my_counter = 42; // Initialized global variable +``` + +The value `42` is stored in flash memory (because flash is non-volatile). But variables need to live in RAM to be modified! So the startup code **copies** these initial values from flash to RAM. + +### Step 8: Find the Data Copy Table + +The data copy table contains entries that describe what to copy where. Let's examine it: + +**Type this command:** + +```gdb +(gdb) x/12x 0x100001a0 +``` + +**You should see something like:** + +``` +0x100001a0 : 0x10001b4c 0x20000110 0x200002ac 0x10001ce8 +0x100001b0 : 0x20080000 0x20080000 0x10001ce8 0x20081000 +0x100001c0 : 0x20081000 0x00000000 0x00004770 0xe000ed08 +``` + +The data_cpy_table contains multiple entries. Each entry has three values: +1. **Source address** (in flash) +2. **Destination address** (in RAM) +3. **End address** (where to stop copying) + +In the output above, we see: +- **First entry**: `0x10001b4c` (source), `0x20000110` (dest), `0x200002ac` (end) +- **Second entry starts**: `0x10001ce8` (source of next entry), ... + +The table ends with an entry where the source address is `0x00000000` (which signals "no more entries"). + +### Step 9: Watch the Data Copy Loop + +The data copy loop works like this: + +``` ++---------------------------------------------+ +| 1. Load source, dest, end from table | +| 2. If source == 0, we're done | +| 3. Otherwise, copy word by word | +| 4. Go back to step 1 for next entry | ++---------------------------------------------+ +``` + +The actual code (starting at **`0x1000016c`** in the reset handler): + +```assembly +0x1000016c : ldmia r4!, {r1, r2, r3} +0x1000016e : cmp r1, #0 +0x10000170 : +beq.n 0x10000178 +0x10000172 : +bl 0x1000019a +0x10000176 : +b.n 0x1000016c +``` + +> Tip: **Note:** You can see this code in **Step 6** earlier where we examined the reset handler with `x/20i 0x1000015c`. + +--- + +## Part 11: Understanding the BSS Clear Phase + +### What is BSS? + +**BSS** stands for "Block Started by Symbol" (historical name). It's the section of memory for **uninitialized global variables**. + +When you write: + +```c +int my_counter; // Uninitialized - will be in BSS +``` + +The C standard says this variable **must start at zero**. The BSS clear phase zeros out this entire region. + +### Step 10: Examine the BSS Clear Loop + +**Type this command:** + +```gdb +(gdb) x/5i 0x10000178 +``` + +**You should see:** + +``` +0x10000178 : +ldr r1, [pc, #84] @ (0x100001d0 ) +0x1000017a : +ldr r2, [pc, #88] @ (0x100001d4 ) +0x1000017c : movs r0, #0 +0x1000017e : +b.n 0x10000182 +0x10000180 : stmia r1!, {r0} +``` + +The first two `ldr` instructions are still part of the **BSS clear setup**, even though GDB shows the source words as `data_cpy_table+48` and `data_cpy_table+52`. That label means the two literal words live in the same nearby constant block as the copy-table entries; it does **not** mean the code is still performing `.data` copies. At this point, `r1` becomes the BSS start address, `r2` becomes the BSS end address, and the loop beginning at `0x10000180` zeros that range. + +### Understanding the Loop + +``` ++---------------------------------------------+ +| r1 = start of BSS section | +| r2 = end of BSS section | +| r0 = 0 | +| | +| LOOP: | +| Store 0 at address r1 | +| Increment r1 by 4 bytes | +| If r1 != r2, repeat | ++---------------------------------------------+ +``` + +--- + +## Part 12: Examining Exception Handlers + +### Step 11: Look at the Default Exception Handlers + +What happens if an exception occurs (like a HardFault)? Let's look: + +**Type this command:** + +```gdb +(gdb) x/10i 0x10000110 +``` + +**You should see:** + +``` +0x10000110 : mrs r0, IPSR +0x10000114 : subs r0, #16 +0x10000116 : bkpt 0x0000 +0x10000118 : bkpt 0x0000 +0x1000011a : bkpt 0x0000 +0x1000011c : bkpt 0x0000 +0x1000011e : bkpt 0x0000 +0x10000120 : bkpt 0x0000 +0x10000122 : bkpt 0x0000 +0x10000124 <__default_isrs_end>: + @ instruction: 0xebf27188 +``` + +### What is `bkpt`? + +The `bkpt` instruction is a **breakpoint**. When executed, it stops the processor and triggers the debugger! + +These are the **default** exception handlers - they just stop the program so you can debug. In your own code, you can override these with real handlers. + +### Why So Many Handlers? + +Each type of exception has its own handler: + +| Handler | Purpose | +| --------------- | ------------------------------------------ | +| `isr_nmi` | Non-Maskable Interrupt (can't be disabled) | +| `isr_hardfault` | Serious error (bad memory access, etc.) | +| `isr_svcall` | Supervisor Call (used by RTOSes) | +| `isr_pendsv` | Pendable Supervisor (also for RTOSes) | +| `isr_systick` | System Timer tick interrupt | + +--- + +## Part 13: Finding Where Main is Called + +### Step 12: Look at Platform Entry + +After all the setup, the code finally calls `main()`. Let's find it: + +**Type this command:** + +```gdb +(gdb) x/10i 0x10000186 +``` + +**You should see:** + +``` +0x10000186 : +ldr r1, [pc, #80] @ (0x100001d8 ) +0x10000188 : blx r1 +0x1000018a : +ldr r1, [pc, #80] @ (0x100001dc ) +0x1000018c : blx r1 +0x1000018e : +ldr r1, [pc, #80] @ (0x100001e0 ) +0x10000190 : blx r1 +0x10000192 : bkpt 0x0000 +0x10000194 : +b.n 0x10000192 +0x10000196 : ldmia r1!, {r0} +0x10000198 : stmia r2!, {r0} +``` + +### Understanding Platform Entry + +The platform entry code makes **three function calls** using `ldr` + `blx`: + +1. **First call**: `runtime_init()` - SDK initialization +2. **Second call**: `main()` - YOUR CODE! +3. **Third call**: `exit()` - Called when main returns + +After `main()` returns, `exit()` is called to handle cleanup. The `bkpt` instruction after `exit()` should never be reached - it's there to catch errors if `exit()` somehow returns. + +### Step 13: Set a Breakpoint at Main + +> **REVIEW:** We've used `b main` and `b *ADDRESS` many times in Weeks 1-2. This is the same technique! + +Let's verify we understand the boot process by setting a breakpoint at main: + +**Type this command:** + +```gdb +(gdb) b main +``` + +**You should see:** + +``` +Breakpoint 1 at 0x10000234: file C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0001_hello-world/0x0001_hello-world.c, line 5. +Note: automatically using hardware breakpoints for read-only addresses. +``` + +**Now continue:** + +```gdb +(gdb) c +``` + +**You should see:** + +``` +Continuing. + +Thread 1 "rp2350.cm0" hit Breakpoint 1, main () + at C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0001_hello-world/0x0001_hello-world.c:5 +5 stdio_init_all(); +(gdb) +``` + + We've traced the entire boot process from power-on to `main()`! + +--- + +## Part 14: Understanding the Binary Info Header + +### Step 14: Examine the Binary Info Header + +Between the default ISRs and the reset handler, there's a special data structure called the **binary info header**. Let's look at it: + +**Type this command:** + +```gdb +(gdb) x/5x 0x10000138 +``` + +**You should see:** + +``` +0x10000138 <__binary_info_header_end>: 0xffffded3 0x10210142 0x000001ff 0x00001bb0 +0x10000148 <__binary_info_header_end+16>: 0xab123579 +``` + +### Decoding the Binary Info Header + +| Address | Value | Meaning | +| ------------ | ------------ | ----------------------------------------- | +| `0x10000138` | `0xffffded3` | Start marker (PICOBIN_BLOCK_MARKER_START) | +| `0x1000013c` | `0x10212142` | Image type descriptor | +| `0x10000140` | `0x000001ff` | Item header/size field | +| `0x10000144` | `0x00001bb0` | Link to next block or data | +| `0x10000148` | `0xab123579` | End marker (PICOBIN_BLOCK_MARKER_END) | + +**Why does GDB show this as instructions?** + +GDB doesn't know this is data, not code! It tries to disassemble it as Thumb instructions, which results in nonsense. This is why you'll see things like: + +```gdb +(gdb) x/i 0x10000138 +``` + +``` + 0x10000138 <__binary_info_header_end>: udf #211 @ 0xd3 +``` + +That's not real code - it's the magic number `0xffffded3` being misinterpreted! + +--- + +## Part 15: Static Analysis with Ghidra - Examining the Boot Sequence + +> **REVIEW:** In Week 1, we set up a Ghidra project and analyzed our hello-world binary. Now we'll use Ghidra to understand the boot sequence from a static analysis perspective! + +### Why Use Ghidra for Boot Analysis? + +While GDB is excellent for dynamic analysis (watching code execute), Ghidra excels at: +- **Seeing the big picture** - Understanding code flow without running it +- **Cross-references** - Finding all places that call a function +- **Decompilation** - Seeing C-like code even for assembly routines +- **Annotation** - Adding notes and renaming functions for clarity + +### Step 15: Open Your Project in Ghidra + +> **REVIEW:** If you haven't created the project yet, refer back to Week 1 Part 5 for setup instructions. + +1. Launch Ghidra and open your `0x0001_hello-world` project +2. Double-click on the `.elf` file to open it in the CodeBrowser +3. If prompted to auto-analyze, click **Yes** + +### Step 16: Navigate to the Vector Table + +1. In the **Navigation** menu, select **Go To...** +2. Type `0x10000000` and press Enter +3. You should see the vector table data + +**What you'll see in the Listing view:** + +``` + // + // .text + // SHT_PROGBITS [0x10000000 - 0x100019cb] + // ram:10000000-ram:100019cb + // + assume spsr = 0x0 (Default) + __vectors XREF[4]: Entry Point (*) , + __flash_binary_start runtime_init_install_ram_vector_ + __VECTOR_TABLE _elfProgramHeaders::00000028 (*) , + __logical_binary_start _elfSectionHeaders::00000034 (*) + 10000000 00 undefine 00h + 10000001 20 ?? 20h + 10000002 08 ?? 08h + 10000003 20 ?? 20h + 10000004 5d ?? 5Dh ] ? -> 1000015d + 10000005 01 ?? 01h + 10000006 00 ?? 00h + 10000007 10 ?? 10h + 10000008 1b ?? 1Bh ? -> 1000011b + 10000009 01 ?? 01h + 1000000a 00 ?? 00h + 1000000b 10 ?? 10h + 1000000c 1d ?? 1Dh ? -> 1000011d + 1000000d 01 ?? 01h + 1000000e 00 ?? 00h + 1000000f 10 ?? 10h +... +``` + +> Tip: **Notice:** Ghidra shows the vector table data as individual bytes by default. You can see it has labeled the start as `__vectors`, `__flash_binary_start`, `__VECTOR_TABLE`, and `__logical_binary_start`. The arrows (like `? -> 1000015d`) show that Ghidra recognizes these bytes as pointers to code addresses! To see the data formatted as 32-bit addresses instead of bytes, you can right-click and retype the data. + +### Step 17: Navigate to the Reset Handler + +1. In the Symbol Tree panel (left side), expand **Functions** +2. Find and click on `_reset_handler` (or search for it) +3. Alternatively, double-click on `_reset_handler` in the vector table listing + +**What you'll see in the Decompile view (right panel):** + +Ghidra will show you a decompiled version of the reset handler. While it won't be perfect C code (since this is hand-written assembly), it helps visualize the flow: + +```c +void _reset_handler(void) + +{ + bool bVar1; + undefined4 uVar2; + int iVar3; + undefined4 *puVar4; + int *piVar5; + int *piVar6; + int *piVar7; + + if (_DAT_d0000000 != 0) { + _DAT_e000ed08 = 0; + bVar1 = (bool)isCurrentModePrivileged(); + if (bVar1) { + setMainStackPointer(_gpio_set_function_masked64); + } + /* WARNING: Could not recover jumptable at 0x1000015a. Too many branches */ + /* WARNING: Treating indirect jump as call */ + (*pcRam00000004)(8,_gpio_set_function_masked64); + return; + } + piVar5 = &data_cpy_table; + uVar2 = 0; + while( true ) { + iVar3 = *piVar5; + piVar6 = piVar5 + 1; + piVar7 = piVar5 + 2; + piVar5 = piVar5 + 3; + if (iVar3 == 0) break; + uVar2 = data_cpy(uVar2,iVar3,*piVar6,*piVar7); + } + for (puVar4 = (undefined4 *)&__TMC_END__; puVar4 != (undefined4 *)&end; puVar4 = puVar4 + 1) { + *puVar4 = 0; + } + runtime_init(); + iVar3 = main(); + /* WARNING: Subroutine does not return */ + exit(iVar3); +} +``` + +### Step 18: Trace the Path to Main + +Let's find how the boot code eventually calls `main()`: + +1. In the Symbol Tree, find the `main` function +2. Right-click on `main` and select **References -> Show References to main** +3. This shows everywhere `main` is called from! + +**You should see:** + +| Location | Type | Label | +| ------------------------- | ---- | ------------------ | +| `1000018c` | CALL | `blx r1` (to main) | + +4. Double-click on the reference to jump to `1000018c` + +### Step 19: Examine Platform Entry + +In Ghidra, look at `platform_entry`: + +**Listing View:** +``` + platform_entry + crt0.S:512 (2) + 10000186 14 49 ldr r1,[DAT_100001d8 ] = 1000137Dh + crt0.S:513 (2) + 10000188 88 47 blx r1=>runtime_init void runtime_init(void) + crt0.S:514 (2) + 1000018a 14 49 ldr r1,[DAT_100001dc ] = 10000235h + crt0.S:515 (2) + 1000018c 88 47 blx r1=>main int main(void) + crt0.S:516 (2) + 1000018e 14 49 ldr r1,[DAT_100001e0 ] = 10001375h + crt0.S:517 (2) + 10000190 88 47 blx r1=>exit void exit(int status) + LAB_10000192 XREF[1]: 10000194 (j) + crt0.S:521 (2) + 10000192 00 be bkpt 0x0 + crt0.S:522 (2) + 10000194 fd e7 b LAB_10000192 +``` + +> **Key Insight:** Ghidra's decompiler makes the boot sequence crystal clear! You can see exactly what functions are called before `main()`. + +### Step 20: Create a Boot Sequence Graph + +Ghidra can visualize the call flow: + +1. With `_reset_handler` selected, go to **Window -> Function Call Graph** +2. This shows a visual graph of all function calls from the reset handler +3. You will see `_reset_handler` at the top with arrows going down to its four direct callees: `data_cpy`, `runtime_init`, `main`, and `exit` + +### Comparing GDB and Ghidra for Boot Analysis + +| Aspect | GDB (Dynamic) | Ghidra (Static) | +| ------ | ------------- | --------------- | +| **Sees runtime values** | Yes - register contents, memory | No - must infer from code | +| **Needs hardware** | Yes - Pico 2 must be connected | No - works offline | +| **Shows code flow** | Step-by-step execution | Full graph visualization | +| **Best for** | Watching what happens | Understanding structure | +| **Thumb bit handling** | Shows with +1 (0x1000015d) | Shows actual addr (0x1000015c) | + +### Ghidra Tips for Boot Analysis + +1. **Rename functions** - Right-click and rename unclear labels for future reference +2. **Add comments** - Press `;` to add inline comments explaining code +3. **Set data types** - Help Ghidra understand structures like the vector table +4. **Use bookmarks** - Mark important locations with **Ctrl+D** + +--- + +## Part 16: Summary and Review + +### The Complete Boot Sequence + +``` ++-----------------------------------------------------------------+ +| 1. POWER ON | +| Cortex-M33 begins at 0x00000000 (bootrom) | ++-----------------------------------------------------------------+ +| 2. BOOTROM | +| - Initializes hardware | +| - Configures flash XIP (no separate boot2 on RP2350) | +| - Finds IMAGE_DEF within first 4 kB of flash image | ++-----------------------------------------------------------------+ +| 3. VECTOR TABLE (0x10000000) | +| - Reads SP from offset 0x00 -> 0x20082000 | +| - Reads Reset Handler from offset 0x04 -> 0x1000015d | ++-----------------------------------------------------------------+ +| 4. RESET HANDLER (0x1000015c) | +| - Checks CPUID (Core 0 continues, Core 1 waits) | +| - Copies .data from flash to RAM | +| - Zeros .bss section | ++-----------------------------------------------------------------+ +| 5. PLATFORM ENTRY (0x10000186) | +| - Calls runtime_init() | +| - Calls main() | +| - Calls exit() when main returns | ++-----------------------------------------------------------------+ +| 6. YOUR CODE RUNS! | +| main() at 0x10000234 | ++-----------------------------------------------------------------+ +``` + +### Key Addresses to Remember + +| Address | What's There | +| ------------ | ---------------------------------------- | +| `0x00000000` | Bootrom (32KB, read-only) | +| `0x10000000` | Vector table / XIP flash start | +| `0x1000015c` | Reset handler (`_reset_handler`) | +| `0x10000234` | Your `main()` function | +| `0x20000000` | Start of RAM | +| `0x20082000` | Initial stack pointer (top of SCRATCH_Y) | +| `0xd0000000` | SIO base (CPUID register) | + +### Weeks 1-2 Concepts We Applied + +| Previous Concept | How We Used It This Week | +| ---------------- | ------------------------ | +| Memory Layout (Flash/RAM) | Understood why data must be copied from flash to RAM | +| GDB `x` command | Examined vector table, reset handler, and boot code | +| Breakpoints (`b`) | Set breakpoints to trace the boot sequence | +| Thumb Mode Addresses | Recognized LSB=1 means Thumb code in vector table | +| Stack Pointer | Saw how SP is initialized from the vector table | +| Ghidra Analysis | Used decompiler to understand boot flow | + +### GDB Commands Reference + +| Command | What It Does | New/Review | +| ---------------- | --------------------------------- | ---------- | +| `x/Nx ADDRESS` | Examine N hex values at ADDRESS | Review | +| `x/Ni ADDRESS` | Examine N instructions at ADDRESS | Review | +| `b main` | Set breakpoint at main function | Review | +| `b *ADDRESS` | Set breakpoint at exact address | Review | +| `si` | Step one instruction | Review | +| `c` | Continue execution | Review | +| `info registers` | Show all register values | Review | +| `monitor reset halt` | Reset and halt the target | Review | + +### Key Concepts + +| Concept | Definition | +| ---------------- | ----------------------------------------------------- | +| **Bootrom** | 32KB factory-programmed ROM that initializes the chip | +| **Vector Table** | List of addresses for SP and exception handlers | +| **XIP** | Execute In Place - running code directly from flash | +| **Thumb Mode** | ARM's compact instruction set (LSB=1 in addresses) | +| **BSS** | Section for uninitialized globals (must be zeroed) | +| **crt0.S** | C Runtime startup assembly file | +| **Reset Handler**| First function called after power-on/reset | +| **CPUID** | Register identifying which CPU core is executing | + +### Ghidra Actions We Used + +| Action | How to Access | Purpose | +| ------ | ------------- | ------- | +| Go To Address | Navigation -> Go To... | Jump to specific memory address | +| Show References | Right-click -> References -> Show References to | Find all callers of a function | +| Function Call Graph | Window -> Function Call Graph | Visualize call flow | +| Add Comment | Press `;` | Document your analysis | +| Rename Symbol | Right-click -> Rename | Give meaningful names to functions | + +--- + +--- + +## Key Takeaways + +### Building on Weeks 1-2 + +1. **GDB skills compound** - The `x`, `b`, `si`, and `disas` commands you learned in Weeks 1-2 are essential for understanding the boot process. Each week adds new applications for the same core skills. + +2. **Memory layout is fundamental** - Understanding flash vs RAM from Week 2 explains why startup code must copy data and zero BSS. + +3. **Ghidra complements GDB** - Dynamic analysis (GDB) shows what happens at runtime; static analysis (Ghidra) reveals the overall structure. Use both together! + +### New Concepts This Week + +4. **The boot process is deterministic** - Every RP2350 boots the same way, and understanding this helps you debug startup problems. + +5. **The bootrom can't be changed** - It's burned into silicon. Security features depend on this immutability. + +6. **The vector table is critical** - It tells the CPU where to start and how to handle errors. + +7. **Thumb mode uses the LSB** - Address `0x1000015d` means "run Thumb code at `0x1000015c`". + +8. **Startup code does essential work** - Copying data, zeroing BSS, and initializing the runtime all happen before `main()`. + +9. **Only Core 0 runs startup** - Core 1 waits in the bootrom until explicitly started. + +--- + +## Security Implications + +### How Boot Sequence Knowledge Applies to Security + +Understanding the boot process is critical for both attackers and defenders. Knowledge of how the RP2350 boots reveals potential attack vectors and defense strategies. + +#### Attack Scenarios + +| Scenario | Attack | Boot Process Knowledge Required | +| -------- | ------ | ------------------------------- | +| **Firmware Replacement** | Replace the entire flash image with malicious firmware | Understanding IMAGE_DEF structure and how bootrom validates firmware | +| **Vector Table Hijacking** | Modify the reset handler address to point to malicious code | Knowing the vector table location at `0x10000000` | +| **Bootrom Exploitation** | Find bugs in the immutable bootrom to bypass security | Understanding bootrom behavior and sequence | +| **Debug Port Attack** | Use SWD/JTAG to dump firmware or inject code | Knowledge of how to halt and examine the boot process | +| **Startup Code Modification** | Change how data is copied or BSS is cleared | Understanding crt0 and runtime_init sequences | + +#### Real-World Applications + +**Industrial Control Systems:** +- An attacker with physical access could replace firmware to hide malicious behavior +- Understanding the boot sequence helps identify the earliest point where security checks can be added + +**IoT Devices:** +- Compromised boot code could establish backdoors before the main application runs +- Secure boot implementations verify the vector table and reset handler integrity + +**Medical Devices:** +- Boot-time attacks could modify critical safety parameters before device operation +- Understanding initialization helps implement tamper detection + +### Defense Strategies + +#### 1. Secure Boot Implementation + +``` ++-----------------------------------------------------+ +| SECURE BOOT FLOW | ++-----------------------------------------------------+ +| Bootrom (immutable) | +| ↓ | +| Verify IMAGE_DEF signature | +| ↓ | +| Verify application image signature | +| ↓ | +| If all valid: Jump to reset handler | +| If any invalid: Refuse to boot | ++-----------------------------------------------------+ +``` + +**Implementation:** Use cryptographic signatures to verify each boot stage before execution. + +#### 2. Debug Port Protection + +- **Production devices:** Permanently disable SWD/JTAG in final products +- **Debug authentication:** Require cryptographic challenge-response before allowing debug access +- **Fuses:** Blow hardware fuses to disable debug ports permanently + +#### 3. Flash Protection + +- **Read protection:** Enable flash read protection to prevent dumping firmware +- **Write protection:** Make critical boot sectors write-protected after initial programming +- **Encrypted storage:** Store firmware encrypted in flash + +#### 4. Memory Protection Unit (MPU) + +Configure the Cortex-M33's MPU to: +- Mark code regions as execute-only (no reading code as data) +- Separate privileged and unprivileged memory regions +- Prevent execution from RAM regions (defend against code injection) + +#### 5. Boot-Time Integrity Checks + +```c +// Early in reset handler or runtime_init +void verify_boot_integrity(void) { + // Check vector table hasn't been modified + uint32_t vector_table_checksum = calculate_checksum(0x10000000, VECTOR_TABLE_SIZE); + if (vector_table_checksum != EXPECTED_CHECKSUM) { + // Vector table tampered - refuse to boot + secure_halt(); + } + + // Check critical data structures + // Verify stack pointer is in valid range + // etc. +} +``` + +#### 6. Anti-Tampering Hardware + +- **Tamper detection:** Sensors that detect case opening or voltage glitching +- **Response actions:** Erase sensitive keys, refuse to boot, or alert monitoring systems +- **Secure elements:** Store cryptographic keys in separate tamper-resistant chips + +### Lessons for Defenders + +1. **The bootrom is your trust anchor** - Its immutability makes it the foundation of security. RP2350's secure boot features leverage this. + +2. **Early is critical** - Security checks in the reset handler or runtime_init run before any application code, making them harder to bypass. + +3. **Defense in depth** - Multiple layers (hardware fuses, encrypted storage, secure boot, MPU) make attacks much harder. + +4. **Physical access = game over** - If an attacker can connect a debug probe, they can potentially compromise the device. Physical security matters! + +5. **Know your boot sequence** - Understanding exactly what runs when helps you identify where to add security checks and what assets need protection. + +### Security Research Value + +For security researchers and penetration testers, boot sequence analysis helps: + +- **Find vulnerabilities:** Many security bugs exist in startup code that runs before normal security checks +- **Develop exploits:** Understanding memory layout and initialization is essential for exploit development +- **Assess attack surface:** Knowing what's accessible at boot time reveals potential attack vectors +- **Build better defenses:** You can't defend what you don't understand + +> **"To know your enemy, you must become your enemy."** - Sun Tzu + +Understanding how an attacker would analyze and exploit the boot sequence is essential for building robust defenses. + +--- + +## Glossary + +### New Terms This Week + +| Term | Definition | +| ----------------- | ----------------------------------------------------------------------- | +| **Bootrom** | Factory-programmed ROM containing first-stage bootloader | +| **BSS** | Block Started by Symbol - section for uninitialized global variables | +| **CPUID** | Register that identifies which CPU core is executing | +| **crt0** | C Runtime Zero - the startup code that runs before main | +| **IMAGE_DEF** | Structure that marks valid firmware for the bootrom | +| **Linker Script** | File that defines memory layout for the compiled program | +| **Reset Handler** | First function called after reset/power-on | +| **Thumb Mode** | Compact instruction encoding used by Cortex-M | +| **Vector Table** | Array of addresses for stack pointer and exception handlers | +| **VTOR** | Vector Table Offset Register - tells CPU where to find the vector table | +| **XIP** | Execute In Place - running code directly from flash memory | + +### Review Terms from Weeks 1-2 + +| Term | Definition | How We Used It | +| ---- | ---------- | -------------- | +| **Breakpoint** | Marker that pauses program execution | Set at reset handler and main | +| **Register** | Fast storage inside the processor | Watched SP, LR, PC during boot | +| **Stack Pointer** | Register pointing to top of stack | Saw initial value in vector table | +| **Flash Memory** | Read-only storage for code | Contains vector table and boot code | +| **SRAM** | Read-write memory for data | Where stack and variables live | + +--- + +## Additional Resources + +### RP2350 Datasheet + +For more details on the boot process, see Chapter 5 of the RP2350 Datasheet: +https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf + +### Pico SDK Source Code + +The startup code lives in: +- `crt0.S` - Main startup assembly (vector table at `.section .vectors`, reset handler, data copy, BSS clear, platform_entry) +- `memmap_default.ld` - Default linker script (section ordering: `.vectors` -> `.binary_info_header` -> `.embedded_block` -> `.reset`) +- `embedded_start_block.inc.S` - IMAGE_DEF block (replaces RP2040's `boot2_generic_03h.S`) + +> **Note:** The RP2040 used a `boot2_generic_03h.S` second-stage bootloader occupying the first 256 bytes of flash. The RP2350 eliminated this; the bootrom handles flash XIP setup directly. The SDK still includes a `boot2` mechanism for compatibility, but it is **not** placed at flash address 0 - it is embedded in the data copy table and executed from the stack during startup. + +### Bootrom Source + +The bootrom source is available at: +https://github.com/raspberrypi/pico-bootrom-rp2350 + +--- + +## Part 17: Proving the Boot Sequence with objdump + +Everything we have learned about the boot sequence can be proven directly from the compiled ELF binary using `arm-none-eabi-objdump`. The bootrom is not in your ELF (it is mask ROM burned into the chip at `0x00000000`), but everything your firmware provides - the vector table, the IMAGE_DEF, and the reset handler - lives in your ELF starting at `0x10000000`. + +### Step 1: List All Sections + +```bash +arm-none-eabi-objdump -h build/0x0001_hello-world.elf +``` + +Expected output (key sections): + +``` +Idx Name Size VMA LMA + 0 .text 000019cc 10000000 10000000 + 3 .binary_info 0000002c 10001b20 10001b20 + 4 .ram_vector_table 00000110 20000000 20000000 + 6 .data 0000019c 20000110 10001b4c +``` + +> Tip: The Pico SDK merges `.vectors`, `.embedded_block`, and `.reset` all into `.text` at `0x10000000`. They are not separate named ELF sections - they are sub-regions inside `.text`. + +### Step 2: Dump the First 0x150 Bytes of Flash - One Command, Zero Skips + +```bash +arm-none-eabi-objdump -s --start-address=0x10000000 --stop-address=0x10000150 build/0x0001_hello-world.elf +``` + +Raw output from that command: + +``` + 10000000 00200820 5d010010 1b010010 1d010010 . . ]........... + 10000010 11010010 11010010 11010010 11010010 ................ + 10000020 11010010 11010010 11010010 11010010 ................ + 10000030 11010010 11010010 11010010 11010010 ................ + 10000040 11010010 11010010 11010010 11010010 ................ + 10000050 11010010 11010010 11010010 11010010 ................ + 10000060 11010010 11010010 11010010 11010010 ................ + 10000070 11010010 11010010 11010010 11010010 ................ + 10000080 11010010 11010010 11010010 11010010 ................ + 10000090 11010010 11010010 11010010 11010010 ................ + 100000a0 11010010 11010010 11010010 11010010 ................ + 100000b0 11010010 11010010 11010010 11010010 ................ + 100000c0 11010010 11010010 11010010 11010010 ................ + 100000d0 11010010 11010010 11010010 11010010 ................ + 100000e0 11010010 11010010 11010010 11010010 ................ + 100000f0 11010010 11010010 11010010 11010010 ................ + 10000100 11010010 11010010 11010010 11010010 ................ + 10000110 eff30580 103800be 00be00be 00be00be .....8.......... + 10000120 00be00be f2eb8871 201b0010 4c1b0010 .......q ...L... + 10000130 a0010010 90a31ae7 d3deffff 42012110 ............B.!. + 10000140 ff010000 b01b0000 793512ab ........y5.. +``` + +Every address annotated, no skips: + +#### 0x10000000 - Vector Table, Mandatory Entries + +| Address | Raw Bytes (LE) | Decoded | What it is | +|---------|----------------|---------|------------| +| `0x10000000` | `00 20 08 20` | `0x20082000` | **Initial SP** - top of SCRATCH_Y RAM. Bootrom loads MSP from here before doing anything else. | +| `0x10000004` | `5d 01 00 10` | `0x1000015d` | **Reset_Handler** address with Thumb bit set. Strip bit 0 -> real address `0x1000015c`. Bootrom jumps here. | +| `0x10000008` | `1b 01 00 10` | `0x1000011b` | **NMI** handler address (Thumb, -> `0x1000011a`). | +| `0x1000000c` | `1d 01 00 10` | `0x1000011d` | **HardFault** handler address (Thumb, -> `0x1000011c`). | + +#### 0x10000010-0x1000010f - Vector Table, IRQ Slots (all 52 external IRQs) + +``` + 10000010 11010010 11010010 ...(repeats through 0x1000010f)... +``` + +Every 4-byte word here is `11 01 00 10` = pointer `0x10000111`. +That is the **default IRQ handler** address with Thumb bit set (-> `0x10000110`). +The RP2350 Cortex-M33 has 16 system vectors (offsets 0x00-0x3f) plus up to 52 +external IRQ vectors (offsets 0x40-0xff = addresses `0x10000040`-`0x1000010f`). +Every IRQ the application does not register gets this default handler pointer. +This block is 240 bytes (`0x10000010` to `0x1000010f`) of nothing but that one +repeated pointer. + +#### 0x10000110-0x10000127 - Default IRQ Handler Code + +``` + 10000110 eff30580 103800be 00be00be 00be00be + 10000120 00be00be f2eb8871 +``` + +| Address | Bytes | ARM Thumb-2 Instruction | What it does | +|---------|-------|-------------------------|--------------| +| `0x10000110` | `ef f3 05 80` | `MRS r0, IPSR` | Read the Interrupt Program Status Register into r0. The low 9 bits = the active vector number. | +| `0x10000114` | `10 38` | `SUBS r0, #16` | Vector 16 = IRQ0, so subtract 16 to convert vector number -> IRQ index. | +| `0x10000116` | `00 be` | `BKPT #0` | Software breakpoint. If a debugger is attached, it stops here and you can inspect r0 to see which IRQ fired. If no debugger is attached, the CPU enters a fault loop and the chip hangs. | +| `0x10000118`-`0x10000127` | `00 be` *12 | `BKPT #0` repeating | Alignment padding to the next 4-byte boundary. | + +This is the **entire** default IRQ handler. It is intentionally minimal: if your +code triggers an IRQ you did not register, it crashes visibly instead of silently. + +#### 0x10000128-0x1000013b - Binary Info Pointer Table + +``` + 10000120 201b0010 4c1b0010 + 10000130 a0010010 90a31ae7 +``` + +| Address | Bytes (LE) | Decoded Value | What it is | +|---------|------------|---------------|------------| +| `0x10000128` | `20 1b 00 10` | `0x10001b20` | Pointer to **start** of `.binary_info` data section in flash. | +| `0x1000012c` | `4c 1b 00 10` | `0x10001b4c` | Pointer to **end** of `.binary_info` data section in flash. | +| `0x10000130` | `a0 01 00 10` | `0x100001a0` | Pointer to `binary_info_callback` function. | +| `0x10000134` | `90 a3 1a e7` | (magic marker) | `BINARY_INFO_MARKER_END` - marks the end of this pointer table. | + +`picotool` reads this table to extract the program name, version string, URL, +and GPIO pin map from any compiled binary without running it. + +#### 0x10000138-0x1000014c - IMAGE_DEF Block (this build) + +``` + 10000130 a0010010 90a31ae7 d3deffff 42012110 + 10000140 ff010000 b01b0000 793512ab 4ff00000 +``` + +| Address | Bytes | What it is | +|---------|-------|------------| +| `0x10000138` | `d3 de ff ff` | `PICOBIN_BLOCK_MARKER_START` - the bootrom scans flash for this exact 4-byte sequence to locate the IMAGE_DEF. | +| `0x1000013c` | `42 01 21 10` | IMAGE_DEF content (image type, flags, version). | +| `0x10000140` | `ff 01 00 00` | IMAGE_DEF content (continuation). | +| `0x10000144` | `b0 1b 00 00` | IMAGE_DEF content (continuation). | +| `0x10000148` | `79 35 12 ab` | `PICOBIN_BLOCK_MARKER_END` - bootrom stops scanning here. | + +The IMAGE_DEF sits at `0x10000138`-`0x1000014b` in this build, +well within the 4 KB scan window the bootrom uses (Datasheet 5.9.5, p. 429). + +#### Full Flash Map: 0x10000000-0x1000015c + +``` + 0x10000000-0x1000000f Vector Table: mandatory entries (SP, Reset, NMI, HardFault) + 0x10000010-0x1000010f Vector Table: 52 external IRQ slots -> all point to default handler + 0x10000110-0x10000127 Default IRQ handler code (MRS / SUBS / BKPT) + 0x10000128-0x10000137 Binary info pointer table (start / end / callback / magic end) + 0x10000138-0x1000014b IMAGE_DEF block (d3 de ff ff ... 79 35 12 ab) + 0x10000150-0x1000015b (padding / alignment) + 0x1000015c Reset_Handler (_reset_handler in crt0.S) ↠bootrom jumps here +``` + +### Step 4: Confirmed Boot Sequence (proven from ELF) + +``` ++-----------------------------------------------------------------+ +| PROVEN BOOT SEQUENCE (0x0001_hello-world) | ++-----------------------------------------------------------------+ +| 1. Bootrom reads 0x10000000 | +| -> SP = 0x20082000 (offset +0x00 of vector table) | +| -> RST = 0x1000015d (offset +0x04, Thumb -> 0x1000015c) | ++-----------------------------------------------------------------+ +| 2. Bootrom scans first 4 kB for IMAGE_DEF | +| -> Found at 0x10000138 (this build) | +| -> Start marker: d3 de ff ff | +| -> End marker: 79 35 12 ab | ++-----------------------------------------------------------------+ +| 3. Bootrom jumps to reset handler at 0x1000015c | +| -> _reset_handler (crt0.S) runs | +| -> Checks CPUID - Core 1 sent back to bootrom | +| -> Core 0: .data copied, .bss zeroed, platform_entry called | ++-----------------------------------------------------------------+ +| 4. platform_entry calls runtime_init -> main -> exit | ++-----------------------------------------------------------------+ +``` + +> **Datasheet References:** +> - 5.1.5.1 (p. 357): Block markers `0xffffded3` (start) and `0xab123579` (end) +> - 5.9.5 (p. 429): IMAGE_DEF must appear within first 4 kB of flash image +> - 5.9.5.1 (p. 429): Bootrom enters via reset handler at vector table offset +4 + +--- + +**Remember:** Understanding the boot process is fundamental to embedded systems work. Whether you're debugging a system that won't start, reverse engineering firmware, or building secure boot chains, this knowledge is essential! + +Happy exploring! + + + diff --git a/WEEK03/slides/WEEK03-IMG00.svg b/WEEK03/slides/WEEK03-IMG00.svg new file mode 100644 index 0000000..d40fbfd --- /dev/null +++ b/WEEK03/slides/WEEK03-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 03 + + +Embedded System Analysis: +Understanding the RP2350 Architecture +w/ Comprehensive Firmware Analysis + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEK03/slides/WEEK03-IMG01.svg b/WEEK03/slides/WEEK03-IMG01.svg new file mode 100644 index 0000000..0b8b8bd --- /dev/null +++ b/WEEK03/slides/WEEK03-IMG01.svg @@ -0,0 +1,70 @@ + + + + + +RP2350 Boot Sequence +Power-On to main() — 5 Steps + + + +STEP 1 +Power On +Cortex-M33 wakes, execution at 0x00000000 (Bootrom) + + +▼ + + + +STEP 2 +Bootrom Executes +32KB on-chip ROM — finds IMAGE_DEF at 0x10000000 + + +▼ + + + +STEP 3 +Flash XIP Setup (bootrom-managed) +Bootrom configures flash interface & XIP (no boot2 on RP2350) + + +▼ + + + +STEP 4 +Vector Table & Reset Handler +Reads SP from offset 0x00 -> 0x20082000 +Reads Reset Handler from 0x04 -> 0x1000015d + + +▼ + + + +STEP 5 +C Runtime Startup (crt0.S) +Copy .data from flash -> RAM +Zero .bss section +Call runtime_init() -> main() + + + +Key Insight +Your main() is the LAST thing to run. +All 5 steps must complete first! + \ No newline at end of file diff --git a/WEEK03/slides/WEEK03-IMG02.svg b/WEEK03/slides/WEEK03-IMG02.svg new file mode 100644 index 0000000..b7c0025 --- /dev/null +++ b/WEEK03/slides/WEEK03-IMG02.svg @@ -0,0 +1,84 @@ + + + + + +The Bootrom +32KB Factory-Programmed ROM — Where It All Begins + + + +Bootrom Properties + + +Size +32 KB + + +Location +0x00000000 + + +Modifiable? +NO — mask ROM + + +Purpose +Boot the chip + +Burned into silicon at factory +Like BIOS in your computer + + + +What It Does + + +1. +Initialize hardware + + +2. +Check boot sources + + +3. +Validate IMAGE_DEF + + +4. +Configure flash + + +5. +Jump to your code + + + +IMAGE_DEF — Magic Markers +Bootrom looks for these to validate firmware + + +Start Marker +0xFFFFDED3 +"I'm a valid Pico binary!" + + +End Marker +0xAB123579 +"End of header block" + +Bootrom reads flash at 0x10000000, +finds these markers, then boots. + \ No newline at end of file diff --git a/WEEK03/slides/WEEK03-IMG03.svg b/WEEK03/slides/WEEK03-IMG03.svg new file mode 100644 index 0000000..4db555b --- /dev/null +++ b/WEEK03/slides/WEEK03-IMG03.svg @@ -0,0 +1,74 @@ + + + + + +XIP — Execute In Place +Run Code Directly from Flash — No Copy Needed + + + +Book Analogy + + +Without XIP +Photocopy every page, read copy + + +With XIP +Read directly from the book! + + + +Why Use XIP? + + +Saves RAM +Code stays in flash + + +Faster Boot +No bulk copy needed + + +Simpler +Less memory mgmt + + + +XIP Flash Region at 0x10000000 + + + +Vector Table +SP at offset 0x00 | Reset Handler at offset 0x04 | Exception handlers... + + + +Your Code +_reset_handler | main() | other functions + + + +Read-Only Data +Strings like "hello, world" | constant values + + +0x10000000 +0x100001xx +0x10001xxx + +CPU fetches instructions directly +from flash via XIP cache. + \ No newline at end of file diff --git a/WEEK03/slides/WEEK03-IMG04.svg b/WEEK03/slides/WEEK03-IMG04.svg new file mode 100644 index 0000000..22034ba --- /dev/null +++ b/WEEK03/slides/WEEK03-IMG04.svg @@ -0,0 +1,80 @@ + + + + + +The Vector Table +CPU's Instruction Manual at 0x10000000 + + + +Vector Table Layout + + + +Offset +Address +Value +Meaning + + + +0x00 +0x10000000 +0x20082000 +Initial SP + + + +0x04 +0x10000004 +0x1000015D +Reset Handler + + + +0x08 +0x10000008 +0x1000011B +NMI Handler + + + +0x0C +0x1000000C +0x1000011D +HardFault Handler + + + +GDB: +x/4x 0x10000000 + + + +On Power-On +1. CPU reads SP from 0x00 +2. Sets SP = 0x20082000 +3. Reads Reset from 0x04 +4. Jumps to 0x1000015C + + + +Default Handlers +NMI, HardFault, SVCall, +PendSV, SysTick all use: + +bkpt 0x0000 +<- stops debugger + \ No newline at end of file diff --git a/WEEK03/slides/WEEK03-IMG05.svg b/WEEK03/slides/WEEK03-IMG05.svg new file mode 100644 index 0000000..c3d03a0 --- /dev/null +++ b/WEEK03/slides/WEEK03-IMG05.svg @@ -0,0 +1,70 @@ + + + + + +Thumb Mode Addressing +Why Addresses End in Odd Numbers + + + +The LSB Rule +ARM Cortex-M uses the Least Significant +Bit (LSB) to indicate instruction mode: + + + +LSB = 1 (odd) +Thumb mode + + + +LSB = 0 (even) +ARM mode + + + +Reset Handler Example + +Vector table stores: +0x1000015D + +Actual code address: +0x1000015C + +The +1 means: +"Use Thumb mode" + + + +GDB Shows + +0x1000015D +with Thumb bit + + +Vector table raw value + + + +Ghidra Shows + +0x1000015C +actual address + + +Real instruction location + +Both are correct — just displayed differently! + \ No newline at end of file diff --git a/WEEK03/slides/WEEK03-IMG06.svg b/WEEK03/slides/WEEK03-IMG06.svg new file mode 100644 index 0000000..f7d8b17 --- /dev/null +++ b/WEEK03/slides/WEEK03-IMG06.svg @@ -0,0 +1,69 @@ + + + + + +Linker Script Memory Map +memmap_default.ld — Where Everything Lives + + + +Memory Regions + + + +Flash (XIP) +0x10000000 +varies +Your code (read-only) + + + +RAM +0x20000000 +512 KB +Main RAM (r/w) + + + +SCRATCH_X +0x20080000 +4 KB +Core 0 scratch (HW: SRAM8) + + + +SCRATCH_Y +0x20081000 +4 KB +Core 0 stack! (HW: SRAM9) + + + +Stack Pointer Calculation + +__StackTop = ORIGIN(SCRATCH_Y) + + LENGTH(SCRATCH_Y) + + +ORIGIN +0x20081000 + ++ LENGTH +0x1000 +(4 KB) + += __StackTop = 0x20082000 +<- matches vector table! + \ No newline at end of file diff --git a/WEEK03/slides/WEEK03-IMG07.svg b/WEEK03/slides/WEEK03-IMG07.svg new file mode 100644 index 0000000..262aac0 --- /dev/null +++ b/WEEK03/slides/WEEK03-IMG07.svg @@ -0,0 +1,87 @@ + + + + + +Reset Handler — 4 Phases +_reset_handler at 0x1000015C + + + +Phase 1: Core Check +0x1000015C — 0x10000168 + +mov r0, #0xD0000000 +Read CPUID -> Core 0 continues + + + +Phase 2: Data Copy +0x1000016A — 0x10000176 + +ldmia r4!, {r1,r2,r3} +Copy .data from flash -> RAM + + + +Phase 3: BSS Clear +0x10000178 — 0x10000184 + +stmia r1!, {r0} +r0 = 0 +Zero all uninitialized globals + + + +Phase 4: Platform Entry +0x10000186+ + +blx r1 +-> main() +runtime_init -> main -> exit + + + +Execution Flow + + + +Core Check +CPUID == 0? + +-> + + +Data Copy +flash -> RAM + +-> + + +BSS Clear +zero globals + +-> + + +Platform Entry +-> main()! + + + +Why check cores? +RP2350 has 2 cores. +Only Core 0 runs startup. +Core 1 returns to bootrom and waits. + \ No newline at end of file diff --git a/WEEK03/slides/WEEK03-IMG08.svg b/WEEK03/slides/WEEK03-IMG08.svg new file mode 100644 index 0000000..b85e7ce --- /dev/null +++ b/WEEK03/slides/WEEK03-IMG08.svg @@ -0,0 +1,93 @@ + + + + + +Data Copy & BSS Clear +Initializing RAM Before main() Can Run + + + +Phase 2: Data Copy +Copy initialized variables flash -> RAM + +C code: + +int counter = 42; + +Value 42 stored in flash +but variables live in RAM! + + + +Flash + +-> + + +RAM + + +data_cpy_table has entries: + +src: 0x10001B4C (flash) +dst: 0x20000110 (RAM) + + + +Phase 3: BSS Clear +Zero uninitialized global variables + +C code: + +int my_counter; + +C standard requires +this to start at zero. + + + +r1 = BSS start +r2 = BSS end +r0 = 0 + +Loop: store 0, advance r1 +Until r1 == r2 -> done! + + + +Key Assembly Instructions + + + +ldmia r4!, {r1,r2,r3} + +Load source, dest, end from table + + +bl data_cpy + +Copy word-by-word until done + + + +movs r0, #0 + +Load zero into r0 + + +stmia r1!, {r0} + +Store zero, advance pointer + \ No newline at end of file diff --git a/WEEK03/slides/WEEK03-IMG09.svg b/WEEK03/slides/WEEK03-IMG09.svg new file mode 100644 index 0000000..8f0373c --- /dev/null +++ b/WEEK03/slides/WEEK03-IMG09.svg @@ -0,0 +1,82 @@ + + + + + +Platform Entry -> main() +The Final Step — 3 Function Calls at 0x10000186 + + + +platform_entry Assembly + + +0x10000186 +ldr r1, [DAT] +-> load runtime_init addr + + +0x10000188 +blx r1 +-> call runtime_init() + + +0x1000018C +blx r1 +-> call main() + + +0x10000190 +blx r1 +-> call exit() + + + +Call Sequence + + +runtime_init() +SDK setup + +-> + + +main() +YOUR CODE + +-> + + +exit() +cleanup + + + +runtime_init() +Initializes SDK systems: +• Clock configuration +• GPIO setup +• C++ constructor calls +• Peripheral initialization + + +After main() Returns +exit() handles cleanup. +Then: + +bkpt 0x0000 +<- infinite halt + +Should never be reached! + \ No newline at end of file diff --git a/WEEK03/slides/WEEK03-IMG10.svg b/WEEK03/slides/WEEK03-IMG10.svg new file mode 100644 index 0000000..73b2c7c --- /dev/null +++ b/WEEK03/slides/WEEK03-IMG10.svg @@ -0,0 +1,92 @@ + + + + + +Secure Boot & Attack Vectors +Why Boot Sequence Knowledge Matters for Security + + + +Attack Scenarios + + +Firmware Replacement +Replace flash with malicious code + + +Vector Table Hijack +Modify reset handler address + + +Debug Port Attack +SWD/JTAG to dump or inject code + + +Startup Code Modification +Change crt0 data copy / BSS init + +Physical access = game over + + + +Defense Strategies + + +1. Secure Boot + + +2. Debug Port Lock + + +3. Flash Read Protect + + +4. MPU Configuration + + +5. Integrity Checks + +Defense in depth! + + + +Secure Boot Chain + + +Bootrom +immutable + +-> + + +Verify Sig +IMAGE_DEF + +-> + + +Verify App +signature + +-> + + +Boot! +or refuse + +Each stage cryptographically verifies +the next before handing off control. +Bootrom = trust anchor (can't be changed) + \ No newline at end of file diff --git a/WEEK04/WEEK04-SLIDES.pdf b/WEEK04/WEEK04-SLIDES.pdf new file mode 100644 index 0000000..9846259 Binary files /dev/null and b/WEEK04/WEEK04-SLIDES.pdf differ diff --git a/WEEK04/WEEK04.md b/WEEK04/WEEK04.md new file mode 100644 index 0000000..ae5b177 --- /dev/null +++ b/WEEK04/WEEK04.md @@ -0,0 +1,910 @@ +# Week 4: Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics + +## What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand what variables are and how they're stored in memory +- Know the difference between initialized, uninitialized, and constant variables +- Use Ghidra to analyze binaries without debug symbols +- Patch binary files to change program behavior permanently +- Control GPIO pins to blink LEDs on the Pico 2 +- Convert patched binaries to UF2 format for flashing +- Understand the `.data`, `.bss`, and `.rodata` memory sections + +--- + +## Part 1: Understanding Variables + +### What is a Variable? + +A **variable** is like a labeled box where you can store information. Imagine you have a row of boxes numbered 0 to 9. Each box can hold one item. In programming: + +- The **boxes** are memory locations (addresses in SRAM) +- The **items** are the values you store +- The **labels** are the variable names you choose + +``` ++-----------------------------------------------------------------+ +| Memory (SRAM) - Like a row of numbered boxes | +| | +| Box 0 Box 1 Box 2 Box 3 Box 4 ... | +| +----+ +----+ +----+ +----+ +----+ | +| | 42 | | 17 | | 0 | |255 | | 99 | | +| +----+ +----+ +----+ +----+ +----+ | +| age score count max temp | +| | ++-----------------------------------------------------------------+ +``` + +### Declaration vs Definition + +When working with variables, there are two important concepts: + +| Concept | What It Does | Example | +| ------------------ | ------------------------------------ | -------------------------- | +| **Declaration** | Tells the compiler the name and type | `uint8_t age;` | +| **Definition** | Allocates memory for the variable | (happens with declaration) | +| **Initialization** | Assigns an initial value | `uint8_t age = 42;` | + +**Important Rule:** You must declare a variable BEFORE you use it! + +### Understanding Data Types + +The **data type** tells the compiler how much memory to allocate: + +| Type | Size | Range | Description | +| ---------- | ------- | ------------------------------- | ----------------------- | +| `uint8_t` | 1 byte | 0 to 255 | Unsigned 8-bit integer | +| `int8_t` | 1 byte | -128 to 127 | Signed 8-bit integer | +| `uint16_t` | 2 bytes | 0 to 65,535 | Unsigned 16-bit integer | +| `int16_t` | 2 bytes | -32,768 to 32,767 | Signed 16-bit integer | +| `uint32_t` | 4 bytes | 0 to 4,294,967,295 | Unsigned 32-bit integer | +| `int32_t` | 4 bytes | -2,147,483,648 to 2,147,483,647 | Signed 32-bit integer | + +### Anatomy of a Variable Declaration + +Let's break down this line of code: + +```c +uint8_t age = 42; +``` + +| Part | Meaning | +| --------- | ----------------------------------------------------- | +| `uint8_t` | Data type - unsigned 8-bit integer (1 byte) | +| `age` | Variable name - how we refer to this storage location | +| `=` | Assignment operator - puts a value into the variable | +| `42` | The initial value | +| `;` | Semicolon - tells compiler the statement is complete | + +--- + +## Part 2: Memory Sections - Where Variables Live + +### The Three Main Sections + +When your program is compiled, variables go to different places depending on how they're declared: + +``` ++-----------------------------------------------------------------+ +| .data Section (Flash -> copied to RAM at startup) | +| Contains: Initialized global/static variables | +| Example: int counter = 42; | ++-----------------------------------------------------------------+ +| .bss Section (RAM - zeroed at startup) | +| Contains: Uninitialized global/static variables | +| Example: int counter; (will be 0) | ++-----------------------------------------------------------------+ +| .rodata Section (Flash - read only) | +| Contains: Constants, string literals | +| Example: const int MAX = 100; | +| Example: "hello, world" | ++-----------------------------------------------------------------+ +``` + +### What Happens to Uninitialized Variables? + +In older C compilers, uninitialized variables could contain "garbage" - random leftover data. But modern compilers (including the Pico SDK) are smarter: + +1. Uninitialized global variables go into the `.bss` section +2. The `.bss` section is **NOT stored in the binary** (saves space!) +3. At boot, the startup code uses `memset` to **zero out** all of `.bss` +4. So uninitialized variables are always `0`! + +This is why in our code: +```c +uint8_t age; // This will be 0, not garbage! +``` + +--- + +## Part 3: Understanding GPIO (General Purpose Input/Output) + +### What is GPIO? + +**GPIO** stands for **General Purpose Input/Output**. These are pins on the microcontroller that you can control with software. Think of them as tiny switches you can turn on and off. + +``` ++-----------------------------------------------------------------+ +| Raspberry Pi Pico 2 | +| | +| GPIO 16 -------â–º Red LED | +| GPIO 17 -------â–º Green LED | +| GPIO 18 -------â–º Blue LED | +| ... | +| GPIO 25 -------â–º Onboard LED | ++-----------------------------------------------------------------+ +``` + +### GPIO Functions in the Pico SDK + +The Pico SDK provides simple functions to control GPIO pins: + +| Function | Purpose | +| ------------------------------ | ------------------------------- | +| `gpio_init(pin)` | Initialize a GPIO pin for use | +| `gpio_set_dir(pin, direction)` | Set pin as INPUT or OUTPUT | +| `gpio_put(pin, value)` | Set pin HIGH (1) or LOW (0) | +| `sleep_ms(ms)` | Wait for specified milliseconds | + +### What Happens Behind the Scenes? + +Each high-level function calls lower-level code. Let's trace `gpio_init()`: + +``` +gpio_init(LED_PIN) + ↓ +gpio_set_dir(LED_PIN, GPIO_IN) // Initially set as input + ↓ +gpio_put(LED_PIN, 0) // Set output value to 0 + ↓ +gpio_set_function(LED_PIN, GPIO_FUNC_SIO) // Connect to SIO block +``` + +The SIO (Single-cycle I/O) block is a special hardware unit in the RP2350 that provides fast GPIO control! + +--- + +## Part 4: Setting Up Your Environment + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. Ghidra installed (for static analysis) +3. Python installed (for UF2 conversion) +4. The sample projects: + - `0x0005_intro-to-variables` + - `0x0008_uninitialized-variables` +5. A serial monitor (PuTTY, minicom, or screen) + +### Project Structure + +``` +Embedded-Hacking/ ++-- 0x0005_intro-to-variables/ +| +-- build/ +| | +-- 0x0005_intro-to-variables.uf2 +| | +-- 0x0005_intro-to-variables.bin +| +-- 0x0005_intro-to-variables.c ++-- 0x0008_uninitialized-variables/ +| +-- build/ +| | +-- 0x0008_uninitialized-variables.uf2 +| | +-- 0x0008_uninitialized-variables.bin +| +-- 0x0008_uninitialized-variables.c ++-- uf2conv.py +``` + +--- + +## Part 5: Hands-On Tutorial - Analyzing Variables in Ghidra + +### Step 1: Review the Source Code + +First, let's look at the code we'll be analyzing: + +**File: `0x0005_intro-to-variables.c`** + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + uint8_t age = 42; + + age = 43; + + stdio_init_all(); + + while (true) + printf("age: %d\r\n", age); +} +``` + +**What this code does:** +1. Declares a variable `age` and initializes it to `42` +2. Changes `age` to `43` +3. Initializes the serial output +4. Prints `age` forever in a loop + +### Step 2: Flash the Binary to Your Pico 2 + +1. Hold the BOOTSEL button on your Pico 2 +2. Plug in the USB cable (while holding BOOTSEL) +3. Release BOOTSEL - a drive called "RPI-RP2" appears +4. Drag and drop `0x0005_intro-to-variables.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 3: Verify It's Working + +Open your serial monitor (PuTTY, minicom, or screen) and you should see: + +``` +age: 43 +age: 43 +age: 43 +... +``` + +The program is printing `43` because that's what we assigned after the initial `42`. + +--- + +## Part 6: Setting Up Ghidra for Binary Analysis + +### Step 4: Start Ghidra + +**Open a terminal and type:** + +```cmd +ghidraRun +``` + +Ghidra will open. Now we need to create a new project. + +### Step 5: Create a New Project + +1. Click **File** -> **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `0x0005_intro-to-variables` +5. Click **Finish** + +### Step 6: Import the Binary + +1. Open your file explorer +2. Navigate to the `Embedded-Hacking` folder +3. Find `0x0005_intro-to-variables.bin` +4. Select Cortex M Little Endian 32 +5. Select Options and set up the .text and offset 10000000 +6. **Drag and drop** the `.bin` file into Ghidra's project window + +### Step 7: Configure the Binary Format + +A dialog appears. The file is identified as a "BIN" (raw binary without debug symbols). + +**Click the three dots (...) next to "Language" and:** +1. Search for "Cortex" +2. Select **ARM Cortex 32 little endian default** +3. Click **OK** + +**Click the "Options..." button and:** +1. Change **Block Name** to `.text` +2. Change **Base Address** to `10000000` (the XIP address!) +3. Click **OK** + +### Step 8: Open and Analyze + +1. Double-click on the file in the project window +2. A dialog asks "Analyze now?" - Click **Yes** +3. Use default analysis options and click **Analyze** + +Wait for analysis to complete (watch the progress bar in the bottom right). + +--- + +## Part 7: Navigating and Resolving Functions + +### Step 9: Find the Functions + +Look at the **Symbol Tree** panel on the left. Expand **Functions**. + +You'll see function names like: +- `FUN_1000019a` +- `FUN_10000210` +- `FUN_10000234` + +These are auto-generated names because we imported a raw binary without symbols! + +### Step 10: Resolve Known Functions + +From our previous chapters, we know what some of these functions are: + +| Ghidra Name | Actual Name | How We Know | +| -------------- | ------------- | -------------------------- | +| `FUN_1000019a` | `data_cpy` | From Week 3 boot analysis | +| `FUN_10000210` | `frame_dummy` | From Week 3 boot analysis | +| `FUN_10000234` | `main` | This is where our code is! | + +### Step 11: Update Main's Signature + +For `main`, let's also fix the return type: + +1. Right-click on `main` in the Decompile window +2. Select **Edit Function Signature** +3. Change to: `int main(void)` +4. Click **OK** + +--- + +## Part 8: Analyzing the Main Function + +### Step 12: Examine Main in Ghidra + +Click on `main` (or `FUN_10000234`). Look at the **Decompile** window: + +You'll see something like: + +```c +void FUN_10000234(void) + +{ + FUN_10002f54(); + do { + FUN_100030e4(DAT_10000244,0x2b); + } while( true ); +} +``` + +### Step 13: Resolve stdio_init_all + +1. Click on `FUN_10002f54` +2. Right-click -> **Edit Function Signature** +3. Change to: `bool stdio_init_all(void)` +4. Click **OK** + +### Step 14: Resolve printf + +1. Click on `FUN_100030e4` +2. Right-click -> **Edit Function Signature** +3. Change the name to `void printf (undefined4 param_1, ...)` +4. Check the **Varargs** checkbox (printf takes variable arguments!) +5. Click **OK** + +### Step 15: Understand the Optimization + +Look at the updated decompiled code. This will look different if you resolved your functions however do you notice something interesting? + +```c +int main(void) + +{ + stdio_init_all(); + do { + printf(DAT_10000244,0x2b); + } while( true ); +} +``` + +**Where's `uint8_t age = 42`?** It's gone! + +The compiler **optimized it out**! Here's what happened: + +1. Original code: `age = 42`, then `age = 43` +2. Compiler sees: "The `42` is never used, only `43` matters" +3. Compiler removes the unused `42` and just uses `43` directly + +**What is `0x2b`?** Let's check: +- `0x2b` in hexadecimal = `43` in decimal + +The compiler replaced our variable with the constant value! + +--- + +## Part 9: Patching the Binary - Changing the Value + +### Step 16: Find the Value to Patch + +Look at the **Listing** window (assembly view). Find the instruction that loads `0x2b`: + +```assembly +1000023a 2b 21 movs r1,#0x2b +``` + +This instruction loads the value `0x2b` (43) into register `r1` before calling `printf`. + +### Step 17: Patch the Instruction + +We're going to change `0x2b` (43) to `0x46` (70)! + +1. At address `1000023a`, click the instruction `movs r1,#0x2b` +2. Right-click and select **Patch Instruction** +3. Replace immediate `0x2b` with `0x46` +4. Press Enter and verify the instruction bytes change from `2b 21` to `46 21` + +The instruction now reads: +```assembly +1000023a 46 21 movs r1,#0x46 +``` + +### Step 18: Export the Patched Binary + +1. Click **File** -> **Export Program** +2. Set **Format** to **Raw Bytes** +3. Navigate to your build directory +4. Name the file `0x0005_intro-to-variables-h.bin` +5. Click **OK** + +--- + +## Part 10: Converting and Flashing the Hacked Binary + +### Step 19: Convert to UF2 Format + +The Pico 2 expects UF2 files, not raw BIN files. We need to convert it! + +**Open a terminal and navigate to your project directory:** + +```cmd +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0005_intro-to-variables +``` + +**Run the conversion command:** + +```cmd +python ..\uf2conv.py build\0x0005_intro-to-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +**What this command means:** +- `uf2conv.py` = the conversion script +- `--base 0x10000000` = the XIP base address +- `--family 0xe48bff59` = the RP2350 family ID +- `--output build\hacked.uf2` = the output filename + +### Step 20: Flash the Hacked Binary + +1. Hold BOOTSEL and plug in your Pico 2 +2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive +3. Open your serial monitor + +**You should see:** + +``` +age: 70 +age: 70 +age: 70 +... +``` + + **BOOM! We hacked it!** The value changed from 43 to 70! + +--- + +## Part 11: Uninitialized Variables and GPIO + +Now let's work with a more complex example that includes GPIO control. + +### Step 21: Review the Uninitialized Variables Code + +**File: `0x0008_uninitialized-variables.c`** + +```c +#include +#include "pico/stdlib.h" + +#define LED_PIN 16 + +int main(void) { + uint8_t age; // Uninitialized! + + stdio_init_all(); + + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); + + while (true) { + printf("age: %d\r\n", age); + + gpio_put(LED_PIN, 1); + sleep_ms(500); + + gpio_put(LED_PIN, 0); + sleep_ms(500); + } +} +``` + +**What this code does:** +1. Declares `age` without initializing it (will be 0 due to BSS zeroing) +2. Initializes GPIO 16 as an output +3. In a loop: prints age, blinks the LED + +### Step 22: Flash and Verify + +1. Flash `0x0008_uninitialized-variables.uf2` to your Pico 2 +2. Open your serial monitor + +**You should see:** + +``` +age: 0 +age: 0 +age: 0 +... +``` + +And the **red LED on GPIO 16 should be blinking**! + +The value is `0` because uninitialized variables in the `.bss` section are zeroed at startup. + +--- + +## Part 12: Analyzing GPIO Code in Ghidra + +### Step 23: Set Up Ghidra for the New Binary + +1. Create a new project: `0x0008_uninitialized-variables` +2. Import `0x0008_uninitialized-variables.bin` +3. Set Language to **ARM Cortex 32 little endian** +4. Set Base Address to `.text` and `10000000` +5. Auto-analyze + +### Step 24: Resolve the Functions + +Find and rename these functions: + +| Ghidra Name | Actual Name | +| -------------- | ---------------- | +| `FUN_10000234` | `main` | +| `FUN_100030cc` | `stdio_init_all` | +| `FUN_100002b4` | `gpio_init` | +| `FUN_1000325c` | `printf` | + +For `gpio_init`, set the signature to: +```c +void gpio_init(uint gpio) +``` + +### Step 25: Examine the Main Function + +The decompiled main should look something like: + +```c +void FUN_10000234(void) + +{ + undefined4 extraout_r1; + undefined4 extraout_r2; + undefined4 in_cr0; + undefined4 in_cr4; + + FUN_100030cc(); + FUN_100002b4(0x10); + coprocessor_moveto2(0,4,0x10,1,in_cr4); + do { + FUN_1000325c(DAT_10000274,0); + coprocessor_moveto2(0,4,0x10,1,in_cr0); + FUN_10000d10(500); + coprocessor_moveto2(0,4,0x10,0,in_cr0); + FUN_10000d10(500,extraout_r1,extraout_r2,0); + } while( true ); +} +``` + +--- + +## Part 13: Hacking GPIO - Changing the LED Pin + +### Step 26: Find the GPIO Pin Value + +Look in the assembly for instructions that use `0x10` (which is 16 in decimal - our LED pin): + +```assembly +1000023a 10 20 movs r0,#0x10 +``` + +This is where `gpio_init(LED_PIN)` is called with GPIO 16. + +### Step 27: Patch GPIO 16 to GPIO 17 + +We'll change the red LED (GPIO 16) to the green LED (GPIO 17)! + +1. At address `1000023a`, select `movs r0,#0x10` +2. Right-click -> **Patch Instruction** +3. Replace immediate `0x10` with `0x11` (17 decimal) +4. Click **OK** and verify bytes change from `10 20` to `11 20` + +### Step 28: Find All GPIO 16 References + +There are more places that use GPIO 16. Look for: + +```assembly +10000244 10 23 movs r3,#0x10 +``` + +This is used in `gpio_set_dir`. Patch this to `0x11` as well. + +```assembly +10000252 10 24 movs r4,#0x10 +``` + +This is inside the loop for `gpio_put`. Patch this to `0x11` as well. +Patch each one with **Patch Instruction**, then verify: +- `10000244`: `10 23` -> `11 23` +- `10000252`: `10 24` -> `11 24` + +### Step 29: Bonus - Change the Printed Value + +Let's also change the printed value from `0` to `0x42` (66 in decimal): + +```assembly +1000024a 00 21 movs r1,#0x0 +``` + +1. Right-click -> **Patch Instruction** +2. Replace immediate `0x0` with `0x42` +3. Click **OK** and verify bytes change from `00 21` to `42 21` + +--- + +## Part 14: Export and Test the Hacked GPIO + +### Step 30: Export the Patched Binary + +1. Click **File** -> **Export Program** +2. Format: **Raw Bytes** +3. Filename: `0x0008_uninitialized-variables-h.bin` +4. Click **OK** + +### Step 31: Convert to UF2 + +```cmd +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0008_uninitialized-variables +python ..\uf2conv.py build\0x0008_uninitialized-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +### Step 32: Flash and Verify + +1. Flash `hacked.uf2` to your Pico 2 +2. Check your serial monitor + +**You should see:** + +``` +age: 66 +age: 66 +age: 66 +... +``` + +And now the **GREEN LED on GPIO 17** should be blinking instead of the red one! + + **We successfully:** +1. Changed the printed value from 0 to 66 +2. Changed which LED blinks from red (GPIO 16) to green (GPIO 17) + +--- + +## Part 15: Deep Dive - GPIO at the Assembly Level + +### Understanding the GPIO Coprocessor + +The RP2350 has a special **GPIO coprocessor** that provides fast, single-cycle GPIO control. This is different from the RP2040! + +The coprocessor is accessed using special ARM instructions: + +```assembly +mcrr p0, #4, r4, r5, c0 ; GPIO output control +mcrr p0, #4, r4, r5, c4 ; GPIO direction control +``` + +**What this means:** +- `mcrr` = Move to Coprocessor from two ARM Registers +- `p0` = Coprocessor 0 (the GPIO coprocessor) +- `r4` = Contains the GPIO pin number +- `r5` = Contains the value (0 or 1) +- `c0` = Output value register +- `c4` = Output enable register + +### The Full GPIO Initialization Sequence + +When you call `gpio_init(16)`, here's what actually happens: + +``` +Step 1: Configure pad (address 0x40038044) ++-----------------------------------------------------------------+ +| - Clear OD bit (output disable) | +| - Set IE bit (input enable) | +| - Clear ISO bit (isolation) | ++-----------------------------------------------------------------+ + +Step 2: Set function (address 0x40028084) ++-----------------------------------------------------------------+ +| - Set FUNCSEL to 5 (SIO - Software I/O) | ++-----------------------------------------------------------------+ + +Step 3: Enable output (via coprocessor) ++-----------------------------------------------------------------+ +| - mcrr p0, #4, r4, r5, c4 (where r4=16, r5=1) | ++-----------------------------------------------------------------+ +``` + +### Raw Assembly LED Blink + +Here's what a completely hand-written assembly LED blink looks like: + +```assembly +; Initialize GPIO 16 as output +movs r4, #0x10 ; GPIO 16 +movs r5, #0x01 ; Enable +mcrr p0, #4, r4, r5, c4 ; Set as output + +; Configure pad registers +ldr r3, =0x40038044 ; Pad control for GPIO 16 +ldr r2, [r3] ; Load current config +bic r2, r2, #0x80 ; Clear OD (output disable) +orr r2, r2, #0x40 ; Set IE (input enable) +str r2, [r3] ; Store config + +; Set GPIO function to SIO +ldr r3, =0x40028084 ; IO bank control for GPIO 16 +movs r2, #5 ; FUNCSEL = SIO +str r2, [r3] ; Set function + +; Main loop +loop: + ; LED ON + movs r4, #0x10 ; GPIO 16 + movs r5, #0x01 ; High + mcrr p0, #4, r4, r5, c0 + + ; Delay + ldr r2, =0x17D7840 ; ~25 million iterations +delay1: + subs r2, r2, #1 + bne delay1 + + ; LED OFF + movs r4, #0x10 ; GPIO 16 + movs r5, #0x00 ; Low + mcrr p0, #4, r4, r5, c0 + + ; Delay + ldr r2, =0x17D7840 +delay2: + subs r2, r2, #1 + bne delay2 + + b loop ; Repeat forever +``` + +--- + +## Part 16: Summary and Review + +### What We Accomplished + +1. **Learned about variables** - How they're declared, initialized, and stored +2. **Understood memory sections** - `.data`, `.bss`, and `.rodata` +3. **Analyzed binaries in Ghidra** - Without debug symbols! +4. **Patched binaries** - Changed values directly in the binary +5. **Controlled GPIO** - Made LEDs blink +6. **Changed program behavior** - Different LED, different value + +### The Binary Patching Workflow + +``` ++-----------------------------------------------------------------+ +| 1. Import .bin file into Ghidra | +| - Set language to ARM Cortex | +| - Set base address to 0x10000000 | ++-----------------------------------------------------------------+ +| 2. Analyze and resolve functions | +| - Rename functions to meaningful names | +| - Fix function signatures | ++-----------------------------------------------------------------+ +| 3. Find the values/instructions to patch | +| - Look in the assembly listing | +| - Patch Instruction, then verify old bytes -> new bytes | ++-----------------------------------------------------------------+ +| 4. Export the patched binary | +| - File -> Export Program | +| - Format: Raw Bytes | ++-----------------------------------------------------------------+ +| 5. Convert to UF2 | +| - python uf2conv.py file.bin --base 0x10000000 | +| --family 0xe48bff59 --output hacked.uf2 | ++-----------------------------------------------------------------+ +| 6. Flash and verify | +| - Hold BOOTSEL, plug in, drag UF2 | +| - Check serial output and LED behavior | ++-----------------------------------------------------------------+ +``` + +### Key Memory Sections + +| Section | Location | Contains | Writable? | +| --------- | -------- | ------------------------------ | --------- | +| `.text` | Flash | Code | No | +| `.rodata` | Flash | Constants, strings | No | +| `.data` | RAM | Initialized globals | Yes | +| `.bss` | RAM | Uninitialized globals (zeroed) | Yes | + +### Important Ghidra Commands + +| Action | How To Do It | +| ----------------- | ------------------------------------- | +| Rename function | Right-click -> Edit Function Signature | +| Patch instruction | Right-click -> Patch Instruction, then verify old bytes -> new bytes | +| Export binary | File -> Export Program -> Raw Bytes | +| Go to address | Press 'G' and enter address | + +--- + +--- + +## Key Takeaways + +1. **Variables are just memory locations** - The compiler assigns them addresses in SRAM. + +2. **Compilers optimize aggressively** - Unused code and values may be removed entirely. + +3. **Uninitialized doesn't mean random** - Modern compilers zero out the `.bss` section. + +4. **Ghidra works without symbols** - You can analyze any binary, even stripped ones. + +5. **Binary patching is powerful** - You can change behavior without source code. + +6. **UF2 conversion is required** - The Pico 2 needs UF2 format, not raw binaries. + +7. **GPIO is just memory-mapped I/O** - Writing to specific addresses controls hardware. + +--- + +## Glossary + +| Term | Definition | +| ------------------ | --------------------------------------------------------------------- | +| **BSS** | Block Started by Symbol - section for uninitialized global variables | +| **Declaration** | Telling the compiler a variable's name and type | +| **Definition** | Allocating memory for a variable | +| **GPIO** | General Purpose Input/Output - controllable pins on a microcontroller | +| **Initialization** | Assigning an initial value to a variable | +| **Linker** | Tool that combines compiled code and assigns memory addresses | +| **Optimization** | Compiler removing or simplifying code for efficiency | +| **Patching** | Modifying bytes directly in a binary file | +| **rodata** | Read-only data section for constants and string literals | +| **SIO** | Single-cycle I/O - fast GPIO control block in RP2350 | +| **UF2** | USB Flashing Format - file format for Pico 2 firmware | +| **Variable** | A named storage location in memory | + +--- + +## Additional Resources + +### GPIO Coprocessor Reference + +The RP2350 GPIO coprocessor instructions: + +| Instruction | Description | +| -------------------------- | ---------------------------- | +| `mcrr p0, #4, Rt, Rt2, c0` | Set/clear GPIO output | +| `mcrr p0, #4, Rt, Rt2, c4` | Set/clear GPIO output enable | + +### RP2350 Memory Map Quick Reference + +| Address | Description | +| ------------ | ------------------------ | +| `0x10000000` | XIP Flash (code) | +| `0x20000000` | SRAM (data) | +| `0x40028000` | IO_BANK0 (GPIO control) | +| `0x40038000` | PADS_BANK0 (pad control) | +| `0xd0000000` | SIO (single-cycle I/O) | + +--- + +**Remember:** Every binary you encounter in the real world can be analyzed and understood using these same techniques. Practice makes perfect! + +Happy hacking! + + diff --git a/WEEK04/slides/WEEK04-IMG00.svg b/WEEK04/slides/WEEK04-IMG00.svg new file mode 100644 index 0000000..2b2aae1 --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 04 + + +Variables in Embedded Systems: +Debugging and Hacking Variables +w/ GPIO Output Basics + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEK04/slides/WEEK04-IMG01.svg b/WEEK04/slides/WEEK04-IMG01.svg new file mode 100644 index 0000000..5331adf --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG01.svg @@ -0,0 +1,96 @@ + + + + + +What is a Variable? +Labeled Boxes in Memory (SRAM) + + + +Memory — A Row of Numbered Boxes + + + +42 +age +Box 0 + + + +17 +score +Box 1 + + + +0 +count +Box 2 + + + +255 +max +Box 3 + + + +99 +temp +Box 4 + + + +Anatomy of a Declaration + + +uint8_t age = 42; + +uint8_t +Data type (1 byte) + +age +Variable name (label) + += 42 +Initial value + +; +End of statement + + + +Key Concepts + + +Declaration +name + type + + +Definition +allocates memory + + +Initialization +assigns value + + + +Important Rule +You MUST declare a +variable BEFORE you +use it! +Compiler needs to know the type + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG02.svg b/WEEK04/slides/WEEK04-IMG02.svg new file mode 100644 index 0000000..8933406 --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG02.svg @@ -0,0 +1,86 @@ + + + + + +Data Types & Sizes +How Much Memory Each Type Uses + + + + + + +Type +Size +Range +Description + + + +uint8_t +1 byte +0 — 255 +Unsigned 8-bit + + + +int8_t +1 byte +-128 — 127 +Signed 8-bit + + + +uint16_t +2 bytes +0 — 65,535 +Unsigned 16-bit + + + +int16_t +2 bytes +-32,768 — 32,767 +Signed 16-bit + + + +uint32_t +4 bytes +0 — 4,294,967,295 +Unsigned 32-bit + + + +int32_t +4 bytes +-2.1B — 2.1B +Signed 32-bit + + +Size Comparison + + +1B +uint8_t + + +2B +uint16_t + + +4 Bytes +uint32_t + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG03.svg b/WEEK04/slides/WEEK04-IMG03.svg new file mode 100644 index 0000000..0cbcf5a --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG03.svg @@ -0,0 +1,63 @@ + + + + + +Memory Sections +Where Variables Live After Compilation + + + + +.data +Flash -> copied to RAM at startup +Contains: Initialized global/static variables +int counter = 42; +Initial value stored in flash, copied to SRAM by data_cpy + + + + +.bss +RAM — zeroed at startup +Contains: Uninitialized global/static variables +int counter; +NOT stored in binary (saves space!) — memset to 0 at boot + + + + +.rodata +Flash — read only +Contains: Constants and string literals +const int MAX = 100; +Lives in flash permanently — cannot be modified at runtime + + + +.data +RAM +Writable +Initialized globals + +.bss +RAM +Writable +Uninitialized globals (zeroed) + +.rodata +Flash +Read-only +Constants & strings + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG04.svg b/WEEK04/slides/WEEK04-IMG04.svg new file mode 100644 index 0000000..82437d2 --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG04.svg @@ -0,0 +1,79 @@ + + + + + +GPIO Basics +General Purpose Input/Output on RP2350 + + + +Pico 2 GPIO Pins + +GPIO 16 + +Red LED + +GPIO 17 + +Green LED + +GPIO 18 + +Blue LED + +GPIO 25 + +Onboard LED + +Software-controlled switches + + + +Pico SDK Functions + + +gpio_init(pin) +Init pin + + +gpio_set_dir(pin,d) +I/O dir + + +gpio_put(pin,val) +Set H/L + + +sleep_ms(ms) +Delay + + + +Basic LED Blink Code + + +#define LED_PIN 16 +int main(void) { +gpio_init(LED_PIN); +gpio_set_dir(LED_PIN, GPIO_OUT); +while (true) { +gpio_put(LED_PIN, 1); +// ON +sleep_ms(500); +gpio_put(LED_PIN, 0); +// OFF +sleep_ms(500); +}} + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG05.svg b/WEEK04/slides/WEEK04-IMG05.svg new file mode 100644 index 0000000..c6a0ef6 --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG05.svg @@ -0,0 +1,79 @@ + + + + + +Ghidra Binary Analysis +Analyzing a Raw .bin Without Symbols + + + +1. Import + +File -> Import +Language: +ARM Cortex 32 LE +Block: +.text +Base: +10000000 +XIP address for RP2350 + + + +2. Analyze + +Auto-Analyze: Yes +Ghidra finds: +FUN_1000019a +FUN_10000210 +FUN_10000234 +Auto-generated names + + + +3. Resolve + +Edit Function Sig +Rename to: +data_cpy +frame_dummy +main +Fix signatures + + + +Decompiled main() in Ghidra + + +Before Resolving: +void FUN_10000234(void){ +FUN_10002f54(); +do { +FUN_100030e4( +DAT_10000244,0x2b); +} while(true); +} + + +After Resolving: +int main(void) { +stdio_init_all(); +do { +printf( +"age: %d\r\n" +, 0x2b); +} while(true); +} + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG06.svg b/WEEK04/slides/WEEK04-IMG06.svg new file mode 100644 index 0000000..156aeeb --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG06.svg @@ -0,0 +1,77 @@ + + + + + +Compiler Optimization +Why Your Variable Disappeared + + + +Source Code + + +int main(void) { +uint8_t age = 42; +age = 43; +stdio_init_all(); +while (true) +printf("age: %d", age); + + + +Compiler Thinks... + + +age = 42 is NEVER read + + +Dead store -> REMOVED + + +age = 43 -> constant fold + +Replaces variable with literal + + + +Resulting Assembly + + +1000023a +2b 21 +movs r1, #0x2b +; 0x2b = 43 +No age=42 instruction — compiler removed it + + + +Key Takeaway + + +Source Code +age = 42 +age = 43 + +-> + + +Binary +movs r1, #0x2b + + +Compiler +Optimizes dead +stores away! + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG07.svg b/WEEK04/slides/WEEK04-IMG07.svg new file mode 100644 index 0000000..2041444 --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG07.svg @@ -0,0 +1,86 @@ + + + + + +Binary Patching +Changing Values in the Binary + + + +Before Patch + + +1000023a +2b 21 +movs r1,#0x2b + +0x2b = 43 decimal +Output: +age: 43 +Compiler-optimized constant + + + +After Patch + + +1000023a +46 21 +movs r1,#0x46 + +0x46 = 70 decimal +Output: +age: 70 +Changed program behavior! + + + +How to Patch in Ghidra + + +1. Find Instr + +-> + + +2. Rt-Click + +-> + + +3. Patch Val + +-> + + +Done! + +Patch Instruction: change operand + + + +Export Patched Binary + + +File: Export + + +Format: Raw Bytes + + +Save as *-h.bin + +Exported binary has your patches + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG08.svg b/WEEK04/slides/WEEK04-IMG08.svg new file mode 100644 index 0000000..eada6e7 --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG08.svg @@ -0,0 +1,99 @@ + + + + + +GPIO Hacking +Patching GPIO 16 to GPIO 17 + + + +Original: GPIO 16 +Red LED on pin 16 + + +1000023a +10 20 +movs r0,#0x10 + + +10000244 +10 23 +movs r3,#0x10 + + +10000252 +10 24 +movs r4,#0x10 + +0x10 = 16, three locations + + + +Patched: GPIO 17 +Green LED on pin 17 + + +1000023a +11 20 +movs r0,#0x11 + + +10000244 +11 23 +movs r3,#0x11 + + +10000252 +11 24 +movs r4,#0x11 + +0x11 = 17, all patched! + + + +What Each Patch Controls + + +gpio_init +r0 + + +gpio_set_dir +r3 + + +gpio_put +r4 + +ALL pin refs must be patched + + + +Bonus: Change Print Value + + +00 21 +movs r1,#0x0 +age: 0 + +-> + + +42 21 +movs r1,#0x42 +age: 66 + +Changed value: 0 to 66 (0x42) + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG09.svg b/WEEK04/slides/WEEK04-IMG09.svg new file mode 100644 index 0000000..6743432 --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG09.svg @@ -0,0 +1,78 @@ + + + + + +GPIO Coprocessor +RP2350 Single-Cycle I/O via mcrr + + + +mcrr Instruction Breakdown + + +mcrr p0, #4, r4, r5, c0 + +mcrr +Move to Coprocessor (2 regs) +p0 +Coprocessor 0 (GPIO) +r4 +GPIO pin number +r5 +Value (0=LOW, 1=HIGH) + + + +Output Value (c0) + + +mcrr p0,#4,r4,r5,c0 + +r4 = pin number +r5 = 0 or 1 +Controls GPIO output state + + +Output Enable (c4) + + +mcrr p0,#4,r4,r5,c4 + +r4 = pin number +r5 = 1 (enable output) +Sets pin direction to OUTPUT + + + +gpio_init(16) Sequence + + +Step 1: Config Pad +addr 0x40038044 + + +Step 2: Set Func +FUNCSEL = 5 (SIO) + + +Step 3: Enable Out +mcrr p0,#4,r4,r5,c4 + + + + +Pad: clear OD, set IE, clear ISO +SIO = fast single-cycle GPIO access + \ No newline at end of file diff --git a/WEEK04/slides/WEEK04-IMG10.svg b/WEEK04/slides/WEEK04-IMG10.svg new file mode 100644 index 0000000..23741fe --- /dev/null +++ b/WEEK04/slides/WEEK04-IMG10.svg @@ -0,0 +1,119 @@ + + + + + +Full Patching Pipeline +End-to-End Binary Hacking Workflow + + + + +1 +Import .bin +Ghidra: Import +ARM Cortex 32 LE +Base: 0x10000000 + + + + + + + +2 +Analyze +Auto-analyze +Rename functions +Fix signatures + + + + + + + +3 +Find Target +Listing window +Find movs rN,#val +Identify bytes to change + + + + +4 +Patch +Right-click: +Patch Instruction +Change operand value + + + + + + + +5 +Export +File: Export +Format: Raw Bytes +Save as *-h.bin + + + + + + + +6 +Convert UF2 +uf2conv.py +--family 0xe48bff59 +RP2350 family ID + + + +UF2 Command + +python uf2conv.py file.bin --base 0x10000000 -o hacked.uf2 + + + +Flash to Pico 2 +1. Hold BOOTSEL + USB +2. Drop hacked.uf2 +3. Pico reboots hacked +RPI-RP2 drive in BOOTSEL + + + +Key Sections + +.text +Flash +Code + +.rodata +Flash +Constants + +.data +RAM +Init globals + +.bss +RAM +Zeroed globals + \ No newline at end of file diff --git a/WEEK05/WEEK05-SLIDES.pdf b/WEEK05/WEEK05-SLIDES.pdf new file mode 100644 index 0000000..68bb942 Binary files /dev/null and b/WEEK05/WEEK05-SLIDES.pdf differ diff --git a/WEEK05/WEEK05.md b/WEEK05/WEEK05.md new file mode 100644 index 0000000..887ed40 --- /dev/null +++ b/WEEK05/WEEK05.md @@ -0,0 +1,1353 @@ +# Week 5: Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis + +## What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand how integers and floating-point numbers are stored in memory +- Know the difference between signed and unsigned integers (`uint8_t` vs `int8_t`) +- Understand how floats and doubles are represented using IEEE 754 encoding +- Use inline assembly to control GPIO pins directly at the hardware level +- Debug numeric data types using GDB and the OpenOCD debugger +- Hack integer values by modifying registers at runtime +- Hack floating-point values by understanding and manipulating their binary representation +- Reconstruct 64-bit doubles from two 32-bit registers +--- + +## Part 1: Understanding Integer Data Types + +### What is an Integer? + +An **integer** is a whole number without any decimal point. Think of it like counting apples: you can have 0 apples, 1 apple, 42 apples, but you can't have 3.5 apples (that would be a fraction!). + +In C programming for embedded systems, we have special integer types that tell the compiler exactly how much memory to use: + +``` ++-----------------------------------------------------------------+ +| Integer Types - Different Sizes for Different Needs | +| | +| uint8_t: 1 byte (0 to 255) - like a small box | +| int8_t: 1 byte (-128 to 127) - can hold negatives! | +| uint16_t: 2 bytes (0 to 65,535) - medium box | +| uint32_t: 4 bytes (0 to 4 billion) - big box | +| | ++-----------------------------------------------------------------+ +``` + +### Signed vs Unsigned Integers + +The difference between `uint8_t` and `int8_t` is whether the number can be **negative**: + +| Type | Prefix | Range | Use Case | +| --------- | ------ | ----------- | ----------------------------- | +| `uint8_t` | `u` | 0 to 255 | Ages, counts, always positive | +| `int8_t` | none | -128 to 127 | Temperature, can be negative | + +#### The Integer Variables + +Let's say a program declares two integer variables that demonstrate the difference between **signed** and **unsigned** types: + +```c +uint8_t age = 43; +int8_t range = -42; +``` + +The variable `age` is a `uint8_t` - an **unsigned** 8-bit integer that can only hold values from `0` to `255`. Since age is always a positive number, unsigned is the right choice. The variable `range` is an `int8_t` - a **signed** 8-bit integer that can hold values from `-128` to `127`. The signed type allows it to represent negative numbers like `-42`. Under the hood, negative values are stored using **two's complement** encoding: the CPU flips all the bits of `42` (`0x2A`) and adds `1`, producing `0xD6`, which is how `-42` lives in a single byte of memory. + +--- + +## Part 2: Understanding Floating-Point Data Types + +### What is a Float? + +A **float** is a number that can have a decimal point. Unlike integers which can only hold whole numbers like `42`, a float can hold values like `42.5`, `3.14`, or `-0.001`. In C, the `float` type uses **32 bits (4 bytes)** to store a number using the **IEEE 754** standard. + +``` ++-----------------------------------------------------------------+ +| IEEE 754 Single-Precision (32-bit float) | +| | +| +------+----------+---------------------------+ | +| | Sign | Exponent | Mantissa (Fraction) | | +| | 1bit | 8 bits | 23 bits | | +| +------+----------+---------------------------+ | +| | +| Value = (-1)^sign * 2^(exponent-127) * 1.mantissa | +| | +| Example: 42.5 | +| Sign: 0 (positive) | +| Exponent: 10000100 (132 - 127 = 5) | +| Mantissa: 01010100000000000000000 | +| Full: 0 10000100 01010100000000000000000 | +| Hex: 0x422A0000 | +| | ++-----------------------------------------------------------------+ +``` + +### How to Compute This by Hand (42.5 -> IEEE 754) + +Use this exact process any time you need to encode a decimal float manually. + +1. Determine the sign bit. + - `42.5` is positive, so `sign = 0`. + +2. Convert the number to binary. + - Integer part: `42 = 101010 (base 2)` + - Fractional part: use repeated multiply-by-2 on the fraction. + - Start with `0.5` + - `0.5 * 2 = 1.0` -> integer part is `1` (this is the first binary fractional bit) + - Remaining fractional part is now `0.0`, so we stop. + - Therefore `0.5 = 0.1 (base 2)`. + - Combined: `42.5 = 101010.1 (base 2)` + +3. Normalize to the form `1.xxxxx * 2^n`. + - `101010.1 (base 2) = 1.010101 (base 2) * 2^5` + - So the true exponent is `n = 5`. + +4. Compute the stored exponent (bias 127 for float). + - `stored exponent = n + 127 = 5 + 127 = 132` + - `132` in binary is `10000100` (8 bits). + + > Tip: **Why 127?** The exponent field is 8 bits wide, giving $2^8 = 256$ total values. Half of that range should represent negative exponents and half positive. The midpoint is $(2^8 / 2) - 1 = 127$. So a stored exponent of `127` means a real exponent of **0**, values below `127` are negative exponents, and values above `127` are positive exponents. Doubles use an 11-bit exponent field so their midpoint (bias) is $( 2^{11} / 2) - 1 = 1023$ instead. + +5. Build the mantissa (fraction bits). + - Take bits after the leading `1.` from `1.010101` -> `010101` + - Pad with zeros to 23 bits: + - `01010100000000000000000` + +6. Assemble all fields. + - `sign | exponent | mantissa` + - `0 | 10000100 | 01010100000000000000000` + - Full 32-bit pattern: + - `01000010001010100000000000000000` + +7. Convert the 32-bit binary to hex (group by 4 bits). + - `0100 0010 0010 1010 0000 0000 0000 0000` + - `4 2 2 A 0 0 0 0` + - Final result: `0x422A0000` + +Quick decode check (reverse direction, fully expanded): + +Given the 32-bit pattern: +- `0 | 10000100 | 01010100000000000000000` + +Decode it field by field: + +1. Sign bit + - Sign bit is `0` -> number is positive. + - So the sign multiplier is `(+1)`. + +2. Exponent field + - Exponent bits are `10000100`. + - Convert to decimal: `10000100 (base 2) = 132`. + - Float bias is `127`, so true exponent is: + - `132 - 127 = 5`. + +3. Mantissa field + - Stored mantissa bits are `01010100000000000000000`. + - IEEE 754 normal numbers use an implicit leading `1`, so significand becomes: + - `1.010101 (base 2)`. + +4. Rebuild the value + - Formula: `value = (+1) * 1.010101 (base 2) * 2^5`. + - Shift binary point right by 5: + - `1.010101 * 2^5 = 101010.1 (base 2)`. + +5. Convert `101010.1 (base 2)` to decimal + - Integer part: `101010 = 32 + 8 + 2 = 42` + - Fraction part: `.1 = 1/2 = 0.5` + - Total: `42 + 0.5 = 42.5` + +So the decoded value is exactly `42.5`. + +### Float vs Integer - Key Differences + +| Property | Integer (`uint8_t`) | Float (`float`) | +| -------------- | ---------------------- | --------------------------- | +| **Size** | 1 byte | 4 bytes | +| **Precision** | Exact | ~7 decimal digits | +| **Range** | 0 to 255 | 3.4 10^38 | +| **Encoding** | Direct binary | IEEE 754 (sign/exp/mantissa)| +| **printf** | `%d` | `%f` | + +### Our Floating-Point Program + +Let's look at a simple program that uses a `float` variable: + +**File: `0x000e_floating-point-data-type.c`** + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + float fav_num = 42.5; + + stdio_init_all(); + + while (true) + printf("fav_num: %f\r\n", fav_num); +} +``` + +> Note: `fav_num` is declared inside `main`, so by C rules it is an automatic (stack) variable. In optimized embedded builds, the compiler may avoid creating a real stack slot and instead materialize the value from read-only constant storage (typically `.rodata` and/or an ARM literal pool). +> +> An ARM **literal pool** is a small table of constants that the assembler places near code in memory. Instead of encoding a large immediate value directly in an instruction, the CPU executes a load instruction (such as `ldr`) that reads the constant from that nearby table. That is why Ghidra can show constant loads rather than a classic stack local. + +**What this code does:** +1. Declares a `float` variable `fav_num` and initializes it to `42.5` +2. Initializes the serial output +3. Prints `fav_num` forever in a loop using the `%f` format specifier + +> Tip: **Why `%f` instead of `%d`?** The `%d` format specifier tells `printf` to expect an integer. The `%f` specifier tells it to expect a floating-point number. Using the wrong one would print garbage! + +### Step 1: Flash the Binary to Your Pico 2 + +1. Hold the BOOTSEL button on your Pico 2 +2. Plug in the USB cable (while holding BOOTSEL) +3. Release BOOTSEL - a drive called "RPI-RP2" appears +4. Drag and drop `0x000e_floating-point-data-type.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 2: Verify It's Working + +Open your serial monitor (PuTTY) and you should see: + +**You should see:** + +``` +fav_num: 42.500000 +fav_num: 42.500000 +fav_num: 42.500000 +... +``` + +The program is printing `42.500000` because `printf` with `%f` defaults to 6 decimal places. + +--- + +## Part 2.5: Setting Up Ghidra for Float Analysis + +### Step 3: Start Ghidra + +**Open a terminal and type:** + +```cmd +ghidraRun +``` + +Ghidra will open. Now we need to create a new project. + +### Step 4: Create a New Project + +1. Click **File** -> **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `0x000e_floating-point-data-type` +5. Click **Finish** + +### Step 5: Import the Binary + +1. Open your file explorer +2. Navigate to the `Embedded-Hacking` folder +3. Find `0x000e_floating-point-data-type.bin` +4. Select Cortex M Little Endian 32 +5. Select Options and set up the .text and offset 10000000 +6. **Drag and drop** the `.bin` file into Ghidra's project window + +### Step 6: Configure the Binary Format + +A dialog appears. The file is identified as a "BIN" (raw binary without debug symbols). + +**Click the three dots (...) next to "Language" and:** +1. Search for "Cortex" +2. Select **ARM Cortex 32 little endian default** +3. Click **OK** + +**Click the "Options..." button and:** +1. Change **Block Name** to `.text` +2. Change **Base Address** to `10000000` (the XIP address!) +3. Click **OK** + +### Step 7: Open and Analyze + +1. Double-click on the file in the project window +2. A dialog asks "Analyze now?" - Click **Yes** +3. Use default analysis options and click **Analyze** + +Wait for analysis to complete (watch the progress bar in the bottom right). + +--- + +## Part 2.6: Navigating and Resolving Functions + +### Step 8: Find the Functions + +Look at the **Symbol Tree** panel on the left. Expand **Functions**. + +You'll see function names like: +- `FUN_1000019a` +- `FUN_10000210` +- `FUN_10000234` + +These are auto-generated names because we imported a raw binary without symbols! + +### Step 9: Resolve Known Functions + +From our previous chapters, we know what some of these functions are: + +| Ghidra Name | Actual Name | How We Know | +| -------------- | ------------- | -------------------------- | +| `FUN_1000019a` | `data_cpy` | From Week 3 boot analysis | +| `FUN_10000210` | `frame_dummy` | From Week 3 boot analysis | +| `FUN_10000234` | `main` | This is where our code is! | + +### Step 10: Update Main's Signature + +For `main`, let's also fix the return type: + +1. Right-click on `main` in the Decompile window +2. Select **Edit Function Signature** +3. Change to: `int main(void)` +4. Click **OK** + +--- + +## Part 2.7: Analyzing the Main Function + +### Step 11: Examine Main in Ghidra + +Click on `main` (or `FUN_10000234`). Look at the **Decompile** window: + +You'll see something like: + +```c +int main(void) + +{ + undefined4 uVar1; + undefined4 extraout_r1; + undefined4 uVar2; + undefined4 extraout_r1_00; + + FUN_10002f5c(); + uVar1 = DAT_1000024c; + uVar2 = extraout_r1; + do { + FUN_100030ec(DAT_10000250,uVar2,0,uVar1); + uVar2 = extraout_r1_00; + } while( true ); +} +``` + +### Step 12: Resolve stdio_init_all + +1. Click on `FUN_10002f5c` +2. Right-click -> **Edit Function Signature** +3. Change to: `bool stdio_init_all(void)` +4. Click **OK** + +### Step 13: Resolve printf + +1. Click on `FUN_100030ec` +2. Right-click -> **Edit Function Signature** +3. Change to: `int printf(char *format,...)` +4. Check the **Varargs** checkbox (printf takes variable arguments!) +5. Click **OK** + +### Step 14: Understand the Float Encoding + +Look at the decompiled code after resolving functions: + +```c +int main(void) + +{ + undefined4 uVar1; + undefined4 extraout_r1; + undefined4 uVar2; + undefined4 extraout_r1_00; + + stdio_init_all(); + uVar1 = DAT_1000024c; + uVar2 = extraout_r1; + do { + printf(DAT_10000250,uVar2,0,uVar1); + uVar2 = extraout_r1_00; + } while( true ); +} +``` + +**Where's `float fav_num = 42.5`?** It's been optimized into an immediate value! + +The compiler replaced our float variable with constants passed directly to `printf`. But wait - we see **two** values: `0x0`, in `r2` and `DAT_1000024c` or `0x40454000`, in `r3`. That's because `printf` with `%f` always receives a **double** (64-bit), not a `float` (32-bit). The C standard requires that `float` arguments to variadic functions like `printf` are **promoted to `double`**. + +A 64-bit double is passed in two 32-bit registers: + +| Register | Value | Role | +| -------- | ------------ | ------------ | +| `r2` | `0x00000000` | Low 32 bits | +| `r3` | `0x40454000` | High 32 bits | + +Together they form `0x40454000_00000000` - the IEEE 754 **double-precision** encoding of `42.5`. + +### Step 15: Verify the Double Encoding + +We need to decode `0x4045400000000000` field by field. The two registers give us the full 64-bit value: + +``` +r3 (high 32 bits): 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000 +r2 (low 32 bits): 0x00000000 = 0000 0000 0000 0000 0000 0000 0000 0000 +``` + +Laid out as a single 64-bit value with every bit numbered: + +``` +Bit: 63 62-52 (11 bits) 51-32 (20 bits) 31-0 (32 bits) + +---+-----------------------+------------------------------------------+----------------------------------+ + | 0 | 1 0 0 0 0 0 0 0 1 0 0 | 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | 00000000000000000000000000000000 | + +---+-----------------------+------------------------------------------+----------------------------------+ + Sign Exponent (11) Mantissa high 20 bits Mantissa low 32 bits + (from r3 bits 19-0) (from r2, all zero) +``` + +**Step-by-step field extraction:** + +**1. Sign bit** + +In IEEE 754, the **sign bit** is the very first (leftmost) bit of the 64-bit double. In the full 64-bit layout we call it **bit 63**: + +``` +64-bit double: [bit 63] [bit 62 ... bit 0] + ^ + sign bit +``` + +But we don't have a single 64-bit register - we have **two** 32-bit registers. The high register `r3` holds bits 63-32 of the double. So bit 63 of the double is the same physical bit as **bit 31 of r3** (the topmost bit of r3): + +``` +r3 holds bits 63-32 of the double +r2 holds bits 31-0 of the double +``` + +Now let's check it. IEEE 754 uses a simple rule for the sign bit: + +| Sign bit | Meaning | +|----------|----------| +| `0` | Positive | +| `1` | Negative | + +``` +r3 = 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000 + ^ + r3 bit 31 = 0 -> sign = 0 -> Positive number +``` + +The topmost bit of r3 is `0`, so the number is **positive**. If that bit were `1` instead (e.g. `0xC0454000`), the number would be negative (`-42.5`). + +**2. Exponent - bits 62-52 of the 64-bit value = bits 30-20 of r3** + +Extract bits 30-20 from `0x40454000`: + +``` +0x40454000 in binary: 0 10000000100 01010100000000000000 + sign exponent mantissa (top 20 bits) +``` + +Exponent bits: `10000000100` + +Convert to decimal: $2^{10} + 2^{2} = 1024 + 4 = 1028$ + +But `1028` is **not** the actual power of 2 yet. IEEE 754 stores exponents with a **bias** - a fixed number that gets added during encoding so that the stored value is always positive (no sign bit needed for the exponent). For doubles, the bias is **1023**. + +> Tip: **Why 1023?** The exponent field is 11 bits wide, giving $2^{11} = 2048$ total values. Half of that range should represent negative exponents and half positive. The midpoint is $(2^{11} / 2) - 1 = 1023$. So a stored exponent of `1023` means a real exponent of **0**, values below `1023` are negative exponents, and values above `1023` are positive exponents. + +To recover the real exponent, we subtract the bias: + +$$\text{real exponent} = \text{stored exponent} - \text{bias}$$ + +$$\text{real exponent} = 1028 - 1023 = \mathbf{5}$$ + +This means the number is scaled by $2^5 = 32$. In other words, the mantissa gets shifted left by 5 binary places. + +**3. Mantissa - bits 51-0 of the 64-bit value** + +- **High 20 bits of mantissa** (bits 51-32) = bits 19-0 of r3: + +``` +r3 bits 19-0: 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +``` + +- **Low 32 bits of mantissa** (bits 31-0) = all of r2: + +``` +r2 = 0x00000000 -> 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +``` + +Full 52-bit mantissa: + +``` +0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + <- top 20 bits from r3 -> <- bottom 32 bits from r2 (all zero) -> +``` + +IEEE 754 always prepends an **implied leading `1`**, so the actual value represented is: + +``` +1.010101 00000... (the 1. is implicit, not stored) +``` + +**4. Reconstruct the value** + +$$1.010101\text{ (base 2)} \times 2^5$$ + +Shift the binary point 5 places right: + +$$101010.1\text{ (base 2)}$$ + +Now convert each bit position to decimal: + +| Bit position | Power of 2 | Value | +|---|---|---| +| `1` (bit 5) | $2^5$ | 32 | +| `0` (bit 4) | $2^4$ | 0 | +| `1` (bit 3) | $2^3$ | 8 | +| `0` (bit 2) | $2^2$ | 0 | +| `1` (bit 1) | $2^1$ | 2 | +| `0` (bit 0) | $2^0$ | 0 | +| `1` (bit -1) | $2^{-1}$ | 0.5 | + +$$32 + 8 + 2 + 0.5 = \mathbf{42.5}$$ + +### Step 16: Examine the Assembly + +Look at the **Listing** window (assembly view). Find the main function: + +``` + ************************************************************* + * FUNCTION + ************************************************************* + int __stdcall main (void ) + int r0:4 + main+1 XREF[1,1]: 1000018c (c) , 1000018a (*) + main + 10000234 38 b5 push {r3,r4,r5,lr} + 10000236 02 f0 91 fe bl stdio_init_all bool stdio_init_all(void) + 1000023a 00 24 movs r4,#0x0 + 1000023c 03 4d ldr r5,[DAT_1000024c ] = 40454000h + LAB_1000023e XREF[1]: 10000248 (j) + 1000023e 22 46 mov r2,r4 + 10000240 2b 46 mov r3,r5 + 10000242 03 48 ldr r0=>s_fav_num:_%f_100034a8 ,[DAT_10000250 ] = "fav_num: %f\r\n" + = 100034A8h + 10000244 02 f0 52 ff bl printf int printf(char * format, ...) + 10000248 f9 e7 b LAB_1000023e + 1000024a 00 ?? 00h + 1000024b bf ?? BFh + DAT_1000024c XREF[1]: main:1000023c (R) + 1000024c 00 40 45 40 undefine 40454000h + DAT_10000250 XREF[1]: main:10000242 (R) + 10000250 a8 34 00 10 undefine 100034A8h * -> 100034a8 +``` + +> **Key Insight:** The `mov.w r2, #0x0` loads the low 32 bits (all zeros) and `ldr r3, [DAT_...]` loads the high 32 bits (`0x40454000`) of the double. Together, `r2:r3` = `0x40454000_00000000` = `42.5` as a double. + +### Step 17: Find the Format String + +In the Listing view, click on the data reference to find the format string: + +``` + s_fav_num:_%f_100034a8 XREF[1]: main:10000242 (*) + 100034a8 66 61 76 ds "fav_num: %f\r\n" + 5f 6e 75 + 6d 3a 20 +``` + +This confirms `printf` is called with the format string `"fav_num: %f\r\n"` and the double-precision value of `42.5`. + +--- + +## Part 2.8: Patching the Float - Changing 42.5 to 99.0 + +### Step 18: Calculate the New IEEE 754 Encoding + +We want to change `42.5` to `99.0`. First, we need to figure out the double-precision encoding of `99.0`: + +**Step A - Convert the integer part (99) to binary:** + +| Division | Quotient | Remainder | +|---------------|----------|-----------| +| 99 2 | 49 | **1** | +| 49 2 | 24 | **1** | +| 24 2 | 12 | **0** | +| 12 2 | 6 | **0** | +| 6 2 | 3 | **0** | +| 3 2 | 1 | **1** | +| 1 2 | 0 | **1** | + +Read remainders bottom-to-top: 99 (base 10) = 1100011 (base 2) + +**Step B - Convert the fractional part (.0) to binary:** + +There is no fractional part - `.0` is exactly zero, so the fractional binary is just `0`. + +**Step C - Combine:** + +$$99.0\text{ (base 10)} = 1100011.0\text{ (base 2)}$$ + +**Step D - Normalize to IEEE 754 form** (move the binary point so there's exactly one `1` before it): + +$$1100011.0\text{ (base 2)} = 1.100011\text{ (base 2)} \times 2^6$$ + +We shifted the binary point 6 places left, so the exponent is **6**. + +**Step E - Extract the IEEE 754 fields:** + +1. **Sign:** `0` (positive) +2. **Exponent:** $6 + 1023 = 1029 = 10000000101\text{ (base 2)}$ +3. **Mantissa:** `1000110000000000...` (everything after the `1.`, padded with zeros to 52 bits) +4. **Full double:** `0x4058C00000000000` + +| Register | Old Value | New Value | +| -------- | ------------ | ------------ | +| `r2` | `0x00000000` | `0x00000000` | +| `r3` | `0x40454000` | `0x4058C000` | + +Since `r2` stays `0x00000000`, we only need to patch the high word loaded into `r3`. + +### Step 19: Find the Value to Patch + +Look in the Listing view for the data that loads the high word of the double: + +``` + 1000024c 00 40 45 40 undefined4 40454000h +``` + +This is the 32-bit constant that gets loaded into `r3` - the high word of our double `42.5`. + +### Step 20: Patch the Constant + +1. Click on Window -> Bytes +2. Click on the Pencil icon to enable byte editing +3. At address `1000024c`, overwrite `00 40 45 40` with `00 C0 58 40` (little-endian for `0x40454000 -> 0x4058C000`) +4. Press Enter + +This changes the high word from `0x40454000` (42.5 as double) to `0x4058C000` (99.0 as double). + +--- + +## Part 2.9: Export and Test the Hacked Binary + +### Step 21: Export the Patched Binary + +1. Click **File** -> **Export Program** +2. Set **Format** to **Raw Bytes** +3. Navigate to your build directory +4. Name the file `0x000e_floating-point-data-type-h.bin` +5. Click **OK** + +### Step 22: Convert to UF2 Format + +**Open a terminal and navigate to your project directory:** + +```cmd +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x000e_floating-point-data-type +``` + +**Run the conversion command:** + +```cmd +python ..\uf2conv.py build\0x000e_floating-point-data-type-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +### Step 23: Flash the Hacked Binary + +1. Hold BOOTSEL and plug in your Pico 2 +2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive +3. Open your serial monitor + +**You should see:** + +``` +fav_num: 99.000000 +fav_num: 99.000000 +fav_num: 99.000000 +... +``` + + **BOOM! We hacked the float!** The value changed from `42.5` to `99.0`! + +--- + +## Part 3: Understanding Double-Precision Floating-Point Data Types + +### What is a Double? + +A **double** (short for "double-precision floating-point") is like a `float` but with **twice the precision**. While a `float` uses 32 bits, a `double` uses **64 bits (8 bytes)**, giving it roughly **15-16 significant decimal digits** of precision compared to a float's ~7. + +``` ++-----------------------------------------------------------------+ +| IEEE 754 Double-Precision (64-bit double) | +| | +| +------+-----------+--------------------------------------+ | +| | Sign | Exponent | Mantissa (Fraction) | | +| | 1bit | 11 bits | 52 bits | | +| +------+-----------+--------------------------------------+ | +| | +| Value = (-1)^sign * 2^(exponent-1023) * 1.mantissa | +| | +| Example: 42.52525 | +| Sign: 0 (positive) | +| Exponent: 10000000100 (1028 - 1023 = 5) | +| Mantissa: 0101010000110011101101100100010110100001110010101100 | +| Hex: 0x4045433B645A1CAC | +| | ++-----------------------------------------------------------------+ +``` + +### Float vs Double - Key Differences + +| Property | Float (`float`) | Double (`double`) | +| --------------- | ---------------------- | --------------------------- | +| **Size** | 4 bytes (32 bits) | 8 bytes (64 bits) | +| **Precision** | ~7 decimal digits | ~15 decimal digits | +| **Exponent** | 8 bits (bias 127) | 11 bits (bias 1023) | +| **Mantissa** | 23 bits | 52 bits | +| **Range** | 3.4 10^38 | 1.8 10^308 | +| **printf** | `%f` | `%lf` | +| **ARM passing** | Promoted to double | Native in `r2:r3` | + +> Tip: **Why does precision matter?** With a `float`, the value `42.52525` might be stored as `42.525249` due to rounding. A `double` can represent it as `42.525250` with much higher fidelity. For scientific or financial applications, that extra precision is critical! + +### Our Double-Precision Program + +Let's look at a program that uses a `double` variable: + +**File: `0x0011_double-floating-point-data-type.c`** + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + double fav_num = 42.52525; + + stdio_init_all(); + + while (true) + printf("fav_num: %lf\r\n", fav_num); +} +``` + +**What this code does:** +1. Declares a `double` variable `fav_num` and initializes it to `42.52525` +2. Initializes the serial output +3. Prints `fav_num` forever in a loop using the `%lf` format specifier + +> Tip: **`%lf` vs `%f`:** While `printf` actually treats `%f` and `%lf` identically (both expect a `double`), using `%lf` makes your intent clear - you're explicitly working with a `double`, not a `float`. It's good practice to match the format specifier to your variable type. + +### Step 1: Flash the Binary to Your Pico 2 + +1. Hold the BOOTSEL button on your Pico 2 +2. Plug in the USB cable (while holding BOOTSEL) +3. Release BOOTSEL - a drive called "RPI-RP2" appears +4. Drag and drop `0x000A_intro-to-doubles.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 2: Verify It's Working + +Open your serial monitor (PuTTY) and you should see: + +**You should see:** + +``` +fav_num: 42.525250 +fav_num: 42.525250 +fav_num: 42.525250 +... +``` + +The program is printing `42.525250` because `printf` with `%lf` defaults to 6 decimal places. + +--- + +## Part 3.5: Setting Up Ghidra for Double Analysis + +### Step 3: Start Ghidra + +**Open a terminal and type:** + +```cmd +ghidraRun +``` + +Ghidra will open. Now we need to create a new project. + +### Step 4: Create a New Project + +1. Click **File** -> **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `0x000A_intro-to-doubles` +5. Click **Finish** + +### Step 5: Import the Binary + +1. Open your file explorer +2. Navigate to the `Embedded-Hacking` folder +3. Find `0x0011_double-floating-point-data-type.bin` +4. Select Cortex M Little Endian 32 +5. Select Options and set up the .text and offset 10000000 +6. **Drag and drop** the `.bin` file into Ghidra's project window + +### Step 6: Configure the Binary Format + +A dialog appears. The file is identified as a "BIN" (raw binary without debug symbols). + +**Click the three dots (...) next to "Language" and:** +1. Search for "Cortex" +2. Select **ARM Cortex 32 little endian default** +3. Click **OK** + +**Click the "Options..." button and:** +1. Change **Block Name** to `.text` +2. Change **Base Address** to `10000000` (the XIP address!) +3. Click **OK** + +### Step 7: Open and Analyze + +1. Double-click on the file in the project window +2. A dialog asks "Analyze now?" - Click **Yes** +3. Use default analysis options and click **Analyze** + +Wait for analysis to complete (watch the progress bar in the bottom right). + +--- + +## Part 3.6: Navigating and Resolving Functions + +### Step 8: Find the Functions + +Look at the **Symbol Tree** panel on the left. Expand **Functions**. + +You'll see function names like: +- `FUN_1000019a` +- `FUN_10000210` +- `FUN_10000238` + +These are auto-generated names because we imported a raw binary without symbols! + +### Step 9: Resolve Known Functions + +From our previous chapters, we know what some of these functions are: + +| Ghidra Name | Actual Name | How We Know | +| -------------- | ------------- | -------------------------- | +| `FUN_1000019a` | `data_cpy` | From Week 3 boot analysis | +| `FUN_10000210` | `frame_dummy` | From Week 3 boot analysis | +| `FUN_10000238` | `main` | This is where our code is! | + +### Step 10: Update Main's Signature + +For `main`, let's also fix the return type: + +1. Right-click on `main` in the Decompile window +2. Select **Edit Function Signature** +3. Change to: `int main(void)` +4. Click **OK** + +--- + +## Part 3.7: Analyzing the Main Function + +### Step 11: Examine Main in Ghidra + +Click on `main` (or `FUN_10000234`). Look at the **Decompile** window: + +You'll see something like: + +```c +int main(void) + +{ + undefined4 uVar1; + undefined4 uVar2; + undefined4 extraout_r1; + undefined4 uVar3; + undefined4 extraout_r1_00; + + uVar2 = DAT_10000258; + uVar1 = DAT_10000254; + FUN_10002f64(); + uVar3 = extraout_r1; + do { + FUN_100030f4(DAT_10000250,uVar3,uVar1,uVar2); + uVar3 = extraout_r1_00; + } while( true ); +} +``` + +### Step 12: Resolve stdio_init_all + +1. Click on `FUN_10002f64` +2. Right-click -> **Edit Function Signature** +3. Change to: `bool stdio_init_all(void)` +4. Click **OK** + +### Step 13: Resolve printf + +1. Click on `FUN_100030f4` +2. Right-click -> **Edit Function Signature** +3. Change to: `int printf(char *format,...)` +4. Check the **Varargs** checkbox (printf takes variable arguments!) +5. Click **OK** + +### Step 14: Understand the Double Encoding + +Look at the decompiled code after resolving functions: + +```c +int main(void) + +{ + undefined4 uVar1; + undefined4 uVar2; + undefined4 extraout_r1; + undefined4 uVar3; + undefined4 extraout_r1_00; + + uVar2 = DAT_10000258; + uVar1 = DAT_10000254; + stdio_init_all(); + uVar3 = extraout_r1; + do { + printf(DAT_10000250,uVar3,uVar1,uVar2); + uVar3 = extraout_r1_00; + } while( true ); +} +``` + +**Where's `double fav_num = 42.52525`?** It's been optimized into immediate values! + +This time we see **two** non-zero values: `0x645a1cac` and `0x4045433b`. Unlike the float example where the low word was `0x0`, a double with a fractional part like `42.52525` needs **all 52 mantissa bits** - so both halves carry data. + +A 64-bit double is passed in two 32-bit registers: + +| Register | Value | Role | +| -------- | ------------ | ------------ | +| `r2` | `0x645A1CAC` | Low 32 bits | +| `r3` | `0x4045433B` | High 32 bits | + +Together they form `0x4045433B645A1CAC` - the IEEE 754 **double-precision** encoding of `42.52525`. + +> **Key Difference from Float:** In the float example, `r2` was `0x00000000` because `42.5` has a clean fractional part. But `42.52525` has a repeating binary fraction, so the low 32 bits are non-zero (`0x645A1CAC`). This means **both** registers matter when patching doubles with complex fractional values! + +### Step 15: Verify the Double Encoding + +We need to decode `0x4045433B645A1CAC` field by field. The two registers give us the full 64-bit value: + +``` +r3 (high 32 bits): 0x4045433B = 0100 0000 0100 0101 0100 0011 0011 1011 +r2 (low 32 bits): 0x645A1CAC = 0110 0100 0101 1010 0001 1100 1010 1100 +``` + +Laid out as a single 64-bit value with every bit numbered: + +``` +Bit: 63 62-52 (11 bits) 51-32 (20 bits) 31-0 (32 bits) + +---+-----------------------+------------------------------------------+------------------------------------------+ + | 0 | 1 0 0 0 0 0 0 0 1 0 0 | 0 1 0 1 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 | 01100100010110100001110010101100 | + +---+-----------------------+------------------------------------------+------------------------------------------+ + Sign Exponent (11) Mantissa high 20 bits Mantissa low 32 bits + (from r3 bits 19-0) (from r2) +``` + +**Step-by-step field extraction:** + +**1. Sign bit** + +The sign bit is bit 63 of the 64-bit double, which is bit 31 of r3 (the high register holds bits 63-32): + +``` +r3 = 0x4045433B = 0100 0000 0100 0101 0100 0011 0011 1011 + ^ + r3 bit 31 = 0 -> sign = 0 -> Positive number ✓ +``` + +**2. Exponent - bits 62-52 = bits 30-20 of r3** + +Extract bits 30-20 from `0x4045433B`: + +``` +0x4045433B in binary: 0 10000000100 01010100001100111011 + sign exponent mantissa (top 20 bits) +``` + +Exponent bits: `10000000100` + +Convert to decimal: $2^{10} + 2^{2} = 1024 + 4 = 1028$ + +Subtract the bias (same formula as Part 2 - the bias is 1023 for all doubles): + +$$\text{real exponent} = 1028 - 1023 = \mathbf{5}$$ + +This means the mantissa gets shifted left by 5 binary places (i.e. multiplied by $2^5 = 32$). + +**3. Mantissa - bits 51-0** + +Unlike the `42.5` example where r2 was all zeros, **both registers contribute non-zero bits** here: + +- **High 20 bits of mantissa** (bits 51-32) = bits 19-0 of r3: + +``` +r3 bits 19-0: 0 1 0 1 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 +``` + +- **Low 32 bits of mantissa** (bits 31-0) = all of r2: + +``` +r2 = 0x645A1CAC -> 0 1 1 0 0 1 0 0 0 1 0 1 1 0 1 0 0 0 0 1 1 1 0 0 1 0 1 0 1 1 0 0 +``` + +Full 52-bit mantissa: + +``` +0 1 0 1 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 | 0 1 1 0 0 1 0 0 0 1 0 1 1 0 1 0 0 0 0 1 1 1 0 0 1 0 1 0 1 1 0 0 + <- top 20 bits from r3 -> <- bottom 32 bits from r2 -> +``` + +IEEE 754 always prepends an **implied leading `1`**, so the actual value represented is: + +``` +1.0101010000110011101101100100010110100001110010101100 (the 1. is implicit, not stored) +``` + +**4. Reconstruct the value** + +$$1.0101010000110011101101100100...\text{ (base 2)} \times 2^5$$ + +Shift the binary point 5 places right: + +$$101010.10000110011101101100100010110100001110010101100\text{ (base 2)}$$ + +**Integer part** (`101010`): + +| Bit position | Power of 2 | Value | +|---|---|---| +| `1` (bit 5) | $2^5$ | 32 | +| `0` (bit 4) | $2^4$ | 0 | +| `1` (bit 3) | $2^3$ | 8 | +| `0` (bit 2) | $2^2$ | 0 | +| `1` (bit 1) | $2^1$ | 2 | +| `0` (bit 0) | $2^0$ | 0 | + +$$32 + 8 + 2 = \mathbf{42}$$ + +**Fractional part** (`.10000110011101101...`): + +| Bit position | Power of 2 | Decimal value | +|---|---|---| +| `1` (bit -1) | $2^{-1}$ | 0.5 | +| `0` (bit -2) | $2^{-2}$ | 0 | +| `0` (bit -3) | $2^{-3}$ | 0 | +| `0` (bit -4) | $2^{-4}$ | 0 | +| `0` (bit -5) | $2^{-5}$ | 0 | +| `1` (bit -6) | $2^{-6}$ | 0.015625 | +| `1` (bit -7) | $2^{-7}$ | 0.0078125 | +| `0` (bit -8) | $2^{-8}$ | 0 | +| `0` (bit -9) | $2^{-9}$ | 0 | +| `1` (bit -10) | $2^{-10}$ | 0.0009765625 | +| `1` (bit -11) | $2^{-11}$ | 0.00048828125 | +| `1` (bit -12) | $2^{-12}$ | 0.000244140625 | +| ... | ... | *(remaining 35 bits add smaller and smaller fractions)* | + +First 12 fractional bits sum: $0.5 + 0.015625 + 0.0078125 + 0.0009765625 + 0.00048828125 + 0.000244140625 \approx 0.5251$ + +The remaining 35 fractional bits refine this to $\approx 0.52525$. This is because `0.52525` is a **repeating fraction** in binary - it can never be represented with a finite number of bits, so double precision stores the closest possible 52-bit approximation. + +$$42 + 0.52525 = \mathbf{42.52525} \checkmark$$ + +### Step 16: Examine the Assembly + +Look at the **Listing** window (assembly view). Find the main function: + +``` + ************************************************************* + * FUNCTION + ************************************************************* + int __stdcall main (void ) + int r0:4 + main+1 XREF[1,1]: 1000018c (c) , 1000018a (*) + main + 10000238 38 b5 push {r3,r4,r5,lr} + 1000023a 06 a5 adr r5,[0x10000254 ] + 1000023c d5 e9 00 45 ldrd r4,r5,[r5,#0x0 ]=>DAT_10000254 = 645A1CACh + = 4045433Bh + 10000240 02 f0 90 fe bl stdio_init_all bool stdio_init_all(void) + LAB_10000244 XREF[1]: 1000024e (j) + 10000244 22 46 mov r2,r4 + 10000246 2b 46 mov r3,r5 + 10000248 01 48 ldr r0=>s_fav_num:_%lf_100034b0 ,[DAT_10000250 ] = "fav_num: %lf\r\n" + = 100034B0h + 1000024a 02 f0 53 ff bl printf int printf(char * format, ...) + 1000024e f9 e7 b LAB_10000244 + DAT_10000250 XREF[1]: main:10000248 (R) + 10000250 b0 34 00 10 undefine 100034B0h ? -> 100034b0 + DAT_10000254 XREF[1]: main:1000023c (R) + 10000254 ac 1c 5a 64 undefine 645A1CACh + DAT_10000258 XREF[1]: main:1000023c (R) + 10000258 3b 43 45 40 undefine 4045433Bh +``` + +> **Key Insight:** Notice that **both** `r2` and `r3` are loaded from data constants using `ldr`. Compare this to the float example where `r2` was loaded with `mov.w r2, #0x0`. Because `42.52525` requires all 52 mantissa bits, neither word can be zero - the compiler must store both halves as separate data constants. + +### Step 17: Find the Format String + +In the Listing view, click on the data reference to find the format string: + +``` + s_fav_num:_%lf_100034b0 XREF[1]: main:10000248 (*) + 100034b0 66 61 76 ds "fav_num: %lf\r\n" + 5f 6e 75 + 6d 3a 20 +``` + +This confirms `printf` is called with the format string `"fav_num: %lf\r\n"` and the double-precision value of `42.52525`. + +--- + +## Part 3.8: Patching the Double - Changing 42.52525 to 99.99 + +### Step 18: Calculate the New IEEE 754 Encoding + +We want to change `42.52525` to `99.99`. First, we need to figure out the double-precision encoding of `99.99`: + +1. $99.99 = 1.5623... \times 2^6 = 1.100011111111...\text{ (base 2)} \times 2^6$ +2. **Sign:** `0` (positive) +3. **Exponent:** $6 + 1023 = 1029 = 10000000101\text{ (base 2)}$ +4. **Mantissa:** `1000111111010111000010100011110101110000101000111... (base 2)` +5. **Full double:** `0x4058FF5C28F5C28F` + +| Register | Old Value | New Value | +| -------- | ------------ | ------------ | +| `r2` | `0x645A1CAC` | `0x28F5C28F` | +| `r3` | `0x4045433B` | `0x4058FF5C` | + +Unlike the float example, **both** registers change! The value `99.99` has a repeating binary fraction, so both the high and low words are different. + +### Step 19: Find the Values to Patch + +Look in the Listing view for the two data constants: + +**Low word (loaded into `r2`):** +``` + 10000254 ac 1c 5a 64 undefined4 645A1CACh +``` + +**High word (loaded into `r3`):** +``` + 10000258 3b 43 45 40 undefined4 4045433Bh +``` + +### Step 20: Patch Both Constants + +**Patch the low word:** +1. Click on the data at address `10000254` containing `645A1CAC` +2. Open the Bytes window and enable byte editing (Pencil icon) +3. Overwrite bytes `ac 1c 5a 64` with `8f c2 f5 28` (little-endian for `0x645A1CAC -> 0x28F5C28F`) +4. Press Enter + +**Patch the high word:** +1. Click on the data at address `10000258` containing `4045433B` +2. Keep byte editing enabled in the Bytes window +3. Overwrite bytes `3b 43 45 40` with `5c ff 58 40` (little-endian for `0x4045433B -> 0x4058FF5C`) +4. Press Enter + +This changes the full 64-bit double from `0x4045433B645A1CAC` (42.52525) to `0x4058FF5C28F5C28F` (99.99). + +> **Key Difference from Float Patching:** When we patched the float `42.5`, we only needed to change one word (the high word in `r3`) because the low word was all zeros. With `42.52525 -> 99.99`, **both** words change. Always check whether the low word is non-zero before patching! + +--- + +## Part 3.9: Export and Test the Hacked Binary + +### Step 21: Export the Patched Binary + +1. Click **File** -> **Export Program** +2. Set **Format** to **Raw Bytes** +3. Navigate to your build directory +4. Name the file `0x0011_double-floating-point-data-type-h.bin` +5. Click **OK** + +### Step 22: Convert to UF2 Format + +**Open a terminal and navigate to your project directory:** + +```cmd +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0011_double-floating-point-data-type +``` + +**Run the conversion command:** + +```cmd +python ..\uf2conv.py build\0x0011_double-floating-point-data-type-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +### Step 23: Flash the Hacked Binary + +1. Hold BOOTSEL and plug in your Pico 2 +2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive +3. Open your serial monitor + +**You should see:** + +``` +fav_num: 99.990000 +fav_num: 99.990000 +fav_num: 99.990000 +... +``` + + **BOOM! We hacked the double!** The value changed from `42.52525` to `99.99`! + +--- + +## Part 3.95: Summary - Float and Double Analysis + +### What We Accomplished + +1. **Learned about IEEE 754** - How floating-point numbers are encoded in 32-bit (float) and 64-bit (double) formats +2. **Discovered float-to-double promotion** - `printf` with `%f` always receives a `double`, even when you pass a `float` +3. **Decoded register pairs** - 64-bit doubles are split across `r2` (low) and `r3` (high) +4. **Patched a float value** - Changed `42.5` to `99.0` by modifying only the high word +5. **Patched a double value** - Changed `42.52525` to `99.99` by modifying **both** words +6. **Understood the key difference** - Clean fractions (like `42.5`) have a zero low word; complex fractions (like `42.52525`) require patching both words + +### IEEE 754 Quick Reference for Common Values + +| Value | Double Hex | High Word (r3) | Low Word (r2) | +| -------- | ------------------------ | --------------- | -------------- | +| 42.0 | `0x4045000000000000` | `0x40450000` | `0x00000000` | +| 42.5 | `0x4045400000000000` | `0x40454000` | `0x00000000` | +| 42.52525 | `0x4045433B645A1CAC` | `0x4045433B` | `0x645A1CAC` | +| 43.0 | `0x4045800000000000` | `0x40458000` | `0x00000000` | +| 99.0 | `0x4058C00000000000` | `0x4058C000` | `0x00000000` | +| 99.99 | `0x4058FF5C28F5C28F` | `0x4058FF5C` | `0x28F5C28F` | +| 100.0 | `0x4059000000000000` | `0x40590000` | `0x00000000` | +| 3.14 | `0x40091EB851EB851F` | `0x40091EB8` | `0x51EB851F` | + +### The Float/Double Patching Workflow + +``` ++-----------------------------------------------------------------+ +| 1. Identify the float/double value in the decompiled view | +| - Look for hex constants like 0x40454000 or 0x4045433B | ++-----------------------------------------------------------------+ +| 2. Determine if it's float (32-bit) or double (64-bit) | +| - printf promotes floats to doubles! | +| - Check if value spans r2:r3 (double) or just r0 (float) | ++-----------------------------------------------------------------+ +| 3. Check if the low word (r2) is zero or non-zero | +| - Zero low word = only patch the high word | +| - Non-zero low word = patch BOTH words | ++-----------------------------------------------------------------+ +| 4. Calculate the new IEEE 754 encoding | +| - Convert your desired value to IEEE 754 | +| - Split into high/low words | ++-----------------------------------------------------------------+ +| 5. Patch the constant(s) in Ghidra | +| - Edit bytes in the Bytes window (Pencil mode) | +| - Replace the old encoding with the new one | ++-----------------------------------------------------------------+ +| 6. Export -> Convert to UF2 -> Flash -> Verify | +| - Same workflow as integer patching | ++-----------------------------------------------------------------+ +``` + +> Tip: **Key takeaway:** Hacking doubles is the same process as hacking floats - find the IEEE 754 constant, calculate the new encoding, patch it. The only extra step is checking whether the **low word** (`r2`) is also non-zero. Clean values like `42.5` only need one patch; messy fractions like `42.52525` need two! + +--- + +--- + +## Key Takeaways + +1. **Integers have fixed sizes** - `uint8_t` is 1 byte (0-255), `int8_t` is 1 byte (-128 to 127). The `u` prefix means unsigned. + +2. **IEEE 754 encodes floats in binary** - Sign bit, exponent (with bias), and mantissa form the encoding for both 32-bit floats and 64-bit doubles. + +3. **printf promotes floats to doubles** - Even when you pass a `float`, `printf` receives a 64-bit `double` due to C's variadic function rules. + +4. **64-bit values span two registers** - On ARM Cortex-M33, doubles use `r2` (low 32 bits) and `r3` (high 32 bits). + +5. **Clean fractions have zero low words** - Values like `42.5` have `0x00000000` in the low word; complex fractions like `42.52525` have non-zero low words. + +6. **Inline assembly controls hardware directly** - The `mcrr` coprocessor instruction talks to the GPIO block without any SDK overhead. + +7. **Binary patching works on any data type** - Integers, floats, and doubles can all be patched in Ghidra using the same workflow. + +--- + +## Glossary + +| Term | Definition | +| ----------------------- | ------------------------------------------------------------------------------ | +| **Bias** | Constant added to the exponent in IEEE 754 (127 for float, 1023 for double) | +| **Double** | 64-bit floating-point type following IEEE 754 double-precision format | +| **Exponent** | Part of IEEE 754 encoding that determines the magnitude of the number | +| **Float** | 32-bit floating-point type following IEEE 754 single-precision format | +| **FUNCSEL** | Function Select - register field that assigns a GPIO pin's function (e.g., SIO)| +| **GPIO** | General Purpose Input/Output - controllable pins on a microcontroller | +| **IEEE 754** | International standard for floating-point arithmetic and binary encoding | +| **Inline Assembly** | Assembly code embedded directly within C source using `__asm volatile` | +| **int8_t** | Signed 8-bit integer type (-128 to 127) | +| **IO_BANK0** | Register block at `0x40028000` that controls GPIO pin function selection | +| **Mantissa** | Fractional part of IEEE 754 encoding (23 bits for float, 52 bits for double) | +| **mcrr** | ARM coprocessor register transfer instruction used for GPIO control | +| **PADS_BANK0** | Register block at `0x40038000` that controls GPIO pad electrical properties | +| **Promotion** | Automatic conversion of a smaller type to a larger type (float -> double) | +| **Register Pair** | Two 32-bit registers (r2:r3) used together to hold a 64-bit value | +| **UF2** | USB Flashing Format - file format for Pico 2 firmware | +| **uint8_t** | Unsigned 8-bit integer type (0 to 255) | + +--- + +## Additional Resources + +### GPIO Coprocessor Reference + +The RP2350 GPIO coprocessor instructions: + +| Instruction | Description | +| --------------------------- | ---------------------------- | +| `mcrr p0, #4, Rt, Rt2, c0` | Set/clear GPIO output | +| `mcrr p0, #4, Rt, Rt2, c4` | Set/clear GPIO output enable | + +### RP2350 Memory Map Quick Reference + +| Address | Description | +| ------------ | ------------------------ | +| `0x10000000` | XIP Flash (code) | +| `0x20000000` | SRAM (data) | +| `0x40028000` | IO_BANK0 (GPIO control) | +| `0x40038000` | PADS_BANK0 (pad control) | +| `0xd0000000` | SIO (single-cycle I/O) | + +### IEEE 754 Encoding Formula + +``` ++-----------------------------------------------------------------+ +| Float (32-bit): [1 sign] [8 exponent] [23 mantissa] | +| Double (64-bit): [1 sign] [11 exponent] [52 mantissa] | +| | +| Value = (-1)^sign * 2^(exponent - bias) * (1 + mantissa) | +| | +| Float bias: 127 | +| Double bias: 1023 | ++-----------------------------------------------------------------+ +``` + +--- + +**Remember:** Every binary you encounter in the real world can be analyzed and understood using these same techniques. Whether it's an integer, a float, or a double - it's all just bits waiting to be decoded. Practice makes perfect! + +Happy hacking! + diff --git a/WEEK05/slides/WEEK05-IMG00.svg b/WEEK05/slides/WEEK05-IMG00.svg new file mode 100644 index 0000000..77db88a --- /dev/null +++ b/WEEK05/slides/WEEK05-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 05 + + +Integers and Floats in Embedded Systems: +Debugging and Hacking Integers and Floats +w/ Intermediate GPIO Output Analysis + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEK05/slides/WEEK05-IMG01.svg b/WEEK05/slides/WEEK05-IMG01.svg new file mode 100644 index 0000000..1659bfa --- /dev/null +++ b/WEEK05/slides/WEEK05-IMG01.svg @@ -0,0 +1,77 @@ + + + + + +Integer Data Types +Fixed-Size Types for Embedded Systems + + + +uint8_t +Unsigned 8-bit +1 byte +Range: +0 to 255 +Ages, counts, always positive + + + +int8_t +Signed 8-bit +1 byte +Range: +-128 to 127 +Temperature, can be negative + + + +uint16_t +Unsigned 16-bit +2 bytes +Range: +0 to 65,535 +Sensor readings, medium values + + + +uint32_t +Unsigned 32-bit +4 bytes +Range: +0 to ~4 billion +Addresses, timestamps + + + +Code Example + +uint8_t age = 43; +// unsigned, 0-255 +int8_t range = -42; +// signed, -128 to 127 + + + +Key Insight +The +u +prefix means +unsigned +(no negatives) +Without +u += signed (allows negatives) +Choose the smallest type that fits your data + \ No newline at end of file diff --git a/WEEK05/slides/WEEK05-IMG02.svg b/WEEK05/slides/WEEK05-IMG02.svg new file mode 100644 index 0000000..229f5e9 --- /dev/null +++ b/WEEK05/slides/WEEK05-IMG02.svg @@ -0,0 +1,79 @@ + + + + + +Two's Complement +How Negative Numbers are Stored + + + +Encoding -42 as int8_t + + +Step 1: Start +42 = 0x2A + + +Step 2: Flip +~0x2A = 0xD5 + + +Step 3: Add 1 +0xD5+1 = 0xD6 + +Binary: +00101010 +-> +11010101 +-> +11010110 + +Result: -42 stored as 0xD6 in memory +Top bit = 1 means negative + + + +Signed vs Unsigned: Same Bits! + +Hex +Binary +uint8_t +int8_t + + + +0x2A +00101010 +42 +42 + +0xD6 +11010110 +214 +-42 + +Same byte 0xD6 = 214 unsigned, -42 signed + + + +GDB Verification + + +(gdb) +x/1xb &range +0x200003e7: +0xd6 +// -42 in memory + \ No newline at end of file diff --git a/WEEK05/slides/WEEK05-IMG03.svg b/WEEK05/slides/WEEK05-IMG03.svg new file mode 100644 index 0000000..497a13d --- /dev/null +++ b/WEEK05/slides/WEEK05-IMG03.svg @@ -0,0 +1,75 @@ + + + + + +Inline Assembly GPIO +Direct Hardware Control via ASM + + + +GPIO Init Loop (pins 16-19) + + +1. Config Pad +PADS_BANK0 +Clear OD+ISO, set IE +0x40038000 + + +2. Set Function +IO_BANK0 +FUNCSEL = 5 (SIO) +0x40028004 + + +3. Enable Out +GPIO Coprocessor +mcrr p0,#4,r4,r5,c4 +Output Enable + +Loop: r0 = 16 to 19 +Red, Green, Blue, Yellow LEDs +Each pin: pad config + function select + OE + + + +Blink Loop + + +mcrr p0,#4,r4,r5,c0 + +r4 = pin, r5 = value +c0 = output value register +r5=1 ON, r5=0 OFF + + + +Pin Cycling + + +pin++; +if (pin > 18) pin=16; + +Cycles: 16 -> 17 -> 18 -> 16 +Red -> Green -> Blue -> repeat + + + +Why Inline Assembly? +gpio_put(16,1) calls +mcrr +underneath +Inline ASM shows what the SDK does at hardware level + \ No newline at end of file diff --git a/WEEK05/slides/WEEK05-IMG04.svg b/WEEK05/slides/WEEK05-IMG04.svg new file mode 100644 index 0000000..ecfa467 --- /dev/null +++ b/WEEK05/slides/WEEK05-IMG04.svg @@ -0,0 +1,83 @@ + + + + + +IEEE 754 Float +32-bit Single Precision Encoding + + + +Float Bit Layout (32 bits) + + +S + + +Exponent (8) + + +Mantissa (23) + +1 bit +sign + +8 bits +bias=127 + +23 bits ++implicit 1 + + + +Decode Formula +Value = (-1)^sign × 2^(exp-127) × (1 + mantissa) +Sign determines +/- Exponent scales with bias 127 Mantissa adds precision +Special cases: exp=0 or 255 (denorm, inf, NaN) + + + +Decode Example: 42.5 + +Sign Bit: 0 +Positive number + +Exponent: 10000100 += 132 decimal + +Bias subtraction: 132 - 127 = 5 + +Mantissa: 01010100...0 += 1.010101 + +Combine: 1.010101 × 2^5 = 42.5 + +Hex: 0x422A0000 + +Binary: 01000010001010100000000000000000 + + + +32-bit Storage Comparison + +Integer: +Exact values +Range: -2.1B to +2.1B +No decimals + +Float: +Approximate values +Range: ±10^38 +~7 sig digits + \ No newline at end of file diff --git a/WEEK05/slides/WEEK05-IMG05.svg b/WEEK05/slides/WEEK05-IMG05.svg new file mode 100644 index 0000000..90e591f --- /dev/null +++ b/WEEK05/slides/WEEK05-IMG05.svg @@ -0,0 +1,76 @@ + + + + + +Float in Ghidra +Analyzing 42.5 in the Binary + + + +Decompiled main() + + +int main(void) { +stdio_init_all(); +uVar1 = DAT_1000024c; +do { +printf(fmt,0,uVar1); +} while(true); + + + +Key Discovery + +printf with %f always +receives a +double +(64-bit) + +C promotes float to double +for variadic functions! + + +42.5 sent as double + + + +Register Pair r2:r3 + + +r2 (low): +0x00000000 + + +r3 (high): +0x40454000 + +Combined: 0x4045400000000000 += 42.5 + + + +Assembly View + + +1000023a +00 24 +movs r4, #0x0 +// r2 = 0 + +1000023c +03 4d +ldr r5,[DAT...] +// r3 = 0x40454000 + \ No newline at end of file diff --git a/WEEK05/slides/WEEK05-IMG06.svg b/WEEK05/slides/WEEK05-IMG06.svg new file mode 100644 index 0000000..3c82e9e --- /dev/null +++ b/WEEK05/slides/WEEK05-IMG06.svg @@ -0,0 +1,87 @@ + + + + + +Patching Float +Changing 42.5 to 99.0 in Ghidra + + + +Calculate 99.0 as Double + +99 decimal = +1100011 +binary + +Normalize: +1.100011 x 2^6 + +Sign: +0 +Exp: +6+1023 = 1029 + +Mantissa: +100011000...0 + +Full double: 0x4058C00000000000 + + + +Before (42.5) + + +r2: +0x00000000 + + +r3: +0x40454000 + +Output: fav_num: 42.500000 + + +After (99.0) + + +r2: +0x00000000 +same! + + +r3: +0x4058C000 +changed + +Output: fav_num: 99.000000 + + + +Patch in Ghidra + + +1. Window: Bytes +Open byte editor + + +2. Find 00404540 +High word of 42.5 + + +3. Patch 00C05840 +High word of 99.0 + +Only one word to patch (low word is 0) + \ No newline at end of file diff --git a/WEEK05/slides/WEEK05-IMG07.svg b/WEEK05/slides/WEEK05-IMG07.svg new file mode 100644 index 0000000..d67d52e --- /dev/null +++ b/WEEK05/slides/WEEK05-IMG07.svg @@ -0,0 +1,75 @@ + + + + + +Double Precision +IEEE 754 64-bit Floating Point + + + +64-Bit Layout + + +Sign +1 bit + + +Exponent +11 bits (bias 1023) + + +Mantissa (Fraction) +52 bits + +Formula: (-1)^S x 1.Mantissa x 2^(Exp-1023) + + + +Encoding 42.52525 + +Sign: +0 (positive) + +Exponent: +5 + 1023 = 1028 += 0x404 (hex) + +Mantissa: +0101010000110011... + +Full 64-bit hex: +0x4045433B645A1CAC + + + +Float (32-bit) + +Size: 4 bytes +Exp: 8 bits +Mantissa: 23 bits +Precision: ~7 digits +Bias: 127 +1 register (ARM) + + +Double (64-bit) + +Size: 8 bytes +Exp: 11 bits +Mantissa: 52 bits +Precision: ~15 digits +Bias: 1023 +2 registers (r2:r3) + \ No newline at end of file diff --git a/WEEK05/slides/WEEK05-IMG08.svg b/WEEK05/slides/WEEK05-IMG08.svg new file mode 100644 index 0000000..ad7749c --- /dev/null +++ b/WEEK05/slides/WEEK05-IMG08.svg @@ -0,0 +1,71 @@ + + + + + +Double in Ghidra +Analyzing 42.52525 in memory + + + +Decompiled main() + + +int main(void) { +double fav_num + = 42.52525; +stdio_init_all(); +} + + + +Key Difference + +Float (42.5): +r2 = 0x00000000 (zero!) +r3 = 0x40454000 + +Double (42.52525): +r2 = 0x645A1CAC (non-zero!) + + + +Register Pair r2:r3 + + +r3 (HIGH): +0x4045433B + + +r2 (LOW): +0x645A1CAC + +Combined: 0x4045433B645A1CAC = 42.52525 + + + +Assembly (ldrd) + + +ldrd r2,r3,[PC,#0x10] +; Loads BOTH words at once +; r2 gets low word +; r3 gets high word + + +ldrd = Load +Register +Doubleword +ARM Cortex-M33 + \ No newline at end of file diff --git a/WEEK05/slides/WEEK05-IMG09.svg b/WEEK05/slides/WEEK05-IMG09.svg new file mode 100644 index 0000000..fcf359f --- /dev/null +++ b/WEEK05/slides/WEEK05-IMG09.svg @@ -0,0 +1,88 @@ + + + + + +Patching Double +Changing 42.52525 to 99.99 + + + +99.99 as IEEE 754 Double + +Sign: +0 +Exp: +6 + 1023 = 1029 + +Result: +0x4058FF5C28F5C28F + +r3 (HIGH) = 0x4058FF5C +r2 (LOW) = 0x28F5C28F + + + +Before (42.52525) + + +r3: +0x4045433B + + +r2: +0x645A1CAC + +printf: 42.525250 +Both words non-zero + + +After (99.99) + + +r3: +0x4058FF5C +changed + + +r2: +0x28F5C28F +changed + +printf: 99.990000 +BOTH words changed! + + + +Float Patch + +Words changed: +1 +r2 (low): +stays 0x0 +r3 (high): +patched +Easier to patch + + +Double Patch + +Words changed: +2 +r2 (low): +patched +r3 (high): +patched +Both words must change + \ No newline at end of file diff --git a/WEEK05/slides/WEEK05-IMG10.svg b/WEEK05/slides/WEEK05-IMG10.svg new file mode 100644 index 0000000..ce71556 --- /dev/null +++ b/WEEK05/slides/WEEK05-IMG10.svg @@ -0,0 +1,101 @@ + + + + + +IEEE 754 & Data Types +Data Types and IEEE 754 Reference + + + +IEEE 754 Hex Values + + +Value +Float (32b) +Double (64b) + + + + +42.0 +0x42280000 +0x4045000000000000 + + +42.5 +0x422A0000 +0x4045400000000000 + + +99.0 +0x42C60000 +0x4058C00000000000 + + +99.99 +0x42C7F5C3 +0x4058FF5C28F5C28F + + +3.14 +0x4048F5C3 +0x40091EB851EB851F + + +100.0 +0x42C80000 +0x4059000000000000 + +Tip: float low word often 0x0; double low word usually non-zero + + + +Patching Workflow + + +1. Identify +float / double + + +2. Check r2 +zero = float + + +3. Calculate +new hex value + + +4. Patch +in byte editor + + +5. Export +UF2 + test + + + +Integer Types + +uint8_t: 0-255 +int8_t: -128 to 127 +Two's complement for signed + + +Key Insight + +Float: patch 1 word +Double: patch 2 words +Check r2 to detect type + \ No newline at end of file diff --git a/WEEK06/WEEK06-SLIDES.pdf b/WEEK06/WEEK06-SLIDES.pdf new file mode 100644 index 0000000..54ad3ac Binary files /dev/null and b/WEEK06/WEEK06-SLIDES.pdf differ diff --git a/WEEK06/WEEK06.md b/WEEK06/WEEK06.md new file mode 100644 index 0000000..0cadd8c --- /dev/null +++ b/WEEK06/WEEK06.md @@ -0,0 +1,1269 @@ +# Week 6: Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics + +## What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand the difference between regular (automatic) variables and static variables +- Know where different types of variables are stored (stack vs static storage) +- Configure GPIO pins as inputs and use internal pull-up resistors +- Read button states using `gpio_get()` and control LEDs based on input +- Use GDB to examine how the compiler handles static vs automatic variables +- Identify compiler optimizations by stepping through assembly +- Hack variable values and invert GPIO input/output logic using a hex editor +- Convert patched binaries to UF2 format for flashing + +--- + +## Part 1: Understanding Static Variables + +### What is a Static Variable? + +A **static variable** is a special kind of variable that "remembers" its value between function calls or loop iterations. Unlike regular variables that get created and destroyed each time, static variables **persist** for the entire lifetime of your program. + +Think of it like this: +- **Regular variable:** Like writing on a whiteboard that gets erased after each class +- **Static variable:** Like writing in a notebook that you keep forever + +``` ++-----------------------------------------------------------------+ +| Regular vs Static Variables | +| | +| REGULAR (automatic): | +| +------------------------------------------------------------+ | +| | Loop 1: Create -> Set to 42 -> Increment to 43 -> Destroy | +| | Loop 2: Create -> Set to 42 -> Increment to 43 -> Destroy | +| | Loop 3: Create -> Set to 42 -> Increment to 43 -> Destroy | +| | Result: Always appears as 42! | +| +------------------------------------------------------------+ | +| | +| STATIC: | +| +------------------------------------------------------------+ | +| | Loop 1: Already exists -> Read 42 -> Increment -> Store 43 | +| | Loop 2: Already exists -> Read 43 -> Increment -> Store 44 | +| | Loop 3: Already exists -> Read 44 -> Increment -> Store 45 | +| | Result: Keeps incrementing! | +| +------------------------------------------------------------+ | +| | ++-----------------------------------------------------------------+ +``` + +### The `static` Keyword + +In C, you declare a static variable by adding the `static` keyword: + +```c +uint8_t regular_fav_num = 42; // Regular - recreated each time +static uint8_t static_fav_num = 42; // Static - persists forever +``` + +### Where Do Variables Live in Memory? + +Different types of variables are stored in different memory locations: + +| Variable Type | Storage Location | Lifetime | Example | +| ----------------- | ---------------- | ------------------------- | ------------------------------------ | +| Automatic (local) | Stack | Until function/block ends | `uint8_t x = 5;` | +| Static | Static Storage | Entire program lifetime | `static uint8_t x = 5;` | +| Global | Static Storage | Entire program lifetime | `uint8_t x = 5;` (outside functions) | +| Dynamic (heap) | Heap | Until `free()` is called | `malloc(sizeof(int))` | + +### Stack vs Static Storage vs Heap + +``` ++-----------------------------------------------------------------+ +| Memory Layout | +| | +| +-------------------+ High Address (0x20082000) | +| | STACK | ?? Automatic/local variables | +| | (grows down) | Created/destroyed per function | +| +-------------------+ | +| | | | +| | (free space) | | +| | | | +| +-------------------+ | +| | HEAP | ?? Dynamic allocation (malloc/free) | +| | (grows up) | | +| +-------------------+ | +| | .bss section | ?? Uninitialized static/global vars | +| +-------------------+ | +| | .data section | ?? Initialized static/global vars | +| +-------------------+ Low Address (0x20000000) | +| | ++-----------------------------------------------------------------+ +``` + +**Key Point:** Static variables are NOT on the heap! They live in a fixed location in the `.data` section (if initialized) or `.bss` section (if uninitialized). This is different from heap memory which is dynamically allocated at runtime. + +### What Happens with Overflow? + +Since `static_fav_num` is a `uint8_t` (unsigned 8-bit), it can only hold values 0-255. What happens when it reaches 255 and we add 1? + +``` +255 + 1 = 256... but that doesn't fit in 8 bits! +Binary: 11111111 + 1 = 100000000 (9 bits) +The 9th bit is lost, so we get: 00000000 = 0 +``` + +This is called **overflow** or **wrap-around**. The value "wraps" back to 0 and starts counting again! + +--- + +## Part 2: Understanding GPIO Inputs + +### Input vs Output + +So far, we've used GPIO pins as **outputs** to control LEDs. Now we'll learn to use them as **inputs** to read button states! + +``` ++-----------------------------------------------------------------+ +| GPIO Direction | +| | +| OUTPUT (what we've done before): | +| +---------+ | +| | Pico | -------? LED | +| | GPIO 16 | (We control the LED) | +| +---------+ | +| | +| INPUT (new this week): | +| +---------+ | +| | Pico | ?------- Button | +| | GPIO 15 | (We read the button state) | +| +---------+ | +| | ++-----------------------------------------------------------------+ +``` + +### The Floating Input Problem + +When a GPIO pin is set as an input but nothing is connected, it's called a **floating input**. The voltage on the pin is undefined and can randomly read as HIGH (1) or LOW (0) due to electrical noise. + +``` ++-----------------------------------------------------------------+ +| Floating Input = Random Values! | +| | +| GPIO Pin (no connection): | +| Reading 1: HIGH | +| Reading 2: LOW | +| Reading 3: HIGH | +| Reading 4: HIGH | +| Reading 5: LOW | +| (Completely unpredictable!) | +| | ++-----------------------------------------------------------------+ +``` + +### Pull-Up and Pull-Down Resistors + +To solve the floating input problem, we use **pull resistors**: + +| Resistor Type | Default State | When Button Pressed | +| ------------- | ------------- | ------------------- | +| **Pull-Up** | HIGH (1) | LOW (0) | +| **Pull-Down** | LOW (0) | HIGH (1) | + +The Pico 2 has **internal** pull resistors that you can enable with software - no external components needed! + +``` ++-----------------------------------------------------------------+ +| Pull-Up Resistor (what we're using) | +| | +| 3.3V | +| | | +| + (internal pull-up resistor) | +| | | +| +------? GPIO 15 (reads HIGH normally) | +| | | +| +-+-+ | +| |BTN| ?? Button connects GPIO to GND when pressed | +| +-+-+ | +| | | +| GND | +| | +| Button NOT pressed: GPIO reads 1 (HIGH) | +| Button PRESSED: GPIO reads 0 (LOW) | +| | ++-----------------------------------------------------------------+ +``` + +### GPIO Input Functions + +| Function | Purpose | +| ---------------------------- | --------------------------------------- | +| `gpio_init(pin)` | Initialize a GPIO pin for use | +| `gpio_set_dir(pin, GPIO_IN)` | Set pin as INPUT | +| `gpio_pull_up(pin)` | Enable internal pull-up resistor | +| `gpio_pull_down(pin)` | Enable internal pull-down resistor | +| `gpio_get(pin)` | Read the current state (returns 0 or 1) | + +### The Ternary Operator + +The code uses a **ternary operator** to control the LED based on button state: + +```c +gpio_put(LED_GPIO, pressed ? 0 : 1); +``` + +This is a compact if-else statement: +- If `pressed` is **true (1)**: output `0` (LED OFF... wait, that seems backwards!) +- If `pressed` is **false (0)**: output `1` (LED ON) + +**Why is it inverted?** Because of the pull-up resistor! +- Button **released** -> GPIO reads `1` -> `pressed = 1` -> output `0` -> LED OFF +- Button **pressed** -> GPIO reads `0` -> `pressed = 0` -> output `1` -> LED ON + +A clearer way to write this: +```c +gpio_put(LED_GPIO, !gpio_get(BUTTON_GPIO)); +``` + +--- + +## Part 3: Understanding Compiler Optimizations + +### Why Does Code Disappear? + +When you compile code, the compiler tries to make it faster and smaller. This is called **optimization**. Sometimes the compiler removes code that it thinks has no effect! + +**Example from our code:** +```c +while (true) { + uint8_t regular_fav_num = 42; // Created + regular_fav_num++; // Incremented to 43 + // But then it's destroyed and recreated as 42 next loop! +} +``` + +The compiler sees that incrementing `regular_fav_num` has no lasting effect (because it's recreated as 42 each loop), so it may **optimize away** the increment operation entirely! + +### Function Inlining + +Sometimes the compiler **inlines** functions, meaning it replaces a function call with the function's code directly. + +**Original code:** +```c +gpio_pull_up(BUTTON_GPIO); +``` + +**What the compiler might do:** +```c +// Instead of calling gpio_pull_up, it calls the underlying function: +gpio_set_pulls(BUTTON_GPIO, true, false); +``` + +This is why when you look for `gpio_pull_up` in the binary, you might find `gpio_set_pulls` instead! + +--- + +## Part 4: Setting Up Your Environment + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. A Raspberry Pi Pico Debug Probe +3. OpenOCD installed and configured +4. GDB (`arm-none-eabi-gdb`) installed +5. Python installed (for UF2 conversion) +6. A serial monitor (PuTTY, minicom, or screen) +7. A push button connected to GPIO 15 +8. An LED connected to GPIO 16 (or use the breadboard LED) +9. A hex editor (HxD, ImHex, or similar) +10. The sample project: `0x0014_static-variables` + +### Hardware Setup + +Connect your button like this: +- One side of button -> GPIO 15 +- Other side of button -> GND + +The internal pull-up resistor provides the 3.3V connection, so you only need to connect to GND! + +``` ++-----------------------------------------------------------------+ +| Breadboard Wiring | +| | +| Pico 2 | +| +----------+ | +| | | | +| | GPIO 15 |--------+ | +| | | | | +| | GPIO 16 |--------+---? LED (with resistor to GND) | +| | | | | +| | GND |--------+---+ | +| | | | | | +| +----------+ +-+-+ | | +| |BTN|-+ | +| +---+ | +| | ++-----------------------------------------------------------------+ +``` + +### Project Structure + +``` +Embedded-Hacking/ ++-- 0x0014_static-variables/ +| +-- build/ +| | +-- 0x0014_static-variables.uf2 +| | +-- 0x0014_static-variables.elf +| +-- 0x0014_static-variables.c ++-- uf2conv.py +``` + +--- + +## Part 5: Hands-On Tutorial - Static Variables and GPIO Input + +### Step 1: Review the Source Code + +Let's examine the static variables code: + +**File: `0x0014_static-variables.c`** + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + stdio_init_all(); + + const uint BUTTON_GPIO = 15; + const uint LED_GPIO = 16; + bool pressed = 0; + + gpio_init(BUTTON_GPIO); + gpio_set_dir(BUTTON_GPIO, GPIO_IN); + gpio_pull_up(BUTTON_GPIO); + + gpio_init(LED_GPIO); + gpio_set_dir(LED_GPIO, GPIO_OUT); + + while (true) { + uint8_t regular_fav_num = 42; + static uint8_t static_fav_num = 42; + + printf("regular_fav_num: %d\r\n", regular_fav_num); + printf("static_fav_num: %d\r\n", static_fav_num); + + regular_fav_num++; + static_fav_num++; + + pressed = gpio_get(BUTTON_GPIO); + gpio_put(LED_GPIO, pressed ? 0 : 1); + } +} +``` + +**What this code does:** + +1. **Line 6-8:** Defines constants for button (GPIO 15) and LED (GPIO 16) pins +2. **Line 10-12:** Sets up GPIO 15 as input with internal pull-up resistor +3. **Line 14-15:** Sets up GPIO 16 as output for the LED +4. **Line 18-19:** Creates two variables: + - `regular_fav_num` - a normal local variable (recreated each loop) + - `static_fav_num` - a static variable (persists across loops) +5. **Line 21-22:** Prints both values to the serial terminal +6. **Line 24-25:** Increments both values +7. **Line 27-28:** Reads button and controls LED accordingly + +### Step 2: Flash the Binary to Your Pico 2 + +1. Hold the BOOTSEL button on your Pico 2 +2. Plug in the USB cable (while holding BOOTSEL) +3. Release BOOTSEL - a drive called "RPI-RP2" appears +4. Drag and drop `0x0014_static-variables.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 3: Open Your Serial Monitor + +Open PuTTY, minicom, or screen and connect to your Pico's serial port. + +**You should see output like this:** + +``` +... +regular_fav_num: 42 +static_fav_num: 42 +regular_fav_num: 42 +static_fav_num: 43 +regular_fav_num: 42 +static_fav_num: 44 +regular_fav_num: 42 +static_fav_num: 45 +... +``` + +**Notice the difference:** +- `regular_fav_num` stays at 42 every time (it's recreated each loop) +- `static_fav_num` increases each time (it persists and remembers its value) + +### Step 4: Test the Button + +Now test the button behavior: +- **Button NOT pressed:** LED should be OFF +- **Button PRESSED:** LED should turn ON + +That may feel backwards at first glance, but it matches the pull-up + ternary logic in the code. We'll hack this later to make the behavior more intuitive. + +### Step 5: Watch for Overflow + +Keep the program running and watch `static_fav_num`. After 255, you'll see: + +``` +static_fav_num: 254 +static_fav_num: 255 +static_fav_num: 0 ?? Wrapped around! +static_fav_num: 1 +static_fav_num: 2 +... +``` + +This demonstrates unsigned integer overflow! + +--- + +## Part 6: Debugging with GDB (Dynamic Analysis) + +> ? **REVIEW:** This setup is identical to previous weeks. If you need a refresher on OpenOCD and GDB connection, refer back to Week 3 Part 6. + +### Starting the Debug Session + +**Terminal 1 - Start OpenOCD:** + +```powershell +openocd -s "$env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev\scripts" -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000" +``` + +**Terminal 2 - Start GDB:** + +```cmd +arm-none-eabi-gdb build\0x0014_static-variables.elf +``` + +**Connect to target:** + +```gdb +(gdb) target extended-remote :3333 +(gdb) monitor reset halt +``` + +### Step 6: Examine Main Function + +Let's examine the main function at its entry point. First, disassemble from the start: + +``` +x/38i 0x10000234 +``` + +You should see output like: + +``` +(gdb) x/38i 0x10000234 + 0x10000234
: push {r4, lr} + 0x10000236 : bl 0x10003014 + 0x1000023a : movs r0, #15 + 0x1000023c : bl 0x10000300 + 0x10000240 : movs r0, #15 + 0x10000242 : mov.w r3, #0 + 0x10000246 : mcrr 0, 4, r0, r3, cr4 + 0x1000024a : movs r2, #0 + 0x1000024c : movs r1, #1 + 0x1000024e : bl 0x100002d8 + 0x10000252 : movs r0, #16 + 0x10000254 : bl 0x10000300 + 0x10000258 : movs r3, #16 + 0x1000025a : mov.w r2, #1 + 0x1000025e : mcrr 0, 4, r3, r2, cr4 + 0x10000262 : ldr r4, [pc, #44] @ (0x10000290 ) + 0x10000264 : movs r1, #42 @ 0x2a + 0x10000266 : ldr r0, [pc, #44] @ (0x10000294 ) + 0x10000268 : bl 0x100031a4 <__wrap_printf> + 0x1000026c : ldrb r1, [r4, #0] + 0x1000026e : ldr r0, [pc, #40] @ (0x10000298 ) + 0x10000270 : bl 0x100031a4 <__wrap_printf> + 0x10000274 : mov.w r1, #3489660928 @ 0xd0000000 + 0x10000278 : ldrb r3, [r4, #0] + 0x1000027a : movs r2, #16 + 0x1000027c : adds r3, #1 + 0x1000027e : strb r3, [r4, #0] + 0x10000280 : ldr r3, [r1, #4] + 0x10000282 : ubfx r3, r3, #15, #1 + 0x10000286 : eor.w r3, r3, #1 + 0x1000028a : mcrr 0, 4, r2, r3, cr0 + 0x1000028e : b.n 0x10000264 + 0x10000290 : lsls r0, r5, #22 + 0x10000292 : movs r0, #0 + 0x10000294 : adds r5, #96 @ 0x60 + 0x10000296 : asrs r0, r0, #32 + 0x10000298 : adds r5, #120 @ 0x78 + 0x1000029a : asrs r0, r0, #32 +``` + +### Step 7: Set a Breakpoint at Main + +``` +b *0x10000234 +c +``` + +### Step 8: Examine the Static Variable Location + +Static variables live at fixed RAM addresses. But how do we find that address? Look at `0x10000262` in the disassembly from Step 6: + +``` +(gdb) x/16i 0x10000234 +=> 0x10000234
: push {r4, lr} + 0x10000236 : bl 0x10003014 + 0x1000023a : movs r0, #15 + 0x1000023c : bl 0x10000300 + 0x10000240 : movs r0, #15 + 0x10000242 : mov.w r3, #0 + 0x10000246 : mcrr 0, 4, r0, r3, cr4 + 0x1000024a : movs r2, #0 + 0x1000024c : movs r1, #1 + 0x1000024e : bl 0x100002d8 + 0x10000252 : movs r0, #16 + 0x10000254 : bl 0x10000300 + 0x10000258 : movs r3, #16 + 0x1000025a : mov.w r2, #1 + 0x1000025e : mcrr 0, 4, r3, r2, cr4 + 0x10000262 : ldr r4, [pc, #44] @ (0x10000290 ) +``` + +This loads `r4` from the **literal pool** at address `0x10000290`. The literal pool stores constants that are too large for immediate encoding - in this case, a 32-bit RAM address. Let's examine what's stored there: + +```gdb +(gdb) x/1wx 0x10000290 +0x10000290 : 0x200005a8 +``` + +That's `0x200005a8` - the RAM address of `static_fav_num`! The compiler placed this address in the literal pool because it can't encode a full 32-bit address in a single Thumb instruction. + +> **Common confusion three things that trip people up here:** +> +> **1. GDB shows `` instead of ``.** +> `static_fav_num` is a function-local static, so it does not appear in GDB's global symbol table. GDB labels the address with the nearest *named* global symbol it can find in this case an SDK-internal spin-lock variable that happens to be nearby. The address `0x200005a8` is still correct; the label is just GDB's best guess at a name. +```gdb +(gdb) x/x 0x200005a8 +0x200005a8 : 0x0000122a +``` +> +> **2. `x/x 0x200005a8` shows `0x0000122a`, not `0x0000002a` (42).** +> `x/x` reads a **4-byte word** (32 bits). `static_fav_num` is a `uint8_t` only **1 byte**. The low byte of `0x0000122a` is `0x2a` = 42. That *is* the variable. The upper bytes belong to whatever happens to be in adjacent RAM. To read exactly one byte and see `42`, use: +```gdb +(gdb) x/1ub 0x200005a8 +0x200005a8 : 42 +``` +> +> **3. `x/x *0x200005a8` shows junk at address `0x122a`.** +> The `*` is a pointer dereference. GDB first reads the 4-byte word at `0x200005a8` (= `0x0000122a`), then treats that *value* as a new address and reads from `0x0000122a`. You are asking GDB to follow a pointer that was never meant to be a pointer the result is meaningless. Drop the `*`: use `x/1ub 0x200005a8` to read the variable's value directly. + +> Tip: **Why did the disassembly at `0x10000290` show `lsls r0, r5, #22` instead?** Because `x/i` (disassemble) interprets raw data as instructions. The bytes `A8 05 00 20` at that address are the little-endian encoding of `0x200005A8`, but GDB's disassembler doesn't know it's data - it tries to decode it as a Thumb instruction. Using `x/wx` (examine as word) shows the actual value. + +### Step 9: Step Through the Loop + +The loop body starts at `0x10000264`. Set a breakpoint there and continue: + +```gdb +(gdb) b *0x10000264 +(gdb) c +(gdb) x/i $pc +=> 0x10000264 : movs r1, #42 @ 0x2a +``` + +GDB will stop at `movs r1, #42` that is the compiler's constant for `regular_fav_num`. Let's set a breakpoint and continue at `0x10000278`. + +```gdb +(gdb) b *0x10000278 +(gdb) c +(gdb) x/i $pc +=> 0x10000278 : ldrb r3, [r4, #0] +``` + +This is where the static variable is actually manipulated: + +```gdb +(gdb) x/4i $pc +=> 0x10000278 : ldrb r3, [r4, #0] <- load static_fav_num from RAM into r3 + 0x1000027a : movs r2, #16 <- load LED pin number (16) into r2 for later + 0x1000027c : adds r3, #1 <- increment r3 by 1 + 0x1000027e : strb r3, [r4, #0] <- store incremented value back to RAM +``` + +Confirm what `r4` actually contains right now: + +```gdb +(gdb) x/x $r4 +0x200005a8 : 0x2a +``` + +`r4` is `0x200005a8`, a RAM address. The value at that address is `0x2a` (42 in decimal), which is the current value of `static_fav_num`. After `adds r3, #1` and `strb r3, [r4, #0]` execute, examining `r4` again will show `0x0000002b` (43). + +**What is the literal pool?** + +A Thumb `ldr` instruction is only 16 or 32 bits wide. There is no room inside those bits to encode a full 32-bit constant like `0x200005a8`. To solve this, the compiler collects all such large constants and writes them into a small block of **raw data** embedded directly in the flash image this block is called a **literal pool** (also called a constant pool). + +**There is not one literal pool per flash, there is (at least) one per function.** Every function that references large constants gets its own pool appended to its code. A large function can have several pools scattered through it wherever the compiler decides to emit them. Each pool is purely data; the CPU must never try to execute it. + +**Where exactly is a literal pool placed?** Always **after an unconditional branch**, never before or in the middle of a straight-line code path. This is by design: the CPU falls through code sequentially, so the pool must be unreachable by fall-through. In our case the pool sits at `0x10000290`, directly after the `b.n 0x10000264` branch at `0x1000028e` that closes the `while (true)` loop: + +``` +0x1000028e: b.n 0x10000264 <- unconditional branch back to loop top +0x10000290: [literal pool] <- CPU never reaches here by fall-through +``` + +**Why does the pool have to be nearby?** The `ldr rN, [pc, #offset]` instruction encodes the offset in a limited number of bits. In standard Thumb the maximum reach is **1020 bytes** from the instruction. If the function is long enough that the end is out of range, the assembler emits an intermediate pool mid-function after the next unconditional branch it can find. This is in the ARM v8-M Architecture Reference Manual p. 672. + +**How the load works in our function:** At `0x10000262` the compiler emits: + +``` +ldr r4, [pc, #44] @ (0x10000290) +``` + +On ARM, `PC` reads as the address of the instruction **plus 4** during execution. So the effective address is `0x10000262 + 4 + 44 = 0x10000290`. That word in the pool contains `0x200005a8`, the RAM address of `static_fav_num`. The pool is also where the `printf` format-string pointers live (the other `ldr` instructions you saw in the disassembly). + +``` +Flash (read-only) ++-------------------------------------------+ RAM +| 0x10000262: ldr r4, [pc, #44] | +------------------+ +| ...loop body... | | 0x200005a8: 42 | <- static_fav_num +| 0x1000028e: b.n 0x10000264 (end of loop) | +------------------+ ++------- literal pool (data, not code) -----+ ^ +| 0x10000290: 0x200005a8 --------------------+----------------+ +| 0x10000294: ptr to "regular_fav_num: %d" | r4 holds this address +| 0x10000298: ptr to "static_fav_num: %d" | ++------- next function ---------------------+ +| 0x1000029c: push {r4} | ++-------------------------------------------+ +``` + +You can see this directly. Run `x/10i 0x1000028e` in GDB and you will get exactly this: + +```gdb +(gdb) x/10i 0x1000028e + (gdb) x/10i 0x1000028e + 0x1000028e : b.n 0x10000264 <- unconditional branch, loop top + 0x10000290 : lsls r0, r5, #22 <-+ + 0x10000292 : movs r0, #0 <-+-- word 1: 0x200005a8 (fixed RAM addr static_fav_num) + 0x10000294 : adds r5, #96 @ 0x60 <-+ + 0x10000296 : asrs r0, r0, #32 <-+-- word 2: 0x10004b60 (flash addr regular_fav_num text) + 0x10000298 : adds r5, #120 <-+ + 0x1000029a : asrs r0, r0, #32 <-+-- word 3: 0x10004b78 (flash addr static_fav_num text) +``` + +`0x10000290` - `0x1000029b` is the literal pool 12 bytes, 3 32-bit words. GDB's `x/i` (disassemble) has no idea those bytes are data, so it blindly decodes them as Thumb instructions and produces nonsense (`lsls r0, r5, #22`, etc.). The giveaway that you have entered pool territory is always the same: **garbled, implausible instructions immediately after an unconditional branch**. Use `x/wx` to read those addresses as data and you get the real values: + +```gdb +(gdb) x/3wx 0x10000290 +0x10000290 : 0x200005a8 0x10003560 0x10003578 +``` + +```gdb +(gdb) x/1ub 0x200005a8 +0x200005a8 : 42 +``` +- `0x200005a8` RAM address of `static_fav_num`, lives in the **`.data` section** (initialized static RAM, `0x20000000``0x200xxxxx`). It is `.data` and not `.bss` because the variable has an explicit initializer (`= 42`). `.bss` is for variables that are zero-initialized or have no initializer at all. + +```gdb +(gdb) x/s 0x10003560 +0x10003560: "regular_fav_num: %d\r\n" +``` +- `0x10003560` flash address of the `"regular_fav_num: %d\r\n"` string literal, lives in the **`.rodata` section** (read-only data) inside flash (`0x10000000`+). String literals are constants baked into the binary at compile time they never change and never move, so they stay in flash. + +```gdb +(gdb) x/s 0x10003578 +0x10003578: "static_fav_num: %d\r\n" +- `0x10003578` flash address of the `"static_fav_num: %d\r\n"` string literal, also in **`.rodata`** in flash for the same reason. + +So the pool contains addresses into three different regions: RAM `.data` (the static variable), and flash `.rodata` (both format strings). Only the RAM address needed to be in the pool, the flash addresses could in principle be reached other ways, but they are also too large to encode as 16-bit immediates, so they go in the pool too. + +**Why `.data` and not `.bss`?** The distinction matters: + +| Section | What goes here | Example declarations | Initial value stored in flash? | +|---------|---------------|----------------------|-------------------------------| +| `.data` | Any static-duration variable with a **non-zero initializer** | `static uint8_t x = 42;` (inside a function) | Yes the initial value lives in flash; startup copies it to RAM | +| `.data` | Same rule applies to **global** variables with non-zero initializers | `uint8_t g = 10;` (outside all functions) | Yes same startup copy | +| `.bss` | Any static-duration variable with **no initializer or zero initializer** | `static uint8_t x;` or `static uint8_t x = 0;` | No startup just zeroes the RAM range; no flash copy needed | +| `.bss` | Same rule for **global** variables that are zero/uninitialized | `uint8_t g;` or `uint8_t g = 0;` (outside functions) | No same zero-fill at startup | + +The rule is not about `static` specifically it is about **lifetime and initial value**. Any variable whose lifetime spans the whole program (static locals, globals) goes into `.data` or `.bss`. The `static` keyword inside a function just forces that lifetime. A global variable without `static` has the same lifetime and follows the same rule. + +`static_fav_num = 42` has an explicit non-zero initializer, so the linker puts it in `.data`. The startup code copies the initial value `42` from flash into RAM address `0x200005a8` before `main()` runs. If it had been `static uint8_t static_fav_num;` (no initializer), the linker would put it in `.bss` instead and the startup code would zero that RAM region no flash copy needed. + +**Why is there no pool entry for the address of `regular_fav_num`?** + +Because `regular_fav_num` is an **automatic (stack) variable** it has no fixed address. Automatic variables are allocated on the call stack at runtime, relative to the current stack pointer (`sp`). Their address changes every time the function runs and is different on every call. There is no constant 32-bit address to put in a pool. + +In this specific case the compiler went even further: it **never gave `regular_fav_num` a stack slot at all**. The compiler saw that the value is always `42` when printed, so it baked the constant `42` directly into the `movs r1, #42` instruction. The variable lives only in a register at the moment it is needed — no stack space reserved, no pool entry, no load-store cycle. + +**You can prove this with three GDB checks:** + +**Proof 1 `info locals` shows the value came from a register, not a stack slot:** + +```gdb +(gdb) b *0x10000264 +(gdb) c +(gdb) info locals +regular_fav_num = 42 '*' +static_fav_num = 42 '*' +BUTTON_GPIO = 15 +LED_GPIO = 16 +pressed = +``` + +The `'*'` suffix after `42` means GDB retrieved the value from a **register** (specifically `r1`, which holds `42` at this point), not from a stack slot. The variable has no fixed memory address — GDB can report its current value only because the right register happens to hold it right now. Notice that `pressed` is the one showing `` — the compiler removed it from tracking entirely because its value is never needed after the ternary expression. + +**Proof 2 The function prologue allocates no stack space for locals:** + +```gdb +(gdb) x/2i 0x10000234 + 0x10000234
: push {r4, lr} + 0x10000236 : bl 0x10003014 +``` + +The very first instruction is `push {r4, lr}`, that saves `r4` (a callee-saved register) and `lr` (return address). There is **no** `sub sp, #N` instruction after it. On ARM, allocating stack space for local variables requires subtracting from `sp` to reserve room. If `regular_fav_num` lived on the stack, there would be a `sub sp, #4` (or similar) here. There isn't, so no stack slot was ever created. + +**Proof 3 The loop body contains no store to the stack for `regular_fav_num`:** + +```gdb +(gdb) x/6i 0x10000264 +=> 0x10000264 : movs r1, #42 @ 0x2a + // constant 42 baked directly into instruction + 0x10000266 : ldr r0, [pc, #44] @ (0x10000294 ) + // load format string address from pool + 0x10000268 : bl 0x100031a4 <__wrap_printf> + // call printf + 0x1000026c : ldrb r1, [r4, #0] + // load static_fav_num from RAM + 0x1000026e : ldr r0, [pc, #40] @ (0x10000298 ) + // load format string address from pool + 0x10000270 : bl 0x100031a4 <__wrap_printf> + call printf +``` + +For `regular_fav_num`, the value `42` goes straight into `r1` via `movs`, it is never written to memory and never read back from memory. There is no `strb r1, [sp, #N]` storing it to the stack, and no `ldrb r1, [sp, #N]` loading it back. The variable exists only in the source code. In the binary it is just a number in an instruction encoding. + +`static_fav_num` is the exact opposite. It lives in the `.data` section of RAM, a fixed, known address (`0x200005a8`) that is set at link time and never changes for the life of the program. That fixed address is exactly the kind of value that cannot fit in a 16-bit Thumb instruction, so the linker writes it into the literal pool and the CPU fetches it with `ldr r4, [pc, #44]`. + +> **IMPORTANT:** Static variables are **not** on the heap. The heap is for dynamic memory (`malloc`/`free`), memory whose lifetime is controlled explicitly at runtime. Static and global variables live in the `.data` section (if initialized) or `.bss` section (if zero/uninitialized), a region of RAM with a fixed layout determined at link time, not at runtime. + +``` +Memory regions for our two variables: ++------------------------------------------+ +| FLASH (read-only, 0x10000000+) | +| - Code (instructions) | +| - Literal pool (our 3 words) | +| - String literals ("regular_fav_num...")| ++------------------------------------------+ +| RAM .data section (0x20000000+) | +| - static_fav_num @ 0x200005a8 <- pool | ++------------------------------------------+ +| STACK (grows down from 0x20082000) | +| - regular_fav_num (if not optimized) | +| frame-relative, no fixed address | ++------------------------------------------+ +| HEAP (grows up, between stack and .bss) | +| - malloc/free only, nothing here today | ++------------------------------------------+ +``` + +The pool ends at `0x1000029b`. At `0x1000029d` a new function begins, specifically `gpio_set_function`. The first two instructions, `push {r4}` (save caller's `r4` to the stack) and `mov.w r4, #256` (load a working constant), are a standard SDK function prologue, nothing to do with `main`. The `ldr r3, [pc, #48]`i r/. at `0x100002a3` (`gpio_set_function+6`) is that next function loading from **its own** literal pool further ahead in flash — proof that every function manages its own pool. + +### Step 10: Examine Register Values + +After breaking at `0x10000264`, check the registers: + +```gdb +(gdb) i r +``` + +What you will actually see: + +- `r4` = `0x200005a8` — the static variable's fixed RAM address. This was loaded from the literal pool in the function prologue and never changes across loop iterations. +- `r1` = leftover value from the previous `printf` call (e.g. `0x40039044`). The breakpoint is stopped **at** `movs r1, #42` — that instruction has not executed yet, so `r1` does not yet hold `42`. +- `r3` = leftover from prior operations (e.g. `0x10`). It will be used later in the loop body to load, increment, and store `static_fav_num`, but not yet. +- `pc` = `0x10000264` — the loop top, pointing at the `movs r1, #42` instruction. + +You can confirm `r4` holds the right address and value: + +```gdb +(gdb) x/1db $r4 +0x200005a8 : 42 +``` + +The `` label is just the nearest named global symbol in the SDK that GDB finds at that address — the actual variable stored there is our `static_fav_num`. + +### Step 11: Watch the Static Variable Change + +Now that we know the static variable lives at `0x200005a8`, examine it directly: + +```gdb +(gdb) x/1db 0x200005a8 +0x200005a8 : 42 +``` + +Step through a full loop iteration (back to `0x10000264`) and re-examine: + +```gdb +(gdb) c +(gdb) x/1db 0x200005a8 +0x200005a8 : 43 +``` + +The value incremented from 42 to 43! Each loop iteration, the `adds r3, #1` at `0x1000027c` bumps it by 1, and `strb r3, [r4, #0]` at `0x1000027e` writes it back to RAM. + +### Step 12: Examine GPIO State + +Read the GPIO input register to see the button state: + +```gdb +(gdb) x/1wx 0xd0000004 +0xd0000004: 0x00008003 +``` + +The SIO GPIO input register at `0xd0000004` shows the current state of all GPIO pins. Bit 15 corresponds to our button on GPIO 15. To extract just bit 15: + +```gdb +(gdb) p/x (*(unsigned int *)0xd0000004 >> 15) & 1 +$1 = 0x1 +``` + +- Returns `1` when button is **not pressed** (pull-up holds it HIGH) +- Returns `0` when button is **pressed** (connected to GND) + +TRY IT! + +--- + +## Part 7: Understanding the Assembly + +Now that we've explored the binary in GDB, let's make sense of the key patterns. + +### Step 13: Analyze the Regular Variable + +In GDB, examine the code at the start of the loop: + +```gdb +(gdb) x/5i 0x10000262 + 0x10000262 : ldr r4, [pc, #44] @ (0x10000290 ) +=> 0x10000264 : movs r1, #42 @ 0x2a + 0x10000266 : ldr r0, [pc, #44] @ (0x10000294 ) + 0x10000268 : bl 0x100031a4 <__wrap_printf> + 0x1000026c : ldrb r1, [r4, #0] +``` + +Look for this instruction: + +``` +0x10000264 : movs r1, #42 @ 0x2a +``` + +This loads the value `0x2a` (42 in decimal) directly into register `r1` for the first `printf` call. + +**Key insight:** The compiler **never allocated stack space** for `regular_fav_num`. Since it is always `42` when printed, the compiler bakes the constant directly into the `movs r1, #42` instruction. The `regular_fav_num++` after the print is also removed because it has no observable effect — the variable is recreated as `42` on the next loop iteration anyway. + +### Step 14: Analyze the Static Variable + +Examine the static variable operations in the second half of the loop body: + +```gdb +(gdb) x/10i 0x10000274 + 0x10000274 : mov.w r1, #3489660928 @ 0xd0000000 + 0x10000278 : ldrb r3, [r4, #0] + 0x1000027a : movs r2, #16 + 0x1000027c : adds r3, #1 + 0x1000027e : strb r3, [r4, #0] + 0x10000280 : ldr r3, [r1, #4] + 0x10000282 : ubfx r3, r3, #15, #1 + 0x10000286 : eor.w r3, r3, #1 + 0x1000028a : mcrr 0, 4, r2, r3, cr0 + 0x1000028e : b.n 0x10000264 +``` + +Look for the load-increment-store pattern using `r4` (which holds the static variable's RAM address): + +``` + ... + 0x10000278 : ldrb r3, [r4, #0] + 0x1000027a : movs r2, #16 + 0x1000027c : adds r3, #1 + 0x1000027e : strb r3, [r4, #0] + ... +``` + +Note that `r4` was loaded earlier at `0x10000262` via `ldr r4, [pc, #44]` - this pulled the static variable's RAM address (`0x200005a8`) from the literal pool at `0x10000290`. + +**Key insight:** The static variable lives at a **fixed RAM address** (`0x200005a8`). It's loaded, incremented, and stored back. The regular variable, by contrast, never gets a stack slot — the compiler holds it in a register and bakes the constant `42` directly into the instruction, so there is nothing to load or store. + +Verify the static variable value which should be `43`: + +```gdb +(gdb) x/1db 0x200005a8 +0x200005a8 : 43 +``` + +### Step 15: Analyze the GPIO Logic + +Examine the GPIO input/output code: + +```gdb +(gdb) x/10i 0x10000274 + 0x10000274 : mov.w r1, #3489660928 @ 0xd0000000 + 0x10000278 : ldrb r3, [r4, #0] + 0x1000027a : movs r2, #16 + 0x1000027c : adds r3, #1 + 0x1000027e : strb r3, [r4, #0] + 0x10000280 : ldr r3, [r1, #4] + 0x10000282 : ubfx r3, r3, #15, #1 + 0x10000286 : eor.w r3, r3, #1 + 0x1000028a : mcrr 0, 4, r2, r3, cr0 + 0x1000028e : b.n 0x10000264 +``` + +**Breaking this down:** + +| Address | Instruction | Purpose | +| -------------- | -------------------------- | ---------------------------------------------------- | +| `0x10000274` | `mov.w r1, #0xd0000000` | Load SIO (Single-cycle I/O) base address into `r1` | +| `0x10000278` | `ldrb r3, [r4, #0]` | Load `static_fav_num` from RAM into `r3` | +| `0x1000027a` | `movs r2, #16` | Load LED pin number (16) into `r2` for later | +| `0x1000027c` | `adds r3, #1` | Increment `static_fav_num` by 1 | +| `0x1000027e` | `strb r3, [r4, #0]` | Store incremented value back to RAM | +| `0x10000280` | `ldr r3, [r1, #4]` | Read GPIO input state (SIO_GPIO_IN at offset `0x04`) | +| `0x10000282` | `ubfx r3, r3, #15, #1` | Extract bit 15 (GPIO 15 = button) | +| `0x10000286` | `eor.w r3, r3, #1` | XOR with 1 to invert (implements `? 0 : 1`) | +| `0x1000028a` | `mcrr 0, 4, r2, r3, cr0` | Write `r3` (button) and `r2` (pin 16) to GPIO output | +| `0x1000028e` | `b.n 0x10000264` | Loop back to start (`while (true)`) | + +> Tip: **Notice how the compiler interleaves the static variable increment with the GPIO logic.** It loads the SIO base address (`r1`) *before* doing the increment, and sets up `r2 = 16` (LED pin) in between. This is called **instruction scheduling** - the compiler reorders instructions to avoid pipeline stalls while waiting for memory reads. + +### Step 16: Find the Infinite Loop + +The last instruction at `0x1000028e` is already covered in the table above: + +``` + 0x1000028e : b.n 0x10000264 +``` + +This is an **unconditional branch** back to `0x10000264` (the `movs r1, #42` at the top of the loop) - this is the `while (true)` in our code! There is no `pop` or `bx lr` to return from `main` because the loop never exits. + +--- + +## Part 8: Hacking the Binary with a Hex Editor + +Now for the fun part - we'll patch the `.bin` file directly using a hex editor! + +> Tip: **Why a hex editor?** GDB **cannot write to flash memory** - the `0x10000000+` address range where program instructions live. Trying `set *(char *)0x10000264 = 0x2b` in GDB gives `Writing to flash memory forbidden in this context`. To make **permanent** patches that survive a power cycle, we edit the `.bin` file directly with a hex editor and re-flash it. + +### Step 17: Open the Binary in a Hex Editor + +1. Open **HxD** (or your preferred hex editor: ImHex, 010 Editor, etc.) +2. Click **File** -> **Open** +3. Navigate to `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\` +4. Open `0x0014_static-variables.bin` + +### Step 18: Calculate the File Offset + +The binary is loaded at base address `0x10000000`. To find the file offset of any address: + +``` +file_offset = address - 0x10000000 +``` + +For example: +- Address `0x10000264` -> file offset `0x264` (612 in decimal) +- Address `0x10000286` -> file offset `0x286` (646 in decimal) + +### Step 19: Hack #1 - Change regular_fav_num from 42 to 43 + +From our GDB analysis, we know the instruction at `0x10000264` is: + +``` +movs r1, #0x2a -> bytes: 2a 21 +``` + +To change the value from 42 (`0x2a`) to 43 (`0x2b`): + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\0x0014_static-variables.bin` +2. Press **Ctrl+G** (Go to offset) +3. Enter offset: `264` +4. You should see the byte `2A` at this position +5. Change `2A` to `2B` +6. The instruction is now `movs r1, #0x2b` (43 in decimal) + +> ?? **How Thumb encoding works:** In `movs r1, #imm8`, the immediate value is the first byte, and the opcode `21` is the second byte. So the bytes `2a 21` encode `movs r1, #0x2a`. + +### Step 20: Hack #2 - Invert the Button Logic + +#### Understand the Encoding + +From GDB, we found the `eor.w r3, r3, #1` instruction at `0x10000286` that inverts the button value. Examine the exact bytes: + +```gdb +(gdb) x/4bx 0x10000286 +0x10000286 : 0x83 0xf0 0x01 0x03 +``` + +This is the 32-bit Thumb-2 encoding of `eor.w r3, r3, #1`. The bytes break down as: + +``` ++-----------------------------------------------------------------+ +| eor.w r3, r3, #1 -> bytes: 83 F0 01 03 | +| | +| Byte 0: 0x83 -?? | +| Byte 1: 0xF0 -+ First halfword (opcode + source register) | +| Byte 2: 0x01 ---- Immediate value (#1) ?? CHANGE THIS | +| Byte 3: 0x03 ---- Destination register (r3) | +| | ++-----------------------------------------------------------------+ +``` + +To change `eor.w r3, r3, #1` to `eor.w r3, r3, #0` (making XOR do nothing): + +The file offset is `0x10000286 - 0x10000000 = 0x286`. The immediate byte is the 3rd byte of the instruction, so: `0x286 + 2 = 0x288`. + +To change `eor.w r3, r3, #1` to `eor.w r3, r3, #0`: + +1. In HxD, press **Ctrl+G** (Go to offset) +2. Enter offset: `288` (the third byte of the 4-byte instruction) +3. You should see the byte `01` at this position +4. Change `01` to `00` + +> ?? **Why offset `0x288` and not `0x286`?** The immediate value `#1` is in the **third byte** of the 4-byte instruction. The instruction starts at file offset `0x286`, so the immediate byte is at `0x286 + 2 = 0x288`. + +Now the logic is permanently changed: +- Button released (input = 1): `1 XOR 0 = 1` -> LED **ON** +- Button pressed (input = 0): `0 XOR 0 = 0` -> LED **OFF** + +This is the **opposite** of the original behavior! + +### Step 21: Save the Patched Binary + +1. Click **File** -> **Save As** +2. Save as `0x0014_static-variables-h.bin` in the build directory +3. Close the hex editor + +--- + +## Part 9: Converting and Flashing the Hacked Binary + +### Step 22: Convert to UF2 Format + +Open a terminal and navigate to your project directory: + +```cmd +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables +``` + +Run the conversion command: + +```cmd +python ..\uf2conv.py build\0x0014_static-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +**What this command means:** +- `uf2conv.py` = the conversion script (in the parent `Embedded-Hacking` directory) +- `--base 0x10000000` = the XIP base address where code runs from +- `--family 0xe48bff59` = the RP2350 family ID +- `--output build\hacked.uf2` = the output filename + +### Step 23: Flash the Hacked Binary + +1. Hold BOOTSEL and plug in your Pico 2 +2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive +3. Open your serial monitor + +### Step 24: Verify the Hacks + +**Check the serial output:** +``` +regular_fav_num: 43 ?? Changed from 42! +static_fav_num: 42 +regular_fav_num: 43 +static_fav_num: 43 +... +``` + +**Check the LED behavior:** +- LED should now be **ON by default** (when button is NOT pressed) +- LED should turn **OFF** when you press the button + + **BOOM! We successfully:** +1. Changed the printed value from 42 to 43 +2. Inverted the LED/button logic + +--- + +## Part 10: Summary and Review + +### What We Accomplished + +1. **Learned about static variables** - How they persist across function calls and loop iterations +2. **Understood memory layout** - Stack vs static storage vs heap +3. **Configured GPIO inputs** - Using pull-up resistors and reading button states +4. **Analyzed compiled code in GDB** - Saw how the compiler optimizes code +5. **Discovered function inlining** - `gpio_pull_up` became `gpio_set_pulls` +6. **Hacked variable values** - Changed 42 to 43 using a hex editor +7. **Inverted GPIO logic** - Made LED behavior opposite + +### Static vs Automatic Variables + +| Aspect | Automatic (Regular) | Static | +| ------------------ | ------------------------ | --------------------------- | +| **Storage** | Stack | Static storage (.data/.bss) | +| **Lifetime** | Block/function scope | Entire program | +| **Initialization** | Every time block entered | Once at program start | +| **Persistence** | Lost when scope exits | Retained between calls | +| **Compiler view** | May be optimized away | Always has memory location | + +### GPIO Input Configuration + +``` ++-----------------------------------------------------------------+ +| GPIO Input Setup Steps | +| | +| 1. gpio_init(pin) - Initialize the pin | +| 2. gpio_set_dir(pin, GPIO_IN) - Set as input | +| 3. gpio_pull_up(pin) - Enable pull-up | +| OR gpio_pull_down(pin) - OR enable pull-down | +| 4. gpio_get(pin) - Read the state | +| | ++-----------------------------------------------------------------+ +``` + +### The Binary Hacking Workflow + +``` ++-----------------------------------------------------------------+ +| 1. Analyze the binary with GDB | +| - Disassemble functions with x/Ni | +| - Identify key instructions and addresses | ++-----------------------------------------------------------------+ +| 2. Understand compiler optimizations | +| - Some functions get inlined (gpio_pull_up -> gpio_set_pulls)| +| - Some variables are optimized away | ++-----------------------------------------------------------------+ +| 3. Calculate file offsets | +| - file_offset = address - 0x10000000 | ++-----------------------------------------------------------------+ +| 4. Patch the .bin file with a hex editor | +| - Open the .bin file in HxD / ImHex | +| - Go to the calculated offset | +| - Change the target byte(s) | ++-----------------------------------------------------------------+ +| 5. Convert to UF2 | +| python uf2conv.py file.bin --base 0x10000000 | +| --family 0xe48bff59 --output hacked.uf2 | ++-----------------------------------------------------------------+ +| 6. Flash and verify | +| - Hold BOOTSEL, plug in, drag UF2 | +| - Check serial output and button/LED behavior | ++-----------------------------------------------------------------+ +``` + +### Key Memory Addresses + +| Address | Description | +| ------------ | ----------------------------------- | +| `0x10000234` | Typical main() entry point | +| `0x10003014` | stdio_init_all() function | +| `0x200005a8` | Static variable storage (example) | +| `0xd0000000` | SIO (Single-cycle I/O) base address | + +--- + +--- + +## Key Takeaways + +1. **Static variables persist** - They keep their value between function calls and loop iterations. + +2. **Static storage ? heap** - Static variables are in a fixed location, not dynamically allocated. + +3. **Compilers optimize aggressively** - Regular variables may be optimized away if the compiler sees no effect. + +4. **Function inlining is common** - `gpio_pull_up` becomes `gpio_set_pulls` in the binary. + +5. **Pull-up resistors invert logic** - Button pressed = LOW, button released = HIGH. + +6. **XOR is useful for inverting** - `eor r3,r3,#0x1` flips a bit between 0 and 1. + +7. **Static variables have fixed addresses** - You can find them in the .data section at known RAM addresses. + +8. **Overflow wraps around** - A `uint8_t` at 255 becomes 0 when incremented. + +9. **UBFX extracts bits** - Used to read a single GPIO pin from a register. + +10. **Binary patching is powerful** - Change values and logic without source code! + +--- + +## Glossary + +| Term | Definition | +| --------------------- | ---------------------------------------------------------------- | +| **Automatic** | Variable that's created and destroyed automatically (local vars) | +| **eor/XOR** | Exclusive OR - flips bits where operands differ | +| **Floating Input** | GPIO input with undefined voltage (reads random values) | +| **Function Inlining** | Compiler replaces function call with the function's code | +| **gpio_get** | Function to read the current state of a GPIO pin | +| **Heap** | Memory area for dynamic allocation (malloc/free) | +| **Overflow** | When a value exceeds its type's maximum and wraps around | +| **Pull-Down** | Resistor that holds a pin LOW when nothing drives it | +| **Pull-Up** | Resistor that holds a pin HIGH when nothing drives it | +| **SIO** | Single-cycle I/O - fast GPIO access on RP2350 | +| **Stack** | Memory area for local variables and function call frames | +| **Static Storage** | Fixed memory area for static and global variables | +| **Static Variable** | Variable declared with `static` that persists across calls | +| **Ternary Operator** | `condition ? value_if_true : value_if_false` | +| **UBFX** | Unsigned Bit Field Extract - extracts bits from a register | +| **Varargs** | Variable arguments - functions that take unlimited parameters | + +--- + +## Additional Resources + +### GPIO Input Reference + +| Function | Purpose | +| ----------------------------- | -------------------------- | +| `gpio_init(pin)` | Initialize GPIO pin | +| `gpio_set_dir(pin, GPIO_IN)` | Set pin as input | +| `gpio_set_dir(pin, GPIO_OUT)` | Set pin as output | +| `gpio_pull_up(pin)` | Enable internal pull-up | +| `gpio_pull_down(pin)` | Enable internal pull-down | +| `gpio_disable_pulls(pin)` | Disable all pull resistors | +| `gpio_get(pin)` | Read pin state (0 or 1) | +| `gpio_put(pin, value)` | Set pin output (0 or 1) | + +### Key Assembly Instructions + +| Instruction | Description | +| ----------------------- | -------------------------------------------- | +| `movs rN, #imm` | Move immediate value to register | +| `ldrb rN, [rM, #off]` | Load byte from memory | +| `strb rN, [rM, #off]` | Store byte to memory | +| `adds rN, #imm` | Add immediate value to register | +| `eor rN, rM, #imm` | Exclusive OR (XOR) with immediate | +| `ubfx rN, rM, #lsb, #w` | Extract unsigned bit field | +| `mcrr p0, ...` | Move to coprocessor (GPIO control on RP2350) | +| `b LABEL` | Unconditional branch (jump) | + +### Memory Map Quick Reference + +| Address Range | Description | +| --------------------- | ------------------------------ | +| `0x10000000` | XIP Flash (code execution) | +| `0x20000000-200005xx` | SRAM (.data section) | +| `0x20082000` | Stack top (initial SP) | +| `0x40038000` | PADS_BANK0 (pad configuration) | +| `0xd0000000` | SIO (single-cycle I/O) | + +--- + +**Remember:** Static variables are your friends when you need to remember values across function calls. But they also make your program's behavior more complex to analyze - which is exactly why we practice reverse engineering! + +Happy hacking! ? + + + diff --git a/WEEK06/slides/WEEK06-IMG00.svg b/WEEK06/slides/WEEK06-IMG00.svg new file mode 100644 index 0000000..0feedec --- /dev/null +++ b/WEEK06/slides/WEEK06-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 06 + + +Static Variables in Embedded Systems: +Debugging and Hacking Static Variables +w/ GPIO Input Basics + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEK06/slides/WEEK06-IMG01.svg b/WEEK06/slides/WEEK06-IMG01.svg new file mode 100644 index 0000000..45e7fee --- /dev/null +++ b/WEEK06/slides/WEEK06-IMG01.svg @@ -0,0 +1,74 @@ + + + + + +Static vs Regular Vars +Persistence across loop iterations + + + +Regular (auto) + + +Loop 1: 42 -> 43 -> destroy + + +Loop 2: 42 -> 43 -> destroy + + +Loop 3: 42 -> 43 -> destroy + +Always prints: +42 + + + +Static + + +Loop 1: 42 -> 43 (kept!) + + +Loop 2: 43 -> 44 (kept!) + + +Loop 3: 44 -> 45 (kept!) + +Keeps incrementing: +42,43,44... + + + +Declaration Syntax + + +uint8_t reg = 42; +Recreated each iteration + + +static uint8_t s = 42; +Persists for program life + + + +uint8_t Overflow + +255 + 1 = +0 +(wraps around!) + +Binary: 11111111 + 1 = 100000000 (9 bits) +Only 8 bits kept: 00000000 = 0 + \ No newline at end of file diff --git a/WEEK06/slides/WEEK06-IMG02.svg b/WEEK06/slides/WEEK06-IMG02.svg new file mode 100644 index 0000000..dca4829 --- /dev/null +++ b/WEEK06/slides/WEEK06-IMG02.svg @@ -0,0 +1,93 @@ + + + + + +Memory Layout +Where variables live in RAM + + + +RP2350 SRAM Map + + + +STACK (grows down) +Local/auto variables + +0x20082000 + + + +(free space) + + + +HEAP (grows up) +malloc / free + + + +.bss section +Uninit static/global + + + +.data section +Initialized static/global + +0x20000000 + +Static vars: .data (init) +Static vars: .bss (uninit) + + + +Variable Storage + +Type +Location + + + +Automatic +Stack + +Static +.data / .bss + +Global +.data / .bss + +Dynamic +Heap + +Static vars are NOT on heap! +Fixed location, set at compile +time. Lives entire program. +Example: +static_fav_num @ 0x200005A8 + + + +Key Insight + +Stack vars: +Created + destroyed +each function call + +Static vars: +Fixed RAM address +persist entire runtime + \ No newline at end of file diff --git a/WEEK06/slides/WEEK06-IMG03.svg b/WEEK06/slides/WEEK06-IMG03.svg new file mode 100644 index 0000000..279854a --- /dev/null +++ b/WEEK06/slides/WEEK06-IMG03.svg @@ -0,0 +1,93 @@ + + + + + +GPIO Input Basics +Reading buttons with the RP2350 + + + +OUTPUT (before) + + +Pico GPIO 16 +--> +LED + +We CONTROL the LED +gpio_put(pin, value) + + +INPUT (new!) + + +Pico GPIO 15 +<-- +BTN + +We READ button state +gpio_get(pin) + + + +Floating Input + +No connection = +RANDOM values! + +Read 1: HIGH +Read 2: LOW +Read 3: HIGH +Read 4: HIGH +??? + + + +Pull Resistors + +Type +Default +Pressed + + + +Pull-Up +HIGH(1) +LOW(0) + +Pull-Down +LOW(0) +HIGH(1) + +Pico 2 has internal pull resistors! + + + +GPIO Input Functions + +gpio_init(pin) +Initialize pin + +gpio_set_dir(pin, GPIO_IN) +Set as input + +gpio_pull_up(pin) +Enable pull-up + +gpio_get(pin) +Read state (0 or 1) + +No external resistor needed -- internal pull-up! + \ No newline at end of file diff --git a/WEEK06/slides/WEEK06-IMG04.svg b/WEEK06/slides/WEEK06-IMG04.svg new file mode 100644 index 0000000..4ab0ae6 --- /dev/null +++ b/WEEK06/slides/WEEK06-IMG04.svg @@ -0,0 +1,99 @@ + + + + + +Pull-Up Resistor +Internal pull-up on GPIO 15 + + + +Circuit + +3.3V + + + +pull-up R + + + +GPIO 15 + + + + +BTN + + +GND + + + +Button Logic + +State +GPIO +LED + + + +Released +HIGH (1) +OFF + +Pressed +LOW (0) +ON + +Inverted! Pull-up = backwards + + + +Ternary Operator + + +gpio_put(LED, pressed?0:1); + +pressed=1 -> LED OFF (inverted!) + + + +Hardware Wiring + + +Pico 2 +GPIO 15 +GPIO 16 +GND +BOOTSEL + +--> +--> +--> + + +Components +Button (one leg) +LED + resistor +Button (other leg) +Hold to flash UF2 + + +Key Point +No external +resistor needed! +Internal pull-up +handles it all + \ No newline at end of file diff --git a/WEEK06/slides/WEEK06-IMG05.svg b/WEEK06/slides/WEEK06-IMG05.svg new file mode 100644 index 0000000..b612ee1 --- /dev/null +++ b/WEEK06/slides/WEEK06-IMG05.svg @@ -0,0 +1,83 @@ + + + + + +Source Code +0x0014_static-variables.c + + + +main() + + +int main(void) { +stdio_init_all(); +// GPIO setup +gpio_init(15); +gpio_set_dir(15, GPIO_IN); +gpio_pull_up(15); +gpio_init(16); +gpio_set_dir(16, GPIO_OUT); +while (true) { +uint8_t reg = 42; +static uint8_t s = 42; +printf(... reg, s); +reg++; s++; +} +} + + + +GPIO Setup + +Pin 15: +Input +Pull-up enabled + +Pin 16: +Output +LED control + + + +Serial Output + + +reg: 42 +s: 42 +reg: 42 +s: 43 + +reg always 42, s grows + + + +Button Logic + +pressed = gpio_get(15); + + + +Key Behaviors + + +reg: always 42 + + +s: 42,43,44... + + +wraps at 255->0 + \ No newline at end of file diff --git a/WEEK06/slides/WEEK06-IMG06.svg b/WEEK06/slides/WEEK06-IMG06.svg new file mode 100644 index 0000000..a0919c3 --- /dev/null +++ b/WEEK06/slides/WEEK06-IMG06.svg @@ -0,0 +1,65 @@ + + + + + +Compiler Optimizations +What the compiler does to your code + + + +Optimization 1: Dead Code Removal + + +Your code: +uint8_t reg = 42; +reg++; +// No lasting effect! + + +Compiler output: +movs r1, #42 +// reg++ is GONE +// Uses constant 42 directly + + + +Optimization 2: Function Inlining + + +Your code: +gpio_pull_up(15); +// Simple wrapper func + + +Compiler output: +gpio_set_pulls(15,1,0); +// Inlined to real func + + + +Optimization 3: Scheduling + +Compiler reorders instructions +to avoid pipeline stalls: + + +Load SIO base + + +Increment s + + +Read GPIO + \ No newline at end of file diff --git a/WEEK06/slides/WEEK06-IMG07.svg b/WEEK06/slides/WEEK06-IMG07.svg new file mode 100644 index 0000000..c359080 --- /dev/null +++ b/WEEK06/slides/WEEK06-IMG07.svg @@ -0,0 +1,90 @@ + + + + + +Assembly Analysis +Key instructions in main() loop + + + +Loop Body (0x10000264) + + +movs r1, #0x2a +; reg=42 +bl __wrap_printf +; print reg +ldrb r1, [r4] +; load static +bl __wrap_printf +; print s +mov.w r1, #0xd0000000 +; SIO base +ldrb r3, [r4] +; reload s +adds r3, #1 +; s++ +strb r3, [r4] +; store s +ldr r3, [r1, #4] +; read GPIO +ubfx r3, r3, #15, #1 +; bit 15 +eor.w r3, r3, #1 +; invert +mcrr 0, 4, r2, r3, cr0 +; GPIO out +b.n 0x10000264 +; loop + + + +Key Registers + +r1: +printf arg +r3: +temp / static +r4: +0x200005a8 +(static var addr) +r2: +LED pin (16) + + + +Key Patterns + +ldrb/adds/strb +Load-increment-store +(static var update) + +ubfx #15, #1 +Extract GPIO bit 15 + +eor.w #1 +Inverts (? 0 : 1) + + + +Infinite Loop + + +b.n 0x10000264 +; while(true) + +No pop/bx lr +main() never returns + \ No newline at end of file diff --git a/WEEK06/slides/WEEK06-IMG08.svg b/WEEK06/slides/WEEK06-IMG08.svg new file mode 100644 index 0000000..702d322 --- /dev/null +++ b/WEEK06/slides/WEEK06-IMG08.svg @@ -0,0 +1,75 @@ + + + + + +GDB: Static Variable +Finding static_fav_num in RAM + + + +Literal Pool Lookup + + +ldr r4, [pc, #44] +@ 0x10000290 + +Examine literal pool: + +x/1wx 0x10000290 = 0x200005A8 + +r4 now holds the +RAM address of +static_fav_num! + + + +Read Value + + +x/1db 0x200005a8 = 42 + +After one loop iteration: + +x/1db 0x200005a8 = 43 + +It incremented! Persists in RAM. + + + +Disasm Gotcha + +x/i 0x10000290 shows: + + +lsls r0, r5, #22 + +GDB decodes DATA as code! +Bytes A8 05 00 20 = 0x200005A8 +Use x/wx not x/i for data + + + +GPIO Input Register + +Read button state in GDB: + + +p/x (*(uint*)0xd0000004 >> 15) & 1 + +Returns 1: +not pressed (pull-up) +Returns 0: +button pressed + \ No newline at end of file diff --git a/WEEK06/slides/WEEK06-IMG09.svg b/WEEK06/slides/WEEK06-IMG09.svg new file mode 100644 index 0000000..c603290 --- /dev/null +++ b/WEEK06/slides/WEEK06-IMG09.svg @@ -0,0 +1,63 @@ + + + + + +Hacking the Binary +Two patches with a hex editor + + + +File Offset Formula +offset = address - 0x10000000 +Example: 0x10000264 -> 0x264 + + + +Hack 1: Change 42 to 43 + +Target: movs r1, #0x2a +at address 0x10000264 (offset 0x264) + + +Before: 2A 21 +movs r1, #42 + + +After: 2B 21 +movs r1, #43 + +Thumb encoding: imm8 byte first, opcode 0x21 second + + + +Hack 2: Invert Button Logic + +Target: eor.w r3, r3, #1 +at address 0x10000286 (offset 0x286) + + +Before: 83 F0 01 03 + + +After: 83 F0 00 03 + +Byte at offset 0x288: +01 +-> +00 + +XOR with 0 = no invert +LED now ON by default, OFF when pressed + \ No newline at end of file diff --git a/WEEK06/slides/WEEK06-IMG10.svg b/WEEK06/slides/WEEK06-IMG10.svg new file mode 100644 index 0000000..f88fa45 --- /dev/null +++ b/WEEK06/slides/WEEK06-IMG10.svg @@ -0,0 +1,112 @@ + + + + + +Static Vars & GPIO Input +Static variables, GPIO input, hacking + + + +Static vs Auto + +Aspect +Auto +Static + + + +Where +Stack +.data + +Life +Scope +Forever + +Init +Every +Once + +Keeps? +No +Yes + +Optimized? +Often +In RAM + +Compiler may remove auto vars + + + +GPIO Input Setup + +1. +gpio_init(pin) + +2. +gpio_set_dir(pin, GPIO_IN) + +3. +gpio_pull_up(pin) + +4. +gpio_get(pin) + +Pull-up: released=HIGH +pressed=LOW (inverted!) +Internal R, no hardware + + + +Key Instructions + +ubfx r3,r3,#15,#1 +Extract single GPIO bit + +eor.w r3,r3,#1 +XOR to invert logic + +b.n 0x10000264 + + + +Hacking Workflow + +1. +Analyze in GDB + +2. +Calculate offset + +3. +Patch .bin in HxD + +4. +uf2conv.py + flash + + + +Takeaways + + +42 -> 43 (1 byte) + + +XOR 1->0 (invert) + + +offset = addr - base + \ No newline at end of file diff --git a/WEEK07/WEEK07-SLIDES.pdf b/WEEK07/WEEK07-SLIDES.pdf new file mode 100644 index 0000000..e4a3b56 Binary files /dev/null and b/WEEK07/WEEK07-SLIDES.pdf differ diff --git a/WEEK07/WEEK07.md b/WEEK07/WEEK07.md new file mode 100644 index 0000000..25a723e --- /dev/null +++ b/WEEK07/WEEK07.md @@ -0,0 +1,1074 @@ +# Week 7: Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics + +## What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand the difference between `#define` macros and `const` variables +- Know how constants are stored differently in memory (compile-time vs runtime) +- Understand the I2C (Inter-Integrated Circuit) communication protocol +- Configure I2C peripherals and communicate with LCD displays +- Understand C structs and how the Pico SDK uses them for hardware abstraction +- Use GDB to examine constants, structs, and string literals in memory +- Hack constant values and string literals using a hex editor +- Patch LCD display text without access to source code + +--- + +## Part 1: Understanding Constants in C + +### Two Types of Constants + +In C, there are two ways to create values that shouldn't change: + +| Type | Syntax | Where It Lives | When Resolved | +| ----------- | ----------------------- | ------------------ | ------------- | +| **#define** | `#define FAV_NUM 42` | Nowhere (replaced) | Compile time | +| **const** | `const int NUM = 1337;` | Flash (.rodata) | Runtime | + +### Preprocessor Macros (#define) + +A **preprocessor macro** is a text replacement that happens BEFORE your code is compiled: + +```c +#define FAV_NUM 42 + +printf("Value: %d", FAV_NUM); +// Becomes: printf("Value: %d", 42); +``` + +Think of it like a "find and replace" in a text editor. The compiler never sees `FAV_NUM` - it only sees `42`! + +``` ++-----------------------------------------------------------------+ +| Preprocessor Macro Flow | +| | +| Source Code Preprocessor Compiler | +| +----------+ +----------+ +----------+ | +| | #define | | Replace | | Compile | | +| | FAV_NUM | -----? | FAV_NUM | -----? | binary | | +| | 42 | | with 42 | | code | | +| +----------+ +----------+ +----------+ | +| | +| FAV_NUM doesn't exist in the final binary! | +| The value 42 is embedded directly in instructions. | +| | ++-----------------------------------------------------------------+ +``` + +### Const Variables + +A **const variable** is an actual variable stored in memory, but marked as read-only: + +```c +const int OTHER_FAV_NUM = 1337; +``` + +Unlike `#define`, this creates a real memory location in the `.rodata` (read-only data) section of flash: + +``` ++-----------------------------------------------------------------+ +| Const Variable in Memory | +| | +| Flash Memory (.rodata section) | +| +------------------------------------------------------------+ | +| | Address: 0x10001234 | | +| | Value: 0x00000539 (1337 in hex) | | +| | Name: OTHER_FAV_NUM (in debug symbols only) | | +| +------------------------------------------------------------+ | +| | +| The variable EXISTS in memory and can be read at runtime. | +| | ++-----------------------------------------------------------------+ +``` + +### Comparison: #define vs const + +| Feature | #define | const | +| -------------------- | ------------------------ | ---------------------------- | +| **Type checking** | None (just text) | Yes (compiler enforced) | +| **Memory usage** | None (inlined) | Uses flash space | +| **Debugger visible** | No | Yes (with symbols) | +| **Can take address** | No (`&FAV_NUM` fails) | Yes (`&OTHER_FAV_NUM` works) | +| **Scope** | Global (from definition) | Normal C scoping rules | + +--- + +## Part 2: Understanding I2C Communication + +### What is I2C? + +**I2C** (pronounced "I-squared-C" or "I-two-C") stands for **Inter-Integrated Circuit**. It's a way for chips to talk to each other using just TWO wires! + +``` ++-----------------------------------------------------------------+ +| I2C Bus - Two Wires, Many Devices | +| | +| 3.3V | +| | | +| + Pull-up + Pull-up | +| | | | +| SDA -+------------+--------------------------------------- | +| | | | +| SCL -+------------+--------------------------------------- | +| | | | | | +| +---+----+ +--+---+ +----+--+ +-----+---+ | +| | Pico | | LCD | |Sensor | | EEPROM | | +| |(Master)| | 0x27 | | 0x48 | | 0x50 | | +| +--------+ +------+ +-------+ +---------+ | +| | +| Each device has a unique address (0x27, 0x48, 0x50...) | +| | ++-----------------------------------------------------------------+ +``` + +### The Two I2C Wires + +| Wire | Name | Purpose | +| ------- | ------------ | ------------------------------------ | +| **SDA** | Serial Data | Carries the actual data bits | +| **SCL** | Serial Clock | Timing signal that synchronizes data | + +### Why Pull-Up Resistors? + +I2C uses **open-drain** signals, meaning devices can only pull the line LOW. They can't drive it HIGH! Pull-up resistors are needed to bring the lines back to HIGH when no device is pulling them down. + +The Pico 2 has internal pull-ups that we can enable with `gpio_pull_up()`. + +### I2C Addresses + +Every I2C device has a unique **7-bit address**. Common addresses: + +| Device Type | Typical Address | +| --------------------- | ---------------- | +| 1602 LCD with PCF8574 | `0x27` or `0x3F` | +| Temperature sensor | `0x48` | +| EEPROM | `0x50` | +| Real-time clock | `0x68` | + +### I2C Communication Flow + +``` ++-----------------------------------------------------------------+ +| I2C Transaction | +| | +| 1. Master sends START condition | +| 2. Master sends device address (7 bits) + R/W bit | +| 3. Addressed device sends ACK (acknowledge) | +| 4. Data is transferred (8 bits at a time) | +| 5. Receiver sends ACK after each byte | +| 6. Master sends STOP condition | +| | +| START --? Address --? ACK --? Data --? ACK --? STOP | +| | ++-----------------------------------------------------------------+ +``` + +--- + +## Part 3: Understanding C Structs + +### What is a Struct? + +A **struct** (short for "structure") is a way to group related variables together under one name. Think of it like a form with multiple fields: + +```c +// A struct definition - like a template +struct student { + char name[50]; + int age; + float gpa; +}; + +// Creating a variable of this struct type +struct student alice = {"Alice", 16, 3.8}; +``` + +### Why Use Structs? + +Instead of passing many separate variables: +```c +void print_student(char *name, int age, float gpa); // Messy! +``` + +You pass one struct: +```c +void print_student(struct student s); // Clean! +``` + +### The typedef Keyword + +Writing `struct student` everywhere is tedious. The `typedef` keyword creates an alias: + +```c +typedef struct student student_t; + +// Now you can write: +student_t alice; // Instead of: struct student alice; +``` + +### Forward Declaration + +Sometimes you need to tell the compiler "this struct exists" before defining it: + +```c +typedef struct i2c_inst i2c_inst_t; // Forward declaration + alias + +// Later, the full definition: +struct i2c_inst { + i2c_hw_t *hw; + bool restart_on_next; +}; +``` + +--- + +## Part 4: Understanding the Pico SDK's I2C Structs + +### The i2c_inst_t Struct + +The Pico SDK uses a struct to represent each I2C controller: + +```c +struct i2c_inst { + i2c_hw_t *hw; // Pointer to hardware registers + bool restart_on_next; // SDK internal flag +}; +``` + +**What each member means:** + +| Member | Type | Purpose | +| ----------------- | ------------ | ---------------------------------------- | +| `hw` | `i2c_hw_t *` | Pointer to the actual hardware registers | +| `restart_on_next` | `bool` | Tracks if next transfer needs a restart | + +### The Macro Chain + +When you write `I2C_PORT` in your code, here's what happens: + +``` ++-----------------------------------------------------------------+ +| Macro Expansion Chain | +| | +| In your code: #define I2C_PORT i2c1 | +| | | +| ? | +| In i2c.h: #define i2c1 (&i2c1_inst) | +| | | +| ? | +| In i2c.c: i2c_inst_t i2c1_inst = {i2c1_hw, false}; | +| | | +| ? | +| In i2c.h: #define i2c1_hw ((i2c_hw_t *)I2C1_BASE) | +| | | +| ? | +| In addressmap.h: #define I2C1_BASE 0x40098000 | +| | ++-----------------------------------------------------------------+ +``` + +So `I2C_PORT` eventually becomes a pointer to a struct that contains a pointer to hardware registers at address `0x40098000`! + +### The Hardware Register Pointer + +The `i2c_hw_t *hw` member points to the actual silicon: + +``` ++-----------------------------------------------------------------+ +| Memory Map | +| | +| Address 0x40098000: I2C1 Hardware Registers | +| +------------------------------------------------------------+ | +| | Offset 0x00: IC_CON (Control register) | | +| | Offset 0x04: IC_TAR (Target address register) | | +| | Offset 0x10: IC_DATA_CMD (Data command register) | | +| | ... | | +| +------------------------------------------------------------+ | +| | +| The i2c_hw_t struct maps directly to these registers! | +| | ++-----------------------------------------------------------------+ +``` + +--- + +## Part 5: The ARM Calling Convention (AAPCS) + +### How Arguments Are Passed + +On ARM Cortex-M, the **ARM Architecture Procedure Call Standard (AAPCS)** defines how functions receive arguments: + +| Register | Purpose | +| -------- | ---------------- | +| `r0` | First argument | +| `r1` | Second argument | +| `r2` | Third argument | +| `r3` | Fourth argument | +| Stack | Fifth+ arguments | +| `r0` | Return value | + +### Example: i2c_init(i2c1, 100000) + +```c +i2c_init(I2C_PORT, 100000); +``` + +In assembly: +```assembly +ldr r0, [address of i2c1_inst] ; r0 = pointer to struct (first arg) +ldr r1, =0x186A0 ; r1 = 100000 (second arg) +bl i2c_init ; Call the function +``` + +--- + +## Part 6: Setting Up Your Environment + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. A Raspberry Pi Pico Debug Probe +3. OpenOCD installed and configured +4. GDB (`arm-none-eabi-gdb`) installed +5. Python installed (for UF2 conversion) +6. A serial monitor (PuTTY, minicom, or screen) +7. A 1602 LCD display with I2C backpack (PCF8574) +8. A hex editor (HxD, ImHex, or similar) +9. The sample project: `0x0017_constants` + +### Hardware Setup + +Connect your LCD like this: + +| LCD Pin | Pico 2 Pin | +| ------- | ---------- | +| VCC | 3.3V or 5V | +| GND | GND | +| SDA | GPIO 2 | +| SCL | GPIO 3 | + +``` ++-----------------------------------------------------------------+ +| I2C LCD Wiring | +| | +| Pico 2 1602 LCD + I2C Backpack | +| +----------+ +----------------------+ | +| | | | | | +| | GPIO 2 |------- SDA -----?| SDA | | +| | (SDA) | | | | +| | | | +------------+ | | +| | GPIO 3 |------- SCL -----?| SCL| Reverse | | | +| | (SCL) | | |Engineering | | | +| | | | +------------+ | | +| | 3.3V |------- VCC -----?| VCC | | +| | | | | | +| | GND |------- GND -----?| GND | | +| | | | | | +| +----------+ +----------------------+ | +| | ++-----------------------------------------------------------------+ +``` + +### Project Structure + +``` +Embedded-Hacking/ ++-- 0x0017_constants/ +| +-- build/ +| | +-- 0x0017_constants.uf2 +| | +-- 0x0017_constants.bin +| +-- 0x0017_constants.c +| +-- lcd_1602.h ++-- uf2conv.py +``` + +--- + +## Part 7: Hands-On Tutorial - Constants and I2C LCD + +### Step 1: Review the Source Code + +Let's examine the constants code: + +**File: `0x0017_constants.c`** + +```c +#include +#include +#include "pico/stdlib.h" +#include "hardware/i2c.h" +#include "lcd_1602.h" + +#define FAV_NUM 42 +#define I2C_PORT i2c1 +#define I2C_SDA_PIN 2 +#define I2C_SCL_PIN 3 + +const int OTHER_FAV_NUM = 1337; + +int main(void) { + stdio_init_all(); + + i2c_init(I2C_PORT, 100000); + gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(I2C_SDA_PIN); + gpio_pull_up(I2C_SCL_PIN); + + lcd_i2c_init(I2C_PORT, 0x27, 4, 0x08); + lcd_set_cursor(0, 0); + lcd_puts("Reverse"); + lcd_set_cursor(1, 0); + lcd_puts("Engineering"); + + while (true) { + printf("FAV_NUM: %d\r\n", FAV_NUM); + printf("OTHER_FAV_NUM: %d\r\n", OTHER_FAV_NUM); + } +} +``` + +**What this code does:** + +1. **Lines 7-10:** Define preprocessor macros for constants and I2C configuration +2. **Line 12:** Define a `const` variable stored in flash +3. **Line 15:** Initialize UART for serial output +4. **Lines 17-21:** Initialize I2C1 at 100kHz, configure GPIO pins, enable pull-ups +5. **Lines 23-27:** Initialize LCD and display "Reverse" on line 0, "Engineering" on line 1 +6. **Lines 29-32:** Infinite loop printing both constant values to serial terminal + +### Step 2: Flash the Binary to Your Pico 2 + +1. Hold the BOOTSEL button on your Pico 2 +2. Plug in the USB cable (while holding BOOTSEL) +3. Release BOOTSEL - a drive called "RPI-RP2" appears +4. Drag and drop `0x0017_constants.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 3: Verify It's Working + +**Check the LCD:** +- Line 1 should show: `Reverse` +- Line 2 should show: `Engineering` + +**Check the serial monitor (PuTTY/screen):** +``` +FAV_NUM: 42 +OTHER_FAV_NUM: 1337 +FAV_NUM: 42 +OTHER_FAV_NUM: 1337 +... +``` + +--- + +## Part 8: Debugging with GDB (Dynamic Analysis) + +> ? **REVIEW:** This setup is identical to previous weeks. If you need a refresher on OpenOCD and GDB connection, refer back to Week 3 Part 6. + +### Starting the Debug Session + +**Terminal 1 - Start OpenOCD:** + +```powershell +openocd -s "$env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev\scripts" -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000" +``` + +**Terminal 2 - Start GDB:** + +```cmd +arm-none-eabi-gdb build\0x0017_constants.elf +``` + +**Connect to target:** + +```gdb +(gdb) target extended-remote :3333 +(gdb) monitor reset halt +``` + +### Step 4: Examine Main Function + +Let's examine the main function. Disassemble from the entry point: + +``` +x/54i 0x10000234 +``` + +You should see output like: + +``` +(gdb) x/54i 0x10000234 + 0x10000234
: push {r3, lr} + 0x10000236 : bl 0x100037fc + 0x1000023a : ldr r1, [pc, #104] @ (0x100002a4 ) + 0x1000023c : ldr r0, [pc, #104] @ (0x100002a8 ) + 0x1000023e : bl 0x10003cdc + 0x10000242 : movs r1, #3 + 0x10000244 : movs r0, #2 + 0x10000246 : bl 0x100008f0 + 0x1000024a : movs r1, #3 + 0x1000024c : mov r0, r1 + 0x1000024e : bl 0x100008f0 + 0x10000252 : movs r2, #0 + 0x10000254 : movs r1, #1 + 0x10000256 : movs r0, #2 + 0x10000258 : bl 0x1000092c + 0x1000025c : movs r2, #0 + 0x1000025e : movs r1, #1 + 0x10000260 : movs r0, #3 + 0x10000262 : bl 0x1000092c + 0x10000266 : movs r3, #8 + 0x10000268 : movs r2, #4 + 0x1000026a : movs r1, #39 @ 0x27 + 0x1000026c : ldr r0, [pc, #56] @ (0x100002a8 ) + 0x1000026e : bl 0x100002bc + 0x10000272 : movs r1, #0 + 0x10000274 : mov r0, r1 + 0x10000276 : bl 0x100006f4 + 0x1000027a : ldr r0, [pc, #48] @ (0x100002ac ) + 0x1000027c : bl 0x100007f0 + 0x10000280 : movs r0, #1 + 0x10000282 : movs r1, #0 + 0x10000284 : bl 0x100006f4 + 0x10000288 : ldr r0, [pc, #36] @ (0x100002b0 ) + 0x1000028a : bl 0x100007f0 + 0x1000028e : movs r1, #42 @ 0x2a + 0x10000290 : ldr r0, [pc, #32] @ (0x100002b4 ) + 0x10000292 : bl 0x1000398c <__wrap_printf> + 0x10000296 : movw r1, #1337 @ 0x539 + 0x1000029a : ldr r0, [pc, #28] @ (0x100002b8 ) + 0x1000029c : bl 0x1000398c <__wrap_printf> + 0x100002a0 : b.n 0x1000028e + 0x100002a2 : nop + 0x100002a4 : strh r0, [r4, #52] @ 0x34 + 0x100002a6 : movs r1, r0 + 0x100002a8 : lsls r4, r5, #24 + 0x100002aa : movs r0, #0 + 0x100002ac : subs r6, #232 @ 0xe8 + 0x100002ae : asrs r0, r0, #32 + 0x100002b0 : subs r6, #240 @ 0xf0 + 0x100002b2 : asrs r0, r0, #32 + 0x100002b4 : subs r6, #252 @ 0xfc + 0x100002b6 : asrs r0, r0, #32 + 0x100002b8 : subs r7, #12 + 0x100002ba : asrs r0, r0, #32 +``` + +### Step 5: Set a Breakpoint at Main + +``` +b *0x10000234 +c +``` + +### Step 6: Find the #define Constant (FAV_NUM) + +Step through to the printf call and examine the registers: + +``` +x/20i 0x1000028e +``` + +Look for: +``` +... + 0x1000028e : movs r1, #42 @ 0x2a +... +``` + +The `#define` constant is embedded directly as an immediate value in the instruction! + +### Step 7: Find the const Variable (OTHER_FAV_NUM) + +Continue examining the loop body: + +```gdb +(gdb) x/5i 0x10000296 +``` + +Look for this instruction: + +``` +... + 0x10000296 : movw r1, #1337 @ 0x539 +... +``` + +**Surprise!** The `const` variable is ALSO embedded as an immediate value - not loaded from memory! The compiler saw that `OTHER_FAV_NUM` is never address-taken (`&OTHER_FAV_NUM` is never used), so it optimized the `const` the same way as `#define` - as a constant embedded directly in the instruction. + +The difference is the instruction encoding: +- `FAV_NUM` (42): `movs r1, #0x2a` - 16-bit Thumb instruction (values 0-255) +- `OTHER_FAV_NUM` (1337): `movw r1, #0x539` - 32-bit Thumb-2 instruction (values 0-65535) + +> Tip: **Why `movw` instead of `movs`?** The value 1337 doesn't fit in 8 bits (max 255), so the compiler uses `movw` (Move Wide) which can encode any 16-bit immediate (0-65535) in a 32-bit instruction. + +### Step 8: Examine the Literal Pool + +The literal pool after the loop contains addresses and constants that are too large for regular instruction immediates. Let's examine it: + +```gdb +(gdb) x/6wx 0x100002a4 +0x100002a4 : 0x000186a0 0x2000062c 0x10003ee8 0x10003ef0 +0x100002b4 : 0x10003efc 0x10003f0c +``` + +These are the values that `ldr rN, [pc, #offset]` instructions load: + +| Literal Pool Addr | Value | Used By | +| ----------------- | -------------- | ------------------------------ | +| `0x100002a4` | `0x000186A0` | I2C baudrate (100000) | +| `0x100002a8` | `0x2000062C` | &i2c1_inst (I2C struct in RAM) | +| `0x100002ac` | `0x10003EE8` | "Reverse" string address | +| `0x100002b0` | `0x10003EF0` | "Engineering" string address | +| `0x100002b4` | `0x10003EFC` | "FAV_NUM: %d\r\n" format str | +| `0x100002b8` | `0x10003F0C` | "OTHER_FAV_NUM: %d\r\n" fmt | + +> Tip: **Why does the disassembly at `0x100002a4` show `strh r0, [r4, #52]` instead of data?** Same reason as Week 6 - GDB's `x/i` tries to decode raw data as instructions. Use `x/wx` to see the actual word values or we can also use `x/x`. + +```gdb +(gdb) x/x 0x100002a4 +0x100002a4 : 0x000186a0 +(gdb) x/x 0x100002a8 +0x100002a8 : 0x2000062c +(gdb) x/x 0x100002ac +0x100002ac : 0x10003ee8 +(gdb) x/x 0x100002b0 +0x100002b0 : 0x10003ef0 +(gdb) x/x 0x100002b4 +0x100002b4 : 0x10003efc +(gdb) x/x 0x100002b8 +0x100002b8 : 0x10003f0c +``` + +### Step 9: Examine the I2C Struct + +Find the i2c1_inst struct address loaded into r0 before i2c_init: + +``` +x/2wx 0x2000062c +``` + +You should see: +``` +0x2000062c : 0x40098000 0x00000000 +``` + +### Step 10: Examine the LCD String Literals + +Find the strings passed to lcd_puts: + +``` +x/s 0x10003ee8 +``` + +Output: +``` +0x10003ee8: "Reverse" +``` + +``` +x/s 0x10003ef0 +``` + +Output: +``` +0x10003ef0: "Engineering" +``` + +### Step 11: Step Through I2C Initialization + +Step through instructions and watch the I2C setup: + +```gdb +(gdb) b *0x1000023e +(gdb) c +(gdb) i r r0 r1 +r0 0x2000062c 536872492 +r1 0x186a0 100000 +``` + +--- + +## Part 9: Understanding the Assembly + +Now that we've explored the binary in GDB, let's make sense of the key patterns we found. + +### Step 12: Analyze #define vs const in Assembly + +From GDB, we discovered something interesting - **both constants ended up as instruction immediates!** + +**For FAV_NUM (42) - a `#define` macro:** +``` +0x1000028e <+90>: movs r1, #42 @ 0x2a +``` + +The value 42 is embedded directly in a 16-bit Thumb instruction. This is expected - `#define` is text replacement, so the compiler never sees `FAV_NUM`, only `42`. + +**For OTHER_FAV_NUM (1337) - a `const` variable:** +``` +0x10000296 <+98>: movw r1, #1337 @ 0x539 +``` + +The value 1337 is ALSO embedded directly in an instruction - but this time a 32-bit Thumb-2 `movw` because the value doesn't fit in 8 bits. + +**Why wasn't `const` stored in memory?** In theory, `const int OTHER_FAV_NUM = 1337` creates a variable in the `.rodata` section. But the compiler optimized it away because: +1. We never take the address of `OTHER_FAV_NUM` (no `&OTHER_FAV_NUM`) +2. The value fits in a 16-bit `movw` immediate +3. Loading from an immediate is faster than loading from memory + +> Tip: **Key takeaway for reverse engineering:** Don't assume `const` variables will appear as memory loads. Modern compilers aggressively inline constant values. The C keyword `const` is a **source-level** concept - the compiler may or may not honor it in the final binary. + +### Step 13: Analyze the I2C Struct Layout + +In GDB, we examined the `i2c1_inst` struct at `0x2000062c`: + +```gdb +(gdb) x/2wx 0x2000062c +0x2000062c : 0x40098000 0x00000000 +``` + +This maps to the `i2c_inst_t` struct: + +``` ++-----------------------------------------------------------------+ +| i2c_inst_t at 0x2000062c | +| | +| +------------------------------------------------------------+ | +| | Offset Type Name Value | | +| | 0x00 i2c_hw_t * hw 0x40098000 | | +| | 0x04 bool restart_on_next 0x00 (false) | | +| +------------------------------------------------------------+ | +| | ++-----------------------------------------------------------------+ +``` + +The first member (`hw`) points to `0x40098000` - the I2C1 hardware register base. This is the end of the macro chain: `I2C_PORT` -> `i2c1` -> `&i2c1_inst` -> `hw` -> `0x40098000`. + +### Step 14: Locate the String Literals + +We found the LCD strings in flash memory: + +```gdb +(gdb) x/s 0x10003ee8 +0x10003ee8: "Reverse" + +(gdb) x/s 0x10003ef0 +0x10003ef0: "Engineering" +``` + +These are stored consecutively in the `.rodata` section. Note the addresses - we'll need them for patching. + +--- + +## Part 10: Hacking the Binary with a Hex Editor + +Now for the fun part - we'll patch the `.bin` file directly using a hex editor! + +> Tip: **Why a hex editor?** GDB **cannot write to flash memory** - the `0x10000000+` address range where program instructions and read-only data live. Trying `set *(char *)0x1000028e = 0x2b` in GDB gives `Writing to flash memory forbidden in this context`. To make **permanent** patches that survive a power cycle, we edit the `.bin` file directly with a hex editor and re-flash it. + +### Step 15: Open the Binary in a Hex Editor + +1. Open **HxD** (or your preferred hex editor: ImHex, 010 Editor, etc.) +2. Click **File** -> **Open** +3. Navigate to `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants\build\` +4. Open `0x0017_constants.bin` + +### Step 16: Calculate the File Offset + +The binary is loaded at base address `0x10000000`. To find the file offset of any address: + +``` +file_offset = address - 0x10000000 +``` + +For example: +- Address `0x1000028e` -> file offset `0x28E` (654 in decimal) +- Address `0x10003ee8` -> file offset `0x3EE8` (16104 in decimal) + +### Step 17: Understand FAV_NUM Encoding (movs - 16-bit Thumb) + +From our GDB analysis, we know the instruction at `0x1000028e` is: + +``` +movs r1, #0x2a -> bytes: 2a 21 +``` + +In HxD, use **Ctrl+G** to navigate to file offset `28E` and verify you see the byte `2A` followed by `21`. + +> ?? **How Thumb encoding works:** In `movs r1, #imm8`, the immediate value is the first byte, and the opcode `21` is the second byte. So the bytes `2a 21` encode `movs r1, #0x2a` (42). If you wanted to change this to 43, you'd change `2A` to `2B`. + +### Step 18: Understand OTHER_FAV_NUM Encoding (movw - 32-bit Thumb-2) + +From GDB, we found the `movw r1, #1337` instruction at `0x10000296`. Examine the exact bytes: + +```gdb +(gdb) x/4bx 0x10000296 +0x10000296 : 0x40 0xf2 0x39 0x51 +``` + +This is the 32-bit Thumb-2 encoding of `movw r1, #0x539` (1337). The bytes break down as: + +``` ++-----------------------------------------------------------------+ +| movw r1, #0x539 -> bytes: 40 F2 39 51 | +| | +| Byte 0: 0x40 -?? | +| Byte 1: 0xF2 -+ First halfword (opcode + upper imm bits) | +| Byte 2: 0x39 ---- Lower 8 bits of immediate (imm8) ?? CHANGE | +| Byte 3: 0x51 ---- Destination register (r1) + upper imm bits | +| | +| imm16 = 0x0539 = 1337 decimal | +| imm8 field = 0x39 (lower 8 bits of the value) | +| | ++-----------------------------------------------------------------+ +``` + +The file offset is `0x10000296 - 0x10000000 = 0x296`. The imm8 byte is the 3rd byte of the instruction: `0x296 + 2 = 0x298`. + +To change `movw r1, #1337` to `movw r1, #1344`: + +1. In HxD, press **Ctrl+G** (Go to offset) +2. Enter offset: `298` (the third byte of the 4-byte instruction) +3. You should see the byte `39` at this position +4. Change `39` to `40` + +> ?? **Why offset `0x298` and not `0x296`?** The lower 8 bits of the immediate (`imm8`) are in the **third byte** of the 4-byte `movw` instruction. The instruction starts at file offset `0x296`, so imm8 is at `0x296 + 2 = 0x298`. Changing `0x39` to `0x40` changes the value from `0x539` (1337) to `0x540` (1344). + +### Step 19: Hack - Change LCD Text from "Reverse" to "Exploit" + +**IMPORTANT:** The new string must be the **same length** as the original! "Reverse" and "Exploit" are both 7 characters - perfect! + +From our GDB analysis in Step 10, we found the string at `0x10003ee8`. File offset = `0x10003ee8 - 0x10000000 = 0x3EE8`. + +1. In HxD, press **Ctrl+G** and enter offset: `3EE8` +2. You should see the bytes for "Reverse": `52 65 76 65 72 73 65 00` +3. Change the bytes to spell "Exploit": `45 78 70 6c 6f 69 74 00` + +**ASCII Reference:** + +| Character | Hex | +| --------- | ------ | +| E | `0x45` | +| x | `0x78` | +| p | `0x70` | +| l | `0x6c` | +| o | `0x6f` | +| i | `0x69` | +| t | `0x74` | + +### Step 20: Save the Patched Binary + +1. Click **File** -> **Save As** +2. Save as `0x0017_constants-h.bin` in the build directory +3. Close the hex editor + +--- + +## Part 11: Converting and Flashing the Hacked Binary + +### Step 21: Convert to UF2 Format + +Open a terminal and navigate to your project directory: + +```cmd +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants +``` + +Run the conversion command: + +```cmd +python ..\uf2conv.py build\0x0017_constants-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +### Step 22: Flash the Hacked Binary + +1. Hold BOOTSEL and plug in your Pico 2 +2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive +3. Check your LCD and serial monitor + +### Step 23: Verify the Hack + +**Check the LCD:** +- Line 1 should now show: `Exploit` (instead of "Reverse") +- Line 2 should still show: `Engineering` + +**Check the serial monitor:** +``` +FAV_NUM: 42 +OTHER_FAV_NUM: 1337 +FAV_NUM: 42 +OTHER_FAV_NUM: 1337 +... +``` + +The numbers are unchanged - we only patched the LCD string! + + **BOOM! We successfully changed the LCD text from "Reverse" to "Exploit" without access to the source code!** + +--- + +## Part 12: Summary and Review + +### What We Accomplished + +1. **Learned about constants** - `#define` macros vs `const` variables +2. **Understood I2C communication** - Two-wire protocol for peripheral communication +3. **Explored C structs** - How the Pico SDK abstracts hardware +4. **Mastered the macro chain** - From `I2C_PORT` to `0x40098000` +5. **Examined structs in GDB** - Inspected memory layout of `i2c_inst_t` +6. **Analyzed instruction encodings** - Both `movs` (8-bit) and `movw` (16-bit) immediates in the hex editor +7. **Patched a string literal** - Changed LCD display text from "Reverse" to "Exploit" + +### #define vs const Summary + +``` ++-----------------------------------------------------------------+ +| #define FAV_NUM 42 | +| ------------------- | +| - Text replacement at compile time | +| - No memory allocated | +| - Cannot take address (&FAV_NUM is invalid) | +| - In binary: value appears as immediate (movs r1, #0x2a) | +| - To hack: patch the instruction operand | ++-----------------------------------------------------------------+ +| const int OTHER_FAV_NUM = 1337 | +| ------------------------------ | +| - Theoretically in .rodata, but compiler optimized it away | +| - Value embedded as immediate: movw r1, #0x539 (32-bit instr) | +| - Optimization: compiler saw &OTHER_FAV_NUM is never used | +| - In binary: immediate in instruction, same as #define! | +| - To hack: patch instruction operand (imm8 byte at offset +2) | ++-----------------------------------------------------------------+ +``` + +### I2C Configuration Summary + +``` ++-----------------------------------------------------------------+ +| I2C Setup Steps | +| | +| 1. i2c_init(i2c1, 100000) - Initialize at 100kHz | +| 2. gpio_set_function(pin, I2C) - Assign pins to I2C | +| 3. gpio_pull_up(sda_pin) - Enable SDA pull-up | +| 4. gpio_pull_up(scl_pin) - Enable SCL pull-up | +| 5. lcd_i2c_init(...) - Initialize the device | +| | ++-----------------------------------------------------------------+ +``` + +### The Struct Chain + +``` ++-----------------------------------------------------------------+ +| I2C_PORT -> i2c1 -> &i2c1_inst -> i2c_inst_t | +| | | +| +-- hw -> i2c_hw_t * | +| | +-- 0x40098000 | +| | | +| +-- restart_on_next (bool) | +| | ++-----------------------------------------------------------------+ +``` + +### Key Memory Addresses + +| Address | Description | +| ------------ | ---------------------------------- | +| `0x10000234` | main() entry point | +| `0x1000028e` | FAV_NUM value in instruction | +| `0x10000296` | OTHER_FAV_NUM value in instruction | +| `0x10003ee8` | "Reverse" string literal (example) | +| `0x40098000` | I2C1 hardware registers base | +| `0x2000062C` | i2c1_inst struct in SRAM | + +--- + +--- + +## Key Takeaways + +1. **#define is text replacement** - It happens before compilation, no memory used. + +2. **const creates real variables** - Stored in .rodata, takes memory, has an address. + +3. **I2C uses two wires** - SDA for data, SCL for clock, pull-ups required. + +4. **Structs group related data** - The SDK uses them to abstract hardware. + +5. **Macros can chain** - `I2C_PORT` -> `i2c1` -> `&i2c1_inst` -> hardware pointer. + +6. **ARM passes args in registers** - r0-r3 for first four arguments. + +7. **GDB reveals struct layouts** - Examine memory to understand data organization. + +8. **String hacking requires same length** - Or you'll corrupt adjacent data! + +9. **Constants aren't constant** - With binary patching, everything can change! + +10. **Compiler optimization changes code** - `gpio_pull_up` becomes `gpio_set_pulls`. + +--- + +## Glossary + +| Term | Definition | +| ----------------------- | --------------------------------------------------- | +| **#define** | Preprocessor directive for text replacement | +| **AAPCS** | ARM Architecture Procedure Call Standard | +| **const** | Keyword marking a variable as read-only | +| **Forward Declaration** | Telling compiler a type exists before defining it | +| **I2C** | Inter-Integrated Circuit - two-wire serial protocol | +| **Immediate Value** | A constant embedded directly in an instruction | +| **Open-Drain** | Output that can only pull low, not drive high | +| **PCF8574** | Common I2C I/O expander chip used in LCD backpacks | +| **Preprocessor** | Tool that processes code before compilation | +| **Pull-Up Resistor** | Resistor that holds a line HIGH by default | +| **SCL** | Serial Clock - I2C timing signal | +| **SDA** | Serial Data - I2C data line | +| **Struct** | User-defined type grouping related variables | +| **typedef** | Creates an alias for a type | + +--- + +## Additional Resources + +### I2C Timing Reference + +| Speed Mode | Maximum Frequency | +| ---------- | ----------------- | +| Standard | 100 kHz | +| Fast | 400 kHz | +| Fast Plus | 1 MHz | + +### Common I2C Addresses + +| Device | Address | +| --------------------- | ------------- | +| PCF8574 LCD (default) | `0x27` | +| PCF8574A LCD | `0x3F` | +| DS3231 RTC | `0x68` | +| BMP280 Sensor | `0x76`/`0x77` | +| SSD1306 OLED | `0x3C`/`0x3D` | + +### Key ARM Instructions for Constants + +| Instruction | Description | +| -------------------- | ------------------------------------------- | +| `movs rN, #imm` | Load small immediate (0-255) directly | +| `ldr rN, [pc, #off]` | Load larger value from literal pool | +| `ldr rN, =value` | Pseudo-instruction for loading any constant | + +### RP2350 I2C Memory Map + +| Address | Description | +| ------------ | ----------------------- | +| `0x40090000` | I2C0 hardware registers | +| `0x40098000` | I2C1 hardware registers | + +--- + +**Remember:** When you see complex nested structures in a binary, take your time to understand the hierarchy. Use GDB to examine struct layouts in memory and trace pointer chains. And always remember - even "constants" can be hacked! + +Happy hacking! ? \ No newline at end of file diff --git a/WEEK07/slides/WEEK07-IMG00.svg b/WEEK07/slides/WEEK07-IMG00.svg new file mode 100644 index 0000000..427c6f7 --- /dev/null +++ b/WEEK07/slides/WEEK07-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 07 + + +Constants in Embedded Systems: +Debugging and Hacking Constants +w/ 1602 LCD I2C Basics + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEK07/slides/WEEK07-IMG01.svg b/WEEK07/slides/WEEK07-IMG01.svg new file mode 100644 index 0000000..bbe54c4 --- /dev/null +++ b/WEEK07/slides/WEEK07-IMG01.svg @@ -0,0 +1,63 @@ + + + + +#define vs const +Preprocessor Macros vs Constant Variables + + + +#define FAV_NUM 42 + +Preprocessor text replacement +Happens BEFORE compilation +No memory allocated +Cannot take address (&) + +In Binary: + +movs r1, #42 @ 0x2a + +16-bit Thumb instruction +Value embedded as immediate +Compiler sees only "42" + + + +const int OTHER_FAV_NUM=1337 + +Creates real variable +Theoretically in .rodata +Has an address (if needed) +Type-checked by compiler + +In Binary: + +movw r1, #1337 @ 0x539 + +32-bit Thumb-2 instruction +Also embedded as immediate! +Compiler optimized it away + + + +KEY INSIGHT: +Both ended up as instruction immediates! +The compiler saw &OTHER_FAV_NUM is never used, so it +optimized const the same way as #define -- no memory load needed. +Lesson: const is a source-level concept -- not guaranteed in binary + + + + \ No newline at end of file diff --git a/WEEK07/slides/WEEK07-IMG02.svg b/WEEK07/slides/WEEK07-IMG02.svg new file mode 100644 index 0000000..d798cd5 --- /dev/null +++ b/WEEK07/slides/WEEK07-IMG02.svg @@ -0,0 +1,85 @@ + + + + +I2C Protocol +Two-Wire Serial Communication + + + +What is I2C? +Two-wire serial protocol +SDA += Serial Data +SCL += Serial Clock +Open-drain with pull-up resistors + + + +I2C Bus + +Pico + + +SDA +SCL + +LCD +GPIO 2 = SDA, GPIO 3 = SCL +Pull-ups hold lines HIGH + + + +Common I2C Addresses (7-bit) + +0x27 +LCD + +0x3F +LCD Alt + +0x48 +Sensor + +0x50 +EEPROM + + + +I2C Transaction Flow + +START +--> + +Address +--> + +ACK +--> + +Data +--> + +ACK +--> + +STOP + +Master sends START, then 7-bit address + R/W bit +Slave responds with ACK, then data bytes follow + + + + \ No newline at end of file diff --git a/WEEK07/slides/WEEK07-IMG03.svg b/WEEK07/slides/WEEK07-IMG03.svg new file mode 100644 index 0000000..d69b142 --- /dev/null +++ b/WEEK07/slides/WEEK07-IMG03.svg @@ -0,0 +1,66 @@ + + + + +C Structs & typedef +Grouping Related Data in C + + + +Struct Definition + +typedef struct { +i2c_hw_t *hw; +bool restart_on_next; +} i2c_inst_t; +typedef creates an alias +so we can write: i2c_inst_t var; +instead of: struct { ... } var; + + + +Memory Layout +i2c_inst_t at 0x2000062C + + +Offset 0x00 +hw += 0x40098000 +i2c_hw_t* (4 bytes) + + +Offset 0x04 +restart_on_next += 0x00 (false) +bool (1 byte) + +Total struct size: 8 bytes +hw points to I2C1 registers + + + +Forward Declaration + +struct i2c_inst; +// tells compiler: this type exists, define later + + + +Why Structs Matter in RE +GDB shows raw memory -- you must recognize struct layouts +x/2wx 0x2000062c shows: 0x40098000 0x00000000 + + + + \ No newline at end of file diff --git a/WEEK07/slides/WEEK07-IMG04.svg b/WEEK07/slides/WEEK07-IMG04.svg new file mode 100644 index 0000000..927fd8c --- /dev/null +++ b/WEEK07/slides/WEEK07-IMG04.svg @@ -0,0 +1,80 @@ + + + + +Pico SDK Macro Chain +From I2C_PORT to Hardware Registers + + + +Macro Expansion Chain + + + +I2C_PORT +#define I2C_PORT i2c1 + + +--> + + + +i2c1 +#define i2c1 (&i2c1_inst) + + +--> + + + +&i2c1_inst +Address of global struct + + +Struct Contents at 0x2000062C + +i2c_inst_t i2c1_inst = { +.hw = (i2c_hw_t *)0x40098000, +.restart_on_next = false +}; + + +Hardware Register Access + + +i2c1_inst.hw +--> +i2c1_hw +--> +(i2c_hw_t*)0x40098000 + +I2C1_BASE = 0x40098000 +I2C0_BASE = 0x40090000 +Direct memory-mapped I/O to RP2350 peripheral + + + +FULL CHAIN: +I2C_PORT +--> +i2c1 +--> +&i2c1_inst +--> +0x40098000 +Macro --> Macro --> Struct pointer --> HW register base + + + + \ No newline at end of file diff --git a/WEEK07/slides/WEEK07-IMG05.svg b/WEEK07/slides/WEEK07-IMG05.svg new file mode 100644 index 0000000..de2e153 --- /dev/null +++ b/WEEK07/slides/WEEK07-IMG05.svg @@ -0,0 +1,53 @@ + + + + +Source Code +0x0017_constants.c + + + + + +//--- Defines and Constants --- +#define FAV_NUM 42 +#define I2C_PORT i2c1 +#define I2C_SDA_PIN 2 +#define I2C_SCL_PIN 3 +const int OTHER_FAV_NUM = 1337; + +//--- Main Loop --- +lcd_set_cursor(0, 0); +lcd_puts("Reverse"); +lcd_set_cursor(1, 0); +lcd_puts("Engineering"); + +//--- Serial Output Loop --- +printf("FAV_NUM: %d\r\n", FAV_NUM); +printf("OTHER_FAV_NUM: %d\r\n", OTHER_FAV_NUM); + + + +LCD Output +Line 0: "Reverse" +Line 1: "Engineering" + + +Serial Output +FAV_NUM: 42 +OTHER_FAV_NUM: 1337 + + + + \ No newline at end of file diff --git a/WEEK07/slides/WEEK07-IMG06.svg b/WEEK07/slides/WEEK07-IMG06.svg new file mode 100644 index 0000000..738b366 --- /dev/null +++ b/WEEK07/slides/WEEK07-IMG06.svg @@ -0,0 +1,65 @@ + + + + +GDB Analysis +Disassembly of main() at 0x10000234 + + + +Key Instructions from x/54i 0x10000234 + + +push {r3, lr} +// save return addr +bl stdio_init_all +// init serial +ldr r1, [pc, #104] +// r1 = 100000 (baud) +ldr r0, [pc, #104] +// r0 = &i2c1_inst +bl i2c_init +// init I2C at 100kHz +movs r0, #2 +// GPIO 2 (SDA) +bl gpio_set_function +// set pin to I2C +movs r1, #39 +// 0x27 = LCD addr +bl lcd_i2c_init +// init LCD device + +b.n 0x1000028e +// infinite loop start +... +AAPCS: r0-r3 = first 4 args, r0 = return value + + + +Literal Pool at 0x100002A4 + + +0x000186A0 +I2C baudrate (100000) +0x2000062C +&i2c1_inst struct in RAM +0x10003EE8 +"Reverse" string in flash +0x10003EF0 +"Engineering" string in flash +0x10003EFC +"FAV_NUM: %d\r\n" +0x10003F0C +"OTHER_FAV_NUM: %d\r\n" + \ No newline at end of file diff --git a/WEEK07/slides/WEEK07-IMG07.svg b/WEEK07/slides/WEEK07-IMG07.svg new file mode 100644 index 0000000..15a1e54 --- /dev/null +++ b/WEEK07/slides/WEEK07-IMG07.svg @@ -0,0 +1,71 @@ + + + + +Instruction Encoding +movs (16-bit Thumb) vs movw (32-bit Thumb-2) + + + +movs r1, #42 (FAV_NUM) +At address 0x1000028E + + +Bytes: 2A 21 + +2A = immediate value (42) +21 = opcode (movs r1) +16-bit Thumb instruction +Fits values 0-255 in 8 bits +File offset: 0x28E + + + +movw r1, #1337 (OTHER_FAV) +At address 0x10000296 + + +Bytes: 40 F2 39 51 + +40 F2 = opcode (first halfword) +39 = imm8 (lower 8 bits) +51 = dest reg + upper imm +32-bit Thumb-2 instruction +File offset: 0x296 + + + +movw Byte Layout (40 F2 39 51) + + +40 F2 +Opcode + upper imm + + +39 +imm8 (lower 8 bits) + + +51 +Dest reg (r1) + bits + +imm16 = 0x539 += 1337 decimal + + + +Why movw instead of movs? +1337 > 255 -- does not fit in 8-bit movs immediate +movw encodes 0-65535 in 32-bit instruction + \ No newline at end of file diff --git a/WEEK07/slides/WEEK07-IMG08.svg b/WEEK07/slides/WEEK07-IMG08.svg new file mode 100644 index 0000000..9f8ea92 --- /dev/null +++ b/WEEK07/slides/WEEK07-IMG08.svg @@ -0,0 +1,68 @@ + + + + +I2C Struct in Memory +Examining i2c1_inst at 0x2000062C + + + +GDB Memory Dump + +x/2wx 0x2000062c: +0x40098000 +0x00000000 + + + +i2c_inst_t Struct Layout + + + +Offset 0x00 | 4 bytes +i2c_hw_t *hw += 0x40098000 + + +--> + + + +I2C1 HW Registers +Base: 0x40098000 (MMIO) + + + +Offset 0x04 | 1 byte +bool restart_on_next += false + +I2C0 base = 0x40090000 +I2C1 base = 0x40098000 + + + +String Literals in Flash (.rodata) + + +x/s 0x10003ee8: +"Reverse" + + +x/s 0x10003ef0: +"Engineering" + +Stored consecutively in .rodata (flash) +These addresses are targets for patching + \ No newline at end of file diff --git a/WEEK07/slides/WEEK07-IMG09.svg b/WEEK07/slides/WEEK07-IMG09.svg new file mode 100644 index 0000000..5934306 --- /dev/null +++ b/WEEK07/slides/WEEK07-IMG09.svg @@ -0,0 +1,63 @@ + + + + +Hacking the Binary +Patching LCD Text: "Reverse" --> "Exploit" + + + +File Offset Formula +file_offset = address - 0x10000000 +Binary loaded at 0x10000000 + + + +Hack: Change LCD String +Address 0x10003EE8 --> File offset 0x3EE8 + +Original: + +52 65 76 65 72 73 65 00 +"Reverse" + +Patched: + +45 78 70 6C 6F 69 74 00 +"Exploit" + +Same length (7 chars) -- null terminator stays + + + +Flash the Hacked Binary + + +python uf2conv.py build\patched.bin + +1. +Save patched .bin file +2. +Convert to .uf2 format +3. +Hold BOOTSEL, plug in Pico +4. +Drag hacked.uf2 to drive + + + +LCD now shows: "Exploit" +instead of "Reverse" +No source code needed! + \ No newline at end of file diff --git a/WEEK07/slides/WEEK07-IMG10.svg b/WEEK07/slides/WEEK07-IMG10.svg new file mode 100644 index 0000000..3fda6dc --- /dev/null +++ b/WEEK07/slides/WEEK07-IMG10.svg @@ -0,0 +1,88 @@ + + + + +I2C & Macro Exploitation +Constants, I2C, Structs, and Hacking + + + +Key Concepts + +#define +Text replacement, no memory +const +Variable in .rodata (maybe) +I2C +Two-wire: SDA + SCL +struct +Groups related data fields +typedef +Creates type alias +AAPCS +r0-r3 args, r0 return +movs +16-bit, imm 0-255 +movw +32-bit, imm 0-65535 +Literal Pool +Large consts after code + + + +Key Addresses + +0x10000234 +main() entry +0x1000028E +FAV_NUM (movs) +0x10000296 +OTHER_FAV_NUM (movw) +0x10003EE8 +"Reverse" string +0x10003EF0 +"Engineering" string +0x40098000 +I2C1 HW base +0x2000062C +i2c1_inst struct + +file_offset = addr - 0x10000000 +String patches must be same length + + + +Macro Chain +I2C_PORT +--> +i2c1 +--> +&i2c1_inst +--> +0x40098000 + + + +Binary Hack Result +LCD: "Reverse" --> +"Exploit" +Patched at 0x3EE8 +Compiler may optimize const same as #define + + + +TAKEAWAY: +const is a source-level concept. +In binary, everything can change! + \ No newline at end of file diff --git a/WEEK09/WEEK09-SLIDES.pdf b/WEEK09/WEEK09-SLIDES.pdf new file mode 100644 index 0000000..130da37 Binary files /dev/null and b/WEEK09/WEEK09-SLIDES.pdf differ diff --git a/WEEK09/WEEK09.md b/WEEK09/WEEK09.md new file mode 100644 index 0000000..ff9d82f --- /dev/null +++ b/WEEK09/WEEK09.md @@ -0,0 +1,1205 @@ +# Week 9: Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics. + +## What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand all six types of C operators (arithmetic, increment, relational, logical, bitwise, assignment) +- Know how the DHT11 temperature and humidity sensor communicates with the Pico 2 +- Understand how post-increment operators affect variable values +- Navigate to the Reset_Handler and main function in stripped binaries +- Identify function arguments by analyzing register values in Ghidra +- Understand IEEE-754 floating-point representation +- Hack floating-point constants to manipulate sensor readings + +--- + +## Part 1: Understanding C Operators + +### What Are Operators? + +**Operators** are symbols that tell the compiler to perform specific mathematical, logical, or data manipulation operations. Think of them as the "verbs" of programming - they describe actions to perform on data. + +### The Six Types of Operators + +| Type | Example | What It Does | +| -------------- | -------------------- | ----------------------------------- | +| **Arithmetic** | `x * y` | Math operations (+, -, *, /, %) | +| **Increment** | `x++` or `++x` | Increase/decrease by 1 | +| **Relational** | `x > y` | Compare values (returns true/false) | +| **Logical** | `(x > y) && (y > x)` | Combine conditions (AND, OR, NOT) | +| **Bitwise** | `x << 1` | Manipulate individual bits | +| **Assignment** | `x += 5` | Assign and modify values | + +--- + +## Part 2: Arithmetic Operators + +### Basic Math in C + +Arithmetic operators perform mathematical calculations: + +```c +int x = 5; +int y = 10; +int result = x * y; // result = 50 +``` + +``` ++-----------------------------------------------------------------+ +| Arithmetic Operators | +| | +| Operator Example Result Description | +| ------------------------------------------------------------- | +| + 5 + 10 15 Addition | +| - 10 - 5 5 Subtraction | +| * 5 * 10 50 Multiplication | +| / 10 / 5 2 Division | +| % 10 % 3 1 Modulus (remainder) | +| | ++-----------------------------------------------------------------+ +``` + +--- + +## Part 3: Increment and Decrement Operators + +### Pre-Increment vs Post-Increment + +This is where many beginners get confused! There are TWO ways to increment: + +```c +int x = 5; +int a = x++; // Post-increment: a = 5, then x becomes 6 +int b = ++x; // Pre-increment: x becomes 7, then b = 7 +``` + +**The Key Difference:** + +| Type | Syntax | When Value Changes | Example Result | +| ------------------ | ------ | --------------------- | -------------------- | +| **Post-increment** | `x++` | AFTER the expression | `a = x++` -> a=5, x=6 | +| **Pre-increment** | `++x` | BEFORE the expression | `b = ++x` -> x=7, b=7 | + +``` ++-----------------------------------------------------------------+ +| Post-Increment (x++) | +| | +| int x = 5; | +| int result = x++; | +| | +| Step 1: result = x (result gets 5) | +| Step 2: x = x + 1 (x becomes 6) | +| | +| Final: result = 5, x = 6 | +| | +| Think of it as: "Use first, THEN increment" | +| | ++-----------------------------------------------------------------+ +``` + +--- + +## Part 4: Relational Operators + +### Comparing Values + +Relational operators compare two values and return `true` (1) or `false` (0): + +```c +int x = 6; +int y = 10; +bool result = (x > y); // false, because 6 is NOT greater than 10 +``` + +``` ++-----------------------------------------------------------------+ +| Relational Operators | +| | +| Operator Example Result Description | +| ------------------------------------------------------------- | +| > 6 > 10 false Greater than | +| < 6 < 10 true Less than | +| >= 6 >= 6 true Greater than or equal | +| <= 6 <= 10 true Less than or equal | +| == 6 == 10 false Equal to | +| != 6 != 10 true Not equal to | +| | ++-----------------------------------------------------------------+ +``` + +--- + +## Part 5: Logical Operators + +### Combining Conditions + +Logical operators combine multiple conditions: + +```c +int x = 6; +int y = 10; +bool result = (x > y) && (y > x); // false AND true = false +``` + +``` ++-----------------------------------------------------------------+ +| Logical Operators | +| | +| Operator Name Example Result | +| ------------------------------------------------------------- | +| && AND true && true true | +| && AND true && false false | +| || OR true || false true | +| || OR false || false false | +| ! NOT !true false | +| | +| Truth Table for AND (&&): | +| +-------+-------+--------+ | +| | A | B | A && B | | +| +-------+-------+--------+ | +| | false | false | false | | +| | false | true | false | | +| | true | false | false | | +| | true | true | true | | +| +-------+-------+--------+ | +| | ++-----------------------------------------------------------------+ +``` + +--- + +## Part 6: Bitwise Operators + +### Manipulating Individual Bits + +Bitwise operators work on the binary representation of numbers: + +```c +int x = 6; // Binary: 0b00000110 +int result = x << 1; // Shift left by 1: 0b00001100 = 12 +``` + +``` ++-----------------------------------------------------------------+ +| Bitwise Left Shift (<<) | +| | +| x = 6 = 0b00000110 | +| | +| x << 1 means "shift all bits LEFT by 1 position" | +| | +| Before: 0 0 0 0 0 1 1 0 (6) | +| ? ? ? ? ? ? ? ? | +| After: 0 0 0 0 1 1 0 0 (12) | +| | +| Each left shift DOUBLES the value! | +| 6 << 1 = 12 (same as 6 * 2) | +| | ++-----------------------------------------------------------------+ +``` + +**Common Bitwise Operators:** + +| Operator | Name | Example | Result | +| -------- | ----------- | -------- | ------------------ | +| `<<` | Left shift | `6 << 1` | 12 (multiply by 2) | +| `>>` | Right shift | `6 >> 1` | 3 (divide by 2) | +| `&` | AND | `6 & 3` | 2 (bits in common) | +| `\|` | OR | `6 \| 3` | 7 (all set bits) | +| `^` | XOR | `6 ^ 3` | 5 (different bits) | +| `~` | NOT | `~6` | Inverts all bits | + +--- + +## Part 7: Assignment Operators + +### Shorthand for Math + Assign + +Assignment operators combine math with assignment: + +```c +int x = 6; +x += 5; // Same as: x = x + 5; Result: x = 11 +``` + +``` ++-----------------------------------------------------------------+ +| Compound Assignment Operators | +| | +| Operator Example Equivalent To Result (if x=6) | +| ------------------------------------------------------------- | +| += x += 5 x = x + 5 x = 11 | +| -= x -= 2 x = x - 2 x = 4 | +| *= x *= 3 x = x * 3 x = 18 | +| /= x /= 2 x = x / 2 x = 3 | +| %= x %= 4 x = x % 4 x = 2 | +| | ++-----------------------------------------------------------------+ +``` + +--- + +## Part 8: Understanding the DHT11 Sensor + +### What is the DHT11? + +The **DHT11** is a low-cost digital temperature and humidity sensor. It uses a single wire for communication (plus power and ground). + +``` ++-----------------------------------------------------------------+ +| DHT11 Pinout | +| | +| +-------------+ | +| | DHT11 | | +| | +-----+ | | +| | | | | | +| | | | | | +| | +-----+ | | +| | 1 2 3 4 | | +| +--+--+--+--+-+ | +| | | | | | +| VCC DATA NC GND | +| | +| Pin 1: VCC (3.3V or 5V) | +| Pin 2: DATA (connect to GPIO) | +| Pin 3: Not Connected | +| Pin 4: GND (Ground) | +| | ++-----------------------------------------------------------------+ +``` + +### DHT11 Specifications + +| Parameter | Range | Accuracy | +| --------------- | ------------ | -------- | +| **Humidity** | 20% - 90% RH | ?5% RH | +| **Temperature** | 0 deg C - 50 deg C | ?2 deg C | + +### How DHT11 Communication Works + +The DHT11 uses a custom one-wire protocol: + +1. **Host sends start signal** - Pull data line LOW for 18ms +2. **DHT11 responds** - Pulls line LOW for 80 us, then HIGH for 80 us +3. **Data transmission** - 40 bits sent (8 humidity int, 8 humidity decimal, 8 temp int, 8 temp decimal, 8 checksum) + +--- + +## Part 9: Understanding Pointers (Quick Review) + +### The & Operator (Address-Of) + +When you see `&variable`, it means "the memory address of variable": + +```c +float hum, temp; + +// Pass ADDRESSES to the function so it can modify our variables +if (dht11_read(&hum, &temp)) { + printf("Humidity: %.1f%%, Temperature: %.1f?C\n", hum, temp); +} +``` + +``` ++-----------------------------------------------------------------+ +| Passing by Reference | +| | +| Stack Memory | +| +----------------------------+ | +| | Address 0x20000008: hum |?--- &hum (passed to function) | +| | Value: 51.0 | | +| +----------------------------+ | +| | Address 0x2000000C: temp |?--- &temp (passed to function) | +| | Value: 23.8 | | +| +----------------------------+ | +| | +| dht11_read() receives the ADDRESSES, so it can write | +| new values directly into hum and temp! | +| | ++-----------------------------------------------------------------+ +``` + +--- + +## Part 10: Setting Up Your Environment + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. A Raspberry Pi Pico Debug Probe +3. Ghidra installed (for static analysis) +4. Python installed (for UF2 conversion) +5. A serial monitor (PuTTY, minicom, or screen) +6. A DHT11 temperature and humidity sensor +7. The sample project: `0x001a_operators` + +### Hardware Setup + +Connect your DHT11 like this: + +| DHT11 Pin | Pico 2 Pin | +| --------- | ---------- | +| VCC | 3.3V | +| DATA | GPIO 4 | +| GND | GND | + +``` ++-----------------------------------------------------------------+ +| DHT11 Wiring | +| | +| Pico 2 DHT11 Sensor | +| +----------+ +----------+ | +| | | | | | +| | GPIO 4 |---------- DATA ------?| DATA | | +| | | | | | +| | 3.3V |---------- VCC -------?| VCC | | +| | | | | | +| | GND |---------- GND -------?| GND | | +| | | | | | +| +----------+ +----------+ | +| | +| Note: Some DHT11 modules have a built-in pull-up resistor. | +| If yours doesn't, add a 10K resistor between DATA and VCC. | +| | ++-----------------------------------------------------------------+ +``` + +### Project Structure + +``` +Embedded-Hacking/ ++-- 0x001a_operators/ +| +-- build/ +| | +-- 0x001a_operators.uf2 +| | +-- 0x001a_operators.bin +| +-- main/ +| | +-- 0x001a_operators.c +| +-- dht11.h ++-- uf2conv.py +``` + +--- + +## Part 11: Hands-On Tutorial - The Operators Code + +### Step 1: Review the Source Code + +Let's examine the operators code: + +**File: `0x001a_operators.c`** + +```c +#include +#include "pico/stdlib.h" +#include "dht11.h" + +int main(void) { + stdio_init_all(); + + dht11_init(4); + + int x = 5; + int y = 10; + int arithmetic_operator = (x * y); + int increment_operator = x++; + bool relational_operator = (x > y); + bool logical_operator = (x > y) && (y > x); + int bitwise_operator = (x<<1); // x is now 6 because of x++ or 0b00000110 and (x<<1) is 0b00001100 or 12 + int assignment_operator = (x += 5); + + while (true) { + printf("arithmetic_operator: %d\r\n", arithmetic_operator); + printf("increment_operator: %d\r\n", increment_operator); + printf("relational_operator: %d\r\n", relational_operator); + printf("logical_operator: %d\r\n", logical_operator); + printf("bitwise_operator: %d\r\n", bitwise_operator); + printf("assignment_operator: %d\r\n", assignment_operator); + + float hum, temp; + if (dht11_read(&hum, &temp)) { + printf("Humidity: %.1f%%, Temperature: %.1f?C\r\n", hum, temp); + } else { + printf("DHT11 read failed\r\n"); + } + + sleep_ms(2000); + } +} +``` + +### Step 2: Understand the Variable Flow + +Let's trace through what happens to `x`: + +``` ++-----------------------------------------------------------------+ +| Variable x Through the Program | +| | +| Line | x value | Result | +| ------------------+---------+--------------------------------- | +| int x = 5; | 5 | x initialized to 5 | +| x * y | 5 | arithmetic = 5 * 10 = 50 | +| x++ | 5->6 | increment = 5 (then x becomes 6)| +| x > y | 6 | relational = (6 > 10) = false | +| (x>y) && (y>x) | 6 | logical = false && true = false | +| x << 1 | 6 | bitwise = 6 << 1 = 12 | +| x += 5 | 6->11 | assignment = 6 + 5 = 11 | +| | ++-----------------------------------------------------------------+ +``` + +### Step 3: Flash the Binary to Your Pico 2 + +1. Hold the BOOTSEL button on your Pico 2 +2. Plug in the USB cable (while holding BOOTSEL) +3. Release BOOTSEL - a drive called "RPI-RP2" appears +4. Drag and drop `0x001a_operators.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 4: Verify It's Working + +Open your serial monitor (PuTTY at 115200 baud) and you should see: + +``` +arithmetic_operator: 50 +increment_operator: 5 +relational_operator: 0 +logical_operator: 0 +bitwise_operator: 12 +assignment_operator: 11 +Humidity: 51.0%, Temperature: 23.8 deg C +``` + +**Understanding the Output:** + +| Variable | Value | Explanation | +| ------------------- | ------ | --------------------------------------------- | +| arithmetic_operator | 50 | 5 * 10 = 50 | +| increment_operator | 5 | Post-increment returns value BEFORE increment | +| relational_operator | 0 | 6 > 10 is false (0) | +| logical_operator | 0 | false AND true = false (0) | +| bitwise_operator | 12 | 6 (0b0110) << 1 = 12 (0b1100) | +| assignment_operator | 11 | 6 + 5 = 11 | +| Humidity | 51.0% | Real reading from DHT11 | +| Temperature | 23.8 deg C | Real reading from DHT11 | + +--- + +## Part 12: Debugging with GDB + +### Step 5: Start OpenOCD (Terminal 1) + +Open a terminal and start OpenOCD: + +```powershell +openocd -s "$env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev\scripts" -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000" +``` + +You should see output indicating OpenOCD connected successfully to your Pico 2 via the Debug Probe. + +### Step 6: Start GDB (Terminal 2) + +Open a **new terminal** and launch GDB with the binary: + +```cmd +arm-none-eabi-gdb build\0x001a_operators.elf +``` + +### Step 7: Connect to the Remote Target + +Inside GDB, type: + +``` +target extended-remote :3333 +``` + +This connects GDB to OpenOCD. + +### Step 8: Halt the Running Binary + +``` +monitor reset halt +``` + +This stops the Pico 2 so we can examine its state. + +### Step 9: Examine Main Function + +Let's examine the main function. Disassemble from the entry point: + +``` +x/60i 0x10000234 +``` + +You should see the operator calculations and function calls: + +``` + 0x10000234
: push {r4, r5, r6, r7, lr} + 0x10000236 : sub sp, #20 + 0x10000238 : bl 0x10003384 + 0x1000023c : movs r0, #4 + 0x1000023e : bl 0x100002d4 +... +``` + +### Step 10: Set a Breakpoint at Main + +``` +b *0x10000234 +c +``` + +### Step 11: Find the Operator Calculations + +The compiler likely optimized many of these calculations at compile time. Look for immediate values: + +``` +x/32i 0x10000240 +``` + +You may see values like: +- `#0x32` (50) for arithmetic_operator +- `#0x5` (5) for increment_operator +- `#0x0` (0) for relational and logical operators +- `#0xc` (12) for bitwise_operator +- `#0xb` (11) for assignment_operator + +### Step 12: Examine Printf Arguments + +Set a breakpoint before the first printf and examine registers: + +```gdb +b *0x10000262 +c +i r r0 r1 +``` + +You should see: +- `r0` = address of format string +- `r1` = value to print + +### Step 13: Examine the Format Strings + +```gdb +x/s 0x10003978 +``` + +Find the format strings and value for print: +```gdb +(gdb) x/s 0x10003978 +0x10003978: "Humidity: %.1f%%, Temperature: %.1fA°C\r\n" +(gdb) x/x 0x4037cccc +0x4037cccc: 0x00 +(gdb) x/x $r1 +0x4037cccc: 0x00 +... +``` + +### Step 14: Examine DHT11 Function Call + +Find where dht11_read is called: + +```gdb +(gdb) x/3i 0x1000029f +``` + +You'll see stack addresses being passed as arguments: +``` + 0x1000029f : add r1, sp, #12 + 0x100002a1 : add r0, sp, #8 + 0x100002a3 : bl 0x100002f4 +``` + +### Step 15: Watch the Float Values + +After dht11_read returns, examine the float values on the stack: + +```gdb +(gdb) x/2fw $sp+8 +0x20081fe0: 62 23.7999992 +``` + +This shows the humidity and temperature as floats. + +### Step 16: Step Through the Loop + +Continue execution and watch the values: + +```gdb +c +``` + +The program will loop, printing values to serial. + +--- + +## Part 13: Setting Up Ghidra for Analysis + +### Step 17: Start Ghidra + +Open a terminal and type: + +```cmd +ghidraRun +``` + +### Step 18: Create a New Project + +1. Click **File** -> **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `0x001a_operators` +5. Click **Finish** + +### Step 19: Import the Binary + +1. Open your file explorer +2. Navigate to the `0x001a_operators/build/` folder +3. **Drag and drop** the `.bin` file into Ghidra's project window + +### Step 20: Configure the Binary Format + +**Click the three dots (...) next to "Language" and:** +1. Search for "Cortex" +2. Select **ARM Cortex 32 little endian default** +3. Click **OK** + +**Click the "Options..." button and:** +1. Change **Block Name** to `.text` +2. Change **Base Address** to `10000000` +3. Click **OK** + +### Step 21: Analyze the Binary + +1. Double-click on the file in the project window +2. A dialog asks "Analyze now?" - Click **Yes** +3. Use default analysis options and click **Analyze** + +Wait for analysis to complete. + +--- + +## Part 14: Finding the Reset_Handler + +### Step 22: Understand the Vector Table + +In ARM Cortex-M, the **vector table** is at the base of flash (0x10000000). The second entry (offset 4) contains the Reset_Handler address. + +``` ++-----------------------------------------------------------------+ +| ARM Vector Table at 0x10000000 | +| | +| Offset Contents Description | +| ------------------------------------------------------------- | +| 0x00 Initial SP value Stack pointer at reset | +| 0x04 Reset_Handler addr First code to execute | +| 0x08 NMI_Handler addr Non-maskable interrupt | +| 0x0C HardFault_Handler Hard fault handler | +| ... | +| | ++-----------------------------------------------------------------+ +``` + +### Step 23: Read the Reset_Handler Address + +1. Press `G` (Go to address) and type `10000004` +2. You'll see bytes like `5d 01 00 10` (your exact bytes may vary) + +**Important:** This is **little-endian**, so we need to reverse the byte order! + +``` ++-----------------------------------------------------------------+ +| Little-Endian Byte Order | +| | +| In memory: 5d 01 00 10 | +| Reversed: 10 00 01 5d | +| As hex: 0x1000015d | +| | +| But wait! ARM uses the THUMB bit! | +| The lowest bit indicates Thumb mode (always set for Cortex-M) | +| Real address: 0x1000015d - 1 = 0x1000015c | +| | ++-----------------------------------------------------------------+ +``` + +### Step 24: Navigate to Reset_Handler + +1. Press `G` and type `1000015c` (or your calculated address) +2. You might see undefined data - that's OK! + +### Step 25: Create the Reset_Handler Function + +If Ghidra didn't automatically recognize this as a function: + +1. Click on the address `0x1000015c` +2. Right-click and press `F` to create a function +3. Right-click -> **Edit Function Signature** +4. Change the name to `Reset_Handler` +5. Click **OK** + +### Step 26: Find Main from Reset_Handler + +The Reset_Handler typically calls three functions: + +``` ++-----------------------------------------------------------------+ +| Reset_Handler Flow (crt0.S) | +| | +| Reset_Handler: | +| 1. Call some_init() ?-- Initialize hardware | +| 2. Call main() ?-- THIS IS WHAT WE WANT! | +| 3. Call exit() ?-- Never returns | +| | +| The MIDDLE function is main! | +| | ++-----------------------------------------------------------------+ +``` + +Look at the end of Reset_Handler for three function calls. The middle one is `main`! + +### Step 27: Navigate to Main + +1. Double-click on the middle function call (should be around `0x10000234`) +2. Right-click -> **Edit Function Signature** +3. Change to: `int main(void)` +4. Click **OK** + +--- + +## Part 15: Resolving Functions in Ghidra + +### Step 28: Resolve stdio_init_all + +The first function call in main is `stdio_init_all`: + +1. Find the call at approximately `0x10000238` +2. Double-click to navigate to the function +3. Right-click -> **Edit Function Signature** +4. Change to: `bool stdio_init_all(void)` +5. Click **OK** + +### Step 29: Resolve dht11_init + +Look for a function call where `r0` is loaded with `0x4`: + +```assembly +movs r0, #0x4 ; GPIO pin 4 +bl FUN_xxxxx ; dht11_init +``` + +**How do we know it's dht11_init?** +- The argument `4` is the GPIO pin number +- We physically connected the DHT11 to GPIO 4! + +1. Right-click -> **Edit Function Signature** +2. Change to: `void dht11_init(uint pin)` +3. Click **OK** + +### Step 30: Resolve printf + +Look for repeated function calls with string addresses: + +1. Find a call like the one at `0x10000262` +2. Right-click -> **Edit Function Signature** +3. Change to: `int printf(char *format,...)` +4. Check the **Varargs** checkbox +5. Click **OK** + +### Step 31: Resolve sleep_ms + +Look for a function call where `r0` is loaded with `0x7d0` (2000 in decimal): + +```assembly +ldr r0, =0x7d0 ; 2000 milliseconds +bl FUN_xxxxx ; sleep_ms +``` + +1. Right-click -> **Edit Function Signature** +2. Change to: `void sleep_ms(uint ms)` +3. Click **OK** + +### Step 32: Resolve dht11_read + +This is trickier! Look for a function call with TWO address arguments: + +```assembly +add r1, sp, #0xc ; Address of temp on stack +add r0, sp, #0x8 ; Address of hum on stack +bl FUN_xxxxx ; dht11_read +``` + +**Understanding the stack offsets:** +- `sp + 0x8` = address of `hum` variable +- `sp + 0xc` = address of `temp` variable +- These are `float` pointers passed to the function + +1. Right-click -> **Edit Function Signature** +2. Change to: `bool dht11_read(float *humidity, float *temperature)` +3. Click **OK** + +### Step 33: Resolve puts + +Look for a function call after the `if` statement that takes a single string argument: + +```assembly +ldr r0, ="DHT11 read failed" +bl FUN_xxxxx ; puts +``` + +1. Right-click -> **Edit Function Signature** +2. Change to: `int puts(char *s)` +3. Click **OK** + +--- + +## Part 16: Understanding IEEE-754 Floating-Point + +### What is IEEE-754? + +IEEE-754 is the standard for representing decimal numbers in binary. A 32-bit float is divided into three parts: + +``` ++-----------------------------------------------------------------+ +| IEEE-754 Single Precision (32-bit) Float | +| | +| +-----+-------------+---------------------------------------+ | +| | S | Exponent | Mantissa (Fraction) | | +| | 1 | 8 bits | 23 bits | | +| +-----+-------------+---------------------------------------+ | +| bit bits bits | +| 31 30-23 22-0 | +| | +| Value = (-1)^S * (1 + Mantissa) * 2^(Exponent - 127) | +| | ++-----------------------------------------------------------------+ +``` + +### Example: Decoding 0x3dcccccc (0.1f) + +Let's decode the bytes `cc cc cc 3d`: + +1. **Reverse for little-endian:** `0x3dcccccc` +2. **Convert to binary:** `00111101 11001100 11001100 11001100` +3. **Extract fields:** + - Sign (bit 31): `0` (positive) + - Exponent (bits 30-23): `01111011` = 123 + - Mantissa (bits 22-0): `10011001100110011001100` + +4. **Calculate value:** + - Actual exponent: 123 - 127 = -4 + - Mantissa value: 1.6 (approximately) + - Final value: 1.6 * 2^(-4) ? 0.1 + +### Example: Encoding -1.0f as 0xbf800000 + +For the number -1.0: + +1. **Sign:** 1 (negative) +2. **Exponent:** 127 (for 2^0 = 1) +3. **Mantissa:** 0 (because value is exactly 1.0) + +Binary: `1 01111111 00000000000000000000000` +Hex: `0xbf800000` +Little-endian: `00 00 80 bf` + +### Python for Float Conversion + +```python +import struct + +# Decode bytes to float +bytes_data = bytes.fromhex('cdcccc3d') +value = struct.unpack(' **Bytes** +2. A new panel appears showing raw hex bytes + +### Step 38: Enable Editing + +Look for the pencil icon in the Bytes window toolbar and click it to enable editing mode. + +### Step 39: Hack the Scaling Constant + +Let's change the temperature scaling to add 25% more! + +1. Press `G` and go to address `1000042c` +2. Current bytes: `cd cc cc 3d` (0.1f in little-endian) +3. Change to: `00 00 a0 40` (5.0f in little-endian) + +This changes the multiplier from 0.1 to 5.0, which will dramatically increase the temperature reading! + +### Step 40: Verify the Change + +Use Python to verify what we changed: + +```python +import struct + +# Original value +original = struct.unpack('>> import struct +>>> +>>> # Original value +>>> original = struct.unpack('>> print(f"Original: {original}") # 0.1 +Original: 0.10000000149011612 +>>> +>>> # New value +>>> new = struct.unpack('>> print(f"New: {new}") # 5.0 +New: 5.0 +``` + +--- + +## Part 19: Exporting and Testing + +### Step 41: Export the Patched Binary + +1. Click **File** -> **Export Program** +2. Set **Format** to **Raw Bytes** +3. Navigate to your build directory +4. Name the file `0x001a_operators-h.bin` +5. Click **OK** + +### Step 42: Convert to UF2 Format + +Open a terminal and run: + +```cmd +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators +python ..\uf2conv.py build\0x001a_operators-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +### Step 43: Flash and Test + +1. Hold BOOTSEL and plug in your Pico 2 +2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive +3. Open your serial monitor + +You should see dramatically increased temperature readings! + +``` +Humidity: 60.0%, Temperature: 63.0°C +arithmetic_operator: 50 +increment_operator: 5 +relational_operator: 0 +logical_operator: 0 +bitwise_operator: 12 +assignment_operator: 11 +``` + +--- + +## Part 20: Summary and Review + +### What We Accomplished + +1. **Learned all six C operator types** - Arithmetic, increment, relational, logical, bitwise, assignment +2. **Understood post-increment behavior** - `x++` returns value BEFORE incrementing +3. **Learned about the DHT11 sensor** - One-wire protocol for temperature/humidity +4. **Found Reset_Handler from vector table** - Offset 4 contains the address +5. **Identified functions by their arguments** - GPIO pin 4, sleep 2000ms, etc. +6. **Understood IEEE-754 floating-point** - How computers represent decimals +7. **Hacked floating-point constants** - Changed 0.1f to other values + +### The Hacking Workflow + +``` ++-----------------------------------------------------------------+ +| Binary Hacking Workflow | +| | +| 1. Analyze the binary in Ghidra | +| 2. Identify target values/instructions | +| 3. Calculate file offsets from memory addresses | +| 4. Determine replacement bytes | +| 5. Patch the binary (manual in hex editor) | +| 6. Export and convert to UF2 | +| 7. Flash and test | +| | ++-----------------------------------------------------------------+ +``` + +### Key Memory Addresses + +| Memory Address | File Offset | Description | +| -------------- | ----------- | ------------------------------- | +| `0x10000000` | `0x000` | Vector table start | +| `0x10000004` | `0x004` | Reset_Handler address | +| `0x10000234` | `0x234` | main() function (approximately) | +| `0x10000410` | `0x410` | Humidity vfma instruction | +| `0x10000414` | `0x414` | Temperature vfma instruction | +| `0x1000042C` | `0x42C` | 0.1f scaling constant | + +--- + +--- + +## Key Takeaways + +1. **Post-increment returns the OLD value** - `x++` gives you x, THEN adds 1 + +2. **Bitwise left shift multiplies by 2** - `x << 1` is the same as `x * 2` + +3. **Vector table points to Reset_Handler** - Offset 4 from flash base + +4. **Arguments go in r0-r3** - Follow them to identify functions + +5. **IEEE-754 is how floats are stored** - Sign, exponent, mantissa + +6. **File offset = Memory address - Base** - 0x10000410 -> offset 0x410 + +7. **Little-endian reverses byte order** - 0x3dcccccc stored as cc cc cc 3d + +8. **Incremental testing is essential** - Test each change before the next + +10. **Binary patching has real consequences** - Sensor spoofing can be dangerous! + +--- + +## Glossary + +| Term | Definition | +| ------------------ | --------------------------------------------------- | +| **Arithmetic Op** | Operators for math (+, -, *, /, %) | +| **Assignment Op** | Operators that assign and modify (+=, -=, etc.) | +| **Bitwise Op** | Operators on individual bits (<<, >>, &, \|, ^) | +| **DHT11** | Digital humidity and temperature sensor | +| **Exponent** | Power of 2 in IEEE-754 float representation | +| **IEEE-754** | Standard for floating-point number representation | +| **Increment Op** | Operators that add/subtract 1 (++, --) | +| **Little-Endian** | Byte order where least significant byte comes first | +| **Logical Op** | Operators combining conditions (&&, \|\|, !) | +| **Mantissa** | Fractional part of IEEE-754 float | +| **Post-Increment** | `x++` - returns value, then increments | +| **Pre-Increment** | `++x` - increments, then returns value | +| **Relational Op** | Operators comparing values (<, >, ==, !=) | +| **Reset_Handler** | First function executed after CPU reset | +| **Thumb Bit** | Lowest bit of ARM address indicating Thumb mode | +| **Vector Table** | Table of exception/interrupt handler addresses | +| **vfma.f32** | ARM floating-point fused multiply-add instruction | +| **vadd.f32** | ARM floating-point add instruction | + +--- + +## Additional Resources + +### IEEE-754 Float Quick Reference + +| Value | Hex Encoding | Bytes (LE) | +| ----- | ------------ | ----------- | +| 0.1 | 0x3dcccccd | cd cc cc 3d | +| 1.0 | 0x3f800000 | 00 00 80 3f | +| -1.0 | 0xbf800000 | 00 00 80 bf | +| 2.0 | 0x40000000 | 00 00 00 40 | +| -2.0 | 0xc0000000 | 00 00 00 c0 | +| 5.0 | 0x40a00000 | 00 00 a0 40 | +| 10.0 | 0x41200000 | 00 00 20 41 | + +### ARM Floating-Point Instructions + +| Instruction | Description | +| --------------------- | ---------------------------------------- | +| `vfma.f32 Sd, Sn, Sm` | Sd = Sd + (Sn * Sm) (fused multiply-add) | +| `vadd.f32 Sd, Sn, Sm` | Sd = Sn + Sm | +| `vsub.f32 Sd, Sn, Sm` | Sd = Sn - Sm | +| `vmul.f32 Sd, Sn, Sm` | Sd = Sn * Sm | +| `vldr.f32 Sd, [addr]` | Load float from memory | +| `vstr.f32 Sd, [addr]` | Store float to memory | + +--- + +## Real-World Implications + +### Why This Matters + +Imagine a scenario where temperature sensors control critical systems: + +- **Industrial processes** - Chemical reactions that must stay within temperature ranges +- **Medical equipment** - Refrigerators storing vaccines or organs +- **Nuclear facilities** - Cooling systems for reactors +- **HVAC systems** - Climate control in sensitive environments + +By manipulating sensor readings, an attacker could: +- Cause equipment to overheat while displaying normal temperatures +- Trigger false alarms +- Bypass safety interlocks +- Cause physical damage or safety hazards + +### Defensive Measures + +1. **Redundant sensors** - Multiple sensors with consistency checks +2. **Physical security** - Prevent access to programming interfaces +3. **Anomaly detection** - Alert on sudden reading changes + +--- + +**Remember:** The techniques you learned today can be used for good (security research, debugging) or bad (sabotage, fraud). Always use your skills ethically and legally. Understanding how attacks work helps us build more secure systems! + +Happy hacking! diff --git a/WEEK09/slides/WEEK09-IMG00.svg b/WEEK09/slides/WEEK09-IMG00.svg new file mode 100644 index 0000000..3aadd8e --- /dev/null +++ b/WEEK09/slides/WEEK09-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 09 + + +Operators in Embedded Systems: +Debugging and Hacking Operators +w/ DHT11 Sensor Single-Wire Protocol + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEK09/slides/WEEK09-IMG01.svg b/WEEK09/slides/WEEK09-IMG01.svg new file mode 100644 index 0000000..b431706 --- /dev/null +++ b/WEEK09/slides/WEEK09-IMG01.svg @@ -0,0 +1,74 @@ + + + + +C Operators Overview +Six Types of Operators in C + + + +Arithmetic + ++ - * / % +Math operations +5 * 10 = 50 + + +Increment + +x++ ++x x-- +Add/subtract by 1 +x++ returns old val + + +Relational + +> < >= <= == != +Compare values +(6 > 10) = false + + + +Logical + +&& || ! +Combine conditions +AND, OR, NOT + + +Bitwise + +<< >> & | ^ ~ +Manipulate bits +6 << 1 = 12 + + +Assignment + ++= -= *= /= +Assign and modify +x += 5 (x=x+5) + + + +This Week's Program +0x001a_operators.c demonstrates all 6 types +DHT11 temperature/humidity sensor + operator calculations + + + +KEY: +Compiler pre-computes constant expressions +In the binary, most operators become immediate values + \ No newline at end of file diff --git a/WEEK09/slides/WEEK09-IMG02.svg b/WEEK09/slides/WEEK09-IMG02.svg new file mode 100644 index 0000000..3ba9f91 --- /dev/null +++ b/WEEK09/slides/WEEK09-IMG02.svg @@ -0,0 +1,75 @@ + + + + +Arithmetic & Increment +Math Operations and Post/Pre Increment + + + +Arithmetic Operators + ++ +5 + 10 = 15 +Addition +- +10 - 5 = 5 +Subtraction +* +5 * 10 = 50 +Multiplication +/ +10 / 5 = 2 +Division +% +10 % 3 = 1 +Modulus + + + +Post vs Pre Increment + + +Post: x++ +Use value THEN increment +a = x++ --> a=5, x=6 + + +Pre: ++x +Increment THEN use value +b = ++x --> x=7, b=7 + + + +Post-Increment Step by Step + + +int x = 5; +int result = x++; + +Step 1: result = x +result gets 5 +Step 2: x = x + 1 +x becomes 6 + +Final: result = 5 +x = 6 +"Use first, THEN increment" + + + +In our code: +int increment_operator = x++; +x was 5, so increment_operator = 5, then x becomes 6 + \ No newline at end of file diff --git a/WEEK09/slides/WEEK09-IMG03.svg b/WEEK09/slides/WEEK09-IMG03.svg new file mode 100644 index 0000000..1244936 --- /dev/null +++ b/WEEK09/slides/WEEK09-IMG03.svg @@ -0,0 +1,95 @@ + + + + +Relational & Logical +Comparing Values and Combining Conditions + + + +Relational Operators +Compare two values --> true (1) or false (0) + +> +6 > 10 +false +Greater than +< +6 < 10 +true +Less than +>= +6 >= 6 +true +Greater/equal +<= +6 <= 10 +true +Less or equal +== +6 == 10 +false +Equal to +!= +6 != 10 +true +Not equal + + + +Logical Operators +Combine conditions into one result + +&& +AND -- both must be true +|| +OR -- at least one true +! +NOT -- inverts result + + +AND Truth Table + +A +B +A && B +false +false +false +false +true +false +true +false +false +true +true +true + + + +In Our Code (x=6, y=10) + +bool relational = (x > y); +(6 > 10) = false = 0 +bool logical = (x>y) && (y>x); +false && true = false = 0 + + + +In the binary: +Both compile to immediate #0 +Compiler pre-computes: constants are known at compile time +Result 0 = false, Result 1 = true + \ No newline at end of file diff --git a/WEEK09/slides/WEEK09-IMG04.svg b/WEEK09/slides/WEEK09-IMG04.svg new file mode 100644 index 0000000..02cb827 --- /dev/null +++ b/WEEK09/slides/WEEK09-IMG04.svg @@ -0,0 +1,89 @@ + + + + +Bitwise & Assignment +Bit Manipulation and Compound Assignment + + + +Bitwise Operators + +<< +6 << 1 = 12 +Left shift +>> +6 >> 1 = 3 +Right shift +& +6 & 3 = 2 +AND +| +6 | 3 = 7 +OR +^ +6 ^ 3 = 5 +XOR +~ +~6 +NOT (invert) + + +Left shift = multiply by 2 + +0 0 0 0 0 1 1 0 += 6 +0 0 0 0 1 1 0 0 += 12 + + + +Assignment Operators +Shorthand for math + assign + ++= +x += 5 +x = x + 5 +-= +x -= 2 +x = x - 2 +*= +x *= 3 +x = x * 3 +/= +x /= 2 +x = x / 2 +%= +x %= 4 +x = x % 4 + + +In our code (x=6 after x++): + +x += 5 --> 6 + 5 = 11 + + + +In Our Code (x=6, y=10) + +int bitwise = (x<<1); +6 << 1 = 12 (0b0110 --> 0b1100) + + + +Expected Output +bitwise_operator: 12 +assignment_operator: 11 +Both pre-computed by compiler as immediates + \ No newline at end of file diff --git a/WEEK09/slides/WEEK09-IMG05.svg b/WEEK09/slides/WEEK09-IMG05.svg new file mode 100644 index 0000000..4975302 --- /dev/null +++ b/WEEK09/slides/WEEK09-IMG05.svg @@ -0,0 +1,72 @@ + + + + +DHT11 Sensor +Single-Wire Temperature and Humidity + + + +DHT11 Pinout + + +DHT11 +1:VCC 2:DATA 3:NC 4:GND + +Humidity: 20-90% RH (+/-5%) +Temp: 0-50C (+/-2C) +Protocol: custom one-wire + + + +Wiring to Pico 2 + + +Pico + + +DHT11 + + + + +GPIO 4 = DATA +3.3V = VCC +GND = GND + + + +1. Host pulls LOW 18ms +2. DHT11 responds, sends 40 bits + + + +Source Code: 0x001a_operators.c + +int x = 5, y = 10; +int arithmetic = (x * y); +// 50 +int increment = x++; +// 5 (post) +bool relational = (x > y); +// false +bool logical = (x>y)&&(y>x); +// false +int bitwise = (x<<1); +// 12 +int assignment = (x += 5); +// 11 +float hum, temp; +dht11_read(&hum, &temp); + \ No newline at end of file diff --git a/WEEK09/slides/WEEK09-IMG06.svg b/WEEK09/slides/WEEK09-IMG06.svg new file mode 100644 index 0000000..ef11927 --- /dev/null +++ b/WEEK09/slides/WEEK09-IMG06.svg @@ -0,0 +1,75 @@ + + + + +Variable Flow +Tracing x Through Every Operator + + + +Tracing x Step-by-Step + + +Line +x +Result + + + +int x = 5, y = 10; +5 +x initialized to 5 + + +int arithmetic = (x * y); +5 +arithmetic = 50 + + +int increment = x++; +5-->6 +increment = 5 +use THEN increment + + +bool relational = (x > y); +6 +relational = false +6 > 10 is false + + +bool logical = (x>y)&&(y>x); +6 +logical = false +false AND true = false + + +int bitwise = (x<<1); +6 +bitwise = 12 +0b0110 << 1 = 0b1100 + + +int assignment = (x += 5); +6-->11 +assignment = 11 +6 + 5 = 11 + + + +DHT11 Output +Humidity: 51.0% +Temperature: 23.8C +dht11_read(&hum, &temp) -- passes addresses so function can write values + diff --git a/WEEK09/slides/WEEK09-IMG07.svg b/WEEK09/slides/WEEK09-IMG07.svg new file mode 100644 index 0000000..1e5d297 --- /dev/null +++ b/WEEK09/slides/WEEK09-IMG07.svg @@ -0,0 +1,77 @@ + + + + +Vector Table +Finding Reset_Handler and main() + + + +ARM Vector Table +Base address: 0x10000000 + +Offset +Contents +Purpose + + +0x00 +Initial SP +Stack ptr + +0x04 +Reset_Handler +Entry point + +0x08 +NMI_Handler +NMI + +0x0C +HardFault +Fault + + + +Decoding the Address + +At 0x10000004: +Bytes: 5d 01 00 10 + +Step 1: Reverse (little-endian) +10 00 01 5d = 0x1000015d + +Step 2: Remove Thumb bit +0x1000015d - 1 = 0x1000015c + + + +Reset_Handler --> main() + +Reset_Handler at 0x1000015c calls 3 functions: + + +Call 1: some_init() +Hardware initialization + +Call 2: main() +THIS IS WHAT WE WANT +Address: 0x10000234 + +Call 3: exit() +Never returns + +The MIDDLE function call is always main() +Navigate to 0x10000234 in Ghidra to find it + diff --git a/WEEK09/slides/WEEK09-IMG08.svg b/WEEK09/slides/WEEK09-IMG08.svg new file mode 100644 index 0000000..f520db0 --- /dev/null +++ b/WEEK09/slides/WEEK09-IMG08.svg @@ -0,0 +1,81 @@ + + + + +IEEE-754 Floats +How Computers Store Decimal Numbers + + + +32-bit Float Structure + + + +S +1 bit + + +Exponent +8 bits + + +Mantissa (Fraction) +23 bits + +Value = (-1)^S x (1 + Mantissa) x 2^(Exponent - 127) + + + +Example: Decoding 0.1f + +Little-endian bytes: +cd cc cc 3d + +Reversed (big-endian): +0x3dcccccd + +Sign: 0 +Exp: 01111011 = 123 +Mantissa: 1001100... + +Exp - 127 = -4, so value = 1.6 x 2^(-4) += 0.1 + + + +IEEE-754 Quick Reference + +Value +Hex +Bytes (LE) + + +0.1 +0x3dcccccd +cd cc cc 3d +1.0 +0x3f800000 +00 00 80 3f + +5.0 +0x40a00000 +00 00 a0 40 +10.0 +0x41200000 +00 00 20 41 + +-1.0 +0xbf800000 +00 00 80 bf + diff --git a/WEEK09/slides/WEEK09-IMG09.svg b/WEEK09/slides/WEEK09-IMG09.svg new file mode 100644 index 0000000..5c40649 --- /dev/null +++ b/WEEK09/slides/WEEK09-IMG09.svg @@ -0,0 +1,64 @@ + + + + +Hacking the Float +Changing the DHT11 Scaling Constant + + + +DHT11 Scaling Calculation +result = integer + (decimal x 0.1) +Example: temp = 23 + (8 x 0.1) = 23.8C +0.1f is our target! + + + +Key Offsets in Binary + +Offset +Bytes +Meaning + + +0x410 +a6 ee 25 7a +vfma.f32 s14,s12,s11 (humidity) + +0x414 +e6 ee a5 7a +vfma.f32 s15,s13,s11 (temp) + +0x42C +cd cc cc 3d +0.1f -- the scaling constant + + + +The Hack: 0.1f --> 5.0f + +At offset 0x42C, change: + + +Original: cd cc cc 3d +(0.1f) + + +Patched: 00 00 a0 40 +(5.0f) + +New result: 23 + (8 x 5.0) = 63.0C +Decimal part is now multiplied by 5.0 instead of 0.1 +Export .bin from Ghidra, convert to UF2, flash to Pico + diff --git a/WEEK09/slides/WEEK09-IMG10.svg b/WEEK09/slides/WEEK09-IMG10.svg new file mode 100644 index 0000000..9655865 --- /dev/null +++ b/WEEK09/slides/WEEK09-IMG10.svg @@ -0,0 +1,97 @@ + + + + +Operators & DHT11 Hacking +Operators, DHT11, IEEE-754, and Hacking + + + +6 Operator Types + +Arithmetic +x * y = 50 + +Increment +x++ returns 5, x becomes 6 + +Relational +(6 > 10) = false + +Logical +false && true = false + +Bitwise +6 << 1 = 12 + +Assignment +x += 5 = 11 + +Post-increment: use THEN increment + + + +Key Addresses + +0x10000000 +Vector table + +0x10000004 +Reset_Handler addr + +0x10000234 +main() + +0x10000410 +Humidity vfma + +0x10000414 +Temp vfma + +0x1000042C +0.1f constant (hack) + + + +IEEE-754 Format +S(1) + Exp(8) + Mantissa(23) +(-1)^S x (1+M) x 2^(E-127) +0.1f = 0x3dcccccd = cd cc cc 3d + + + +Hack Workflow +1. Analyze in Ghidra +2. Find float at 0x42C +3. Patch cd cc cc 3d + + + +Binary Hacking Steps + +Analyze +--> +Identify +--> +Offset +--> +Patch +--> +Export +--> +Test + +Project: 0x001a_operators +Source: 0x001a_operators.c with DHT11 sensor on GPIO 4 + diff --git a/WEEK10/WEEK10-SLIDES.pdf b/WEEK10/WEEK10-SLIDES.pdf new file mode 100644 index 0000000..286c32a Binary files /dev/null and b/WEEK10/WEEK10-SLIDES.pdf differ diff --git a/WEEK10/WEEK10.md b/WEEK10/WEEK10.md new file mode 100644 index 0000000..375d2ba --- /dev/null +++ b/WEEK10/WEEK10.md @@ -0,0 +1,1600 @@ +# Week 10: Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics + +## What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand the difference between static and dynamic conditionals in C +- Know how if/else statements and switch/case blocks work at the assembly level +- Understand Pulse Width Modulation (PWM) and how it controls servo motors +- Calculate PWM timing from system clock to servo pulse width +- Identify conditional branches in Ghidra (beq, bne instructions) +- Hack string literals and timing delays in binary files +- Modify branch targets to change program flow +- Create "stealth" functionality by NOP-ing out print statements +- Understand IEEE-754 floating-point for angle calculations + +--- + +## Part 1: Understanding Conditionals in C + +### What Are Conditionals? + +**Conditionals** are programming structures that make decisions. They let your program choose different paths based on whether a condition is true or false. Think of them like a fork in the road - the program checks a condition and decides which way to go. + +### Two Types of Conditionals + +| Type | Description | Example | +| ----------- | ---------------------------------------------- | ------------------------------------------------- | +| **Static** | Condition value is known/fixed at compile time | `if (choice == 1)` where choice never changes | +| **Dynamic** | Condition value changes based on runtime input | `if (choice == getchar())` where user types input | + +--- + +## Part 2: Static Conditionals + +### What Makes a Conditional "Static"? + +A **static conditional** is one where the outcome is predetermined because the condition variable never changes during program execution: + +```c +int choice = 1; // This NEVER changes! + +while (true) { + if (choice == 1) { + printf("1\r\n"); // This ALWAYS runs + } else if (choice == 2) { + printf("2\r\n"); // This NEVER runs + } else { + printf("?\r\n"); // This NEVER runs + } +} +``` + +```ext ++-----------------------+ +| choice = 1 | +| set once, never | +| changes | ++-----------------------+ + | + v + [ choice == 1 ] + | + | YES + v ++-----------------------+ +| printf \'1\' | ++-----------------------+ + . + . NO (never taken) + v + [ choice == 2 ] + . + . YES (never reached) + v ++-----------------------+ +| printf \'2\' | ++-----------------------+ + . + . NO + v ++-----------------------+ +| printf \'?\' | ++-----------------------+ +``` + +### The if/else Statement + +The `if/else` structure checks conditions in order: + +```c +if (choice == 1) { +// Do something if choice is 1 +} else if (choice == 2) { +// Do something if choice is 2 +} else { +// Do something for all other values +} +``` + +### The switch Statement + +The `switch` statement is another way to handle multiple conditions: + +```c +switch (choice) { + case 1: + printf("one\r\n"); + break; + case 2: + printf("two\r\n"); + break; + default: + printf("??\r\n"); +} +``` + +**Key Differences:** + +| Feature | if/else | switch | +| ---------------- | ----------------------- | -------------------------- | +| **Condition** | Any boolean expression | Single variable comparison | +| **Values** | Ranges, complex logic | Discrete values only | +| **Fall-through** | No | Yes (without `break`) | +| **Readability** | Good for 2-3 conditions | Better for many conditions | + +--- + +## Part 3: Dynamic Conditionals + +### What Makes a Conditional "Dynamic"? + +A **dynamic conditional** is one where the condition variable changes based on runtime input: + +```c +uint8_t choice = 0; + +while (true) { + choice = getchar(); // User types a key - VALUE CHANGES! + + if (choice == '1') { + printf("1\r\n"); + } else if (choice == '2') { + printf("2\r\n"); + } else { + printf("??\r\n"); + } +} +``` + +```text + +-------------------+ + | getchar() | <----------------------------------+ + +-------------------+ | + | User types input | + v | + +-------------------+ | + | choice = input | | + +-------------------+ | + | | + v | + [ choice == '1' ] -- YES --> +-----------------------+ | + | | printf("1") | | + | NO | move servo | | + | +-----------------------+ | + v | + [ choice == '2' ] -- YES --> +-----------------------+ | + | | printf("2") | | + | NO | move servo | | + | +-----------------------+ | + v | + +-------------------+ | + | printf("??") | | + +-------------------+ | + | | + +-----------------------------------------------+ +``` + +### The getchar() Function + +`getchar()` reads a single character from the serial terminal: + +```c +uint8_t choice = getchar(); // Waits for user to type something +``` + +- Returns the ASCII value of the key pressed +- `'1'` = 0x31, `'2'` = 0x32, `'x'` = 0x78, `'y'` = 0x79 +- Blocks (waits) until a key is pressed + +--- + +## Part 4: Understanding PWM (Pulse Width Modulation) + +### What is PWM? + +**PWM** (Pulse Width Modulation) is a technique for controlling power by rapidly switching a signal on and off. The ratio of "on time" to "off time" determines the average power delivered. + +```text + PWM Signal - 50% Duty Cycle + + HIGH +-----+ +-----+ +-----+ + | | | | | | + LOW + +-----+ +-----+ +----- + |--T--| + ON OFF + + Duty Cycle = ON time / Total period = 50% +``` + +### PWM for Servo Control + +Servo motors use PWM differently - they care about the **pulse width**, not the duty cycle percentage: + +```text + Servo PWM Signal (50 Hz = 20ms period) + + 0° Position (1ms pulse): + HIGH -+ + | 1ms + LOW +----------------------------------- (19ms) ----- + + 90° Position (1.5ms pulse): + HIGH ---+ + | 1.5ms + LOW +-------------------------------- (18.5ms) ---- + + 180° Position (2ms pulse): + HIGH -----+ + | 2ms + LOW +-------------------------------- (18ms) ---- +``` + +### The Magic Numbers + +| Angle | Pulse Width | PWM Ticks (at 1MHz) | +| ----- | ----------- | ------------------- | +| 0° | 1000 µs | 1000 | +| 90° | 1500 µs | 1500 | +| 180° | 2000 µs | 2000 | + +--- + +## Part 5: PWM Timing Calculations + +### From 150 MHz to 50 Hz + +The RP2350's system clock runs at **150 MHz** (150 million cycles per second). A servo needs a **50 Hz** signal (one pulse every 20 ms). How do we bridge this gap? + +``` ext ++-----------------------+ +| System Clock | +| 150 MHz | ++-----------------------+ + | + | Divide by 150 + v ++-----------------------+ +| PWM Tick Rate | +| 1 MHz | +| (1 tick = 1 µs) | ++-----------------------+ + | + | Count to 20,000 + | Wrap at 19,999 + v ++-----------------------+ +| Servo PWM Signal | +| 50 Hz | +| (20 ms period) | ++-----------------------+ +``` + +### The Math + +**Step 1: Clock Division** +``` +PWM Tick Rate = System Clock / Divider +1,000,000 Hz = 150,000,000 Hz / 150 +``` + +**Step 2: Frame Period** +``` +Period = (Wrap Value + 1) * Tick Duration +20 ms = 20,000 ticks * 1 µs/tick +``` + +**Step 3: Pulse Width to Ticks** +``` +Ticks = Pulse Width (µs) * 1 tick/µs +1500 ticks = 1500 µs * 1 +``` + +### Worked Example: 90° Angle + +Let's calculate what happens when we command 90°: + +1. **Angle to Pulse Width:** + ``` + Pulse = MIN + (angle/180) * (MAX - MIN) + Pulse = 1000 + (90/180) * (2000 - 1000) + Pulse = 1000 + 0.5 * 1000 + Pulse = 1500 µs + ``` + +2. **Pulse to PWM Ticks:** + ``` + Level = 1500 µs * 1 tick/µs = 1500 ticks + ``` + +3. **Hardware Timing:** + - Signal HIGH for 1500 ticks (1.5 ms) + - Signal LOW for 18,500 ticks (18.5 ms) + - Total period: 20,000 ticks (20 ms) + +--- + +## Part 6: Understanding the SG90 Servo Motor + +### What is the SG90° + +The **SG90** is a small, inexpensive hobby servo motor commonly used in robotics projects: + +``` ext +[ SG90 Servo Motor ] ++-----------------------------------+ +| Motor ---> Gearbox ---> Arm | +| (0° to 180°) | ++-----------------------------------+ + | Wires + v +[ Wires ] ++-----------------------------------+ +| Orange: Signal / PWM | +| Red: VCC / 5V | +| Brown: GND / Ground | ++-----------------------------------+ +``` + +### SG90 Specifications + +| Parameter | Value | +| ----------------- | ------------------------- | +| **Voltage** | 4.8V - 6V (typically 5V) | +| **Rotation** | 0° to 180° | +| **Pulse Width** | 1000 us - 2000 us | +| **Frequency** | 50 Hz (20ms period) | +| **Stall Current** | ~650mA (can spike to 1A+) | + +### Wire Colors + +| Wire Color | Function | Connect To | +| ---------- | -------- | --------------- | +| **Brown** | GND | Ground | +| **Red** | VCC | 5V Power (VBUS) | +| **Orange** | Signal | GPIO Pin (PWM) | + +--- + +## Part 7: Power Supply Safety + +### CRITICAL WARNING + +**NEVER power the servo directly from the Pico's 3.3V pin!** + +Servos can draw over 1000mA during movement spikes. The Pico's 3.3V regulator cannot handle this and you will: +- Cause brownouts (Pico resets) +- Damage the Pico's voltage regulator +- Potentially damage your USB port + +### Correct Power Setup + +``` ext ++-----------------+ +------------------------+ +| USB Power | ---> | VBUS 5V | ++-----------------+ +------------------------+ + | | + v v + [ Servo VCC ] [ Capacitor + ] + (Red) (1000 uF 25V) + ++-----------------+ +| Pico GND | ++-----------------+ + | + +--------------------+--------------------+ + | | + v v + [ Servo GND ] [ Capacitor - ] + (Brown) + ++-----------------+ +| Pico GPIO 6 | ---> [ Servo Signal ] (Orange) ++-----------------+ +``` + +### Why the Capacitor? + +The **1000 uF capacitor** acts as a tiny battery: +- Absorbs sudden current demands when servo moves +- Prevents voltage drops that could reset the Pico +- Smooths out electrical noise + +--- + +## Part 8: Setting Up Your Environment + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. A Raspberry Pi Pico Debug Probe +3. Ghidra installed (for static analysis) +4. Python installed (for UF2 conversion) +5. A serial monitor (PuTTY, minicom, or screen) +6. An SG90 servo motor +7. A 1000 uF 25V capacitor +8. The sample projects: `0x001d_static-conditionals` and `0x0020_dynamic-conditionals` + +### Hardware Setup + +Connect your servo like this: + +| Servo Wire | Pico 2 Pin | +| --------------- | ---------- | +| Brown (GND) | GND | +| Red (VCC) | VBUS (5V) | +| Orange (Signal) | GPIO 6 | + +``` ext +Hardware Setup: + + Pico 2 Servo SG90 Capacitor (1000uF 25V) ++----------+ +-------------+ +-------------+ +| | | | | | +| GPIO 6 | -------> | Signal (Org)| | | +| | | | | | +| VBUS 5V | -------> | VCC (Red) | | | +| | | | | | | +| | +----> | | -----> | + | +| | | | | | +| GND | -------> | GND (Brn) | | | +| | | | | | | +| | +----> | | -----> | - | ++----------+ +-------------+ +-------------+ +``` + +### Project Structure + +``` +Embedded-Hacking/ ++-- 0x001d_static-conditionals/ +| +-- build/ +| | +-- 0x001d_static-conditionals.uf2 +| | +-- 0x001d_static-conditionals.bin +| +-- main/ +| | +-- 0x001d_static-conditionals.c +| +-- servo.h ++-- 0x0020_dynamic-conditionals/ +| +-- build/ +| | +-- 0x0020_dynamic-conditionals.uf2 +| | +-- 0x0020_dynamic-conditionals.bin +| +-- main/ +| | +-- 0x0020_dynamic-conditionals.c +| +-- servo.h ++-- uf2conv.py +``` + +--- + +## Part 9: Hands-On Tutorial - Static Conditionals Code + +### Step 1: Review the Source Code + +Let's examine the static conditionals code: + +**File: `0x001d_static-conditionals.c`** + +```c +#include +#include "pico/stdlib.h" +#include "servo.h" + +#define SERVO_GPIO 6 + +int main(void) { + stdio_init_all(); + + int choice = 1; // STATIC - never changes! + + servo_init(SERVO_GPIO); + + while (true) { + // if/else conditional + if (choice == 1) { + printf("1\r\n"); + } else if (choice == 2) { + printf("2\r\n"); + } else { + printf("?\r\n"); + } + + // switch/case conditional + switch (choice) { + case 1: + printf("one\r\n"); + break; + case 2: + printf("two\r\n"); + break; + default: + printf("??\r\n"); + } + + // Servo movement + servo_set_angle(0.0f); + sleep_ms(500); + servo_set_angle(180.0f); + sleep_ms(500); + } +} +``` + +### Step 2: Understand the Program Flow + +Since `choice = 1` and NEVER changes: + +``` ext ++-------------------------+ +| Start Loop Iteration | <-----------------------------------+ ++-------------------------+ | + | | + v | + [ choice == 1 ] -- TRUE --> +-------------------------+ | + | print \'1\' | | + +-------------------------+ | + | | + v | + [ switch case 1 ] | + | MATCH | + v | + +-------------------------+ | + | print \'one\' | | + +-------------------------+ | + | | + v | + +-------------------------+ | + | Move servo to 0° | | + +-------------------------+ | + | | + v | + +-------------------------+ | + | Wait 500ms | | + +-------------------------+ | + | | + v | + +-------------------------+ | + | Move servo to 180° | | + +-------------------------+ | + | | + v | + +-------------------------+ | + | Wait 500ms | ---+ + +-------------------------+ +``` + +### Step 3: Flash the Binary to Your Pico 2 + +1. Hold the BOOTSEL button on your Pico 2 +2. Plug in the USB cable (while holding BOOTSEL) +3. Release BOOTSEL - a drive called "RPI-RP2" appears +4. Drag and drop `0x001d_static-conditionals.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 4: Verify It's Working + +**Check the serial monitor (PuTTY at 115200 baud):** +``` +1 +one +1 +one +1 +one +... +``` + +**Watch the servo:** +- It should sweep from 0° to 180° every second +- The movement is continuous and repetitive + +--- + +## Part 10: Debugging with GDB (Static Conditionals) + +### Step 5: Start OpenOCD (Terminal 1) + +Open a terminal and start OpenOCD: + +```powershell +openocd -s "$env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev\scripts" -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000" +``` + +You should see output indicating OpenOCD connected successfully to your Pico 2 via the Debug Probe. + +### Step 6: Start GDB (Terminal 2) + +Open a **new terminal** and launch GDB with the binary: + +```cmd +arm-none-eabi-gdb build\0x001d_static-conditionals.elf +``` + +### Step 7: Connect to the Remote Target + +In GDB, connect to OpenOCD: + +```gdb +target extended-remote :3333 +``` + +### Step 8: Halt the Running Binary + +Stop the processor: + +```gdb +monitor halt +``` + +### Step 9: Examine Main Function + +Disassemble around main to see the conditionals: + +```gdb +disassemble 0x10000234,+200 +``` + +Look for comparison and branch instructions that implement the if/else and switch logic. + +### Step 10: Set a Breakpoint at Main + +```gdb +break *0x10000234 +``` + +Reset and run to hit the breakpoint: + +```gdb +monitor reset halt +continue +``` + +### Step 11: Find the Comparison Instructions... Or Not! + +To see the assembly instructions as you execute them, tell GDB to display the current instruction automatically: + +```gdb +display/i $pc +nexti +``` + +Keep pressing **Enter** to repeat the `nexti` command and watch the instructions. + +**Wait, where is the `cmp` instruction?!** +You might notice that there is NO `cmp` instruction comparing our `choice` variable anywhere! Why? Because we hardcoded `int choice = 1;` at the start of our C code and never changed it. + +The C compiler is smart (even at basic optimization levels). It realized the `if (choice == 1)` condition would *always* be true, and the `else` conditions would *never* happen. Instead of wasting CPU cycles checking a condition that never changes, the compiler **optimized out the check entirely**! It just compiled the code inside the `choice == 1` block unconditionally. + +This is the defining characteristic of a "Static Conditional"—the condition is resolved at compile-time, not run-time! + +### Step 12: Examine the Printf/Puts Arguments + +If you look closely at your disassembly, you'll notice there are no `printf` calls! The compiler optimized our simple `printf` statements into `puts` (specifically `__wrap_puts` in the Pico SDK) because they didn't contain any complex formatting variables. + +When you reach a `bl <__wrap_puts>` instruction, check `r0` for the string address: + +```gdb +x/s $r0 +``` + +You should see strings like `"1\r"` or `"one\r"`. + +*(Wait, what happened to the `\n`? Because the `puts` function automatically adds a newline to the end of whatever it prints, the compiler cleverly trimmed the `\n` out of the string literal in memory to save space!)* + +### Step 13: Watch the Servo Commands + +Let's set a breakpoint on `servo_set_angle`. How do you know the address? If you look at your `main` disassembly, you can find the `bl` instruction calling it (for example, `0x10000310`). + +But hardcoded addresses change every time you recompile! Luckily, GDB is smart enough to resolve function names directly from the ELF file: + +```gdb +break servo_set_angle +continue +``` + +Check the argument passed to the function. You might assume the float is in `s0`, but check out the very first instruction of `servo_set_angle`: + +`vmov s14, r0` + +Because of how the ARM compiler handles arguments, the floating-point angle is actually passed into the function via the standard integer register `r0`. The `vmov` instruction immediately moves it from `r0` into the floating-point register `s14` so the math unit can use it! + +To see the angle, you can ask GDB to interpret the raw hex value in `r0` as a float. You can also step forward (`nexti` or `n`) to let the `vmov` execute, and then check `s14` directly! + +Here is exactly what that process looks like in your GDB terminal (assuming you hit `continue` to catch the second call where the angle is 180): + +```gdb +=> 0x10000310 : vmov s14, r0 +(gdb) print /f $r0 +$2 = 180 +(gdb) nexti +=> 0x10000314 : push {r4, r5, lr} +(gdb) info registers s14 +s14 180 (raw 0x43340000) +``` + +*(Note: On your very first breakpoint hit, `print /f $r0` will just show `0` because the first call in the C code is `servo_set_angle(0)`!)* + +### Step 14: Examine the Timing Delay + +Set a breakpoint on `sleep_ms` (using the function name, not a hardcoded address!) and check the delay value: + +```gdb +break sleep_ms +continue +info registers r0 +``` + +You should see `0x1f4` (500 decimal) for the 500ms delay. + +### Step 15: Watch the Loop Iterate + +Because this loop contains `sleep_ms(500)` calls, trying to use `nexti 100` to step forward will cause GDB to "hang" (either because it's waiting for multiple seconds of sleep to finish, or because GDB stepping interferes with the Pico's hardware timer interrupts!). + +Instead, since we already have breakpoints set on `servo_set_angle` and `sleep_ms`, just type `continue`! + +```gdb +continue +``` + +Every time you type `continue` (or press **Enter** to repeat it), you will see the CPU safely jump to the next function call. Because this is a *Static Conditional*, it will never hit a `cmp` instruction or take a different branch—it just bounces between `servo_set_angle` and `sleep_ms` forever! + +### Step 16: Exit GDB + +When done exploring: + +```gdb +quit +``` + +--- + +## Part 11: Setting Up Ghidra for Static Conditionals + +### Step 17: Start Ghidra + +Open a terminal and type: + +```cmd +ghidraRun +``` + +### Step 18: Create a New Project + +1. Click **File** -> **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `0x001d_static-conditionals` +5. Click **Finish** + +### Step 19: Import the Binary + +1. Open your file explorer +2. Navigate to the `0x001d_static-conditionals/build/` folder +3. **Drag and drop** the `.bin` file into Ghidra's project window + +### Step 20: Configure the Binary Format + +**Click the three dots (...) next to "Language" and:** +1. Search for "Cortex" +2. Select **ARM Cortex 32 little endian default** +3. Click **OK** + +**Click the "Options..." button and:** +1. Change **Block Name** to `.text` +2. Change **Base Address** to `10000000` +3. Click **OK** + +### Step 21: Analyze the Binary + +1. Double-click on the file in the project window +2. A dialog asks "Analyze now?" - Click **Yes** +3. Use default analysis options and click **Analyze** + +Wait for analysis to complete. + +--- + +## Part 12: Resolving Functions in Ghidra (Static) + +### Step 22: Navigate to Main + +1. Press `G` (Go to address) and type `10000234` +2. Right-click -> **Edit Function Signature** +3. Change to: `int main(void)` +4. Click **OK** + +### Step 23: Resolve stdio_init_all + +At address `0x10000236`: + +1. Double-click on the called function +2. Right-click -> **Edit Function Signature** +3. Change to: `bool stdio_init_all(void)` +4. Click **OK** + +### Step 24: Resolve servo_init + +Look for a function call where `r0` is loaded with `0x6` (GPIO pin 6): + +```assembly +movs r0, #0x6 ; GPIO pin 6 +bl FUN_1000027c ; servo_init +``` + +1. Right-click -> **Edit Function Signature** +2. Change to: `void servo_init(uint pin)` +3. Click **OK** + +### Step 25: Resolve puts + +Look for function calls that load string addresses into `r0`: + +```assembly +ldr r0=>DAT_10001c54 ,[DAT_10000274 ] = 00000D31h +bl FUN_10001884 undefined FUN_10001884() +``` + +**How do we know it's puts?** +- It takes a single string argument +- The hex `0x31` is ASCII "1" +- The hex `0x0d` is carriage return "\r" +- We saw "1" echoed in PuTTY + +1. Right-click -> **Edit Function Signature** +2. Change to: `int puts(char *s)` +3. Click **OK** + +### Step 26: Resolve servo_set_angle + +Look for a function call (like `bl FUN_10000310`) that occurs right after the float arguments are loaded into `r0`. + +```assembly +mov r0,r5 +bl FUN_10000310 undefined FUN_10000310() +``` + +If you double-click the `FUN_10000310` label to look inside the function, you'll find a section of code checking the pulse limits: + +```assembly +cmp.w r3,#0x7d0 +it cs +mov.cs.w r3,#0x7d0 +cmp.w r3,#0x3e8 +it cc +mov.cc.w r3,#0x3e8 +``` + +These values are: +- `0x7D0` (2000 decimal) - maximum pulse width +- `0x3E8` (1000 decimal) - minimum pulse width + +These are the servo pulse limits! + +1. Right-click -> **Edit Function Signature** +2. Change to: `void servo_set_angle(float degrees)` +3. Click **OK** + +### Step 27: Resolve sleep_ms + +Look for a function where `r0` is loaded with `0x1f4` (500 decimal): + +```assembly +mov.w r0,#0x1f4 +bl FUN_10000e20 undefined FUN_10000e20() +``` + +1. Right-click -> **Edit Function Signature** +2. Change to: `void sleep_ms(uint ms)` +3. Click **OK** + +--- + +## Part 13: Hacking Static Conditionals + +### Step 28: Open the Bytes Editor + +1. Click **Window** -> **Bytes** +2. A new panel appears showing raw hex bytes +3. Click the pencil icon to enable editing + +### Step 29: Hack #1 - Change "1" to "2" + +First, we need to find the string "1" in the binary. + +1. Go back to your `main` assembly code and look for the first `puts` call. +2. In the `ldr` instruction right before it, look for the reference next to the arrow: `r0=>DAT_10001c54`. **Double-click exactly on `DAT_10001c54`**. *(Warning: Do NOT click the reference inside the brackets like `[DAT_10000274]`, or you'll end up in the pointer table instead of the string!)* +3. The Listing and Bytes windows will automatically jump to the string's exact address! +4. Look at your **Bytes** window. You should see the byte `31` (which is ASCII for "1"). +5. Click on the `31` and type `32` to overwrite it (which is ASCII for "2"). + +### Step 30: Hack #2 - Change "one" to "fun" + +Next, let's change the word "one" to "fun": + +1. Go back to `main` and look for the second `puts` call. +2. In the `ldr` instruction, double-click the `DAT_10001c5c` reference next to the arrow (again, ignore the one in the brackets!). +3. Look at the **Bytes** window again. You'll find the bytes `6f 6e 65` (which are ASCII for "o-n-e"). +4. Click on the `6f` byte and type `66 75 6e` on your keyboard to overwrite those three bytes with "f-u-n". + +**ASCII Reference:** +| Character | Hex | +| --------- | ---- | +| o | 0x6f | +| n | 0x6e | +| e | 0x65 | +| f | 0x66 | +| u | 0x75 | +| n | 0x6e | + +### Step 31: Hack #3 - Speed Up the Servo + +Let's change the 500ms delay to a 100ms delay. Since the compiler packed the `500` directly into a `mov.w` instruction, we can't just find it in the data section. Instead, let's use Ghidra's built-in assembler to rewrite the instruction! + +1. In your `main` assembly code, look for the two `mov.w r0,#0x1f4` instructions (which happen right before calling `sleep_ms`). +2. Right-click the first `mov.w r0,#0x1f4` instruction and select **Patch Instruction** (or press `Ctrl+Shift+G`). +3. Delete the `#0x1f4` and type `#0x64` (which is 100 in hex). Press **Enter**! +4. Repeat this for the second `mov.w r0,#0x1f4` instruction. + +**Before:** 500ms delay (servo moves slowly) +**After:** 100ms delay (servo moves FAST!) + +### Step 32: Export and Flash + +When exporting from Ghidra, you must make sure to save the file inside your `build/` directory so the python script can find it! + +1. Click **File** -> **Export Program** +2. Set **Format** to **Raw Bytes** +3. **IMPORTANT:** Click the `...` next to the Output File field and navigate into your `0x001d_static-conditionals/build/` folder! +4. Save the file as `0x001d_static-conditionals-h.bin`. +5. Click **OK** + +Convert and flash (make sure your terminal is inside the `0x001d_static-conditionals/` folder, NOT the `build/` folder!): + +```cmd +python ..\uf2conv.py build\0x001d_static-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +*(Troubleshooting: If you get `FileNotFoundError` for the `.bin` file, it means you didn't save the exported file into the `build/` folder! Go back to Ghidra and export it again. If you get an error that Python can't open `..\uf2conv.py`, it means you accidentally `cd`'d into the `build/` folder. Type `cd ..` to go back up one directory and run the command again!)* + +### Step 33: Verify the Hacks + +**Serial output now shows:** +``` +2 +fun +2 +fun +... +``` + +**The servo now moves 5x faster!** It's spinning back and forth like crazy! + +--- + +## Part 14: Dynamic Conditionals - The Source Code + +### Step 34: Review the Dynamic Code + +**File: `0x0020_dynamic-conditionals.c`** + +```c +#include +#include "pico/stdlib.h" +#include "servo.h" + +#define SERVO_GPIO 6 + +int main(void) { + stdio_init_all(); + + uint8_t choice = 0; // DYNAMIC - changes with user input! + + servo_init(SERVO_GPIO); + + while (true) { + choice = getchar(); // Wait for keyboard input + + if (choice == 0x31) { // '1' + printf("1\r\n"); + } else if (choice == 0x32) { // '2' + printf("2\r\n"); + } else { + printf("??\r\n"); + } + + switch (choice) { + case '1': + printf("one\r\n"); + servo_set_angle(0.0f); + sleep_ms(500); + servo_set_angle(180.0f); + sleep_ms(500); + break; + case '2': + printf("two\r\n"); + servo_set_angle(180.0f); + sleep_ms(500); + servo_set_angle(0.0f); + sleep_ms(500); + break; + default: + printf("??\r\n"); + } + } +} +``` + +### Step 35: Understand the Dynamic Behavior + +| User Types | Output | Servo Action | +| ------------- | ----------- | ------------ | +| '1' (0x31) | "1" + "one" | 0° -> 180° | +| '2' (0x32) | "2" + "two" | 180° -> 0° | +| Anything else | "??" + "??" | No movement | + +### Step 36: Flash and Test + +1. Flash `0x0020_dynamic-conditionals.uf2` +2. Open PuTTY +3. Press '1' - servo sweeps one direction +4. Press '2' - servo sweeps the other direction +5. Press 'x' - prints "??" and no movement + +--- + +## Part 15: Debugging with GDB (Dynamic Conditionals) + +### Step 37: Start OpenOCD (Terminal 1) + +Open a terminal and start OpenOCD: + +```powershell +openocd -s "$env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev\scripts" -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000" +``` + +You should see output indicating OpenOCD connected successfully to your Pico 2 via the Debug Probe. + +### Step 38: Start GDB (Terminal 2) + +Open a **new terminal** and launch GDB with the binary: + +```cmd +arm-none-eabi-gdb build\0x0020_dynamic-conditionals.elf +``` + +### Step 39: Connect to the Remote Target + +In GDB, connect to OpenOCD: + +```gdb +target extended-remote :3333 +``` + +### Step 40: Halt the Running Binary + +Stop the processor: + +```gdb +monitor halt +``` + +### Step 41: Examine Main Function + +Disassemble around main to see the dynamic conditionals: + +```gdb +disassemble 0x10000234,+250 +``` + +Look for the `bl <__wrap_getchar>` call. Because `choice` is now dynamic (based on user input), the compiler *had* to generate the comparison logic! It should look something like this: + +```assembly +bl 0x10001860 <__wrap_getchar> +uxtb r4, r0 +cmp r4, #49 @ 0x31 +beq.n 0x10000270 +cmp r4, #50 @ 0x32 +``` +There they are! The `cmp` instructions checking for `0x31` ('1') and `0x32` ('2'). + +### Step 42: Set a Breakpoint After getchar + +We want to pause execution right after we type a character so we can inspect the comparison. +Find the address of the first `cmp` instruction in your disassembly (in the example above, it's `0x1000024a`) and set a breakpoint: + +```gdb +break *0x1000024a +``` +*(Make sure you use the exact address of the `cmp` instruction from YOUR disassembly!)* + +Reset and continue: + +```gdb +monitor reset halt +continue +``` + +### Step 43: Watch the Input Value + +When you press a key in PuTTY, the breakpoint hits. Check the return value: + +```gdb +info registers r0 +``` + +If you pressed '1', you should see `0x31`. If you pressed '2', you should see `0x32`. + +### Step 44: Execute the Comparison + +Right now, GDB is paused *before* the `cmp` instruction runs (the `=>` arrow points to the instruction that will execute *next*). + +To see the result of the comparison, we must execute the `cmp` instruction by stepping forward exactly once: + +```gdb +display/i $pc +stepi +``` + +Notice that the `=>` arrow has now moved to the `beq.n` (Branch if Equal) instruction. The `cmp` instruction has just executed. + +### Step 45: Examine the Branch Decisions + +Now that the comparison has run, we can look at the CPU's condition flags to see what happened: + +```gdb +info registers xpsr +``` +*(Note: Older ARM chips call this `cpsr`, but Cortex-M chips like the Pico's RP2350 call it `xpsr`!)* + +The zero flag (`Z`) determines if the branch is taken. The flags are stored in the highest nibble (the first hex digit) of the `xpsr` register in the order `N Z C V`. + +If you typed '1', the comparison resulted in zero difference, setting the `Z` flag to 1. You should see `xpsr` start with a `6` (like `0x69000000`). `6` in binary is `0110`, which means the `Z` flag (the second bit) is 1! The `beq.n` instruction will see this flag and take the branch. + +### Step 46: Watch Different Input Paths + +Continue and press different keys to see how the program takes different branches: + +```gdb +continue +``` + +Press '2' in PuTTY, then examine registers again. + +### Step 47: Examine Servo Control + +Set a breakpoint on servo_set_angle: + +```gdb +break *0x10000280 +continue +``` + +Check the angle value: + +```gdb +info registers s0 +``` + +### Step 48: Exit GDB + +When done exploring: + +```gdb +quit +``` + +--- + +## Part 16: Setting Up Ghidra for Dynamic Conditionals + +### Step 49: Create New Project + +1. Create project: `0x0020_dynamic-conditionals` +2. Import the `.bin` file +3. Configure as ARM Cortex, base address `10000000` +4. Analyze + +### Step 50: Navigate to Main + +Press `G` and go to `10000234`. + +### Step 51: Resolve Functions + +Follow the same process: + +1. **main** at `0x10000234` -> `int main(void)` +2. **stdio_init_all** -> `bool stdio_init_all(void)` +3. **servo_init** -> `void servo_init(uint pin)` +4. **puts** -> `int puts(char *s)` +5. **servo_set_angle** -> `void servo_set_angle(float degrees)` +6. **sleep_ms** -> `void sleep_ms(uint ms)` + +### Step 52: Identify getchar + +Look for a function that: +- Returns a value in `r0` +- That value is then compared against `0x31` ("1") + +```assembly +bl FUN_10001860 undefined FUN_10001860() +uxtb r4,r0 +cmp r4,#0x31 +beq LAB_10000270 +``` + +1. Right-click -> **Edit Function Signature** +2. Change to: `int getchar(void)` +3. Click **OK** + +### Step 53: Identify Hardware Addresses + +Double-click into `stdio_init_all` and look for hardware addresses: + +```assembly +ldr r0, =0x40070000 ; UART0 base address +``` + +Check the RP2350 datasheet Section 2.2 (Address Map): +- `0x40070000` = UART0 + +This confirms it's a UART initialization function! + +--- + +## Part 17: Understanding Branch Instructions + +### ARM Branch Instructions + +| Instruction | Meaning | Condition | +| ----------- | ---------------------- | ------------------ | +| `b` | Branch (always) | Unconditional jump | +| `beq` | Branch if Equal | Zero flag set | +| `bne` | Branch if Not Equal | Zero flag clear | +| `bgt` | Branch if Greater Than | Signed greater | +| `blt` | Branch if Less Than | Signed less | + +### How Conditionals Become Branches + +```c +if (choice == 0x31) { + printf("1"); +} +``` + +Becomes: + +```assembly +cmp r4, #0x31 ; Compare choice to '1' +bne skip_printf ; If NOT equal, skip the printf +; ... printf code here ... +skip_printf: +``` + +``` ext ++---------------------------------+ +| cmp r4, #0x31 | +| Sets flags based on r4 - 0x31 | ++---------------------------------+ + | + v + [ beq target_address ] + | + +--------+--------+ + | | + If r4 == 0x31 If r4 != 0x31 + | | + v v ++---------------+ +----------------------------+ +| Jump to | | Continue to | +| target_address| | next instruction | ++---------------+ +----------------------------+ +``` + +--- + +## Part 18: Advanced Hacking - Creating Stealth Commands + +### The Goal + +We want to create **secret commands** that: +1. Respond to 'x' and 'y' instead of '1' and '2' +2. Move the servo WITHOUT printing anything +3. Leave NO trace in the terminal + +### Step 54: Plan the Patches + +**Original behavior:** +- '1' (0x31) -> prints "1" and "one", moves servo +- '2' (0x32) -> prints "2" and "two", moves servo + +**Hacked behavior:** +- 'x' (0x78) -> moves servo SILENTLY (replacing '1') +- 'y' (0x79) -> moves servo SILENTLY (replacing '2') + +### Step 55: Change Comparison Values + +Navigate to the `main` function and find the two `cmp` instructions right after `getchar`: + +1. At address `1000024a`, you will see `cmp r4,#0x31`. + - Right-click it, select **Patch Instruction**, and change it to `cmp r4,#0x78` (which is ASCII 'x'). +2. At address `1000024e`, you will see `cmp r4,#0x32`. + - Right-click it, select **Patch Instruction**, and change it to `cmp r4,#0x79` (which is ASCII 'y'). + +### Step 56: Redirect Branches to Skip Prints + +For the stealth keys, we need to jump PAST the printf calls directly to the servo code. + +**Original flow:** +``` +compare -> branch -> printf("1") -> printf("one") -> servo code +``` + +**Hacked flow:** +``` +compare 'x' -> branch -> [skip prints] -> servo code +``` + +Use **Patch Instruction** to rewrite the `beq` target addresses: + +1. At address `1000024c`, you will see `beq LAB_10000270`. + - `10000270` is the block that prints "1". We want to skip it! + - Right-click, select **Patch Instruction**, and change it to `beq 0x1000027c` (this jumps straight to the `mov r0,r6` servo code!). +2. At address `10000250`, you will see `beq LAB_1000029a`. + - `1000029a` is the block that prints "2". + - Right-click, select **Patch Instruction**, and change it to `beq 0x100002a6` (this jumps straight to the `mov r0,r5` servo code!). + +### Step 57: NOP Out Print Calls (Alternative Method) + +**NOP** (No Operation) is an instruction that does absolutely nothing. Hackers use it to "erase" code without changing the size of the binary! Since we already redirected the branches to skip the prints, this is technically redundant, but let's do it anyway just to learn the technique. + +The `bl` instruction is 32-bits (4 bytes) long. Standard Thumb `nop` instructions are only 16-bits (2 bytes) long. If you try to patch a 4-byte instruction with a 2-byte instruction, Ghidra's assembler gets very confused and corrupts the code. + +To fix this, we use the special 32-bit version of NOP: `nop.w` (Wide NOP). + +1. In the Listing view, right-click the `bl FUN_10001954` instruction at `10000272`. +2. Select **Patch Instruction**. +3. Type `nop.w` (don't forget the `.w`!) and hit Enter. +4. You will see the entire 4-byte instruction neatly get replaced by a single 32-bit NOP. +5. Repeat this for the second `bl` instruction at `10000278`. + +### Step 58: Summary of Control Flow Patches + +Here is a quick summary of the stealth command patches we just applied to the control flow: + +| Location | Original Action | Patched Action | Purpose | +| ---------- | -------------------- | -------------------- | ---------------------------- | +| `1000024a` | `cmp r4,#0x31` | `cmp r4,#0x78` | Check for 'x' instead of '1' | +| `1000024e` | `cmp r4,#0x32` | `cmp r4,#0x79` | Check for 'y' instead of '2' | +| `1000024c` | `beq LAB_10000270` | `beq LAB_1000027c` | Skip printf for 'x' | +| `10000250` | `beq LAB_1000029a` | `beq LAB_100002a6` | Skip printf for 'y' | + +### Step 59: One Final Hack - Modify the Angle + +Let's also change the servo's movement angle from 180° to 30° for fun! + +If you look back near the top of the `main` function (around address `10000242`), you'll see this instruction: +`ldr r5,[DAT_100002c4] = 43340000h` + +The compiler stored the 180.0 float value (`0x43340000`) in a literal pool at address `100002c4`. To change the angle, we just need to overwrite that raw data! + +**Original:** `0x43340000` (180.0f) +**New:** `0x41f00000` (30.0f) + +Here is how to apply the patch: +1. Press `G` and jump to address `100002c4`. +2. In your **Bytes** window (make sure the Pencil icon is still clicked!), you will see the raw little-endian bytes: `00 00 34 43`. +3. Click on the first `00` and type `00 00 f0 41`. +4. The Listing view will instantly update to show the new `41f00000` value! + +*(For the math nerds, here is how we calculated `0x41f00000` manually using the IEEE-754 standard):* + +**Calculation for 30.0f:** +``` +30.0 = 1.875 * 2^4 +Sign = 0 +Exponent = 127 + 4 = 131 = 0x83 +Mantissa = 0.875 = 0x700000 + +Binary: 0 10000011 11100000000000000000000 +Hex: 0x41f00000 +Little-endian: 00 00 f0 41 +``` + +### Step 60: Export and Test + +When exporting, make sure to save it in your `build/` folder! + +1. Export as `0x0020_dynamic-conditionals-h.bin` inside `build/`. +2. Convert and flash (run from the `0x0020_dynamic-conditionals` directory!): + +```cmd +python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +3. Flash and test: + - Press 'x' -> NO OUTPUT, but servo moves silently! + - Press 'y' -> NO OUTPUT, but servo moves silently! + - (The original '1' and '2' keys no longer work!) + +--- + +## Part 19: Summary and Review + +### What We Accomplished + +1. **Learned static vs dynamic conditionals** - Fixed vs runtime-determined values +2. **Understood if/else and switch/case** - Two ways to branch in C +3. **Mastered PWM calculations** - 150MHz to 50Hz servo signal +4. **Identified conditional branches in assembly** - beq, bne, cmp instructions +5. **Hacked string literals** - Changed "one" to "fun" +6. **Modified timing values** - Sped up servo from 500ms to 100ms +7. **Created stealth commands** - Hidden 'x' and 'y' keys +8. **NOPed out print statements** - Removed logging for stealth +9. **Redirected branch targets** - Changed program flow + +### Static vs Dynamic Summary + +```text + Static Conditionals + ------------------- + - Variable set once, never changes + - Same path taken every iteration + - Compiler may optimize out dead branches + - Example: int choice = 1; if (choice == 1) + + Dynamic Conditionals + -------------------- + - Variable changes based on input/sensors + - Different paths taken based on runtime state + - All branches must remain in binary + - Example: choice = getchar(); if (choice == '1') +``` + +### PWM Calculation Summary + +``` ext ++---------+ +-------------+ +-----------+ +--------------+ +| Angle | Formula | Pulse Width | 1us= | PWM Ticks | Servo | Servo Motion | +| degrees | ------> | µs | 1 tick | | Motion | | ++---------+ +-------------+ ------> +-----------+ ------> +--------------+ + | | | | + 0° ---------------- 1000 µs ------------- 1000 ticks --------- Fully CCW + 90° ---------------- 1500 µs ------------- 1500 ticks --------- Center + 180° ---------------- 2000 µs ------------- 2000 ticks --------- Fully CW +``` + +### Key Memory Addresses + +| Memory Address | Description | +| -------------- | ------------------------ | +| `0x10000234` | main() function | +| `0x40070000` | UART0 hardware registers | +| `0x1f4` | 500 (sleep_ms delay) | +| `0x7D0` | 2000 (max pulse width) | +| `0x3E8` | 1000 (min pulse width) | +| `0x43340000` | 180.0f (max angle) | + +--- + +--- + +## Key Takeaways + +1. **Static conditionals have fixed outcomes** - The same path always executes + +2. **Dynamic conditionals respond to input** - Different paths based on runtime state + +3. **PWM frequency = 50Hz for servos** - One pulse every 20ms + +4. **Pulse width encodes position** - 1ms=0°, 1.5ms=90°, 2ms=180° + +5. **beq = branch if equal** - Jumps when comparison matches + +6. **bne = branch if not equal** - Jumps when comparison doesn't match + +7. **NOP erases code without changing size** - `00 bf` in ARM Thumb + +8. **Branch targets can be redirected** - Change where code jumps to + +9. **IEEE-754 is needed for angles** - Floats have specific bit patterns + +10. **Stealth requires removing ALL output** - NOP out printf AND puts + +--- + +## Glossary + +| Term | Definition | +| ----------------------- | --------------------------------------------------- | +| **beq** | Branch if Equal - ARM conditional jump | +| **bne** | Branch if Not Equal - ARM conditional jump | +| **Dynamic Conditional** | Condition that changes based on runtime input | +| **Duty Cycle** | Percentage of time signal is HIGH | +| **getchar()** | C function that reads one character from input | +| **NOP** | No Operation - instruction that does nothing | +| **PWM** | Pulse Width Modulation - variable duty cycle signal | +| **SG90** | Common hobby servo motor model | +| **Static Conditional** | Condition with fixed/predetermined outcome | +| **switch/case** | C structure for multiple discrete value comparisons | +| **Wrap Value** | PWM counter maximum before reset | + +--- + +## Additional Resources + +### ASCII Reference Table + +| Character | Hex | Decimal | +| --------- | ---- | ------- | +| '0' | 0x30 | 48 | +| '1' | 0x31 | 49 | +| '2' | 0x32 | 50 | +| 'x' | 0x78 | 120 | +| 'y' | 0x79 | 121 | +| '\r' | 0x0d | 13 | +| '\n' | 0x0a | 10 | + +### IEEE-754 Common Angles + +| Angle | IEEE-754 Hex | Little-Endian Bytes | +| ----- | ------------ | ------------------- | +| 0.0 | 0x00000000 | 00 00 00 00 | +| 30.0 | 0x41f00000 | 00 00 f0 41 | +| 45.0 | 0x42340000 | 00 00 34 42 | +| 90.0 | 0x42b40000 | 00 00 b4 42 | +| 135.0 | 0x43070000 | 00 00 07 43 | +| 180.0 | 0x43340000 | 00 00 34 43 | + +### ARM Thumb NOP Encodings + +| Instruction | Encoding | Size | +| ----------- | ------------- | ------- | +| `nop` | `00 bf` | 2 bytes | +| `nop.w` | `00 f0 00 80` | 4 bytes | + +### RP2350 Key Addresses + +| Address | Peripheral | +| ------------ | ---------- | +| `0x40070000` | UART0 | +| `0x40078000` | UART1 | +| `0x40050000` | PWM | + +--- + +## Real-World Implications + +### Why Stealth Commands Matter + +The ability to create hidden commands has serious implications: + +**Legitimate Uses:** +- Factory test modes +- Debugging interfaces +- Emergency recovery features + +**Malicious Uses:** +- Backdoors in firmware +- Hidden surveillance features +- Unauthorized control of systems + +### Real-World Example + +Imagine a drone with hacked firmware: +- Normal keys ('1', '2') control it visibly with logging +- Hidden keys ('x', 'y') control it with NO log entries +- An attacker could operate the drone while security monitors show nothing + +### The Nuclear Fuel Rod Analogy + +A fast-moving servo is like a nuclear fuel rod: +- Both are small components with immense power +- Both require precise control to prevent damage +- Both can "go critical" if pushed beyond limits +- Both teach the importance of safety margins + +--- + +**Remember:** The techniques you learned today demonstrate how conditional logic can be manipulated at the binary level. Understanding these attacks helps us build more secure embedded systems. Always use your skills ethically and responsibly! + +Happy hacking! diff --git a/WEEK10/slides/WEEK10-IMG00.svg b/WEEK10/slides/WEEK10-IMG00.svg new file mode 100644 index 0000000..84c608d --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 10 + + +Conditionals in Embedded Systems: +Debugging and Hacking Static & Dynamic +Conditionals w/ SG90 Servo Motor PWM + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEK10/slides/WEEK10-IMG01.svg b/WEEK10/slides/WEEK10-IMG01.svg new file mode 100644 index 0000000..38d8eb0 --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG01.svg @@ -0,0 +1,82 @@ + + + + +Conditionals Overview +Static vs Dynamic Decision Making + + + +What Are Conditionals? +Structures that let programs choose +different paths based on conditions + + + +Static Conditional +Value fixed at compile time + + +int choice = 1; +// never changes +if (choice == 1) +printf("1"); +// always runs +else printf("2"); +// never runs + + + +Dynamic Conditional +Value changes at runtime + + +choice = getchar(); +// user types a key +if (choice == '1') +printf("1"); +// maybe runs + + + +if/else + +Feature +Description + +Condition +Any boolean expr +Values +Ranges, complex logic +Fall-through +No +Best for +2-3 conditions + + + +switch/case + +Feature +Description + +Condition +Single variable +Values +Discrete only +Fall-through +Yes (no break) +Best for +Many conditions + diff --git a/WEEK10/slides/WEEK10-IMG02.svg b/WEEK10/slides/WEEK10-IMG02.svg new file mode 100644 index 0000000..cbd97b4 --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG02.svg @@ -0,0 +1,89 @@ + + + + +Static Conditionals +Fixed Outcome -- Same Path Every Time + + + +Static Code Pattern + +int choice = 1; +// NEVER changes +while (true) { +if (choice == 1) +printf("1"); +else if (choice == 2) +printf("2"); +// dead code +else + + + +Execution Flow + + +choice == 1? + + +YES + + +print "1" + +NO (never taken) + + + +choice == 2? + +NO (never reached) + + +print "?" + +Only ONE path ever executes! + + + +Every Loop Iteration (Always the Same) +1. if(1==1) --> TRUE +2. print "1" +3. switch(1) case 1 +4. print "one" +5. servo 0deg +6. sleep 500ms +7. servo 180deg + + + +Serial Output (Forever) + +1 +one +1 +// repeats forever + + + +Servo Motion (Forever) +0deg +--> +180deg +--> +0deg +Sweeps back and forth, 500ms each +Continuous, predictable motion + diff --git a/WEEK10/slides/WEEK10-IMG03.svg b/WEEK10/slides/WEEK10-IMG03.svg new file mode 100644 index 0000000..9a22eeb --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG03.svg @@ -0,0 +1,95 @@ + + + + +Dynamic Conditionals +Runtime Input Changes the Path + + + +Dynamic Code Pattern + +uint8_t choice = 0; +while (true) { +choice = getchar(); +// waits for keyboard input +if (choice == 0x31) +printf("1"); +else if (choice == 0x32) +printf("2"); + + + +Execution Flow + + +choice = getchar() + + + + +choice=='1'? + +YES + +servo 0-180 + + +NO + + +choice=='2'? + +YES + +servo 180-0 + + + +print "??" +Each iteration can take a DIFFERENT path + + + +getchar() Returns ASCII +'1' = 0x31 +'2' = 0x32 +'x' = 0x78 +'y' = 0x79 +Blocks until +keypress + + + +Input --> Behavior + +Key +Output +Servo + +'1' +"1" + "one" +0deg --> 180deg +'2' +"2" + "two" +180deg --> 0deg + + + +Two Projects +0x001d_static-conditionals +choice = 1 (fixed) +0x0020_dynamic-conditionals +choice = getchar() (user input) + diff --git a/WEEK10/slides/WEEK10-IMG04.svg b/WEEK10/slides/WEEK10-IMG04.svg new file mode 100644 index 0000000..a405f7e --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG04.svg @@ -0,0 +1,96 @@ + + + + +PWM Basics +Pulse Width Modulation for Servo Control + + + +What is PWM? +Rapidly switching a signal ON and OFF +Ratio of on-time to off-time controls power + + +HIGH + + + + + + + + +ON +OFF +ON +OFF + + + +Servo PWM (50Hz = 20ms period) + + +0deg (1ms pulse): + + + +1ms HIGH +19ms LOW + + +90deg (1.5ms pulse): + + + +1.5ms HIGH +18.5ms LOW + + +180deg (2ms pulse): + + + +2ms HIGH +18ms LOW + +Pulse WIDTH determines angle, not duty cycle +Total period always 20ms (50Hz) + + + +Angle to Pulse Width + +Angle +Pulse +Ticks (1MHz) + +0deg +1000us +1000 +90deg +1500us +1500 +180deg +2000us +2000 + + + +Formula +pulse = 1000 + (angle/180) x 1000 +Example for 90deg: +1000 + (90/180) x 1000 += 1500us = 1500 ticks + diff --git a/WEEK10/slides/WEEK10-IMG05.svg b/WEEK10/slides/WEEK10-IMG05.svg new file mode 100644 index 0000000..9499ded --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG05.svg @@ -0,0 +1,83 @@ + + + + +PWM Timing Chain +150MHz System Clock to 50Hz Servo Signal + + + +Clock Division + + +150 MHz Clock + +/ 150 + + + + +1 MHz PWM + +1 tick = 1us + + + +Step 1: 150,000,000 / 150 = 1,000,000 Hz +Each PWM tick = exactly 1 microsecond + +Step 2: Wrap at 20,000 ticks = 20ms = 50Hz +Wrap value = 19,999 + + + +SG90 Servo Motor + +Parameter +Value + +Voltage +4.8V - 6V (use 5V) +Rotation +0deg to 180deg +Pulse Width +1000us - 2000us +Frequency +50Hz (20ms period) + + + +Wiring to Pico 2 + + +Pico + + +SG90 + + + + +GPIO 6 = Signal (Orange) +VBUS 5V = VCC (Red) +GND = GND (Brown) +Add 1000uF capacitor on power! + + + +Power Safety +NEVER use 3.3V pin for servo! +Servos draw 650mA+ (spikes to 1A) +Use VBUS (5V from USB) with 1000uF 25V capacitor + diff --git a/WEEK10/slides/WEEK10-IMG06.svg b/WEEK10/slides/WEEK10-IMG06.svg new file mode 100644 index 0000000..bb7867d --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG06.svg @@ -0,0 +1,55 @@ + + + + +Static Source Code +0x001d_static-conditionals.c + + + +Full Source + + +#include <stdio.h> +#include "pico/stdlib.h" +#include "servo.h" +#define SERVO_GPIO 6 + +int main(void) { +stdio_init_all(); +int choice = 1; +// STATIC! +servo_init(SERVO_GPIO); + +while (true) { +if (choice == 1) +printf("1\r\n"); +else if (choice == 2) +printf("2\r\n"); +// dead code + + + +switch Block + +switch(choice) { +case 1: puts("one"); break; + + +Servo Loop + +servo_set_angle(0.0f); +sleep_ms(500); +// then 180 + diff --git a/WEEK10/slides/WEEK10-IMG07.svg b/WEEK10/slides/WEEK10-IMG07.svg new file mode 100644 index 0000000..edb9558 --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG07.svg @@ -0,0 +1,50 @@ + + + + +Dynamic Source Code +0x0020_dynamic-conditionals.c + + + +Full Source + + +#include <stdio.h> +#include "pico/stdlib.h" +#include "servo.h" +#define SERVO_GPIO 6 + +int main(void) { +stdio_init_all(); +uint8_t choice = 0; +// DYNAMIC! +servo_init(SERVO_GPIO); + +while (true) { +choice = getchar(); +// wait for input +if (choice == 0x31) +// '1' +printf("1\r\n"); +else if (choice == 0x32) +// '2' +printf("2\r\n"); + + + +switch Block (with servo control) +case '1': print "one", servo 0-->180, sleep 500ms +case '2': print "two", servo 180-->0, sleep 500ms + diff --git a/WEEK10/slides/WEEK10-IMG08.svg b/WEEK10/slides/WEEK10-IMG08.svg new file mode 100644 index 0000000..295c649 --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG08.svg @@ -0,0 +1,101 @@ + + + + +Branch Instructions +How Conditionals Become Assembly + + + +ARM Branch Instructions + +Instr +Meaning +Condition + + +b +Branch always +Always + +beq +Branch if Equal +Z flag set + +bne +Branch if != +Z flag clear + +bgt +Branch if > +Signed > + +blt +Branch if < +Signed < + + + +C --> Assembly + +C code: + +if (choice == 0x31) +printf("1"); + +Assembly: + +cmp r4, #0x31 +// compare +bne skip_printf +// skip if != + + + +Conditional Branch Flow + + +cmp r4, #0x31 + + + + +beq target_addr + + +r4==0x31: JUMP + + +r4!=0x31: continue next + +cmp sets CPU flags, branch reads them + + + +NOP (No Operation) +ARM Thumb NOP: +00 bf +2 bytes +Wide NOP: +00 f0 00 80 +Replaces 4-byte bl instruction + + + +Hacking Branches +Change branch target addr +Redirect program flow +NOP out instructions +Erase code silently + diff --git a/WEEK10/slides/WEEK10-IMG09.svg b/WEEK10/slides/WEEK10-IMG09.svg new file mode 100644 index 0000000..68d47e9 --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG09.svg @@ -0,0 +1,83 @@ + + + + +Hacking Conditionals +Strings, Timing, Stealth Commands + + + +Hack 1: Change Strings +Change "1" to "2": +0x31 +--> +0x32 +"one" to "fun": +6f 6e 65 +--> +66 75 6e + + + +Hack 2: Speed Up Servo +Change sleep_ms delay: +0x1F4 (500ms) +--> +0x064 (100ms) + + + +Hack 3: Stealth Commands +Hidden keys move servo with NO output + +Patch +Original +Hacked +Purpose + + +Compare 1 +#0x31 ('1') +#0x78 ('x') +New trigger key + +Compare 2 +#0x32 ('2') +#0x79 ('y') +New trigger key + +puts calls +bl puts +00 bf 00 bf +NOP out prints + + + +Hack 4: Change Angle +180.0f --> 30.0f: +00 00 34 43 +--> +00 00 f0 41 + + + +Stealth Result +'1','2': normal output + servo +'x','y': NO output, servo moves + + + +Workflow +Patch bytes in Ghidra --> export .bin --> convert to UF2 --> flash to Pico + diff --git a/WEEK10/slides/WEEK10-IMG10.svg b/WEEK10/slides/WEEK10-IMG10.svg new file mode 100644 index 0000000..eb92463 --- /dev/null +++ b/WEEK10/slides/WEEK10-IMG10.svg @@ -0,0 +1,85 @@ + + + + +PWM & Servo Hacking +Conditionals, PWM, Servo, and Hacking + + + +Static vs Dynamic + +Static +choice = 1 (fixed) +Same path every iteration +Compiler may optimize + +Dynamic +choice = getchar() +Different paths at runtime + + + +PWM for Servos +150MHz / 150 = 1MHz tick +Wrap 20000 = 50Hz (20ms) +0deg=1000us 90deg=1500us 180deg=2000us +pulse = 1000 + (angle/180) x 1000 + + + +Branch Instructions +cmp r4, #0x31 +Compare +beq target +Jump if equal +bne target +Jump if not equal +NOP = 00 bf (erase code) + + + +Key Values +0x10000234 +main() +0x40070000 +UART0 +0x1F4 +500 (sleep_ms) +0x43340000 +180.0f IEEE-754 + + + +4 Hack Types Applied +String +"one"-->"fun" +Timing +500ms-->100ms +Stealth +NOP out prints +Angle +180.0f-->30.0f + + + +Projects +0x001d_static-conditionals +0x0020_dynamic-conditionals + + +IEEE-754 Angles +0.0f=00000000 90.0f=42b40000 +180.0f=43340000 30.0f=41f00000 + diff --git a/WEEK11/WEEK11-SLIDES.pdf b/WEEK11/WEEK11-SLIDES.pdf new file mode 100644 index 0000000..2e9a7c6 Binary files /dev/null and b/WEEK11/WEEK11-SLIDES.pdf differ diff --git a/WEEK11/WEEK11.md b/WEEK11/WEEK11.md new file mode 100644 index 0000000..577a89a --- /dev/null +++ b/WEEK11/WEEK11.md @@ -0,0 +1,1567 @@ +# Week 11: Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics + +## What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand C structures (structs) and how they organize related data +- Know how structs are represented in memory and assembly code +- Understand the NEC infrared (IR) protocol for remote control communication +- Create and use functions with parameters and return values +- Identify struct member access patterns in Ghidra +- Recognize how compilers "flatten" structs into individual operations +- Hack GPIO pin assignments to swap LED behavior +- Understand the security implications of log/behavior desynchronization +- Analyze .elf files in addition to .bin files in Ghidra + +--- + +## Part 1: Understanding C Structures (Structs) + +### What is a Struct? + +A **structure** (or **struct**) is a user-defined data type that groups related variables together under one name. Think of it like a form with multiple fields - each field can hold different types of data, but they all belong together. + +```c +// Define a struct type +typedef struct { + uint8_t led1_pin; // GPIO pin for LED 1 + uint8_t led2_pin; // GPIO pin for LED 2 + uint8_t led3_pin; // GPIO pin for LED 3 + bool led1_state; // Is LED 1 on? + bool led2_state; // Is LED 2 on? + bool led3_state; // Is LED 3 on? +} simple_led_ctrl_t; +``` + +``` ++-----------------------------------------------------------------+ +| Structure as a Container | +| | +| simple_led_ctrl_t leds | +| +-------------------------------------------------------------+| +| | led1_pin: 16 led2_pin: 17 led3_pin: 18 || +| | +--------+ +--------+ +--------+ || +| | | 16 | | 17 | | 18 | || +| | +--------+ +--------+ +--------+ || +| | || +| | led1_state: false led2_state: false led3_state: false || +| | +--------+ +--------+ +--------+ || +| | | false | | false | | false | || +| | +--------+ +--------+ +--------+ || +| +-------------------------------------------------------------+| +| | +| All 6 members live together as ONE variable called "leds" | +| | ++-----------------------------------------------------------------+ +``` + +### Why Use Structs? + +| Without Structs (Messy!) | With Structs (Clean!) | +| -------------------------- | --------------------------- | +| `uint8_t led1_pin = 16;` | `simple_led_ctrl_t leds;` | +| `uint8_t led2_pin = 17;` | `leds.led1_pin = 16;` | +| `uint8_t led3_pin = 18;` | `leds.led2_pin = 17;` | +| `bool led1_state = false;` | `leds.led3_pin = 18;` | +| `bool led2_state = false;` | `leds.led1_state = false;` | +| `bool led3_state = false;` | ... (all in one container!) | + +**Benefits of Structs:** +1. **Organization** - Related data stays together +2. **Readability** - Code is easier to understand +3. **Maintainability** - Changes are easier to make +4. **Scalability** - Easy to add more LEDs or features +5. **Passing to Functions** - Pass one struct instead of many variables + +--- + +## Part 2: Struct Memory Layout + +### How Structs are Stored in Memory + +When you create a struct, the compiler places each member in consecutive memory locations: + +``` ++-----------------------------------------------------------------+ +| Memory Layout of simple_led_ctrl_t | +| | +| Address Member Size Value | +| ------------------------------------------------------------- | +| 0x2000000 led1_pin 1 byte 16 (0x10) | +| 0x2000001 led2_pin 1 byte 17 (0x11) | +| 0x2000002 led3_pin 1 byte 18 (0x12) | +| 0x2000003 led1_state 1 byte 0 (false) | +| 0x2000004 led2_state 1 byte 0 (false) | +| 0x2000005 led3_state 1 byte 0 (false) | +| | +| Total struct size: 6 bytes | +| | ++-----------------------------------------------------------------+ +``` + +### Accessing Struct Members + +Use the **dot operator** (`.`) to access members: + +```c +simple_led_ctrl_t leds; + +// Set values +leds.led1_pin = 16; +leds.led1_state = true; + +// Read values +printf("Pin: %d\n", leds.led1_pin); +``` + +### Pointer to Struct (Arrow Operator) + +When you have a **pointer** to a struct, use the **arrow operator** (`->`): + +```c +simple_led_ctrl_t leds; +simple_led_ctrl_t *ptr = &leds; // Pointer to the struct + +// These are equivalent: +leds.led1_pin = 16; // Using dot with struct variable +ptr->led1_pin = 16; // Using arrow with pointer +(*ptr).led1_pin = 16; // Dereferencing then dot (same thing) +``` + +``` ++-----------------------------------------------------------------+ +| Dot vs Arrow Operator | +| | +| struct_variable.member <-- Use with actual struct | +| | +| pointer_to_struct->member <-- Use with pointer to struct | +| | +| The arrow (->) is shorthand for (*pointer).member | +| | ++-----------------------------------------------------------------+ +``` + +--- + +## Part 3: Designated Initializers + +### Clean Struct Initialization + +C allows you to initialize struct members by name using **designated initializers**: + +```c +simple_led_ctrl_t leds = { + .led1_pin = 16, + .led2_pin = 17, + .led3_pin = 18, + .led1_state = false, + .led2_state = false, + .led3_state = false +}; +``` + +**Benefits:** +- Clear which value goes to which member +- Order doesn't matter (can rearrange lines) +- Self-documenting code +- Easy to add new members later + +--- + +## Part 4: Understanding the NEC IR Protocol + +### What is Infrared (IR) Communication? + +**Infrared** communication uses invisible light pulses to send data. Your TV remote uses IR to send commands to your TV. The LED in the remote flashes on and off very quickly in specific patterns that represent different buttons. + +``` ++-----------------------------------------------------------------+ +| IR Communication | +| | +| Remote Control IR Receiver | +| +----------+ +----------+ | +| | Button | | | | +| | 1 | --- IR Light Pulses --- | ++ | | +| | +---+ | ~~~~~~~~~~~~> | Sensor | | +| | | Tx | | | | | +| | +---+ | +----+-----+ | +| | IR LED | | | +| +----------+ v | +| GPIO Pin | +| (Digital signal) | +| | ++-----------------------------------------------------------------+ +``` + +### The NEC Protocol + +**NEC** is one of the most common IR protocols. When you press a button, the remote sends: + +1. **Leader pulse** - 9ms HIGH, 4.5ms LOW (says "attention!") +2. **Address** - 8 bits identifying the device +3. **Address Inverse** - 8 bits (for error checking) +4. **Command** - 8 bits for the button pressed +5. **Command Inverse** - 8 bits (for error checking) + +``` ++-----------------------------------------------------------------+ +| NEC Protocol Frame | +| | +| +---------+---------+---------+---------+---------+---------+ | +| | Leader | Address | Address | Command | Command | Stop | | +| | Pulse | 8-bit | Inverse | 8-bit | Inverse | Bit | | +| | 9+4.5ms | | 8-bit | | 8-bit | | | +| +---------+---------+---------+---------+---------+---------+ | +| | +| Total: 32 bits of data (+ leader + stop) | +| | ++-----------------------------------------------------------------+ +``` + +### NEC Command Codes for Our Remote + +| Button | NEC Command Code | Hex Value | +| ------ | ---------------- | --------- | +| 1 | 0x0C | 12 | +| 2 | 0x18 | 24 | +| 3 | 0x5E | 94 | + +**Note:** Different remotes have different codes. These are specific to our example remote. + +--- + +## Part 5: Understanding Functions in C + +### What is a Function? + +A **function** is a reusable block of code that performs a specific task. Functions help organize code and avoid repetition. + +```c +// Function definition +int add_numbers(int a, int b) { + return a + b; +} + +// Function call +int result = add_numbers(5, 3); // result = 8 +``` + +### Function Components + +``` ++-----------------------------------------------------------------+ +| Anatomy of a Function | +| | +| return_type function_name ( parameters ) { | +| // function body | +| return value; | +| } | +| | +| Example: | +| +-------------------------------------------------------------+| +| | int ir_to_led_number ( int ir_command ) { || +| | --- --------------- --------------- || +| | | | | || +| | | | +-- Parameter (input) || +| | | +-- Function name || +| | +-- Return type (what it gives back) || +| | || +| | if (ir_command == 0x0C) return 1; <-- Body || +| | if (ir_command == 0x18) return 2; || +| | return 0; <-- Return value || +| | } || +| +-------------------------------------------------------------+| +| | ++-----------------------------------------------------------------+ +``` + +### Types of Functions + +| Type | Description | Example | +| ---------------------------- | ------------------------- | ---------------------------- | +| **No params, no return** | Just does something | `void leds_all_off(void)` | +| **With params, no return** | Takes input, no output | `void blink_led(pin, count)` | +| **No params, with return** | No input, gives output | `int ir_getkey(void)` | +| **With params, with return** | Takes input, gives output | `int ir_to_led_number(cmd)` | + +--- + +## Part 6: Functions with Struct Pointers + +### Passing Structs to Functions + +When passing a struct to a function, you usually pass a **pointer** to avoid copying all the data: + +```c +// Function takes a POINTER to the struct +void leds_all_off(simple_led_ctrl_t *leds) { + gpio_put(leds->led1_pin, false); // Use arrow operator! + gpio_put(leds->led2_pin, false); + gpio_put(leds->led3_pin, false); +} + +// Call with address-of operator +simple_led_ctrl_t my_leds; +leds_all_off(&my_leds); // Pass the ADDRESS of my_leds +``` + +``` ++-----------------------------------------------------------------+ +| Passing Struct by Pointer | +| | +| main() { | +| simple_led_ctrl_t leds; <-- Struct lives here | +| leds_all_off(&leds); <-- Pass ADDRESS (pointer) | +| } | | +| | | +| v | +| leds_all_off(simple_led_ctrl_t *leds) { | +| gpio_put(leds->led1_pin, false); | +| ---- | +| | | +| +-- Arrow because leds is a POINTER | +| } | +| | +| WHY use pointers? | +| - Efficient: Only 4 bytes (address) instead of entire struct | +| - Allows modification: Function can change the original | +| | ++-----------------------------------------------------------------+ +``` + +--- + +## Part 7: How Compilers Handle Structs + +### Struct "Flattening" in Assembly + +When the compiler converts your C code to assembly, it "flattens" struct operations into individual memory accesses: + +**C Code:** +```c +gpio_init(leds.led1_pin); // leds.led1_pin = 16 +gpio_init(leds.led2_pin); // leds.led2_pin = 17 +gpio_init(leds.led3_pin); // leds.led3_pin = 18 +``` + +**Assembly (what the compiler produces):** +```assembly +movs r0, #0x10 ; r0 = 16 (led1_pin value) +bl gpio_init ; call gpio_init(16) + +movs r0, #0x11 ; r0 = 17 (led2_pin value) +bl gpio_init ; call gpio_init(17) + +movs r0, #0x12 ; r0 = 18 (led3_pin value) +bl gpio_init ; call gpio_init(18) +``` + +``` ++-----------------------------------------------------------------+ +| Struct Flattening | +| | +| C Level (High-level abstraction): | +| +-------------------------------------------------------------+| +| | gpio_init(leds.led1_pin); || +| | gpio_init(leds.led2_pin); || +| | gpio_init(leds.led3_pin); || +| +-------------------------------------------------------------+| +| | | +| | Compiler transforms | +| v | +| Assembly Level (Flattened): | +| +-------------------------------------------------------------+| +| | movs r0, #16 ; Just the VALUE, no struct reference || +| | bl gpio_init || +| | movs r0, #17 ; Next value directly || +| | bl gpio_init || +| | movs r0, #18 ; Next value directly || +| | bl gpio_init || +| +-------------------------------------------------------------+| +| | +| The struct abstraction DISAPPEARS at the assembly level! | +| We just see individual values being loaded and used. | +| | ++-----------------------------------------------------------------+ +``` + +### Why This Matters for Reverse Engineering + +- In Ghidra, you won't always see "struct" - just individual values +- You must recognize PATTERNS (sequential values like 16, 17, 18) +- Understanding flattening helps you reconstruct the original struct + +--- + +## Part 8: Setting Up Your Environment + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. A Raspberry Pi Pico Debug Probe +3. Ghidra installed (for static analysis) +4. Python installed (for UF2 conversion) +5. A serial monitor (PuTTY, minicom, or screen) +6. An IR receiver module (like VS1838B) +7. An IR remote control (any NEC-compatible remote) +8. Three LEDs (red, green, yellow) with resistors +9. The sample projects: `0x0023_structures` and `0x0026_functions` + +### Hardware Setup + +**IR Receiver Wiring:** + +| IR Receiver Pin | Pico 2 Pin | +| --------------- | ---------- | +| VCC | 3.3V | +| GND | GND | +| OUT/DATA | GPIO 5 | + +**LED Wiring:** + +| LED | GPIO Pin | Resistor | +| ------ | -------- | --------- | +| Red | GPIO 16 | 220-330 ohm | +| Green | GPIO 17 | 220-330 ohm | +| Yellow | GPIO 18 | 220-330 ohm | + +``` ++-----------------------------------------------------------------+ +| Complete Wiring Diagram | +| | +| Pico 2 Components | +| +----------+ | +| | | +-------------+ | +| | GPIO 5 |--------------+ IR Receiver | | +| | | | (VS1838B) | | +| | | +------+------+ | +| | | | | +| | GPIO 16 |---[220 ohm]---(RED LED)----+ | +| | | | | +| | GPIO 17 |---[220 ohm]---(GRN LED)----+ | +| | | | | +| | GPIO 18 |---[220 ohm]---(YEL LED)----+ | +| | | | | +| | 3.3V |-------------------------+-- IR VCC | +| | | | | +| | GND |-------------------------+-- All GNDs | +| | | | +| +----------+ | +| | ++-----------------------------------------------------------------+ +``` + +### Project Structure + +``` +Embedded-Hacking/ ++-- 0x0023_structures/ +| +-- build/ +| | +-- 0x0023_structures.uf2 +| | +-- 0x0023_structures.bin +| +-- main/ +| | +-- 0x0023_structures.c +| +-- ir.h ++-- 0x0026_functions/ +| +-- build/ +| | +-- 0x0026_functions.uf2 +| | +-- 0x0026_functions.bin +| | +-- 0x0026_functions.elf +| +-- main/ +| | +-- 0x0026_functions.c +| +-- ir.h ++-- uf2conv.py +``` + +--- + +## Part 9: Hands-On Tutorial - Structures Code + +### Step 1: Review the Source Code + +Let's examine the structures code: + +**File: `0x0023_structures.c`** + +```c +#include +#include +#include "pico/stdlib.h" +#include "ir.h" + +#define IR_PIN 5 + +typedef struct { + uint8_t led1_pin; + uint8_t led2_pin; + uint8_t led3_pin; + bool led1_state; + bool led2_state; + bool led3_state; +} simple_led_ctrl_t; + +int main(void) { + stdio_init_all(); + + simple_led_ctrl_t leds = { + .led1_pin = 16, + .led2_pin = 17, + .led3_pin = 18, + .led1_state = false, + .led2_state = false, + .led3_state = false + }; + + gpio_init(leds.led1_pin); gpio_set_dir(leds.led1_pin, GPIO_OUT); + gpio_init(leds.led2_pin); gpio_set_dir(leds.led2_pin, GPIO_OUT); + gpio_init(leds.led3_pin); gpio_set_dir(leds.led3_pin, GPIO_OUT); + + ir_init(IR_PIN); + printf("IR receiver on GPIO %d ready\n", IR_PIN); + + while (true) { + int key = ir_getkey(); + if (key >= 0) { + printf("NEC command: 0x%02X\n", key); + + // Turn all off first + leds.led1_state = false; + leds.led2_state = false; + leds.led3_state = false; + + // Check NEC codes + if (key == 0x0C) leds.led1_state = true; // GPIO16 + if (key == 0x18) leds.led2_state = true; // GPIO17 + if (key == 0x5E) leds.led3_state = true; // GPIO18 + + // Apply states + gpio_put(leds.led1_pin, leds.led1_state); + gpio_put(leds.led2_pin, leds.led2_state); + gpio_put(leds.led3_pin, leds.led3_state); + + sleep_ms(10); + } else { + sleep_ms(1); + } + } +} +``` + +### Step 2: Understand the Program Flow + +``` ++-----------------------------------------------------------------+ +| Program Flow | +| | +| 1. Initialize UART (stdio_init_all) | +| 2. Create LED struct with pins 16, 17, 18 | +| 3. Initialize GPIO pins as outputs | +| 4. Initialize IR receiver on GPIO 5 | +| 5. Enter infinite loop: | +| a. Check for IR key press | +| b. If key received: | +| - Print the NEC command code | +| - Turn all LEDs off | +| - Check which button: 0x0C, 0x18, or 0x5E | +| - Turn on the matching LED | +| - Apply states to GPIO pins | +| c. Sleep briefly and repeat | +| | ++-----------------------------------------------------------------+ +``` + +### Step 3: Flash the Binary to Your Pico 2 + +1. Hold the BOOTSEL button on your Pico 2 +2. Plug in the USB cable (while holding BOOTSEL) +3. Release BOOTSEL - a drive called "RPI-RP2" appears +4. Drag and drop `0x0023_structures.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 4: Verify It's Working + +**Open PuTTY (115200 baud) and test:** +- Press "1" on remote -> Red LED lights, terminal shows `NEC command: 0x0C` +- Press "2" on remote -> Green LED lights, terminal shows `NEC command: 0x18` +- Press "3" on remote -> Yellow LED lights, terminal shows `NEC command: 0x5E` + +--- + +## Part 10: Debugging with GDB (Structures) + +### Step 5: Start OpenOCD (Terminal 1) + +Open a terminal and start OpenOCD: + +```powershell +openocd -s "$env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev\scripts" -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000" +``` + +You should see output indicating OpenOCD connected successfully to your Pico 2 via the Debug Probe. + +### Step 6: Start GDB (Terminal 2) + +Open a **new terminal** and launch GDB with the binary: + +```cmd +arm-none-eabi-gdb build\0x0023_structures.elf +``` + +### Step 7: Connect to the Remote Target + +In GDB, connect to OpenOCD: + +```gdb +target extended-remote :3333 +``` + +### Step 8: Halt the Running Binary + +Stop the processor: + +```gdb +monitor halt +``` + +### Step 9: Examine Main Function + +Disassemble around main to see struct initialization: + +```gdb +disassemble 0x10000234,+200 +``` + +Look for the struct member initialization sequence (mov instructions with values 16, 17, 18). + +### Step 10: Set a Breakpoint at Main + +```gdb +break *0x10000234 +``` + +Reset and run to hit the breakpoint: + +```gdb +monitor reset halt +continue +``` + +### Step 11: Observe the Flattened Struct + +Instead of allocating the struct on the stack, the compiler completely **flattened and optimized** it out! There is no `sub sp` instruction for the struct. + +Examine the `main` disassembly again: + +```gdb +disassemble 0x10000234,+200 +``` + +Notice how values 16, 17, and 18 are just loaded directly into registers (`movs r0, #16`, etc.) and passed to functions. The struct abstraction is entirely gone. + +### Step 12: Watch GPIO Initialization + +Set a breakpoint on the first `gpio_init` call and watch the LED pin get initialized: + +```gdb +break *0x1000023c +continue +info registers r0 +``` + +You should see `r0 = 16`. If you set breakpoints on the subsequent calls (`0x1000024c`, `0x10000258`) and continue, you will see `17` and `18`. + +### Step 13: Examine IR Key Processing + +Because `ir_getkey` is polled in a loop and returns `-1` constantly when no key is pressed, breaking at the immediate return (`0x10000274`) will just flood you with `-1`s! + +Instead, set a breakpoint *inside* the `if (key >= 0)` block at `0x10000278`. This is right after the `blt.n` conditional branch that skips over the processing logic when no key is pressed: + +```gdb +break *0x10000278 +continue +``` + +Now press a button on the remote. The program will halt. At this point, the key value has been moved into `r4` (from the `subs r4, r0, #0` instruction earlier): + +```gdb +info registers r4 +``` + +You'll see the decimal value of the NEC code (e.g., 12 for `0x0C`, 24 for `0x18`, or 94 for `0x5E`). + +### Step 14: Watch the Conditional Checks + +Let's look at the comparisons where the code checks which button was pressed: + +```gdb +break *0x10000280 +continue +``` + +*(Note: If the program doesn't halt immediately, press a button on the remote to trigger the breakpoint!)* + +```gdb +x/8i $pc +``` + +Notice the compiler uses decimal comparisons: `cmp r4, #12` (which is `0x0c`), `cmp r4, #24` (which is `0x18`), and a subtraction trick `sub.w r4, r4, #94` (which is `0x5e`) to determine which button was pressed. + +### Step 15: Observe Inlined GPIO Operations + +The compiler even inlined the `gpio_put` calls! First, let's clear any old breakpoints so we don't accidentally stop somewhere else (like you might have seen if you hit an old breakpoint first!): + +```gdb +delete +break *0x10000298 +continue +``` + +*(Note: Again, press a button on the remote if it doesn't halt immediately!)* + +Now check the registers: + +```gdb +info registers r1 r2 r3 r4 +``` + +Depending on which button you pressed, one of the state registers will be `1` (ON) and the others will be `0` (OFF): +- `r1` = State for the **Red** LED (GPIO 16) +- `r3` = State for the **Green** LED (GPIO 17) +- `r4` = State for the **Yellow** LED (GPIO 18) +- `r2` = `16` (`0x10`), which is the starting pin number being written to. + +*(Note: Since you are paused here, the physical LEDs haven't updated yet! They will only change after these `mcrr` instructions execute.)* + +Instead of branching to `gpio_put`, it uses direct hardware instructions (disassembled as `mcrr` on the RP2350) to write the states directly to the GPIO hardware. + +### Step 16: Exit GDB + +When done exploring: + +```gdb +quit +``` + +--- + +## Part 11: Setting Up Ghidra for Structures + +### Step 17: Start Ghidra + +Open a terminal and type: + +```cmd +ghidraRun +``` + +### Step 18: Create a New Project + +1. Click **File** -> **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `0x0023_structures` +5. Click **Finish** + +### Step 19: Import the Binary + +1. Navigate to the `0x0023_structures/build/` folder +2. **Drag and drop** the `.bin` file into Ghidra's project window + +### Step 20: Configure the Binary Format + +**Click the three dots (...) next to "Language" and:** +1. Search for "Cortex" +2. Select **ARM Cortex 32 little endian default** +3. Click **OK** + +**Click the "Options..." button and:** +1. Change **Block Name** to `.text` +2. Change **Base Address** to `10000000` +3. Click **OK** + +### Step 21: Analyze the Binary + +1. Double-click on the file in the project window +2. A dialog asks "Analyze now?" - Click **Yes** +3. Use default analysis options and click **Analyze** + +Wait for analysis to complete. + +--- + +## Part 12: Resolving Functions - Structures Project + +### Step 22: Navigate to Main + +1. Press `G` (Go to address) and type `10000234` +2. Right-click -> **Edit Function Signature** +3. Change to: `int main(void)` +4. Click **OK** + +### Step 23: Resolve stdio_init_all + +At address `0x10000236`: + +1. Double-click on the called function +2. Right-click -> **Edit Function Signature** +3. Change to: `bool stdio_init_all(void)` +4. Click **OK** + +### Step 24: Identify gpio_init from Struct Pattern + +Look for three consecutive calls with values 16, 17, 18: + +```assembly +1000023a 10 20 movs r0,#0x10 +1000023c 00 f0 8c f9 bl FUN_10000558 ; gpio_init + +1000024a 11 20 movs r0,#0x11 +1000024c 00 f0 84 f9 bl FUN_10000558 ; gpio_init + +10000256 12 20 movs r0,#0x12 +10000258 00 f0 7e f9 bl FUN_10000558 ; gpio_init +``` + +This pattern reveals the struct members! Update the function signature: +1. Right-click on `FUN_10000558` -> **Edit Function Signature** +2. Change to: `void gpio_init(uint gpio)` +3. Click **OK** + +### Step 25: Resolve ir_init + +Look for a function call with GPIO 5: + +```assembly +10000262 05 20 movs r0,#0x5 +10000264 00 f0 38 f8 bl FUN_100002d8 ; ir_init +``` + +1. Right-click on `FUN_100002d8` -> **Edit Function Signature** +2. Change to: `void ir_init(uint pin)` +3. Click **OK** + +### Step 26: Resolve printf + +Right after ir_init, look for the "IR receiver on GPIO" string being loaded and passed to a function: + +```assembly +1000026a 19 48 ldr r0=>s_IR_receiver_on_GPIO_%d... +1000026c 03 f0 46 f9 bl FUN_100034fc ; printf +``` + +1. Right-click on `FUN_100034fc` -> **Edit Function Signature** +2. Change to: `int printf(char *format,...)` +3. Check the **Varargs** checkbox +4. Click **OK** + +### Step 27: Resolve ir_getkey + +Look for a function that returns a value checked against conditions: + +```assembly +10000270 00 f0 46 f8 bl FUN_10000300 ; Call ir_getkey +10000274 04 1e subs r4,r0,#0x0 ; Check if >= 0 +10000276 1e db blt LAB_100002b6 ; If negative, no key pressed +``` + +1. Right-click on `FUN_10000300` -> **Edit Function Signature** +2. Change to: `int ir_getkey(void)` +3. Click **OK** + +### Step 28: Resolve sleep_ms + +Look for calls with 10 (0x0A) or 1 (0x01): + +```assembly +100002a8 0a 20 movs r0,#0xa +100002aa 00 f0 81 fe bl FUN_10000fb0 ; sleep_ms +``` + +1. Right-click on `FUN_10000fb0` -> **Edit Function Signature** +2. Change to: `void sleep_ms(uint ms)` +3. Click **OK** + +--- + +## Part 13: Recognizing Struct Patterns in Assembly + +### Step 29: Identify GPIO Set Direction + +After each `gpio_init`, look for direction setting: + +```assembly +10000240 4f f0 01 04 mov.w r4,#0x1 ; direction = output +10000244 10 23 movs r3,#0x10 ; GPIO 16 +10000246 44 ec 44 30 mcrr p0,0x4,r3,r4,cr4 ; Configure GPIO direction register +``` + +This is the compiler's heavily optimized version of `gpio_set_dir(pin, GPIO_OUT)`. + +### Step 30: Map the Struct Members + +Create a mental (or written) map: + +``` ++-----------------------------------------------------------------+ +| Struct Member Mapping | +| | +| Assembly Value -> Struct Member -> Physical LED | +| ------------------------------------------------------------- | +| 0x10 (16) -> led1_pin -> Red LED | +| 0x11 (17) -> led2_pin -> Green LED | +| 0x12 (18) -> led3_pin -> Yellow LED | +| | +| NEC Code -> State Member -> Action | +| ------------------------------------------------------------- | +| 0x0C -> led1_state=true -> Red LED ON | +| 0x18 -> led2_state=true -> Green LED ON | +| 0x5E -> led3_state=true -> Yellow LED ON | +| | ++-----------------------------------------------------------------+ +``` + +--- + +## Part 14: Hacking Structures + +### Step 31: Enable Instruction Patching + +We will use Ghidra's **Patch Instruction** feature to modify the assembly directly instead of editing raw bytes. + +### Step 32: Swap LED Pin Assignments + +We'll swap the red and green LED pins to reverse their behavior! Because the compiler fully flattened the struct, modifying the `gpio_init` pins won't actually change the main loop's behavior (since all three pins are initialized anyway). We must patch the hardcoded pins inside the **main loop** itself! + +**Find and patch the `movs` calls in the loop:** + +1. Navigate to `10000296` where the red LED pin is loaded: `movs r2,#0x10` +2. Right-click the instruction -> **Patch Instruction** (or press Ctrl+Shift+G) +3. Change `#0x10` to `#0x11` (swap red to green's pin) and press **Enter** +4. Navigate to `1000029c` where the green LED pin is loaded: `movs r2,#0x11` +5. Right-click the instruction -> **Patch Instruction** +6. Change `#0x11` to `#0x10` (swap green to red's pin) and press **Enter** + +**Before:** +``` +LED 1 (0x0C) -> GPIO 16 -> Red LED +LED 2 (0x18) -> GPIO 17 -> Green LED +``` + +**After:** +``` +LED 1 (0x0C) -> GPIO 17 -> Green LED (SWAPPED!) +LED 2 (0x18) -> GPIO 16 -> Red LED (SWAPPED!) +``` + +### Step 33: Export and Flash + +1. Click **File** -> **Export Program** +2. Set **Format** to **Raw Bytes** +3. Name: `0x0023_structures-h.bin` +4. Click **OK** + +Convert and flash: + +```cmd +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0023_structures +python ..\uf2conv.py build\0x0023_structures-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +### Step 34: Verify the Hack + +**Open PuTTY and test:** +- Press "1" on remote -> **GREEN** LED lights (was red!) +- Terminal still shows `NEC command: 0x0C` +- Press "2" on remote -> **RED** LED lights (was green!) +- Terminal still shows `NEC command: 0x18` + +**The log says one thing, but the hardware does another!** + +--- + +## Part 15: Security Implications - Log Desynchronization + +### The Danger of Mismatched Logs + +``` ++-----------------------------------------------------------------+ +| Log vs Reality Desynchronization | +| | +| +-----------------+ +-----------------+ | +| | Terminal Log | | Physical LEDs | | +| +-----------------+ +-----------------+ | +| | NEC: 0x0C | +------- | GREEN LED on | <-- Mismatch! | +| | (expects RED) | | (not red!) | | +| +-----------------+ +-----------------+ | +| | NEC: 0x18 | +------- | RED LED on | <-- Mismatch! | +| | (expects GREEN) | | (not green!) | | +| +-----------------+ +-----------------+ | +| | +| The OPERATOR sees correct logs but WRONG physical behavior! | +| | ++-----------------------------------------------------------------+ +``` + +### Real-World Example: Stuxnet + +**Stuxnet** was a cyberweapon that: +- Attacked Iranian nuclear centrifuges +- Made centrifuges spin at dangerous speeds +- Fed FALSE "everything normal" data to operators +- Operators saw stable readings while equipment was destroyed + +Our LED example demonstrates the same principle: +- Logs show expected behavior +- Hardware performs different actions +- Attackers can hide malicious activity + +--- + +## Part 16: Functions Project - Advanced Code + +### Step 35: Review the Functions Code + +**File: `0x0026_functions.c`** (key functions shown) + +```c +// Map IR command to LED number +int ir_to_led_number(int ir_command) { + if (ir_command == 0x0C) return 1; + if (ir_command == 0x18) return 2; + if (ir_command == 0x5E) return 3; + return 0; +} + +// Get GPIO pin for LED number +uint8_t get_led_pin(simple_led_ctrl_t *leds, int led_num) { + if (led_num == 1) return leds->led1_pin; + if (led_num == 2) return leds->led2_pin; + if (led_num == 3) return leds->led3_pin; + return 0; +} + +// Turn off all LEDs +void leds_all_off(simple_led_ctrl_t *leds) { + gpio_put(leds->led1_pin, false); + gpio_put(leds->led2_pin, false); + gpio_put(leds->led3_pin, false); +} + +// Blink an LED +void blink_led(uint8_t pin, uint8_t count, uint32_t delay_ms) { + for (uint8_t i = 0; i < count; i++) { + gpio_put(pin, true); + sleep_ms(delay_ms); + gpio_put(pin, false); + sleep_ms(delay_ms); + } +} + +// Main command processor +int process_ir_led_command(int ir_command, simple_led_ctrl_t *leds, uint8_t blink_count) { + if (!leds || ir_command < 0) return -1; + + leds_all_off(leds); + int led_num = ir_to_led_number(ir_command); + if (led_num == 0) return 0; + + uint8_t pin = get_led_pin(leds, led_num); + blink_led(pin, blink_count, 50); + gpio_put(pin, true); + + return led_num; +} +``` + +### Step 36: Understand the Function Call Chain + +``` ++-----------------------------------------------------------------+ +| Function Call Chain | +| | +| main() | +| | | +| +--> process_ir_led_command(key, &leds, 3) | +| | | +| +--> leds_all_off(&leds) | +| | +--> gpio_put() * 3 | +| | | +| +--> ir_to_led_number(ir_command) | +| | +--> returns 1, 2, or 3 | +| | | +| +--> get_led_pin(&leds, led_num) | +| | +--> returns GPIO pin number | +| | | +| +--> blink_led(pin, 3, 50) | +| | +--> gpio_put() + sleep_ms() in loop | +| | | +| +--> gpio_put(pin, true) | +| | ++-----------------------------------------------------------------+ +``` + +### Step 37: Flash and Test + +1. Flash `0x0026_functions.uf2` to your Pico 2 +2. Open PuTTY +3. Press remote buttons: + - "1" -> Red LED blinks 3 times, then stays on + - "2" -> Green LED blinks 3 times, then stays on + - "3" -> Yellow LED blinks 3 times, then stays on + +--- + +## Part 17: Debugging with GDB (Functions) + +### Step 38: Start OpenOCD (Terminal 1) + +Open a terminal and start OpenOCD: + +```powershell +openocd -s "$env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev\scripts" -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000" +``` + +You should see output indicating OpenOCD connected successfully to your Pico 2 via the Debug Probe. + +### Step 39: Start GDB (Terminal 2) + +Open a **new terminal** and launch GDB with the binary: + +```cmd +arm-none-eabi-gdb build\0x0026_functions.elf +``` + +### Step 40: Connect to the Remote Target + +In GDB, connect to OpenOCD: + +```gdb +target extended-remote :3333 +``` + +### Step 41: Halt the Running Binary + +Stop the processor: + +```gdb +monitor halt +``` + +### Step 42: Examine the Function Layout + +Disassemble to see the multiple functions: + +```gdb +disassemble 0x10000234,+300 +``` + +You'll see multiple function prologues (push) and epilogues (pop) for the helper functions. + +### Step 43: Discover the Missing Functions + +The C code relies heavily on helper functions (`process_ir_led_command`, `leds_all_off`, `ir_to_led_number`, `blink_led`). But if you scroll through the disassembly, you'll notice a distinct lack of function prologues, epilogues, or `bl` calls to anything other than `sleep_ms` or `printf`! + +The compiler has aggressively **inlined** every single helper function into the main loop. + +Let's trace exactly how the compiler flattened this logic. First, set a breakpoint right when a key press is detected: + +```gdb +break *0x10000284 +continue +``` + +*(Press a button on your remote to trigger the breakpoint!)* + +### Step 44: Examine leds_all_off (Inlined) + +In the C code, `process_ir_led_command` starts by calling `leds_all_off(&leds)`. + +Let's look at the next few instructions starting from our breakpoint: +```gdb +x/8i $pc +``` + +You'll see: +```assembly +0x10000284 : mov r1, r4 +0x10000286 : ldr r0, [pc, #156] +0x10000288 : bl 0x10003554 <__wrap_printf> +0x1000028c : movs r5, #16 +0x1000028e : mcrr 0, 4, r5, r6, cr0 +0x10000292 : movs r3, #17 +0x10000294 : mcrr 0, 4, r3, r6, cr0 +0x10000298 : movs r2, #18 +``` +Because `r6` was set to `0` earlier, the compiler is just directly writing `0` to pins 16, 17, and 18 using `mcrr`. It bypassed the function call entirely! + +### Step 45: Examine ir_to_led_number (Inlined) + +Next, the C code calls `ir_to_led_number` and `get_led_pin`. Let's see how the compiler handled that by inspecting further down: + +```gdb +x/4i 0x1000029e +``` + +```assembly +0x1000029e : cmp r4, #12 +0x100002a0 : beq.n 0x100002c6 +0x100002a2 : cmp r4, #24 +0x100002a4 : beq.n 0x1000030a +``` +Instead of a separate function, it simply compares the button code in `r4` (`12`, `24`, `94`) and branches straight to the correct blinking logic! + +### Step 46: Watch the blink_led Loop + +Let's set a breakpoint where the blinking loop begins for Button 1 (which handles the Red LED on pin 16). + +```gdb +break *0x100002c6 +continue +``` + +Once halted, inspect the setup: +```gdb +x/6i $pc +``` + +```assembly +0x100002c6 : mov.w r8, #1 @ led_num = 1 +0x100002ca : movs r4, #3 @ blink_count = 3 +0x100002cc : mcrr 0, 4, r5, r7, cr0 @ Turn LED ON (r7=1) +0x100002d0 : movs r0, #50 @ Delay 50ms +0x100002d2 : bl 0x10001008 +0x100002d6 : mcrr 0, 4, r5, r6, cr0 @ Turn LED OFF (r6=0) +``` +The compiler placed the blink count in `r4` and handles the toggling with `mcrr` instructions surrounding `sleep_ms`. + +### Step 47: The Struct Pointer is a Lie + +If you were trying to find the `leds` struct pointer to examine the pins using `x/6xb`, you'd be looking forever. Because the compiler realized the struct is never passed to any external non-inlined functions, it **never bothered creating it in memory**. It simply kept track of the pin numbers (16, 17, 18) directly in registers during compilation! + +### Step 48: Observe the Loop Condition + +Continue execution until you hit the end of the blink loop: + +```gdb +break *0x100002e0 +continue +``` + +At this point, GDB is halted **right before** it performs the subtraction. If you check `r3` now, it will contain random leftover garbage (like `0x400b0000`) because the instruction hasn't run yet! Check `r4` (the current blink count): + +```gdb +info registers r4 +``` + +Now, step forward two instructions to let it actually do the math: + +```gdb +stepi 2 +info registers r3 r4 +``` + +```assembly +0x100002e0 : subs r3, r4, #1 +0x100002e2 : ands.w r4, r3, #255 +0x100002e6 : bne.n 0x100002cc +``` +You will now see `r3` become `2`, and `r4` become `2`! It successfully subtracted 1 from the blink count and stored it back. Since it hasn't hit 0 yet, the `bne.n` instruction will branch back to the start of the blink (`0x100002cc`) to flash the LED again! + +### Step 49: Exit GDB + +When done exploring: + +```gdb +quit +``` + +--- + +## Part 18: Analyzing .ELF Files in Ghidra + +### Step 50: Create New Ghidra Project + +1. Create project: `0x0026_functions` +2. Import the `.elf` file (NOT the .bin this time!) + +### Why Use .ELF Instead of .BIN? + +| Feature | .BIN File | .ELF File | +| -------------- | -------------------- | --------------------------- | +| **Symbols** | None | Function/variable names | +| **Sections** | Raw bytes only | .text, .data, .rodata, etc. | +| **Debug info** | None | May include debug symbols | +| **Size** | Smaller | Larger | +| **Use case** | Flashing to hardware | Analysis and debugging | + +### Step 51: Import and Analyze the .ELF + +1. Drag and drop the `.elf` file into Ghidra +2. Ghidra automatically detects ARM format! +3. Click **Yes** to analyze +4. Wait for analysis to complete + +### Step 52: Explore the Symbol Tree + +With .ELF files, you get more information: +1. Look at the **Symbol Tree** panel +2. Expand **Functions** - you may see named functions! +3. Expand **Labels** - data labels may appear + +--- + +## Part 19: Hacking the Functions Project + +### Step 53: Find LED Pin Values + +Look for the struct initialization pattern: + +```assembly +movs r0, #0x10 ; led1_pin = 16 +movs r0, #0x11 ; led2_pin = 17 +movs r0, #0x12 ; led3_pin = 18 +``` + +### Step 54: Swap LED 1 and LED 3 + +We'll swap the red (GPIO 16) and yellow (GPIO 18) LEDs: + +**Find and patch in the .bin file:** +1. Change `0x10` (16) to `0x12` (18) +2. Change `0x12` (18) to `0x10` (16) + +**Before:** +``` +Button 1 -> LED 1 -> GPIO 16 -> Red +Button 3 -> LED 3 -> GPIO 18 -> Yellow +``` + +**After:** +``` +Button 1 -> LED 1 -> GPIO 18 -> Yellow (SWAPPED!) +Button 3 -> LED 3 -> GPIO 16 -> Red (SWAPPED!) +``` + +### Step 55: Export the Patched .BIN + +**Important:** Even though we analyzed the .elf, we patch the .bin! + +1. Open the original `.bin` file in Ghidra (or a hex editor) +2. Apply the patches +3. Export as `0x0026_functions-h.bin` + +### Step 56: Convert and Flash + +```cmd +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions +python ..\uf2conv.py build\0x0026_functions-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +### Step 57: Verify the Hack + +**Open PuTTY and test:** +- Press "1" -> **YELLOW** LED blinks (was red!) +- Terminal shows: `LED 1 activated on GPIO 16` (WRONG - it's actually GPIO 18!) +- Press "3" -> **RED** LED blinks (was yellow!) +- Terminal shows: `LED 3 activated on GPIO 18` (WRONG - it's actually GPIO 16!) + +**Again, logs don't match reality!** + +--- + +## Part 20: Summary and Review + +### What We Accomplished + +1. **Learned C structures** - Grouping related data together +2. **Understood struct memory layout** - How members are stored consecutively +3. **Mastered dot and arrow operators** - Accessing struct members +4. **Learned the NEC IR protocol** - How remotes communicate +5. **Understood functions with parameters** - Passing data in and out +6. **Saw struct flattening in assembly** - How compilers transform structs +7. **Analyzed .ELF files** - Getting more symbol information +8. **Hacked GPIO assignments** - Swapping LED behavior +9. **Discovered log desynchronization** - Security implications + +### Struct Operations Summary + +``` ++-----------------------------------------------------------------+ +| Struct Operations | +| | +| Definition: | +| typedef struct { | +| uint8_t pin; | +| bool state; | +| } led_t; | +| | +| Creation: | +| led_t led = { .pin = 16, .state = false }; | +| | +| Access (variable): led.pin | +| Access (pointer): ptr->pin or (*ptr).pin | +| | +| Passing to function: void func(led_t *led) | +| Calling: func(&led) | +| | ++-----------------------------------------------------------------+ +``` + +### Function Types Summary + +``` ++-----------------------------------------------------------------+ +| Function Patterns | +| | +| No params, no return: | +| void leds_all_off(void) | +| | +| With params, no return: | +| void blink_led(uint8_t pin, uint8_t count, uint32_t delay) | +| | +| No params, with return: | +| int ir_getkey(void) | +| | +| With params, with return: | +| int ir_to_led_number(int ir_command) | +| | +| With struct pointer: | +| uint8_t get_led_pin(simple_led_ctrl_t *leds, int led_num) | +| | ++-----------------------------------------------------------------+ +``` + +### Key Memory Addresses + +| Memory Address | Description | +| -------------- | ------------------------------- | +| `0x10000234` | main() function | +| `0x10` (16) | GPIO 16 - Red LED (led1_pin) | +| `0x11` (17) | GPIO 17 - Green LED (led2_pin) | +| `0x12` (18) | GPIO 18 - Yellow LED (led3_pin) | +| `0x05` | GPIO 5 - IR receiver | +| `0x0C` | NEC code for button 1 | +| `0x18` | NEC code for button 2 | +| `0x5E` | NEC code for button 3 | + +--- + +--- + +## Key Takeaways + +1. **Structs group related data** - Better organization than separate variables + +2. **Dot operator for variables, arrow for pointers** - `.` vs `->` + +3. **Designated initializers are cleaner** - `.member = value` syntax + +4. **Compilers flatten structs** - You see values, not struct names, in assembly + +5. **NEC protocol uses 8-bit commands** - 0x0C, 0x18, 0x5E for our buttons + +6. **Functions separate concerns** - Each function does one job + +7. **.ELF files contain more info than .BIN** - Symbols, sections, debug data + +8. **Log desynchronization is dangerous** - Logs can lie about real behavior + +9. **Pattern recognition is key** - Consecutive values like 16, 17, 18 reveal structs + +10. **Always patch the .bin for flashing** - .elf is for analysis only + +--- + +## Glossary + +| Term | Definition | +| -------------------------- | -------------------------------------------------- | +| **Arrow Operator (->)** | Accesses struct member through a pointer | +| **Designated Initializer** | Syntax `.member = value` for struct initialization | +| **Dot Operator (.)** | Accesses struct member from a struct variable | +| **.ELF File** | Executable and Linkable Format - contains symbols | +| **Flattening** | Compiler converting structs to individual values | +| **IR (Infrared)** | Invisible light used for remote control | +| **Log Desynchronization** | When logs don't match actual system behavior | +| **Member** | A variable inside a struct | +| **NEC Protocol** | Common IR communication standard | +| **Struct** | User-defined type grouping related variables | +| **typedef** | Creates an alias for a type | + +--- + +## Additional Resources + +### NEC IR Command Reference + +| Button | Command | Binary | +| ------ | ------- | --------- | +| 1 | 0x0C | 0000 1100 | +| 2 | 0x18 | 0001 1000 | +| 3 | 0x5E | 0101 1110 | + +### GPIO Pin Quick Reference + +| GPIO | Default Function | Our Usage | +| ---- | ---------------- | ----------- | +| 5 | General I/O | IR Receiver | +| 16 | General I/O | Red LED | +| 17 | General I/O | Green LED | +| 18 | General I/O | Yellow LED | + +### Struct Size Calculation + +| Type | Size (bytes) | +| ---------- | ------------ | +| `uint8_t` | 1 | +| `bool` | 1 | +| `uint16_t` | 2 | +| `uint32_t` | 4 | +| `int` | 4 | +| `float` | 4 | +| `pointer` | 4 (on ARM32) | + +--- + +## Real-World Implications + +### What You've Learned in This Course + +Over these weeks, you've built skills that few people possess: + +1. **Hardware fundamentals** - GPIO, I2C, PWM, IR protocols +2. **Reverse engineering** - Ghidra, disassembly, function identification +3. **Binary patching** - Modifying compiled code +4. **Security awareness** - Understanding vulnerabilities + +### The Power and Responsibility + +The techniques you've learned can be used for: + +**Good:** +- Security research +- Debugging proprietary systems +- Understanding how things work +- Career in cybersecurity + +**Danger:** +- Unauthorized system access +- Sabotage of critical infrastructure +- Fraud and deception + +**Always use your skills ethically and legally!** + +### Keep Learning + +This is just the beginning: +- Explore more complex protocols (SPI, CAN bus) +- Learn dynamic analysis with debuggers +- Study cryptographic implementations +- Practice on CTF challenges + +--- + +**Congratulations on completing this course! You now have the curiosity, persistence, and skills that embedded systems engineers and security researchers thrive on. Keep experimenting, documenting, and sharing your work. The world needs more builders and defenders like you!** + +Happy hacking! :) diff --git a/WEEK11/slides/WEEK11-IMG00.svg b/WEEK11/slides/WEEK11-IMG00.svg new file mode 100644 index 0000000..0469f90 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 11 + + +Structures and Functions in +Embedded Systems: Debugging and Hacking +w/ IR Remote Control & NEC Protocol + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEK11/slides/WEEK11-IMG01.svg b/WEEK11/slides/WEEK11-IMG01.svg new file mode 100644 index 0000000..6701ba4 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG01.svg @@ -0,0 +1,70 @@ + + + + +C Structures (Structs) +Grouping Related Data Together + + + +What is a Struct? +A user-defined type that groups +related variables under one name +Like a form with multiple fields +-- each field holds different data + + + +Struct Definition + +typedef struct { +uint8_t led1_pin; +uint8_t led2_pin; +uint8_t led3_pin; +bool led1_state; +bool led2_state; +bool led3_state; +} simple_led_ctrl_t; + + + +Why Use Structs? +1. +Organization +Related data stays together +2. +Readability +Code easier to understand +3. +Scalability +Easy to add more features +4. +Pass to Functions + + + +simple_led_ctrl_t leds + +pin1: 16 + +pin2: 17 + +pin3: 18 + +state1: 0 + +state2: 0 + +state3: 0 + diff --git a/WEEK11/slides/WEEK11-IMG02.svg b/WEEK11/slides/WEEK11-IMG02.svg new file mode 100644 index 0000000..6157da6 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG02.svg @@ -0,0 +1,85 @@ + + + + +Struct Memory Layout +How Structs Are Stored and Accessed + + + +Memory Layout (6 bytes total) + +Address +Member +Size +Value + + +0x20000000 +led1_pin +1 byte +16 (0x10) + +0x20000001 +led2_pin +1 byte +17 (0x11) + +0x20000002 +led3_pin +1 byte +18 (0x12) + +0x20000003 +led1_state +1 byte +0 (false) + +0x20000004 +led2_state +1 byte +0 (false) + +0x20000005 +led3_state +1 byte +0 (false) + + + +Dot Operator ( . ) +Use with struct variable + +leds.led1_pin = 16; +leds.led1_state = true; + + + +Arrow Operator ( -> ) +Use with pointer to struct + +ptr->led1_pin = 16; +// same as (*ptr).led1_pin + + + +Designated Initializers + +simple_led_ctrl_t leds = { +.led1_pin = 16, .led2_pin = 17, .led3_pin = 18 +}; +Clear which value goes +to which member +Order doesn't matter + diff --git a/WEEK11/slides/WEEK11-IMG03.svg b/WEEK11/slides/WEEK11-IMG03.svg new file mode 100644 index 0000000..c44ccd0 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG03.svg @@ -0,0 +1,88 @@ + + + + +NEC IR Protocol +Infrared Remote Control Communication + + + +How IR Works +Remote sends invisible light pulses +IR receiver on GPIO 5 reads signal +IR LED flashes in specific patterns +Each pattern = different button + + + +NEC Protocol Frame (32 bits) + + +Leader + +Address + +Addr Inv + +Command + +Cmd Inv + +Stop + +9ms+4.5ms +8-bit +8-bit check +8-bit +8-bit check + +Leader says "attention!", address identifies device, command is the button pressed + + + +NEC Command Codes + +Button +NEC Code +LED + + +1 +0x0C +Red (GP16) + +2 +0x18 +Green (GP17) + +3 +0x5E +Yellow (GP18) + + + +Hardware Wiring +GPIO 5 +IR Receiver (VS1838B) +GPIO 16 +Red LED + 220 ohm +GPIO 17 +Green LED + 220 ohm +GPIO 18 +Yellow LED + 220 ohm + + + +Projects: 0x0023_structures and 0x0026_functions + diff --git a/WEEK11/slides/WEEK11-IMG04.svg b/WEEK11/slides/WEEK11-IMG04.svg new file mode 100644 index 0000000..719946b --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG04.svg @@ -0,0 +1,91 @@ + + + + +Functions in C +Reusable Blocks of Code + + + +Anatomy of a Function + +int ir_to_led_number(int ir_command) { + +^^^ +^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^ +ret +function name +parameter + + +if (ir_command == 0x0C) return 1; +// body + return value + + + +Function Types + +Type +Example + + +No params, no ret +leds_all_off() + +Params, no return +blink_led(..) + +No params, return +ir_getkey() + +Params + return +ir_to_led_num() + +Struct pointer +get_led_pin() + + + +Key Functions + +ir_to_led_number(cmd) +Maps NEC code to LED 1/2/3 + +get_led_pin(leds, num) +Returns GPIO pin for LED + +blink_led(pin, cnt, ms) +Blinks LED cnt times + + + +Function Call Chain + +main() +--> +process_ir_led_command() + +1. leds_all_off() +Turn all LEDs off + +2. ir_to_led_number() +Map NEC to LED + +3. get_led_pin() +Get GPIO pin + +4. blink_led() +Blink + stay on + diff --git a/WEEK11/slides/WEEK11-IMG05.svg b/WEEK11/slides/WEEK11-IMG05.svg new file mode 100644 index 0000000..9058acc --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG05.svg @@ -0,0 +1,66 @@ + + + + +Struct Pointers in Functions +Passing Data Efficiently + + + +Why Pass by Pointer? +Efficient +4 bytes (address) not 6 +Modifiable +Function can change original +Standard +Embedded systems practice + + + +Arrow Operator +leds->led1_pin +Same as (*leds).led1_pin +Use -> when leds is a pointer + + + +leds_all_off() + +void leds_all_off( +simple_led_ctrl_t *leds) { +gpio_put(leds->led1_pin, 0); +gpio_put(leds->led2_pin, 0); + + + +blink_led() + +void blink_led(uint8_t pin, +uint8_t count, uint32_t ms){ +gpio_put(pin, true); +sleep_ms(ms); + + + +process_ir_led_command() -- Main Command Processor + +int process_ir_led_command(int cmd, +simple_led_ctrl_t *leds, uint8_t blink_count) { +leds_all_off(leds); +// turn all off first +int num = ir_to_led_number(cmd); +// map NEC to LED +blink_led(get_led_pin(leds, num), +// blink then stay on + diff --git a/WEEK11/slides/WEEK11-IMG06.svg b/WEEK11/slides/WEEK11-IMG06.svg new file mode 100644 index 0000000..84c7359 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG06.svg @@ -0,0 +1,57 @@ + + + + +Structures Source Code +0x0023_structures.c + + + +Full Source + + +#include <stdio.h> +#include "pico/stdlib.h" +#include "ir.h" + +typedef struct { +uint8_t led1_pin, led2_pin, led3_pin; +bool led1_state, led2_state, led3_state; +} simple_led_ctrl_t; + +int main(void) { +stdio_init_all(); +simple_led_ctrl_t leds = { +.led1_pin=16, .led2_pin=17, .led3_pin=18 +}; + +gpio_init(leds.led1_pin); +// init 16, 17, 18 +ir_init(5); +// IR on GPIO 5 +while (true) { +// main loop + + + +Main Loop Flow +ir_getkey() +--> +check NEC code +--> +set state +--> +gpio_put() +0x0C=LED1(red) 0x18=LED2(green) 0x5E=LED3(yellow) + diff --git a/WEEK11/slides/WEEK11-IMG07.svg b/WEEK11/slides/WEEK11-IMG07.svg new file mode 100644 index 0000000..514e9f0 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG07.svg @@ -0,0 +1,73 @@ + + + + +Struct Flattening +How Compilers Transform Structs + + + +C Code (High Level) + +gpio_init(leds.led1_pin); +// leds.led1_pin = 16 +gpio_init(leds.led2_pin); +// leds.led2_pin = 17 + + + +Assembly (Flattened) + +movs r0, #0x10 +// 16 +bl gpio_init +movs r0, #0x11 +// 17 +bl gpio_init + + + +The Key Insight +Struct abstraction DISAPPEARS +at assembly level +You see individual values (16, 17, 18) not struct names + + + +Struct Member Mapping + +Assembly +Struct Member +Physical +NEC Code + + +0x10 (16) +led1_pin +Red LED +0x0C + +0x11 (17) +led2_pin +Green LED +0x18 + +0x12 (18) +led3_pin +Yellow LED +0x5E + +Sequential values (16,17,18) reveal the struct pattern +Recognize patterns to reconstruct original structs in Ghidra + diff --git a/WEEK11/slides/WEEK11-IMG08.svg b/WEEK11/slides/WEEK11-IMG08.svg new file mode 100644 index 0000000..8eb24ae --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG08.svg @@ -0,0 +1,77 @@ + + + + +Hacking Structures +Swapping GPIO Pin Assignments + + + +Swap LED 1 and LED 2 +Find gpio_init values: +0x10 (16) +--> +0x11 (17) +0x11 (17) +--> +0x10 (16) +Swap the two byte values + + + +Result After Hack + +Before: +Btn 1 --> GPIO 16 --> Red +Btn 2 --> GPIO 17 --> Green + +After: +SWAPPED! + + + +Before (Normal) +Btn 1 (0x0C) --> GPIO 16 +Red +Btn 2 (0x18) --> GPIO 17 +Green +Log and LED match correctly + + +After (Hacked) +Btn 1 (0x0C) --> GPIO 17 +Green! +Btn 2 (0x18) --> GPIO 16 +Red! +Log says RED but GREEN lights + + + +Log Desynchronization + +Terminal Log: +NEC command: 0x0C +(expects Red) + +Physical LED: +GREEN LED on +MISMATCH! + +Operator sees correct logs but WRONG behavior + + + +Stuxnet: +False "normal" data to operators, equipment destroyed + diff --git a/WEEK11/slides/WEEK11-IMG09.svg b/WEEK11/slides/WEEK11-IMG09.svg new file mode 100644 index 0000000..e6ae7a2 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG09.svg @@ -0,0 +1,73 @@ + + + + +.ELF vs .BIN Analysis +Ghidra Analysis of 0x0026_functions + + + +.ELF vs .BIN Comparison + +Feature +.BIN File +.ELF File + + +Symbols +None +Function names + +Sections +Raw bytes only +.text .data .rodata + +Debug info +None +May include debug + +Use case +Flash to device +Analysis + debug + + + +Importing .BIN +Manual setup required: +ARM Cortex 32 little endian +Block: .text +Base: 10000000 +No function names + + +Importing .ELF +Auto-detected by Ghidra: +ARM format recognized +Sections auto-loaded +Symbol tree populated +Named functions visible + + + +Important Rule +Analyze the .ELF +for symbol information +Patch the .BIN +for flashing + + + +Export Workflow +Patch .bin in Ghidra --> uf2conv.py --> flash to Pico 2 + diff --git a/WEEK11/slides/WEEK11-IMG10.svg b/WEEK11/slides/WEEK11-IMG10.svg new file mode 100644 index 0000000..3906d05 --- /dev/null +++ b/WEEK11/slides/WEEK11-IMG10.svg @@ -0,0 +1,85 @@ + + + + +Structs & IR Protocol +Structs, Functions, IR, and Hacking + + + +C Structures +Group related data together +Dot (.) for variables +Arrow (->) for pointers +Designated init: .pin = 16 + + + +NEC IR Protocol +32-bit frame: addr + cmd +0x0C +Button 1 +0x18 +Button 2 +0x5E +Button 3 + + + +Functions +Reusable blocks, one job each +ir_to_led_number() +get_led_pin() / blink_led() +process_ir_led_command() + + + +Assembly Flattening +Structs vanish in assembly +Only see values: 0x10 0x11 0x12 +Pattern recognition is key +.ELF has symbols, .BIN doesn't + + + +Key Values +0x10000234 +main() +GPIO 5 +IR receiver +GPIO 16/17/18 +Red/Green/Yellow + + + +Hacking Techniques +GPIO swap +0x10 <--> 0x11 +Log desync +Logs lie! +Stuxnet +Same concept + + + +Projects +0x0023_structures +0x0026_functions + + + +Key Takeaway +Patch bytes, mislead logs +hardware does what YOU say + diff --git a/aapcs32.pdf b/aapcs32.pdf new file mode 100644 index 0000000..4373fef --- /dev/null +++ b/aapcs32.pdf @@ -0,0 +1,3592 @@ +%PDF-1.4 +%“Œ‹ž ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R /F10 256 0 R /F11 268 0 R /F2+0 304 0 R /F3 3 0 R /F4 14 0 R + /F5+0 308 0 R /F6 152 0 R /F7 215 0 R /F8+0 312 0 R /F9 246 0 R +>> +endobj +2 0 obj +<< +/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font +>> +endobj +3 0 obj +<< +/BaseFont /Times-Roman /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font +>> +endobj +4 0 obj +<< +/Contents 398 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +5 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa) +>> /Border [ 0 0 0 ] /Rect [ 118.301 561.0236 391.1152 571.8236 ] /Subtype /Link /Type /Annot +>> +endobj +6 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/issues) +>> /Border [ 0 0 0 ] /Rect [ 275.1154 533.4236 406.6965 544.2236 ] /Subtype /Link /Type /Annot +>> +endobj +7 0 obj +<< +/Annots [ 5 0 R 6 0 R ] /Contents 399 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +8 0 obj +<< +/A << +/S /URI /Type /Action /URI (http://creativecommons.org/licenses/by-sa/4.0/) +>> /Border [ 0 0 0 ] /Rect [ 202.7023 719.4236 438.1145 730.2236 ] /Subtype /Link /Type /Annot +>> +endobj +9 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 97 0 R /XYZ 77.02362 668.0336 0 ] /Rect [ 193.1223 539.8236 232.2262 550.6236 ] /Subtype /Link /Type /Annot +>> +endobj +10 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://www.apache.org/licenses/LICENSE-2.0) +>> /Border [ 0 0 0 ] /Rect [ 57.02362 507.4236 262.4416 518.2236 ] /Subtype /Link /Type /Annot +>> +endobj +11 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 97 0 R /XYZ 77.02362 668.0336 0 ] /Rect [ 434.0436 354.2236 468.2331 365.0236 ] /Subtype /Link /Type /Annot +>> +endobj +12 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://www.arm.com/company/policies/trademarks) +>> /Border [ 0 0 0 ] /Rect [ 57.02362 256.2236 290.6413 267.0236 ] /Subtype /Link /Type /Annot +>> +endobj +13 0 obj +<< +/Annots [ 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R ] /Contents 400 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +14 0 obj +<< +/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font +>> +endobj +15 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 57.02362 718.0436 102.0974 727.2236 ] /Subtype /Link /Type /Annot +>> +endobj +16 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 533.9986 718.6174 538.252 727.7974 ] /Subtype /Link /Type /Annot +>> +endobj +17 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 716.2236 0 ] /Rect [ 77.02362 702.8636 128.5976 712.0436 ] /Subtype /Link /Type /Annot +>> +endobj +18 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 716.2236 0 ] /Rect [ 533.3848 703.4374 538.252 712.6174 ] /Subtype /Link /Type /Annot +>> +endobj +19 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 650.6236 0 ] /Rect [ 77.02362 687.6836 133.659 696.8636 ] /Subtype /Link /Type /Annot +>> +endobj +20 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 650.6236 0 ] /Rect [ 533.3848 688.2574 538.252 697.4374 ] /Subtype /Link /Type /Annot +>> +endobj +21 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 595.8236 0 ] /Rect [ 77.02362 672.5036 223.9948 681.6836 ] /Subtype /Link /Type /Annot +>> +endobj +22 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 595.8236 0 ] /Rect [ 533.3848 673.0774 538.252 682.2574 ] /Subtype /Link /Type /Annot +>> +endobj +23 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 77.02362 657.3236 125.5458 666.5036 ] /Subtype /Link /Type /Annot +>> +endobj +24 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 533.3848 657.8974 538.252 667.0774 ] /Subtype /Link /Type /Annot +>> +endobj +25 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 574.6236 0 ] /Rect [ 77.02362 642.1436 163.2243 651.3236 ] /Subtype /Link /Type /Annot +>> +endobj +26 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 574.6236 0 ] /Rect [ 533.3848 642.7174 538.252 651.8974 ] /Subtype /Link /Type /Annot +>> +endobj +27 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 399.8236 0 ] /Rect [ 77.02362 626.9636 147.9692 636.1436 ] /Subtype /Link /Type /Annot +>> +endobj +28 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 399.8236 0 ] /Rect [ 533.3848 627.5374 538.252 636.7174 ] /Subtype /Link /Type /Annot +>> +endobj +29 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 334.2236 0 ] /Rect [ 77.02362 611.7836 164.2665 620.9636 ] /Subtype /Link /Type /Annot +>> +endobj +30 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 334.2236 0 ] /Rect [ 533.3848 612.3574 538.252 621.5374 ] /Subtype /Link /Type /Annot +>> +endobj +31 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 236.2236 0 ] /Rect [ 77.02362 596.6036 133.8645 605.7836 ] /Subtype /Link /Type /Annot +>> +endobj +32 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 236.2236 0 ] /Rect [ 533.3848 597.1774 538.252 606.3574 ] /Subtype /Link /Type /Annot +>> +endobj +33 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 158 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 57.02362 581.4236 147.133 590.6036 ] /Subtype /Link /Type /Annot +>> +endobj +34 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 158 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 533.9986 581.9974 538.252 591.1774 ] /Subtype /Link /Type /Annot +>> +endobj +35 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 158 0 R /XYZ 57.02362 716.2236 0 ] /Rect [ 77.02362 566.2436 156.0263 575.4236 ] /Subtype /Link /Type /Annot +>> +endobj +36 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 158 0 R /XYZ 57.02362 716.2236 0 ] /Rect [ 533.3848 566.8174 538.252 575.9974 ] /Subtype /Link /Type /Annot +>> +endobj +37 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 158 0 R /XYZ 57.02362 678.2236 0 ] /Rect [ 97.02362 551.0636 278.233 560.2436 ] /Subtype /Link /Type /Annot +>> +endobj +38 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 158 0 R /XYZ 57.02362 678.2236 0 ] /Rect [ 533.3848 551.6374 538.252 560.8174 ] /Subtype /Link /Type /Annot +>> +endobj +39 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 158 0 R /XYZ 57.02362 437.4236 0 ] /Rect [ 97.02362 535.8836 182.7201 545.0636 ] /Subtype /Link /Type /Annot +>> +endobj +40 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 158 0 R /XYZ 57.02362 437.4236 0 ] /Rect [ 533.3848 536.4574 538.252 545.6374 ] /Subtype /Link /Type /Annot +>> +endobj +41 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 216 0 R /XYZ 57.02362 573.8236 0 ] /Rect [ 77.02362 520.7036 139.5048 529.8836 ] /Subtype /Link /Type /Annot +>> +endobj +42 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 216 0 R /XYZ 57.02362 573.8236 0 ] /Rect [ 533.3848 521.2774 538.252 530.4574 ] /Subtype /Link /Type /Annot +>> +endobj +43 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 216 0 R /XYZ 57.02362 243.4236 0 ] /Rect [ 77.02362 505.5236 192.4796 514.7036 ] /Subtype /Link /Type /Annot +>> +endobj +44 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 216 0 R /XYZ 57.02362 243.4236 0 ] /Rect [ 533.3848 506.0974 538.252 515.2774 ] /Subtype /Link /Type /Annot +>> +endobj +45 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 221 0 R /XYZ 57.02362 574.6236 0 ] /Rect [ 77.02362 490.3436 171.3823 499.5236 ] /Subtype /Link /Type /Annot +>> +endobj +46 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 221 0 R /XYZ 57.02362 574.6236 0 ] /Rect [ 528.5176 490.9174 538.252 500.0974 ] /Subtype /Link /Type /Annot +>> +endobj +47 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 223 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 57.02362 475.1636 90.61477 484.3436 ] /Subtype /Link /Type /Annot +>> +endobj +48 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 223 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 529.7452 475.7374 538.252 484.9174 ] /Subtype /Link /Type /Annot +>> +endobj +49 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 228 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 57.02362 459.9836 112.2796 469.1636 ] /Subtype /Link /Type /Annot +>> +endobj +50 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 228 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 529.7452 460.5574 538.252 469.7374 ] /Subtype /Link /Type /Annot +>> +endobj +51 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 228 0 R /XYZ 57.02362 688.6236 0 ] /Rect [ 77.02362 444.8036 146.7365 453.9836 ] /Subtype /Link /Type /Annot +>> +endobj +52 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 228 0 R /XYZ 57.02362 688.6236 0 ] /Rect [ 528.5176 445.3774 538.252 454.5574 ] /Subtype /Link /Type /Annot +>> +endobj +53 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 228 0 R /XYZ 57.02362 539.8236 0 ] /Rect [ 77.02362 429.6236 147.7749 438.8036 ] /Subtype /Link /Type /Annot +>> +endobj +54 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 228 0 R /XYZ 57.02362 539.8236 0 ] /Rect [ 528.5176 430.1974 538.252 439.3774 ] /Subtype /Link /Type /Annot +>> +endobj +55 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 232 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 57.02362 414.4436 163.7258 423.6236 ] /Subtype /Link /Type /Annot +>> +endobj +56 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 232 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 529.7452 415.0174 538.252 424.1974 ] /Subtype /Link /Type /Annot +>> +endobj +57 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 232 0 R /XYZ 57.02362 716.2236 0 ] /Rect [ 77.02362 399.2636 192.8233 408.4436 ] /Subtype /Link /Type /Annot +>> +endobj +58 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 232 0 R /XYZ 57.02362 716.2236 0 ] /Rect [ 528.5176 399.8374 538.252 409.0174 ] /Subtype /Link /Type /Annot +>> +endobj +59 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 232 0 R /XYZ 57.02362 213.4236 0 ] /Rect [ 97.02362 384.0836 231.3357 393.2636 ] /Subtype /Link /Type /Annot +>> +endobj +60 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 232 0 R /XYZ 57.02362 213.4236 0 ] /Rect [ 528.5176 384.6574 538.252 393.8374 ] /Subtype /Link /Type /Annot +>> +endobj +61 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 234 0 R /XYZ 57.02362 723.4236 0 ] /Rect [ 97.02362 368.9036 208.2587 378.0836 ] /Subtype /Link /Type /Annot +>> +endobj +62 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 234 0 R /XYZ 57.02362 723.4236 0 ] /Rect [ 528.5176 369.4774 538.252 378.6574 ] /Subtype /Link /Type /Annot +>> +endobj +63 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 234 0 R /XYZ 57.02362 639.2236 0 ] /Rect [ 77.02362 353.7236 213.0838 362.9036 ] /Subtype /Link /Type /Annot +>> +endobj +64 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 234 0 R /XYZ 57.02362 639.2236 0 ] /Rect [ 528.5176 354.2974 538.252 363.4774 ] /Subtype /Link /Type /Annot +>> +endobj +65 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 237 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 77.02362 338.5436 162.4922 347.7236 ] /Subtype /Link /Type /Annot +>> +endobj +66 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 237 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 528.5176 339.1174 538.252 348.2974 ] /Subtype /Link /Type /Annot +>> +endobj +67 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 237 0 R /XYZ 57.02362 546.2236 0 ] /Rect [ 97.02362 323.3636 167.816 332.5436 ] /Subtype /Link /Type /Annot +>> +endobj +68 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 237 0 R /XYZ 57.02362 546.2236 0 ] /Rect [ 528.5176 323.9374 538.252 333.1174 ] /Subtype /Link /Type /Annot +>> +endobj +69 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 237 0 R /XYZ 57.02362 450.8236 0 ] /Rect [ 97.02362 308.1836 149.8714 317.3636 ] /Subtype /Link /Type /Annot +>> +endobj +70 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 237 0 R /XYZ 57.02362 450.8236 0 ] /Rect [ 528.5176 308.7574 538.252 317.9374 ] /Subtype /Link /Type /Annot +>> +endobj +71 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 237 0 R /XYZ 57.02362 355.4236 0 ] /Rect [ 97.02362 293.0036 148.508 302.1836 ] /Subtype /Link /Type /Annot +>> +endobj +72 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 237 0 R /XYZ 57.02362 355.4236 0 ] /Rect [ 528.5176 293.5774 538.252 302.7574 ] /Subtype /Link /Type /Annot +>> +endobj +73 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 237 0 R /XYZ 57.02362 260.0236 0 ] /Rect [ 97.02362 277.8236 157.4093 287.0036 ] /Subtype /Link /Type /Annot +>> +endobj +74 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 237 0 R /XYZ 57.02362 260.0236 0 ] /Rect [ 528.5176 278.3974 538.252 287.5774 ] /Subtype /Link /Type /Annot +>> +endobj +75 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 238 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 97.02362 262.6436 225.445 271.8236 ] /Subtype /Link /Type /Annot +>> +endobj +76 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 238 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 528.5176 263.2174 538.252 272.3974 ] /Subtype /Link /Type /Annot +>> +endobj +77 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 239 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 57.02362 247.4636 193.0712 256.6436 ] /Subtype /Link /Type /Annot +>> +endobj +78 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 239 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 529.7452 248.0374 538.252 257.2174 ] /Subtype /Link /Type /Annot +>> +endobj +79 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 239 0 R /XYZ 57.02362 677.8236 0 ] /Rect [ 77.02362 232.2836 166.7654 241.4636 ] /Subtype /Link /Type /Annot +>> +endobj +80 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 239 0 R /XYZ 57.02362 677.8236 0 ] /Rect [ 528.5176 232.8574 538.252 242.0374 ] /Subtype /Link /Type /Annot +>> +endobj +81 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 239 0 R /XYZ 57.02362 601.4236 0 ] /Rect [ 97.02362 217.1036 177.7446 226.2836 ] /Subtype /Link /Type /Annot +>> +endobj +82 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 239 0 R /XYZ 57.02362 601.4236 0 ] /Rect [ 528.5176 217.6774 538.252 226.8574 ] /Subtype /Link /Type /Annot +>> +endobj +83 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 242 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 97.02362 201.9236 212.2144 211.1036 ] /Subtype /Link /Type /Annot +>> +endobj +84 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 242 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 528.5176 202.4974 538.252 211.6774 ] /Subtype /Link /Type /Annot +>> +endobj +85 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 242 0 R /XYZ 57.02362 180.8236 0 ] /Rect [ 77.02362 186.7436 225.8961 195.9236 ] /Subtype /Link /Type /Annot +>> +endobj +86 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 242 0 R /XYZ 57.02362 180.8236 0 ] /Rect [ 528.5176 187.3174 538.252 196.4974 ] /Subtype /Link /Type /Annot +>> +endobj +87 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 243 0 R /XYZ 57.02362 525.0236 0 ] /Rect [ 97.02362 171.5636 161.6228 180.7436 ] /Subtype /Link /Type /Annot +>> +endobj +88 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 243 0 R /XYZ 57.02362 525.0236 0 ] /Rect [ 528.5176 172.1374 538.252 181.3174 ] /Subtype /Link /Type /Annot +>> +endobj +89 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 248 0 R /XYZ 57.02362 707.4236 0 ] /Rect [ 77.02362 156.3836 159.0968 165.5636 ] /Subtype /Link /Type /Annot +>> +endobj +90 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 248 0 R /XYZ 57.02362 707.4236 0 ] /Rect [ 528.5176 156.9574 538.252 166.1374 ] /Subtype /Link /Type /Annot +>> +endobj +91 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 248 0 R /XYZ 57.02362 275.8236 0 ] /Rect [ 97.02362 141.2036 207.7955 150.3836 ] /Subtype /Link /Type /Annot +>> +endobj +92 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 248 0 R /XYZ 57.02362 275.8236 0 ] /Rect [ 528.5176 141.7774 538.252 150.9574 ] /Subtype /Link /Type /Annot +>> +endobj +93 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 253 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 77.02362 126.0236 148.761 135.2036 ] /Subtype /Link /Type /Annot +>> +endobj +94 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 253 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 528.5176 126.5974 538.252 135.7774 ] /Subtype /Link /Type /Annot +>> +endobj +95 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 253 0 R /XYZ 57.02362 490.2236 0 ] /Rect [ 77.02362 110.8436 168.1625 120.0236 ] /Subtype /Link /Type /Annot +>> +endobj +96 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 253 0 R /XYZ 57.02362 490.2236 0 ] /Rect [ 528.5176 111.4174 538.252 120.5974 ] /Subtype /Link /Type /Annot +>> +endobj +97 0 obj +<< +/Annots [ 15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 23 0 R 24 0 R + 25 0 R 26 0 R 27 0 R 28 0 R 29 0 R 30 0 R 31 0 R 32 0 R 33 0 R 34 0 R + 35 0 R 36 0 R 37 0 R 38 0 R 39 0 R 40 0 R 41 0 R 42 0 R 43 0 R 44 0 R + 45 0 R 46 0 R 47 0 R 48 0 R 49 0 R 50 0 R 51 0 R 52 0 R 53 0 R 54 0 R + 55 0 R 56 0 R 57 0 R 58 0 R 59 0 R 60 0 R 61 0 R 62 0 R 63 0 R 64 0 R + 65 0 R 66 0 R 67 0 R 68 0 R 69 0 R 70 0 R 71 0 R 72 0 R 73 0 R 74 0 R + 75 0 R 76 0 R 77 0 R 78 0 R 79 0 R 80 0 R 81 0 R 82 0 R 83 0 R 84 0 R + 85 0 R 86 0 R 87 0 R 88 0 R 89 0 R 90 0 R 91 0 R 92 0 R 93 0 R 94 0 R + 95 0 R 96 0 R ] /Contents 401 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +98 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 255 0 R /XYZ 57.02362 154.6236 0 ] /Rect [ 77.02362 752.8436 144.7829 762.0236 ] /Subtype /Link /Type /Annot +>> +endobj +99 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 255 0 R /XYZ 57.02362 154.6236 0 ] /Rect [ 528.5176 753.4174 538.252 762.5974 ] /Subtype /Link /Type /Annot +>> +endobj +100 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 260 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 57.02362 737.6636 148.8542 746.8436 ] /Subtype /Link /Type /Annot +>> +endobj +101 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 260 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 529.7452 738.2374 538.252 747.4174 ] /Subtype /Link /Type /Annot +>> +endobj +102 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 260 0 R /XYZ 57.02362 688.6236 0 ] /Rect [ 77.02362 722.4836 255.0654 731.6636 ] /Subtype /Link /Type /Annot +>> +endobj +103 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 260 0 R /XYZ 57.02362 688.6236 0 ] /Rect [ 528.5176 723.0574 538.252 732.2374 ] /Subtype /Link /Type /Annot +>> +endobj +104 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 260 0 R /XYZ 57.02362 612.2236 0 ] /Rect [ 97.02362 707.3036 307.5219 716.4836 ] /Subtype /Link /Type /Annot +>> +endobj +105 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 260 0 R /XYZ 57.02362 612.2236 0 ] /Rect [ 528.5176 707.8774 538.252 717.0574 ] /Subtype /Link /Type /Annot +>> +endobj +106 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 260 0 R /XYZ 57.02362 401.6236 0 ] /Rect [ 97.02362 692.1236 191.2329 701.3036 ] /Subtype /Link /Type /Annot +>> +endobj +107 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 260 0 R /XYZ 57.02362 401.6236 0 ] /Rect [ 528.5176 692.6974 538.252 701.8774 ] /Subtype /Link /Type /Annot +>> +endobj +108 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 263 0 R /XYZ 57.02362 434.6236 0 ] /Rect [ 77.02362 676.9436 323.9453 686.1236 ] /Subtype /Link /Type /Annot +>> +endobj +109 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 263 0 R /XYZ 57.02362 434.6236 0 ] /Rect [ 528.5176 677.5174 538.252 686.6974 ] /Subtype /Link /Type /Annot +>> +endobj +110 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 263 0 R /XYZ 57.02362 351.0236 0 ] /Rect [ 77.02362 661.7636 256.8248 670.9436 ] /Subtype /Link /Type /Annot +>> +endobj +111 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 263 0 R /XYZ 57.02362 351.0236 0 ] /Rect [ 528.5176 662.3374 538.252 671.5174 ] /Subtype /Link /Type /Annot +>> +endobj +112 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 263 0 R /XYZ 57.02362 263.8236 0 ] /Rect [ 77.02362 646.5836 177.5457 655.7636 ] /Subtype /Link /Type /Annot +>> +endobj +113 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 263 0 R /XYZ 57.02362 263.8236 0 ] /Rect [ 528.5176 647.1574 538.252 656.3374 ] /Subtype /Link /Type /Annot +>> +endobj +114 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 263 0 R /XYZ 57.02362 165.8236 0 ] /Rect [ 97.02362 631.4036 266.6161 640.5836 ] /Subtype /Link /Type /Annot +>> +endobj +115 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 263 0 R /XYZ 57.02362 165.8236 0 ] /Rect [ 528.5176 631.9774 538.252 641.1574 ] /Subtype /Link /Type /Annot +>> +endobj +116 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 264 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 97.02362 616.2236 272.1182 625.4036 ] /Subtype /Link /Type /Annot +>> +endobj +117 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 264 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 528.5176 616.7974 538.252 625.9774 ] /Subtype /Link /Type /Annot +>> +endobj +118 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 264 0 R /XYZ 57.02362 702.4236 0 ] /Rect [ 97.02362 601.0436 267.7367 610.2236 ] /Subtype /Link /Type /Annot +>> +endobj +119 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 264 0 R /XYZ 57.02362 702.4236 0 ] /Rect [ 528.5176 601.6174 538.252 610.7974 ] /Subtype /Link /Type /Annot +>> +endobj +120 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 264 0 R /XYZ 57.02362 639.8236 0 ] /Rect [ 97.02362 585.8636 259.6123 595.0436 ] /Subtype /Link /Type /Annot +>> +endobj +121 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 264 0 R /XYZ 57.02362 639.8236 0 ] /Rect [ 528.5176 586.4374 538.252 595.6174 ] /Subtype /Link /Type /Annot +>> +endobj +122 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 57.02362 570.6836 199.0076 579.8636 ] /Subtype /Link /Type /Annot +>> +endobj +123 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 529.7452 571.2574 538.252 580.4374 ] /Subtype /Link /Type /Annot +>> +endobj +124 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 57.02362 677.8236 0 ] /Rect [ 77.02362 555.5036 139.9307 564.6836 ] /Subtype /Link /Type /Annot +>> +endobj +125 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 57.02362 677.8236 0 ] /Rect [ 528.5176 556.0774 538.252 565.2574 ] /Subtype /Link /Type /Annot +>> +endobj +126 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 57.02362 639.8236 0 ] /Rect [ 97.02362 540.3236 188.8049 549.5036 ] /Subtype /Link /Type /Annot +>> +endobj +127 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 57.02362 639.8236 0 ] /Rect [ 528.5176 540.8974 538.252 550.0774 ] /Subtype /Link /Type /Annot +>> +endobj +128 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 270 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 97.02362 525.1436 176.0823 534.3236 ] /Subtype /Link /Type /Annot +>> +endobj +129 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 270 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 528.5176 525.7174 538.252 534.8974 ] /Subtype /Link /Type /Annot +>> +endobj +130 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 270 0 R /XYZ 57.02362 580.4236 0 ] /Rect [ 97.02362 509.9636 196.0478 519.1436 ] /Subtype /Link /Type /Annot +>> +endobj +131 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 270 0 R /XYZ 57.02362 580.4236 0 ] /Rect [ 528.5176 510.5374 538.252 519.7174 ] /Subtype /Link /Type /Annot +>> +endobj +132 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 274 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 97.02362 494.7836 187.5013 503.9636 ] /Subtype /Link /Type /Annot +>> +endobj +133 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 274 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 528.5176 495.3574 538.252 504.5374 ] /Subtype /Link /Type /Annot +>> +endobj +134 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 274 0 R /XYZ 57.02362 514.8236 0 ] /Rect [ 97.02362 479.6036 198.345 488.7836 ] /Subtype /Link /Type /Annot +>> +endobj +135 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 274 0 R /XYZ 57.02362 514.8236 0 ] /Rect [ 528.5176 480.1774 538.252 489.3574 ] /Subtype /Link /Type /Annot +>> +endobj +136 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 274 0 R /XYZ 57.02362 267.4236 0 ] /Rect [ 97.02362 464.4236 254.323 473.6036 ] /Subtype /Link /Type /Annot +>> +endobj +137 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 274 0 R /XYZ 57.02362 267.4236 0 ] /Rect [ 528.5176 464.9974 538.252 474.1774 ] /Subtype /Link /Type /Annot +>> +endobj +138 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 274 0 R /XYZ 57.02362 194.0236 0 ] /Rect [ 97.02362 449.2436 157.4093 458.4236 ] /Subtype /Link /Type /Annot +>> +endobj +139 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 274 0 R /XYZ 57.02362 194.0236 0 ] /Rect [ 528.5176 449.8174 538.252 458.9974 ] /Subtype /Link /Type /Annot +>> +endobj +140 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 287 0 R /XYZ 57.02362 606.6236 0 ] /Rect [ 77.02362 434.0636 216.1318 443.2436 ] /Subtype /Link /Type /Annot +>> +endobj +141 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 287 0 R /XYZ 57.02362 606.6236 0 ] /Rect [ 528.5176 434.6374 538.252 443.8174 ] /Subtype /Link /Type /Annot +>> +endobj +142 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 292 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 57.02362 418.8836 290.3945 428.0636 ] /Subtype /Link /Type /Annot +>> +endobj +143 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 292 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 529.7452 419.4574 538.252 428.6374 ] /Subtype /Link /Type /Annot +>> +endobj +144 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 292 0 R /XYZ 57.02362 687.4236 0 ] /Rect [ 77.02362 403.7036 142.9787 412.8836 ] /Subtype /Link /Type /Annot +>> +endobj +145 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 292 0 R /XYZ 57.02362 687.4236 0 ] /Rect [ 528.5176 404.2774 538.252 413.4574 ] /Subtype /Link /Type /Annot +>> +endobj +146 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 292 0 R /XYZ 57.02362 589.4236 0 ] /Rect [ 77.02362 388.5236 185.9539 397.7036 ] /Subtype /Link /Type /Annot +>> +endobj +147 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 292 0 R /XYZ 57.02362 589.4236 0 ] /Rect [ 528.5176 389.0974 538.252 398.2774 ] /Subtype /Link /Type /Annot +>> +endobj +148 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 294 0 R /XYZ 57.02362 516.2236 0 ] /Rect [ 97.02362 373.3436 179.3246 382.5236 ] /Subtype /Link /Type /Annot +>> +endobj +149 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 294 0 R /XYZ 57.02362 516.2236 0 ] /Rect [ 528.5176 373.9174 538.252 383.0974 ] /Subtype /Link /Type /Annot +>> +endobj +150 0 obj +<< +/Annots [ 98 0 R 99 0 R 100 0 R 101 0 R 102 0 R 103 0 R 104 0 R 105 0 R 106 0 R 107 0 R + 108 0 R 109 0 R 110 0 R 111 0 R 112 0 R 113 0 R 114 0 R 115 0 R 116 0 R 117 0 R + 118 0 R 119 0 R 120 0 R 121 0 R 122 0 R 123 0 R 124 0 R 125 0 R 126 0 R 127 0 R + 128 0 R 129 0 R 130 0 R 131 0 R 132 0 R 133 0 R 134 0 R 135 0 R 136 0 R 137 0 R + 138 0 R 139 0 R 140 0 R 141 0 R 142 0 R 143 0 R 144 0 R 145 0 R 146 0 R 147 0 R + 148 0 R 149 0 R ] /Contents 402 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +151 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 274 0 R /XYZ 57.02362 665.8236 0 ] /Rect [ 376.5736 314.6236 472.6996 325.4236 ] /Subtype /Link /Type /Annot +>> +endobj +152 0 obj +<< +/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F6 /Subtype /Type1 /Type /Font +>> +endobj +153 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 270 0 R /XYZ 57.02362 582.9236 0 ] /Rect [ 411.7756 303.8236 496.7922 314.6236 ] /Subtype /Link /Type /Annot +>> +endobj +154 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 242 0 R /XYZ 57.02362 598.4236 0 ] /Rect [ 411.2333 251.4236 495.7797 262.2236 ] /Subtype /Link /Type /Annot +>> +endobj +155 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 242 0 R /XYZ 57.02362 598.4236 0 ] /Rect [ 221.829 240.6236 276.4838 251.4236 ] /Subtype /Link /Type /Annot +>> +endobj +156 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 274 0 R /XYZ 57.02362 196.5236 0 ] /Rect [ 273.7679 229.8236 376.6703 240.6236 ] /Subtype /Link /Type /Annot +>> +endobj +157 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 248 0 R /XYZ 57.02362 278.3236 0 ] /Rect [ 299.0673 198.2236 397.9047 209.0236 ] /Subtype /Link /Type /Annot +>> +endobj +158 0 obj +<< +/Annots [ 151 0 R 153 0 R 154 0 R 155 0 R 156 0 R 157 0 R ] /Contents 403 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +159 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 260 0 R /XYZ 57.02362 351.8236 0 ] /Rect [ 387.9862 710.8236 503.0658 721.6236 ] /Subtype /Link /Type /Annot +>> +endobj +160 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 260 0 R /XYZ 57.02362 351.8236 0 ] /Rect [ 221.829 700.0236 271.1927 710.8236 ] /Subtype /Link /Type /Annot +>> +endobj +161 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 270 0 R /XYZ 57.02362 582.9236 0 ] /Rect [ 265.3612 626.0236 350.3778 636.8236 ] /Subtype /Link /Type /Annot +>> +endobj +162 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 253 0 R /XYZ 57.02362 768.0236 0 ] /Rect [ 378.037 583.6236 491.161 594.4236 ] /Subtype /Link /Type /Annot +>> +endobj +163 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/products/software-development-tools/compilers/arm-compiler-5/docs/101028/latest/1-preface) +>> /Border [ 0 0 0 ] /Rect [ 363.1351 562.0236 386.2767 572.8236 ] /Subtype /Link /Type /Annot +>> +endobj +164 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 57.02362 642.3236 0 ] /Rect [ 396.16 562.0236 472.6556 572.8236 ] /Subtype /Link /Type /Annot +>> +endobj +165 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 292 0 R /XYZ 57.02362 769.8236 0 ] /Rect [ 221.829 551.2236 490.1187 562.0236 ] /Subtype /Link /Type /Annot +>> +endobj +166 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 292 0 R /XYZ 57.02362 592.4236 0 ] /Rect [ 221.829 519.6236 327.0868 530.4236 ] /Subtype /Link /Type /Annot +>> +endobj +167 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 253 0 R /XYZ 57.02362 768.0236 0 ] /Rect [ 371.8451 476.4236 484.9691 487.2236 ] /Subtype /Link /Type /Annot +>> +endobj +168 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 253 0 R /XYZ 57.02362 493.2236 0 ] /Rect [ 221.829 465.6236 357.7782 476.4236 ] /Subtype /Link /Type /Annot +>> +endobj +169 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 260 0 R /XYZ 57.02362 614.7236 0 ] /Rect [ 363.4999 465.6236 506.1508 476.4236 ] /Subtype /Link /Type /Annot +>> +endobj +170 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 260 0 R /XYZ 57.02362 614.7236 0 ] /Rect [ 221.829 454.8236 292.4799 465.6236 ] /Subtype /Link /Type /Annot +>> +endobj +171 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 260 0 R /XYZ 57.02362 351.8236 0 ] /Rect [ 298.2015 454.8236 465.5057 465.6236 ] /Subtype /Link /Type /Annot +>> +endobj +172 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 237 0 R /XYZ 57.02362 768.0236 0 ] /Rect [ 440.3426 444.0236 517.9984 454.8236 ] /Subtype /Link /Type /Annot +>> +endobj +173 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 253 0 R /XYZ 57.02362 493.2236 0 ] /Rect [ 221.829 433.2236 357.7782 444.0236 ] /Subtype /Link /Type /Annot +>> +endobj +174 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 283 0 R /XYZ 57.02362 391.0236 0 ] /Rect [ 233.0482 412.4236 509.5829 423.2236 ] /Subtype /Link /Type /Annot +>> +endobj +175 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 283 0 R /XYZ 57.02362 391.0236 0 ] /Rect [ 221.829 401.6236 262.3817 412.4236 ] /Subtype /Link /Type /Annot +>> +endobj +176 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 244 0 R /XYZ 57.02362 691.4236 0 ] /Rect [ 233.0482 380.0236 295.3759 390.8236 ] /Subtype /Link /Type /Annot +>> +endobj +177 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 242 0 R /XYZ 57.02362 598.4236 0 ] /Rect [ 233.0482 358.4236 375.1102 369.2236 ] /Subtype /Link /Type /Annot +>> +endobj +178 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 244 0 R /XYZ 57.02362 599.4236 0 ] /Rect [ 225.3402 305.2236 308.4012 316.0236 ] /Subtype /Link /Type /Annot +>> +endobj +179 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 239 0 R /XYZ 57.02362 680.8236 0 ] /Rect [ 314.1229 305.2236 396.806 316.0236 ] /Subtype /Link /Type /Annot +>> +endobj +180 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 232 0 R /XYZ 57.02362 215.9236 0 ] /Rect [ 225.3402 283.6236 351.8719 294.4236 ] /Subtype /Link /Type /Annot +>> +endobj +181 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 263 0 R /XYZ 57.02362 437.6236 0 ] /Rect [ 357.5936 283.6236 528.5277 294.4236 ] /Subtype /Link /Type /Annot +>> +endobj +182 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 263 0 R /XYZ 57.02362 437.6236 0 ] /Rect [ 221.829 272.8236 315.6346 283.6236 ] /Subtype /Link /Type /Annot +>> +endobj +183 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 57.02362 642.3236 0 ] /Rect [ 321.3563 272.8236 397.8519 283.6236 ] /Subtype /Link /Type /Annot +>> +endobj +184 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 232 0 R /XYZ 57.02362 215.9236 0 ] /Rect [ 280.2103 251.2236 406.7421 262.0236 ] /Subtype /Link /Type /Annot +>> +endobj +185 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 263 0 R /XYZ 57.02362 437.6236 0 ] /Rect [ 412.4637 251.2236 518.5389 262.0236 ] /Subtype /Link /Type /Annot +>> +endobj +186 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 263 0 R /XYZ 57.02362 437.6236 0 ] /Rect [ 221.829 240.4236 380.4935 251.2236 ] /Subtype /Link /Type /Annot +>> +endobj +187 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 264 0 R /XYZ 57.02362 642.3236 0 ] /Rect [ 386.2152 240.4236 483.2684 251.2236 ] /Subtype /Link /Type /Annot +>> +endobj +188 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 264 0 R /XYZ 57.02362 642.3236 0 ] /Rect [ 221.829 229.6236 281.7133 240.4236 ] /Subtype /Link /Type /Annot +>> +endobj +189 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 57.02362 583.8236 0 ] /Rect [ 287.4349 229.6236 465.8925 240.4236 ] /Subtype /Link /Type /Annot +>> +endobj +190 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 280 0 R /XYZ 57.02362 443.8236 0 ] /Rect [ 429.576 208.8236 513.1029 219.6236 ] /Subtype /Link /Type /Annot +>> +endobj +191 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 280 0 R /XYZ 57.02362 443.8236 0 ] /Rect [ 221.829 198.0236 275.4203 208.8236 ] /Subtype /Link /Type /Annot +>> +endobj +192 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 280 0 R /XYZ 57.02362 268.6236 0 ] /Rect [ 298.0741 198.0236 388.0917 208.8236 ] /Subtype /Link /Type /Annot +>> +endobj +193 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 283 0 R /XYZ 57.02362 391.0236 0 ] /Rect [ 221.829 176.4236 498.3636 187.2236 ] /Subtype /Link /Type /Annot +>> +endobj +194 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 283 0 R /XYZ 57.02362 391.0236 0 ] /Rect [ 221.829 165.6236 262.3817 176.4236 ] /Subtype /Link /Type /Annot +>> +endobj +195 0 obj +<< +/Annots [ 159 0 R 160 0 R 161 0 R 162 0 R 163 0 R 164 0 R 165 0 R 166 0 R 167 0 R 168 0 R + 169 0 R 170 0 R 171 0 R 172 0 R 173 0 R 174 0 R 175 0 R 176 0 R 177 0 R 178 0 R + 179 0 R 180 0 R 181 0 R 182 0 R 183 0 R 184 0 R 185 0 R 186 0 R 187 0 R 188 0 R + 189 0 R 190 0 R 191 0 R 192 0 R 193 0 R 194 0 R ] /Contents 404 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +196 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 97 0 R /XYZ 77.02362 668.0336 0 ] /Rect [ 266.2919 706.6236 300.4813 717.4236 ] /Subtype /Link /Type /Annot +>> +endobj +197 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 97 0 R /XYZ 77.02362 637.6736 0 ] /Rect [ 320.327 689.8236 402.5151 700.6236 ] /Subtype /Link /Type /Annot +>> +endobj +198 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 97 0 R /XYZ 77.02362 622.4936 0 ] /Rect [ 386.6185 689.8236 502.3921 700.6236 ] /Subtype /Link /Type /Annot +>> +endobj +199 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 97 0 R /XYZ 77.02362 607.3136 0 ] /Rect [ 244.829 679.0236 288.8051 689.8236 ] /Subtype /Link /Type /Annot +>> +endobj +200 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 57.02362 680.8236 0 ] /Rect [ 233.0482 598.8236 284.161 609.6236 ] /Subtype /Link /Type /Annot +>> +endobj +201 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 63.02362 501.0986 104.2136 511.8986 ] /Subtype /Link /Type /Annot +>> +endobj +202 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 63.02362 469.4986 102.6667 480.2986 ] /Subtype /Link /Type /Annot +>> +endobj +203 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 63.02362 448.6986 101.3483 459.4986 ] /Subtype /Link /Type /Annot +>> +endobj +204 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 63.02362 417.0986 106.5998 427.8986 ] /Subtype /Link /Type /Annot +>> +endobj +205 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/docs/ddi0406/c/arm-architecture-reference-manual-armv7-a-and-armv7-r-edition) +>> /Border [ 0 0 0 ] /Rect [ 63.02362 396.2986 103.3742 407.0986 ] /Subtype /Link /Type /Annot +>> +endobj +206 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/docs/ddi0100/latest/armv5-architecture-reference-manual) +>> /Border [ 0 0 0 ] /Rect [ 161.5428 385.4986 328.3724 396.2986 ] /Subtype /Link /Type /Annot +>> +endobj +207 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/docs/ddi0100/latest/armv5-architecture-reference-manual) +>> /Border [ 0 0 0 ] /Rect [ 161.5428 374.6986 327.6473 385.4986 ] /Subtype /Link /Type /Annot +>> +endobj +208 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/docs/ddi0100/latest/armv5-architecture-reference-manual) +>> /Border [ 0 0 0 ] /Rect [ 161.5428 363.8986 214.6859 374.6986 ] /Subtype /Link /Type /Annot +>> +endobj +209 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/docs/ddi0406/c/arm-architecture-reference-manual-armv7-a-and-armv7-r-edition) +>> /Border [ 0 0 0 ] /Rect [ 161.5428 332.2986 328.3724 343.0986 ] /Subtype /Link /Type /Annot +>> +endobj +210 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/docs/ddi0406/c/arm-architecture-reference-manual-armv7-a-and-armv7-r-edition) +>> /Border [ 0 0 0 ] /Rect [ 161.5428 321.4986 329.9632 332.2986 ] /Subtype /Link /Type /Annot +>> +endobj +211 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/docs/ddi0406/c/arm-architecture-reference-manual-armv7-a-and-armv7-r-edition) +>> /Border [ 0 0 0 ] /Rect [ 161.5428 310.6986 315.659 321.4986 ] /Subtype /Link /Type /Annot +>> +endobj +212 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/products/software-development-tools/compilers/arm-compiler-5/docs/101028/latest/1-preface) +>> /Border [ 0 0 0 ] /Rect [ 63.02362 289.8986 86.16522 300.6986 ] /Subtype /Link /Type /Annot +>> +endobj +213 0 obj +<< +/A << +/S /URI /Type /Action /URI (http://itanium-cxx-abi.github.io/cxx-abi/abi.html) +>> /Border [ 0 0 0 ] /Rect [ 63.02362 269.0986 102.1218 279.8986 ] /Subtype /Link /Type /Annot +>> +endobj +214 0 obj +<< +/A << +/S /URI /Type /Action /URI (http://itanium-cxx-abi.github.io/) +>> /Border [ 0 0 0 ] /Rect [ 161.5428 269.0986 304.7826 279.8986 ] /Subtype /Link /Type /Annot +>> +endobj +215 0 obj +<< +/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F7 /Subtype /Type1 /Type /Font +>> +endobj +216 0 obj +<< +/Annots [ 196 0 R 197 0 R 198 0 R 199 0 R 200 0 R 201 0 R 202 0 R 203 0 R 204 0 R 205 0 R + 206 0 R 207 0 R 208 0 R 209 0 R 210 0 R 211 0 R 212 0 R 213 0 R 214 0 R ] /Contents 405 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +217 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 201.0056 748.2236 248.5746 759.0236 ] /Subtype /Link /Type /Annot +>> +endobj +218 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 449.613 748.2236 502.4421 759.0236 ] /Subtype /Link /Type /Annot +>> +endobj +219 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 268.4177 737.4236 314.9821 748.2236 ] /Subtype /Link /Type /Annot +>> +endobj +220 0 obj +<< +/Annots [ 217 0 R 218 0 R 219 0 R ] /Contents 406 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +221 0 obj +<< +/Contents 407 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +222 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 310.5617 285.8236 360.5518 296.6236 ] /Subtype /Link /Type /Annot +>> +endobj +223 0 obj +<< +/Annots [ 222 0 R ] /Contents 408 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +224 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 300 0 R /XYZ 63.02362 669.7843 0 ] /Rect [ 240.57 439.0836 245.1509 447.7236 ] /Subtype /Link /Type /Annot +>> +endobj +225 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 243 0 R /XYZ 57.02362 381.6236 0 ] /Rect [ 377.6037 414.4236 499.2927 425.2236 ] /Subtype /Link /Type /Annot +>> +endobj +226 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 248 0 R /XYZ 57.02362 278.3236 0 ] /Rect [ 243.6935 386.8236 342.5309 397.6236 ] /Subtype /Link /Type /Annot +>> +endobj +227 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 300 0 R /XYZ 63.02362 534.1843 0 ] /Rect [ 164.5568 347.2836 169.1377 355.9236 ] /Subtype /Link /Type /Annot +>> +endobj +228 0 obj +<< +/Annots [ 224 0 R 225 0 R 226 0 R 227 0 R ] /Contents 409 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +229 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 232 0 R /XYZ 57.02362 215.9236 0 ] /Rect [ 356.9717 385.6236 483.5035 396.4236 ] /Subtype /Link /Type /Annot +>> +endobj +230 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/docs/ddi0406/c/arm-architecture-reference-manual-armv7-a-and-armv7-r-edition) +>> /Border [ 0 0 0 ] /Rect [ 398.8121 354.0236 439.1626 364.8236 ] /Subtype /Link /Type /Annot +>> +endobj +231 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 234 0 R /XYZ 57.02362 725.9236 0 ] /Rect [ 356.9717 301.6236 456.3541 312.4236 ] /Subtype /Link /Type /Annot +>> +endobj +232 0 obj +<< +/Annots [ 229 0 R 230 0 R 231 0 R ] /Contents 410 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +233 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 300 0 R /XYZ 63.02362 501.7843 0 ] /Rect [ 446.6472 482.8836 451.228 491.5236 ] /Subtype /Link /Type /Annot +>> +endobj +234 0 obj +<< +/Annots [ 233 0 R ] /Contents 411 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +235 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 300 0 R /XYZ 63.02362 480.1843 0 ] /Rect [ 137.2326 189.8836 145.6102 198.5236 ] /Subtype /Link /Type /Annot +>> +endobj +236 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 57.02362 769.8236 0 ] /Rect [ 115.5676 174.2236 280.8063 185.0236 ] /Subtype /Link /Type /Annot +>> +endobj +237 0 obj +<< +/Annots [ 235 0 R 236 0 R ] /Contents 412 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +238 0 obj +<< +/Contents 413 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +239 0 obj +<< +/Contents 414 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +240 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 248 0 R /XYZ 57.02362 278.3236 0 ] /Rect [ 153.1144 705.0236 262.5182 715.8236 ] /Subtype /Link /Type /Annot +>> +endobj +241 0 obj +<< +/Annots [ 240 0 R ] /Contents 415 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +242 0 obj +<< +/Contents 416 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +243 0 obj +<< +/Contents 417 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +244 0 obj +<< +/Contents 418 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +245 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 255 0 R /XYZ 57.02362 157.6236 0 ] /Rect [ 79.15888 591.0236 135.9802 601.8236 ] /Subtype /Link /Type /Annot +>> +endobj +246 0 obj +<< +/BaseFont /Symbol /Name /F9 /Subtype /Type1 /Type /Font +>> +endobj +247 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 354.7829 127.4236 394.426 138.2236 ] /Subtype /Link /Type /Annot +>> +endobj +248 0 obj +<< +/Annots [ 245 0 R 247 0 R ] /Contents 419 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +249 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 253 0 R /XYZ 57.02362 493.2236 0 ] /Rect [ 113.11 528.0236 256.7985 538.8236 ] /Subtype /Link /Type /Annot +>> +endobj +250 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 254 0 R /XYZ 63.02362 692.6236 0 ] /Rect [ 254.7809 528.0236 303.9894 538.8236 ] /Subtype /Link /Type /Annot +>> +endobj +251 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 57.02362 769.8236 0 ] /Rect [ 390.1159 345.8236 536.3139 356.6236 ] /Subtype /Link /Type /Annot +>> +endobj +252 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 267 0 R /XYZ 57.02362 769.8236 0 ] /Rect [ 57.02362 335.0236 100.3361 345.8236 ] /Subtype /Link /Type /Annot +>> +endobj +253 0 obj +<< +/Annots [ 249 0 R 250 0 R 251 0 R 252 0 R ] /Contents 420 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +254 0 obj +<< +/Contents 421 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +255 0 obj +<< +/Contents 422 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +256 0 obj +<< +/BaseFont /Courier-Oblique /Encoding /WinAnsiEncoding /Name /F10 /Subtype /Type1 /Type /Font +>> +endobj +257 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 357.5946 333.4236 405.3076 344.2236 ] /Subtype /Link /Type /Annot +>> +endobj +258 0 obj +<< +/Annots [ 257 0 R ] /Contents 423 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +259 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 242 0 R /XYZ 57.02362 598.4236 0 ] /Rect [ 366.9435 369.8236 509.0056 380.6236 ] /Subtype /Link /Type /Annot +>> +endobj +260 0 obj +<< +/Annots [ 259 0 R ] /Contents 424 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +261 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 300 0 R /XYZ 63.02362 458.5843 0 ] /Rect [ 260.5519 288.6836 265.1328 297.3236 ] /Subtype /Link /Type /Annot +>> +endobj +262 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 260 0 R /XYZ 57.02362 769.8236 0 ] /Rect [ 173.9401 229.0236 284.324 239.8236 ] /Subtype /Link /Type /Annot +>> +endobj +263 0 obj +<< +/Annots [ 261 0 R 262 0 R ] /Contents 425 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +264 0 obj +<< +/Contents 426 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +265 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/products/software-development-tools/compilers/arm-compiler-5/docs/101028/latest/1-preface) +>> /Border [ 0 0 0 ] /Rect [ 447.7723 282.2236 470.9139 293.0236 ] /Subtype /Link /Type /Annot +>> +endobj +266 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/products/software-development-tools/compilers/arm-compiler-5/docs/101028/latest/1-preface) +>> /Border [ 0 0 0 ] /Rect [ 447.7723 239.8236 470.9139 250.6236 ] /Subtype /Link /Type /Annot +>> +endobj +267 0 obj +<< +/Annots [ 265 0 R 266 0 R ] /Contents 427 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +268 0 obj +<< +/BaseFont /Courier-Bold /Encoding /WinAnsiEncoding /Name /F11 /Subtype /Type1 /Type /Font +>> +endobj +269 0 obj +<< +/Contents 428 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +270 0 obj +<< +/Contents 429 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +271 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 237 0 R /XYZ 57.02362 768.0236 0 ] /Rect [ 79.15888 224.8236 158.9265 235.6236 ] /Subtype /Link /Type /Annot +>> +endobj +272 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 231.2228 214.0236 274.799 224.8236 ] /Subtype /Link /Type /Annot +>> +endobj +273 0 obj +<< +/A << +/S /URI /Type /Action /URI (http://itanium-cxx-abi.github.io/cxx-abi/abi.html) +>> /Border [ 0 0 0 ] /Rect [ 297.4528 214.0236 336.551 224.8236 ] /Subtype /Link /Type /Annot +>> +endobj +274 0 obj +<< +/Annots [ 271 0 R 272 0 R 273 0 R ] /Contents 430 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +275 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 280 0 R /XYZ 57.02362 268.6236 0 ] /Rect [ 102.1589 740.4236 192.1765 751.2236 ] /Subtype /Link /Type /Annot +>> +endobj +276 0 obj +<< +/Annots [ 275 0 R ] /Contents 431 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +277 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 283 0 R /XYZ 57.02362 391.0236 0 ] /Rect [ 436.0915 297.4236 537.0585 308.2236 ] /Subtype /Link /Type /Annot +>> +endobj +278 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 283 0 R /XYZ 57.02362 391.0236 0 ] /Rect [ 57.02362 286.6236 291.0456 297.4236 ] /Subtype /Link /Type /Annot +>> +endobj +279 0 obj +<< +/A << +/S /URI /Type /Action /URI (http://itanium-cxx-abi.github.io/cxx-abi/abi.html) +>> /Border [ 0 0 0 ] /Rect [ 116.8552 227.0236 160.2882 237.8236 ] /Subtype /Link /Type /Annot +>> +endobj +280 0 obj +<< +/Annots [ 277 0 R 278 0 R 279 0 R ] /Contents 432 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +281 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 232 0 R /XYZ 57.02362 719.2236 0 ] /Rect [ 328.7849 703.6236 457.2395 714.4236 ] /Subtype /Link /Type /Annot +>> +endobj +282 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 276 0 R /XYZ 57.02362 569.8236 0 ] /Rect [ 243.7155 430.8236 426.2441 441.6236 ] /Subtype /Link /Type /Annot +>> +endobj +283 0 obj +<< +/Annots [ 281 0 R 282 0 R ] /Contents 433 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +284 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 448.7028 502.8236 492.279 513.6236 ] /Subtype /Link /Type /Annot +>> +endobj +285 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 253 0 R /XYZ 57.02362 493.2236 0 ] /Rect [ 475.884 462.2236 537.2426 473.0236 ] /Subtype /Link /Type /Annot +>> +endobj +286 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 253 0 R /XYZ 57.02362 493.2236 0 ] /Rect [ 57.02362 451.4236 142.884 462.2236 ] /Subtype /Link /Type /Annot +>> +endobj +287 0 obj +<< +/Annots [ 284 0 R 285 0 R 286 0 R ] /Contents 434 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +288 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/products/software-development-tools/compilers/arm-compiler-5/docs/101028/latest/1-preface) +>> /Border [ 0 0 0 ] /Rect [ 150.9074 416.0236 174.049 426.8236 ] /Subtype /Link /Type /Annot +>> +endobj +289 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 292 0 R /XYZ 57.02362 219.6236 0 ] /Rect [ 212.4265 353.2236 530.2035 364.0236 ] /Subtype /Link /Type /Annot +>> +endobj +290 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 292 0 R /XYZ 57.02362 219.6236 0 ] /Rect [ 62.02362 342.4236 159.9137 353.2236 ] /Subtype /Link /Type /Annot +>> +endobj +291 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 293 0 R /XYZ 57.02362 364.8236 0 ] /Rect [ 181.8933 342.4236 456.1706 353.2236 ] /Subtype /Link /Type /Annot +>> +endobj +292 0 obj +<< +/Annots [ 288 0 R 289 0 R 290 0 R 291 0 R ] /Contents 435 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +293 0 obj +<< +/Contents 436 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +294 0 obj +<< +/Contents 437 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +295 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 228 0 R /XYZ 240.57 446.8236 0 ] /Rect [ 63.02362 657.1843 68.7497 667.9843 ] /Subtype /Link /Type /Annot +>> +endobj +296 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 228 0 R /XYZ 164.5568 355.0236 0 ] /Rect [ 63.02362 521.5843 68.7497 532.3843 ] /Subtype /Link /Type /Annot +>> +endobj +297 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 234 0 R /XYZ 446.6472 490.6236 0 ] /Rect [ 63.02362 489.1843 68.7497 499.9843 ] /Subtype /Link /Type /Annot +>> +endobj +298 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 237 0 R /XYZ 137.2326 197.6236 0 ] /Rect [ 63.02362 467.5843 68.7497 478.3843 ] /Subtype /Link /Type /Annot +>> +endobj +299 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 263 0 R /XYZ 260.5519 296.4236 0 ] /Rect [ 63.02362 445.9843 68.7497 456.7843 ] /Subtype /Link /Type /Annot +>> +endobj +300 0 obj +<< +/Annots [ 295 0 R 296 0 R 297 0 R 298 0 R 299 0 R ] /Contents 438 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 397 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +301 0 obj +<< +/Filter [ /FlateDecode ] /Length 707 +>> +stream +xœmÕKoÚ@…á=¿ÂËV]€ç» !H¤,zQ©ºw`H©ŠAù÷õñAé¨-R2/¾$ƈo¼|Z=µûK5þÒ7ë|©vûvÛåóñÚmrõœ_öí¨NÕv¿¹ÜÞ ¿7‡æ4÷7¯_Ï—|xjwÇÑlV¿ö'Ï—îµz·^VùgóýºnÚóûÑøs·Íݾ}ùÿÙõõtú•¹½T“Ñ|^mó®ÿ›Ó§æ«ñ?·ü¹àÛë)Wix_S¹9nóùÔlr×´/y4›LæÕ,ç£Ünÿ:W§)ïyÞm~4ÝíÚIÿš÷]½x@'öZúN“z‰Vö +mlA;{¸>†N =åqEßñozãɇã÷lC/yý½*lE?þéºð×uÑ©h)Z‹¶¢½è(zZô]Ñ‹¢ï‹^]øëÂ_þDšþ~é›þaý ûœèOØ·D¾%úû¥oúö9ÑŸ°Ÿ‰þ„g”èï—¾éï—¾éOxÖ‰þ„gèOøl$úûe4ú~¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¡_àWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_á7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßàwú~§ßáwú~§ßáwú‡ï §øÞpú~§ßáwú~§ßáwú~§ßáwú~§ßáwú~§ßáúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àúþ ÿ6!n“³sîmm®]×§aƒ#gßæ·yy:žp~~ÁÆŸkendstream +endobj +302 0 obj +<< +/Filter [ /FlateDecode ] /Length 19706 /Length1 37228 +>> +stream +xœì½ xTEÖ0\uën}{盧/7!’°@èD `B$é˜a‚„°‰ÑÁ€è F™qgðuÜFDÆ—’ËwªnwÇq–wþçùioß­êÔÙÏ©SÕa„ÍGe“˜œ×mF7xrŽÜÂò‚J㋦ápøΨQQYh*B\Ük%•SÊì1c*B<Ü£ç§L›U’·t±Ü_ChðùÒ₢ؼ¬Q ? ï{•Âóé,BY&¸.-¯yH‰¾ûÏpŸðöNs¬ 7¡‚÷,/x¨’?+>ˆÐÈCp¯V”‡»G< ÷€ßÝr¥»ºææ94¡åéûʪâʾÒ_àrPŠ0oÁ+‘×)Â!L?“ߣÎTEBdžãøÏQÅÍß¡–›Jt~TSvIFr!õæMÑWóÅ¥r|!á›ÝDú?Œ0;›ˆv5Ÿ=å€<Œ(" ÉÈ€dD&ôÓþ™‘Y‘ Ù‘ù _äD~È @„‚Q +Ea(©(E¢(bP'‹:£8ÔuEñ¨J@‰( uGÉ(õ@=Q/ÔÝ…RQÔõCw£þhP•†ÒÑ@4e ÁhŠ2Ñ04e¡h$…²Ñ=h4ƒrÐX”‹Æ¡ñ(8|/ºMD“Ðý( É¨¡bT‚¦ RT†¦¢Sz¢Ft>o¢]hÞw%ðøAxÒÀíC‹Ñtxò6>Ž—qÝàÙvt‚–µè8ÙÅ#< °>íÏ +ºŠsÐ+#ûâTIä?’…Í7òŸó'Qo¾š?ÉçóÕ8…<#ä +ÛáH%ï€L‡ñG¨$_’rˆÄ[ÐGä$Ù…>…Q@/`Œ:´Í\|±Íãæp£áÉQá$Ú7¼?‰7ãS€ÝA¼Aë Ï E›ñ ë8úZDr¸y E)\ à`„þQ5þ hƒÆu…g€=Œ5™}‡’nÂö¹‚æÁÈ9h«Ø(úJQ0 +åØvü6¾$®F è¹b ž´ÓÏ +›Éçã]èK>_š °ß¡Á˜¯p£¢tŽ™¢ hê‹“e€)}ŠNJÃøD褹@5BnÒ$é†÷{Ð>Ô¬Eu‰Ñ+öþ=7ñÍuø1îoè$šWÂ_^ƒ²¢µ½*‰O8ŒâUÛ^.&³h¯ëžñê»yÝâo¹Um’ºeï5ÏRoÞÌÏ y{…½$FÞËÇD}|§—w‹ž=^ÝÛ’1È5#<3.é<†çƒØ;:è^!þËÌß«–ªÚêó¨­¸¸8•hkùa+X¬„‚\&þo`Y˜Çñ(±éô¥îÈvúÒéKI>ö{L„=¢„GÍÕ$¸ùSm­d¹öm•‡xŒø)ðÙ,ï.°¡|W¯Œô’ M:–:là{¦Ô÷б€wMÇ¢‡f¤$ñÝÓû Hí•Ù=x¯üý&9 aÃ'‰†°û»Œ°¿Ø|ɦµ4]¶]N¶§¦:Rþ©ÝQâå¿i_Û¾N¶]þà2QvhFÛ$û9}E) ‡ã(hßÛ£ìfê]0èî¤I] â IaÑ1Ìvþåå‘ίà•~%á;0ÅÿLôw&à¨H^ЧN ÜtêÙ£WJ²îÀ•õ·áàsé ÓëŸ{þI×ìôËÿáÆ?d.NÞ‰%SnÇÀ¿.ˆP_|jËžðÈy¡!Wûþ·!ÔÁrhxµrá D#Šuùˆh³,01P"¼„±Év¾ùÄé6íô¯A¯ö»”œ„£zú¤}ã§¶.7Òoòå x€öæ ìû‡° d³ˆ(l)PÄ„Ç`_Ðç/·ƒâ$Q>1ì£6ØÂ™ÝÚaìÚ;!.á/É&nØšm?·œF‰GH3ØW„3¢ˆÜÕü.7k%‹¡ûˆ†ÏðpÉf´@d£‹lt:ð%ϰ˜×‡Z¯½‰°„Sðûd18^5%&ñ>ÀÚcËI˜‰¬ÄŠS^øÅÃÏïž;w7wýáçŸxîîÝHÇSÒñ´þð¤hžÐ;34¥»®yÐ~Ýwónˆ>W`¬þh•«_bRù‡&õâͱv2?jêr¤Ó;Q¶#¦wú­Šï’xŸÐn†^Äg\j·qªaœ)2(µS$—2H»Øtú"¸Ûe{ªÝ‘j÷Oë·5]½xÉö¸x Oè›$<|¯’3|¯1çÞá{MôË’sïø×YdÇð=àæá»ì½ïÊú‚3Õöñêµ?ÕxÝ èlˆíÉ, gDœ-{õfßÀ%%~øÁü‰÷öíûîöÌ dG„„¬ÅµÝîM¾”[½566{È„j«„dQâ¢ Ô š¹Z«Óäîã°Û,f“Aö&$AJ€¨ÚšO°Úït¿~ $ønJÝIrÙØÈMF³/drø†Ëx"Ž FœµaG”;zF°ƒ÷×>ÌÁ1ƒJqtYÃ|—öôhÜ_{²´aŠvnÊÓ¥Ú;8?G{—•ÅÚ>R«à-ZÁFmßm2ÞL xäF¼ÒH´K;Å—‹¾×w†È÷ºkl$§•XÜ©3g4*¡8,„KŒK æââÓ}ì¶H%8Ž÷ ]"ΉŸ¸D°G=ê·Ì†º<*@f-û)aR\Äi3,u’Í‚˜Dea ò¨¨ë¾×F݇• G@.ªWýû5û÷¯CuáuIÉMÌû€KïãÏ<SiªÊ–¯Á+µž¨©á(¹8»îOz¦8ÃX\µëž‡ØuÇdgž‰{¥—ýnjÉûîýñDÑÄ=cƼxß….|PT3ûÁç-˜£ÂݸnÝ^q¥aünôîµÏ²|õüb—^õʦo[ ÂÍSÇçæŸÑFÙ+&Œ/¥º) o^â˜ö‡ùR,Ì.–¹zE[c:Åt²ÆFǦ£'LaO$<ðD´ø„é±NŽ£WõˆŽ1³Ób0[#Ì]-Áfkwcyí®¯ÇóßkfüëN¹Ú&o»ü7Ý…ƒš_L¾Úï"{b»¬sMøšÀ'o–Ñš™é©Fûç1)8ʧÝ;áýq……ãÆŽÝ|ðµ§¾Ö¼.·pò¸q…E¤{Có„†ð͇^Û²åÀAnÕšGÖ×/\T?ïük¯;÷Ú¡s\AýÂGÖ¬ydÁÚyßÿ¯h>÷ÚëMw X-èŠÙáÔñ™ü-öv›¹ÏšŒñD×>É(DâBPçü]PÜÉÑõp 2NByx‚2Ö1!hBx^Ò”°9èI´oàÖÉõÆÕÎz¿úÈ Ý FƒI¶›bM¸`C 1Ðhñ qú…†'Ç¢XgˆrtñéâÛÙ™˜Ü×ÐÑê3 y˜!Ëw¸spà¨ä\<ÁgëÈó¹/üþ䩦2{~òt<Ë4Û¾­Æë¸za„ -òFÓƦ•É É{“S'¢‰z@sYî{§ˆœÉ?MkQûXß–öâoï{ºáþí™ÚÜÜ7]$ðàG£\vdÀ›$"pNù+¢S¦ó ˆ¦4^œ†sS¶C°¤a¼g„ìẵœÚÑrŠë&ð-§vÑ‹]\7€¹ù¦¿4ðÒ.äø‹ô?@Ö±Ä8Ø›%ùWh^£µÍ#>âæq‹@‡ìûÑ&òsæí2t.¸åSnÑVŠ÷9øÚc@ÛWÑ"Ž‚e£˜2ØQçNÒ4ZÇ»™ÎíczÙÍå‹‚ Ãã‚"éÜPQa’ØÄ„ +Ùчƒæä]-×@!¿/×}iíÍ |7æÀü¬ÁL«+ !Ö0â  ®Rý¾HCLç9»Í‘’ ›Œì6¶ ßÜòMO=ÿ=õÔ lо»qCû„lí¤vŽ“0t +îS´jm‰V«UãÇð,<?Féþ!~Ъîr¦“žkH¨Á ‡‹!Ýö´}¹Áw8dƒ˜ „øÛˆJÞ Fï牥€ aôô¥Ã‡Yðdø´c¹ð5Þbc>»’Çò¹B®4›Ÿ-Ì® ”xÄòA|°RƒfˆÓƒªƒkB¢% ƒ/ ÙvÛÁÆ={!}ºOêÙ&?<ÍJD„—qo6g#S +F<·äþSÍ>=þ ì›qo vu×®]3ñª>åë2g®Mx¢{òoÝ·­2TûŠÑ¿ d^ ôwF•®äôQ–—¨> NsƒaµÒ ®ŽZ%®p>ç⃈o`H'ÕB|à beƒ_Ž—Æ`¸}þwébÛ%aà EaájQžæT|D»y\§®¸§~Ñ@2`Õ³Úoµ/&šónùGlÛ³¿~ó³ëǼQU},ï3lzœÄ„7­üðÛ˜˜·»'¯­{¤~ûÌÊê9Ñ^QÕ÷÷=ü<Õí"óVÐ+¼ßW(63"ÄœŽˆQj€YÅ6)(D”yó½Æord¢„îN‰Eì‹ÔCµæ‘Ç@¼Ç¨P»Q4å¡24=Š$?ÜuÂ]I/<22çâ<Ï&‹±„ ó9’b§êkˆ¨qXë©9s¬e’Ó|œlNÙ¡5àü·™Œ6ƒŒŠ÷P4ÉÅIö%¶Ð É·Á¶ÌÌ5 æÒÖ0ÿ¬˜‰a¶fÜ^2¶vÄFm„dkºL͘Ú1HkÒåCr9!£o/*I`KCüøøë8Z;­}3éíÒ ‡xá½÷^¸çéáÌ.í «U»üç¿hUÕãÝ“öoÚ´?ºó+u€ÿZæW¢ÑxW´ˆÌKL¨ÁOlñÛfk0-‹\²"Æi ó !áÁ1àh@‘.2Ws±ùb› +¹|£ãø$w’œä ÇE |_7æ<í’OÌrŽxI‰R©[ŠHöã¶.ݲe)ØõdÖ»§¬}÷=ð1´+Ÿh-Úeœƒ³ž$}>óôk¯=ýÌAnVct'í[í›qµo¾úLû3sT“ñ¶0}eèT)ÈED…®ÁÎŽØyðÈ„ƒƒ%˜¸5±¸šøL…4þu˜|Òà !ÉSüp9ÆÃ< ©ÂPa +Ù‹öŠè GáˆäpË'§°Ö’"œÉ½¾@ ©xÿåÀãåŒÇQ0èŠ ÇŠ aÝ«ÂVÄ>›`Šî⌱À“ƒ;·Fü +²þ¦KŒ¹^›ewz±¶-›§ÙVtŠ· + f ù—·è·|å¶m+Wnߦm[¸ +ÝüŸ´U žxVûî»ï´ï¶]µháêÕ ­âÞÙX[»ñÉ%µsÕ}ó_þío_ž¿Oy„]{Iû4kîïѨpÐÈW¹÷aëWŸa?ܶh÷†që¼úDiºŠó6Åj!.‹¸ˆßá•5dVÒ‹/,ÈSqåÔ)êù(M¯#Ð܃õ7 —dÒf~ÚS…ai-Ê_†\ŽbÙÈ)š¨–³Þœä,š$œD4ÂÕåïT¯vˆÏI˜CÑ|h5+Ù~¼l……s7®ñ2-W‘+Ú*mõ~üþvü>«Sá|áyÆÃ3¤Câ&HàQeCSrë +ø$ú9ÛZözfÇ•]žÜ΋û(W׿WéÚŸ“D.àK”C¿C‰ ³1y‘\Ù¯%n×÷ãr&ƒÕ`p{A/ èe±«kt˜Aä•Pù.ñYj«÷_ 2^ Ãæ >ä‹:ùÄÐÈb¢~؇EËfOÍ”Mž“«—m'áM÷Ï2ÌVf«z-Ë' + nîÿ©AZa¦DWpíñãïüê® RSMõrÁ¤7§4~4tÂøÄXY5 ¯ÚX¼07¯ç¤îyƒJ½ë­-YËrs{:ûõÐó?m“ô ð ø,TïJ0ÃÎ@g0Ù™eí‘bÝ´³·swôÎÞG¤ô Bb€©sP×°Î™Ž®]:gÆß=Âvþ[ÒúavG™|º‰-pœ>b{çr2„ - é V‹`eo&õ:qó0ˆv|\º74XG†L9`$N¾¿ÁíV8ü{¦è%™ØNÑ”7ú´È§³GÿŽË'RÐ2² ¦o×xÎc+gϪã"ú=9e÷ïÿðü”M}ëžØ6ÀUªÙ;ç“ü§^ª./þO-ø¾tÂ\íìúZãüùK–þbýúiüÀœá£´·´/¸Àºg·>¾bÛVmèˆÌïß}÷úð¬E-ªßG/=p({Ñò4W‰öË7·hžZZ>îwÁ”EsçâÌ×÷ãasçÕîi˜üÙí{í·"å¿®û³EAo¹Ò&`z4Åv…(ÈÎA΢H Šô¡ÁN™¾€,Fª§9Œ@«†¬hhÈ”›ôšáÅÓ—í ­'ùko8Òõ~Ÿj¢ƒt+¶rVÉ*[Ñx4U¢È a™‰÷Ã\.Ïe›¦àRî!<ƒ{˜Tñ3¥‡äZ¼”›oZÏm ky=É¡³ A¢¸CÚe.F›ó)—ú»¥-÷/=#XZÉžë]ñ€EàÕxгO=õ¬vw]³jÕÍÈñŸ_Ÿÿpý6íÊ–/¸c-Ö._±˜+Ñú»«¬Ü~ø¥eÏøªÇ׿ûGPÐꛄXð¨—+Èü´eRoÇO£=<˜¿}EhFI¾¶ Š¢'éÕµ¤W¬ÁáÁ GsO^¢/×y“!¶äó…7‘vÛ0ZøyÉÔ¯Ñ^Ðfã%xÌ’¯…ÉgÕþ ÕŽNºÿÔСx Ià-C˜ …½>&¸œ¨Þ´ÉœMAB 9…x›Å¦z +ýIûò}Ã<±/&‚ã0^}¼`¸ö±v\K‡qöáµZ©–­‰7f✀ã±ÿvm6_û…¶–ùd*Çå0¾‘Ž.Öó\=Z ×ó/(6HŸò&Ê’ÓMM­òJÚn†ÑYîé9Ž‘½-AÜÑ–TîZsšZÞÕraW+ü(€o@q.‡>ÿ\ÑëdQÐVc{ÐQÇÈ„–J.»eï{êÐ]-½‘G–4w +E©. Á¸ž×ËŽ§í{œõ–UòŠ0…Ø{ð)F$×—š/67µÊT;Í\Z «€«ñH‘÷o/_þmm?瘮}Ö =£MÇËñ¤'°ä®l^®]Ö¾Æ>ØñÀŽ3xÕö–ycÆâ ¸Wà Cÿþþ|í×ÚûÚï´_Çxiú2ÞÆ»|åzî-PD \¸Ë€½¬mfD¿‹p‘´/›q¦Àö}qâØ{Üÿ¼÷^K$Ðß²‰+ºÞ•rÙ¯fu„„WыNJ[6¶ÊÂfÚ¬ö€]æ$A/lÕ [‘B¨ï:d¢øæZ­„Á1¢t—‘CR½°-0 ²˜êA±Ì‹,F@¢ByHa›]æls¾¹Î¼ÅÌ`ÛDÏœðØ{'/Œ°¤Z­}{u×Ú·Zy2•Õ ¾qu–í±HvB¾Ýë9ÓeH>zÑ Br!Ê £®%l:×֚ڼΦ0ûdBá¾æ9ƒìÇu:˽¹^By7X(å¦p3¸™Â"n©P'¯áž”?çœà#ƒL%<³@: ]Å.R/¾—ÐKì)%™Òˆ‹Ï\¢Kr™&“|˜AL‘f +•¦åd¹ð¸X'Õ™6’§Ä§¤ýä—Ò;äé÷äé ò%ÿ…ðgñ;rMø^ŒŸø šø 0GPˤºó-Á$Hû[K +•í2nfËÐæ ÜoZº£V»™ÊÖâb\&f48„ôA<­Yéë%I.C’”-Í'óy^W0Ä÷¸ß7ß,?³K‡!† ú•«±K²ÄÙ1'Óá Š‚”bHW$ŽÈÀpÙÑB‘ ˆ!|øn¦¶C½5å;O·«v·&tf´¯ÒB9ŸKh2pŠ“ó•|”N\'I•:)ªÒCê©”qss¤YÊ|n¡´PYÉùñØH|p0‰Âñ$Vîlèû‘\9ÏP,O5Ìg|ŒÔã'‰/›Kãh=6ŠrwÃsñ<ÜímÞqm^“p¦Y&×®w›®ܪg)ÌïÌr…IvZg³Cž‘Ä©‚ˆ%.„ï%y|P³^JMd+“mÚÅ´*œº$WR/î.i(7D*ãJ¤ùœ$bƒèÄAâ`œ)ŽÃãÅb\&ÎãGÅz¼QÜb´1¬ÁEÛ™À±[Û¤]i™ +ØÞç?¾Þ•ÿøF8øêËζ«áÕ;P½^à ´¦@§-€¡×®†G]T +«ÞÅêîŠ}“ØóZ &çÏc¬Ý<ûà‡´¥ÚíZc²´FíSí3­ÅA8ݪݫm¦³¼æÇ0CöÆ"þ1‹|PWÄ!Ž6EæxØi8òÕ½–®¬Šç2ZáÎÎû/:—Zã7‘›îþÁ«µÇ6n|L» ¿{ƒbxC{OHlù͵KžØ~á܇Ÿ´ì ¼Ð®yxŠF»ºØmœ›Ì& 6›MéÖ0cN0Çf¶B¶ÌXæ• mc`ŒJm—@ÁÁ*yíXç£/M´1ëÀÀX¹úaD”MçîKùùþÙxý7Ú‡ßÀœe. LdLmÖ÷Æõॠ+A/¹zBžGÑN'í»& ÞÎ×£51œuSŒáÁŠ ´t“}#;ÛÎ7À/Ò‹®z>´º¨öëlta ’£‰ûÃããFÅ=—c…‚ðÛ5õÝ)´z­>qÿ¶—gnŸýÉﵵϧ~3Î¥ªÕnœóÉ{Øÿ¯e¶¾Ó»×ü…Åá]Ïî?û§¤Äßf ^ú‹Š‡Ãº~þÈÅN4Æ^»¢û$4Ìeugî‚ôÇ%ȶÓ›/2;JÖ÷ÚÀŒJfõ%ÉÞú’2„#¸pÉfp* [ †‰Ä³ò!òß´\>Þr¤ëghu £}àSâ`<;r¹üdÎnDB½e…-pÈ!Ê]QÓma-é&Ù“eÂD6ܧÎg‹¡aEŸÂ#Ó-eßñ=o¿µç¸ö§ÚGà|§_9uê +YÞ|Ÿv^ûwÁÑïÜHD¯ºbyç‰#z¤'tï²c”Nxü"¢ $@Fèÿ€¾CœÉ–©ŽOF¤ Â&¹d‘D$q2Oý±/Ä ]P'܉‹ãã„Q•ïB)8…ëÇ÷z‹CQÎà2ùLaˆ˜‡rÅ®Œ/f£0-šÅϦ‹óåõh6“!̇¸a-GNá³ø¿k9 +¾ÛŸÿ'Œ!$m§±Ïqe +A¢ñ”R $H1*\¦»)DrÁÞOÈ5Ak;B¦tRâQ6ƒ¬ï1JÈl;íÙ5r)9ùöÁ¶õÜ:D,ö~+r¢À)W)¥³ Q·?×_è¡$)YÜ!]q)yÜTîaŠ’¯Ìáæq ó„ùÊZ®^•ƒ €º™K<èžd@^QLÈDœ¼S4Ù,*!¨¢*©r”!Z‰1ªÕÒëCzò)B’ÜËj`J² Fƒñ0ŽåLB:ÜtÙ%» ƒ”&—ÅeÏAŒ7e[J¸)¤€Ÿ,ä‹ùR¾\d(RŠŒ3As¸‡ÈL¾F˜%Î’fÊ•òC¦y¦y–%\-YÊ/5ÖYÖñ[,/Zî¥–ŠˆJ)Ê€£7z~Ô–ià»ßÒ@bþ2= ?°]¿¢¯¶æå%.ÑÌ‹H1ê‡9Î+óÑ<…›–É2P*‡¤á{ӳǻ|Yšj°ƒtY® +‚³’l—ZÿsùÂ+ É^B‚1§[á_žMŠ$^pWãég5•Cgµû´¼?rNÏÖ£”ækÜœ–Å$”úŒfðŸ³X¼ÆÕÙ“cN 'Â|¨"Éé4™9‰w àM$ó&Ž7!¤Pué'p¾\O.‰KÉ æ\œKpÉ÷p÷÷ÈÅÜ#ÜjÎæ‡ƒH¸Ò Ç‘Þø.âR`ÎJ"•Ê….ˆÆuº«÷,ÞŒŸ<Ûrå8P±‘+iþfGõö>às8ËÁVº‚Yþ)Ó¢{º Ô  r8G¢B«l´¾¤°åQ_š»hoîv¬Â»\ çR€饞’ • 1ÊÈèG‚d›1ÑØ“¤ÊŒCÈ0y”q,É“KH™ì6Î$ÉóŒ[Œ~žâ<] ÃÕ|}s69zãn²·yŠpfã ÷®üªÖõþñ¢/øí¾.+¿SÜÇíD/ÁÔ• DrkeÍ‹Úo*`µùD2[W¾Ê +¢´ž±ï¸rExuß7×1øÖ‚ÁgÀ“ üÁg-’¼‹-Óµ`q5…u”í ¸&¹ld'Þ'P\Ñ@^ôl³ê[°è®¶ûŠmcb˜ +ä°'çÖÆóè{íêD¾ŽÖkÉáóá.\5Z  :þÃêè˜G©É!.ü˜6TzŒ·œÂ˵é\7ªÇ—´PÞWÛÍö¸¢à÷y­%Ð ²À~Þ÷Æ´Ýuuº¾ìâ¯pËÄhÛÝeÀ¯ —y2ó¶óžž*¤¾•geG>zÙ‘bÝ8hƒvP,ÑÅ3€Ì9otãç€OŒA‡\±áFƒíôXìê’ðƒ!¢í+üMÈŸ˜ ²1œÈ¾€)'NƒÿÕõ²Éæ«t'Û+K/WERhRXRx’š‘9 Öê +s…»TW„+2;4;,;<[ÍŽÈŽÌŽ­Œ]ZV^«ÖF,Ž\Û{%6ÌÛÕÛÉÛ!?,?<_ͨ « ¯T+#æ‡ÍŸ¯Îh¿Vv7î ‚j-¤Ft(-so|´{{ÃÆÆ‡–î>ÞrsÏ­ËߟSüÆ„ÿ½Â¥”Ì™\}ö•¸¬–»J +Þ|æõÃŽyËvÅÆ6Ó|õ ðj+èòÕ»\ä€Éj8à\am ^ˆŽ!&QÌrÒ䫬¶p‘®D¹œ´??l~XC<½ë/€*f‹yXÓ¿ÔȧÏ=ñÄsôhy¼ÏKsN ›7OÌy©Ï\âñÏ??7º¨@;¤]ƒÏ¡‚¢€ ¦ûêÈç Ã@4ÀŒ–ॼe‰y©rÀÎðo¤…;‡ õͲ5_ôîl´$ÿ×ËtZl ž¼2¸!XÀí’¾Öýöžù|äSÙ/9òröS#Gl›ØYL7,Ž}†ï¹»k× 'O^èÚuWt4dÁÜ'ŠÍ•/~`hÓùtY|ò +K#^é6’¹!v‡1#”™Xrr+¿š:ð‹–y˜8963ñk_/'Ï46öyéáã7ÑÍã¿Ôr8·cpìç&}iGQ„eø *МzðšüòEÁ¨Ò ù¿a‰¼TpîÄÂ~-à€£Ñ´"$ØÉÉN çÖŒ†b“gáÕKúâíU}.n@hehCèoC¯„ +Ð<€à,ÄK‰r¢!^q#7vsn§;Ø0ñAÊâý7­åQP‰±]âç5ï3|uêÑÉ…¿}@»ªÅqÍŸ`©‘Û¶tã 7iÂG{ôØÓ%ß…ìƒj6­{eÏfêá×€×>(Ï"ذIÞ)âZ´Î"R8 IA6[Y¾ÔÏ)Ô)u§la×lËqSs¿¦&‡¾í8™®³%³¸ìw9³ N:%$C±žTGõL¡æÅ]Û[8'jïØ»wÏë¢ï†ìÒºæDò~ÝÈמ§¼Örù Àk#ê ™}T )ÔàXâãwÀJtŠjŒ=d8`}=(´S ’MCD‡C͈cë·º:4]ÔB;ÃV^@+ºÌïÒÐå+ò·qms“»±GUú +yf[ýšmÛÖÔokÔ´ë»ï¹góè_¾’ºïá_77ÿúá}©ÜÝïž?ÿîÑóç¿Ò>Ѿ {9¾Ë뿺·p2¤Htµ»ÏäB¶ïû äEŒ¿=Àò ˆX°Xk±7šÖ)r‘Ô7fÓ~føýè4Ýÿ•´/ßÉêÐQve;ÝÀÍ|_ÔøðÃõ»Hyú›G¸­-÷q›·l~ckK­èÛ²¹¸èjCoÂà³`\º¦ØfFoð/¡Cœ€e n][½ØL ¶Ö½Û«u±¥Ö7៣AôýàÝ<§å2xFdEƒ\!FNB–7LR­ð::dzÉ&Ûq”Ë&4ØÆ _Lu´­¡3!À@v—=Ûžo¯´ëùzë™ú€Ïþrp÷²,6êŠo*Ø vþyy¸ ÆV ÒÄݹ®y­ó6e4¸caóâ ›6¶Ês?Ï)²ËÅ ]å\2h¹š›),ä– Ë«¹µÂ:ùYÎA«™œ‘(RgËÓZfWÉe*%ù¦ed1dЉuÒF²NÚEžöKïHHß‘+ä;þ +D«”´HI3UéÁ\ÌW-{¸®´= ú6—á -W[vsQ-½m²‹|­ã(5­ûÝ\f›àÝKyEu°Dßï/yx%…‚ÝD¢ ®N¢Ã`Eb¨ä4Õ†ª¤1øP MBv«,‹ÙvÙša'Š•Bš›/é«®ýú]¼Ê +’T ]>IÑÙÑ•Ñ+£àó«è¢oF@+õåÖöºÙ¦¤N]Iã2/|ñUÓ붨šùØöì5ûy²ìáý„ªìÓ›¨Êr›ŸyòW϶Ôòù{¦L~µÊ»hðA½:ÚÌ¡ÛÛÌE¯Í¼’ïü“»ÕjœÇj`hj4ºŸÎ|Ž?øñ€05Òz¡Ãzq83nÙóçŠ8ÍçIóäy†yÊ<ãÓ<ó<Ë<ë<Û<ûGCà•@{ÇÝ8¶V¯Ùý|ýêÝ»W_Áíò•¿hß`;ùèócÇ>ÿâÝ£_nÒÞÕ.i_ƒ3OŸí‹ïb±ñ øÅ­€#ý]ÁÞØØhY_'‡B!.a²]6a»xÑ]=>þ)ŒÇcZ™ãI%:¤Õ´eÜ]ÞübGËQÙÕ.—À_y¤»[ý6ÃÏ›ë4ZW¿x(”e:C çi½½ø¹¿,p{9]àŽÂ‰Þ˜ÍU·Eò>­OËžva¼h×÷óêøÙ!—÷` FRki4’¦~ƒ4Œ0ßqûô ¨_ÉöÙâCµJÏqÚTÊŸ ÏŒßôpêàbŸ„òŠÃ~ü–} P%…‚ÀÆsCŽuÆ‹EŸ{êrc”›ÊÑ:×n>·ŠÛÊÉt 1°šx â;!Z܈ãU¹'ê‰û>|’LkW™$“, ]r.ÊÅy$Ï–KP .#eü¡TÌ—§£<‡Ìá§ ³ÅÅh1^F–Ad]"®Ekñ:n#YϯÖ‰;„çĽòaù#ù¦Üß[«ÂQw¿'áIok÷]çó›sÈî LGr=G&ü•+S«×Ç*2–ÖÇþ¤zâ¯nSOÔ¿i§ûu­;wŒ:#)g±þã,³gk—¿ÿp»n +œç'D*=•L.S¬¸”{¹{…±J¶RÁU%Ê,Æ,ažPËmàÖ k”CÜ!á×ÜQò!Tà Dä‚" p29¹@âÇ Ár°Á×è4ÑÕ‹(.–Dð1B¤)Åȱ†h%ÂeJ%½ø^r*­;rCÉ`Þŧëkµò à e‘Ö©s¹lþa´8ZÊ–Çr”±ÆBT„‹¹©¤˜Ÿ*L§J†ã“Û2Mdz¸¹ä!~.Èwž8[š'=$Ï2Ì3ÌQfçšjéê±eZ‡×p«É&þI®šl]‰kM[,ÛÑv¼•ÛJžçŸvŠ;¥çå­¦-¿ä^"¯ó¯ †_Yš¸·É þ=aÓ‰`LÿÃQF•ÛøÙ§g?û´Q;wö/ßžíXK¦ÒãFYÛ<t¤/ØÑ,Ð#è,ÐåLÞNx‰žs˜Ø9»Z*vƒ‚éɨ€Êì 0éŠÄc^ã8•ôzH´ÞœdÎ$ƒ… Ã0£ËœG­•Ë#ã„\1OÊ•s yÆl³Ûüžg~¯‘žÇ[¥½æß˜?2ß4'ÒíN\«^YòEÚx×Yí vð,~Y«:‹ãpŸßòQË›¸QÊ ãü´qóe;P_fÅË]%™3Ø‘•²!«ÅnEV³ÝdFôd1ƒášì`¶éf£Á†ŒB-yÝbM€Ùz·z䚇WOËÊI‰Ðúê·ôÝÙ‡.ÉáG6¯!Óô¸<ò¬s KzÊ$ëûÁÁ\ÒåèÙ)È#ëåi£¾ýÑk žßz1+iJnºc©:úÄNÜ.SŒ²Õ@‚宲jìERå$#åWã×@yÉ“ï7æã|®„äóùÂdyžq¾ñEcp‡bõƒdjK÷Jó\î•–b>Gó¹Õ;H ý[HÛÇûÃ|,Ítõ´:bŒ!Èæ 5 +jAÆ´68wú¼c2(B´_ +UΩ«C‘´õçzR}£kªgohó¥&:©€¼À‘Úº 65 ïƒP:Q ™ÉÝØû£¸êÕ×SUa{øBi®Ìû÷ÿ~ç´Çï¾»îß÷ôXî½î ¹½±r͇߬«©«®¿òáêºñ]{êñÀàÇ7]{l<¥×Bñ1暎W1z‰ý’Oÿ; ôät[Èž]bè5$#ýï2´ñ¢ÖÕ+<&ØÏd•ƒÎ@+/¨î F;£[wÚ_Š rZ1¤`AÎ(‚ÂÁTù˜Öôƒ×ÅÍ*I¼Î™d`‡)·åþ‹Q»ç(è¼ñ0¤wnåeH‡ºÞƒWŒàvO»bð€ïŸ›öxÿþO{îûoäÖ]ÛôxpàãO]{|\Ýê¯ÔW×Õ¬ûæÃ5lO5>%œ#ÝQ(ŠrÙ°-Ø„|øMÁ>›LaÈf£[ël§›O_²Ö%ÅÊž±è§µpìïG?€¡p®ì傊'Œ‚d}rÒøÝ“éÝjE-ONÊÝIºï5¨/Ï¡ÿˆ1ûFeôc—Ylq“÷”qÆû­ýþŠÂu1üéúû©Þów4gYò ô'Àv< +úIåZ(Bí»®ßcÉóü•«¶Óø“¨DF8˜| ÚÄЏåh߈S`¤"¸¿O|¥<ß"à¡Èô<Û,ø£]ÒFô x7ZÀ¥ wH8Ú ÇVèw·§ÏfîƒyŽÛ‡´¯…ãc8Ö± Ž"8(œ:8vÀ±ŽÐö +›½cÓƒ€VNµÂ,dæ¢cÂZT-ÆÁÙ‚ŽñÑ11îytŒ»7× +àùtxþ9´i†sªæOég¡žù¢ZþÂÍëÂ9´Â”¾Dƒ„9ènxÖ çû(-g8e㣛—€®]üçhô=È— áü  =Ƚéµà@¹Tô&—zóÿŒ~-GésþSÖþ mG†Á}Wä&Q¨7¼ÛÃ~-G¹pîK¯ù4^ðLjۇyz¦ãƒÌ"Ñ»x^Š÷ãøÏœÂ©\ÌËF“Jøäû…óˆ_€ßl¿?ú+þIþüöÂÿ`@vÀ¦€/{nüsÐÈ Gƒö] . ^üEHtÈŒ#¡~¡#Cµ°~a¿·©‘êň÷"“"7GÐõM#9¨+*E&¶û{õ8¼“óƒ3ý[_A0éòêðzœì¹Æ¯]ð\sP¾÷\ƒW†žú5×}<×2qùžk)Ü"ϵŒì kýÚˆB ò\›Oužà¹¶ }'{®Áõ}ÑsmG|ßw`DÌ [^cä‡{®9$ão<מkžkùq‘žkpƒ=×"òåÊ=×2Šä÷\Q®ÉsmŽéCÂ<×TÚçšçÚ†üú®ó\Û‘Ü÷54¹Q%š…ªPû;o5HEQ!Šƒs2J‚O +\M†*J‡65¨Ž*TŒ +P9Ї§™¨Ú'ÀUšn…UÍîŠá\ }fÀw´T~¨½ZGÍ‘fÀXô¯–U@kŠGôùÇFWS¡_.š- +¡mƒVÌz0ŠT€Rß•Ðf2À-ƒv*ôwÃè줛ݕ³ªÊ¦”Ö¨ ãÔ䤤uò,5½¬¦º¦ª¸ <^ͬ(LPÓ¦MSGÓVÕêèâêâªÅE ʺö¢]s +f”OuWLQÓ JïÐqPñÔ‚ÜéjaiAÅ”âjµ ªX-«P+§OžVV¨¹Ë Ê*³Ž$ŽaVÃc½ó˜‚ +¸IbÜôù¥»Ýü´.?¥M.ãv5ðÈÍ8˜ ¨´¦¦²Obb1=¡Ú=½ª°¸Ä]5¥8¡¢^n‡WG¼zúCk ï¨Þ3Ý- r£™Ð–jê¿Fÿ(¤!ðf´)e=Ëà]%£«†é:åZëA­ƒBq 'o¥£Í¾¦w°¯;QC÷:ÝŽv] +àª=×~hé +êöO|”Ÿä=þõ>ëöòn£¹ Þ(쪆=¡ZXÎxýÌ:s<ØÑ¿ëfž¡Mí}Q~è * }Ǫ;´õÚJÇÚû€öýTFsÃ\a¾¹£®éÜÐcIÁÈÓÍ¢ ê‘}9;·ùŸ"‹‰hd-ðP”ÐS?Ö—òd–'¶è£Sž—0‹<š4éiUëSÊÓ¢v2o¯uÞZÀ"bóÓØÒJQÔʫ¢7¦tˆ«úH^ZÀ´G×]ï·ò§úïÒäÅRñPЦaLF?ƒŽãÜÊÛáï‘÷4Ö¯ìÞ\i•Nó³̯´Áõ>©nÕH¯½Ü=Š=~®˜Qái&£ªˆõ¼M<Œl¥ûÖ +¼óFÛÈvZ¦ÛLÖ-ñe2³ww;\§{ìÀ«'3àmÙm8VŒb|®ðXr%|ôèUÀQnk)¥Ìëì\íÁ±˜iÒôÄëën绋X$¨`roϯÛqUiǹö2ü¹¶Zͼ¦7V·Y›×’hæ0­5÷¨òôè±’iôð=Å#1=R­RZ½ê¿ÓSÝ™ªÉ©ñÄÃ’VN ElœQh$ÜÑqFÁ]yähö.ž©Ç†7¹pGÿû &—4ö†¾dÖ8®)ÄQh,ƒ¥Ã ßv<¡°UvOï†Cû‘‹öÍ@ãÙm `6 +®)ìð4 Ξv´Ç@x2îéõD³P}<úwàs˜íÐ~ÓxÞ6jG¬2Ùˆ^ÌFÀÝh€?Ôó–þÍùLâÏò#z=Òƒ§Î¹Ñ :å…LaŒ²Ø}:ÎÙÐn ãg£YÇv$£a0¼×iÉ`è’Ð1Èþ¶}kAÿê}ã)ÇÓ2žÉ‘Ò3ˆõ§£g­tÌFy¤L¯Û $xx©ãAùŸÛ:òF|TFû»úT6iß ×«;CŠ·Â¸1–Ñ—Æø0ŠÎÚQ.R~fµjÜèvRÈøEåF1ÄFJcs[J¼ÐÚKçvÚ¡´Ž0„Ñ—Á8•ÅZ>f@ûÌÖ'º>f2Zzx­ÃÔõ^׉¬vÜÈh¤’½FÍðèTã]G*¨œÆ1üÛ¨Ð%æùØŽgmÒé‘®Ÿ6rÎm¸2ŽÙbk•Æd=¦ÕF3ûáÁ|l«†µù€±ýÕŠYGþzíÈÛî§ø–wìŽÄô)˃á˜Vnè-”«û® ˆk…lžSÓê·;FîöYc[6Ú>ïŒoçkÛgºÂÚ–ßÒ®í©>[ÒcVÛ\§}îv»¶wv¬çòÞ¬·-ûÐ}·>'jŸõ±ü\Ï«[³7ËÝ­™ÉLö¶-¦Wzj'îó<:r‹ýñ­cycQ,=¯,`Ù­ú6ܼs„R~03¬dñ^e&»®ñd&”¾éž¶ôùì[fÃÞúÏe ÞV^Zn—9´ç“w¥g.UÆ8LóÉÜ*ä—µñ„r@¯»•ß"õ6í£Ðú [« +”SÚa^Äx­ ½†GÇT˜¿òÖ¸þï«NÿêšõS=HéPº5óú÷Õƒ”ÛÖƒÔÿp=HùIõ Ž™|a;œÚjÞ–?­‚z» +‹òVWRPWRþÿºR»ºR[…áÿ›u%¥C„ý¿«+)·™­ý7Ô•”ÛÖ•Ú(úÏÔ•”©ügêJ +úGëJm«NÿʺR›½u¬+Ý)úÞ¹º¤ÏÏõLâ¿­º¤ ŽÕ¥ÛW7þ3Õ%åG¸«¶ãàw•Ia:öÃlæ?_eRþ‹«LÊ-U¦¶¹î²Ê¤üÝ*“ú«2)ÿ@•Iý·U™ƃ\€:Œa«s; ÞÿçjGÊmeþU;R~P;RÿÏjGÊkGm5 íHùjG?÷ß[;òzÖ;G”V|”ŸQñi_¥ùWV|”ªâóÃ9ÛÏ«ø(í*>?VwøWThj~ß…Ú* +‡Þ% 4˜mТ[Õèf·Öýqjçêâburñ4÷̸õ'llKP‡L›UYZ­–•Wº«jŠ‹Ô’*w¹šVU<ó Ì;ÛH7]ßH×~Ei=·¸ª@ÕQkݧtûÑÊ÷íýä-ê-#—U+jMUAQqyAÕª»äV(Š’]\U^VÍ6Í•U«¥ÅUÅ0Ö”ª‚ + =h² p¬jJq¼ZãV *f©•ÅUÕÐÁ=¹8V,(P iZÖ”{ùTXè.¯„æ´AM)@.WT÷"K"ãX‘ZP]í.,+€ñ”"wáôò⊚‚ŠOIÙ4Rg +‘uPǸKjfû#ã&UÅ•Uî¢é…Å LQV6yzM1ÅAéÐ!Ä\8mzÅdfYM©{z S^戎P¥³ÀN¯†ö”œxµ¼˜R­0©.o7F<3Ñ]¥Vƒ u ê!ÿ–¡)r¶’2ºFÑYÇšY +ŠõƒT %Ó«*`ÀbֱȭV»ãÕêé“§ÖÐ'”¾÷4P6JP¡»¢¨ŒÒQÝGQr\Ád÷ŒbF®E V%¨p×€ªõ§T*•m ¿S«K ¦MS&{¸h€•t Ó]zQ¥–»«ŠoK¶Z3«²¸¤JÐ‘êø¶¼`X t/*+)£ŠV0­T.hAQ£\g5Ђ*Àkú´‚*…TT\]6¥‚¡1E·UèD5´ €TÓ^|ªo‰‚T`ư‚i·àéãÅ£  W1m–ZÖNÍJNU1ý߈³¶ô¢š2’ÊÅkÅ sÅU¬ÓLwUQµÙj‡‘tlï %’šm$cH&Ëc/“‹Á’(Ôé Ê“î²VÄŠª‹Q *+Á¼ +&O+¦/tÚ2½PÚ„RZP£–TÄâŠ<¡ZצÝEêôŠ"Âm¨* 9“jµ{µj&6*¤uõ`+Þ†•…LÂÀ+Ü +UÕL©:  P,žVB‘š¡52G3jpθ´Ñjæ5{ô¨ÜÌAƒÔÈ´1p¯ŽËÌ:jlŽ +-F§ÌÉSG VÓFæ©Ã3GŠW3ÆgÎ3F5ZÍ‘•™Ï2GÌ;(sä5ú•£feŽÈÌ 9£XW¨ÌŒ1؈ŒÑ‡ÂmZzfVfN^¼283g$ÀäF«ijvÚèœÌc³ÒF«ÙcGg“0Ø‘™#†Q2Fdhà¨ì¼Ñ™C†æÄC§x¯äŒN”1"môðx€’G«¬I` 0ÔŒ\ÚyÌд¬,5=3gLÎ茴´-åΑ£Fd(ƒG9(-'sÔH5=HIKÏÊÐqRf¥eŽˆW¥HBÉñB›éä´±C¡†dŒÌ–¯ŽÉΘI/€™£3æ°–À{àDCw਑c2î  wˆxeÜÐ 6ÿ d˜1òG¹NΨÑ9­¨ŒË“¯¦ÎC%2xô(@—ÊsÔ`¦cŸTx#=øRÑg?ÔhE{{”‘–ÇP4àÒ¡-hWÆC…Å•5T·=Æ­»FæFußÏ´Vw ÂC*ÀpõgìÂX‹:ºwk Ø4Çë®—¹ÐnˆDºë-šQ °šºw•â¦ÎdfY5³tån=æ©ÕÓ`0èE­ˆµ_Y0 ºU·¢ÙÁ o0¬¬*ƒ.3«ÊjÀ™¨ÓáiUÙlO®ò„)FÚF¥Í9èøWWWB”*›QšF–-5 +Ë|ÉR#©…µEd pjI Yl'hdÑB»°H# ídFækdžF\71w®ð ÌK."srœÂœ2[#³4ò…Ì4‘ +™®‘šë¤ú:©ºN¼N*5âÖH…F¦E42Õž.LCÊ4R:—L›k¤H#…™¬‘‚>$ÿ:™d"5r¯F&h$o¼"ä]'ã2ÎïÿUK&[i„A¶ù¬(ùùÓ¿Mƒ¢‚SÚÄÀG Κ¼@¿F–YºõYrNÞŒE%ÿd›“Úݺ·NÕ½§Jò­ÁWåÕo~íòRà9pò<ÅSÌc?/ʃá^¹»ur§Ü:¾(Ï ”~ÏI?O¯l¥ç¸±\+Wï\¾s¡œgêr>¤û‹³§Ê‰r܉ä8¦Ó¥Ñ>²Ò>ýrd9T”ýV,ûCZM'­˜æž‘¦cϰ[aÇÒØ6ÒP¶ [›F¶,›†zV6õ,ë jÕDj)ÕµHª k«+‰¬ž±’°œYI Ÿ•%e1dÁû\ˆ˜O™Rñ*)eˬOpV™2Ý¥äAI™J™ôIM*E?T,QPb%¯D^)ÞëD÷0å“bsE±JΫsEŒòÑ‘Uƽl\‹ù2êÉQÿ|%ãq¦NàQ‚ŸAúã-¨ý5ò¯øk•ÿYyLendstream +endobj +303 0 obj +<< +/Ascent 759.7656 /CapHeight 759.7656 /Descent -240.2344 /Flags 4 /FontBBox [ -1020.508 -462.8906 1793.457 1232.422 ] /FontFile2 302 0 R + /FontName /AAAAAA+DejaVuSans /ItalicAngle 0 /MissingWidth 600.0977 /StemV 87 /Type /FontDescriptor +>> +endobj +304 0 obj +<< +/BaseFont /AAAAAA+DejaVuSans /FirstChar 0 /FontDescriptor 303 0 R /LastChar 127 /Name /F2+0 /Subtype /TrueType + /ToUnicode 301 0 R /Type /Font /Widths [ 600.0977 1000 1000 518.0664 518.0664 500 317.8711 589.8438 1000 500 + 837.8906 837.8906 317.8711 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 317.8711 400.8789 459.9609 837.8906 636.2305 950.1953 779.7852 274.9023 + 390.1367 390.1367 500 837.8906 317.8711 360.8398 317.8711 336.9141 636.2305 636.2305 + 636.2305 636.2305 636.2305 636.2305 636.2305 636.2305 636.2305 636.2305 336.9141 336.9141 + 837.8906 837.8906 837.8906 530.7617 1000 684.082 686.0352 698.2422 770.0195 631.8359 + 575.1953 774.9023 751.9531 294.9219 294.9219 655.7617 557.1289 862.793 748.0469 787.1094 + 603.0273 787.1094 694.8242 634.7656 610.8398 731.9336 684.082 988.7695 685.0586 610.8398 + 685.0586 390.1367 336.9141 390.1367 837.8906 500 500 612.793 634.7656 549.8047 + 634.7656 615.2344 352.0508 634.7656 633.7891 277.832 277.832 579.1016 277.832 974.1211 + 633.7891 611.8164 634.7656 634.7656 411.1328 520.9961 392.0898 633.7891 591.7969 817.8711 + 591.7969 591.7969 524.9023 636.2305 336.9141 636.2305 837.8906 600.0977 ] +>> +endobj +305 0 obj +<< +/Filter [ /FlateDecode ] /Length 677 +>> +stream +xœ}ÕËjÛP…ṟBÖRœã}KÁri ƒ^hJçŽ}’ºÄ²íAÞ¾{y…ôPJöþ…tÐ7ÛÓ«ÛëÛ~sè¦_ÇÝꮺ‡M¿ë~wWµ»¯›~RfÝz³:¼ÜþWÛå0™æá»çý¡noû‡Ýd>ï¦ßòáþ0>wo.N×»ëúkùãx·ì÷ï/wOë·“é—q]ÇMÿøŸWîŽÃðT·µ?tg“Å¢[ׇüاåðy¹­Ýôßçþ¼õýy¨Ýìt_ˆ^íÖu?,Wu\öu2?;[tó¸YLj¿þëY™óÌýÃêçr|y÷,¯EvizÖ´4­M[ÓÞt4}Þô‡¦/š¾lúªéë¦?6}ó§Kã/¿4þÒøKã/¿4þÒøKã/¿4þÒøKã/¿4þÒøgôÏNMŽlúsdÓŸ#›þÙôçȦ?G6ý9²éÏ‘MŽlúsdÓŸ#›þÙôçȦ?G6ý9&s¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~§ßáwú~§ßáwú~§ßáwú~§ßáwú~§ßáwú~§ßáwú~§ßáwú~§ßáwúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àú_6ÄË&À®ÀÚ{]D«ã8æŽ:íÆÓâÁÊÙôõu}»§ðû "‘£©endstream +endobj +306 0 obj +<< +/Filter [ /FlateDecode ] /Length 18175 /Length1 35128 +>> +stream +xœí½ \U8~Μ™¹3sßÀ½pyàQDÑð‘E$Äg>@¸ +\â¡™š¶½feššå#33×HÝ2S³¢¶‡nµi­™™›nm‘µýìE0þ¾ç̽<ÌÚ¶ÚÇÿÿù1ÎÌ™™s¾çûþ~ÏwD!dFËAã'¤¤M›¼æ^¸óì“J«KjåÉJ(BxìÎÒù êåO÷“âò`/*¯S}%7ï „øÐÇœª…å«~¿ín„x>4©Â[RÖknÞP„2¯†ç+à†åqÛpÝ×=*ª®þî@j\øËª|¥%—Ý7¡‘Eðüùê’«kŇže…ÁµZSRí­‹»ÿc¸¾ ¡aéµ¾ú† 'Ð4„®«¥Ïkë¼µ‹K6×w!dxa~· €Oa-Ì­ŸÉ;¨œ  +Œ!"ÏqüG¨æÂŸQû¥Gq"TSAyvR‘zá‚¢…àu†jüa1Â.\@úF˜MHÄ +k-C¿ö#¤À¾"2 ÉHAF˜ÁŒ,ÈŠlÈŽ‚P0 +AäD¡( ¹P8Š@‘( +E£À6Å¡xÔ¹QO”€z¡DÔõAI¨/JF)(õCi¨?€ÒÑ@4]†2Ð`4 EÃÐåh8ò L4DY(å Qh4ÊEcÐX”‡Æ¡|4 +P!š€ŠÐD4 MFSÐTàü•h:šf¢YŒ€rÜ—£ýèïІ6£V4q¨îÒó\„Zàùlèy#.‚s5¿qð|)@p¸?𮂖›ß‚÷£}è,Œ¾/F ÓhoÆ) +ëkáü…Áe )|5?ŒßÅ_Çï‚|9j‚c÷&¿ž_Ä¿Î/BS(f8î´Áñh·gaÎ⎠çþÃñ:Bs1Ç(ÒW8Ûhš œ99á˜bˆå˹oQ ¾žÛÊ}‹ã1[ŽnÎ$Gøbþü­ð¸ƒ9ÒŸÄpœN{Çð:Àâ´XŽB?º-‚yZ¸¸½@ãAtè‚Ù¹éÜ"n:‰wâ}€1B7â|±a6Ö‰ëø)èå z“;ü(`ü¸Ý.öC_ó"ú‚äáb~år Ï)ÄƈAh5c¸(AdZzŠÐ+ Ïéô’Ä(´šO î·$À7¼á2Èl´žm+ñ^´íEõ@žOD'FIª­‰sç–5y®˜¢¾<5¶oÒE—ªÍ 6¡‚&óBuï… Søaj“ÙDÜRïŽ?ýcO÷M[0EÝ‹{egùÁfgÁÍ S I¯à6ÜÏÎbÏè¬M‚þå7©¥êm¶ÛâßfóîK5][Í— [ÀŽ (f?âqO`ƒˆ{>‰%áVŽG)ÍG[ú!ÛÑ–£-©ÁöX»;Ö[Σ¶zÑvV[m°|ûe˜ÈtÇÀáfÐ{í3€*LwÎí@U¢püO ëéçð=‘¡†`«ñžh‡LÐ +»9&:*2"<,Ôd·Y-f“"Kº$ÃÅÕvôp(Å}è™´£Ca6ÿ1“Øàþ$ÖËöø`¶§Ç²ÃM²¢K%'K°C{2GjMÅ'‹µo‹OkÇ£s´³¸°hßæ»jKñuÚÒcÚ¹w´ëðRº¿ƒƒŽáëÚ·kç@IÑFí-ACÀgößX煮îˆ0{hHcT>L”“â9aµ¸*)tuتˆ•A!I+!fôÂQ)H°ˆ½zØe‚ }"‚ JCÕ4ÛÑæ¶£¡CÛB‡mk¶eÀ? °¹nŸo±}uΞ[Ph†þ((##Ç“dœ`Æ¡ö„dœ>`àpÜßWz74;BD±[àì ¶_z’ˆÍxÉ'¾úOÆ~þÊWâÍe/{៪9ÿÍæQó¿˜<¹@{ ÷’±8d8‰½÷<ºûù£¥¸­wŠ {=óÔÞ-døìô!#µ'´OðˆY#AÓF\øÐðø##D˜ˆ"ýQ©'=ÂîrÃÂ"ÜèSòqk{=.>`Z´&2|ýW¿>2‘²,YûI1rÉê`{¯¹È>÷5¥›rÂv&íüÐ3ìŽí\*X—Á&|F÷©q8Nû§  +Òœ@v|\O`¸¾þ±]®C»ôûÊ7ztÞ¸QûÞ~{ß¾wÞiwŸ$¿ÿýò}ÇŽí£ûèüüÑ£Çås‹êê-ª¯[´í/œ8q`ÿñ¶·DóñÞ}÷ÀãÛ×Õ/YR_·X×ëE>·ý=Q*è™jÂf£U–«$KI}“Sø>©VŒQ/œ˜`…xíNM0ÏûÊ!ˆ¾;ªï&K솨Ã×[ÖðrBoÔGÎS{D…ôîaŽ +å± ‡ö6ÛMýlm-ÍG¶Ø^:Ç”AW{Ó˜—Î|ý7`‘ Çt%4Cç–å3hvœ€wmMÛ%%`4Ãsƒˆ¬ÄÊ˲U±åcªAÄà]r”eŒLu£%Åèî9\nôÆ*ccSƦNÃÓ¸‰†iÊ4ãÄ^Ó“§§NI›•V†*¹9†bC¥Ri,ë¹,-I6* .cxBoc†1=25*5:=ÆÃ1æ&äöšŒ'sÓW&Ì!UFÇ ¼Ÿû;zô÷ÚÎcÄ×u%€A±>®¡ýî”vRç)¬SØ\ÙŸBÛ8:®4)z÷7iˆ… –еÀ…\ Ó±à§8Ð0a’ÒÌÄyþp*$ŸñœåýöœŽ}WMýÌì  +)~?ï –wXÉÇzëš0ä4…ŠÎ 0˜‰)èêDS±­'˜ÝF ÛP¬ŠììHvV.^w÷÷/p±¹9˧½}Íï´k° '^û*Ž€”"ŸqmVå²qyxtŸ¾-o]óÖŒÆ[/|ÈŸ{¡rÛ$‡»ˆ;8ÜÓÃMíöŒí\3CŠõÜÛT¨]2¬Ç@ šÆPÕ9ÍÉõÂO¹×‹çÏÚV°ãmĺ+Ïb—ö’ö­vR{7âì9Ïs§¯÷ÿpG´–¾}žÝ߯ŸvþøÚ)|+®Äuø•ê&Øß9ੈ²=¡‚‹#ï"„s ÀNi8ÁÈ`{OÏÁÁûuËÆvÆ3ðxK€?AÌ—Å£4ÊÓ+Ì”°‰;¢QS_°òè5©aÙDz„[{‡;zÊá $ÜÚ3¶G*ø8Ê¢ @Šx¾å Í|2ºsÌMÓ‡±º­êæ¡ö€„"8ÐØÉ½RÕÐP5¯®N[|Ëm8ÄlÅá·ß²æ~p‰ïûÞ¹ÿËÒéÓfÏž6½”[?¿¦¦±±Æ×¸4qûÒ/½xpéöÄÞîzÿÃß¿ëž8µ¸xêÔYÅTös6 È>L—½ÁµCuî*~èâÛzçw¤ÛŠâÂÍLöÍmmgtBš=H×ÃA ÅA2w\OÀ=2}[áŽCö¬uÓÎjá lÀ=°G»MÛWy/õ–ƒR”—Çâ> ï´4l|÷8N›¯­ÑîÔ¦Æp箿þw7Üð»ë¯gö|ù|1[ãEz,Ü6´›ß& +–ŸÁ°Ä‹¥ÕóèvWj÷Ñ/Ö–h[! 18×A¬¥pèúÓí – ÖÒ€»[!<[‚+ ‹ °…(¥G^?Èo!úR¸þÌ[t¸4/&œàÿºã± 4MÈÄ=ñš±¨ÙIˆÌ9p:N´ %Í­šÀ‘#ÚíÉVüz ~]Çñ8NN~ZÍèîIñ^0$–Þ°Å6K +l;.pR«FN|úÞg?ÄG ?ø?à=f @:ð NÇ!ƒ¼HŽ´jýZ´~­àÅ(.ËÁæbÀ¢P,íIˆ +8^tYÑѶ»­«L›‚—G߮ЂŒÃ]HæœÖÄEã8¶Ž?J£?€h¨Ûø ‚ÍÃbÙ²°[ÌJ£v`Å@ ÀjîEí¾s=éÅ“®Ø_¾ðåç1ÊŸéÉê­ËÛ¯péìÁ¦'çŒ9îÝëÏξjÆåcû>G«ºY[oð Á~Ç¡*Ï`“• HÍ×ßæ’#È^kÿTy¯Ë‘º×úÔ {O\‘ß@^¸fêŽzE'†÷Ê êÕ;qÀ°|š.@\M°iC_ +b‘Çn5Ÿ³ÏN³û—;ô˜Š»˜/¦ÖÑÓ¿HHﯯvzö 4³ì:ÔÉS# +ùø¸ zÞ=°ð‚®“ÁœâÉðÊiSçΛ6¥¯‹¾qêŽwþòØÔ£ß\|çà!³´·6vïÃó¼e˜¬¼¶mê¼%Úñ5Ok{—-»é–k¯Åã÷|€k§íÓÞIà"ݹ⚅˗kÓFÿîå—[ F]ß>6ø•ÊϽæ†aCfk¯üa•ö}Ùì93 6—̹~Éœ{Rã%‹oylÓì³×jÿÐŽR¾*‰ àƒ ÐÒ<% +†(Q”(I@h›‹â †]Š"ó¯ ºïr‡¦GË”e’R®,EKñRi©Ü Ü¦lPž…íuØN)¶ )RŽ1¹¥Þ²jÊås„QÒhy +™ÊO&‹¤’/æˆÅ¦´_Ã7 +‹¤ùVþ&á&éVy-¿ZX%­“Ÿ”ž–_C/â¹× ÏKGäãèmü6wÜpLzWNa‰‰Ål#|NûÖYÚ.¿Æ%jKڷᵇ±MûB8ÖÚ‡ss…Ô® Ú9É­À; V4**ð¸ƒÐÓÖƒâÓáËå§ÃDXLE›¬rXo Îh“Óa38]$üo-­ÙEÝœ`ËHõÈ(ΗWÇãØŸVÙƒ =ëJ„eˆž[]9sæ•ÇþÚÐØÐøWnôâ[´÷´·Û¯ãFàA8´œ¬,Èw…ÖÜ^?»´¤D[ȹz<¿ü/G…cû_¯^Ë|J9Äéà \hˆ'Üd“‘“˜! ÜIl;åõdMxp_û„Û`5HòµfÛ‹:Ö©{R"fEpx†›­Ÿõä@_9SK f LŸû·%ÚíZÞƒ—ümî¼7êÿÔÒò§ú7æº oÂ^\Ž7]6H{-7Kûöã´o³r™¾ŠŒ¯¡(Ùã@‡äåøS✠+úÚ’‘S&úʼO€{©» +\`»ßX1°(T_™ŠÀ1nØm-ß}ûYûWx5.ÂãT–—W^­5Á6—ßÕvÕ'§ÞÿÇ—4xµoyTûÆÛPBùxð§#Jõ„ð‡ÄƒÜ!´\:¤`A‰"bb¢lnÈ/õI›¹À\kD-ºíç­íuÜœöµÜ–ïO@wRûöít§ÏñÌ!£>{Çf(ú,ýaàÆZc7ðÀµ¥Ðí7ûeÛÊüüXÛî ˆ7üéÈGFG¸ú™ú£”à¾bŸh˜ R|'›Ùr6¿¨©´wÍŠöºÁ±ùeÍw—5M¤…Ö»´Wfß· +Û.ùfñß¾ûâäÐ9‰ŸrS|99Lè•øA*ôQ9Ú…Ï?Ó4« ÇÑ ŒÂ׌ߗ{‚¹CÒA-WDà„£v³Ä`èù¡ÀƒÖ’¬‚U´¬R¹x¿Á¬\Ä}|w­±¶¿LYÔþG.lx!0 ùç䆱5qÒSè9ŽÎëç±MÖ¢±M¶¢+a™‡ç²©ú‚?iÁ”{tåÏXø ¸©IøïÚ½Ú¶óÚ6m¥p¬í4‰iíÃgµ îï÷wµ-öDÒ7p!qS⟓¸~¼¡ŸîGXaB§:£ËZ˜g´hmv€""£K¼ ÂcÄIxªX`œƒ+Äbã>ü¤hqqá†Ë¹þ†\ Þc˜ÈM3”s•#=fït(Ú;¹\­¶Ÿf#ƨÍÿ>|’Xî¯'¥y‚ÅAh§i}К0¹¯uéëèsQ=ÉcD®Ð`›+Å5Ü%€o÷;Zu$ÚIyÓóÏ7=ñüóOà +¼F«€ÇZm^Ë×ÚZ>ÕÚ0ÿi æq¨V¦­ÒVkex=ž‹çáõXÃb¸‚‚Q?Ó´Ó`܉–ËÁf–l}y£†¢;ž@´aE¦]È)é4ó`«±‘g?·Gá~° øP£owŽÕ^sM-˜À'Ÿ¶··òµYÕeeU:?´cŒVf{ÂÍÎȶӰ­±DZlvõU÷åûD‚ËUuï Æœ$™P”Ëd‹J@)Q“LÌå¦bóÓð‚fƒiªqªyjÈ”°¹ÆJóÕ& ä$Äw0-„¾?~~®ˆÖVhåx-ž¬YõΫ¸¯v·öaÓó÷c#ð\EY̽»M»wºÖ$òÀÜ/.è¼õÇlÄbv$ºÙ“ÎEº¢œ¡Î°¨ÐP§ÛåT‚ÑNYÜiZª8ƒÃˆ-Ò%"Þ ¾=Ô&œF¥;+ 24Ãož”Ïì T—…¿þ.!,’ÚfLDX„+<<""¿K ƒ¶`‡Ú,ƒ…C–52Z$9• ÿ‚:¼IÚyêòSŸô×oÖó'}Ygõ[Ä™…·Þ¶¢íó°¾zF{Ü×uOoØð4YÚvö‚ö*ˆ‡é4±õ’ˆ®÷$Z3æ¢0'ÐâD$â(èà¦X «t,°åùžÒþe8àKÇìIBœS¹&Ž÷CBq ãC…I•F¡Qx4Íg cÄÉx +Y!ÙAÇ*øz|¾_ßþŽ–¡rŸßÚG³Ã’VÒx‰«=cÄ(ˆ— ñRhT{y ànÈN(+„p\áLÐè0¹H*DF%“ÖyúWF2SùÉET†WŽm2Òƒ©3±9¯i—Ž®—¢ü0Q@{ˆ`lŠ[Qa ¡v°0\è¯¤ÂæaÛLn¦P¢ìåžPš` 8¼, FÑääÂH(ïRˆn 7%p½HßK€%£)Å2òƒ4>MHS ©Rš<ènÉ%9üh![c¤ë¿iÜ42‘Ÿ(Š…†Biš<Ñ8ÃäC>ìãjH_aðJò<¥ÆXiò™ÒÕò|ãÕ¦[ 7J·šžâö‘'ù½ÂÒAÓØ€d˜pà0 _ÁázØ9\£mÔ²`ý}^ËY½À£;ÄIß÷wë2KâX.ù…'›¢Šr ‹r#2ÊQ’ŒèI‘!ØKQƒzHè—YQ¹aŽxÅðYË`ÄÁ07+•”½ÓæšßkîÈ…ü²ê²4ï&²NQM‘ˆ•ˆ2’cˆMN!=eUJú˳H¼‚,“Ÿ%M²]æDÈBe%‚ áC¤¥‚u3ßCtK}@®é|º4X)0{Ñ\Ñg~šûÿiŸlÓ™¦ÿ#mœ¨ù´‡´­Z5´,øÈLrð½\k»ˆ5ãZ¹Z >Myv¡|Í)Vyzu·KÂIH¢ú,1»9?Xp¢Á9 ÅfÊ+c‹Á~ž¥ºA½R9WÀƒÕ*°@…(Nܸ7IǃIª2^ñ)T¹ˆ?9¶‡à‡!µ+Â[ÛÿÈ2ã.¤mcûí\£.{úžã*–ÓAF@¨½J {Î-÷$‡Èi!ÜŠ +¸”Ñ*‚K v¬»¯à‰ˆQrpÉM¥$CnÌ!£¥ñÆId–Ñg´ês{l:Ž­ç?j;E.ký”ĶAJù÷¶AÚÉ¿“Wtœè»æ(1„~-ä±ñÍâ®=kTbG6ú9Îá4öNÓ_À¤ïH›¸jí#ìj_)†œnÝxú¢wÓww¾{pÌYÚ¿<ÉYŠ+¿«Öç½½‡Ñ¿‚9·ÀÜIÈ3C9ÌŠlZL_ÈÂÎ}Û¾»´`ö/O ÅtÞ×É6á,ó×a…ð-7,€K>¬O –q<üÃägû‡V®Íù’ƒPÓþd@oq})H‹â‘Fë·–=”ý\ +]%=œŠ±é4½ÕN<ýfò縕b9ôË +¿„^ä1¬%Ù _ ÒU¼ˆ¿púó°y.³ºl*¦/qO8N¸=¼‡7k+Ärí|µžOO¹ð!ÿ"¿b’=àé-‹ÈcD}]Ü`yîŽy-rCü«ö5&JÂ̲Ù8,†˜C†ô¤¯ ЦÙõ„Vô·s_ŸËðç!y) ÃÔa±ÃÆ©ãbg¨3bk á¸V½6¶6áõŽØÔb¯þ>ö€z Ö‘32Ú3!º ¦4º8æÆèe1+£ïŠÙ½1fWtSŒæ÷$ðå/£öˆíßíu!·¹öª+¯ðÞF+£÷\·ó8¶â¸·nº³þ¥‰õ7àlÆßæÉwwuâÍí×m-ŸñÚæ÷FNŸœŒí‘QŸ3žì€¼`&èŒòÐË<â&ÓV´É±ÆújÄCao¸Èˆ O˜I6‡ÛX ‰[l²wtçR÷Œ.ަ%­.ï<{Y œ@µ› ú… ëë-Z4fOãëXѾ~½qÏm.ÿhÛÆÛݰáQîØìÚSZ;lO͘½Y Ñõ™Ê+äåBƒ<áè0>Ê[›*›ìü¦PS¸a„… é^o;Îö 6+b)­µuæq¬æÒ¥òƧ箿ú‘GVÝç)úýdíMm;¬S¦<ÆÓÞKK}üO마‰Áƒ°¶A1ºM§ïŽA›Î¯ÐMè ‹}“ð†´Æò*~ˆ„ðÈÌyÂG‡P~Q¡®æÌù3-¶3übë?&UŠMÿ4'éÊÀ­ô„1»_×¾ÆÊë »7SþÕ-\HösS¾kÙ\:çb[W(éÐoAáéWšh¤'9þ‚åÃÒQaƒ ¿¶!èUÓšÈ'9Ì(‹3[‡D2 ›»¼¤£Ü;G+•Qã(šŽXý%W'ãT$è((mϘ×ßqÕgK–B®ù†ö8‹ã°„‡iw-(®øë_~íµ#³´–Ô~8‡â ‰mŽ _0oÛ†öw¹Ñûæ=ú@ûq¾xˬâZ>f3@ßÅ6³ûÒ6sæŸÛŒãŸØÌýwlðÐMÆïÇZ÷ëë 7¡M¦WiÝo„5Œp ¹¨î÷ä àá®QtÞõûŠ®%?î¹K–,h\¼¸Ô$–ë§´÷µ§ñ(²è±M›£;FÚËZ l/ãËpl—é¸ìÐ& 3ï†x¢:ãÝ«–5ø}²; +b‡E½.‚íÌ™‹Cž»ƒþÜ ¸ ª\åK—ô` ý¸ÂÞæö=¢²¥Kr@Ñ|E<ê¯'1mÔqóç.a 2×îˆ÷­k¢Xæâ¦K4àÖ%}éʵà.Q™úêXn„Þ:ÀeaGHÖ}ó² }ÛûtåQà–¿ûºCŸH ðÎù‹Ónå'"‡Œ÷Z^•wÑŒ$[5Ž`jû8ŽyCúåX³ÿeúŪJZRf¤Ü¶Šò+{÷’ Þ½HŠÓñÄÃím|ñÞ/è¼s!oš ó& ÷<³‰³FÇD ¢A’^íÖëo,§ +9ì8¶ÁÎop¿ÚYƒ›Qh 1ÄëÅ>jn9Ó½ +÷Ͳ‚.õ 3ýrÙH«3eYVd£Ñd4ËV!>Ün·„Y“¤d9YI6&›’͉j†4D¢ 16 6•Ç(cŒcL£YMvŸ´OÞ§ì3î3í3»-¢Å`‘,²E1™‡'ÎJ”iý¡K‘Žw^üy^¤£_ð¡õoÏ*/[2Ô¾ÕZ}Ÿ-™wº¡rnnõðÏo+}r¾/RSû§÷I6ÊñÛ½'>Û œ‘šb–¢7?¼kG4åkÈs‹ð ä,Õžp‹ YÉ&;Þ-mBŠd”9ŒÀd) +éòêŠÆ6±ÜÅÂr—æÎÜ¥¹…þN{—å ކÓ_"V›Õ^ÀG1WLtBi0 q¬hJcçúã«´—Oß«9úÄ®]ƒÚóæÎt=qŸÀ_Îtp#ø‘/fï¬# ¡Á¦!o„®±áÝA"‡‚­fû(ðm¶pÝ>t·|&­Ó³E,£yt0è_­~‡êìíÈ_{nÄœÅ+þPýükx·£öJí³ä›DÄ÷ܱ–Kü~ãfæÝ0‚4Zp>"zëâz éV<Õ­¸áçÕÓß"³±J­2LâʹeÜ]ÜF®É¿‚í Ø>`Û°¹Q(` Á8’„ó=Q܇$òÑœA2øT)å`ˆÞ|±tµx3¾…Ü,Ü"®F«ñZ²–_%¬·‘'ñÓ¤Gg}1Âp(vâ9Ú(m_ÜÖJÄï7ê>a4ÄÐ( ß„wQ}q€^_@ë‹h}qÀÖO]²¾H V¿EiñoEUÒ¹t!UÅŠˆ³”*e©Â(À¦…7•7`¡ð¹êäB`Ý$ØD€ ’C§±'îÁõÅÞR¹‡ÑmJ±¤£t<€" I—™†[Fqz9q”4Ú4•Lâ'IWÊEÆ©¦Y–r®˜Ÿ-‹Å†b©L)66rµ|½P+Öj¥¥ÖBon•o2ÞnZa¹Wºßt—e+÷(ÙÊ?*<,=*o5n7HÄ'¥§M/áfò*ÿŠxŒ;NÞáßÎJ—ÿfüÄt%W¦ÿp¬ÇŽÆÓ÷À}`Ÿ®×د-ѵ¾çÚ¾ßH¸6ͯ¿ +Èψzr~¬¾ØµÙYk y|J1° ØØ½ØD!kæ£?¨8þ ¾ nD¶‘Y%)r2TIòäB2]žMªäzr|YF–Ë+äûÐ]ä.rŸ¸^Þ o$ËM„nÏÊt;D‘×å×å7Èä”|Jþ€|@>—?—¿A_oÄ r(&o’ˆÌEò‘R°c¡ó}¤ +š" PRÍYÜX~¬”¥øÌ7¡eÜ +þVq…´L¹­âÖñkÅuÒj屉{–V¢zõ:ÿºtHy½Áâÿ"ž’ÞP>BpŸó‹ŸK(ß¡¯ÅÁzÑ3‚e¼ 5üG­ +/:û1^çmÚmßi7røxmÎk?Õþž­=HíÎk» ›ôŒ´F!+X™ÉL,ôëõ‹Ýz¹YÝ…³ HÙ ½KÐn›Õb”%ê“ÀÓÛBë"/ý#…ÐŒÀöË??&1˜Õ–N—EË¢^®» œÖ½Ìmíéâ¶ŽúÝVe" aArb‡à†B±‡ÚØed 8Ðaɰf£l<†Œá³…lqçoânno1ßb¹[ Kµæµ–mÜv²Ôò¨õx?ÙË7ÉMʳƧÍO[^æ^3¿fyÅú6w–Ëèpm¬¯–†cø¸¾fʵÄQî½öÊ ¡`í÷í÷ŸYõéà±1ú,Ÿ|—Ñ­žpVšeEZ7DÍÝd­Ïò¸[}öpÚÖg³¦r“ •\¹a!7ßpƒa¥A5—l¸G`#ØŒ=I¢”b¤…Û1Ò,ãÒƒÒä€j, +K’ÉÅH ”qÉ›í»¸ü6'—ßþ_ÜÚ¾îjåÊت¶‹ßë¤h¦§ŸËê0Šn9Üæˆ2 +j,Ar³ñ4jÆ8šƒrG¹L²"ôps!HuÙ«à¶zâ|Í™hæ¤ÿ +ý*‰¾½Ìп eEÁQtü>´ñ0Øû×(úA%¿ëø»/WÝ9lØU/¿{<{ùÄi5¾©—WŸnzîÐêÆS «<÷øéÉwn}àNWÄë·®˜Lyy- +$FÑßuy’UI9Þ¦ÿ¦êaý÷£>úPŒúI¨½Åž´w„Ód•"Œ—•T‚"š]@oüÖfûCîHW¸ä°âø …Ç8 U6òŒà´¶¡‡õ4‘ØbýóPFuªà'sP2î ÛIÉ줻 ”sÇÄi>ß´‰wät¥½zòŠ­ëïŒpÝùÀÖ;'Ÿ~ü¹#«N5®>ô\ýæïNBXhÆ{@+"L(˜$ÂôHp4²Fë¿t´å½Û!Æxì  LBOºÑee:M°Ct…swÏ[a‘ ÖûgNÙ1{Þ.¸²èÕ¤GIáù‚ìÁ"!â°q:šcYíq3÷î¼íñý³¬C¿B1û”ãƒÖ73çoêÚvZÈs ¯„¸Ž¿€ Õø[ì7uߥYøÿAçO1•s¯@ºÇð}Ð ât»p7Ú(ñh„ØŠqõè² 5Á¾…OA‰ðü8QP5ðÃç79úÆçn4ö`ß û­°O‡ýAØ—ú¯—À>—ôGga¿ŽÂìüú;Åèf±RDÚ/œBåâ8/Ðwñn¸Þ…ös­t¿°\ì÷¡Ÿákx÷EÀ_8©ŸÅDxöZ)4¬¥p`Jo¡ab” ¼v¡E8‚¦SZ(Îp¾æ&À>S(GS„}h§ Õh +µkïB;¸ƒt¿°_(ÔÛÒ$´Þ–êãh?ò%Œè| EÀ³Â c(C£…Dm¿Â‚ Ý‚yz¦ôÃô=a+@ èm\Œ?à^åþJld(¹ æŸÈßHÂOâo࿦ ë„OÄ,ññcÃC¹áÃ^ûMš&‹ò…W +”{•—”/yÆ›ŒÛgL#L3MgÌCÍ7™›-½-­¬³­Z¿¶½aŸkÿSPXÐà ¢ ùAk‚v ® Þ"†…l9ï˜æxÐñ²“w–;opž½,ôÚÐÃa‘aS\¼k¦kkxÏ1⯑F¶FMª‹:íÔõ “|Ô]L }6䡿/|ÆÅ™²8Ö Ý[ƒÓüm ù·þ6û;› ˆ[þ6En‚¿- w­¿-"+÷˜¿-!;wÊß6Ò¿”ào›ƒèu³¿mA†¬ð·Á· yÏß¶#~È—ˆ¾Ïƒt §²Ùi#'~Íßæ„?÷· R±æoóHåúùÛ +ãÊümEs·ûÛŠãžñ·h0÷ÛìLÆúÛT1ÄíoÛsH³¿mGÒÑHäCµh!ªC•hªMQQ/TŠᜆRaë­ÙÐCE# Oª‡½yQ ªFIp7Õ@ÿdhe¢*ØTTØ«ž]yáì…1óáX=•Ÿ1ëÀŽY‹`¦ù0×\S½)%0æ_›1 ZsaÜ$Ô=J¡o ƒæe#JE*@©c-ô™ p+¡Ÿ +ã}0{ {¦ 4ÒW»°®rNEƒÚ«4QMƒ%¸:{¡:¢²¡¾¡Î[R¤æÖ”&«™UUj!íU¯zë½uó½eÉʆ¤C‹JæWÏõÕÌQG”TüÈÀ,ïÜ’IjiEIÍo½ZRçU+kÔÚÆÙU•¥j™¯º¤²0ëNâF`=ÜÖO(©‹@L„FøªÊ~lˆÚÙ­Ë`õ™ÄdQô1þ¦Dú£AðÀ[W_é«QÓ’ûê9·ïÅp)ؾ—¤œ× Á¯ž\Ê}5ÀÏbJÒ"ŒR`+óØ0’a¬Îu v/ƒWÇ$àza ªhh¨œ’R@ç7&×ûëJ½å¾º9Þä/<Îé‚A@¡JýCӡϨ’z™¢{FZ}©Zÿ6ÊJ!‚' ¡OY Ïj] Ì0(×êØjJêü‹8y1ÆØØÍŒ¶KÑ®«D ´ºrí‡nA øå›ò³\Íoïà.-ïNš+á‰ÂZ ìÕÂjÆëypÏøg¸PÊ +¼j­Ó¸*Nì™×O×6K_êI~¹ëÒÒgÓuL×÷$†—I¿†¯õ°>ƒ 6øu¬Ò¯% †Îiųaq±>•²~Tuè´·Ž»®Ë^fÿºîÅuÑ’8&9:¶Œë^¥0¦ÄOŸÂ¬ 4´šAi`Oü)‡V•ß’zuàØ9õiÿÐ_]ûéŒ<¡wj™Õ”Á ¥lt›2FAÓµÙð´=ÕçP~b†$¿5—f ŠÎ“L*˜Wjðs¦šÝëJQ€†ºnZ©cÛÈx˜ÔE:´]Íä©ËZéâAêatÒБÔAg +ó *ƒ¬Ûƒ»ÒÏÕîÒÿiªœÓ±­íÐè†W§ÖuR´€ñ£úgͰ†ræÕküz»ÌXÆŽtŽ$v¦œ˜ =J<½O@~å,éž- ¡R6wøÒé`fE~ìJ¢y†NtõEø¡'¨þ ~k¨ïÖ7`+ë꺎SÍ% s…ùæîº¦sC%%?!O‹‚ª_öÕìÜé?~Ž,X$¢‘µÄOQr7NýÔXÊ“…þØ¢ÏNy^Îp,ókRÓÓºŽ;:¦”§e]dÞUë´„EÄJæ3ªØ•ÒAQÔʫ¦ 7æt‹«úLZ´G×ÝÀó§þŸÒÀRñSЩa%LF?ƒîó\ÌKá–ä—wWù#Þ\éNó³%̯t ܩïÐÈ€½\=¼~?çeTfZÀ¨*cãã.ã:è¾x„ÏÑ6®‹–é6“wQ|™ÍìÝ××F¿ôd><­¼Ǽ°$mðÇÚ»6=z•0êíÑUî:Î;Ê%-¥‚yx•ëý8z™&ý˜ž|Ý¥|w‹5Lî]ùu)®*]8×U†¿ÔVëýù»ê§$`mK¢™CUGîQçÑb-Óèypœã—˜©V)^õßé©~œªÙ~iðÇÃòNFÙlžñ(®è<ãáªM†<²=Ë…{*äq…ðd\Ñ?y™Åä’ÉžÐçqÌ'C›B&2X:ŒB8RØSá…­²kz5úç,:6Masd´ €ÙxhSØãànœ³ýý舑pg"\Óö(D³P}>ú‡7‹˜íÐqÓ"¸ß9kw¬rÙŒÌÆÁU!ÀíJÿÈg.ƒGñObùmçûñÔ9WÈ SQÈæHÀ(]Ñ»á\ý&0~f2šuló 9ð\§%›a KBÇh$ûc¢SYúgF‹èLEþžILŽ”ž,6žÎ:–õÒ1ï—2mwBIöóRǃòRÇÌýy°©Œþ"ö‡L©l2~n@wF1o…qc"£/“ña<›aëG¹Hù™×¡q…]¤2’ñ‹ÊbžÅfÊd™pIJкJçRÚ¡tÌ0ŠÑ—Í8•ÇzO>fCÿÜŽ;º>æ2ZGúy­ÃÔõ^׉¼.ÜÉh¤’½fÍöëT&ã]w*¨œ&3ü;©Ð%é?Žì³Néçû¥À§ˆÍ\t ®Lf¶˜Íze2YOè°‘f¿ãü˜OìаN0ѯŸã;0ëÎ߀úýߡà +ÌÝ]‚YLŸòüNèà†ÞCù ¸ºïʆ¸VÊÖ9 ~»{äîš5vf£]óΤ.¾¶k& {áQ¬oõEý:ïê«%=fu®uºæn—ZaVÇz.Èz;³Ýwëk¢®YoËÏõ°¾#+ñ±<Ðב™,`O;cz­¿vâë¶Î£3—°ØŸÔ1W uÂÒóÊ–-ÐÙê/ÁÍPÊV†µ,Þë³,`ífBékô÷¥÷¯¹h5¨ÿüPê%e åR™CWþ×1y×ú×R•ŒÃ4ŸLöíCuY'O(ôº[õERïÔ> +m0º¸ª@y0§ æeŒ× +ÒkxtN…ù«@ë¿_uú­ ÜÿKõ ¥[=èâÌëßWR.YRÿÃõ ågÕƒºgò¥]pê¬uzþ¼ +ê¥*,Ê­®¤þ ®¤ü¿ºR—ºRg…áÿ›u%¥[„ýïÕ•”K¬ÖþêJÊ%ëJýgêJÊOÔ þ3u%ý«u¥Î·N¿e]©Ó޺ו~,úþxuI_Ÿë™ÄÿZuIAÝ«K—®nügªKÊOpWíÂÁÿí*“Âtì‡Ù̾ʤüW™”‹ªLkÝÿd•Iù§U&õ?VeRþ…*“úo«2)Œ“ꆭÎíLxþŸ«)—”ù«v¤ü v¤þ×jGÊÖŽ:k@ÿþÚ‘ò/ÔŽ~ +î¿·vð¬?Q~XñQ~Aŧk•æ·¬ø(¿ªâóÃ5Û/«ø(]*>?Uwø-*4 ?€ïA•…ÍC¯’ÊahÑïÚè—qÓ©½ê½^u¶·Ê· 1Yý_Á%«£ªÖVÔ«•Õµ¾ºo™Z^ç«V3ë¼óýæ`_Ý5ê_ÝuFQ:gŸä­+QuÔ:>ÝSúþäòÃü~ö÷êE3WÖ+%jC]I™·º¤nžê+¿Š¢xëª+ëÙ7t•õj…·Î sÍ©+©Ò“€v †Çêæx“ÔŸZR³P­õÖÕÃßìàX%° D-¤èÙPá ð©´ÔW] Ýi‡† +€\öÖÔ÷âKâX™ZR_ï+­,ù”2_icµ·¦¡¤âS^YBêE!²ê_yÃ`\"äÎ[[ç+k,õ20e•@XåìÆ/ÅAé6 Ä\ZÕXF1YPÙPákldª+ýÑêtVØÆzèOÉIR«½”j…)H}ER—9’èœ)¾:µÞ r€Þ•€ªŸü‹¦¦ÈØZÊèEg›hA(ÖP1”7ÖÕÀ„^6°Ì§Öû’ÔúÆÙs½¥ ô¥¯ÜWÊF *õÕ”UR:ê+J€+™í›ïeèZÄèP‚_ˆ¡^¿K¥RÛ©ú3µ¾¢¤ªJ™íõs Ð+)éF§¯ô¢N­öÕy/I¶Ú°°Ö[^%ëHuZ]²¬†—U–WRE+©jÕƒ-)+c”묣ZRx5V•Ô)t¢2o}圆ÆÝVaÕÐ’RROGð©¿x& +R ÃJª. À?&€G'4@¯¦j¡ZÙEÍJN—þެ/mÔSFR¹Ìà :ç­cƒøêÊêÕ¸;Œ£s(qÔlãË@2y~{™íK¢PA”'ó}•ˆy¯n‹QKjkÁ¼JfWyév€LJ§P*JÔŠ’z€è­éƪuÚ]¦6Ö”ùîDUaÈéþ”Të}UÔª™Ø¨JÔ*ê=ÀVkKJç•ÌÂÀk| +UÕM©ºM PôV•S¤Fg«9ãó‹Ô ãsŠ&gf«¹Ô‚Âñ“r³²³Ô¸Ì p—¤NÎ-=~b‘ += +3󋦪ãsÔÌü©êØÜü¬$5{JAaö„ ÊøB5w\A^n6ÜËÍ™71+7”:Æå/RórÇåТñl¨Tnö +l\váÈÑp™9"7/·hj’’“[”0¹B5S-È,,Ê91/³P-˜XX0~B6ÀȰù¹ù9…0Kö¸l _0µ0wÔè¢$T7“”¢Â̬ìq™…c“T6H.TY—dÀ`¨Ù“èà £3óòÔ¹EŠ +³3ÇѾ”;£òÇËVrÆOÌÏÊ,ÊŸ¯ŽÈR2Gäeë¸)#ó2sÇ%©Y™ã2GQr“Ðn:9ìPè€QÙùÙ…™yIꄂ쑹´|Ì-ÌYÄzïy Ý‘ãó'd_1n@¿ÀIÊäÑÙl + þd˜1òó\ +§h|aQ*“s'd'©™…¹¨Dr +ǺTžãs˜L~Ráåûñ¥2¢÷~¨ЋŽö˜•™'P4à†Ò­/hWöÕ¥ÞÚªÛ~ãÖ]#s£ºïLbZ«;PáQ5`¸ú=Ö„°–Å¢ŽîÝ:6 ÇIºëeî´"‘îzËæ{ÁÖSWâ«S|Ô™,¨¬g–!°Ú§Ç<µ¾¤ +&ƒQÔŠX/ð•%U0¬¾Ín¥‚am]% YPWÙÎD-i„»u•×øÃp?L1 +ÔN +è,ÎAÇ¿Î[_ Qªr¾·ja2ô­£±ŒaRYSî««ö“ÎØWÚ08*4¨sð2_ƒâ«›“¬* +˸~uêôs?â·Éƒ=RI¤tæAê/̃”æA~'_Ê ÕbÆ%Ô΄Eù5¹’È•”ÿ\IÑåðoË•Ý`U®¤ü†¹’Ò™+©¿0WRºå¿ WR~,WR~®¤tÉ•ºšo·t â98‰ß*]Rüé’ú«Ò%¥ºlÝø[§LJOýÕ)“ò›¦LŠ?eRyʤ\œ2©¿$eR.™2©ÿJʤeN7f!p\÷ŒXA=á}Bàè{ôÁ2)±£™Ø‘ŽwÝ—,dc‘Ñ%°#ÏzFÇî`vDž Kȅˉ¦‘¶ï“„6|ŸDZ5òÝ·£„ï–oG‘oZÉ×ùJ#ç5òž!_jäùB#ŸG“sù¬E>ÓH‹BZ<ü§Ÿ(§iä…ü½•||·SøX#µ’¿µ’³pqV#g4ò¡Fþª‘Óù@#§4ò~+9ù^˜p²Œ¼FNlŒN”‘w»…w[Éq7ùË›ná/­ä·C„wœäíc6áírÌF޾eŽªä-#ù3ôøs+yà¿é&oÜkÞˆ'¯ÿ)Dx½'ùÓ‘ áO!äH9 G‘×BÈ«¯<#¼ª‘W^ž!¼ò yeÿ²çÂÝÂË3ÈËþnò’F^,#ÍwÙ„f¼Iž×Ès9ôì`áP+yö÷³ƒÉÁáÂÁ4r`¿]8Nö?cöÛÉ3ûLÂ3V²ÏDž†ÉžÖÈ^<å O‘?hdFvkdW(yÂEšœäq€óx+Ù §­ä÷Ðÿ÷dœv,!id{Oò¨F¶iälÕÈà +Ù¢‘‡6[„‡4²ÙB6{øMÀ¨M­d# ÙM6ÀiC+yˆ0’< ‘õ÷?#¬×Èýëf÷?Cî_Ư[áÖÍ ë<üZ¬íX£‘û’Éj¸:Ús¬‚¡«Tr¯‰¬„[+Ç’{àtFî>Üí$wÙÈ +7¹S#Ë5r‡Fn×Èm¹U#·ÜìnÑÈÍnr“FnÔÈ iäúÕäw¹N#Ë\d©B®ÕÈ,ÖÈ¢VrM+Y¨‘ó· + 42+ilˆ[IC©o%uKÈU©õ% ¾$RÓJª[IU+™§‘¹©ÔHE©I¨H#s4RžF¼eŠàÕH™BÊ<|élE(5‘Ù +))v%«I1¶ Å2K!352C#ÓázºF®œ!\©‘ip5-‚LÕÈ”V2Y#“àÚsa’F&j¤(šL!…W¸„ÂVr<¸ÂE +Æ»„‚V2>ß.Œw‘|;MòƆy2vŒ]BÆäZ„1v’k!£[ɨœa”ƒä„ìV’5Ò"dYÉH ‘éF´’L€™é&žáVÁ£‘á—[„áVr¹… j†9ÉP3RFk$#„\¦‘AÁd`z¸0ÐMÒ„éá$ý?@1 BÈ€e|ÿ4“Ð?„ô÷ði&Ò/u«ÐO#©?u+I1‘ä`Ò7i°Ð·•$9ÜBÒ`Ò§Œô.#‰éå ¡v!!šôT‰;šôˆôéMâí$™…¸Vk%±^ !1 +‰Ž&Q‘.!ÊM"­ÁB¤‹DîŸq7a&á®±Bøâ‚I]cI˜FBíÄ ³9[‰î9Ü$¤ŒÛIFìpm׈­ŒX-6ÁL¬‡x‹X–ñfxbn%¦4bÒŒNb\Æ+f¢xxY#’F E5"(Dðð|+!e„ƒQœÞË,`;Af‚÷â²—ã>ÿÿøAÿmþ?QèÿƒÑmendstream +endobj +307 0 obj +<< +/Ascent 759.7656 /CapHeight 759.7656 /Descent -240.2344 /Flags 262148 /FontBBox [ -1069.336 -415.0391 1975.098 1175.293 ] /FontFile2 306 0 R + /FontName /AAAAAA+DejaVuSans-Bold /ItalicAngle 0 /MissingWidth 600.0977 /StemV 165 /Type /FontDescriptor +>> +endobj +308 0 obj +<< +/BaseFont /AAAAAA+DejaVuSans-Bold /FirstChar 0 /FontDescriptor 307 0 R /LastChar 127 /Name /F5+0 /Subtype /TrueType + /ToUnicode 305 0 R /Type /Font /Widths [ 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 348.1445 456.0547 520.9961 837.8906 695.8008 1001.953 872.0703 306.1523 + 457.0312 457.0312 522.9492 837.8906 379.8828 415.0391 379.8828 365.2344 695.8008 695.8008 + 695.8008 695.8008 695.8008 695.8008 695.8008 695.8008 695.8008 695.8008 399.9023 399.9023 + 837.8906 837.8906 837.8906 580.0781 1000 773.9258 762.207 733.8867 830.0781 683.1055 + 683.1055 820.8008 836.9141 372.0703 372.0703 774.9023 637.207 995.1172 836.9141 850.0977 + 732.9102 850.0977 770.0195 720.2148 682.1289 812.0117 773.9258 1103.027 770.9961 724.1211 + 725.0977 457.0312 365.2344 457.0312 837.8906 500 500 674.8047 715.8203 592.7734 + 715.8203 678.2227 435.0586 715.8203 711.9141 342.7734 342.7734 665.0391 342.7734 1041.992 + 711.9141 687.0117 715.8203 715.8203 493.1641 595.2148 478.0273 711.9141 651.8555 923.8281 + 645.0195 651.8555 582.0312 711.9141 365.2344 711.9141 837.8906 600.0977 ] +>> +endobj +309 0 obj +<< +/Filter [ /FlateDecode ] /Length 680 +>> +stream +xœ}ÕMkQÆñ}>Å,‘ôæ¼UÚZèB+FܧÉm4“8Iýöž'Oi/‚$ç?Ì\æ·;ãË›«›~}èÆ_‡ír^Ýýº_ u¿=ËÚÝÕ‡u?*“nµ^žïNÿËÍb7çáùÓþP77ýýv4vãoùpžº7§ëÝUýµøqœ/úýûÛ»Çõïc};ß«:¬û‡ÿ¿5?îvuSûCw6šÍºU½ÏO~^ì¾,6µÿóèë‹ßŸvµ›œî õËíªîw‹eýCMÏÎfÝ4®g£Ú¯þzV&ç> +stream +xœì½ |TÕµ?¾÷yÏ™÷Lžä5“wÈ BHD$ I!„H !“$™˜! 3É$µj{ûÿ|þ 'sæœ}ö^¯½Öw­½'A!¤G‹Š‹&§ô¿û·•på]8Êjš«[5‹e„ðT8üjVwØî;ÜSŸ›êZë›·ò/±ýÐ~g}Swݺßo…—…P‘³¡¶Ú]’ÿB Âýi pA÷ˆŸŸ#š;ÖüÇc!ÿP)ýmlrÕT7>¹X‡Ð¢Ûáþ/š«×´²§ ’ Ÿm-Õ͵§¦¼1 >7 tÊVW{Ç•×QB{^!÷[Ûj[°› ‡Ï€œß…x„øTþš†CÉû•lö-TÇX€ ­À²,§e¸ß!Õ4|EŽ´Ù9d“Šë²#Û•+‚âƒŠÍøÃ*„Ÿ{ÿ‘ú#LßuHÀ2=Ûˆ¾í?ŒÐô +HDÒ ia=2 #2!3² +òA¾Èù£ˆ&¡ ŒBP( +C6dGá(E¢(bP,ŠC“QÈUÑ×:°£.tˆ«ÂÇtŠÐ +WŠÒ:ô,]èU|=ÞÂnÊÖ +øóèUq—¬R%Þʦ?ŽÇQJd÷Áó”¡bê`¬Ï’WÙ,ÐgjG`hò=!ðË`”`3õ2QyÎ^Ç‚rÛ/—ØÆ}´™D[/*îÕwÛú¯\).ç‚ø%½|p/%õrQ\ëæ‰ ùÅå¶ÞŸdg¹{ͮʂk Ëá”|‚Ëp=;+ØEuÊ>®ŽìWD“:îI$<‰%>‹áPòÀ¹ ×!Ó¹ ç.L±šíæ(»Ù^Ç¡¡v6hè#eŸhøâ³6!úx:ÚÒáÁò78=žáyÄc2%^#hD^Ðd²B.Ê‘8AY–Üc¤‘Mç/sÝ«üùᛕ{qíp¿Bx>Åc>z Ï¡#ó8B˜³—$ÓÐл„ï‹3.¤LÁæà<ÂŒ]ǘÈcÐÏy&žˆô³ !!™? s9 :¦û‹, +äq¨¯¦ÀjÔšõa¡!ÁA“üý|­³ÉhÐë´²FU |l¦s¯øÆgœ›1chù9r!ÆeíÖTÖ.Úéa¥Gº.²›pdcO=.ŽQzKpi¤òHCO½òNý ʉ(<¯TéÁÅuxºò^)ŽâO*Õø°R}Pé; ¬À÷“ã.<ˆ¿¬ô/,ÊRÎr»`൒Q•#CÐ[#9›1ßPdçÇO¶å‡E ù‰“Ã&Å$ÄOÒq%Z¬‘“ƒ%™5XÉO–b¦Pƒyþy3èÔ­Õ)C¦OL/}’òù' Î)Q3qÚÔè«ûúøùc1 ǰ¡pΉÀ(|HÅþðž65rNC1S¾óÁºvÓíû–71ááe?**íAùퟮw^ßñÊÂð¥¿\YþóǰõƒdgBÅ£»…Mõ¥K­L…a£3§ÈŠ/ü4)…Q…<¶í‰tÊYœÈ&OÅá_FÞ½þÈÿX>þ˜…ÈÖûÌ•…÷Á¿hÁKG€RQ‘#!hñdßŨĸX°/ž\r]ä‚)BÙÔ)‰)6TŽÑÉš9F“h‘5v*°¼_F?1[2à?˜Ñ`ÊŃpés¸8‡G§Mµ¤¦øùú0Ö©Ñá°Ÿš2 {Ÿ{·¿Œ?¸ãðïŸ#·p~ëúù«nc—Ìwm€³WÐ÷ÿî™;îûݽ—“z¸³Ì‹Oîÿ/¬ûÉ_)Ãï4®üÅ—:OŸ\µòÅW:O*/öÁ]mï·”¿ +ú¿Ã6\uåCþCàYÜ^v”² Ãa>Ù,iñ†d‹™±šàÝÈ É™Sôˆqr m’Å +ktÔ’¸Šð2ëÙ0%9šM0ÈQȤçRq`‚!8•—ƒ9ƒŸ~ªéâð$¦O22@ó– 5øÁÏ?ùü?ANä3ü§ÓžÌwŽLw‘NzÑÄ©s¿O2b\é(ךñ$Ódœ©ËCøü}ó>t·æYgDZƒl”Mš(Mô$€ýô~¦À¨Àèx‡'GMŽž¦ÏˆÊˆž–œòð*´w&oÇ;Œ;L÷£ƒÆƒ¦°JT©ÁÔ‰üF´FÄpDx$("Xá´ô™4âvªêÉÏÄéìËJY÷­~ÙSLKâžú7†V®¼¸å=åïOŸ IŠÏ~}Máý "òïιåÞèl|OþÊ`Ì1aå/Õo88›¹é°òE“òÙÎ…‹þûøAléHZ>-kmJIEdä‰äë69Êʇ¿(zu-E௰‹ú+_‡ŒóPGg¢nêuRà TïíïW>cW +˜É!½€–DtÍdƒéÜÐ@FµKð´XÂf‹D4“fB˜]©ü¯r¹  gi¶b?Á¢ütøÇÊ…û•ÓÃÙ‡p3Gz®lR>c +iÿv‡ K Œ G²Æ0¸`!ZvÁÂS™˜T?‹/Œ±˘Ë'C(T.(Ÿá|f!ö¹§ø±ròò«áŸ*¿QÕ Ì<öE°K3xÖ“^ ¨P#r '±•¼YW)cC™EÒɬF0³˜eÑÈi5F½`1›1˜ò.à|¨©™.žO>p!Eµ3¬ÇbŒIÄé¼9Õž¨Àõ™Jo›Ò›‰ë•™¸¸ sïÿâÅg”m¸ûÌŠQsw+ÛÎPÚÞgñÏŸˆp˜‘W‰,Ϙ8d‘“¤3½;®T“|Þ@;v_Bìiv3û8“8|öèðY&‘ç†Ï#'Ç]a$\±àH/´ÕèAùLvÊ0ÁT€h9ð<ì¯09.“žJ\qÿYû‡›îRSžÇÊûRü>³ŸÙ ¶8Ë¡ajÇ€åpÆ22(V˜ýH¥Cœ‡ýrf9ëb\ìf+ J+D¶[™ °á™ÍѾ»àÇ:‹8|žE,Al&Êe‚ÚÉ8ƒ¦ç‰û¸0â/ภ+L0<°qôìug…Úו•WndWÒ9•ä°2d(ˆ©þOiÀô<5â‹nHr†: C¥â¦¿oèÉ'ùóo¦8 îʇ\/µÕ”å°95'*Ó,ˆ´Kl€¤³FÂ$¥Pjð⼈ÕRb#)±‘(ÒbR,àüEâ‚â‚fbÿpAÕA¥O³@ìd gÞª½©òòþ_Õ/]ú¿[ ¶§/mîl+¾#ý¦Uü§ÊgÃÏ+[öí»›.ŸÄ·Þ½ïPÏ‚¢Û•Î×~dAÑf¼ó‡¡2w³€dT꘬‘l <ÏãLÖÉ1N>GDNFı¬,b t!kMçTù˜É,Ë I!Lz«Å?eI8À>=*ƒ®·àyï+;#pûEÁy¶¡ãnÍ}<÷Òùc„ ò¾ý Ãt¯#Œ]àý?Ä Ÿ) l¬5-Ñ9­eØÉ²ŒŸQÀ¬ qÉ J.˜3(Ô d\„œz¡aÄm"2±Ë@*ë@è±ËXð9 %ÔJݨ“_#thylÁ¾BŒ0/Â5x5–h”FÒ1/D5,øúlïð:ætÄPºypÛÍw´,ÿýíQ·ü¶jàÏmÌÃo=ÆÜØùdeõÚ¬Ù¿¼.åõGÚ^£²çÀnöÏñhŸ#^æ´ûåû–:-ñù¨<ÖÉ–YØëì¶0?N8ß`I% ¾ „e`šà+0ª ƒ„e`dLñ°™@Ù–<&•ß«/%SŒÎDî¨-C„¡ÕáºÉÕñœò‹g€]ìaO$ØL M°X!H §«'D8«å5çK¿êõ«Û›Jï^ö|MԲ眧þvh÷ãaýGž»sCÕé&¾×fëÝ»ûõèÈý·®y³ 3#*깩©gl¸ù¡ý·-«‹£ó) +la µI=ú¥ãz(H ~ò:-°ž E0dÎÔ"‰sê`ê +z(q-sX`k ¤ÉŃ$XF¡†úß$‘Ã3»ó{u¥DT7¹eet ÌãW Tjx¹¥™©Å°§x4Çi3Ñtœ¡¦«Óéî@[tÑ>‰È2•$%ƃ±7+Ï·ŸŸs:kxY ›>\‡þüPêQ¥W½È2Cûˆû8r°¡íŽiÀˇÙÂÞf ƒ\*,Ø)Xó-E~N#v¢ã,ÅÊp ‡ ´ @Ic±Sy^Å¢îh80è6eÀKîéêfßËJì”_;¼T~1¤##Váe‘n3°rG†÷å&-¿îµO×Ý\ü»íÏÖG-}zÙ‹èÊö=Z±›?TÙe4üúä–‹KÙ6Û³‰‰è½í®¨(æ¢ûMÀó³ƒOF?wDX5¬ÓOã ó[eÒçëL+cËÂÄ[!KÑ!)P­ñÔ¹’|#Cõ°ÀÞGN†x:âQ¼gÎW­åÖñÌ<Ý<ý<üɺ +}…¡bòJÝJýJÃÊÉkukõk Ý–µ“wèvèwvL> ; ?`80ùÝ#úG L~Z÷´þiÃÓ“OëNëONO~[÷¶þmÃÛ“?Ö}¬ÿØðñäD¢poLO#3VrÔµO‹„+ìc·µÞ{vSÛ¡7°)†ã+ø~ ×ÍK•à0!‡±cU,CÇ~ÍÆ #þ|Ù¥Méι’Q#"2€uújœ¡Fçä²Ð¾õS",H +²H1A:)QbìSÆEìký‘n×¾ëþ‡ÎÜ{û}§ëïìØþäÎö}Ûo^£xôÓcãh/rÄ UHêù* Ua U¼5Sà!-ä¹ÜL3ôÄϸ0†x7Õê!õÖ4Lȧc†øô„’|DI>Uœ?væó–…hØãȱðV«V§7M‚læ}|¬˜øZËdúû˜ œ>ÀÂåÛ C+D ÊËp€¿Õb6@®!ë9&ˆ—íL8Dh7€UëŸ&åâ'òq¼{#Y™øáÔÂQ¸Ç´º}‡¡ÃØaâ+£Äˆô‘xcÄlÄ0g‚W¬\wæ:KÝ™•<³ª»¡tº%ðd˜¥Ðµ²4«¹ªøTƒ¢(ܦŒéû6Õ_Qzã²¥ë.Û¼tIÎÊŒ‚²¹7Ryt)÷‹ù4©p¤êPš1Ü>õz£‹m³‡º¢Ò\zM›¯+*iöL£AÏéìáSo™î/Xü¯&ù_;<ùZ.sƒYø<4p°Éð')§L/‘ÜbJ”Ûý RIOeIúƒìáQ« Ù!!òwdCf5œÂ‰ tàqëÀ,÷Qö‹÷¼€ã^^º;&h×®'•Ê¥>åø†ÝâJlØti{É6å凕“Ê¢7žÝ‰~Žg㇕Fe`Ó÷NµÕ~ÿùöOæ¼´ÿ]œWÁ ÷æë?Ù}Ë-ÊO”÷‡ÿ‚KWâì§^Ú¼ûÕrœøÀO×nåϺï®7ü‚ï¿ýåszø½£V71uÊÌ;o©^¢Ö¥Ê®|Èï†:&™Ìú +\‹:¸Š2Ÿ66#9ùëm¦‹dRBÒO~õÒwd6RWn£QØ/5 +÷¹ì`‹'r¢»™N€âˆe§øñ»âªÞºõQexÙŒYgOïĨ;¹ë¯k•”Ÿ¯k_‹3^ßø¿â|õ²‡Ž6ýeí+¿Z²äÏ©Sñtìׇ˿Ï]sÉœ%úyô£E>(Û¡çµH«ã‘6SúÐWÈ>kÖk±ŒÌD Ú _ªsƨ +.zŸˆØ@ØAæ„Úh;®À}pTo]Ý¥üà’’_ýKx!îøô +X‡òŽòþsÊáî[ÖòÉ—»ðV\óÖi¡M¦´-q\'ÊD  "¨P+(Tp P)Upí²¤•5"]J“Èú«6Ht¾B-eÄLÆ'z*µî58pÛk,cNÛ‡Ó˜/ìC7ðç)9dž?¤5  ‡ëz4¨Ô‘³AÄG GCèÑxèáÚ%ÉM‹@hÑÉchùJRÀ0ÙrûðÍeL±}¸wçiBDî±átZo{{ì-mt@\ÁØ((aÞ.Á³ö +É·Ö§# Â\&-ˆ$kK +á:4X«§Å§ ³ê_UcT†èúÄ„E’Q ¤IªSÔB£Èê;âjIláÜñ‘Ú·¬P9ÆX6mQ^:¬üNyñ…c«”{ïĸ3®â³í‡?[¾ òWçV¾ô>ÞvdØ•WDŒówø‹ÍûÖ²šî MÊ;ûß8›¨Î5b¯R{¸Õ‘ ?OOjC,?db!°L-Dª`Ú9”# ÜÖj$d! áC<&rqê…f‹à8S†.\­š/êq,©d5ÓU8\q¦‹y¯ëŒòÞ™Ã;Ïðç‡1ÎKñÌËÃnš™(ZWLqø{×0s@ªp“ †µ¶˜<8.õ‚A`蛸U"¥W©cìTùŽh™ –42+jI°ÈÔ2H¬àW£( Ãk% :º¦hV+ð€6',†¥bFPË7é¸ìŒò—´Ë¿-¶ÏÚv'~iò—¿;xrT/ÓZÌ_sN$‹—/‘5\Ž”$0`_NÃ$•xQB2CP—Lx–3%€C,ʤ,CØX 0‘ƒ´Þ³ƒêG¤ Ñÿ&ï&y±Ä’ص’gDÞ‰!GHÓ˜t>MÊåËùUL#¿†_#dL2«hàϰB€ÍG QbŒ”ÎLgÓù„b;—Ëá—°åüRá&±©ãW+ĵìn ¿^X/na·qÛù½l¤Z´Áv (…*~kÂú¡íÌöχûÑüN¦s8}è"óÖpœ[Vì%ºžáòÊ ¨“`xƒU0|@ùW—a’Çgß´VŠËÏ\AÌ[ö¡e ³GÕ¾…^è[‡C @¬ŒFÖ`V–5, :!zˆÄ?â-§åuH'ó!]¦ ÷%P„¤…ÄiuEÀÄÏp¡j鯚ƒ)foìGIcÎGPÅmŒh”ŒÆG “Â4qb:3EžÇ̑׈;™mâ6y?s˜Ù+Þ-ÿ˜y‚ñã±ÈX_ìÏ„²&C4ŽccAKqšœÆL•Ò5iržËÌcçHyš¹-as lÔ%íÄÛ˜mìâ6Í6y>ÀìeˆGð1æ‡ìd¢1²>SCØáäÇ™qºòÞ€2|âÔIå~ÈÂ~r)žb8h?EýüY‡ƒÈüfEQ e†§6îöÿ‚HÐô§Õ¯}x霃ƒÄ%·ß{@ù¹òñcÊñm‡ö»ñ ÷õWV´Ü‘ ÐFÖgq<¸):¢&¤Æª×i%Þ¤‘a"˜‚©gòuÆ,ixE„¾'| {p"{ˆ–Mõ`L|ö•_)Êo•Î3øÕ&å±þ[oæÏŸ|êså“á£|òðk½5v¨u Rå *oÖ;¬väc23F‹!ŸLc ­`}+teVvAŒÕׂؠÈ`£-(XÐÇE\UåEnEÀä¥î€ÂeOr`}Sbƒe–µh­•­ä½q¦šAY#¬W©ùP ü^ènºgüœèR©ÏÏüÖ¬ƒÀšgã áâ­)g]½ò¡t¬U9‘8/¡`ô}Ç Ìûù"ÞD 'VÞ×Ï7Ó¨Ók01G¿½Ÿ¯~’Õb4Ht$?Ý$<Éñ +ñ(ÐLW¦<ÞŒ¨1Ã’‘1¡*ËéÞßrãÓóË*ã*Ó*ó:ã:Ó:³\9’DXɆ”6 Ç“‰¤f W‡¹?ßÐÕéX4}ÃùÎÄÕkß_wÜÎ˯½÷,¨ú“Gîhî¾ß¸ºWiÀö:‡·óçÖ{høÝ•¹³ û´×‘®Ãz-ªÁ’̇……fÊÚÐ0¦[˜£%\ET™yAl¨¦ olƒÃcGvÚdŒf¤¶?âºÝ +7¸·Qi0ªtØ´HÐMÂVŸ8È’2PNµ¦ú¤ú¦úé|4Û}·û±•žD„¤Ða˜æSvï…ŸdLê£zûqgcj_®ü鯊ê~µG¹Oùu×§Ýq®÷]?¼i_÷k÷àÅØºêMþ¡SÓÒïßœ;?!4í‡{~« &Oy=;çè¶ŠÊè°ißÛð>‰¦X£üpÍÛvŽ@<  ‰§K€4†"‘x!`€›<-÷¹ý¬:US<êõOñÓ¢Ú(x€Y‘ógý¹(ÅÌD3Å"T$ŸL®bž@OˆºMJÕÝÜŸmü‚Î`4ü)@ñKçÕZ5F]à}þè'Ž:^oÐkdFË zŠþy‰áý‘?‰ùþÍü Hª0/Сœä£ÓB–Oð|°O(Îôðå² ‰ØPç E¡;vû3»sh©ò¦ò›^ˆXœ¢Ê¢tõ)]c¸ÝMuÅÚÈÇ’,ˆÇe²ÎAA>‚EÏ^7ˆ†£ŒJÞOd‰}N &£Xn:7—+”s‹…z¶‘«º¹n¾K¸ƒ;†a޲õaØ­ì#,z´ƒtÂG”K/½ÍœwèNC;—Ÿ#åÊsµeR™¶žid„zm½®›]#ÜN·UÚ¢¹]¾‡9ÀÞËcò?’®\`%³ÒÚŽ85ˤ~0I|u¸AÉ~Cl sŸ“ãR<÷Þå²gâ(輇ä"x¾#Ï[`Þ8‰OëÎÇ /ÐÒ¼œ Á–áh: •D’ IÇɲÎ4`zÞSÇJ™s^-мô ŽÑH Üx)N›ÉL—®×æñó¤<í¾Bj`øm7¿‘¹SÚÇÜÃåýy$`\C¦R ŠÁ 6–âÄhM¬v:“ÉfrÓøiRàÏéZ²,2—™ËgÍÕ.–ˆåR©¦T.×Ö¡:\Ëåë…z±NªÕÔÊuÚµìZn­Ð-vj:åmh+ÞÉîbwqÛÅmÒmš»Ñ=øþ€°_:¬9,?„Žò +Ohžóh–¡!ÿ#H¢_Á¿vaøÙ(qvøý?2Ï@¦ñD3Îá,ÑÚäö•z‘>^þ’ˆü%oeÈ* ¤ˆ"â$ ‰Ó¹"{‹g ÇÈÄœXúªC… šI`o,“Áæ¡<\Á.áÖ±ÝÜìæ¤Ì<"™DÆR´4¦ŒYÉofö0?àÄáJ:˜ªVÂ-Q릷ýû|8|ÓŽÿ€À8|©úl8ƒy™îÏ9ë¶5 *v„Hê&0>Êa+xDÜŒ cA)ƒ^¿’Ù·†’«»Ý.ý$Îu‰˜Ä6»¨nã°c·7äò:¶ßvù†nËPþàe×±ƒÜnêãJ”ÏØ¥‚Ä£XAÒ‹˜É¹Æ…–Jàñ8‘%.? œ0ýꥑŸCãöÃAwÕ¾;9JDN…ˆs ù³rþÏ“”rÁg×߇v©þõñ+72´Ž0ÅáƒÉ²YUÀ$²$*^½AÉ4²C p +.áè‹dƒ’°GÝïÔ§|Æ—ÿKyl ˆñîS6H¨ƒ¶«N}_îMi¤ñ+Sº¦®s´¬Å‘bžõÒ‘U¶±pz¢MlY( /BõÌjf+#TQ3óÞ88$Ô);ðj‚éבïØP<v8é¶oÞÇÂÕêYïáL¤Þã&3±ñVÆ×¢øÈȈL»EÇøê +Bø®\…4öK$2DFEDFeDàÊÁsÇMÏ0 ò,îR0MŽ¿D ãtPC²Ìã=1c÷~qÃÍÄžíYŒ{#ñ(ìç[:Ýž9gsûâ½Ó3r†²^m}ìLWKî³õÍ=)ö~œÜÏ<¶û¡…E ßiÞqß‚¢’ÿÆÍKÿÒƒÊë« K5äË0`”Ë@FóÀŽÃP¾#Øœo*Ôø |\È„¬õ¬tøØ•blÊEºÅÑR:Á"‡;­eBt›€ZNöãæEÝxwqùáieËî9XðheDñ#KÞÿôÖu¥¿ÝZxK;1eÊ̌Ÿõž½II'ìö÷_{Gu—D€N^>H·‚N8 +ÂB[o1N +âüƒáŒ'ŠàÝÊ[`N‰ÞÀ茼ÙbÎ4"[ο€] [kZj ±BnD>–(³A§*ÈürˆËÍFó,3[9¨¦˜*Õ­JÓXuz”z¢ +´ÙÃâʨ±¼û“3öè6Í£m¶|Þ¬;úâêÖyÏÔ5=œ¸æi姇¿¹£lÏôë³@ÉûÒ2³V;?zùNì*ZØPͬøûÜ»ûÁ… Jß]µóPÉü…ïªòávƒ>5(ÝçˆD.Ù7ß§Ð\ ß"Ø×,ˆ‘#µÄ||ÙˆHCDPx á;ˆò­ý4òõÈÑË#gEò•2ÔÜZM8‰¾IÁ§•Ô°#Ážšìòm’\—ìÒº"]Q®èÖ>¬|Œ»¬»|vùîò;h=è#ìÓ7M§¡ù4öõØžØY±\åè5o¿–m9o³ì®uÛߜƦ ¸G­ùÕÒˆš³Í_¾µ»ð­î“蘽¦póÚïÿøð®èÓÎýÓ¦žˆ}ÿÕ'pâêyŃÏ'5Ò8µ!ö,µ›~Ç ™×´µ ÙÁóF£6“7aäð­h©AX/ëdƒžÓZMÇjŒÈ¨õ1»­—,Òßtí„O#-Ä”%Ç[UF¦ò•ã=¨1•fw剬L˜>I±\eV#…â=¾Å¾_ÈÖ‰§4bõ‹Bé©DDìÙ^çM~>89HyãäÓÏõNê5F?÷ªàs ¨±f×P2ûƮ—zÀ.nTʸStž'¡ŽòpAbSb“`#±Élb,F¸ Ó3Z‰Oað!8„ +%w3¡Ø`ó%m*¡€Û€Ö[laHò5C!fÇox2±%_\9 F'uâ Òµ¨ód-*cÂùãõMqŽqŠê§lœŽ‘沞òÄ,$ô' “ŸûIiùÒNÝ‹ãÚ J«WjïØ›9µìã[vìÅ7€8râ¶-¢'ó‰mp<Ùn§v=o4ÈZF£ã FƒÕÂø˜á‚ ÌË©cÍk +|Ö—ê±ÞnÕ¤X†7qŒ¿1Ê9‰ˆÏ@£ÊrÿYþ4ªx•·.Œ/Œ*žž4®ZÓé}ü® YADˆÖ‘‰Ç’@â™h\çæ§7kä•Ë÷¾më&á£+ç¿×ìý¡†yhx)s_r®oé²_Þ#ø ZÖܳ;²„ÆX“ ² û|Z“'ÞÃUƒ–r눺dûC,ŠâÄH‰° +0}ÀÁ·b*/’}S*£ƒCרÕÕçÐlPlÕTiŠ50#RÅ1¸èêOìé§_ðùøÌå²#P)cÒ.² W³Í¼Åh!Z°P}±‚†!Û0ݦÀJŒ‘‘¹Æ(ß*Y—ZÍØd”­“QÒj,±Fd5sè[QL^šqN¨<7˜””dzþøFÃF LhOvåÞ¤HmÝ2Q¥ÇSç‰3&£Ùh 3†™ÂÌa–dc²)Ùœl©2V™ªÌU—Ñer™]p›¬.ërk‘•#呹gUìc‹ëGMIüñêìÐ䦛Âp*ˆƒ«º|'ÿÜÜ9¹‡*z„Ø‘Ûn?™ÈèG>Žä•qî5>ï%>yÌßz´T#h$™xC¬£ –Œ,ò©&:8®D"ݧ2ºÊG2$ÇLžñc¦1 ™R¾Tjà¥DÊ:Vâ2…LÑ¡+gËùÅâb©‘oVŠÝl7dÝ[˜ï³Û…âAÖ‡Ä7ÑR¢…—¤²4ׯÔ:z™®?;æ}%|%ÉP'~kø÷ÃýLÄð{ª\Ü6h’ +t‚&óAA“2Œ:– ‰B}5ѶµAëõ8pRp€Åhàƒô,! 8¬‹ ÒÇDà šIˆI0厷U+‹¾×¯»â\qEqBå… çŽ?QAýŽ9ÃmÒfÏ—vgÌ$ß•¥[¸Æ¹ 1Æ=ÎÖÝiF"Y†p$VAfÀBòp%ñýÄç S ¹‚ r…¢DÍxß4æSé{ëîºO¢Îëþø™~ÎÓ ïbîšxS-9åª_Q¿±mõ-GöÛ‹G?Ü6ÐO z|"¿nð8ökûõõøõÉ_é×'öëW •ÂÊ)àÆï +bÇøöñ‚s kŒSÿ•}]ÿÛýQóÿ«k¬O?õÈ𮪷z¥Û¥»1$‘ƒü\¶Ãâ[€‚ +4kÑ{€Ag1°áv÷dĨ9¬Û [®›¥ã*½¾øÔ×N±^xOø†ðYá\%?º­ØÇPlA¿Œt꣢Y5>÷ÇÂY+.*Ý6=ý×ÛÖ7<èHN ùë›k‡Ë¶nüäù-Ê‹·nÄgö߃ç|±{ouͼ“Ä×_ls‚Ò…>v]•ç}E¢ÇB–¹ÞÚÐá"×G‡GGiÐhªý¾øA6–Ì +0pjvhq#spð+Óñ0C.Ñs8æ¦'æ.È+6Äp_#O¤áý‹kJîÎÌÈÚ¸zñÝi'¾4ïôºÇN®vÍ{¦¾ëÁØö~œØÏôí9TZ\ô›–;ï)),ù5n¬v~pæˆrÒŒšà 2± ¥‚{dlEáèÇŽ‚`DÓ.ÆÉ­¾4ë +”+¤@·–]`Ù`ZAÒ.äcœäλ"È'øpÏ¢æ¹>“¦Ššx™'ØU©×xÜIvW÷DnŒd®J¿Ò=éרõ±eóN¯=öB—+÷gõ·ôDÚžVÎ?­dÞÒùøçºï…$›$^§ÄIk +KWÔýû[²÷^<ÿ“»,(,y×ã;ØãÔ÷;ôZ œYÔ˜5R¤çã°ðßj.63•µjãÐ Z™ÙÃZÍz1x<÷ +å±7³¸Ü1Iä%Ä‘U ^#éuŒA ï2¯ldLYIHµ:;‡Áat˜f‡µØª©ì;l/¡æä^ΔÝœ·çÝÈ›‰ÛÌ;Óšž0šÞÿÙð)pÕ €oþàð‹žrÌ2裎·…¹·'…ÙÂ2Cõº0‡}óýŠ ,(qÑk- âBµ:,B2a0Š|D™Mdg¾ÅóÅ ÷|ù]Þ†¿}bñEQê"§{;bmȆ“}“ýâüË5år¹¶\WZ¦õ¬vÎCóð´„~Ÿod™Óß½ÎÉyÁ-™ñ| „Cy–íþÑ/Œâõýë”>Z2°:²v bÛ#¿¶Îºó6¬ÿÝM?æf=[³¨&c,©­Õ}¿ <9uZÇ’ªëyST÷²Ÿ¼Jd¶ìa?Ä’ƒŽ™4ä™å-þ†÷·ÂÀûû[2-¬ÆŠ]þ¾h½æV­çwH%(²u2Nã$ÓÀøÊ¥–ÊÍHäfz~dûáŒÎc¿½#p5m `­èöëä+É—¼¨ÿ¸žä–ÔE“ÄҚʲ+‚•Ý]‚0Ñ3a½= ]‡BÌì¿«Ú5Üž¤wdÜx›º©Ú…g*¿Pãg'ØÉ ® +x>ï("=ÁSG>\tFè=^oô Øh€:ék»`Š?‘Jßþ$<_n˜e`ðl0º ù&A׳óé«×bGgX ¶Aã–VG +›Ó¶C”•5 Ë÷þ—e ßêÞ󌟅8[s]Žÿ¢Ê_>Â4]îé]±ê»B+ˆ¬Èñó +°F Oðw¼º®O¶— +8!¬å¬'K® æ9$zÄ ‘€a\èA„ù¯\}åéæð5Ìf#ʌĂ«üø(>IãóØ<®ž©ã;˜v¾SØÆlgwò2…ŽO‘î’u9Gìyñ!&àÅáÍ_@æœËöCÒ…Ñã@ò÷€'nvÌâI~Oó ™ÕÊÀ!¨œç¼c ¯d6Þkvý °Ä]ÖG\†Ÿ_¶,{5ët]všœÍgÉ ùR¹žqò«ùù6~›¼QÞ#ß%4àp9Iв~¼ä«M`£¥XM¤:Ó˜Øt~š4]NÕNÓ°¹ü¹^ò]†1GØ£Üø‡äG´'˜§øRŸü”N7~…÷ñÉøÁó3ñ÷úôVºRú6(h»…—{Øž¡*uè Ù‹ºÒâõŽ\.žç0¤æñ Hœ¬¤Ëñ“7­ ZÔÄKD•‚(pñ,'ŠB¦,r˜“À6É7D¢(I‡ÈŽÔ±ßô˜¥@ÕõµG–Ñ!ID•Žsd5WäxÞûq>üdðïÑL ÅG qRh&ž~w—Ƨ éb†|½vÊÃs¹l>[˜+æÉó´e¨ /âË„rq±†¬éÖ1ul-_'Ôkêä:íjÔ‰Ws`î]òjívv·‰¿]Ø&nÕlÝöáýì=Üø}Â=òÃèaíSìSÂ< §yã¹81Zž†§sÓÅirÎãòĹ7â±AîâÖÈwrÛÅ-òA|w@Ü'÷‹¿ÃÈúdß]z\©aˆÖ4Dܫà +¾þìŸçûLŒòÎÙ·pW5üÎð)ü¬’Å2e¾t÷*àݳ ;#žäÈ!:BFA”X^óÈhˆ‡ ¡×éy3èa¶éâɺ¼^«1!-+»Á ]o¢¿¡H@¬däMFÎäq-z²*¶ÄÑÈjÚ’WÜƽ£Åœa¹æ$.UÝ£S­O×çèWk@Òú­ú=zI@<–Éà‡|q€!EâH&ΡÉ3 Óyš<9WŸkèB]¸‹Y£Y#“)Ômè6îvj¶Ë·ko×m×ßaØJÚÇì“÷j÷êöéÉQÞS"™…Y:9†6>pëî°•ó›æd|ñâ3 óBÃ[«–®]Å¥a‰O¾r æD9ÈUƒÚ!Rú ÈWâHÀ½2ÿŽea©¶nWÞILæùEÕ…!sŸ}PyÅÍ7ϼkƒ-yÒÜô¤[Ÿ]Eû†zvûZêónŒ¾–0+ã®æu¶¬¨?²š”ÄEù{šï¹¸nWLxñ«˜SBðãBb‘å)Œ6’ßàÙè/!¿ˆ‡üŸÇ !_߃A1“Aéc?!úðé31-ÎfòÓÄÃbüx!‰Å1®8ìBq Ó¥ L4N›ÐqÌäØQ}ª ¦!õ—6©@[­Ù-uZGDJq’ûû†TÒê7}DÁ£ UE– !…w, Mµ¯qå‹b²Àäƒæý쇢D%?3dæ]ëCÒ&å¤%ïØ¢ôÝE%ïc­+tDÿ5<%Б±«i]XV HþºÄ²y{Zö]\ÿ½èðP²Ÿåßa¯C!(Âa¦ ²rUAÖ*](2†šÎ Cd;ýª!ö%È-&š¼HN”Fò!?ò®øwRÝò-/ï]VþØ +òiÌK†{—•=Â^×W”u=ù% 3ç/ì+ÊžAOɯÛ$µ“eýËwZžZnœñ7¦ªø7—ÞÈð¼ÿïo†Sõ9ÒNȧ%8<¿U‰Í +X~ËÿþæïOës²lïUÜ«¨Ž!+XièAöqtJ8Žvñþ(K<ˆžn@UL*:ņ¡ûÙ°+›8„n€ûoC{ù-…÷.¦ïÊJh_‡ Ž(8âàØÇIÒLZ CúðÜ,4ÜtñÝWöð·¢t~*âàÝ€Ò¹ƒ(]HEK¥3KÉ¡ôò³àz'\ÿ´‚÷TÂ¥ï¥ü.¸æƒt܇¨œ‡ô‰zÄÑ)~Ý•£pm¼Ÿ^J€†ÇŸ>:>ºrCx ÷;xÞÍãêÐ2x_Æ]@˘7ÐN8¿‘· \&ƒW¹Õsñ Ê%×¹Hû+—È3ì<øüÙ´îurÏ¢=ÂNô8÷ì•3p¼Ê¥B;ôÓ‡‰Œ d|ÐA8¼Ö¢û1‡·2ˆy“ù;‰-`²\—ÃÝÂ=È#>‹?)ÌþSÌÿ 9¤g4ó4{5Còt¹BÞ§ ÕžÐÅêöë¾€è÷}ýë†BÃQÃëF“ñˆñœé6³`n0Ÿ´ÜhyÖšcÝçìÓæëçû®_ß÷ý^ñ»èŸà_èïô?°6Pè ¼8ic +:œ¼&„ ù~È/BæÖb³Ùzí¡öþð”ð_F¤DJ‘ÿõƒ¨ÁèãѯĴÆô©v†ªØB¬Ï„H›20z°F04 ÏtÿÖT„öã÷9Œø¡ûœA"¾â>g‘•IvŸsp¾Ê}Î#sÀ}. óŠû\Bf–uŸkQ»À}®·Üû¨ûÜ€¦^ÿ÷9€•È}nFâŒ`s¸4…ŽNÎ1òÃgÜç 2à?»ÏY͈îsÎç¸ÏyÀlpŸ (’9â>—P8ó¾û\‹2Y_÷¹>*“u¹Ï ¨áú<÷¹ ù]ÿ{÷¹fèÐäB­¨µ¡FTî@6‹jP¼§ÐßJ‘ +g+ … Ým:P;m¨U£f”WóP ´¿f£&xÙPÉH_íôS-¼×Â3«á§ZÊ_cÔi#£–ÂH«a¬•ðL ´&tTÃ3ÿ؈Yp¶ž+CТÚVÓÞjéÕ”#ôÒ?[¡Í +è·ÚÙàyŒ^MïÉÍqµv·5Ö7tØbkâl)S¦¤ÚVtÛnlìhïh«­nN°åµÔ$Ùf75ÙJH«v[Im{mÛêZg’|Õ£ÓÈ£¥Õ«›WºZêm7V7\ãÁ¬Ú•Õe¶š†ê–úÚv[u[­­±ÅÖÚ¹¢©±Ææt5W7¶ecY\Hl‡Ëêà «[àC‘›±›¡M-|„nֳ¶ñí½ú²}'Ï–Q5µC+} +(+•|᢬¶­½ÑÕbKIJM;„g€Ä‰ ý'^ƒ¶::Šj$nöÐUçj™w€ +5¤0ƒL” /§»H·€ºvè£h®«¤¿6jDIÐo-<ƒ::Z3““ÐéêΤvWg[Mm«­¾6©¥nçxQà1:á_=½È=bȵt2Ô³.Ôm‰é7Mzš wº¡M}²îµR¾:èä!Rk£OéFz]=N’ãù°c&쵸‘á5ïªmTÙ·Ô®v2˜Â7É_Ë}÷Npb}òÜwdzÖA¯+l¦²^×\ ¯¢…pVLûk¦½Î²FJS½Wëæ«žŽÒâÖz‚[襁ÔÑTSí=Òå¢Úo¡Ï·ºg²:‚ zípÛX£Û +ªiª¤ewŸ”ŠñöTCÛ;T{÷ô@Z«´«¶\Kj{á^VN5GžuÒ÷vJW ¨}7º¥:Vû_εGr*µ­#ÝA鵺QŽº¨<š¿ÖžÙPG½z‹›ÃZ¯ô'#¾I¬„5´?µGÄŽ›ÜžÍ£¡:¶“RÜè¦4“ÎÎR7uÕУ‹z†Qxû¢Q \í Z }‡{6´ië™+£óöÞÏÙ(ÏÕ”r™úæ±¶¦JC%Õ_¢O‚6·î›éû¨ÿø:ºè ‘ˆDÖj7GIc$õeÏ™t»c‹::‘y¥Ñ鶤&j§m#WTJ‰L^:÷¶:O­¦±‘úŒ&úIáÈI)%újñ’Fý˜¸ªŽäñ¡ÕÔzTÛõŒ1^>í_É“‡JÙÍÁ¨…US}} +ÆŽ3^Ñ–àÖw}®ñÞ\ÑNõ³ÕÔ¯Œöë¹Ò>b‘žù2>zÔºý\-åÂ3RåÊIŸŸ †ð=þ îy¢m¸—•©s¦`\|YAç»Ë‹ÖN÷<ðØÉj¸Û8ÄjÑ*ç÷Ln…—½ª©G­yÂ[ï*Íž+ò„3¥zx}owÓXK-éZvâñuùn'-TïÞòšHª²—ä¼uøMçj;õšžX=:Û<3‰ ‡¦ìÑæ~bl­Ô¢WÁÏz·ÆÔxH¬JñªÿLOum®V¸çH‡;ÖH*eÓqŠP!|"ãÁ§R´pd ½—×l€ãJàN|"F%‹êe6½Cî‡ÓÙ¸ÎIEhíKí£~’¾— íÛF?“Oùоú"Ïf£r:F6ô¶(+‚sÒ÷|¸ZïÙîvä‰9pe|&çsA¡êxä¹”Ò¹Cž#´¨”–ÂõÑQÇR•GGôP6>•@ÿ¹î»äÇäÑþý ‘óB7ªäJhïDF¤gÒç ¨€~"WÁ{1´[Hå9›ò¬R[HyÈû*/Ù”U*Esè¨YB[?]SJ¥@F*u·L z$üdÑçɨù´•JY‘[Ëä|´—$·,U:ˆüËFF^Hù/€—ò_Jÿ8ÑÍlèßÓ¯ÇvæÒÝ2•Æ"Êßl*‡":´‘"‘gÁˆÅ•xie•Ñ¡<‹Ž4›JdᄜxzóÖÎDÖ!Œ0—ò—M%U@[/9fCû¼‘+ª=æQ^ç¸e­ö©Ú½j^ÒCy$š]£f»mj6•ÝX.ˆžSúG¹P50ÛýsŽ—ÌFµ_èÖ®‡žR:réRYLçb6m5›êzáÈÉ¡ów¾›òE#6ê¹í³h„²±òõÌ#O»¯ã;Ô¾Ò[&_U 2¨÷¢ÜIe-#µ†GÆ”©¿òÔ¸þï«Nßuüß©$©G^ÿ¼zò·ªø\³}³ŠìUñù²ºÃwQ¡é¸ª­4Ètò) ¡ºA‹ì}#»çF6ÜÙbÛkkm+j›\]qI¶¯±S.É6·©»µ¡ÝÖØÜêjë¨uÚêÚ\ͶÙmµ«Ý›À9ñKÿÉWoüÚ{mãFnl—«mmÕÎÚæê¶U6WÝø^d¹¸¶­¹±n¦kl·5Ô¶ÕÂXõmÕ-ÀzðlÁc ±¶úÚ[‡ËVÝÒmk­mk‡\+:@b ‚j[ -CËŽ†Zœjj\ͭМ4èh€ÞAʵ-í ½p*’ð8èÌi«nowÕ4VÃx²ÓUÓÙ\ÛÒQÝAè©kl%Å’é¶…®ºŽ.x¥¤­¶µÍå쬩¥Ý8±Æµ„yÌ  æš¦N'¡¤«±£ÁÕÙÄ47º"#´©¢„n;Û¡=a'ÁÖ\K¸–©´7$x‘@ÆLvµÙÚkAкHu³?nhBtÛJÝ!«¢£u5€a]õQC]g[ XKtºlí®[{犕µ5ä +á¯ÎÕÆFªqµ8 홲\ +ÝU¯p­®¥¨VD 1‚W¨¡]½J´Ò:jê=[{CuS“¼¢Ö-5 fIõ>]-`m¶fW[í„lÛ:º[këªa $•¨±w›«»a¶ÀãÎÆºFbhÕM`zpV;”sUtd‚V·]MÕm2ÈYÛÞXßBɨWç*iK¤3·°h~¶œS´¨0kvi^Q¡íÆl`eöÙ*mÀÊœ‚ÙyólY³çÏžKØñ Bš©ìŒŠC&ÌÍ.Ì.™]`[Xœ='œ€óJ²ç”Ò– {D%wNQáÂì‹à´ó ‘ /ÎͦC³áÿJe¿Ø%ý”•”޲8oav‚mvIÞB¢‘œ’" —è³(‡ZÀ"'Q^¡›^¢#ríjë€Väi7ƒYÙ³  Ã…„ ¸ i Ö•½¦¦¶µƒØ¶{r«®‘ºQÕw&P«U˜ðܘ¸ê5z +a f:ªw Ø$'¨®—º°nˆDªëu®®ØN\‰«MvgÒÕØNg:„Àf—ólíÕM0¶¯|d +|Æb‡¯4žö +ä$xKú6ßL¦u»Up$ÓÚ™“®ê%ÑõÕV¸6vµðË¿a˜ÜÕ¸ª1¹œÕš¤Ö†Öd·Çüv_%ßZFWþÇ¡ +4Á¿~f£ãÊ“ +ÛÅ>‘Â>¾ý±}¬ÝÀ?–Â>ª°Ç¢ØG ìÑ(öGûØ#—؇/±)ì3Ùö¶çðB¾g{xþlþðBöþö>öÐ>ö^™=¨°,ìþ[Ù{~ÆîSؽÐbïÿ«íL–š +ƒ(lê»Mi†K1$" +E5*j¤45Î$¨@Äá:à8‘¨Wq*gE¬ÊÂ'¡*—>S­ù.èM÷©>}úô´Çeé{^–<¾çYü–”Eå[’¯Êå³òIùø!%•)Þ§y§¼‰ñZy¥¼T”ÊsÅqÄsy¦<ð¤R“'J¥\’JÊ‚Užw¤\¢œµæ+ª«GúrìQv+»2¤ëì²]éQ¶)ݦݽ“­aºKW;[Rlî´e³K§ã󋓦#—vÉH»²É M56þÆ$mÒÖHÛòŸ_ÙŸÖ†©5¤²Vk˜Co©’¬²>áÈz—D<* ‡x”æ˜#ÍÄÖ)MÊÚ:ÑHB¢JĨF„•FÅ6 +v•Yò‚ŒâWV›Öê* †Þ ˆ¹B2XY=æß§ì8>?¾¬µªß²Ï}ûÅ×½²±j…õÿ7ZÿÌP°endstream +endobj +311 0 obj +<< +/Ascent 759.7656 /CapHeight 759.7656 /Descent -240.2344 /Flags 68 /FontBBox [ -1015.625 -350.0977 1659.18 1067.871 ] /FontFile2 310 0 R + /FontName /AAAAAA+DejaVuSans-Oblique /ItalicAngle -11 /MissingWidth 600.0977 /StemV 87 /Type /FontDescriptor +>> +endobj +312 0 obj +<< +/BaseFont /AAAAAA+DejaVuSans-Oblique /FirstChar 0 /FontDescriptor 311 0 R /LastChar 127 /Name /F8+0 /Subtype /TrueType + /ToUnicode 309 0 R /Type /Font /Widths [ 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 317.8711 400.8789 459.9609 837.8906 636.2305 950.1953 779.7852 274.9023 + 390.1367 390.1367 500 837.8906 317.8711 360.8398 317.8711 336.9141 636.2305 636.2305 + 636.2305 636.2305 636.2305 636.2305 636.2305 636.2305 636.2305 636.2305 336.9141 336.9141 + 837.8906 837.8906 837.8906 530.7617 1000 684.082 686.0352 698.2422 770.0195 631.8359 + 575.1953 774.9023 751.9531 294.9219 294.9219 655.7617 557.1289 862.793 748.0469 787.1094 + 603.0273 787.1094 694.8242 634.7656 610.8398 731.9336 684.082 988.7695 685.0586 610.8398 + 685.0586 390.1367 336.9141 390.1367 837.8906 500 500 612.793 634.7656 549.8047 + 634.7656 615.2344 352.0508 634.7656 633.7891 277.832 277.832 579.1016 277.832 974.1211 + 633.7891 611.8164 634.7656 634.7656 411.1328 520.9961 392.0898 633.7891 591.7969 817.8711 + 591.7969 591.7969 524.9023 636.2305 336.9141 636.2305 837.8906 600.0977 ] +>> +endobj +313 0 obj +<< +/Outlines 315 0 R /PageLabels 439 0 R /PageMode /UseNone /Pages 397 0 R /Type /Catalog +>> +endobj +314 0 obj +<< +/Author () /CreationDate (D:20260121160837+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260121160837+00'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (\(unspecified\)) /Title (Procedure Call Standard for the Arm\256 Architecture) /Trapped /False +>> +endobj +315 0 obj +<< +/Count 90 /First 316 0 R /Last 393 0 R /Type /Outlines +>> +endobj +316 0 obj +<< +/Count 8 /Dest [ 7 0 R /XYZ 57.02362 765.0236 0 ] /First 317 0 R /Last 324 0 R /Next 325 0 R /Parent 315 0 R + /Title (\376\377\0001\000\240\000\240\000\240\000P\000r\000e\000a\000m\000b\000l\000e) +>> +endobj +317 0 obj +<< +/Dest [ 7 0 R /XYZ 57.02362 716.2236 0 ] /Next 318 0 R /Parent 316 0 R /Title (\376\377\0001\000.\0001\000\240\000\240\000\240\000A\000b\000s\000t\000r\000a\000c\000t) +>> +endobj +318 0 obj +<< +/Dest [ 7 0 R /XYZ 57.02362 650.6236 0 ] /Next 319 0 R /Parent 316 0 R /Prev 317 0 R /Title (\376\377\0001\000.\0002\000\240\000\240\000\240\000K\000e\000y\000w\000o\000r\000d\000s) +>> +endobj +319 0 obj +<< +/Dest [ 7 0 R /XYZ 57.02362 595.8236 0 ] /Next 320 0 R /Parent 316 0 R /Prev 318 0 R /Title (\376\377\0001\000.\0003\000\240\000\240\000\240\000L\000a\000t\000e\000s\000t\000 \000r\000e\000l\000e\000a\000s\000e\000 \000a\000n\000d\000 \000d\000e\000f\000e\000c\000t\000s\000 \000r\000e\000p\000o\000r\000t) +>> +endobj +320 0 obj +<< +/Dest [ 13 0 R /XYZ 57.02362 765.0236 0 ] /Next 321 0 R /Parent 316 0 R /Prev 319 0 R /Title (\376\377\0001\000.\0004\000\240\000\240\000\240\000L\000i\000c\000e\000n\000c\000e) +>> +endobj +321 0 obj +<< +/Dest [ 13 0 R /XYZ 57.02362 574.6236 0 ] /Next 322 0 R /Parent 316 0 R /Prev 320 0 R /Title (\376\377\0001\000.\0005\000\240\000\240\000\240\000A\000b\000o\000u\000t\000 \000t\000h\000e\000 \000l\000i\000c\000e\000n\000s\000e) +>> +endobj +322 0 obj +<< +/Dest [ 13 0 R /XYZ 57.02362 399.8236 0 ] /Next 323 0 R /Parent 316 0 R /Prev 321 0 R /Title (\376\377\0001\000.\0006\000\240\000\240\000\240\000C\000o\000n\000t\000r\000i\000b\000u\000t\000i\000o\000n\000s) +>> +endobj +323 0 obj +<< +/Dest [ 13 0 R /XYZ 57.02362 334.2236 0 ] /Next 324 0 R /Parent 316 0 R /Prev 322 0 R /Title (\376\377\0001\000.\0007\000\240\000\240\000\240\000T\000r\000a\000d\000e\000m\000a\000r\000k\000 \000n\000o\000t\000i\000c\000e) +>> +endobj +324 0 obj +<< +/Dest [ 13 0 R /XYZ 57.02362 236.2236 0 ] /Parent 316 0 R /Prev 323 0 R /Title (\376\377\0001\000.\0008\000\240\000\240\000\240\000C\000o\000p\000y\000r\000i\000g\000h\000t) +>> +endobj +325 0 obj +<< +/Count 6 /Dest [ 158 0 R /XYZ 57.02362 765.0236 0 ] /First 326 0 R /Last 331 0 R /Next 332 0 R /Parent 315 0 R + /Prev 316 0 R /Title (\376\377\0002\000\240\000\240\000\240\000A\000b\000o\000u\000t\000 \000T\000h\000i\000s\000 \000D\000o\000c\000u\000m\000e\000n\000t) +>> +endobj +326 0 obj +<< +/Count 2 /Dest [ 158 0 R /XYZ 57.02362 716.2236 0 ] /First 327 0 R /Last 328 0 R /Next 329 0 R /Parent 325 0 R + /Title (\376\377\0002\000.\0001\000\240\000\240\000\240\000C\000h\000a\000n\000g\000e\000 \000C\000o\000n\000t\000r\000o\000l) +>> +endobj +327 0 obj +<< +/Dest [ 158 0 R /XYZ 57.02362 678.2236 0 ] /Next 328 0 R /Parent 326 0 R /Title (\376\377\0002\000.\0001\000.\0001\000\240\000\240\000\240\000C\000u\000r\000r\000e\000n\000t\000 \000S\000t\000a\000t\000u\000s\000 \000a\000n\000d\000 \000A\000n\000t\000i\000c\000i\000p\000a\000t\000e\000d\000 \000C\000h\000a\000n\000g\000e\000s) +>> +endobj +328 0 obj +<< +/Dest [ 158 0 R /XYZ 57.02362 437.4236 0 ] /Parent 326 0 R /Prev 327 0 R /Title (\376\377\0002\000.\0001\000.\0002\000\240\000\240\000\240\000C\000h\000a\000n\000g\000e\000 \000H\000i\000s\000t\000o\000r\000y) +>> +endobj +329 0 obj +<< +/Dest [ 216 0 R /XYZ 57.02362 573.8236 0 ] /Next 330 0 R /Parent 325 0 R /Prev 326 0 R /Title (\376\377\0002\000.\0002\000\240\000\240\000\240\000R\000e\000f\000e\000r\000e\000n\000c\000e\000s) +>> +endobj +330 0 obj +<< +/Dest [ 216 0 R /XYZ 57.02362 243.4236 0 ] /Next 331 0 R /Parent 325 0 R /Prev 329 0 R /Title (\376\377\0002\000.\0003\000\240\000\240\000\240\000T\000e\000r\000m\000s\000 \000a\000n\000d\000 \000A\000b\000b\000r\000e\000v\000i\000a\000t\000i\000o\000n\000s) +>> +endobj +331 0 obj +<< +/Dest [ 221 0 R /XYZ 57.02362 574.6236 0 ] /Parent 325 0 R /Prev 330 0 R /Title (\376\377\0002\000.\0004\000\240\000\240\000\240\000A\000c\000k\000n\000o\000w\000l\000e\000d\000g\000e\000m\000e\000n\000t\000s) +>> +endobj +332 0 obj +<< +/Dest [ 223 0 R /XYZ 57.02362 765.0236 0 ] /Next 333 0 R /Parent 315 0 R /Prev 325 0 R /Title (\376\377\0003\000\240\000\240\000\240\000S\000c\000o\000p\000e) +>> +endobj +333 0 obj +<< +/Count 2 /Dest [ 228 0 R /XYZ 57.02362 765.0236 0 ] /First 334 0 R /Last 335 0 R /Next 336 0 R /Parent 315 0 R + /Prev 332 0 R /Title (\376\377\0004\000\240\000\240\000\240\000I\000n\000t\000r\000o\000d\000u\000c\000t\000i\000o\000n) +>> +endobj +334 0 obj +<< +/Dest [ 228 0 R /XYZ 57.02362 688.6236 0 ] /Next 335 0 R /Parent 333 0 R /Title (\376\377\0004\000.\0001\000\240\000\240\000\240\000D\000e\000s\000i\000g\000n\000 \000G\000o\000a\000l\000s) +>> +endobj +335 0 obj +<< +/Dest [ 228 0 R /XYZ 57.02362 539.8236 0 ] /Parent 333 0 R /Prev 334 0 R /Title (\376\377\0004\000.\0002\000\240\000\240\000\240\000C\000o\000n\000f\000o\000r\000m\000a\000n\000c\000e) +>> +endobj +336 0 obj +<< +/Count 10 /Dest [ 232 0 R /XYZ 57.02362 765.0236 0 ] /First 337 0 R /Last 341 0 R /Next 347 0 R /Parent 315 0 R + /Prev 333 0 R /Title (\376\377\0005\000\240\000\240\000\240\000D\000a\000t\000a\000 \000T\000y\000p\000e\000s\000 \000a\000n\000d\000 \000A\000l\000i\000g\000n\000m\000e\000n\000t) +>> +endobj +337 0 obj +<< +/Count 2 /Dest [ 232 0 R /XYZ 57.02362 716.2236 0 ] /First 338 0 R /Last 339 0 R /Next 340 0 R /Parent 336 0 R + /Title (\376\377\0005\000.\0001\000\240\000\240\000\240\000F\000u\000n\000d\000a\000m\000e\000n\000t\000a\000l\000 \000D\000a\000t\000a\000 \000T\000y\000p\000e\000s) +>> +endobj +338 0 obj +<< +/Dest [ 232 0 R /XYZ 57.02362 213.4236 0 ] /Next 339 0 R /Parent 337 0 R /Title (\376\377\0005\000.\0001\000.\0001\000\240\000\240\000\240\000H\000a\000l\000f\000-\000p\000r\000e\000c\000i\000s\000i\000o\000n\000 \000F\000l\000o\000a\000t\000i\000n\000g\000 \000P\000o\000i\000n\000t) +>> +endobj +339 0 obj +<< +/Dest [ 234 0 R /XYZ 57.02362 723.4236 0 ] /Parent 337 0 R /Prev 338 0 R /Title (\376\377\0005\000.\0001\000.\0002\000\240\000\240\000\240\000C\000o\000n\000t\000a\000i\000n\000e\000r\000i\000z\000e\000d\000 \000V\000e\000c\000t\000o\000r\000s) +>> +endobj +340 0 obj +<< +/Dest [ 234 0 R /XYZ 57.02362 639.2236 0 ] /Next 341 0 R /Parent 336 0 R /Prev 337 0 R /Title (\376\377\0005\000.\0002\000\240\000\240\000\240\000E\000n\000d\000i\000a\000n\000n\000e\000s\000s\000 \000a\000n\000d\000 \000B\000y\000t\000e\000 \000O\000r\000d\000e\000r\000i\000n\000g) +>> +endobj +341 0 obj +<< +/Count 5 /Dest [ 237 0 R /XYZ 57.02362 765.0236 0 ] /First 342 0 R /Last 346 0 R /Parent 336 0 R /Prev 340 0 R + /Title (\376\377\0005\000.\0003\000\240\000\240\000\240\000C\000o\000m\000p\000o\000s\000i\000t\000e\000 \000T\000y\000p\000e\000s) +>> +endobj +342 0 obj +<< +/Dest [ 237 0 R /XYZ 57.02362 546.2236 0 ] /Next 343 0 R /Parent 341 0 R /Title (\376\377\0005\000.\0003\000.\0001\000\240\000\240\000\240\000A\000g\000g\000r\000e\000g\000a\000t\000e\000s) +>> +endobj +343 0 obj +<< +/Dest [ 237 0 R /XYZ 57.02362 450.8236 0 ] /Next 344 0 R /Parent 341 0 R /Prev 342 0 R /Title (\376\377\0005\000.\0003\000.\0002\000\240\000\240\000\240\000U\000n\000i\000o\000n\000s) +>> +endobj +344 0 obj +<< +/Dest [ 237 0 R /XYZ 57.02362 355.4236 0 ] /Next 345 0 R /Parent 341 0 R /Prev 343 0 R /Title (\376\377\0005\000.\0003\000.\0003\000\240\000\240\000\240\000A\000r\000r\000a\000y\000s) +>> +endobj +345 0 obj +<< +/Dest [ 237 0 R /XYZ 57.02362 260.0236 0 ] /Next 346 0 R /Parent 341 0 R /Prev 344 0 R /Title (\376\377\0005\000.\0003\000.\0004\000\240\000\240\000\240\000B\000i\000t\000-\000f\000i\000e\000l\000d\000s) +>> +endobj +346 0 obj +<< +/Dest [ 238 0 R /XYZ 57.02362 765.0236 0 ] /Parent 341 0 R /Prev 345 0 R /Title (\376\377\0005\000.\0003\000.\0005\000\240\000\240\000\240\000H\000o\000m\000o\000g\000e\000n\000e\000o\000u\000s\000 \000A\000g\000g\000r\000e\000g\000a\000t\000e\000s) +>> +endobj +347 0 obj +<< +/Count 10 /Dest [ 239 0 R /XYZ 57.02362 765.0236 0 ] /First 348 0 R /Last 363 0 R /Next 364 0 R /Parent 315 0 R + /Prev 336 0 R /Title (\376\377\0006\000\240\000\240\000\240\000T\000h\000e\000 \000B\000a\000s\000e\000 \000P\000r\000o\000c\000e\000d\000u\000r\000e\000 \000C\000a\000l\000l\000 \000S\000t\000a\000n\000d\000a\000r\000d) +>> +endobj +348 0 obj +<< +/Count 2 /Dest [ 239 0 R /XYZ 57.02362 677.8236 0 ] /First 349 0 R /Last 351 0 R /Next 353 0 R /Parent 347 0 R + /Title (\376\377\0006\000.\0001\000\240\000\240\000\240\000M\000a\000c\000h\000i\000n\000e\000 \000R\000e\000g\000i\000s\000t\000e\000r\000s) +>> +endobj +349 0 obj +<< +/Count -1 /Dest [ 239 0 R /XYZ 57.02362 601.4236 0 ] /First 350 0 R /Last 350 0 R /Next 351 0 R /Parent 348 0 R + /Title (\376\377\0006\000.\0001\000.\0001\000\240\000\240\000\240\000C\000o\000r\000e\000 \000r\000e\000g\000i\000s\000t\000e\000r\000s) +>> +endobj +350 0 obj +<< +/Dest [ 241 0 R /XYZ 57.02362 285.0236 0 ] /Parent 349 0 R /Title (\376\377\0006\000.\0001\000.\0001\000.\0001\000\240\000\240\000\240\000H\000a\000n\000d\000l\000i\000n\000g\000 \000v\000a\000l\000u\000e\000s\000 \000l\000a\000r\000g\000e\000r\000 \000t\000h\000a\000n\000 \0003\0002\000 \000b\000i\000t\000s) +>> +endobj +351 0 obj +<< +/Count -1 /Dest [ 242 0 R /XYZ 57.02362 765.0236 0 ] /First 352 0 R /Last 352 0 R /Parent 348 0 R /Prev 349 0 R + /Title (\376\377\0006\000.\0001\000.\0002\000\240\000\240\000\240\000C\000o\000-\000p\000r\000o\000c\000e\000s\000s\000o\000r\000 \000R\000e\000g\000i\000s\000t\000e\000r\000s) +>> +endobj +352 0 obj +<< +/Dest [ 242 0 R /XYZ 57.02362 596.4236 0 ] /Parent 351 0 R /Title (\376\377\0006\000.\0001\000.\0002\000.\0001\000\240\000\240\000\240\000V\000F\000P\000 \000r\000e\000g\000i\000s\000t\000e\000r\000 \000u\000s\000a\000g\000e\000 \000c\000o\000n\000v\000e\000n\000t\000i\000o\000n\000s) +>> +endobj +353 0 obj +<< +/Count 1 /Dest [ 242 0 R /XYZ 57.02362 180.8236 0 ] /First 354 0 R /Last 354 0 R /Next 359 0 R /Parent 347 0 R + /Prev 348 0 R /Title (\376\377\0006\000.\0002\000\240\000\240\000\240\000P\000r\000o\000c\000e\000s\000s\000e\000s\000,\000 \000M\000e\000m\000o\000r\000y\000 \000a\000n\000d\000 \000t\000h\000e\000 \000S\000t\000a\000c\000k) +>> +endobj +354 0 obj +<< +/Count -4 /Dest [ 243 0 R /XYZ 57.02362 525.0236 0 ] /First 355 0 R /Last 358 0 R /Parent 353 0 R /Title (\376\377\0006\000.\0002\000.\0001\000\240\000\240\000\240\000T\000h\000e\000 \000S\000t\000a\000c\000k) +>> +endobj +355 0 obj +<< +/Dest [ 243 0 R /XYZ 57.02362 379.6236 0 ] /Next 356 0 R /Parent 354 0 R /Title (\376\377\0006\000.\0002\000.\0001\000.\0001\000\240\000\240\000\240\000U\000n\000i\000v\000e\000r\000s\000a\000l\000 \000s\000t\000a\000c\000k\000 \000c\000o\000n\000s\000t\000r\000a\000i\000n\000t\000s) +>> +endobj +356 0 obj +<< +/Dest [ 244 0 R /XYZ 57.02362 765.0236 0 ] /Next 357 0 R /Parent 354 0 R /Prev 355 0 R /Title (\376\377\0006\000.\0002\000.\0001\000.\0002\000\240\000\240\000\240\000S\000t\000a\000c\000k\000 \000c\000o\000n\000s\000t\000r\000a\000i\000n\000t\000s\000 \000a\000t\000 \000a\000 \000p\000u\000b\000l\000i\000c\000 \000i\000n\000t\000e\000r\000f\000a\000c\000e) +>> +endobj +357 0 obj +<< +/Dest [ 244 0 R /XYZ 57.02362 689.4236 0 ] /Next 358 0 R /Parent 354 0 R /Prev 356 0 R /Title (\376\377\0006\000.\0002\000.\0001\000.\0003\000\240\000\240\000\240\000S\000t\000a\000c\000k\000 \000p\000r\000o\000b\000i\000n\000g) +>> +endobj +358 0 obj +<< +/Dest [ 244 0 R /XYZ 57.02362 597.4236 0 ] /Parent 354 0 R /Prev 357 0 R /Title (\376\377\0006\000.\0002\000.\0001\000.\0004\000\240\000\240\000\240\000T\000h\000e\000 \000F\000r\000a\000m\000e\000 \000P\000o\000i\000n\000t\000e\000r) +>> +endobj +359 0 obj +<< +/Count 1 /Dest [ 248 0 R /XYZ 57.02362 707.4236 0 ] /First 360 0 R /Last 360 0 R /Next 361 0 R /Parent 347 0 R + /Prev 353 0 R /Title (\376\377\0006\000.\0003\000\240\000\240\000\240\000S\000u\000b\000r\000o\000u\000t\000i\000n\000e\000 \000C\000a\000l\000l\000s) +>> +endobj +360 0 obj +<< +/Dest [ 248 0 R /XYZ 57.02362 275.8236 0 ] /Parent 359 0 R /Title (\376\377\0006\000.\0003\000.\0001\000\240\000\240\000\240\000U\000s\000e\000 \000o\000f\000 \000I\000P\000 \000b\000y\000 \000t\000h\000e\000 \000l\000i\000n\000k\000e\000r) +>> +endobj +361 0 obj +<< +/Dest [ 253 0 R /XYZ 57.02362 765.0236 0 ] /Next 362 0 R /Parent 347 0 R /Prev 359 0 R /Title (\376\377\0006\000.\0004\000\240\000\240\000\240\000R\000e\000s\000u\000l\000t\000 \000R\000e\000t\000u\000r\000n) +>> +endobj +362 0 obj +<< +/Dest [ 253 0 R /XYZ 57.02362 490.2236 0 ] /Next 363 0 R /Parent 347 0 R /Prev 361 0 R /Title (\376\377\0006\000.\0005\000\240\000\240\000\240\000P\000a\000r\000a\000m\000e\000t\000e\000r\000 \000P\000a\000s\000s\000i\000n\000g) +>> +endobj +363 0 obj +<< +/Dest [ 255 0 R /XYZ 57.02362 154.6236 0 ] /Parent 347 0 R /Prev 362 0 R /Title (\376\377\0006\000.\0006\000\240\000\240\000\240\000I\000n\000t\000e\000r\000w\000o\000r\000k\000i\000n\000g) +>> +endobj +364 0 obj +<< +/Count 10 /Dest [ 260 0 R /XYZ 57.02362 765.0236 0 ] /First 365 0 R /Last 373 0 R /Next 378 0 R /Parent 315 0 R + /Prev 347 0 R /Title (\376\377\0007\000\240\000\240\000\240\000T\000h\000e\000 \000S\000t\000a\000n\000d\000a\000r\000d\000 \000V\000a\000r\000i\000a\000n\000t\000s) +>> +endobj +365 0 obj +<< +/Count 2 /Dest [ 260 0 R /XYZ 57.02362 688.6236 0 ] /First 366 0 R /Last 367 0 R /Next 371 0 R /Parent 364 0 R + /Title (\376\377\0007\000.\0001\000\240\000\240\000\240\000V\000F\000P\000 \000a\000n\000d\000 \000S\000I\000M\000D\000 \000v\000e\000c\000t\000o\000r\000 \000R\000e\000g\000i\000s\000t\000e\000r\000 \000A\000r\000g\000u\000m\000e\000n\000t\000s) +>> +endobj +366 0 obj +<< +/Dest [ 260 0 R /XYZ 57.02362 612.2236 0 ] /Next 367 0 R /Parent 365 0 R /Title (\376\377\0007\000.\0001\000.\0001\000\240\000\240\000\240\000M\000a\000p\000p\000i\000n\000g\000 \000b\000e\000t\000w\000e\000e\000n\000 \000r\000e\000g\000i\000s\000t\000e\000r\000s\000 \000a\000n\000d\000 \000m\000e\000m\000o\000r\000y\000 \000f\000o\000r\000m\000a\000t) +>> +endobj +367 0 obj +<< +/Count -3 /Dest [ 260 0 R /XYZ 57.02362 401.6236 0 ] /First 368 0 R /Last 370 0 R /Parent 365 0 R /Prev 366 0 R + /Title (\376\377\0007\000.\0001\000.\0002\000\240\000\240\000\240\000P\000r\000o\000c\000e\000d\000u\000r\000e\000 \000C\000a\000l\000l\000i\000n\000g) +>> +endobj +368 0 obj +<< +/Dest [ 260 0 R /XYZ 57.02362 349.8236 0 ] /Next 369 0 R /Parent 367 0 R /Title (\376\377\0007\000.\0001\000.\0002\000.\0001\000\240\000\240\000\240\000V\000F\000P\000 \000c\000o\000-\000p\000r\000o\000c\000e\000s\000s\000o\000r\000 \000r\000e\000g\000i\000s\000t\000e\000r\000 \000c\000a\000n\000d\000i\000d\000a\000t\000e\000s) +>> +endobj +369 0 obj +<< +/Dest [ 263 0 R /XYZ 57.02362 765.0236 0 ] /Next 370 0 R /Parent 367 0 R /Prev 368 0 R /Title (\376\377\0007\000.\0001\000.\0002\000.\0002\000\240\000\240\000\240\000R\000e\000s\000u\000l\000t\000 \000r\000e\000t\000u\000r\000n) +>> +endobj +370 0 obj +<< +/Dest [ 263 0 R /XYZ 57.02362 688.6236 0 ] /Parent 367 0 R /Prev 369 0 R /Title (\376\377\0007\000.\0001\000.\0002\000.\0003\000\240\000\240\000\240\000P\000a\000r\000a\000m\000e\000t\000e\000r\000 \000p\000a\000s\000s\000i\000n\000g) +>> +endobj +371 0 obj +<< +/Dest [ 263 0 R /XYZ 57.02362 434.6236 0 ] /Next 372 0 R /Parent 364 0 R /Prev 365 0 R /Title (\376\377\0007\000.\0002\000\240\000\240\000\240\000A\000r\000m\000 \000A\000l\000t\000e\000r\000n\000a\000t\000i\000v\000e\000 \000F\000o\000r\000m\000a\000t\000 \000H\000a\000l\000f\000-\000p\000r\000e\000c\000i\000s\000i\000o\000n\000 \000F\000l\000o\000a\000t\000i\000n\000g\000 \000P\000o\000i\000n\000t\000 \000v\000a\000l\000u\000e\000s) +>> +endobj +372 0 obj +<< +/Dest [ 263 0 R /XYZ 57.02362 351.0236 0 ] /Next 373 0 R /Parent 364 0 R /Prev 371 0 R /Title (\376\377\0007\000.\0003\000\240\000\240\000\240\000R\000e\000a\000d\000-\000W\000r\000i\000t\000e\000 \000P\000o\000s\000i\000t\000i\000o\000n\000 \000I\000n\000d\000e\000p\000e\000n\000d\000e\000n\000c\000e\000 \000\(\000R\000W\000P\000I\000\)) +>> +endobj +373 0 obj +<< +/Count 4 /Dest [ 263 0 R /XYZ 57.02362 263.8236 0 ] /First 374 0 R /Last 377 0 R /Parent 364 0 R /Prev 372 0 R + /Title (\376\377\0007\000.\0004\000\240\000\240\000\240\000V\000a\000r\000i\000a\000n\000t\000 \000C\000o\000m\000p\000a\000t\000i\000b\000i\000l\000i\000t\000y) +>> +endobj +374 0 obj +<< +/Dest [ 263 0 R /XYZ 57.02362 165.8236 0 ] /Next 375 0 R /Parent 373 0 R /Title (\376\377\0007\000.\0004\000.\0001\000\240\000\240\000\240\000V\000F\000P\000 \000a\000n\000d\000 \000B\000a\000s\000e\000 \000S\000t\000a\000n\000d\000a\000r\000d\000 \000C\000o\000m\000p\000a\000t\000i\000b\000i\000l\000i\000t\000y) +>> +endobj +375 0 obj +<< +/Dest [ 264 0 R /XYZ 57.02362 765.0236 0 ] /Next 376 0 R /Parent 373 0 R /Prev 374 0 R /Title (\376\377\0007\000.\0004\000.\0002\000\240\000\240\000\240\000R\000W\000P\000I\000 \000a\000n\000d\000 \000B\000a\000s\000e\000 \000S\000t\000a\000n\000d\000a\000r\000d\000 \000C\000o\000m\000p\000a\000t\000i\000b\000i\000l\000i\000t\000y) +>> +endobj +376 0 obj +<< +/Dest [ 264 0 R /XYZ 57.02362 702.4236 0 ] /Next 377 0 R /Parent 373 0 R /Prev 375 0 R /Title (\376\377\0007\000.\0004\000.\0003\000\240\000\240\000\240\000V\000F\000P\000 \000a\000n\000d\000 \000R\000W\000P\000I\000 \000S\000t\000a\000n\000d\000a\000r\000d\000 \000C\000o\000m\000p\000a\000t\000i\000b\000i\000l\000i\000t\000y) +>> +endobj +377 0 obj +<< +/Dest [ 264 0 R /XYZ 57.02362 639.8236 0 ] /Parent 373 0 R /Prev 376 0 R /Title (\376\377\0007\000.\0004\000.\0004\000\240\000\240\000\240\000H\000a\000l\000f\000-\000p\000r\000e\000c\000i\000s\000i\000o\000n\000 \000F\000o\000r\000m\000a\000t\000 \000C\000o\000m\000p\000a\000t\000i\000b\000i\000l\000i\000t\000y) +>> +endobj +378 0 obj +<< +/Count 9 /Dest [ 267 0 R /XYZ 57.02362 765.0236 0 ] /First 379 0 R /Last 392 0 R /Next 393 0 R /Parent 315 0 R + /Prev 364 0 R /Title (\376\377\0008\000\240\000\240\000\240\000A\000r\000m\000 \000C\000 \000a\000n\000d\000 \000C\000+\000+\000 \000L\000a\000n\000g\000u\000a\000g\000e\000 \000M\000a\000p\000p\000i\000n\000g\000s) +>> +endobj +379 0 obj +<< +/Count 7 /Dest [ 267 0 R /XYZ 57.02362 677.8236 0 ] /First 380 0 R /Last 386 0 R /Next 392 0 R /Parent 378 0 R + /Title (\376\377\0008\000.\0001\000\240\000\240\000\240\000D\000a\000t\000a\000 \000T\000y\000p\000e\000s) +>> +endobj +380 0 obj +<< +/Dest [ 267 0 R /XYZ 57.02362 639.8236 0 ] /Next 381 0 R /Parent 379 0 R /Title (\376\377\0008\000.\0001\000.\0001\000\240\000\240\000\240\000A\000r\000i\000t\000h\000m\000e\000t\000i\000c\000 \000T\000y\000p\000e\000s) +>> +endobj +381 0 obj +<< +/Dest [ 270 0 R /XYZ 57.02362 765.0236 0 ] /Next 382 0 R /Parent 379 0 R /Prev 380 0 R /Title (\376\377\0008\000.\0001\000.\0002\000\240\000\240\000\240\000P\000o\000i\000n\000t\000e\000r\000 \000T\000y\000p\000e\000s) +>> +endobj +382 0 obj +<< +/Dest [ 270 0 R /XYZ 57.02362 580.4236 0 ] /Next 383 0 R /Parent 379 0 R /Prev 381 0 R /Title (\376\377\0008\000.\0001\000.\0003\000\240\000\240\000\240\000E\000n\000u\000m\000e\000r\000a\000t\000e\000d\000 \000T\000y\000p\000e\000s) +>> +endobj +383 0 obj +<< +/Dest [ 274 0 R /XYZ 57.02362 765.0236 0 ] /Next 384 0 R /Parent 379 0 R /Prev 382 0 R /Title (\376\377\0008\000.\0001\000.\0004\000\240\000\240\000\240\000A\000d\000d\000i\000t\000i\000o\000n\000a\000l\000 \000T\000y\000p\000e\000s) +>> +endobj +384 0 obj +<< +/Dest [ 274 0 R /XYZ 57.02362 514.8236 0 ] /Next 385 0 R /Parent 379 0 R /Prev 383 0 R /Title (\376\377\0008\000.\0001\000.\0005\000\240\000\240\000\240\000V\000o\000l\000a\000t\000i\000l\000e\000 \000D\000a\000t\000a\000 \000T\000y\000p\000e\000s) +>> +endobj +385 0 obj +<< +/Dest [ 274 0 R /XYZ 57.02362 267.4236 0 ] /Next 386 0 R /Parent 379 0 R /Prev 384 0 R /Title (\376\377\0008\000.\0001\000.\0006\000\240\000\240\000\240\000S\000t\000r\000u\000c\000t\000u\000r\000e\000,\000 \000U\000n\000i\000o\000n\000 \000a\000n\000d\000 \000C\000l\000a\000s\000s\000 \000L\000a\000y\000o\000u\000t) +>> +endobj +386 0 obj +<< +/Count -5 /Dest [ 274 0 R /XYZ 57.02362 194.0236 0 ] /First 387 0 R /Last 391 0 R /Parent 379 0 R /Prev 385 0 R + /Title (\376\377\0008\000.\0001\000.\0007\000\240\000\240\000\240\000B\000i\000t\000-\000f\000i\000e\000l\000d\000s) +>> +endobj +387 0 obj +<< +/Dest [ 276 0 R /XYZ 57.02362 567.8236 0 ] /Next 388 0 R /Parent 386 0 R /Title (\376\377\0008\000.\0001\000.\0007\000.\0001\000\240\000\240\000\240\000B\000i\000t\000-\000f\000i\000e\000l\000d\000s\000 \000n\000o\000 \000l\000a\000r\000g\000e\000r\000 \000t\000h\000a\000n\000 \000t\000h\000e\000i\000r\000 \000c\000o\000n\000t\000a\000i\000n\000e\000r) +>> +endobj +388 0 obj +<< +/Dest [ 280 0 R /XYZ 57.02362 441.8236 0 ] /Next 389 0 R /Parent 386 0 R /Prev 387 0 R /Title (\376\377\0008\000.\0001\000.\0007\000.\0002\000\240\000\240\000\240\000B\000i\000t\000-\000f\000i\000e\000l\000d\000 \000e\000x\000t\000r\000a\000c\000t\000i\000o\000n\000 \000e\000x\000p\000r\000e\000s\000s\000i\000o\000n\000s) +>> +endobj +389 0 obj +<< +/Dest [ 280 0 R /XYZ 57.02362 266.6236 0 ] /Next 390 0 R /Parent 386 0 R /Prev 388 0 R /Title (\376\377\0008\000.\0001\000.\0007\000.\0003\000\240\000\240\000\240\000O\000v\000e\000r\000-\000s\000i\000z\000e\000d\000 \000b\000i\000t\000-\000f\000i\000e\000l\000d\000s) +>> +endobj +390 0 obj +<< +/Dest [ 283 0 R /XYZ 57.02362 645.0236 0 ] /Next 391 0 R /Parent 386 0 R /Prev 389 0 R /Title (\376\377\0008\000.\0001\000.\0007\000.\0004\000\240\000\240\000\240\000C\000o\000m\000b\000i\000n\000i\000n\000g\000 \000b\000i\000t\000-\000f\000i\000e\000l\000d\000 \000a\000n\000d\000 \000n\000o\000n\000-\000b\000i\000t\000-\000f\000i\000e\000l\000d\000 \000m\000e\000m\000b\000e\000r\000s) +>> +endobj +391 0 obj +<< +/Dest [ 283 0 R /XYZ 57.02362 389.0236 0 ] /Parent 386 0 R /Prev 390 0 R /Title (\376\377\0008\000.\0001\000.\0007\000.\0005\000\240\000\240\000\240\000V\000o\000l\000a\000t\000i\000l\000e\000 \000b\000i\000t\000-\000f\000i\000e\000l\000d\000s\000 \023\000 \000p\000r\000e\000s\000e\000r\000v\000i\000n\000g\000 \000n\000u\000m\000b\000e\000r\000 \000a\000n\000d\000 \000w\000i\000d\000t\000h\000 \000o\000f\000 \000c\000o\000n\000t\000a\000i\000n\000e\000r\000 \000a\000c\000c\000e\000s\000s\000e\000s) +>> +endobj +392 0 obj +<< +/Dest [ 287 0 R /XYZ 57.02362 606.6236 0 ] /Parent 378 0 R /Prev 379 0 R /Title (\376\377\0008\000.\0002\000\240\000\240\000\240\000A\000r\000g\000u\000m\000e\000n\000t\000 \000P\000a\000s\000s\000i\000n\000g\000 \000C\000o\000n\000v\000e\000n\000t\000i\000o\000n\000s) +>> +endobj +393 0 obj +<< +/Count 3 /Dest [ 292 0 R /XYZ 57.02362 765.0236 0 ] /First 394 0 R /Last 395 0 R /Parent 315 0 R /Prev 378 0 R + /Title (\376\377\0009\000\240\000\240\000\240\000A\000P\000P\000E\000N\000D\000I\000X\000:\000 \000S\000u\000p\000p\000o\000r\000t\000 \000f\000o\000r\000 \000A\000d\000v\000a\000n\000c\000e\000d\000 \000S\000I\000M\000D\000 \000E\000x\000t\000e\000n\000s\000i\000o\000n\000s\000 \000a\000n\000d\000 \000M\000V\000E) +>> +endobj +394 0 obj +<< +/Dest [ 292 0 R /XYZ 57.02362 687.4236 0 ] /Next 395 0 R /Parent 393 0 R /Title (\376\377\0009\000.\0001\000\240\000\240\000\240\000I\000n\000t\000r\000o\000d\000u\000c\000t\000i\000o\000n) +>> +endobj +395 0 obj +<< +/Count 1 /Dest [ 292 0 R /XYZ 57.02362 589.4236 0 ] /First 396 0 R /Last 396 0 R /Parent 393 0 R /Prev 394 0 R + /Title (\376\377\0009\000.\0002\000\240\000\240\000\240\000S\000I\000M\000D\000 \000v\000e\000c\000t\000o\000r\000 \000d\000a\000t\000a\000 \000t\000y\000p\000e\000s) +>> +endobj +396 0 obj +<< +/Dest [ 294 0 R /XYZ 57.02362 516.2236 0 ] /Parent 395 0 R /Title (\376\377\0009\000.\0002\000.\0001\000\240\000\240\000\240\000C\000+\000+\000 \000M\000a\000n\000g\000l\000i\000n\000g) +>> +endobj +397 0 obj +<< +/Count 41 /Kids [ 4 0 R 7 0 R 13 0 R 97 0 R 150 0 R 158 0 R 195 0 R 216 0 R 220 0 R 221 0 R + 223 0 R 228 0 R 232 0 R 234 0 R 237 0 R 238 0 R 239 0 R 241 0 R 242 0 R 243 0 R + 244 0 R 248 0 R 253 0 R 254 0 R 255 0 R 258 0 R 260 0 R 263 0 R 264 0 R 267 0 R + 269 0 R 270 0 R 274 0 R 276 0 R 280 0 R 283 0 R 287 0 R 292 0 R 293 0 R 294 0 R + 300 0 R ] /Type /Pages +>> +endobj +398 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1126 +>> +stream +Gas1^9og5P%))O>kdX?kMF?a9\"mku"?gj2hi\t0ahApg_mV?ZVNN>HRtLGqRITkQdJTMbMV6jUROnc:(^o(]Em[PH36=XYlg"67EB*U-6fJe`]W9ZEE;.uK0Ur\^Ek,e$Y-'StLq1@bJtS@oR@k:=(@$I`bqAWf^'NOfZF_S.[>*si7?6A%Ql3"nI>:]KQ7c,,ZG[tqs*A#l!PQNc+BmOkJN@8=G[4i"],(^[^8(Nma]?0*p\pMrU/Wi2h",8.4q)mZ(6#A<`s[#P\b;L,"8!d,i#T#FQ4)5nLN2*_K_.1Qd(:]=PNS`2LDXa1'f<\l>q>uofj=kKkkrJ[q9VS3T;F*Mram#.U8YCJED.40Zqg:ZngQWFR.R2k%^r!lM5@VRO?3?jl!A`50@dd+Ks,-:j%XD(\Nas\VS^e@Oc1-r7HmnPHu8=^hHuKW9eck?#:@<1DNE$LH#;^fHojWW`s^NJ=#lK_j'-%T!@0,@aJS@0'FH`lj4mPCF63>RM@UJnqVZA'djnEP6ZImujqsK]OI_M8HquFTeI;2c]kjl\%a2/.)LDr8Uf0;9dgaB&O4+k:4/N@<=-IQ:qq$Zu/i?/_J60"*endstream +endobj +399 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 851 +>> +stream +GatU19lldX&A@7.m*TMf+OI*a,&j\^:chYPP$b"e='106c3@#dq*:NA[>fIT5riCpOOKX55@@Z?\O`;<2"@s.[QGh^I#fAl)i)'Im2bk1ir,>--(R;Oar&<[Y!r>^*N\H9qoFqB>nC^Iu9_N2gYXq*P/R%V-p1L$GPf.oj_H@Z''Q=Io3$qV%.:O;1A":Jg)aPYS_sIEk\7@`;\e?8pfbTH7GAC-(-qp\4?'lE*b+M"Z2FZd;go.6)?XD9:EWpQeJ]5(T1>?UpX-L.g%G/$hD@2a7XN&rB*][U'-Z4>#**9iDRP$6=0uV51J-$HQXOjs1$KeAeL9l#b-'=/OMbRa[U+Q'l3FC"0!nP*PZL\\D:>%a*18'J(r'E2JU5S+2m!pkQj">M'ZtXDUZ+:BM!NMRmtj6_?H)pK2Y7Q:+Lcuh1;5*)1<#Y.7-CF'(B*m;NYtMB0(=h_E7&b](M(]L?#%=LI4kDRdAq7%Rk/*S@.`m5E%aM7NY/E89@26_/A_jCaeAYY.!c_6G"u+QD7Enku2q(=kgBUJaer:2\%`\M5f+rebX4?i^NBDWPcOV2Z>]b@>L#YWF^?#80V3(abup7nWDh]iSOSPO+\]Pj;SBl\q24Rb#%5aKPm7'f%K0-".GJWVc6>WPZJ^-E6EJJAKTA43Q4E.:P6VpDV++?U2Oh*V,q5B.i[8:@"uFHrp[U@3Q:Fp9;7!a`0o2d#?-^S=h#`/Mendstream +endobj +400 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2546 +>> +stream +Gau0D>E@OK)1Bi7+St0+[Og]UWB_Pq6+.%QgSPhV/f'fj>,H.SAB?-oEW#9P5#UrB^*HJ%71`T?nT)A8*I\fEk2RPmf6kWk]*4]'`#0*<@a0/Y7;VM8hCma`pC2%2`\YpO_B`NqaPC8G?=P0DXVJ.\Nq&@rqTOk]1^RdK[/9UUUaNZKd\b-4705;E'OK[%RQCW_plXPQSCTV\U;$Z?%/u4ol,++RjYC\"a`c%ns4?g)jJ*`eLZ>`p?,3E$+FkZ)pbD-&I@j`+bJ'_%u;V;(X$=J_FJjU-eN=kCU-&GkU\NA:>*Dt^@1$l:;5R/L8IAM$m'IYD';p+U,^j@@%RrUMf>^@(MNMg?GZNH?S'/0cT/H;PZ:K^tS$s/AFi'?)%^/TGuTc;V(I6fp6W[I/ds42V1\aK'?XaHboC1[KCoXQlo.s05_dYMF4UEBr0ppGh>5`[uO\OWf8<[ppaR^29M$J?@=AH"7iqC^AV\<-&3K"c8HTU&:DsNIb$lEKW\P)`^)Pi?oL=/3>(^*CWgI-Yk:g7;4>\Wa%(_Z3tHiIEdFRV?Y<\K.efk"-119o38YIpcC).E$mhXTrihrG(NOJUu#k"^(cZqn\@qjp5LV2N4Z+G^=)],&eA`R4t'SR*11kq&0JNjq(i"aAVhP4Qt\fm#=n.TKp$CX=WEu..jKf\E.32_$m;pcjFpFBcFoMM35@.nnk-S1TEVrm17,udXg#Mhf34FY@)@.r<16d-$4kH-"270#PnGH[!$^pL1euaIJB9d]9B@@Q;qpuQ%]YfM"W:k:@B0C]ik@0JQlunu<)H;@BpLi7o=jeJLUG[%.S[&1VO"FRY1:PN',s6@iN"3@.odZ7**#=Y/!*'E9NT5-/?'=\EXE.O]4^>!$ud=6J'2[G8!OdM*S!SgQ"9lknuW^VdsVFt#!f?/0Q-(gFEr\B?;(@:U3Xf:Bu8m5/%O@3ZFW7+1=A)IY.BD"W;K%sD'U!m\G'i5Wm@'`2234[smm0<<*"n"ZF#k4S)MN",VJE(d]SA\lKqt=5b^6S&1b+MtTeh[U6@g0nHcAEpOdZf9OT4Q^,P-_rTYBPGe50ap*_7)gA_ZGnqb)aA6HW^);cmb=>CI0F";h+LX_LBuEJap=pX*iD>[[gFXY\M]YuKl3m[n,-s<5o0"joS,a:!]\1IJ>[hSj*4%#*3+N,*p;TD!>-87GL$bTUT"E`GNBpQc3#k%hK+ZZ8t28Ihi$!W*[r%J"B?j]1WlA`X:T78YfC"O)`Z3!K6G7rmPmobmQB`7.](le'P^R\-5RJ4WhS5"__KLf6L34B0i>6-*2L(6`m,8*gX!SUre@Ek^X4a^@6]'`&a!cfo@nUs__WtEB%M-U"DC.rNc+Xs6:TommW\t+.X/M_9+j`=6\.T2[Ir=tEIQ&0#3ELnF)Z:$ARuXT1O$Xhg8/BE$e^3C!p=uEF>JYarRsZ$b\Ynkcm'O#`LJ%XZ^HHA0Gac5:>a3'%s]X+iTM1L5J!lLps:LZ3gHR1kqLUV='AiE*NhfB6QKUXHg:CrPa3Gg4nNItEsI@8>.Y8q#?VNc&1ZN2JpCG4dGWHskO"P$"5cK4=5aO'VbBlGAVSpX*qoJCY_51jVSZn$P$"]DOJ2I^mqW/N7Gs,:c@O",Bpb9c&BMA]Bucj8/`Tg,7<64U](c3`L".&,A9+q(+r:4DDR##YBQp:AaJW<10Vd%"caJ`79r#0_`1k$>`_corHWo0H*Yhj'N-cW)[AqPDLqP5.gZAZFPX4M#CFH1@C5)Z1'=hP/ag*VKG,ua`.@=/*D,&QCOnsc8YE+AOYgS!nq;s_0ml\P<.K8dUL8XnU/-ErbeY')f-Ybq`?&[@3d^usBZfO<:J:A3\,Nt.d(\%(sHKnBIMt]_7h:K,+a7(+API_<"NN(6B(CDN4O@B_0#=uX?]#GCg1:lRu]uKc7dP^Oa1.]CeC"Zu'.>TkX2?ef(^-[RJp`u2E1Oh*/T0BK**1UQsa:Hsb3[tKM=j6+`Mq(gZ_@GR93;*u)#:KqlpcAIu%RUntiL&C'eFT'r#Cj>&!d(h!8+Uendstream +endobj +401 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1964 +>> +stream +GauaA?#uJp'Sc)J.s.38D,_c+!LHC4Cce-3VCgW)]tkg)2LK`QDTDY?pIM`F;EM!Oj^C9(4Z8s7O,mTf*CN^(04KreK@!VU?=":5-q]h^0[*@>KAND:s.5A/R68=e%0]0t-np?NE#nib(QsQ#0cZsq5r@0]2t."5+r3"%3+@P/!iZg$Tt^be1>U=V`:I&9432U/ImN<`./d/TquL+o=slUhA%$;=0L)W/Cp-&WqWWS-!!s9GH:0R*4Og)=9T5:(f*k)ME.&rNB[n('%NL61-)Lq1"3Zp;bRHCClhjISa02r"t)=@fcu#*=6H([bZ9sb9IkTl,RI*#t3,HBMbAlLYR:E<:P&QO#/C*F4(U.Mo2gto/&e,EK^j;+-;Ynb^,,O[QoOOb'`F3]2bb6K#Z4t-RP-e(7tR?e!R.?[^h$UfID9d#UdCn)>q2I=`@juh0M?(SOBl_d^rXKBU^?30NU[k_+"Pk%BWpj0[Zju4l"UGCUDDaL+XTS]$UN&=')(of[SYOI6#`Jm6T2#mPFPmdi$?h[ZBet?be\+4R<-f.`Dgdn5X&l/0neCarQqO'!u4JSS5]d5Z%'*VL<2s,AX1Qr>b].LigWI)c6H$@CB&Q12C\rM>cHMT5E\K2Iq"K^2Q&tnXq6@fgMp;tM]r*&eofqRB9XEGHkQ5&fJJPI$m'\4B**L(DRfreV(Wl^d2!Oe.oM$*l%Ws1duPirIilYOX=\lm/2<)Q)F\7P2r&AJ;oF<3+m+di)/!cTr'Y1INQBbX]e;>N!`>EnkI$/G];7KQNg.4X4$oP_'^H*L7&YBhm)FBs*PJ$^N5m4+IdZ.7)k8Iuj]N[Gp5FqulE<@_)B+09M^Ou_Up/hpn9nkOs7TYiaLrS&taCu2NfQ*ma\\(5W.)'o&W'u6h$*=uB%AjmC<3'%)6cpU[9lM:jt\9cIL`]FhtJI1;&ql_:GPjLiBZ;e-Z:HKPP=Aia"&d10VKc>eZ/:DElepb&d3H:,t*:afP)k)>!WDp!Ep0hnE9O.FCe)%Q>e!75(h7B4h-^H7NE5Vl=@=j3214ao6Q/5)SV&pW5W\0XW>"S?9X(u"CKd9b8%U^hVI[:/!o5]@XL7/(A^+bbrq^fL76k4X0A^q=>-&],&G`S/(6F:DlrlnL19D]`_Mjf18Ou"JOK]0_]_ha)l+'(lcR/4Nq;hE"ejW.+U%O[-REe9EAc"8S\7J8C]HeELUA(#k?#m'B>^]-$_;;7Uo^l9Q<3A)F0K.;#a@0uJKK2*h#ZP`:4R'.rKY8=3C&))O+]]AVdBN4"4b*6W]jJ/VlhX*Yn1X+M-'"tnGMp6,U:O=a[96p7tesphQXKV1:1<_j5m+:n1Do5a=eGh*LhMFoS8,;'+,tu%"S#NKO0E].h^3;'24CaSA*uZ68;8gS*06D^N8]9K_.=?lL'&N;qB6]I=*lge#D7<(mYXqTG/M8dTQ\4,&kcs@CptWdSdendstream +endobj +402 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1497 +>> +stream +Gaua@?$"^Z'Sc)J/'__;8Q8?gCjGIT8S4A^3](9o]uZ1k"nAb#=X!#"n+Xmg&;m(]PSA6.'CX44a6V.r$:ciZq4e?M-NHBCV:A4)kO[@"a9kN?]ba\NJdB!NEk5n)4HdC1o(+O(t32ct8rGa7G.�IPBh/;3sm"S"UM=t>2^CIeM^djZ%='p&e'Z-:"`o?9KR@5TFb+XERo>`N_hTL]KEOe,7jV::=S\ip4f%$su*d"I)'Q*,N?K>_OJ;p>'u]1](lc>D]JFU4S2LGO>QuF\ptN^/[E\(6tZ"ekbkNp#Y/D%b8$.=j"jCFR/R`ha*/Jl)IRQ)m.D9kmQC/>l55,?K\>.rfjIB-fB,8E/N(bB:AD__4%``sKLXb'su5k]W.%komoFgA*O0%-IK(e9[30`2214Q!kKbE;$J)jZJi@Eo2RT+!)ik1;,CXtk)b8>Sm*qFufjdlF.>6)D9;57i1Mg]nge%9KE@V=:@]9f1Y;$c`3J>Db![+0_8OfU4r5MX]HMi:D/M%R+si=[:RHE1YK'g;FJUT/!M.]81!Nmr9JG4\X=qg-qUEn@06/TPAct.`!VO^pNXcG+,&&%"rL[+HV&(U+>:`GZNhsT&(-)15ZIR5f~>endstream +endobj +403 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2792 +>> +stream +Gb!#^8UK2]'#!U4e<.k/#T=YubXn,eN:Y^YEQmL>9+tup*J"3LKaR"p:K9>S$5=X$L\YJ%5qc5Dp$-["2]8"Xa4+n/#C*_/T0=r:(2P1&No@e9'>34-ICis2:NpB;QL.hZZ9?+B:H/iRE?K&[EXX$JHa%mDs+CmkfCM.@"ie\nCumt^J7>U0Qs_p"W9Lgr!8qIY<[DR*Z@KPDW:hDaIt5dNYkaCqq1=)@b'7_%S2*"%_Cd:S>d`Qp%>1>u[l^0D#e3Tm1/Hp:3dC?Y*A#*.>.Bp+:`Z=B,O(mZWU_%R!+/B*rJqf:u33@pi`R^XF"29s_pqALhha_Oh0ts"!h1(agK3='kl88N"Y[f*\M!a0DrmFf=\DWN@]ES53`lTlh)ZD!;4;m^$"lS7soe_p_?b45D2!JWL-/>Z!pL?M:,@,l%^Ce+Y8.SmYEQU-p,/PCS;,AOPD;,tRr8TLt%"TJV0q'8hsr\:?C[5SO9O>a@@&/$+CJ'1k`V-Z-,8&b&m6n5"^A^?IU\G`^?/rg)ZNSeM5H^aZ8b=Y:1>`SeQcK`W2-HQL5Z:oNL-SI=)nLE252O1=n%PsfK;6k%!h"1ca%/L>RR]PH.DH0nNMkPbLh\)*MQ;qC5ULN!*J-/eBkX2KQmNF1O9C5[U]H>S`OPE>iZ0HAr!8/+:tkM2lJ09J"&0XB;(7Et_T+&Wa!d#>FKZqu[=8@]*6]T(q-\=YB=eJ[E`PpgCNp;k(iG$`i%UmYXnlhq?-U8s.GSIFQ"QT15)7%:C4@Vd'Z`t8`g$9r4.tF%G?Ril3Dg'%&T>2B\fOP%#*'bkHme\Q!FtG"RSW=Boop+pA,@:YN-8U;fs;NM55q7('(VUZKB&^O/Dl1.o?[oEHh?@KQlgZrHWXVu]8XsiAW^UZ24q1ST'hMGTTL\$ecR"NP0_n"WCQMW.nlGH[_NqkRCS^DHDZKHNhBCq]UKB%E_Yi;282n$$4QI+]_Dp5o3&.8\J>oot[_7G9^O1jR%V&3ARRFq`PGf.#B(/q1BNE/PJ=KR=jjA`qG#A>chZ%.1+00H(V\1H]j7=,lV&..J&\P@])J`*gtoVthO:Fj2Ap$'Ss7:cT>)Zcm\[FON>1d4!7Kj>tr9EZ%F\LBU]B(nT81[R[nAi)O#g=N.miZMVTm_EZ5j&4OiPDl1(15;12?Qf6eXr')F:cWoknhIWL0X7;_[q_i@eF:nWIZkEba13k.H'@Ypc.In)_T0aV;+irk4\]V/1`_qUfQ$S-E@9IO1fIIZT"`4-7#cd5UrmojmO_BkQ5h8S-l?MnUl$3JJU",!i&R'!67q0Q'MWY=RX3::,eT?:OR*:E`F@O&?Gi6Z<+aj7`q$u7?jB!/%!6Y4qi)&@!^_$Ms:O%Hpuk\:1h7nW>[FKin(W\p4a7H#>Of9d'i?\4DQoMI;^#E2Uk3EDjNf9kc]ZRlUJpu@B5C;Z;K)r>[J_-g=n8d]0;PKceWB20kTlGcNtg@T&&2+ju5c,_%;Cf2dt-TCh)Q^HEmiI&_aP^EnL,3LpQf,`Gj_-:m3J>1:>,tjK(\WBm._N%hS/'do+5Sf;^*!ildcS1/#.ONBR_U\#6cP*FB[)S!')n$6FXXZ5X_u%A`qT"pM0hU+Kh.\p=U?dg?,L'7/i!^AcSZ&2`[U\W5:r]B0NGZ8)eo$*t6+&:*i7(M?dPPSY@J0rMI\M\U$'j%Oc,/OjY9o9=41_7!lID`#m<;1/9uTs]ZR]"Rajc**e9'flb?_;NEMq;1J>8!KoN/95KcQ@tiN8hi'3fSZs,oVAGH=u.o+l6s%Tp'k@%5_FeUD.7%I:m8WE:iI$\>RWDi)EHOATeMD&%$(k!99aW]CA9Z8'2/oQh:-8QO_3NJAW[LV_sRr#G`_Lc6D2p7*:068o;3-"I>[H@60m)i""6NIC:_Qd\+Nb0jD9[q;FhS]-#r&O1#p)WW-uRr/E&"0GRT3Q&/LI%bp(7i6nFc_PR6?l/h5PY3"(ld^7PaH74%/$J72:pA"m`A@lP4K-DNFo4I2iNa-eK21h40RiSbaXT=gA6p.+KAqaY5ddNFc3VqcM!5^@YUP\YmR6?6@?K't82`FOJBHk68l7c>Ud2rkC]tb:baFQJa,;2L5L8jeJ^Sq76kqGJ4seu3i'%AaTC"cTZ'E`mVZ6,;?K0t7=*Yt!("$h]AC>E/V%_mK?pMBK?'?!]`CL&+9(`2g+HW_H+9E_1PbE1`2F/CW-kRR:OEV<3obV7d=\pS6t'1]EIL!L#>BYnceHEDK$K5JB@ZqFJWJUC4t,<#=Oq6"RZ/HHkAr+BmN"^N:E]d-C/f(<4;Q4P@qa>@-sg?/iRd8$]@.@7.*Geb!VD5?P#@3-%<'(:o]Yuh&,TT&.VeTE)?TXXK).H*VUi;HYMK2AN?Z!4a0g/\'p-$+5`6!M`T=RW"-^-bn.Y+#$Dor"=/&_FWs8cNZ>UFN/toKP*:eX_)N"Ql`u*i&S?b$qM6'q^T?P+aN_@4r73`toBFl-?I+,i$-G9K1_?oCn.+72(^_[ZUR]:oOiq\qKf0U`M2h_*mQ)+c"e,XH/-gTUSIUKA7B?g3PB?CZ'0B-)"]Ba]j4GfPsiRk@#^XN=jlEo#%eQ+8&"!HZU]V-3aRU6"uu9]\5qbs3dK>5J;'/QnMEQH$(4\R&C.@2[%(hdsbrAYD?jNogBR$`&sT7C/W2a!S_!jfn?D.0pe5DuVEh*D+:0T=)`+XT#'F09VXY;*#,LVn22-g%e$>P/1\3F46Smeh0JLTWC^kE5>D:1*M)NF0+G5O[tpAa\s_)]X#Ci[OJ_q2M")E\EZK1c;NR+r]D\ofVA;ORf+ga82GL[qG+f?\a),P`2_mTZ0#lYU(\:gXisO28l:^mhJ[CPWsssP4@8Z_sHSBKX?l<%B<&a>CZ,0+2luEWtr?&\*h$pK(%='ja>ish0rM85Q3[?:mrNr),4g]UY]W/V+TWQ`R[;9WfAtg5dK)4o7gr/%'+JGR!OMO'#7Tdri5(Z[4/O7d1rt*n8LG^a9mL8fE!-]L]7id,!/kZS>Ye<2%K05?lsMDV/U\qU2D,WFuq]PW!*hM/-dlgtV-g0lQB0eck+u/H!JG+qTDRn@Fp@-mfPWX."fa3<0?+Y2=hMZgVU:FSOmd#Qs+Xj_/Xg&9ru]3a*V]Rp_2[t1?7E.L0k)u>hs$7*+ZpkP*$>sErm_p>;*?&Z(6VRajV7#_86#ad.IsWM7]hCsK7o[7(sT\2E:^@TW%OR'D?&]_H.a#Z`gW7U5^?MAG@iK5b0/L`a0ls90AB]KgZED'e7QXfENK;<2P\fe.,W'[lJDq\)/HW+aQh%pDQW7HNK&X+"iK.Ka(rXEARe`f&?kPL18(+k1:B"XRJEs(H!EL.fp7&WF@q<^?*W8F:s%lkpCl?IHQAP%lPCTn6L~>endstream +endobj +405 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2775 +>> +stream +Gb!;fl]\&6'*$g<7=K?%'W[,cI'`5)\\@:`l\E(g(XWg2Lh0f&#TdAnG@VbH[EDRVbl9>On3\mM8W0@+=O75drSMp7nG9mN0Jb[mWYuTc6Gsfbu_X?O=5727Wmj2Rl>A/cPmYhdQ5B<:jL6=X\(H//tF+*R/Vlaa06;b"J4XU6_K5`&-1AKf^0R?V=*p?MJmr'i@b=]-uSKoaL]t)g&0AQHH#R(j.Fi!<+Y5_>e@]3mK2&0Z(oEdD9gLY'!t3q__#Gc^cQ2L1\:RfhpB?X3`,kU*4l@l5jo`ID2C[%$m!iJs&AD1?Y$T5h-k1AQ7e6(u,IjLOPOl>.,K"KB[K"q6/3+A7T?q"/aaW`6B9"]BV?m\#4Pqc9;*WFI*DZjG"P/=N&4=?dJ7J_YmG%@3S(8c&^=%9Ofg7.cR5[*4G8mMnK:$Hgr0h7]GSONZiZ++u^L()E1:!H&or9.d[]gHO#]1$3p3ZDa28[]r/o3ZTZbY"-g+OVcgSlEgf]\YmBDfeuV;K:r>?)'%fL][P/$#==i@gFlfGB;HRuN_7GS7nDLeA4.+kf9YlXZZc&J*0^%@aEK%+3hK**1?6Gh,^mj;$&t4j&r>.Rjp9PO)UiWrplkFt@NoAntD4Lq8"Zf[l-@l3U.j2#!r%J?Ar=!P5Gah>s7;$Mh<8JVjFin0O-,&Y(]@O+P.7W_^;SY1ZDm:hkrT.WIpF^_7D`e;Y;,-$aOF%j>4u9YWk)]uo]25F42SfokEB+aGX?ZZJ49W#I0?b=)tlDPY4piqPO$P;nk?;O=u.%SG*9bo)`_Os=b)M5$l-K6aXrRD-GpE>X!&r;(Be-lbTq(^.\)+rn$a%^Z$IDC3fiN56.Um9M9mSK48tQqZqFO#lUb\q4;8Z.HfHbf[QJ4t>a-3:clSMf,&2=?\mFfL'rS6Ij@:bU;qlW/i=2^Ci!HbXgSk,87CsMp?FbpY5!LGO00Eicr1)OD.jGkbpC^N2!./9C9pCalQ55[4*0mVq0?L:dD1CHGe4idRsr?#<>PN^9h%]eGHJjo^8l_6-T>JK[[,3U!4cuHL%XU[koWY$9iT\Gf/sXW'Z2V&.;[bF"Z%:U>j>T,'iH)O*P`kDR\*S`E7m6QL;*D()$,B&82F8%[9m2U0eMO)`Sfma]aQ.$g$S]D,G$TAr5(h*R4#dIZrr3s.t2](2SH4Qp1BRJV%rQX_Yn;t]-LY\AJ_GH&VlsQ-RDNooCX[Hol*DB,3)uF=YWK'0J)oXK7F$aI0lpe6r<#:OAa`VcTYA7+0=5V>p,8Ii6\P)H=lejQ+k)dfHP+q_LVT:ju15F"mbtMb77OjVQ%uBonNK^%+1npQAA)HKi&u5WFN5t60bI$kdb[6$i[`8>R(uf5[s)'Bp9mC\4qFM&l]s=RW#!oWee"Mt:2*U^d*I&3s&XU&!hXsqEg9,&R`28^nN^kNA&c$b"h!h2n,Ht?$^'VQ>X_bGsjkD6[`Z^3U4I3o'%@?h%@^!r/l9Rl`?-^>(&P&N'3t8?t"6FIXH5#daoW"K$?e_!1K3Jq*hi@H2FaAHEeAG7Ai%WA<3"D8HP6&Sj/GO=_*(LH""Z=fd74`MntU;:Z_JrZis;N>jr3C9:,qPY_f-of#MYZ`b#?f(!Msf^go"4pX8QfrN7#94XUJcO<_79.,/VIF9ZR6ZCl-Tli,rK^k;.i"W?DT+/GnR%5cj]M~>endstream +endobj +406 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2192 +>> +stream +Gb!;f968iG&AJ$Ci97N\9n;6(2q^ndT(0X:1qggf5U7V[+[H83S`nsT(SkBGBT"X0EQ/Yq+i3!RIf&mU1*nRN:jYnD67sYHiWCKkOi`R/OY\2F)tJGup?des/,dh7R27OiHEK&U:[fDfG3rcs5?gd"XT[4f%sR8-'r+Mh-D55Ha1cBPid2^-"XIgrkE2H>Mqc]V=oADE4O9">OA*B@Hm_86%\n%%7ek^KU&]XtXrg)?$(/R.F@g<9BNCapKcjl*^qXEB)3L:Y$1dm4RJ]PpQdRSW$2Dl(]U7(HKq)N+hMs7m4]\b#f67.)5Z%;_-&3ft5gACJR?`YO=*ZahV")S["D>=0R.He`Wjl6G6N>T\p#V;O3,@=Mj@B"@B!3RZT31heZg7$mmZ\Hg.H.KEp[L(Hm4LpY2^n5;'OUM2AsYgE4&LM,I1O`qZ>5*>h,2n0CCf*D@S%D0RjtuN\#.*.f%!ZS<0M>l6noEj$4lRNF]f!oCeYP6kA\VUi8TCh7-%#J6+#T+c/cN?oi1J[42R?-4A33+8uZ/mV6+D/^jbPTcJ,Bq8"_l:4$)8(U>2b(&Lj$S.fb?P6K`ETG&\u>*7eU/)B\-C8p@ZA/oGNE/GIM#1L15M/A-Ja!Gqdg1*0mhBoZIBb4NaX85YnV/>L*U=[#%W#C;9B@:i:g(cp=W%+b0"6mJF@V<)DE/+&(TV'H/f)t+5.!A+a(f/1_VYIehT*+gmWcG7eJP#?ZOX1Bc8/bLj^cs-5$=X^9H7"kChR_WWJ:g0d(/(T*5WhVM1bi6b*=sNi3D7%E-rL:EU=S,k[:E*fDcq[m#WDlCAPsII,o@BDp[c&qNl&'uj"dO'#2efUF_J5e'"c$V#2)D-F-W..lr65=0_WYu1JU*f8thBCUL*m8iM;;7'SQ1e>#Ogeh%1te+Di'(`sHS>=dNZ-r>)AT;*?*jkaO2&isiR:gZoCtZ@n:MapiXSGpPLrg5C;09-5WtF5gb$HP(VJ*5BJ?r+&"s@BVL/]_l/-A7P__=6KaRI7'rtKrAo1!J/SR1?_bHH)[AQ+W`0'X_Q=K`Fkql^H!].h$/8tE)H:J#Nkg9n-e9@8b[Im5EHf[<1*gL'_'nM$D;Z,&.+P?c`Gq#be.-0?)P-`(ZA=d1q:7UHlbqdh%#ARk.:WkZp6m8J6h3bOPuTcQF#sqY-f[TGe_)*b:,UBsq0H-adf2Y*0L]3l09fNn5LM#CL5&om+4C:L6ei7]a5c&b,Xl'5@-cJ2_WR07FDgAm\@q9>)s]J<^fd%_G7U!%nTRQbnibb12qpCo:08kF4E8fMA1]/5p'jK7.:.Pl^.Mq3qDiJ>H-N'Kb'/#k:8-PpI,25%L[X(ft"ipYnA>rbZ`-!Q<"uPM0jF)t8-ZBqW^JcFrNJ\fm9!WA;RGgB0%))W9-QlA'u%7dflk:j4M"dR$Z\qTqiN*GhD>KGLt`_EER~>endstream +endobj +407 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1275 +>> +stream +Gb!#Z?#SIU'Re<2\<$&iZQf),]qn.&"DEO4(##"eck9o0V*pZGS7KB`QaDu[FYc!["r%,PRNIlOpDi%:.7EsYcrM1&RhB'-8qA`G)k[L/TR7:=b)FBk6r,[tc5+AX2E?k<"jF;1Usp0t<="Uf/%O/)@+-q[&8-(\0:1q1^>0Q;\!]$@#On2JUL%"Ijs/W0(K,?*r:M@iJ0,/N;WnTabK"RC#U6jeu_E=#*NV00Yj?ptN"6`X2r]Ed-MBI@Q:SU`k)K7J8%ZJP_=MQ:Jgge$>>c!mmZ=6b5@G,X-4;@D7ckd>a@je)N9O[lY>3>t+t.I),2dBKpmrs"#JLKE'8@R!2)V5"`QZ:tZ?7AjoM@5<1Z,7ta$`GK.:e?A*M<+&%fKAP&CS7XNg78r_oF"5O)<_EF#"lc`0/@]aTU"@h!1F^XUbFKeQ`5ZNoh1mY8H_k8rA"#J@s6=WXJ_#M,N?'@5&$75QS"EoC4:":HVPoGZ)E93b@]gZpq"\6WbY3%>bChH/<45$AHFUt4MF=ldgk&.#2JS(sB`4PI8!rE*XAU/UoRU8T[ljPbJ\E-5;+s^e=:&b_YAL0EHRR8Ik^865&kt!X)g]m;fm("+V=m3O1a.L!t2G3l\>$o0r2>oOfH=ZN#8K]:U8PqSg805qrY+9T&+ETiZA-lbK!0-GSC!7fN$OSCgiSGM'Q'9ZTq+K,4WkXs6pNk\QK&WQ.*XQd5@XXdi'=J>%QkF=/[qqrkd"CYL!mTtNJI.VOT&C?f;jQbe"1$;2MLKPQ-YlW0B+(Ak[eI#j0J9DD6:%2ReLbZI@/ZGPk`\`s\%QZLE#]^I`q+dZ#_41sTR#]5-HKB>BkD#V6IR<-;d7*BBISSFf'lb7MMI[8P&(9g5)plVl]/o`_:0WsPr:E`Mi&k:;^+<%!dbt54dd`2s5e[p/Q\#k:P-t4%^U3PqWFG7unG:'OHGJ.HfKZ5iTceJRendstream +endobj +408 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2137 +>> +stream +Gb"/(968iG&AI=/lqqY2L>\Q73LXP,1S1RECO5OT+s@7M#q-p08,;g`J.IPQ-!=t9Bftr_1:\MCqdF]`>nCuF6gQZ\!C"ff_^>N(KD?-NhukaKL#;%]^:khoA/ID2k=6T3IcPbK$ch49*(]pM"\o-j'"l/1hr5(mN0;8?_2/7\iX"$@Vt&WUi!!_e:2o1p^jROgljDm)_>G**ob[Ob9HU+9PP!.^l%.C0#00=`LR3R-rhSA@^Y&bGRTo^@S"%SSJo^NZ5JR+m/NPWG.0i%P<2[kQ?_@H7?S8^[R79@31Ln;WU.)o;$P[qgI*W@qJ#OK!1]P0;pkif=70QR30W`)_)=TH$E3CC3)^SRCQ;D;e58Wh%k1TT"q?>Q8DN0n/Aj#t1ZRRW3m,f^\_qm)H9anM?$KZ&5M.AfhP'Pfm@FBr5E@oln[^/PLIa`W^tH$/Rr5j1=6N[B9`-+(.PGD&dL'0WqGlY]Bg8?gL!0i2/SmCq(2*M9K-C"OZg'Rk<@%*4EkpYL"iAB+1D0pnX`i4aR$_Z]Sm_c8d9?NYPIMGo'Q>VX&g*mA%Ib_20r/U:s9U)F_M((AZ]"[o#a4W-f#`&qflVkk*.f;'tL5EV('$&#;XQEo54B/3!._nVh-=2H4HSl`cgHHRh0HSL5W=Ak9PA`D?*ScutbIEM]Zbb&X4RD_f-Y.#7W9^$!DBXoSE'UT2])#nWbW)kJpdo0)N\CS=2A?fb"UV0[XRuKh-AVO6$W\B_RI%`72Z0L<5*8lG@G]j?UE^?,B-]TGupii[^\X0fUC9g-h&K+#Hfu@_kJ3qnnNQi1To&V[H!X7l/:4K(!+PHg4L69'c4DH(P'lQVkg3OV!R<[^`MNS;5^jKG&VXt-cGTohR&dZnnkpmO<5H#7&gYge.83fMn41#U(Zp]AI5n;$H[KcEK7!_eI(0a^8X#.0VISXG9NlCrTkk2XdoVG#L8?CUaa56?%[k"N^tY)aof%rKJ-\IZV._$&!(k=#DSL!4H"WhYS6Ss,#k$(kI@fF=9W+4U9c`mBITHn<1s7R%W,^9Q[mqQ186@nGP`DcAmH9"b_6XDl72K#cV64;h^s)jThHV_\eCa%D=T9_KcsZ@4/:',lVT(Q)4+nBA1LZ)Zf!Z9GaJ"Nu3Rc[D/haL>42\:rBCL\Qr$C)MVN`\*;'L;lTF'T!JmJW>OZY*A7#WPbl"2R0gg/D]h`-`!-!PN?j-f9s;B8FUE5)MGu(p\Q1JhmDEXBf54lW:&Qn2c9"c/,f+&Sp4(.39jaTe>?P`7*N>oKlo..(Ne]b^g?j"^gm5\AX_^.-e*PJ*\\$.2f*bro*5?6?f;;jg4>YiGjV?%m3Ln!J::.D,_#6iMV<9+Q)#1q@XRrF457J%nM=Q7AjO-N^Wj9TtAth%)9pSs=0ru?!\H1`bje:3gS"t=i:FiaX8kb!&oMBs#3$.U6$:,[jEHhAdeEL1+HR\E7<[">h9JFm9Qc8irHsh;*JVm!F`9@4P(oY3`%Dg7WAQ\3bB*ca[k:8htMBI7oMP)S3n1p1Y%2?4keMDP2O\kmk.aCZ@J)Md49>A<8&C3UF>1oSu!IhH--;KoZmRTMZZ)8aHR/,(!.]RG;WGZ&!,UTRgA"T:0P?NL//m,bD](p6qALkMei^R_k%6iKfhcYgm=f@'EujH%D*M)4lm?]K(&tQL25MpdcA]$;K51Z58CImendstream +endobj +409 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1921 +>> +stream +Gb"/(9lo&I&A@7.i97N`"+]:B3H!=9N'!4se=/**"^+CkT`C;lp"/pi8C=06%nk.jdpuudZj6_kI;a>e;M&o$KKJb4k"[GNhJPhoh:3d51be:S#'!=EL0*Khf@Dc00VBb*!8ogc:]0gScEH=/$&+hp1Kp2p&u4b^6b?">0Epo5i'oC4U`nP7^8iim0*[tEGs3l"8qkoh%FpugC:Z;im(g)/=Ib@o$?k.=J(J$8&Uk\:7p<]*cH3c)@h8Z#jIssa0([l/f0Ea/0<-(j^_nk$k0"p!*?f4e0gt\?64)R:=F=e[9'd]QJY3Thd"HI8`+;A%;@phP=[U/P2M^0WT\aeC&^E6=g;`3#="Rs'10rpsF;(rP,jDJ8%W=+9Zmk\aQRH]=&Om2)T4KB=L,"T"d"\al1;P]6ik[.+V*m)c#@82i:cR62hPG`HNel&YA*D[h3ii*Pra^1O$-<@VlV,N1XP0HLdHjYhFmcG$C?tY%maj?\RSDFAf'ioqb?(2pC?S:61YharBI5,Nt2fls$LUO6UG%#fh7[S?8CaAr%fj"00?QsafNe"YHn;D4"1u4Vh@ldXTU4GL*3WguBjXg[]AK)&0A:8h_ZuEqQ)R^-p<$5SB8\G)!)u$,cJaRnJ@V2KMfB@;g'Sc$H(F:l;i2#_aa"N?a6Ykj#M7g>t"eRUq'";muo6Ko&^rc23]Lf&)-i6F5$:b'[ERq2'T'mGWKMdtR7NA#DL7Q!P;"5DTe4,OHcu'is>I;[)b@b_U=>W&@'OCf1c];U<50\XK7pD+P(@U!kdG+e9Z0UG;NXAAdVMmI5btBC4?&.GER9WOAS0kNW7s[Ve'W9$ZE?>D%mYjdf=`ieL$]be=+Pruo5mfOfFF.:dsW-]-JO$6aag$jT#K8qH\BMluBb)H)(A0G%8?aglmma)H]!8uG"eA*ApZ*@8`p)-sgd=gt1r]$A^FP1^7)*:5!)Yq)UM2gPF=K&fJ!+q@e//tcl`p$a$Y,"'ZmIQDEUHhB&'-&@'~>endstream +endobj +410 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2410 +>> +stream +GauHN>Beg[&q801R($),((,Td^,^)DRlsM)D:H;LqZ)P9`#afcP*EBBYMT@(hIKK8&k'csCdQ&850OE>^]4ckril6?^'8g@K-0P"0FkDm0UK:'H[tjs2ueqQFl[K[eQh[e@)4?rI`!$lM$$[a@g#fH[;J\)8f&Y+(Gn?]%rb[*35"HgZBRjQ

>5eSj>j<77;M6E9ZLN/2TB"L44W$Y".At#hI/FkXBG=f5r8j&p_b#Q/+Ti0e?.*%3cM<9g'BEC/_#sH/"P"nTmVp$p^bHtO=W8E:`:f$uN*/M_,0m&e*_%F7DK(1SV_7L%*a1GZVn>'t*Ne5J3pLF:eo1%ZP8"K>O'S-!a-h=j>dl7PC(UC\;1Hb]FH+cVaWS/H68O7]]ae9dHPYT.)OU4JE1LFf7alPE)B.0V[#&M#"P=S.QYt\:$-2iWGGh<0JpA-<@VB0;.fm*@AdE.rfO)A/e1>\mVFL_e?OP3ck-Bo9HG[i!BeHD2>IJ_SXQh#7Y4fBEU&7]VC11&^Gu>u7B&I$skQ^YGeGu'j5nEIPeje1LRH%dL8uT!$JV:-Q=?ruM6fI=,fA:I&3VSQkkfEbn50uD>^YaP/Z(Mcm=:oR'jNEUe0p1HOrFlVnK3eMDf7eR%SVYA>f/;gSl-!pu)=1*I7'CoG#ZPb'2Cpa#WM;_cF#O4FA`n-Acnj=.GHNnIWV,[4=ig;mC[pfUaLInaN8LYm]ulNtn^GVm6?t04*Rq"+AaUPm/15S#H!OnE(.$\#m%ED,Nd3&mdBZ^P>0B'M5]9b:8p\U[;n:_ioc@^:Z-`G["(;]aS`OL#\)rZ9r8U`l3RMRsr0o7:ZjfcY1I2r)1;s.B0U`*`Q7L3TrOL[i*>DlpY3GhnIu(M$ttPX3*S"!cD&Fn*H(+'[^aT`k+/NpQ)2)\/ZgjH+p>h/FcrIgnC%L-mDN%lAR8d2Y(*U`0f>(p4Y[S90CL6>-&q.dHP#!DY?O(u9-Z`Ol;?\tOZ]h7a)@3dBJ)aIp0%dXs!p-DV?>5;cr"Ah*/rGAQ;7Htb>A9UKtYY_K42rCjJh76GO@Lm/\'F@WXDu&Q)sR'R$/>J=?q6-M_:PB)"H!\bSe$]p$[m_p`]n;^Ii*q+O1IB4[EK8a'!8n8h+'ID@9.H+uF@1Q#PEqgnDQ%0qQO=cV(V9hp@SY=H?g^h?^VbZ2?K$_>o]dn%te5i*:]&GQt4,@6!Z,WW+mTc0]P>i5ji&\O-\K'3YN(%+c.=TD;RlY@-cqEX:VQ-.4CA]_#8XcUN.)D"b5ZPt]dNYRi$#sOS#u/nacJlCDP+H]!LL=SL#%iEFrs-oJejf~>endstream +endobj +411 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 3422 +>> +stream +Gb"/+>BAQ/'n5n\^nZ,*`o9atDb2;?UpE/;1nMEK!g0B^3b[`.`mS]Q2:ulZ@!=N"cDjfD'$m@-qt4#rl642/A83#*c8iSJ5*E*-/1;gO3;qkDl^TsPN@QHZsTt1h`a)Ok,V"fhLHbHMTWgh2Gsn/*m@0`5!Cb00T0;[qsoBaZXpFG`OYNX,)lqnHY*X77rNO,X$2u^?9gD-JYEqpS`F'V"slW'hJ3n$fp^B`5_/Qc:\YJ*/a1&H%_+VR5F0"T$BtO/R^\]0p_IJAMXTJ?Dr++E8W`mH@[[^K'b'F@*LDW:W)27!If8SNPkgZuhud0ZS)>8g!cfbV\U$VT["XEZ'JX-q\c-n)_-C8OdjRUpV2[Z?>]Qp9EVWG;aLVe1k+j3B4!agOZ&cAYP31IR*Y^8D3Tk$6.1?O7iT^pnhK!Uc0EUsXZ_edpXor2bhEklF151Z56W2?#T(hQq@J$2;6p#^1_W*RCNX*l.UkRt6]i';GV3'7^!(O5q2cUcs*`HGQ+#!q"3VB/1\CJkB72NA/+Mn815QZ[@%&fL!E6XsquV"g3AZ@;GdNF!-=jH*f#n\:Q.36f"9`-eSja:S&TV4*@.O^cL`rB;Y/XJ.P[F4_1(p=F_l?+LOWV'L_a8C+&dWCnP/k?Y^>`JVg?Q,_A`#ei[Km.4K0[cNho'l;fXQ-Ua[j1+&C3MGFrjDO,1aYYlsL%q';Te]5:f57CTpn<$8>UU;=soK'9g$i5R]C)_@FNcfk[K1mP)S*AJd?BBJr_$_VQ"EHjE63`&:!(4/1`J-?OQ/kc:Zl?k*=-`1"Y^^00,7,-uC/Y0#!/-#RR1&Y+[JKE-otYP!Le>W)j6=ac!$-CZLX_\eq[t7ad_%%>%ZqHCLeLGK019YuA;FdC\)0`"m?b1u^Z;!b;L8$q&gn(7]IFC$&_/d-+]d_<[k7!$W,7P'&YjdONFZrGOa[+@i6NU@B7D.GZ4P9]i`Z#lB_XUN5"=or#";>'M6LcWONLrg4_`Zg2:O+/8CLS2k&aniV#[qg$80qu2&YWeXph,$6/Bp;&AVi*@99S1*&&(TC^.*bCu*LT;G(3L\iEpK#NH=OgeKs7WWo25A0)R-9&$Mukn;Rlm7CcMQqsKp:^Lm7MO":UFdX'n"uI8D.l[&KLYW83gYj^O%(M,dC\L;P0B>KN]Z_JFlq4,qWOJhdPQp^gG@4mbib[AR9>CtGVVq:gqr,lTU(bA`tU3/'b9F_Js9t?"c4NKu`OT`^Tcc*PI8hAq>D7C![8T^r?<`*A,o4FL3uC8iiEm`+D_6o8.]eIRGCN*f7su84;U(Ltj?iq:%Oi]IJr9U:[(cQ;Sc\ei$$Q8;2Kh]PkRUWH]/&p,Bd(Ta0TBt[Fs&]ZtZ+W9l4Aa4k/ke&O3KfK"I6s%h#smhGJT=XLE[^cTUP8-P$Jg?bb(Zs$;]._4IAG7ooO-tpL\bT+UOK9+<+ZZ]+%nc',M&4S8RJ/scF7O_7QV.#+%nW#52XP0FDhUmO.XM[Om$;SAp2,`Z9HB75rE:KA+k0FYnSuM2a436mFFE@[0ofu<^$+Y?-d$qo4U9n:,)j$PY/Vebl7\Xo7aGoV3pG?R&Q1%p6hCZG%#!YQsH's.:sB.7Z,lQR?:s%@:l=&?'lKYk':Ic;Tpc;N"iT+1F'm*_TYP*].h]3b`)1nh7CY#G0OA)sIsbZqk[bc_`P/&hEY1&oj'bYT?YrlbfE8hgjSQsDJu)aN/%*FW^N'G#N?!Y^Y+>2+JfkuTH5dCs;1++-a92];u,>JleCeGO$o=f.HHYL0suGK?!@6]&OERfeT\IUbR\p+P7]dqe;\mke1\ojYqH^A0'sU9U=C_bPGjFul>PmgnFi\RB)U=<&=#T6H!J;`2s7k:[g_P@:3l'6"^V'7fdUD1~>endstream +endobj +412 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2109 +>> +stream +Gb"/(bAu>q']&X:m]K/l1B9u8dB"=s?DWS(W&no?&X&776NK`ilIYb3'Icllol;gOA29a=/`RJR^c=CK5mdaEW;E+I!$B5MmZ@Q^0V/:S]E#qniNMdrQQ*NEjH2s"+#8Y(jtD)lL[d[ac/li )?0$$\Rk6Pb4h"dDJ"kW(93hP)"\Aj`h-];]63$FrZ#s1-=1ZD$-Kp-qVd,o'(?%D00kBW.ZqQ>U(Y`ETj!6Z"HG-'O_-?P@Abi[QtN]f)e(`a_FIV6!]7(r&sg,!b-].nV]s;,PKu#Ui-sj6ENjtM"Au8IT0LU]Cqi'u.h)c9d_SB-Yb'aZX<[/[fH03B\FZ1[L6:3kh]@.TYnn`D+JCrpoH5nbKLG4Qn+u0n#Jp>`O$eh2bH>I66ablp0?_nWJtC]-<(=`?%J,FC*IO<$68\JP:_oN]A6$bZKeiKRf_YTg=]lTuH_h4RYr85h+b^-W=i(aU1rLCM+29@&+HGk,`e/QPAmh/0,Mi*l2r4_rndTbt%0G[s%bqGS3jcXV&i#Y-<@Ag4)U6ZM>&0n)jngBF.SSjn>blgb9?_%tctO[r0h%9VON:G>'=FgEHWs`*!Y6f;-eaWDn\ResQE-ae]%3jPjO3f=A@j@=d_dV^B%)"J^('43[)NJ_Ea:ith1a@brFhrX[3EKS[2sqEW;kpu`(Z1uUqm=:B9cY4Z:/X5jZ9pR;pT&jWnb?]=HlqqG+p!oF(o3d@9?eLG*u@F8/0NeWDcg@S+nBd#Lt\XE<.^H3#o+T!e7gWr)u_2:l51V-.Nk_KX2lm`KSq2eB_6B<6GHMiA!&_o-=++A&opiq]TQb2\PD48I/Sj$VU!j`D64)4f`Y\DVj$QGJ7gfRFcK$9Np9hW:SehiEZKu:$3!OOC)a$Tq:ps;+'=6N,(](&'dG;CPOoF1,[;(tt5ESaO/,B6!$lX!bsX5;ZKZW1sapoTbGcQSgUSttXSV$NM>:uWkY#2b&R#hsrTVj^$qKq'tQgI?<_)gls7lD@!`H_*)1j_]Wgo6RGZ9M?aSo,_[JAm+q=e'b[VYM?Nm2[aq)h[to)&dHV7gB"@sGkWSl)m)h%gq#`dQW)M3>s%DhA#YsLBskqn?+M%M75h8(;7TqWLTb%B#B:=D33^+,gAn>D7DCq3]1@^NWA.OZpQ]sS`[X464:L'**EEqgHOj&Yl+D8ZP*r)Ci5L/^W$?In)/[qUMEKJM;:%Eb9Kob-G,J2Iocb&">@?Ai2["^K<@r%]GFOo^WTtpM6p`Gk\m/u]8)'VA?QUVpE(.t!a@LSmLSK3e_ZMNY_Sb-;n?M?lrf(^O"Qn2T7u+\Gh7]]AgJ*ag'Zj?@J72%`a"\SE!gImPTPlsZ;1N+uKh(HJJ-ei0i)pP9oM@/A>0Riu@":Rd-,dR+TDT0`')O%fmZY9/fR>T-K.OY=$cto(.spA/0J:[>*QTei`e/WN%;t`3;&O!F0oBW(cq%Z7Wh5F.Rq#kTa4*n2`r\4""5@qujT~>endstream +endobj +413 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1032 +>> +stream +Gat=)9lldX&A@7.lnN>t"j%lpH?.GEHK_1]*.*f6@!26`,a$UTp"bn<'9[TDGF/sJX`iUiHRj)lnDRe,4+Ne%Y4"Ai!C[G[Ahb"Wi3'S7\DWQ^P-RV0V1:CZ_ADXXA_b@f^'%*%k^Y%f3s(_h9SsNd)KVKBYRLdKT?oJ5es#'(_Nm.dpci)]`o1bDA>4/M*k>Q$MplqLO-2/RBRRD$])7Gf'!H?mBqY*,.;5FN1Le6@&lFBpD#e.^VPV,pe/cS.8NjlK0&RUaIR>-oF6pKGpq-WCOTdmO1OtNOUku1A\?PCeAWF98n=DPE(QnpFLTiP&J_X00q$';(85sdkJRumN3`N_W-e)%^J0cRoV_kfX[Dq4lT)0c@A#AuNH4;IK6ma%Ijq+1!U>qZUs+**#6[T:=SgDdh,]T_>3Ur'N*".T#Q+BG^MSi+*GPob9HFrji`$P(:a,AKR#D1~>endstream +endobj +414 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2376 +>> +stream +GauHN968iG&AI=/lqp>+L&g+$a4#.YEG$7qduuEr"\D8[:d@XQqs1io78U?ZR`Squ*`F8+M>cr2K32"S>l5iCR-"C@(@^iM!CC5"#3%^./FMKk`Od5\a@S1D*1(spKuaWBMVmna']XncbiLo@/97DgqRjiL-OAao7c6Zu+]/McK4a=c/l5mmC)(A)!%bPu5-^!_K.Ks8g.Mp"/BctNB#TYLeU(DGK>e(Ll7;j>5o(f0',tHl,t[=?J=d/PIR\2da,$E"RX/_V3B3`$HQhB/ShML[$hjd$_-8C6eHlbfpf\BE!X:YX\+k*;nH7*T+>"n39>2!`'pfh:65-k@F[:#rU"U.s!sA]1,kqE(CrnDsAiVhrdWD79.NZ=pr!IicX@uQPul@\^+<\jKPi_$J)&,l3/d&'ca\&=fh9n<)Ag`c:V+^Nqogl71EHhEb(?j!+)M0HO+3l[UQS^Q#7l1:p(B2/nobcj`e,"UStZK5YK:c[NYI-]o[X'9Q4'pS@P]`'2LWA]G3g[clP6%O@(^Xrb=OrO"L5B?r5kX^E,6r9h7OiU.5#\,-IB4[0l4PE)`XGp+#F7]oj+$/!l-8[TWt`A_FV3"X;i84QkpkIc(cm,]4H6:pVIf?9-oBS>rSe@a$5o8PoYQ%\^MkZ6pcgi@i-1hdCPX+G^_^_=1o6Lid5.POgbE;E1&s/PL3Yoi3s3(#Xp%',8-k7V3t],of(,7,?R@8O0J@l\0T^ihfb,h`9"8Q7TJUMqH]-bZ1c;@/_<_JXNM%BWleE/n?)D0cD:6%2[P*J.[@2=P"2l00_=7lg,(h#X.Mb_-87m$/G(NmhH/MWT4`?Y:QH>!op4-#WLmVr8hW7X$TuTIHr[;H,T1`!BO=d/j2;6um,S'Gs"Mk/Xlm_-2[ZhAKJT?0J'bN,9?DX'OCfSS/L0[9Mph"[_qbtY'#X)',n\@Is#OUB6G+?(3=-/]X,ssH6h&HQJ!C0O7)"HR*LeUeaZ$`d-ob?M!TlF?(am6OYc!h^/7.m5>Zb1"J5H*Wj&2.pAV7LkMdDGN)8S`dcUn&UDE'ijQ6]BY?=A$M[#GC*AU!g#oO>^u6&B.PjDAT6.b=V+DRF6MiGO7bXBJS'*rD"XA3"N1p1h&u>UPYBJfTV8LA_De>;^^+3.YM=()=a]UA/Ua&7bXd,dPLopGTWA%6cqfc)j]eMHOb]e*ld&MROdr7bG[ajH`R!euHhF;qSKm;=XO[W$Rq&-PZ0L;Q&0V?uI9f7!'!IpD5?mPIJ"$G"j%;$(eS?nadGjgU2k!T=7t!\s0sLQ`mjHk,Aj/UJCN1X#F@/Js3=*4@Bi2[qbGriFnHain;!7-5-ZnC@Mj1auRk^)s&jb01Yh%`+aXB#dJf,%f=gi,6U$o!jkT>#;Ab_D6A8'Y82>&X@e5b'_#pq:qq\:Wg8dg2HZdc0Vb6B#FX_=#k5NFfWBRuFRenM&a(FDe?unM2qQ;1c&-[91q6.r7_[n8-VtRo"U:)B@d_Zk1^%l;PSLNrU^LA.qS?YmCsUf\T!&pp5Ond.l.*`4+7G^"Nm0)^5lHoqmp<"O!uX3Eh?ceXm!4u)k@-S^`7T=R4g?&A`AF:O6?R$2qL.83:_+BtmY-"lRK4YQSQl\+1XM"N*;([t#)kMMqi`r@7dLBYadW;UVLF$>c%$Dl^1>G&7IGP7IjHLB3.[UnHqcX#CCDt_0<\]F.I]04\#V5of.SapC'Va/rXcFFG$*l^=\gXB5NW/=f]uWXe=8FM`W%29A[E&KOH0)hYQ@YX_r'Z=OOVendstream +endobj +415 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 3027 +>> +stream +Gb!ku9lo(1&\\'CoT;m>GR!'XS(=hWV7]saAs00qEYoEV&daBQ%RA1l?_?a-J-78u?`kB=Pc?-((gq[$?b_'GO>t^8C9G\g=08/R[pb7-flPnN[N"Sq]60prOpg/p:n34LD2p0%Er.&ZiK9u@^(aUlL#:[/8kr?1pD0"DYcW$XNId(hC])4n]@Ksa4&p0@%7PtP'\(m*2;>",1oQ:5>MX+4"mR[5rrpm/U%rB)(WA-U0AOIdl?Z[Cc[gd-COF?8>KrYS<$%]dHW[=3W3S<84'JmEAPTjP]@M8$)VnP:?Sneb2h]J<]?tHLN,e7#K[E\!g8]0%VCM>UfA9f2%pUs?c+V\.Hi9WGqa[6YZE3NOHJM%-fh_Ba-G"14HN"T*:"(p-r,7DZk;JihH#W1'G>\#Pk"%QK"4n3(D^<)p?pT=S\?Ng62064mbpAG-;s]eC4Bh5I'La&$R=r9Xe>P;iV+Ya5Q^04R&ckc_AuZ[eNLZEQWN3ZHm,QX):3cbiJ0;5f4_H%:]ln/6hQ`DUpZdhk:s_K"AShofUBK.etD3VXd6R>AqMF-4.%PK]I$3C-P1Q+;_hA`Lp\*72/Gn8+QV*MDtL\E'QXef?rtumX30HkVNn_FQ5)cUT7nX^Ygg\u'&_BrHAHPo$'HQVr0H=Uoi#;-g^T-t(eAs`]Dj>+lMO4bNJWV*df+#<=%Pq)u:OW$Nj2X^HR(Aqm9St=O)iYriM$5)^U+g[)W(+VWjI4065.4b'Z?EXH2/J0qr0=-A`/%jPTuFONRTi,oVC^J43to4>`_#2m,Uo5!cX5XV.g)&+W"SdQ>@]VQd""n0(j>Nb@YYR#SWW+%IH<$,H;^da5)Rg+\&TRIT)?Qq_:1C=4+pB9ufd=Jg'Le@j`Y9#jkC^J%P?KdPFH9@T??7%jZHK6sL8=OCiqg"%O#`REf.V_Oh,J]%1`/V2[iSlAhmQt&lc/@LV2[Xgh]b['dafc5^=p#1Z^mA[2HU5;e6I'AKf7?XK>"6:1//rSUTcue75B1C8m$SnF(2'oR;el03Ap+@lRa`#ih]%IBSAA.RSU_XnCO;[d1&R-2Vla=!EP_?qlh@!d1KWaM)RI83Gf)^"FEd<-JDJ5ML2K@".B+9"gVg3>e5rFK3QP>@LF*[@7GLZ92oD?"@oSe5bEWhdm@/p.V7._u$$^(U5lMJebUY(`Y'I8`Zo(]=h2h3f,W/%bVgH:[b)=k?IInnY4?,]>bRt(>)tk&nTWW)r>"QR_NFgiX,D^P7!8@9'=$Xli"-m+:?NG7k]LY-]8$J(9BjKGFKnqS@67mOd'aCeV&kQlYP^'Xil88?CE>GNY2ATQ)2]Mb$=S[]`%r4rWd`FjJQa"\MX+pTY5M3I\]rl[a@#q"!apiJE>djc5n=f"\]]U1#)/_tI2_thGY(HDgZ.Y2O.B/78brI<5G@C8U:YT':b'*5F;_]ZH'phSC0MT/uT=>g!#GpBr3._Ye"PLYYe_'sSuNSt^8h;GHI(7efLR)O:eoBu9@2rFHn.;Muph#8";A:h@srB>_`k1M=DYXC>FBCpIun3e5ioE+PIe)t"]Rmsl:bPRSUa:2a[T%k9S6XBUZ/^q5/cR#.;!#1s;k$9'lEE]c1[(l&EN&,2>W,shI8$FkH[[-u2J?8/Jm!fUF1;6"nZ?tbp&cl:.]a>g0.-Qqu]F5==*l'L8r/e?KMbq&VYHXrUI#/YOEHKm.pL^b@`H$[gNY%Fr+rJdA1r563^.P"^,Q:C$dhd8VO2j@cpfOVUVU\0fRMLgUUt;U+ZD'0+jr/Ulaa1<9Z;JN*T0N[<]q~>endstream +endobj +416 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2960 +>> +stream +Gb!l!968iI'#+6EoMM39*!1L`:0qkE2-Z7ra*.9S$UmAp'9\F_;NBqlD`9pYUb'WEB4bpp>V8[8fK_C,^OF$.*;j7A2(\k.E=Y9`AAC1[07\>V#PZ!LL\m"S"m[T8cuuoL]SWL*Ib@a7msNUBnDkKloOhMUL3ea?g[Rr7FRWCXI)o/_h)*8\3%p0lrjM6/@A$8i6>($0D<-[mj>baP\dk,uA94Rar2I-c&+SM(:TI':>gh!$)PugV+Y\Zs;V8`SHI`S%p#Tt:iE)A2[giZrH[R#mjZ7,HGp/-4B[JrY*c5uo./&>08R&]0u,/m.rpC4Gd&KqiiH=O\DTBXIq^h;_`kab2gJI(2Qpdk!>k%4j)8UGQ>2rHUiO)2aK2;ldm#Bu.q2Iio9>VaaQ;,4aZ;7HD+'kAE>/m"rIo.jXMTO`nH080iF(G3VP)k=<%!3'C,9b032<=:eTX\;EH(&Db`,B1W@=2bL(s@iG1B*`b\e]!Y4Mbi#!f1bd\Ltf72#Y`B"/Dh%UA@NnSkWg`h1Ol_,];5f1W?h&XESXeY2?p^>onXn^[_'A"fd00Se$VF\,X%T\GWj,E4O#nr`m70Icq3432<[J+m"7:fN+V?\fM6*cT4\V8)D&SStk*ar*0FE3eGXA2T#sqnF:*4iD&B(AVEt2WdQm>GVL<$`p_/?@;*<2fcbEHm[m*?pP<[Nd+\&qbH)<[/dLs,6K%S^bnVD(G9ukpa&4OZ_W*,dS3T>8$7.-Hoa(p`@DN5U(k,rOl[1%CmJPK%R:Bfqb@_J/g+rs^r"eK3jK6Qjp?XqQD#?02YMJpqRM<"6^[8rt;F""V71hkuW,6Y?&jJF_hIa^TZQ0gTS^&;r%hr+`.j,4n#L8o<%49umak87KFa=#obQWXFKSDmJOPSJFc-8('s]3T9g1^fQsJu"eJQqt8se_2\K*_;m'*gdEBBBQc_uP,>2WQT]]m@4VKQWgdLfYqJYg7MWPN%&L^_QeD3A%_(deO4G/2gjJX%C_1'AB[^);PR6Z?TuE+t.GO+nk3JGL,gBRR;k1('n#>a8]c3?m.#PS'MUg;+;TKO(<`\2HNVSG#B,H<pu)q*r7:BY_fFjo4OCga;7=SWgTFlqg3/&K3*_Qm!]"G>;u*SLS"tK+)gSMD`,BRDJfu",6FaDY`:u61p&h*2S/%HO72Zfb%7OIP15uD9\IF\^^b%u2F>?dtc6f0ed'+[(O;?#nWIO&=>,RT9[*NgXC&HEH&!X5'>$S-7E12t@Zr)I^%H`q:3o:0*'N)P+&=h/G=YGY8mIkH9Z_V]`:S)hOocA[+$rA>)c>E[EhV8he4_3CFLh\D)RE;7((L2:D>,e?UIk.+9G;A#s)5_-QV#hM!%-/s)aW=>h]A#(7`I2+P8Ea^Ths=2V8#7"dG&lgG&VerRTonBUe"Y308HneU1o@IO_N'ZB/7:G=eIQ1k#E0+WXEIb*!b4+5t*!6B4%/m*#,;L,^.2G=ItP8dhT5.K*45,[Gtc"Ng)8J/>fpcDtDul4Et/gq-dM*^L4q"E=)lLWUiD\E%K9=.*s+a]_"V3&`r;hIlf$tI.hhSuOL]Hbo=p,bgl'K+)",,DZ2'lX$R8$I)lEA3"dPs7;-1Ti(V0t!hJ--^`fnR'46LI5ud22B4ZI\F=[Sd+g1eo(%h=o]k`(qTcHW68s&/mK1R]7W4%>GRSFjm,GrBOgb4is6.uK)YiBBh:9R1`Nol1j-'XXjB=*&?(:XrU+p(We"H3q?j+b[e=m.M8#5TScC>Kl%<3b_j_M[/Z&?P8SN^<2gWDWk'_Qi&)EHKa9.$nY#Jq)MbFYIo_55e8D9f+l3ZH.W=PHoXK@?7r3J\%`a*66nMrBdAc;XFfF0JN^FDQj%lG:plu[nkE??:C`2=pP\F%$tU>7L^[(SHg:J5d]f2$P'>W%&Z>=TKR9!>s_0/endstream +endobj +417 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2655 +>> +stream +Gb"/(9p=>O&\[Y:khK2G$-hBIY4?#gZ;XMt>dB]q*3qR5JH7b<5%k?i/tL.7GP;(?JL.P4>l0U499_[I(^t`%?bb2UjM:9&bq[KE^0/:-ml>>+<[i">EaeVQ^0BFkEl5MO?=5MBnSJOofTELbp/j7JDZq?qY2'b0+=Q.gu+^8IgrkR=QT;jql^DPo+jf^5/Q]%.j:7)=WO206,9WQA#V?(U4Au>Efjp'=A+H+Ts[LX9L'>8$)uY=78_6&=Xg>bH3^LsY^0)Ws+I!ne(q5erJ^nqht$Z`1NU06Wc9+:$aVLVkc!0bE'0VI(ZaW-JeF3=E%I?E]j9:'=J!9k=RgPb9oYnMn=-LiS"d*B#J7)iSh5b?t'*pQ,k[lWn;iLP]9XFjPenr%U0Zd+`DHs!Ni.g`&a(i=>r)O&d)@&k0BYuFa*!S39=k&N8B$Akqg*Bihrni?%!kuM%g`(6>Q;6^:#*+@@X!f]"89AMpg.LA?i8[U(L\<`k8FT;BE([:@-e?aL4_?UVck-Oo;_K0M=cF*R!Kuo.U5gS\lg["[9(JLn5+h=Q,qIY10MDrZ7;2n.j0]PRUUMQPg`+=\E/FIb<."^h`\u"=$p.ld!Ll".LA_B,,.j3d\oe#Z=la"JKej/+Y&kjdL#:jj1*b_Z%CE&]==`J/8DS,b!nU@3KKm_jQ>Qhgo.M1tn2/EF3>Ir$mBLT-ql4:U"lJakdb:E]_COQbmWl(m58%B@#a0Mcp[aFid&Jd(8Je[jEdml"c=I(2<]-Cdgto%*oDjD/gF[0Z,,P2[7]mOn2UW@XYcl?#u28d-K\`Ug(IV[%We[oC/DGD(*YUncPA+oE;Jfea5ZTdeVCoZPcUDYF%`Fs8I"ZJFW,!Y%u>^r&Z4]+FL]%lr_8ptQuBXr!ln@@&\,CH5f3>ICKP0lP?e/hoKLrlO[W\H$TB:#YW1rBGeUfNrdTp8G,&GCf-`pb5:^1Po/,F\n+D0`KDma2Xa=QB]K!%W"K&,\5^\/CZW")h%h/Ihc6Y'Wd0Q]5rOHQXk@]AAQ7(WXksIb"#u_0eK^/`<7/]]6mN>e2O<"?[,\Ul#TSU/Jq(g@,.QfhneNCrYlZ8YRVnpk5S(%Ioj`=f=itqD[nT22FI1J5`C"@'AP9s[/;rTM[Ah7Qlk)0V7BV`521;lCgV%k_1K#]Djl@o$s"k(;jbD"R>60&[Xgg`?E]3oiZ>;gJCh+\>J`hd>f1=hUQe@(oKpM>SeV_B5_jSYI4o]W;/Xt"NCt_kjq&kuXTQe!Vaj7JaE/G8b"s!cs#X%)tOm!(H)ehF@(Kk6%//@.22#sq75CVdjof&;q.OV%W&bA\+jT(Z=BR\"XL>Ye9:",'4JpJZ)ccDKlTRHU%`fH4GLZ0GV"?#K[966h@TWQhO%C[PLpcNk_DQ8HdJ[cjn?H-!$,*l`o;SnJ%Xnkj

nshMM_jW@#eKGJj<7VEmX2#4=N,<>#p!M`i+L2TN8VQM\^jLRPPPHV]3kbUBt>[X:]#SPcRap^P6SF5FAKP9P)p9"j]c]TQb&Q`(7s6$FNCd6do8;%K,`'UHCHek4f#iHoH#JM.Be!ol;0?+r?oqL>ao%h3M47MQJoEanj[bKU2p'm[%46m`kJ.@X):jlMFBEGKUgkgI(\lPm6Ji^bR3qlu[h7+ZNQ1H]B+1^L,shi6-Sg.;GIt0,&<`CLG'L[!gU]o17ptXFApX=2:N2YT5/3P_uHkC-FbM5JQ#_LXD`9s4_"Vj*7mC2s?]-b.HKYOp$b*XtFqXXOOA\Tsc(rG=dJ`K^F1(gdISfD;30^h1&UMf-s3u3EA0>Dla!MVH?%Z%__G@o7G;5RT:dmJh/0l2-h:1l.'NpVM*3Vd%Wc$G;.?H%P7V^eUiuK]N,7eoG)pJ2SXDfnnXQ_s@Bnp3TsLN$rf\8p7&VW#+m;Eu&$GV9P'5.J?0,YJRSIVoM)WPpdY'S?S)B@:"mi-26&DR6I]&`_SZ0#cRRigh;GV=t.`,FkOm1kH"!rP=$P11&bmWj^VP$&"\7Mr3mX3^(3Td/UdK]#1V9Ml>.Ea7a-;PhAC5i=8PctQR%'.`8Z_rD?9dF/.9pM[fAPd*t80spcq4)043c,-bd)]8,U$))@r6KLU.pQ5G[,t9"-`Id2='bGf2:8ea/*H6qSGnGM&OLclamF;C&XBH,`i#C?c_LTCBj^Wh^e*Hq)S.-9bE%:!<%V\_:k8fW-b@Fk``9RS_/P@%BX2hsoUe(X[1KTDVAIm=bu:c##AO`C?3IB]rjNjCGUHb+3Kt]:8OZSlm&N*PG#qD9S\-%/IP_Y!iB]'XKu,IcZ6N/NSFRo^B(S\6endstream +endobj +418 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 3032 +>> +stream +Gb!ku>ArQA'n5%I0lQ`^(-BDq$hV7AZ:3J9YZ\S6EDVV8OYp9J'OIS8Iso'f6dhbuH#7&L+p\EM5]D([h]/RL<&cKcnEk(Fh/rKeWggM71A"jgl_X,.@nK.]i5o>Ap`14mb+pqKOJ1]P@@jF"&Qsj'ij#Y%s).U.X9L#eh.\!l4\ni<4>:/%+>N\%YGbF>]9\!?jpFElQL4oF1T-XM$/34.%\0;6)bj\=.iB-$b*(-7,m)"TNH7NH6,inS'1>Zj_CQ*@(f0H4GWCYVa==Gte#.aP/J/fuDF=I/ujcT(tI&+I^B,'!NX"l-c!#<#VS(gpt9OKH/sBe=MF7//m_PIkX.L\I5Nk9P2]Bnft%FfeD;k]S9nQ`Ku!K$#$6'QVF@!@Ap%KB6[-r8CS%'"-TH;H>Irh7p1Oo1lT;UmV*O@Q/Th=a5"!30J=-rd'TX7@JVl,p(RiDZ^l-g0M8_MUE;8n*kLY@Ns`<`c-AqZWH0S;c92("Jo2&_>jh.:+[M,fV1-f7lE"aH_^\!D??^NB#F$_FR1tJY2ONa@=G(:VJX*1M/,]@1d=QiX!?")u+O$eFs-CUVAk_qT@!:$\r&O!ViKE$YIUh]>u*&Upf(?Wuh?'RYFBZBZu+,u&(6?=gVIXjQ.7]s#keY#X/l=M9\Je4OTNG4%V.J.1GqX8@S]^EBYmX(A!WLWX<')5?%AIX/_JSWEunq`lDCS`Ak[*X%F*rRP[B#hdCUMCramUe-kf=:9=RQQ%si=hdODD6RD[=l$tjH-Wi:VQ825O`2i67KS)]Yei24iCC).QsnVt+H@[B"IBhl]hGp/.'2*bbdf>jNH*E0a,)\54e2j""#LfdQAnn$[H0oO'`!/_HnZJJEgjJ)7Z+CM%\,r9p4f0=o)[N'VkKUWX*5YAT1EA"T;%i#bA+7/At(bkG;BU:'(0MBf2^GT1X*cK:?$TT[tNG,SVW0#D=tg?iW]Br.sjT>b%<_L`o,PIO/.$aUHT/3oZ3Qim5dAl?>)4#kt2&$HSe`+K:\MXh9qmf4^D[RE_EU>?:ac3b2HOeaHW`3,Y'ogU0)R%S1Qk8W9Y]/Q`5@[[[Bm<4mX<`c^j2`i8"b]hse"s[-i2I-f]*N^Tb:D=0,1%G-^;5opsa=ds8.,O?o@/K)aCqQUGG\@j4%T2N@K,fSWurOg$KVqX4bg/EZ-$"i%4W=qjHf^oP(_;l^5qt5p^6KQ;T:;9$mrJ_Jp_]oMG7i[orF*;>*o'%)WHd&B4-Y&I.\sQFng`=eQ35CH(8I>5k1CK!0Qk'&ULi$k1!*R0GD'8&KW*J^i:6t5S6!:)-:^5EFlnDV?TMeUOjunHkZZFI6I6q''9QnKfXfdN?16j&9C^:s>Q33,@>%s&DW$f84):eE&!Ij[PH4l(bn$J`-_%MHqLm*b):OADYc$blBQp*o0$DKSNq`QeCJ?`-"e[oGYF/jE!c5)J1!\`\4BtAGZI5I6=:q`(@.hItid!H'Gp7i#RX%"<++E'=9#Y5UXeWp>m#DELRk,Vchn#E5qpM+8ToO6<.(&TH$\*G6/M2$%iY;QaN,eJ8=bV+fnp6%-h<><^\%q$ESu5f^_?8da[m+.#C@Li?Su_%kfUaK"NNZ!CN>jd*F5SgmG1CRso(JZDnaN_#XNY]KLGV//hO;jmA3KKp2#Y[u.[Nbu(l7,,%_@RP7]MIG='e38Q^Jc1^KtIA;DM:q3cW?!EZ7bMG1\caJ:&5@`dS8L%\lp8l!JalqjC`ba")n.Xi#L5l9%P#nJ+Xq.bUZ+-KA!36`fUMmB)bT+4~>endstream +endobj +419 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2749 +>> +stream +Gb!S-?#Sa]niaa>O5@R;-)p@i;\F3,/[BS7eh%56MF*Hi=C#)#MK6AD?[jk!8X=0Ap81*+?C]X[3LoOUcAX%_n<3F8X;JR@Z#AJj-5E"%Quom^B".-=fu<+^=[RK9(Wu7_$QOr6n/n0qD1%UE(HF`@P5EH4-!)R8L6*NEW[7hK%3;'4+-+TrWS-#.(>qVL\WW(.63a'R+8YIE#VQU%r3DS^.Z9-V8%:b(Zj#IS++cpL?18Ei:Prdj/p09hMkXb9<:OIaRUqpPZ"aU#Qgm\SD45%;N(]`/R/W7^4bq9[CDP7ZBpBHZS?Xn0l+AKiDqOCe'MBQ!';Id1FIiJm*3a:<;n,G*D9AA+a*<4GPk5X%:f0Hoh]EZipJp-tm-sWGq[;hsmG:mE]s[&a@ioD`FE^,k=`bfK0LMdfCABp_+Vh@J1^N#p;9-e)7Wm(POt7st8,K?;n\CDF!,m4a/M'%?Yr/c3*QHaPc!smmW\i>o-I+A&TYkPq<#E+g)ROL_EVCGp^[%UtP'sOV'tM1t\QM(=4TJ/^)3tt^'n\$TCgEngE0P"[b[..LAEk5IACn;WCqEB)A^BbRO=G9"(aa$(K$UV/i-[*94BYFgBsjICC&Wr(YSeZ0%Ld'K(CQd5XB:#MlNBdC!6&Ak1O3r$r]mmqFV*pW-&]j`sdg),_sc&kde8&CPf.5aM)EdRiU`^V(ec6<,7SA]<+j2jLV@+QeNP>;BCTA86b]]&3E*ZpPlJI#PR(pQg<32F3fUBHj4)kc*WEY-s#s$]*;^c-EAq6%FLX(>P@-$W59VP%j'bMbofa"R,d[bBss@&8F(RdirKarR=)EJU[,rI-/Hu7I+%Lc1IqqqhF9(m)4La0.3o,"h7TVHJnJcki#&@,\_&0dAe10"B7mBZMVNjf9MNZ?(RkE7')eOS+6+,`8"7gEf](DfEd\2SXefh3R/Mk,LXH_L[V%Wc0?24uRaY31!SO#l&4:"QJed37HT#)WlOO8eNkq]o0gK\P@U]b0@7[nDoH)"FAb_n9/77&Hp(C#eUh8MQ_H4>PEaqJbnj]0b8ZU!f7Kpd5aB7pjR]t4!=;I4h>!V^UpOYamfK?=j-tpNP!oRAViSVE&q[-Lupc81=Qo\SncS!o=O[66AbG&:P4H4%d;1797E6s>=_t3Q)BKC[VW8_=+1j@;.I><>8mL(AQhC)\H*e%p=LUukl(51C)pKmk7gO`(eXd7O6oJ%E-0M%XJr]3/9tR2TrO%1ICk;GPG0UZ,Nu,K3sJlH\hFuc^["@rmn*helPVj;njQ(KI3iqaBsFdiGrA$?M]*/)1L*9;r7MXBn*a*JVIe??BpN9T3p$@O0%!@3\?A#%WA0It:1pGNqq"]sn(48T"b54dnRJHD)QdF]&-uV#W%#`#ZYn?6!l^f]=P=:*mQ/)N"_=?-]YfaYf*NFRb8j;+pZb%*^k+0>/UQ'ZL8@CGD0=Z`UA&*HfG9nJUgq'p-4ZoG;Wp."E^:aq+hWOQ06%3o&jpt_HXX6`Y7%"a)JWkci(VV22eBenI$_mS1a/H!M6e_rphF[upNh9BgMgG"#to`T0boA1#.S<80sN24+#iC;(g]q4(`L]&/+fQk8L[YP[KC1/pq_,0M_'<4rrf173Y&t9.qr[4?0QT_02@=efU0p`mS-oVgS^!T!91E:)VM?3_C!l0fo?_7CAMqL^T7fQ[>S`A/!@D`4'6p#@l7m&Hs.HBS.Wt##i!E<##K&Nf`-G#&I`%DJK%!CGB23!Z7cYjmXN!_s7#p1ea;bf@.KKR)rCG8O$A-(;sr8/]"@e71UA00a!%$9_'MP)Mhbk3gT@Xi@DOd(GL!''TRM+^XqqDV2=+1u>V/m,2t7eQD9(>M'6CL\.mW3pP9VkU[$8#^\r+sQ0VJClAX.q_D=@oLfmG"h\-,f"Y8[?gLuL)sEFh$M1oO<3m\YEjj.L3JEhQ*M"\6gR-@ggXCd6(i%2@3$qR!?nlN&DLKQ&R6h4)8cK`=Y5miK/tpoXa9.fYZ(_-?gI"uA>#Hj[tT?7aMUKqcAum=n[T,&brA'$tpI^pOSFlFUlXpM*PHGGGoFeAendstream +endobj +420 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2879 +>> +stream +Gb!Sn95iiM'#++Di6dsQ1jVCKes"cp#Nu=H)$>6UKpHDAgQLbD-%"(TI]H"B%pMeFG7N)]"rtOTa^We%?[YplQT4K\eZW+IaKK[Uo:nr`O3C\D%IAVh5EgTA5Q(2jD-)IsmI>D2fMr6!MSsa>N)ZJ6GXR=1-Iq8Nl*rcKCRkhg(Cp,Y2p=qKF&d!RQ8GF,c@?SEWA*[A]62QTQWOBXJN>;$o,:0HA-4-lsI`&K$TgTg"\:Sp<(sC#nM@uM^ROYAKW:3s8C*lT6:R<`1L7RUr/L+^.mqLTq@UF('f=-D,][W^tLX1_6^Fm*[hZ[qE60Jd!niKUdWP]3mT""5`>VQSOia9O^dii&AR(]rsM)P"ha2mU$K9U4;3Sj@=nHr4nm./-OESb%=p`Sk]3k`<:DI_XT>Zsqc/tgpX__At"\JYU^BU/h.FAC\CL-ejTrR&hOks)Z`+6N4%l]EJ9bSIEHaTP]K,"&)eA*V^OX;4]._+F)sS+./q>L5UZ"ugKcHm%1b0*3=U`irG+aY\=KNF(PGVoK3LZn1&a@hiVD,pPgEB;DrI:kgCL%6$H6dmo!]5h.B$KIeES7[-J/^-KIfiOVm'd0"^0RhCaeLp`i;lG>)r!gU$JR:+Vb.;lrUJja;4n^RC[=a!X&NdUNm@H.@m*8Gt"B(jSCs2&Kl#d]S6ol,+j@3L,Y5d$/67#+*ssdSsrhU3+?dZ*\23\cSC1jM0ORKV!7Sm-k#)s"&,LHecF11]l(#0_hO1Es0ntJDR&Wgi!==E'PuX)?GN^Oq$/@kj<+u&W5QO44'f(k[[k`N^.&DTEKZP(IXg#_="^gS\f0m@<>/-6HC?S$#!I5#oMB(N6O58SP+S*)q_W3U%oIr5T_52!cAgFVb$'soTEZim9ck_N6n=1>_>;Xc[ck@5r]P?6J%04_+f]tBJPRut>0,M/`^lb"kHJp=6f^O8-V&jf=po(EJ8nGRo1\M'olWY!l%?lVMAjpE`8!)gBJK4r,4&-g(W?1u5aO(+V>+?7gqEXd\%Z]r8/OEenZDG@l"TQf3!P,]sti$VpemD3M(_\JtZIlnN0,B/9Pt]s*8$FG6-H8%h;O1X*J@1KJ:he5i+o1R#Dn#]HXRY*`sS%t);E^Ok\!Eg'F#gj6lRBeUeaK(+R2pS7;1[>[$gBPn?A*edNsM0uHp2oTpX`IdD8_ngXkX%5%<]Kp(tpNqamC_.K=oc,SQ;+*2^MZ.A,gDuc^Ag6YGgrp@._P!4]ZA6_e!!hLR4^gl)DCO-`I4V*a?Z,6@&0%_gk!US7Lq<95-cSE<@UK-(V#"s5GDXS)ImY?iA.IV?F3Z-(UN*;O/+3Lt/"gL7psb`MfU?E@O#E`Rc"D9h?@](bT'PnK%T*^$AuOWJhHLsG;\4\?CQbZ>''/X;aNe!]_2W)r@MJ-)g!=r4Z/t%Hi8"dGXGEcirGObtgFnUS.9Z1DJ**pe'l=toW?TeW8HI4lAZ29rU*-k_GD[6T%$;b[m\3PL361j;Fb'Nh5OW+PF+AcO)aW9]JR,rUemCRg.EXV^4J''ilV#~>endstream +endobj +421 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2532 +>> +stream +Gb!#]a`?,q&A@rkqWQ.**!0AfZp!GP.[]p\8fRro*_FLV%aQ8gnrg<=[Hcp284^5IBVnAJ.bZKN_5m==jF17gipQdHc0Wa?8HnR7grYTh]l6JSYK81/S@DL``]2H)BY$E#Igojm+r&\,dN-l#"F#/0)^>r$-n_EQ!ghABJ`2MFCnJ\sEt4Y9aNMREk3]jiDNn*#-q$@^gNqkBd'#a@2u%qT+hgj3ZPAGOO\483G\Jl,Dj-(8H(7)?(XlM3R\Hbj.l3>`">(>\B2-WG9>8_,[nee9dEpSQ'9IORb5pgC#lBp#;Mr>M5bllD`M]BUfBe:eNcMg;(#%DH=J7oP?9/npg0/9*:'Ds#dVMGWO%;5'.qO2^F3LTqe1l\4*[H2QI&>5pm[0l0#M&+XjKZUOG+\KMnQG)`r-P`)b<&LRY3Xpi3W'CpHR7m8;J$Vod%E4gU0j82B#;ogb^q;V"2;YQC/Q^u)C=g)J?l%\;KXm"*%hgtB+@+!k8.W-aXiB\fCinuPe;PfK>)Q@!J\h],p*DR[.i"IC(76dDE#Nt+Jq/0kLqqBcsjmo!1T)mt#q-hpI!WG0S?,_&_\FarKn7?NBdKH6T%l-;';_/Y3b4X0*b(u?7mG"$O2)tsHX6`U7QL<"nU'#O_MlBS,PjuQ:r]G)*9e[FE*[)ktZ..ID5Rq5`/_*(km?\sRbagQhn2+RR>&]E7>^P2Qtg).??:5J5Y.ePC.fNBuP$WO),+G4GmRPccVln!F7A@Mnt2Jfb*,ql]J;1kTkT0@'r=nJr\89f7OFhM;WL(jbZnK:s92g)Q@WWDRN?UdA%DJ/Y*r/#q#780:I85^ImoXa"QrUoF=3Or-b+O+[_un&e7s0?4*NWi$[Q\u-&>Z?HGe)\3$+a%.#g*S+aBqaL_u*eB>.HR.VJs?0(>R(Z=A*\kt.I%A9ta54XLpm\huSV[-ft*$f$$/3BS"Q]U6S<5\"#RG^_h(n26O1J!YaM#p$k)KXpK(S+AA?gdjt=f-B$\nG@oj!\XWIu!X>MVP'\N1Er/hI2O"8aAW+9B'Ws$)0hO`I+Z4+qXu^'JcYEU=*_L.cF5c;A#f?Gi$X^;*IE1S=7,,Gb$o,tCV`Wg[h34*IY"]enZ7H5=dFB7ZBrN8#V03,Mo/iJZ418r#Y:W6mauePPqK8^'97J*,884@m'R%(c/T_16JgGEuJQ(Y_R_D'GRC0/1ZFEHiL%?7h4f2C9M*]fHhc"W&<__Sr$f^c:,bp4Nqamu?X5kA?21:_kUQ`&;^1)YJ"qc4(u@:>_YBES]APNo*gh7;KNGOI/=+$=iSS]HW%=25nTW*N+Ae"8sf+YClMFC_pT=n]f6]d$nYEQ%)JLUnQ?)971)rPP$N*GtZ`ko`]>$>bWnL6o(Nf@35(LEY?C=4"*$ZP[oCNMl0(>\\PffMQ..,d?&M-5_)RFH)5\MMFp&kRU.$gNG4!eQ^sCWGW$m=&30jFepuS;Z?eNAI@lp.Q"(]79#!B.o@If[,;SG&NtDPXjk?QS-6"J&-eJabK#1[0.<]L%WRdZZ,#&-dM,SO~>endstream +endobj +422 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2835 +>> +stream +Gb!kuhilbR&q6If6B>Y530o'#Dqs^A.Ta/[2T+Rf#[;g;[cPU(Yj>_Hh3)Vk0JK_pWhcdq@DJ%1XmWC)^Qp:Qi?-UBECS$a(?F:u8uP)m.mcDB,E%WDpVkst45.pN6d'Z.^`d%F=:T_2pL)Y)_eOVO9!G]+kNMkp35NhO"&ipC5.)T?aP]5ZqiOtjSHX1iJ,LJsi,<<+;$8D"punhSAm0X>Hh(QA.k]\?`Lg]2$SM2Zr7`ksS]D-?a!H`^_OgDbVcO]bk.#)YCin>@.oL4D@;9(\4KjPh#pTtQ^H<'n-TIo@OHZtVX[F^Z8tE:FPqf=a6=Mr,P`0lj9%E2j;1't*!$74UF&uVf^muJ:P6rrl1<9p?B^b]Aqi#hg8)=`]1feG>Ug8/0A(uI8eNNLknVA+N$,''uS.bWR_B=M2I,a7/[C;Y(d4HbQ60$1#H0;#V:48\\XMIdsHe0a4l7h40UsZPP#l(70H;2AnfN3(*T,=QF%U22R;Bg?sG.KOc4ptG+b&U@89#cp0W'FXAg3A6KVmTh81+c#2XO?QCR,bW1F)8oj)OtlN>PL%)ch&*=@E!E-iX[NTWk?*peF.,REu*EB_E/riKc]d15*qnafV:G?-8Q<)dDMX"Bc]Lqc\7^bGb&gp%KlX9_.&,QKm)I8&[k"1_pG-?Xn9if\iRZqbRF\Xd(T\2lmFM**Mk5)d3f;;!S%[6BJA:*,j=&5/h['rirscB7>=ahVS0*7cJ)[=XgJF#7!%C4klTn7qG&,0TclkRK5?Hb],EnP)e,fhkP8g\8GW^?WGMbPQI8CT@hc0#i$h"9lUl'VHb7oePW-/2r(^Z-N%3dj1A7G85URUSBm="*L5M!Hjc_9i1(h_#LEh&sG^L$9=_<^J!FB/mHtX4nC;Gdfc&7WBY54g?4+KW8EBl;=o(La"7$.q#=toB>=BAcLT*N/k6iD+WJ^Wp/7QsOkVuhaWp0J"d=RT1]HS7NnqTk*.Xe\Gm@LQh4maA'?Y"!oMUS[gr7.W!e`.#M#FCW^K&hf2.!9uGC2Wl#t@i=d=8Q,!\fO:N'CMDXeRB[B=L##Z3fk!jGnQU<8s[+r)S_ThI,AX$'G-Y!nkT($C6f>=>C<)TN.2.d@1[,-c(UM;;%[ZXg&5)!kUjH#(W4T_C%]7qg>mo&'_ZKCAR<(N=[.^;H7srG:t@q3g?gm*J^n0eMP.PS`l#/%EE3PL+GBVe\Z&1[pe'L8^_tL:`2HLo_TT.(g0+o)of^JAg1&U?l8Gp\?jMV0Wr"gj3*))IALGEBQcRL4(36cmDP&$N3CmScge(U_GB=-rYiHTDB#/s%\pAt\mn:^qN)6I=7>5+qp6lKq#ZNUC!Tl$f"T/5iItmgna5'Dm8"h2(6*"g3i?F&Dk9RQFDoOt*8Ci0_fB*I@1U)_/3jM%2Am`[e3W/e\QCo[@;+`JVe(27h'.i]53Mf/>hiXA>!S])6ek1Y5CoUL.84r+h7+CK3=uSS'!;Y&l@N=!)C"hlKgG0(;YTcQB*`Yp(da:(ZT>3VOn+()OuuI-!tGb_CfQ"WJU1+13X#m^1!L"j%TPdQV8*l3#.-(;X]@R8BDtDt@ECIp=\*ZE^?jd*#,.1RJ!rZrm!HHnqP7kTJ1SshNp=Nm>:1]SO>@FJ^td;5-7scbO$)130KG]$Y40*<7R$-`2s>O9f[4L3BJ\;Z6>JV*YsON"0\D^kl>(_aCZ2As+WmpcLKGhU/FZGNoH9'_''i8GjVfqGmG?m$5D>.9I\i)(o^ujMT8jA5:2mBGr_[2-UJ5+M@["3'JS0r-jo>`?]W2'n%2f\.as]*_S?AH1Q^^ponL_Q:Cg\$!pHPU#@Nb~>endstream +endobj +423 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2057 +>> +stream +Gb"/(D/\/e&H88.E?DM><[I%MdP_M)Jn?O[!GuE_"1Fnb/_XeeM&mtrc+i-V.Eh:o''H).<]?0mS]H0D)"`5X?f?A,fETM0_LV8g/8C8Wf?E'i7S8PpT-8btreOU?-.Z8dQ4)YTDs8qG)?U-.1$O:V6:,j;RQ.8BMk=Pl7Yi^Z\+0YjAWaHKA5nuR+7`Zi*/B"2MfP8W[7lOmfBd)2L\3&+n/U@H9QUca0bV//C/^J*Zc6XahBm9a+IqSr>,jtr6H@]k0_"h.":fG`e#.(X,joR/'>Kh,.`3DUcP<9.Drl:lNK'r#jQF1gK>`#'R3-/O7$1sRHg6k^?*I#3l+R#R-%0B\5m;[LF,$Rd9V'!h9BY;0FAN#+2/O@`,E4s#WEi-@o>,k'\+p`e$Sgj!q\<@5l*'H,CC#P:!E,fo;-+hPGDkd8N:OVajoFP*\T,q3g2<\ZhGVXBj>\%JeAhO%M\&!<9ZN0@RKV59QUB_78#YY)/'SN87L4"[<;me^cl!d%Znb'9:kBAhD?J\Rg1r$ZqtFtm\%"XmC+`)R]DA5uK9\L8n8co28BTk5C[KiDFCV+aR[qXH?"]%@SthbsDt(7O3c(PfFaL__$En;kdif,smCifhL[K1X4%Ui5#4NJlga4g9fd.7(]":9G8`^GkAlejTLDum4.,A'IZ(g#EUTcUFedlX"nO^UX"f;Dj412)PmQne_HQ."oYBb'36!"I21TfP>E9))G[3Vm!JK!aNe$I.)?9J2)J\T@@^j;IFX.4]*nTfn!e.-j$1R9'EWh:\p;#\9l'CN.aDamfDgmrZ53Ig]>nef:80kK?,%JR)F;YohX))Ib;"OUbW"=%<&6/'"XV0a]-GrYbRYTg62j*K"mL@1d`3B^Lg;27&S8G-!MG])'(+@J50oG-r-PW2/6DH>Rq4Ro])PJ6BkfL(eNF#Y*$k?>Z>iCEGb8$pqQk&]kcJBnK:2;A;JhZakaQe=*BW)K67T3/n+&u_LMJ>&5$AhL&"aXl7X(eX"5Umf_JL3pFO"&B,!t2G_$o:NEf",L1e?^#NHIT_^:_-KS0Ag.:hBcKE03_UtHQ[,mOa@Eb][[3opF3G-k6Xr.C*dJrie/@)3&3pu4K3$qc2'=:\(,L9lbZCkVERe)ft8T0@R&J*\8h29)6mb&(;i867oQe;]h/BV<,QI.QdGAO^4FLX6"/i3ML&qF!MM]()Q)OW17)`e0"cVV9U'qbN56Cot%bDPbF4pUi0(:gSB.ZqIN6IHPVIJ[]nR]4?d<[H;o-9_j0^jM/&`_H=Fu5=/7To7Nn$TF'"ub[DP2&VbC?OR+fa2*^p1eqLS5G+&oWnfOOPZ?bYendstream +endobj +424 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2171 +>> +stream +Gb!l!gN&cS&:O:SoV7qaaNSV\2ogdL;YF/NW;P_CYo%"LLf#!7`OiPX^-)Qb&:n_B;H]ns#$EcMcT.nYmKO"$5LaMi;h-eOG^W/71)Rtq*Qs#]'tbKVHp46hV<_?=fcC7Qp+1sUn2rgl-dm;l`sQ32"W$>#(&hUO?JmO[/Dm/NpE6*dA_8X=R%l=A1*$&h0R=<"Z/Ad'o*B5ZT(&gXV_IrMUK?07&,>C0LBNoE]43Q6H6`LGlV$9p-Z0LRL"i1sApN*1C=t3I"8$2.Gnq;$8EJLf+]4oN\4C-7Ho=V2Tg35S,8:bGR10tdd3ol"4O8tOZJ\XC^gZ7/'KF!WXot]R1i[K+0)9!?:SJjIba""B?5=%_l_3mkHn)E'AmD;\RfK#g\PVn)^g/\@1M\pCSkg7Y[BQkE&A&dSXoYEZbG*)1mH&]TbJuiMhnfF\6>(5T,Z09:iOn`-%gkIh2&*GOL!8*teJ('9m5&Z2Ko8i)Lc5+,$klR2Aa!ci_=A`e,Li^R"iX"a)X/_j]EoWo/#G"H*kpf($P,#'I$JSXgW\?#Angi:QCW<^hlY8I!GH&V"`g*7KO&=7]L6Pa2$JN-jO"u5[`Dku\@"9^"E&/W&F8/@(Y$#&Q\Lf+(dEfkD<9P/97\VmM6^*E06AK'2`1`DPBoP]W:!/Y*:b9BscGW93f0/9U47uj=1iSite7]D7\=C06k_Y]VgK>`h](Tb_TZtj]d&Ru'X4co!?u@3Ds3G`Lj.(d9KQ+-7n+X:]'cT7tZS$K,'tpul1;Q&e0n+Q8Q#7(LYMS,85Ag7GLeqbel_)_B_8Y8X\O*QQ^pW!r#pkc+qsPqtNQZ_dTf=3tFjOuU)'sRE6D5Z4:DA]L$uq]U/1rG4-B!0?2_n_db"idE20rIWe_mi%mKRS[0a&HpA\Bk048lV9nsilDh$,c#D@N=2q@B[0FiiVUlBG<3dGglOpkJ'Kabj7c,IVNB@Bd?2VURdl6#(N\[&LV%hNN*]U^N`&.1q=!9P1k0Y+k>7rtIMM1Mhi)pqir8<1NVfF\j+sjQ,["Ln.!.^\uG/jZJM2\F>pCSK8CTCr]^p#@:d1re(W;@0QA=/nQ`?QpD(&f-[pB2CaWfBKB&?\c\`rTij7[USVui0JfqhT"YRLE)pA%N#F)Kc2%k2MGJncT*EqjedMZ+>dQmJ#SW$p&2@[76ao9"qB?d-g.H=lc#8eZ*Vc=/3&>ej^JED_BO'3)XttD.9sTEK2Pq%PBnoPS1KuVR#J-DC2L'FjbTOP/Y:GHQBp(^X8&:fs*2KDU4)e5dtre=KC9TZ_Ll.:sXn\k-&!-f2ssOf6_k,kZ@QshBmECXG;nqptIl:N-R(Pj)o#;'T_!0iT]`Zr[t*h+u1>)$I1"4bTefc=QSL[F?2;-og>K93Rm]g'1QSuYF\3o69d&[jZJ<:U;1VKVdTun#e4LPP[JlEYom7Sl#kmQ`.*DWX%1Y;/hK@F`Gm^HTSUY?H&l@_5-=rBC7N?89Q6Rt[A?/T,M_cVHQRX_T:LgTOI7g!<5o@B)mC-i@G0P=Q\VlM`A.-*Fh[D-5qqC\KJ63NE%$-"+3=':0d/KtVl^2T0Dph;EG)kZ*iR-pf(=l3RkOs7E+_Qj]13k8J=!YFC&n7KtXZOHqW4u5B[]\9fHHQR8ADS2ok>Sl_VFdJnK&sTP^uICG'AYjVi&j`M$spCU(r<_T^/I'Lcu0BS$P?NW=Iendstream +endobj +425 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2654 +>> +stream +Gatm=9lo&K'#"0Di2H!G>^hm*4ZT+YDPk&q:TU"qkQ6m.KnLWbJs>9np%oX-.FKHoZ:j:7D$saH5@/QKL-=\YbL=dR_qdn])0bknM;gAc'BUR6k9Vj`+)uu6GO0GE+aVlgEBM9cI)af?U"Z5L[:n:*r4qIPf_fZ`_XI7\VM60Q@BYFGHaD@!)1L#=pmJWhRc;]!L7[kPUneZYW>g(u^:-_ZZqdcfC9;Br-Rb_^frlnP=S&m7CgXS=c4k2m>!9-&/Qc&=\SPeV.^l?l`n;H+mb."+f39Se[7'(LQ?k@EDX2%A9ATqT[[X+l4"FUb@oBF"Se>2\aIU\PC@N]P,AP1s_s9dXO)-J=[2)hCH_D?.$)_llfj^]2CR4#XLV*?>SDd(!FlKmBP'jYtm_5E''m-Obh\>+Y3-^&p?]bXf%bJS'1?We/L8&Z#%hu\coE%;HT%A?7aELjk'YJgJ.acI30Bof'ga*4ap7Zn*2:?A.PF-LF*F'#M\"OZeeWN1Z/(5A;q%AVLL4Elk`VLE_TK;]R%]RRHn*1+)Xqtc8%;L^$SKXTVd/8d*F5kJ@b6Fap_GgatG?u6A%S3X-LmSD*-R5.P7k\@P[:d\l0)X6lX=a*e.:p#2O+LFgGD]&jKPsAqH%BD72VVnP[HFoR_pAHZ&gPD`]_5hA7j4:_&48N_-S>NK2rDq4mTMSVeFL/doBEKYs``I!N64d?LJBb8bl`D&8O:r+t!i6qD2'*JBMGUKu!hd!:1=b/[D\=fqN6Su]VF;.r=/lhb%BVBf,!lA/:.Ee#_WV[(]\jCd"D*l%nNOaUk]LK5Z"egg4o)*'U!e^g1ca>0e"M&HA4Bc*G)rt?19]g5+YJ,C4=Z`Nk:.4?f%e^*.BrQR6pYQ)=W,.>692(MCOf*u*q7cL=srLdO/!7MgToWfF2!">4e#MjD_>)U"HfX'4QZ/#B=01K@CFnFDZIB9:5CQCaoV%6@"L^Wa_Q2<,K)R27SJ@Rq/1f>$b7O#2n\fQk\54]$0)J0-uTpK+d:=Yh$ioZhLX&Y-Kp#i:2h5[PK"I/7J-OfeACd_e'AN$btJGS"js9YULOs*=.nFnJRs"uG(;^!JJI-?9d[EhN$R#4s`7:5?TF45Cf',rWr`c9LlPWN#L3r*XOkitr<2Xe&ugWjCkY^$q/BBK(I2ps*V9[")rK>r7T)dN2@:m]CA'W\4eEqo"Q]=Vc#^B:?0[_P!s#iV$CNQfW!:0#-C_akI#&hN2TGM$b45L[E+PK8e9%QhnYbT093a]m(4%j*[EFS1=VF]IL.[HS40-iRSR.5A`;TE;,>=i$>*A^rmrBgseE:Qd.=bRK8##:doiMl5Ol*o7oIif)[__rr@*hig,FEp!rCcApIr$T,`B93K?ul#g"=oVU,]/g4(>0YfA3(7.d!.q&uM-?:)u.qHS3Y0$%UP8@s4XSOSS>#%2o/eY2T-dK\VK_N7c*g'*]lX:>=H5_ir)m,Z_$ih+;_4W/D@bkrRnP<]r'aaPg`??%61OrEChgP`X+Mc?Gk0@;/.=Gk6=*$nK6SH(0QiZPuSfBX?-=lU@Amu)M.?:Q_Ts(Ph2(NX!5jL8k0!D/7nk-qE0gtAC$tX_)6@&^W1aShgHZQ;X"^f0e%]l*$A&endstream +endobj +426 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1029 +>> +stream +Gatn$9iKe#&;KZOMETCRW0ZGh,;CVa'Z=bl?>Y)n+kI(pg9#db5M]5g[;K'@i!QiD7fH:$g((QYW2kY?#)$u4e4NrC%%*!iA[b`&APPM-VNq\u)E9ml_@1WrP]]GK*E66q4!*&@[]mn_;M7A[\!E$[EMI8Qf68H=pA`jdBel^UHYG]7mD!#`Gj0XAhCo$L$%Zce-cksQ>$Ap!2d7?AHCj?5gY<[[>!)Ks"9Qq)o+P:P1/CBK>kg2ICJ(W5n>1q:]T'+;UWI\TSotl[!h"4/cjf'HAX*m,ZN2Wnc>EEV5`&+bcR"9~>endstream +endobj +427 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1994 +>> +stream +Gb!#^gN)%,&:O:Slr-EbL]P:1puj\4-QTgiUj>Zh)-A(/A11>%+hRaN/jSqR;&$.Z8Ok"__ueS4Fr-&E7=jBPRd5>1n9,"ShG^'UAe@k[EaX%mf<^Bb0'KI8lW]%q*WtHnUqKIlUE3<`?^`q"cRCioK=JZN4pH2`"A/o2AXMkBH`$P7.'Pa+Gbqr9HW)\WgjsujQdl=uNA&oAt(>6QV=^!R+)?dH1lMHP*qh]2,rid"0WLf?IXT:>(K8.WTL:+Ehl)?q@Y-IRN$nR9en4kA4Zc>d"`95?)VOq"o6/\>$?n+XXMAh+5ELBD+>),_q#@eq4gc[7gBJF,1K#^hOGRt)Pf@_>TR0Sr/e3ZHQEZ6AB*HnbNC8<="$ajZ1%cNhL"n<5A-h+il(s;b8[uf4D8Y-eNUiQqjL_OQeJ-+XZjkM=O&8@b*c!KL*7nXWM^e]Dic&10@)5.rfl]5[#ar4I86@$OffKDj1;p$?u@fqaUl?DGJl>J1WpuuipLX:CRo332D0T^+Fq4ipp%ji'R)kgq4p5!ch.SRm,po9:\)Z<(l!:S@qUkLrJen-Dpa;c!t=RYVp9kT6!G-u4nli@$g0:34-s`DJ!k\o^:s;HB?CKp82G\H<_4rS'kW;!nim!B@Xqg#aITh50Ki:f"^N5MRBsHZ*"Ae:XZh][TTKP.2Q/1$GG[!c)N3>NUZPfkEd;eK?-%AIa]Aj6,TnS^pEGf1+m0DRPN"9:0&d_45+[.`G]st@7cZSp(O;e3/qPSe_nCl84f:PY/fL6#`eRE6(Cc6O&j0%O0(k`p.TG9s&fX#Ga\0N;MR!kBcmA*`i`H#=iOmfb=0DN9GouB_#r"JHfEhY?&Yl6]RN3.+n%b^ds9tWDQNfc_-B.Q&2f2LBF%j'NO>=Wj1_n=[h!8ZP_mJAceA%AdN*>4E8GQ4f99]R)6e("8+Jnio$26+qhq5SZN#/7HAqk/I_Y?s\Ng%A@Cbl(]^T;K'@aUBj\Z`1n2CCu/!4EI@Y?,_)DUc%a>_)8TG6-c^KYE.Mh4Rf[G'od8n;8V1khg"&i6EpX!jpb`,o3=;:meMl$D++(k6n/RAiYB@(a%Z0a1`XFR/WQ+;7RXdogXI)`hS?md`l8KQR`5H#[upX@_^Jq=)YMKI7p!=]&q0.Pn=]cn$Vu?_i7no]h6h)foQPb)c7kj.9)[=E/@d2ds:Vendstream +endobj +428 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2400 +>> +stream +Gb"/)>Ar7S'RnB330.qq.jT8X[K90hC`Uq4?2_2?JR[*:#$LLB3GpnE^[I8qgG1(e0%+X^7C("PF6E?i=qh8A;L'99!%p`[ISGAK(eoQ"RK90&\/Lq9F[C.Ae(+fR/I']ZjX2#q]>\\N3sh%lF9i]neJN@L(fF"J+Qb@@4X$JdQ=DWg1u"\Q[`!uZ[frX?Brm"5!S>[8@Ak"h-+e@lf#eWMTVZ36ksl,gMnMM:dP/K=%3&QD?bUmoUSfDf!M_kfF=M*T-i"P<.:lD]68cmoNS)`CWbQIcn_=Yc=ug1S)RFROh\`[Vf/0"m.X)i.4PA)=aFP3BR6s@P@9XV&g?Ouik=nl!_e'Tca>q#D1;Om(Y!j=M(J<`D-DlFXb[:uMiCp7,JF#1NbhEK-4Jn<0HIce2Qc)@!gEJ-)^,`%=9t\F(*,Bb5Q(1Er15@N"YG4eb3c4jD9YW6K2aM'(#H_GKGNkSoQ)4%B^7"ukC;ZqA0lS6[]r(q-"b;"\C2]H0Oa76/sV$`CUF_WLg+T2%3Vf(I0?[)=%rVIkb?U$Kc%CWFD.Y"Gh780`52a]YYu22\EWgeD9o:)An`CTN`>5s8cqf!TZ6gFPm=ZZ0[0;-gV$>hZB\gQ-SdZO:GKNg(;\HKo1F<5t+$FP<(+F:o`PpdW@fGh5<*!+TeJG:+n)j"U7O:YJs]kW="]gMk@,`T[0W4!DP9C><[7lcn&7S(%Rbags%=WO2s;9U@5Z_b45YRc`u$i(Hp=$)Ym/^c1=!hD@8rCP8-`S:WAMTB^ZBUL-bQ':aM@m5kKJuUun=_K+d/ZY5&8KgL-aPW.54D%CV-Hu"LVV49.Jt&3l?PM;ZN`4NuB7_)Y-oXrZi21)S6?cILRrgHdFuTiP7$6lR)nBkT68!9QeI>g.4UuRV\OM9'q/31L.8"-nG;>`W:bn0I:f?LhLCcIqrW<.&f&!SM5bH/Yk6#,#n!RF3+P$aGgg%SpU!Ug)ZJT&\@@uKQB][LY[!oQnX<_]oZ+0jC.-Tl-KqAs"H?DO?P8lKMU]Np4S>C=9`(mNT*AEQ=Pj!Tm:,cq]V2M0sWci$fB_QPe0E#XMKA5gG#:Q]a7PRuZ41;nHk')4jp1T5Z=bu@P@S%GRBAZHj$d?9jM5U%01dk94VHHpqdR$G.FqFi3M0`5/O?c+EB2749OaS=a>7&5&(aZ@WE^WLm$8kRb6?"A721=YJY/g\#aOnm&0?86Y,%b#Jp7m$s-9*n@X5@`bj+OOka7Fm^d,iKDO>BQDb)V=c]0Be$;V/"YTbf+Zt_K17G5:Ud6`H34l5LOUXag@6+!UnQ9rHnKMGU;`5?G\fnAaet"[_h8F0!QSfK5bdO!UAIW30gQ7s`']%99:$,SGdI!;qhXYs0Z)$@.Rd3U"P(lVF'\StAFdL\XSHjd(>"R:CA$Q^?BXDUb!9LR,/uDR7FMSpFo1L2ppQ+,q_cok]p9V(+WOG`[eUR'*W/6O8L`i%5B3Sj(B$b@OU#aNeNjf^Q/4AH8K?d>Gm/^_^=nkNH"la#A@'f=tgt&bM95IA[HiBrRj;e4$jc<6M*'R22?Er`CJ)3eWa-*>T(@QAt?bRo_OQ^nDu\3!Ct#iFQs)p@V:;/#m-44bJQS7E77R!,?&9+JB6ck)E&#Cf3^Shh1f.ljBposT.0~>endstream +endobj +429 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2929 +>> +stream +Gb!SnflH,m'n)^Xkbf+PU+kfNm['.hc_TXmDPir(0Fob@8]&<\!C5&TfB:r/:4fO2ZK"i>$bC?Tp>+$)%HZt>d2>%ESI_Nj;[f4o'Z*2?HmRS_\ACeR-0k5'CNalDWHd.@5r'91[`WG-8QXnGD4'GcfX#(=gl6miYHV.`'lP'j;ZkcN3OJQ0I]u@-80/"hlt5+D"=0^p^r!CM?mYCN?X,8JSY)jn$FY&4k&qL/DA\fCV-C,K(%*j/6$*VTJ0XJf/,5b'T0iD4L:JW&_M:!)>m&Jk^Stu*b1R-3lEKWGa>AkZVQ_>JsjEEgc;5QWAfD(O*=u]KbYh=0/jFo=6E@oKV`.G3L1G[Eug+%B:P7(T,a0[7VssfOZm"W0@[t+Q9+SNN=@>al]&ZL[(c,njh]]RgI(:XKtkY-Y>m1Nl#6%+A%&r[]&4b`DBVT$O4_e*l/WNsZoKnP1T8(?K7^Jr%-m:5F='s`g(E0\1qOuEn$:W;fpOWAlGmrgIW0m+_"6])dfS;%Qu$5s8`Euf&l-L<%0((ZBil2WL*%\2@F";UHt7:5W?p%<*]RB#+o"sH5YS=:Q%L8Q>mYI=[JH5#'P%e)'?L;<2:eM!U[:[)5\:S=!7oKLdn?t*&sY)_G2Y2Tm'R0\2F:"`]]EEJ&gIflFI:F@!1K'!Y46rM.=9Y#2D+BEq"J'1/lZolE/t:Vsd\hODAV;7I$Q.Nm.Z]@d-L8]2*Sp),ZX;15]C?3h(!;U!A=.M@c>=G/+XI=C'Sr9L69HI?WaNP(N;XIFB-f4YH"GB/3',U.miV`VM$:<"'@8SP;5iBLDStF`-#PbgR$T_?7E,dU_%k4jAZgpGXYa/?.;#?MhR;knYMM1;\,W@^r_':To^PW44F4_<#)=h'.)TaGQeaG&3c0uUaUshiPuEF9][.C==WL%@6l$KBr-(=XPR1'/eR\E\b&W#C:7J@>X0E4pdnf+@s'nSM!P!'[9[2!FqoAV+AllKMl1BV,0st,"n5OVXu3#Jmcsp<9tKsA;oMOI:(fKGlI>IgYNJtnbA%FNn-&0&');JED\%A5i-WHK^B7Y@1K%G4S%aqs=8G-7)n5fm5P2VQ@JUjLQ8-HbOSEs]7oEV?\8".@dioHq?pYgpkGu6c\uQ"fDgT4FTr!s-^n<)YU$N%:Ru8sf.BU@l56rYJBr__lV[;`\3'-\\3,atjb+b5+bSQW8M"+WG6l/ESH@dufpgEBPaiSu*3%O-Wf7D7SHimS@aXg`L'>f/$Zhuo\W&[;>o)uS4`(Ad9OIsuRpr5otR^i^!lG.#7nlZ_lOrg][2*]BP\q(59-fR(B"mea8P2Ig_'K.N$qtr-E>,$SiroCs8ftq^2d&?AB4i7b7kHIDdl1u\6Q`G]=`'HU]di-]9F,-*45'!?(*W>9PX"Z7VgbHEriP,]ZYc,i9.5KudjX-e$MEjJcN>CB$N%iZZF3cHR)Njd#B/MVOZO"UU\:2ZWRYAHP0<;nY<<~>endstream +endobj +430 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 3414 +>> +stream +Gatm>=``=W&q801^n[(F*.Tu3-TeZ+bu?0^fkC-%fAkapNNUEIk0b'NJWK9G$RdI[7$T"5;?J\Z3a$eX$eR8ZjQ$<2/p5ig\".;RX.kAt',:-?<&3fQeGjhRClC>6IE9+!+m.m8E'=5-qnfH/$!\nZ3T/Q7c6MtAO\cI`jJC&g?J1Ds@Q+X[]$sGd1YVQj]Yg9'1Kfh7_d??3S;_La@)VT8kjGX:GAuJ263ZN0L64bMN5\V"GD-dXt!nG*chuEWA'iZ@20nN]NpkR8@o1=a,&-1AU)^jAGkBmK3"?mBU?uAA@Q4-W4S0ifbL*jisGF0ehkjoE^.t5(#Je(mYT*':PDe-T$=)-A3B1jC*''1o#_IN/h[K:[H+:3o)kLGlt.5a8!K<%(R?:HRSn@qJC,COcS#Cb/.7:pafdlQQ+AmOgLZi?K_-[+FeS.WBXK/+A[6&keuAekIjP[$Dk`#T4$Z@DAkRls@2meFdGd/.lp)E.T`*--fdIiQAdkPuGid9f6#X.M!_9>8L,(TF,m_Y99ZA+7`m<.cgb!tNnJf8s[+8RHp[T3!`n,fm4QD3G`##0g+C\nG%<=6QniBSiX\$%I+U@!MYL;GQC$\d^(R&gs1.tK-+++i+c%u&5-?&O*gn7]9C#;e)&WTp$E5+9dX@c+'[=5\U/ARBpPC*jZt;/^\3*]5OongKPdjmqm0s#4"JFt!P[,`;Z#b+Senh[AuidcHZ<\Bc_]9rCH+&X6F^G(CECFOWLg3?)Erl^[\\o4>WVTX(o6mn&[KQKV(OP0V=4'77[,RC_m<2dR]A0E.Mr*'o_DA@d$MLEC1_hF2KUIJl?:5i`gNYb#16GgJm?FUnW)Hs?o>;fPr+'kCOa"N&'h(aMYEc1<=D)'9qQ"hGLQ0Mdr#*DA0NU.&TD`Z3c7&WQU/9RkNUi3:8QXs\'I%6#&.>bb49Dj=C(]%+t7WBtfq&,EqE"2mU:Bb-7aM@d'JG:;Y[Lp^WADI\Hpf%V$%u%eVaQ@TQXgfB)&Df=FKEjM_i3gLpQu(AHM[hK9-4Ji33&:V,m=]E7fXU>[?_=)Vb2gPT!,cB&SSW6Al^YS\8cZ'Q9YYgQ?sCj_T7E>3X=h1T\Y17\Jugji$ii`_2Bo&BtO]tLB(M"60L.nN_R"HT*k:&TSOGo&bZHG`Ml0DoO&,`236-1tqFP;#2_O3\/#W?>8"R(^esZ4Y+5\f]_eQu9^K50S#q<\BlLIW%cq&!#\^$eU7."&`ZT2.B.o]d[]`,`G4K'GUn^pe`IgEie0cE*.8'F2Hq["l(X_1k\B*Z_L)D9*r]@I%sp)mdmXj6RrXdgZ3J=,oO)9(;LnSM.>V9[RO\A37q-0)eG)N]W_YG_?6%A=KEZ36Ej]nb8dcud;!G?&8#r@3[LP@Rb4!(V29(gB!2!q%PN\g:@DCXHiR;YKN)[d7I(LHdfcbtAY$<,DKqGYn[H%^#[MfR#>#QWd#QLtbp4ie%!=H2A\bu>eC,OqIHbs3'*XV%mc^9<7#aYr)&aC$Kb1/Lq*;1__oAj%B[i!)*S_H8"P4+uoDk3Cj_W,:%N5)DHman8!fb(Z\p-A'HNp!jJ)?i]G[3T19@-Z1-tXY9>]teDBq43Uh*T*?MG0)*@3a76Va9H$@q979OTtmCW/s2#/\QQA,liUb93G1n+I'lW0;rI=HOQ,;\h/T,TYg\tW`c0ub:injQ/orQKlNu:.Al]?0'p3YP:D"VEcq=8n=S4L\E"@%h^kfU)4ba^(mHF%s.VB%X)KY[9;j%ljV_>8Mu?KhoE)3#Y/m>!I[!d2SU!YI?[&\KDeJ2qVMT=j[3^j7&U_Fo$`,D:Lu?$>PQ^DMJC[$$F:l;)t2a1HD'oQqIJ8\j!MhUGg8cS>p]1h.]J_9..NcqLp5-Cs:c;t@s^H%P5%ETlJEi.+"-N)uhJMCGa6)c\NB@KsKsbX,Y7*s[j:^&~>endstream +endobj +431 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2381 +>> +stream +Gb!SmD0+E#&H;*)Z!Z5$2CQ/cHTV%2Hg4@0:HD(h,bEtQ5R1OD,UsfXjO\VM,_Q:+0MXZm%(=O+l#(F3kFUKm(mt),qDaoEh"K^G4Ti]t56b^=2Y8*L\,O@](T_H;G_X#NuRie%:;48*F;GYLsn4G_;WJ*Sc7<#f.!`J-Ab:`qX\9=H&g*l;]hbT.gOVm%e&a9eWF#M.ocCg@/!W&Br1?Q+W+HD1p"e<,6D!.ut,3r*E&IbN$NMVH+]Qg,uMOhS[Yo]k7k*ms+DV/B7fWi2Bknnk.KlJfIQ/"F$s+"W4N9iEkTm-BHG4F^i"BX22T2kCc_2h)=Jh!9lG?H!Bt@+`5**WeaY\QgR_]f&V'RO7'ddY#R\bqTL=!+#GJ*2FTq,T8'WKq1(7^^fj2W>5]SbWRXBRjbiM1oEMEB^;F9?DgE&])b,lU(b!C@sb)hD!hJX+[Y!j?UM!5,RUl6:,J.?>?mbHi1^sF$phTA<9QZ?imO[XMe!N[0SAId+LNuA>IR9EFCL!h"g60/[It&[gqpZd,#4g@q32.cUH1-#XBUl+"k(XS]JnbS&o!%cUMasVTm9@!8#b.a\j,]aj+ZG9Ji9XPf([qV0+8)O)cFp=[*mP\r2h'0od=A3[TRNOCU:4F'bQJ'9h)3a$'f^g!YCAk]Rhs`R3?I-.Y2N\f*P+-n\&uH[='[2&ufCYa"(+?JH9DBen?Lk_\L90)Hu?&\I()4fS4EE%pCaCR`R!.juE%dYcA.c(@2]sI?NP.bjZ#FR,(_>\fHC3/3Ug;mNW,6RTRNWh[BR>i;=#C6)Yuf^USKG8"KPJHFGU;0r!4>[[a3ThMIt#j>eBnPkgOH+VICX1RtBB506'8"pP=bC'r>1\_,"K%Kf5p3&p.ZMJO?aM;NmFB,JRC51*G]/*%i;M'!Qc@s:*F;^e=[*3frg$K']T>u%=k,#@OPX)"\MBn?ndYl$`c\P9$0Ys?13Is'CXLrX]""OZLfoK=:R\&b*lktB:*]$V$gq9"u'C8,WP'>q;Sa#HCMD]@51*XbZ'I*FuUuZe)I^VY^RumM)Yqhg:(a[%TmaHj]LdGN$La/c2XJYC`[%7RA_l^0/OQIOBlei]IhC&F$`,*.uK^"D$JfL;cFX6WG\):`+Vb2.@LLm[R*Ak%oU">0Wuhlh]Rl\o;&;&d2d_m*mL5^f-FW7[d2Q`Srcrlhl0?Mqoc!WX)*'P?X83nf2*^E$+UaolFH$U#QC3U"[@K;X&>.;erf87&t!bQ#F?[^k)TA&r^*D%tmaf3l;YK';+GEh'/?+q<*CRDFr:4@W.Ce9[cj0d^5Di]nP9Z%K>e`p<`;gEq7!Yi7<*X=J-H7aIOs=FlB3"$IAmdKseqF',JF9hTpM/;SC0D-=q%h5\5iViRL$AGXa($0k)3-g%PAQe:7A`p"`@B[mB1EpJGGmLdEtZdhI_T%keu"7u97n$,\RZPqL"ejY#B?:J\;cXQ2JZHc"EF]4R;HrUu!!endstream +endobj +432 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2897 +>> +stream +Gb"/)D0+Gi')q<+Z/6bG&e"3"GF-UrC6RtCP9u<1mEFfY^a(J;V+?B;XLQ%E(*H%s'M6!G:3)P\9o0!<0)!u;m^"#>JfcJ'rW;"u$K&Om5]rIN!];hKVH<9Io^(TU/ULZ]7WcQ!_(hqeL#]C_e"O::El$=Cq(FC9[]MWg"jQN%=(ds*.i]ful23)eOY-Q+Qf)@Y"*@gi+m!\=r9TuaO\S'/\:EJ&mFq@&&Bf48:n5Hn(lG)c$_3IkSt$K,(Tq08Ao]o'%5,[g(sgLd"\c*a]U=_V5Dj5q!FlrIQ#5\$F@K)Ui2Hdg6LG`Fs*)_<\l,/\X99O-6d*!Dj_6tCE00%=0k&)&f\#JK3$OI#]\EO+4[uA.7,sC3SGO_^1Xp6mGNQ19N*"q!Yo*/ORarTfg&/0=lTLAYe8C>M5KNclo%&^o-t%"4#i8_*eP%7qiX7nEFltaTeM5h3A=a0!*Bls/<"i]=ni4D65-@oTSC8A!*Br\irpX@26n1\+TspjklRIq^s/YHnGMf_HF*Au]c#I^E;)JG)r^g*+ME7_E/-^GiECk%b^Em_6dR[W+Ob=DAJskUCiuTSk2(g"'[Wqu+MZY4r0L0o&[iQCAaO>=ZZGL6.'3qVm2ZU6M,Abs5$fRM&ZhuL[&'"R4^NZT/`;)r;O3)0$qVA1(RF#T(jhY#*G'FoZZW`AB^P4QSL9D3(dk8gg`Ef"Ppq[WSY+!eIo7)Z6S3d95l?84aUiNZI*2Q53&i<)"ts_(7BVB#@Ic.mnJDt.>q0':!@.&f.P-MH>Yam/d96T?)IV)jA+6l)8er/6bpL8S::1@@$InjI@G\goBq885MKcmdPPYC)PXFIq?afHM@k1tIQMfc6,GF6'4dk8PcTCb\kg`oe#>%@bj(MifSUZDA3k1\r84b6bL<;[09NqW4*(bDO\cgTJ1pl(JNrl1MG!q^g*=V\`!p3Q^*2BBD;Yu+iEg[@Sbo4Zal9qJRDN#L/)_C1FcTa7'7"!^')rEUB[#YnK&gd_V:iG&t(tOoAhQOa$0>mTmCk;K9d$J3-q@5us]=&>glA;hj2Z5@Cn-mJ_Vei:0Fo8PJs#^*VpBm":#"kBQlSjfE]b@MN?JlrGe:)7F#g6Q16gdQ,YE_R1US=>?h3rFb&un4-q:f\6KbR&ZFo6:-o>STbd)(t:D/m[7i5MT8/aCY.0GIGJZWK5MY$+WGiq.8-iheo\>Hlj8a?p>Z6Q*4kI4'l"=,\GY6LFJrf>3jIKSr,[4/[r,+@D=9Z(m(>OJ8RY&"/f03'F-#&8m8&`6P)uQEt\>(Vi8sc&I5UbgeOoKG28up'PAPQat=EM?oB@$XAd+"'k>@.[-4P\Us"M8qnBh[,c)->5%#0qo>Gu%B/^/.%j+g[Nt+\U5g&$mmYYl9N?<5/!Z@Qh^XoGQ--u\[XC'kgZo!u.s[4;,ia''Wem&1:o1+L=N5)o;LMk40PR4NhBu)+ji?LU?-_maZ:`:,km@6354Um7UJA$j[tcY4(N:*5&JYl^M2ku>m.N>r6C.4:"'k7ZhRoT.3l,?[3bBP^TI:`J@.TPendstream +endobj +433 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2752 +>> +stream +Gau`UhfIhd&UsI]JOa"kjq6P?bEL8S46`Q38Y^!,quW"BKp3hs&3NDGoRH<^5`(.^q;cVFN:1;>>V6f(hgW0F;\HqX[@_"taJU?[n43jsA@X$D%UuaAT)A`nSA]-[9r+T"-"8kiZ8=Y-)<>7NQBTT+u$Ro6H5\H1R^4=TT%%Pd0Ihm\NCYsofe@iMn_*(c((8r?DIENF&+44$CNrPl?>a:(X=YH<>$.=]Lp0DU?oc<'Jl.1,=ZfI9q\)#*M(cCu/r+K;oT&8r6=Xu_p^XamftEKL&^Ne)Xs^m=nU1ZAQC[8l)8Q1crr7'RUk%D@X??=WO2'HQ$/(;PeVo>rYA<^(I2G_OK\l*8.QNWZ;t\%B,)kb]O6Jh]O='[jbF?[=oM^Vk=I]+#C$1"69&>^GppDYAf>3YG2VWOR9gHukT!rq(SOPFdf^+sL9Stf#0:3V#@3\!j1etsL/c7HupsG97hqOOJHj^67[t0&l.d\(If3?c3S#DUfS97TrW55C&'[2T*9r'Scf_8/9nDVjpAbOm#\ji>pSuF3tX-#36mL8>a?Iu4<`kdT&[1W7#DPa;Y=Ds+HWaCU_,\P/:bmUal!^!TG-:s:olDVCDI3";7G\t0T7X:*p!HjV^B+YNg32Gn83,SBm^ffQ3+iY?Rp]^2@U;in6lFo*g7LX6'ACEe?+l'jK0#@Yti[N<[9Q%C!"'GJoO")l:RiYsj&EEd+/Q='%[4G>8)CuNg)=1]2hmZSmMj+O]gZtmi`C+a\;B,imikNa3Sf?=Y!&6XoOcdjjbKKuZhUZ*r8YI8d5kS7p-[bFthZLj9,TIk.7L9_as!;A)".DQGHD;9*="#_EkX!%V=@>G47L;M,mi?ID\YVkE=O/Gsbqa>ilCQO2;GnqR&W"$Nh;9@8=Qe/3>EmN;pXkAE#/^P5I7tN(j:bnqE0r<8n;U,;4eqd]qK)@q$$4GX,>LUDd9A&Q>"Ya-b-Zd,\Z>:F#-FR^qb9QZL6*MoTFo`dc(?)7mdb8Xq%WJ'5+M&s&LL5CGKMe95^FTKo#:bL/q[h*eY`WGKViB`Y^+3.>9W;"(#GYonp^/9HO/;"cPR_b3VB>aitHf7\HC6_;2GP_G.]qupoCmU&D^4jhSEcZCHZeAUcbm#u7_t0;12S9d)n*9l1I)$JP:&tZdWn@GBl0aeo!M.-N"#Xo"/MY8iGIU(t"4nI&Fa_o;QhsPdfp6c!kJ@^T*FhhmhMuDcT%qmE4,9pSD",&Q?*AmmIX5X^97%5-Ugu@BZrtND)Ia5`hhe,`Q@ZOa=9Ma0p%M%9-kD+(HC,,sEo'[^O5AE:]IVWhGKi@Xpfm2^\`)8SZ1=OepY3jr.GX@?ZKK=0b:gV@6Y=HHmaS2N9ioUn7$V>uPn#MrRTC:_]"Ki*pX7?Y0@>=6Rc26s]B%-(_!K$cr@CUgXK07.O\G-@U]fB!4`Dbc_N7X`K!d\c;eBYE$^AD593/Cr(Z@/4mNl4E,F^daYlJ*ahk0J+Bituu>jNio4469?#%d4[Qpkpj2,&ROBT[a@REBF:Q[D;SV9C6QiBot1K9`u0hftDe7%pq@kQDLl4+#Csdmhm^7H!(`Y9(9b##((-e!EpaGV^AbIpbT/hA+&0A.LeoKgF''cWuNq3K?9,\8Bn5u+c=)74>bXVajOmMJTKLdm*](39"2*(M5,~>endstream +endobj +434 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1842 +>> +stream +Gb!SlhfIO1%"@qXJOd2+>L+hFIZOO^*"h_D=_<8%)JKK1]/:N;F(6cK[ra%@KH]/1aLH&#@(dHt>[nSOI0I]jKNq$MEp`ATi!&OB"86EQs0W,)n3^_+/id3Jkg=_<+o#X!T[*'IPW-K/XM]C6$%be=p[*`0;OW.U>q(*$?j)f2jdg_mOP4>qHCH62fk!uRT.T<8fTL87mREl$,#29;2)Jks=/[`!2C-op_po8Fg$T_>_e5$OpP*c_JY[:U?3V"[pmaaV(T)^ljL\NQFel2?6nW.ia:aO9^jG\"3(gq![A2Lr?#GK.;JAk"@VkW8)P,O39O1;j2S8@e2&I!TjXp\r(NJ@@;#UGmT*0#KM/^i!C"8P`If)7Y&P/c;NNClSAn'Xtq-OfJmk9prPPGs+RUWmJUH1"74?i:+/e[V\UW/qp%Me;2phVZ4l(+cDJA"fO&]WZPNm5;H"5QbJYH=@q.-2TF[XW\okmC5T.U:BFa#d:mpCLL(,:7$KBjctH;YWTPSQDl%HPW5pB:E-^mo[6RSR9iD*5aM-]*>`g4Eh:onC^tmQ:ie(VRVZ%QW/i;pendstream +endobj +435 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2770 +>> +stream +Gb!;ehfGR$(4OS:E?@Q%IeZJhn>jO)9`j]4lX^b>f@3tS]gdIR/I&lDdi5$h1odUq,5AB:g$tP87VGaq5,3'cl_f,".\sr&@b2(:LU-7d8#C_:rN8a/b:OMc:`i]"9_I=!F[hE:'c*p'(Hl"IMefl/i_09kQm/7hhbb7=M]h&)Q2$ACaC$+J^KTWb]K%]$^F9X/E\=ZZV_^,)3jugHjq9lY>g>l[^cMQa&',OJ.'S4;5eS>DpZlK(nj'dOf*\)MNse3?pk\3?H]n3(=K;a`o)+5l4kQg?#IVZ((b,?Au/i06,aIo&uUOJL2sc>;$<3.9eHgAQ?nBSFp+Qa:iTYf.(Bn8$"C&'%l>sp9!R(o83t>d5n4Ws!`Z-XT[H9B"`g'#IR%8i3>af4@A1(?`1dq]nIr-O/d_E[:hCp\-*@o8-smX^Ee.YKl?/Jp2IcrqC-?2'V,4/QO\4fY2V)K'b37V7#>O`UTs#_5p8H#4;T(Plh6-OeA%D'bEf4BNo(?9\`_rhLs`iS-3=Ng+pY]'5#(*j9FU/Z4@!5U7#=cL"'LKqD$S\o2u5LI6Ui"&oM4nMG$=_L';=[jTf/pf#U@P&\3#jPp\)I>RG"O3diq9V>[]+M\&*:>B'"8,mY:AOoR&a:Rca;_,K?#cRP%#Lj>!S\]Ap+Za8msLS%*r7&=f+QC!2R"(AL2'VDsKTX;AR3'#m8FKt5/h@e&h70"#N4_5Fm0`*1Z.W!*2.MF/+=NtY@AZu#2YD/#3+Z;M1Q48^'G)s1*_M;Nj_P;UV=lij#jIX)PXRcG[Uq5;]q%^",`"mS8gN[TZhC4=VV%3&/f3ZY506NAN==;R78dYl#5U7$]A-_/F-A08C'THWn=,h4*,S%b2%1aP,=YBn0tDW%hU_9CC7b@[=4R5)It(]7NXW`2naQCu%i<0>a$".gHeOqc"?r5>V`j$]*]rbWXZ:f&%D/CqO,%7)ElTO]oSKg0*@[:MXQ>N:rp(Sf@X#fr4H_I[@t1[Tm_-?@Rd\uVJdV'8cXkFEbCf.sI54YgM*Y@B0M9ehE8Pk?=dn]'C<7FPQr8iVO!dGn!S2P=+B2GiH"d:"gHqF-ORp0bkCjZ:S/*RRf@3.Vl,V7lkYd@`t))fdm_<&5[ea'pb#H\p'NqQM5@&@^$X)L5pX\'6$f1+nsBa!r\ApK&=k9/ObO_gk3;=+:^f\\]/mOCiQ7d"BQC903&ctV.4pCkROG&pt.3CNIpX;\gu2ErG7l?#;,$M1J>^;ma8it96P$X`]Q2k(&tqpO^MZ47!.cTVA/c]7]qrQN[Q%MgaY.[#$71t:7o!PLE?1:(r`A`m$n@nps2R$%,,IoFe%`#;;cc!_-OrfIINAN>&P$fgr9n")>2O5IU3MlRAH2Em"6ZsdIu)h1qK;6Qq(ESgj4b$3"M>qa\[eu,p*(Xbd+jucbXb2Ci<Is%Wk%6gt\^n?(\@u[3?1(q1lf$tQgnP?@]*T"+kN5'[j]i6:Q-H]3(I"O+_!!><1Cr.MLAK$-iP"!ahJ%3)EFi;unegoC5ZIbB0YkWPrF&*4ZHY"'b8'`\Nh_M]a1^![rY3*krKB#.m@3Q@Ikq<7)8A$.+b-]H8tQCB/ejeY`BkkCPkt=:$7c+_GZ-KaVBVgeB*%8s'$L3HeSIqto8-,rW1t-endstream +endobj +436 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2164 +>> +stream +Gb!#^hfG;G&:X@\TgpKm'SF!Id4&!+E*6!CRq8()PoHioo,/.OABHJXA,"#l!F>EM)UCs3eNS6q@oN!QpOW0dklnHO^-l#Zbl)+E1T*)>8U@;1sUi\>(+7!TG;so5I)'(j14u\J/GIL:\./X[@ud5jHH&(,meqikb+[&GYh4D/rl+,:?\&hJID9:d&AOgH*kpZ#^MH9"*bfg"mTq`0&LkXlB=E`6O.Ug?)tct#MW)HquJS4+ibhGm;k02%eV9[MX3H_A$EKSdiaQ,aW4l.prm`7:Ck%,GGAVoR%biNp^pkG#-ULu[VbAG8>W(#)`Z\dl0c",GmoRklg&Dp?8?gY+nYi8#sZ69LYfiO9X^s?)r2h!&q&T49*;1t2#a*`B?25uZi7^PaaY[Ab?pm)^H6EA&u&X=2%h\@Ll=l,X.>`5ibCfs$$J;h">V)C6j)!4/3a:CeRGl?lu(h0P`cef+XtOs1eM3e*GgRq4$`lGaA")9:cg0R,pX-r:9h\[7JQJ=`6>F9;&c=d-%!#3H--OrF.7W?I,p_<7^J7uCTDSQ!Q6&V-Dm5t[GKSNM3nl.-WoAi-.II3`#Cft&)lW:+C5p\Q8ZE"abeY<=sD9:.8=;CV0-rRRY?j`XY;5T`/kG"R>2@%OGh\[?mUt(g`=%RoPr88P\fQSSW.6dj:k7GMC"BT#LCIYpc'[n[-8)OnK`.3[4TZRh?:hkG=1o%bUc$t;0.=@7]kK;Cj61[Q'gWHc$#*B;QXVXT8'i&[ZQ1kgg`THMiP'JMBdeUc(38eV@.Z:nba(Y>K`qs@ao/9`;0-@R`*P?0eXjeft+_T^CC(pt<\hU'lIO-]_9-H^((MFUE"lR7Qhe)hfI_8l/J@smt3H$3A4N6NA(ZaN0Gm:3dpB.pPT0-D-LL;qtVe.V6(@jtl1Bkl$SPLfQe`;eGp2Y?7oOlM&^73Rt;@>o>7N%GrWn6).-5UZXSX"rt:<8HK;;?dfD9QHITF[Ib@P;4dsaf#Ci6^V)TN(]SM6-0DBZC]j).iYL+lt`0f:Y!F_rh)]4l5$'=f=GOGl@u$9l]qu[2e'K2X(Y>c!i1!`d84t,WiMH#X%nOpf^ed.H_BMiL'*\oT]VW=7-+`R\YUICquAUP1aIsIJ(#i@O-T-d=t;.-)M@AgY]VeQ"M5KYoe%Y^0DF>]el)tt!ot4"=6r3+cU#@$]Cc5FhfR7H4Dp_756(KV)0cF*ZV:dm5o.O^p(m#c&5hpi0$3$pP@QtDKPC'S\1FJgX7>JS&F)C6JL,BXSaX?jeL+_sq2T?_YPro>2tS2=^8&aA5PTUrO0`3bq$t@1jdfp"ZT%etj?m8slQg)GCXY?,'K`[UL^$EuWuB.`*bNqqS(@&])dNqJ0JR$tM.1+.[!PkHBBPssnoM/M7W%(G4#lp/r#$$VfK5WZMhjVh1bX/#Oios**9bZr6rcD#Z6GmR""@J-j\,1gLm;j:rZg!G";WU_QHF@k\])_r-2g^]NkG.RP\'[)N(Fq7)lVS.uW6q%A/;B,$lOV=4G;BRECUNd*d4B+(@#.[",8m)fTBW_P7:4](**m>!lJ7QC;5p+YP(o+ZGXK,3M._R\DAIXS.=CK^j,r]=M>!qSf'5/<^T=2S(H%4b>C6=CQ8g9lTJ@\Z5mI_AN=0&H'a3Y]DcnVq?Q8!+A3ACr%nd$?,)RCb"JYJC7:P[$DX0dabNAdQNUFMki!MdZ0SJL4^Ug?&ioaY\Q<.-oH%*_/FCKCphPG",&Zqg%C`n@@$)25DK%2?.UEBrR%X8f15Rsq0f,/fD7ij5^*N.jS9HV-J;k!s&%RJ%bbQendstream +endobj +437 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1664 +>> +stream +GauHL99Z,/&AHJokbE>CMBmida.:K#t$fY\I/0Aq+T.fVYkA6q0%CbqIafM^2sFnaLNBEf0#RD(j;!Nr?idgZE!@6+W?Lk+_AI%Hr.3+En!gEDecP(RUC_XS:'Vl0.DA8rBY'^(?%&;m03ZJ^U%6pfnNr;;KTZ)*W=!TFC)*19#iBpZW7+M:,j"Y#8n8m;:.V7J`&CK64OYgPJMnU]p;?/l_8>jWJ!K]q1OpXDt]HX$:f@/:n@k:hb*AbcL8^XGXZAVq9H3cQnuM5"M?r;:%F4FBg&Dp8qjp3.A1tYb+lR32XOZtA>I29$$Kg*/fa_X1ka%XPr[m/oa-5mctk:ZW*Z8(q%fBDZu>f]Un6BgF`Eur>-B(=J:*`UXj%)4GQ2lHq#o7UN<1.;W0VT"JTCoBX;j_'-fM^e'T@C:Tf')s[WCC?KNciBAlQlF:JMkA'g`^X/s=)n#4?C;d$d`V"TP#R&P7*uYlYtGo\pN'mUQ^djY^]-!3Zm&-h%%]tXQRPHlU:IiTf-L%CDO/=:k#G6hK#WhT7>+'egM@M_,Se-^QcD(#W%$b:,L5qXkP7FJ)i;`Pr]@GLdB>fN@e&n7J%.,%YnZo,^!7#u5F;_qHQ[?^IrNTD;MU'ba4;*B<9Y:hu@Y-qZUVYHPpS8SNX0&_Ha^o*(upiTrYWPfr"MoZOOTl7hnUCO'oq5(c-&gnSnp>gkoJbcXX3dqDVC2IrpQ6/dE/4]Nmk/?3Nr9$a`)\Co<@IXr=ip\5#uSC_>!5@(IZ[Ee$A9pKYdU'LeK#hd4RIEBc2P+NdZ/17i=U\/p&UWcXQ4a>8'cKleCDOR,CfGhnqBd_=hkRf38WYU:Lt+9#elAt,P4*l7C]m@;'TK5[_=p^I84&TB.,#k5@u$8)mcdO_$9g)DrW5^LrphTZieq*OEm&8bHIL@6\'l#n2sP06MnQn72S=9_pR]k@U9L(!Ht*e]*2q'glU@0g%^a\,BKQ!fV+J0p,FqT1NG_,XUI9e9neNNg4OIsScYY5~>endstream +endobj +438 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2069 +>> +stream +Gb!#\9lJcU&A@7.bY`4gQHO1n-c^(S!\'5LlDZ@e'tKPeiJ5!Lg/a.2pV'd.Z6WXF*;E>9TW$smAp.eCRi)P'nIb1m1dnM`o]$lV,+q@n,4Zp]]VV5:0E-8@VO__lq*CANPfBE/_g?XpP8>TL&u=TbOUu4nB(Z5R`*NI42@pr-J=jOh+0b7fS$')7B3"sP/O%SAHWKk^T1;j-H/aOOT(.02+q90d)N$(^/I8AZ3PqO;[t"c]K?.*Eop0K;YEuX@buuS'F)H,+do%4Nnj5S8&TUoPFNm;,Whe_liYD-l;)Bkn3Q.8ZCQ^#G5lN4tETOYfP9/*En'A7<'`5LJNdM-tG@46/o8X?32OK9UH26]>$C8H9;!-O#SZ=aJC8h2&+#APomc,(kI[HR'6SK%'EUfc^f]IlBSng+0.a!Bb<2bL5RL#fEfq8H@sqeP50RT;K5Gr4S,'Q[-\;d$+e$a)Y8aYpXUIf(k%,F_Qi+'A"GF,LrnIk9]Bfnn&ioh_ZTi$;]$3JKlFQq.7gMT'Ukr-]RJ41I%uSYVA9-ZSF/f^.D:*-`eBa+%Jin5MVQJkbF:d>b44BP4'XK6o@;:7uqV4<[/All2E#s*8sJnCdV0"A!"aX!!>,r-TH7b1Ds%e6BrT)E$]Dn^beKa@t%n6%=p/!o8UbW)`0RTmL&2J/.gK#?V4?XY3.ifaGE8_*Wqq@,;6=QU/o\r&TYo!!8HndTs`[MhX7;A?"Z5hDl>C1i8%#_E?2Eaj&@iE2\3%g`BN-pE[*dP;6o91dg)l[\H-9R4?MYI2+7b)i^e$K);7sDQZ4U#IoH-6!)2r2QF7C=5m1k3)6YU6a/OALU0emYIeYS9cCtRbp,?)1Oh8F$SqBSZ>'/;3[`4:_J+uS5!0EiE286jPk?AP.C[A2A?NVA\K4I`DBY`M?:d%;j+lq$NA(T6cnj"6L#\m7'A0hCV%[K`\sLTj-a"Ir:)CFT~>endstream +endobj +439 0 obj +<< +/Nums [ 0 440 0 R 1 441 0 R 2 442 0 R 3 443 0 R 4 444 0 R + 5 445 0 R 6 446 0 R 7 447 0 R 8 448 0 R 9 449 0 R + 10 450 0 R 11 451 0 R 12 452 0 R 13 453 0 R 14 454 0 R + 15 455 0 R 16 456 0 R 17 457 0 R 18 458 0 R 19 459 0 R + 20 460 0 R 21 461 0 R 22 462 0 R 23 463 0 R 24 464 0 R + 25 465 0 R 26 466 0 R 27 467 0 R 28 468 0 R 29 469 0 R + 30 470 0 R 31 471 0 R 32 472 0 R 33 473 0 R 34 474 0 R + 35 475 0 R 36 476 0 R 37 477 0 R 38 478 0 R 39 479 0 R + 40 480 0 R ] +>> +endobj +440 0 obj +<< +/S /D /St 1 +>> +endobj +441 0 obj +<< +/S /D /St 2 +>> +endobj +442 0 obj +<< +/S /D /St 3 +>> +endobj +443 0 obj +<< +/S /D /St 4 +>> +endobj +444 0 obj +<< +/S /D /St 5 +>> +endobj +445 0 obj +<< +/S /D /St 6 +>> +endobj +446 0 obj +<< +/S /D /St 7 +>> +endobj +447 0 obj +<< +/S /D /St 8 +>> +endobj +448 0 obj +<< +/S /D /St 9 +>> +endobj +449 0 obj +<< +/S /D /St 10 +>> +endobj +450 0 obj +<< +/S /D /St 11 +>> +endobj +451 0 obj +<< +/S /D /St 12 +>> +endobj +452 0 obj +<< +/S /D /St 13 +>> +endobj +453 0 obj +<< +/S /D /St 14 +>> +endobj +454 0 obj +<< +/S /D /St 15 +>> +endobj +455 0 obj +<< +/S /D /St 16 +>> +endobj +456 0 obj +<< +/S /D /St 17 +>> +endobj +457 0 obj +<< +/S /D /St 18 +>> +endobj +458 0 obj +<< +/S /D /St 19 +>> +endobj +459 0 obj +<< +/S /D /St 20 +>> +endobj +460 0 obj +<< +/S /D /St 21 +>> +endobj +461 0 obj +<< +/S /D /St 22 +>> +endobj +462 0 obj +<< +/S /D /St 23 +>> +endobj +463 0 obj +<< +/S /D /St 24 +>> +endobj +464 0 obj +<< +/S /D /St 25 +>> +endobj +465 0 obj +<< +/S /D /St 26 +>> +endobj +466 0 obj +<< +/S /D /St 27 +>> +endobj +467 0 obj +<< +/S /D /St 28 +>> +endobj +468 0 obj +<< +/S /D /St 29 +>> +endobj +469 0 obj +<< +/S /D /St 30 +>> +endobj +470 0 obj +<< +/S /D /St 31 +>> +endobj +471 0 obj +<< +/S /D /St 32 +>> +endobj +472 0 obj +<< +/S /D /St 33 +>> +endobj +473 0 obj +<< +/S /D /St 34 +>> +endobj +474 0 obj +<< +/S /D /St 35 +>> +endobj +475 0 obj +<< +/S /D /St 36 +>> +endobj +476 0 obj +<< +/S /D /St 37 +>> +endobj +477 0 obj +<< +/S /D /St 38 +>> +endobj +478 0 obj +<< +/S /D /St 39 +>> +endobj +479 0 obj +<< +/S /D /St 40 +>> +endobj +480 0 obj +<< +/S /D /St 41 +>> +endobj +xref +0 481 +0000000000 65535 f +0000000061 00000 n +0000000220 00000 n +0000000327 00000 n +0000000436 00000 n +0000000643 00000 n +0000000832 00000 n +0000001029 00000 n +0000001260 00000 n +0000001458 00000 n +0000001626 00000 n +0000001822 00000 n +0000001991 00000 n +0000002191 00000 n +0000002444 00000 n +0000002557 00000 n +0000002725 00000 n +0000002892 00000 n +0000003060 00000 n +0000003227 00000 n +0000003394 00000 n +0000003561 00000 n +0000003729 00000 n +0000003896 00000 n +0000004065 00000 n +0000004233 00000 n +0000004402 00000 n +0000004570 00000 n +0000004739 00000 n +0000004907 00000 n +0000005076 00000 n +0000005244 00000 n +0000005413 00000 n +0000005581 00000 n +0000005750 00000 n +0000005919 00000 n +0000006089 00000 n +0000006258 00000 n +0000006427 00000 n +0000006596 00000 n +0000006766 00000 n +0000006935 00000 n +0000007105 00000 n +0000007274 00000 n +0000007444 00000 n +0000007613 00000 n +0000007783 00000 n +0000007952 00000 n +0000008122 00000 n +0000008291 00000 n +0000008461 00000 n +0000008630 00000 n +0000008800 00000 n +0000008969 00000 n +0000009139 00000 n +0000009308 00000 n +0000009478 00000 n +0000009647 00000 n +0000009817 00000 n +0000009986 00000 n +0000010156 00000 n +0000010325 00000 n +0000010495 00000 n +0000010664 00000 n +0000010834 00000 n +0000011003 00000 n +0000011173 00000 n +0000011342 00000 n +0000011511 00000 n +0000011680 00000 n +0000011850 00000 n +0000012019 00000 n +0000012188 00000 n +0000012357 00000 n +0000012527 00000 n +0000012696 00000 n +0000012865 00000 n +0000013034 00000 n +0000013204 00000 n +0000013373 00000 n +0000013543 00000 n +0000013712 00000 n +0000013882 00000 n +0000014051 00000 n +0000014221 00000 n +0000014390 00000 n +0000014560 00000 n +0000014729 00000 n +0000014899 00000 n +0000015068 00000 n +0000015238 00000 n +0000015407 00000 n +0000015577 00000 n +0000015746 00000 n +0000015915 00000 n +0000016084 00000 n +0000016254 00000 n +0000016423 00000 n +0000017241 00000 n +0000017411 00000 n +0000017580 00000 n +0000017751 00000 n +0000017921 00000 n +0000018092 00000 n +0000018262 00000 n +0000018433 00000 n +0000018603 00000 n +0000018774 00000 n +0000018944 00000 n +0000019115 00000 n +0000019285 00000 n +0000019456 00000 n +0000019626 00000 n +0000019797 00000 n +0000019967 00000 n +0000020138 00000 n +0000020308 00000 n +0000020479 00000 n +0000020649 00000 n +0000020820 00000 n +0000020990 00000 n +0000021161 00000 n +0000021331 00000 n +0000021502 00000 n +0000021672 00000 n +0000021843 00000 n +0000022013 00000 n +0000022184 00000 n +0000022354 00000 n +0000022525 00000 n +0000022695 00000 n +0000022866 00000 n +0000023036 00000 n +0000023207 00000 n +0000023377 00000 n +0000023547 00000 n +0000023717 00000 n +0000023887 00000 n +0000024057 00000 n +0000024228 00000 n +0000024398 00000 n +0000024569 00000 n +0000024739 00000 n +0000024910 00000 n +0000025080 00000 n +0000025251 00000 n +0000025421 00000 n +0000025592 00000 n +0000025762 00000 n +0000025933 00000 n +0000026103 00000 n +0000026753 00000 n +0000026924 00000 n +0000027031 00000 n +0000027202 00000 n +0000027373 00000 n +0000027543 00000 n +0000027714 00000 n +0000027885 00000 n +0000028154 00000 n +0000028325 00000 n +0000028495 00000 n +0000028666 00000 n +0000028835 00000 n +0000029104 00000 n +0000029273 00000 n +0000029443 00000 n +0000029613 00000 n +0000029784 00000 n +0000029954 00000 n +0000030125 00000 n +0000030295 00000 n +0000030466 00000 n +0000030637 00000 n +0000030807 00000 n +0000030978 00000 n +0000031148 00000 n +0000031319 00000 n +0000031490 00000 n +0000031661 00000 n +0000031831 00000 n +0000032002 00000 n +0000032173 00000 n +0000032343 00000 n +0000032514 00000 n +0000032685 00000 n +0000032856 00000 n +0000033026 00000 n +0000033197 00000 n +0000033367 00000 n +0000033538 00000 n +0000033708 00000 n +0000033878 00000 n +0000034049 00000 n +0000034219 00000 n +0000034389 00000 n +0000034907 00000 n +0000035077 00000 n +0000035246 00000 n +0000035416 00000 n +0000035585 00000 n +0000035755 00000 n +0000035956 00000 n +0000036157 00000 n +0000036358 00000 n +0000036559 00000 n +0000036815 00000 n +0000037050 00000 n +0000037285 00000 n +0000037520 00000 n +0000037776 00000 n +0000038032 00000 n +0000038287 00000 n +0000038556 00000 n +0000038759 00000 n +0000038946 00000 n +0000039063 00000 n +0000039439 00000 n +0000039640 00000 n +0000039840 00000 n +0000040041 00000 n +0000040286 00000 n +0000040495 00000 n +0000040696 00000 n +0000040925 00000 n +0000041094 00000 n +0000041265 00000 n +0000041436 00000 n +0000041607 00000 n +0000041860 00000 n +0000042031 00000 n +0000042287 00000 n +0000042458 00000 n +0000042703 00000 n +0000042873 00000 n +0000043102 00000 n +0000043273 00000 n +0000043444 00000 n +0000043681 00000 n +0000043890 00000 n +0000044099 00000 n +0000044270 00000 n +0000044499 00000 n +0000044708 00000 n +0000044917 00000 n +0000045126 00000 n +0000045297 00000 n +0000045376 00000 n +0000045576 00000 n +0000045813 00000 n +0000045982 00000 n +0000046153 00000 n +0000046324 00000 n +0000046495 00000 n +0000046748 00000 n +0000046957 00000 n +0000047166 00000 n +0000047282 00000 n +0000047483 00000 n +0000047712 00000 n +0000047883 00000 n +0000048112 00000 n +0000048283 00000 n +0000048453 00000 n +0000048690 00000 n +0000048899 00000 n +0000049168 00000 n +0000049437 00000 n +0000049674 00000 n +0000049787 00000 n +0000049996 00000 n +0000050205 00000 n +0000050376 00000 n +0000050576 00000 n +0000050778 00000 n +0000051023 00000 n +0000051194 00000 n +0000051423 00000 n +0000051594 00000 n +0000051765 00000 n +0000051968 00000 n +0000052213 00000 n +0000052384 00000 n +0000052555 00000 n +0000052792 00000 n +0000052992 00000 n +0000053162 00000 n +0000053332 00000 n +0000053577 00000 n +0000053845 00000 n +0000054016 00000 n +0000054187 00000 n +0000054358 00000 n +0000054611 00000 n +0000054820 00000 n +0000055029 00000 n +0000055197 00000 n +0000055367 00000 n +0000055537 00000 n +0000055707 00000 n +0000055877 00000 n +0000056138 00000 n +0000056922 00000 n +0000076722 00000 n +0000076983 00000 n +0000078303 00000 n +0000079057 00000 n +0000097326 00000 n +0000097598 00000 n +0000098955 00000 n +0000099712 00000 n +0000119938 00000 n +0000120209 00000 n +0000121559 00000 n +0000121669 00000 n +0000121977 00000 n +0000122055 00000 n +0000122277 00000 n +0000122468 00000 n +0000122673 00000 n +0000123003 00000 n +0000123204 00000 n +0000123455 00000 n +0000123686 00000 n +0000123932 00000 n +0000124129 00000 n +0000124422 00000 n +0000124686 00000 n +0000125039 00000 n +0000125272 00000 n +0000125489 00000 n +0000125771 00000 n +0000126004 00000 n +0000126186 00000 n +0000126444 00000 n +0000126657 00000 n +0000126865 00000 n +0000127184 00000 n +0000127488 00000 n +0000127796 00000 n +0000128064 00000 n +0000128371 00000 n +0000128640 00000 n +0000128853 00000 n +0000129060 00000 n +0000129267 00000 n +0000129494 00000 n +0000129767 00000 n +0000130126 00000 n +0000130405 00000 n +0000130680 00000 n +0000131014 00000 n +0000131329 00000 n +0000131638 00000 n +0000132001 00000 n +0000132234 00000 n +0000132542 00000 n +0000132924 00000 n +0000133176 00000 n +0000133434 00000 n +0000133722 00000 n +0000133986 00000 n +0000134218 00000 n +0000134470 00000 n +0000134683 00000 n +0000134987 00000 n +0000135371 00000 n +0000135749 00000 n +0000136039 00000 n +0000136392 00000 n +0000136644 00000 n +0000136902 00000 n +0000137364 00000 n +0000137728 00000 n +0000138027 00000 n +0000138365 00000 n +0000138722 00000 n +0000139074 00000 n +0000139412 00000 n +0000139765 00000 n +0000140009 00000 n +0000140252 00000 n +0000140494 00000 n +0000140751 00000 n +0000141008 00000 n +0000141280 00000 n +0000141622 00000 n +0000141877 00000 n +0000142255 00000 n +0000142602 00000 n +0000142894 00000 n +0000143306 00000 n +0000143834 00000 n +0000144127 00000 n +0000144581 00000 n +0000144794 00000 n +0000145098 00000 n +0000145307 00000 n +0000145697 00000 n +0000146916 00000 n +0000147859 00000 n +0000150498 00000 n +0000152555 00000 n +0000154145 00000 n +0000157030 00000 n +0000159974 00000 n +0000162842 00000 n +0000165127 00000 n +0000166495 00000 n +0000168725 00000 n +0000170739 00000 n +0000173242 00000 n +0000176757 00000 n +0000178959 00000 n +0000180084 00000 n +0000182553 00000 n +0000185673 00000 n +0000188726 00000 n +0000191474 00000 n +0000194599 00000 n +0000197441 00000 n +0000200413 00000 n +0000203038 00000 n +0000205966 00000 n +0000208116 00000 n +0000210380 00000 n +0000213127 00000 n +0000214249 00000 n +0000216336 00000 n +0000218829 00000 n +0000221851 00000 n +0000225358 00000 n +0000227832 00000 n +0000230822 00000 n +0000233667 00000 n +0000235602 00000 n +0000238465 00000 n +0000240722 00000 n +0000242479 00000 n +0000244641 00000 n +0000245139 00000 n +0000245174 00000 n +0000245209 00000 n +0000245244 00000 n +0000245279 00000 n +0000245314 00000 n +0000245349 00000 n +0000245384 00000 n +0000245419 00000 n +0000245454 00000 n +0000245490 00000 n +0000245526 00000 n +0000245562 00000 n +0000245598 00000 n +0000245634 00000 n +0000245670 00000 n +0000245706 00000 n +0000245742 00000 n +0000245778 00000 n +0000245814 00000 n +0000245850 00000 n +0000245886 00000 n +0000245922 00000 n +0000245958 00000 n +0000245994 00000 n +0000246030 00000 n +0000246066 00000 n +0000246102 00000 n +0000246138 00000 n +0000246174 00000 n +0000246210 00000 n +0000246246 00000 n +0000246282 00000 n +0000246318 00000 n +0000246354 00000 n +0000246390 00000 n +0000246426 00000 n +0000246462 00000 n +0000246498 00000 n +0000246534 00000 n +0000246570 00000 n +trailer +<< +/ID +[<553fa1e33cd4ae10e40af1d79a1c109d><553fa1e33cd4ae10e40af1d79a1c109d>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 314 0 R +/Root 313 0 R +/Size 481 +>> +startxref +246606 +%%EOF diff --git a/advnote132.pdf b/advnote132.pdf new file mode 100644 index 0000000..052c117 --- /dev/null +++ b/advnote132.pdf @@ -0,0 +1,1247 @@ +%PDF-1.4 +%“Œ‹ž ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R /F2+0 99 0 R /F3 3 0 R /F4 14 0 R /F5+0 103 0 R /F6 73 0 R + /F7 80 0 R +>> +endobj +2 0 obj +<< +/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font +>> +endobj +3 0 obj +<< +/BaseFont /Times-Roman /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font +>> +endobj +4 0 obj +<< +/Contents 135 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 134 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +5 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa) +>> /Border [ 0 0 0 ] /Rect [ 118.301 457.0236 391.1152 467.8236 ] /Subtype /Link /Type /Annot +>> +endobj +6 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/issues) +>> /Border [ 0 0 0 ] /Rect [ 275.1154 429.4236 406.6965 440.2236 ] /Subtype /Link /Type /Annot +>> +endobj +7 0 obj +<< +/Annots [ 5 0 R 6 0 R ] /Contents 136 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 134 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +8 0 obj +<< +/A << +/S /URI /Type /Action /URI (http://creativecommons.org/licenses/by-sa/4.0/) +>> /Border [ 0 0 0 ] /Rect [ 202.7023 719.4236 438.1145 730.2236 ] /Subtype /Link /Type /Annot +>> +endobj +9 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 63 0 R /XYZ 77.02362 668.0336 0 ] /Rect [ 193.1223 539.8236 232.2262 550.6236 ] /Subtype /Link /Type /Annot +>> +endobj +10 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://www.apache.org/licenses/LICENSE-2.0) +>> /Border [ 0 0 0 ] /Rect [ 57.02362 507.4236 262.4416 518.2236 ] /Subtype /Link /Type /Annot +>> +endobj +11 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 63 0 R /XYZ 77.02362 668.0336 0 ] /Rect [ 434.0436 354.2236 468.2331 365.0236 ] /Subtype /Link /Type /Annot +>> +endobj +12 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://www.arm.com/company/policies/trademarks) +>> /Border [ 0 0 0 ] /Rect [ 57.02362 256.2236 290.6413 267.0236 ] /Subtype /Link /Type /Annot +>> +endobj +13 0 obj +<< +/Annots [ 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R ] /Contents 137 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 134 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +14 0 obj +<< +/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font +>> +endobj +15 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 57.02362 718.0436 102.0974 727.2236 ] /Subtype /Link /Type /Annot +>> +endobj +16 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 533.9986 718.6174 538.252 727.7974 ] /Subtype /Link /Type /Annot +>> +endobj +17 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 716.2236 0 ] /Rect [ 77.02362 702.8636 128.5976 712.0436 ] /Subtype /Link /Type /Annot +>> +endobj +18 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 716.2236 0 ] /Rect [ 533.3848 703.4374 538.252 712.6174 ] /Subtype /Link /Type /Annot +>> +endobj +19 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 546.6236 0 ] /Rect [ 77.02362 687.6836 133.659 696.8636 ] /Subtype /Link /Type /Annot +>> +endobj +20 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 546.6236 0 ] /Rect [ 533.3848 688.2574 538.252 697.4374 ] /Subtype /Link /Type /Annot +>> +endobj +21 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 491.8236 0 ] /Rect [ 77.02362 672.5036 223.9948 681.6836 ] /Subtype /Link /Type /Annot +>> +endobj +22 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 7 0 R /XYZ 57.02362 491.8236 0 ] /Rect [ 533.3848 673.0774 538.252 682.2574 ] /Subtype /Link /Type /Annot +>> +endobj +23 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 77.02362 657.3236 125.5458 666.5036 ] /Subtype /Link /Type /Annot +>> +endobj +24 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 533.3848 657.8974 538.252 667.0774 ] /Subtype /Link /Type /Annot +>> +endobj +25 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 574.6236 0 ] /Rect [ 77.02362 642.1436 163.2243 651.3236 ] /Subtype /Link /Type /Annot +>> +endobj +26 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 574.6236 0 ] /Rect [ 533.3848 642.7174 538.252 651.8974 ] /Subtype /Link /Type /Annot +>> +endobj +27 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 399.8236 0 ] /Rect [ 77.02362 626.9636 147.9692 636.1436 ] /Subtype /Link /Type /Annot +>> +endobj +28 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 399.8236 0 ] /Rect [ 533.3848 627.5374 538.252 636.7174 ] /Subtype /Link /Type /Annot +>> +endobj +29 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 334.2236 0 ] /Rect [ 77.02362 611.7836 164.2665 620.9636 ] /Subtype /Link /Type /Annot +>> +endobj +30 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 334.2236 0 ] /Rect [ 533.3848 612.3574 538.252 621.5374 ] /Subtype /Link /Type /Annot +>> +endobj +31 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 236.2236 0 ] /Rect [ 77.02362 596.6036 133.8645 605.7836 ] /Subtype /Link /Type /Annot +>> +endobj +32 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 13 0 R /XYZ 57.02362 236.2236 0 ] /Rect [ 533.3848 597.1774 538.252 606.3574 ] /Subtype /Link /Type /Annot +>> +endobj +33 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 68 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 57.02362 581.4236 147.133 590.6036 ] /Subtype /Link /Type /Annot +>> +endobj +34 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 68 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 533.9986 581.9974 538.252 591.1774 ] /Subtype /Link /Type /Annot +>> +endobj +35 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 68 0 R /XYZ 57.02362 716.2236 0 ] /Rect [ 77.02362 566.2436 154.8908 575.4236 ] /Subtype /Link /Type /Annot +>> +endobj +36 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 68 0 R /XYZ 57.02362 716.2236 0 ] /Rect [ 533.3848 566.8174 538.252 575.9974 ] /Subtype /Link /Type /Annot +>> +endobj +37 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 68 0 R /XYZ 57.02362 678.2236 0 ] /Rect [ 97.02362 551.0636 275.6818 560.2436 ] /Subtype /Link /Type /Annot +>> +endobj +38 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 68 0 R /XYZ 57.02362 678.2236 0 ] /Rect [ 533.3848 551.6374 538.252 560.8174 ] /Subtype /Link /Type /Annot +>> +endobj +39 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 68 0 R /XYZ 57.02362 437.4236 0 ] /Rect [ 97.02362 535.8836 181.8161 545.0636 ] /Subtype /Link /Type /Annot +>> +endobj +40 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 68 0 R /XYZ 57.02362 437.4236 0 ] /Rect [ 533.3848 536.4574 538.252 545.6374 ] /Subtype /Link /Type /Annot +>> +endobj +41 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 68 0 R /XYZ 57.02362 135.2236 0 ] /Rect [ 77.02362 520.7036 139.5048 529.8836 ] /Subtype /Link /Type /Annot +>> +endobj +42 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 68 0 R /XYZ 57.02362 135.2236 0 ] /Rect [ 533.3848 521.2774 538.252 530.4574 ] /Subtype /Link /Type /Annot +>> +endobj +43 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 77 0 R /XYZ 57.02362 580.2236 0 ] /Rect [ 77.02362 505.5236 191.9342 514.7036 ] /Subtype /Link /Type /Annot +>> +endobj +44 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 77 0 R /XYZ 57.02362 580.2236 0 ] /Rect [ 533.3848 506.0974 538.252 515.2774 ] /Subtype /Link /Type /Annot +>> +endobj +45 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 79 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 57.02362 490.3436 187.525 499.5236 ] /Subtype /Link /Type /Annot +>> +endobj +46 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 79 0 R /XYZ 57.02362 765.0236 0 ] /Rect [ 533.9986 490.9174 538.252 500.0974 ] /Subtype /Link /Type /Annot +>> +endobj +47 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 79 0 R /XYZ 57.02362 716.2236 0 ] /Rect [ 77.02362 475.1636 335.2522 484.3436 ] /Subtype /Link /Type /Annot +>> +endobj +48 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 79 0 R /XYZ 57.02362 716.2236 0 ] /Rect [ 533.3848 475.7374 538.252 484.9174 ] /Subtype /Link /Type /Annot +>> +endobj +49 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 79 0 R /XYZ 57.02362 435.0236 0 ] /Rect [ 77.02362 459.9836 260.9299 469.1636 ] /Subtype /Link /Type /Annot +>> +endobj +50 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 79 0 R /XYZ 57.02362 435.0236 0 ] /Rect [ 533.3848 460.5574 538.252 469.7374 ] /Subtype /Link /Type /Annot +>> +endobj +51 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 79 0 R /XYZ 57.02362 315.0236 0 ] /Rect [ 97.02362 444.8036 295.2849 453.9836 ] /Subtype /Link /Type /Annot +>> +endobj +52 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 79 0 R /XYZ 57.02362 315.0236 0 ] /Rect [ 533.3848 445.3774 538.252 454.5574 ] /Subtype /Link /Type /Annot +>> +endobj +53 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 79 0 R /XYZ 57.02362 176.4236 0 ] /Rect [ 97.02362 429.6236 193.2911 438.8036 ] /Subtype /Link /Type /Annot +>> +endobj +54 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 79 0 R /XYZ 57.02362 176.4236 0 ] /Rect [ 533.3848 430.1974 538.252 439.3774 ] /Subtype /Link /Type /Annot +>> +endobj +55 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 87 0 R /XYZ 57.02362 543.8236 0 ] /Rect [ 77.02362 414.4436 159.0333 423.6236 ] /Subtype /Link /Type /Annot +>> +endobj +56 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 87 0 R /XYZ 57.02362 543.8236 0 ] /Rect [ 533.3848 415.0174 538.252 424.1974 ] /Subtype /Link /Type /Annot +>> +endobj +57 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 87 0 R /XYZ 57.02362 505.8236 0 ] /Rect [ 97.02362 399.2636 303.5512 408.4436 ] /Subtype /Link /Type /Annot +>> +endobj +58 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 87 0 R /XYZ 57.02362 505.8236 0 ] /Rect [ 533.3848 399.8374 538.252 409.0174 ] /Subtype /Link /Type /Annot +>> +endobj +59 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 87 0 R /XYZ 57.02362 279.6236 0 ] /Rect [ 97.02362 384.0836 231.6083 393.2636 ] /Subtype /Link /Type /Annot +>> +endobj +60 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 87 0 R /XYZ 57.02362 279.6236 0 ] /Rect [ 533.3848 384.6574 538.252 393.8374 ] /Subtype /Link /Type /Annot +>> +endobj +61 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 94 0 R /XYZ 57.02362 230.3117 0 ] /Rect [ 97.02362 368.9036 333.942 378.0836 ] /Subtype /Link /Type /Annot +>> +endobj +62 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 94 0 R /XYZ 57.02362 230.3117 0 ] /Rect [ 533.3848 369.4774 538.252 378.6574 ] /Subtype /Link /Type /Annot +>> +endobj +63 0 obj +<< +/Annots [ 15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 23 0 R 24 0 R + 25 0 R 26 0 R 27 0 R 28 0 R 29 0 R 30 0 R 31 0 R 32 0 R 33 0 R 34 0 R + 35 0 R 36 0 R 37 0 R 38 0 R 39 0 R 40 0 R 41 0 R 42 0 R 43 0 R 44 0 R + 45 0 R 46 0 R 47 0 R 48 0 R 49 0 R 50 0 R 51 0 R 52 0 R 53 0 R 54 0 R + 55 0 R 56 0 R 57 0 R 58 0 R 59 0 R 60 0 R 61 0 R 62 0 R ] /Contents 138 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 134 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +64 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 63 0 R /XYZ 77.02362 668.0336 0 ] /Rect [ 302.384 194.8236 336.5734 205.6236 ] /Subtype /Link /Type /Annot +>> +endobj +65 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 63 0 R /XYZ 77.02362 637.6736 0 ] /Rect [ 356.4191 178.0236 425.9329 188.8236 ] /Subtype /Link /Type /Annot +>> +endobj +66 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 63 0 R /XYZ 77.02362 622.4936 0 ] /Rect [ 422.7107 178.0236 517.3604 188.8236 ] /Subtype /Link /Type /Annot +>> +endobj +67 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 63 0 R /XYZ 77.02362 607.3136 0 ] /Rect [ 300.7141 167.2236 344.6901 178.0236 ] /Subtype /Link /Type /Annot +>> +endobj +68 0 obj +<< +/Annots [ 64 0 R 65 0 R 66 0 R 67 0 R ] /Contents 139 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 134 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +69 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 63.02362 733.0986 104.2136 743.8986 ] /Subtype /Link /Type /Annot +>> +endobj +70 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/products/architecture/m-profile/docs/ddi0403/e/armv7-m-architecture-reference-manual) +>> /Border [ 0 0 0 ] /Rect [ 63.02362 701.4986 125.1403 712.2986 ] /Subtype /Link /Type /Annot +>> +endobj +71 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/docs/ddi0406/c/arm-architecture-reference-manual-armv7-a-and-armv7-r-edition) +>> /Border [ 0 0 0 ] /Rect [ 63.02362 659.0986 129.7853 669.8986 ] /Subtype /Link /Type /Annot +>> +endobj +72 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://developer.arm.com/docs/ddi0100/latest/armv5-architecture-reference-manual) +>> /Border [ 0 0 0 ] /Rect [ 63.02362 627.4986 112.8752 638.2986 ] /Subtype /Link /Type /Annot +>> +endobj +73 0 obj +<< +/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F6 /Subtype /Type1 /Type /Font +>> +endobj +74 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 444.0978 428.8236 512.2048 439.6236 ] /Subtype /Link /Type /Annot +>> +endobj +75 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 103.5349 418.0236 141.7233 428.8236 ] /Subtype /Link /Type /Annot +>> +endobj +76 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 172.0983 418.0236 218.6628 428.8236 ] /Subtype /Link /Type /Annot +>> +endobj +77 0 obj +<< +/Annots [ 69 0 R 70 0 R 71 0 R 72 0 R 74 0 R 75 0 R 76 0 R ] /Contents 140 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 134 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +78 0 obj +<< +/A << +/S /URI /Type /Action /URI (https://github.com/ARM-software/abi-aa/releases) +>> /Border [ 0 0 0 ] /Rect [ 305.0993 663.4236 362.7353 674.2236 ] /Subtype /Link /Type /Annot +>> +endobj +79 0 obj +<< +/Annots [ 78 0 R ] /Contents 141 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 134 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +80 0 obj +<< +/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F7 /Subtype /Type1 /Type /Font +>> +endobj +81 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 79 0 R /XYZ 57.02362 719.2236 0 ] /Rect [ 113.1682 474.0236 400.7302 484.8236 ] /Subtype /Link /Type /Annot +>> +endobj +82 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 87 0 R /XYZ 57.02362 148.6236 0 ] /Rect [ 420.0637 432.6236 537.5495 443.4236 ] /Subtype /Link /Type /Annot +>> +endobj +83 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 87 0 R /XYZ 57.02362 148.6236 0 ] /Rect [ 80.02362 421.8236 91.16376 432.6236 ] /Subtype /Link /Type /Annot +>> +endobj +84 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 87 0 R /XYZ 57.02362 148.6236 0 ] /Rect [ 495.7331 394.2236 536.5146 405.0236 ] /Subtype /Link /Type /Annot +>> +endobj +85 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 87 0 R /XYZ 57.02362 148.6236 0 ] /Rect [ 80.02362 383.4236 176.7736 394.2236 ] /Subtype /Link /Type /Annot +>> +endobj +86 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 94 0 R /XYZ 57.02362 232.8117 0 ] /Rect [ 173.048 299.6236 420.2932 310.4236 ] /Subtype /Link /Type /Annot +>> +endobj +87 0 obj +<< +/Annots [ 81 0 R 82 0 R 83 0 R 84 0 R 85 0 R 86 0 R ] /Contents 142 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 134 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +88 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 94 0 R /XYZ 57.02362 599.0236 0 ] /Rect [ 167.5275 723.6236 300.3698 734.4236 ] /Subtype /Link /Type /Annot +>> +endobj +89 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 79 0 R /XYZ 57.02362 317.5236 0 ] /Rect [ 425.2018 683.0236 536.2774 693.8236 ] /Subtype /Link /Type /Annot +>> +endobj +90 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 79 0 R /XYZ 57.02362 317.5236 0 ] /Rect [ 57.02362 672.2236 174.4719 683.0236 ] /Subtype /Link /Type /Annot +>> +endobj +91 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 79 0 R /XYZ 57.02362 178.9236 0 ] /Rect [ 57.02362 644.6236 140.6153 655.4236 ] /Subtype /Link /Type /Annot +>> +endobj +92 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 87 0 R /XYZ 57.02362 508.3236 0 ] /Rect [ 447.1701 149.3117 537.7905 160.1117 ] /Subtype /Link /Type /Annot +>> +endobj +93 0 obj +<< +/Border [ 0 0 0 ] /Contents () /Dest [ 87 0 R /XYZ 57.02362 508.3236 0 ] /Rect [ 57.02362 138.5117 180.5715 149.3117 ] /Subtype /Link /Type /Annot +>> +endobj +94 0 obj +<< +/Annots [ 88 0 R 89 0 R 90 0 R 91 0 R 92 0 R 93 0 R ] /Contents 143 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 134 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 + /Trans << + +>> /Type /Page +>> +endobj +95 0 obj +<< +/Contents 144 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 134 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +96 0 obj +<< +/Filter [ /FlateDecode ] /Length 699 +>> +stream +xœmÕÍnÚ@†á=Wáe«.Àsþˆ„ÒHYôG¥êžÀRƒ ,r÷ÏJFm‘`^ðX~d#ñÝãâ±Û›ñ×þ°^æs³Ýu›>Ÿ—~›§ü¼ëFmj6»õùúmø\ïWÇѸœ¼|9óþ±ÛF³Y3þVžÎýKóîvx}Xä_«—åª;½¿ô›Üïºçÿ]^ŽÇßyŸ»s3ÍçÍ&oË%>­ŽŸWûÜŒÿ9åmÃ÷—cnÒð½¥r}ØäÓqµÎýª{ΣÙd2ofñ0ånó×±6MyÎÓvýsÕ_÷NÊk^ºúöJ§I+h:%´rÏ Ú¸çíì:ØÃž)÷ú†¿OÑ·Õu?V}Wõ¢êûªÞº­üm[uªZªÖª­j¯:ªžV}Suåo+[ùÛÊßVþ¶ò'úÓÐô—¥4ýÃ=Oô'<‹DYJÓ_–Òô—¥4ý ÷<ÑŸpÏý Ï%Ñ_–Òô—¥4ý Ï4ÑŸðLý ÿDYF3¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~§ßáwú~§ßáwú~§ßáwú~§ßáwú~§ßáwú~§ßáwú~§ßáwú~§ßáwúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àú¯â: 0+0ç^gÐúÒ÷e< Ãp<9».¿ÎËãሳðþï0ŸGendstream +endobj +97 0 obj +<< +/Filter [ /FlateDecode ] /Length 19554 /Length1 36880 +>> +stream +xœì½ |Eö8^ÕÕÇLÏ=Éä>:7„„Ã’I @€B (˜ƒÉa"Ë%  ‡Q] ˆwDØ]p—×UPô»ˆ¬_vUHšß«ê™ˆ®«î~÷ÿùü{ºº»êջ߫×5a„ -DeŸ—×wv_¸sŽÜÂò‚*ƒ·á,B¸?^…³kT˜„— ×jIÕŒò{ûÏž‰×è鳿–,y°ïŸáú„ÒÖ•EåeŽGhô*x>°n˜¶JïÃu\‡—–×ÞwõðSpýÀÛ6«²°àÎ÷fB(“ö?U^p_ÿ¾x/BcóáZ©((/®û$\/Dèv]UeMí³h +B.§Ï«ª‹«†Hƒæƒ;JæÍx5 (l„‚´3ù*áì@…A$DÇsUÜø=ê¸!‡çGóH1f•¤!'RnÜ=UO¼I*Ççó¾ñá ¤ýó³‰Xf­…ì.|äaFIH‡ôè_ù'#À3!3² +²!;ò@žÈ¼7òA¾Èù£ˆ‚P0RP +Ea(E H…z¡hÔõA1¨/ŠEq(õC (õGÐ@4݆’Ð`4 E·£a(hKA©h8ÒP:‰F¡ 4A™h,‡Æ£,tÊFPšˆrÑ$4åŸïDw¡©hºå£4¢"TŒJÐ Tʘ2µ¢cðyíF›ñ¸*Û÷Ân?ZŠêàΛø^Áõ…{;Ðtz6 cd7ðhÀ÷ô?#pè*ÎA/Œ$쉓$‘Gü8þ>›oå/ò'Ð ¾†?Áçó58‘Yx€ 5¢m¨pñÄ•hWÏe÷…h|*áù ¼Ÿìá%è4Ú@xnÚ‚O]ÇÐ?Ð’Ã--JäJÿ·Ö ¿ Õ€àOƒ6¨\¸ØÃ\ÓÙw é+œfŸ+hÌœƒ¶‰­¢§³PŽíÀoâKâZÔ‚N’»È½ä,^ʇñ;ùQ¨QãÉG{#–à¹@;ýÔSèÜ>ïFŸóùÒt€ý¥æ|ËŠJÐa8æˆV i^JV¦ôi :!æã`<@æÕU’h&´êÑ^´õ%ͨ 1zÅAÂ?`äfþ# ¹?Äý #@çJøËÀkPSԌЋ’(ð„Ã(F±îã"2Šö9¼“Ò7æ¦KÅ*)ûPÖ>Ó\¥õƬɼ¿·OØG"tûøˆ°¾ïáG}cÆdMVöu¤pAMË÷&L†&½‚Ûp?m{F'Ý'DÀùû”ÂRåAëƒaƒ´Ç¡µ™/¶ÅJÈÏiä¯#ñ:Ö 8ŵºÔYO]:u)ÞÃb‹±…”𨽆ø·¢6Kæo¾ª£ý?>{ƒÍÝ֓:<9áè¨ø£I£‡¿kLzõyÇx4|TZb<ß/uhrÒÀŒ~ùcæ:Í®3MÔÝÝ{¬õÜ…öKVµ£í²õr‚-)Éžd÷Nê‡â.ÿCýÂúE‚õòû—á^R<%3vxzyáÄ„ƒ†áý#£bqTïíewxr`,a¡áQ‘Ü€þöAÃ=ÂlÐöIðrxŠRÆaп¶…Ùx´äãiÓòó§MûxÉç§M:í®içØ€¥òòY³ÊËÕo7lP¿ÕÚXš___1irɾ½eù>ÃcWìY4$c`ê£BÉ´iÓ>Y¼èü´iwß=mêÇK–žŸ:mÚŒ +}mÓõzyy´±°a¡­NW/ “™öϸ•°þ¾(Dyö±­{ƒC\ýÝ{_PgΡ½àAË…ÓÍ (Êé!¢-z²È¨—E_‰ðòÅFë¹öã§Ž[ÕS¿Æ_z)!‡ ðH$ûÆ‹Nn[”7Ñoòù38Y}ýöý]Øz²ED¶ä+bÂc_æ¹KúÜån°$Ì#‚}cÔ[8½G} ;÷°oУýDÅ`°}œ²-D‘A¤À.¹@a^¾A}'k4ßuãvˆ(W«ahsh\|oyÆäMQFš4ö>ùV˜õˆñ­¡kÂ’ãàY<ïØW?xLJê;IÑO2†ú%E†’I‰É0Ý…¶SÀ…X/Û’lö$›wX´µíê…KÖOÁÌá6Ü¡Oâñ˜}rΘ}†œ;Çì3Ò/sΓ_fqÃwò×n³ º-kŠÄTÈí?ÞT³4ÓÖT>jÓ´ýãp,ô8ˆ}ƒbáúäa+¯%k<>¡ñ¶‚Y¿ñ3Ÿ\r×ÔaIÇŸÉØ4yÒºÁ%÷~àûþ©wòÎŽŒdgì”ØÌ¥ }œ[»-**k䔋nΦâ)«ô/w>¼30îæd–Ô›ïÛXrgc¿˜ª´Å[iòÅ'Ý2@[•h È ½SݔĥLE€ `,üi€Ó"¡%ü"N'  $YÛ)Ÿ&D˜’zA™w¡ýx<>€ Š!Ë@¦‚Ž€Û³‘0‚;vÌó ‡ª +§;îUÅÅšœß"»¹ÏÙJuZüéxADÇëèv‡Há âèDíç.uê¡Ó¯wê³ôùú*}‹þU½4•:Ó8pån.|7LušëC6×ð5ÞÂ+0W0ć~þÔ ñzÁ×Ä5x{X¶à ?opXv›Õl2êuî@ê'ûˆŠµý8 CO Ú>”~·%\JHˆwÚ ØÀŒ“'ïgó³{ëðTBB 88 .“aìÂÞ[ý GŒ(Åáe-3ðmêãÙx˜úhiË õìŒÇKÕ·p~Žú +.+!KÕý¤A-À[Õ‚Mêþêt¼…ñ¸Mx+øP´[=É—‹ž‰ö/ú²sb('ä(Ù‹3ä@ÀÅEÇùsÑÑq©6k¨ìÍ{‹úÞ!ç½ÌK\&ØÂüôZaE½ #ÔyÉAR´ŸÄjÕ,EêL‚Oe¡§ò¨¨yŒÙg¥&ba“jÁUï¡íÞCÁ²¨.\¢f—ÐÆ, Ì,Ì›Y¤ ’•ÿB²š¿Ëë<ñ_ä…â01 ÇÛ¦ÙÌ€DGóÑQ6ͺˆM3>³>î…"\öû™%ïU~pôOÇ‹¦î0áٻοþý¢Úy÷~´`Q½z÷åúö}Á™‚ñ;á{šŸäóH¸øˆñ¡Hûª^ákúG…øGè‰ÉaÖ›,!¦>f“¥Ÿ¡¿Æ¼îŽÅÓå]Æì31þõ£\m»tœÔ?47j~!áêÐ ìŽõ²Æ5á zŸÜ«3£ÐÂV÷û‰8Ì£Û3á½I……“&NÜrè¥ÇZ½Ô¾>·pú¤I…E¤_Kû”–à-‡_Úºõà!nͺ75-^Ò´àÜK/=ûÒá³\AÓâÖ­{`Qó‚oÿW4}éå?=|èœfËnœTà“ÅãÎf6—Ùì6Ã2Ù`·ÛôËÞáå°¨[æååà0Á˃Ñ2= + + +T‚¹ ›ì!ƒÙO›‡,é8Qoóð9̤=â|ðÄ“ÞMµôDDréå0‹½¢Ä&Ÿ>MQëÂ×ø¬2{ȱ³ÅÚQæ k +1Û,`uvk?È^ÚNA8¢1´‘*$5ç#þÁ¢À-4À¬4ÅìqêyO÷-¦w:ïæ…î·$`<Õ¹_2¡ú考9oCoÔGÚ£þö†i(O‘'Ú§øM ΋ŸTE›ðFn½®É°ÖÑäÕº±o°Þ 7êlÆ(c/Î_ïkð5úÚ<^Á Q( +GëÃì½=z{örÄ% Ñ÷·'y$'ŒÖgzŽq¤ûŽOÈÅSôyƉö<»‚ïN˜i,³å'Ôá¹Æy¶µh-^Ï5 ›¥Íº­ºMúG ›Œ«Zö%$MESµ8Gó"=†%ŠœI+M‘P÷xÖ•Bá¯îœxªåîj=n’*2–NÅÞí¥–üMýͲeñ n°}â¤-#Ê– !aw<>ù‘7’\cÇ7yǪPÕ_©ç׿MÂXøQaòü¡O¼~ ®_åäÄË VàJ+9eüZÄÓ Áß"H\rE6§É)d ùB•°ZhD 4hqÕ ö©_qõ¢VûƒœqZo6IˆØEä!›­ç(P€)kÏÂÚÔÏ]H¸dc9¤ÄXäžvï0-Ïäê—-^²´¥¹iÝzÑþ©:ìâEuÈ'ÅGþò!n»ómƒù*Ù|ÁQé|F;ï¡C0ßЫ]p=µ,6l }@n€ljnYºd‰h¿¤ýð/êà¿~‚ߺx¿Áè¸MÞ´¡qΫɀx£^â!„’‚͸QÆæ5vQ&zÑF0á§…7è=M¢Ýzjè…„spx3÷Lw +‚kÛ%¸×ñØ„¥`›Õ ˜G7ÕxÆ`u_µºo0ž¡nŒ³ªqÿáoN?¦6à¹Ç¦¿ùFá1ÀDþ>€ÁUªßh¨€e(g³Ú aá¢ÍŠÀ6á›[¹ù±Çà¿Ç»Žõê×ׯ«_c½¥žPÃq¦NÄýqb‹Z£.SÔüž‹çá‡(Ý!ÄOz@ÕŽTÒÂs-Â" µèuÁb$OØ`=å²L­áR›Æ”„«t™$i/Xˆ…ç¦ +± "©HU<ô¦ø]<º}Ûn¾fTë¨k§w3ýh†ùFÍh‹3Ê×ÏŸøØ@]l‚À§Z·­3µx®áQ ‡¬äo+©Ñ;Àè½\±p!,‚žºôÚk,x2|º±\øï °2ŸŒ ù\!WšÇÏfû7øJ<â}y?Þ_¨E³Å:¿ÿÚ€Åh™ïb¿Åþ‹v¢þ6p‡@Æ€H[:‚Ó ƒŸ§Y‰ˆð +îõöL`dbÁا–Ý}ò¾y§&†=ÓîôU¯îÞ½{^3¸|}ÆœæÔáÇû%|öÆ]Û«Õ¿2ú7ƒÌk€þ^¨Ê‹ò2}ð2Å£ÅajѯZ”µakÄUŽ'£½<ñô ˆT¬Ä3X/FS6xå¸9 g€ÑÖ8—.t-rX>Q_T\ …ðààiNŇt[«DöÁ´FIòš'Õß©ŸM{{fÎ;导}pûÞM[žÜ0á•ꚣyŸbãÃ$"¸mõ_ED¼Ù/¡¹ñ¦sªjêÃ#_P”÷ößÿ4Õí"ó6Ð+¼ß"g 6"Ä”ŠˆAjUÅ"=6Ê(@ÔñFæ{ 9îäÈH ;5œ‹Ø¨‡êÌ#‚xR¡ö6 ÞhÊCehzI^¸ŠÄ}È@<7Ž7åâ\‡ç‘¥ØÂÔC†žh£ êkˆ¨rX ž>}´cšÑ~žœhOÜ©¶àü7™Œ¶€ŒŠ÷@4ÍÆûI¶eÖ@¿ɳźÂĵ E¦UÒ¶ ï,“X‰AÖvÜ]2ÖnÄJm„dm»L͘Ú1HmÓäCr9 £ï.*ˆoGKÌä˜k8\=¥~9íÍÒ)¯ÝóÌ»ï>sÇã9ÂéÝê#‹zùþ¦þ]QŽõ‹?°yóðHæWÿfæWÂÑdg¸‡ˆLËŒ¨ÅKl ðÚnm1®]°*ªð ò !Áþàh@‘.0Ws¡ýB— +9=¡cøw‚œà ÇD |7Ö<Ý’OÌrޏI S¨[ +Iðâ¶-ߺu9XŸùhæ;'-CößóÔ+«êeœ…ý3%C=ñøK/=þÄ!nnkx¤ú•ú天ê—ýTý樦ãíAZõ'èT)ÈED…NÁÆŽØxðÈ„ƒƒ%X¸µ±¸÷L… |‰-ð%$¹øNûd‹ÄOHF 3È>´O”@g@88 ‡ì$¯u||«‰ÂéÜk‹šJ€÷_ <^Éxë°áÎàp”ØԷž&hUÔ“ñ>ÆðÞŽð‹<9¸sKˆ?¬« ëo»Ä˜ë¶Yv¥»²yšm…'º«‡`¶a¡áy¸;€~p+Woß¾zõŽíêöÅkÐ?¨®YôÈ“ê×_­~½mÔš%‹×®]¼d ÷Ö¦††M.kØ”«ì_øüï~÷üÂýJè‘Æ3Ÿ}v¦ñ.¨]¼¸wnÏ7M>Lo¤`_¼ ù¶ÈÛù´Â+¸ÅºÆkU„â„BCLLm€w„úTý»[k¼Ú|ßð{Íÿµ€×ßj –vÛÛ?·ЛALÇí®Ä%jº‰Ý„>ÊÜ<´eðþYQ¯cëÇ°Ž°©Ï©ŸdnÆÃ\ ºùŠ=÷.lùë§Ø‹·­êAÜz·>Qš®€â¼É‡±šC€Ó,.áw@xge r‘=®jAžzˆ+'OÒPχ©Zæl¼E8= ›¶ðKÐXºØ€È KCh1ù2är ËFNÒ|@uœqç$gÐ4á,à ¢±ÎÞÜf¼ažž8,ˆÚ, +©Ïa$âSæP8Z})AK+\+þ‹®Õ]Kë9€…³×¿áu×T#WÔ5êÚø½ø=JÇœ/œ%O¸ø`‚tHÜÌ < +£lhK謼ƒO¢Ÿ3Gá\SÉ;¯ìvåvnÜÇ;ûˆ›¤aDØ ¸£Í‰›‘ãpª(@Ê)ð;ñS’È…|‰r¨ò.¬µCØ{ À}6'/’+Ô¸jÜ\Îd° nè%½,vö Ò‹¼èÁ#ÏeË­MÞk@¢~&½ÀËAØàÇ€|Q¤ŸG,Fê‡=X´lwÕYaÐîªô_½l=·hú{`®~ž½{eÄÜ>Özî{¡=ÂìŽ2ùT+–Ÿ:b}ër„ -i V‹`ew&õ2{ã5íXø85o¨·Œ 7.yN¾›¿ÁݪåÞµ’LTd8å¶,òâéêÑ»g)R +Z*µÂòíÚÃ÷×?´zÞÜF.dè£3öüáOÏØ<¤ñ‘íÉÎRõô¾úó{®¦¼ {>¶èÛÒ)óÕ3ª­ .[þ«E8ûåSøžú1ãÕ7ÔÏ8߯'·=¼jû6uÔØŒoßyçÚ˜Ì%Š×‡ÏÝs8kÉÊg‰úë×·ªÿ3³´|Ò•3–ÌŸ3^>€GÏ_а·eú§õê·êïDÊ+}_Ír½áLA6X €éÑ\Ûd"#9‹,A‚*Ò›z‘uôd1RÍaZ5dEC½ C¦Ü¦Õ /œºdï^Xè<é¾p‡#Mï÷+FZ0Hµ` g‘,: šŒf£*´ +é%¬ãD¢ç½°/—‹'sYƸ”»Ïæî'Õüé>]^Î-4nà6’fÞ[Krè*ƒ„0î°z™‹Pë?á’~¿¼ãîå§s‡/Ù{­^ .bñë(ÄÒK@»V‘ +į0_Ô$ë›ì‹p“üL°Í ã<|ƒdð|bõ(À·P'DMˆUЩÛd¬¤øý–P Öršªt6"Bº°¼xò±ÇžTã>ëÖ¬Y§8þâµ…÷7mW¯\ïøŒ;ÚñAÃÊUK¹uXeõ½U;^{nŞʱ ïü ´æÆy! +|€/èô3=nÞ+7Ùðãh/æo[å'ùšP¼§Õ¢è +GZu-þ‹°?èÑÅ•—h¯~ÜIŠUrqñ ¤^ÁVŒ_,™ùÅê3ê<¼ OXö…0ýôÝÓÔ·Õ?ªgÔ·§Ý}rÔ(¼ƒ$ð֑̆Â>cÔ¤ZuœUF‚¯)èy;[ÅjLƒX³?߃1Ìû"BØ9ãµWÁ «©ÇÔT˜g?nVKÕ,µ@ˆ»>ûàXƒ½w¨ëÕ…ê¯Ôfæ“©WÂü:»ØÄsMh‘®‰F°^‚ü”7R–œjkë”Wüþ`ÌÎrO×q”ìëðãÞîHâ¾iFSËôÝçwwÂøzí´»àóÏ@à`Àe ¸Fm1tv”Lé¨â²:ö½K¡ŽÚÝ1¹dIs§@”äTà›ˆ“Îþ¸m¯£É¼F·*ˆC¶þ|¢¯Á +Éõ¥ö ím2UO1—Ájàj\R佻˗S=ÀÙëÔO[Ô'Ô:¼O{K•Uí+ÕËêØÛïÙy¯Ùѱ`ÂD¼—ã +¼qTúîÎW£¾§þ^ýM„›vaãmŒÓS×Ä=ãE²„ ·é±›µí,ƒzñû³ga lKÔ^N}—ûó»ïv„ý›¹¢k}(—]°ñZVGˆ}=ËQp¬¸eeoYØJ›Õ°Ó/h…­Fa« Rèà]ƒLßhVKJuz8$5 ûÐ"£ “\(ö€yÅHT()l“Ó”eÊ75š¶šl«èZ}÷Äù±ÉË*`¢µêWWw7¿ÑÉ“™¬^𥳗΋d!äÛÜž3UÉAÏêEH.DE i [Î ¥µ¦®—ngHS˜ý:Bá#žæ9½Î‹ë%ôÒ â +ýu#¹ta¸n"7ƒ›ÍÍ–pË…FÝ:îQÝEÎ>RЋþÄWÀ3K>¤—ÐGì- ä +ÅR¼1…8ù4Á):%§q:ɇÄ iŽPe\IV +‹R£qyL|L:@~-½EÞ’þ@Þ—>#ŸóŸ ÿ#~M¾¾c¦Þ‹¦Þ ÌÁ!ÔÇ2©nÁ|‡?ñSÿÑ‘He»‚›Ó1ªý<÷ÛŽ~¨Ónf²wqN#3@#žÖ¬´÷%ñN}¼”%-$ y^S0Äw¹?´ß ,?½[ƒ! #zÕÙŸØ$ÄÙ0§£'Âée=)YŸ*KÑÃuˆFŠY à‡ÉÀwµê­)ßézº[µ»3y¤+£ýUfÊù\BCž“œ§ä!Gr‘’"EÊŠÜ_ —q÷sõÒ\y!·XZ,¯æ¼xl ØŸ„á¥ë¥ï‡’\]ž¾X7S?[7üàC¤ ?J<ÙZ +Gë±a”{¸/žà¾o© Ž© Ú„Óí:ò͵>Bp;âѵ:õ,‘ù¹Î ÉFël6È3RX U±Äð%—j×J©qìÍd—v1­ +¦.É?»MŔʸi!'‰X/:°Ÿ˜Ž3ÄIx²XŒËĹâRü Ø„7‰[ V†5¸h8¶rÍmꕎ™€íõ`þ£k}ø®ƒÿ§¾ìL·^“5i5<_K"ñuX}zÝjxÔE%²ê]”æ®Ø7‰:§v`rîÆêsx0¾O]®Qߢ5V!SmU?Q?U[ñ(ì‡ýñ¨mêêºÚÁÛ`} +dw,âb±È vú@¢áÈn•uO£Q²†#OÍkijÁªxNƒÅìHvÜíxÖ!°¸Ô¿yˆÜt× +^«>´iÓCêmøëÃëê»B\ÇoiXöÈŽóg?ø¸c'å…ú‹(ÛÙÛfå,Øh2š±ÉdLµs|€9¦ “¿²]_Æ¢ ·mìU=cTR· +VÉëÆ:íÕD¹ LÆòÕB¬ÿðÊÏ÷¾ËÆk¿U?øÖ,Ûqe"cj»ú°;®ç/=PzÎ9ò<"‹6ºà±Á‚'U䑃ðŽ&½g“i‘DbƒÉË,Ⱦ¾¼-ÙS0òŒÑm”Ó6-ö¥ì¶ÓmF=ˆÓÞ9ƒX*8Ï HÀ$ï@ìÉyo>Eà.’D‰‘R¤.R¯ ĹtœÎ• +u|0Çc¹¸\Ú n‚§²RŸ·}»Êv…(4 ë+y(¥~؉3¯Ž^yß¹wñ;µ/éX¡>ÒÔôwØkõ¯ÔR¼ yzÇ +áôû|è7¾ãrÃ’%K©MÒz5ݧ…~åj2rf¤Ós’̥ʆ `Þ‘ãqÏu>M6¾ ­‹€ä¬Wlö—P¨¿¯¹¯äëÚËz® ~®X´xtÕõâóH§‹êþž¾Xƒähêàè¸èñÑDËåX¡ øEMm­žð£jŽß½ýù9;æ}üõõâÌ/Ö_ª~æpæúßÅÞ/û“°í­AÎ.,öísæÀ™¿ÄÇý.-}ù¯*îöéûÚÓG.DÒ{ ìŠî[Ðh§YÔœ¹Ò§ ³žºÐ~ÙQ‚¶ŸVT:V_Ò!»¾äôÁÈ +.$X²êú*ýV½~*q½ùù/;.ë¸ ÒµÓ´º„Ñ~ð)Ñ0Ÿ 9^:Îf@B“y•-²ëäÛ ¢¦Ø»Â:{¦9˜W– Ù`F­„†m ŽL³”ýÇö¾ùÆÞcê‡`Ÿ¨‚ó­»ròä²²ý.õœú>îÃ)ˆ^tFñ4ÎG´HOèž[Æ(•ðøYD@H€ŒÐ½þ'ü=q&KGu|:"½ÉH~¤0…Ì'Kˆ$"‰ÓñÔ{r~¼ŸÐEâH.š"DEwJĉÜP~¨0H…Òp—Ág#Å<”+–pe|™0͆eÑ\~®P'.Ôm@ëÅh°X éa=Äî8rŸÁú}ÇÛ໽ùÏ!qÂhBÒ[q½3Cðˆ§¼Ÿ¬'~²Aæü0ÝM!Ò ö.¸B®zÛ2¦Ê∇ :£AÖë´}# ™¬§\»F.%$Ü:Øvž;—€ˆÅÞ¯DN8ò*Ù.÷Â!êㆠýåx9“+¤ÊN9›ÉÝ#Ìóåznw¿°@X(7sMB „ôd¼(ÐMÐXâA÷$=Òó²lDf?âà:_£Õ¬ð!‚"*’¢ Ó‡ËŬ˜‡rƒÉ>Qˆ× Ô'’ñæt”ŽGs,gR!à¦êœ:§~„<Öè4;Í“9ˆñÆ,s 7ƒðÓ…|1_Ê×é‹ä"ÃC=w™Ã× +sŹÒ]•î>ããó2®,çWKõÍëù­ægÍwÒKED¥¦Ça#Žƒ›N:O¿N¨+TðÝo¨ 1;™X¯]ÑÞvæå%NÑÄ‹H6‚êƒ5Î Ñ™› +–É2P*‡ø1ûR³&;=Yšª·tY® +‚³’¬—:ÿszÂ# éô¼„=/bN&"¶À¿<šI¼è ®ÁugT…CgÔ»Ô¼?q×Ö£Äöo¸úŽ¥„îźÑ>ã"‹Å뜽\Ù1æz"ȇ*’.•&3"'ñN¼‰¤gÞÄþÃ&„dª.CΓÀÅsñ ¹tÎÉ9§îîá]1÷·–³za?,Gâh2ßFœ2¬YÉ}¤JÞ*Ó"„qîF=ƒ·àGÏt\9TlâJÚ¿‚UàÛZ{ð9˜å`«þ,ÿÔÑ¢{ª< i@åp*D™VÙh}If¯?DíÕÜ[çr·gÞé=—|¤hH/µ”l”N/ƒ¼ˆŸÎjˆ3 IºdÃH2Z7Þ0‘äéJH™®Ò0‡Ü§[`Øjðrçé :RÃ7µg‘·¯ßNöµÏNoº^¹{¿¦ó}ÿdÑüö§…ß%îçv¡ç`éJ†#]ge­‹ºo*`µýx{¯|•Di=7dÞyåŠ +ð¿modð{¼ Ÿwüðÿ“wÁ¼àÝìe0},®¥°Þf{(®ñN+Ù…÷ W4œ][ã,Ú,ºëí¾b[ã‚–°(ÀUª“¿üRôüæÏ"ßHëµd§p–ùp§ ®-’?wœautÌ¥Ôä0|T¥Ž:ÊÀ;Nâ•j×—êñ%5÷T÷–_£]à÷y+­%Ð ¿À~ÞóúÕ=š¾ìæ¯p+ÄèÛÏ©Ç/ çy2óÖs®®*¤¶Ë’geG>ZÙ‘b+±ßFõX¢>ˆg™õoôåëÁ'F ÃÎ(ß`ƒ·ÞŒvy‹Í6eY𡀃a­¶UÞFäM|Lz!˜è<Ó")ÇOÿÕô²Éö«t'ÛJ/gE|`|P|p¼šå t9ƒŠ3Äš˜”œ¥d…d…fEUE- ljnPB–†®Žj‰ºäê䔜¯ä‡TUW)U! ƒ/T†øtWv;‚ê,¤†ô(-s¯|¸gQåÆƒ­­É‡—ï9ÖqsO­Ï?SüÊ”ÿ½Â%–ÔO¯9óBtfÇ¢Ý%¯?ñòkö+ccwGEµÓ|õðjèòÕÛœ¾ä Ñ¢?èãXeiõ_ï‹ìö‘>FQç—ÎrÒ„«¬¶p¾‰:r9þ@~Р– xºß¿ª˜½ÌƒÄšnn¥@>yê‘Gž¢GÇß«?ŽnÜ8^ÿÜàƒ¹¸c/ƒƒË.*P«ßÀçpAÑNÀÓ}uä"ÈÐ%;ýÑ2¼œ7/3-—ÚøƒÞ­´pg7¡Qži~Öö î•–äÿ~™.Kü­þ ýWû·ø ¸[Ò×¹wÛUÀ#Ç=–õü‘#Ïg=6nìö©ÅôÅâÄ'ø{úô9âÄù>}v‡‡AflÇƒÃØZ ð⧆V_~‘Ùó  [enÅë!ÝF:n¤ÍnH d&–Ðɯ¶ü¢e&NŽ­L¼º×ËÉ­­ƒŸ»ÿØ tãØýÏu¼ œÛ¹¸GpÓ¾½´³¨À:øŒ(P.ºðZüòDþ¨Êù¿~™n¹àØ……ƒFü’ÏA{«qU€¿ƒÓ9th g·¤0Û\›¯^Ò^Þ^ÕÞÃE'V¶þ.ðJ Œ’q2—ìHöb¤8]œ>F®D•¸’«tTúë§ÞKY¢í—ï,‚ +HŒí¿ }¿ñÄ‹3ßž^ø»{Ô«êÛ8ºýc,µrÛ—o:hæ¦Myåíþý÷öŽÁ·a{àáêmë_Ø»…ú…8`ø7Àk”ç ¬Ø¨Û%â´Þ,–9 IzAg²2=©Ÿ“©S6hNÙÌÚlËq[ûж6»¶í8¾gK`?Ì8àtd9ZtIHb-©HÍ‹ûf_áX§¾wpß¾½/‹ž³J ÛãÈ{ã^zšòZÍ姯 ¨döa¾Æ@½}™‡×A 9ÖuXÐò²_`¤/ÒGŠv»’ÍÞßjêÐvASõ4{óZÑ{aï–Þ7Y‘·•ëZ›ÜŽ]ªb×^¡'¶7­Û¾}]ÓöVU½V°çŽ;¶dÿú…¤ý÷ÿ¦½ý7÷ïOjånçܹwÞ>wî¯êÇêçAÏÇô~ùÕ; §CŠDßvž^¸›ò÷äEŒ¿ýÁòõˆ˜±Ø`¶µ×ËrqÔ7¦³e?3ü¡ô4Ýÿ¿?ßÁêÐa6 eÝÀÍ|_ÔzÿýM{L}¾îõ#ܶ޻¸-[·¼²­£AôìØR\ô%µ¡×aò¹0/}§ØVF¯ðϡÜ€u$JcC BZýûZ%d³ètb–MgÉ +ð°ÆJ!íí—´·®C‡^¸Ê +’T ñáYáUá«Ã[àójø‡á7Âõ •ÚëÖîºÙ¥¤MI£Ó^[üì+«ëw¬žóÐŽƒ“÷Í÷4Yqÿì¿LUöñÍTe¹-O<úê“ |þÞÓïGò.<ÐÀž6søÖ6sÁm3/ä;~ëàn¶Ç?±˜šæßë˜ÏñŸã!´£ƒÆVZ/´[î vGÚM{þœaɾõ¨^\ -Ð-Ð/ê L Ì , ¬ lõöß+¾¶ž»qzl ¬Y·ç馵{ö¬½‚íêå+S¿Ä6òáÅ£G/~öÎÛŸoVßQ/©_€3OŸí‰oc±ñøÅm€#ÜþîØØj^…_&‡!.Žd²[6a½pÁz->þ%ˆÇS#:™ãJ%z¤5veÜmîübgÇ^QÞÝ-—ÀuH-vwúm†Ÿ;×iµ¬òÙ÷p ËtFBÎÓ-z»ñ;r~ßyÁí +äôwŽsÇl®¦+’nmíÌx:öv ãE»¿ý‡[·ÈhÀϹ¼§hk0s«þ°$‹°ôK·Ó0Â|#ÄíSÇi ~!Ëc«Õ*-ÇéR)o2:8#fóSÀ©CK=bÈ vÛ±W:öƒB• +›¯r¬·a¾(tÑU—›à*ËMè*ËA÷\æXáCs¯ˆÖ®º\¶¿Î,é°ú»÷æ`_`¹ +€tŸY9nç”+¦¯KnÛþõ§¼9«äHÁâUÅO;ŸÞð—ß”¼À'ïíÕ+'Ç™bî½qÅæaa¯ wǘ¬KxÓâ-{\ûÎÒ}%l_™¢YÐYÈ.dÇu ²¸ –`µ›©¯`IJ‚kÙ«m`„û¬cifâé5„æ)‘h†bÃsp½ºtLÍË/Ÿ~¢¡AØ¢¾ÑØÑ²bܦ­¿çòñ0×ïÁ_Lf~ÊV±]žj•Œ{¶ÁOyÆÇJwPeOÒôêBB§»ªt¼FÝ•‡­[%еÁ{©»z¦µuøsu¯¿ƒ‹q;: +¶n}eW½eOIá²ÓUoœ4Ö‘×Q7×2D$ÒZ†Hk¯Ò2!‡It×°Üm}í™C÷ðv®¯ÿI¡;ÅÍähk·[Ãmãtt"=ѳš¸ñã#-nDóŠn€“Á|¼ŽÖ®2HŸ.Œº\”‹óHŸ¥+A%¸Œ”ñ3„R1_W‡jq=©çë„yâR´¯ + ².›Q3^Ïm"ø Âzq§ð”¸O÷šîCÝ Ý0w­ +‡Ýþ&ž†§½©ÞuÏoÏ!{®·0É ñ_ÂD­ž8QÖ“‰´ž8ñGÕ_½E=Qû¢î×±wîÜ1hŒ¤œÅÚ³L®­=nþþËeHì¼!p^œ—*3¸ !]vÊwrw +å,¹‚«Jä¹ ¹Â¡ÛÈmÖɇ¹ÃÂo¸·Éo…@Ó‘7²Î ‡“ÑÁù/ÞOð×ùë= #}{ÆE‘>BC¥]”>\1„“È@~ .‰Ö¹Q$wò©Ú»ZÝýy„Ö©s¹,þ![Ì–²tô9òDC!*ÂÅÜLRÌÏfŠ3¥ +}a†±Ò\‡êð\n>¹Ÿò] ΓH÷éæêèëåÙ†ùÆúöؼ­Ç븵d3ÿ¨@ßšlÔ9ãš[Í;мÛFžæŸv‰»¤§uیϚÍ=G^æ_Zõ¯šÛ¸7Éqþ]a.Ó LÿÃa–Ûúé'g>ý¤U={æo_íh&3éq½…4·Ïv4tÄ€‡;Óú:“·^¢'Ç&6Änƒž²M/cz2È 2z(Lª,ñ˜×q®˜„Ñ­ –έ]6­綺®‚\›Íû¦ð7«Äw­pƒÌó²ï#åÛù~òD~’4Y.‘gãyül©V~ˆ_,oä·òë¥GäÕò¼‹–ß.=)·È2á°ƒq½Ÿ!šD +úÞÅ4'‘AB‰Ö›ãM$]HÓ68MyÔZ¹<2IÈó¤\]®>Ïeª4݇˜Å뤧ñ6iŸé·¦M7Lqt»ƪW`–|‘zÞ}F=¤:ƒŸW«ÏàhÍçw|Øñ:nUGq£9/õ^ÜÈ|äÔ—YðJçpIÇémÈBÙŒÅl³ ‹Éf4!z2›Àp60ÛT“AoE¡¼l6¦¿•õ`­: o1XÝÐ1¶º±Ý mÐg\w½›±õxñw“5 +_x'Pž_‘ õÄä%{›¬¦0ÓS†<^gš¢Ÿ"Ï”L MkMv`i³Á✕· +Þ²§ÁÓègö³D¡pˆ¼ +¯Ѻ^ú9ÜnŒ2õ6÷¶(¶Aà-pñ|¼p›<Ð0Ðx›)Éœd‰·¥ 'vrNâä. LÕ§É#Mæ ‹Ó–ƒîÀwpIŸò™ò™¤ŸV8јgγdÙJp W*—™Ë,ù¶zÝ}æû,+Ѓú¥†¥Æ¦æ–ú&C“q“y“e›a›ñióÓ–}¶ßÚ>´Ý°ƒ,3Ö–iɘ½àÖŽ[wÿÚY™9‰!êÍá–¾3oÓ¨e9ü¸öud–—'Cžud©G9ýtÚ~p0—TÝ.t˜ìt#kåiƒ¶ýÑm ®ßz1+iKhûÞRu*õ‰‘ÜH.C :‹Á‡øëúèÃ@’¤‹7P~¥1~ ×M"yº» ù8Ÿ+!ù|¾0]·À°Ðð¬Á¿G±ú^2³#“{¡}>÷BG1Ÿ¿³ýìÚ$‚þm¤îç½a=Žæ8ûûZ1Bïgu%„ Ã.=Ú…_Ó;vy<aÔËB¸—/ +”Î)¾Ãe‹A‘´µçºRm£k’kohû¥6º¨€¼ÀžÔ¹ 6)ï‡P:U ™ÉíØý£h ⪪°=|4W潇}»kÖ÷ßÞxÏ®o‡x(÷ΊÊ)¹½²zÝ_®¯m¬iºòÁÚÆÉ}óØÃ¾þoþæ¡É”6^ Ä{Å@XkÚ_Äè9öK>íïFÐÓm!{w‹ß û‹<ÝyÑàáïe´èü _ /(ùîòG»Â^ó·ì²=àëç°`HÁüavù;†ƒ©ò鯉›U’x3 ÀSnÉí£6× ÐxãbÈ XÜÉ!/Ê.õ¼ÓWMœRY9eâªôäoŸšõð°aÏzêÛäWr¿Ùü°¿ïÃ}óð¤Æµ\iªi¬]ÿåëØžj|R8Kú¡@æ´b«¿yð›ý=6ƒ%ÈJ·ÖYOµŸºd}M“+{FEÒOgáØÛ‹~CálÙóÉòè´É{¦Ó«µ² 3?:-wé·üˆ!ípÎD5üIí,4Â=OÔÀŸ¿qM8‹öS˜Òçh„Pn‡{íp¾‹ÒBq†óÛl~tãе›¿ˆêaì!¾Ý ç{ùKè^î=GÛ‚â’Ðë\Ò³üZ[:†Ñûü'¬ÿ!ÚŒ†ë>¨’„¡Aðl/øµåÂymó‰h²à·óôLç9…¢wП‘ŠWãWð%NæörÇᣒþ¤…Ïäÿ"zŠÃÄÙââÉO(eJùÒ2éXÈ.Ô]Õgë÷ËAr‘üÃm†Éàñþlôƒu]º©Í`.5ï4ÿÑÂYr-XvZþb­µ6Û«}®ù¦ûîöýÂZ¦_›ßÇþ#üúÿ> 4 :`wà”À  +šü+Ų*äZ¨=tyè»LËHêƒJ‘‘íôÞH½ ïà¼àLÿ•,°Üúº'¸Úr³ó®6Áã[W<0ŒÔÚ<´»Ú2rù®¶ˆdn‰«­C6±Ö6 @‚\m“ý±^S\m3ê?dº« >kȳ®¶ ñCÞ‚1¯‡añlvÚÆÈ sµ9¤Ã_ºÚî«®6¼¸PW[@>\º«-"O®ÜÕÖ¡PîaWÛ€sm®¶)b0 rµÍ¨tð7®¶y YïjÛnÈKh8ªDUh.ªFeì¯Õ"õB…(Î (>‰Ðš=” +}jQ Õ¨ rw3Pô…V +šewªaWÅp.†1³á»zÊ?bÖ³æÀL³a.ú—µ* 7Å£Æük3Ž€ÖL—‹ê G!ô-`ЊوF‘P*à» +úL¸eÐOñ•0{{©åðʪ¹Õe3Jk•^…ÑJB||¢2}®’ZV[S[]\P£dTÆ*)³f)Ù´W’]\S\=»¸(VþÎÐthNÁìò™•3”Ô‚Òï8¢xfAnRXZP1£¸F)¨.VÊ*”ªºé³Ê +•¢Êò‚² +À¬'‰5p[<¡ .R˜Jt4*+ïùqC~LŸ\ÆíàQ%ã`ð<‘ÖZr‹«kÊ*+”„ØÄA=AÝèVs•0hšLk]çž·¤²XT GLîµ µÁ(>E.³F,Œ­„s5H²˜Á«f2¸Å0•ÖÖV Ž‹+ ³ëbk*ëª ‹K*«gÇVÃãôn¸uÄ­§ßµúŒê]1ÓÝbРJ4úRMýeôB OæBŸR6² žU1ºj™®S®U³Ô:(ÔÙ7qòf:ºì«®‡}}5t_Ó­h×t Zݹö]K—Qߟñ‘”÷øå}Ö­åÝEs<‘Y«–Ý¡ZXÎx}Ü« ü3\(eY ^9ƒÖeMe §Rö¬ØE× 6K…Kê1.¹kÒÒfÓtLÓ÷†W%“~_å²Xm†J€ZëÒ±2—0§eÌZ†ÅÍúTÈúQ=Ô »!ÐÞîš.3ƒ×t/´›–„2ÉѱEì\Ãð*„1.údf… ¡å J-{âæO ´f¹,©W'Ž]3P¯Eñ¯ýÕ´ŸÎØÅz§ŠYMÌPÈF»±)bÔ2]›OkÙSmùfˆqYs!`VÇ h<™Ãt ”y¥ZgÊÙ½î¹i¨î¡•¶uŒ‡1ݤCÛåLžš¬ån¤FÇ|1tÆ1¢0Èš=h°Ë\\í)ý¦ÚÍ9 ÛªN®exui]Es?ÊÔ nk(a^½ÂEaq·‹Ø7#†)'fBBOëã–ÕãY.Ïæ–P!›»ˆa\æÂt0³Îvô/·V2ÏÐ%ƒî¾¨‹ßõпÖe 5=úºm¥‹cÝ}@÷q +£¹€a.3ßÜS×4nh±¤àäYÉ¢ â’}9;wù#‹Z‰hd-pQÛƒS?4–òd®+¶h³Sž—0‹\š4‹éiuç SÊÓ¢n2ï®uîZÀ"b󳨕ÜIQÔʫ¢7fôˆ«ÚLnZÀ´GÓ]÷7ó§æŸÒäÆRvQÐ¥aLF?ƒžóÜÌ[áã’÷,6®ì{¼¹Ü)jæg ˜_é‚ë¾SÓ©‘n{¹9z»ü\1£Â=ÓFUz‹xÚI÷Í#dx掶¡Ý´L³™Ì›âËtfï•Ýp­sÙ[OfÃÓ²[p¬ÝÇø\á²ä*øhÑ«€yÔâÎÝå®áì¾#ßÒRJ™‡Wعƅc1Ó¤ïÓ·¯»•ï.b‘ ‚ɽ;¿nÅU¹çºËð§Új óšîXÝemnK¢™Ã¬ÎÜ£Ú5¢'Ä*¦Ñ÷À÷ —Ä´xHµJîôªÿNOõýTMwÙH­+–trjJcóŒGãàŠÎ3®rÐ$È#³Ù³ ¸§@— OráŠþ}ðL.)ì }ʬq´)Äñh"ƒ¥ÁȆo +;îPØ +»¦Wc ÿ8€EǦ¡ÉlŽ4€60m +{,ÜÍ„sš«1îL„kÚ‰hªÍGÿJy³:Žâ¢aš÷»fí‰U›ÑÙX¸Êø£\Oé_DÏ`ð(þ1,?¢íq.<5Îe3è”G2…90ÊdWôîD8gA¿ ŒŸ)Œf ÛqŒ†tx®Ñ’Æ0Ð$¡a4œýåõ<ÖƒþMöÆ:SŽ«g “#¥gOgÃzi˜wI™¶» Äºx©áAùŸÛ9óF&|Fû«ïT6)ß ×­;#Š·Ì¸1‘Ñ—Âø0žÍÊúQ.R~fvj\v7© gü¢r£˜`3¥0ŽL¸%%nhÝ¥s+í;gÉèKcœÊd½'Ó FçM3­Ã]¼Ö`jz¯éDf7îg4RÉÞ³¦¹t*…ñ®'TN“þ]ThHq}ïÆ³.ésI×O›9ç\™Äl1õJa²žÐi#éÌ~Ǻ0ŸØ©a]>`¢K?ÇwbÖ“¿n;r÷û1¾Cƒåž»§G0}Êta8¡“Zùàj¾+ âZ![çÔvúíž‘»{ÖØ•vÏ;cºùÚæ…G²¾å7õ뺫­–´˜ÕµÖéž»Ýj…í^k¹¼;ëíÊ>4ß­­‰ºg½E,?×rÀšÎ¬¤’å•™Éö´+¦W¹j'•=Öytæûc:çrÇ¢.XZ^YÀ²:[Í-¸ùýJþÎʰŠÅ{m–9¬]ëÊL(}u®¾ôþ¼›VÃîúÏwe ÜRnZn•9tç5“w•k-UÆ8LóÉXÜjä^—uñ„r@«»•ß$õ.í£Ð£›« +”3ºa^Äx-#­†Gç”™¿r׸þï«N¿tÍú¿©$÷¨ÝœyýûêAò-ëAʸ$ÿ¨zPÏL¾°N]µwÏWA½U…Eþ?«+)ß©+Éÿ]©[]©«Âðÿͺ’Ü#ÂþßÕ•ä[¬ÖþêJò-ëJ]ýgêJòÔ þ3u%ý«u¥®·N¿d]©ËÞzÖ•¾/ú~uI[Ÿk™Ä[uIF=«K·®nügªKòpWéÆÁÿî*“Ìtì»Ù̾Ê$ÿW™ä›ªL]kÝÿd•Iþ§U&å?Ve’ÿ…*“òo«2ÉŒ¹u4ÃVãv +<ÿÏÕŽä[Êüÿªv$§v¤üŸÕŽäï­uÕ€þýµ#ù_¨ýÜoíÈíY¿?¢|·â#ÿ„ŠO÷*Í/Yñ‘VÅç»k¶ŸVñ‘»U|~¨îðKThj¿߉º* 2›‡^Å"”Î6hÑ­jt³[çþ8¥WMq±2½xVåœèXåGll‹UFΚ[UZ£”•WUV×)%Õ•åJJuñl×&0÷l#]¶‘®û4²Ü5{nqu¢¡Ö¹OîûƒÿäïîÛûÑ[þ”›f.«‘ ”Úê‚¢âò‚ê{”Ê’›¡ÈrVquyY Û4WV£”WÃ\3ª *€ô È‚aÀ±êÅ1Jm¥RP1W©*®®•ÓkceÀ‚¥–¡gmi±›O……•åUÐv¨-èÀåâŠà^(cIh4+R +jj* Ë +`>¹¨²°®¼¸¢¶ –âSR6 „Ô‹Bd” •%µs€ý¡Ñ “êâªêÊ¢ºÂb¦¨ +›^W[Lq{ ˆ1Ϊ+¢˜Ì)«-­¬«dÊË\Ѫ5VغèOɉQÊ‹)Õ2SšÒ˜nsÄÐ9ã*«•šbô.T]äß45EÀVQF×ÊëØDsJA±¾3€Š¡¤®º&,f‹*•šÊ¥¦núÌâÂZz‡ÒWR9 ”TXYQTFé¨,Ë9®`zåìbF¦E N%¨¨¬1Ôhw©Tªº4@{¦Ô”Ìš%O/vq Ð+)èAgeèEµR^Y]|K²•Ú¹UÅ%0Q¬†TϧåsÁZ`xQYIU´‚Yµ zРEEŒruÔ@ ª¯ºYÕ2¨¨¸¦lFCc†f«0ˆjhA!©¡#ÜøÔÜ<)ÃŒa³n À5ÆG4@¯bÖ\¥¬›šË”œêbú¿ºf}i£†2’ÊÅmÅ sÅÕlМÊê¢%´ÓCéÜîr(5ÛPÆ2L¦Ë^¦ƒ%Q¨u ʓٕeˆßW £TUyLŸULh´dÚ»„RZP«”ÔÄâŠ<¡Z×¥ÝEJ]E‘ á.Te†œFáIµ¦rµj&6*¤eõ`+îŽU…÷ÌÂÀ+*eªªÿšRõ˜ + X<«„"5*MI?.G™0>=gRJvš’1AÉÊŸ›1"m„š2®Cc”I9£ÆOÌQ GvʸœGÉÌ›‘@sƳ¡.Pi(°±iÙÃGÁeJjFfFN^Œœž‘3`rÙJŠ’•’“1|bfJ¶’51;kü„4€1ÀŽË—ž ³¤M"ÐðñYyÙ#GåÄÀ ¸#çd§ŒH›’=&F`ãäl…u‰,†’–KO•’™©¤fäLÈÉNKKûRîŒ7~lšœ>~â¸)9ãÇ)©i@JJjfš†2<3%clŒ2"elÊHJŽ{ÚM#§‹202m\ZvJfŒ2!+mxm3²Ó†ç°žÀ{àD&CwøøqÒî˜7 Ÿ{ŠyÒ¨46ÿ g˜1òǹNÎøìœNT&eLH‹QR²3&P‰¤gt©<ǧ3 ˜ü¤Âç—ʈÞû®v@/:ÚEàˆ´”L8¢7ä}A»Òî+,®ª¥ºí2nÍ527ªùΦµšY†«ÝcMK`Y,êhÞ­+`Óp£¹^æ>@»!i®·hv1xÀêJ*«åJêLæ”Õ0K‡X^©Å<¥¦`L£¨±^à+ fÁ°šN4{”ì†UÕe0dNuY-8¥ îV—Ís…ájW˜b(]ÐYºœƒ†uqMD©²ÙųæÆBßjË&e%•Õå.Òû +k»S…Ze^TY+WVψUd™e\?;uú±?yøeò Y˃”Ÿ’É]yòó ù»yËÉ2H5î˜q‹µ+a‘N®¤¸s%ù¿#W’59üÛr%Y3ØŸ•+É¿`®$wåJÊOÌ•äyÁOÈ•äïË•”Ÿ+ÉÝr¥îæÛ#]‚xNâ—J—dWº¤ü¬tIî.[7þÒ)“\Q©üì”IþES&Ù•2)?=e’oN™”Ÿ’2É·L™”%e’sRrÇŽOÑNõ“²#¹‹òŸ“ÉîìHù9Ù‘Ü=;R~Rv$ß2;R~NvD•µ‡¡t&>ò÷&>Ê¿øÈ?œø(?"ñ‘YâÓ3wøç M­»¿“% r,œbÎoãXÝî8âXí¬ˆ½Õ‹eïW«à^Ï·…?ü ø9e÷”Å•³º/¶ª´*Îå1Òo9‰öèDSÐ-þµr 7®«äš'ù6‚|“@¾n&ÿ0“¿«äªJþ7‚|e&k&W"È—¦_ªär3ù¢™\ºFþzüJ>L>K%UòiùäÂá“fr:^˜@Î'œ¿F>Ž#©ä/*ù0üÙ“|ÐLΩä¬üi>9óù£JÞ‡îïÏ'§ONÏ'§F’“¿÷Nªä÷þä=•üN%¿UÉoTr¢™?$Wɱ òn9ª’#Km‘ò–iSÉ›*yC%¯«ä5•¼ª’WTò²J«ä%•²‘ƒË"„ƒ*i}ñ%¡U%/˜*¼øyq!àשÎ䀓ÿuyA%Ï7“ý*yN%ûTò¬Jö‘gÌdÏÓž"òôn»ðtÙm'»é]×ÈN•<¥’*Ùn'ÛTòäfáÉò„™<^DZ KK3Ùª’-…-*yÌH6?ê+l."n² +ú’MV²Q&T²¾Ù$¬WI³‰4Á ¦f²n­YX׋¬5“G®‘5«_Ö¨duãTaõKdõB¾ñá¡q*itòG‡T²je¬°J%+cɃ@æƒ)dÅrƒ°Â“,7¸ÑPD–§–E¥6ò€J–,¶ KT²ØF©d¡J¨ÄyãWóç ¿RÉüùäþ"RŸãê#È<•ÌUÉ}f2ÇHfˤN%µ×HÍ5R}Ü{T©¤R%*™BîQÉL[ª0s)SIé|2.JTR¬’"•ªdºJ +“ükdš‘LUÉ*™¢’¼É²wL–É$/_aRÉUÉD˜yb*Éq Ø*Lð!ÙžäŽÑÂ*É2ñ*7Ö*ŒSÉX+ÉTÉx2F%£3¬Âh’h2¬d”‰ŒTIz3Ik&#T2œë+ ¿FR_")cˆS%É*v»]æInjn·“ÿW-™d%A”æS +e7mÓˆ2ˆÚŒ2*ƒÂB9K]Ã¥KïãÙz‘š'pë3v‘ù^F¼<ì9¼~‡ì/³°Û&f—³ÝDf›°Y[³‰X[ž:<:VKkVÂÒ²˜[³pÌ-³iÅÌ"¦VLÆ™™xƣ،3F1ÃAf†o 2ú™5ýÌr/Ü ·!=ÍÙ‹¹ñts:¡ãi;ZÚ`K¸Î¹:ÒTÒ.= mª!¤º”6© ‰p!Äjˆ…šf­‰Þ =ç‚«¦Æ UuWS¬pQÊj+ § 'ž’Š%ý€::E(*/N " +BðøÏ`òPøë~Eû+ !’endstream +endobj +98 0 obj +<< +/Ascent 759.7656 /CapHeight 759.7656 /Descent -240.2344 /Flags 4 /FontBBox [ -1020.508 -462.8906 1793.457 1232.422 ] /FontFile2 97 0 R + /FontName /AAAAAA+DejaVuSans /ItalicAngle 0 /MissingWidth 600.0977 /StemV 87 /Type /FontDescriptor +>> +endobj +99 0 obj +<< +/BaseFont /AAAAAA+DejaVuSans /FirstChar 0 /FontDescriptor 98 0 R /LastChar 127 /Name /F2+0 /Subtype /TrueType + /ToUnicode 96 0 R /Type /Font /Widths [ 600.0977 1000 500 589.8438 1000 518.0664 518.0664 317.8711 500 317.8711 + 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 317.8711 400.8789 459.9609 837.8906 636.2305 950.1953 779.7852 274.9023 + 390.1367 390.1367 500 837.8906 317.8711 360.8398 317.8711 336.9141 636.2305 636.2305 + 636.2305 636.2305 636.2305 636.2305 636.2305 636.2305 636.2305 636.2305 336.9141 336.9141 + 837.8906 837.8906 837.8906 530.7617 1000 684.082 686.0352 698.2422 770.0195 631.8359 + 575.1953 774.9023 751.9531 294.9219 294.9219 655.7617 557.1289 862.793 748.0469 787.1094 + 603.0273 787.1094 694.8242 634.7656 610.8398 731.9336 684.082 988.7695 685.0586 610.8398 + 685.0586 390.1367 336.9141 390.1367 837.8906 500 500 612.793 634.7656 549.8047 + 634.7656 615.2344 352.0508 634.7656 633.7891 277.832 277.832 579.1016 277.832 974.1211 + 633.7891 611.8164 634.7656 634.7656 411.1328 520.9961 392.0898 633.7891 591.7969 817.8711 + 591.7969 591.7969 524.9023 636.2305 336.9141 636.2305 837.8906 600.0977 ] +>> +endobj +100 0 obj +<< +/Filter [ /FlateDecode ] /Length 677 +>> +stream +xœ}ÕËjÛP…ṟBÖRœã}KÁri ƒ^hJçŽ}’ºÄ²íAÞ¾{y…ôPJöþ…tÐ7ÛÓ«ÛëÛ~sè¦_ÇÝꮺ‡M¿ë~wWµ»¯›~RfÝz³:¼ÜþWÛå0™æá»çý¡noû‡Ýd>ï¦ßòáþ0>wo.N×»ëúkùãx·ì÷ï/wOë·“é—q]ÇMÿøŸWîŽÃðT·µ?tg“Å¢[ׇüاåðy¹­Ýôßçþ¼õýy¨Ýìt_ˆ^íÖu?,Wu\öu2?;[tó¸YLj¿þëY™óÌýÃêçr|y÷,¯EvizÖ´4­M[ÓÞt4}Þô‡¦/š¾lúªéë¦?6}ó§Kã/¿4þÒøKã/¿4þÒøKã/¿4þÒøKã/¿4þÒøgôÏNMŽlúsdÓŸ#›þÙôçȦ?G6ý9²éÏ‘MŽlúsdÓŸ#›þÙôçȦ?G6ý9&s¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¡_àú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~¥_áWú~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~£ßà7ú ~§ßáwú~§ßáwú~§ßáwú~§ßáwú~§ßáwú~§ßáwú~§ßáwú~§ßáwúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àúþ ?àú_6ÄË&À®ÀÚ{]D«ã8æŽ:íÆÓâÁÊÙôõu}»§ðû "‘£©endstream +endobj +101 0 obj +<< +/Filter [ /FlateDecode ] /Length 18175 /Length1 35128 +>> +stream +xœí½ \U8~Μ™¹3sßÀ½pyàQDÑð‘E$Äg>@¸ +\â¡™š¶½feššå#33×HÝ2S³¢¶‡nµi­™™›nm‘µýìE0þ¾ç̽<ÌÚ¶ÚÇÿÿù1ÎÌ™™s¾çûþ~ÏwD!dFËAã'¤¤M›¼æ^¸óì“J«KjåÉJ(BxìÎÒù êåO÷“âò`/*¯S}%7ï „øÐÇœª…å«~¿ín„x>4©Â[RÖknÞP„2¯†ç+à†åqÛpÝ×=*ª®þî@j\øËª|¥%—Ý7¡‘Eðüùê’«kŇže…ÁµZSRí­‹»ÿc¸¾ ¡aéµ¾ú† 'Ð4„®«¥Ïkë¼µ‹K6×w!dxa~· €Oa-Ì­ŸÉ;¨œ  +Œ!"ÏqüG¨æÂŸQû¥Gq"TSAyvR‘zá‚¢…àu†jüa1Â.\@úF˜MHÄ +k-C¿ö#¤À¾"2 ÉHAF˜ÁŒ,ÈŠlÈŽ‚P0 +AäD¡( ¹P8Š@‘( +E£À6Å¡xÔ¹QO”€z¡DÔõAI¨/JF)(õCi¨?€ÒÑ@4]†2Ð`4 EÃÐåh8ò L4DY(å Qh4ÊEcÐX”‡Æ¡|4 +P!š€ŠÐD4 MFSÐTàü•h:šf¢YŒ€rÜ—£ýèïІ6£V4q¨îÒó\„Zàùlèy#.‚s5¿qð|)@p¸?𮂖›ß‚÷£}è,Œ¾/F ÓhoÆ) +ëkáü…Áe )|5?ŒßÅ_Çï‚|9j‚c÷&¿ž_Ä¿Î/BS(f8î´Áñh·gaÎ⎠çþÃñ:Bs1Ç(ÒW8Ûhš œ99á˜bˆå˹oQ ¾žÛÊ}‹ã1[ŽnÎ$Gøbþü­ð¸ƒ9ÒŸÄpœN{Çð:Àâ´XŽB?º-‚yZ¸¸½@ãAtè‚Ù¹éÜ"n:‰wâ}€1B7â|±a6Ö‰ëø)èå z“;ü(`ü¸Ý.öC_ó"ú‚äáb~år Ï)ÄƈAh5c¸(AdZzŠÐ+ Ïéô’Ä(´šO î·$À7¼á2Èl´žm+ñ^´íEõ@žOD'FIª­‰sç–5y®˜¢¾<5¶oÒE—ªÍ 6¡‚&óBuï… Søaj“ÙDÜRïŽ?ýcO÷M[0EÝ‹{egùÁfgÁÍ S I¯à6ÜÏÎbÏè¬M‚þå7©¥êm¶ÛâßfóîK5][Í— [ÀŽ (f?âqO`ƒˆ{>‰%áVŽG)ÍG[ú!ÛÑ–£-©ÁöX»;Ö[Σ¶zÑvV[m°|ûe˜ÈtÇÀáfÐ{í3€*LwÎí@U¢püO ëéçð=‘¡†`«ñžh‡LÐ +»9&:*2"<,Ôd·Y-f“"Kº$ÃÅÕvôp(Å}è™´£Ca6ÿ1“Øàþ$ÖËöø`¶§Ç²ÃM²¢K%'K°C{2GjMÅ'‹µo‹OkÇ£s´³¸°hßæ»jKñuÚÒcÚ¹w´ëðRº¿ƒƒŽáëÚ·kç@IÑFí-ACÀgößX煮îˆ0{hHcT>L”“â9aµ¸*)tuتˆ•A!I+!fôÂQ)H°ˆ½zØe‚ }"‚ JCÕ4ÛÑæ¶£¡CÛB‡mk¶eÀ? °¹nŸo±}uΞ[Ph†þ((##Ç“dœ`Æ¡ö„dœ>`àpÜßWz74;BD±[àì ¶_z’ˆÍxÉ'¾úOÆ~þÊWâÍe/{៪9ÿÍæQó¿˜<¹@{ ÷’±8d8‰½÷<ºûù£¥¸­wŠ {=óÔÞ-døìô!#µ'´OðˆY#AÓF\øÐðø##D˜ˆ"ýQ©'=ÂîrÃÂ"ÜèSòqk{=.>`Z´&2|ýW¿>2‘²,YûI1rÉê`{¯¹È>÷5¥›rÂv&íüÐ3ìŽí\*X—Á&|F÷©q8Nû§  +Òœ@v|\O`¸¾þ±]®C»ôûÊ7ztÞ¸QûÞ~{ß¾wÞiwŸ$¿ÿýò}ÇŽí£ûèüüÑ£Çås‹êê-ª¯[´í/œ8q`ÿñ¶·DóñÞ}÷ÀãÛ×Õ/YR_·X×ëE>·ý=Q*è™jÂf£U–«$KI}“Sø>©VŒQ/œ˜`…xíNM0ÏûÊ!ˆ¾;ªï&K솨Ã×[ÖðrBoÔGÎS{D…ôîaŽ +å± ‡ö6ÛMýlm-ÍG¶Ø^:Ç”AW{Ó˜—Î|ý7`‘ Çt%4Cç–å3hvœ€wmMÛ%%`4Ãsƒˆ¬ÄÊ˲U±åcªAÄà]r”eŒLu£%Åèî9\nôÆ*ccSƦNÃÓ¸‰†iÊ4ãÄ^Ó“§§NI›•V†*¹9†bC¥Ri,ë¹,-I6* .cxBoc†1=25*5:=ÆÃ1æ&äöšŒ'sÓW&Ì!UFÇ ¼Ÿû;zô÷ÚÎcÄ×u%€A±>®¡ýî”vRç)¬SØ\ÙŸBÛ8:®4)z÷7iˆ… –еÀ…\ Ó±à§8Ð0a’ÒÌÄyþp*$ŸñœåýöœŽ}WMýÌì  +)~?ï –wXÉÇzëš0ä4…ŠÎ 0˜‰)èêDS±­'˜ÝF ÛP¬ŠììHvV.^w÷÷/p±¹9˧½}Íï´k° '^û*Ž€”"ŸqmVå²qyxtŸ¾-o]óÖŒÆ[/|ÈŸ{¡rÛ$‡»ˆ;8ÜÓÃMíöŒí\3CŠõÜÛT¨]2¬Ç@ šÆPÕ9ÍÉõÂO¹×‹çÏÚV°ãmĺ+Ïb—ö’ö­vR{7âì9Ïs§¯÷ÿpG´–¾}žÝ߯ŸvþøÚ)|+®Äuø•ê&Øß9ੈ²=¡‚‹#ï"„s ÀNi8ÁÈ`{OÏÁÁûuËÆvÆ3ðxK€?AÌ—Å£4ÊÓ+Ì”°‰;¢QS_°òè5©aÙDz„[{‡;zÊá $ÜÚ3¶G*ø8Ê¢ @Šx¾å Í|2ºsÌMÓ‡±º­êæ¡ö€„"8ÐØÉ½RÕÐP5¯®N[|Ëm8ÄlÅá·ß²æ~p‰ïûÞ¹ÿËÒéÓfÏž6½”[?¿¦¦±±Æ×¸4qûÒ/½xpéöÄÞîzÿÃß¿ëž8µ¸xêÔYÅTös6 È>L—½ÁµCuî*~èâÛzçw¤ÛŠâÂÍLöÍmmgtBš=H×ÃA ÅA2w\OÀ=2}[áŽCö¬uÓÎjá lÀ=°G»MÛWy/õ–ƒR”—Çâ> ï´4l|÷8N›¯­ÑîÔ¦Æp箿þw7Üð»ë¯gö|ù|1[ãEz,Ü6´›ß& +–ŸÁ°Ä‹¥ÕóèvWj÷Ñ/Ö–h[! 18×A¬¥pèúÓí – ÖÒ€»[!<[‚+ ‹ °…(¥G^?Èo!úR¸þÌ[t¸4/&œàÿºã± 4MÈÄ=ñš±¨ÙIˆÌ9p:N´ %Í­šÀ‘#ÚíÉVüz ~]Çñ8NN~ZÍèîIñ^0$–Þ°Å6K +l;.pR«FN|úÞg?ÄG ?ø?à=f @:ð NÇ!ƒ¼HŽ´jýZ´~­àÅ(.ËÁæbÀ¢P,íIˆ +8^tYÑѶ»­«L›‚—G߮ЂŒÃ]HæœÖÄEã8¶Ž?J£?€h¨Ûø ‚ÍÃbÙ²°[ÌJ£v`Å@ ÀjîEí¾s=éÅ“®Ø_¾ðåç1ÊŸéÉê­ËÛ¯péìÁ¦'çŒ9îÝëÏξjÆåcû>G«ºY[oð Á~Ç¡*Ï`“• HÍ×ßæ’#È^kÿTy¯Ë‘º×úÔ {O\‘ß@^¸fêŽzE'†÷Ê êÕ;qÀ°|š.@\M°iC_ +b‘Çn5Ÿ³ÏN³û—;ô˜Š»˜/¦ÖÑÓ¿HHﯯvzö 4³ì:ÔÉS# +ùø¸ zÞ=°ð‚®“ÁœâÉðÊiSçΛ6¥¯‹¾qêŽwþòØÔ£ß\|çà!³´·6vïÃó¼e˜¬¼¶mê¼%Úñ5Ok{—-»é–k¯Åã÷|€k§íÓÞIà"ݹ⚅˗kÓFÿîå—[ F]ß>6ø•ÊϽæ†aCfk¯üa•ö}Ùì93 6—̹~Éœ{Rã%‹oylÓì³×jÿÐŽR¾*‰ àƒ ÐÒ<% +†(Q”(I@h›‹â †]Š"ó¯ ºïr‡¦GË”e’R®,EKñRi©Ü Ü¦lPž…íuØN)¶ )RŽ1¹¥Þ²jÊås„QÒhy +™ÊO&‹¤’/æˆÅ¦´_Ã7 +‹¤ùVþ&á&éVy-¿ZX%­“Ÿ”ž–_C/â¹× ÏKGäãèmü6wÜpLzWNa‰‰Ål#|NûÖYÚ.¿Æ%jKڷᵇ±MûB8ÖÚ‡ss…Ô® Ú9É­À; V4**ð¸ƒÐÓÖƒâÓáËå§ÃDXLE›¬rXo Îh“Óa38]$üo-­ÙEÝœ`ËHõÈ(ΗWÇãØŸVÙƒ =ëJ„eˆž[]9sæ•ÇþÚÐØÐøWnôâ[´÷´·Û¯ãFàA8´œ¬,Èw…ÖÜ^?»´¤D[ȹz<¿ü/G…cû_¯^Ë|J9Äéà \hˆ'Üd“‘“˜! ÜIl;åõdMxp_û„Û`5HòµfÛ‹:Ö©{R"fEpx†›­Ÿõä@_9SK f LŸû·%ÚíZÞƒ—ümî¼7êÿÔÒò§ú7æº oÂ^\Ž7]6H{-7Kûöã´o³r™¾ŠŒ¯¡(Ùã@‡äåøS✠+úÚ’‘S&úʼO€{©» +\`»ßX1°(T_™ŠÀ1nØm-ß}ûYûWx5.ÂãT–—W^­5Á6—ßÕvÕ'§ÞÿÇ—4xµoyTûÆÛPBùxð§#Jõ„ð‡ÄƒÜ!´\:¤`A‰"bb¢lnÈ/õI›¹À\kD-ºíç­íuÜœöµÜ–ïO@wRûöít§ÏñÌ!£>{Çf(ú,ýaàÆZc7ðÀµ¥Ðí7ûeÛÊüüXÛî ˆ7üéÈGFG¸ú™ú£”à¾bŸh˜ R|'›Ùr6¿¨©´wÍŠöºÁ±ùeÍw—5M¤…Ö»´Wfß· +Û.ùfñß¾ûâäÐ9‰ŸrS|99Lè•øA*ôQ9Ú…Ï?Ó4« ÇÑ ŒÂ׌ߗ{‚¹CÒA-WDà„£v³Ä`èù¡ÀƒÖ’¬‚U´¬R¹x¿Á¬\Ä}|w­±¶¿LYÔþG.lx!0 ùç䆱5qÒSè9ŽÎëç±MÖ¢±M¶¢+a™‡ç²©ú‚?iÁ”{tåÏXø ¸©IøïÚ½Ú¶óÚ6m¥p¬í4‰iíÃgµ îï÷wµ-öDÒ7p!qS⟓¸~¼¡ŸîGXaB§:£ËZ˜g´hmv€""£K¼ ÂcÄIxªX`œƒ+Äbã>ü¤hqqá†Ë¹þ†\ Þc˜ÈM3”s•#=fït(Ú;¹\­¶Ÿf#ƨÍÿ>|’Xî¯'¥y‚ÅAh§i}К0¹¯uéëèsQ=ÉcD®Ð`›+Å5Ü%€o÷;Zu$ÚIyÓóÏ7=ñüóOà +¼F«€ÇZm^Ë×ÚZ>ÕÚ0ÿi æq¨V¦­ÒVkex=ž‹çáõXÃb¸‚‚Q?Ó´Ó`܉–ËÁf–l}y£†¢;ž@´aE¦]È)é4ó`«±‘g?·Gá~° øP£owŽÕ^sM-˜À'Ÿ¶··òµYÕeeU:?´cŒVf{ÂÍÎȶӰ­±DZlvõU÷åûD‚ËUuï Æœ$™P”Ëd‹J@)Q“LÌå¦bóÓð‚fƒiªqªyjÈ”°¹ÆJóÕ& ä$Äw0-„¾?~~®ˆÖVhåx-ž¬YõΫ¸¯v·öaÓó÷c#ð\EY̽»M»wºÖ$òÀÜ/.è¼õÇlÄbv$ºÙ“ÎEº¢œ¡Î°¨ÐP§ÛåT‚ÑNYÜiZª8ƒÃˆ-Ò%"Þ ¾=Ô&œF¥;+ 24Ãož”Ïì T—…¿þ.!,’ÚfLDX„+<<""¿K ƒ¶`‡Ú,ƒ…C–52Z$9• ÿ‚:¼IÚyêòSŸô×oÖó'}Ygõ[Ä™…·Þ¶¢íó°¾zF{Ü×uOoØð4YÚvö‚ö*ˆ‡é4±õ’ˆ®÷$Z3æ¢0'ÐâD$â(èà¦X «t,°åùžÒþe8àKÇìIBœS¹&Ž÷CBq ãC…I•F¡Qx4Íg cÄÉx +Y!ÙAÇ*øz|¾_ßþŽ–¡rŸßÚG³Ã’VÒx‰«=cÄ(ˆ— ñRhT{y ànÈN(+„p\áLÐè0¹H*DF%“ÖyúWF2SùÉET†WŽm2Òƒ©3±9¯i—Ž®—¢ü0Q@{ˆ`lŠ[Qa ¡v°0\è¯¤ÂæaÛLn¦P¢ìåžPš` 8¼, FÑääÂH(ïRˆn 7%p½HßK€%£)Å2òƒ4>MHS ©Rš<ènÉ%9üh![c¤ë¿iÜ42‘Ÿ(Š…†Biš<Ñ8ÃäC>ìãjH_aðJò<¥ÆXiò™ÒÕò|ãÕ¦[ 7J·šžâö‘'ù½ÂÒAÓØ€d˜pà0 _ÁázØ9\£mÔ²`ý}^ËY½À£;ÄIß÷wë2KâX.ù…'›¢Šr ‹r#2ÊQ’ŒèI‘!ØKQƒzHè—YQ¹aŽxÅðYË`ÄÁ07+•”½ÓæšßkîÈ…ü²ê²4ï&²NQM‘ˆ•ˆ2’cˆMN!=eUJú˳H¼‚,“Ÿ%M²]æDÈBe%‚ áC¤¥‚u3ßCtK}@®é|º4X)0{Ñ\Ñg~šûÿiŸlÓ™¦ÿ#mœ¨ù´‡´­Z5´,øÈLrð½\k»ˆ5ãZ¹Z >Myv¡|Í)Vyzu·KÂIH¢ú,1»9?Xp¢Á9 ÅfÊ+c‹Á~ž¥ºA½R9WÀƒÕ*°@…(Nܸ7IǃIª2^ñ)T¹ˆ?9¶‡à‡!µ+Â[ÛÿÈ2ã.¤mcûí\£.{úžã*–ÓAF@¨½J {Î-÷$‡Èi!ÜŠ +¸”Ñ*‚K v¬»¯à‰ˆQrpÉM¥$CnÌ!£¥ñÆId–Ñg´ês{l:Ž­ç?j;E.ký”ĶAJù÷¶AÚÉ¿“Wtœè»æ(1„~-ä±ñÍâ®=kTbG6ú9Îá4öNÓ_À¤ïH›¸jí#ìj_)†œnÝxú¢wÓww¾{pÌYÚ¿<ÉYŠ+¿«Öç½½‡Ñ¿‚9·ÀÜIÈ3C9ÌŠlZL_ÈÂÎ}Û¾»´`ö/O ÅtÞ×É6á,ó×a…ð-7,€K>¬O –q<üÃägû‡V®Íù’ƒPÓþd@oq})H‹â‘Fë·–=”ý\ +]%=œŠ±é4½ÕN<ýfò縕b9ôË +¿„^ä1¬%Ù _ ÒU¼ˆ¿púó°y.³ºl*¦/qO8N¸=¼‡7k+Ärí|µžOO¹ð!ÿ"¿b’=àé-‹ÈcD}]Ü`yîŽy-rCü«ö5&JÂ̲Ù8,†˜C†ô¤¯ ЦÙõ„Vô·s_ŸËðç!y) ÃÔa±ÃÆ©ãbg¨3bk á¸V½6¶6áõŽØÔb¯þ>ö€z Ö‘32Ú3!º ¦4º8æÆèe1+£ïŠÙ½1fWtSŒæ÷$ðå/£öˆíßíu!·¹öª+¯ðÞF+£÷\·ó8¶â¸·nº³þ¥‰õ7àlÆßæÉwwuâÍí×m-ŸñÚæ÷FNŸœŒí‘QŸ3žì€¼`&èŒòÐË<â&ÓV´É±ÆújÄCao¸Èˆ O˜I6‡ÛX ‰[l²wtçR÷Œ.ަ%­.ï<{Y œ@µ› ú… ëë-Z4fOãëXѾ~½qÏm.ÿhÛÆÛݰáQîØìÚSZ;lO͘½Y Ñõ™Ê+äåBƒ<áè0>Ê[›*›ìü¦PS¸a„… é^o;Îö 6+b)­µuæq¬æÒ¥òƧ箿ú‘GVÝç)úýdíMm;¬S¦<ÆÓÞKK}üO마‰Áƒ°¶A1ºM§ïŽA›Î¯ÐMè ‹}“ð†´Æò*~ˆ„ðÈÌyÂG‡P~Q¡®æÌù3-¶3übë?&UŠMÿ4'éÊÀ­ô„1»_×¾ÆÊë »7SþÕ-\HösS¾kÙ\:çb[W(éÐoAáéWšh¤'9þ‚åÃÒQaƒ ¿¶!èUÓšÈ'9Ì(‹3[‡D2 ›»¼¤£Ü;G+•Qã(šŽXý%W'ãT$è((mϘ×ßqÕgK–B®ù†ö8‹ã°„‡iw-(®øë_~íµ#³´–Ô~8‡â ‰mŽ _0oÛ†öw¹Ñûæ=ú@ûq¾xˬâZ>f3@ßÅ6³ûÒ6sæŸÛŒãŸØÌýwlðÐMÆïÇZ÷ëë 7¡M¦WiÝo„5Œp ¹¨î÷ä àá®QtÞõûŠ®%?î¹K–,h\¼¸Ô$–ë§´÷µ§ñ(²è±M›£;FÚËZ l/ãËpl—é¸ìÐ& 3ï†x¢:ãÝ«–5ø}²; +b‡E½.‚íÌ™‹Cž»ƒþÜ ¸ ª\åK—ô` ý¸ÂÞæö=¢²¥Kr@Ñ|E<ê¯'1mÔqóç.a 2×îˆ÷­k¢Xæâ¦K4àÖ%}éʵà.Q™úêXn„Þ:ÀeaGHÖ}ó² }ÛûtåQà–¿ûºCŸH ðÎù‹Ónå'"‡Œ÷Z^•wÑŒ$[5Ž`jû8ŽyCúåX³ÿeúŪJZRf¤Ü¶Šò+{÷’ Þ½HŠÓñÄÃím|ñÞ/è¼s!oš ó& ÷<³‰³FÇD ¢A’^íÖëo,§ +9ì8¶ÁÎop¿ÚYƒ›Qh 1ÄëÅ>jn9Ó½ +÷Ͳ‚.õ 3ýrÙH«3eYVd£Ñd4ËV!>Ün·„Y“¤d9YI6&›’͉j†4D¢ 16 6•Ç(cŒcL£YMvŸ´OÞ§ì3î3í3»-¢Å`‘,²E1™‡'ÎJ”iý¡K‘Žw^üy^¤£_ð¡õoÏ*/[2Ô¾ÕZ}Ÿ-™wº¡rnnõðÏo+}r¾/RSû§÷I6ÊñÛ½'>Û œ‘šb–¢7?¼kG4åkÈs‹ð ä,Õžp‹ YÉ&;Þ-mBŠd”9ŒÀd) +éòêŠÆ6±ÜÅÂr—æÎÜ¥¹…þN{—å ކÓ_"V›Õ^ÀG1WLtBi0 q¬hJcçúã«´—Oß«9úÄ®]ƒÚóæÎt=qŸÀ_Îtp#ø‘/fï¬# ¡Á¦!o„®±áÝA"‡‚­fû(ðm¶pÝ>t·|&­Ó³E,£yt0è_­~‡êìíÈ_{nÄœÅ+þPýükx·£öJí³ä›DÄ÷ܱ–Kü~ãfæÝ0‚4Zp>"zëâz éV<Õ­¸áçÕÓß"³±J­2LâʹeÜ]ÜF®É¿‚í Ø>`Û°¹Q(` Á8’„ó=Q܇$òÑœA2øT)å`ˆÞ|±tµx3¾…Ü,Ü"®F«ñZ²–_%¬·‘'ñÓ¤Gg}1Âp(vâ9Ú(m_ÜÖJÄï7ê>a4ÄÐ( ß„wQ}q€^_@ë‹h}qÀÖO]²¾H V¿EiñoEUÒ¹t!UÅŠˆ³”*e©Â(À¦…7•7`¡ð¹êäB`Ý$ØD€ ’C§±'îÁõÅÞR¹‡ÑmJ±¤£t<€" I—™†[Fqz9q”4Ú4•Lâ'IWÊEÆ©¦Y–r®˜Ÿ-‹Å†b©L)66rµ|½P+Öj¥¥ÖBon•o2ÞnZa¹Wºßt—e+÷(ÙÊ?*<,=*o5n7HÄ'¥§M/áfò*ÿŠxŒ;NÞáßÎJ—ÿfüÄt%W¦ÿp¬ÇŽÆÓ÷À}`Ÿ®×د-ѵ¾çÚ¾ßH¸6ͯ¿ +Èψzr~¬¾ØµÙYk y|J1° ØØ½ØD!kæ£?¨8þ ¾ nD¶‘Y%)r2TIòäB2]žMªäzr|YF–Ë+äûÐ]ä.rŸ¸^Þ o$ËM„nÏÊt;D‘×å×å7Èä”|Jþ€|@>—?—¿A_oÄ r(&o’ˆÌEò‘R°c¡ó}¤ +š" PRÍYÜX~¬”¥øÌ7¡eÜ +þVq…´L¹­âÖñkÅuÒj屉{–V¢zõ:ÿºtHy½Áâÿ"ž’ÞP>BpŸó‹ŸK(ß¡¯ÅÁzÑ3‚e¼ 5üG­ +/:û1^çmÚmßi7røxmÎk?Õþž­=HíÎk» ›ôŒ´F!+X™ÉL,ôëõ‹Ýz¹YÝ…³ HÙ ½KÐn›Õb”%ê“ÀÓÛBë"/ý#…ÐŒÀöË??&1˜Õ–N—EË¢^®» œÖ½Ìmíéâ¶ŽúÝVe" aArb‡à†B±‡ÚØed 8Ðaɰf£l<†Œá³…lqçoânno1ßb¹[ Kµæµ–mÜv²Ôò¨õx?ÙË7ÉMʳƧÍO[^æ^3¿fyÅú6w–Ëèpm¬¯–†cø¸¾fʵÄQî½öÊ ¡`í÷í÷ŸYõéà±1ú,Ÿ|—Ñ­žpVšeEZ7DÍÝd­Ïò¸[}öpÚÖg³¦r“ •\¹a!7ßpƒa¥A5—l¸G`#ØŒ=I¢”b¤…Û1Ò,ãÒƒÒä€j, +K’ÉÅH ”qÉ›í»¸ü6'—ßþ_ÜÚ¾îjåÊت¶‹ßë¤h¦§ŸËê0Šn9Üæˆ2 +j,Ar³ñ4jÆ8šƒrG¹L²"ôps!HuÙ«à¶zâ|Í™hæ¤ÿ +ý*‰¾½Ìп eEÁQtü>´ñ0Øû×(úA%¿ëø»/WÝ9lØU/¿{<{ùÄi5¾©—WŸnzîÐêÆS «<÷øéÉwn}àNWÄë·®˜Lyy- +$FÑßuy’UI9Þ¦ÿ¦êaý÷£>úPŒúI¨½Åž´w„Ód•"Œ—•T‚"š]@oüÖfûCîHW¸ä°âø …Ç8 U6òŒà´¶¡‡õ4‘ØbýóPFuªà'sP2î ÛIÉ줻 ”sÇÄi>ß´‰wät¥½zòŠ­ëïŒpÝùÀÖ;'Ÿ~ü¹#«N5®>ô\ýæïNBXhÆ{@+"L(˜$ÂôHp4²Fë¿t´å½Û!Æxì  LBOºÑee:M°Ct…swÏ[a‘ ÖûgNÙ1{Þ.¸²èÕ¤GIáù‚ìÁ"!â°q:šcYíq3÷î¼íñý³¬C¿B1û”ãƒÖ73çoêÚvZÈs ¯„¸Ž¿€ Õø[ì7uߥYøÿAçO1•s¯@ºÇð}Ð ât»p7Ú(ñh„ØŠqõè² 5Á¾…OA‰ðü8QP5ðÃç79úÆçn4ö`ß û­°O‡ýAØ—ú¯—À>—ôGga¿ŽÂìüú;Åèf±RDÚ/œBåâ8/Ðwñn¸Þ…ös­t¿°\ì÷¡Ÿákx÷EÀ_8©ŸÅDxöZ)4¬¥p`Jo¡ab” ¼v¡E8‚¦SZ(Îp¾æ&À>S(GS„}h§ Õh +µkïB;¸ƒt¿°_(ÔÛÒ$´Þ–êãh?ò%Œè| EÀ³Â c(C£…Dm¿Â‚ Ý‚yz¦ôÃô=a+@ èm\Œ?à^åþJld(¹ æŸÈßHÂOâo࿦ ë„OÄ,ññcÃC¹áÃ^ûMš&‹ò…W +”{•—”/yÆ›ŒÛgL#L3MgÌCÍ7™›-½-­¬³­Z¿¶½aŸkÿSPXÐà ¢ ùAk‚v ® Þ"†…l9ï˜æxÐñ²“w–;opž½,ôÚÐÃa‘aS\¼k¦kkxÏ1⯑F¶FMª‹:íÔõ “|Ô]L }6䡿/|ÆÅ™²8Ö Ý[ƒÓüm ù·þ6û;› ˆ[þ6En‚¿- w­¿-"+÷˜¿-!;wÊß6Ò¿”ào›ƒèu³¿mA†¬ð·Á· yÏß¶#~È—ˆ¾Ïƒt §²Ùi#'~Íßæ„?÷· R±æoóHåúùÛ +ãÊümEs·ûÛŠãžñ·h0÷ÛìLÆúÛT1ÄíoÛsH³¿mGÒÑHäCµh!ªC•hªMQQ/TŠᜆRaë­ÙÐCE# Oª‡½yQ ªFIp7Õ@ÿdhe¢*ØTTØ«ž]yáì…1óáX=•Ÿ1ëÀŽY‹`¦ù0×\S½)%0æ_›1 ZsaÜ$Ô=J¡o ƒæe#JE*@©c-ô™ p+¡Ÿ +ã}0{ {¦ 4ÒW»°®rNEƒÚ«4QMƒ%¸:{¡:¢²¡¾¡Î[R¤æÖ”&«™UUj!íU¯zë½uó½eÉʆ¤C‹JæWÏõÕÌQG”TüÈÀ,ïÜ’IjiEIÍo½ZRçU+kÔÚÆÙU•¥j™¯º¤²0ëNâF`=ÜÖO(©‹@L„FøªÊ~lˆÚÙ­Ë`õ™ÄdQô1þ¦Dú£AðÀ[W_é«QÓ’ûê9·ïÅp)ؾ—¤œ× Á¯ž\Ê}5ÀÏbJÒ"ŒR`+óØ0’a¬Îu v/ƒWÇ$àza ªhh¨œ’R@ç7&×ûëJ½å¾º9Þä/<Îé‚A@¡JýCӡϨ’z™¢{FZ}©Zÿ6ÊJ!‚' ¡OY Ïj] Ì0(×êØjJêü‹8y1ÆØØÍŒ¶KÑ®«D ´ºrí‡nA øå›ò³\Íoïà.-ïNš+á‰ÂZ ìÕÂjÆëypÏøg¸PÊ +¼j­Ó¸*Nì™×O×6K_êI~¹ëÒÒgÓuL×÷$†—I¿†¯õ°>ƒ 6øu¬Ò¯% †Îiųaq±>•²~Tuè´·Ž»®Ë^fÿºîÅuÑ’8&9:¶Œë^¥0¦ÄOŸÂ¬ 4´šAi`Oü)‡V•ß’zuàØ9õiÿÐ_]ûéŒ<¡wj™Õ”Á ¥lt›2FAÓµÙð´=ÕçP~b†$¿5—f ŠÎ“L*˜Wjðs¦šÝëJQ€†ºnZ©cÛÈx˜ÔE:´]Íä©ËZéâAêatÒБÔAg +ó *ƒ¬Ûƒ»ÒÏÕîÒÿiªœÓ±­íÐè†W§ÖuR´€ñ£úgͰ†ræÕküz»ÌXÆŽtŽ$v¦œ˜ =J<½O@~å,éž- ¡R6wøÒé`fE~ìJ¢y†NtõEø¡'¨þ ~k¨ïÖ7`+ë꺎SÍ% s…ùæîº¦sC%%?!O‹‚ª_öÕìÜé?~Ž,X$¢‘µÄOQr7NýÔXÊ“…þØ¢ÏNy^Îp,ókRÓÓºŽ;:¦”§e]dÞUë´„EÄJæ3ªØ•ÒAQÔʫ¦ 7æt‹«úLZ´G×ÝÀó§þŸÒÀRñSЩa%LF?ƒîó\ÌKá–ä—wWù#Þ\éNó³%̯t ܩïÐÈ€½\=¼~?çeTfZÀ¨*cãã.ã:è¾x„ÏÑ6®‹–é6“wQ|™ÍìÝ××F¿ôd><­¼Ǽ°$mðÇÚ»6=z•0êíÑUî:Î;Ê%-¥‚yx•ëý8z™&ý˜ž|Ý¥|w‹5Lî]ùu)®*]8×U†¿ÔVëýù»ê§$`mK¢™CUGîQçÑb-Óèypœã—˜©V)^õßé©~œªÙ~iðÇÃòNFÙlžñ(®è<ãáªM†<²=Ë…{*äq…ðd\Ñ?y™Åä’ÉžÐçqÌ'C›B&2X:ŒB8RØSá…­²kz5úç,:6Masd´ €ÙxhSØãànœ³ýý舑pg"\Óö(D³P}>ú‡7‹˜íÐqÓ"¸ß9kw¬rÙŒÌÆÁU!ÀíJÿÈg.ƒGñObùmçûñÔ9WÈ SQÈæHÀ(]Ñ»á\ý&0~f2šuló 9ð\§%›a KBÇh$ûc¢SYúgF‹èLEþžILŽ”ž,6žÎ:–õÒ1ï—2mwBIöóRǃòRÇÌýy°©Œþ"ö‡L©l2~n@wF1o…qc"£/“ña<›aëG¹Hù™×¡q…]¤2’ñ‹ÊbžÅfÊd™pIJкJçRÚ¡tÌ0ŠÑ—Í8•ÇzO>fCÿÜŽ;º>æ2ZGúy­ÃÔõ^׉¼.ÜÉh¤’½fÍöëT&ã]w*¨œ&3ü;©Ð%é?Žì³Néçû¥À§ˆÍ\t ®Lf¶˜Íze2YOè°‘f¿ãü˜OìаN0ѯŸã;0ëÎ߀úýߡà +ÌÝ]‚YLŸòüNèà†ÞCù ¸ºïʆ¸VÊÖ9 ~»{äîš5vf£]óΤ.¾¶k& {áQ¬oõEý:ïê«%=fu®uºæn—ZaVÇz.Èz;³Ýwëk¢®YoËÏõ°¾#+ñ±<Ðב™,`O;cz­¿vâë¶Î£3—°ØŸÔ1W uÂÒóÊ–-ÐÙê/ÁÍPÊV†µ,Þë³,`ífBékô÷¥÷¯¹h5¨ÿüPê%e åR™CWþ×1y×ú×R•ŒÃ4ŸLöíCuY'O(ôº[õERïÔ> +m0º¸ª@y0§ æeŒ× +ÒkxtN…ù«@ë¿_uú­ ÜÿKõ ¥[=èâÌëßWR.YRÿÃõ ågÕƒºgò¥]pê¬uzþ¼ +ê¥*,Ê­®¤þ ®¤ü¿ºR—ºRg…áÿ›u%¥[„ýïÕ•”K¬ÖþêJÊ%ëJýgêJÊOÔ þ3u%ý«u¥Î·N¿e]©Ó޺ו~,úþxuI_Ÿë™ÄÿZuIAÝ«K—®nügªKÊOpWíÂÁÿí*“Âtì‡Ù̾ʤüW™”‹ªLkÝÿd•Iù§U&õ?VeRþ…*“úo«2)Œ“ꆭÎíLxþŸ«)—”ù«v¤ü v¤þ×jGÊÖŽ:k@ÿþÚ‘ò/ÔŽ~ +î¿·vð¬?Q~XñQ~Aŧk•æ·¬ø(¿ªâóÃ5Û/«ø(]*>?Uwø-*4 ?€ïA•…ÍC¯’ÊahÑïÚè—qÓ©½ê½^u¶·Ê· 1Yý_Á%«£ªÖVÔ«•Õµ¾ºo™Z^ç«V3ë¼óýæ`_Ý5ê_ÝuFQ:gŸä­+QuÔ:>ÝSúþäòÃü~ö÷êE3WÖ+%jC]I™·º¤nžê+¿Š¢xëª+ëÙ7t•õj…·Î sÍ©+©Ò“€v †Çêæx“ÔŸZR³P­õÖÕÃßìàX%° D-¤èÙPá ð©´ÔW] Ýi‡† +€\öÖÔ÷âKâX™ZR_ï+­,ù”2_icµ·¦¡¤âS^YBêE!²ê_yÃ`\"äÎ[[ç+k,õ20e•@XåìÆ/ÅAé6 Ä\ZÕXF1YPÙPákldª+ýÑêtVØÆzèOÉIR«½”j…)H}ER—9’èœ)¾:µÞ r€Þ•€ªŸü‹¦¦ÈØZÊèEg›hA(ÖP1”7ÖÕÀ„^6°Ì§Öû’ÔúÆÙs½¥ ô¥¯ÜWÊF *õÕ”UR:ê+J€+™í›ïeèZÄèP‚_ˆ¡^¿K¥RÛ©ú3µ¾¢¤ªJ™íõs Ð+)éF§¯ô¢N­öÕy/I¶Ú°°Ö[^%ëHuZ]²¬†—U–WRE+©jÕƒ-)+c”묣ZRx5V•Ô)t¢2o}圆ÆÝVaÕÐ’RROGð©¿x& +R ÃJª. À?&€G'4@¯¦j¡ZÙEÍJN—þެ/mÔSFR¹Ìà :ç­cƒøêÊêÕ¸;Œ£s(qÔlãË@2y~{™íK¢PA”'ó}•ˆy¯n‹QKjkÁ¼JfWyév€LJ§P*JÔŠ’z€è­éƪuÚ]¦6Ö”ùîDUaÈéþ”Të}UÔª™Ø¨JÔ*ê=ÀVkKJç•ÌÂÀk| +UÕM©ºM PôV•S¤Fg«9ãó‹Ô ãsŠ&gf«¹Ô‚Âñ“r³²³Ô¸Ì p—¤NÎ-=~b‘ += +3󋦪ãsÔÌü©êØÜü¬$5{JAaö„ ÊøB5w\A^n6ÜËÍ™71+7”:Æå/RórÇåТñl¨Tnö +l\váÈÑp™9"7/·hj’’“[”0¹B5S-È,,Ê91/³P-˜XX0~B6ÀȰù¹ù9…0Kö¸l _0µ0wÔè¢$T7“”¢Â̬ìq™…c“T6H.TY—dÀ`¨Ù“èà £3óòÔ¹EŠ +³3ÇѾ”;£òÇËVrÆOÌÏÊ,ÊŸ¯ŽÈR2Gäeë¸)#ó2sÇ%©Y™ã2GQr“Ðn:9ìPè€QÙùÙ…™yIꄂ쑹´|Ì-ÌYÄzïy Ý‘ãó'd_1n@¿ÀIÊäÑÙl + þd˜1òó\ +§h|aQ*“s'd'©™…¹¨Dr +ǺTžãs˜L~Ráåûñ¥2¢÷~¨ЋŽö˜•™'P4à†Ò­/hWöÕ¥ÞÚªÛ~ãÖ]#s£ºïLbZ«;PáQ5`¸ú=Ö„°–Å¢ŽîÝ:6 ÇIºëeî´"‘îzËæ{ÁÖSWâ«S|Ô™,¨¬g–!°Ú§Ç<µ¾¤ +&ƒQÔŠX/ð•%U0¬¾Ín¥‚am]% YPWÙÎD-i„»u•×øÃp?L1 +ÔN +è,ÎAÇ¿Î[_ Qªr¾·ja2ô­£±ŒaRYSî««ö“ÎØWÚ08*4¨sð2_ƒâ«›“¬* +˸~uêôs?â·Éƒ=RI¤tæAê/̃”æA~'_Ê ÕbÆ%Ô΄Eù5¹’È•”ÿ\IÑåðoË•Ý`U®¤ü†¹’Ò™+©¿0WRºå¿ WR~,WR~®¤tÉ•ºšo·t â98‰ß*]Rüé’ú«Ò%¥ºlÝø[§LJOýÕ)“ò›¦LŠ?eRyʤ\œ2©¿$eR.™2©ÿJʤeN7f!p\÷ŒXA=á}Bàè{ôÁ2)±£™Ø‘ŽwÝ—,dc‘Ñ%°#ÏzFÇî`vDž Kȅˉ¦‘¶ï“„6|ŸDZ5òÝ·£„ï–oG‘oZÉ×ùJ#ç5òž!_jäùB#ŸG“sù¬E>ÓH‹BZ<ü§Ÿ(§iä…ü½•||·SøX#µ’¿µ’³pqV#g4ò¡Fþª‘Óù@#§4ò~+9ù^˜p²Œ¼FNlŒN”‘w»…w[Éq7ùË›ná/­ä·C„wœäíc6áírÌF޾eŽªä-#ù3ôøs+yà¿é&oÜkÞˆ'¯ÿ)Dx½'ùÓ‘ áO!äH9 G‘×BÈ«¯<#¼ª‘W^ž!¼ò yeÿ²çÂÝÂË3ÈËþnò’F^,#ÍwÙ„f¼Iž×Ès9ôì`áP+yö÷³ƒÉÁáÂÁ4r`¿]8Nö?cöÛÉ3ûLÂ3V²ÏDž†ÉžÖÈ^<å O‘?hdFvkdW(yÂEšœäq€óx+Ù §­ä÷Ðÿ÷dœv,!id{Oò¨F¶iälÕÈà +Ù¢‘‡6[„‡4²ÙB6{øMÀ¨M­d# ÙM6ÀiC+yˆ0’< ‘õ÷?#¬×Èýëf÷?Cî_Ư[áÖÍ ë<üZ¬íX£‘û’Éj¸:Ús¬‚¡«Tr¯‰¬„[+Ç’{àtFî>Üí$wÙÈ +7¹S#Ë5r‡Fn×Èm¹U#·ÜìnÑÈÍnr“FnÔÈ iäúÕäw¹N#Ë\d©B®ÕÈ,ÖÈ¢VrM+Y¨‘ó· + 42+ilˆ[IC©o%uKÈU©õ% ¾$RÓJª[IU+™§‘¹©ÔHE©I¨H#s4RžF¼eŠàÕH™BÊ<|élE(5‘Ù +))v%«I1¶ Å2K!352C#ÓázºF®œ!\©‘ip5-‚LÕÈ”V2Y#“àÚsa’F&j¤(šL!…W¸„ÂVr<¸ÂE +Æ»„‚V2>ß.Œw‘|;MòƆy2vŒ]BÆäZ„1v’k!£[ɨœa”ƒä„ìV’5Ò"dYÉH ‘éF´’L€™é&žáVÁ£‘á—[„áVr¹… j†9ÉP3RFk$#„\¦‘AÁd`z¸0ÐMÒ„éá$ý?@1 BÈ€e|ÿ4“Ð?„ô÷ði&Ò/u«ÐO#©?u+I1‘ä`Ò7i°Ð·•$9ÜBÒ`Ò§Œô.#‰éå ¡v!!šôT‰;šôˆôéMâí$™…¸Vk%±^ !1 +‰Ž&Q‘.!ÊM"­ÁB¤‹DîŸq7a&á®±Bøâ‚I]cI˜FBíÄ ³9[‰î9Ü$¤ŒÛIFìpm׈­ŒX-6ÁL¬‡x‹X–ñfxbn%¦4bÒŒNb\Æ+f¢xxY#’F E5"(Dðð|+!e„ƒQœÞË,`;Af‚÷â²—ã>ÿÿøAÿmþ?QèÿƒÑmendstream +endobj +102 0 obj +<< +/Ascent 759.7656 /CapHeight 759.7656 /Descent -240.2344 /Flags 262148 /FontBBox [ -1069.336 -415.0391 1975.098 1175.293 ] /FontFile2 101 0 R + /FontName /AAAAAA+DejaVuSans-Bold /ItalicAngle 0 /MissingWidth 600.0977 /StemV 165 /Type /FontDescriptor +>> +endobj +103 0 obj +<< +/BaseFont /AAAAAA+DejaVuSans-Bold /FirstChar 0 /FontDescriptor 102 0 R /LastChar 127 /Name /F5+0 /Subtype /TrueType + /ToUnicode 100 0 R /Type /Font /Widths [ 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 348.1445 456.0547 520.9961 837.8906 695.8008 1001.953 872.0703 306.1523 + 457.0312 457.0312 522.9492 837.8906 379.8828 415.0391 379.8828 365.2344 695.8008 695.8008 + 695.8008 695.8008 695.8008 695.8008 695.8008 695.8008 695.8008 695.8008 399.9023 399.9023 + 837.8906 837.8906 837.8906 580.0781 1000 773.9258 762.207 733.8867 830.0781 683.1055 + 683.1055 820.8008 836.9141 372.0703 372.0703 774.9023 637.207 995.1172 836.9141 850.0977 + 732.9102 850.0977 770.0195 720.2148 682.1289 812.0117 773.9258 1103.027 770.9961 724.1211 + 725.0977 457.0312 365.2344 457.0312 837.8906 500 500 674.8047 715.8203 592.7734 + 715.8203 678.2227 435.0586 715.8203 711.9141 342.7734 342.7734 665.0391 342.7734 1041.992 + 711.9141 687.0117 715.8203 715.8203 493.1641 595.2148 478.0273 711.9141 651.8555 923.8281 + 645.0195 651.8555 582.0312 711.9141 365.2344 711.9141 837.8906 600.0977 ] +>> +endobj +104 0 obj +<< +/Outlines 106 0 R /PageLabels 145 0 R /PageMode /UseNone /Pages 134 0 R /Type /Catalog +>> +endobj +105 0 obj +<< +/Author () /CreationDate (D:20260121160842+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260121160842+00'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (\(unspecified\)) /Title (ABI for the Arm\256 Architecture Advisory Note \205 SP must be 8-byte aligned on entry to AAPCS-conforming functions) /Trapped /False +>> +endobj +106 0 obj +<< +/Count 31 /First 107 0 R /Last 122 0 R /Type /Outlines +>> +endobj +107 0 obj +<< +/Count 8 /Dest [ 7 0 R /XYZ 57.02362 765.0236 0 ] /First 108 0 R /Last 115 0 R /Next 116 0 R /Parent 106 0 R + /Title (\376\377\0001\000\240\000\240\000\240\000P\000r\000e\000a\000m\000b\000l\000e) +>> +endobj +108 0 obj +<< +/Dest [ 7 0 R /XYZ 57.02362 716.2236 0 ] /Next 109 0 R /Parent 107 0 R /Title (\376\377\0001\000.\0001\000\240\000\240\000\240\000A\000b\000s\000t\000r\000a\000c\000t) +>> +endobj +109 0 obj +<< +/Dest [ 7 0 R /XYZ 57.02362 546.6236 0 ] /Next 110 0 R /Parent 107 0 R /Prev 108 0 R /Title (\376\377\0001\000.\0002\000\240\000\240\000\240\000K\000e\000y\000w\000o\000r\000d\000s) +>> +endobj +110 0 obj +<< +/Dest [ 7 0 R /XYZ 57.02362 491.8236 0 ] /Next 111 0 R /Parent 107 0 R /Prev 109 0 R /Title (\376\377\0001\000.\0003\000\240\000\240\000\240\000L\000a\000t\000e\000s\000t\000 \000r\000e\000l\000e\000a\000s\000e\000 \000a\000n\000d\000 \000d\000e\000f\000e\000c\000t\000s\000 \000r\000e\000p\000o\000r\000t) +>> +endobj +111 0 obj +<< +/Dest [ 13 0 R /XYZ 57.02362 765.0236 0 ] /Next 112 0 R /Parent 107 0 R /Prev 110 0 R /Title (\376\377\0001\000.\0004\000\240\000\240\000\240\000L\000i\000c\000e\000n\000c\000e) +>> +endobj +112 0 obj +<< +/Dest [ 13 0 R /XYZ 57.02362 574.6236 0 ] /Next 113 0 R /Parent 107 0 R /Prev 111 0 R /Title (\376\377\0001\000.\0005\000\240\000\240\000\240\000A\000b\000o\000u\000t\000 \000t\000h\000e\000 \000l\000i\000c\000e\000n\000s\000e) +>> +endobj +113 0 obj +<< +/Dest [ 13 0 R /XYZ 57.02362 399.8236 0 ] /Next 114 0 R /Parent 107 0 R /Prev 112 0 R /Title (\376\377\0001\000.\0006\000\240\000\240\000\240\000C\000o\000n\000t\000r\000i\000b\000u\000t\000i\000o\000n\000s) +>> +endobj +114 0 obj +<< +/Dest [ 13 0 R /XYZ 57.02362 334.2236 0 ] /Next 115 0 R /Parent 107 0 R /Prev 113 0 R /Title (\376\377\0001\000.\0007\000\240\000\240\000\240\000T\000r\000a\000d\000e\000m\000a\000r\000k\000 \000n\000o\000t\000i\000c\000e) +>> +endobj +115 0 obj +<< +/Dest [ 13 0 R /XYZ 57.02362 236.2236 0 ] /Parent 107 0 R /Prev 114 0 R /Title (\376\377\0001\000.\0008\000\240\000\240\000\240\000C\000o\000p\000y\000r\000i\000g\000h\000t) +>> +endobj +116 0 obj +<< +/Count 5 /Dest [ 68 0 R /XYZ 57.02362 765.0236 0 ] /First 117 0 R /Last 121 0 R /Next 122 0 R /Parent 106 0 R + /Prev 107 0 R /Title (\376\377\0002\000\240\000\240\000\240\000A\000b\000o\000u\000t\000 \000T\000h\000i\000s\000 \000D\000o\000c\000u\000m\000e\000n\000t) +>> +endobj +117 0 obj +<< +/Count 2 /Dest [ 68 0 R /XYZ 57.02362 716.2236 0 ] /First 118 0 R /Last 119 0 R /Next 120 0 R /Parent 116 0 R + /Title (\376\377\0002\000.\0001\000\240\000\240\000\240\000C\000h\000a\000n\000g\000e\000 \000c\000o\000n\000t\000r\000o\000l) +>> +endobj +118 0 obj +<< +/Dest [ 68 0 R /XYZ 57.02362 678.2236 0 ] /Next 119 0 R /Parent 117 0 R /Title (\376\377\0002\000.\0001\000.\0001\000\240\000\240\000\240\000C\000u\000r\000r\000e\000n\000t\000 \000s\000t\000a\000t\000u\000s\000 \000a\000n\000d\000 \000a\000n\000t\000i\000c\000i\000p\000a\000t\000e\000d\000 \000c\000h\000a\000n\000g\000e\000s) +>> +endobj +119 0 obj +<< +/Dest [ 68 0 R /XYZ 57.02362 437.4236 0 ] /Parent 117 0 R /Prev 118 0 R /Title (\376\377\0002\000.\0001\000.\0002\000\240\000\240\000\240\000C\000h\000a\000n\000g\000e\000 \000h\000i\000s\000t\000o\000r\000y) +>> +endobj +120 0 obj +<< +/Dest [ 68 0 R /XYZ 57.02362 135.2236 0 ] /Next 121 0 R /Parent 116 0 R /Prev 117 0 R /Title (\376\377\0002\000.\0002\000\240\000\240\000\240\000R\000e\000f\000e\000r\000e\000n\000c\000e\000s) +>> +endobj +121 0 obj +<< +/Dest [ 77 0 R /XYZ 57.02362 580.2236 0 ] /Parent 116 0 R /Prev 120 0 R /Title (\376\377\0002\000.\0003\000\240\000\240\000\240\000T\000e\000r\000m\000s\000 \000a\000n\000d\000 \000a\000b\000b\000r\000e\000v\000i\000a\000t\000i\000o\000n\000s) +>> +endobj +122 0 obj +<< +/Count 8 /Dest [ 79 0 R /XYZ 57.02362 765.0236 0 ] /First 123 0 R /Last 127 0 R /Parent 106 0 R /Prev 116 0 R + /Title (\376\377\0003\000\240\000\240\000\240\000T\000h\000e\000 \000P\000r\000o\000b\000l\000e\000m\000 \000a\000n\000d\000 \000H\000o\000w\000 \000t\000o\000 \000A\000v\000o\000i\000d\000 \000i\000t) +>> +endobj +123 0 obj +<< +/Dest [ 79 0 R /XYZ 57.02362 716.2236 0 ] /Next 124 0 R /Parent 122 0 R /Title (\376\377\0003\000.\0001\000\240\000\240\000\240\000T\000h\000e\000 \000n\000e\000e\000d\000 \000t\000o\000 \000a\000l\000i\000g\000n\000 \000S\000P\000 \000t\000o\000 \000a\000 \000m\000u\000l\000t\000i\000p\000l\000e\000 \000o\000f\000 \0008\000 \000a\000t\000 \000c\000o\000n\000f\000o\000r\000m\000i\000n\000g\000 \000c\000a\000l\000l\000 \000s\000i\000t\000e\000s) +>> +endobj +124 0 obj +<< +/Count 2 /Dest [ 79 0 R /XYZ 57.02362 435.0236 0 ] /First 125 0 R /Last 126 0 R /Next 127 0 R /Parent 122 0 R + /Prev 123 0 R /Title (\376\377\0003\000.\0002\000\240\000\240\000\240\000P\000o\000s\000s\000i\000b\000l\000e\000 \000c\000o\000n\000s\000e\000q\000u\000e\000n\000c\000e\000s\000 \000o\000f\000 \000S\000P\000 \000m\000i\000s\000a\000l\000i\000g\000n\000m\000e\000n\000t) +>> +endobj +125 0 obj +<< +/Dest [ 79 0 R /XYZ 57.02362 315.0236 0 ] /Next 126 0 R /Parent 124 0 R /Title (\376\377\0003\000.\0002\000.\0001\000\240\000\240\000\240\000A\000l\000i\000g\000n\000m\000e\000n\000t\000 \000f\000a\000u\000l\000t\000 \000o\000r\000 \000U\000N\000P\000R\000E\000D\000I\000C\000T\000A\000B\000L\000E\000 \000b\000e\000h\000a\000v\000i\000o\000r) +>> +endobj +126 0 obj +<< +/Dest [ 79 0 R /XYZ 57.02362 176.4236 0 ] /Parent 124 0 R /Prev 125 0 R /Title (\376\377\0003\000.\0002\000.\0002\000\240\000\240\000\240\000A\000p\000p\000l\000i\000c\000a\000t\000i\000o\000n\000 \000f\000a\000i\000l\000u\000r\000e) +>> +endobj +127 0 obj +<< +/Count 3 /Dest [ 87 0 R /XYZ 57.02362 543.8236 0 ] /First 128 0 R /Last 133 0 R /Parent 122 0 R /Prev 124 0 R + /Title (\376\377\0003\000.\0003\000\240\000\240\000\240\000C\000o\000r\000r\000e\000c\000t\000i\000v\000e\000 \000s\000t\000e\000p\000s) +>> +endobj +128 0 obj +<< +/Dest [ 87 0 R /XYZ 57.02362 505.8236 0 ] /Next 129 0 R /Parent 127 0 R /Title (\376\377\0003\000.\0003\000.\0001\000\240\000\240\000\240\000O\000p\000e\000r\000a\000t\000i\000n\000g\000 \000s\000y\000s\000t\000e\000m\000s\000 \000a\000n\000d\000 \000r\000u\000n\000-\000t\000i\000m\000e\000 \000e\000n\000v\000i\000r\000o\000n\000m\000e\000n\000t\000s) +>> +endobj +129 0 obj +<< +/Count -3 /Dest [ 87 0 R /XYZ 57.02362 279.6236 0 ] /First 130 0 R /Last 132 0 R /Next 133 0 R /Parent 127 0 R + /Prev 128 0 R /Title (\376\377\0003\000.\0003\000.\0002\000\240\000\240\000\240\000S\000o\000f\000t\000w\000a\000r\000e\000 \000d\000e\000v\000e\000l\000o\000p\000m\000e\000n\000t\000 \000t\000o\000o\000l\000s) +>> +endobj +130 0 obj +<< +/Dest [ 87 0 R /XYZ 57.02362 244.6236 0 ] /Next 131 0 R /Parent 129 0 R /Title (\376\377\0003\000.\0003\000.\0002\000.\0001\000\240\000\240\000\240\000O\000p\000t\000i\000o\000n\000 \000t\000o\000 \000a\000l\000i\000g\000n\000 \000S\000P\000 \000o\000n\000 \000e\000n\000t\000r\000y\000 \000t\000o\000 \000d\000e\000s\000i\000g\000n\000a\000t\000e\000d\000 \000f\000u\000n\000c\000t\000i\000o\000n\000s) +>> +endobj +131 0 obj +<< +/Dest [ 87 0 R /XYZ 57.02362 146.6236 0 ] /Next 132 0 R /Parent 129 0 R /Prev 130 0 R /Title (\376\377\0003\000.\0003\000.\0002\000.\0002\000\240\000\240\000\240\000S\000a\000f\000e\000 \000o\000p\000t\000i\000o\000n\000 \000n\000o\000t\000 \000t\000o\000 \000a\000l\000i\000g\000n\000 \000S\000P) +>> +endobj +132 0 obj +<< +/Dest [ 94 0 R /XYZ 57.02362 597.0236 0 ] /Parent 129 0 R /Prev 131 0 R /Title (\376\377\0003\000.\0003\000.\0002\000.\0003\000\240\000\240\000\240\000R\000e\000p\000a\000i\000r\000 \000o\000f\000 \000v\000a\000_\000s\000t\000a\000r\000t\000 \000a\000n\000d\000 \000v\000a\000_\000a\000r\000g) +>> +endobj +133 0 obj +<< +/Dest [ 94 0 R /XYZ 57.02362 230.3117 0 ] /Parent 127 0 R /Prev 129 0 R /Title (\376\377\0003\000.\0003\000.\0003\000\240\000\240\000\240\000S\000p\000e\000c\000i\000a\000l\000 \000c\000o\000n\000s\000i\000d\000e\000r\000a\000t\000i\000o\000n\000s\000 \000f\000o\000r\000 \000C\000o\000r\000t\000e\000x\000-\000M\000 \000b\000a\000s\000e\000d\000 \000a\000p\000p\000l\000i\000c\000a\000t\000i\000o\000n\000s) +>> +endobj +134 0 obj +<< +/Count 10 /Kids [ 4 0 R 7 0 R 13 0 R 63 0 R 68 0 R 77 0 R 79 0 R 87 0 R 94 0 R 95 0 R ] /Type /Pages +>> +endobj +135 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1201 +>> +stream +Gas1^9og5P%))O>n@22tMF;me[V;3T5uD/+hqB,0@schDg.#ptMuVL`k5LY\bfcN$%4_$C9I:>h!/&F;_>;@pu`0j:/cK.Ik`u1V^0gS4ONPpLBX&t`BOGgl]`)a1Kk.8*Mo8Zk`4WU/\FLU0*B!&slGZl2U_sqIb>a.KRNiSk$Mg$MFo]c\f+f>Rnk,>o5Zh=k9ZDJcX5Z/n@X5*/f1nLdW0\&Lu[oF[RT9ta3`/hh0o(te*XBoK$%]=IAGE:FuZXKgV`9k/J=K43OKOIkT6@33lQ?@dtK&R'FF,"[\7>W79$doAf&WZD2l-_YiU*O7S9#@reD*k_q-=t;hUD0kWPgW.;Lc4@98XEne?CpAfFC2aAq?Q]l/@EJEFHY]fR3?QJW:$-&M_rq4SnOL+eSR([>KXO3%]Ou]l80Mr%kD3j%ieW$c[O@^W_0**G^SIf1,W0>7X%mMu31W^A>#AVhJ?#'RRs]Q?0u?MH@pu`soXIFc5BB>[5S&9=-Hb.+#'YDL#S`'h^^iBlW`T\c+cpm!0E/2_^?Bkt(7l:[^F>)b(7-/?'$MYQ`L9&^+r:Z++QAXDCN@4rUK\TkA(?bDtHf*_Vgm!OKcmO`P^*Ki0$a3GR7qNtQHSEU:Omk[Yi$=la[0R#V"(A?dMd!s*=AiYU9-\GIH7AT>LlQXe(mg(DG3S:"kmE=ul6IlS!6`E1_%PMb?XL4g(f_:YaLcggcb^a)]Hgk)"En'DGp.~>endstream +endobj +136 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1232 +>> +stream +Gb"/&bECjC']&Lbga/02MC$$DUSm[M$E9Ic\[%Z`YQ\(IfSh*)+^^il)%6Y[Bp^GL8W"u89@pmJkNG*'_8@@;A%i.lb2AcX.8U6%&njN3aQDttp@.m/mOc>DGg)3c`-t)krkmo<)'>[Q])n-\[K`&MlPq9C(aTcPJ]%ScEej$k1SVD$OHf)bfT.REI4?'J5*d]0t]b!qe7%n@9Ot!L^l,>ENS6\S(]f??H)n?gGpoL&Kntt!tF"?N\5dLSMK%\U7:cKhf=GtnKZ;FG>djeZE2X+?Y'4/d\h4&5F>Y?^+r@hePlhJ%M9*KPd7Q93gV;>)!5m,B-X&>4P4Wn/9XKL@6u]Z:^e%*J=loDHM_+4&1bW*oW'8;;N%[16]L@lD"+LnGI:q4U4UE6Vu]n8>PGg&H[d-%&fK.usiE)2Yn(M`Wojri0i^fmm5!t/)+2F:jN.^2P,VV:"7C>)[EZ"0]d`]kQ/)60$m7U;AK8:D3Gj:<3Jd7GqiDl]'Z9a,ga,mKPHBdIMf3&VnjbtD,'DP97O?EW-.2Lj-3fYfL\idMNPk=-3_HM8"LAE*;0mY1"gh3MoML2tZ5]V2rt/r9H>FC3=QO5.rV8St[VX9@.leZ(qe&YR.S9qu%Bq$+ZKkC7/`IDU@7Q.X+I^s+jNK1s#J!9U-!5Cio*4M/3:n>h3fI`eVDl"%6eRne3k2+MuMflXO\fE@g[JK[JPHIS^:kr>\7s)`BYR]A/@T=<-8PIIuI^'p#p!a0hI%#0enQZ:R_g$:,'#8d#%jDoO=$9oTuem;fdC>E?r`p]Ts=?/#AJX'(K\_jObY^pg/h#.UPJKEn77)G@LVbr,g6#mHZ"1eR.c>-GX!endstream +endobj +137 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2507 +>> +stream +Gau0D>E@OK)1Bi7+St0+[Og]UWB_OPK5;--[S8L5>q@Z_ZqT60aHB1gj7qO+I&(:G^*HJ%71`T?nT)A8SaSmbk2RPmf6kco]/cA[`#0*KZ@VT_AA7uudOA_=`FT,<"b_Ihkl2<@_NEsq+e?r>8VH-\WH/8fY*t%[4ZH2XjLU*5W'_G!3c;5`K2Qo/OI0(=56/_?fDF^m&m[7I.[6(U-Kagg#`p?\CE$+FkZ)plD-&I@`F35o'_%u;V;(X$=J_FJjV!@V=kCU-&GkU\NA:>*Dt^@1$l:;5R/L8IAM$m'IYD';p@uG/aH(p-qrRXiqmsi./F%NY2U\99ZAbRZks]S/'$>!-rhgNC9b`'k[D^nK3R>t0&"2%Re@HSgD[dB<+Krqa,$14Sc`ib`U8g.qr5WE#^NRfJ_uX9[hrC4s)7Stc7>:eKhN"!o>H'Uf'\(.4IgTStRMd^k&3k-F;&R;2Do"<,GbgjI2C@`*YG@CQ!b?=?WiEP[pU`A6J_7RE15.6="hfTI:[r?(uj*EK1j1[dlEEH8+uITLrY;;WH2p6N:F1;C;%I;BU0f+dQ2(@ADJLrDK3RpFKZ&TP^,;4%Ja]AS]J#BCYaVH2%3sq-cU,"(d:rMeu=_V$TBWe!]TA7UkWI&JQtD_<ER:_OWrd3J];UL)GIsh/k\Bhg]dN/=_hR=<_rLWD4hmoV^ZpnjcJ2LFeM\o@G=4obAA-:_hH$H<9>*:nJf2>RN;TKYWN#O'8e(&_ZB7Z(Ki0e'$4=o_dH1oXk&R:jHF1qRWJFQeC5K>cc+KXC2JT5TN.f=NQkt-A-4h_Ds`OT.?q8JHp@T>>r@(p@A:Ygo^X3NdE%Z`pUGrj?M_pLkn4<_cXZ3&L9/Zl>c$M%aK)i&;Af]5uO).KOq'"A?gbLQ0[oCT$^#Fci`?o3LEufW=)&8HW@3ZkiEmleSTbK'hp'@VF9`(e4/%n>W*V*7Cto-@ENr^q!jN5TVOaEH44j"KG[n)%Y4je&1EVfOs&`R&(Iod.*$,,jA;n'sdF8O;u*FA"gMC9Y'\I/=@Nn*Fl/p3<;eXqgsmWpMsLi5mR;I$Z6]LDc1EZ7tPKUqCO0cP`N-n5b[P9ai'1%?Zlt6oRlat7&'`FAIBlU+dh"LdVK'%0`hL6@`_cutHWo*FSeR2S)##f&CK7!h&:7::[tbciU'spANeW],>clA&GpYID0j,8]g(ULlC'pigH;dadA>F[NUY!OQ$GtkWV!V^mFcY(9Y`kKdj+(:W!dkHIuV%hk2P45;Kp1T549LBmVNYO)dp:LEne:$L)_fU+PAJTBD.eAQ)A^5p]MDGlq>ES,?uj%31hWJfj%Uq[okU+^lMjN]Fa4;hdnk6^Gh'2rFN^U8jq;tS.*GE,45G"JJWf#TYTT-1qSaFk8<*;rbT>C#'a=@fpi!B54>q,~>endstream +endobj +138 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1459 +>> +stream +Gaua@9lo&I&;KZQ'mk$eN"@,IO/(_pdn\`MM:l)d'EuTl8O5p=&dp"Q>GG[&"RpCo$.C&sP4%b2"n=(e/NkdI>/8]&o76(V\Ai>u<\Vt!+MF>$ArW&e=?qnKrL6tAa+EZdB?1s`rgYoEC8!fXaOFq)d$FDk`/LEE2c#HAkOcQDY#V"Z\ZUeGsF-iUJUT'c.t?9^MJT0OkCU&9^[8jqrDQ\9Z>Dr,QV8LE5`8E+.f5TZ^l91Jjkc7&bK)Hh9jgtDpeY;^11P'W5p(*0F3tT$+8ps$EokMOHb\S11G5#nC?j.#!_GeUp`BqZpgDA5!;^$6ftNopb5%p>d=G]c,h"DUohoPn>DG&YkY*,.^0P8OCkq9pPMemHVFRQP(3L,.`cl&Vq;d2FMq/cpA:ikc"FVR64AM)h[)MK<=NenC:+dUQ_VkQ7[/k4L>C#%:r:+.k=2:BXVEt@f%"ZM.O)?LnGC%R?EGHDl)Ji.u311f!<;\DD=G_f.eS2Zm=?"FY*.7IO.^tSOTrROW.:j<\'lDZ/K5sJAXA*,CLd*4KsXT21!irc;XO-Ig2t4Ehh/)<('ms"c\t:o'!GKSX/f(g=.(G"4TuN>,5'*C>$M\Xl"nOh-.4$-0PLH:g99t(>fTDH>7om`'1;4'khLoe>0IVl7LM2dAf%uH-2e:U@_/kU:Jc_2NZH1Dm!Lqn*>t]k3KDJ3[]TGV'J:4o.aqS>'dTnQZ2EQa@ipV'Ch5#8=C(u08LI[RSJ:&]@'e?2HFZ?QB#u#@7,U7Z'c<0psOrga02B1VLE:Y\WR.a"V#d&5V1Y^^o[D@X#^Qmh^G8ugl(td\sGH$0NtuE:'&S`Xuj>$T5,Z[/iU)d.rS']lSe$nj%7pg!Hf#5DMm8PA>AG@k;5'jJVAH$-%MJ5V@bW`tHB0C#q[gb_s;Hd\E<5WYsP;N9NV)A1s'2]H6e3C+SEAV/kBdg^f94Z.fjc)JRJ)9m<"`>]D1EsP=&>imPo9],4ID:BKPD$OY&_YKJS,7'e[^:!X7PA_5-SJ1!2l]gEQ/+H\ZKOE/KkclWHm"gI>OZiYCCYtR)`];C6&0cp@tq>i!3M2R`;~>endstream +endobj +139 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2616 +>> +stream +Gb!#^8TWXr'#+6Eo[0'SE<'t.bXo8'P[?FBfqWb9,8BQ;U[X]Oh-t?`$7%Mm^Xa/e*]*-i"!Lr!A9g'Ta\i*<>-D]OP>C"biNL6>^RZ"iHu=jEBo<0-#R#a^lA"^u1U#0JLDq.-sk\oYdu&W,Rtl$7X@3OQCptADegcr67YQ2L`qQ&87`i!/-YJ=:ITLd@!?a_#F`Prca4dH&/=.SsF2Y#\P%sWVBYu3J;)\V/IQ'JZRL?;OKnj?[2c)*f8W#H"@PVH5JT![tCb1mpadY@`;o5rl.Xs3?%H&f)@0)Sts?K)FV5p\B;.@L'bMp7BeDB802a?MTfX#M"cg&2A2i.6^2hUb*t?"c?Y^c+AA#O="Zq?f_0@tplcjHN8bs=5XiqTkIua-84++')5`f93Src]=BY/':KAt+'j@-!"b+Bii9r(,*jMeEH&sL3IYrMW$*Eb";M5)ja^7Joab(Zj*GghVIM>L!e)o7R"L32S/%*b?D?!H=7$At1Vj86'q`L?^[=.44@#;iQ:8Os0)G_TfQ5196/sJhfZt:AV@)lAr2Bto%I]^.`Eh7dq0<%@W&?g+=1)6Z!]pWKc<'`d#9+dk!%%VNXh8i3+_BLXDCHaMO!iDmk\fX(R,)T2U6mh?+s(A`R]b^#rY0_U\!l"DhU1];3e,&i1.31p=LZVu.pW;qmVATX.D^s#[j`e*M2L]*bNYcL2Kk@N.Pmca.IT21U/+>l$c"VbuaPn6rRr4;SqWgd"`JTpih^7G&U$-%^(,u:_9Q,j!UQsO!&\e?&gbA>?YdG]8H&7Fe`$iQ:QZB&d^%V+c`1RM&F*J*Fl?`jknfp*'2H(;N#'AH)ff_,a8BtotnSobE&+EH\+i'A+>UY]t&9'(%lfM3>3ETsF#m]bS/$ApSdV-SIPida$LEPP9GR$Qmig&`O3AH:6*84jWR5\f_80*&>q?DlftWnY4qa'S86'a5-ge3\!XqcTKID>b,9kZS-=13K1Oh*I%DRbPf?7a"N@+q.%)oe6LROS!3LcWO-_

une'iDOq$FY$Gei@&DsEnUHW9H^7_7[++^c^Inn\@r'ag'o^)o>pC[#s-&_@(R(k!=L'=CG&Um&(r9(I?S70Lbndp0Ia96C/@])\J.n!KO[nUuh?N*=.s7^!:?rJ_Ap[W^As^1TPa0?%p%o;Z)LFMG:_cQ,3+HsJ:!*](4L1eVI&*42(fm"k,fm-,+G,iN\'\JT3HESkR7qTeTS)5I$ZlM=?\C=+H-*#F&%)r0:FDuN!"@m7:1[*D1`1*'ln.&PI\Sk[b3IE+RjakfG3OS@g@f+oN.:IJI/3M\#t7A(!TR;lA%3A)1stHM2PP_RBRNX41[(FM*hInQs-V\';2=MaFg!pc],[&sZ+nK0I>Q:6gFR)-<:#?Zbi1SdS4]Y'MWdq)3Rsl338>ldg#,hc'>,$>IRW8/G=%6tI(JeTu>l=go([r(~>endstream +endobj +140 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1861 +>> +stream +Gatm<=``Rl&:XAWkXVq0fpU70^>WR#:>%'8PKH7U8dT36-IKZp^ujOPB64t.aadISZ9XjZpG^Q.?SJ/Ja7RoC!%o)Wq'uQe@BflG&ec%)E3m(LI_dL*b9<)h2@#^L_XSSZVjGq@qX#J-;RT/%0O9D7XoT2Jj)$8Rp$%&0Z#&%S%M6Zkbd#\[YM*i$(@0=<_/[THUs9YkD3R2X=JnL@#&AD%:js#8^A-"gVKu`E"`A-5(TX09nLlFWWD,QkL%U@&]/J;oLq,-X3>rkMd<4FbObe(7MeFP;p^B)7\O^3`K\ts:]jUZc+cbrYrEpY$R);6ra?1/T&5iC8WC.DVli2$IUOqdJpJ6WZd\5OPpBUOtVP^,k[=dhsp$p).qJrJ'KaJ"#i_nHua\$RH1oEnhsf('epfL?HeuDP&P2;tQsr*5c2`G2,aAE07OVdojjAd.j@*:?drsE/^PACrZ=0#[k2qCMt_U7efP'd7pkSA[X2pAjIQG,bFoWZ;BmY`tj0Ds*q(!1]QQg=R]R=*2X+kCWE,d0dG`8NsOqqCer:+U4,ol40:C'Fi!/+fCXL%32I?9-jhdiAQr,+n0om$Ua2CuI>POpg'?24VYa#nopE0UpOkSm4hb?$B8b@j@+fq2#Ahf0:g@Iu5$_G`75JOiSs:ke_uP=A8>AkV@W/eN`E%;B7p.M&?br*F0cMeKK]$;k&P0,p:8U+/P?^A8M+e`[<>(&.Z])XbhMo"&]l*oj=Em%?f^.mU:9_mB4f_'k[W[C%Ft;[$q*mXgXq[GRlJZU3P:e#g691St6ZnU4r/Qi`X@;%?S2SNCFXA5:jqLV(LPe`]P@FYgoiSQUu(RI7U_h[d3*Y\"j5b>mX,bUs2aYGK"RPQKi5pdOU&6NL&HjkqL@`^'__L*R4I4@J.+7]9s+UT<5q3l)ZnT\55_sm7pZ$rU=C7Nq3[1d&Bac4X\KVD=*f%Oktc/nZhK4%e\_L[WZ'UK3M+XU>$7??mqSFIc;Arljk]=cM:t^^KI/eSegLWU#sMURHCQ7E'<7:*moRN8=or#n^8jlRO3Jc*9Y"r,3e4f9it4ciRI=*Cga]b3>ZTFZ^F832"2C0(@q>!G-]TGm_lAN@,uQ<;YVW@i9"0Za8Be^".tGV;HHJK)"Xs3r7.Yg-oVUp=H)pN@CsXuD:q;Eept!9=<@dd$+!='+4!rmfqmE781kQY:$o3s3r1m;=6?M5B+qr9U+2:L1=@t^b)nI"iF%A](Ztj^&X[!*X#4MQWD'*l&\OF$(7o9eE:H-n?&M)~>endstream +endobj +141 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2497 +>> +stream +Gb"/(gMZ%@&q-CUW(5+8b[L+G!-uq9Ap+Du2U5D,"GlZr-rq""\S3+8%>?;+*6#SBR;->L+]gI_77R;.+/pl,\0uJh\pd;Ihe;X@(@!:F*=sboggbq]K&mWCU_9BWr^nM-XJ@>1b#f@GlUaBd\+ZD*9j[.2Y,E==6F*Jq/B4NSl!cuYbk&6X]h6nVH@bjBE0;I*q[<`ElaU@?LG=c08[tnj*)?te2rRQh(-Z4e9TmYt#'NeCdCkg2;jB40d5C!\],Kt#-6l&W24b?'%0,Q+Ei1c*qo)1r\?)fo+H)HY"&E%DonLB=lT=!>LE%,-pF@-SH_ZS#BQ!H9f6X,=PI7343QI1Wl@W9@agG>pOsGmW)Vk&o#uMi5Z@r=8a>>JC`p1REOBW[(oRAUEI?/Thp[gV*5'_P`qS;<'#pAZn3qT-,[n=;C*d+;-2+lj?bt>;m6#YTu,c$pP!`hilR4hP$]Dt\CTQP2p=3NCp7GnXQJ;#rcj6$KtKL-"Rg)N^bAmn]Up=cOHN[PJm+Gg-s^4_r+YCKur'+0Ou,(L`KPI$Q9sEZR3=\`n&Ouq^AmojhKM]7Vl<#>c&VTE!5r&:F(k@7[fXL\^$J>Ce0aiV.L8-YZqs-7SXXK[%(M?1Q6Q.mnZG*1@dMR3]t1c\&d_@KC]0-9ZHq8%.7Rr%]t_XIDAMtVhu&fO-DYa[Admf'arS2R+F2>%pOX=Nc89*GfR>m=bDl34Y7g#2NT.Lb1/i?o]'5L^5Vj0&&]R.\Ho+"`@A\"[A<]0/#F@JS7pJEuNt`j8\_Jh3(2u!O2\%0Z\6"l>>[rOIBV\SK)7D<36CM2&3?&3nq]9)4e!).]YO\?`EhQ@7C_*&765`pmL+BT_3o>or\k)E>;_MNf5<_B?V[#WIR(,fC]4u@;O[]pjr;*VO0-KDAp.\u2tKPdj#5>O\.\t7@CXU'+Wc):sD;]1BP['+qT#E>FS"J7#<)Ll&O)\AUTn(30k?RfL6B/nbmaA1/8\0u4#X6VP"d*O/?>cEH=O0:gE6s67\s[[B?6:+8oa^8EQ(^h=0/9t,QBH]H[bHHP=)*pTHEG%K?+Rq8V'^aXZ-o^/NE&Fe$Lj*M]GNl[`cAOS/!JeEii$ij5%4m;=:(]^YUYDgCn_C5np[hdB_E!JF/(:VnMt,&G)FYTA9BMe_@4Q*Dgd;1Ms=)58iddnJK_q,CODsaK4Ho[6ZYhMa3GJRL98HT&A-d!U%r;@b;(,nbk%K3P4$tV]pN.2<@c)OP`j2SI*u.+'N3*2BKBWK=:0k-WTBAC!!Hj$QNfoWtpmNOJFLFZcBMm`tBhBeR1ja_nU`6KbpA#^68ZSm6eS%(3bOdcp*!-Eml;I2Z]4OQX2PTl\:HL)9RP,T])R(Z-G3om3%SSA7ssm,+lTKbh6fY>FsB:l-95.'d;k[~>endstream +endobj +142 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2784 +>> +stream +Gb!SmbDt=:(>\.(\9)>!7;p;]X%>?;"3GX2Abii`hF"(7'fp6!qMdR)aAP3__?g$33g0uE.=o'&5N;[D/5('f+A[C'A=g$_hfbaAZm+p!3<]e_;%+BBIRg#C!f_C-onCZ6ObN6A'%&TW45'Yst';p,pYcXmCL(&Pp5P4J)0YK/V-a>_U/0e]3P_FiV[9V2$SQUn/m!kL@WY[C(^_\nT1em-rS,JDmrl$5_i@s40rk4b/41Ep`Cup_'/6JLKC5"m^PY=Z[Zd5*3<9B'PB0UbRROLaBrhM#MIY=JfRh<-0Ju?EDKZW,D1M*Bk3NGR\Y`g8>%$YJk'FD#iM#%SkDe"-j_5kO+Kpj2U(jV&;0tHjEE;RlVoX2+BK4"OV(eplnSHHCa^Z3n7@cl3mp9^17M,?\X3iX'8DAh;TX*6C.b$O,:/US>4g0P$"&(7%--AiH;B/>u(Z6t1F1,W*GmSkS7:$<>&PhJX]ok37b=<.$bE=Y8GL$YLh=kh99Y&7G%gp9"YKL\2XMi:fQg&5&P=,\bQCP\_=15#e'3]]n5V`,/&[rFOp>(*nM2W1W>%(jUb='4-@c*nBCKG>LH5tnEWf&&kD!F\fWV(q\BIU3jV<>3rK)^[L)iO24X/9)[DaC1_VQ"BB"\u,d/i+R1>L>Ag2N/aa*U16mT<(l'Ccu?<)\&#A$%/PP$/q2[-;a(@JA+BjG91CSP*EBOD%MRF',,KapVKHrG:b&LoP"7-1g)eiC/rnlGC0C:3%,%@3/.dZjk'ks'o#I(k:Et_Ckg(UUJ;a@O658^t(%0QkZGJN,aIm*>gS"j)g4?1RX?_Be$FgKD*,,V,$\?dD;&pE2(<97]-nA1'DNQMHC10>Tej).mC$%iR:o#"-(HGN$_Xk%.bUiZOD0]<;.$1S:p3AlJrlJ4LQ*74>MSX::[aD=1f`c:_,S$03Z;8UUak.s2)ktCkp8::.1>[,'tN9"`RKTJ9U69VXZD%m_1i#1aJTY?-chXbi`sUnN>k2Fh7R6C`_M"(,HI6?g7H-KloU"nZb`((-tncB'17-ioMlDk_p2BSc&1G"fSZ+-u;C[M1rA\V1i/f`k;HBE/E+c=Bd">=g2K,#K?7*]\d]lh38hUkk3dm)BtA$G1t(g4$sAGfpJsgT!A+&qSGBm6;[rMmiM7XWUG;.@Y-9Nm?agT;*W&A2iXf:tI_$Gm<#@Gh^N[!pG(BCgVO&eeN2g]7JuQMPB7oMjMg`OoQ]K7EYZ$B"sOuqodSC81nmhpFA;>BqE%rs1)8%[EQg6]2NBs7pJ2kPB&>gM?S)mOf4n(oA4`E_S0!l:YZ6OYVN0*.6;9\C6Zl+'3T95YJj#Y7S`jpc:]=h@n?runjJ-8l*H(D!h0uHHhrARflf1%-"E'A4]bIY=W@/b%[NtA:ZXb#Pms=@mb?^O="$LRLHuM#Vht?`IrAd.W$RB'I(JDR\B^Q(O-q#>cZ_>Ua=BeGAEi!g3Vt<0rC:0rIBZ71OW$IZYWqC)J),2um%QP&">D\]g=#T4ud>hNtYG4"FMA0c)th!(>@Ho^uHdmj!;4#YDFt#kZG-H6K-61#E?X:3mn.S)fbt+;3=T_J:I\ajqT&77g49L5E#Z/X32D<'ce9I#16:@%YH+KqqBho#JQag!c-9Ih!Qg^mfrm@M'nClf/8S5/&+dH+[3.W1L"=jh.Is[c=f<".l)*8+$5PO>saueHcS>+nDYa*j:&BKXo+kQD*Z`l2;r8EXPfE)U#DUSGITrT0E@Y[69V$;;?6fp4$R(cX%uR-euu5;iS=ZA=3Im1YAP=f>.;W.s2/2ss,.#'SD-WU.qIPeCHq/UgZf9*%0pK?D_g!h*@(&)CH-EE(UsCr_ZJO1j?c&cD@lZUk6AitI*@LCW>Q^JFj[6aIn\HZ?`/"R6Ftn%";a1RIUc_q.Z*mk-V?3s^48*e_5@]b#;rMo,[IH=\_]@9SFl/^I:hsI#$qG81&~>endstream +endobj +143 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 3089 +>> +stream +Gb!l!gN)%>%XlZ)i)-)R(uE3S]K&`mQ)\AXfLjE+BI_OVRoW<5[aIlBdGHV('IhG(XGaEm][+;-EQ8WUo\*-@r7i`6a&DNCOqC%UC!5*q6#_V?*u"!hF,,b#$hUF_%`C1Ot.!mHsd+:'P3;NYN75tkP8cleu[-Kq""+N-XX?>t<^@"NXhp\k>iCUn:iHBZ=Qim>`j4sO9L@%lW`-:ki93W"P8.jT"p2Yd:EhEQ$^J2GGo\Yc.sM)]o`i4>/FY`2L:g=\q3PpX*Ple5ao[bMFqd&6YV!OteJ"4-NhfCAoL>@!0s0AhMRPtqRr.7E<_;t"G#ep02g$Ja+%nTQimJ%g5;'\WIA&p>gW/Z#,!qMV*]-lrrg2)C'jKgHeUKX&s-Ohq;8RV7`]i(7JmMAJpZAC1Ybb`YpD/WiT#n?N;Wle>_cQt3`,KR"]HElC_@06o/dL*T?:B[APV!]'c+Yt2(`kKc!h)Mkk26lL0jVjM@9O_b?HE,t2`;M[&r;MM!^/_(`2$uLuHVUi8%":\Rf".=AT'[Nf!WTDi2])uq9T:g#"pptlM*gZ%b/Y=uqXs(6Y:oBba-9>8Xk7ed:,g>8Lo2k<_)ilg=KXVVftLiTb4P7X$`MeUK&YL&)+)kZTL$[.O*a'cm;-Jc8f%c\Djb2a:EZFn&SU4KX]#'e,0==C.@aWfD?O!n$+HR0AlTBUZc6YLOlM3HL`)dR-7K9qe6V[LP/9pq-;,;4ko:E8?M?P,2X&!fW^@pAYmJk$],fgiZV[sV$pQJ^(C)KVP="+q%GS<8QFYt_OtW4@4p"YQOn_AcOrNE-W8QSM(D1q&1ogn4NW02e#N9>!e49pT@"1'\G-$'L8_l%p1!C`T$q&-8X;8&[hC&[O+nFrk&'pOYUP`Ti"0"uWbs8/rHt"N-IVhjqh^#"Z/2[7"_c)>2Nb)GmC_8(si_Yf@)02kP!V)of$l&nruZQMnG"\lhNA7`$eNjQ`0N8cRcICB)saa2dG'2C6`f[1]uPq](FFdF7\'0A:8TrcSio/aO^6+FFC5DIqPkbPe?t>j*NM#p\MF]H:T1XV(][-.J."q#];\;Db7gOj$jN:R'64<>>%Qdls@tq!PcI1$sRWR7T96/TmKe4LCO`PUZrN<+5L/:*Pd(_DMMg%/bD0(1-qIr:lBEDJ?tK][2(%YOsN#Z&t@,=kP-YaUPkg&bJt]eMnRJ30"(O2'":'rS8H2;]_]"IBi7\:$)]5U\c9\r-nCd(Y/jmGUEA3%hp]+karq/$+Q@ek^IdLMm;^:!D9lR@3O[\<]TtMiqg6QT:]Z>@&Ndo)YK.VE*CMTnAZ?H.EnX=Omrn)f]=SQ&cO9c4*c>m_js29eC34e?1qBdPNpOCb6cE!K`%C/V=)SC5M9Ce].;oj$M.;Op'fNYh*4fA:MABA&:c7*cIBPJNReUQ"'f9-GhNAWiH0d7ARoK4RB=n.Y^oo?]Y'91AG?Rf:TH)!*^fH,\%,R3!JI5aoeoa)S9DgNFU__3C9:cAOk>RG6`q#Mj44(L:I%[&24qI([4K].q"YWN%G&,\O^+2*Z'XqE`RqYB>]\7i3chkIM/:H0g)ImpKLBXB%g)UQVVg#5+r6h]akkqt<$0tfU[]t2K#?go0b#eUsp@@Ao]!C7s///,Q:O_hj.SqFQt;%Y^'9:f>A:%-fBfe^;9(Wt4M/-~>endstream +endobj +144 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 767 +>> +stream +Gb!l\95iT.&BF8:.H[!]9EDot0oamc+uQKLX&Q>^hU'op!M!]8I0]J.Y-KRc;/VYq=W@A3JD\-tT_Pa)RfVGW%3Ibi?l\GA=@P'E0B#jC\6H2>1+rsqBu-Le;NfuXqVa8jdrr.O7A)@:htlBs/jKGh'Mr;FqX%^)SjJTX_@b(nJdTrf0;[do.GW!GC;\Z49P5(K]is94=3Y#;:!1aD%qlZ8+=&jnokZ&BH,S(aX9IScL$4n5eM9"F,(d2*ZGh=\:4NQX$+4mJjKa[s%C.tA@+t8P?5U]FL.V0&DJ5ic2!)E4HlYk#Y`t77XI8s5o.:@FZ4+6fW]dI1gcYg;*MfX&?r8ZY=OP_;S@l#cYtCf@&HRN]'"/`I`?)/;8u3,nqoD8GugLhke^#/)t.$JU's#X`VX&dBWGO/#aVe"%8NML!A=&<_gO&U(:89.4/endstream +endobj +145 0 obj +<< +/Nums [ 0 146 0 R 1 147 0 R 2 148 0 R 3 149 0 R 4 150 0 R + 5 151 0 R 6 152 0 R 7 153 0 R 8 154 0 R 9 155 0 R ] +>> +endobj +146 0 obj +<< +/S /D /St 1 +>> +endobj +147 0 obj +<< +/S /D /St 2 +>> +endobj +148 0 obj +<< +/S /D /St 3 +>> +endobj +149 0 obj +<< +/S /D /St 4 +>> +endobj +150 0 obj +<< +/S /D /St 5 +>> +endobj +151 0 obj +<< +/S /D /St 6 +>> +endobj +152 0 obj +<< +/S /D /St 7 +>> +endobj +153 0 obj +<< +/S /D /St 8 +>> +endobj +154 0 obj +<< +/S /D /St 9 +>> +endobj +155 0 obj +<< +/S /D /St 10 +>> +endobj +xref +0 156 +0000000000 65535 f +0000000061 00000 n +0000000165 00000 n +0000000272 00000 n +0000000381 00000 n +0000000588 00000 n +0000000777 00000 n +0000000974 00000 n +0000001205 00000 n +0000001403 00000 n +0000001571 00000 n +0000001767 00000 n +0000001936 00000 n +0000002136 00000 n +0000002389 00000 n +0000002502 00000 n +0000002670 00000 n +0000002837 00000 n +0000003005 00000 n +0000003172 00000 n +0000003339 00000 n +0000003506 00000 n +0000003674 00000 n +0000003841 00000 n +0000004010 00000 n +0000004178 00000 n +0000004347 00000 n +0000004515 00000 n +0000004684 00000 n +0000004852 00000 n +0000005021 00000 n +0000005189 00000 n +0000005358 00000 n +0000005526 00000 n +0000005694 00000 n +0000005862 00000 n +0000006031 00000 n +0000006199 00000 n +0000006368 00000 n +0000006536 00000 n +0000006705 00000 n +0000006873 00000 n +0000007042 00000 n +0000007210 00000 n +0000007379 00000 n +0000007547 00000 n +0000007715 00000 n +0000007883 00000 n +0000008052 00000 n +0000008220 00000 n +0000008389 00000 n +0000008557 00000 n +0000008726 00000 n +0000008894 00000 n +0000009063 00000 n +0000009231 00000 n +0000009400 00000 n +0000009568 00000 n +0000009737 00000 n +0000009905 00000 n +0000010074 00000 n +0000010242 00000 n +0000010410 00000 n +0000010578 00000 n +0000011146 00000 n +0000011314 00000 n +0000011483 00000 n +0000011652 00000 n +0000011821 00000 n +0000012069 00000 n +0000012269 00000 n +0000012532 00000 n +0000012787 00000 n +0000013021 00000 n +0000013137 00000 n +0000013337 00000 n +0000013537 00000 n +0000013737 00000 n +0000014006 00000 n +0000014206 00000 n +0000014433 00000 n +0000014539 00000 n +0000014708 00000 n +0000014877 00000 n +0000015046 00000 n +0000015215 00000 n +0000015384 00000 n +0000015552 00000 n +0000015814 00000 n +0000015983 00000 n +0000016152 00000 n +0000016321 00000 n +0000016490 00000 n +0000016659 00000 n +0000016828 00000 n +0000017090 00000 n +0000017298 00000 n +0000018073 00000 n +0000037720 00000 n +0000037979 00000 n +0000039300 00000 n +0000040054 00000 n +0000058323 00000 n +0000058595 00000 n +0000059952 00000 n +0000060062 00000 n +0000060434 00000 n +0000060512 00000 n +0000060734 00000 n +0000060925 00000 n +0000061130 00000 n +0000061460 00000 n +0000061661 00000 n +0000061912 00000 n +0000062143 00000 n +0000062389 00000 n +0000062586 00000 n +0000062878 00000 n +0000063141 00000 n +0000063493 00000 n +0000063725 00000 n +0000063941 00000 n +0000064208 00000 n +0000064546 00000 n +0000065018 00000 n +0000065425 00000 n +0000065792 00000 n +0000066049 00000 n +0000066322 00000 n +0000066699 00000 n +0000067047 00000 n +0000067474 00000 n +0000067795 00000 n +0000068112 00000 n +0000068544 00000 n +0000068668 00000 n +0000069962 00000 n +0000071287 00000 n +0000073887 00000 n +0000075439 00000 n +0000078148 00000 n +0000080102 00000 n +0000082692 00000 n +0000085569 00000 n +0000088751 00000 n +0000089610 00000 n +0000089746 00000 n +0000089781 00000 n +0000089816 00000 n +0000089851 00000 n +0000089886 00000 n +0000089921 00000 n +0000089956 00000 n +0000089991 00000 n +0000090026 00000 n +0000090061 00000 n +trailer +<< +/ID +[<0293ba22517b7d9ef96f74d93d387f67><0293ba22517b7d9ef96f74d93d387f67>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 105 0 R +/Root 104 0 R +/Size 156 +>> +startxref +90097 +%%EOF diff --git a/arm_cortex_m33_trm_100230_0100_03_en.pdf b/arm_cortex_m33_trm_100230_0100_03_en.pdf new file mode 100644 index 0000000..a3278c5 Binary files /dev/null and b/arm_cortex_m33_trm_100230_0100_03_en.pdf differ diff --git a/dp.png b/dp.png new file mode 100644 index 0000000..92079cd Binary files /dev/null and b/dp.png differ diff --git a/drivers/0x01_uart_rust/.cargo/config.toml b/drivers/0x01_uart_rust/.cargo/config.toml new file mode 100644 index 0000000..d2f9e8c --- /dev/null +++ b/drivers/0x01_uart_rust/.cargo/config.toml @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2021–2024 The rp-rs Developers +# Copyright (c) 2021 rp-rs organization +# Copyright (c) 2025 Raspberry Pi Ltd. +# +# Cargo Configuration for the https://github.com/rp-rs/rp-hal.git repository. +# +# You might want to make a similar file in your own repository if you are +# writing programs for Raspberry Silicon microcontrollers. +# + +[build] +target = "thumbv8m.main-none-eabihf" +# Set the default target to match the Cortex-M33 in the RP2350 +# target = "thumbv8m.main-none-eabihf" +# target = "thumbv6m-none-eabi" +# target = "riscv32imac-unknown-none-elf" + +# Target specific options +[target.thumbv6m-none-eabi] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as the linker +# script. This is usually provided by the cortex-m-rt crate, and by default +# the version in that crate will include a file called `memory.x` which +# describes the particular memory layout for your specific chip. +# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't +# have SIMD) +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "picotool load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the hard-float ABI for Arm mode. +# +# The FPU is enabled by default, and float function arguments use FPU +# registers. +[target.thumbv8m.main-none-eabihf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as a linker script. +# This is usually provided by the cortex-m-rt crate, and by default the +# version in that crate will include a file called `memory.x` which describes +# the particular memory layout for your specific chip. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "picotool load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the soft-float ABI for RISC-V mode. +# +# Hazard 3 does not have an FPU and so float function arguments use integer +# registers. +[target.riscv32imac-unknown-none-elf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Trp235x_riscv.x also tells the linker to use +# `rp235x_riscv.x` as a linker script. This adds in RP2350 RISC-V specific +# things that the riscv-rt crate's `link.x` requires and then includes +# `link.x` automatically. This is the reverse of how we do it on Cortex-M. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp2350_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "picotool load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +[env] +DEFMT_LOG = "debug" diff --git a/drivers/0x01_uart_rust/.gitignore b/drivers/0x01_uart_rust/.gitignore new file mode 100644 index 0000000..03381c1 --- /dev/null +++ b/drivers/0x01_uart_rust/.gitignore @@ -0,0 +1,113 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,macos,windows,linux + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux diff --git a/drivers/0x01_uart_rust/.pico-rs b/drivers/0x01_uart_rust/.pico-rs new file mode 100644 index 0000000..1b6702a --- /dev/null +++ b/drivers/0x01_uart_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 \ No newline at end of file diff --git a/drivers/0x01_uart_rust/.vscode/extensions.json b/drivers/0x01_uart_rust/.vscode/extensions.json new file mode 100644 index 0000000..5f2fd0c --- /dev/null +++ b/drivers/0x01_uart_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/drivers/0x01_uart_rust/.vscode/launch.json b/drivers/0x01_uart_rust/.vscode/launch.json new file mode 100644 index 0000000..0bc38c3 --- /dev/null +++ b/drivers/0x01_uart_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} \ No newline at end of file diff --git a/drivers/0x01_uart_rust/.vscode/settings.json b/drivers/0x01_uart_rust/.vscode/settings.json new file mode 100644 index 0000000..b03cd57 --- /dev/null +++ b/drivers/0x01_uart_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} \ No newline at end of file diff --git a/drivers/0x01_uart_rust/.vscode/tasks.json b/drivers/0x01_uart_rust/.vscode/tasks.json new file mode 100644 index 0000000..4194af4 --- /dev/null +++ b/drivers/0x01_uart_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/drivers/0x01_uart_rust/Cargo.toml b/drivers/0x01_uart_rust/Cargo.toml new file mode 100644 index 0000000..9b42e9c --- /dev/null +++ b/drivers/0x01_uart_rust/Cargo.toml @@ -0,0 +1,79 @@ + +[package] +edition = "2024" +name = "uart" +version = "0.1.0" +license = "MIT or Apache-2.0" + + +[lib] +name = "uart_lib" +path = "src/lib.rs" + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embedded-hal = "1.0.0" +embedded-hal-nb = "1.0.0" +nb = "1.1.0" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = 'fat' +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = 'fat' +opt-level = 3 diff --git a/drivers/0x01_uart_rust/LICENSE-APACHE b/drivers/0x01_uart_rust/LICENSE-APACHE new file mode 100644 index 0000000..8d99cbc --- /dev/null +++ b/drivers/0x01_uart_rust/LICENSE-APACHE @@ -0,0 +1,204 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2021–2024 The rp-rs Developers + Copyright (c) 2021 rp-rs organization + Copyright (c) 2025 Raspberry Pi Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/drivers/0x01_uart_rust/LICENSE-MIT b/drivers/0x01_uart_rust/LICENSE-MIT new file mode 100644 index 0000000..5369c70 --- /dev/null +++ b/drivers/0x01_uart_rust/LICENSE-MIT @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2021–2024 The rp-rs Developers +Copyright (c) 2021 rp-rs organization +Copyright (c) 2025 Raspberry Pi Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + \ No newline at end of file diff --git a/drivers/0x01_uart_rust/README.md b/drivers/0x01_uart_rust/README.md new file mode 100644 index 0000000..f96f22a --- /dev/null +++ b/drivers/0x01_uart_rust/README.md @@ -0,0 +1,115 @@ +# 0x01 UART Rust Driver + +This repository contains a Bare-Metal Rust driver for the UART (Universal Asynchronous Receiver-Transmitter) peripheral on the **RP2350** (and RP2040) microcontrollers. + +It includes: +- A thin demo (`src/main.rs`) that runs an uppercase echo server over UART. +- A reusable library module (`src/uart.rs`) providing a hardware-agnostic `UartDriver`. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the UART driver logic is separated into a reusable library, you can run the unit tests natively on your computer (no microcontroller required!). + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we need to wake up the specific hardware subsystems we want to use (Clocks, Pins, and the UART peripheral). + +* **`run(...)`**: The master setup function. It sequentially calls the helper initialization functions below, and then kicks off the echo server. +* **`init_clocks(...)`**: Wakes up the external 12 MHz crystal (`XOSC`) and configures the PLLs (Phase-Locked Loops) to drive the system clock at its maximum speed. +* **`init_uart_pins(...)`**: Takes control of physical pins `GPIO0` and `GPIO1` and configures them specifically for UART TX (Transmit) and RX (Receive) mode. +* **`init_uart_peri(...)`**: Configures the hardware `UART0` peripheral to operate at a standard `115200` baud rate with an `8N1` configuration (8 data bits, no parity, 1 stop bit). +* **`start_echo_server(...)`**: Wraps the hardware UART peripheral in our custom `UartDriver`, prints a "UART ready" welcome message to the serial console, and jumps into the `echo_loop`. +* **`echo_loop(...)`**: An infinite `loop { ... }` that waits for a character to arrive, reads it, and immediately transmits the uppercase version back. + +### 3. The Reusable UART Driver (`src/uart.rs`) +The hardware-specific logic from `board.rs` uses the `UartDriver` struct to abstract away the messy details of sending and receiving bytes. + +* **`UartDriver::init(...)`**: Creates a new instance of our driver, taking ownership of the hardware UART peripheral. +* **`getchar(&mut self)`**: A blocking function that waits until a byte physically arrives over the RX wire, reads it out of the hardware buffer, and returns it. +* **`putchar(&mut self, c: u8)`**: A blocking function that waits until the TX wire is ready, and then writes a single byte into the hardware buffer to be transmitted. +* **`puts(&mut self, s: &[u8])`**: A convenience function that takes an array of characters (a string) and transmits them one-by-one by repeatedly calling `putchar()`. +* **`to_upper(c: u8)`**: A helper function that takes an ASCII byte and uses Rust's built-in `.to_ascii_uppercase()` to convert it to an uppercase letter. diff --git a/drivers/0x01_uart_rust/build.rs b/drivers/0x01_uart_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x01_uart_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x01_uart_rust/rp2040.x b/drivers/0x01_uart_rust/rp2040.x new file mode 100644 index 0000000..0cc665b --- /dev/null +++ b/drivers/0x01_uart_rust/rp2040.x @@ -0,0 +1,91 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + /* + * Here we assume you have 2048 KiB of Flash. This is what the Pi Pico + * has, but your board may have more or less Flash and you should adjust + * this value to suit. + */ + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + /* + * RAM consists of 4 banks, SRAM0-SRAM3, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 256K + /* + * RAM banks 4 and 5 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k + + /* SRAM banks 0-3 can also be accessed directly. However, those ranges + alias with the RAM mapping, above. So don't use them at the same time! + SRAM0 : ORIGIN = 0x21000000, LENGTH = 64k + SRAM1 : ORIGIN = 0x21010000, LENGTH = 64k + SRAM2 : ORIGIN = 0x21020000, LENGTH = 64k + SRAM3 : ORIGIN = 0x21030000, LENGTH = 64k + */ +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + /* ### Boot loader + * + * An executable block of code which sets up the QSPI interface for + * 'Execute-In-Place' (or XIP) mode. Also sends chip-specific commands to + * the external flash chip. + * + * Must go at the start of external flash, where the Boot ROM expects it. + */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 512 bytes of flash, + * where picotool can find it + */ + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; diff --git a/drivers/0x01_uart_rust/rp2350.x b/drivers/0x01_uart_rust/rp2350.x new file mode 100644 index 0000000..bda94d8 --- /dev/null +++ b/drivers/0x01_uart_rust/rp2350.x @@ -0,0 +1,83 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + /* move .text to start /after/ the boot info */ + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + \ No newline at end of file diff --git a/drivers/0x01_uart_rust/rp2350_riscv.x b/drivers/0x01_uart_rust/rp2350_riscv.x new file mode 100644 index 0000000..84388aa --- /dev/null +++ b/drivers/0x01_uart_rust/rp2350_riscv.x @@ -0,0 +1,259 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut _heap_size }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all `32`-byte aligned. These alignments are assumed by the RAM + initialization routine. There's also a second benefit: `32`-byte aligned boundaries + means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ +PROVIDE(_setup_interrupts = default_setup_interrupts); + +/* # Multi-processing hook function + fn _mp_hook() -> bool; + + This function is called from all the harts and must return true only for one hart, + which will perform memory initialization. For other harts it must return false + and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). +*/ +PROVIDE(_mp_hook = default_mp_hook); + +/* # Start trap function override + By default uses the riscv crates default trap handler + but by providing the `_start_trap` symbol external crates can override. +*/ +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + /* This section is intended to make _stext address work */ + . = ABSOLUTE(_stext); + } > FLASH + + .text _stext : + { + /* Put reset handler first in .text section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + __start_block_addr = .; + KEEP(*(.start_block)); + . = ALIGN(4); + *(.trap); + *(.trap.rust); + *(.text.abort); + *(.text .text.*); + . = ALIGN(4); + } > FLASH + + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + + .rodata : ALIGN(4) + { + *(.srodata .srodata.*); + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + } > FLASH + + .data : ALIGN(32) + { + _sidata = LOADADDR(.data); + __sidata = LOADADDR(.data); + _sdata = .; + __sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + . = ALIGN(32); + _edata = .; + __edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : ALIGN(32) + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(32); + _ebss = .; + } > RAM + + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + _sheap = .; + . += _heap_size; + . = ALIGN(4); + _eheap = .; + } > RAM + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + _estack = .; + . = ABSOLUTE(_stack_start); + _sstack = .; + } > RAM + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + .eh_frame (INFO) : { KEEP(*(.eh_frame)) } + .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } +} + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(FLASH) % 4 == 0, " +ERROR(riscv-rt): the start of the FLASH must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 32 == 0, " +ERROR(riscv-rt): the start of the RAM must be 32-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 32 == 0 && _edata % 32 == 0, " +BUG(riscv-rt): .data is not 32-byte aligned"); + +ASSERT(_sidata % 32 == 0, " +BUG(riscv-rt): the LMA of .data is not 32-byte aligned"); + +ASSERT(_sbss % 32 == 0 && _ebss % 32 == 0, " +BUG(riscv-rt): .bss is not 32-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), " +ERROR(riscv-rt): The .text section must be placed inside the FLASH region. +Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'"); + +ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +/* Do not exceed this mark in the error messages above | */ diff --git a/drivers/0x01_uart_rust/src/board.rs b/drivers/0x01_uart_rust/src/board.rs new file mode 100644 index 0000000..ea52f00 --- /dev/null +++ b/drivers/0x01_uart_rust/src/board.rs @@ -0,0 +1,160 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import rp235x_hal as hal +#[cfg(rp2350)] +use rp235x_hal as hal; +// Import rp2040_hal as hal +#[cfg(rp2040)] +use rp2040_hal as hal; +// Import fugit::RateExtU32 +use fugit::RateExtU32; +// Import hal::Clock +use hal::Clock; +// Import uart_lib::uart +use uart_lib::uart; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// UART TX pin type alias. +pub type UartTx = hal::gpio::Pin; +/// UART RX pin type alias. +pub type UartRx = hal::gpio::Pin; +/// UART Pins tuple. +pub type UartPins = (UartTx, UartRx); + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +/// +/// # Arguments +/// +/// * `x` - XOSC peripheral. +/// * `c` - CLOCKS peripheral. +/// * `ps` - PLL_SYS peripheral. +/// * `pu` - PLL_USB peripheral. +/// * `r` - Mutable reference to RESETS. +/// * `w` - Mutable reference to Watchdog. +/// +/// # Returns +/// +/// Configured clocks manager. +pub(crate) fn init_clocks( + x: hal::pac::XOSC, c: hal::pac::CLOCKS, ps: hal::pac::PLL_SYS, pu: hal::pac::PLL_USB, + r: &mut hal::pac::RESETS, w: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls(XTAL_FREQ_HZ, x, c, ps, pu, r, w).unwrap() +} + +/// Unlock the GPIO bank and return configured UART pins. +/// +/// # Arguments +/// +/// * `io` - IO_BANK0 peripheral. +/// * `pads` - PADS_BANK0 peripheral. +/// * `sio` - SIO peripheral. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// +/// # Returns +/// +/// Tuple of (TX, RX) pins configured for UART. +pub(crate) fn init_uart_pins( + io: hal::pac::IO_BANK0, pads: hal::pac::PADS_BANK0, sio: hal::pac::SIO, + resets: &mut hal::pac::RESETS, +) -> UartPins { + let p = hal::gpio::Pins::new(io, pads, hal::Sio::new(sio).gpio_bank0, resets); + (p.gpio0.into_pull_type().into_function(), p.gpio1.into_pull_type().into_function()) +} + +/// Initialises the UART peripheral. +/// +/// # Arguments +/// +/// * `u0` - UART0 peripheral. +/// * `pins` - Configured UART pins. +/// * `r` - Mutable reference to RESETS. +/// * `freq` - Peripheral clock frequency. +/// +/// # Returns +/// +/// Enabled UART peripheral. +pub(crate) fn init_uart_peri( + u0: hal::pac::UART0, pins: UartPins, r: &mut hal::pac::RESETS, freq: fugit::HertzU32, +) -> hal::uart::UartPeripheral { + let cfg = hal::uart::UartConfig::new(UART_BAUD.Hz(), hal::uart::DataBits::Eight, None, hal::uart::StopBits::One); + hal::uart::UartPeripheral::new(u0, pins, r).enable(cfg, freq).unwrap() +} + +/// Starts the UART driver and echo server. +/// +/// # Arguments +/// +/// * `u` - Enabled UART peripheral. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn start_echo_server( + u: hal::uart::UartPeripheral, +) -> ! { + let mut drv = uart::UartDriver::init(u); + drv.puts(b"UART ready\r\n"); + echo_loop(&mut drv) +} + +/// Initialise all peripherals and run the UART echo demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut pac: hal::pac::Peripherals) -> ! { + let mut wdg = hal::Watchdog::new(pac.WATCHDOG); + let clk = init_clocks(pac.XOSC, pac.CLOCKS, pac.PLL_SYS, pac.PLL_USB, &mut pac.RESETS, &mut wdg); + let pins = init_uart_pins(pac.IO_BANK0, pac.PADS_BANK0, pac.SIO, &mut pac.RESETS); + let u = init_uart_peri(pac.UART0, pins, &mut pac.RESETS, clk.peripheral_clock.freq()); + start_echo_server(u) +} + +/// Run the uppercase echo loop forever. +/// +/// # Arguments +/// +/// * `drv` - Mutable reference to the UART driver. +fn echo_loop + embedded_hal_nb::serial::Write>( + drv: &mut uart::UartDriver, +) -> ! { + loop { + let c = drv.getchar(); + drv.putchar(uart::UartDriver::::to_upper(c)); + } +} diff --git a/drivers/0x01_uart_rust/src/lib.rs b/drivers/0x01_uart_rust/src/lib.rs new file mode 100644 index 0000000..33cba98 --- /dev/null +++ b/drivers/0x01_uart_rust/src/lib.rs @@ -0,0 +1,6 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +#![cfg_attr(not(test), no_std)] +pub mod uart; diff --git a/drivers/0x01_uart_rust/src/main.rs b/drivers/0x01_uart_rust/src/main.rs new file mode 100644 index 0000000..c0a53c0 --- /dev/null +++ b/drivers/0x01_uart_rust/src/main.rs @@ -0,0 +1,101 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; + +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the UART uppercase echo demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"UART Uppercase Echo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/drivers/0x01_uart_rust/src/uart.rs b/drivers/0x01_uart_rust/src/uart.rs new file mode 100644 index 0000000..d192502 --- /dev/null +++ b/drivers/0x01_uart_rust/src/uart.rs @@ -0,0 +1,257 @@ +//! Implementation module +//! +//! **File:** `uart.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Embedded HAL traits for UART +use embedded_hal_nb::serial::{Read, Write}; +// Non-blocking block macro +use nb::block; + +/// Generic UART driver that owns a serial peripheral. +pub struct UartDriver + Write> { + /// The underlying UART peripheral. + uart: T, +} + +impl + Write> UartDriver { + /// Initialize UART driver with a configured peripheral. + /// + /// # Arguments + /// + /// * `uart` - The hardware UART or mock peripheral. + /// + /// # Returns + /// + /// A new UartDriver instance. + /// + /// # Arguments + /// + /// * `uart` - The `uart` parameter. + /// + /// # Returns + /// + /// A new instance of the struct. + #[inline] + pub fn init(uart: T) -> Self { + Self { uart } + } + + /// Read one character from UART (blocking). + /// + /// Blocks until a byte arrives, then returns it. + /// + /// # Returns + /// + /// The received byte. + /// + /// # Returns + /// + /// An 8-bit unsigned integer value. + #[inline] + pub fn getchar(&mut self) -> u8 { + block!(self.uart.read()).unwrap() + } + + /// Transmit one character over UART (blocking). + /// + /// Waits until there is space, then transmits. + /// + /// # Arguments + /// + /// * `c` - Byte to transmit. + /// + /// # Arguments + /// + /// * `c` - Command byte. + #[inline] + pub fn putchar(&mut self, c: u8) { + block!(self.uart.write(c)).unwrap(); + } + + /// Transmit a null-terminated string over UART. + /// + /// Sends every byte in the slice sequentially. + /// + /// # Arguments + /// + /// * `s` - Byte slice to transmit. + /// + /// # Arguments + /// + /// * `s` - The `s` parameter. + #[inline] + pub fn puts(&mut self, s: &[u8]) { + for &c in s { + self.putchar(c); + } + } + + /// Convert a lowercase ASCII character to uppercase. + /// + /// Returns the uppercase equivalent if the character is in `b'a'`–`b'z'`. + /// + /// # Arguments + /// + /// * `c` - Input byte. + /// + /// # Returns + /// + /// Uppercase equivalent, or the original byte. + /// + /// # Arguments + /// + /// * `c` - Command byte. + /// + /// # Returns + /// + /// An 8-bit unsigned integer value. + #[inline] + pub fn to_upper(c: u8) -> u8 { + c.to_ascii_uppercase() + } +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + // Import dependencies from embedded_hal_nb::serial + use embedded_hal_nb::serial::{ErrorType, Read, Write}; + + struct MockUart { + rx_buf: std::collections::VecDeque, + tx_buf: std::collections::VecDeque, + } + + impl MockUart { + /// Creates a new hardware wrapper instance. + /// + /// # Returns + /// + /// A new instance of the struct. + fn new() -> Self { + Self { + rx_buf: std::collections::VecDeque::new(), + tx_buf: std::collections::VecDeque::new(), + } + } + } + + impl ErrorType for MockUart { + type Error = core::convert::Infallible; + } + + impl Read for MockUart { + /// Executes the read operation. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn read(&mut self) -> nb::Result { + if let Some(c) = self.rx_buf.pop_front() { + Ok(c) + } else { + Err(nb::Error::WouldBlock) + } + } + } + + impl Write for MockUart { + /// Executes the write operation. + /// + /// # Arguments + /// + /// * `c` - Command byte. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn write(&mut self, c: u8) -> nb::Result<(), Self::Error> { + self.tx_buf.push_back(c); + Ok(()) + } + + /// Executes the flush operation. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn flush(&mut self) -> nb::Result<(), Self::Error> { + Ok(()) + } + } + + /// Executes the to upper lowercase a operation. + #[test] + fn to_upper_lowercase_a() { + assert_eq!(UartDriver::::to_upper(b'a'), b'A'); + } + + /// Executes the to upper lowercase z operation. + #[test] + fn to_upper_lowercase_z() { + assert_eq!(UartDriver::::to_upper(b'z'), b'Z'); + } + + /// Executes the to upper already uppercase operation. + #[test] + fn to_upper_already_uppercase() { + assert_eq!(UartDriver::::to_upper(b'A'), b'A'); + } + + /// Executes the to upper digit unchanged operation. + #[test] + fn to_upper_digit_unchanged() { + assert_eq!(UartDriver::::to_upper(b'5'), b'5'); + } + + /// Executes the getchar reads from buffer operation. + #[test] + fn getchar_reads_from_buffer() { + let mut mock = MockUart::new(); + mock.rx_buf.push_back(b'x'); + let mut drv = UartDriver::init(mock); + assert_eq!(drv.getchar(), b'x'); + } + + /// Executes the putchar writes to buffer operation. + #[test] + fn putchar_writes_to_buffer() { + let mut drv = UartDriver::init(MockUart::new()); + drv.putchar(b'y'); + assert_eq!(drv.uart.tx_buf.pop_front(), Some(b'y')); + } + + /// Executes the puts writes all chars operation. + #[test] + fn puts_writes_all_chars() { + let mut drv = UartDriver::init(MockUart::new()); + drv.puts(b"Hi"); + assert_eq!(drv.uart.tx_buf.pop_front(), Some(b'H')); + assert_eq!(drv.uart.tx_buf.pop_front(), Some(b'i')); + } +} diff --git a/drivers/0x02_blink_rust/.cargo/config.toml b/drivers/0x02_blink_rust/.cargo/config.toml new file mode 100644 index 0000000..1791d74 --- /dev/null +++ b/drivers/0x02_blink_rust/.cargo/config.toml @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2021–2024 The rp-rs Developers +# Copyright (c) 2021 rp-rs organization +# Copyright (c) 2025 Raspberry Pi Ltd. +# +# Cargo Configuration for the https://github.com/rp-rs/rp-hal.git repository. +# +# You might want to make a similar file in your own repository if you are +# writing programs for Raspberry Silicon microcontrollers. +# + +[build] +target = "thumbv8m.main-none-eabihf" +# Set the default target to match the Cortex-M33 in the RP2350 +# target = "thumbv8m.main-none-eabihf" +# target = "thumbv6m-none-eabi" +# target = "riscv32imac-unknown-none-elf" + +# Target specific options +[target.thumbv6m-none-eabi] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as the linker +# script. This is usually provided by the cortex-m-rt crate, and by default +# the version in that crate will include a file called `memory.x` which +# describes the particular memory layout for your specific chip. +# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't +# have SIMD) +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the hard-float ABI for Arm mode. +# +# The FPU is enabled by default, and float function arguments use FPU +# registers. +[target.thumbv8m.main-none-eabihf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as a linker script. +# This is usually provided by the cortex-m-rt crate, and by default the +# version in that crate will include a file called `memory.x` which describes +# the particular memory layout for your specific chip. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the soft-float ABI for RISC-V mode. +# +# Hazard 3 does not have an FPU and so float function arguments use integer +# registers. +[target.riscv32imac-unknown-none-elf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Trp235x_riscv.x also tells the linker to use +# `rp235x_riscv.x` as a linker script. This adds in RP2350 RISC-V specific +# things that the riscv-rt crate's `link.x` requires and then includes +# `link.x` automatically. This is the reverse of how we do it on Cortex-M. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp2350_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +[env] +DEFMT_LOG = "debug" diff --git a/drivers/0x02_blink_rust/.gitignore b/drivers/0x02_blink_rust/.gitignore new file mode 100644 index 0000000..03381c1 --- /dev/null +++ b/drivers/0x02_blink_rust/.gitignore @@ -0,0 +1,113 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,macos,windows,linux + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux diff --git a/drivers/0x02_blink_rust/.pico-rs b/drivers/0x02_blink_rust/.pico-rs new file mode 100644 index 0000000..1b6702a --- /dev/null +++ b/drivers/0x02_blink_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 \ No newline at end of file diff --git a/drivers/0x02_blink_rust/.vscode/extensions.json b/drivers/0x02_blink_rust/.vscode/extensions.json new file mode 100644 index 0000000..5f2fd0c --- /dev/null +++ b/drivers/0x02_blink_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/drivers/0x02_blink_rust/.vscode/launch.json b/drivers/0x02_blink_rust/.vscode/launch.json new file mode 100644 index 0000000..0bc38c3 --- /dev/null +++ b/drivers/0x02_blink_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} \ No newline at end of file diff --git a/drivers/0x02_blink_rust/.vscode/settings.json b/drivers/0x02_blink_rust/.vscode/settings.json new file mode 100644 index 0000000..b03cd57 --- /dev/null +++ b/drivers/0x02_blink_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} \ No newline at end of file diff --git a/drivers/0x02_blink_rust/.vscode/tasks.json b/drivers/0x02_blink_rust/.vscode/tasks.json new file mode 100644 index 0000000..4194af4 --- /dev/null +++ b/drivers/0x02_blink_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/drivers/0x02_blink_rust/Cargo.toml b/drivers/0x02_blink_rust/Cargo.toml new file mode 100644 index 0000000..f3e829f --- /dev/null +++ b/drivers/0x02_blink_rust/Cargo.toml @@ -0,0 +1,81 @@ + +[package] +edition = "2024" +name = "blink" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "blink_lib" +path = "src/lib.rs" + +[[bin]] +name = "blink" +path = "src/main.rs" + + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embedded-hal = "1.0.0" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 3 diff --git a/drivers/0x02_blink_rust/LICENSE-APACHE b/drivers/0x02_blink_rust/LICENSE-APACHE new file mode 100644 index 0000000..8d99cbc --- /dev/null +++ b/drivers/0x02_blink_rust/LICENSE-APACHE @@ -0,0 +1,204 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2021–2024 The rp-rs Developers + Copyright (c) 2021 rp-rs organization + Copyright (c) 2025 Raspberry Pi Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/drivers/0x02_blink_rust/LICENSE-MIT b/drivers/0x02_blink_rust/LICENSE-MIT new file mode 100644 index 0000000..5369c70 --- /dev/null +++ b/drivers/0x02_blink_rust/LICENSE-MIT @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2021–2024 The rp-rs Developers +Copyright (c) 2021 rp-rs organization +Copyright (c) 2025 Raspberry Pi Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + \ No newline at end of file diff --git a/drivers/0x02_blink_rust/README.md b/drivers/0x02_blink_rust/README.md new file mode 100644 index 0000000..c3e1181 --- /dev/null +++ b/drivers/0x02_blink_rust/README.md @@ -0,0 +1,116 @@ +# 0x02 Blink Rust Driver + +This repository contains a Bare-Metal Rust driver for blinking an LED using the GPIO peripheral on the **RP2350** (and RP2040) microcontrollers. + +It includes: +- A thin demo (`src/main.rs`) that blinks an LED and prints the state over UART. +- A reusable library module (`src/blink.rs`) providing a hardware-agnostic `BlinkDriver`. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the Blink driver logic is separated into a reusable library with hardware mocked using traits, you can run the unit tests natively on your computer (no microcontroller required!). + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we need to wake up the specific hardware subsystems we want to use (Clocks, Pins, UART, and SysTick). + +* **`run(...)`**: The master setup function. It sequentially calls the helper initialization functions below, and then kicks off the blink loop. +* **`init_clocks(...)`**: Wakes up the external 12 MHz crystal (`XOSC`) and configures the PLLs (Phase-Locked Loops) to drive the system clock at its maximum speed. +* **`init_pins(...)`**: Takes control of physical pins across `IO_BANK0`. +* **`init_uart(...)`**: Configures the hardware `UART0` peripheral to operate at a standard `115200` baud rate with an `8N1` configuration for debug printing. +* **`init_delay(...)`**: Captures the ARM Cortex-M `SYST` (SysTick) peripheral to create a blocking delay timer. +* **`start_blink(...)`**: Wraps the hardware GPIO 25 pin in our custom `BlinkDriver`, prints a "Blink driver initialized" welcome message to the serial console, and jumps into the `blink_loop`. +* **`blink_loop(...)`**: An infinite `loop { ... }` that toggles the LED, reads back the state, prints the state over UART, and delays for 500ms. + +### 3. The Reusable Blink Driver (`src/blink.rs`) +The hardware-specific logic from `board.rs` uses the `BlinkDriver` struct to abstract away the messy details of toggling digital pins. + +* **`BlinkDriver::init(...)`**: Creates a new instance of our driver, taking ownership of the hardware GPIO pin and guaranteeing it starts in a LOW (off) state. +* **`on(&mut self)`**: Drives the GPIO pin HIGH, turning on the LED. +* **`off(&mut self)`**: Drives the GPIO pin LOW, turning off the LED. +* **`toggle(&mut self)`**: Reads the current state of the pin and aggressively flips it. +* **`get_state(&mut self)`**: Reads the hardware register to determine if the pin is currently being driven HIGH or LOW. diff --git a/drivers/0x02_blink_rust/build.rs b/drivers/0x02_blink_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x02_blink_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x02_blink_rust/rp2040.x b/drivers/0x02_blink_rust/rp2040.x new file mode 100644 index 0000000..0cc665b --- /dev/null +++ b/drivers/0x02_blink_rust/rp2040.x @@ -0,0 +1,91 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + /* + * Here we assume you have 2048 KiB of Flash. This is what the Pi Pico + * has, but your board may have more or less Flash and you should adjust + * this value to suit. + */ + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + /* + * RAM consists of 4 banks, SRAM0-SRAM3, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 256K + /* + * RAM banks 4 and 5 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k + + /* SRAM banks 0-3 can also be accessed directly. However, those ranges + alias with the RAM mapping, above. So don't use them at the same time! + SRAM0 : ORIGIN = 0x21000000, LENGTH = 64k + SRAM1 : ORIGIN = 0x21010000, LENGTH = 64k + SRAM2 : ORIGIN = 0x21020000, LENGTH = 64k + SRAM3 : ORIGIN = 0x21030000, LENGTH = 64k + */ +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + /* ### Boot loader + * + * An executable block of code which sets up the QSPI interface for + * 'Execute-In-Place' (or XIP) mode. Also sends chip-specific commands to + * the external flash chip. + * + * Must go at the start of external flash, where the Boot ROM expects it. + */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 512 bytes of flash, + * where picotool can find it + */ + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; diff --git a/drivers/0x02_blink_rust/rp2350.x b/drivers/0x02_blink_rust/rp2350.x new file mode 100644 index 0000000..bda94d8 --- /dev/null +++ b/drivers/0x02_blink_rust/rp2350.x @@ -0,0 +1,83 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + /* move .text to start /after/ the boot info */ + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + \ No newline at end of file diff --git a/drivers/0x02_blink_rust/rp2350_riscv.x b/drivers/0x02_blink_rust/rp2350_riscv.x new file mode 100644 index 0000000..84388aa --- /dev/null +++ b/drivers/0x02_blink_rust/rp2350_riscv.x @@ -0,0 +1,259 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut _heap_size }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all `32`-byte aligned. These alignments are assumed by the RAM + initialization routine. There's also a second benefit: `32`-byte aligned boundaries + means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ +PROVIDE(_setup_interrupts = default_setup_interrupts); + +/* # Multi-processing hook function + fn _mp_hook() -> bool; + + This function is called from all the harts and must return true only for one hart, + which will perform memory initialization. For other harts it must return false + and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). +*/ +PROVIDE(_mp_hook = default_mp_hook); + +/* # Start trap function override + By default uses the riscv crates default trap handler + but by providing the `_start_trap` symbol external crates can override. +*/ +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + /* This section is intended to make _stext address work */ + . = ABSOLUTE(_stext); + } > FLASH + + .text _stext : + { + /* Put reset handler first in .text section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + __start_block_addr = .; + KEEP(*(.start_block)); + . = ALIGN(4); + *(.trap); + *(.trap.rust); + *(.text.abort); + *(.text .text.*); + . = ALIGN(4); + } > FLASH + + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + + .rodata : ALIGN(4) + { + *(.srodata .srodata.*); + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + } > FLASH + + .data : ALIGN(32) + { + _sidata = LOADADDR(.data); + __sidata = LOADADDR(.data); + _sdata = .; + __sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + . = ALIGN(32); + _edata = .; + __edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : ALIGN(32) + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(32); + _ebss = .; + } > RAM + + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + _sheap = .; + . += _heap_size; + . = ALIGN(4); + _eheap = .; + } > RAM + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + _estack = .; + . = ABSOLUTE(_stack_start); + _sstack = .; + } > RAM + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + .eh_frame (INFO) : { KEEP(*(.eh_frame)) } + .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } +} + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(FLASH) % 4 == 0, " +ERROR(riscv-rt): the start of the FLASH must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 32 == 0, " +ERROR(riscv-rt): the start of the RAM must be 32-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 32 == 0 && _edata % 32 == 0, " +BUG(riscv-rt): .data is not 32-byte aligned"); + +ASSERT(_sidata % 32 == 0, " +BUG(riscv-rt): the LMA of .data is not 32-byte aligned"); + +ASSERT(_sbss % 32 == 0 && _ebss % 32 == 0, " +BUG(riscv-rt): .bss is not 32-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), " +ERROR(riscv-rt): The .text section must be placed inside the FLASH region. +Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'"); + +ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +/* Do not exceed this mark in the error messages above | */ diff --git a/drivers/0x02_blink_rust/src/blink.rs b/drivers/0x02_blink_rust/src/blink.rs new file mode 100644 index 0000000..a7b5d8e --- /dev/null +++ b/drivers/0x02_blink_rust/src/blink.rs @@ -0,0 +1,257 @@ +//! Implementation module +//! +//! **File:** `blink.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Embedded HAL digital output traits for GPIO pin control +use embedded_hal::digital::{OutputPin, StatefulOutputPin}; + +/// GPIO output / LED blink driver that owns a single output pin. +pub struct BlinkDriver { + pin: P, +} + +impl BlinkDriver

{ + /// Initialize a GPIO pin as a push-pull digital output. + /// + /// The initial drive level is low (LED off). + /// + /// # Arguments + /// + /// * `pin` - GPIO pin configured as a digital output. + /// + /// # Returns + /// + /// A new BlinkDriver instance owning the configured pin. + /// + /// # Arguments + /// + /// * `pin` - GPIO pin. + /// + /// # Returns + /// + /// A new instance of the struct. + #[inline] + pub fn init(mut pin: P) -> Self { + pin.set_low().unwrap(); + Self { pin } + } + + /// Drive the output pin high (LED on). + /// + /// # Arguments + /// + #[inline] + pub fn on(&mut self) { + self.pin.set_high().unwrap(); + } + + /// Drive the output pin low (LED off). + /// + /// # Arguments + /// + #[inline] + pub fn off(&mut self) { + self.pin.set_low().unwrap(); + } + + /// Toggle the current state of the output pin. + /// + /// Reads the current GPIO output level and inverts it. If the LED was on + /// it is turned off, and vice versa. + /// + /// # Arguments + /// + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle().unwrap(); + } + + /// Query the current drive state of the output pin. + /// + /// # Arguments + /// + /// + /// # Returns + /// + /// `true` if the pin is driven high, `false` if driven low. + /// + /// # Returns + /// + /// `true` if successful or set, `false` otherwise. + #[inline] + pub fn get_state(&mut self) -> bool { + self.pin.is_set_high().unwrap() + } +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + // Infallible error type for mock pin implementations + use core::convert::Infallible; + // Error type trait for mock pin implementations + use embedded_hal::digital::ErrorType; + + struct MockPin { + state: bool, + } + + impl MockPin { + /// Creates a new hardware wrapper instance. + /// + /// # Returns + /// + /// A new instance of the struct. + fn new() -> Self { + Self { state: false } + } + } + + impl ErrorType for MockPin { + type Error = Infallible; + } + + impl OutputPin for MockPin { + /// Executes the set low operation. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn set_low(&mut self) -> Result<(), Self::Error> { + self.state = false; + Ok(()) + } + + /// Executes the set high operation. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn set_high(&mut self) -> Result<(), Self::Error> { + self.state = true; + Ok(()) + } + } + + impl StatefulOutputPin for MockPin { + /// Executes the is set high operation. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn is_set_high(&mut self) -> Result { + Ok(self.state) + } + + /// Executes the is set low operation. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn is_set_low(&mut self) -> Result { + Ok(!self.state) + } + + /// Executes the toggle operation. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn toggle(&mut self) -> Result<(), Self::Error> { + self.state = !self.state; + Ok(()) + } + } + + /// Executes the init starts low operation. + #[test] + fn init_starts_low() { + let drv = BlinkDriver::init(MockPin::new()); + assert!(!drv.pin.state); + } + + /// Executes the on sets high operation. + #[test] + fn on_sets_high() { + let mut drv = BlinkDriver::init(MockPin::new()); + drv.on(); + assert!(drv.pin.state); + } + + /// Executes the off sets low operation. + #[test] + fn off_sets_low() { + let mut drv = BlinkDriver::init(MockPin::new()); + drv.on(); + drv.off(); + assert!(!drv.pin.state); + } + + /// Executes the toggle from low goes high operation. + #[test] + fn toggle_from_low_goes_high() { + let mut drv = BlinkDriver::init(MockPin::new()); + drv.toggle(); + assert!(drv.pin.state); + } + + /// Executes the toggle from high goes low operation. + #[test] + fn toggle_from_high_goes_low() { + let mut drv = BlinkDriver::init(MockPin::new()); + drv.on(); + drv.toggle(); + assert!(!drv.pin.state); + } + + /// Executes the get state reflects on operation. + #[test] + fn get_state_reflects_on() { + let mut drv = BlinkDriver::init(MockPin::new()); + drv.on(); + assert!(drv.get_state()); + } + + /// Executes the get state reflects off operation. + #[test] + fn get_state_reflects_off() { + let mut drv = BlinkDriver::init(MockPin::new()); + drv.on(); + drv.off(); + assert!(!drv.get_state()); + } + + /// Executes the double toggle returns to original operation. + #[test] + fn double_toggle_returns_to_original() { + let mut drv = BlinkDriver::init(MockPin::new()); + drv.toggle(); + drv.toggle(); + assert!(!drv.get_state()); + } +} diff --git a/drivers/0x02_blink_rust/src/board.rs b/drivers/0x02_blink_rust/src/board.rs new file mode 100644 index 0000000..3a7ca98 --- /dev/null +++ b/drivers/0x02_blink_rust/src/board.rs @@ -0,0 +1,233 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Rate extension trait for .Hz() baud rate construction +use fugit::RateExtU32; +// Clock trait for accessing system clock frequency +use hal::Clock; +// GPIO pin types and function selectors +use hal::gpio::{FunctionNull, FunctionSioOutput, FunctionUart, Pin, PullDown, PullNone}; +// UART configuration and peripheral types +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// Delay between LED toggles in milliseconds. +pub(crate) const BLINK_DELAY_MS: u32 = 500; + +/// Type alias for the configured TX pin (GPIO 0, UART function, no pull). +pub(crate) type TxPin = Pin; + +/// Type alias for the configured RX pin (GPIO 1, UART function, no pull). +pub(crate) type RxPin = Pin; + +/// Type alias for the default TX pin state from `Pins::new()`. +pub(crate) type TxPinDefault = Pin; + +/// Type alias for the default RX pin state from `Pins::new()`. +pub(crate) type RxPinDefault = Pin; + +/// Type alias for the fully-enabled UART0 peripheral with TX/RX p. +pub(crate) type EnabledUart = UartPeripheral; + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +/// +/// # Arguments +/// +/// * `xosc` - XOSC peripheral singleton. +/// * `clocks` - CLOCKS peripheral singleton. +/// * `pll_sys` - PLL_SYS peripheral singleton. +/// * `pll_usb` - PLL_USB peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `watchdog` - Mutable reference to the watchdog timer. +/// +/// # Returns +/// +/// Configured clocks manager. +/// +/// # Panics +/// +/// Panics if clock initialisation fails. +pub(crate) fn init_clocks( + xosc: hal::pac::XOSC, + clocks: hal::pac::CLOCKS, + pll_sys: hal::pac::PLL_SYS, + pll_usb: hal::pac::PLL_USB, + resets: &mut hal::pac::RESETS, + watchdog: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + xosc, + clocks, + pll_sys, + pll_usb, + resets, + watchdog, + ) + .unwrap() +} + +/// Unlock the GPIO bank and return the pin set. +/// +/// # Arguments +/// +/// * `io_bank0` - IO_BANK0 peripheral singleton. +/// * `pads_bank0` - PADS_BANK0 peripheral singleton. +/// * `sio` - SIO peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// +/// # Returns +/// +/// GPIO pin set for the entire bank. +pub(crate) fn init_pins( + io_bank0: hal::pac::IO_BANK0, + pads_bank0: hal::pac::PADS_BANK0, + sio: hal::pac::SIO, + resets: &mut hal::pac::RESETS, +) -> hal::gpio::Pins { + let sio = hal::Sio::new(sio); + hal::gpio::Pins::new(io_bank0, pads_bank0, sio.gpio_bank0, resets) +} + +/// Initialise UART0 for serial output (stdio equivalent). +/// +/// # Arguments +/// +/// * `uart0` - PAC UART0 peripheral singleton. +/// * `tx_pin` - GPIO pin to use as UART0 TX (GPIO 0). +/// * `rx_pin` - GPIO pin to use as UART0 RX (GPIO 1). +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Enabled UART0 peripheral ready for blocking writes. +/// +/// # Panics +/// +/// Panics if the HAL cannot achieve the requested baud rate. +pub(crate) fn init_uart( + uart0: hal::pac::UART0, + tx_pin: TxPinDefault, + rx_pin: RxPinDefault, + resets: &mut hal::pac::RESETS, + clocks: &hal::clocks::ClocksManager, +) -> EnabledUart { + let pins = ( + tx_pin.reconfigure::(), + rx_pin.reconfigure::(), + ); + let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One); + UartPeripheral::new(uart0, pins, resets) + .enable(cfg, clocks.peripheral_clock.freq()) + .unwrap() +} + +/// Create a blocking delay timer from the ARM SysTick peripheral. +/// +/// # Arguments +/// +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Blocking delay provider. +/// +/// # Panics +/// +/// Panics if the cortex-m core peripherals have already been taken. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +pub(crate) fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay { + let core = cortex_m::Peripherals::take().unwrap(); + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()) +} + +/// Type alias for the onboard LED pin configured as push-pull output. +type LedPin = Pin; + +/// Initialise all peripherals and run the blink demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `pac` - The `pac` parameter. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! { + let mut w = hal::Watchdog::new(p.WATCHDOG); + let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut w); + let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS); + let u = init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c); + let mut d = init_delay(&c); + start_blink(&u, &mut d, pins.gpio25.into_push_pull_output()) +} + +/// Helper to start blink. +fn start_blink(u: &EnabledUart, d: &mut cortex_m::delay::Delay, p: LedPin) -> ! { + let mut led = crate::blink::BlinkDriver::init(p); + u.write_full_blocking(b"Blink driver initialized on GPIO 25\r\n"); + blink_loop(u, d, &mut led) +} + +/// Toggle LED, report state, and delay in a loop forever. +fn blink_loop(u: &EnabledUart, d: &mut cortex_m::delay::Delay, l: &mut crate::blink::BlinkDriver) -> ! { + loop { + l.toggle(); + u.write_full_blocking(if l.get_state() { b"LED: ON\r\n" } else { b"LED: OFF\r\n" }); + d.delay_ms(BLINK_DELAY_MS); + } +} + diff --git a/drivers/0x02_blink_rust/src/lib.rs b/drivers/0x02_blink_rust/src/lib.rs new file mode 100644 index 0000000..3efff6d --- /dev/null +++ b/drivers/0x02_blink_rust/src/lib.rs @@ -0,0 +1,37 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `lib.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![cfg_attr(not(test), no_std)] + +// Blink driver module +pub mod blink; diff --git a/drivers/0x02_blink_rust/src/main.rs b/drivers/0x02_blink_rust/src/main.rs new file mode 100644 index 0000000..9c1a9a3 --- /dev/null +++ b/drivers/0x02_blink_rust/src/main.rs @@ -0,0 +1,104 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; +// Blink driver module — suppress warnings for unused public API functions +#[allow(dead_code)] +mod blink; + +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the LED blink demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Blink LED Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/drivers/0x03_button_rust/.cargo/config.toml b/drivers/0x03_button_rust/.cargo/config.toml new file mode 100644 index 0000000..1791d74 --- /dev/null +++ b/drivers/0x03_button_rust/.cargo/config.toml @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2021–2024 The rp-rs Developers +# Copyright (c) 2021 rp-rs organization +# Copyright (c) 2025 Raspberry Pi Ltd. +# +# Cargo Configuration for the https://github.com/rp-rs/rp-hal.git repository. +# +# You might want to make a similar file in your own repository if you are +# writing programs for Raspberry Silicon microcontrollers. +# + +[build] +target = "thumbv8m.main-none-eabihf" +# Set the default target to match the Cortex-M33 in the RP2350 +# target = "thumbv8m.main-none-eabihf" +# target = "thumbv6m-none-eabi" +# target = "riscv32imac-unknown-none-elf" + +# Target specific options +[target.thumbv6m-none-eabi] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as the linker +# script. This is usually provided by the cortex-m-rt crate, and by default +# the version in that crate will include a file called `memory.x` which +# describes the particular memory layout for your specific chip. +# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't +# have SIMD) +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the hard-float ABI for Arm mode. +# +# The FPU is enabled by default, and float function arguments use FPU +# registers. +[target.thumbv8m.main-none-eabihf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as a linker script. +# This is usually provided by the cortex-m-rt crate, and by default the +# version in that crate will include a file called `memory.x` which describes +# the particular memory layout for your specific chip. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the soft-float ABI for RISC-V mode. +# +# Hazard 3 does not have an FPU and so float function arguments use integer +# registers. +[target.riscv32imac-unknown-none-elf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Trp235x_riscv.x also tells the linker to use +# `rp235x_riscv.x` as a linker script. This adds in RP2350 RISC-V specific +# things that the riscv-rt crate's `link.x` requires and then includes +# `link.x` automatically. This is the reverse of how we do it on Cortex-M. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp2350_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +[env] +DEFMT_LOG = "debug" diff --git a/drivers/0x03_button_rust/.gitignore b/drivers/0x03_button_rust/.gitignore new file mode 100644 index 0000000..03381c1 --- /dev/null +++ b/drivers/0x03_button_rust/.gitignore @@ -0,0 +1,113 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,macos,windows,linux + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux diff --git a/drivers/0x03_button_rust/.pico-rs b/drivers/0x03_button_rust/.pico-rs new file mode 100644 index 0000000..1b6702a --- /dev/null +++ b/drivers/0x03_button_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 \ No newline at end of file diff --git a/drivers/0x03_button_rust/.vscode/extensions.json b/drivers/0x03_button_rust/.vscode/extensions.json new file mode 100644 index 0000000..5f2fd0c --- /dev/null +++ b/drivers/0x03_button_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/drivers/0x03_button_rust/.vscode/launch.json b/drivers/0x03_button_rust/.vscode/launch.json new file mode 100644 index 0000000..0bc38c3 --- /dev/null +++ b/drivers/0x03_button_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} \ No newline at end of file diff --git a/drivers/0x03_button_rust/.vscode/settings.json b/drivers/0x03_button_rust/.vscode/settings.json new file mode 100644 index 0000000..b03cd57 --- /dev/null +++ b/drivers/0x03_button_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} \ No newline at end of file diff --git a/drivers/0x03_button_rust/.vscode/tasks.json b/drivers/0x03_button_rust/.vscode/tasks.json new file mode 100644 index 0000000..4194af4 --- /dev/null +++ b/drivers/0x03_button_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/drivers/0x03_button_rust/Cargo.toml b/drivers/0x03_button_rust/Cargo.toml new file mode 100644 index 0000000..7dba3a9 --- /dev/null +++ b/drivers/0x03_button_rust/Cargo.toml @@ -0,0 +1,81 @@ + +[package] +edition = "2024" +name = "button" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "button_lib" +path = "src/lib.rs" + +[[bin]] +name = "button" +path = "src/main.rs" + + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embedded-hal = "1.0.0" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 3 diff --git a/drivers/0x03_button_rust/LICENSE-APACHE b/drivers/0x03_button_rust/LICENSE-APACHE new file mode 100644 index 0000000..8d99cbc --- /dev/null +++ b/drivers/0x03_button_rust/LICENSE-APACHE @@ -0,0 +1,204 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2021–2024 The rp-rs Developers + Copyright (c) 2021 rp-rs organization + Copyright (c) 2025 Raspberry Pi Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/drivers/0x03_button_rust/LICENSE-MIT b/drivers/0x03_button_rust/LICENSE-MIT new file mode 100644 index 0000000..5369c70 --- /dev/null +++ b/drivers/0x03_button_rust/LICENSE-MIT @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2021–2024 The rp-rs Developers +Copyright (c) 2021 rp-rs organization +Copyright (c) 2025 Raspberry Pi Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + \ No newline at end of file diff --git a/drivers/0x03_button_rust/README.md b/drivers/0x03_button_rust/README.md new file mode 100644 index 0000000..9489896 --- /dev/null +++ b/drivers/0x03_button_rust/README.md @@ -0,0 +1,116 @@ +# 0x03 Button Rust Driver + +This repository contains a Bare-Metal Rust driver for reading a debounced button via GPIO on the **RP2350** (and RP2040) microcontrollers. + +It includes: +- A thin demo (`src/main.rs`) that reads a button, toggles an LED, and prints edge events over UART. +- A reusable library module (`src/button.rs`) providing a hardware-agnostic `ButtonDriver` with debouncing logic. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the Button driver logic is separated into a reusable library with hardware mocked using traits, you can run the unit tests natively on your computer (no microcontroller required!). + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we need to wake up the specific hardware subsystems we want to use (Clocks, Pins, UART for printing, and SysTick for debouncing delay). + +* **`run(...)`**: The master setup function. It sequentially calls the helper initialization functions below, and then kicks off the button demo. +* **`init_clocks(...)`**: Wakes up the external 12 MHz crystal (`XOSC`) and configures the PLLs (Phase-Locked Loops) to drive the system clock at its maximum speed. +* **`init_pins(...)`**: Takes control of physical pins across `IO_BANK0`. +* **`init_uart(...)`**: Configures the hardware `UART0` peripheral to operate at a standard `115200` baud rate with an `8N1` configuration for debug printing. +* **`init_delay(...)`**: Captures the ARM Cortex-M `SYST` (SysTick) peripheral to create a blocking delay timer, which is shared via a `RefCell`. +* **`button_demo(...)`**: Initializes the button input (`GPIO 15`) and LED output (`GPIO 25`) into their respective drivers, and jumps into the infinite `poll_button` loop. +* **`poll_button(...)`**: Checks the button state. If it changes, it toggles the LED and prints `PRESSED` or `RELEASED` over UART, then sleeps for a 10ms polling interval. + +### 3. The Reusable Button Driver (`src/button.rs`) +The hardware-specific logic from `board.rs` uses the `ButtonDriver` struct to abstract away the messy details of debouncing mechanical buttons. + +* **`ButtonDriver::init(...)`**: Creates a new instance of our driver, taking ownership of an input pin (configured with a pull-up resistor), a debounce delay duration, and a delay closure. +* **`debounce_confirm(&mut self)`**: A private helper that waits out the debounce period (e.g. 20ms) and re-samples the pin to ensure the electrical connection is physically stable. +* **`is_pressed(&mut self)`**: Reads the pin. If it is LOW (active), it calls `debounce_confirm()`. If the signal is stable, it returns true; otherwise it ignores the bounce and returns false. +* **`ButtonLed::init(...)`**: A tiny wrapper around an output pin to serve as a status indicator. +* **`set(&mut self, on: bool)`**: Drives the indicator LED HIGH or LOW based on the `on` flag. diff --git a/drivers/0x03_button_rust/build.rs b/drivers/0x03_button_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x03_button_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x03_button_rust/rp2040.x b/drivers/0x03_button_rust/rp2040.x new file mode 100644 index 0000000..0cc665b --- /dev/null +++ b/drivers/0x03_button_rust/rp2040.x @@ -0,0 +1,91 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + /* + * Here we assume you have 2048 KiB of Flash. This is what the Pi Pico + * has, but your board may have more or less Flash and you should adjust + * this value to suit. + */ + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + /* + * RAM consists of 4 banks, SRAM0-SRAM3, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 256K + /* + * RAM banks 4 and 5 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k + + /* SRAM banks 0-3 can also be accessed directly. However, those ranges + alias with the RAM mapping, above. So don't use them at the same time! + SRAM0 : ORIGIN = 0x21000000, LENGTH = 64k + SRAM1 : ORIGIN = 0x21010000, LENGTH = 64k + SRAM2 : ORIGIN = 0x21020000, LENGTH = 64k + SRAM3 : ORIGIN = 0x21030000, LENGTH = 64k + */ +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + /* ### Boot loader + * + * An executable block of code which sets up the QSPI interface for + * 'Execute-In-Place' (or XIP) mode. Also sends chip-specific commands to + * the external flash chip. + * + * Must go at the start of external flash, where the Boot ROM expects it. + */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 512 bytes of flash, + * where picotool can find it + */ + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; diff --git a/drivers/0x03_button_rust/rp2350.x b/drivers/0x03_button_rust/rp2350.x new file mode 100644 index 0000000..bda94d8 --- /dev/null +++ b/drivers/0x03_button_rust/rp2350.x @@ -0,0 +1,83 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + /* move .text to start /after/ the boot info */ + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + \ No newline at end of file diff --git a/drivers/0x03_button_rust/rp2350_riscv.x b/drivers/0x03_button_rust/rp2350_riscv.x new file mode 100644 index 0000000..84388aa --- /dev/null +++ b/drivers/0x03_button_rust/rp2350_riscv.x @@ -0,0 +1,259 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut _heap_size }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all `32`-byte aligned. These alignments are assumed by the RAM + initialization routine. There's also a second benefit: `32`-byte aligned boundaries + means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ +PROVIDE(_setup_interrupts = default_setup_interrupts); + +/* # Multi-processing hook function + fn _mp_hook() -> bool; + + This function is called from all the harts and must return true only for one hart, + which will perform memory initialization. For other harts it must return false + and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). +*/ +PROVIDE(_mp_hook = default_mp_hook); + +/* # Start trap function override + By default uses the riscv crates default trap handler + but by providing the `_start_trap` symbol external crates can override. +*/ +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + /* This section is intended to make _stext address work */ + . = ABSOLUTE(_stext); + } > FLASH + + .text _stext : + { + /* Put reset handler first in .text section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + __start_block_addr = .; + KEEP(*(.start_block)); + . = ALIGN(4); + *(.trap); + *(.trap.rust); + *(.text.abort); + *(.text .text.*); + . = ALIGN(4); + } > FLASH + + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + + .rodata : ALIGN(4) + { + *(.srodata .srodata.*); + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + } > FLASH + + .data : ALIGN(32) + { + _sidata = LOADADDR(.data); + __sidata = LOADADDR(.data); + _sdata = .; + __sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + . = ALIGN(32); + _edata = .; + __edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : ALIGN(32) + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(32); + _ebss = .; + } > RAM + + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + _sheap = .; + . += _heap_size; + . = ALIGN(4); + _eheap = .; + } > RAM + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + _estack = .; + . = ABSOLUTE(_stack_start); + _sstack = .; + } > RAM + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + .eh_frame (INFO) : { KEEP(*(.eh_frame)) } + .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } +} + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(FLASH) % 4 == 0, " +ERROR(riscv-rt): the start of the FLASH must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 32 == 0, " +ERROR(riscv-rt): the start of the RAM must be 32-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 32 == 0 && _edata % 32 == 0, " +BUG(riscv-rt): .data is not 32-byte aligned"); + +ASSERT(_sidata % 32 == 0, " +BUG(riscv-rt): the LMA of .data is not 32-byte aligned"); + +ASSERT(_sbss % 32 == 0 && _ebss % 32 == 0, " +BUG(riscv-rt): .bss is not 32-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), " +ERROR(riscv-rt): The .text section must be placed inside the FLASH region. +Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'"); + +ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +/* Do not exceed this mark in the error messages above | */ diff --git a/drivers/0x03_button_rust/src/board.rs b/drivers/0x03_button_rust/src/board.rs new file mode 100644 index 0000000..8d493af --- /dev/null +++ b/drivers/0x03_button_rust/src/board.rs @@ -0,0 +1,282 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Interior mutability for shared delay access +use core::cell::RefCell; +// Rate extension trait for .Hz() baud rate construction +use fugit::RateExtU32; +// Clock trait for accessing system clock frequency +use hal::Clock; +// GPIO pin types and function selectors +use hal::gpio::{ + FunctionNull, FunctionSioInput, FunctionSioOutput, FunctionUart, Pin, PullDown, PullNone, + PullUp, +}; +// UART configuration and peripheral types +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// Debounce settling time in milliseconds. +pub(crate) const DEBOUNCE_MS: u32 = 20; + +/// Main-loop polling interval in milliseconds. +pub(crate) const POLL_MS: u32 = 10; + +/// Type alias for the configured TX pin (GPIO 0, UART function, no pull). +pub(crate) type TxPin = Pin; + +/// Type alias for the configured RX pin (GPIO 1, UART function, no pull). +pub(crate) type RxPin = Pin; + +/// Type alias for the default TX pin state from `Pins::new()`. +pub(crate) type TxPinDefault = Pin; + +/// Type alias for the default RX pin state from `Pins::new()`. +pub(crate) type RxPinDefault = Pin; + +/// Type alias for the fully-enabled UART0 peripheral with TX/RX p. +pub(crate) type EnabledUart = UartPeripheral; + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +/// +/// # Arguments +/// +/// * `xosc` - XOSC peripheral singleton. +/// * `clocks` - CLOCKS peripheral singleton. +/// * `pll_sys` - PLL_SYS peripheral singleton. +/// * `pll_usb` - PLL_USB peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `watchdog` - Mutable reference to the watchdog timer. +/// +/// # Returns +/// +/// Configured clocks manager. +/// +/// # Panics +/// +/// Panics if clock initialisation fails. +pub(crate) fn init_clocks( + xosc: hal::pac::XOSC, + clocks: hal::pac::CLOCKS, + pll_sys: hal::pac::PLL_SYS, + pll_usb: hal::pac::PLL_USB, + resets: &mut hal::pac::RESETS, + watchdog: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + xosc, + clocks, + pll_sys, + pll_usb, + resets, + watchdog, + ) + .unwrap() +} + +/// Unlock the GPIO bank and return the pin set. +/// +/// # Arguments +/// +/// * `io_bank0` - IO_BANK0 peripheral singleton. +/// * `pads_bank0` - PADS_BANK0 peripheral singleton. +/// * `sio` - SIO peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// +/// # Returns +/// +/// GPIO pin set for the entire bank. +pub(crate) fn init_pins( + io_bank0: hal::pac::IO_BANK0, + pads_bank0: hal::pac::PADS_BANK0, + sio: hal::pac::SIO, + resets: &mut hal::pac::RESETS, +) -> hal::gpio::Pins { + let sio = hal::Sio::new(sio); + hal::gpio::Pins::new(io_bank0, pads_bank0, sio.gpio_bank0, resets) +} + +/// Initialise UART0 for serial output (stdio equivalent). +/// +/// # Arguments +/// +/// * `uart0` - PAC UART0 peripheral singleton. +/// * `tx_pin` - GPIO pin to use as UART0 TX (GPIO 0). +/// * `rx_pin` - GPIO pin to use as UART0 RX (GPIO 1). +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Enabled UART0 peripheral ready for blocking writes. +/// +/// # Panics +/// +/// Panics if the HAL cannot achieve the requested baud rate. +pub(crate) fn init_uart( + uart0: hal::pac::UART0, + tx_pin: TxPinDefault, + rx_pin: RxPinDefault, + resets: &mut hal::pac::RESETS, + clocks: &hal::clocks::ClocksManager, +) -> EnabledUart { + let pins = ( + tx_pin.reconfigure::(), + rx_pin.reconfigure::(), + ); + let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One); + UartPeripheral::new(uart0, pins, resets) + .enable(cfg, clocks.peripheral_clock.freq()) + .unwrap() +} + +/// Create a blocking delay timer from the ARM SysTick peripheral. +/// +/// # Arguments +/// +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Blocking delay provider. +/// +/// # Panics +/// +/// Panics if the cortex-m core peripherals have already been taken. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +pub(crate) fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay { + let core = cortex_m::Peripherals::take().unwrap(); + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()) +} + +/// Type alias for the button input pin (GPIO 15, pull-up). +type BtnPin = Pin; + +/// Type alias for the LED output pin (GPIO 25, push-pull). +type LedPin = Pin; + +/// Initialise all peripherals and run the button debounce demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `pac` - The `pac` parameter. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! { + let mut w = hal::Watchdog::new(p.WATCHDOG); + let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut w); + let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS); + let uart = init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c); + uart.write_full_blocking(b"Button driver initialized: button=GPIO15 led=GPIO25\r\n"); + button_demo(&uart, pins.gpio15, pins.gpio25, &RefCell::new(init_delay(&c))) +} + +/// Create button and LED drivers, then run the polling loop. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// * `btn_pin` - Default GPIO 15 pin for the button input. +/// * `led_pin` - Default GPIO 25 pin for the LED output. +/// * `delay` - Shared reference to the delay provider. +fn button_demo( + u: &EnabledUart, + bp: Pin, + lp: Pin, + d: &RefCell, +) -> ! { + let mut b = button_lib::button::ButtonDriver::init(bp.into_pull_up_input(), DEBOUNCE_MS, |m| d.borrow_mut().delay_ms(m)); + let mut l = button_lib::button::ButtonLed::init(lp.into_push_pull_output()); + let mut last = false; + loop { poll_button(u, &mut b, &mut l, &mut last, d); } +} + +/// Poll button state, update LED, and report edge transitions. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// * `btn` - Mutable reference to the button driver. +/// * `led` - Mutable reference to the LED driver. +/// * `last` - Mutable reference to the previous button state. +/// * `delay` - Shared reference to the delay provider. +fn poll_button( + u: &EnabledUart, b: &mut button_lib::button::ButtonDriver, + l: &mut button_lib::button::ButtonLed, last: &mut bool, d: &RefCell +) { + let p = b.is_pressed(); + l.set(p); + if p != *last { report_edge(u, p); *last = p; } + d.borrow_mut().delay_ms(POLL_MS); +} + +/// Print button press/release message over UART. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// * `pressed` - `true` if the button is pressed, `false` if released. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +/// * `pressed` - The `pressed` parameter. +fn report_edge(uart: &EnabledUart, pressed: bool) { + uart.write_full_blocking(if pressed { b"Button: PRESSED\r\n" } else { b"Button: RELEASED\r\n" }); +} + diff --git a/drivers/0x03_button_rust/src/button.rs b/drivers/0x03_button_rust/src/button.rs new file mode 100644 index 0000000..f25a365 --- /dev/null +++ b/drivers/0x03_button_rust/src/button.rs @@ -0,0 +1,362 @@ +//! Implementation module +//! +//! **File:** `button.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Embedded HAL digital I/O traits for GPIO pin control +use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin}; + +/// Push-button GPIO input driver with debounce. +pub struct ButtonDriver { + btn_pin: B, + debounce_ms: u32, + delay_fn: D, +} + +impl ButtonDriver { + /// Initialize a GPIO pin as an active-low button input with pull-up. + /// + /// The pin reads logic high when the button is open and logic low when + /// the button connects the pin to GND. + /// + /// # Arguments + /// + /// * `btn_pin` - GPIO pin configured as input with pull-up. + /// * `debounce_ms` - Debounce settling time in milliseconds (e.g. 20). + /// * `delay_fn` - Closure that sleeps for the given number of milliseconds. + /// + /// # Returns + /// + /// A new ButtonDriver instance. + /// + /// # Arguments + /// + /// * `btn_pin` - The `btn_pin` parameter. + /// * `debounce_ms` - The `debounce_ms` parameter. + /// * `delay_fn` - The `delay_fn` parameter. + /// + /// # Returns + /// + /// A new instance of the struct. + #[inline] + pub fn init(btn_pin: B, debounce_ms: u32, delay_fn: D) -> Self { + Self { + btn_pin, + debounce_ms, + delay_fn, + } + } + + /// Confirm a raw active-low pin read by waiting for the debounce period. + /// + /// After an initial low reading on the pin, this helper re-samples the pin + /// after debounce_ms milliseconds to confirm the reading is stable. + /// + /// # Returns + /// + /// `true` if the pin is still low after the debounce delay. + /// + /// # Returns + /// + /// `true` if successful or set, `false` otherwise. + #[inline] + fn debounce_confirm(&mut self) -> bool { + (self.delay_fn)(self.debounce_ms); + self.btn_pin.is_low().unwrap() + } + + /// Read the debounced state of the button. + /// + /// Returns true only when the pin reads low continuously for the debounce + /// period configured in init(). This prevents mechanical contact bounce + /// from registering as multiple rapid presses. + /// + /// # Returns + /// + /// `true` if the button is firmly pressed, `false` if released. + /// + /// # Returns + /// + /// `true` if successful or set, `false` otherwise. + #[inline] + pub fn is_pressed(&mut self) -> bool { + if self.btn_pin.is_low().unwrap() { + return self.debounce_confirm(); + } + false + } +} + +/// Indicator LED driver that owns a single output pin. +pub struct ButtonLed { + led_pin: L, +} + +impl ButtonLed { + /// Initialize a GPIO pin as a push-pull digital output for an indicator LED. + /// + /// Configures the output and drives it low (LED off). + /// + /// # Arguments + /// + /// * `led_pin` - GPIO pin configured as a digital output. + /// + /// # Returns + /// + /// A new ButtonLed instance. + /// + /// # Arguments + /// + /// * `led_pin` - The `led_pin` parameter. + /// + /// # Returns + /// + /// A new instance of the struct. + #[inline] + pub fn init(mut led_pin: L) -> Self { + led_pin.set_low().unwrap(); + Self { led_pin } + } + + /// Set the indicator LED state. + /// + /// Drives the output pin high (LED on) or low (LED off). + /// + /// # Arguments + /// + /// * `on` - `true` to turn the LED on, `false` to turn it off. + /// + /// # Arguments + /// + /// * `on` - The `on` parameter. + #[inline] + pub fn set(&mut self, on: bool) { + if on { + self.led_pin.set_high().unwrap(); + } else { + self.led_pin.set_low().unwrap(); + } + } +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + // Infallible error type for mock pin implementations + use core::convert::Infallible; + // Error type trait for mock pin implementations + use embedded_hal::digital::ErrorType; + + struct MockInputPin { + low: bool, + } + + impl MockInputPin { + /// Creates a new hardware wrapper instance. + /// + /// # Arguments + /// + /// * `low` - The `low` parameter. + /// + /// # Returns + /// + /// A new instance of the struct. + fn new(low: bool) -> Self { + Self { low } + } + } + + impl ErrorType for MockInputPin { + type Error = Infallible; + } + + impl InputPin for MockInputPin { + /// Executes the is high operation. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn is_high(&mut self) -> Result { + Ok(!self.low) + } + + /// Executes the is low operation. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn is_low(&mut self) -> Result { + Ok(self.low) + } + } + + struct MockOutputPin { + state: bool, + } + + impl MockOutputPin { + /// Creates a new hardware wrapper instance. + /// + /// # Returns + /// + /// A new instance of the struct. + fn new() -> Self { + Self { state: false } + } + } + + impl ErrorType for MockOutputPin { + type Error = Infallible; + } + + impl OutputPin for MockOutputPin { + /// Executes the set low operation. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn set_low(&mut self) -> Result<(), Self::Error> { + self.state = false; + Ok(()) + } + + /// Executes the set high operation. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn set_high(&mut self) -> Result<(), Self::Error> { + self.state = true; + Ok(()) + } + } + + impl StatefulOutputPin for MockOutputPin { + /// Executes the is set high operation. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn is_set_high(&mut self) -> Result { + Ok(self.state) + } + + /// Executes the is set low operation. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn is_set_low(&mut self) -> Result { + Ok(!self.state) + } + + /// Executes the toggle operation. + /// + /// # Returns + /// + /// A Result indicating success or failure. + fn toggle(&mut self) -> Result<(), Self::Error> { + self.state = !self.state; + Ok(()) + } + } + + /// Executes the noop delay operation. + /// + /// # Arguments + /// + /// * `ms` - The `ms` parameter. + fn noop_delay(_ms: u32) {} + + /// Executes the is pressed when low and confirmed operation. + #[test] + fn is_pressed_when_low_and_confirmed() { + let pin = MockInputPin::new(true); + let mut drv = ButtonDriver::init(pin, 20, noop_delay); + assert!(drv.is_pressed()); + } + + /// Executes the is not pressed when high operation. + #[test] + fn is_not_pressed_when_high() { + let pin = MockInputPin::new(false); + let mut drv = ButtonDriver::init(pin, 20, noop_delay); + assert!(!drv.is_pressed()); + } + + /// Executes the debounce ms stored operation. + #[test] + fn debounce_ms_stored() { + let pin = MockInputPin::new(false); + let drv = ButtonDriver::init(pin, 50, noop_delay); + assert_eq!(drv.debounce_ms, 50); + } + + /// Executes the debounce calls delay operation. + #[test] + fn debounce_calls_delay() { + let mut called_with: u32 = 0; + let pin = MockInputPin::new(true); + let mut drv = ButtonDriver::init(pin, 25, |ms| called_with = ms); + drv.is_pressed(); + assert_eq!(called_with, 25); + } + + /// Executes the led init starts off operation. + #[test] + fn led_init_starts_off() { + let led = ButtonLed::init(MockOutputPin::new()); + assert!(!led.led_pin.state); + } + + /// Executes the led set on operation. + #[test] + fn led_set_on() { + let mut led = ButtonLed::init(MockOutputPin::new()); + led.set(true); + assert!(led.led_pin.state); + } + + /// Executes the led set off operation. + #[test] + fn led_set_off() { + let mut led = ButtonLed::init(MockOutputPin::new()); + led.set(true); + led.set(false); + assert!(!led.led_pin.state); + } + + /// Executes the led set on then off then on operation. + #[test] + fn led_set_on_then_off_then_on() { + let mut led = ButtonLed::init(MockOutputPin::new()); + led.set(true); + led.set(false); + led.set(true); + assert!(led.led_pin.state); + } +} diff --git a/drivers/0x03_button_rust/src/lib.rs b/drivers/0x03_button_rust/src/lib.rs new file mode 100644 index 0000000..5c41471 --- /dev/null +++ b/drivers/0x03_button_rust/src/lib.rs @@ -0,0 +1,37 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `lib.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![cfg_attr(not(test), no_std)] + +// Button driver module +pub mod button; diff --git a/drivers/0x03_button_rust/src/main.rs b/drivers/0x03_button_rust/src/main.rs new file mode 100644 index 0000000..a1fa27e --- /dev/null +++ b/drivers/0x03_button_rust/src/main.rs @@ -0,0 +1,102 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; +// Button driver module — suppress warnings for unused public API functions +#[allow(dead_code)] +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the button debounce demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Button Debounce Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/drivers/0x04_pwm_rust/.cargo/config.toml b/drivers/0x04_pwm_rust/.cargo/config.toml new file mode 100644 index 0000000..1791d74 --- /dev/null +++ b/drivers/0x04_pwm_rust/.cargo/config.toml @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2021–2024 The rp-rs Developers +# Copyright (c) 2021 rp-rs organization +# Copyright (c) 2025 Raspberry Pi Ltd. +# +# Cargo Configuration for the https://github.com/rp-rs/rp-hal.git repository. +# +# You might want to make a similar file in your own repository if you are +# writing programs for Raspberry Silicon microcontrollers. +# + +[build] +target = "thumbv8m.main-none-eabihf" +# Set the default target to match the Cortex-M33 in the RP2350 +# target = "thumbv8m.main-none-eabihf" +# target = "thumbv6m-none-eabi" +# target = "riscv32imac-unknown-none-elf" + +# Target specific options +[target.thumbv6m-none-eabi] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as the linker +# script. This is usually provided by the cortex-m-rt crate, and by default +# the version in that crate will include a file called `memory.x` which +# describes the particular memory layout for your specific chip. +# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't +# have SIMD) +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the hard-float ABI for Arm mode. +# +# The FPU is enabled by default, and float function arguments use FPU +# registers. +[target.thumbv8m.main-none-eabihf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as a linker script. +# This is usually provided by the cortex-m-rt crate, and by default the +# version in that crate will include a file called `memory.x` which describes +# the particular memory layout for your specific chip. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the soft-float ABI for RISC-V mode. +# +# Hazard 3 does not have an FPU and so float function arguments use integer +# registers. +[target.riscv32imac-unknown-none-elf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Trp235x_riscv.x also tells the linker to use +# `rp235x_riscv.x` as a linker script. This adds in RP2350 RISC-V specific +# things that the riscv-rt crate's `link.x` requires and then includes +# `link.x` automatically. This is the reverse of how we do it on Cortex-M. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp2350_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +[env] +DEFMT_LOG = "debug" diff --git a/drivers/0x04_pwm_rust/.gitignore b/drivers/0x04_pwm_rust/.gitignore new file mode 100644 index 0000000..03381c1 --- /dev/null +++ b/drivers/0x04_pwm_rust/.gitignore @@ -0,0 +1,113 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,macos,windows,linux + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux diff --git a/drivers/0x04_pwm_rust/.pico-rs b/drivers/0x04_pwm_rust/.pico-rs new file mode 100644 index 0000000..1b6702a --- /dev/null +++ b/drivers/0x04_pwm_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 \ No newline at end of file diff --git a/drivers/0x04_pwm_rust/.vscode/extensions.json b/drivers/0x04_pwm_rust/.vscode/extensions.json new file mode 100644 index 0000000..5f2fd0c --- /dev/null +++ b/drivers/0x04_pwm_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/drivers/0x04_pwm_rust/.vscode/launch.json b/drivers/0x04_pwm_rust/.vscode/launch.json new file mode 100644 index 0000000..0bc38c3 --- /dev/null +++ b/drivers/0x04_pwm_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} \ No newline at end of file diff --git a/drivers/0x04_pwm_rust/.vscode/settings.json b/drivers/0x04_pwm_rust/.vscode/settings.json new file mode 100644 index 0000000..b03cd57 --- /dev/null +++ b/drivers/0x04_pwm_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} \ No newline at end of file diff --git a/drivers/0x04_pwm_rust/.vscode/tasks.json b/drivers/0x04_pwm_rust/.vscode/tasks.json new file mode 100644 index 0000000..4194af4 --- /dev/null +++ b/drivers/0x04_pwm_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/drivers/0x04_pwm_rust/Cargo.toml b/drivers/0x04_pwm_rust/Cargo.toml new file mode 100644 index 0000000..13911fe --- /dev/null +++ b/drivers/0x04_pwm_rust/Cargo.toml @@ -0,0 +1,81 @@ + +[package] +edition = "2024" +name = "pwm" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "pwm_lib" +path = "src/lib.rs" + +[[bin]] +name = "pwm" +path = "src/main.rs" + + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embedded-hal = "1.0.0" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 3 diff --git a/drivers/0x04_pwm_rust/LICENSE-APACHE b/drivers/0x04_pwm_rust/LICENSE-APACHE new file mode 100644 index 0000000..8d99cbc --- /dev/null +++ b/drivers/0x04_pwm_rust/LICENSE-APACHE @@ -0,0 +1,204 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2021–2024 The rp-rs Developers + Copyright (c) 2021 rp-rs organization + Copyright (c) 2025 Raspberry Pi Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/drivers/0x04_pwm_rust/LICENSE-MIT b/drivers/0x04_pwm_rust/LICENSE-MIT new file mode 100644 index 0000000..5369c70 --- /dev/null +++ b/drivers/0x04_pwm_rust/LICENSE-MIT @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2021–2024 The rp-rs Developers +Copyright (c) 2021 rp-rs organization +Copyright (c) 2025 Raspberry Pi Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + \ No newline at end of file diff --git a/drivers/0x04_pwm_rust/README.md b/drivers/0x04_pwm_rust/README.md new file mode 100644 index 0000000..70c5b43 --- /dev/null +++ b/drivers/0x04_pwm_rust/README.md @@ -0,0 +1,116 @@ +# 0x04 PWM Rust Driver + +This repository contains a Bare-Metal Rust driver for Pulse Width Modulation (PWM) on the **RP2350** (and RP2040) microcontrollers. + +It includes: +- A thin demo (`src/main.rs`) that slowly breathes an LED (fades it in and out) and prints the duty cycle percentage over UART. +- A reusable library module (`src/pwm.rs`) providing a hardware-agnostic `pwm_lib` with helper math to calculate clock dividers and level targets. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the PWM math logic is separated into a reusable library, you can run the unit tests natively on your computer (no microcontroller required!). + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we need to wake up the specific hardware subsystems we want to use (Clocks, Pins, UART for printing, SysTick for delay, and PWM). + +* **`run(...)`**: The master setup function. It sequentially calls the helper initialization functions below, and then kicks off the PWM sweep loop. +* **`init_clocks(...)`**: Wakes up the external 12 MHz crystal (`XOSC`) and configures the PLLs (Phase-Locked Loops) to drive the system clock at its maximum speed. +* **`init_pins(...)`**: Takes control of physical pins across `IO_BANK0`. +* **`init_uart(...)`**: Configures the hardware `UART0` peripheral to operate at a standard `115200` baud rate with an `8N1` configuration for debug printing. +* **`init_delay(...)`**: Captures the ARM Cortex-M `SYST` (SysTick) peripheral to create a blocking delay timer. +* **`init_pwm(...)`**: Obtains `PWM Slice 4` and its `channel_b` mapped to `GPIO 25` (the onboard LED). Sets up the clock divider and TOP value (wrap) to give us a 1 kHz frequency. +* **`pwm_loop(...)`**: Loops infinitely, sweeping the LED duty cycle up and then down, simulating a breathing effect. +* **`sweep_up(...)`** / **`sweep_down(...)`**: Walks through duty cycle percentages `0` to `100` and back down in steps of 5%. +* **`apply_duty(...)`**: Computes the hardware register level using `pwm_lib`, sets the new duty cycle, formats a `Duty: XX%` string without heap allocation, transmits it, and delays 50ms. + +### 3. The Reusable PWM Math Library (`src/pwm.rs`) +Instead of burying math inside initialization functions, `src/pwm.rs` extracts pure mathematical logic that's decoupled from hardware structs so we can thoroughly unit-test it natively. + +* **`calc_clk_div(...)`**: Calculates the precise floating-point clock divider required to step down the fast system clock so the counter overflows exactly `freq_hz` times per second. +* **`clamp_percent(...)`**: Constrains duty cycle percentages between `0` and `100`. +* **`duty_to_level(...)`**: Translates a high-level percentage (e.g., `50%`) into the raw integer counter compare-value (e.g., `5000` out of `10000`) for the hardware registers. diff --git a/drivers/0x04_pwm_rust/build.rs b/drivers/0x04_pwm_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x04_pwm_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x04_pwm_rust/rp2040.x b/drivers/0x04_pwm_rust/rp2040.x new file mode 100644 index 0000000..0cc665b --- /dev/null +++ b/drivers/0x04_pwm_rust/rp2040.x @@ -0,0 +1,91 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + /* + * Here we assume you have 2048 KiB of Flash. This is what the Pi Pico + * has, but your board may have more or less Flash and you should adjust + * this value to suit. + */ + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + /* + * RAM consists of 4 banks, SRAM0-SRAM3, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 256K + /* + * RAM banks 4 and 5 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k + + /* SRAM banks 0-3 can also be accessed directly. However, those ranges + alias with the RAM mapping, above. So don't use them at the same time! + SRAM0 : ORIGIN = 0x21000000, LENGTH = 64k + SRAM1 : ORIGIN = 0x21010000, LENGTH = 64k + SRAM2 : ORIGIN = 0x21020000, LENGTH = 64k + SRAM3 : ORIGIN = 0x21030000, LENGTH = 64k + */ +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + /* ### Boot loader + * + * An executable block of code which sets up the QSPI interface for + * 'Execute-In-Place' (or XIP) mode. Also sends chip-specific commands to + * the external flash chip. + * + * Must go at the start of external flash, where the Boot ROM expects it. + */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 512 bytes of flash, + * where picotool can find it + */ + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; diff --git a/drivers/0x04_pwm_rust/rp2350.x b/drivers/0x04_pwm_rust/rp2350.x new file mode 100644 index 0000000..bda94d8 --- /dev/null +++ b/drivers/0x04_pwm_rust/rp2350.x @@ -0,0 +1,83 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + /* move .text to start /after/ the boot info */ + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + \ No newline at end of file diff --git a/drivers/0x04_pwm_rust/rp2350_riscv.x b/drivers/0x04_pwm_rust/rp2350_riscv.x new file mode 100644 index 0000000..84388aa --- /dev/null +++ b/drivers/0x04_pwm_rust/rp2350_riscv.x @@ -0,0 +1,259 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut _heap_size }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all `32`-byte aligned. These alignments are assumed by the RAM + initialization routine. There's also a second benefit: `32`-byte aligned boundaries + means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ +PROVIDE(_setup_interrupts = default_setup_interrupts); + +/* # Multi-processing hook function + fn _mp_hook() -> bool; + + This function is called from all the harts and must return true only for one hart, + which will perform memory initialization. For other harts it must return false + and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). +*/ +PROVIDE(_mp_hook = default_mp_hook); + +/* # Start trap function override + By default uses the riscv crates default trap handler + but by providing the `_start_trap` symbol external crates can override. +*/ +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + /* This section is intended to make _stext address work */ + . = ABSOLUTE(_stext); + } > FLASH + + .text _stext : + { + /* Put reset handler first in .text section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + __start_block_addr = .; + KEEP(*(.start_block)); + . = ALIGN(4); + *(.trap); + *(.trap.rust); + *(.text.abort); + *(.text .text.*); + . = ALIGN(4); + } > FLASH + + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + + .rodata : ALIGN(4) + { + *(.srodata .srodata.*); + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + } > FLASH + + .data : ALIGN(32) + { + _sidata = LOADADDR(.data); + __sidata = LOADADDR(.data); + _sdata = .; + __sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + . = ALIGN(32); + _edata = .; + __edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : ALIGN(32) + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(32); + _ebss = .; + } > RAM + + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + _sheap = .; + . += _heap_size; + . = ALIGN(4); + _eheap = .; + } > RAM + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + _estack = .; + . = ABSOLUTE(_stack_start); + _sstack = .; + } > RAM + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + .eh_frame (INFO) : { KEEP(*(.eh_frame)) } + .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } +} + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(FLASH) % 4 == 0, " +ERROR(riscv-rt): the start of the FLASH must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 32 == 0, " +ERROR(riscv-rt): the start of the RAM must be 32-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 32 == 0 && _edata % 32 == 0, " +BUG(riscv-rt): .data is not 32-byte aligned"); + +ASSERT(_sidata % 32 == 0, " +BUG(riscv-rt): the LMA of .data is not 32-byte aligned"); + +ASSERT(_sbss % 32 == 0 && _ebss % 32 == 0, " +BUG(riscv-rt): .bss is not 32-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), " +ERROR(riscv-rt): The .text section must be placed inside the FLASH region. +Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'"); + +ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +/* Do not exceed this mark in the error messages above | */ diff --git a/drivers/0x04_pwm_rust/src/board.rs b/drivers/0x04_pwm_rust/src/board.rs new file mode 100644 index 0000000..fee95ac --- /dev/null +++ b/drivers/0x04_pwm_rust/src/board.rs @@ -0,0 +1,374 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// PWM duty-cycle trait for .set_duty_cycle() +use embedded_hal::pwm::SetDutyCycle; +// Rate extension trait for .Hz() baud rate construction +use fugit::RateExtU32; +// Clock trait for accessing system clock frequency +use hal::Clock; +// GPIO pin types and function selectors +use hal::gpio::{FunctionNull, FunctionUart, Pin, PullDown, PullNone}; +// UART configuration and peripheral types +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// Desired PWM output frequency in Hz. +pub(crate) const PWM_FREQ_HZ: u32 = 1000; + +/// PWM counter wrap value (period - 1). +pub(crate) const PWM_WRAP: u32 = 10000 - 1; + +/// Type alias for the configured TX pin (GPIO 0, UART function, no pull). +pub(crate) type TxPin = Pin; + +/// Type alias for the configured RX pin (GPIO 1, UART function, no pull). +pub(crate) type RxPin = Pin; + +/// Type alias for the default TX pin state from `Pins::new()`. +pub(crate) type TxPinDefault = Pin; + +/// Type alias for the default RX pin state from `Pins::new()`. +pub(crate) type RxPinDefault = Pin; + +/// Type alias for the fully-enabled UART0 peripheral with TX/RX p. +pub(crate) type EnabledUart = UartPeripheral; + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +/// +/// # Arguments +/// +/// * `xosc` - XOSC peripheral singleton. +/// * `clocks` - CLOCKS peripheral singleton. +/// * `pll_sys` - PLL_SYS peripheral singleton. +/// * `pll_usb` - PLL_USB peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `watchdog` - Mutable reference to the watchdog timer. +/// +/// # Returns +/// +/// Configured clocks manager. +/// +/// # Panics +/// +/// Panics if clock initialisation fails. +pub(crate) fn init_clocks( + xosc: hal::pac::XOSC, + clocks: hal::pac::CLOCKS, + pll_sys: hal::pac::PLL_SYS, + pll_usb: hal::pac::PLL_USB, + resets: &mut hal::pac::RESETS, + watchdog: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + xosc, + clocks, + pll_sys, + pll_usb, + resets, + watchdog, + ) + .unwrap() +} + +/// Unlock the GPIO bank and return the pin set. +/// +/// # Arguments +/// +/// * `io_bank0` - IO_BANK0 peripheral singleton. +/// * `pads_bank0` - PADS_BANK0 peripheral singleton. +/// * `sio` - SIO peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// +/// # Returns +/// +/// GPIO pin set for the entire bank. +pub(crate) fn init_pins( + io_bank0: hal::pac::IO_BANK0, + pads_bank0: hal::pac::PADS_BANK0, + sio: hal::pac::SIO, + resets: &mut hal::pac::RESETS, +) -> hal::gpio::Pins { + let sio = hal::Sio::new(sio); + hal::gpio::Pins::new(io_bank0, pads_bank0, sio.gpio_bank0, resets) +} + +/// Initialise UART0 for serial output (stdio equivalent). +/// +/// # Arguments +/// +/// * `uart0` - PAC UART0 peripheral singleton. +/// * `tx_pin` - GPIO pin to use as UART0 TX (GPIO 0). +/// * `rx_pin` - GPIO pin to use as UART0 RX (GPIO 1). +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Enabled UART0 peripheral ready for blocking writes. +/// +/// # Panics +/// +/// Panics if the HAL cannot achieve the requested baud rate. +pub(crate) fn init_uart( + uart0: hal::pac::UART0, + tx_pin: TxPinDefault, + rx_pin: RxPinDefault, + resets: &mut hal::pac::RESETS, + clocks: &hal::clocks::ClocksManager, +) -> EnabledUart { + let pins = ( + tx_pin.reconfigure::(), + rx_pin.reconfigure::(), + ); + let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One); + UartPeripheral::new(uart0, pins, resets) + .enable(cfg, clocks.peripheral_clock.freq()) + .unwrap() +} + +/// Create a blocking delay timer from the ARM SysTick peripheral. +/// +/// # Arguments +/// +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Blocking delay provider. +/// +/// # Panics +/// +/// Panics if the cortex-m core peripherals have already been taken. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +pub(crate) fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay { + let core = cortex_m::Peripherals::take().unwrap(); + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()) +} + +/// Format a duty percentage into a fixed byte buffer as "Duty: NNN%\r\n". +/// +/// # Arguments +/// +/// * `buf` - Mutable byte slice (must be at least 16 bytes). +/// * `duty` - Duty cycle percentage to format. +/// +/// # Returns +/// +/// Number of bytes written into the buffer. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `duty` - The `duty` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +pub(crate) fn format_duty(buf: &mut [u8], duty: u8) -> usize { + buf[..6].copy_from_slice(b"Duty: "); + let pos = 6 + write_duty_digits(&mut buf[6..], duty); + buf[pos..pos + 3].copy_from_slice(b"%\r\n"); + pos + 3 +} + +/// Write the 3-character right-justified duty digits into `buf`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `duty` - The `duty` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `duty` - The `duty` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +fn write_duty_digits(buf: &mut [u8], duty: u8) -> usize { + if duty >= 100 { buf[..3].copy_from_slice(b"100"); } + else if duty >= 10 { buf[..3].copy_from_slice(&[b' ', b'0' + duty / 10, b'0' + duty % 10]); } + else { buf[..3].copy_from_slice(&[b' ', b' ', b'0' + duty]); } + 3 +} + +/// Sweep the PWM duty cycle from 0% to 100% in steps of 5. +/// +/// # Arguments +/// +/// * `uart` - UART peripheral for serial output. +/// * `channel` - PWM channel to set duty on. +/// * `delay` - Delay provider for 50 ms pauses. +/// * `buf` - Scratch buffer for formatting output. +pub(crate) fn sweep_up(u: &EnabledUart, ch: &mut impl SetDutyCycle, d: &mut cortex_m::delay::Delay, buf: &mut [u8; 16]) { + let mut duty = 0; + while duty <= 100 { apply_duty(u, ch, d, buf, duty); duty += 5; } +} + +/// Sweep the PWM duty cycle from 100% to 0% in steps of 5. +/// +/// # Arguments +/// +/// * `uart` - UART peripheral for serial output. +/// * `channel` - PWM channel to set duty on. +/// * `delay` - Delay provider for 50 ms pauses. +/// * `buf` - Scratch buffer for formatting output. +pub(crate) fn sweep_down(u: &EnabledUart, ch: &mut impl SetDutyCycle, d: &mut cortex_m::delay::Delay, buf: &mut [u8; 16]) { + let mut duty: i8 = 100; + while duty >= 0 { apply_duty(u, ch, d, buf, duty as u8); duty -= 5; } +} + +/// Apply a single duty step: set PWM level, format, print, and delay. +fn apply_duty(u: &EnabledUart, ch: &mut impl SetDutyCycle, d: &mut cortex_m::delay::Delay, buf: &mut [u8; 16], duty: u8) { + ch.set_duty_cycle(pwm_lib::pwm::duty_to_level(duty, PWM_WRAP) as u16).ok(); + let n = format_duty(buf, duty); + u.write_full_blocking(&buf[..n]); + d.delay_ms(50u32); +} + +/// Type alias for PWM slice 4 (onboard LED, GPIO 25). +type PwmSlice4 = hal::pwm::Slice; + +/// Initialise all peripherals and run the PWM breathing demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `pac` - The `pac` parameter. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! { + let mut w = hal::Watchdog::new(p.WATCHDOG); + let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut w); + let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS); + let u = init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c); + u.write_full_blocking(b"PWM initialized: GPIO25 @ 1000 Hz\r\n"); + pwm_loop(&u, &mut init_pwm(p.PWM, &mut p.RESETS, &c, pins.gpio25), &mut init_delay(&c)) +} + +/// Configure PWM slice 4 for 1 kHz output on channel B (GPIO 25). +/// +/// # Arguments +/// +/// * `pwm_pac` - PAC PWM peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// * `led_pin` - Default GPIO 25 pin to bind to PWM channel B. +/// +/// # Returns +/// +/// Configured PWM slice 4 in free-running mode. +fn init_pwm( + p: hal::pac::PWM, r: &mut hal::pac::RESETS, c: &hal::clocks::ClocksManager, led: Pin +) -> PwmSlice4 { + let mut s = hal::pwm::Slices::new(p, r).pwm4; + configure_pwm_div(&mut s, c); + s.set_top(PWM_WRAP as u16); + s.enable(); + s.channel_b.output_to(led); + s +} + +/// Set the clock divider for a PWM slice based on the system clock. +/// +/// # Arguments +/// +/// * `slice` - Mutable reference to the PWM slice to configure. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Arguments +/// +/// * `slice` - The `slice` parameter. +/// * `clocks` - The `clocks` parameter. +fn configure_pwm_div(s: &mut PwmSlice4, c: &hal::clocks::ClocksManager) { + let div = pwm_lib::pwm::calc_clk_div(c.system_clock.freq().to_Hz(), PWM_FREQ_HZ, PWM_WRAP); + s.set_div_int(div as u8); + s.set_div_frac((((div - (div as f32)) * 16.0) as u8).min(15)); +} + +/// Run the PWM duty-cycle sweep loop forever. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// * `pwm` - Mutable reference to the configured PWM slice. +/// * `delay` - Mutable reference to the blocking delay provider. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +/// * `pwm` - The `pwm` parameter. +/// * `delay` - Delay value. +/// +/// # Returns +/// +/// A value of type `!`. +fn pwm_loop(u: &EnabledUart, p: &mut PwmSlice4, d: &mut cortex_m::delay::Delay) -> ! { + let mut buf = [0u8; 16]; + loop { sweep_up(u, &mut p.channel_b, d, &mut buf); sweep_down(u, &mut p.channel_b, d, &mut buf); } +} + diff --git a/drivers/0x04_pwm_rust/src/lib.rs b/drivers/0x04_pwm_rust/src/lib.rs new file mode 100644 index 0000000..a3f36ec --- /dev/null +++ b/drivers/0x04_pwm_rust/src/lib.rs @@ -0,0 +1,6 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +#![cfg_attr(not(test), no_std)] +pub mod pwm; diff --git a/drivers/0x04_pwm_rust/src/main.rs b/drivers/0x04_pwm_rust/src/main.rs new file mode 100644 index 0000000..f5b7651 --- /dev/null +++ b/drivers/0x04_pwm_rust/src/main.rs @@ -0,0 +1,102 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; +// PWM driver module — suppress warnings for unused public API functions + +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the PWM LED breathing demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PWM LED Breathing Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/drivers/0x04_pwm_rust/src/pwm.rs b/drivers/0x04_pwm_rust/src/pwm.rs new file mode 100644 index 0000000..f3e0d59 --- /dev/null +++ b/drivers/0x04_pwm_rust/src/pwm.rs @@ -0,0 +1,183 @@ +//! Implementation module +//! +//! **File:** `pwm.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +/// Compute the PWM clock divider that yields the target frequency. +/// +/// Derives a floating-point divider from the system clock frequency, +/// the desired output frequency, and the chosen wrap value so that the PWM +/// counter overflows exactly freq_hz times per second. +/// +/// # Arguments +/// +/// * `sys_hz` - System clock frequency in Hz. +/// * `freq_hz` - Desired PWM output frequency in Hz. +/// * `wrap_val` - Chosen PWM counter wrap value (period - 1). +/// +/// # Returns +/// +/// Clock divider to program into the PWM slice. +/// +/// # Arguments +/// +/// * `sys_hz` - The `sys_hz` parameter. +/// * `freq_hz` - The `freq_hz` parameter. +/// * `wrap_val` - The `wrap_val` parameter. +/// +/// # Returns +/// +/// A value of type `f32`. + #[inline] +pub fn calc_clk_div(sys_hz: u32, freq_hz: u32, wrap_val: u32) -> f32 { + sys_hz as f32 / (freq_hz as f32 * (wrap_val + 1) as f32) +} + +/// Clamp a duty percentage to the valid 0–100 range. +/// +/// Values above 100 are clamped to 100. +/// +/// # Arguments +/// +/// * `percent` - Raw duty cycle percentage. +/// +/// # Returns +/// +/// Clamped percentage in the range 0..=100. +/// +/// # Arguments +/// +/// * `percent` - The `percent` parameter. +/// +/// # Returns +/// +/// An 8-bit unsigned integer value. + #[inline] +pub fn clamp_percent(percent: u8) -> u8 { + if percent > 100 { 100 } else { percent } +} + +/// Map a duty percentage to a PWM channel level. +/// +/// Converts a 0–100 percentage to the internal PWM counter range based +/// on the configured wrap value. +/// +/// # Arguments +/// +/// * `percent` - Duty cycle from 0 (always low) to 100 (always high). +/// * `wrap` - PWM counter wrap value (period - 1). +/// +/// # Returns +/// +/// Channel level value to write to the PWM slice. +/// +/// # Arguments +/// +/// * `percent` - The `percent` parameter. +/// * `wrap` - The `wrap` parameter. +/// +/// # Returns +/// +/// A 32-bit unsigned integer value. + #[inline] +pub fn duty_to_level(percent: u8, wrap: u32) -> u32 { + let p = clamp_percent(percent); + (p as u32 * (wrap + 1)) / 100 +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + + /// Executes the calc clk div 1khz operation. + #[test] + fn calc_clk_div_1khz() { + let div = calc_clk_div(150_000_000, 1000, 9999); + assert!((div - 15.0).abs() < 0.01); + } + + /// Executes the calc clk div 10khz operation. + #[test] + fn calc_clk_div_10khz() { + let div = calc_clk_div(150_000_000, 10000, 9999); + assert!((div - 1.5).abs() < 0.01); + } + + /// Executes the clamp percent within range operation. + #[test] + fn clamp_percent_within_range() { + assert_eq!(clamp_percent(50), 50); + } + + /// Executes the clamp percent at 100 operation. + #[test] + fn clamp_percent_at_100() { + assert_eq!(clamp_percent(100), 100); + } + + /// Executes the clamp percent above 100 operation. + #[test] + fn clamp_percent_above_100() { + assert_eq!(clamp_percent(255), 100); + } + + /// Executes the clamp percent zero operation. + #[test] + fn clamp_percent_zero() { + assert_eq!(clamp_percent(0), 0); + } + + /// Executes the duty to level zero operation. + #[test] + fn duty_to_level_zero() { + assert_eq!(duty_to_level(0, 9999), 0); + } + + /// Executes the duty to level 100 operation. + #[test] + fn duty_to_level_100() { + assert_eq!(duty_to_level(100, 9999), 10000); + } + + /// Executes the duty to level 50 operation. + #[test] + fn duty_to_level_50() { + assert_eq!(duty_to_level(50, 9999), 5000); + } + + /// Executes the duty to level clamped operation. + #[test] + fn duty_to_level_clamped() { + assert_eq!(duty_to_level(200, 9999), 10000); + } + + /// Executes the duty to level 5 operation. + #[test] + fn duty_to_level_5() { + assert_eq!(duty_to_level(5, 9999), 500); + } +} diff --git a/drivers/0x05_servo_rust/.cargo/config.toml b/drivers/0x05_servo_rust/.cargo/config.toml new file mode 100644 index 0000000..1791d74 --- /dev/null +++ b/drivers/0x05_servo_rust/.cargo/config.toml @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2021–2024 The rp-rs Developers +# Copyright (c) 2021 rp-rs organization +# Copyright (c) 2025 Raspberry Pi Ltd. +# +# Cargo Configuration for the https://github.com/rp-rs/rp-hal.git repository. +# +# You might want to make a similar file in your own repository if you are +# writing programs for Raspberry Silicon microcontrollers. +# + +[build] +target = "thumbv8m.main-none-eabihf" +# Set the default target to match the Cortex-M33 in the RP2350 +# target = "thumbv8m.main-none-eabihf" +# target = "thumbv6m-none-eabi" +# target = "riscv32imac-unknown-none-elf" + +# Target specific options +[target.thumbv6m-none-eabi] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as the linker +# script. This is usually provided by the cortex-m-rt crate, and by default +# the version in that crate will include a file called `memory.x` which +# describes the particular memory layout for your specific chip. +# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't +# have SIMD) +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the hard-float ABI for Arm mode. +# +# The FPU is enabled by default, and float function arguments use FPU +# registers. +[target.thumbv8m.main-none-eabihf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as a linker script. +# This is usually provided by the cortex-m-rt crate, and by default the +# version in that crate will include a file called `memory.x` which describes +# the particular memory layout for your specific chip. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the soft-float ABI for RISC-V mode. +# +# Hazard 3 does not have an FPU and so float function arguments use integer +# registers. +[target.riscv32imac-unknown-none-elf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Trp235x_riscv.x also tells the linker to use +# `rp235x_riscv.x` as a linker script. This adds in RP2350 RISC-V specific +# things that the riscv-rt crate's `link.x` requires and then includes +# `link.x` automatically. This is the reverse of how we do it on Cortex-M. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp2350_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +[env] +DEFMT_LOG = "debug" diff --git a/drivers/0x05_servo_rust/.gitignore b/drivers/0x05_servo_rust/.gitignore new file mode 100644 index 0000000..03381c1 --- /dev/null +++ b/drivers/0x05_servo_rust/.gitignore @@ -0,0 +1,113 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,macos,windows,linux + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux diff --git a/drivers/0x05_servo_rust/.pico-rs b/drivers/0x05_servo_rust/.pico-rs new file mode 100644 index 0000000..1b6702a --- /dev/null +++ b/drivers/0x05_servo_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 \ No newline at end of file diff --git a/drivers/0x05_servo_rust/.vscode/extensions.json b/drivers/0x05_servo_rust/.vscode/extensions.json new file mode 100644 index 0000000..5f2fd0c --- /dev/null +++ b/drivers/0x05_servo_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/drivers/0x05_servo_rust/.vscode/launch.json b/drivers/0x05_servo_rust/.vscode/launch.json new file mode 100644 index 0000000..0bc38c3 --- /dev/null +++ b/drivers/0x05_servo_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} \ No newline at end of file diff --git a/drivers/0x05_servo_rust/.vscode/settings.json b/drivers/0x05_servo_rust/.vscode/settings.json new file mode 100644 index 0000000..b03cd57 --- /dev/null +++ b/drivers/0x05_servo_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} \ No newline at end of file diff --git a/drivers/0x05_servo_rust/.vscode/tasks.json b/drivers/0x05_servo_rust/.vscode/tasks.json new file mode 100644 index 0000000..4194af4 --- /dev/null +++ b/drivers/0x05_servo_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/drivers/0x05_servo_rust/Cargo.toml b/drivers/0x05_servo_rust/Cargo.toml new file mode 100644 index 0000000..47ae9c7 --- /dev/null +++ b/drivers/0x05_servo_rust/Cargo.toml @@ -0,0 +1,80 @@ +[package] +edition = "2024" +name = "servo" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "servo_lib" +path = "src/lib.rs" + +[[bin]] +name = "servo" +path = "src/main.rs" + + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embedded-hal = "1.0.0" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 3 diff --git a/drivers/0x05_servo_rust/LICENSE-APACHE b/drivers/0x05_servo_rust/LICENSE-APACHE new file mode 100644 index 0000000..8d99cbc --- /dev/null +++ b/drivers/0x05_servo_rust/LICENSE-APACHE @@ -0,0 +1,204 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2021–2024 The rp-rs Developers + Copyright (c) 2021 rp-rs organization + Copyright (c) 2025 Raspberry Pi Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/drivers/0x05_servo_rust/LICENSE-MIT b/drivers/0x05_servo_rust/LICENSE-MIT new file mode 100644 index 0000000..5369c70 --- /dev/null +++ b/drivers/0x05_servo_rust/LICENSE-MIT @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2021–2024 The rp-rs Developers +Copyright (c) 2021 rp-rs organization +Copyright (c) 2025 Raspberry Pi Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + \ No newline at end of file diff --git a/drivers/0x05_servo_rust/README.md b/drivers/0x05_servo_rust/README.md new file mode 100644 index 0000000..04139b9 --- /dev/null +++ b/drivers/0x05_servo_rust/README.md @@ -0,0 +1,117 @@ +# 0x05 Servo Rust Driver + +This repository contains a Bare-Metal Rust driver for controlling standard 50Hz hobby Servos via PWM on the **RP2350** (and RP2040) microcontrollers. + +It includes: +- A thin demo (`src/main.rs`) that repeatedly sweeps a servo horn back and forth between 0 and 180 degrees while printing the current angle over UART. +- A reusable library module (`src/servo.rs`) providing a hardware-agnostic `servo_lib` with helper math to map angles to precise pulse-width microsecond targets. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the Servo mapping logic is separated into a reusable math library without touching hardware registers, you can run the unit tests natively on your computer! + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we initialize the system clocks, pins, UART (for logging), SysTick (for delay), and PWM (for servo control). + +* **`run(...)`**: The master setup function. Calls the helper initialization functions below, prints an announcement over UART, and enters the servo sweep loop. +* **`init_clocks(...)`**: Wakes up the external 12 MHz crystal (`XOSC`) and configures the PLLs (Phase-Locked Loops) to drive the system clock at its maximum speed. +* **`init_pins(...)`**: Takes control of physical pins across `IO_BANK0`. +* **`init_uart(...)`**: Configures the hardware `UART0` peripheral to operate at a standard `115200` baud rate with an `8N1` configuration for debug printing. +* **`init_delay(...)`**: Captures the ARM Cortex-M `SYST` (SysTick) peripheral to create a blocking delay timer. +* **`init_servo_pwm(...)`**: Obtains `PWM Slice 3` and binds its `channel_a` to `GPIO 6`. Sets up the clock divider and TOP value to achieve a standard 50 Hz PWM frequency. +* **`servo_loop(...)`**: Loops infinitely, sweeping the servo angle from 0 to 180 degrees and back down to 0. +* **`sweep_angle_up(...)`** / **`sweep_angle_down(...)`**: Walks through angles in 10-degree increments. +* **`apply_angle(...)`**: Computes the hardware register level using `servo_lib`, sets the new duty cycle, formats an `Angle: XXX deg` string without heap allocation, transmits it, and delays 150ms to allow the physical servo motor to arrive at the target angle. + +### 3. The Reusable Servo Math Library (`src/servo.rs`) +Unlike basic LEDs which simply vary duty cycle from 0-100%, Servos expect a precise 50 Hz signal (20ms period) where the "HIGH" time represents the angle: usually ~1ms for 0 degrees, ~1.5ms for 90 degrees, and ~2ms for 180 degrees. + +* **`calc_clk_div(...)`**: Calculates the precise floating-point clock divider required to step down the fast system clock so the counter overflows exactly 50 times per second. +* **`angle_to_pulse_us(...)`**: Linearly maps an angle (0.0 to 180.0 degrees) to a pulse width (e.g. 1000 to 2000 microseconds). +* **`pulse_us_to_level(...)`**: Translates the desired microsecond pulse into a raw integer counter compare-value for the PWM hardware registers. +* **`clamp_pulse_us(...)`** / **`clamp_degrees(...)`**: Safety constraints to ensure we do not physically damage the servo by driving it past its mechanical limits. diff --git a/drivers/0x05_servo_rust/build.rs b/drivers/0x05_servo_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x05_servo_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x05_servo_rust/rp2040.x b/drivers/0x05_servo_rust/rp2040.x new file mode 100644 index 0000000..0cc665b --- /dev/null +++ b/drivers/0x05_servo_rust/rp2040.x @@ -0,0 +1,91 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + /* + * Here we assume you have 2048 KiB of Flash. This is what the Pi Pico + * has, but your board may have more or less Flash and you should adjust + * this value to suit. + */ + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + /* + * RAM consists of 4 banks, SRAM0-SRAM3, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 256K + /* + * RAM banks 4 and 5 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k + + /* SRAM banks 0-3 can also be accessed directly. However, those ranges + alias with the RAM mapping, above. So don't use them at the same time! + SRAM0 : ORIGIN = 0x21000000, LENGTH = 64k + SRAM1 : ORIGIN = 0x21010000, LENGTH = 64k + SRAM2 : ORIGIN = 0x21020000, LENGTH = 64k + SRAM3 : ORIGIN = 0x21030000, LENGTH = 64k + */ +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + /* ### Boot loader + * + * An executable block of code which sets up the QSPI interface for + * 'Execute-In-Place' (or XIP) mode. Also sends chip-specific commands to + * the external flash chip. + * + * Must go at the start of external flash, where the Boot ROM expects it. + */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 512 bytes of flash, + * where picotool can find it + */ + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; diff --git a/drivers/0x05_servo_rust/rp2350.x b/drivers/0x05_servo_rust/rp2350.x new file mode 100644 index 0000000..bda94d8 --- /dev/null +++ b/drivers/0x05_servo_rust/rp2350.x @@ -0,0 +1,83 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + /* move .text to start /after/ the boot info */ + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + \ No newline at end of file diff --git a/drivers/0x05_servo_rust/rp2350_riscv.x b/drivers/0x05_servo_rust/rp2350_riscv.x new file mode 100644 index 0000000..84388aa --- /dev/null +++ b/drivers/0x05_servo_rust/rp2350_riscv.x @@ -0,0 +1,259 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut _heap_size }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all `32`-byte aligned. These alignments are assumed by the RAM + initialization routine. There's also a second benefit: `32`-byte aligned boundaries + means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ +PROVIDE(_setup_interrupts = default_setup_interrupts); + +/* # Multi-processing hook function + fn _mp_hook() -> bool; + + This function is called from all the harts and must return true only for one hart, + which will perform memory initialization. For other harts it must return false + and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). +*/ +PROVIDE(_mp_hook = default_mp_hook); + +/* # Start trap function override + By default uses the riscv crates default trap handler + but by providing the `_start_trap` symbol external crates can override. +*/ +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + /* This section is intended to make _stext address work */ + . = ABSOLUTE(_stext); + } > FLASH + + .text _stext : + { + /* Put reset handler first in .text section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + __start_block_addr = .; + KEEP(*(.start_block)); + . = ALIGN(4); + *(.trap); + *(.trap.rust); + *(.text.abort); + *(.text .text.*); + . = ALIGN(4); + } > FLASH + + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + + .rodata : ALIGN(4) + { + *(.srodata .srodata.*); + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + } > FLASH + + .data : ALIGN(32) + { + _sidata = LOADADDR(.data); + __sidata = LOADADDR(.data); + _sdata = .; + __sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + . = ALIGN(32); + _edata = .; + __edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : ALIGN(32) + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(32); + _ebss = .; + } > RAM + + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + _sheap = .; + . += _heap_size; + . = ALIGN(4); + _eheap = .; + } > RAM + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + _estack = .; + . = ABSOLUTE(_stack_start); + _sstack = .; + } > RAM + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + .eh_frame (INFO) : { KEEP(*(.eh_frame)) } + .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } +} + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(FLASH) % 4 == 0, " +ERROR(riscv-rt): the start of the FLASH must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 32 == 0, " +ERROR(riscv-rt): the start of the RAM must be 32-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 32 == 0 && _edata % 32 == 0, " +BUG(riscv-rt): .data is not 32-byte aligned"); + +ASSERT(_sidata % 32 == 0, " +BUG(riscv-rt): the LMA of .data is not 32-byte aligned"); + +ASSERT(_sbss % 32 == 0 && _ebss % 32 == 0, " +BUG(riscv-rt): .bss is not 32-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), " +ERROR(riscv-rt): The .text section must be placed inside the FLASH region. +Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'"); + +ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +/* Do not exceed this mark in the error messages above | */ diff --git a/drivers/0x05_servo_rust/src/board.rs b/drivers/0x05_servo_rust/src/board.rs new file mode 100644 index 0000000..e611c2d --- /dev/null +++ b/drivers/0x05_servo_rust/src/board.rs @@ -0,0 +1,481 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// PWM duty-cycle trait for .set_duty_cycle() +use embedded_hal::pwm::SetDutyCycle; +// Rate extension trait for .Hz() baud rate construction +use fugit::RateExtU32; +// Clock trait for accessing system clock frequency +use hal::Clock; +// GPIO pin types and function selectors +use hal::gpio::{FunctionNull, FunctionUart, Pin, PullDown, PullNone}; +// UART configuration and peripheral types +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// Angle increment per sweep step in degrees. +pub(crate) const STEP_DEGREES: i32 = 10; + +/// Delay between sweep steps in milliseconds. +pub(crate) const STEP_DELAY_MS: u32 = 150; + +/// Type alias for the configured TX pin (GPIO 0, UART function, no pull). +pub(crate) type TxPin = Pin; + +/// Type alias for the configured RX pin (GPIO 1, UART function, no pull). +pub(crate) type RxPin = Pin; + +/// Type alias for the default TX pin state from `Pins::new()`. +pub(crate) type TxPinDefault = Pin; + +/// Type alias for the default RX pin state from `Pins::new()`. +pub(crate) type RxPinDefault = Pin; + +/// Type alias for the fully-enabled UART0 peripheral with TX/RX p. +pub(crate) type EnabledUart = UartPeripheral; + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +/// +/// # Arguments +/// +/// * `xosc` - XOSC peripheral singleton. +/// * `clocks` - CLOCKS peripheral singleton. +/// * `pll_sys` - PLL_SYS peripheral singleton. +/// * `pll_usb` - PLL_USB peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `watchdog` - Mutable reference to the watchdog timer. +/// +/// # Returns +/// +/// Configured clocks manager. +/// +/// # Panics +/// +/// Panics if clock initialisation fails. +pub(crate) fn init_clocks( + xosc: hal::pac::XOSC, + clocks: hal::pac::CLOCKS, + pll_sys: hal::pac::PLL_SYS, + pll_usb: hal::pac::PLL_USB, + resets: &mut hal::pac::RESETS, + watchdog: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + xosc, + clocks, + pll_sys, + pll_usb, + resets, + watchdog, + ) + .unwrap() +} + +/// Unlock the GPIO bank and return the pin set. +/// +/// # Arguments +/// +/// * `io_bank0` - IO_BANK0 peripheral singleton. +/// * `pads_bank0` - PADS_BANK0 peripheral singleton. +/// * `sio` - SIO peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// +/// # Returns +/// +/// GPIO pin set for the entire bank. +pub(crate) fn init_pins( + io_bank0: hal::pac::IO_BANK0, + pads_bank0: hal::pac::PADS_BANK0, + sio: hal::pac::SIO, + resets: &mut hal::pac::RESETS, +) -> hal::gpio::Pins { + let sio = hal::Sio::new(sio); + hal::gpio::Pins::new(io_bank0, pads_bank0, sio.gpio_bank0, resets) +} + +/// Initialise UART0 for serial output (stdio equivalent). +/// +/// # Arguments +/// +/// * `uart0` - PAC UART0 peripheral singleton. +/// * `tx_pin` - GPIO pin to use as UART0 TX (GPIO 0). +/// * `rx_pin` - GPIO pin to use as UART0 RX (GPIO 1). +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Enabled UART0 peripheral ready for blocking writes. +/// +/// # Panics +/// +/// Panics if the HAL cannot achieve the requested baud rate. +pub(crate) fn init_uart( + uart0: hal::pac::UART0, + tx_pin: TxPinDefault, + rx_pin: RxPinDefault, + resets: &mut hal::pac::RESETS, + clocks: &hal::clocks::ClocksManager, +) -> EnabledUart { + let pins = ( + tx_pin.reconfigure::(), + rx_pin.reconfigure::(), + ); + let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One); + UartPeripheral::new(uart0, pins, resets) + .enable(cfg, clocks.peripheral_clock.freq()) + .unwrap() +} + +/// Create a blocking delay timer from the ARM SysTick peripheral. +/// +/// # Arguments +/// +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Blocking delay provider. +/// +/// # Panics +/// +/// Panics if the cortex-m core peripherals have already been taken. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +pub(crate) fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay { + let core = cortex_m::Peripherals::take().unwrap(); + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()) +} + +/// Write 3-character right-justified angle digits into `buf`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `a` - The `a` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `a` - The `a` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +fn write_angle_digits(buf: &mut [u8], a: u32) -> usize { + if a >= 100 { write_angle_hundreds(buf, a); } + else if a >= 10 { write_angle_tens(buf, a); } + else { write_angle_ones(buf, a); } + 3 +} + +/// Write digits for angles >= 100. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `a` - The `a` parameter. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `a` - The `a` parameter. +fn write_angle_hundreds(buf: &mut [u8], a: u32) { + buf[0] = b'0' + (a / 100) as u8; + buf[1] = b'0' + ((a / 10) % 10) as u8; + buf[2] = b'0' + (a % 10) as u8; +} + +/// Write digits for angles 10..99 with leading space. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `a` - The `a` parameter. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `a` - The `a` parameter. +fn write_angle_tens(buf: &mut [u8], a: u32) { + buf[0] = b' '; + buf[1] = b'0' + (a / 10) as u8; + buf[2] = b'0' + (a % 10) as u8; +} + +/// Write digit for angles 0..9 with leading spaces. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `a` - The `a` parameter. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `a` - The `a` parameter. +fn write_angle_ones(buf: &mut [u8], a: u32) { + buf[0] = b' '; + buf[1] = b' '; + buf[2] = b'0' + a as u8; +} + +/// Format an angle into "Angle: NNN deg\r\n". +/// +/// # Arguments +/// +/// * `buf` - Mutable byte slice (must be at least 20 bytes). +/// * `angle` - Angle in degrees (0..180). +/// +/// # Returns +/// +/// Number of bytes written into the buffer. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `angle` - The `angle` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +pub(crate) fn format_angle(buf: &mut [u8], angle: i32) -> usize { + buf[..7].copy_from_slice(b"Angle: "); + let pos = 7 + write_angle_digits(&mut buf[7..], if angle < 0 { 0 } else { angle as u32 }); + buf[pos..pos + 6].copy_from_slice(b" deg\r\n"); + pos + 6 +} + +/// Sweep the servo angle upward from 0 to 180 in STEP_DEGREES increments. +/// +/// # Arguments +/// +/// * `uart` - UART peripheral for serial output. +/// * `channel` - PWM channel implementing SetDutyCycle. +/// * `delay` - Delay provider for pause between steps. +/// * `buf` - Scratch buffer for formatting output. +pub(crate) fn sweep_angle_up(u: &EnabledUart, ch: &mut impl SetDutyCycle, d: &mut cortex_m::delay::Delay, buf: &mut [u8; 20]) { + let mut angle: i32 = 0; + while angle <= 180 { apply_angle(u, ch, d, buf, angle); angle += STEP_DEGREES; } +} + +/// Sweep the servo angle downward from 180 to 0 in STEP_DEGREES decrements. +/// +/// # Arguments +/// +/// * `uart` - UART peripheral for serial output. +/// * `channel` - PWM channel implementing SetDutyCycle. +/// * `delay` - Delay provider for pause between steps. +/// * `buf` - Scratch buffer for formatting output. +pub(crate) fn sweep_angle_down(u: &EnabledUart, ch: &mut impl SetDutyCycle, d: &mut cortex_m::delay::Delay, buf: &mut [u8; 20]) { + let mut angle: i32 = 180; + while angle >= 0 { apply_angle(u, ch, d, buf, angle); angle -= STEP_DEGREES; } +} + +/// Apply a single angle step: compute pulse, set PWM, format, print, delay. +fn apply_angle(u: &EnabledUart, ch: &mut impl SetDutyCycle, d: &mut cortex_m::delay::Delay, buf: &mut [u8; 20], a: i32) { + ch.set_duty_cycle(compute_servo_level(a) as u16).ok(); + let n = format_angle(buf, a); + u.write_full_blocking(&buf[..n]); + d.delay_ms(STEP_DELAY_MS); +} + +/// Compute the pulse width in microseconds for the given angle. +/// +/// # Arguments +/// +/// * `angle` - The `angle` parameter. +/// +/// # Returns +/// +/// A 32-bit unsigned integer value. +/// +/// # Arguments +/// +/// * `angle` - The `angle` parameter. +/// +/// # Returns +/// +/// A 32-bit unsigned integer value. +fn compute_pulse_us(a: i32) -> u32 { + servo_lib::servo::angle_to_pulse_us(a as f32, servo_lib::servo::SERVO_DEFAULT_MIN_US, servo_lib::servo::SERVO_DEFAULT_MAX_US) as u32 +} + +/// Compute the PWM level for a given angle using servo constants. +/// +/// # Arguments +/// +/// * `angle` - The `angle` parameter. +/// +/// # Returns +/// +/// A 32-bit unsigned integer value. +/// +/// # Arguments +/// +/// * `angle` - The `angle` parameter. +/// +/// # Returns +/// +/// A 32-bit unsigned integer value. +fn compute_servo_level(a: i32) -> u32 { + servo_lib::servo::pulse_us_to_level(compute_pulse_us(a), servo_lib::servo::SERVO_WRAP, servo_lib::servo::SERVO_HZ) +} + +/// Type alias for PWM slice 3 (servo on GPIO 6, channel A). +type PwmSlice3 = hal::pwm::Slice; + +/// Initialise all peripherals and run the servo sweep demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `pac` - The `pac` parameter. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! { + let mut w = hal::Watchdog::new(p.WATCHDOG); + let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut w); + let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS); + let u = init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c); + announce_servo(&u); + servo_loop(&u, &mut init_servo_pwm(p.PWM, &mut p.RESETS, &c, pins.gpio6), &mut init_delay(&c)) +} + +/// Configure PWM slice 3 for 50 Hz servo output on channel A (GPIO 6). +/// +/// # Arguments +/// +/// * `pwm_pac` - PAC PWM peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// * `servo_pin` - Default GPIO 6 pin to bind to PWM channel A. +/// +/// # Returns +/// +/// Configured PWM slice 3 in free-running mode. +fn init_servo_pwm( + p: hal::pac::PWM, r: &mut hal::pac::RESETS, c: &hal::clocks::ClocksManager, servo: Pin +) -> PwmSlice3 { + let mut s = hal::pwm::Slices::new(p, r).pwm3; + configure_servo_div(&mut s, c); + s.enable(); + s.channel_a.output_to(servo); + s +} + +/// Set the clock divider and wrap for a servo PWM slice. +/// +/// # Arguments +/// +/// * `slice` - Mutable reference to the PWM slice to configure. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Arguments +/// +/// * `slice` - The `slice` parameter. +/// * `clocks` - The `clocks` parameter. +fn configure_servo_div(s: &mut PwmSlice3, c: &hal::clocks::ClocksManager) { + let div = servo_lib::servo::calc_clk_div(c.system_clock.freq().to_Hz(), servo_lib::servo::SERVO_HZ, servo_lib::servo::SERVO_WRAP); + s.set_div_int(div as u8); + s.set_div_frac((((div - (div as f32)) * 16.0) as u8).min(15)); + s.set_top(servo_lib::servo::SERVO_WRAP as u16); +} + +/// Print the servo initialisation banner over UART. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +fn announce_servo(uart: &EnabledUart) { + uart.write_full_blocking(b"Servo driver initialized on GPIO 6\r\n"); + uart.write_full_blocking(b"Sweeping 0 -> 180 -> 0 degrees in 10-degree steps\r\n"); +} + +/// Run the servo angle sweep loop forever. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// * `pwm` - Mutable reference to the configured PWM slice. +/// * `delay` - Mutable reference to the blocking delay provider. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +/// * `pwm` - The `pwm` parameter. +/// * `delay` - Delay value. +/// +/// # Returns +/// +/// A value of type `!`. +fn servo_loop(u: &EnabledUart, p: &mut PwmSlice3, d: &mut cortex_m::delay::Delay) -> ! { + let mut buf = [0u8; 20]; + loop { sweep_angle_up(u, &mut p.channel_a, d, &mut buf); sweep_angle_down(u, &mut p.channel_a, d, &mut buf); } +} + diff --git a/drivers/0x05_servo_rust/src/lib.rs b/drivers/0x05_servo_rust/src/lib.rs new file mode 100644 index 0000000..75de91f --- /dev/null +++ b/drivers/0x05_servo_rust/src/lib.rs @@ -0,0 +1,6 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +#![cfg_attr(not(test), no_std)] +pub mod servo; diff --git a/drivers/0x05_servo_rust/src/main.rs b/drivers/0x05_servo_rust/src/main.rs new file mode 100644 index 0000000..0416852 --- /dev/null +++ b/drivers/0x05_servo_rust/src/main.rs @@ -0,0 +1,102 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; +// Servo driver module — suppress warnings for unused public API functions + +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the servo sweep demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"SG90 Servo Sweep Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/drivers/0x05_servo_rust/src/servo.rs b/drivers/0x05_servo_rust/src/servo.rs new file mode 100644 index 0000000..1c2a058 --- /dev/null +++ b/drivers/0x05_servo_rust/src/servo.rs @@ -0,0 +1,293 @@ +//! Implementation module +//! +//! **File:** `servo.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +/// Default minimum pulse width in microseconds (0 degrees). +pub const SERVO_DEFAULT_MIN_US: u16 = 1000; + +/// Default maximum pulse width in microseconds (180 degrees). +pub const SERVO_DEFAULT_MAX_US: u16 = 2000; + +/// Default PWM wrap value for 50 Hz servo (20 000 - 1). +pub const SERVO_WRAP: u32 = 20000 - 1; + +/// Default servo frequency in Hz. +pub const SERVO_HZ: f32 = 50.0; + +/// Convert a pulse width in microseconds to a PWM counter level. +/// +/// Uses the configured PWM wrap and servo frequency to map pulse time +/// into the channel compare value expected by the PWM hardware. +/// +/// # Arguments +/// +/// * `pulse_us` - Pulse width in microseconds. +/// * `wrap` - PWM counter wrap value. +/// * `hz` - PWM frequency in Hz. +/// +/// # Returns +/// +/// PWM level suitable for the channel compare register. +/// +/// # Arguments +/// +/// * `pulse_us` - The `pulse_us` parameter. +/// * `wrap` - The `wrap` parameter. +/// * `hz` - The `hz` parameter. +/// +/// # Returns +/// +/// A 32-bit unsigned integer value. +#[inline] +pub fn pulse_us_to_level(pulse_us: u32, wrap: u32, hz: f32) -> u32 { + let period_us = 1_000_000.0f32 / hz; + let counts_per_us = (wrap + 1) as f32 / period_us; + (pulse_us as f32 * counts_per_us + 0.5f32) as u32 +} + +/// Clamp a pulse width to the valid servo range. +/// +/// Values below min_us are raised to min_us; values above max_us are +/// lowered to max_us. +/// +/// # Arguments +/// +/// * `pulse_us` - Raw pulse width in microseconds. +/// * `min_us` - Minimum allowed pulse width. +/// * `max_us` - Maximum allowed pulse width. +/// +/// # Returns +/// +/// Clamped pulse width. +/// +/// # Arguments +/// +/// * `pulse_us` - The `pulse_us` parameter. +/// * `min_us` - The `min_us` parameter. +/// * `max_us` - The `max_us` parameter. +/// +/// # Returns +/// +/// A 16-bit unsigned integer value. +#[inline] +pub fn clamp_pulse_us(pulse_us: u16, min_us: u16, max_us: u16) -> u16 { + if pulse_us < min_us { + min_us + } else if pulse_us > max_us { + max_us + } else { + pulse_us + } +} + +/// Clamp a floating-point angle to the valid servo range [0.0, 180.0]. +/// +/// # Arguments +/// +/// * `degrees` - The `degrees` parameter. +/// +/// # Returns +/// +/// A value of type `f32`. +/// +/// # Arguments +/// +/// * `degrees` - The `degrees` parameter. +/// +/// # Returns +/// +/// A value of type `f32`. +#[inline] +fn clamp_degrees(degrees: f32) -> f32 { + if degrees < 0.0f32 { + 0.0f32 + } else if degrees > 180.0f32 { + 180.0f32 + } else { + degrees + } +} + +/// Map a servo angle in degrees to a pulse width in microseconds. +/// +/// Clamps degrees to [0, 180], then linearly maps to the pulse range. +/// +/// # Arguments +/// +/// * `degrees` - Angle in degrees (0.0 to 180.0). +/// * `min_us` - Pulse width at 0 degrees. +/// * `max_us` - Pulse width at 180 degrees. +/// +/// # Returns +/// +/// Pulse width in microseconds corresponding to the given angle. +/// +/// # Arguments +/// +/// * `degrees` - The `degrees` parameter. +/// * `min_us` - The `min_us` parameter. +/// * `max_us` - The `max_us` parameter. +/// +/// # Returns +/// +/// A 16-bit unsigned integer value. +#[inline] +pub fn angle_to_pulse_us(degrees: f32, min_us: u16, max_us: u16) -> u16 { + let d = clamp_degrees(degrees); + let ratio = d / 180.0f32; + let span = (max_us - min_us) as f32; + (min_us as f32 + ratio * span + 0.5f32) as u16 +} + +/// Compute the PWM clock divider for the servo frequency. +/// +/// # Arguments +/// +/// * `sys_hz` - System clock frequency in Hz. +/// * `servo_hz` - Desired servo PWM frequency in Hz. +/// * `wrap` - PWM counter wrap value. +/// +/// # Returns +/// +/// Clock divider value. +/// +/// # Arguments +/// +/// * `sys_hz` - The `sys_hz` parameter. +/// * `servo_hz` - The `servo_hz` parameter. +/// * `wrap` - The `wrap` parameter. +/// +/// # Returns +/// +/// A value of type `f32`. +#[inline] +pub fn calc_clk_div(sys_hz: u32, servo_hz: f32, wrap: u32) -> f32 { + sys_hz as f32 / (servo_hz * (wrap + 1) as f32) +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + + /// Executes the pulse us to level 1000us operation. + #[test] + fn pulse_us_to_level_1000us() { + let level = pulse_us_to_level(1000, SERVO_WRAP, SERVO_HZ); + assert_eq!(level, 1000); + } + + /// Executes the pulse us to level 2000us operation. + #[test] + fn pulse_us_to_level_2000us() { + let level = pulse_us_to_level(2000, SERVO_WRAP, SERVO_HZ); + assert_eq!(level, 2000); + } + + /// Executes the pulse us to level 1500us operation. + #[test] + fn pulse_us_to_level_1500us() { + let level = pulse_us_to_level(1500, SERVO_WRAP, SERVO_HZ); + assert_eq!(level, 1500); + } + + /// Executes the pulse us to level zero operation. + #[test] + fn pulse_us_to_level_zero() { + let level = pulse_us_to_level(0, SERVO_WRAP, SERVO_HZ); + assert_eq!(level, 0); + } + + /// Executes the clamp pulse us below min operation. + #[test] + fn clamp_pulse_us_below_min() { + assert_eq!( + clamp_pulse_us(500, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US), + 1000 + ); + } + + /// Executes the clamp pulse us above max operation. + #[test] + fn clamp_pulse_us_above_max() { + assert_eq!( + clamp_pulse_us(3000, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US), + 2000 + ); + } + + /// Executes the clamp pulse us within range operation. + #[test] + fn clamp_pulse_us_within_range() { + assert_eq!( + clamp_pulse_us(1500, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US), + 1500 + ); + } + + /// Executes the angle to pulse us zero operation. + #[test] + fn angle_to_pulse_us_zero() { + let pulse = angle_to_pulse_us(0.0, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US); + assert_eq!(pulse, 1000); + } + + /// Executes the angle to pulse us 180 operation. + #[test] + fn angle_to_pulse_us_180() { + let pulse = angle_to_pulse_us(180.0, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US); + assert_eq!(pulse, 2000); + } + + /// Executes the angle to pulse us 90 operation. + #[test] + fn angle_to_pulse_us_90() { + let pulse = angle_to_pulse_us(90.0, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US); + assert_eq!(pulse, 1500); + } + + /// Executes the angle to pulse us clamped negative operation. + #[test] + fn angle_to_pulse_us_clamped_negative() { + let pulse = angle_to_pulse_us(-10.0, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US); + assert_eq!(pulse, 1000); + } + + /// Executes the angle to pulse us clamped above operation. + #[test] + fn angle_to_pulse_us_clamped_above() { + let pulse = angle_to_pulse_us(200.0, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US); + assert_eq!(pulse, 2000); + } + + /// Executes the calc clk div 150mhz operation. + #[test] + fn calc_clk_div_150mhz() { + let div = calc_clk_div(150_000_000, SERVO_HZ, SERVO_WRAP); + assert!((div - 150.0).abs() < 0.01); + } +} diff --git a/drivers/0x06_adc_rust/.cargo/config.toml b/drivers/0x06_adc_rust/.cargo/config.toml new file mode 100644 index 0000000..1791d74 --- /dev/null +++ b/drivers/0x06_adc_rust/.cargo/config.toml @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2021–2024 The rp-rs Developers +# Copyright (c) 2021 rp-rs organization +# Copyright (c) 2025 Raspberry Pi Ltd. +# +# Cargo Configuration for the https://github.com/rp-rs/rp-hal.git repository. +# +# You might want to make a similar file in your own repository if you are +# writing programs for Raspberry Silicon microcontrollers. +# + +[build] +target = "thumbv8m.main-none-eabihf" +# Set the default target to match the Cortex-M33 in the RP2350 +# target = "thumbv8m.main-none-eabihf" +# target = "thumbv6m-none-eabi" +# target = "riscv32imac-unknown-none-elf" + +# Target specific options +[target.thumbv6m-none-eabi] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as the linker +# script. This is usually provided by the cortex-m-rt crate, and by default +# the version in that crate will include a file called `memory.x` which +# describes the particular memory layout for your specific chip. +# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't +# have SIMD) +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the hard-float ABI for Arm mode. +# +# The FPU is enabled by default, and float function arguments use FPU +# registers. +[target.thumbv8m.main-none-eabihf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as a linker script. +# This is usually provided by the cortex-m-rt crate, and by default the +# version in that crate will include a file called `memory.x` which describes +# the particular memory layout for your specific chip. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the soft-float ABI for RISC-V mode. +# +# Hazard 3 does not have an FPU and so float function arguments use integer +# registers. +[target.riscv32imac-unknown-none-elf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Trp235x_riscv.x also tells the linker to use +# `rp235x_riscv.x` as a linker script. This adds in RP2350 RISC-V specific +# things that the riscv-rt crate's `link.x` requires and then includes +# `link.x` automatically. This is the reverse of how we do it on Cortex-M. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp2350_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +[env] +DEFMT_LOG = "debug" diff --git a/drivers/0x06_adc_rust/.gitignore b/drivers/0x06_adc_rust/.gitignore new file mode 100644 index 0000000..03381c1 --- /dev/null +++ b/drivers/0x06_adc_rust/.gitignore @@ -0,0 +1,113 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,macos,windows,linux + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux diff --git a/drivers/0x06_adc_rust/.pico-rs b/drivers/0x06_adc_rust/.pico-rs new file mode 100644 index 0000000..1b6702a --- /dev/null +++ b/drivers/0x06_adc_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 \ No newline at end of file diff --git a/drivers/0x06_adc_rust/.vscode/extensions.json b/drivers/0x06_adc_rust/.vscode/extensions.json new file mode 100644 index 0000000..5f2fd0c --- /dev/null +++ b/drivers/0x06_adc_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/drivers/0x06_adc_rust/.vscode/launch.json b/drivers/0x06_adc_rust/.vscode/launch.json new file mode 100644 index 0000000..0bc38c3 --- /dev/null +++ b/drivers/0x06_adc_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} \ No newline at end of file diff --git a/drivers/0x06_adc_rust/.vscode/settings.json b/drivers/0x06_adc_rust/.vscode/settings.json new file mode 100644 index 0000000..b03cd57 --- /dev/null +++ b/drivers/0x06_adc_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} \ No newline at end of file diff --git a/drivers/0x06_adc_rust/.vscode/tasks.json b/drivers/0x06_adc_rust/.vscode/tasks.json new file mode 100644 index 0000000..4194af4 --- /dev/null +++ b/drivers/0x06_adc_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/drivers/0x06_adc_rust/Cargo.toml b/drivers/0x06_adc_rust/Cargo.toml new file mode 100644 index 0000000..ac39b1c --- /dev/null +++ b/drivers/0x06_adc_rust/Cargo.toml @@ -0,0 +1,80 @@ +[package] +edition = "2024" +name = "adc" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "adc_lib" +path = "src/lib.rs" + +[[bin]] +name = "adc" +path = "src/main.rs" + + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embedded-hal = "1.0.0" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 3 diff --git a/drivers/0x06_adc_rust/LICENSE-APACHE b/drivers/0x06_adc_rust/LICENSE-APACHE new file mode 100644 index 0000000..8d99cbc --- /dev/null +++ b/drivers/0x06_adc_rust/LICENSE-APACHE @@ -0,0 +1,204 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2021–2024 The rp-rs Developers + Copyright (c) 2021 rp-rs organization + Copyright (c) 2025 Raspberry Pi Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/drivers/0x06_adc_rust/LICENSE-MIT b/drivers/0x06_adc_rust/LICENSE-MIT new file mode 100644 index 0000000..5369c70 --- /dev/null +++ b/drivers/0x06_adc_rust/LICENSE-MIT @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2021–2024 The rp-rs Developers +Copyright (c) 2021 rp-rs organization +Copyright (c) 2025 Raspberry Pi Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + \ No newline at end of file diff --git a/drivers/0x06_adc_rust/README.md b/drivers/0x06_adc_rust/README.md new file mode 100644 index 0000000..271fe70 --- /dev/null +++ b/drivers/0x06_adc_rust/README.md @@ -0,0 +1,114 @@ +# 0x06 ADC Rust Driver + +This repository contains a Bare-Metal Rust driver for the ADC (Analog-to-Digital Converter) peripheral on the **RP2350** (and RP2040) microcontrollers. + +It includes: +- A thin demo (`src/main.rs`) that continuously reads the analog voltage on GPIO 26 and the internal chip temperature, printing both over UART. +- A reusable library module (`src/adc.rs`) providing a hardware-agnostic `adc_lib` with helper math to map raw ADC counts to millivolts and degrees Celsius. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the ADC mathematical conversion logic is separated into a reusable math library without touching hardware registers, you can run the unit tests natively on your computer! + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we initialize the system clocks, pins, UART (for logging), SysTick (for delay), and the ADC (for reading analog inputs). + +* **`run(...)`**: The master setup function. Calls the helper initialization functions below, prints an announcement over UART, and enters the infinite ADC polling loop. +* **`init_clocks(...)`**: Wakes up the external 12 MHz crystal (`XOSC`) and configures the PLLs (Phase-Locked Loops) to drive the system clock at its maximum speed. +* **`init_pins(...)`**: Takes control of physical pins across `IO_BANK0`. +* **`init_uart(...)`**: Configures the hardware `UART0` peripheral to operate at a standard `115200` baud rate with an `8N1` configuration for debug printing. +* **`init_adc(...)`**: Initializes the hardware ADC peripheral, allocates `GPIO 26` as an analog input pin, and claims the internal chip temperature sensor. +* **`adc_loop(...)`**: Loops infinitely, reading the latest voltage and temperature, formatting the line, transmitting it over UART, and sleeping for `POLL_MS` (500ms). +* **`read_adc(...)`**: Queries the hardware ADC peripheral for the current 12-bit analog counts for both `GPIO 26` and the internal temp sensor, then passes those raw counts to the `adc_lib` math helpers to convert them to millivolts (mV) and degrees Celsius. +* **`format_adc_line(...)`**: Safely constructs a formatted string like `ADC0: 1650 mV | Chip temp: 34.5 C\r\n` without using the heap or standard library formatting mechanisms, relying instead on custom integer-to-ascii division algorithms (`write_mv_digits`, `write_temp`, etc.). + +### 3. The Reusable ADC Math Library (`src/adc.rs`) +The hardware ADC returns a raw 12-bit number between `0` and `4095`. This number represents a ratio of the measured voltage compared to the ADC reference voltage (which is 3.3V, or 3300mV). + +* **`raw_to_mv(...)`**: Linearly maps a 12-bit ADC result (`0..=4095`) to a voltage in millivolts (`0..=3300`). +* **`raw_to_celsius(...)`**: Takes a 12-bit reading from the internal temperature sensor (Channel 4) and applies the datasheet-specified polynomial equation to yield the core temperature in degrees Celsius as a floating-point number. diff --git a/drivers/0x06_adc_rust/build.rs b/drivers/0x06_adc_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x06_adc_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x06_adc_rust/rp2040.x b/drivers/0x06_adc_rust/rp2040.x new file mode 100644 index 0000000..0cc665b --- /dev/null +++ b/drivers/0x06_adc_rust/rp2040.x @@ -0,0 +1,91 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + /* + * Here we assume you have 2048 KiB of Flash. This is what the Pi Pico + * has, but your board may have more or less Flash and you should adjust + * this value to suit. + */ + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + /* + * RAM consists of 4 banks, SRAM0-SRAM3, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 256K + /* + * RAM banks 4 and 5 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k + + /* SRAM banks 0-3 can also be accessed directly. However, those ranges + alias with the RAM mapping, above. So don't use them at the same time! + SRAM0 : ORIGIN = 0x21000000, LENGTH = 64k + SRAM1 : ORIGIN = 0x21010000, LENGTH = 64k + SRAM2 : ORIGIN = 0x21020000, LENGTH = 64k + SRAM3 : ORIGIN = 0x21030000, LENGTH = 64k + */ +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + /* ### Boot loader + * + * An executable block of code which sets up the QSPI interface for + * 'Execute-In-Place' (or XIP) mode. Also sends chip-specific commands to + * the external flash chip. + * + * Must go at the start of external flash, where the Boot ROM expects it. + */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 512 bytes of flash, + * where picotool can find it + */ + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; diff --git a/drivers/0x06_adc_rust/rp2350.x b/drivers/0x06_adc_rust/rp2350.x new file mode 100644 index 0000000..bda94d8 --- /dev/null +++ b/drivers/0x06_adc_rust/rp2350.x @@ -0,0 +1,83 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + /* move .text to start /after/ the boot info */ + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + \ No newline at end of file diff --git a/drivers/0x06_adc_rust/rp2350_riscv.x b/drivers/0x06_adc_rust/rp2350_riscv.x new file mode 100644 index 0000000..84388aa --- /dev/null +++ b/drivers/0x06_adc_rust/rp2350_riscv.x @@ -0,0 +1,259 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut _heap_size }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all `32`-byte aligned. These alignments are assumed by the RAM + initialization routine. There's also a second benefit: `32`-byte aligned boundaries + means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ +PROVIDE(_setup_interrupts = default_setup_interrupts); + +/* # Multi-processing hook function + fn _mp_hook() -> bool; + + This function is called from all the harts and must return true only for one hart, + which will perform memory initialization. For other harts it must return false + and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). +*/ +PROVIDE(_mp_hook = default_mp_hook); + +/* # Start trap function override + By default uses the riscv crates default trap handler + but by providing the `_start_trap` symbol external crates can override. +*/ +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + /* This section is intended to make _stext address work */ + . = ABSOLUTE(_stext); + } > FLASH + + .text _stext : + { + /* Put reset handler first in .text section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + __start_block_addr = .; + KEEP(*(.start_block)); + . = ALIGN(4); + *(.trap); + *(.trap.rust); + *(.text.abort); + *(.text .text.*); + . = ALIGN(4); + } > FLASH + + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + + .rodata : ALIGN(4) + { + *(.srodata .srodata.*); + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + } > FLASH + + .data : ALIGN(32) + { + _sidata = LOADADDR(.data); + __sidata = LOADADDR(.data); + _sdata = .; + __sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + . = ALIGN(32); + _edata = .; + __edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : ALIGN(32) + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(32); + _ebss = .; + } > RAM + + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + _sheap = .; + . += _heap_size; + . = ALIGN(4); + _eheap = .; + } > RAM + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + _estack = .; + . = ABSOLUTE(_stack_start); + _sstack = .; + } > RAM + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + .eh_frame (INFO) : { KEEP(*(.eh_frame)) } + .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } +} + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(FLASH) % 4 == 0, " +ERROR(riscv-rt): the start of the FLASH must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 32 == 0, " +ERROR(riscv-rt): the start of the RAM must be 32-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 32 == 0 && _edata % 32 == 0, " +BUG(riscv-rt): .data is not 32-byte aligned"); + +ASSERT(_sidata % 32 == 0, " +BUG(riscv-rt): the LMA of .data is not 32-byte aligned"); + +ASSERT(_sbss % 32 == 0 && _ebss % 32 == 0, " +BUG(riscv-rt): .bss is not 32-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), " +ERROR(riscv-rt): The .text section must be placed inside the FLASH region. +Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'"); + +ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +/* Do not exceed this mark in the error messages above | */ diff --git a/drivers/0x06_adc_rust/src/adc.rs b/drivers/0x06_adc_rust/src/adc.rs new file mode 100644 index 0000000..3d24c0b --- /dev/null +++ b/drivers/0x06_adc_rust/src/adc.rs @@ -0,0 +1,150 @@ +//! Implementation module +//! +//! **File:** `adc.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +/// ADC reference voltage in millivolts. +pub const ADC_VREF_MV: u32 = 3300; + +/// ADC full-scale value for 12-bit resolution. +pub const ADC_FULL_SCALE: u32 = 4095; + +/// Convert a raw 12-bit ADC value to millivolts. +/// +/// Scales the raw value linearly against the 3.3 V reference. +/// +/// # Arguments +/// +/// * `raw` - 12-bit ADC conversion result (0–4095). +/// +/// # Returns +/// +/// Equivalent voltage in millivolts (0–3300). +/// +/// # Arguments +/// +/// * `raw` - The `raw` parameter. +/// +/// # Returns +/// +/// A 32-bit unsigned integer value. +#[inline] +pub fn raw_to_mv(raw: u16) -> u32 { + raw as u32 * ADC_VREF_MV / ADC_FULL_SCALE +} + +/// Convert a raw temperature-sensor ADC value to degrees Celsius. +/// +/// Applies the RP2350 datasheet formula: +/// T = 27 - (V - 0.706) / 0.001721 +/// +/// # Arguments +/// +/// * `raw` - 12-bit ADC result from the internal temperature sensor (channel 4). +/// +/// # Returns +/// +/// Die temperature in degrees Celsius. +/// +/// # Arguments +/// +/// * `raw` - The `raw` parameter. +/// +/// # Returns +/// +/// A value of type `f32`. +#[inline] +pub fn raw_to_celsius(raw: u16) -> f32 { + let voltage = raw as f32 * 3.3f32 / ADC_FULL_SCALE as f32; + 27.0f32 - (voltage - 0.706f32) / 0.001721f32 +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + + /// Executes the raw to mv zero operation. + #[test] + fn raw_to_mv_zero() { + assert_eq!(raw_to_mv(0), 0); + } + + /// Executes the raw to mv full scale operation. + #[test] + fn raw_to_mv_full_scale() { + assert_eq!(raw_to_mv(4095), 3300); + } + + /// Executes the raw to mv half operation. + #[test] + fn raw_to_mv_half() { + let mv = raw_to_mv(2048); + assert!(mv >= 1649 && mv <= 1651); + } + + /// Executes the raw to mv quarter operation. + #[test] + fn raw_to_mv_quarter() { + let mv = raw_to_mv(1024); + assert!(mv >= 824 && mv <= 826); + } + + /// Executes the raw to celsius room temp operation. + #[test] + fn raw_to_celsius_room_temp() { + let temp = raw_to_celsius(876); + assert!(temp > 20.0 && temp < 35.0); + } + + /// Executes the raw to celsius known voltage operation. + #[test] + fn raw_to_celsius_known_voltage() { + let raw = (0.706f32 / 3.3f32 * ADC_FULL_SCALE as f32 + 0.5f32) as u16; + let temp = raw_to_celsius(raw); + assert!((temp - 27.0).abs() < 1.0); + } + + /// Executes the raw to celsius higher voltage operation. + #[test] + fn raw_to_celsius_higher_voltage() { + let temp_low = raw_to_celsius(1000); + let temp_high = raw_to_celsius(800); + assert!(temp_high > temp_low); + } + + /// Executes the raw to mv one count operation. + #[test] + fn raw_to_mv_one_count() { + assert_eq!(raw_to_mv(1), 0); + } + + /// Executes the raw to mv ten counts operation. + #[test] + fn raw_to_mv_ten_counts() { + assert_eq!(raw_to_mv(10), 8); + } +} diff --git a/drivers/0x06_adc_rust/src/board.rs b/drivers/0x06_adc_rust/src/board.rs new file mode 100644 index 0000000..9565135 --- /dev/null +++ b/drivers/0x06_adc_rust/src/board.rs @@ -0,0 +1,424 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Rate extension trait for .Hz() baud rate construction +use fugit::RateExtU32; +// ADC one-shot trait for .read() +use cortex_m::prelude::_embedded_hal_adc_OneShot; +// Clock trait for accessing system clock frequency +use hal::Clock; +// GPIO pin types and function selectors +use hal::gpio::{FunctionNull, FunctionUart, Pin, PullDown, PullNone}; +// UART configuration and peripheral types +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// Main-loop polling interval in milliseconds. +pub(crate) const POLL_MS: u32 = 500; + +/// Type alias for the configured TX pin (GPIO 0, UART function, no pull). +pub(crate) type TxPin = Pin; + +/// Type alias for the configured RX pin (GPIO 1, UART function, no pull). +pub(crate) type RxPin = Pin; + +/// Type alias for the default TX pin state from `Pins::new()`. +pub(crate) type TxPinDefault = Pin; + +/// Type alias for the default RX pin state from `Pins::new()`. +pub(crate) type RxPinDefault = Pin; + +/// Type alias for the fully-enabled UART0 peripheral with TX/RX p. +pub(crate) type EnabledUart = UartPeripheral; + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +/// +/// # Arguments +/// +/// * `xosc` - XOSC peripheral singleton. +/// * `clocks` - CLOCKS peripheral singleton. +/// * `pll_sys` - PLL_SYS peripheral singleton. +/// * `pll_usb` - PLL_USB peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `watchdog` - Mutable reference to the watchdog timer. +/// +/// # Returns +/// +/// Configured clocks manager. +/// +/// # Panics +/// +/// Panics if clock initialisation fails. +pub(crate) fn init_clocks( + xosc: hal::pac::XOSC, + clocks: hal::pac::CLOCKS, + pll_sys: hal::pac::PLL_SYS, + pll_usb: hal::pac::PLL_USB, + resets: &mut hal::pac::RESETS, + watchdog: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + xosc, + clocks, + pll_sys, + pll_usb, + resets, + watchdog, + ) + .unwrap() +} + +/// Unlock the GPIO bank and return the pin set. +/// +/// # Arguments +/// +/// * `io_bank0` - IO_BANK0 peripheral singleton. +/// * `pads_bank0` - PADS_BANK0 peripheral singleton. +/// * `sio` - SIO peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// +/// # Returns +/// +/// GPIO pin set for the entire bank. +pub(crate) fn init_pins( + io_bank0: hal::pac::IO_BANK0, + pads_bank0: hal::pac::PADS_BANK0, + sio: hal::pac::SIO, + resets: &mut hal::pac::RESETS, +) -> hal::gpio::Pins { + let sio = hal::Sio::new(sio); + hal::gpio::Pins::new(io_bank0, pads_bank0, sio.gpio_bank0, resets) +} + +/// Initialise UART0 for serial output (stdio equivalent). +/// +/// # Arguments +/// +/// * `uart0` - PAC UART0 peripheral singleton. +/// * `tx_pin` - GPIO pin to use as UART0 TX (GPIO 0). +/// * `rx_pin` - GPIO pin to use as UART0 RX (GPIO 1). +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Enabled UART0 peripheral ready for blocking writes. +/// +/// # Panics +/// +/// Panics if the HAL cannot achieve the requested baud rate. +pub(crate) fn init_uart( + uart0: hal::pac::UART0, + tx_pin: TxPinDefault, + rx_pin: RxPinDefault, + resets: &mut hal::pac::RESETS, + clocks: &hal::clocks::ClocksManager, +) -> EnabledUart { + let pins = ( + tx_pin.reconfigure::(), + rx_pin.reconfigure::(), + ); + let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One); + UartPeripheral::new(uart0, pins, resets) + .enable(cfg, clocks.peripheral_clock.freq()) + .unwrap() +} + +/// Create a blocking delay timer from the ARM SysTick peripheral. +/// +/// # Arguments +/// +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Blocking delay provider. +/// +/// # Panics +/// +/// Panics if the cortex-m core peripherals have already been taken. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +pub(crate) fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay { + let core = cortex_m::Peripherals::take().unwrap(); + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()) +} + +/// Write a conditional digit into `buf` if `val` meets the threshold. +fn write_conditional_digit( + buf: &mut [u8], + pos: &mut usize, + val: u32, + threshold: u32, + divisor: u32, +) { + if val >= threshold { + buf[*pos] = b'0' + ((val / divisor) % 10) as u8; + *pos += 1; + } +} + +/// Write a u32 with minimum digits (no leading zeros). +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `val` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `val` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +fn write_min_digits(buf: &mut [u8], val: u32) -> usize { + let mut pos = 0; + write_conditional_digit(buf, &mut pos, val, 100, 100); + write_conditional_digit(buf, &mut pos, val, 10, 10); + buf[pos] = b'0' + (val % 10) as u8; + pos + 1 +} + +/// Write 4-digit millivolt value into `buf`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `mv` - The `mv` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `mv` - The `mv` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +fn write_mv_digits(buf: &mut [u8], mv: u32) -> usize { + buf[0] = b'0' + ((mv / 1000) % 10) as u8; buf[1] = b'0' + ((mv / 100) % 10) as u8; + buf[2] = b'0' + ((mv / 10) % 10) as u8; buf[3] = b'0' + (mv % 10) as u8; + 4 +} + +/// Write a negative sign if needed and return the absolute temperature value. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `pos` - The `pos` parameter. +/// * `temp_int` - The `temp_int` parameter. +/// +/// # Returns +/// +/// A 32-bit unsigned integer value. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `pos` - The `pos` parameter. +/// * `temp_int` - The `temp_int` parameter. +/// +/// # Returns +/// +/// A 32-bit unsigned integer value. +fn write_sign(buf: &mut [u8], pos: &mut usize, temp_int: i32) -> u32 { + if temp_int >= 0 { return temp_int as u32; } + buf[*pos] = b'-'; *pos += 1; + (-temp_int) as u32 +} + +/// Write temperature as "[-]NN.F" into `buf`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `temp_int` - The `temp_int` parameter. +/// * `temp_frac` - The `temp_frac` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `temp_int` - The `temp_int` parameter. +/// * `temp_frac` - The `temp_frac` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +fn write_temp(buf: &mut [u8], temp_int: i32, temp_frac: u8) -> usize { + let mut pos = 0; let abs_temp = write_sign(buf, &mut pos, temp_int); + pos += write_min_digits(&mut buf[pos..], abs_temp); + buf[pos] = b'.'; buf[pos + 1] = b'0' + temp_frac; + pos + 2 +} + +/// Format a millivolt value into "ADC0: NNNN mV | Chip temp: NN.N C\r\n". +/// +/// # Arguments +/// +/// * `buf` - Mutable byte slice (must be at least 48 bytes). +/// * `mv` - Voltage in millivolts. +/// * `temp_int` - Integer part of temperature. +/// * `temp_frac` - Single decimal digit of temperature fraction. +/// +/// # Returns +/// +/// Number of bytes written into the buffer. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `mv` - The `mv` parameter. +/// * `temp_int` - The `temp_int` parameter. +/// * `temp_frac` - The `temp_frac` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +pub(crate) fn format_adc_line(buf: &mut [u8], mv: u32, temp_int: i32, temp_frac: u8) -> usize { + buf[..6].copy_from_slice(b"ADC0: "); + let p1 = 6 + write_mv_digits(&mut buf[6..], mv); + buf[p1..p1 + 19].copy_from_slice(b" mV | Chip temp: "); + let p2 = p1 + 19 + write_temp(&mut buf[p1 + 19..], temp_int, temp_frac); + buf[p2..p2 + 4].copy_from_slice(b" C\r\n"); p2 + 4 +} + +/// Type alias for the ADC input pin on GPIO 26. +type Gpio26Adc = hal::adc::AdcPin>; + +/// Initialise all peripherals and run the ADC demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `pac` - The `pac` parameter. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! { + let mut w = hal::Watchdog::new(p.WATCHDOG); + let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut w); + let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS); + let u = init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c); + let (mut adc, mut adc_pin, mut temp) = init_adc(p.ADC, pins.gpio26, &mut p.RESETS); + u.write_full_blocking(b"ADC driver initialized: GPIO26 (channel 0)\r\n"); + adc_loop(&u, &mut adc, &mut adc_pin, &mut temp, &mut init_delay(&c)) +} + +/// Create the ADC peripheral, GPIO 26 input channel, and temperature sensor. +/// +/// # Arguments +/// +/// * `adc_pac` - PAC ADC peripheral singleton. +/// * `gpio26` - Default GPIO 26 pin to use as ADC input. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// +/// # Returns +/// +/// Tuple of (ADC driver, ADC pin channel, temperature sensor channel). +fn init_adc(adc: hal::pac::ADC, p26: Pin, r: &mut hal::pac::RESETS) -> (hal::Adc, Gpio26Adc, hal::adc::TempSense) { + let mut a = hal::Adc::new(adc, r); + let t = a.take_temp_sensor().unwrap(); + (a, hal::adc::AdcPin::new(p26).unwrap(), t) +} + +/// Sample voltage and temperature, format, and print in a loop. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// * `adc` - Mutable reference to the ADC driver. +/// * `adc_pin` - Mutable reference to the GPIO 26 ADC channel. +/// * `temp` - Mutable reference to the temperature sensor channel. +/// * `delay` - Mutable reference to the blocking delay provider. +fn adc_loop(u: &EnabledUart, a: &mut hal::Adc, ap: &mut Gpio26Adc, t: &mut hal::adc::TempSense, d: &mut cortex_m::delay::Delay) -> ! { + let mut buf = [0u8; 48]; + loop { + let (mv, temp_int, temp_frac) = read_adc(a, ap, t); + let n = format_adc_line(&mut buf, mv, temp_int, temp_frac); + u.write_full_blocking(&buf[..n]); d.delay_ms(POLL_MS); + } +} + +/// Read voltage and temperature from the ADC. +/// +/// # Arguments +/// +/// * `adc` - Mutable reference to the ADC driver. +/// * `adc_pin` - Mutable reference to the GPIO 26 ADC channel. +/// * `temp` - Mutable reference to the temperature sensor channel. +/// +/// # Returns +/// +/// Tuple of (millivolts, integer temperature, fractional temperature digit). +fn read_adc(a: &mut hal::Adc, ap: &mut Gpio26Adc, t: &mut hal::adc::TempSense) -> (u32, i32, u8) { + let mv = adc_lib::adc::raw_to_mv(a.read(ap).unwrap()); + let c = adc_lib::adc::raw_to_celsius(a.read(t).unwrap()); + (mv, c as i32, (((c - (c as i32) as f32) * 10.0) as u8).min(9)) +} + diff --git a/drivers/0x06_adc_rust/src/lib.rs b/drivers/0x06_adc_rust/src/lib.rs new file mode 100644 index 0000000..114b04e --- /dev/null +++ b/drivers/0x06_adc_rust/src/lib.rs @@ -0,0 +1,6 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +#![cfg_attr(not(test), no_std)] +pub mod adc; diff --git a/drivers/0x06_adc_rust/src/main.rs b/drivers/0x06_adc_rust/src/main.rs new file mode 100644 index 0000000..4f0c9d0 --- /dev/null +++ b/drivers/0x06_adc_rust/src/main.rs @@ -0,0 +1,102 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; +// ADC driver module — suppress warnings for unused public API functions + +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the ADC voltage and temperature demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"ADC Voltage and Temperature Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/drivers/0x07_i2c_rust/.cargo/config.toml b/drivers/0x07_i2c_rust/.cargo/config.toml new file mode 100644 index 0000000..1791d74 --- /dev/null +++ b/drivers/0x07_i2c_rust/.cargo/config.toml @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2021–2024 The rp-rs Developers +# Copyright (c) 2021 rp-rs organization +# Copyright (c) 2025 Raspberry Pi Ltd. +# +# Cargo Configuration for the https://github.com/rp-rs/rp-hal.git repository. +# +# You might want to make a similar file in your own repository if you are +# writing programs for Raspberry Silicon microcontrollers. +# + +[build] +target = "thumbv8m.main-none-eabihf" +# Set the default target to match the Cortex-M33 in the RP2350 +# target = "thumbv8m.main-none-eabihf" +# target = "thumbv6m-none-eabi" +# target = "riscv32imac-unknown-none-elf" + +# Target specific options +[target.thumbv6m-none-eabi] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as the linker +# script. This is usually provided by the cortex-m-rt crate, and by default +# the version in that crate will include a file called `memory.x` which +# describes the particular memory layout for your specific chip. +# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't +# have SIMD) +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the hard-float ABI for Arm mode. +# +# The FPU is enabled by default, and float function arguments use FPU +# registers. +[target.thumbv8m.main-none-eabihf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as a linker script. +# This is usually provided by the cortex-m-rt crate, and by default the +# version in that crate will include a file called `memory.x` which describes +# the particular memory layout for your specific chip. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the soft-float ABI for RISC-V mode. +# +# Hazard 3 does not have an FPU and so float function arguments use integer +# registers. +[target.riscv32imac-unknown-none-elf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Trp235x_riscv.x also tells the linker to use +# `rp235x_riscv.x` as a linker script. This adds in RP2350 RISC-V specific +# things that the riscv-rt crate's `link.x` requires and then includes +# `link.x` automatically. This is the reverse of how we do it on Cortex-M. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp2350_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +[env] +DEFMT_LOG = "debug" diff --git a/drivers/0x07_i2c_rust/.gitignore b/drivers/0x07_i2c_rust/.gitignore new file mode 100644 index 0000000..03381c1 --- /dev/null +++ b/drivers/0x07_i2c_rust/.gitignore @@ -0,0 +1,113 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,macos,windows,linux + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux diff --git a/drivers/0x07_i2c_rust/.pico-rs b/drivers/0x07_i2c_rust/.pico-rs new file mode 100644 index 0000000..1b6702a --- /dev/null +++ b/drivers/0x07_i2c_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 \ No newline at end of file diff --git a/drivers/0x07_i2c_rust/.vscode/extensions.json b/drivers/0x07_i2c_rust/.vscode/extensions.json new file mode 100644 index 0000000..5f2fd0c --- /dev/null +++ b/drivers/0x07_i2c_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/drivers/0x07_i2c_rust/.vscode/launch.json b/drivers/0x07_i2c_rust/.vscode/launch.json new file mode 100644 index 0000000..0bc38c3 --- /dev/null +++ b/drivers/0x07_i2c_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} \ No newline at end of file diff --git a/drivers/0x07_i2c_rust/.vscode/settings.json b/drivers/0x07_i2c_rust/.vscode/settings.json new file mode 100644 index 0000000..b03cd57 --- /dev/null +++ b/drivers/0x07_i2c_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} \ No newline at end of file diff --git a/drivers/0x07_i2c_rust/.vscode/tasks.json b/drivers/0x07_i2c_rust/.vscode/tasks.json new file mode 100644 index 0000000..4194af4 --- /dev/null +++ b/drivers/0x07_i2c_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/drivers/0x07_i2c_rust/Cargo.toml b/drivers/0x07_i2c_rust/Cargo.toml new file mode 100644 index 0000000..a3c2c57 --- /dev/null +++ b/drivers/0x07_i2c_rust/Cargo.toml @@ -0,0 +1,80 @@ +[package] +edition = "2024" +name = "i2c" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "i2c_lib" +path = "src/lib.rs" + +[[bin]] +name = "i2c" +path = "src/main.rs" + + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embedded-hal = "1.0.0" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 3 diff --git a/drivers/0x07_i2c_rust/LICENSE-APACHE b/drivers/0x07_i2c_rust/LICENSE-APACHE new file mode 100644 index 0000000..8d99cbc --- /dev/null +++ b/drivers/0x07_i2c_rust/LICENSE-APACHE @@ -0,0 +1,204 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2021–2024 The rp-rs Developers + Copyright (c) 2021 rp-rs organization + Copyright (c) 2025 Raspberry Pi Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/drivers/0x07_i2c_rust/LICENSE-MIT b/drivers/0x07_i2c_rust/LICENSE-MIT new file mode 100644 index 0000000..5369c70 --- /dev/null +++ b/drivers/0x07_i2c_rust/LICENSE-MIT @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2021–2024 The rp-rs Developers +Copyright (c) 2021 rp-rs organization +Copyright (c) 2025 Raspberry Pi Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + \ No newline at end of file diff --git a/drivers/0x07_i2c_rust/README.md b/drivers/0x07_i2c_rust/README.md new file mode 100644 index 0000000..641aac7 --- /dev/null +++ b/drivers/0x07_i2c_rust/README.md @@ -0,0 +1,116 @@ +# 0x07 I2C Rust Driver + +This repository contains a Bare-Metal Rust driver for the I2C (Inter-Integrated Circuit) peripheral on the **RP2350** (and RP2040) microcontrollers. + +It includes: +- A thin demo (`src/main.rs`) that continuously scans the I2C bus and prints out a nicely formatted hex grid of all connected devices over UART. +- A reusable library module (`src/i2c.rs`) providing a hardware-agnostic `i2c_lib` with helper math and string formatting functions to neatly layout the scan grid. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the I2C grid string formatting logic is separated into a reusable math library without touching hardware registers, you can run the unit tests natively on your computer! + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we initialize the system clocks, pins, UART (for logging), SysTick (for delay), and the I2C peripheral (for scanning). + +* **`run(...)`**: The master setup function. Calls the helper initialization functions below, prints an announcement over UART, and enters the infinite I2C scanning loop. +* **`init_clocks(...)`**: Wakes up the external 12 MHz crystal (`XOSC`) and configures the PLLs (Phase-Locked Loops) to drive the system clock at its maximum speed. +* **`init_pins(...)`**: Takes control of physical pins across `IO_BANK0`. +* **`init_uart(...)`**: Configures the hardware `UART0` peripheral to operate at a standard `115200` baud rate with an `8N1` configuration for debug printing. +* **`init_i2c(...)`**: Initializes the hardware `I2C1` peripheral, reconfigures `GPIO 2` and `GPIO 3` as `SDA` and `SCL` lines (with internal pull-ups enabled), and sets the clock speed to 100 kHz (standard mode). +* **`scan_loop(...)`**: Loops infinitely, printing the table header, iterating through all 128 possible addresses to probe them, and sleeping for `SCAN_DELAY_MS` (5 seconds). +* **`scan_addresses(...)`**: Loops from 0x00 to 0x7F, ignoring reserved addresses, and uses `probe_addr` to see if a device ACKs a 1-byte read. +* **`probe_addr(...)`**: The core interaction. Attempts to read exactly 1 byte from a target 7-bit address. If the HAL's `i2c.read()` succeeds without error, we know a device exists at that address. + +### 3. The Reusable I2C String Formatting Library (`src/i2c.rs`) +Because formatting a 16x8 hex grid in standard Rust would usually require `alloc`, `String`, and `format!()` (which we do not have), this module provides a lightweight, zero-allocation way to generate the table bytes. + +* **`format_scan_header(...)`**: Injects the table column headers (`0 1 2 3 ... F`). +* **`format_scan_entry(...)`**: Determines if the current address is at the beginning of a row (adds `XX:` prefix) or at the end (adds `\r\n`). +* **`cell_bytes(...)`**: Given a specific address and whether a device was found there, returns a fixed 3-byte array like `51 ` (found), `-- ` (not found), or ` ` (reserved address block). +* **`is_reserved(...)`**: True for addresses strictly less than 0x08 and strictly greater than 0x77 (these ranges are globally reserved by the I2C spec). diff --git a/drivers/0x07_i2c_rust/build.rs b/drivers/0x07_i2c_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x07_i2c_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x07_i2c_rust/rp2040.x b/drivers/0x07_i2c_rust/rp2040.x new file mode 100644 index 0000000..0cc665b --- /dev/null +++ b/drivers/0x07_i2c_rust/rp2040.x @@ -0,0 +1,91 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + /* + * Here we assume you have 2048 KiB of Flash. This is what the Pi Pico + * has, but your board may have more or less Flash and you should adjust + * this value to suit. + */ + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + /* + * RAM consists of 4 banks, SRAM0-SRAM3, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 256K + /* + * RAM banks 4 and 5 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k + + /* SRAM banks 0-3 can also be accessed directly. However, those ranges + alias with the RAM mapping, above. So don't use them at the same time! + SRAM0 : ORIGIN = 0x21000000, LENGTH = 64k + SRAM1 : ORIGIN = 0x21010000, LENGTH = 64k + SRAM2 : ORIGIN = 0x21020000, LENGTH = 64k + SRAM3 : ORIGIN = 0x21030000, LENGTH = 64k + */ +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + /* ### Boot loader + * + * An executable block of code which sets up the QSPI interface for + * 'Execute-In-Place' (or XIP) mode. Also sends chip-specific commands to + * the external flash chip. + * + * Must go at the start of external flash, where the Boot ROM expects it. + */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 512 bytes of flash, + * where picotool can find it + */ + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; diff --git a/drivers/0x07_i2c_rust/rp2350.x b/drivers/0x07_i2c_rust/rp2350.x new file mode 100644 index 0000000..bda94d8 --- /dev/null +++ b/drivers/0x07_i2c_rust/rp2350.x @@ -0,0 +1,83 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + /* move .text to start /after/ the boot info */ + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + \ No newline at end of file diff --git a/drivers/0x07_i2c_rust/rp2350_riscv.x b/drivers/0x07_i2c_rust/rp2350_riscv.x new file mode 100644 index 0000000..84388aa --- /dev/null +++ b/drivers/0x07_i2c_rust/rp2350_riscv.x @@ -0,0 +1,259 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut _heap_size }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all `32`-byte aligned. These alignments are assumed by the RAM + initialization routine. There's also a second benefit: `32`-byte aligned boundaries + means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ +PROVIDE(_setup_interrupts = default_setup_interrupts); + +/* # Multi-processing hook function + fn _mp_hook() -> bool; + + This function is called from all the harts and must return true only for one hart, + which will perform memory initialization. For other harts it must return false + and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). +*/ +PROVIDE(_mp_hook = default_mp_hook); + +/* # Start trap function override + By default uses the riscv crates default trap handler + but by providing the `_start_trap` symbol external crates can override. +*/ +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + /* This section is intended to make _stext address work */ + . = ABSOLUTE(_stext); + } > FLASH + + .text _stext : + { + /* Put reset handler first in .text section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + __start_block_addr = .; + KEEP(*(.start_block)); + . = ALIGN(4); + *(.trap); + *(.trap.rust); + *(.text.abort); + *(.text .text.*); + . = ALIGN(4); + } > FLASH + + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + + .rodata : ALIGN(4) + { + *(.srodata .srodata.*); + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + } > FLASH + + .data : ALIGN(32) + { + _sidata = LOADADDR(.data); + __sidata = LOADADDR(.data); + _sdata = .; + __sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + . = ALIGN(32); + _edata = .; + __edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : ALIGN(32) + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(32); + _ebss = .; + } > RAM + + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + _sheap = .; + . += _heap_size; + . = ALIGN(4); + _eheap = .; + } > RAM + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + _estack = .; + . = ABSOLUTE(_stack_start); + _sstack = .; + } > RAM + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + .eh_frame (INFO) : { KEEP(*(.eh_frame)) } + .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } +} + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(FLASH) % 4 == 0, " +ERROR(riscv-rt): the start of the FLASH must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 32 == 0, " +ERROR(riscv-rt): the start of the RAM must be 32-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 32 == 0 && _edata % 32 == 0, " +BUG(riscv-rt): .data is not 32-byte aligned"); + +ASSERT(_sidata % 32 == 0, " +BUG(riscv-rt): the LMA of .data is not 32-byte aligned"); + +ASSERT(_sbss % 32 == 0 && _ebss % 32 == 0, " +BUG(riscv-rt): .bss is not 32-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), " +ERROR(riscv-rt): The .text section must be placed inside the FLASH region. +Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'"); + +ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +/* Do not exceed this mark in the error messages above | */ diff --git a/drivers/0x07_i2c_rust/src/board.rs b/drivers/0x07_i2c_rust/src/board.rs new file mode 100644 index 0000000..5ae1150 --- /dev/null +++ b/drivers/0x07_i2c_rust/src/board.rs @@ -0,0 +1,312 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// I2C bus trait for device probing +use embedded_hal::i2c::I2c; +// Rate extension trait for .Hz() baud rate construction +use fugit::RateExtU32; +// Clock trait for accessing system clock frequency +use hal::Clock; +// GPIO pin types and function selectors +use hal::gpio::{FunctionI2C, FunctionNull, FunctionUart, Pin, PullDown, PullNone, PullUp}; +// UART configuration and peripheral types +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// I2C bus speed in Hz (100 kHz standard mode). +pub(crate) const I2C_BAUD: u32 = 100_000; + +/// Delay between scan cycles in milliseconds. +pub(crate) const SCAN_DELAY_MS: u32 = 5_000; + +/// Type alias for the configured TX pin (GPIO 0, UART function, no pull). +pub(crate) type TxPin = Pin; + +/// Type alias for the configured RX pin (GPIO 1, UART function, no pull). +pub(crate) type RxPin = Pin; + +/// Type alias for the default TX pin state from `Pins::new()`. +pub(crate) type TxPinDefault = Pin; + +/// Type alias for the default RX pin state from `Pins::new()`. +pub(crate) type RxPinDefault = Pin; + +/// Type alias for the fully-enabled UART0 peripheral with TX/RX p. +pub(crate) type EnabledUart = UartPeripheral; + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +/// +/// # Arguments +/// +/// * `xosc` - XOSC peripheral singleton. +/// * `clocks` - CLOCKS peripheral singleton. +/// * `pll_sys` - PLL_SYS peripheral singleton. +/// * `pll_usb` - PLL_USB peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `watchdog` - Mutable reference to the watchdog timer. +/// +/// # Returns +/// +/// Configured clocks manager. +/// +/// # Panics +/// +/// Panics if clock initialisation fails. +pub(crate) fn init_clocks( + xosc: hal::pac::XOSC, + clocks: hal::pac::CLOCKS, + pll_sys: hal::pac::PLL_SYS, + pll_usb: hal::pac::PLL_USB, + resets: &mut hal::pac::RESETS, + watchdog: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + xosc, + clocks, + pll_sys, + pll_usb, + resets, + watchdog, + ) + .unwrap() +} + +/// Unlock the GPIO bank and return the pin set. +/// +/// # Arguments +/// +/// * `io_bank0` - IO_BANK0 peripheral singleton. +/// * `pads_bank0` - PADS_BANK0 peripheral singleton. +/// * `sio` - SIO peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// +/// # Returns +/// +/// GPIO pin set for the entire bank. +pub(crate) fn init_pins( + io_bank0: hal::pac::IO_BANK0, + pads_bank0: hal::pac::PADS_BANK0, + sio: hal::pac::SIO, + resets: &mut hal::pac::RESETS, +) -> hal::gpio::Pins { + let sio = hal::Sio::new(sio); + hal::gpio::Pins::new(io_bank0, pads_bank0, sio.gpio_bank0, resets) +} + +/// Initialise UART0 for serial output (stdio equivalent). +/// +/// # Arguments +/// +/// * `uart0` - PAC UART0 peripheral singleton. +/// * `tx_pin` - GPIO pin to use as UART0 TX (GPIO 0). +/// * `rx_pin` - GPIO pin to use as UART0 RX (GPIO 1). +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Enabled UART0 peripheral ready for blocking writes. +/// +/// # Panics +/// +/// Panics if the HAL cannot achieve the requested baud rate. +pub(crate) fn init_uart( + uart0: hal::pac::UART0, + tx_pin: TxPinDefault, + rx_pin: RxPinDefault, + resets: &mut hal::pac::RESETS, + clocks: &hal::clocks::ClocksManager, +) -> EnabledUart { + let pins = ( + tx_pin.reconfigure::(), + rx_pin.reconfigure::(), + ); + let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One); + UartPeripheral::new(uart0, pins, resets) + .enable(cfg, clocks.peripheral_clock.freq()) + .unwrap() +} + +/// Create a blocking delay timer from the ARM SysTick peripheral. +/// +/// # Arguments +/// +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Blocking delay provider. +/// +/// # Panics +/// +/// Panics if the cortex-m core peripherals have already been taken. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +pub(crate) fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay { + let core = cortex_m::Peripherals::take().unwrap(); + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()) +} + +/// Probe a 7-bit I2C address by attempting a 1-byte read. +/// +/// # Arguments +/// +/// * `i2c` - Mutable reference to the I2C bus. +/// * `addr` - 7-bit I2C address to probe. +/// +/// # Returns +/// +/// `true` if a device acknowledged, `false` otherwise. +/// +/// # Arguments +/// +/// * `i2c` - The `i2c` parameter. +/// * `addr` - Device address. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +pub(crate) fn probe_addr(i2c: &mut impl I2c, addr: u8) -> bool { + let mut dummy = [0u8; 1]; + i2c.read(addr, &mut dummy).is_ok() +} + +/// Initialise all peripherals and run the I2C bus scanner demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `pac` - The `pac` parameter. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! { + let mut w = hal::Watchdog::new(p.WATCHDOG); + let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut w); + let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS); + let u = init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c); + let mut i2c = init_i2c(p.I2C1, pins.gpio2, pins.gpio3, &mut p.RESETS, &c); + u.write_full_blocking(b"I2C driver initialized: I2C1 @ 100000 Hz SDA=GPIO2 SCL=GPIO3\r\n"); + scan_loop(&u, &mut i2c, &mut init_delay(&c)) +} + +/// Initialise I2C1 on SDA=GPIO2 / SCL=GPIO3. +/// +/// # Arguments +/// +/// * `i2c1` - PAC I2C1 peripheral singleton. +/// * `sda` - Default GPIO 2 pin (will be reconfigured for I2C). +/// * `scl` - Default GPIO 3 pin (will be reconfigured for I2C). +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Configured I2C1 bus controller. +fn init_i2c( + i2c: hal::pac::I2C1, sda: Pin, scl: Pin, r: &mut hal::pac::RESETS, c: &hal::clocks::ClocksManager +) -> impl I2c { + hal::I2C::i2c1(i2c, sda.reconfigure::(), scl.reconfigure::(), I2C_BAUD.Hz(), r, c.system_clock.freq()) +} + +/// Run the I2C address scan loop forever. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// * `i2c` - Mutable reference to the I2C bus controller. +/// * `delay` - Mutable reference to the blocking delay provider. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +/// * `i2c` - The `i2c` parameter. +/// * `delay` - Delay value. +/// +/// # Returns +/// +/// A value of type `!`. +fn scan_loop(u: &EnabledUart, i2c: &mut impl I2c, d: &mut cortex_m::delay::Delay) -> ! { + let mut buf = [0u8; 80]; + loop { + let n = i2c_lib::i2c::format_scan_header(&mut buf); + u.write_full_blocking(&buf[..n]); scan_addresses(u, i2c, &mut buf); d.delay_ms(SCAN_DELAY_MS); + } +} + +/// Scan all 128 addresses and print the formatted result. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// * `i2c` - Mutable reference to the I2C bus controller. +/// * `buf` - Scratch buffer for formatting output. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +/// * `i2c` - The `i2c` parameter. +/// * `buf` - The `buf` parameter. +fn scan_addresses(u: &EnabledUart, i2c: &mut impl I2c, buf: &mut [u8; 80]) { + for addr in 0u8..128 { + let n = i2c_lib::i2c::format_scan_entry(buf, addr, !i2c_lib::i2c::is_reserved(addr) && probe_addr(i2c, addr)); + u.write_full_blocking(&buf[..n]); + } +} + diff --git a/drivers/0x07_i2c_rust/src/i2c.rs b/drivers/0x07_i2c_rust/src/i2c.rs new file mode 100644 index 0000000..7f7fd5e --- /dev/null +++ b/drivers/0x07_i2c_rust/src/i2c.rs @@ -0,0 +1,308 @@ +//! Implementation module +//! +//! **File:** `i2c.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +/// Lowest valid (non-reserved) 7-bit I2C address. +pub const SCAN_ADDR_MIN: u8 = 0x08; + +/// Highest valid (non-reserved) 7-bit I2C address. +pub const SCAN_ADDR_MAX: u8 = 0x77; + +/// Return `true` when `addr` falls in a reserved 7-bit I2C range. +/// +/// # Arguments +/// +/// * `addr` - 7-bit I2C address to check. +/// +/// # Returns +/// +/// `true` if the address is in the reserved low or high range. +/// +/// # Arguments +/// +/// * `addr` - Device address. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +#[inline] +pub fn is_reserved(addr: u8) -> bool { + addr < SCAN_ADDR_MIN || addr > SCAN_ADDR_MAX +} + +/// Convert a 4-bit value to its uppercase ASCII hex digit. +/// +/// # Arguments +/// +/// * `val` - Value in the range 0..=15. +/// +/// # Returns +/// +/// ASCII byte `b'0'`..`b'9'` or `b'A'`..`b'F'`. +/// +/// # Arguments +/// +/// * `val` - Value to use. +/// +/// # Returns +/// +/// An 8-bit unsigned integer value. +#[inline] +fn hex_digit(val: u8) -> u8 { + if val < 10 { b'0' + val } else { b'A' + val - 10 } +} + +/// Write the scan-table header into `buf` and return the byte count. +/// +/// # Arguments +/// +/// * `buf` - Mutable byte slice (must be at least 56 bytes). +/// +/// # Returns +/// +/// Number of bytes written into the buffer. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_scan_header(buf: &mut [u8]) -> usize { + let h = b"\r\nI2C bus scan:\r\n 0 1 2 3 4 5 6 7 8 9 A B C D E F\r\n"; + buf[..h.len()].copy_from_slice(h); h.len() +} + +/// Format one cell of the 16-column scan table into `buf`. +/// +/// Prepends the row label when `addr` is at a 16-byte boundary and +/// appends `\r\n` when `addr` is the last column of a row. +/// +/// # Arguments +/// +/// * `buf` - Mutable byte slice for formatted output. +/// * `addr` - 7-bit I2C address being reported. +/// * `found` - `true` if a device acknowledged at this address. +/// +/// # Returns +/// +/// Number of bytes written into the buffer. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `addr` - Device address. +/// * `found` - The `found` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_scan_entry(buf: &mut [u8], addr: u8, found: bool) -> usize { + let mut pos = format_row_prefix(buf, addr); + pos += format_cell(&mut buf[pos..], addr, found); + if addr % 16 == 15 { buf[pos] = b'\r'; buf[pos + 1] = b'\n'; pos += 2; } + pos +} + +/// Write the row prefix ("XX: ") if addr is at a 16-byte boundary. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `addr` - Device address. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `addr` - Device address. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn format_row_prefix(buf: &mut [u8], addr: u8) -> usize { + if addr % 16 != 0 { return 0; } + buf[0] = hex_digit((addr >> 4) & 0x0F); buf[1] = hex_digit(addr & 0x0F); + buf[2] = b':'; buf[3] = b' '; 4 +} + +/// Write a single cell: "XX " if found, "-- " if not, " " if reserved. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `addr` - Device address. +/// * `found` - The `found` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `addr` - Device address. +/// * `found` - The `found` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn format_cell(buf: &mut [u8], addr: u8, found: bool) -> usize { + buf[..3].copy_from_slice(&cell_bytes(addr, found)); 3 +} + +/// Return the 3-byte cell content for an I2C scan address. +/// +/// # Arguments +/// +/// * `addr` - Device address. +/// * `found` - The `found` parameter. +/// +/// # Returns +/// +/// A value of type `[u8; 3]`. +/// +/// # Arguments +/// +/// * `addr` - Device address. +/// * `found` - The `found` parameter. +/// +/// # Returns +/// +/// A value of type `[u8; 3]`. +#[inline] +fn cell_bytes(addr: u8, found: bool) -> [u8; 3] { + if is_reserved(addr) { return [b' ', b' ', b' ']; } + if found { [hex_digit((addr >> 4) & 0x0F), hex_digit(addr & 0x0F), b' '] } + else { [b'-', b'-', b' '] } +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + + /// Executes the is reserved below min operation. + #[test] + fn is_reserved_below_min() { + assert!(is_reserved(0x00)); + assert!(is_reserved(0x07)); + } + + /// Executes the is reserved at min boundary operation. + #[test] + fn is_reserved_at_min_boundary() { + assert!(!is_reserved(0x08)); + } + + /// Executes the is reserved mid range operation. + #[test] + fn is_reserved_mid_range() { + assert!(!is_reserved(0x50)); + } + + /// Executes the is reserved at max boundary operation. + #[test] + fn is_reserved_at_max_boundary() { + assert!(!is_reserved(0x77)); + } + + /// Executes the is reserved above max operation. + #[test] + fn is_reserved_above_max() { + assert!(is_reserved(0x78)); + assert!(is_reserved(0x7F)); + } + + /// Executes the format scan header content operation. + #[test] + fn format_scan_header_content() { + let mut buf = [0u8; 80]; + let n = format_scan_header(&mut buf); + let s = core::str::from_utf8(&buf[..n]).unwrap(); + assert!(s.starts_with("\r\nI2C bus scan:\r\n")); + assert!(s.contains("0 1 2 3")); + assert!(s.contains("D E F\r\n")); + } + + /// Executes the format scan entry reserved row start operation. + #[test] + fn format_scan_entry_reserved_row_start() { + let mut buf = [0u8; 16]; + let n = format_scan_entry(&mut buf, 0x00, false); + assert_eq!(&buf[..n], b"00: "); + } + + /// Executes the format scan entry found operation. + #[test] + fn format_scan_entry_found() { + let mut buf = [0u8; 16]; + let n = format_scan_entry(&mut buf, 0x51, true); + assert_eq!(&buf[..n], b"51 "); + } + + /// Executes the format scan entry not found operation. + #[test] + fn format_scan_entry_not_found() { + let mut buf = [0u8; 16]; + let n = format_scan_entry(&mut buf, 0x51, false); + assert_eq!(&buf[..n], b"-- "); + } + + /// Executes the format scan entry row start valid operation. + #[test] + fn format_scan_entry_row_start_valid() { + let mut buf = [0u8; 16]; + let n = format_scan_entry(&mut buf, 0x10, false); + assert_eq!(&buf[..n], b"10: -- "); + } + + /// Executes the format scan entry row end operation. + #[test] + fn format_scan_entry_row_end() { + let mut buf = [0u8; 16]; + let n = format_scan_entry(&mut buf, 0x1F, false); + assert_eq!(&buf[..n], b"-- \r\n"); + } + + /// Executes the hex digit values operation. + #[test] + fn hex_digit_values() { + assert_eq!(hex_digit(0), b'0'); + assert_eq!(hex_digit(9), b'9'); + assert_eq!(hex_digit(10), b'A'); + assert_eq!(hex_digit(15), b'F'); + } +} diff --git a/drivers/0x07_i2c_rust/src/lib.rs b/drivers/0x07_i2c_rust/src/lib.rs new file mode 100644 index 0000000..f9f956e --- /dev/null +++ b/drivers/0x07_i2c_rust/src/lib.rs @@ -0,0 +1,6 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +#![cfg_attr(not(test), no_std)] +pub mod i2c; diff --git a/drivers/0x07_i2c_rust/src/main.rs b/drivers/0x07_i2c_rust/src/main.rs new file mode 100644 index 0000000..c3f1647 --- /dev/null +++ b/drivers/0x07_i2c_rust/src/main.rs @@ -0,0 +1,102 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; +// I2C driver module — suppress warnings for unused public API functions + +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the I2C bus scanner demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"I2C Bus Scanner Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/drivers/0x08_lcd1602_rust/.cargo/config.toml b/drivers/0x08_lcd1602_rust/.cargo/config.toml new file mode 100644 index 0000000..1791d74 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/.cargo/config.toml @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2021–2024 The rp-rs Developers +# Copyright (c) 2021 rp-rs organization +# Copyright (c) 2025 Raspberry Pi Ltd. +# +# Cargo Configuration for the https://github.com/rp-rs/rp-hal.git repository. +# +# You might want to make a similar file in your own repository if you are +# writing programs for Raspberry Silicon microcontrollers. +# + +[build] +target = "thumbv8m.main-none-eabihf" +# Set the default target to match the Cortex-M33 in the RP2350 +# target = "thumbv8m.main-none-eabihf" +# target = "thumbv6m-none-eabi" +# target = "riscv32imac-unknown-none-elf" + +# Target specific options +[target.thumbv6m-none-eabi] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as the linker +# script. This is usually provided by the cortex-m-rt crate, and by default +# the version in that crate will include a file called `memory.x` which +# describes the particular memory layout for your specific chip. +# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't +# have SIMD) +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the hard-float ABI for Arm mode. +# +# The FPU is enabled by default, and float function arguments use FPU +# registers. +[target.thumbv8m.main-none-eabihf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as a linker script. +# This is usually provided by the cortex-m-rt crate, and by default the +# version in that crate will include a file called `memory.x` which describes +# the particular memory layout for your specific chip. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the soft-float ABI for RISC-V mode. +# +# Hazard 3 does not have an FPU and so float function arguments use integer +# registers. +[target.riscv32imac-unknown-none-elf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Trp235x_riscv.x also tells the linker to use +# `rp235x_riscv.x` as a linker script. This adds in RP2350 RISC-V specific +# things that the riscv-rt crate's `link.x` requires and then includes +# `link.x` automatically. This is the reverse of how we do it on Cortex-M. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp2350_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +[env] +DEFMT_LOG = "debug" diff --git a/drivers/0x08_lcd1602_rust/.gitignore b/drivers/0x08_lcd1602_rust/.gitignore new file mode 100644 index 0000000..03381c1 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/.gitignore @@ -0,0 +1,113 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,macos,windows,linux + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux diff --git a/drivers/0x08_lcd1602_rust/.pico-rs b/drivers/0x08_lcd1602_rust/.pico-rs new file mode 100644 index 0000000..1b6702a --- /dev/null +++ b/drivers/0x08_lcd1602_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/.vscode/extensions.json b/drivers/0x08_lcd1602_rust/.vscode/extensions.json new file mode 100644 index 0000000..5f2fd0c --- /dev/null +++ b/drivers/0x08_lcd1602_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/.vscode/launch.json b/drivers/0x08_lcd1602_rust/.vscode/launch.json new file mode 100644 index 0000000..0bc38c3 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/.vscode/settings.json b/drivers/0x08_lcd1602_rust/.vscode/settings.json new file mode 100644 index 0000000..b03cd57 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/.vscode/tasks.json b/drivers/0x08_lcd1602_rust/.vscode/tasks.json new file mode 100644 index 0000000..4194af4 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/Cargo.toml b/drivers/0x08_lcd1602_rust/Cargo.toml new file mode 100644 index 0000000..aaff630 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/Cargo.toml @@ -0,0 +1,80 @@ +[package] +edition = "2024" +name = "lcd1602" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "lcd1602_lib" +path = "src/lib.rs" + +[[bin]] +name = "lcd1602" +path = "src/main.rs" + + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embedded-hal = "1.0.0" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 3 diff --git a/drivers/0x08_lcd1602_rust/LICENSE-APACHE b/drivers/0x08_lcd1602_rust/LICENSE-APACHE new file mode 100644 index 0000000..8d99cbc --- /dev/null +++ b/drivers/0x08_lcd1602_rust/LICENSE-APACHE @@ -0,0 +1,204 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2021–2024 The rp-rs Developers + Copyright (c) 2021 rp-rs organization + Copyright (c) 2025 Raspberry Pi Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/LICENSE-MIT b/drivers/0x08_lcd1602_rust/LICENSE-MIT new file mode 100644 index 0000000..5369c70 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/LICENSE-MIT @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2021–2024 The rp-rs Developers +Copyright (c) 2021 rp-rs organization +Copyright (c) 2025 Raspberry Pi Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/README.md b/drivers/0x08_lcd1602_rust/README.md new file mode 100644 index 0000000..910cd90 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/README.md @@ -0,0 +1,114 @@ +# 0x08 LCD1602 Rust Driver + +This repository contains a Bare-Metal Rust driver for the HD44780-based LCD1602 character display (interfaced via a PCF8574 I2C backpack) on the **RP2350** (and RP2040) microcontrollers. + +It includes: +- A demo (`src/main.rs`) that displays a title on the first row and a continuously incrementing 6-digit counter on the second row. +- A reusable library module (`src/lcd1602.rs`) providing a hardware-agnostic `lcd1602_lib` with helper math and string formatting functions to assemble commands, DDRAM addresses, and zero-padded counter strings. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the LCD command/nibble construction and string formatting logic is separated into a reusable math library without touching hardware registers, you can run the unit tests natively on your computer! + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we initialize the system clocks, pins, UART (for logging), SysTick (for delay), and the I2C peripheral (for LCD communication). + +* **`run(...)`**: The master setup function. Calls the helper initialization functions below, triggers the LCD setup sequence, and enters the infinite counter loop. +* **`init_i2c(...)`**: Initializes the hardware `I2C1` peripheral, reconfigures `GPIO 2` and `GPIO 3` as `SDA` and `SCL` lines (with internal pull-ups enabled), and sets the clock speed to 100 kHz. +* **`lcd_hd44780_reset(...)` & `lcd_hd44780_configure(...)`**: Executes the strict timing requirements of the HD44780 datasheet to force the LCD into 4-bit mode (using three successive `0x03` nibbles followed by `0x02`), then configures a 2-line display, turns on the screen, and clears the RAM. +* **`lcd_write4(...)` & `lcd_send(...)`**: The core functions for pushing data to the screen. Because the I2C backpack (PCF8574) uses an 8-bit GPIO expander, we must send an 8-bit LCD command by breaking it into two 4-bit nibbles. +* **`pcf_pulse_enable(...)`**: Simulates the physical `EN` (Enable) pin being toggled high, then low, to clock data into the HD44780 shift register over I2C. +* **`update_counter(...)`**: Uses the `lcd1602_lib` to format the current `count` into a byte array without memory allocation, then sends those bytes over I2C and UART. + +### 3. The Reusable LCD1602 Library (`src/lcd1602.rs`) +Because manipulating bits for a 4-bit LCD over an 8-bit I2C GPIO expander can be messy, this module cleanly separates the bitwise logic from the hardware I/O. + +* **`build_nibble(...)`**: Takes a 4-bit data value and combines it with the Register Select (`RS`) bit and the Backlight enable bit to construct the raw byte that must be written to the PCF8574 expander. +* **`nibble_with_en(...)` & `nibble_without_en(...)`**: Trivial helpers that apply/remove the Enable (`EN`) mask bit to the assembled I2C byte. +* **`cursor_address(...)`**: Safely translates a `(line, position)` coordinate into the memory-mapped DDRAM address required by the HD44780 controller. +* **`format_counter(...)`**: Efficiently splits a 32-bit integer into its component ASCII decimal digits using division, right-justifies them into a 6-character field (`Count: 12`), and returns the exact number of bytes written to the buffer so the caller knows how much to transmit. diff --git a/drivers/0x08_lcd1602_rust/build.rs b/drivers/0x08_lcd1602_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x08_lcd1602_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x08_lcd1602_rust/rp2040.x b/drivers/0x08_lcd1602_rust/rp2040.x new file mode 100644 index 0000000..0cc665b --- /dev/null +++ b/drivers/0x08_lcd1602_rust/rp2040.x @@ -0,0 +1,91 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + /* + * Here we assume you have 2048 KiB of Flash. This is what the Pi Pico + * has, but your board may have more or less Flash and you should adjust + * this value to suit. + */ + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + /* + * RAM consists of 4 banks, SRAM0-SRAM3, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 256K + /* + * RAM banks 4 and 5 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k + + /* SRAM banks 0-3 can also be accessed directly. However, those ranges + alias with the RAM mapping, above. So don't use them at the same time! + SRAM0 : ORIGIN = 0x21000000, LENGTH = 64k + SRAM1 : ORIGIN = 0x21010000, LENGTH = 64k + SRAM2 : ORIGIN = 0x21020000, LENGTH = 64k + SRAM3 : ORIGIN = 0x21030000, LENGTH = 64k + */ +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + /* ### Boot loader + * + * An executable block of code which sets up the QSPI interface for + * 'Execute-In-Place' (or XIP) mode. Also sends chip-specific commands to + * the external flash chip. + * + * Must go at the start of external flash, where the Boot ROM expects it. + */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 512 bytes of flash, + * where picotool can find it + */ + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; diff --git a/drivers/0x08_lcd1602_rust/rp2350.x b/drivers/0x08_lcd1602_rust/rp2350.x new file mode 100644 index 0000000..bda94d8 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/rp2350.x @@ -0,0 +1,83 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + /* move .text to start /after/ the boot info */ + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + \ No newline at end of file diff --git a/drivers/0x08_lcd1602_rust/rp2350_riscv.x b/drivers/0x08_lcd1602_rust/rp2350_riscv.x new file mode 100644 index 0000000..84388aa --- /dev/null +++ b/drivers/0x08_lcd1602_rust/rp2350_riscv.x @@ -0,0 +1,259 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut _heap_size }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all `32`-byte aligned. These alignments are assumed by the RAM + initialization routine. There's also a second benefit: `32`-byte aligned boundaries + means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ +PROVIDE(_setup_interrupts = default_setup_interrupts); + +/* # Multi-processing hook function + fn _mp_hook() -> bool; + + This function is called from all the harts and must return true only for one hart, + which will perform memory initialization. For other harts it must return false + and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). +*/ +PROVIDE(_mp_hook = default_mp_hook); + +/* # Start trap function override + By default uses the riscv crates default trap handler + but by providing the `_start_trap` symbol external crates can override. +*/ +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + /* This section is intended to make _stext address work */ + . = ABSOLUTE(_stext); + } > FLASH + + .text _stext : + { + /* Put reset handler first in .text section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + __start_block_addr = .; + KEEP(*(.start_block)); + . = ALIGN(4); + *(.trap); + *(.trap.rust); + *(.text.abort); + *(.text .text.*); + . = ALIGN(4); + } > FLASH + + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + + .rodata : ALIGN(4) + { + *(.srodata .srodata.*); + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + } > FLASH + + .data : ALIGN(32) + { + _sidata = LOADADDR(.data); + __sidata = LOADADDR(.data); + _sdata = .; + __sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + . = ALIGN(32); + _edata = .; + __edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : ALIGN(32) + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(32); + _ebss = .; + } > RAM + + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + _sheap = .; + . += _heap_size; + . = ALIGN(4); + _eheap = .; + } > RAM + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + _estack = .; + . = ABSOLUTE(_stack_start); + _sstack = .; + } > RAM + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + .eh_frame (INFO) : { KEEP(*(.eh_frame)) } + .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } +} + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(FLASH) % 4 == 0, " +ERROR(riscv-rt): the start of the FLASH must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 32 == 0, " +ERROR(riscv-rt): the start of the RAM must be 32-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 32 == 0 && _edata % 32 == 0, " +BUG(riscv-rt): .data is not 32-byte aligned"); + +ASSERT(_sidata % 32 == 0, " +BUG(riscv-rt): the LMA of .data is not 32-byte aligned"); + +ASSERT(_sbss % 32 == 0 && _ebss % 32 == 0, " +BUG(riscv-rt): .bss is not 32-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), " +ERROR(riscv-rt): The .text section must be placed inside the FLASH region. +Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'"); + +ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +/* Do not exceed this mark in the error messages above | */ diff --git a/drivers/0x08_lcd1602_rust/src/board.rs b/drivers/0x08_lcd1602_rust/src/board.rs new file mode 100644 index 0000000..e1197a4 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/src/board.rs @@ -0,0 +1,506 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// I2C bus trait for LCD communication +use embedded_hal::i2c::I2c; +// Rate extension trait for .Hz() baud rate construction +use fugit::RateExtU32; +// Clock trait for accessing system clock frequency +use hal::Clock; +// GPIO pin types and function selectors +use hal::gpio::{FunctionI2C, FunctionNull, FunctionUart, Pin, PullDown, PullNone, PullUp}; +// UART configuration and peripheral types +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// I2C bus speed in Hz (100 kHz standard mode). +pub(crate) const I2C_BAUD: u32 = 100_000; + +/// 7-bit I2C address of the PCF8574 LCD backpack. +pub(crate) const LCD_I2C_ADDR: u8 = 0x27; + +/// Number of bit positions to shift a 4-bit nibble. +pub(crate) const NIBBLE_SHIFT: u8 = 4; + +/// PCF8574 backlight enable mask. +pub(crate) const BACKLIGHT_MASK: u8 = 0x08; + +/// Delay between counter updates in milliseconds. +pub(crate) const COUNTER_DELAY_MS: u32 = 1_000; + +/// Type alias for the configured TX pin (GPIO 0, UART function, no pull). +pub(crate) type TxPin = Pin; + +/// Type alias for the configured RX pin (GPIO 1, UART function, no pull). +pub(crate) type RxPin = Pin; + +/// Type alias for the default TX pin state from `Pins::new()`. +pub(crate) type TxPinDefault = Pin; + +/// Type alias for the default RX pin state from `Pins::new()`. +pub(crate) type RxPinDefault = Pin; + +/// Type alias for the fully-enabled UART0 peripheral with TX/RX p. +pub(crate) type EnabledUart = UartPeripheral; + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +/// +/// # Arguments +/// +/// * `xosc` - XOSC peripheral singleton. +/// * `clocks` - CLOCKS peripheral singleton. +/// * `pll_sys` - PLL_SYS peripheral singleton. +/// * `pll_usb` - PLL_USB peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `watchdog` - Mutable reference to the watchdog timer. +/// +/// # Returns +/// +/// Configured clocks manager. +/// +/// # Panics +/// +/// Panics if clock initialisation fails. +pub(crate) fn init_clocks( + xosc: hal::pac::XOSC, + clocks: hal::pac::CLOCKS, + pll_sys: hal::pac::PLL_SYS, + pll_usb: hal::pac::PLL_USB, + resets: &mut hal::pac::RESETS, + watchdog: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + xosc, + clocks, + pll_sys, + pll_usb, + resets, + watchdog, + ) + .unwrap() +} + +/// Unlock the GPIO bank and return the pin set. +/// +/// # Arguments +/// +/// * `io_bank0` - IO_BANK0 peripheral singleton. +/// * `pads_bank0` - PADS_BANK0 peripheral singleton. +/// * `sio` - SIO peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// +/// # Returns +/// +/// GPIO pin set for the entire bank. +pub(crate) fn init_pins( + io_bank0: hal::pac::IO_BANK0, + pads_bank0: hal::pac::PADS_BANK0, + sio: hal::pac::SIO, + resets: &mut hal::pac::RESETS, +) -> hal::gpio::Pins { + let sio = hal::Sio::new(sio); + hal::gpio::Pins::new(io_bank0, pads_bank0, sio.gpio_bank0, resets) +} + +/// Initialise UART0 for serial output (stdio equivalent). +/// +/// # Arguments +/// +/// * `uart0` - PAC UART0 peripheral singleton. +/// * `tx_pin` - GPIO pin to use as UART0 TX (GPIO 0). +/// * `rx_pin` - GPIO pin to use as UART0 RX (GPIO 1). +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Enabled UART0 peripheral ready for blocking writes. +/// +/// # Panics +/// +/// Panics if the HAL cannot achieve the requested baud rate. +pub(crate) fn init_uart( + uart0: hal::pac::UART0, + tx_pin: TxPinDefault, + rx_pin: RxPinDefault, + resets: &mut hal::pac::RESETS, + clocks: &hal::clocks::ClocksManager, +) -> EnabledUart { + let pins = ( + tx_pin.reconfigure::(), + rx_pin.reconfigure::(), + ); + let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One); + UartPeripheral::new(uart0, pins, resets) + .enable(cfg, clocks.peripheral_clock.freq()) + .unwrap() +} + +/// Create a blocking delay timer from the ARM SysTick peripheral. +/// +/// # Arguments +/// +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Blocking delay provider. +/// +/// # Panics +/// +/// Panics if the cortex-m core peripherals have already been taken. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +pub(crate) fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay { + let core = cortex_m::Peripherals::take().unwrap(); + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()) +} + +/// Write one raw byte to the PCF8574 expander over I2C. +/// +/// # Arguments +/// +/// * `i2c` - Mutable reference to the I2C bus. +/// * `addr` - 7-bit I2C address of the PCF8574. +/// * `data` - Byte to write. +/// +/// # Arguments +/// +/// * `i2c` - The `i2c` parameter. +/// * `addr` - Device address. +/// * `data` - Data to send/write. +fn pcf_write_byte(i2c: &mut impl I2c, addr: u8, data: u8) { + let _ = i2c.write(addr, &[data]); +} + +/// Toggle EN to latch a nibble into the LCD controller. +/// +/// # Arguments +/// +/// * `i2c` - Mutable reference to the I2C bus. +/// * `addr` - 7-bit I2C address of the PCF8574. +/// * `data` - Nibble byte without EN asserted. +/// * `delay` - Delay provider for timing. +/// +/// # Arguments +/// +/// * `i2c` - The `i2c` parameter. +/// * `addr` - Device address. +/// * `data` - Data to send/write. +/// * `delay` - Delay value. +fn pcf_pulse_enable(i2c: &mut impl I2c, addr: u8, data: u8, d: &mut cortex_m::delay::Delay) { + pcf_write_byte(i2c, addr, lcd1602_lib::lcd1602::nibble_with_en(data)); d.delay_us(1); + pcf_write_byte(i2c, addr, lcd1602_lib::lcd1602::nibble_without_en(data)); d.delay_us(50); +} + +/// Write one 4-bit nibble to the LCD. +/// +/// # Arguments +/// +/// * `i2c` - Mutable reference to the I2C bus. +/// * `addr` - 7-bit I2C address of the PCF8574. +/// * `nibble` - 4-bit value to send. +/// * `mode` - Register select: 0 for command, 1 for data. +/// * `delay` - Delay provider for timing. +fn lcd_write4(i2c: &mut impl I2c, addr: u8, nibble: u8, mode: u8, d: &mut cortex_m::delay::Delay) { + pcf_pulse_enable(i2c, addr, lcd1602_lib::lcd1602::build_nibble(nibble, NIBBLE_SHIFT, mode, BACKLIGHT_MASK), d); +} + +/// Send one full 8-bit command/data value as two nibbles. +/// +/// # Arguments +/// +/// * `i2c` - Mutable reference to the I2C bus. +/// * `addr` - 7-bit I2C address of the PCF8574. +/// * `value` - 8-bit value to send. +/// * `mode` - Register select: 0 for command, 1 for data. +/// * `delay` - Delay provider for timing. +/// +/// # Arguments +/// +/// * `i2c` - The `i2c` parameter. +/// * `addr` - Device address. +/// * `value` - Value to use. +/// * `mode` - The `mode` parameter. +/// * `delay` - Delay value. +fn lcd_send(i2c: &mut impl I2c, addr: u8, value: u8, mode: u8, d: &mut cortex_m::delay::Delay) { + lcd_write4(i2c, addr, (value >> 4) & 0x0F, mode, d); + lcd_write4(i2c, addr, value & 0x0F, mode, d); +} + +/// Send three 0x03 nibbles with required power-on delays. +/// +/// # Arguments +/// +/// * `i2c` - The `i2c` parameter. +/// * `addr` - Device address. +/// * `delay` - Delay value. +/// +/// # Arguments +/// +/// * `i2c` - The `i2c` parameter. +/// * `addr` - Device address. +/// * `delay` - Delay value. +fn lcd_reset_pulse_3x(i2c: &mut impl I2c, addr: u8, d: &mut cortex_m::delay::Delay) { + lcd_write4(i2c, addr, 0x03, 0, d); d.delay_ms(5); + lcd_write4(i2c, addr, 0x03, 0, d); d.delay_us(150); + lcd_write4(i2c, addr, 0x03, 0, d); d.delay_us(150); +} + +/// Execute the HD44780 4-bit mode power-on reset sequence. +/// +/// # Arguments +/// +/// * `i2c` - Mutable reference to the I2C bus. +/// * `addr` - 7-bit I2C address of the PCF8574. +/// * `delay` - Delay provider for timing. +/// +/// # Arguments +/// +/// * `i2c` - The `i2c` parameter. +/// * `addr` - Device address. +/// * `delay` - Delay value. +fn lcd_hd44780_reset(i2c: &mut impl I2c, addr: u8, d: &mut cortex_m::delay::Delay) { + lcd_reset_pulse_3x(i2c, addr, d); + lcd_write4(i2c, addr, 0x02, 0, d); d.delay_us(150); +} + +/// Send post-reset configuration commands to the HD44780. +/// +/// # Arguments +/// +/// * `i2c` - Mutable reference to the I2C bus. +/// * `addr` - 7-bit I2C address of the PCF8574. +/// * `delay` - Delay provider for timing. +/// +/// # Arguments +/// +/// * `i2c` - The `i2c` parameter. +/// * `addr` - Device address. +/// * `delay` - Delay value. +fn lcd_hd44780_configure(i2c: &mut impl I2c, addr: u8, d: &mut cortex_m::delay::Delay) { + lcd_send(i2c, addr, 0x28, 0, d); lcd_send(i2c, addr, 0x0C, 0, d); + lcd_send(i2c, addr, 0x01, 0, d); d.delay_ms(2); lcd_send(i2c, addr, 0x06, 0, d); +} + +/// Set the LCD cursor position. +/// +/// # Arguments +/// +/// * `i2c` - Mutable reference to the I2C bus. +/// * `addr` - 7-bit I2C address of the PCF8574. +/// * `line` - Display row (0 or 1). +/// * `position` - Column offset. +/// * `delay` - Delay provider for timing. +fn lcd_set_cursor(i2c: &mut impl I2c, addr: u8, line: u8, position: u8, d: &mut cortex_m::delay::Delay) { + lcd_send(i2c, addr, lcd1602_lib::lcd1602::cursor_address(line, position), 0, d); +} + +/// Write a byte slice as character data to the LCD. +/// +/// # Arguments +/// +/// * `i2c` - Mutable reference to the I2C bus. +/// * `addr` - 7-bit I2C address of the PCF8574. +/// * `s` - Byte slice of ASCII characters to display. +/// * `delay` - Delay provider for timing. +/// +/// # Arguments +/// +/// * `i2c` - The `i2c` parameter. +/// * `addr` - Device address. +/// * `s` - The `s` parameter. +/// * `delay` - Delay value. +fn lcd_puts(i2c: &mut impl I2c, addr: u8, s: &[u8], d: &mut cortex_m::delay::Delay) { + for &ch in s { lcd_send(i2c, addr, ch, 1, d); } +} + +/// Initialize the LCD, display the title, and log over UART. +/// +/// # Arguments +/// +/// * `i2c` - Mutable reference to the I2C bus. +/// * `uart` - UART peripheral for serial log output. +/// * `delay` - Delay provider for timing. +pub(crate) fn setup_display(i2c: &mut impl I2c, u: &EnabledUart, d: &mut cortex_m::delay::Delay) { + lcd_hd44780_reset(i2c, LCD_I2C_ADDR, d); lcd_hd44780_configure(i2c, LCD_I2C_ADDR, d); + lcd_show_title(i2c, d); u.write_full_blocking(b"LCD 1602 driver initialized at I2C addr 0x27\r\n"); +} + +/// Write the title text on LCD row 0. +/// +/// # Arguments +/// +/// * `i2c` - The `i2c` parameter. +/// * `delay` - Delay value. +/// +/// # Arguments +/// +/// * `i2c` - The `i2c` parameter. +/// * `delay` - Delay value. +fn lcd_show_title(i2c: &mut impl I2c, d: &mut cortex_m::delay::Delay) { + lcd_set_cursor(i2c, LCD_I2C_ADDR, 0, 0, d); lcd_puts(i2c, LCD_I2C_ADDR, b"Reverse Eng.", d); +} + +/// Format and display the next counter value on LCD line 1. +/// +/// # Arguments +/// +/// * `i2c` - Mutable reference to the I2C bus. +/// * `uart` - UART peripheral for serial log output. +/// * `delay` - Delay provider for timing. +/// * `count` - Mutable reference to the counter state. +pub(crate) fn update_counter(i2c: &mut impl I2c, u: &EnabledUart, d: &mut cortex_m::delay::Delay, count: &mut u32) { + let mut buf = [0u8; 16]; + let n = lcd1602_lib::lcd1602::format_counter(&mut buf, *count); *count += 1; + lcd_display_counter(i2c, d, &buf[..n]); uart_log_counter(u, &buf[..n]); d.delay_ms(COUNTER_DELAY_MS); +} + +/// Write counter text to LCD line 1. +/// +/// # Arguments +/// +/// * `i2c` - The `i2c` parameter. +/// * `delay` - Delay value. +/// * `text` - The `text` parameter. +/// +/// # Arguments +/// +/// * `i2c` - The `i2c` parameter. +/// * `delay` - Delay value. +/// * `text` - The `text` parameter. +fn lcd_display_counter(i2c: &mut impl I2c, d: &mut cortex_m::delay::Delay, text: &[u8]) { + lcd_set_cursor(i2c, LCD_I2C_ADDR, 1, 0, d); lcd_puts(i2c, LCD_I2C_ADDR, text, d); +} + +/// Log counter text over UART with trailing CRLF. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +/// * `text` - The `text` parameter. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +/// * `text` - The `text` parameter. +fn uart_log_counter(u: &EnabledUart, text: &[u8]) { + u.write_full_blocking(text); u.write_full_blocking(b"\r\n"); +} + +/// Initialise all peripherals and run the LCD 1602 counter demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `pac` - The `pac` parameter. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! { + let mut w = hal::Watchdog::new(p.WATCHDOG); + let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut w); + let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS); + let u = init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c); + let mut i2c = init_i2c(p.I2C1, pins.gpio2, pins.gpio3, &mut p.RESETS, &c); + setup_display(&mut i2c, &u, &mut init_delay(&c)); + counter_loop(&mut i2c, &u, &mut init_delay(&c)) +} + +/// Initialise I2C1 on SDA=GPIO2 / SCL=GPIO3. +/// +/// # Arguments +/// +/// * `i2c1` - PAC I2C1 peripheral singleton. +/// * `sda` - Default GPIO 2 pin (will be reconfigured for I2C). +/// * `scl` - Default GPIO 3 pin (will be reconfigured for I2C). +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Configured I2C1 bus controller. +fn init_i2c( + i2c: hal::pac::I2C1, sda: Pin, scl: Pin, r: &mut hal::pac::RESETS, c: &hal::clocks::ClocksManager +) -> impl I2c { + hal::I2C::i2c1(i2c, sda.reconfigure::(), scl.reconfigure::(), I2C_BAUD.Hz(), r, c.system_clock.freq()) +} + +/// Run the counter display loop forever. +/// +/// # Arguments +/// +/// * `i2c` - Mutable reference to the I2C bus controller. +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// * `delay` - Mutable reference to the blocking delay provider. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `i2c` - The `i2c` parameter. +/// * `uart` - The `uart` parameter. +/// * `delay` - Delay value. +/// +/// # Returns +/// +/// A value of type `!`. +fn counter_loop(i2c: &mut impl I2c, u: &EnabledUart, d: &mut cortex_m::delay::Delay) -> ! { + let mut count: u32 = 0; + loop { update_counter(i2c, u, d, &mut count); } +} + diff --git a/drivers/0x08_lcd1602_rust/src/lcd1602.rs b/drivers/0x08_lcd1602_rust/src/lcd1602.rs new file mode 100644 index 0000000..4384010 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/src/lcd1602.rs @@ -0,0 +1,316 @@ +//! Implementation module +//! +//! **File:** `lcd1602.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +/// PCF8574 -> LCD control pin: Register Select. +pub const PIN_RS: u8 = 0x01; + +/// PCF8574 -> LCD control pin: Read/Write. +pub const PIN_RW: u8 = 0x02; + +/// PCF8574 -> LCD control pin: Enable. +pub const PIN_EN: u8 = 0x04; + +/// Build a PCF8574 output byte for a 4-bit LCD nibble. +/// +/// # Arguments +/// +/// * `nibble` - 4-bit data value (0x00..0x0F). +/// * `nibble_shift` - Number of bits to shift the nibble left. +/// * `mode` - Register select mode (0 = command, non-zero = data). +/// * `backlight_mask` - Bitmask to enable the backlight LED. +/// +/// # Returns +/// +/// Assembled PCF8574 output byte. +/// +/// # Arguments +/// +/// * `nibble` - The `nibble` parameter. +/// * `nibble_shift` - The `nibble_shift` parameter. +/// * `mode` - The `mode` parameter. +/// * `backlight_mask` - The `backlight_mask` parameter. +/// +/// # Returns +/// +/// An 8-bit unsigned integer value. +#[inline] +pub fn build_nibble(nibble: u8, nibble_shift: u8, mode: u8, backlight_mask: u8) -> u8 { + let mut data = (nibble & 0x0F) << nibble_shift; + if mode != 0 { data |= PIN_RS; } + data | backlight_mask +} + +/// Build the PCF8574 byte with EN asserted. +/// +/// # Arguments +/// +/// * `nibble_byte` - PCF8574 output byte before EN assertion. +/// +/// # Returns +/// +/// Byte with the EN bit set. +/// +/// # Arguments +/// +/// * `nibble_byte` - The `nibble_byte` parameter. +/// +/// # Returns +/// +/// An 8-bit unsigned integer value. +#[inline] +pub fn nibble_with_en(nibble_byte: u8) -> u8 { nibble_byte | PIN_EN } + +/// Build the PCF8574 byte with EN de-asserted. +/// +/// # Arguments +/// +/// * `nibble_byte` - PCF8574 output byte before EN de-assertion. +/// +/// # Returns +/// +/// Byte with the EN bit cleared. +/// +/// # Arguments +/// +/// * `nibble_byte` - The `nibble_byte` parameter. +/// +/// # Returns +/// +/// An 8-bit unsigned integer value. +#[inline] +pub fn nibble_without_en(nibble_byte: u8) -> u8 { nibble_byte & !PIN_EN } + +/// HD44780 row-offset lookup. +const ROW_OFFSETS: [u8; 2] = [0x00, 0x40]; + +/// Compute the DDRAM address byte for `lcd_set_cursor`. +/// +/// # Arguments +/// +/// * `line` - Display row (0 or 1; values > 1 are clamped to 1). +/// * `position` - Column offset within the row. +/// +/// # Returns +/// +/// HD44780 set-DDRAM-address command byte (0x80 | offset). +/// +/// # Arguments +/// +/// * `line` - The `line` parameter. +/// * `position` - The `position` parameter. +/// +/// # Returns +/// +/// An 8-bit unsigned integer value. +#[inline] +pub fn cursor_address(line: u8, position: u8) -> u8 { + let row = if line > 1 { 1 } else { line as usize }; + 0x80 | (position + ROW_OFFSETS[row]) +} + +/// Extract the six decimal digits of a counter value into an array. +/// +/// # Arguments +/// +/// * `c` - Command byte. +/// +/// # Returns +/// +/// A value of type `[u8; 6]`. +/// +/// # Arguments +/// +/// * `c` - Command byte. +/// +/// # Returns +/// +/// A value of type `[u8; 6]`. +#[inline] +fn fill_digit_array(c: u32) -> [u8; 6] { + [ + ((c / 100000) % 10) as u8, ((c / 10000) % 10) as u8, ((c / 1000) % 10) as u8, + ((c / 100) % 10) as u8, ((c / 10) % 10) as u8, (c % 10) as u8, + ] +} + +/// Write six counter digits into `buf` starting at `start`, suppressing leading zeros. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `pos` - The `pos` parameter. +/// * `digits` - The `digits` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `pos` - The `pos` parameter. +/// * `digits` - The `digits` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn write_counter_digits(buf: &mut [u8], mut pos: usize, digits: &[u8; 6]) -> usize { + let mut leading = true; + for (i, &d) in digits.iter().enumerate() { + if i == 5 || d != 0 { leading = false; } + buf[pos] = if leading { b' ' } else { b'0' + d }; pos += 1; + } + pos +} + +/// Format a counter value as `"Count: NNNNNN"` (right-justified, 6 digits). +/// +/// # Arguments +/// +/// * `buf` - Mutable byte slice (must be at least 13 bytes). +/// * `count` - Counter value to format (0..999999). +/// +/// # Returns +/// +/// Number of bytes written into the buffer. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `count` - The `count` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_counter(buf: &mut [u8], count: u32) -> usize { + buf[..7].copy_from_slice(b"Count: "); + write_counter_digits(buf, 7, &fill_digit_array(count)) +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + + /// Executes the build nibble command mode operation. + #[test] + fn build_nibble_command_mode() { + let b = build_nibble(0x03, 4, 0, 0x08); + assert_eq!(b, 0x38); + } + + /// Executes the build nibble data mode operation. + #[test] + fn build_nibble_data_mode() { + let b = build_nibble(0x04, 4, 1, 0x08); + assert_eq!(b, 0x49); + } + + /// Executes the build nibble no backlight operation. + #[test] + fn build_nibble_no_backlight() { + let b = build_nibble(0x0F, 4, 0, 0x00); + assert_eq!(b, 0xF0); + } + + /// Executes the nibble with en sets bit operation. + #[test] + fn nibble_with_en_sets_bit() { + assert_eq!(nibble_with_en(0x38), 0x3C); + } + + /// Executes the nibble without en clears bit operation. + #[test] + fn nibble_without_en_clears_bit() { + assert_eq!(nibble_without_en(0x3C), 0x38); + } + + /// Executes the cursor address line0 col0 operation. + #[test] + fn cursor_address_line0_col0() { + assert_eq!(cursor_address(0, 0), 0x80); + } + + /// Executes the cursor address line1 col0 operation. + #[test] + fn cursor_address_line1_col0() { + assert_eq!(cursor_address(1, 0), 0xC0); + } + + /// Executes the cursor address line0 col5 operation. + #[test] + fn cursor_address_line0_col5() { + assert_eq!(cursor_address(0, 5), 0x85); + } + + /// Executes the cursor address line1 col15 operation. + #[test] + fn cursor_address_line1_col15() { + assert_eq!(cursor_address(1, 15), 0xCF); + } + + /// Executes the cursor address clamps line operation. + #[test] + fn cursor_address_clamps_line() { + assert_eq!(cursor_address(5, 0), 0xC0); + } + + /// Executes the format counter zero operation. + #[test] + fn format_counter_zero() { + let mut buf = [0u8; 16]; + let n = format_counter(&mut buf, 0); + assert_eq!(&buf[..n], b"Count: 0"); + } + + /// Executes the format counter one operation. + #[test] + fn format_counter_one() { + let mut buf = [0u8; 16]; + let n = format_counter(&mut buf, 1); + assert_eq!(&buf[..n], b"Count: 1"); + } + + /// Executes the format counter large operation. + #[test] + fn format_counter_large() { + let mut buf = [0u8; 16]; + let n = format_counter(&mut buf, 123456); + assert_eq!(&buf[..n], b"Count: 123456"); + } + + /// Executes the format counter six digits operation. + #[test] + fn format_counter_six_digits() { + let mut buf = [0u8; 16]; + let n = format_counter(&mut buf, 999999); + assert_eq!(&buf[..n], b"Count: 999999"); + } +} diff --git a/drivers/0x08_lcd1602_rust/src/lib.rs b/drivers/0x08_lcd1602_rust/src/lib.rs new file mode 100644 index 0000000..41b5280 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/src/lib.rs @@ -0,0 +1,6 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +#![cfg_attr(not(test), no_std)] +pub mod lcd1602; diff --git a/drivers/0x08_lcd1602_rust/src/main.rs b/drivers/0x08_lcd1602_rust/src/main.rs new file mode 100644 index 0000000..ae29d80 --- /dev/null +++ b/drivers/0x08_lcd1602_rust/src/main.rs @@ -0,0 +1,102 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; +// LCD1602 driver module — suppress warnings for unused public API functions + +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the LCD 1602 counter demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"LCD 1602 Counter Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/drivers/0x09_dht11_rust/.cargo/config.toml b/drivers/0x09_dht11_rust/.cargo/config.toml new file mode 100644 index 0000000..1791d74 --- /dev/null +++ b/drivers/0x09_dht11_rust/.cargo/config.toml @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2021–2024 The rp-rs Developers +# Copyright (c) 2021 rp-rs organization +# Copyright (c) 2025 Raspberry Pi Ltd. +# +# Cargo Configuration for the https://github.com/rp-rs/rp-hal.git repository. +# +# You might want to make a similar file in your own repository if you are +# writing programs for Raspberry Silicon microcontrollers. +# + +[build] +target = "thumbv8m.main-none-eabihf" +# Set the default target to match the Cortex-M33 in the RP2350 +# target = "thumbv8m.main-none-eabihf" +# target = "thumbv6m-none-eabi" +# target = "riscv32imac-unknown-none-elf" + +# Target specific options +[target.thumbv6m-none-eabi] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as the linker +# script. This is usually provided by the cortex-m-rt crate, and by default +# the version in that crate will include a file called `memory.x` which +# describes the particular memory layout for your specific chip. +# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't +# have SIMD) +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the hard-float ABI for Arm mode. +# +# The FPU is enabled by default, and float function arguments use FPU +# registers. +[target.thumbv8m.main-none-eabihf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as a linker script. +# This is usually provided by the cortex-m-rt crate, and by default the +# version in that crate will include a file called `memory.x` which describes +# the particular memory layout for your specific chip. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +# This is the soft-float ABI for RISC-V mode. +# +# Hazard 3 does not have an FPU and so float function arguments use integer +# registers. +[target.riscv32imac-unknown-none-elf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Trp235x_riscv.x also tells the linker to use +# `rp235x_riscv.x` as a linker script. This adds in RP2350 RISC-V specific +# things that the riscv-rt crate's `link.x` requires and then includes +# `link.x` automatically. This is the reverse of how we do it on Cortex-M. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp2350_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" +#runner = "probe-rs run --chip ${CHIP} --protocol swd" + +[env] +DEFMT_LOG = "debug" diff --git a/drivers/0x09_dht11_rust/.gitignore b/drivers/0x09_dht11_rust/.gitignore new file mode 100644 index 0000000..03381c1 --- /dev/null +++ b/drivers/0x09_dht11_rust/.gitignore @@ -0,0 +1,113 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,macos,windows,linux + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,macos,windows,linux diff --git a/drivers/0x09_dht11_rust/.pico-rs b/drivers/0x09_dht11_rust/.pico-rs new file mode 100644 index 0000000..1b6702a --- /dev/null +++ b/drivers/0x09_dht11_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 \ No newline at end of file diff --git a/drivers/0x09_dht11_rust/.vscode/extensions.json b/drivers/0x09_dht11_rust/.vscode/extensions.json new file mode 100644 index 0000000..5f2fd0c --- /dev/null +++ b/drivers/0x09_dht11_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/drivers/0x09_dht11_rust/.vscode/launch.json b/drivers/0x09_dht11_rust/.vscode/launch.json new file mode 100644 index 0000000..0bc38c3 --- /dev/null +++ b/drivers/0x09_dht11_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} \ No newline at end of file diff --git a/drivers/0x09_dht11_rust/.vscode/settings.json b/drivers/0x09_dht11_rust/.vscode/settings.json new file mode 100644 index 0000000..b03cd57 --- /dev/null +++ b/drivers/0x09_dht11_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} \ No newline at end of file diff --git a/drivers/0x09_dht11_rust/.vscode/tasks.json b/drivers/0x09_dht11_rust/.vscode/tasks.json new file mode 100644 index 0000000..4194af4 --- /dev/null +++ b/drivers/0x09_dht11_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/drivers/0x09_dht11_rust/Cargo.toml b/drivers/0x09_dht11_rust/Cargo.toml new file mode 100644 index 0000000..4e6a427 --- /dev/null +++ b/drivers/0x09_dht11_rust/Cargo.toml @@ -0,0 +1,79 @@ +[package] +edition = "2024" +name = "dht11" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "dht11_lib" +path = "src/lib.rs" + +[[bin]] +name = "dht11" +path = "src/main.rs" + + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 3 diff --git a/drivers/0x09_dht11_rust/LICENSE-APACHE b/drivers/0x09_dht11_rust/LICENSE-APACHE new file mode 100644 index 0000000..8d99cbc --- /dev/null +++ b/drivers/0x09_dht11_rust/LICENSE-APACHE @@ -0,0 +1,204 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2021–2024 The rp-rs Developers + Copyright (c) 2021 rp-rs organization + Copyright (c) 2025 Raspberry Pi Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/drivers/0x09_dht11_rust/LICENSE-MIT b/drivers/0x09_dht11_rust/LICENSE-MIT new file mode 100644 index 0000000..5369c70 --- /dev/null +++ b/drivers/0x09_dht11_rust/LICENSE-MIT @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2021–2024 The rp-rs Developers +Copyright (c) 2021 rp-rs organization +Copyright (c) 2025 Raspberry Pi Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + \ No newline at end of file diff --git a/drivers/0x09_dht11_rust/README.md b/drivers/0x09_dht11_rust/README.md new file mode 100644 index 0000000..2d428fd --- /dev/null +++ b/drivers/0x09_dht11_rust/README.md @@ -0,0 +1,115 @@ +# 0x09 DHT11 Rust Driver + +This repository contains a Bare-Metal Rust driver for the **DHT11 Temperature and Humidity Sensor** on the **RP2350** (and RP2040) microcontrollers, implemented strictly using a GPIO pin and a hardware timer (no specialized hardware peripheral like PIO or PWM). + +It includes: +- A demo (`src/main.rs`) that continuously reads the sensor every 2 seconds and outputs the results over UART. +- A reusable library module (`src/dht11.rs`) providing a hardware-agnostic `dht11_lib` containing the pure-logic checksum validation, data parsing, and string formatting routines. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the DHT11 data parsing, checksum validation, and string formatting logic is separated into a reusable math library without touching hardware registers, you can run the unit tests natively on your computer! + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we initialize the system clocks, pins, UART (for logging), SysTick (for delays), and a hardware timer (for microsecond precision measurement). + +* **`run(...)`**: The master setup function. Calls the helper initialization functions below, prints an initialization message over UART, and enters the infinite polling loop. +* **`poll_sensor(...)`**: Called repeatedly in the main loop. It invokes the read sequence, formats the result, writes it to UART, and blocks for 2 seconds (the DHT11's minimum polling interval). +* **`read_sensor(...)`**: The high-level function for a single DHT11 read. It delegates to `acquire_data()` to handle the timing protocol, and then uses the `dht11_lib` to parse the resulting 5-byte array into temperature and humidity. +* **`acquire_data(...)`**: Executes the strict protocol: pulls the pin low for 18ms (`send_start_signal()`), waits for the sensor's acknowledgment (`wait_response()`), and reads 40 bits into an array (`read_40_bits()`). +* **`measure_bit_duration(...)`**: Crucial for decoding the data. It waits for the sensor to pull the pin high, takes a timestamp, waits for it to pull low, takes another timestamp, and returns the duration. +* **`read_bit(...)`**: Takes the measured pulse duration and calls the hardware-agnostic `accumulate_bit()` function in the `dht11_lib` to pack the 1 or 0 into the byte array. +* **`gpio_drive(...)` & `gpio_release(...)`**: Manually flips the `GPIO 4` pin direction (output/input) via the `SIO` (Single-Cycle IO) registers to support the bidirectional 1-wire protocol. + +### 3. The Reusable DHT11 Library (`src/dht11.rs`) +Because manipulating bits across byte boundaries and formatting floats without `std::fmt` can be error-prone, this module handles all pure-logic data manipulation. + +* **`accumulate_bit(...)`**: Shifts the current byte left and inserts a `1` if the measured pulse duration exceeds the 40-microsecond threshold. +* **`validate_checksum(...)`**: Adds the first four bytes (humidity int, humidity frac, temp int, temp frac) and ensures the lowest 8 bits match the fifth byte (the checksum). +* **`parse_humidity(...)` & `parse_temperature(...)`**: Converts the specific byte pairs into standard floating-point numbers. +* **`format_reading(...)` & `format_error(...)`**: Efficiently constructs the final ASCII string without allocating memory, allowing the `board.rs` code to pipe the raw bytes directly to UART. diff --git a/drivers/0x09_dht11_rust/build.rs b/drivers/0x09_dht11_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x09_dht11_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x09_dht11_rust/rp2040.x b/drivers/0x09_dht11_rust/rp2040.x new file mode 100644 index 0000000..0cc665b --- /dev/null +++ b/drivers/0x09_dht11_rust/rp2040.x @@ -0,0 +1,91 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + /* + * Here we assume you have 2048 KiB of Flash. This is what the Pi Pico + * has, but your board may have more or less Flash and you should adjust + * this value to suit. + */ + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + /* + * RAM consists of 4 banks, SRAM0-SRAM3, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 256K + /* + * RAM banks 4 and 5 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k + + /* SRAM banks 0-3 can also be accessed directly. However, those ranges + alias with the RAM mapping, above. So don't use them at the same time! + SRAM0 : ORIGIN = 0x21000000, LENGTH = 64k + SRAM1 : ORIGIN = 0x21010000, LENGTH = 64k + SRAM2 : ORIGIN = 0x21020000, LENGTH = 64k + SRAM3 : ORIGIN = 0x21030000, LENGTH = 64k + */ +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + /* ### Boot loader + * + * An executable block of code which sets up the QSPI interface for + * 'Execute-In-Place' (or XIP) mode. Also sends chip-specific commands to + * the external flash chip. + * + * Must go at the start of external flash, where the Boot ROM expects it. + */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 512 bytes of flash, + * where picotool can find it + */ + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; diff --git a/drivers/0x09_dht11_rust/rp2350.x b/drivers/0x09_dht11_rust/rp2350.x new file mode 100644 index 0000000..bda94d8 --- /dev/null +++ b/drivers/0x09_dht11_rust/rp2350.x @@ -0,0 +1,83 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + /* move .text to start /after/ the boot info */ + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + \ No newline at end of file diff --git a/drivers/0x09_dht11_rust/rp2350_riscv.x b/drivers/0x09_dht11_rust/rp2350_riscv.x new file mode 100644 index 0000000..84388aa --- /dev/null +++ b/drivers/0x09_dht11_rust/rp2350_riscv.x @@ -0,0 +1,259 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021–2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut _heap_size }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all `32`-byte aligned. These alignments are assumed by the RAM + initialization routine. There's also a second benefit: `32`-byte aligned boundaries + means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ +PROVIDE(_setup_interrupts = default_setup_interrupts); + +/* # Multi-processing hook function + fn _mp_hook() -> bool; + + This function is called from all the harts and must return true only for one hart, + which will perform memory initialization. For other harts it must return false + and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). +*/ +PROVIDE(_mp_hook = default_mp_hook); + +/* # Start trap function override + By default uses the riscv crates default trap handler + but by providing the `_start_trap` symbol external crates can override. +*/ +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + /* This section is intended to make _stext address work */ + . = ABSOLUTE(_stext); + } > FLASH + + .text _stext : + { + /* Put reset handler first in .text section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + __start_block_addr = .; + KEEP(*(.start_block)); + . = ALIGN(4); + *(.trap); + *(.trap.rust); + *(.text.abort); + *(.text .text.*); + . = ALIGN(4); + } > FLASH + + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + + .rodata : ALIGN(4) + { + *(.srodata .srodata.*); + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + } > FLASH + + .data : ALIGN(32) + { + _sidata = LOADADDR(.data); + __sidata = LOADADDR(.data); + _sdata = .; + __sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + . = ALIGN(32); + _edata = .; + __edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : ALIGN(32) + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(32); + _ebss = .; + } > RAM + + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + _sheap = .; + . += _heap_size; + . = ALIGN(4); + _eheap = .; + } > RAM + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + _estack = .; + . = ABSOLUTE(_stack_start); + _sstack = .; + } > RAM + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + .eh_frame (INFO) : { KEEP(*(.eh_frame)) } + .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } +} + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(FLASH) % 4 == 0, " +ERROR(riscv-rt): the start of the FLASH must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 32 == 0, " +ERROR(riscv-rt): the start of the RAM must be 32-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 32 == 0 && _edata % 32 == 0, " +BUG(riscv-rt): .data is not 32-byte aligned"); + +ASSERT(_sidata % 32 == 0, " +BUG(riscv-rt): the LMA of .data is not 32-byte aligned"); + +ASSERT(_sbss % 32 == 0 && _ebss % 32 == 0, " +BUG(riscv-rt): .bss is not 32-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), " +ERROR(riscv-rt): The .text section must be placed inside the FLASH region. +Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'"); + +ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +/* Do not exceed this mark in the error messages above | */ diff --git a/drivers/0x09_dht11_rust/src/board.rs b/drivers/0x09_dht11_rust/src/board.rs new file mode 100644 index 0000000..f8e6531 --- /dev/null +++ b/drivers/0x09_dht11_rust/src/board.rs @@ -0,0 +1,523 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// DHT11 pure-logic functions (checksum, parsing, formatting) +use dht11_lib::dht11; +// Rate extension trait for .Hz() baud rate construction +use fugit::RateExtU32; +// Clock trait for accessing system clock frequency +use hal::Clock; +// GPIO pin types and function selectors +use hal::gpio::{FunctionNull, FunctionUart, Pin, PullDown, PullNone}; +// UART configuration and peripheral types +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Timer device type for the HAL timer peripheral. +#[cfg(rp2350)] +pub(crate) type HalTimer = hal::Timer; +/// Timer type alias for RP2040 (non-generic). +#[cfg(rp2040)] +pub(crate) type HalTimer = hal::Timer; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// GPIO pin number connected to the DHT11 data line. +pub(crate) const DHT11_GPIO: u8 = 4; + +/// Polling interval in milliseconds (DHT11 minimum is 2 seconds). +pub(crate) const POLL_MS: u32 = 2_000; + +/// Type alias for the configured TX pin (GPIO 0, UART function, no pull). +pub(crate) type TxPin = Pin; + +/// Type alias for the configured RX pin (GPIO 1, UART function, no pull). +pub(crate) type RxPin = Pin; + +/// Type alias for the default TX pin state from `Pins::new()`. +pub(crate) type TxPinDefault = Pin; + +/// Type alias for the default RX pin state from `Pins::new()`. +pub(crate) type RxPinDefault = Pin; + +/// Type alias for the fully-enabled UART0 peripheral with TX/RX p. +pub(crate) type EnabledUart = UartPeripheral; + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +/// +/// # Arguments +/// +/// * `xosc` - XOSC peripheral singleton. +/// * `clocks` - CLOCKS peripheral singleton. +/// * `pll_sys` - PLL_SYS peripheral singleton. +/// * `pll_usb` - PLL_USB peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `watchdog` - Mutable reference to the watchdog timer. +/// +/// # Returns +/// +/// Configured clocks manager. +/// +/// # Panics +/// +/// Panics if clock initialisation fails. +pub(crate) fn init_clocks( + xosc: hal::pac::XOSC, + clocks: hal::pac::CLOCKS, + pll_sys: hal::pac::PLL_SYS, + pll_usb: hal::pac::PLL_USB, + resets: &mut hal::pac::RESETS, + watchdog: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + xosc, + clocks, + pll_sys, + pll_usb, + resets, + watchdog, + ) + .unwrap() +} + +/// Unlock the GPIO bank and return the pin set. +/// +/// # Arguments +/// +/// * `io_bank0` - IO_BANK0 peripheral singleton. +/// * `pads_bank0` - PADS_BANK0 peripheral singleton. +/// * `sio` - SIO peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// +/// # Returns +/// +/// GPIO pin set for the entire bank. +pub(crate) fn init_pins( + io_bank0: hal::pac::IO_BANK0, + pads_bank0: hal::pac::PADS_BANK0, + sio: hal::pac::SIO, + resets: &mut hal::pac::RESETS, +) -> hal::gpio::Pins { + let sio = hal::Sio::new(sio); + hal::gpio::Pins::new(io_bank0, pads_bank0, sio.gpio_bank0, resets) +} + +/// Initialise UART0 for serial output (stdio equivalent). +/// +/// # Arguments +/// +/// * `uart0` - PAC UART0 peripheral singleton. +/// * `tx_pin` - GPIO pin to use as UART0 TX (GPIO 0). +/// * `rx_pin` - GPIO pin to use as UART0 RX (GPIO 1). +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Enabled UART0 peripheral ready for blocking writes. +/// +/// # Panics +/// +/// Panics if the HAL cannot achieve the requested baud rate. +pub(crate) fn init_uart( + uart0: hal::pac::UART0, + tx_pin: TxPinDefault, + rx_pin: RxPinDefault, + resets: &mut hal::pac::RESETS, + clocks: &hal::clocks::ClocksManager, +) -> EnabledUart { + let pins = ( + tx_pin.reconfigure::(), + rx_pin.reconfigure::(), + ); + let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One); + UartPeripheral::new(uart0, pins, resets) + .enable(cfg, clocks.peripheral_clock.freq()) + .unwrap() +} + +/// Create a blocking delay timer from the ARM SysTick peripheral. +/// +/// # Arguments +/// +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Blocking delay provider. +/// +/// # Panics +/// +/// Panics if the cortex-m core peripherals have already been taken. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +pub(crate) fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay { + let core = cortex_m::Peripherals::take().unwrap(); + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()) +} + +/// Set or clear the DHT11 GPIO output level via SIO registers. +/// +/// # Arguments +/// +/// * `sio` - The `sio` parameter. +/// * `high` - The `high` parameter. +/// +/// # Arguments +/// +/// * `sio` - The `sio` parameter. +/// * `high` - The `high` parameter. +fn sio_set_level(sio: &hal::pac::sio::RegisterBlock, high: bool) { + if high { sio.gpio_out_set().write(|w| unsafe { w.bits(1u32 << DHT11_GPIO) }); } + else { sio.gpio_out_clr().write(|w| unsafe { w.bits(1u32 << DHT11_GPIO) }); } +} + +/// Drive the DHT11 data pin to a given level (enables output). +/// +/// # Arguments +/// +/// * `high` - `true` to drive HIGH, `false` to drive LOW. +/// +/// # Arguments +/// +/// * `high` - The `high` parameter. +fn gpio_drive(high: bool) { + unsafe { + let sio = &*hal::pac::SIO::PTR; sio_set_level(sio, high); + sio.gpio_oe_set().write(|w| w.bits(1u32 << DHT11_GPIO)); + } +} + +/// Release the DHT11 data pin back to input mode (disable output driver). +fn gpio_release() { + unsafe { + let sio = &*hal::pac::SIO::PTR; + sio.gpio_oe_clr().write(|w| w.bits(1u32 << DHT11_GPIO)); + } +} + +/// Read the current logic level of the DHT11 data pin. +/// +/// # Returns +/// +/// `true` if the pin reads HIGH, `false` if LOW. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +fn gpio_read() -> bool { + unsafe { (*hal::pac::SIO::PTR).gpio_in().read().bits() & (1u32 << DHT11_GPIO) != 0 } +} + +/// Read the free-running microsecond timer (lower 32 bits). +/// +/// # Arguments +/// +/// * `timer` - Reference to the HAL timer peripheral. +/// +/// # Returns +/// +/// Current timer value in microseconds (wrapping at 2^32). +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// A 32-bit unsigned integer value. +fn time_us_32(timer: &HalTimer) -> u32 { + timer.get_counter().ticks() as u32 +} + +/// Send the DHT11 start signal on the data pin. +/// +/// Drives the pin LOW for 18 ms then HIGH for 40 us before switching +/// the pin to input mode to listen for the sensor response. +/// +/// # Arguments +/// +/// * `delay` - Mutable reference to the blocking delay provider. +/// +/// # Arguments +/// +/// * `delay` - Delay value. +fn send_start_signal(d: &mut cortex_m::delay::Delay) { + gpio_drive(false); d.delay_ms(18); + gpio_drive(true); d.delay_us(40); gpio_release(); +} + +/// Spin until the pin leaves the given logic level, or time out. +/// +/// # Arguments +/// +/// * `level` - Logic level to wait through (`true` = HIGH, `false` = LOW). +/// +/// # Returns +/// +/// `true` once the level changed, `false` on timeout. +/// +/// # Arguments +/// +/// * `level` - The `level` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +fn wait_for_level(level: bool) -> bool { + let mut timeout: u32 = dht11::LEVEL_WAIT_TIMEOUT; + while gpio_read() == level { + timeout -= 1; if timeout == 0 { return false; } + } + true +} + +/// Wait for the DHT11 response after the start signal. +/// +/// The sensor pulls LOW then HIGH then LOW again; each transition +/// is awaited with a timeout. +/// +/// # Returns +/// +/// `true` if the full response was received, `false` on timeout. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +fn wait_response() -> bool { + wait_for_level(true) && wait_for_level(false) && wait_for_level(true) +} + +/// Measure the high-period duration for a single DHT11 bit. +/// +/// # Arguments +/// +/// * `timer` - Reference to the HAL timer for microsecond measurement. +/// +/// # Returns +/// +/// Duration in microseconds, or `None` on timeout. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// An Optional value. +fn measure_bit_duration(timer: &HalTimer) -> Option { + if !wait_for_level(false) { return None; } + let start = time_us_32(timer); + if !wait_for_level(true) { return None; } + Some(time_us_32(timer).wrapping_sub(start)) +} + +/// Read a single bit from the DHT11 data stream. +/// +/// Waits for the low-period to end, measures the high-period duration, +/// and accumulates the result into the data array via +/// [`dht11::accumulate_bit`]. +/// +/// # Arguments +/// +/// * `data` - 5-byte array accumulating the received bits. +/// * `i` - Bit index (0–39). +/// * `timer` - Reference to the HAL timer for microsecond measurement. +/// +/// # Returns +/// +/// `true` on success, `false` on timeout. +/// +/// # Arguments +/// +/// * `data` - Data to send/write. +/// * `i` - The `i` parameter. +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +fn read_bit(data: &mut [u8; 5], i: usize, timer: &HalTimer) -> bool { + let Some(duration) = measure_bit_duration(timer) else { return false; }; + dht11::accumulate_bit(data, i, duration); true +} + +/// Read all 40 data bits from the DHT11. +/// +/// # Arguments +/// +/// * `data` - 5-byte array filled with the received data. +/// * `timer` - Reference to the HAL timer for microsecond measurement. +/// +/// # Returns +/// +/// `true` if all 40 bits were read, `false` on timeout. +/// +/// # Arguments +/// +/// * `data` - Data to send/write. +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +fn read_40_bits(data: &mut [u8; 5], timer: &HalTimer) -> bool { + for i in 0..40 { if !read_bit(data, i, timer) { return false; } } + true +} + +/// Run the full DHT11 acquisition sequence (start, response, 40 bits, checksum). +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// * `delay` - Delay value. +/// * `data` - Data to send/write. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// * `delay` - Delay value. +/// * `data` - Data to send/write. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +fn acquire_data(timer: &HalTimer, delay: &mut cortex_m::delay::Delay, data: &mut [u8; 5]) -> bool { + send_start_signal(delay); + wait_response() && read_40_bits(data, timer) && dht11::validate_checksum(data) +} + +/// Execute the full DHT11 read protocol. +/// +/// Sends the start signal, waits for the sensor response, reads 40 bits +/// of data, validates the checksum, and parses humidity and temperature. +/// +/// # Arguments +/// +/// * `timer` - Reference to the HAL timer for microsecond measurement. +/// * `delay` - Mutable reference to the blocking delay provider. +/// +/// # Returns +/// +/// `Some((humidity, temperature))` on success, `None` on failure. +pub(crate) fn read_sensor(t: &HalTimer, d: &mut cortex_m::delay::Delay) -> Option<(f32, f32)> { + let mut data = [0u8; 5]; + if !acquire_data(t, d, &mut data) { return None; } + Some((dht11::parse_humidity(&data), dht11::parse_temperature(&data))) +} + +/// Format the sensor result (or error) with CRLF into `buf`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `result` - The `result` parameter. +/// * `f32)>` - The `f32)>` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `result` - The `result` parameter. +/// * `f32)>` - The `f32)>` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +fn format_sensor_output(buf: &mut [u8], result: Option<(f32, f32)>) -> usize { + let n = match result { Some((h, t)) => dht11::format_reading(buf, h, t), None => dht11::format_error(buf, DHT11_GPIO), }; + buf[n] = b'\r'; buf[n + 1] = b'\n'; n + 2 +} + +/// Read the sensor, format the result, write it over UART, and wait. +/// +/// On a successful read, prints humidity and temperature; on failure, +/// prints a wiring-check message. Always waits [`POLL_MS`] before +/// returning to respect the DHT11 minimum polling interval. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// * `timer` - Reference to the HAL timer for microsecond measurement. +/// * `delay` - Mutable reference to the blocking delay provider. +pub(crate) fn poll_sensor(u: &EnabledUart, t: &HalTimer, d: &mut cortex_m::delay::Delay) { + let mut buf = [0u8; 64]; let n = format_sensor_output(&mut buf, read_sensor(t, d)); + u.write_full_blocking(&buf[..n]); d.delay_ms(POLL_MS); +} + +/// Initialise all peripherals and run the DHT11 sensor demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `pac` - The `pac` parameter. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! { + let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut hal::Watchdog::new(p.WATCHDOG)); + let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS); + let (u, mut d) = (init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c), init_delay(&c)); + #[cfg(rp2350)] let t = hal::Timer::new_timer0(p.TIMER0, &mut p.RESETS, &c); #[cfg(rp2040)] let t = hal::Timer::new(p.TIMER, &mut p.RESETS); + let _ = pins.gpio4.into_pull_up_input(); u.write_full_blocking(b"DHT11 driver initialized on GPIO 4\r\n"); + loop { poll_sensor(&u, &t, &mut d); } +} + diff --git a/drivers/0x09_dht11_rust/src/dht11.rs b/drivers/0x09_dht11_rust/src/dht11.rs new file mode 100644 index 0000000..d0c3d63 --- /dev/null +++ b/drivers/0x09_dht11_rust/src/dht11.rs @@ -0,0 +1,510 @@ +//! Implementation module +//! +//! **File:** `dht11.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +/// Bit-duration threshold in microseconds. +/// +/// If the high pulse is longer than this value, the bit is a 1; otherwise 0. +pub const BIT_THRESHOLD_US: u32 = 40; + +/// Timeout in spin-loop iterations for waiting on a pin level change. +pub const LEVEL_WAIT_TIMEOUT: u32 = 10_000; + +/// Accumulate a single received bit into the 5-byte data array. +/// +/// Shifts `data[byte_index]` left by one and sets the LSB to 1 if the +/// measured high-pulse `duration_us` exceeds [`BIT_THRESHOLD_US`]. +/// +/// # Arguments +/// +/// * `data` - 5-byte array accumulating the received bits. +/// * `bit_index` - Bit index (0..39). +/// * `duration_us` - Measured duration of the high pulse in microseconds. +/// +/// # Arguments +/// +/// * `data` - Data to send/write. +/// * `bit_index` - The `bit_index` parameter. +/// * `duration_us` - The `duration_us` parameter. +#[inline] +pub fn accumulate_bit(data: &mut [u8; 5], bit_index: usize, duration_us: u32) { + let byte = bit_index / 8; + data[byte] <<= 1; + if duration_us > BIT_THRESHOLD_US { data[byte] |= 1; } +} + +/// Verify the DHT11 checksum byte. +/// +/// The checksum (byte 4) must equal the lower 8 bits of the sum of +/// bytes 0 through 3. +/// +/// # Arguments +/// +/// * `data` - 5-byte received data (bytes 0–3 plus checksum in byte 4). +/// +/// # Returns +/// +/// `true` if the checksum matches, `false` otherwise. +/// +/// # Arguments +/// +/// * `data` - Data to send/write. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +#[inline] +pub fn validate_checksum(data: &[u8; 5]) -> bool { + data[4] == data[0].wrapping_add(data[1]).wrapping_add(data[2]).wrapping_add(data[3]) +} + +/// Parse humidity from the raw DHT11 data bytes. +/// +/// Humidity is encoded as an integer part in byte 0 and a fractional +/// part (tenths) in byte 1. +/// +/// # Arguments +/// +/// * `data` - 5-byte received data. +/// +/// # Returns +/// +/// Humidity as a percentage (e.g. 65.3 for 65.3%). +/// +/// # Arguments +/// +/// * `data` - Data to send/write. +/// +/// # Returns +/// +/// A value of type `f32`. +#[inline] +pub fn parse_humidity(data: &[u8; 5]) -> f32 { data[0] as f32 + data[1] as f32 * 0.1 } + +/// Parse temperature from the raw DHT11 data bytes. +/// +/// Temperature is encoded as an integer part in byte 2 and a fractional +/// part (tenths) in byte 3. +/// +/// # Arguments +/// +/// * `data` - 5-byte received data. +/// +/// # Returns +/// +/// Temperature in degrees Celsius. +/// +/// # Arguments +/// +/// * `data` - Data to send/write. +/// +/// # Returns +/// +/// A value of type `f32`. +#[inline] +pub fn parse_temperature(data: &[u8; 5]) -> f32 { data[2] as f32 + data[3] as f32 * 0.1 } + +/// Format a sensor reading as `"Humidity: XX.X% Temperature: XX.X C"`. +/// +/// # Arguments +/// +/// * `buf` - Mutable byte slice (must be at least 40 bytes). +/// * `humidity` - Humidity percentage. +/// * `temperature` - Temperature in degrees Celsius. +/// +/// # Returns +/// +/// Number of bytes written into the buffer. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `humidity` - The `humidity` parameter. +/// * `temperature` - The `temperature` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_reading(buf: &mut [u8], humidity: f32, temperature: f32) -> usize { + let mut pos = copy_slice(buf, 0, b"Humidity: "); + pos += format_f32_1(&mut buf[pos..], humidity); + pos += copy_slice(buf, pos, b"% Temperature: "); + pos += format_f32_1(&mut buf[pos..], temperature); + pos + copy_slice(buf, pos, b" C") +} + +/// Copy a byte slice into `buf` at the given offset, returning bytes written. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `offset` - The `offset` parameter. +/// * `src` - The `src` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `offset` - The `offset` parameter. +/// * `src` - The `src` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn copy_slice(buf: &mut [u8], offset: usize, src: &[u8]) -> usize { + buf[offset..offset + src.len()].copy_from_slice(src); src.len() +} + +/// Format a failed-read error message. +/// +/// # Arguments +/// +/// * `buf` - Mutable byte slice (must be at least 42 bytes). +/// * `gpio` - GPIO pin number to include in the message. +/// +/// # Returns +/// +/// Number of bytes written into the buffer. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `gpio` - The `gpio` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_error(buf: &mut [u8], gpio: u8) -> usize { + let p = b"DHT11 read failed - check wiring on GPIO "; + buf[..p.len()].copy_from_slice(p); + p.len() + format_u8(&mut buf[p.len()..], gpio) +} + +/// Return the number of decimal digits needed for a `u8`. +/// +/// # Arguments +/// +/// * `val` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `val` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn u8_digit_count(val: u8) -> usize { + if val >= 100 { 3 } else if val >= 10 { 2 } else { 1 } +} + +/// Write `count` decimal digits of `val` into `buf`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `val` - Value to use. +/// * `count` - The `count` parameter. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `val` - Value to use. +/// * `count` - The `count` parameter. +#[inline] +fn write_u8_digits(buf: &mut [u8], val: u8, count: usize) { + if count >= 3 { buf[0] = b'0' + val / 100; } + if count >= 2 { buf[count - 2] = b'0' + (val / 10) % 10; } + buf[count - 1] = b'0' + val % 10; +} + +/// Format a `u8` as decimal ASCII digits. +/// +/// # Arguments +/// +/// * `buf` - Output buffer. +/// * `val` - Value to format. +/// +/// # Returns +/// +/// Number of bytes written. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `val` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn format_u8(buf: &mut [u8], val: u8) -> usize { + let n = u8_digit_count(val); write_u8_digits(buf, val, n); n +} + +/// Format an `f32` with one decimal place (e.g. `"25.3"`). +/// +/// # Arguments +/// +/// * `buf` - Output buffer. +/// * `val` - Value to format. +/// +/// # Returns +/// +/// Number of bytes written. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `val` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn format_f32_1(buf: &mut [u8], val: f32) -> usize { + let scaled = (val * 10.0) as u32; + let pos = format_u32_minimal(buf, scaled / 10); + buf[pos] = b'.'; buf[pos + 1] = b'0' + (scaled % 10) as u8; + pos + 2 +} + +/// Write a conditional digit into `buf` if `val` meets the threshold. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `pos` - The `pos` parameter. +/// * `val` - Value to use. +/// * `threshold` - The `threshold` parameter. +/// * `divisor` - The `divisor` parameter. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `pos` - The `pos` parameter. +/// * `val` - Value to use. +/// * `threshold` - The `threshold` parameter. +/// * `divisor` - The `divisor` parameter. +#[inline] +fn write_digit_if(buf: &mut [u8], pos: &mut usize, val: u32, threshold: u32, divisor: u32) { + if val >= threshold { buf[*pos] = b'0' + ((val / divisor) % 10) as u8; *pos += 1; } +} + +/// Format a u32 as minimal decimal digits (no leading zeros). +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn format_u32_minimal(buf: &mut [u8], value: u32) -> usize { + let mut pos = 0; + write_digit_if(buf, &mut pos, value, 100, 100); write_digit_if(buf, &mut pos, value, 10, 10); + buf[pos] = b'0' + (value % 10) as u8; pos + 1 +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + + /// Executes the accumulate bit zero short pulse operation. + #[test] + fn accumulate_bit_zero_short_pulse() { + let mut data = [0u8; 5]; + accumulate_bit(&mut data, 0, 30); + assert_eq!(data[0], 0); + } + + /// Executes the accumulate bit one long pulse operation. + #[test] + fn accumulate_bit_one_long_pulse() { + let mut data = [0u8; 5]; + accumulate_bit(&mut data, 0, 70); + assert_eq!(data[0], 1); + } + + /// Executes the accumulate bit threshold exact operation. + #[test] + fn accumulate_bit_threshold_exact() { + let mut data = [0u8; 5]; + accumulate_bit(&mut data, 0, BIT_THRESHOLD_US); + assert_eq!(data[0], 0); + } + + /// Executes the accumulate bit two bits operation. + #[test] + fn accumulate_bit_two_bits() { + let mut data = [0u8; 5]; + accumulate_bit(&mut data, 0, 70); + accumulate_bit(&mut data, 1, 30); + assert_eq!(data[0], 0b10); + } + + /// Executes the accumulate bit crosses byte operation. + #[test] + fn accumulate_bit_crosses_byte() { + let mut data = [0u8; 5]; + accumulate_bit(&mut data, 7, 70); + accumulate_bit(&mut data, 8, 70); + assert_eq!(data[0], 1); + assert_eq!(data[1], 1); + } + + /// Executes the validate checksum valid operation. + #[test] + fn validate_checksum_valid() { + let data = [0x28, 0x00, 0x1A, 0x00, 0x42]; + assert!(validate_checksum(&data)); + } + + /// Executes the validate checksum invalid operation. + #[test] + fn validate_checksum_invalid() { + let data = [0x28, 0x00, 0x1A, 0x00, 0xFF]; + assert!(!validate_checksum(&data)); + } + + /// Executes the validate checksum wraps operation. + #[test] + fn validate_checksum_wraps() { + let data = [0xFF, 0x01, 0x00, 0x00, 0x00]; + assert!(validate_checksum(&data)); + } + + /// Executes the parse humidity integer only operation. + #[test] + fn parse_humidity_integer_only() { + let data = [0x41, 0x00, 0x00, 0x00, 0x41]; + let h = parse_humidity(&data); + assert!((h - 65.0).abs() < 0.01); + } + + /// Executes the parse humidity with fraction operation. + #[test] + fn parse_humidity_with_fraction() { + let data = [0x41, 0x03, 0x00, 0x00, 0x44]; + let h = parse_humidity(&data); + assert!((h - 65.3).abs() < 0.01); + } + + /// Executes the parse temperature integer only operation. + #[test] + fn parse_temperature_integer_only() { + let data = [0x00, 0x00, 0x19, 0x00, 0x19]; + let t = parse_temperature(&data); + assert!((t - 25.0).abs() < 0.01); + } + + /// Executes the parse temperature with fraction operation. + #[test] + fn parse_temperature_with_fraction() { + let data = [0x00, 0x00, 0x19, 0x05, 0x1E]; + let t = parse_temperature(&data); + assert!((t - 25.5).abs() < 0.01); + } + + /// Executes the format reading typical operation. + #[test] + fn format_reading_typical() { + let mut buf = [0u8; 48]; + let n = format_reading(&mut buf, 65.0, 25.0); + assert_eq!(&buf[..n], b"Humidity: 65.0% Temperature: 25.0 C"); + } + + /// Executes the format reading with fractions operation. + #[test] + fn format_reading_with_fractions() { + let mut buf = [0u8; 48]; + let n = format_reading(&mut buf, 65.3, 25.5); + assert_eq!(&buf[..n], b"Humidity: 65.3% Temperature: 25.5 C"); + } + + /// Executes the format error gpio4 operation. + #[test] + fn format_error_gpio4() { + let mut buf = [0u8; 48]; + let n = format_error(&mut buf, 4); + assert_eq!(&buf[..n], b"DHT11 read failed - check wiring on GPIO 4"); + } + + /// Executes the format error gpio15 operation. + #[test] + fn format_error_gpio15() { + let mut buf = [0u8; 48]; + let n = format_error(&mut buf, 15); + assert_eq!(&buf[..n], b"DHT11 read failed - check wiring on GPIO 15"); + } + + /// Executes the format u8 single digit operation. + #[test] + fn format_u8_single_digit() { + let mut buf = [0u8; 4]; + let n = format_u8(&mut buf, 4); + assert_eq!(&buf[..n], b"4"); + } + + /// Executes the format u8 two digits operation. + #[test] + fn format_u8_two_digits() { + let mut buf = [0u8; 4]; + let n = format_u8(&mut buf, 15); + assert_eq!(&buf[..n], b"15"); + } + + /// Executes the format u8 three digits operation. + #[test] + fn format_u8_three_digits() { + let mut buf = [0u8; 4]; + let n = format_u8(&mut buf, 255); + assert_eq!(&buf[..n], b"255"); + } +} diff --git a/drivers/0x09_dht11_rust/src/lib.rs b/drivers/0x09_dht11_rust/src/lib.rs new file mode 100644 index 0000000..bc3d4aa --- /dev/null +++ b/drivers/0x09_dht11_rust/src/lib.rs @@ -0,0 +1,6 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +#![cfg_attr(not(test), no_std)] +pub mod dht11; diff --git a/drivers/0x09_dht11_rust/src/main.rs b/drivers/0x09_dht11_rust/src/main.rs new file mode 100644 index 0000000..c80f3f1 --- /dev/null +++ b/drivers/0x09_dht11_rust/src/main.rs @@ -0,0 +1,102 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; +// DHT11 driver module — suppress warnings for unused public API functions + +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the DHT11 sensor demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"DHT11 Sensor Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/drivers/0x0a_ir_rust/.cargo/config.toml b/drivers/0x0a_ir_rust/.cargo/config.toml new file mode 100644 index 0000000..70fb5fa --- /dev/null +++ b/drivers/0x0a_ir_rust/.cargo/config.toml @@ -0,0 +1,32 @@ +[build] +target = "thumbv8m.main-none-eabihf" + +[target.thumbv6m-none-eabi] +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[target.thumbv8m.main-none-eabihf] +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[target.riscv32imac-unknown-none-elf] +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp2350_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[env] +DEFMT_LOG = "debug" diff --git a/drivers/0x0a_ir_rust/.pico-rs b/drivers/0x0a_ir_rust/.pico-rs new file mode 100644 index 0000000..1b6702a --- /dev/null +++ b/drivers/0x0a_ir_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 \ No newline at end of file diff --git a/drivers/0x0a_ir_rust/.vscode/extensions.json b/drivers/0x0a_ir_rust/.vscode/extensions.json new file mode 100644 index 0000000..5f2fd0c --- /dev/null +++ b/drivers/0x0a_ir_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/drivers/0x0a_ir_rust/.vscode/launch.json b/drivers/0x0a_ir_rust/.vscode/launch.json new file mode 100644 index 0000000..0bc38c3 --- /dev/null +++ b/drivers/0x0a_ir_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} \ No newline at end of file diff --git a/drivers/0x0a_ir_rust/.vscode/settings.json b/drivers/0x0a_ir_rust/.vscode/settings.json new file mode 100644 index 0000000..b03cd57 --- /dev/null +++ b/drivers/0x0a_ir_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} \ No newline at end of file diff --git a/drivers/0x0a_ir_rust/.vscode/tasks.json b/drivers/0x0a_ir_rust/.vscode/tasks.json new file mode 100644 index 0000000..d288f27 --- /dev/null +++ b/drivers/0x0a_ir_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} diff --git a/drivers/0x0a_ir_rust/Cargo.lock b/drivers/0x0a_ir_rust/Cargo.lock new file mode 100644 index 0000000..39d839f --- /dev/null +++ b/drivers/0x0a_ir_rust/Cargo.lock @@ -0,0 +1,704 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield 0.13.2", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "crc-any" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62ec9ff5f7965e4d7280bd5482acd20aadb50d632cf6c1d74493856b011fa73" +dependencies = [ + "debug-helper", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "debug-helper" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" +dependencies = [ + "critical-section", + "defmt", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "frunk" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28aef0f9aa070bce60767c12ba9cb41efeaf1a2bc6427f87b7d83f11239a16d7" +dependencies = [ + "frunk_core", + "frunk_derives", +] + +[[package]] +name = "frunk_core" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476eeaa382e3462b84da5d6ba3da97b5786823c2d0d3a0d04ef088d073da225c" + +[[package]] +name = "frunk_derives" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b4095fc99e1d858e5b8c7125d2638372ec85aa0fe6c807105cf10b0265ca6c" +dependencies = [ + "frunk_proc_macro_helpers", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "frunk_proc_macro_helpers" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1952b802269f2db12ab7c0bd328d0ae8feaabf19f352a7b0af7bb0c5693abfce" +dependencies = [ + "frunk_core", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fugit" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e639847d312d9a82d2e75b0edcc1e934efcc64e6cb7aa94f0b1fbec0bc231d6" +dependencies = [ + "gcd", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "ir" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt", + "defmt-rtt", + "fugit", + "panic-halt", + "panic-probe", + "rp2040-boot2", + "rp2040-hal", + "rp235x-hal", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "panic-halt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11" + +[[package]] +name = "panic-probe" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" +dependencies = [ + "cortex-m", + "defmt", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" +dependencies = [ + "arrayvec", + "num_enum", + "paste", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "riscv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f5c1b8bf41ea746266cdee443d1d1e9125c86ce1447e1a2615abd34330d33a9" +dependencies = [ + "critical-section", + "embedded-hal 1.0.0", +] + +[[package]] +name = "riscv-rt" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d35e32cf1383183e8885d8a9aa4402a087fd094dc34c2cb6df6687d0229dfe" +dependencies = [ + "riscv", + "riscv-rt-macros", +] + +[[package]] +name = "riscv-rt-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30f19a85fe107b65031e0ba8ec60c34c2494069fe910d6c297f5e7cb5a6f76d0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rp-binary-info" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f582261945fa215d40e2988b4df595d11c0908c0fff97a0fe23df766d117b790" + +[[package]] +name = "rp-hal-common" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8288358786b1458fb2caac8c4b40fb529ef4200d6c46467e2695b7a8ba573ae8" +dependencies = [ + "fugit", +] + +[[package]] +name = "rp2040-boot2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" +dependencies = [ + "crc-any", +] + +[[package]] +name = "rp2040-hal" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb79a4590775204387f334672e6f79c0d734d0a159da23d60677b3c10fa1245" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "itertools 0.10.5", + "nb 1.1.0", + "paste", + "pio", + "rand_core", + "rp-binary-info", + "rp-hal-common", + "rp2040-hal-macros", + "rp2040-pac", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp2040-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86479063e497efe1ae81995ef9071f54fd1c7427e04d6c5b84cde545ff672a5e" +dependencies = [ + "cortex-m-rt", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rp2040-pac" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83cbcd3f7a0ca7bbe61dc4eb7e202842bee4e27b769a7bf3a4a72fa399d6e404" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "vcell", +] + +[[package]] +name = "rp235x-hal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2939c82776b0b4ae110168b4298b5adf831e6cff249b057bf2a2187453b959c" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "cortex-m-rt", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "gcd", + "itertools 0.13.0", + "nb 1.1.0", + "paste", + "pio", + "rand_core", + "riscv", + "riscv-rt", + "rp-binary-info", + "rp-hal-common", + "rp235x-hal-macros", + "rp235x-pac", + "sha2-const-stable", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp235x-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74edd7a5979e9763bbb98e9746e711bac7464ee3397af7288e6c288ff0d3c764" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rp235x-pac" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffcb6931deee4242886b5a1df62db5e2555b0eb6ae1e8be101f3ea3e58e65c6" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "vcell", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] diff --git a/drivers/0x0a_ir_rust/Cargo.toml b/drivers/0x0a_ir_rust/Cargo.toml new file mode 100644 index 0000000..6fa49d2 --- /dev/null +++ b/drivers/0x0a_ir_rust/Cargo.toml @@ -0,0 +1,79 @@ +[package] +edition = "2024" +name = "ir" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "ir_lib" +path = "src/lib.rs" + +[[bin]] +name = "ir" +path = "src/main.rs" + + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 3 diff --git a/drivers/0x0a_ir_rust/README.md b/drivers/0x0a_ir_rust/README.md new file mode 100644 index 0000000..96bc49b --- /dev/null +++ b/drivers/0x0a_ir_rust/README.md @@ -0,0 +1,113 @@ +# 0x0a IR Rust Driver + +This repository contains a Bare-Metal Rust driver for an **IR (Infrared) Receiver** decoding the **NEC Protocol** on the **RP2350** (and RP2040) microcontrollers, implemented strictly using a GPIO pin and a hardware timer to measure pulse widths. + +It includes: +- A demo (`src/main.rs`) that waits for IR signals and decodes/prints the received command via UART. +- A reusable library module (`src/ir.rs`) providing a hardware-agnostic `ir_lib` containing the timing thresholds, bit accumulation logic, frame validation, and string formatting routines. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the NEC protocol logic (timing constants, bit shifting, checksum validation, and string formatting) is separated into a reusable math library without touching hardware registers, you can run the unit tests natively on your computer! + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we initialize the system clocks, pins, UART (for logging), SysTick (for delay), and a hardware timer (for microsecond precision measurement of IR pulses). + +* **`run(...)`**: The master setup function. Calls the helper initialization functions below, prints an initialization banner over UART, and enters the infinite polling loop waiting for IR signals. +* **`poll_receiver(...)`**: Called repeatedly in the main loop. It invokes the decoding sequence (`ir_getkey`) and, if a valid command is returned, formats it and writes it to UART. +* **`ir_getkey(...)`**: The high-level function for receiving a full NEC frame. It waits for the 9ms leader mark and 4.5ms leader space, then attempts to read all 32 data bits, and finally validates the frame using the library. +* **`wait_leader(...)`**: Executes the strict NEC protocol synchronization: waits for the line to go idle, then validates the lengths of the incoming leader mark (active low) and leader space (idle high). +* **`read_nec_bit(...)`**: Waits for the standard 562.5µs bit mark, measures the duration of the following space, and calls the hardware-agnostic `accumulate_nec_bit()` function in the `ir_lib` to pack a 1 (long space) or 0 (short space) into the byte array. +* **`wait_for_level(...)`**: A robust spin-loop that takes a timestamp, checks the GPIO state, and calculates elapsed time, returning the total duration if the state changes before the timeout. + +### 3. The Reusable IR Library (`src/ir.rs`) +Because distinguishing between logical 0s and 1s requires precise duration thresholds, and manipulating bits across 4 bytes is tedious, this module handles all pure-logic data validation. + +* **`accumulate_nec_bit(...)`**: Shifts the current bit index and inserts a `1` if the measured pulse duration exceeds the 1.2ms threshold (a standard NEC "1" space is ~1.69ms). +* **`validate_nec_frame(...)`**: The NEC protocol transmits the 8-bit Address, the inverse Address, the 8-bit Command, and the inverse Command. This function verifies that `byte0 + byte1 == 0xFF` and `byte2 + byte3 == 0xFF` to ensure data integrity, then returns the Command byte. +* **`format_command(...)`**: Efficiently constructs the final ASCII string containing the hex and decimal representation of the command (e.g., `NEC command: 0x45 (69)`) without allocating memory, allowing the `board.rs` code to pipe the raw bytes directly to UART. diff --git a/drivers/0x0a_ir_rust/build.rs b/drivers/0x0a_ir_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x0a_ir_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x0a_ir_rust/rp2040.x b/drivers/0x0a_ir_rust/rp2040.x new file mode 100644 index 0000000..6e1a654 --- /dev/null +++ b/drivers/0x0a_ir_rust/rp2040.x @@ -0,0 +1,44 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + .bi_entries : ALIGN(4) + { + __bi_entries_start = .; + KEEP(*(.bi_entries)); + . = ALIGN(4); + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; \ No newline at end of file diff --git a/drivers/0x0a_ir_rust/rp2350.x b/drivers/0x0a_ir_rust/rp2350.x new file mode 100644 index 0000000..c183dcd --- /dev/null +++ b/drivers/0x0a_ir_rust/rp2350.x @@ -0,0 +1,47 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + RAM : ORIGIN = 0x20000000, LENGTH = 512K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + .bi_entries : ALIGN(4) + { + __bi_entries_start = .; + KEEP(*(.bi_entries)); + . = ALIGN(4); + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); \ No newline at end of file diff --git a/drivers/0x0a_ir_rust/rp2350_riscv.x b/drivers/0x0a_ir_rust/rp2350_riscv.x new file mode 100644 index 0000000..194563d --- /dev/null +++ b/drivers/0x0a_ir_rust/rp2350_riscv.x @@ -0,0 +1,58 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + RAM : ORIGIN = 0x20000000, LENGTH = 512K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +PROVIDE(__pre_init = default_pre_init); +PROVIDE(_setup_interrupts = default_setup_interrupts); +PROVIDE(_mp_hook = default_mp_hook); +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + . = ABSOLUTE(_stext); + } > FLASH +} \ No newline at end of file diff --git a/drivers/0x0a_ir_rust/src/board.rs b/drivers/0x0a_ir_rust/src/board.rs new file mode 100644 index 0000000..902baf5 --- /dev/null +++ b/drivers/0x0a_ir_rust/src/board.rs @@ -0,0 +1,431 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// IR pure-logic functions and timing constants +use ir_lib::ir; +// Rate extension trait for .Hz() baud rate construction +use fugit::RateExtU32; +// Clock trait for accessing system clock frequency +use hal::Clock; +// GPIO pin types and function selectors +use hal::gpio::{FunctionNull, FunctionUart, Pin, PullDown, PullNone}; +// UART configuration and peripheral types +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Timer device type for the HAL timer peripheral. +#[cfg(rp2350)] +pub(crate) type HalTimer = hal::Timer; +/// Timer type alias for RP2040 (non-generic). +#[cfg(rp2040)] +pub(crate) type HalTimer = hal::Timer; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// GPIO pin number connected to the IR receiver output. +pub(crate) const IR_GPIO: u8 = 5; + +/// Delay between decode attempts in milliseconds. +pub(crate) const POLL_MS: u32 = 10; + +/// Type alias for the configured TX pin (GPIO 0, UART function, no pull). +pub(crate) type TxPin = Pin; + +/// Type alias for the configured RX pin (GPIO 1, UART function, no pull). +pub(crate) type RxPin = Pin; + +/// Type alias for the default TX pin state from `Pins::new()`. +pub(crate) type TxPinDefault = Pin; + +/// Type alias for the default RX pin state from `Pins::new()`. +pub(crate) type RxPinDefault = Pin; + +/// Type alias for the fully-enabled UART0 peripheral with TX/RX p. +pub(crate) type EnabledUart = UartPeripheral; + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +pub(crate) fn init_clocks( + xosc: hal::pac::XOSC, clocks: hal::pac::CLOCKS, pll_sys: hal::pac::PLL_SYS, pll_usb: hal::pac::PLL_USB, + resets: &mut hal::pac::RESETS, watchdog: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls(XTAL_FREQ_HZ, xosc, clocks, pll_sys, pll_usb, resets, watchdog).unwrap() +} + +/// Unlock the GPIO bank and return the pin set. +pub(crate) fn init_pins( + io_bank0: hal::pac::IO_BANK0, + pads_bank0: hal::pac::PADS_BANK0, + sio: hal::pac::SIO, + resets: &mut hal::pac::RESETS, +) -> hal::gpio::Pins { + let sio = hal::Sio::new(sio); + hal::gpio::Pins::new(io_bank0, pads_bank0, sio.gpio_bank0, resets) +} + +/// Initialise UART0 for serial output. +pub(crate) fn init_uart( + uart0: hal::pac::UART0, tx: TxPinDefault, rx: RxPinDefault, rst: &mut hal::pac::RESETS, c: &hal::clocks::ClocksManager +) -> EnabledUart { + let pins = (tx.reconfigure::(), rx.reconfigure::()); + let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One); + UartPeripheral::new(uart0, pins, rst).enable(cfg, c.peripheral_clock.freq()).unwrap() +} + +/// Create a blocking delay timer from the ARM SysTick peripheral. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +pub(crate) fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay { + let core = cortex_m::Peripherals::take().unwrap(); + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()) +} + +/// Read the free-running microsecond timer (lower 32 bits). +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// A 32-bit unsigned integer value. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// A 32-bit unsigned integer value. +fn time_us_32(timer: &HalTimer) -> u32 { + timer.get_counter().ticks() as u32 +} + +/// Read the current logic level of the IR input pin through the SIO block. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +fn gpio_read() -> bool { + unsafe { (*hal::pac::SIO::PTR).gpio_in().read().bits() & (1u32 << IR_GPIO) != 0 } +} + +/// Wait for the IR pin to reach the requested level or time out. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// * `level` - The `level` parameter. +/// * `timeout_us` - The `timeout_us` parameter. +/// +/// # Returns +/// +/// A value of type `Option`. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// * `level` - The `level` parameter. +/// * `timeout_us` - The `timeout_us` parameter. +/// +/// # Returns +/// +/// An Optional value. +fn wait_for_level(timer: &HalTimer, level: bool, timeout_us: u32) -> Option { + let start = time_us_32(timer); + while gpio_read() != level { + let elapsed = time_us_32(timer).wrapping_sub(start) as i64; + if elapsed > timeout_us as i64 { return None; } + } + Some(time_us_32(timer).wrapping_sub(start) as i64) +} + +/// Wait for the IR receiver to go idle (LOW). +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +fn wait_for_idle(timer: &HalTimer) -> bool { + wait_for_level(timer, false, ir::LEADER_START_TIMEOUT_US).is_some() +} + +/// Validate the NEC leader mark pulse width. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +fn validate_leader_mark(timer: &HalTimer) -> bool { + let Some(w) = wait_for_level(timer, true, ir::LEADER_MARK_TIMEOUT_US) else { return false; }; + ir::is_valid_leader_mark(w) +} + +/// Validate the NEC leader space width. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +fn validate_leader_space(timer: &HalTimer) -> bool { + let Some(w) = wait_for_level(timer, false, ir::LEADER_SPACE_TIMEOUT_US) else { return false; }; + ir::is_valid_leader_space(w) +} + +/// Wait for the NEC leader burst and space. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +fn wait_leader(timer: &HalTimer) -> bool { + wait_for_idle(timer) && validate_leader_mark(timer) && validate_leader_space(timer) +} + +/// Wait for the bit mark and measure the bit space width. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// A value of type `Option`. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// An Optional value. +fn measure_bit_space(timer: &HalTimer) -> Option { + if wait_for_level(timer, true, ir::BIT_MARK_TIMEOUT_US).is_none() { return None; } + let w = wait_for_level(timer, false, ir::BIT_SPACE_TIMEOUT_US)?; + if !ir::is_valid_bit_space(w) { return None; } + Some(w) +} + +/// Read one NEC bit and store it in the frame buffer. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// * `data` - Data to send/write. +/// * `bit_index` - The `bit_index` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// * `data` - Data to send/write. +/// * `bit_index` - The `bit_index` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +fn read_nec_bit(timer: &HalTimer, data: &mut [u8; 4], bit_index: usize) -> bool { + let Some(space_width) = measure_bit_space(timer) else { return false; }; + ir::accumulate_nec_bit(data, bit_index, space_width); true +} + +/// Read a full 32-bit NEC frame. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// * `data` - Data to send/write. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// * `data` - Data to send/write. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +fn read_32_bits(timer: &HalTimer, data: &mut [u8; 4]) -> bool { + let mut bit_index = 0usize; + while bit_index < ir::FRAME_BITS { + if !read_nec_bit(timer, data, bit_index) { return false; } + bit_index += 1; + } + true +} + +/// Block until an NEC key is decoded or return `None` on failure. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// A value of type `Option`. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// +/// # Returns +/// +/// An Optional value. +pub(crate) fn ir_getkey(timer: &HalTimer) -> Option { + if !wait_leader(timer) { return None; } + let mut data = [0u8; 4]; + if !read_32_bits(timer, &mut data) { return None; } + ir::validate_nec_frame(&data) +} + +/// Poll the decoder and print the key code when a valid frame is received. +pub(crate) fn poll_receiver(u: &EnabledUart, t: &HalTimer, d: &mut cortex_m::delay::Delay) { + let mut buf = [0u8; 26]; + if let Some(command) = ir_getkey(t) { + let len = ir::format_command(&mut buf, command); u.write_full_blocking(&buf[..len]); + } + d.delay_ms(POLL_MS); +} + +/// Initialise all peripherals and run the NEC IR receiver demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `pac` - The `pac` parameter. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! { + let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut hal::Watchdog::new(p.WATCHDOG)); + let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS); + let (u, mut d) = (init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c), init_delay(&c)); + #[cfg(rp2350)] let t = hal::Timer::new_timer0(p.TIMER0, &mut p.RESETS, &c); #[cfg(rp2040)] let t = hal::Timer::new(p.TIMER, &mut p.RESETS); + let _ = pins.gpio5.into_pull_up_input(); announce_ir(&u); + loop { poll_receiver(&u, &t, &mut d); } +} + +/// Print the IR driver initialisation banner over UART. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +fn announce_ir(uart: &EnabledUart) { + uart.write_full_blocking(b"NEC IR driver initialized on GPIO 5\r\n"); + uart.write_full_blocking(b"Press a button on your NEC remote...\r\n"); +} + diff --git a/drivers/0x0a_ir_rust/src/ir.rs b/drivers/0x0a_ir_rust/src/ir.rs new file mode 100644 index 0000000..83b2104 --- /dev/null +++ b/drivers/0x0a_ir_rust/src/ir.rs @@ -0,0 +1,443 @@ +//! Implementation module +//! +//! **File:** `ir.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +/// Leader wait timeout in microseconds. +pub const LEADER_START_TIMEOUT_US: u32 = 150_000; + +/// Maximum duration accepted for the NEC leader mark wait. +pub const LEADER_MARK_TIMEOUT_US: u32 = 12_000; + +/// Minimum valid NEC leader mark width in microseconds. +pub const LEADER_MARK_MIN_US: i64 = 8_000; + +/// Maximum valid NEC leader mark width in microseconds. +pub const LEADER_MARK_MAX_US: i64 = 10_000; + +/// Maximum duration accepted for the NEC leader space wait. +pub const LEADER_SPACE_TIMEOUT_US: u32 = 7_000; + +/// Minimum valid NEC leader space width in microseconds. +pub const LEADER_SPACE_MIN_US: i64 = 3_500; + +/// Maximum valid NEC leader space width in microseconds. +pub const LEADER_SPACE_MAX_US: i64 = 5_000; + +/// Maximum duration accepted while waiting for the bit mark to end. +pub const BIT_MARK_TIMEOUT_US: u32 = 1_000; + +/// Maximum duration accepted while measuring the data space. +pub const BIT_SPACE_TIMEOUT_US: u32 = 2_500; + +/// Minimum valid data space width in microseconds. +pub const BIT_SPACE_MIN_US: i64 = 200; + +/// Space width above which a NEC bit is interpreted as logical 1. +pub const BIT_ONE_THRESHOLD_US: i64 = 1_200; + +/// Total number of bits in an NEC frame. +pub const FRAME_BITS: usize = 32; + +/// Return true if the measured NEC leader mark width is valid. +/// +/// # Arguments +/// +/// * `duration_us` - The `duration_us` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +/// +/// # Arguments +/// +/// * `duration_us` - The `duration_us` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +#[inline] +pub fn is_valid_leader_mark(duration_us: i64) -> bool { + (LEADER_MARK_MIN_US..=LEADER_MARK_MAX_US).contains(&duration_us) +} + +/// Return true if the measured NEC leader space width is valid. +/// +/// # Arguments +/// +/// * `duration_us` - The `duration_us` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +/// +/// # Arguments +/// +/// * `duration_us` - The `duration_us` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +#[inline] +pub fn is_valid_leader_space(duration_us: i64) -> bool { + (LEADER_SPACE_MIN_US..=LEADER_SPACE_MAX_US).contains(&duration_us) +} + +/// Return true if the measured NEC bit space width is valid. +/// +/// # Arguments +/// +/// * `duration_us` - The `duration_us` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +/// +/// # Arguments +/// +/// * `duration_us` - The `duration_us` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +#[inline] +pub fn is_valid_bit_space(duration_us: i64) -> bool { duration_us >= BIT_SPACE_MIN_US } + +/// Accumulate a single NEC bit into the 4-byte frame buffer. +/// +/// Matches the C implementation exactly: bytes are filled LSB-first. +/// +/// # Arguments +/// +/// * `data` - Data to send/write. +/// * `bit_index` - The `bit_index` parameter. +/// * `duration_us` - The `duration_us` parameter. +/// +/// # Arguments +/// +/// * `data` - Data to send/write. +/// * `bit_index` - The `bit_index` parameter. +/// * `duration_us` - The `duration_us` parameter. +#[inline] +pub fn accumulate_nec_bit(data: &mut [u8; 4], bit_index: usize, duration_us: i64) { + let (byte_idx, bit_idx) = (bit_index / 8, bit_index % 8); + if duration_us > BIT_ONE_THRESHOLD_US { data[byte_idx] |= 1u8 << bit_idx; } +} + +/// Validate an NEC frame and return the command byte on success. +/// +/// # Arguments +/// +/// * `data` - Data to send/write. +/// +/// # Returns +/// +/// A value of type `Option`. +/// +/// # Arguments +/// +/// * `data` - Data to send/write. +/// +/// # Returns +/// +/// An Optional value. +#[inline] +pub fn validate_nec_frame(data: &[u8; 4]) -> Option { + if data[0].wrapping_add(data[1]) == 0xFF && data[2].wrapping_add(data[3]) == 0xFF { Some(data[2]) } else { None } +} + +/// Format the decoded command as hexadecimal and decimal followed by CRLF. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `command` - The `command` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `command` - The `command` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_command(buf: &mut [u8], command: u8) -> usize { + let mut pos = copy_slice(buf, 0, b"NEC command: 0x"); pos += format_hex_u8(buf, pos, command); + pos += copy_slice(buf, pos, b" ("); pos += format_u8(buf, pos, command); + pos + copy_slice(buf, pos, b")\r\n") +} + +/// Copy a byte slice into `buf` at the given offset, returning bytes written. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `offset` - The `offset` parameter. +/// * `src` - The `src` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `offset` - The `offset` parameter. +/// * `src` - The `src` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn copy_slice(buf: &mut [u8], offset: usize, src: &[u8]) -> usize { + buf[offset..offset + src.len()].copy_from_slice(src); src.len() +} + +/// Format an unsigned 8-bit integer at the given buffer offset. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `pos` - The `pos` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `pos` - The `pos` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn format_u8(buf: &mut [u8], pos: usize, value: u8) -> usize { + let n = u8_digit_count(value); write_u8_digits(buf, pos, value, n); n +} + +/// Return the number of decimal digits in a u8. +/// +/// # Arguments +/// +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn u8_digit_count(value: u8) -> usize { + if value >= 100 { 3 } else if value >= 10 { 2 } else { 1 } +} + +/// Write the decimal digits of a u8 into `buf` at `pos`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `pos` - The `pos` parameter. +/// * `value` - Value to use. +/// * `n` - Nibble or number. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `pos` - The `pos` parameter. +/// * `value` - Value to use. +/// * `n` - Nibble or number. +#[inline] +fn write_u8_digits(buf: &mut [u8], pos: usize, value: u8, n: usize) { + if n >= 3 { buf[pos] = b'0' + value / 100; } + if n >= 2 { buf[pos + n - 2] = b'0' + (value / 10) % 10; } + buf[pos + n - 1] = b'0' + value % 10; +} + +/// Format an unsigned 8-bit integer as two uppercase hexadecimal digits. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `pos` - The `pos` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `pos` - The `pos` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn format_hex_u8(buf: &mut [u8], pos: usize, value: u8) -> usize { + buf[pos] = hex_digit((value >> 4) & 0x0F); buf[pos + 1] = hex_digit(value & 0x0F); 2 +} + +/// Convert a 4-bit value to its uppercase ASCII hex digit. +/// +/// # Arguments +/// +/// * `value` - Value to use. +/// +/// # Returns +/// +/// An 8-bit unsigned integer value. +/// +/// # Arguments +/// +/// * `value` - Value to use. +/// +/// # Returns +/// +/// An 8-bit unsigned integer value. +#[inline] +fn hex_digit(value: u8) -> u8 { + if value < 10 { b'0' + value } else { b'A' + (value - 10) } +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + + /// Executes the leader mark accepts lower bound operation. + #[test] + fn leader_mark_accepts_lower_bound() { + assert!(is_valid_leader_mark(8_000)); + } + + /// Executes the leader mark rejects below lower bound operation. + #[test] + fn leader_mark_rejects_below_lower_bound() { + assert!(!is_valid_leader_mark(7_999)); + } + + /// Executes the leader space accepts upper bound operation. + #[test] + fn leader_space_accepts_upper_bound() { + assert!(is_valid_leader_space(5_000)); + } + + /// Executes the leader space rejects above upper bound operation. + #[test] + fn leader_space_rejects_above_upper_bound() { + assert!(!is_valid_leader_space(5_001)); + } + + /// Executes the bit space rejects short pulse operation. + #[test] + fn bit_space_rejects_short_pulse() { + assert!(!is_valid_bit_space(199)); + } + + /// Executes the bit space accepts threshold operation. + #[test] + fn bit_space_accepts_threshold() { + assert!(is_valid_bit_space(200)); + } + + /// Executes the accumulate zero bit leaves byte clear operation. + #[test] + fn accumulate_zero_bit_leaves_byte_clear() { + let mut data = [0u8; 4]; + accumulate_nec_bit(&mut data, 0, 800); + assert_eq!(data[0], 0); + } + + /// Executes the accumulate one bit sets lsb operation. + #[test] + fn accumulate_one_bit_sets_lsb() { + let mut data = [0u8; 4]; + accumulate_nec_bit(&mut data, 0, 1_300); + assert_eq!(data[0], 1); + } + + /// Executes the accumulate crosses into next byte operation. + #[test] + fn accumulate_crosses_into_next_byte() { + let mut data = [0u8; 4]; + accumulate_nec_bit(&mut data, 8, 1_300); + assert_eq!(data[0], 0); + assert_eq!(data[1], 1); + } + + /// Executes the validate frame returns command operation. + #[test] + fn validate_frame_returns_command() { + let data = [0x00, 0xFF, 0x45, 0xBA]; + assert_eq!(validate_nec_frame(&data), Some(0x45)); + } + + /// Executes the validate frame rejects bad inverse operation. + #[test] + fn validate_frame_rejects_bad_inverse() { + let data = [0x00, 0xFE, 0x45, 0xBA]; + assert_eq!(validate_nec_frame(&data), None); + } + + /// Executes the format command single digit operation. + #[test] + fn format_command_single_digit() { + let mut buf = [0u8; 24]; + let n = format_command(&mut buf, 7); + assert_eq!(&buf[..n], b"NEC command: 0x07 (7)\r\n"); + } + + /// Executes the format command three digits operation. + #[test] + fn format_command_three_digits() { + let mut buf = [0u8; 26]; + let n = format_command(&mut buf, 255); + assert_eq!(&buf[..n], b"NEC command: 0xFF (255)\r\n"); + } + + /// Executes the format hex digit alpha operation. + #[test] + fn format_hex_digit_alpha() { + assert_eq!(hex_digit(0x0A), b'A'); + } +} diff --git a/drivers/0x0a_ir_rust/src/lib.rs b/drivers/0x0a_ir_rust/src/lib.rs new file mode 100644 index 0000000..1438f2e --- /dev/null +++ b/drivers/0x0a_ir_rust/src/lib.rs @@ -0,0 +1,6 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +#![cfg_attr(not(test), no_std)] +pub mod ir; diff --git a/drivers/0x0a_ir_rust/src/main.rs b/drivers/0x0a_ir_rust/src/main.rs new file mode 100644 index 0000000..98f51a3 --- /dev/null +++ b/drivers/0x0a_ir_rust/src/main.rs @@ -0,0 +1,102 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; +// IR driver module — suppress warnings for unused public API functions + +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the NEC IR receiver demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"NEC IR Receiver Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/drivers/0x0b_spi_rust/.cargo/config.toml b/drivers/0x0b_spi_rust/.cargo/config.toml new file mode 100644 index 0000000..63db92a --- /dev/null +++ b/drivers/0x0b_spi_rust/.cargo/config.toml @@ -0,0 +1,32 @@ +[build] +target = "thumbv8m.main-none-eabihf" + +[target.thumbv6m-none-eabi] +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[target.thumbv8m.main-none-eabihf] +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[target.riscv32imac-unknown-none-elf] +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp2350_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[env] +DEFMT_LOG = "debug" diff --git a/drivers/0x0b_spi_rust/.pico-rs b/drivers/0x0b_spi_rust/.pico-rs new file mode 100644 index 0000000..be06904 --- /dev/null +++ b/drivers/0x0b_spi_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 diff --git a/drivers/0x0b_spi_rust/.vscode/extensions.json b/drivers/0x0b_spi_rust/.vscode/extensions.json new file mode 100644 index 0000000..5f2fd0c --- /dev/null +++ b/drivers/0x0b_spi_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/drivers/0x0b_spi_rust/.vscode/launch.json b/drivers/0x0b_spi_rust/.vscode/launch.json new file mode 100644 index 0000000..0bc38c3 --- /dev/null +++ b/drivers/0x0b_spi_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} \ No newline at end of file diff --git a/drivers/0x0b_spi_rust/.vscode/settings.json b/drivers/0x0b_spi_rust/.vscode/settings.json new file mode 100644 index 0000000..b03cd57 --- /dev/null +++ b/drivers/0x0b_spi_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} \ No newline at end of file diff --git a/drivers/0x0b_spi_rust/.vscode/tasks.json b/drivers/0x0b_spi_rust/.vscode/tasks.json new file mode 100644 index 0000000..d288f27 --- /dev/null +++ b/drivers/0x0b_spi_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} diff --git a/drivers/0x0b_spi_rust/Cargo.lock b/drivers/0x0b_spi_rust/Cargo.lock new file mode 100644 index 0000000..0fb76aa --- /dev/null +++ b/drivers/0x0b_spi_rust/Cargo.lock @@ -0,0 +1,705 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield 0.13.2", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "crc-any" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62ec9ff5f7965e4d7280bd5482acd20aadb50d632cf6c1d74493856b011fa73" +dependencies = [ + "debug-helper", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "debug-helper" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" +dependencies = [ + "critical-section", + "defmt", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "frunk" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28aef0f9aa070bce60767c12ba9cb41efeaf1a2bc6427f87b7d83f11239a16d7" +dependencies = [ + "frunk_core", + "frunk_derives", +] + +[[package]] +name = "frunk_core" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476eeaa382e3462b84da5d6ba3da97b5786823c2d0d3a0d04ef088d073da225c" + +[[package]] +name = "frunk_derives" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b4095fc99e1d858e5b8c7125d2638372ec85aa0fe6c807105cf10b0265ca6c" +dependencies = [ + "frunk_proc_macro_helpers", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "frunk_proc_macro_helpers" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1952b802269f2db12ab7c0bd328d0ae8feaabf19f352a7b0af7bb0c5693abfce" +dependencies = [ + "frunk_core", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fugit" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e639847d312d9a82d2e75b0edcc1e934efcc64e6cb7aa94f0b1fbec0bc231d6" +dependencies = [ + "gcd", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "panic-halt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11" + +[[package]] +name = "panic-probe" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" +dependencies = [ + "cortex-m", + "defmt", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" +dependencies = [ + "arrayvec", + "num_enum", + "paste", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "riscv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f5c1b8bf41ea746266cdee443d1d1e9125c86ce1447e1a2615abd34330d33a9" +dependencies = [ + "critical-section", + "embedded-hal 1.0.0", +] + +[[package]] +name = "riscv-rt" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d35e32cf1383183e8885d8a9aa4402a087fd094dc34c2cb6df6687d0229dfe" +dependencies = [ + "riscv", + "riscv-rt-macros", +] + +[[package]] +name = "riscv-rt-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30f19a85fe107b65031e0ba8ec60c34c2494069fe910d6c297f5e7cb5a6f76d0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rp-binary-info" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f582261945fa215d40e2988b4df595d11c0908c0fff97a0fe23df766d117b790" + +[[package]] +name = "rp-hal-common" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8288358786b1458fb2caac8c4b40fb529ef4200d6c46467e2695b7a8ba573ae8" +dependencies = [ + "fugit", +] + +[[package]] +name = "rp2040-boot2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" +dependencies = [ + "crc-any", +] + +[[package]] +name = "rp2040-hal" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb79a4590775204387f334672e6f79c0d734d0a159da23d60677b3c10fa1245" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "itertools 0.10.5", + "nb 1.1.0", + "paste", + "pio", + "rand_core", + "rp-binary-info", + "rp-hal-common", + "rp2040-hal-macros", + "rp2040-pac", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp2040-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86479063e497efe1ae81995ef9071f54fd1c7427e04d6c5b84cde545ff672a5e" +dependencies = [ + "cortex-m-rt", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rp2040-pac" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83cbcd3f7a0ca7bbe61dc4eb7e202842bee4e27b769a7bf3a4a72fa399d6e404" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "vcell", +] + +[[package]] +name = "rp235x-hal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2939c82776b0b4ae110168b4298b5adf831e6cff249b057bf2a2187453b959c" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "cortex-m-rt", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "gcd", + "itertools 0.13.0", + "nb 1.1.0", + "paste", + "pio", + "rand_core", + "riscv", + "riscv-rt", + "rp-binary-info", + "rp-hal-common", + "rp235x-hal-macros", + "rp235x-pac", + "sha2-const-stable", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp235x-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74edd7a5979e9763bbb98e9746e711bac7464ee3397af7288e6c288ff0d3c764" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rp235x-pac" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffcb6931deee4242886b5a1df62db5e2555b0eb6ae1e8be101f3ea3e58e65c6" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "vcell", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "spi" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt", + "defmt-rtt", + "embedded-hal 1.0.0", + "fugit", + "panic-halt", + "panic-probe", + "rp2040-boot2", + "rp2040-hal", + "rp235x-hal", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] diff --git a/drivers/0x0b_spi_rust/Cargo.toml b/drivers/0x0b_spi_rust/Cargo.toml new file mode 100644 index 0000000..433dbcb --- /dev/null +++ b/drivers/0x0b_spi_rust/Cargo.toml @@ -0,0 +1,80 @@ +[package] +edition = "2024" +name = "spi" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "spi_lib" +path = "src/lib.rs" + +[[bin]] +name = "spi" +path = "src/main.rs" + + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" +embedded-hal = "1.0" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 3 diff --git a/drivers/0x0b_spi_rust/README.md b/drivers/0x0b_spi_rust/README.md new file mode 100644 index 0000000..a85d638 --- /dev/null +++ b/drivers/0x0b_spi_rust/README.md @@ -0,0 +1,111 @@ +# 0x0b SPI Rust Driver + +This repository contains a Bare-Metal Rust driver for the **SPI (Serial Peripheral Interface)** bus on the **RP2350** (and RP2040) microcontrollers. + +It includes: +- A demo (`src/main.rs`) that executes an SPI loopback transfer (MOSI wired to MISO) and logs the results over UART. +- A reusable library module (`src/spi.rs`) providing a hardware-agnostic `spi_lib` containing formatting helpers for UART logging. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the string formatting logic is separated into a reusable math library without touching hardware registers, you can run the unit tests natively on your computer! + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we initialize the system clocks, pins, UART (for logging), SysTick (for delay), and the SPI peripheral. + +* **`run(...)`**: The master setup function. Calls the helper initialization functions below, prints an initialization banner over UART, and enters the infinite loop executing SPI transfers. +* **`init_spi(...)`**: Configures the hardware `SPI0` peripheral. Reconfigures MISO, MOSI, and SCK for SPI function, and configures CS as a software-controlled SIO output. Sets the SPI speed to 1 MHz in Mode 0 (CPOL=0, CPHA=0). +* **`loopback_transfer(...)`**: Called repeatedly in the main loop. It allocates an RX buffer, calls `execute_transfer()`, formats the TX and RX buffers as ASCII strings, and streams them over UART so you can verify the loopback on your console. +* **`execute_transfer(...)`**: The core hardware interaction. Asserts chip select (low), blocks while executing a full-duplex transfer using the `embedded-hal` SPI trait (`spi_dev.transfer()`), and de-asserts chip select (high). + +### 3. The Reusable SPI Library (`src/spi.rs`) +While `board.rs` handles the hardware registers, `spi.rs` abstracts away the string formatting logic required to cleanly log the SPI transfers over UART without requiring dynamic memory allocation. + +* **`clear_rx_buffer(...)`**: Clears the receive buffer before the next loopback transfer to ensure no stale data remains. +* **`format_tx_line(...)` / `format_rx_line(...)`**: Formats the byte arrays into `TX: ...` and `RX: ...` strings terminating in CRLF. +* **`copy_c_string(...)`**: A safe helper that copies bytes up to the first NUL terminator, allowing us to safely log C-style strings over UART. diff --git a/drivers/0x0b_spi_rust/build.rs b/drivers/0x0b_spi_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x0b_spi_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x0b_spi_rust/rp2040.x b/drivers/0x0b_spi_rust/rp2040.x new file mode 100644 index 0000000..e66f718 --- /dev/null +++ b/drivers/0x0b_spi_rust/rp2040.x @@ -0,0 +1,44 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + .bi_entries : ALIGN(4) + { + __bi_entries_start = .; + KEEP(*(.bi_entries)); + . = ALIGN(4); + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; diff --git a/drivers/0x0b_spi_rust/rp2350.x b/drivers/0x0b_spi_rust/rp2350.x new file mode 100644 index 0000000..570f72c --- /dev/null +++ b/drivers/0x0b_spi_rust/rp2350.x @@ -0,0 +1,47 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + RAM : ORIGIN = 0x20000000, LENGTH = 512K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + .bi_entries : ALIGN(4) + { + __bi_entries_start = .; + KEEP(*(.bi_entries)); + . = ALIGN(4); + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); diff --git a/drivers/0x0b_spi_rust/rp2350_riscv.x b/drivers/0x0b_spi_rust/rp2350_riscv.x new file mode 100644 index 0000000..2c9b445 --- /dev/null +++ b/drivers/0x0b_spi_rust/rp2350_riscv.x @@ -0,0 +1,58 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + RAM : ORIGIN = 0x20000000, LENGTH = 512K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +PROVIDE(__pre_init = default_pre_init); +PROVIDE(_setup_interrupts = default_setup_interrupts); +PROVIDE(_mp_hook = default_mp_hook); +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + . = ABSOLUTE(_stext); + } > FLASH +} diff --git a/drivers/0x0b_spi_rust/src/board.rs b/drivers/0x0b_spi_rust/src/board.rs new file mode 100644 index 0000000..ce6f7f0 --- /dev/null +++ b/drivers/0x0b_spi_rust/src/board.rs @@ -0,0 +1,288 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// SPI pure-logic helpers and constants +use spi_lib::spi; +// Digital output trait for software-controlled chip select +use embedded_hal::digital::OutputPin; +// SPI bus trait for full-duplex transfer +use embedded_hal::spi::SpiBus; +// Rate extension trait for .Hz() baud rate construction +use fugit::RateExtU32; +// Clock trait for accessing system clock frequency +use hal::Clock; +// GPIO pin types and function selectors +use hal::gpio::{FunctionNull, FunctionSio, FunctionSpi, FunctionUart, Pin, PullDown, PullNone}; +// SPI peripheral type +use hal::spi::Spi; +// UART configuration and peripheral types +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// Delay between loopback transfers in milliseconds. +pub(crate) const POLL_MS: u32 = 1_000; + +/// Type alias for the configured TX pin (GPIO 0, UART function, no pull). +pub(crate) type TxPin = Pin; + +/// Type alias for the configured RX pin (GPIO 1, UART function, no pull). +pub(crate) type RxPin = Pin; + +/// Type alias for the default TX pin state from `Pins::new()`. +pub(crate) type TxPinDefault = Pin; + +/// Type alias for the default RX pin state from `Pins::new()`. +pub(crate) type RxPinDefault = Pin; + +/// Type alias for the configured SPI RX pin (GPIO16). +pub(crate) type MisoPin = Pin; + +/// Type alias for the configured SPI chip-select pin (GPIO17). +pub(crate) type CsPin = Pin, PullNone>; + +/// Type alias for the configured SPI clock pin (GPIO18). +pub(crate) type SckPin = Pin; + +/// Type alias for the configured SPI TX pin (GPIO19). +pub(crate) type MosiPin = Pin; + +/// Type alias for the fully-enabled UART0 peripheral with TX/RX p. +pub(crate) type EnabledUart = UartPeripheral; + +/// Type alias for the SPI0 peripheral configured in 8-bit master mode. +pub(crate) type EnabledSpi = Spi; + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +pub(crate) fn init_clocks( + xosc: hal::pac::XOSC, clocks: hal::pac::CLOCKS, pll_sys: hal::pac::PLL_SYS, pll_usb: hal::pac::PLL_USB, + resets: &mut hal::pac::RESETS, watchdog: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls(XTAL_FREQ_HZ, xosc, clocks, pll_sys, pll_usb, resets, watchdog).unwrap() +} + +/// Unlock the GPIO bank and return the pin set. +pub(crate) fn init_pins( + io: hal::pac::IO_BANK0, pads: hal::pac::PADS_BANK0, sio: hal::pac::SIO, rst: &mut hal::pac::RESETS +) -> hal::gpio::Pins { + hal::gpio::Pins::new(io, pads, hal::Sio::new(sio).gpio_bank0, rst) +} + +/// Initialise UART0 for serial output. +pub(crate) fn init_uart( + uart0: hal::pac::UART0, tx: TxPinDefault, rx: RxPinDefault, rst: &mut hal::pac::RESETS, c: &hal::clocks::ClocksManager +) -> EnabledUart { + let pins = (tx.reconfigure::(), rx.reconfigure::()); + let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One); + UartPeripheral::new(uart0, pins, rst).enable(cfg, c.peripheral_clock.freq()).unwrap() +} + +/// Create a blocking delay timer from the ARM SysTick peripheral. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +pub(crate) fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay { + let core = cortex_m::Peripherals::take().unwrap(); + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()) +} + +/// Executes the reconfigure miso, sck, and mosi pins from default to spi operation. +fn reconfigure_data_pins( + miso: hal::gpio::Pin, + sck: hal::gpio::Pin, + mosi: hal::gpio::Pin, +) -> (MosiPin, MisoPin, SckPin) { + (mosi.reconfigure::(), miso.reconfigure::(), sck.reconfigure::()) +} + +/// Reconfigure a pin as digital output and de-assert chip select (drive high). +/// +/// # Arguments +/// +/// * `cs` - The `cs` parameter. +/// * `FunctionNull` - The `FunctionNull` parameter. +/// * `PullDown>` - The `PullDown>` parameter. +/// +/// # Returns +/// +/// A value of type `CsPin`. +/// +/// # Arguments +/// +/// * `cs` - The `cs` parameter. +/// * `FunctionNull` - The `FunctionNull` parameter. +/// * `PullDown>` - The `PullDown>` parameter. +/// +/// # Returns +/// +/// A value of type `CsPin`. +fn init_cs_pin(cs: hal::gpio::Pin) -> CsPin { + let mut cs = cs.reconfigure::, PullNone>(); + let _ = cs.set_high(); cs +} + +/// Configure SPI0 and the software-controlled chip-select pin. +pub(crate) fn init_spi( + spi0: hal::pac::SPI0, miso: hal::gpio::Pin, + cs: hal::gpio::Pin, sck: hal::gpio::Pin, + mosi: hal::gpio::Pin, + resets: &mut hal::pac::RESETS, clocks: &hal::clocks::ClocksManager, +) -> (EnabledSpi, CsPin) { + let (mosi, miso, sck) = reconfigure_data_pins(miso, sck, mosi); + let s = Spi::<_, _, _, 8>::new(spi0, (mosi, miso, sck)).init(resets, clocks.peripheral_clock.freq(), spi::SPI_BAUD_HZ.Hz(), embedded_hal::spi::MODE_0); + (s, init_cs_pin(cs)) +} + +/// Drive chip select active (low). +/// +/// # Arguments +/// +/// * `cs` - The `cs` parameter. +/// +/// # Arguments +/// +/// * `cs` - The `cs` parameter. +fn cs_select(cs: &mut CsPin) { + let _ = cs.set_low(); +} + +/// Drive chip select inactive (high). +/// +/// # Arguments +/// +/// * `cs` - The `cs` parameter. +/// +/// # Arguments +/// +/// * `cs` - The `cs` parameter. +fn cs_deselect(cs: &mut CsPin) { + let _ = cs.set_high(); +} + +/// Execute a full-duplex SPI transfer with chip-select framing. +/// +/// # Arguments +/// +/// * `spi_dev` - The `spi_dev` parameter. +/// * `cs` - The `cs` parameter. +/// * `rx` - The `rx` parameter. +/// +/// # Arguments +/// +/// * `spi_dev` - The `spi_dev` parameter. +/// * `cs` - The `cs` parameter. +/// * `rx` - The `rx` parameter. +fn execute_transfer(spi_dev: &mut EnabledSpi, cs: &mut CsPin, rx: &mut [u8]) { + cs_select(cs); + let _ = spi_dev.transfer(rx, spi::TX_MESSAGE); + cs_deselect(cs); +} + +/// Format and print the TX and RX lines over UART. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +/// * `rx` - The `rx` parameter. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +/// * `rx` - The `rx` parameter. +fn print_transfer_result(uart: &EnabledUart, rx: &[u8]) { + let mut line_buf = [0u8; 24]; + let tx_len = spi::format_tx_line(&mut line_buf, spi::TX_MESSAGE); + uart.write_full_blocking(&line_buf[..tx_len]); + let rx_len = spi::format_rx_line(&mut line_buf, rx); + uart.write_full_blocking(&line_buf[..rx_len]); +} + +/// Perform one SPI loopback transfer and print TX/RX text over UART. +pub(crate) fn loopback_transfer( + spi_dev: &mut EnabledSpi, + cs: &mut CsPin, + uart: &EnabledUart, + delay: &mut cortex_m::delay::Delay, +) { + let mut rx = [0u8; spi::TX_MESSAGE.len()]; + execute_transfer(spi_dev, cs, &mut rx); + print_transfer_result(uart, &rx); + spi::clear_rx_buffer(&mut rx); + delay.delay_ms(POLL_MS); +} + +/// Initialise all peripherals and run the SPI loopback demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `pac` - The `pac` parameter. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! { + let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut hal::Watchdog::new(p.WATCHDOG)); + let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS); + let (u, mut d) = (init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c), init_delay(&c)); + let (mut s, mut cs) = init_spi(p.SPI0, pins.gpio16, pins.gpio17, pins.gpio18, pins.gpio19, &mut p.RESETS, &c); + u.write_full_blocking(b"SPI driver initialized on SPI0 at 1000000 Hz\r\n"); + loop { loopback_transfer(&mut s, &mut cs, &u, &mut d); } +} + diff --git a/drivers/0x0b_spi_rust/src/lib.rs b/drivers/0x0b_spi_rust/src/lib.rs new file mode 100644 index 0000000..40befff --- /dev/null +++ b/drivers/0x0b_spi_rust/src/lib.rs @@ -0,0 +1,6 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +#![cfg_attr(not(test), no_std)] +pub mod spi; diff --git a/drivers/0x0b_spi_rust/src/main.rs b/drivers/0x0b_spi_rust/src/main.rs new file mode 100644 index 0000000..a29e875 --- /dev/null +++ b/drivers/0x0b_spi_rust/src/main.rs @@ -0,0 +1,102 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; +// SPI driver module — suppress warnings for unused public API functions + +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the SPI loopback demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"SPI Loopback Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/drivers/0x0b_spi_rust/src/spi.rs b/drivers/0x0b_spi_rust/src/spi.rs new file mode 100644 index 0000000..0f3eded --- /dev/null +++ b/drivers/0x0b_spi_rust/src/spi.rs @@ -0,0 +1,304 @@ +//! Implementation module +//! +//! **File:** `spi.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +/// SPI port number used by the loopback demo. +pub const SPI_PORT: u8 = 0; + +/// SPI clock frequency used by the loopback demo. +pub const SPI_BAUD_HZ: u32 = 1_000_000; + +/// GPIO pin number for MISO. +pub const PIN_MISO: u8 = 16; + +/// GPIO pin number for chip select. +pub const PIN_CS: u8 = 17; + +/// GPIO pin number for SCK. +pub const PIN_SCK: u8 = 18; + +/// GPIO pin number for MOSI. +pub const PIN_MOSI: u8 = 19; + +/// Fixed transmit message used by the SPI loopback demo. +pub const TX_MESSAGE: &[u8; 16] = b"SPI loopback OK\0"; + +/// Return true when the selected SPI port is supported by the driver. +/// +/// # Arguments +/// +/// * `port` - The `port` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +/// +/// # Arguments +/// +/// * `port` - The `port` parameter. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +#[inline] +pub fn is_valid_port(port: u8) -> bool { + port <= 1 +} + +/// Clear a receive buffer to all zeros. +/// +/// # Arguments +/// +/// * `rx` - The `rx` parameter. +/// +/// # Arguments +/// +/// * `rx` - The `rx` parameter. +#[inline] +pub fn clear_rx_buffer(rx: &mut [u8]) { + rx.fill(0); +} + +/// Copy `src` into `dst` up to the minimum length of both slices. +/// +/// # Arguments +/// +/// * `src` - The `src` parameter. +/// * `dst` - The `dst` parameter. +/// +/// # Arguments +/// +/// * `src` - The `src` parameter. +/// * `dst` - The `dst` parameter. +#[inline] +pub fn copy_transfer_result(src: &[u8], dst: &mut [u8]) { + let len = core::cmp::min(src.len(), dst.len()); dst[..len].copy_from_slice(&src[..len]); +} + +/// Format the `TX:` line with trailing CRLF. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `tx` - The `tx` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `tx` - The `tx` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_tx_line(buf: &mut [u8], tx: &[u8]) -> usize { + format_line(buf, b"TX: ", tx, false) +} + +/// Format the `RX:` line with an extra blank line after it. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `rx` - The `rx` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `rx` - The `rx` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_rx_line(buf: &mut [u8], rx: &[u8]) -> usize { + format_line(buf, b"RX: ", rx, true) +} + +/// Format a single ASCII line for UART output. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `prefix` - The `prefix` parameter. +/// * `text` - The `text` parameter. +/// * `extra_blank_line` - The `extra_blank_line` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `prefix` - The `prefix` parameter. +/// * `text` - The `text` parameter. +/// * `extra_blank_line` - The `extra_blank_line` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn format_line(buf: &mut [u8], prefix: &[u8], text: &[u8], extra_blank_line: bool) -> usize { + let mut pos = 0; buf[pos..pos + prefix.len()].copy_from_slice(prefix); pos += prefix.len(); + pos += copy_c_string(&mut buf[pos..], text); pos += append_crlf(&mut buf[pos..], extra_blank_line); + pos +} + +/// Copy bytes from `text` up to the first NUL into `buf`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `text` - The `text` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `text` - The `text` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn copy_c_string(buf: &mut [u8], text: &[u8]) -> usize { + let len = c_string_len(text); buf[..len].copy_from_slice(&text[..len]); len +} + +/// Append CRLF (and optionally a second blank CRLF) to `buf`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `extra` - The `extra` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `extra` - The `extra` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn append_crlf(buf: &mut [u8], extra: bool) -> usize { + buf[0] = b'\r'; buf[1] = b'\n'; + if extra { buf[2] = b'\r'; buf[3] = b'\n'; 4 } else { 2 } +} + +/// Return the length up to the first NUL byte or full slice length. +/// +/// # Arguments +/// +/// * `text` - The `text` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `text` - The `text` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn c_string_len(text: &[u8]) -> usize { + let mut len = 0usize; + while len < text.len() && text[len] != 0 { len += 1; } + len +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + + /// Executes the valid ports are zero and one operation. + #[test] + fn valid_ports_are_zero_and_one() { + assert!(is_valid_port(0)); + assert!(is_valid_port(1)); + assert!(!is_valid_port(2)); + } + + /// Executes the clear rx buffer zeroes all bytes operation. + #[test] + fn clear_rx_buffer_zeroes_all_bytes() { + let mut rx = [1u8, 2, 3, 4]; + clear_rx_buffer(&mut rx); + assert_eq!(rx, [0u8; 4]); + } + + /// Executes the copy transfer result copies common prefix operation. + #[test] + fn copy_transfer_result_copies_common_prefix() { + let src = b"abcd"; + let mut dst = [0u8; 3]; + copy_transfer_result(src, &mut dst); + assert_eq!(&dst, b"abc"); + } + + /// Executes the format tx line omits trailing nul operation. + #[test] + fn format_tx_line_omits_trailing_nul() { + let mut buf = [0u8; 32]; + let n = format_tx_line(&mut buf, TX_MESSAGE); + assert_eq!(&buf[..n], b"TX: SPI loopback OK\r\n"); + } + + /// Executes the format rx line adds blank line operation. + #[test] + fn format_rx_line_adds_blank_line() { + let mut buf = [0u8; 32]; + let n = format_rx_line(&mut buf, TX_MESSAGE); + assert_eq!(&buf[..n], b"RX: SPI loopback OK\r\n\r\n"); + } + + /// Executes the c string len stops at nul operation. + #[test] + fn c_string_len_stops_at_nul() { + assert_eq!(c_string_len(b"abc\0xyz"), 3); + } +} diff --git a/drivers/0x0c_multicore_rust/.cargo/config.toml b/drivers/0x0c_multicore_rust/.cargo/config.toml new file mode 100644 index 0000000..18ef831 --- /dev/null +++ b/drivers/0x0c_multicore_rust/.cargo/config.toml @@ -0,0 +1,20 @@ +[build] +target = "thumbv8m.main-none-eabihf" + +[target.thumbv6m-none-eabi] +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[target.thumbv8m.main-none-eabihf] +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] diff --git a/drivers/0x0c_multicore_rust/.pico-rs b/drivers/0x0c_multicore_rust/.pico-rs new file mode 100644 index 0000000..be06904 --- /dev/null +++ b/drivers/0x0c_multicore_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 diff --git a/drivers/0x0c_multicore_rust/.vscode/extensions.json b/drivers/0x0c_multicore_rust/.vscode/extensions.json new file mode 100644 index 0000000..5f2fd0c --- /dev/null +++ b/drivers/0x0c_multicore_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} \ No newline at end of file diff --git a/drivers/0x0c_multicore_rust/.vscode/launch.json b/drivers/0x0c_multicore_rust/.vscode/launch.json new file mode 100644 index 0000000..0bc38c3 --- /dev/null +++ b/drivers/0x0c_multicore_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} \ No newline at end of file diff --git a/drivers/0x0c_multicore_rust/.vscode/settings.json b/drivers/0x0c_multicore_rust/.vscode/settings.json new file mode 100644 index 0000000..b03cd57 --- /dev/null +++ b/drivers/0x0c_multicore_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} \ No newline at end of file diff --git a/drivers/0x0c_multicore_rust/.vscode/tasks.json b/drivers/0x0c_multicore_rust/.vscode/tasks.json new file mode 100644 index 0000000..d288f27 --- /dev/null +++ b/drivers/0x0c_multicore_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} diff --git a/drivers/0x0c_multicore_rust/Cargo.lock b/drivers/0x0c_multicore_rust/Cargo.lock new file mode 100644 index 0000000..462ed9c --- /dev/null +++ b/drivers/0x0c_multicore_rust/Cargo.lock @@ -0,0 +1,705 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield 0.13.2", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "crc-any" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62ec9ff5f7965e4d7280bd5482acd20aadb50d632cf6c1d74493856b011fa73" +dependencies = [ + "debug-helper", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "debug-helper" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" +dependencies = [ + "critical-section", + "defmt", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "frunk" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28aef0f9aa070bce60767c12ba9cb41efeaf1a2bc6427f87b7d83f11239a16d7" +dependencies = [ + "frunk_core", + "frunk_derives", +] + +[[package]] +name = "frunk_core" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476eeaa382e3462b84da5d6ba3da97b5786823c2d0d3a0d04ef088d073da225c" + +[[package]] +name = "frunk_derives" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b4095fc99e1d858e5b8c7125d2638372ec85aa0fe6c807105cf10b0265ca6c" +dependencies = [ + "frunk_proc_macro_helpers", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "frunk_proc_macro_helpers" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1952b802269f2db12ab7c0bd328d0ae8feaabf19f352a7b0af7bb0c5693abfce" +dependencies = [ + "frunk_core", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fugit" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e639847d312d9a82d2e75b0edcc1e934efcc64e6cb7aa94f0b1fbec0bc231d6" +dependencies = [ + "gcd", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "multicore" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt", + "defmt-rtt", + "embedded-hal 1.0.0", + "fugit", + "panic-halt", + "panic-probe", + "rp2040-boot2", + "rp2040-hal", + "rp235x-hal", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "panic-halt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11" + +[[package]] +name = "panic-probe" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" +dependencies = [ + "cortex-m", + "defmt", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" +dependencies = [ + "arrayvec", + "num_enum", + "paste", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "riscv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f5c1b8bf41ea746266cdee443d1d1e9125c86ce1447e1a2615abd34330d33a9" +dependencies = [ + "critical-section", + "embedded-hal 1.0.0", +] + +[[package]] +name = "riscv-rt" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d35e32cf1383183e8885d8a9aa4402a087fd094dc34c2cb6df6687d0229dfe" +dependencies = [ + "riscv", + "riscv-rt-macros", +] + +[[package]] +name = "riscv-rt-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30f19a85fe107b65031e0ba8ec60c34c2494069fe910d6c297f5e7cb5a6f76d0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rp-binary-info" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f582261945fa215d40e2988b4df595d11c0908c0fff97a0fe23df766d117b790" + +[[package]] +name = "rp-hal-common" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8288358786b1458fb2caac8c4b40fb529ef4200d6c46467e2695b7a8ba573ae8" +dependencies = [ + "fugit", +] + +[[package]] +name = "rp2040-boot2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" +dependencies = [ + "crc-any", +] + +[[package]] +name = "rp2040-hal" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb79a4590775204387f334672e6f79c0d734d0a159da23d60677b3c10fa1245" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "itertools 0.10.5", + "nb 1.1.0", + "paste", + "pio", + "rand_core", + "rp-binary-info", + "rp-hal-common", + "rp2040-hal-macros", + "rp2040-pac", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp2040-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86479063e497efe1ae81995ef9071f54fd1c7427e04d6c5b84cde545ff672a5e" +dependencies = [ + "cortex-m-rt", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rp2040-pac" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83cbcd3f7a0ca7bbe61dc4eb7e202842bee4e27b769a7bf3a4a72fa399d6e404" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "vcell", +] + +[[package]] +name = "rp235x-hal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2939c82776b0b4ae110168b4298b5adf831e6cff249b057bf2a2187453b959c" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "cortex-m-rt", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "gcd", + "itertools 0.13.0", + "nb 1.1.0", + "paste", + "pio", + "rand_core", + "riscv", + "riscv-rt", + "rp-binary-info", + "rp-hal-common", + "rp235x-hal-macros", + "rp235x-pac", + "sha2-const-stable", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp235x-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74edd7a5979e9763bbb98e9746e711bac7464ee3397af7288e6c288ff0d3c764" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rp235x-pac" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffcb6931deee4242886b5a1df62db5e2555b0eb6ae1e8be101f3ea3e58e65c6" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "vcell", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] diff --git a/drivers/0x0c_multicore_rust/Cargo.toml b/drivers/0x0c_multicore_rust/Cargo.toml new file mode 100644 index 0000000..a8558bd --- /dev/null +++ b/drivers/0x0c_multicore_rust/Cargo.toml @@ -0,0 +1,80 @@ +[package] +edition = "2024" +name = "multicore" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "multicore_lib" +path = "src/lib.rs" + +[[bin]] +name = "multicore" +path = "src/main.rs" + + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" +embedded-hal = "1.0" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 3 diff --git a/drivers/0x0c_multicore_rust/README.md b/drivers/0x0c_multicore_rust/README.md new file mode 100644 index 0000000..2a206a5 --- /dev/null +++ b/drivers/0x0c_multicore_rust/README.md @@ -0,0 +1,111 @@ +# 0x0c Multicore Rust Driver + +This repository contains a Bare-Metal Rust driver demonstrating **Multicore Execution** using the hardware **SIO FIFO** for inter-core communication on the **RP2350** (and RP2040) microcontrollers. + +It includes: +- A demo (`src/main.rs`) where Core 0 sends a counter to Core 1, Core 1 increments it and returns it, and Core 0 prints the round-trip result over UART. +- A reusable library module (`src/multicore.rs`) providing a hardware-agnostic `multicore_lib` containing the logic and formatting helpers. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the data manipulation and string formatting logic is separated into a reusable library without touching hardware registers, you can run the unit tests natively on your computer! + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we initialize the system clocks, pins, UART (for logging), SysTick (for delay), and prepare the Multicore environment. + +* **`run(...)`**: The master setup function. Calls the helper initialization functions, spawns the secondary core, and enters an infinite loop executing FIFO round-trip communication. +* **`spawn_core1(...)`**: Boots up Core 1. It grabs a slice of the pre-allocated `CORE1_STACK` (4096 words), initializes the `Multicore` HAL wrapper, and passes a closure that jumps into `core1_entry()`. +* **`core1_entry()`**: The entry point for Core 1. It steals the hardware peripherals to access the `SIO` block, grabs the `fifo`, and enters an infinite loop waiting to `read_blocking()`, applying the increment logic, and then calling `write_blocking()` to send it back. +* **`send_and_print(...)`**: Executed by Core 0 in the main loop. It writes the counter to the FIFO, blocks until Core 1 replies, formats both the sent and received values using the library, and prints the string over UART. + +### 3. The Reusable Multicore Library (`src/multicore.rs`) +While `board.rs` handles the hardware FIFO registers, `multicore.rs` abstracts away the actual logic applied by the secondary core and the string formatting required for logging. + +* **`increment_value(...)`**: A simple example of isolated logic processed by Core 1. It performs a wrapping addition to avoid panics on overflow. +* **`format_round_trip(...)`**: Formats the `core0 sent: N, core1 returned: N+1` string. +* **`format_u32(...)` / `u32_to_digits_reversed(...)`**: Implements custom `u32` to decimal ASCII string conversion without allocating dynamic memory, enabling `core` library compatibility (`no_std`). diff --git a/drivers/0x0c_multicore_rust/build.rs b/drivers/0x0c_multicore_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x0c_multicore_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x0c_multicore_rust/rp2040.x b/drivers/0x0c_multicore_rust/rp2040.x new file mode 100644 index 0000000..e66f718 --- /dev/null +++ b/drivers/0x0c_multicore_rust/rp2040.x @@ -0,0 +1,44 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + .bi_entries : ALIGN(4) + { + __bi_entries_start = .; + KEEP(*(.bi_entries)); + . = ALIGN(4); + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; diff --git a/drivers/0x0c_multicore_rust/rp2350.x b/drivers/0x0c_multicore_rust/rp2350.x new file mode 100644 index 0000000..570f72c --- /dev/null +++ b/drivers/0x0c_multicore_rust/rp2350.x @@ -0,0 +1,47 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + RAM : ORIGIN = 0x20000000, LENGTH = 512K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + .bi_entries : ALIGN(4) + { + __bi_entries_start = .; + KEEP(*(.bi_entries)); + . = ALIGN(4); + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); diff --git a/drivers/0x0c_multicore_rust/rp2350_riscv.x b/drivers/0x0c_multicore_rust/rp2350_riscv.x new file mode 100644 index 0000000..2c9b445 --- /dev/null +++ b/drivers/0x0c_multicore_rust/rp2350_riscv.x @@ -0,0 +1,58 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + RAM : ORIGIN = 0x20000000, LENGTH = 512K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +PROVIDE(__pre_init = default_pre_init); +PROVIDE(_setup_interrupts = default_setup_interrupts); +PROVIDE(_mp_hook = default_mp_hook); +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + . = ABSOLUTE(_stext); + } > FLASH +} diff --git a/drivers/0x0c_multicore_rust/src/board.rs b/drivers/0x0c_multicore_rust/src/board.rs new file mode 100644 index 0000000..8426cfa --- /dev/null +++ b/drivers/0x0c_multicore_rust/src/board.rs @@ -0,0 +1,199 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Multicore pure-logic helpers and constants +use multicore_lib::multicore; +// Rate extension trait for .Hz() baud rate construction +use fugit::RateExtU32; +// Clock trait for accessing system clock frequency +use hal::Clock; +// GPIO pin types and function selectors +use hal::gpio::{FunctionNull, FunctionUart, Pin, PullDown, PullNone}; +// Multicore execution management and stack type for core 1 +use hal::multicore::{Multicore, Stack}; +// SIO type for inter-core FIFO and GPIO bank ownership +use hal::sio::{Sio, SioFifo}; +// UART configuration and peripheral types +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// Delay between FIFO round-trip messages in milliseconds. +pub(crate) const POLL_MS: u32 = 1_000; + +/// Maximum buffer size for formatting a round-trip message. +pub(crate) const MSG_BUF_LEN: usize = 52; + +/// Type alias for the configured TX pin (GPIO 0, UART function, no pull). +pub(crate) type TxPin = Pin; + +/// Type alias for the configured RX pin (GPIO 1, UART function, no pull). +pub(crate) type RxPin = Pin; + +/// Type alias for the default TX pin state from `Pins::new()`. +pub(crate) type TxPinDefault = Pin; + +/// Type alias for the default RX pin state from `Pins::new()`. +pub(crate) type RxPinDefault = Pin; + +/// Type alias for the fully-enabled UART0 peripheral with TX/RX pins. +pub(crate) type EnabledUart = UartPeripheral; + +// Stack allocation for core 1 (4096 words) +/// The core1 stack static variable. +static CORE1_STACK: Stack<4096> = Stack::new(); + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +pub(crate) fn init_clocks( + xosc: hal::pac::XOSC, clocks: hal::pac::CLOCKS, pll_sys: hal::pac::PLL_SYS, pll_usb: hal::pac::PLL_USB, + resets: &mut hal::pac::RESETS, watchdog: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls(XTAL_FREQ_HZ, xosc, clocks, pll_sys, pll_usb, resets, watchdog).unwrap() +} + +/// Unlock the GPIO bank and return the pin set along with the SIO FIFO. +pub(crate) fn init_pins( + io: hal::pac::IO_BANK0, pads: hal::pac::PADS_BANK0, sio: hal::pac::SIO, rst: &mut hal::pac::RESETS +) -> (hal::gpio::Pins, SioFifo) { + let sio = Sio::new(sio); + (hal::gpio::Pins::new(io, pads, sio.gpio_bank0, rst), sio.fifo) +} + +/// Initialise UART0 for serial output. +pub(crate) fn init_uart( + uart0: hal::pac::UART0, tx: TxPinDefault, rx: RxPinDefault, rst: &mut hal::pac::RESETS, c: &hal::clocks::ClocksManager +) -> EnabledUart { + let pins = (tx.reconfigure::(), rx.reconfigure::()); + let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One); + UartPeripheral::new(uart0, pins, rst).enable(cfg, c.peripheral_clock.freq()).unwrap() +} + +/// Create a blocking delay timer from the ARM SysTick peripheral. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +pub(crate) fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay { + let core = cortex_m::Peripherals::take().unwrap(); + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()) +} + +/// Core 1 entry point: receive values via FIFO, increment, and return. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +fn core1_entry() -> ! { + let mut fifo = Sio::new(unsafe { hal::pac::Peripherals::steal() }.SIO).fifo; + loop { + let value = fifo.read_blocking(); + fifo.write_blocking(multicore::increment_value(value)); + } +} + +/// Launch core 1 with its FIFO echo task. +/// +/// # Arguments +/// +/// * `psm` - The `psm` parameter. +/// * `ppb` - The `ppb` parameter. +/// * `fifo` - The `fifo` parameter. +/// +/// # Arguments +/// +/// * `psm` - The `psm` parameter. +/// * `ppb` - The `ppb` parameter. +/// * `fifo` - The `fifo` parameter. +pub(crate) fn spawn_core1(psm: &mut hal::pac::PSM, ppb: &mut hal::pac::PPB, fifo: &mut SioFifo) { + let mut mc = Multicore::new(psm, ppb, fifo); + let _ = mc.cores()[1].spawn(CORE1_STACK.take().unwrap(), move || core1_entry()); +} + +/// Send the counter to core 1 via FIFO and print the round-trip result. +pub(crate) fn send_and_print( + fifo: &mut SioFifo, uart: &EnabledUart, counter: &mut u32, delay: &mut cortex_m::delay::Delay +) { + fifo.write_blocking(*counter); let response = fifo.read_blocking(); + let mut buf = [0u8; MSG_BUF_LEN]; + let n = multicore::format_round_trip(&mut buf, *counter, response); + uart.write_full_blocking(&buf[..n]); *counter = counter.wrapping_add(1); delay.delay_ms(POLL_MS); +} + +/// Initialise all peripherals and run the multicore FIFO demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `pac` - The `pac` parameter. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! { + let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut hal::Watchdog::new(p.WATCHDOG)); + let (pins, mut fifo) = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS); + let (u, mut d) = (init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c), init_delay(&c)); + spawn_core1(&mut p.PSM, &mut p.PPB, &mut fifo); + let mut counter = 0u32; + loop { send_and_print(&mut fifo, &u, &mut counter, &mut d); } +} + diff --git a/drivers/0x0c_multicore_rust/src/lib.rs b/drivers/0x0c_multicore_rust/src/lib.rs new file mode 100644 index 0000000..cf2940d --- /dev/null +++ b/drivers/0x0c_multicore_rust/src/lib.rs @@ -0,0 +1,6 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +#![cfg_attr(not(test), no_std)] +pub mod multicore; diff --git a/drivers/0x0c_multicore_rust/src/main.rs b/drivers/0x0c_multicore_rust/src/main.rs new file mode 100644 index 0000000..f1d78b6 --- /dev/null +++ b/drivers/0x0c_multicore_rust/src/main.rs @@ -0,0 +1,102 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; +// Multicore driver module — suppress warnings for unused public API functions + +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the multicore FIFO demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Multicore FIFO Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/drivers/0x0c_multicore_rust/src/multicore.rs b/drivers/0x0c_multicore_rust/src/multicore.rs new file mode 100644 index 0000000..4becf83 --- /dev/null +++ b/drivers/0x0c_multicore_rust/src/multicore.rs @@ -0,0 +1,275 @@ +//! Implementation module +//! +//! **File:** `multicore.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +/// Increment a 32-bit value by one (core 1 processing logic). +/// +/// # Arguments +/// +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A 32-bit unsigned integer value. +/// +/// # Arguments +/// +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A 32-bit unsigned integer value. +#[inline] +pub fn increment_value(value: u32) -> u32 { + value.wrapping_add(1) +} + +/// Format a u32 value as decimal ASCII into a buffer. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn format_u32(buf: &mut [u8], value: u32) -> usize { + if value == 0 { buf[0] = b'0'; return 1; } + let mut tmp = [0u8; 10]; + let n = u32_to_digits_reversed(&mut tmp, value); + reverse_copy(buf, &tmp, n); n +} + +/// Convert a u32 to reversed decimal digits in a temporary buffer. +/// +/// # Arguments +/// +/// * `tmp` - The `tmp` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `tmp` - The `tmp` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn u32_to_digits_reversed(tmp: &mut [u8; 10], mut value: u32) -> usize { + let mut n = 0usize; + while value > 0 { tmp[n] = b'0' + (value % 10) as u8; value /= 10; n += 1; } + n +} + +/// Copy digits from a reversed temporary buffer into the output buffer. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `tmp` - The `tmp` parameter. +/// * `n` - Nibble or number. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `tmp` - The `tmp` parameter. +/// * `n` - Nibble or number. +#[inline] +fn reverse_copy(buf: &mut [u8], tmp: &[u8], n: usize) { + for i in 0..n { buf[i] = tmp[n - 1 - i]; } +} + +/// Copy a byte slice into `buf` at the given offset, returning bytes written. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `offset` - The `offset` parameter. +/// * `src` - The `src` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `offset` - The `offset` parameter. +/// * `src` - The `src` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn copy_slice(buf: &mut [u8], offset: usize, src: &[u8]) -> usize { + buf[offset..offset + src.len()].copy_from_slice(src); src.len() +} + +/// Append a CRLF sequence at `pos` in `buf` and return the new position. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `pos` - The `pos` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `pos` - The `pos` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn append_crlf(buf: &mut [u8], pos: usize) -> usize { + buf[pos] = b'\r'; buf[pos + 1] = b'\n'; pos + 2 +} + +/// Format the round-trip message for UART output. +/// +/// Produces: `core0 sent: N, core1 returned: N\r\n` +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `sent` - The `sent` parameter. +/// * `returned` - The `returned` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `sent` - The `sent` parameter. +/// * `returned` - The `returned` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_round_trip(buf: &mut [u8], sent: u32, returned: u32) -> usize { + let mut pos = copy_slice(buf, 0, b"core0 sent: "); pos += format_u32(&mut buf[pos..], sent); + pos += copy_slice(buf, pos, b", core1 returned: "); pos += format_u32(&mut buf[pos..], returned); + append_crlf(buf, pos) +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + + /// Executes the increment value adds one operation. + #[test] + fn increment_value_adds_one() { + assert_eq!(increment_value(0), 1); + assert_eq!(increment_value(41), 42); + } + + /// Executes the increment value wraps at max operation. + #[test] + fn increment_value_wraps_at_max() { + assert_eq!(increment_value(u32::MAX), 0); + } + + /// Executes the format u32 zero operation. + #[test] + fn format_u32_zero() { + let mut buf = [0u8; 10]; + let n = format_u32(&mut buf, 0); + assert_eq!(&buf[..n], b"0"); + } + + /// Executes the format u32 single digit operation. + #[test] + fn format_u32_single_digit() { + let mut buf = [0u8; 10]; + let n = format_u32(&mut buf, 7); + assert_eq!(&buf[..n], b"7"); + } + + /// Executes the format u32 multi digit operation. + #[test] + fn format_u32_multi_digit() { + let mut buf = [0u8; 10]; + let n = format_u32(&mut buf, 12345); + assert_eq!(&buf[..n], b"12345"); + } + + /// Executes the format u32 max operation. + #[test] + fn format_u32_max() { + let mut buf = [0u8; 10]; + let n = format_u32(&mut buf, u32::MAX); + assert_eq!(&buf[..n], b"4294967295"); + } + + /// Executes the format round trip small values operation. + #[test] + fn format_round_trip_small_values() { + let mut buf = [0u8; 52]; + let n = format_round_trip(&mut buf, 0, 1); + assert_eq!(&buf[..n], b"core0 sent: 0, core1 returned: 1\r\n"); + } + + /// Executes the format round trip larger values operation. + #[test] + fn format_round_trip_larger_values() { + let mut buf = [0u8; 52]; + let n = format_round_trip(&mut buf, 42, 43); + assert_eq!(&buf[..n], b"core0 sent: 42, core1 returned: 43\r\n"); + } + + /// Executes the format round trip max values operation. + #[test] + fn format_round_trip_max_values() { + let mut buf = [0u8; 52]; + let n = format_round_trip(&mut buf, u32::MAX, 0); + assert_eq!(&buf[..n], b"core0 sent: 4294967295, core1 returned: 0\r\n"); + } +} diff --git a/drivers/0x0d_timer_rust/.cargo/config.toml b/drivers/0x0d_timer_rust/.cargo/config.toml new file mode 100644 index 0000000..70fb5fa --- /dev/null +++ b/drivers/0x0d_timer_rust/.cargo/config.toml @@ -0,0 +1,32 @@ +[build] +target = "thumbv8m.main-none-eabihf" + +[target.thumbv6m-none-eabi] +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[target.thumbv8m.main-none-eabihf] +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[target.riscv32imac-unknown-none-elf] +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp2350_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[env] +DEFMT_LOG = "debug" diff --git a/drivers/0x0d_timer_rust/.pico-rs b/drivers/0x0d_timer_rust/.pico-rs new file mode 100644 index 0000000..be06904 --- /dev/null +++ b/drivers/0x0d_timer_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 diff --git a/drivers/0x0d_timer_rust/.vscode/extensions.json b/drivers/0x0d_timer_rust/.vscode/extensions.json new file mode 100644 index 0000000..107cc34 --- /dev/null +++ b/drivers/0x0d_timer_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} diff --git a/drivers/0x0d_timer_rust/.vscode/launch.json b/drivers/0x0d_timer_rust/.vscode/launch.json new file mode 100644 index 0000000..34833c2 --- /dev/null +++ b/drivers/0x0d_timer_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} diff --git a/drivers/0x0d_timer_rust/.vscode/settings.json b/drivers/0x0d_timer_rust/.vscode/settings.json new file mode 100644 index 0000000..3744e1f --- /dev/null +++ b/drivers/0x0d_timer_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} diff --git a/drivers/0x0d_timer_rust/.vscode/tasks.json b/drivers/0x0d_timer_rust/.vscode/tasks.json new file mode 100644 index 0000000..d288f27 --- /dev/null +++ b/drivers/0x0d_timer_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} diff --git a/drivers/0x0d_timer_rust/Cargo.lock b/drivers/0x0d_timer_rust/Cargo.lock new file mode 100644 index 0000000..15ed98b --- /dev/null +++ b/drivers/0x0d_timer_rust/Cargo.lock @@ -0,0 +1,704 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield 0.13.2", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "crc-any" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62ec9ff5f7965e4d7280bd5482acd20aadb50d632cf6c1d74493856b011fa73" +dependencies = [ + "debug-helper", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "debug-helper" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" +dependencies = [ + "critical-section", + "defmt", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "frunk" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28aef0f9aa070bce60767c12ba9cb41efeaf1a2bc6427f87b7d83f11239a16d7" +dependencies = [ + "frunk_core", + "frunk_derives", +] + +[[package]] +name = "frunk_core" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476eeaa382e3462b84da5d6ba3da97b5786823c2d0d3a0d04ef088d073da225c" + +[[package]] +name = "frunk_derives" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b4095fc99e1d858e5b8c7125d2638372ec85aa0fe6c807105cf10b0265ca6c" +dependencies = [ + "frunk_proc_macro_helpers", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "frunk_proc_macro_helpers" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1952b802269f2db12ab7c0bd328d0ae8feaabf19f352a7b0af7bb0c5693abfce" +dependencies = [ + "frunk_core", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fugit" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e639847d312d9a82d2e75b0edcc1e934efcc64e6cb7aa94f0b1fbec0bc231d6" +dependencies = [ + "gcd", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "panic-halt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11" + +[[package]] +name = "panic-probe" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" +dependencies = [ + "cortex-m", + "defmt", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" +dependencies = [ + "arrayvec", + "num_enum", + "paste", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "riscv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f5c1b8bf41ea746266cdee443d1d1e9125c86ce1447e1a2615abd34330d33a9" +dependencies = [ + "critical-section", + "embedded-hal 1.0.0", +] + +[[package]] +name = "riscv-rt" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d35e32cf1383183e8885d8a9aa4402a087fd094dc34c2cb6df6687d0229dfe" +dependencies = [ + "riscv", + "riscv-rt-macros", +] + +[[package]] +name = "riscv-rt-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30f19a85fe107b65031e0ba8ec60c34c2494069fe910d6c297f5e7cb5a6f76d0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rp-binary-info" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f582261945fa215d40e2988b4df595d11c0908c0fff97a0fe23df766d117b790" + +[[package]] +name = "rp-hal-common" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8288358786b1458fb2caac8c4b40fb529ef4200d6c46467e2695b7a8ba573ae8" +dependencies = [ + "fugit", +] + +[[package]] +name = "rp2040-boot2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" +dependencies = [ + "crc-any", +] + +[[package]] +name = "rp2040-hal" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb79a4590775204387f334672e6f79c0d734d0a159da23d60677b3c10fa1245" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "itertools 0.10.5", + "nb 1.1.0", + "paste", + "pio", + "rand_core", + "rp-binary-info", + "rp-hal-common", + "rp2040-hal-macros", + "rp2040-pac", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp2040-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86479063e497efe1ae81995ef9071f54fd1c7427e04d6c5b84cde545ff672a5e" +dependencies = [ + "cortex-m-rt", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rp2040-pac" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83cbcd3f7a0ca7bbe61dc4eb7e202842bee4e27b769a7bf3a4a72fa399d6e404" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "vcell", +] + +[[package]] +name = "rp235x-hal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2939c82776b0b4ae110168b4298b5adf831e6cff249b057bf2a2187453b959c" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "cortex-m-rt", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "gcd", + "itertools 0.13.0", + "nb 1.1.0", + "paste", + "pio", + "rand_core", + "riscv", + "riscv-rt", + "rp-binary-info", + "rp-hal-common", + "rp235x-hal-macros", + "rp235x-pac", + "sha2-const-stable", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp235x-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74edd7a5979e9763bbb98e9746e711bac7464ee3397af7288e6c288ff0d3c764" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rp235x-pac" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffcb6931deee4242886b5a1df62db5e2555b0eb6ae1e8be101f3ea3e58e65c6" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "vcell", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "timer" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt", + "defmt-rtt", + "fugit", + "panic-halt", + "panic-probe", + "rp2040-boot2", + "rp2040-hal", + "rp235x-hal", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] diff --git a/drivers/0x0d_timer_rust/Cargo.toml b/drivers/0x0d_timer_rust/Cargo.toml new file mode 100644 index 0000000..1d85d22 --- /dev/null +++ b/drivers/0x0d_timer_rust/Cargo.toml @@ -0,0 +1,79 @@ +[package] +edition = "2024" +name = "timer" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "timer_lib" +path = "src/lib.rs" + +[[bin]] +name = "timer" +path = "src/main.rs" + + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 3 diff --git a/drivers/0x0d_timer_rust/README.md b/drivers/0x0d_timer_rust/README.md new file mode 100644 index 0000000..f3484bf --- /dev/null +++ b/drivers/0x0d_timer_rust/README.md @@ -0,0 +1,113 @@ +# 0x0d Timer Rust Driver + +This repository contains a Bare-Metal Rust driver demonstrating **Hardware Timers** and **Periodic Execution** on the **RP2350** (and RP2040) microcontrollers. + +It includes: +- A demo (`src/main.rs`) that starts a 1000ms repeating timer and prints a heartbeat message to the UART console. +- A reusable library module (`src/timer.rs`) providing a hardware-agnostic `timer_lib` containing the driver state machine and formatting helpers. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the data manipulation and string formatting logic is separated into a reusable library without touching hardware registers, you can run the unit tests natively on your computer! + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we initialize the system clocks, pins, UART (for logging), SysTick (for delay), and the hardware Timer peripheral. + +* **`run(...)`**: The master setup function. Calls the helper initialization functions, gets a handle to the `hal::Timer`, initializes the driver state, and enters the infinite `heartbeat_loop`. +* **`heartbeat_loop(...)`**: An infinite loop that polls the hardware timer's microsecond counter. It continuously checks if the elapsed time exceeds the configured period. When it does, it updates the checkpoint and fires the callback. +* **`tick_elapsed(...)`**: A helper function that handles the 32-bit wrapping arithmetic required to safely calculate elapsed microseconds from the free-running hardware counter. +* **`fire_heartbeat(...)`**: Called by the loop when the period elapses. It asks the driver state to record the fire event, and if active, formats and prints the heartbeat message over UART. + +### 3. The Reusable Timer Library (`src/timer.rs`) +While `board.rs` directly polls the hardware counter registers, `timer.rs` tracks the logical state of our repeating timer and handles string formatting for logging. + +* **`TimerDriverState`**: A struct that acts as a state machine. It stores whether the timer is `active`, the configured `period_ms`, and the total `fire_count`. It abstracts away the global variables commonly used in C SDKs. +* **`start(...)` / `cancel(...)`**: Methods to transition the driver state. +* **`on_fire(&mut self)`**: Called by the board shim when the hardware triggers. It increments the fire count and returns whether the timer should continue repeating. +* **`format_heartbeat(...)` / `format_started(...)`**: Helpers to generate the UART console messages. +* **`format_u32(...)`**: Implements custom `u32` to decimal ASCII string conversion without allocating dynamic memory, enabling `core` library compatibility (`no_std`). diff --git a/drivers/0x0d_timer_rust/build.rs b/drivers/0x0d_timer_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x0d_timer_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x0d_timer_rust/rp2040.x b/drivers/0x0d_timer_rust/rp2040.x new file mode 100644 index 0000000..6e1a654 --- /dev/null +++ b/drivers/0x0d_timer_rust/rp2040.x @@ -0,0 +1,44 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + .bi_entries : ALIGN(4) + { + __bi_entries_start = .; + KEEP(*(.bi_entries)); + . = ALIGN(4); + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; \ No newline at end of file diff --git a/drivers/0x0d_timer_rust/rp2350.x b/drivers/0x0d_timer_rust/rp2350.x new file mode 100644 index 0000000..570f72c --- /dev/null +++ b/drivers/0x0d_timer_rust/rp2350.x @@ -0,0 +1,47 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + RAM : ORIGIN = 0x20000000, LENGTH = 512K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + .bi_entries : ALIGN(4) + { + __bi_entries_start = .; + KEEP(*(.bi_entries)); + . = ALIGN(4); + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); diff --git a/drivers/0x0d_timer_rust/rp2350_riscv.x b/drivers/0x0d_timer_rust/rp2350_riscv.x new file mode 100644 index 0000000..2c9b445 --- /dev/null +++ b/drivers/0x0d_timer_rust/rp2350_riscv.x @@ -0,0 +1,58 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + RAM : ORIGIN = 0x20000000, LENGTH = 512K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +PROVIDE(__pre_init = default_pre_init); +PROVIDE(_setup_interrupts = default_setup_interrupts); +PROVIDE(_mp_hook = default_mp_hook); +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + . = ABSOLUTE(_stext); + } > FLASH +} diff --git a/drivers/0x0d_timer_rust/src/board.rs b/drivers/0x0d_timer_rust/src/board.rs new file mode 100644 index 0000000..b4d1866 --- /dev/null +++ b/drivers/0x0d_timer_rust/src/board.rs @@ -0,0 +1,282 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Timer driver pure-logic functions and constants +use timer_lib::timer; +// Rate extension trait for .Hz() baud rate construction +use fugit::RateExtU32; +// Clock trait for accessing system clock frequency +use hal::Clock; +// GPIO pin types and function selectors +use hal::gpio::{FunctionNull, FunctionUart, Pin, PullDown, PullNone}; +// UART configuration and peripheral types +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Timer device type for the HAL timer peripheral. +#[cfg(rp2350)] +pub(crate) type HalTimer = hal::Timer; +/// Timer type alias for RP2040 (non-generic). +#[cfg(rp2040)] +pub(crate) type HalTimer = hal::Timer; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// Type alias for the configured TX pin (GPIO 0, UART function, no pull). +pub(crate) type TxPin = Pin; + +/// Type alias for the configured RX pin (GPIO 1, UART function, no pull). +pub(crate) type RxPin = Pin; + +/// Type alias for the default TX pin state from `Pins::new()`. +pub(crate) type TxPinDefault = Pin; + +/// Type alias for the default RX pin state from `Pins::new()`. +pub(crate) type RxPinDefault = Pin; + +/// Type alias for the fully-enabled UART0 peripheral with TX/RX p. +pub(crate) type EnabledUart = UartPeripheral; + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +/// +/// # Arguments +/// +/// * `xosc` - XOSC peripheral singleton. +/// * `clocks` - CLOCKS peripheral singleton. +/// * `pll_sys` - PLL_SYS peripheral singleton. +/// * `pll_usb` - PLL_USB peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `watchdog` - Mutable reference to the watchdog timer. +/// +/// # Returns +/// +/// Configured clocks manager. +/// +/// # Panics +/// +/// Panics if clock initialisation fails. +pub(crate) fn init_clocks( + xosc: hal::pac::XOSC, clocks: hal::pac::CLOCKS, pll_sys: hal::pac::PLL_SYS, pll_usb: hal::pac::PLL_USB, + resets: &mut hal::pac::RESETS, watchdog: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls(XTAL_FREQ_HZ, xosc, clocks, pll_sys, pll_usb, resets, watchdog).unwrap() +} + +/// Unlock the GPIO bank and return the pin set. +/// +/// # Arguments +/// +/// * `io_bank0` - IO_BANK0 peripheral singleton. +/// * `pads_bank0` - PADS_BANK0 peripheral singleton. +/// * `sio` - SIO peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// +/// # Returns +/// +/// GPIO pin set for the entire bank. +pub(crate) fn init_pins( + io: hal::pac::IO_BANK0, pads: hal::pac::PADS_BANK0, sio: hal::pac::SIO, rst: &mut hal::pac::RESETS +) -> hal::gpio::Pins { + hal::gpio::Pins::new(io, pads, hal::Sio::new(sio).gpio_bank0, rst) +} + +/// Initialise UART0 for serial output (stdio equivalent). +/// +/// # Arguments +/// +/// * `uart0` - PAC UART0 peripheral singleton. +/// * `tx_pin` - GPIO pin to use as UART0 TX (GPIO 0). +/// * `rx_pin` - GPIO pin to use as UART0 RX (GPIO 1). +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Enabled UART0 peripheral ready for blocking writes. +/// +/// # Panics +/// +/// Panics if the HAL cannot achieve the requested baud rate. +pub(crate) fn init_uart( + uart0: hal::pac::UART0, tx: TxPinDefault, rx: RxPinDefault, rst: &mut hal::pac::RESETS, c: &hal::clocks::ClocksManager +) -> EnabledUart { + let pins = (tx.reconfigure::(), rx.reconfigure::()); + let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One); + UartPeripheral::new(uart0, pins, rst).enable(cfg, c.peripheral_clock.freq()).unwrap() +} + +/// Create a blocking delay timer from the ARM SysTick peripheral. +/// +/// # Arguments +/// +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Blocking delay provider. +/// +/// # Panics +/// +/// Panics if the cortex-m core peripherals have already been taken. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +pub(crate) fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay { + let core = cortex_m::Peripherals::take().unwrap(); + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()) +} + +/// Run the repeating timer heartbeat loop. +/// +/// Polls the HAL timer, and each time the configured period elapses, +/// fires the driver state callback and prints the heartbeat message +/// over UART. This mirrors the C demo's `_heartbeat_callback` being +/// invoked by the Pico SDK repeating timer. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// * `timer` - Reference to the HAL timer for microsecond measurement. +/// * `delay` - Mutable reference to the blocking delay provider. +/// * `state` - Mutable reference to the timer driver state. +pub(crate) fn heartbeat_loop( + uart: &EnabledUart, timer: &HalTimer, delay: &mut cortex_m::delay::Delay, state: &mut timer::TimerDriverState +) -> ! { + let (mut last_us, period_us) = (timer.get_counter().ticks() as u32, state.period_ms() as u64 * 1_000); + loop { + let (now, elapsed) = tick_elapsed(timer, last_us); + if elapsed >= period_us { last_us = now; fire_heartbeat(uart, state); } + delay.delay_us(100); + } +} + +/// Compute elapsed microseconds since last checkpoint. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// * `last_us` - The `last_us` parameter. +/// +/// # Returns +/// +/// A value of type `(u32, u64)`. +/// +/// # Arguments +/// +/// * `timer` - The `timer` parameter. +/// * `last_us` - The `last_us` parameter. +/// +/// # Returns +/// +/// A value of type `(u32, u64)`. +fn tick_elapsed(timer: &HalTimer, last_us: u32) -> (u32, u64) { + let now = timer.get_counter().ticks() as u32; (now, now.wrapping_sub(last_us) as u64) +} + +/// Fire the heartbeat callback and print the message over UART. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +/// * `state` - The state to set. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +/// * `state` - The state to set. +fn fire_heartbeat(uart: &EnabledUart, state: &mut timer::TimerDriverState) { + if state.on_fire() { + let mut buf = [0u8; 32]; + let n = timer::format_heartbeat(&mut buf); uart.write_full_blocking(&buf[..n]); + } +} + +/// Initialise all peripherals and run the repeating timer demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `pac` - The `pac` parameter. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! { + let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut hal::Watchdog::new(p.WATCHDOG)); + let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS); + let (u, mut d) = (init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c), init_delay(&c)); + #[cfg(rp2350)] let t = hal::Timer::new_timer0(p.TIMER0, &mut p.RESETS, &c); #[cfg(rp2040)] let t = hal::Timer::new(p.TIMER, &mut p.RESETS); + let mut state = start_timer(&u); + heartbeat_loop(&u, &t, &mut d, &mut state) +} + +/// Create the timer driver state, start it, and report over UART. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// +/// # Returns +/// +/// Initialised timer driver state. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +/// +/// # Returns +/// +/// A value of type `timer::TimerDriverState`. +fn start_timer(uart: &EnabledUart) -> timer::TimerDriverState { + let mut state = timer::TimerDriverState::new(); state.start(timer::DEFAULT_PERIOD_MS); + let mut buf = [0u8; 64]; let n = timer::format_started(&mut buf, timer::DEFAULT_PERIOD_MS); + uart.write_full_blocking(&buf[..n]); state +} + diff --git a/drivers/0x0d_timer_rust/src/lib.rs b/drivers/0x0d_timer_rust/src/lib.rs new file mode 100644 index 0000000..5921ebd --- /dev/null +++ b/drivers/0x0d_timer_rust/src/lib.rs @@ -0,0 +1,6 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +#![cfg_attr(not(test), no_std)] +pub mod timer; diff --git a/drivers/0x0d_timer_rust/src/main.rs b/drivers/0x0d_timer_rust/src/main.rs new file mode 100644 index 0000000..b80c2ef --- /dev/null +++ b/drivers/0x0d_timer_rust/src/main.rs @@ -0,0 +1,102 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; +// Timer driver module — suppress warnings for unused public API functions + +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the repeating timer demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Repeating Timer Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/drivers/0x0d_timer_rust/src/timer.rs b/drivers/0x0d_timer_rust/src/timer.rs new file mode 100644 index 0000000..5603332 --- /dev/null +++ b/drivers/0x0d_timer_rust/src/timer.rs @@ -0,0 +1,400 @@ +//! Implementation module +//! +//! **File:** `timer.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +/// Default heartbeat message printed by the timer callback. +pub const HEARTBEAT_MSG: &[u8] = b"Timer heartbeat\r\n"; + +/// Default timer period in milliseconds. +pub const DEFAULT_PERIOD_MS: u32 = 1_000; + +/// Timer driver state tracking whether the repeating timer is active. +/// +/// Mirrors the C driver's `g_timer_active` / `g_user_callback` globals +/// as a structured state machine testable on the host. +pub struct TimerDriverState { + /// Whether the repeating timer is currently active. + active: bool, + /// Configured period in milliseconds. + period_ms: u32, + /// Number of times the callback has fired. + fire_count: u32, +} + +impl TimerDriverState { + /// Create a new idle timer driver state. + /// + /// # Returns + /// + /// A `TimerDriverState` in the inactive state with zero fire count. + /// + /// # Returns + /// + /// A new instance of the struct. + #[inline] + pub fn new() -> Self { + Self { active: false, period_ms: 0, fire_count: 0 } + } + + /// Start the repeating timer with the given period. + /// + /// If the timer is already active it is first cancelled, matching + /// the C driver's `timer_driver_start()` behaviour. + /// + /// # Arguments + /// + /// * `period_ms` - Interval between callbacks in milliseconds. + /// + /// # Arguments + /// + /// * `period_ms` - The `period_ms` parameter. + #[inline] + pub fn start(&mut self, period_ms: u32) { + if self.active { self.cancel(); } + self.period_ms = period_ms; self.active = true; + } + + /// Cancel the active repeating timer. + /// + /// Safe to call even if the timer is already inactive, matching + /// the C driver's `timer_driver_cancel()` behaviour. + #[inline] + pub fn cancel(&mut self) { + self.active = false; + } + + /// Return whether the timer is currently active. + /// + /// # Returns + /// + /// `true` if a repeating timer is running, `false` otherwise. + /// + /// # Returns + /// + /// `true` if successful or set, `false` otherwise. + #[inline] + pub fn is_active(&self) -> bool { + self.active + } + + /// Return the configured timer period in milliseconds. + /// + /// # Returns + /// + /// The period set by the most recent `start()` call. + /// + /// # Returns + /// + /// A 32-bit unsigned integer value. + #[inline] + pub fn period_ms(&self) -> u32 { + self.period_ms + } + + /// Return the total number of times the callback has fired. + /// + /// # Returns + /// + /// Cumulative fire count since construction. + /// + /// # Returns + /// + /// A 32-bit unsigned integer value. + #[inline] + pub fn fire_count(&self) -> u32 { + self.fire_count + } + + /// Record that the callback has fired once. + /// + /// Called by the board-level shim each time the hardware alarm + /// triggers. Returns `true` to keep the timer repeating (matching + /// the C `_heartbeat_callback` return value). + /// + /// # Returns + /// + /// `true` if the timer should continue repeating. + /// + /// # Returns + /// + /// `true` if successful or set, `false` otherwise. + #[inline] + pub fn on_fire(&mut self) -> bool { + if !self.active { return false; } + self.fire_count += 1; true + } +} + +/// Format the heartbeat message into a caller-supplied buffer. +/// +/// Writes the fixed `HEARTBEAT_MSG` bytes and returns the number +/// of bytes written. +/// +/// # Arguments +/// +/// * `buf` - Destination buffer (must be at least `HEARTBEAT_MSG.len()` bytes). +/// +/// # Returns +/// +/// Number of bytes written to `buf`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_heartbeat(buf: &mut [u8]) -> usize { + let len = HEARTBEAT_MSG.len(); buf[..len].copy_from_slice(HEARTBEAT_MSG); len +} + +/// Format a timer-started banner message. +/// +/// Writes "Repeating timer started (XXXX ms)\r\n" into `buf` and +/// returns the number of bytes written. +/// +/// # Arguments +/// +/// * `buf` - Destination buffer (must be at least 40 bytes). +/// * `period_ms` - Timer period to include in the message. +/// +/// # Returns +/// +/// Number of bytes written to `buf`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `period_ms` - The `period_ms` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_started(buf: &mut [u8], period_ms: u32) -> usize { + let mut pos = b"Repeating timer started (".len(); buf[..pos].copy_from_slice(b"Repeating timer started ("); + pos += format_u32(&mut buf[pos..], period_ms); + buf[pos..pos + 6].copy_from_slice(b" ms)\r\n"); pos + 6 +} + +/// Format an unsigned 32-bit integer as decimal ASCII. +/// +/// # Arguments +/// +/// * `buf` - Destination buffer. +/// * `value` - Value to format. +/// +/// # Returns +/// +/// Number of ASCII digits written. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn format_u32(buf: &mut [u8], value: u32) -> usize { + if value == 0 { buf[0] = b'0'; return 1; } + let mut tmp = [0u8; 10]; let n = u32_to_digits_reversed(&mut tmp, value); reverse_copy(buf, &tmp, n); n +} + +/// Convert a u32 to reversed decimal digits in a temporary buffer. +/// +/// # Arguments +/// +/// * `tmp` - The `tmp` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `tmp` - The `tmp` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn u32_to_digits_reversed(tmp: &mut [u8; 10], mut value: u32) -> usize { + let mut n = 0usize; + while value > 0 { tmp[n] = b'0' + (value % 10) as u8; value /= 10; n += 1; } + n +} + +/// Copy digits from a reversed temporary buffer into the output buffer. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `tmp` - The `tmp` parameter. +/// * `n` - Nibble or number. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `tmp` - The `tmp` parameter. +/// * `n` - Nibble or number. +#[inline] +fn reverse_copy(buf: &mut [u8], tmp: &[u8], n: usize) { + for i in 0..n { buf[i] = tmp[n - 1 - i]; } +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + + /// Executes the new state is inactive operation. + #[test] + fn new_state_is_inactive() { + let state = TimerDriverState::new(); + assert!(!state.is_active()); + assert_eq!(state.period_ms(), 0); + assert_eq!(state.fire_count(), 0); + } + + /// Executes the start activates timer operation. + #[test] + fn start_activates_timer() { + let mut state = TimerDriverState::new(); + state.start(1_000); + assert!(state.is_active()); + assert_eq!(state.period_ms(), 1_000); + } + + /// Executes the cancel deactivates timer operation. + #[test] + fn cancel_deactivates_timer() { + let mut state = TimerDriverState::new(); + state.start(1_000); + state.cancel(); + assert!(!state.is_active()); + } + + /// Executes the cancel when inactive is safe operation. + #[test] + fn cancel_when_inactive_is_safe() { + let mut state = TimerDriverState::new(); + state.cancel(); + assert!(!state.is_active()); + } + + /// Executes the start while active cancels and restarts operation. + #[test] + fn start_while_active_cancels_and_restarts() { + let mut state = TimerDriverState::new(); + state.start(500); + state.start(2_000); + assert!(state.is_active()); + assert_eq!(state.period_ms(), 2_000); + } + + /// Executes the on fire increments count operation. + #[test] + fn on_fire_increments_count() { + let mut state = TimerDriverState::new(); + state.start(1_000); + assert!(state.on_fire()); + assert_eq!(state.fire_count(), 1); + assert!(state.on_fire()); + assert_eq!(state.fire_count(), 2); + } + + /// Executes the on fire returns false when inactive operation. + #[test] + fn on_fire_returns_false_when_inactive() { + let mut state = TimerDriverState::new(); + assert!(!state.on_fire()); + assert_eq!(state.fire_count(), 0); + } + + /// Executes the on fire after cancel returns false operation. + #[test] + fn on_fire_after_cancel_returns_false() { + let mut state = TimerDriverState::new(); + state.start(1_000); + assert!(state.on_fire()); + state.cancel(); + assert!(!state.on_fire()); + assert_eq!(state.fire_count(), 1); + } + + /// Executes the format heartbeat matches c output operation. + #[test] + fn format_heartbeat_matches_c_output() { + let mut buf = [0u8; 32]; + let n = format_heartbeat(&mut buf); + assert_eq!(&buf[..n], b"Timer heartbeat\r\n"); + } + + /// Executes the format started 1000ms operation. + #[test] + fn format_started_1000ms() { + let mut buf = [0u8; 48]; + let n = format_started(&mut buf, 1_000); + assert_eq!(&buf[..n], b"Repeating timer started (1000 ms)\r\n"); + } + + /// Executes the format started single digit operation. + #[test] + fn format_started_single_digit() { + let mut buf = [0u8; 48]; + let n = format_started(&mut buf, 5); + assert_eq!(&buf[..n], b"Repeating timer started (5 ms)\r\n"); + } + + /// Executes the format u32 zero operation. + #[test] + fn format_u32_zero() { + let mut buf = [0u8; 10]; + let n = format_u32(&mut buf, 0); + assert_eq!(&buf[..n], b"0"); + } + + /// Executes the format u32 large value operation. + #[test] + fn format_u32_large_value() { + let mut buf = [0u8; 10]; + let n = format_u32(&mut buf, 123456); + assert_eq!(&buf[..n], b"123456"); + } + + /// Executes the default period matches c demo operation. + #[test] + fn default_period_matches_c_demo() { + assert_eq!(DEFAULT_PERIOD_MS, 1_000); + } +} diff --git a/drivers/0x0e_watchdog_rust/.cargo/config.toml b/drivers/0x0e_watchdog_rust/.cargo/config.toml new file mode 100644 index 0000000..70fb5fa --- /dev/null +++ b/drivers/0x0e_watchdog_rust/.cargo/config.toml @@ -0,0 +1,32 @@ +[build] +target = "thumbv8m.main-none-eabihf" + +[target.thumbv6m-none-eabi] +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[target.thumbv8m.main-none-eabihf] +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[target.riscv32imac-unknown-none-elf] +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp2350_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[env] +DEFMT_LOG = "debug" diff --git a/drivers/0x0e_watchdog_rust/.pico-rs b/drivers/0x0e_watchdog_rust/.pico-rs new file mode 100644 index 0000000..be06904 --- /dev/null +++ b/drivers/0x0e_watchdog_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 diff --git a/drivers/0x0e_watchdog_rust/.vscode/extensions.json b/drivers/0x0e_watchdog_rust/.vscode/extensions.json new file mode 100644 index 0000000..107cc34 --- /dev/null +++ b/drivers/0x0e_watchdog_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} diff --git a/drivers/0x0e_watchdog_rust/.vscode/launch.json b/drivers/0x0e_watchdog_rust/.vscode/launch.json new file mode 100644 index 0000000..34833c2 --- /dev/null +++ b/drivers/0x0e_watchdog_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} diff --git a/drivers/0x0e_watchdog_rust/.vscode/settings.json b/drivers/0x0e_watchdog_rust/.vscode/settings.json new file mode 100644 index 0000000..3744e1f --- /dev/null +++ b/drivers/0x0e_watchdog_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} diff --git a/drivers/0x0e_watchdog_rust/.vscode/tasks.json b/drivers/0x0e_watchdog_rust/.vscode/tasks.json new file mode 100644 index 0000000..d288f27 --- /dev/null +++ b/drivers/0x0e_watchdog_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} diff --git a/drivers/0x0e_watchdog_rust/Cargo.lock b/drivers/0x0e_watchdog_rust/Cargo.lock new file mode 100644 index 0000000..2f92b8e --- /dev/null +++ b/drivers/0x0e_watchdog_rust/Cargo.lock @@ -0,0 +1,704 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield 0.13.2", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "crc-any" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62ec9ff5f7965e4d7280bd5482acd20aadb50d632cf6c1d74493856b011fa73" +dependencies = [ + "debug-helper", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "debug-helper" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" +dependencies = [ + "critical-section", + "defmt", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "frunk" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28aef0f9aa070bce60767c12ba9cb41efeaf1a2bc6427f87b7d83f11239a16d7" +dependencies = [ + "frunk_core", + "frunk_derives", +] + +[[package]] +name = "frunk_core" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476eeaa382e3462b84da5d6ba3da97b5786823c2d0d3a0d04ef088d073da225c" + +[[package]] +name = "frunk_derives" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b4095fc99e1d858e5b8c7125d2638372ec85aa0fe6c807105cf10b0265ca6c" +dependencies = [ + "frunk_proc_macro_helpers", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "frunk_proc_macro_helpers" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1952b802269f2db12ab7c0bd328d0ae8feaabf19f352a7b0af7bb0c5693abfce" +dependencies = [ + "frunk_core", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fugit" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e639847d312d9a82d2e75b0edcc1e934efcc64e6cb7aa94f0b1fbec0bc231d6" +dependencies = [ + "gcd", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "panic-halt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11" + +[[package]] +name = "panic-probe" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" +dependencies = [ + "cortex-m", + "defmt", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" +dependencies = [ + "arrayvec", + "num_enum", + "paste", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "riscv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f5c1b8bf41ea746266cdee443d1d1e9125c86ce1447e1a2615abd34330d33a9" +dependencies = [ + "critical-section", + "embedded-hal 1.0.0", +] + +[[package]] +name = "riscv-rt" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d35e32cf1383183e8885d8a9aa4402a087fd094dc34c2cb6df6687d0229dfe" +dependencies = [ + "riscv", + "riscv-rt-macros", +] + +[[package]] +name = "riscv-rt-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30f19a85fe107b65031e0ba8ec60c34c2494069fe910d6c297f5e7cb5a6f76d0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rp-binary-info" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f582261945fa215d40e2988b4df595d11c0908c0fff97a0fe23df766d117b790" + +[[package]] +name = "rp-hal-common" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8288358786b1458fb2caac8c4b40fb529ef4200d6c46467e2695b7a8ba573ae8" +dependencies = [ + "fugit", +] + +[[package]] +name = "rp2040-boot2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" +dependencies = [ + "crc-any", +] + +[[package]] +name = "rp2040-hal" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb79a4590775204387f334672e6f79c0d734d0a159da23d60677b3c10fa1245" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "itertools 0.10.5", + "nb 1.1.0", + "paste", + "pio", + "rand_core", + "rp-binary-info", + "rp-hal-common", + "rp2040-hal-macros", + "rp2040-pac", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp2040-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86479063e497efe1ae81995ef9071f54fd1c7427e04d6c5b84cde545ff672a5e" +dependencies = [ + "cortex-m-rt", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rp2040-pac" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83cbcd3f7a0ca7bbe61dc4eb7e202842bee4e27b769a7bf3a4a72fa399d6e404" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "vcell", +] + +[[package]] +name = "rp235x-hal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2939c82776b0b4ae110168b4298b5adf831e6cff249b057bf2a2187453b959c" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "cortex-m-rt", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "gcd", + "itertools 0.13.0", + "nb 1.1.0", + "paste", + "pio", + "rand_core", + "riscv", + "riscv-rt", + "rp-binary-info", + "rp-hal-common", + "rp235x-hal-macros", + "rp235x-pac", + "sha2-const-stable", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp235x-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74edd7a5979e9763bbb98e9746e711bac7464ee3397af7288e6c288ff0d3c764" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rp235x-pac" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffcb6931deee4242886b5a1df62db5e2555b0eb6ae1e8be101f3ea3e58e65c6" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "vcell", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] + +[[package]] +name = "watchdog" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt", + "defmt-rtt", + "fugit", + "panic-halt", + "panic-probe", + "rp2040-boot2", + "rp2040-hal", + "rp235x-hal", +] diff --git a/drivers/0x0e_watchdog_rust/Cargo.toml b/drivers/0x0e_watchdog_rust/Cargo.toml new file mode 100644 index 0000000..d39c167 --- /dev/null +++ b/drivers/0x0e_watchdog_rust/Cargo.toml @@ -0,0 +1,79 @@ +[package] +edition = "2024" +name = "watchdog" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "watchdog_lib" +path = "src/lib.rs" + +[[bin]] +name = "watchdog" +path = "src/main.rs" + + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 3 diff --git a/drivers/0x0e_watchdog_rust/README.md b/drivers/0x0e_watchdog_rust/README.md new file mode 100644 index 0000000..11e7ac9 --- /dev/null +++ b/drivers/0x0e_watchdog_rust/README.md @@ -0,0 +1,112 @@ +# 0x0e Watchdog Rust Driver + +This repository contains a Bare-Metal Rust driver demonstrating the **Hardware Watchdog** on the **RP2350** (and RP2040) microcontrollers. + +It includes: +- A demo (`src/main.rs`) that configures the hardware watchdog, checks the reset reason on boot, and then enters a loop to periodically "feed" the watchdog to prevent a reset. +- A reusable library module (`src/watchdog.rs`) providing a hardware-agnostic `watchdog_lib` containing the driver state machine and formatting helpers. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the data manipulation and string formatting logic is separated into a reusable library without touching hardware registers, you can run the unit tests natively on your computer! + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we initialize the system clocks, pins, UART (for logging), SysTick (for delay), and the hardware Watchdog peripheral. + +* **`run(...)`**: The master setup function. It first gets a handle to the `hal::Watchdog`, initializing the system clocks with it. After setting up UART, it checks the reset reason, initializes the driver state, and enters the infinite `feed_loop`. +* **`watchdog_caused_reboot()`**: Reads the raw `WATCHDOG REASON` hardware register via the PAC (Peripheral Access Crate) to determine if the timer elapsed or if a forced reboot occurred. +* **`watchdog_enable(...)` / `watchdog_feed(...)`**: Small wrapper functions that call into the `rp-hal` implementation to configure the hardware timer and reset its counter, respectively. +* **`feed_loop(...)`**: An infinite loop that calls `watchdog_feed()` to pet the dog, updates the driver state, prints a message, and sleeps for the 1000ms interval. + +### 3. The Reusable Watchdog Library (`src/watchdog.rs`) +While `board.rs` directly manipulates the hardware Watchdog registers, `watchdog.rs` tracks the logical state of our watchdog process and handles string formatting for logging. + +* **`WatchdogDriverState`**: A struct that acts as a state machine. It stores whether the watchdog is `enabled`, the configured `timeout_ms`, and the total `feed_count`. It abstracts away the global variables commonly used in C SDKs. +* **`enable(...)` / `feed(...)`**: Methods to transition the driver state. +* **`format_fed(...)` / `format_reset_reason(...)` / `format_enabled(...)`**: Helpers to generate the UART console messages. +* **`format_u32(...)`**: Implements custom `u32` to decimal ASCII string conversion without allocating dynamic memory, enabling `core` library compatibility (`no_std`). diff --git a/drivers/0x0e_watchdog_rust/build.rs b/drivers/0x0e_watchdog_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x0e_watchdog_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x0e_watchdog_rust/rp2040.x b/drivers/0x0e_watchdog_rust/rp2040.x new file mode 100644 index 0000000..6e1a654 --- /dev/null +++ b/drivers/0x0e_watchdog_rust/rp2040.x @@ -0,0 +1,44 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + .bi_entries : ALIGN(4) + { + __bi_entries_start = .; + KEEP(*(.bi_entries)); + . = ALIGN(4); + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; \ No newline at end of file diff --git a/drivers/0x0e_watchdog_rust/rp2350.x b/drivers/0x0e_watchdog_rust/rp2350.x new file mode 100644 index 0000000..570f72c --- /dev/null +++ b/drivers/0x0e_watchdog_rust/rp2350.x @@ -0,0 +1,47 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + RAM : ORIGIN = 0x20000000, LENGTH = 512K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + .bi_entries : ALIGN(4) + { + __bi_entries_start = .; + KEEP(*(.bi_entries)); + . = ALIGN(4); + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); diff --git a/drivers/0x0e_watchdog_rust/rp2350_riscv.x b/drivers/0x0e_watchdog_rust/rp2350_riscv.x new file mode 100644 index 0000000..2c9b445 --- /dev/null +++ b/drivers/0x0e_watchdog_rust/rp2350_riscv.x @@ -0,0 +1,58 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + RAM : ORIGIN = 0x20000000, LENGTH = 512K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +PROVIDE(__pre_init = default_pre_init); +PROVIDE(_setup_interrupts = default_setup_interrupts); +PROVIDE(_mp_hook = default_mp_hook); +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + . = ABSOLUTE(_stext); + } > FLASH +} diff --git a/drivers/0x0e_watchdog_rust/src/board.rs b/drivers/0x0e_watchdog_rust/src/board.rs new file mode 100644 index 0000000..eea677d --- /dev/null +++ b/drivers/0x0e_watchdog_rust/src/board.rs @@ -0,0 +1,304 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Watchdog driver pure-logic functions and constants +use watchdog_lib::watchdog; +// Microsecond duration type for watchdog timeout +use fugit::ExtU32; +// Rate extension trait for .Hz() baud rate construction +use fugit::RateExtU32; +// Clock trait for accessing system clock frequency +use hal::Clock; +// GPIO pin types and function selectors +use hal::gpio::{FunctionNull, FunctionUart, Pin, PullDown, PullNone}; +// UART configuration and peripheral types +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// Type alias for the configured TX pin (GPIO 0, UART function, no pull). +pub(crate) type TxPin = Pin; + +/// Type alias for the configured RX pin (GPIO 1, UART function, no pull). +pub(crate) type RxPin = Pin; + +/// Type alias for the default TX pin state from `Pins::new()`. +pub(crate) type TxPinDefault = Pin; + +/// Type alias for the default RX pin state from `Pins::new()`. +pub(crate) type RxPinDefault = Pin; + +/// Type alias for the fully-enabled UART0 peripheral with TX/RX p. +pub(crate) type EnabledUart = UartPeripheral; + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +/// +/// # Arguments +/// +/// * `xosc` - XOSC peripheral singleton. +/// * `clocks` - CLOCKS peripheral singleton. +/// * `pll_sys` - PLL_SYS peripheral singleton. +/// * `pll_usb` - PLL_USB peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `watchdog` - Mutable reference to the watchdog timer. +/// +/// # Returns +/// +/// Configured clocks manager. +/// +/// # Panics +/// +/// Panics if clock initialisation fails. +pub(crate) fn init_clocks( + xosc: hal::pac::XOSC, clocks: hal::pac::CLOCKS, pll_sys: hal::pac::PLL_SYS, pll_usb: hal::pac::PLL_USB, + resets: &mut hal::pac::RESETS, watchdog: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls(XTAL_FREQ_HZ, xosc, clocks, pll_sys, pll_usb, resets, watchdog).unwrap() +} + +/// Unlock the GPIO bank and return the pin set. +/// +/// # Arguments +/// +/// * `io_bank0` - IO_BANK0 peripheral singleton. +/// * `pads_bank0` - PADS_BANK0 peripheral singleton. +/// * `sio` - SIO peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// +/// # Returns +/// +/// GPIO pin set for the entire bank. +pub(crate) fn init_pins( + io: hal::pac::IO_BANK0, pads: hal::pac::PADS_BANK0, sio: hal::pac::SIO, rst: &mut hal::pac::RESETS +) -> hal::gpio::Pins { + hal::gpio::Pins::new(io, pads, hal::Sio::new(sio).gpio_bank0, rst) +} + +/// Initialise UART0 for serial output (stdio equivalent). +/// +/// # Arguments +/// +/// * `uart0` - PAC UART0 peripheral singleton. +/// * `tx_pin` - GPIO pin to use as UART0 TX (GPIO 0). +/// * `rx_pin` - GPIO pin to use as UART0 RX (GPIO 1). +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Enabled UART0 peripheral ready for blocking writes. +/// +/// # Panics +/// +/// Panics if the HAL cannot achieve the requested baud rate. +pub(crate) fn init_uart( + uart0: hal::pac::UART0, tx: TxPinDefault, rx: RxPinDefault, rst: &mut hal::pac::RESETS, c: &hal::clocks::ClocksManager +) -> EnabledUart { + let pins = (tx.reconfigure::(), rx.reconfigure::()); + let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One); + UartPeripheral::new(uart0, pins, rst).enable(cfg, c.peripheral_clock.freq()).unwrap() +} + +/// Create a blocking delay timer from the ARM SysTick peripheral. +/// +/// # Arguments +/// +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Blocking delay provider. +/// +/// # Panics +/// +/// Panics if the cortex-m core peripherals have already been taken. +/// +/// # Arguments +/// +/// * `clocks` - The `clocks` parameter. +/// +/// # Returns +/// +/// A value of type `cortex_m::delay::Delay`. +pub(crate) fn init_delay(clocks: &hal::clocks::ClocksManager) -> cortex_m::delay::Delay { + let core = cortex_m::Peripherals::take().unwrap(); + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()) +} + +/// Check whether the last reset was caused by the watchdog. +/// +/// Reads the WATCHDOG REASON register directly from the PAC. Returns +/// `true` if either the timer or force bits are set, matching the +/// C SDK `watchdog_caused_reboot()` behaviour. +/// +/// # Returns +/// +/// `true` if the watchdog triggered the last reset. +/// +/// # Returns +/// +/// `true` if successful or set, `false` otherwise. +pub(crate) fn watchdog_caused_reboot() -> bool { + let r = unsafe { &*hal::pac::WATCHDOG::ptr() }.reason().read(); + r.timer().bit_is_set() || r.force().bit_is_set() +} + +/// Enable the hardware watchdog with the specified timeout. +/// +/// Wraps `hal::Watchdog::start()` converting the timeout from +/// milliseconds to microseconds as required by the HAL. +/// +/// # Arguments +/// +/// * `watchdog` - Mutable reference to the HAL watchdog. +/// * `timeout_ms` - Timeout in milliseconds (1–8388). +/// +/// # Arguments +/// +/// * `watchdog` - The `watchdog` parameter. +/// * `timeout_ms` - The `timeout_ms` parameter. +pub(crate) fn watchdog_enable(watchdog: &mut hal::Watchdog, timeout_ms: u32) { + watchdog.start((timeout_ms * 1_000).micros()); +} + +/// Feed the hardware watchdog to prevent a reboot. +/// +/// Wraps `hal::Watchdog::feed()`. +/// +/// # Arguments +/// +/// * `watchdog` - Reference to the HAL watchdog. +/// +/// # Arguments +/// +/// * `watchdog` - The `watchdog` parameter. +pub(crate) fn watchdog_feed(watchdog: &hal::Watchdog) { + watchdog.feed(); +} + +/// Run the watchdog feed-and-report loop. +/// +/// Feeds the watchdog every 1 second and prints `"Watchdog fed\r\n"` +/// over UART, matching the C demo's `_feed_and_report()` function. +/// This function never returns. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// * `watchdog` - Reference to the HAL watchdog. +/// * `delay` - Mutable reference to the blocking delay provider. +/// * `state` - Mutable reference to the watchdog driver state. +pub(crate) fn feed_loop( + uart: &EnabledUart, wd: &hal::Watchdog, delay: &mut cortex_m::delay::Delay, state: &mut watchdog::WatchdogDriverState +) -> ! { + loop { + watchdog_feed(wd); state.feed(); + let mut buf = [0u8; 32]; let n = watchdog::format_fed(&mut buf); + uart.write_full_blocking(&buf[..n]); delay.delay_ms(watchdog::FEED_INTERVAL_MS); + } +} + +/// Initialise all peripherals and run the watchdog feed demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `pac` - The `pac` parameter. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! { + let mut wd = hal::Watchdog::new(p.WATCHDOG); + let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut wd); + let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS); + let (u, mut d) = (init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c), init_delay(&c)); + report_reset_reason(&u); + let mut state = start_watchdog(&u, &mut wd); + feed_loop(&u, &wd, &mut d, &mut state) +} + +/// Print whether the last reset was caused by the watchdog. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +fn report_reset_reason(uart: &EnabledUart) { + let mut buf = [0u8; 64]; + let n = watchdog::format_reset_reason(&mut buf, watchdog_caused_reboot()); + uart.write_full_blocking(&buf[..n]); +} + +/// Create the driver state, enable the hardware watchdog, and report. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// * `wd` - Mutable reference to the HAL watchdog. +/// +/// # Returns +/// +/// Initialised watchdog driver state. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +/// * `wd` - The `wd` parameter. +/// +/// # Returns +/// +/// A value of type `watchdog::WatchdogDriverState`. +fn start_watchdog(uart: &EnabledUart, wd: &mut hal::Watchdog) -> watchdog::WatchdogDriverState { + let mut state = watchdog::WatchdogDriverState::new(); state.enable(watchdog::DEFAULT_TIMEOUT_MS); + watchdog_enable(wd, watchdog::DEFAULT_TIMEOUT_MS); + let mut buf = [0u8; 64]; let n = watchdog::format_enabled(&mut buf, watchdog::DEFAULT_TIMEOUT_MS); + uart.write_full_blocking(&buf[..n]); state +} + diff --git a/drivers/0x0e_watchdog_rust/src/lib.rs b/drivers/0x0e_watchdog_rust/src/lib.rs new file mode 100644 index 0000000..7191961 --- /dev/null +++ b/drivers/0x0e_watchdog_rust/src/lib.rs @@ -0,0 +1,6 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +#![cfg_attr(not(test), no_std)] +pub mod watchdog; diff --git a/drivers/0x0e_watchdog_rust/src/main.rs b/drivers/0x0e_watchdog_rust/src/main.rs new file mode 100644 index 0000000..c422575 --- /dev/null +++ b/drivers/0x0e_watchdog_rust/src/main.rs @@ -0,0 +1,102 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; +// Watchdog driver module — suppress warnings for unused public API functions + +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the watchdog demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Watchdog Feed Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/drivers/0x0e_watchdog_rust/src/watchdog.rs b/drivers/0x0e_watchdog_rust/src/watchdog.rs new file mode 100644 index 0000000..9616bd4 --- /dev/null +++ b/drivers/0x0e_watchdog_rust/src/watchdog.rs @@ -0,0 +1,486 @@ +//! Implementation module +//! +//! **File:** `watchdog.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +/// Default watchdog timeout used in the C demo (3000 ms). +pub const DEFAULT_TIMEOUT_MS: u32 = 3_000; + +/// Feed interval used in the C demo (1000 ms). +pub const FEED_INTERVAL_MS: u32 = 1_000; + +/// Maximum hardware watchdog timeout in milliseconds (8388 ms). +pub const MAX_TIMEOUT_MS: u32 = 8_388; + +/// UART message for "Watchdog fed\r\n" matching the C demo output. +pub const WATCHDOG_FED_MSG: &[u8] = b"Watchdog fed\r\n"; + +/// UART message for watchdog reboot detection. +pub const REBOOT_MSG: &[u8] = b"System rebooted by watchdog timeout\r\n"; + +/// UART message for normal power-on reset. +pub const NORMAL_RESET_MSG: &[u8] = b"Normal power-on reset\r\n"; + +/// Pure-logic watchdog driver state. +/// +/// Tracks whether the watchdog has been enabled, its configured timeout, +/// and the cumulative number of feeds. The actual hardware watchdog +/// enable / feed / reboot-check calls live in `board.rs`. +pub struct WatchdogDriverState { + /// Whether the watchdog has been enabled. + enabled: bool, + /// Configured timeout in milliseconds. + timeout_ms: u32, + /// Number of times the watchdog has been fed. + feed_count: u32, +} + +impl WatchdogDriverState { + /// Create a new watchdog driver state (disabled, zero timeout). + /// + /// # Returns + /// + /// A `WatchdogDriverState` with the watchdog disabled. + /// + /// # Returns + /// + /// A new instance of the struct. + #[inline] + pub fn new() -> Self { + Self { enabled: false, timeout_ms: 0, feed_count: 0 } + } + + /// Enable the watchdog with the given timeout. + /// + /// If the watchdog is already enabled, this updates the timeout and + /// resets the feed count. + /// + /// # Arguments + /// + /// * `timeout_ms` - Timeout in milliseconds (1–8388). + /// + /// # Arguments + /// + /// * `timeout_ms` - The `timeout_ms` parameter. + #[inline] + pub fn enable(&mut self, timeout_ms: u32) { + self.enabled = true; self.timeout_ms = timeout_ms; self.feed_count = 0; + } + + /// Feed the watchdog, incrementing the feed counter. + /// + /// Returns `true` if the watchdog is enabled and the feed was + /// accepted, `false` if the watchdog is not enabled. + /// + /// # Returns + /// + /// `true` if the watchdog was fed, `false` otherwise. + /// + /// # Returns + /// + /// `true` if successful or set, `false` otherwise. + #[inline] + pub fn feed(&mut self) -> bool { + if self.enabled { self.feed_count += 1; true } else { false } + } + + /// Check whether the watchdog is currently enabled. + /// + /// # Returns + /// + /// `true` if the watchdog has been enabled. + /// + /// # Returns + /// + /// `true` if successful or set, `false` otherwise. + #[inline] + pub fn is_enabled(&self) -> bool { + self.enabled + } + + /// Return the configured timeout in milliseconds. + /// + /// # Returns + /// + /// The timeout value passed to `enable()`, or 0 if never enabled. + /// + /// # Returns + /// + /// A 32-bit unsigned integer value. + #[inline] + pub fn timeout_ms(&self) -> u32 { + self.timeout_ms + } + + /// Return the total number of times the watchdog has been fed. + /// + /// # Returns + /// + /// Cumulative feed count since the last `enable()`. + /// + /// # Returns + /// + /// A 32-bit unsigned integer value. + #[inline] + pub fn feed_count(&self) -> u32 { + self.feed_count + } +} + +/// Format the "Watchdog fed\r\n" message into `buf`. +/// +/// # Arguments +/// +/// * `buf` - Output buffer (must be >= 14 bytes). +/// +/// # Returns +/// +/// Number of bytes written. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_fed(buf: &mut [u8]) -> usize { + let n = WATCHDOG_FED_MSG.len().min(buf.len()); + buf[..n].copy_from_slice(&WATCHDOG_FED_MSG[..n]); n +} + +/// Format the reset-reason message into `buf`. +/// +/// If `caused_reboot` is `true`, writes `REBOOT_MSG`; otherwise writes +/// `NORMAL_RESET_MSG`. +/// +/// # Arguments +/// +/// * `buf` - Output buffer. +/// * `caused_reboot` - Whether the watchdog triggered the last reset. +/// +/// # Returns +/// +/// Number of bytes written. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `caused_reboot` - The `caused_reboot` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_reset_reason(buf: &mut [u8], caused_reboot: bool) -> usize { + let msg = if caused_reboot { REBOOT_MSG } else { NORMAL_RESET_MSG }; + let n = msg.len().min(buf.len()); buf[..n].copy_from_slice(&msg[..n]); n +} + +/// Format the "Watchdog enabled (XXXXs timeout). Feeding every 1s...\r\n" +/// banner message into `buf`. +/// +/// # Arguments +/// +/// * `buf` - Output buffer (should be >= 64 bytes). +/// * `timeout_ms` - Timeout in milliseconds. +/// +/// # Returns +/// +/// Number of bytes written. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `timeout_ms` - The `timeout_ms` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_enabled(buf: &mut [u8], timeout_ms: u32) -> usize { + let mut pos = copy_slice(buf, 0, b"Watchdog enabled ("); + pos += format_u32(&mut buf[pos..], timeout_ms / 1000); + pos += copy_slice(buf, pos, b"s timeout). Feeding every 1s...\r\n"); pos +} + +/// Copy a byte slice into `buf` at the given offset, returning bytes written. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `offset` - The `offset` parameter. +/// * `src` - The `src` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `offset` - The `offset` parameter. +/// * `src` - The `src` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn copy_slice(buf: &mut [u8], offset: usize, src: &[u8]) -> usize { + let n = src.len().min(buf.len().saturating_sub(offset)); + buf[offset..offset + n].copy_from_slice(&src[..n]); n +} + +/// Format zero into `buf`, returning 1 if the buffer is non-empty, else 0. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn format_zero(buf: &mut [u8]) -> usize { + if !buf.is_empty() { buf[0] = b'0'; 1 } else { 0 } +} + +/// Format a `u32` as decimal ASCII into `buf`. +/// +/// # Arguments +/// +/// * `buf` - Output buffer. +/// * `value` - The value to format. +/// +/// # Returns +/// +/// Number of bytes written. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_u32(buf: &mut [u8], value: u32) -> usize { + if value == 0 { return format_zero(buf); } + let mut tmp = [0u8; 10]; let n = u32_to_digits_reversed(&mut tmp, value); + reverse_copy(buf, &tmp, n); n +} + +/// Convert a u32 to reversed decimal digits in a temporary buffer. +/// +/// # Arguments +/// +/// * `tmp` - The `tmp` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `tmp` - The `tmp` parameter. +/// * `value` - Value to use. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn u32_to_digits_reversed(tmp: &mut [u8; 10], mut value: u32) -> usize { + let mut i = 0usize; + while value > 0 { tmp[i] = b'0' + (value % 10) as u8; value /= 10; i += 1; } + i +} + +/// Copy digits from a reversed temporary buffer into the output buffer. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `tmp` - The `tmp` parameter. +/// * `n` - Nibble or number. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `tmp` - The `tmp` parameter. +/// * `n` - Nibble or number. +#[inline] +fn reverse_copy(buf: &mut [u8], tmp: &[u8], n: usize) { + let count = n.min(buf.len()); + for j in 0..count { buf[j] = tmp[n - 1 - j]; } +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + + /// Executes the new state is disabled operation. + #[test] + fn new_state_is_disabled() { + let state = WatchdogDriverState::new(); + assert!(!state.is_enabled()); + assert_eq!(state.timeout_ms(), 0); + assert_eq!(state.feed_count(), 0); + } + + /// Executes the enable activates watchdog operation. + #[test] + fn enable_activates_watchdog() { + let mut state = WatchdogDriverState::new(); + state.enable(3000); + assert!(state.is_enabled()); + assert_eq!(state.timeout_ms(), 3000); + assert_eq!(state.feed_count(), 0); + } + + /// Executes the feed increments count operation. + #[test] + fn feed_increments_count() { + let mut state = WatchdogDriverState::new(); + state.enable(3000); + assert!(state.feed()); + assert_eq!(state.feed_count(), 1); + assert!(state.feed()); + assert_eq!(state.feed_count(), 2); + } + + /// Executes the feed returns false when disabled operation. + #[test] + fn feed_returns_false_when_disabled() { + let mut state = WatchdogDriverState::new(); + assert!(!state.feed()); + assert_eq!(state.feed_count(), 0); + } + + /// Executes the enable resets feed count operation. + #[test] + fn enable_resets_feed_count() { + let mut state = WatchdogDriverState::new(); + state.enable(3000); + state.feed(); + state.feed(); + assert_eq!(state.feed_count(), 2); + state.enable(5000); + assert_eq!(state.feed_count(), 0); + assert_eq!(state.timeout_ms(), 5000); + } + + /// Executes the default timeout matches c demo operation. + #[test] + fn default_timeout_matches_c_demo() { + assert_eq!(DEFAULT_TIMEOUT_MS, 3000); + } + + /// Executes the feed interval matches c demo operation. + #[test] + fn feed_interval_matches_c_demo() { + assert_eq!(FEED_INTERVAL_MS, 1000); + } + + /// Executes the max timeout is 8388 operation. + #[test] + fn max_timeout_is_8388() { + assert_eq!(MAX_TIMEOUT_MS, 8388); + } + + /// Executes the format fed matches c output operation. + #[test] + fn format_fed_matches_c_output() { + let mut buf = [0u8; 32]; + let n = format_fed(&mut buf); + assert_eq!(&buf[..n], b"Watchdog fed\r\n"); + } + + /// Executes the format reset reason watchdog operation. + #[test] + fn format_reset_reason_watchdog() { + let mut buf = [0u8; 64]; + let n = format_reset_reason(&mut buf, true); + assert_eq!(&buf[..n], b"System rebooted by watchdog timeout\r\n"); + } + + /// Executes the format reset reason normal operation. + #[test] + fn format_reset_reason_normal() { + let mut buf = [0u8; 64]; + let n = format_reset_reason(&mut buf, false); + assert_eq!(&buf[..n], b"Normal power-on reset\r\n"); + } + + /// Executes the format enabled 3s operation. + #[test] + fn format_enabled_3s() { + let mut buf = [0u8; 64]; + let n = format_enabled(&mut buf, 3000); + assert_eq!( + &buf[..n], + b"Watchdog enabled (3s timeout). Feeding every 1s...\r\n" + ); + } + + /// Executes the format enabled 5s operation. + #[test] + fn format_enabled_5s() { + let mut buf = [0u8; 64]; + let n = format_enabled(&mut buf, 5000); + assert_eq!( + &buf[..n], + b"Watchdog enabled (5s timeout). Feeding every 1s...\r\n" + ); + } + + /// Executes the format u32 zero operation. + #[test] + fn format_u32_zero() { + let mut buf = [0u8; 16]; + let n = format_u32(&mut buf, 0); + assert_eq!(&buf[..n], b"0"); + } + + /// Executes the format u32 large value operation. + #[test] + fn format_u32_large_value() { + let mut buf = [0u8; 16]; + let n = format_u32(&mut buf, 12345); + assert_eq!(&buf[..n], b"12345"); + } +} diff --git a/drivers/0x0f_flash_rust/.cargo/config.toml b/drivers/0x0f_flash_rust/.cargo/config.toml new file mode 100644 index 0000000..70fb5fa --- /dev/null +++ b/drivers/0x0f_flash_rust/.cargo/config.toml @@ -0,0 +1,32 @@ +[build] +target = "thumbv8m.main-none-eabihf" + +[target.thumbv6m-none-eabi] +linker = "flip-link" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "no-vectorize-loops", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[target.thumbv8m.main-none-eabihf] +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[target.riscv32imac-unknown-none-elf] +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp2350_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] +runner = "${PICOTOOL_PATH} load -u -v -x -t elf" + +[env] +DEFMT_LOG = "debug" diff --git a/drivers/0x0f_flash_rust/.pico-rs b/drivers/0x0f_flash_rust/.pico-rs new file mode 100644 index 0000000..be06904 --- /dev/null +++ b/drivers/0x0f_flash_rust/.pico-rs @@ -0,0 +1 @@ +rp2350 diff --git a/drivers/0x0f_flash_rust/.vscode/extensions.json b/drivers/0x0f_flash_rust/.vscode/extensions.json new file mode 100644 index 0000000..107cc34 --- /dev/null +++ b/drivers/0x0f_flash_rust/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "rust-lang.rust-analyzer", + "probe-rs.probe-rs-debugger", + "raspberry-pi.raspberry-pi-pico" + ] +} diff --git a/drivers/0x0f_flash_rust/.vscode/launch.json b/drivers/0x0f_flash_rust/.vscode/launch.json new file mode 100644 index 0000000..34833c2 --- /dev/null +++ b/drivers/0x0f_flash_rust/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (probe-rs)", + "cwd": "${workspaceFolder}", + "request": "launch", + "type": "probe-rs-debug", + "connectUnderReset": false, + "speed": 5000, + "runtimeExecutable": "probe-rs", + "chip": "${command:raspberry-pi-pico.getChip}", + "runtimeArgs": [ + "dap-server" + ], + "flashingConfig": { + "flashingEnabled": true, + "haltAfterReset": false + }, + "coreConfigs": [ + { + "coreIndex": 0, + "programBinary": "${command:raspberry-pi-pico.launchTargetPath}", + "rttEnabled": true, + "svdFile": "${command:raspberry-pi-pico.getSVDPath}", + "rttChannelFormats": [ + { + "channelNumber": 0, + "dataFormat": "Defmt", + "mode": "NoBlockSkip", + "showTimestamps": true + } + ] + } + ], + "preLaunchTask": "Build + Generate SBOM (debug)", + "consoleLogLevel": "Debug", + "wireProtocol": "Swd" + } + ] +} diff --git a/drivers/0x0f_flash_rust/.vscode/settings.json b/drivers/0x0f_flash_rust/.vscode/settings.json new file mode 100644 index 0000000..3744e1f --- /dev/null +++ b/drivers/0x0f_flash_rust/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", + "rust-analyzer.check.allTargets": false, + "editor.formatOnSave": true, + "files.exclude": { + ".pico-rs": true + } +} diff --git a/drivers/0x0f_flash_rust/.vscode/tasks.json b/drivers/0x0f_flash_rust/.vscode/tasks.json new file mode 100644 index 0000000..d288f27 --- /dev/null +++ b/drivers/0x0f_flash_rust/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile Project", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build", + "--release" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (release)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathRelease}" + ] + }, + "dependsOn": "Compile Project", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Compile Project (debug)", + "type": "process", + "isBuildCommand": true, + "command": "cargo", + "args": [ + "build" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": "$rustc", + "options": { + "env": { + "PICOTOOL_PATH": "${command:raspberry-pi-pico.getPicotoolPath}", + "CHIP": "${command:raspberry-pi-pico.getChip}" + } + } + }, + { + "label": "Build + Generate SBOM (debug)", + "type": "shell", + "command": "bash", + "args": [ + "-lc", + "cargo sbom > ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "cargo sbom | Set-Content -Encoding utf8 ${command:raspberry-pi-pico.sbomTargetPathDebug}" + ] + }, + "dependsOn": "Compile Project (debug)", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Project", + "type": "shell", + "dependsOn": [ + "Build + Generate SBOM (release)" + ], + "command": "${command:raspberry-pi-pico.getPicotoolPath}", + "args": [ + "load", + "-x", + "${command:raspberry-pi-pico.launchTargetPathRelease}", + "-t", + "elf" + ], + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + } + ] +} diff --git a/drivers/0x0f_flash_rust/Cargo.lock b/drivers/0x0f_flash_rust/Cargo.lock new file mode 100644 index 0000000..617a31a --- /dev/null +++ b/drivers/0x0f_flash_rust/Cargo.lock @@ -0,0 +1,704 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield 0.13.2", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "crc-any" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62ec9ff5f7965e4d7280bd5482acd20aadb50d632cf6c1d74493856b011fa73" +dependencies = [ + "debug-helper", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "debug-helper" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" +dependencies = [ + "critical-section", + "defmt", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "flash" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt", + "defmt-rtt", + "fugit", + "panic-halt", + "panic-probe", + "rp2040-boot2", + "rp2040-hal", + "rp235x-hal", +] + +[[package]] +name = "frunk" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28aef0f9aa070bce60767c12ba9cb41efeaf1a2bc6427f87b7d83f11239a16d7" +dependencies = [ + "frunk_core", + "frunk_derives", +] + +[[package]] +name = "frunk_core" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476eeaa382e3462b84da5d6ba3da97b5786823c2d0d3a0d04ef088d073da225c" + +[[package]] +name = "frunk_derives" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b4095fc99e1d858e5b8c7125d2638372ec85aa0fe6c807105cf10b0265ca6c" +dependencies = [ + "frunk_proc_macro_helpers", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "frunk_proc_macro_helpers" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1952b802269f2db12ab7c0bd328d0ae8feaabf19f352a7b0af7bb0c5693abfce" +dependencies = [ + "frunk_core", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fugit" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e639847d312d9a82d2e75b0edcc1e934efcc64e6cb7aa94f0b1fbec0bc231d6" +dependencies = [ + "gcd", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "panic-halt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11" + +[[package]] +name = "panic-probe" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" +dependencies = [ + "cortex-m", + "defmt", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" +dependencies = [ + "arrayvec", + "num_enum", + "paste", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "riscv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f5c1b8bf41ea746266cdee443d1d1e9125c86ce1447e1a2615abd34330d33a9" +dependencies = [ + "critical-section", + "embedded-hal 1.0.0", +] + +[[package]] +name = "riscv-rt" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d35e32cf1383183e8885d8a9aa4402a087fd094dc34c2cb6df6687d0229dfe" +dependencies = [ + "riscv", + "riscv-rt-macros", +] + +[[package]] +name = "riscv-rt-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30f19a85fe107b65031e0ba8ec60c34c2494069fe910d6c297f5e7cb5a6f76d0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rp-binary-info" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f582261945fa215d40e2988b4df595d11c0908c0fff97a0fe23df766d117b790" + +[[package]] +name = "rp-hal-common" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8288358786b1458fb2caac8c4b40fb529ef4200d6c46467e2695b7a8ba573ae8" +dependencies = [ + "fugit", +] + +[[package]] +name = "rp2040-boot2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" +dependencies = [ + "crc-any", +] + +[[package]] +name = "rp2040-hal" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb79a4590775204387f334672e6f79c0d734d0a159da23d60677b3c10fa1245" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "itertools 0.10.5", + "nb 1.1.0", + "paste", + "pio", + "rand_core", + "rp-binary-info", + "rp-hal-common", + "rp2040-hal-macros", + "rp2040-pac", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp2040-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86479063e497efe1ae81995ef9071f54fd1c7427e04d6c5b84cde545ff672a5e" +dependencies = [ + "cortex-m-rt", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rp2040-pac" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83cbcd3f7a0ca7bbe61dc4eb7e202842bee4e27b769a7bf3a4a72fa399d6e404" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "vcell", +] + +[[package]] +name = "rp235x-hal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2939c82776b0b4ae110168b4298b5adf831e6cff249b057bf2a2187453b959c" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "cortex-m-rt", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "gcd", + "itertools 0.13.0", + "nb 1.1.0", + "paste", + "pio", + "rand_core", + "riscv", + "riscv-rt", + "rp-binary-info", + "rp-hal-common", + "rp235x-hal-macros", + "rp235x-pac", + "sha2-const-stable", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp235x-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74edd7a5979e9763bbb98e9746e711bac7464ee3397af7288e6c288ff0d3c764" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rp235x-pac" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffcb6931deee4242886b5a1df62db5e2555b0eb6ae1e8be101f3ea3e58e65c6" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "vcell", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] diff --git a/drivers/0x0f_flash_rust/Cargo.toml b/drivers/0x0f_flash_rust/Cargo.toml new file mode 100644 index 0000000..7313d71 --- /dev/null +++ b/drivers/0x0f_flash_rust/Cargo.toml @@ -0,0 +1,79 @@ +[package] +edition = "2024" +name = "flash" +version = "0.1.0" +license = "MIT or Apache-2.0" + +[lib] +name = "flash_lib" +path = "src/lib.rs" + +[[bin]] +name = "flash" +path = "src/main.rs" + + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +fugit = "0.3" +defmt = "1" +defmt-rtt = "1" + +[target.'cfg( target_arch = "arm" )'.dependencies] +panic-probe = { version = "1", features = ["print-defmt"] } + +[target.'cfg( target_arch = "riscv32" )'.dependencies] +panic-halt = { version = "1.0.0" } + +[target.thumbv6m-none-eabi.dependencies] +rp2040-boot2 = "0.3" +rp2040-hal = { version = "0.11", features = ["rt", "critical-section-impl"] } + +[target.riscv32imac-unknown-none-elf.dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +[target."thumbv8m.main-none-eabihf".dependencies] +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } + +# cargo build/run +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo build/run --release +[profile.release] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 2 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +[profile.release.build-override] +debug = false +debug-assertions = false +overflow-checks = false +opt-level = 0 + +# cargo test +[profile.test] +debug = 2 +debug-assertions = true +opt-level = 2 +overflow-checks = true + +# cargo test --release +[profile.bench] +debug = 2 +debug-assertions = false +lto = "fat" +opt-level = 3 diff --git a/drivers/0x0f_flash_rust/README.md b/drivers/0x0f_flash_rust/README.md new file mode 100644 index 0000000..42dab39 --- /dev/null +++ b/drivers/0x0f_flash_rust/README.md @@ -0,0 +1,112 @@ +# 0x0f Flash Rust Driver + +This repository contains a Bare-Metal Rust driver demonstrating **Hardware Flash Memory** access (reading, erasing, and writing) on the **RP2350** (and RP2040) microcontrollers. + +It includes: +- A demo (`src/main.rs`) that erases a sector of flash, writes a message into it, reads the message back out of flash, and prints it over UART. +- A reusable library module (`src/flash.rs`) providing a hardware-agnostic `flash_lib` containing constants, buffer preparation, and formatting helpers. +- Board initialization logic (`src/board.rs`). + +## 🚀 Getting Started from Scratch + +If you're starting with a fresh machine, follow these exact steps to install the toolchain, build the code, and flash it to your microcontroller. + +### 1. Install Rust +First, install `rustup` (the Rust toolchain installer) if you haven't already: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +*Note: Restart your terminal or run `source $HOME/.cargo/env` after this finishes.* + +Ensure your Rust compiler is up to date: +```bash +rustup update +``` + +### 2. Install the Target Architecture +This project is configured for the **RP2350** (ARM Cortex-M33). We need to install the cross-compilation target for it: +```bash +rustup target add thumbv8m.main-none-eabihf +``` +*(If you were targeting the RP2040, you would use `thumbv6m-none-eabi` instead).* + +### 3. Install Build Tools +You will need a few extra tools to help link and format the firmware for the RP-series chips. + +Install `flip-link` (adds zero-cost stack overflow protection): +```bash +cargo install flip-link +``` + +Install `picotool` (used by `cargo run` to flash the chip): +- **macOS:** `brew install picotool` +- **Linux/Windows:** Follow the official Raspberry Pi documentation to install `picotool` or build it from source. + +### 4. Building the Code +To compile the code for the microcontroller, simply run: +```bash +cargo build +``` + +To build a highly optimized release version (smaller and faster): +```bash +cargo build --release +``` + +### 5. Flashing to the Microcontroller +This project is pre-configured in `.cargo/config.toml` to use `picotool` as the custom runner. + +To flash the code: +1. Hold down the **BOOTSEL** button on your RP2350 board. +2. Plug it into your computer via USB (or press the RUN/RESET button while holding BOOTSEL). +3. Run the following command: + +```bash +cargo run --release +``` +*`cargo` will compile the code and automatically use `picotool` to upload the `.elf` file directly to your board and start executing it!* + +### 6. Testing on the Host +Because the data manipulation and string formatting logic is separated into a reusable library without touching hardware registers, you can run the unit tests natively on your computer! + +However, because this project sets a default bare-metal target (`thumbv8m.main-none-eabihf`) in `.cargo/config.toml`, running a plain `cargo test` will fail because the standard library doesn't exist on the microcontroller. You must explicitly tell Cargo to compile the tests for your host computer's processor architecture: + +**Mac (Apple Silicon):** +```bash +cargo test --lib --target aarch64-apple-darwin +``` + +**Linux (Intel/AMD 64-bit):** +```bash +cargo test --lib --target x86_64-unknown-linux-gnu +``` + +**Windows (64-bit):** +```bash +cargo test --lib --target x86_64-pc-windows-msvc +``` + +## 🧠 Code Walkthrough + +This section explains exactly how the code works, where the entry point is, and traces the flow of execution as if you were stepping through it line-by-line. + +### 1. The Entry Point (`src/main.rs`) +Unlike a standard computer program, bare-metal microcontrollers do not have an operating system to call `main()`. Instead, we use the `#[entry]` macro from the HAL (Hardware Abstraction Layer) to define the very first function that runs after the chip boots up. + +* **`main() -> !`**: This is the absolute start of our code. It takes ownership of all the hardware peripherals (`hal::pac::Peripherals::take().unwrap()`) and immediately passes them into `board::run(...)`. The `-> !` means this function never returns (because embedded devices run in an infinite loop). + +### 2. Board Initialization (`src/board.rs`) +Once execution enters `board.rs`, we initialize the system clocks, pins, UART (for logging), and interact with the hardware Flash peripheral. + +* **`run(...)`**: The master setup function. Initializes the clocks and UART, then runs the `flash_demo(...)`. After the demo finishes, it parks the CPU in a low-power `wfe` (wait for event) infinite loop. +* **`flash_demo(...)`**: Orchestrates the core flash workflow. It prepares a write buffer in RAM (filling it with `0xFF` and inserting a demo string), calls `flash_write(...)` to program it to the last sector of flash memory, reads the data back into a `read_buf`, and then formats/prints the result. +* **`flash_write(...)`**: A high-level wrapper that ensures flash modification is safe. It wraps the low-level flash operations in an interrupt-free critical section (`cortex_m::interrupt::free`) to prevent the CPU from trying to fetch instructions from flash while it is being erased or programmed. +* **`flash_prepare_sector(...)` / `flash_program_and_restore(...)`**: Low-level, `unsafe` functions that call directly into the RP2040/RP2350 BootROM routines. They disconnect flash from the XIP (eXecute In Place) cache, erase the target sector, program the new data, flush the hardware cache, and then safely restore XIP mode so the CPU can resume executing code from flash. +* **`flash_read(...)`**: Since the flash memory on the RP2350/RP2040 is mapped directly into the CPU's memory address space (XIP), reading from flash is as simple as performing a standard pointer read from `XIP_BASE + offset`. + +### 3. The Reusable Flash Library (`src/flash.rs`) +While `board.rs` directly manipulates the flash via BootROM calls, `flash.rs` defines the memory layout constants and handles buffer manipulation and string formatting for logging. + +* **Constants**: Defines `FLASH_SIZE_BYTES`, `FLASH_SECTOR_SIZE`, `FLASH_PAGE_SIZE`, and `XIP_BASE`, keeping magic numbers out of the application code. +* **`prepare_write_buf(...)`**: Fills a provided buffer with `0xFF` (representing the erased state of flash memory) and then copies our `DEMO_MSG` into the start of the buffer. +* **`format_readback(...)`**: Extracts the NUL-terminated C-style string from the raw flash read buffer and formats it nicely for printing over UART. diff --git a/drivers/0x0f_flash_rust/build.rs b/drivers/0x0f_flash_rust/build.rs new file mode 100644 index 0000000..58adfcc --- /dev/null +++ b/drivers/0x0f_flash_rust/build.rs @@ -0,0 +1,105 @@ +//! Implementation module +//! +//! **File:** `build.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Import dependencies from std::fs +use std::fs::{read_to_string, File}; +// Import std::io::Write +use std::io::Write; +// Import std::path::{Path, PathBuf} +use std::path::{Path, PathBuf}; + +/// The main entry point. +fn main() { + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + let c = read_to_string(".pico-rs").unwrap_or_default().trim().to_lowercase(); + setup_target(&c, &out); + write_riscv(&out); + print_cfgs(&out); +} + +/// Executes the setup target operation. +/// +/// # Arguments +/// +/// * `c` - Target string from config. +/// * `out` - Output path. +fn setup_target(c: &str, out: &Path) { + if c == "rp2040" { + write_rp2040(out); + } else { + write_rp2350(out); + } +} + +/// Executes the write rp2040 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2040(out: &Path) { + let b = include_bytes!("rp2040.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2040"); + println!("cargo:rerun-if-changed=rp2040.x"); +} + +/// Executes the write rp2350 operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_rp2350(out: &Path) { + let b = include_bytes!("rp2350.x"); + File::create(out.join("memory.x")).unwrap().write_all(b).unwrap(); + println!("cargo::rustc-cfg=rp2350"); + println!("cargo:rerun-if-changed=rp2350.x"); +} + +/// Executes the write riscv operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn write_riscv(out: &Path) { + let b = include_bytes!("rp2350_riscv.x"); + File::create(out.join("rp2350_riscv.x")).unwrap().write_all(b).unwrap(); +} + +/// Executes the print cfgs operation. +/// +/// # Arguments +/// +/// * `out` - Output path. +fn print_cfgs(out: &Path) { + println!("cargo::rustc-check-cfg=cfg(rp2040)"); + println!("cargo::rustc-check-cfg=cfg(rp2350)"); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=.pico-rs"); + println!("cargo:rerun-if-changed=rp2350_riscv.x"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/drivers/0x0f_flash_rust/rp2040.x b/drivers/0x0f_flash_rust/rp2040.x new file mode 100644 index 0000000..6e1a654 --- /dev/null +++ b/drivers/0x0f_flash_rust/rp2040.x @@ -0,0 +1,44 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K + SRAM4 : ORIGIN = 0x20040000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20041000, LENGTH = 4k +} + +EXTERN(BOOT2_FIRMWARE) + +SECTIONS { + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} INSERT BEFORE .text; + +SECTIONS { + .boot_info : ALIGN(4) + { + KEEP(*(.boot_info)); + } > FLASH + +} INSERT AFTER .vector_table; + +_stext = ADDR(.boot_info) + SIZEOF(.boot_info); + +SECTIONS { + .bi_entries : ALIGN(4) + { + __bi_entries_start = .; + KEEP(*(.bi_entries)); + . = ALIGN(4); + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; \ No newline at end of file diff --git a/drivers/0x0f_flash_rust/rp2350.x b/drivers/0x0f_flash_rust/rp2350.x new file mode 100644 index 0000000..570f72c --- /dev/null +++ b/drivers/0x0f_flash_rust/rp2350.x @@ -0,0 +1,47 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + RAM : ORIGIN = 0x20000000, LENGTH = 512K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K + } + + SECTIONS { + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + + } INSERT AFTER .vector_table; + + _stext = ADDR(.start_block) + SIZEOF(.start_block); + + SECTIONS { + .bi_entries : ALIGN(4) + { + __bi_entries_start = .; + KEEP(*(.bi_entries)); + . = ALIGN(4); + __bi_entries_end = .; + } > FLASH + } INSERT AFTER .text; + + SECTIONS { + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + } INSERT AFTER .uninit; + + PROVIDE(start_to_end = __end_block_addr - __start_block_addr); + PROVIDE(end_to_start = __start_block_addr - __end_block_addr); diff --git a/drivers/0x0f_flash_rust/rp2350_riscv.x b/drivers/0x0f_flash_rust/rp2350_riscv.x new file mode 100644 index 0000000..2c9b445 --- /dev/null +++ b/drivers/0x0f_flash_rust/rp2350_riscv.x @@ -0,0 +1,58 @@ +/* +* SPDX-License-Identifier: MIT OR Apache-2.0 +* +* Copyright (c) 2021-2024 The rp-rs Developers +* Copyright (c) 2021 rp-rs organization +* Copyright (c) 2025 Raspberry Pi Ltd. +*/ + +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + RAM : ORIGIN = 0x20000000, LENGTH = 512K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +PROVIDE(__pre_init = default_pre_init); +PROVIDE(_setup_interrupts = default_setup_interrupts); +PROVIDE(_mp_hook = default_mp_hook); +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + . = ABSOLUTE(_stext); + } > FLASH +} diff --git a/drivers/0x0f_flash_rust/src/board.rs b/drivers/0x0f_flash_rust/src/board.rs new file mode 100644 index 0000000..8a561a3 --- /dev/null +++ b/drivers/0x0f_flash_rust/src/board.rs @@ -0,0 +1,285 @@ +//! Implementation module +//! +//! **File:** `board.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +// Flash driver pure-logic functions and constants +use flash_lib::flash; +// Rate extension trait for .Hz() baud rate construction +use fugit::RateExtU32; +// Clock trait for accessing system clock frequency +use hal::Clock; +// GPIO pin types and function selectors +use hal::gpio::{FunctionNull, FunctionUart, Pin, PullDown, PullNone}; +// ROM data flash functions for low-level flash operations +use hal::rom_data; +// UART configuration and peripheral types +use hal::uart::{DataBits, Enabled, StopBits, UartConfig, UartPeripheral}; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// External crystal frequency in Hz (12 MHz). +pub(crate) const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// UART baud rate in bits per second. +pub(crate) const UART_BAUD: u32 = 115_200; + +/// Type alias for the configured TX pin (GPIO 0, UART function, no pull). +pub(crate) type TxPin = Pin; + +/// Type alias for the configured RX pin (GPIO 1, UART function, no pull). +pub(crate) type RxPin = Pin; + +/// Type alias for the default TX pin state from `Pins::new()`. +pub(crate) type TxPinDefault = Pin; + +/// Type alias for the default RX pin state from `Pins::new()`. +pub(crate) type RxPinDefault = Pin; + +/// Type alias for the fully-enabled UART0 peripheral with TX/RX p. +pub(crate) type EnabledUart = UartPeripheral; + +/// Initialise system clocks and PLLs from the external 12 MHz crystal. +/// +/// # Arguments +/// +/// * `xosc` - XOSC peripheral singleton. +/// * `clocks` - CLOCKS peripheral singleton. +/// * `pll_sys` - PLL_SYS peripheral singleton. +/// * `pll_usb` - PLL_USB peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `watchdog` - Mutable reference to the watchdog timer. +/// +/// # Returns +/// +/// Configured clocks manager. +/// +/// # Panics +/// +/// Panics if clock initialisation fails. +pub(crate) fn init_clocks( + xosc: hal::pac::XOSC, clocks: hal::pac::CLOCKS, pll_sys: hal::pac::PLL_SYS, pll_usb: hal::pac::PLL_USB, + resets: &mut hal::pac::RESETS, watchdog: &mut hal::Watchdog, +) -> hal::clocks::ClocksManager { + hal::clocks::init_clocks_and_plls(XTAL_FREQ_HZ, xosc, clocks, pll_sys, pll_usb, resets, watchdog).unwrap() +} + +/// Unlock the GPIO bank and return the pin set. +/// +/// # Arguments +/// +/// * `io_bank0` - IO_BANK0 peripheral singleton. +/// * `pads_bank0` - PADS_BANK0 peripheral singleton. +/// * `sio` - SIO peripheral singleton. +/// * `resets` - Mutable reference to the RESETS peripheral. +/// +/// # Returns +/// +/// GPIO pin set for the entire bank. +pub(crate) fn init_pins( + io: hal::pac::IO_BANK0, pads: hal::pac::PADS_BANK0, sio: hal::pac::SIO, rst: &mut hal::pac::RESETS +) -> hal::gpio::Pins { + hal::gpio::Pins::new(io, pads, hal::Sio::new(sio).gpio_bank0, rst) +} + +/// Initialise UART0 for serial output (stdio equivalent). +/// +/// # Arguments +/// +/// * `uart0` - PAC UART0 peripheral singleton. +/// * `tx_pin` - GPIO pin to use as UART0 TX (GPIO 0). +/// * `rx_pin` - GPIO pin to use as UART0 RX (GPIO 1). +/// * `resets` - Mutable reference to the RESETS peripheral. +/// * `clocks` - Reference to the initialised clock configuration. +/// +/// # Returns +/// +/// Enabled UART0 peripheral ready for blocking writes. +/// +/// # Panics +/// +/// Panics if the HAL cannot achieve the requested baud rate. +pub(crate) fn init_uart( + uart0: hal::pac::UART0, tx: TxPinDefault, rx: RxPinDefault, rst: &mut hal::pac::RESETS, c: &hal::clocks::ClocksManager +) -> EnabledUart { + let pins = (tx.reconfigure::(), rx.reconfigure::()); + let cfg = UartConfig::new(UART_BAUD.Hz(), DataBits::Eight, None, StopBits::One); + UartPeripheral::new(uart0, pins, rst).enable(cfg, c.peripheral_clock.freq()).unwrap() +} + +/// Transition flash out of XIP mode and erase the target sector. +/// +/// # Safety +/// +/// Must be called with interrupts disabled and no other flash access active. +/// +/// # Arguments +/// +/// * `flash_offset` - The `flash_offset` parameter. +/// +/// # Arguments +/// +/// * `flash_offset` - The `flash_offset` parameter. +unsafe fn flash_prepare_sector(flash_offset: u32) { + unsafe { + rom_data::connect_internal_flash(); rom_data::flash_exit_xip(); + rom_data::flash_range_erase(flash_offset, flash::FLASH_SECTOR_SIZE as usize, flash::FLASH_SECTOR_SIZE, 0x20); + } +} + +/// Program data into the erased sector and restore XIP mode. +/// +/// # Safety +/// +/// Must be called with interrupts disabled and no other flash access active. +/// +/// # Arguments +/// +/// * `flash_offset` - The `flash_offset` parameter. +/// * `data` - Data to send/write. +/// +/// # Arguments +/// +/// * `flash_offset` - The `flash_offset` parameter. +/// * `data` - Data to send/write. +unsafe fn flash_program_and_restore(flash_offset: u32, data: &[u8]) { + unsafe { + rom_data::flash_range_program(flash_offset, data.as_ptr(), data.len()); + rom_data::flash_flush_cache(); rom_data::flash_enter_cmd_xip(); + } +} + +/// Erase one 4096-byte sector and write data to on-chip flash. +/// +/// Disables interrupts, transitions the flash device out of XIP mode, +/// erases the sector at `flash_offset`, programs `data` into it, flushes +/// the cache, re-enters XIP mode, and restores interrupts. This mirrors +/// the C demo's `flash_driver_write()`. +/// +/// # Arguments +/// +/// * `flash_offset` - Byte offset from the start of flash (must be sector-aligned). +/// * `data` - Data buffer to write (length must be a multiple of 256). +/// +/// # Safety +/// +/// Caller must ensure no other core or DMA is accessing flash/XIP during +/// this operation. +/// +/// # Arguments +/// +/// * `flash_offset` - The `flash_offset` parameter. +/// * `data` - Data to send/write. +pub(crate) fn flash_write(flash_offset: u32, data: &[u8]) { + cortex_m::interrupt::free(|_| unsafe { + flash_prepare_sector(flash_offset); + flash_program_and_restore(flash_offset, data); + }); +} + +/// Read bytes from on-chip flash via the XIP memory map. +/// +/// Flash is memory-mapped starting at XIP_BASE (0x10000000). This +/// function copies `out.len()` bytes from the XIP address corresponding +/// to `flash_offset` into `out`, matching the C demo's +/// `flash_driver_read()`. +/// +/// # Arguments +/// +/// * `flash_offset` - Byte offset from the start of flash. +/// * `out` - Destination buffer. +/// +/// # Arguments +/// +/// * `flash_offset` - The `flash_offset` parameter. +/// * `out` - The `out` parameter. +pub(crate) fn flash_read(flash_offset: u32, out: &mut [u8]) { + let addr = (flash::XIP_BASE + flash_offset) as *const u8; + for (i, byte) in out.iter_mut().enumerate() { *byte = unsafe { core::ptr::read_volatile(addr.add(i)) }; } +} + +/// Initialise all peripherals and run the flash demo. +/// +/// # Arguments +/// +/// * `pac` - PAC Peripherals singleton (consumed). +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Arguments +/// +/// * `pac` - The `pac` parameter. +/// +/// # Returns +/// +/// A value of type `!`. +pub(crate) fn run(mut p: hal::pac::Peripherals) -> ! { + let c = init_clocks(p.XOSC, p.CLOCKS, p.PLL_SYS, p.PLL_USB, &mut p.RESETS, &mut hal::Watchdog::new(p.WATCHDOG)); + let pins = init_pins(p.IO_BANK0, p.PADS_BANK0, p.SIO, &mut p.RESETS); + let uart = init_uart(p.UART0, pins.gpio0, pins.gpio1, &mut p.RESETS, &c); + flash_demo(&uart); + loop { cortex_m::asm::wfe(); } +} + +/// Execute the flash write / read-back / report sequence. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +fn flash_demo(uart: &EnabledUart) { + let mut write_buf = [0u8; flash::FLASH_WRITE_LEN]; flash::prepare_write_buf(&mut write_buf); + flash_write(flash::FLASH_TARGET_OFFSET, &write_buf); + let mut read_buf = [0u8; flash::FLASH_WRITE_LEN]; flash_read(flash::FLASH_TARGET_OFFSET, &mut read_buf); + report_readback(uart, &read_buf); +} + +/// Format and print the flash read-back result over UART. +/// +/// # Arguments +/// +/// * `uart` - Reference to the enabled UART peripheral for serial output. +/// * `read_buf` - Buffer containing the data read back from flash. +/// +/// # Arguments +/// +/// * `uart` - The `uart` parameter. +/// * `read_buf` - The `read_buf` parameter. +fn report_readback(uart: &EnabledUart, read_buf: &[u8]) { + let mut out = [0u8; 128]; let n = flash::format_readback(&mut out, read_buf); + uart.write_full_blocking(&out[..n]); +} + diff --git a/drivers/0x0f_flash_rust/src/flash.rs b/drivers/0x0f_flash_rust/src/flash.rs new file mode 100644 index 0000000..883c50f --- /dev/null +++ b/drivers/0x0f_flash_rust/src/flash.rs @@ -0,0 +1,301 @@ +//! Implementation module +//! +//! **File:** `flash.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +/// Total on-chip flash size in bytes (4 MB). +pub const FLASH_SIZE_BYTES: u32 = 4 * 1024 * 1024; + +/// Flash sector size in bytes (4096). +pub const FLASH_SECTOR_SIZE: u32 = 4096; + +/// Flash page size in bytes (256). +pub const FLASH_PAGE_SIZE: u32 = 256; + +/// Flash target offset: last sector of flash. +pub const FLASH_TARGET_OFFSET: u32 = FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE; + +/// Write buffer length matching the C demo (one page = 256 bytes). +pub const FLASH_WRITE_LEN: usize = FLASH_PAGE_SIZE as usize; + +/// XIP base address where flash is memory-mapped. +pub const XIP_BASE: u32 = 0x1000_0000; + +/// Demo string written to flash, matching the C demo exactly. +pub const DEMO_MSG: &[u8] = b"Embedded Hacking flash driver demo"; + +/// Fill the entire buffer with 0xFF (erased flash state). +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +#[inline] +fn fill_erased(buf: &mut [u8]) { + for b in buf.iter_mut() { *b = 0xFF; } +} + +/// Copy the demo string and NUL terminator into `buf`, returning meaningful length. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn copy_demo_msg(buf: &mut [u8]) -> usize { + let (n, len) = ((DEMO_MSG.len() + 1).min(buf.len()), DEMO_MSG.len()); + let copy_len = len.min(n); buf[..copy_len].copy_from_slice(&DEMO_MSG[..copy_len]); + if copy_len < n { buf[copy_len] = 0x00; } + n +} + +/// Prepare a write buffer with 0xFF fill and the demo string at the start. +/// +/// Mirrors the C demo's `_prepare_write_buf()` function. The buffer is +/// filled with 0xFF (erased flash state), then the demo string including +/// its NUL terminator is copied to the beginning. +/// +/// # Arguments +/// +/// * `buf` - Output buffer (should be `FLASH_WRITE_LEN` bytes). +/// +/// # Returns +/// +/// Number of meaningful bytes (string length + NUL terminator). +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn prepare_write_buf(buf: &mut [u8]) -> usize { + fill_erased(buf); copy_demo_msg(buf) +} + +/// Format the "Flash readback: \r\n" message into `buf`. +/// +/// Reads from `read_data` up to the first NUL byte (or end of slice) +/// and formats the output message matching the C demo's printf. +/// +/// # Arguments +/// +/// * `buf` - Output buffer (should be >= 64 bytes). +/// * `read_data` - Data read back from flash. +/// +/// # Returns +/// +/// Number of bytes written to `buf`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `read_data` - The `read_data` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +pub fn format_readback(buf: &mut [u8], read_data: &[u8]) -> usize { + let mut pos = copy_slice(buf, 0, b"Flash readback: "); + pos += copy_c_string(&mut buf[pos..], read_data); + pos += copy_slice(buf, pos, b"\r\n"); pos +} + +/// Copy a byte slice into `buf` at the given offset, returning bytes written. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `offset` - The `offset` parameter. +/// * `src` - The `src` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `offset` - The `offset` parameter. +/// * `src` - The `src` parameter. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn copy_slice(buf: &mut [u8], offset: usize, src: &[u8]) -> usize { + let n = src.len().min(buf.len().saturating_sub(offset)); + buf[offset..offset + n].copy_from_slice(&src[..n]); n +} + +/// Copy bytes from `data` up to the first NUL into `buf`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `data` - Data to send/write. +/// +/// # Returns +/// +/// A value of type `usize`. +/// +/// # Arguments +/// +/// * `buf` - The `buf` parameter. +/// * `data` - Data to send/write. +/// +/// # Returns +/// +/// A value of type `usize`. +#[inline] +fn copy_c_string(buf: &mut [u8], data: &[u8]) -> usize { + let n = data.iter().position(|&b| b == 0).unwrap_or(data.len()).min(buf.len()); + buf[..n].copy_from_slice(&data[..n]); n +} + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; + + /// Executes the flash size is 4mb operation. + #[test] + fn flash_size_is_4mb() { + assert_eq!(FLASH_SIZE_BYTES, 4 * 1024 * 1024); + } + + /// Executes the sector size is 4096 operation. + #[test] + fn sector_size_is_4096() { + assert_eq!(FLASH_SECTOR_SIZE, 4096); + } + + /// Executes the page size is 256 operation. + #[test] + fn page_size_is_256() { + assert_eq!(FLASH_PAGE_SIZE, 256); + } + + /// Executes the target offset is last sector operation. + #[test] + fn target_offset_is_last_sector() { + assert_eq!(FLASH_TARGET_OFFSET, FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE); + } + + /// Executes the write len matches page size operation. + #[test] + fn write_len_matches_page_size() { + assert_eq!(FLASH_WRITE_LEN, 256); + } + + /// Executes the xip base address operation. + #[test] + fn xip_base_address() { + assert_eq!(XIP_BASE, 0x1000_0000); + } + + /// Executes the prepare write buf fills 0xff operation. + #[test] + fn prepare_write_buf_fills_0xff() { + let mut buf = [0u8; FLASH_WRITE_LEN]; + prepare_write_buf(&mut buf); + for &b in &buf[(DEMO_MSG.len() + 1)..] { + assert_eq!(b, 0xFF); + } + } + + /// Executes the prepare write buf has demo string operation. + #[test] + fn prepare_write_buf_has_demo_string() { + let mut buf = [0u8; FLASH_WRITE_LEN]; + prepare_write_buf(&mut buf); + assert_eq!(&buf[..DEMO_MSG.len()], DEMO_MSG); + } + + /// Executes the prepare write buf has nul terminator operation. + #[test] + fn prepare_write_buf_has_nul_terminator() { + let mut buf = [0u8; FLASH_WRITE_LEN]; + prepare_write_buf(&mut buf); + assert_eq!(buf[DEMO_MSG.len()], 0x00); + } + + /// Executes the format readback matches c output operation. + #[test] + fn format_readback_matches_c_output() { + let mut read_data = [0xFFu8; FLASH_WRITE_LEN]; + let msg = b"Embedded Hacking flash driver demo"; + read_data[..msg.len()].copy_from_slice(msg); + read_data[msg.len()] = 0x00; + let mut buf = [0u8; 128]; + let n = format_readback(&mut buf, &read_data); + assert_eq!( + &buf[..n], + b"Flash readback: Embedded Hacking flash driver demo\r\n" + ); + } + + /// Executes the format readback empty string operation. + #[test] + fn format_readback_empty_string() { + let read_data = [0u8; 8]; + let mut buf = [0u8; 64]; + let n = format_readback(&mut buf, &read_data); + assert_eq!(&buf[..n], b"Flash readback: \r\n"); + } + + /// Executes the format readback no nul operation. + #[test] + fn format_readback_no_nul() { + let read_data = [b'A'; 8]; + let mut buf = [0u8; 64]; + let n = format_readback(&mut buf, &read_data); + assert_eq!(&buf[..n], b"Flash readback: AAAAAAAA\r\n"); + } + + /// Executes the demo msg matches c string operation. + #[test] + fn demo_msg_matches_c_string() { + assert_eq!(DEMO_MSG, b"Embedded Hacking flash driver demo"); + } +} diff --git a/drivers/0x0f_flash_rust/src/lib.rs b/drivers/0x0f_flash_rust/src/lib.rs new file mode 100644 index 0000000..05a6a9d --- /dev/null +++ b/drivers/0x0f_flash_rust/src/lib.rs @@ -0,0 +1,6 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +#![cfg_attr(not(test), no_std)] +pub mod flash; diff --git a/drivers/0x0f_flash_rust/src/main.rs b/drivers/0x0f_flash_rust/src/main.rs new file mode 100644 index 0000000..7f3e4dc --- /dev/null +++ b/drivers/0x0f_flash_rust/src/main.rs @@ -0,0 +1,102 @@ +//! Driver crate + +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] + +//! Implementation module +//! +//! **File:** `main.rs` +//! **Author:** Kevin Thomas +//! **Date:** 2025 +//! +//! MIT License +//! +//! Copyright (c) 2025 Kevin Thomas +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + +#![no_std] +#![no_main] + +// Board-level helpers: constants, type aliases, and init functions +mod board; +// Flash driver module — suppress warnings for unused public API functions + +// Debugging output over RTT +use defmt_rtt as _; +// Panic handler for RISC-V targets +#[cfg(target_arch = "riscv32")] +// Import panic_halt as _ +use panic_halt as _; +// Panic handler for ARM targets +#[cfg(target_arch = "arm")] +// Import panic_probe as _ +use panic_probe as _; +// HAL entry-point macro +use hal::entry; +// Alias our HAL crate +#[cfg(rp2350)] +// Import rp235x_hal as hal +use rp235x_hal as hal; +#[cfg(rp2040)] +// Import rp2040_hal as hal +use rp2040_hal as hal; + +/// Second-stage boot loader for RP2040 +#[unsafe(link_section = ".boot2")] +#[used] +#[cfg(rp2040)] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// Boot metadata for the RP2350 Boot ROM +#[unsafe(link_section = ".start_block")] +#[used] +#[cfg(rp2350)] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Application entry point for the on-chip flash demo. +/// +/// # Returns +/// +/// A value of type `!`. +/// +/// # Returns +/// +/// A value of type `!`. +#[entry] +fn main() -> ! { + board::run(hal::pac::Peripherals::take().unwrap()) +} + +/// Picotool binary info metadata +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"On-chip Flash Demo"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +#[cfg(test)] +mod tests { + // Import all parent module items + use super::*; +} diff --git a/pico-2-r4-pinout.svg b/pico-2-r4-pinout.svg new file mode 100644 index 0000000..be839ba --- /dev/null +++ b/pico-2-r4-pinout.svg @@ -0,0 +1,1816 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + + + 39 + + + DEBUG + + + 1 + + + LED + + + USB + + + BOOTSEL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/raspberry-pi-pico-c-sdk.pdf b/raspberry-pi-pico-c-sdk.pdf new file mode 100644 index 0000000..dee8bca Binary files /dev/null and b/raspberry-pi-pico-c-sdk.pdf differ diff --git a/rp2350-datasheet.pdf b/rp2350-datasheet.pdf new file mode 100644 index 0000000..7a11127 Binary files /dev/null and b/rp2350-datasheet.pdf differ diff --git a/uf2conv.py b/uf2conv.py new file mode 100644 index 0000000..529dd96 --- /dev/null +++ b/uf2conv.py @@ -0,0 +1,438 @@ +#!/usr/bin/env python3 +import sys +import struct +import subprocess +import re +import os +import os.path +import argparse +import json +from time import sleep + + +UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" +UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected +UF2_MAGIC_END = 0x0AB16F30 # Ditto + +INFO_FILE = "/INFO_UF2.TXT" + +appstartaddr = 0x2000 +familyid = 0x0 + + +def is_uf2(buf): + w = struct.unpack(" 476: + assert False, "Invalid UF2 data size at " + ptr + newaddr = hd[3] + if (hd[2] & 0x2000) and (currfamilyid == None): + currfamilyid = hd[7] + if curraddr == None or ((hd[2] & 0x2000) and hd[7] != currfamilyid): + currfamilyid = hd[7] + curraddr = newaddr + if familyid == 0x0 or familyid == hd[7]: + appstartaddr = newaddr + padding = newaddr - curraddr + if padding < 0: + assert False, "Block out of order at " + ptr + if padding > 10 * 1024 * 1024: + assert False, "More than 10M of padding needed at " + ptr + if padding % 4 != 0: + assert False, "Non-word padding size at " + ptr + while padding > 0: + padding -= 4 + outp.append(b"\x00\x00\x00\x00") + if familyid == 0x0 or ((hd[2] & 0x2000) and familyid == hd[7]): + outp.append(block[32 : 32 + datalen]) + curraddr = newaddr + datalen + if hd[2] & 0x2000: + if hd[7] in families_found.keys(): + if families_found[hd[7]] > newaddr: + families_found[hd[7]] = newaddr + else: + families_found[hd[7]] = newaddr + if prev_flag == None: + prev_flag = hd[2] + if prev_flag != hd[2]: + all_flags_same = False + if blockno == (numblocks - 1): + print("--- UF2 File Header Info ---") + families = load_families() + for family_hex in families_found.keys(): + family_short_name = "" + for name, value in families.items(): + if value == family_hex: + family_short_name = name + print( + "Family ID is {:s}, hex value is 0x{:08x}".format( + family_short_name, family_hex + ) + ) + print("Target Address is 0x{:08x}".format(families_found[family_hex])) + if all_flags_same: + print("All block flag values consistent, 0x{:04x}".format(hd[2])) + else: + print("Flags were not all the same") + print("----------------------------") + if len(families_found) > 1 and familyid == 0x0: + outp = [] + appstartaddr = 0x0 + return b"".join(outp) + + +def convert_to_carray(file_content): + outp = "const unsigned long bindata_len = %d;\n" % len(file_content) + outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {" + for i in range(len(file_content)): + if i % 16 == 0: + outp += "\n" + outp += "0x%02x, " % file_content[i] + outp += "\n};\n" + return bytes(outp, "utf-8") + + +def convert_to_uf2(file_content): + global familyid + datapadding = b"" + while len(datapadding) < 512 - 256 - 32 - 4: + datapadding += b"\x00\x00\x00\x00" + numblocks = (len(file_content) + 255) // 256 + outp = [] + for blockno in range(numblocks): + ptr = 256 * blockno + chunk = file_content[ptr : ptr + 256] + flags = 0x0 + if familyid: + flags |= 0x2000 + hd = struct.pack( + b"