From e911fe98186a6ca7edf70c838379e38be7cf6c4f Mon Sep 17 00:00:00 2001 From: Khalil Estell Date: Thu, 29 Feb 2024 12:43:57 -0800 Subject: [PATCH] :memo: libhal 3.0.0 docs update --- .github/workflows/static.yml | 2 +- mkdocs/contributor_guide/architecture.md | 62 ++- .../contributor_guide/arm_cortex_bring_up.md | 355 ------------------ mkdocs/contributor_guide/style.md | 74 +--- mkdocs/getting_started.md | 89 +++-- mkdocs/index.md | 12 +- mkdocs/project_information/about.md | 1 - mkdocs/summary.md | 3 +- mkdocs/user_guide/configuration.md | 49 --- mkdocs/user_guide/error_handling.md | 124 +----- mkdocs/user_guide/fundamentals.md | 15 +- 11 files changed, 127 insertions(+), 659 deletions(-) delete mode 100644 mkdocs/contributor_guide/arm_cortex_bring_up.md delete mode 100644 mkdocs/user_guide/configuration.md diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 7a7610b53..e96235207 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -24,7 +24,7 @@ on: permissions: contents: write env: - CURRENT_VERSION: "2.4" + CURRENT_VERSION: "3.0" PRODUCTION: true jobs: diff --git a/mkdocs/contributor_guide/architecture.md b/mkdocs/contributor_guide/architecture.md index 17c0c4706..106ce5413 100644 --- a/mkdocs/contributor_guide/architecture.md +++ b/mkdocs/contributor_guide/architecture.md @@ -19,13 +19,14 @@ Interfaces MUST follow this layout: - Use `#pragma once` at the start of the file: Simpler than an include guard - All `virtual` functions must be private & each `virtual` functions is accompanied by a public API that is used to call the virtual API -- The return type of each API MUST be a `result` where `T` is a structure. +- ~~The return type of each API MUST be a `result` where `T` is a structure.~~ Amendment 1.0: Return types should never be a structure with the + expectation that it can be grown in the future. This is an ABI break. -Pragma once is needed to ensure files are included once. Its also less error -prone then hand writing include guards. +Pragma once is needed to ensure files are included once. Its less error prone +then hand writing include guards. The reasons for a private virtual with public API can be found in this -[article](). +article (TODO: add link to article). Returning a structure for each API means that, in the future, if the return type needs to be extended, it can be done without breaking down stream libraries. For @@ -74,7 +75,7 @@ problematic in many situations and are advised against in the core C++ guidelines. The benefits of tweak files can be found [here](https://vector-of-bool.github.io/2020/10/04/lib-configuration.html). -## A.4 ❌ Header Only Implementations ➡️ (See Amendment A.21) +## A.4 ❌ ~~Header Only Implementations~~ ➡️ (See Amendment A.21) libhal libraries and drivers are, in general, header-only. libhal uses header only implementations in order to enable the broadest set of package managers, @@ -117,7 +118,7 @@ linker errors as the linker sees both `GPIO_TypeDef` from an STM library and Because of this we have style [S.x Encapsulated Memory Mapped classes]() guideline. -## A.6 Using hal::function_ref over std::function +## A.6 Using `hal::function_ref` over `std::function&` `std::function` has all of the flexibility and functionality needed, but it has the potential to allocate and requires potentially expensive copy operations @@ -168,7 +169,7 @@ these package managers. The purpose of this design is to achieve: `libhal-util`, `libhal-mock` and `libhal-soft` were all apart of `libhal` originally, but due to the constant changes and API breaks in those categories of code, the version number of `libhal` would increment constantly, shifting the -foundation of the ecosystem. To prevent constant churn and API breaks `libahl` +foundation of the ecosystem. To prevent constant churn and API breaks `libhal` was split into those 4 libraries. The goal is to keep the version number for `libhal` constant for long periods of @@ -205,7 +206,7 @@ quality. The workflow files can be found in `libhal/libhal/.github/workflows`. Boost.UT was chosen for its lack of macros, stunning compile time performance, and its ease of use. -## A.13 Boost.LEAF for error handling +## A.13 ❌ ~~Boost.LEAF for error handling~~ ➡️ (See Amendment A.22) One major issue with any project is handling errors. Because the `libhal` interfaces can be used in such broad environments, it is hard to determine what @@ -260,7 +261,7 @@ if any of them. This can be used to capture an error code as well as s snapshot of the register map of a peripheral, the object's current state or even a debug message. -## A.14 Using Statement Expressions with `HAL_CHECK()` +## A.14 ~~Using Statement Expressions with `HAL_CHECK()`~~ ➡️ (See Amendment A.22) `HAL_CHECK()` is the only MACRO in `libhal`. It exists because there is nothing like Rust's `?` operator which either unwraps a value or returns an error from @@ -341,7 +342,7 @@ detail about the usability issues faced by unit libraries. Because, at the time of writing `libhal` there is not a unit library that is easy to use and concise, `libhal` decided to simply stick with 32-bit floats and helper UDLs. -## A.17 Always return `hal::result` from every API +## A.17 ~~Always return `hal::result` from every API~~ ➡️ (See Amendment A.22) Every interface in libhal returns a `hal::result` type. @@ -421,7 +422,7 @@ the memory footprint of the `callback` small. In most cases, setting an callback is something that is either done once or done very infrequently, and thus does not get much of a benefit from higher performance function calls. -## A.20 Why functions that setup events do not return `hal::status` +## A.20 ~~Why functions that setup events do not return `hal::status`~~ ➡️ (See Amendment A.22) Functions like `hal::can::on_receive()` and `hal::interrupt_pin::on_trigger()` return void and not `hal::status` like other APIs. Thus these functions cannot @@ -488,3 +489,42 @@ features of the platform, libhal emphasizes prebuilt libraries over header only. they propogate to all packages took time and was a complex process. And header-only libraries allowed the initial version of libhal to bypass all of this. + +## A.22 Use C++ Exception for Error handling + +libhal uses C++ exceptions for these reasons: + +1. Removes runtime cost of calling functions that error codes, result types, + and sentinel variables. +2. Reduces code size greatly by: + 1. Amortizing the error handling mechanism to a single set of functions + rather than distributing the workload across all functions that can error. + 2. Unwind information is a compressed ISA that only needs 8 bits per + instruction in ARM (32 and 64), where as normal ARM or Thumb-2 which require 16 to 32 bits of information, resulting in code bloat. + 3. GCC's Language Specific Data Area (LSDA) is a further compressed data + structure for determining if a frame contains a suitable catch block or cleanup routine. + +Issues regarding memory allocations can be overcome by simply overriding/ +wrapping `__cxa_allocate_exception` and using a statically allocated buffer.In +the case of multiple threads, each thread can be given its own memory buffer +through TLS or a circular pool of blocks with an `std::atomic` for every +thread to use. + +Exception throw runtime can be reduced by optimizing `__cxa_throw`'s +implementation. + +The runtime is determinist because the grand majority of our code is statically +linked. This means our code is not concerned with shared library locking the +exception table down with a mutex to update it. This mutex lock will blocking +threads from continuing to unwind, making the time non-determinist. Once you +remove that, your unwind time is deterministic. + +The issue with massive code bloat when you enable `-fexceptions` only comes +from the linux version of `std::terminate` which uses `` and some +other locale related stuff. Simply overriding this with your own implmentation +fixes this issue and reduces code size by ~100kB. + +A link to a paper written by Khalil Estell will be linked here to describe in +detail why C++ exception handling is the superior error handling mechanism for +embedded systems and software in general when performance and code size are +critical concerns. diff --git a/mkdocs/contributor_guide/arm_cortex_bring_up.md b/mkdocs/contributor_guide/arm_cortex_bring_up.md deleted file mode 100644 index 14b1f61ea..000000000 --- a/mkdocs/contributor_guide/arm_cortex_bring_up.md +++ /dev/null @@ -1,355 +0,0 @@ -# 🔸 Bare-Metal ARM Cortex Target Bring-Up - -!!! info - TO BE MOVED TO `libhal-armcortex` REPO - -This guide will step you through making a libhal + conan target library for a -arm processor microcontroller. Unlike libhal applications that can be executed -on a machine running an OS like linux, example Raspberry Pi and Beagle Boards, -you cannot just execute the binary. - -This guide assumes that `libhal-library` was used as a template and has already -updated and changed all of the names from `libhal-library` to the appropriate -library name. - -In order to build an application that can be loaded and executed onto a -microcontroller you only need: - -1. Add `libhal-armcortex` as a dependency -2. Provide a linker script for each microcontroller -3. Determine minimum compiler flags for each microcontroller -4. Provide a library component for that microcontroller - -The rest can be handled by the `arm-gnu-embedded-toolchain`'s `crt0` -implementation, the `arm-gnu-embedded-toolchain` conan package and the -`libhal-armcortex` conan package. - -## Adding the `libhal-armcortex` dependency - -Simply add `libhal-armcortex` to your `requirements()` method: - -```python -def requirements(self): - # ... - self.requires("libhal-armcortex/[^1.0.1]") -``` - -## Writing the linker scripts - -### Setup linker script directory - -Create a `linker_scripts` directory at the root of the library package. -Add `linker_scripts/*` directory to the export sources in the package -`conanfile.py`, like so: - -```python -exports_sources = "include/*", "linker_scripts/*", "tests/*", "LICENSE" -``` - -### Finding linker scripts info - -Lets consider the `lpc4074` microcontroller. What you'll need to figure out is: - -1. Flash memory address & size -2. Ram memory address & size - -These sections are part of whats called the "memory map". Most modern day -systems use a system called "Memory-mapped I/O" which means that the system uses -the same address space to address both memory and I/O devices. In this case we -simply want to find the addresses of the flash memory and ram memory. This -information can be found in the data sheet or user manual of the chip. - -The LPC40 series of microcontrollers will be used for this example: -The memory map can be found on page 52 of the `LPC408X_7X.pdf` data sheet or -page 14 of the `UM10562.pdf` user manual. - -![lpc40xx memory map](arm_cortex_bring_up/lpc40xx-memory-map.png) - -

-Figure 1. LPC40xx Memory Map -

- -Here you can see that flash starts at address `0x00000000` for all sizes of -flash memory. The SRAM locations all start at `0x10000000` for all sizes of -SRAM. This chart does not provide which chips have which ram and flash sizes. - -Looking through the data sheet and searching for terms like "part numbers", -"ordering options", or even just the number 512 (the maximum flash size), -eventually this section will appear: - -![lpc40xx ordering info part 1](arm_cortex_bring_up/lpc40xx-ordering-1.png) - -

-Figure 2. LPC40xx Part Ordering Info part 1 -

- -![lpc40xx ordering info part 2](arm_cortex_bring_up/lpc40xx-ordering-2.png) - -

-Figure 3. LPC40xx Part Ordering Info part 2 -

- -Now all of the information to write the linker scripts is available: - -=== "lpc4072.ld" - - ```ld - __flash = 0x00000000; - __flash_size = 64K; - __ram = 0x10000000; - __ram_size = 16K; - - INCLUDE "libhal-armcortex/standard.ld" - ``` - -=== "lpc4074.ld" - - ```ld - __flash = 0x00000000; - __flash_size = 128K; - __ram = 0x10000000; - __ram_size = 32K; - - INCLUDE "libhal-armcortex/standard.ld" - ``` - -=== "lpc4076.ld" - - ```ld - __flash = 0x00000000; - __flash_size = 256K; - __ram = 0x10000000; - __ram_size = 64K; - - INCLUDE "libhal-armcortex/standard.ld" - ``` - -=== "lpc4078.ld" - - - ```ld - __flash = 0x00000000; - __flash_size = 512K; - __ram = 0x10000000; - __ram_size = 64K; - - INCLUDE "libhal-armcortex/standard.ld" - ``` - -=== "lpc4088.ld" - - ```ld - __flash = 0x00000000; - __flash_size = 512K; - __ram = 0x10000000; - __ram_size = 64K; - - INCLUDE "libhal-armcortex/standard.ld" - ``` - ---- - -!!! question - - You may be wondering why the RAM size is 64kB and not 96kB for some of the - linker scripts and thats due to the fact that the LPC40xx series has a dual - SRAM architecture. To keep this simple, only the largest RAM block is - supported. - -The linker script only needs 4 lines as `libhal-armcortex` provides a standard -linker script for ARM microcontrollers supporting 1 flash memory and 1 ram -device. Defining the `__flash`, `__flash_size`, `__ram`, and `__ram_size` -linker script variables is all that is needed to make a usable linker script. - -There are plans to support dual flash, dual ram and other varieties of flash -and ram combinations in the future in `libhal-armcortex`. - -!!! warning - - Many of the microcontrollers come in different packages and may have some - differences in the number of peripherals they support, pins they have and - performance. The linker script does not need to worry about such - differences and thus, a linker script should **NOT** be made for every - possible chip variety in the series but for the common flash sizes and ram - sizes for each. - -## Compiler flags - -### Processor flags - -The data sheet will include information about the processor. The compiler flag -will match the following based on the CPU: - -- `-mcpu=cortex-m0` -- `-mcpu=cortex-m0plus` (cortex-M0+) -- `-mcpu=cortex-m1` -- `-mcpu=cortex-m3` -- `-mcpu=cortex-m4` -- `-mcpu=cortex-m7` -- `-mcpu=cortex-m23` -- `-mcpu=cortex-m33` -- `-mcpu=cortex-m35p` -- `-mcpu=cortex-m55` -- `-mcpu=cortex-m85` -- `-mcpu=cortex-m1.small-multiply` -- `-mcpu=cortex-m0.small-multiply` -- `-mcpu=cortex-m0plus.small-multiply` - -### Floating Point Support - -After one of the following to the architecture flags: - -- `-mfloat-abi=soft`: if the processor is an cortex-m3 or below -- `-mfloat-abi=softfp`: if the processor is a cortex-m4 and above AND also has - a floating point unit. This can be determined by searching the data sheet. - -## Creating components for the library - -libhal target library's split up the library into components, one for each -microcontroller variant. For LPC40 that split would look like: -`libhal::lpc4072`, `libhal::lpc4074`, `libhal::lpc4076`, `libhal::lpc4078`, and -`libhal::lpc4088`. When a build system, for example, uses the `libhal::lpc4078` -component, it includes the necessary compiler flags and linker script selection. - -Along with these components, will be a special generic component named -`libhal::lpc` which does not provide any compiler flags or linker script. This -special target is used for applications that want to use their own linker -script, or for software running on a host machine like simulations or unit -tests. - -To add components it must be added in the `package_info` method of the -`ConanFile` package class. Here is what it looks like for the `libhal-lpc` -library. Copy this section and tailor it to your needs. - -```python -def package_info(self): - # Specify, for the component, all requirements of the package - requirements_list = ["libhal::libhal", - "libhal-util::libhal-util", - "libhal-armcortex::libhal-armcortex", - "ring-span-lite::ring-span-lite"] - - # List of REQUIRED compiler flags for the gnu-arm-embedded-toolchain for some - # of the chips. These are determined by the capabilities of the chip. - # For example all but the lpc4072 and lpc4074 have hardware floating point - # arithmetic support so they ought to use "float-abi=softfp" which uses the - # floating point hardware BUT is ABI compatible with the software - # implementation. - m4f_architecture_flags = [ - "-mcpu=cortex-m4", - "-mfloat-abi=softfp", - ] - - # List of REQUIRED compiler flags for the gnu-arm-embedded-toolchain for - # some of the chips. These are determined by the capabilities of the chip. - # For example the lpc4072 and lpc4074 do not have hardware floating point - # arithmetic support so they must use "float-abi=soft" for a software - # implementation. - m4_architecture_flags = [ - "-mcpu=cortex-m4", - "-mfloat-abi=soft" - ] - - # Create a path to the linker_script directory which resides in the - # package's package_folder. - linker_path = os.path.join(self.package_folder, "linker_script") - - # Set the cmake file name - self.cpp_info.set_property("cmake_file_name", "libhal-lpc") - # All the package to be found in anyway with cmake - self.cpp_info.set_property("cmake_find_mode", "both") - - # Create the special/generic component "lpc" and set its component name - self.cpp_info.components["lpc"].set_property( - "cmake_target_name", "libhal::lpc") - - # This is where we add the path to our linker scripts to the set of linker - # flags. - self.cpp_info.components["lpc"].exelinkflags.append("-L" + linker_path) - - # Add the list of requirements to the generic component - self.cpp_info.components["lpc"].requires = requirements_list - - # Helper function for creating components - def create_component(self, component, flags): - - link_script = "-Tlibhal-lpc/" + component + ".ld" - component_name = "libhal::" + component - self.cpp_info.components[component].set_property( - "cmake_target_name", component_name) - # Make the special component the only requirement for the component, - # inheriting all of the transitive dependencies. - self.cpp_info.components[component].requires = ["lpc"] - # Add the link script and flags to the component's linker flags and - # compiler flags - self.cpp_info.components[component].exelinkflags.append(link_script) - self.cpp_info.components[component].exelinkflags.extend(flags) - # Add flags to the cflags & cxxflags to ensure that each compilation unit - # Knows the instruction set and float ABI - self.cpp_info.components[component].cflags = flags - self.cpp_info.components[component].cxxflags = flags - - # Create the components for each chip. - create_component(self, "lpc4072", m4_architecture_flags) - create_component(self, "lpc4074", m4_architecture_flags) - create_component(self, "lpc4076", m4f_architecture_flags) - create_component(self, "lpc4078", m4f_architecture_flags) - create_component(self, "lpc4088", m4f_architecture_flags) -``` - -## Verifying - -### Creating the package - -Run `conan create .` in the folder with the `conanfile.py` recipe in it. -The test package and build stages should show something like this during the -cmake phase: - -``` --- Conan: Component target declared 'libhal::lpc' --- Conan: Component target declared 'libhal::lpc4072' --- Conan: Component target declared 'libhal::lpc4074' --- Conan: Component target declared 'libhal::lpc4076' --- Conan: Component target declared 'libhal::lpc4078' --- Conan: Component target declared 'libhal::lpc4088' -``` - -### Testing out a demo - -Create a demo and have it require the library. In this case the demo -`conafile.py` may include: - -```python -from conan import ConanFile -from conan.tools.cmake import CMake, cmake_layout - - -class Lpc40xxDemos(ConanFile): - settings = "compiler", "build_type" - generators = "CMakeToolchain", "CMakeDeps", "VirtualBuildEnv" - - def requirements(self): - self.requires("libhal-lpc/1.1.4") # <-- change this - self.requires("libhal-util/[^1.0.0]") # <-- update this if necessary - self.tool_requires("gnu-arm-embedded-toolchain/11.3.0") - self.tool_requires("cmake-arm-embedded/0.1.1") - - def layout(self): - cmake_layout(self) - - def build(self): - cmake = CMake(self) - cmake.configure() - cmake.build() -``` - -Change the library name to the library you are creating. - -```python -self.tool_requires("gnu-arm-embedded-toolchain/11.3.0") -self.tool_requires("cmake-arm-embedded/0.1.1") -``` - -The above two requirements are required to download and install the -toolchain/compiler and the cmake toolchain/helper files. The project should -compile if everything was done correctly. diff --git a/mkdocs/contributor_guide/style.md b/mkdocs/contributor_guide/style.md index 72f6c8d14..1a9f3536b 100644 --- a/mkdocs/contributor_guide/style.md +++ b/mkdocs/contributor_guide/style.md @@ -99,6 +99,7 @@ code block that would effectively end or halt the execution of the program without giving control back to the application are prohibited. As an example drivers should never call: + - `std::abort()` - `std::exit()` - `std::terminate()` @@ -109,54 +110,7 @@ This includes placing an **infinite loop block** in a driver. An application should have control over how their application ends. A driver should report severe errors to the application and let the application decide the next steps. If a particular operation cannot be executed as intended, -then `hal::new_error()` should be called. - -Constructors would be the only valid place to put an exit statement, because -they cannot return errors only themselves. - -The solution to this is to use a factory function like so: - -=== "Device Driver Library" - - ```C++ - class device_driver { - public: - result create(/* ... */) { - // Perform operations that may fail here - return device_driver(/* ... */); - } - - private: - device_driver(/* ... */) { - // Constructors should never fail and thus any work done here must not - // fail. - } - }; - ``` - -=== "Peripheral Driver Library" - - ```C++ - class peripheral_driver { - public: - // Since peripherals are constrained and have a finite set of values - // This also ensures that the driver is only constructed once and afterwards - // simply returns back a reference to that object. - template - // NOTE: Returns a reference not an object. - // Objects are owned by the create function - result create(/* ... */) { - // Perform operations that may fail here - static peripheral_driver driver(/* ... */); - return driver; - } - - private: - peripheral_driver(/* ... */) { - // ... - } - }; - ``` +then an appropriate `hal::exception` type should be thrown. ## S.8 Drivers should not pollute the global namespace @@ -198,16 +152,16 @@ class target { See [private virtual method](http://www.gotw.ca/publications/mill18.htm) for more details. Rationale can be found within that link as well. -## S.10 Avoid using `bool` +## S.10 Avoid using `bool` as ... -### S.10.1 As an object member +### S.10.1 an object member `bool` has very poor information density and takes up 8-bits per entry. If only one `bool` is needed, then a bool is a fine object member. If multiple `bool`s are needed, then use a `std::bitset` along with static `constexpr` index positions in order to keep the density down to the lowest amount possible. -### S.10.2 As a parameter +### S.10.2 a parameter See the article ["Clean code: The curse of a boolean parameter"](https://medium.com/@amlcurran/clean-code-the-curse-of-a-boolean-parameter-c237a830b7a3) @@ -260,17 +214,15 @@ manage. ## S.12 Avoid `std::atomic` -Avoid using `std::atomic` because of portability issues across devices in -architectures. Especially when `std::atomic` is not fully supported by the -compiler. - -!!! info +Avoid using `std::atomic` in device libraries due to portability issues across +architectures. Device libraries are designed to work across architectures +meaning they cannot depend on platform specific constructs like this. - `target` and `processor` libraries are allowed to use `std::atomic` if it is - available with their cross compiler and toolchain. In this case, the we can - know which target devices the software is running on, either the target - itself, which we already know can support it, or on a host machine for unit - testing, which is very likely to have a compiler that supports atomics. +Note that `target` and `processor` libraries are allowed to use `std::atomic` +if it is available with their cross compiler and toolchain. In this case, the +we can know which target devices the software is running on, either the target +itself, which we already know can support it, or on a host machine for unit +testing, which is very likely to have a compiler that supports atomics. ## S.13 Avoid `` diff --git a/mkdocs/getting_started.md b/mkdocs/getting_started.md index 52d010389..1aac4038d 100644 --- a/mkdocs/getting_started.md +++ b/mkdocs/getting_started.md @@ -153,7 +153,7 @@ conan's `settings.yml` file to include baremetal architectures. These additional architecture definitions are required for ALMOST ALL libhal applications. ```bash -conan config install -sf profiles/baremetal https://github.com/libhal/conan-config.git +conan config install -sf profiles/baremetal/v2 https://github.com/libhal/conan-config.git ``` Next, setup the host profile. Host profiles define the compiler, @@ -231,54 +231,65 @@ Now install the profile for your particular OS and CPU architecture. Clone the target library you would like to run the demos for. You can download just one or both if you have both devices. +!!! warning + + stm32f103 not ported to libhal 3.0.0 yet, please do not use these steps for + it. This will be fixed when the migration is complete. Thank you for your + patience. + === "LPC4078" ```bash git clone https://github.com/libhal/libhal-lpc40 - cd libhal-lpc40/demos + cd libhal-lpc40 ``` === "STM32F103" ```bash git clone https://github.com/libhal/libhal-stm32f1 - cd libhal-stm32f1/demos + cd libhal-stm32f1 ``` -This command will install the profiles for the ARM cortex processor and LPC40 -series microcontrollers. The LPC40 microcontrollers are: `lpc4072`, `lpc4074`, +The next command will install the profiles for the and LPC40 series +microcontrollers. For LPC40 microcontrollers there are: `lpc4072`, `lpc4074`, `lpc4076`, `lpc4078`, and `lpc4088`. -The LPC40 profiles import `cortex-m4` and `cortex-m4f` profiles from the ARM -cortex processor library `libhal-armcortex` and thus need to be installed as -well. - === "LPC4078" ``` - conan config install -sf conan/profiles/ -tf profiles https://github.com/libhal/libhal-armcortex.git - conan config install -sf conan/profiles/ -tf profiles https://github.com/libhal/libhal-lpc40.git + conan config install -sf conan/profiles/v2 -tf profiles https://github.com/libhal/libhal-lpc40.git ``` === "STM32F103" ``` - conan config install -sf conan/profiles/ -tf profiles https://github.com/libhal/libhal-armcortex.git - conan config install -sf conan/profiles/ -tf profiles https://github.com/libhal/libhal-stm32f1.git + conan config install -sf conan/profiles/v2 -tf profiles https://github.com/libhal/libhal-stm32f1.git ``` -To build using conan and cmake, you just need to run the following: +The compiler used to cross build application for the ARM Cortex M series is the +Arm-Gnu-Toolchain. Profiles are provided that allow you to select which version +of the compiler you want to use. These profiles set the compiler package as the +global compiler ensuring that un0built dependencies use it for building +libraries. It can be installed using: + + +```bash +conan config install -tf profiles -sf conan/profiles/v1 https://github.com/libhal/arm-gnu-toolchain.git +``` + +Now we have everything we need to build our project. To build using conan you just need to run the following: === "LPC4078" ```bash - conan build . -pr lpc4078 -s build_type=MinSizeRel + conan build demo -pr lpc4078 -pr arm-gcc-12.3 ``` === "STM32F103" ```bash - conan build . -pr stm32f103 -s build_type=MinSizeRel + conan build demo -pr stm32f103 -pr arm-gcc-12.3 ``` !!! note @@ -289,31 +300,12 @@ To build using conan and cmake, you just need to run the following: libraries will be cached on your machine and you'll no longer need to include those arguments. -To build a binary for a particular microcontroller, you need to specify the -microcontroller you plan to target such as the `lpc4078` and the build type. +When this completes you should have some applications in the +`build/lpc4078/MinSizeRel/` with names such as `uart.elf` or `blinker.elf`. Each microcontroller has different properties such as more or less ram and the presence or lack of a floating point unit. -The following build types, `build_type` argument are available: - -- ❌ **Debug**: No optimization, do not recommend, normally used for unit - testing. -- 🧪 **RelWithDebInfo**: Turn on some optimizations to reduce binary size and - improve performance while still maintaining the structure to make - debugging easier. Recommended for testing and prototyping. -- ⚡️ **Release**: Turn on optimizations and favor higher performance - optimizations over space saving optimizations. -- 🗜️ **MinSizeRel**: Turn on optimizations and favor higher space saving - optimizations over higher performance. - -Note that `Release` and `MinSizeRel` build types both usually produce -binaries faster and smaller than `RelWithDebInfo` and thus should definitely -be used in production. - -When this completes you should have some applications in the -`build/lpc4078/MinSizeRel/` with names such as `uart.elf` or `blinker.elf`. - !!! error You can get this error if the arm gnu toolchain wasn't installed correctly @@ -395,6 +387,29 @@ In order to complete this tutorial you'll one of these devices: Use `demos/build/stm32f103c8/Debug/blinker.elf.bin` or replace it with any other application to be uploaded. +## ⚡️ Changing Built Type + +The build type determins the optimization level of the project. The libhal default for everything is `MinSizeRel` because code size is one of the most important aspects of the project. + +You can also change the `build_type` to following build types: + +- ❌ **Debug**: No optimization, do not recommend, normally used for unit + testing. +- 🧪 **RelWithDebInfo**: Turn on some optimizations to reduce binary size and + improve performance while still maintaining the structure to make + debugging easier. Recommended for testing and prototyping. +- ⚡️ **Release**: Turn on optimizations and favor higher performance + optimizations over space saving optimizations. +- 🗜️ **MinSizeRel**: Turn on optimizations and favor higher space saving + optimizations over higher performance. + +Note that `Release` and `MinSizeRel` build types both usually produce +binaries faster and smaller than `RelWithDebInfo` and thus should definitely +be used in production. + +To override the default and choose `Release` mode simply add the following to +your conan command: `-s build_type=Release` + ## 🎉 Creating a new Project Start by cloning `libhal-starter`: diff --git a/mkdocs/index.md b/mkdocs/index.md index 66dc6400f..23aada2d2 100644 --- a/mkdocs/index.md +++ b/mkdocs/index.md @@ -13,13 +13,12 @@ processors, microcontrollers, systems, and devices. The design philosophy of libhal is to be: 1. Portable & Cross Platform -2. Light Weight -3. General +2. General +3. Fast & Compact 4. Minimalist -5. Safe & Reliable -6. Tested & Testable -7. Compiled Quickly -8. OS Agnostic +5. Safe, Reliable, Tested & Testable +6. Fast Build TImes +7. OS Agnostic ## The Basics @@ -44,7 +43,6 @@ consider their implementation details and blink and LED at a specified interval. - [Conan](https://conan.io/center/libhal) package manager - Source code is hosted on [GitHub](https://github.com/libhal/libahl) -- `vcpkg` package manager (planned for the future) # Sponsorships diff --git a/mkdocs/project_information/about.md b/mkdocs/project_information/about.md index 9fdfe6db6..226ce5b66 100644 --- a/mkdocs/project_information/about.md +++ b/mkdocs/project_information/about.md @@ -43,5 +43,4 @@ LICENSE file. ## Third Party Library Licenses -- [Boost.LEAF](https://github.com/boostorg/leaf), BOOST license - [tl-function-ref/1.0.0](https://github.com/TartanLlama/function_ref), CC0 diff --git a/mkdocs/summary.md b/mkdocs/summary.md index 7fedcc6e6..907a58602 100644 --- a/mkdocs/summary.md +++ b/mkdocs/summary.md @@ -4,8 +4,7 @@ - [🧱 Fundamentals of libhal](user_guide/fundamentals.md) - [🔗 Interfaces in Software Development and libhal](user_guide/interfaces.md) - [🎯 Debugging Code with PyOCD](user_guide/debugging.md) - - [🎚️ Configuration](user_guide/configuration.md) - - [🪤 Error Handling in libhal using Boost.LEAF](user_guide/error_handling.md) + - [🪤 Error Handling in libhal](user_guide/error_handling.md) - [⚖️ Policies & FAQ](user_guide/policy.md) - 📚 Contributor Guides - [📜 Design Philosophy](contributor_guide/philosophy.md) diff --git a/mkdocs/user_guide/configuration.md b/mkdocs/user_guide/configuration.md deleted file mode 100644 index 6fa398d57..000000000 --- a/mkdocs/user_guide/configuration.md +++ /dev/null @@ -1,49 +0,0 @@ -# 🎚️ Configuration - -## `hal::on_error_callback` - -Libhal provides a simple mechanism for handling calling a function when an error -occurs. The error handler is defined as follows: - -```cpp -using error_handler = void(void); -inline error_handler* on_error_callback = nullptr; -``` - -The `on_error_callback` is a pointer to a function that takes no arguments and -returns no value. If this variable is set to something other than `nullptr`, -then the function it points to is called before the error object is returned. -`hal::new_error()` must be used to get this behavior. Calling -`boost::leaf::new_error` will bypass this behavior. - -This error handler is useful for logging when errors occur. It allows developers -to capture information about the state of your program at the time of the error, -which can be helpful for debugging. - -Most importantly, the error handler is very useful for generating a stack trace. -A stack trace provides a snapshot of the call stack at a specific point in time. -This can be invaluable for understanding the sequence of function calls that led -to an error. However, generating a stack trace is only possible at this depth in -the call stack, which is why the error handler is so important. - -To use the error handler, you simply need to define a function that matches the -`error_handler` type, and then assign the address of this function to -`on_error_callback`. Here's an example: - -```cpp -void my_error_handler() { - // Code to execute when an error occurs -} - -int main() { - hal::on_error_callback = &my_error_handler; - - // Rest of your code -} -``` - -In this example, `my_error_handler` will be called whenever an error occurs in -your code. You can put any code you want in this function to handle the error in -a way that makes sense for your application. - -Note that this pointer is global and care should be taken when modifying it. diff --git a/mkdocs/user_guide/error_handling.md b/mkdocs/user_guide/error_handling.md index 204cb50cf..23c549cdd 100644 --- a/mkdocs/user_guide/error_handling.md +++ b/mkdocs/user_guide/error_handling.md @@ -1,123 +1,3 @@ -# 🪤 Error Handling in libhal using Boost.LEAF +# 🪤 Error Handling in libhal -Libhal uses Boost.LEAF for error handling. Boost.LEAF is a lightweight error -handling library for C++11. It is designed for maximum efficiency and does not -require dynamic memory allocations, even with heavy payloads. It can be used -with or without exception handling and supports multi-thread programming. - -## Basic Concepts - -Boost.LEAF introduces a few key concepts for error handling: - -- **Error Objects**: These are instances of any type that you want to associate - with an error. They can be error codes, strings, or any other type of object - that provides information about the error. - -- **Context**: This is a scope where error objects can be loaded and associated - with a specific error. It is usually local to a `try_handle_all`, - `try_handle_some`, or `try_catch` scope. - -- **Error ID**: This is a unique identifier for an error. It is generated when - an error is reported using `new_error` or `exception`. - -## Error Handling Process - -The error handling process in Boost.LEAF involves the following steps: - -1. **Detecting an Error**: When an error is detected in your code, you can - report it by calling `new_error` or `exception`. These functions generate a - new error ID and return it. You can also pass any number of error objects to - these functions to associate them with the error. - -2. **Loading Error Objects**: Error objects can be loaded into an active context - using the `load` function. These objects become associated with a specific - error ID. If storage is not available, the error objects are discarded. - -3. **Handling Errors**: When an error is reported, it is forwarded up the call - stack until it reaches an error-handling scope. This scope probes the context - for any error objects associated with the error ID and processes a list of - user-provided error handlers. The first handler that can be supplied with the - available error objects is called to handle the error. - -## Boost.LEAF Aliases - -libhal creates aliases for a few of the APIs in Boost.LEAF to shorten their -names and use the `hal` namespace. The mapping can be found in -[libhal/error.hpp](https://github.com/libhal/libhal/blob/main/include/libhal/error.hpp). - -Including `libhal/error.hpp` will pull in all of the Boost.LEAF libraries, so -if you prefer to use them directly, you can do so. - -## Example - -Here is an example of how you can use Boost.LEAF for error handling in libhal: - -```cpp -#include - -enum class error_code -{ - bad_command_line = 1, - open_error, - read_error, - size_error, - eof_error, - output_error -}; - -hal::result do_something() -{ - // Some operation that may fail... - if (/* failure condition */) - { - return hal::new_error(open_error, leaf::e_errno{errno}); - } - - return {}; -} - -int main(int argc, char const *argv[]) -{ - return hal::attempt_all( - []() -> hal::result - { - HAL_CHECK(do_something()); - return 0; - }, - [](hal::match, leaf::e_errno const &errn) - { - // Do something here - return 2; - }, - []() - { - // Unknown error, handle it here - return 6; - }); -} -``` - -In this example, `do_something` is a function that may fail. If it fails, it -reports an error by calling `new_error` and associates the error with an -`open_error` error code and the system `errno`. The `main` function calls -`do_something` and handles any errors that it reports. It uses `try_handle_all` -to handle all errors and - -provides two error handlers. The first handler handles `open_error` errors and -includes the system `errno`. The second handler handles any other errors. - -Please note that this is a basic example. Boost.LEAF provides many more features -for error handling, such as error object composition, error object accumulation, -and error object matching. You can use these features to create more -sophisticated error handling systems. - -For more information, please refer to the [Boost.LEAF -documentation](https://www.boost.org/doc/libs/1_77_0/libs/leaf/doc/html/index.html). - -## RAM Usage when using an RTOS - -To be written... - -## Error Types in libhal - -To be written... +TBD diff --git a/mkdocs/user_guide/fundamentals.md b/mkdocs/user_guide/fundamentals.md index a797eb95c..f63b24bf6 100644 --- a/mkdocs/user_guide/fundamentals.md +++ b/mkdocs/user_guide/fundamentals.md @@ -112,26 +112,15 @@ concrete class and use its methods in order to implement the interface APIs. In libhal, there is a common language policy for adaptors. To create them you must call a factory function called `make_()` and it will -return a `hal::result`. There is an overload for every driver +return an `adaptor_object`. There is an overload for every driver that implements a particular interface. For example, in order to generate a servo from an RMD X6 servo object, it would look like this: ```C++ -auto smart_servo_driver = HAL_CHECK(make_servo(rmd_x6_driver)); +auto smart_servo_driver = make_servo(rmd_x6_driver); ``` This approach allows for a consistent and efficient way to create adaptors for various interfaces from a single concrete driver. It ensures that the concrete driver can be utilized to its full potential, providing access to all its capabilities through the appropriate interfaces. - -## Return Types `hal::result` and `hal::status` - -`hal::result` is an alias for the `boost::leaf::result` type. This type -can either be the value T or an error. `hal::status` is simply a concise alias -for the type `boost::leaf::result`. See -[Boost.LEAF](https://boostorg.github.io/leaf/) for more details about it and -how it works. - -`HAL_CHECK()` is a macro that takes an expression that evaluates to a -`hal::result`.