UPDATED DOCUMENTATION

This commit is contained in:
Nicolas Clauvelin 2018-03-20 08:38:44 -04:00
parent 8d2cdbf25f
commit c6c2c5055a
2 changed files with 36 additions and 28 deletions

23
API.md
View File

@ -5,7 +5,7 @@ Copyright Sendyne Corp., 2010-2018. All rights reserved ([LICENSE](LICENSE)).
## Introduction ##
`cppreg` provides ways to define custom C++ data types to represent memory-mapped input/output (MMIO) registers and fields. In essence, `cppreg` does contain very little *executable* code, but it does provide a framework to efficiently manipulate MMIO registers and fields.
The entire implementation is encapsulated in the `cppreg::` namespace. All the code examples assume this namespace is accessible (*i.e.*, `using namespace cppreg` is implicit).
The entire implementation is encapsulated in the `cppreg::` namespace. All the code examples assume this namespace is accessible (*i.e.*, `using namespace cppreg;` is implicit).
## Overview ##
@ -13,7 +13,7 @@ The entire implementation is encapsulated in the `cppreg::` namespace. All the c
* define a pack of registers: a register pack is simply a group of registers contiguous in memory (this is often the case when dealing with registers associated with a peripheral),
* define single register at a specific memory address: this is provided as a fallback when the register pack implementation cannot be used,
* define fields within registers (packed or not): a field corresponds to a group of bits within a register and comes with a specific access policy which control read and write accesses.
* define fields within registers (packed or not): a field corresponds to a group of bits within a register and comes with a specific access policy which control read and write access.
The API was designed such that `cppreg`-based code is safer and more expressive than traditional low-level code while providing the same level of performance.
@ -28,14 +28,14 @@ As explained below, when using `cppreg`, registers and fields are defined as C++
* `FieldWidth_t` and `FieldOffset_t` are the data types to represent field sizes and offsets; both are equivalent to `std::uint8_t`.
### Register size ###
The `RegBitSize` enumeration type represents the register sizes supported in `cppreg` and the various values are:
The `RegBitSize` enumeration type represents the register sizes supported in `cppreg` and the values are:
* `RegBitSize::b8` for 8-bit registers,
* `RegBitSize::b16` for 16-bit registers,
* `RegBitSize::b32` for 32-bit registers,
* `RegBitSize::b64` for 64-bit registers.
The register size is used to define the C++ data type that represent the register content in [Traits.h](TypeTraits.h).
The register size is used to define the C++ data type that represent the register content in [Traits.h](Traits.h).
## Register interface ##
@ -46,7 +46,7 @@ In `cppreg`, registers are represented as memory locations that contain fields,
Most of the times registers are part of groups related to peripherals or specific functionalities within a MCU. It is therefore recommended to use the register pack implementation rather than the standalone one. This ensures that the assembly generated from `cppreg`-based code will be optimal. In other words, the difference between packed registers and standalone registers is only a matter of performance in the generated assembly: the packed register interface relies on mapping an array on the pack memory region, which provides to the compiler the ability to use offset between the various registers versus reloading their absolute addresses.
Moreover, the same level of functionality is provided by both implementations (`RegisterPack` is simply deriving from `Register` and overriding accessor and modifier methods). That is, a packed register type can be replaced by a standalone register type (and *vice versa*).
Moreover, the same level of functionality is provided by both implementations (`RegisterPack` is simply deriving from `Register` and redefining accessor and modifier methods). That is, a packed register type can be replaced by a standalone register type (and *vice versa*).
### Register pack interface ###
To define a pack of registers:
@ -54,16 +54,23 @@ To define a pack of registers:
1. define a `RegisterPack` type with the base address of the pack and the number of bytes,
2. define `PackedRegister` types for all the registers in the pack.
The interface is (see [RegisterPack.h](register/RegisterPack.h)):
* `struct RegisterPack<pack_base_address, pack_size_in_bytes>`:
* `pack_base_address` is the starting address of the memory region that contains all the registers of the pack,
* `pack_size_in_bytes` is the size in bytes of the said memory region.
| parameter | description |
|:---------------------|:-------------------------------------------|
| `pack_base_address` | starting address of the pack memory region |
| `pack_size_in_bytes` | size in bytes of the pack memory region |
* `struct PackedRegister<pack_type, RegBitSize_value, offset_in_bits, reset_value, use_shadow_value>`:
| parameter | description |
|:---------------------|:-------------------------------------------|
| `pack_type` | starting address of the pack memory region |
| `RegBitSize_value` | size in bytes of the pack memory region |
| `RegBitSize_value` | size in bytes of the pack memory region |
* `pack_type` is the `RegisterPack` type to which the register belongs,
* `RegBitSize_value` is the register size a `RegBitSize` value,
* `offet_in_bits` is the offset in bits with the respect to the pack base address,

View File

@ -28,7 +28,7 @@ There are two recommended ways to use `cppreg` in a project:
### Recommended compiler settings ###
Although `cppreg` is entirely written in C++, there is little (if any) overhead in term of runtime performance if at least some level of optimization are enabled (mostly to take care of inlining). For GCC ARM the following settings are recommended (and similar settings should be used for other compilers):
Although `cppreg` is entirely written in C++, there is little (if any) overhead in term of runtime performance if at least some level of optimization are enabled (mostly to take care of inlining). For GCC/Clang the following settings are recommended (and similar settings should be used for other compilers):
* enable `-Og` for debug builds (this helps significantly with inlining),
* for optimized builds (`-O2`,`-O3` or `-Os`) the default settings will be sufficient,
@ -36,7 +36,7 @@ Although `cppreg` is entirely written in C++, there is little (if any) overhead
## Prologue ##
When developing firmware code for embedded MCUs it is customary that peripherals and devices are configured and operated through MMIO registers (for example, a UART or GPIO peripheral). For a given peripheral the related registers are grouped together at a specific location in memory. `cppreg` makes it possible to *map* C++ constructs to such peripherals and registers in order to get safer and more expressive code. The following two examples illustrate how to define such constructs using `cppreg`.
When developing firmware code for embedded MCUs it is customary that peripherals and devices are configured and operated through MMIO registers (for example, a UART or GPIO peripheral). For a given peripheral the related registers are often grouped together at a specific location in memory. `cppreg` makes it possible to *map* C++ constructs to such peripherals registers and fields in order to get safer and more expressive code. The following two examples illustrate how to define such constructs using `cppreg`.
## Example: peripheral setup ##
@ -106,7 +106,7 @@ struct Peripheral {
RegBitSize::b8, // Register width in bits
8 * 2 // Offset in bits from the base.
> {
using Data = Field<TX, 8u, 0u, read_only>;
using Data = Field<TX, 8u, 0u, write_only>;
};
};
@ -118,7 +118,8 @@ Now let's assume that we want to setup and enable the peripheral following the p
1. we first set the mode, say with value `0x2`,
2. then the frequency, say with value `0x1A`,
3. then write `1` to the enable field to start the peripheral.
3. then write `1` to the enable field to start the peripheral,
4. if the peripheral fails to start or stop this will be set to zero.
Once enabled we also want to implement a echo loop that will simply read data from the RX register and put them in the TX register so that the peripheral will echo whatever it receives.
@ -130,14 +131,14 @@ Peripheral::Setup::Mode::write<0x2>();
Peripheral::Setup::Frequency::write<0x1A>();
Peripheral::Setup::Enable::set();
// Check if properly enabled.
if (!Peripheral::Setup::Enable::is_set()) {
// Peripheral failed to start ...
};
// Echo loopback.
while (true) {
// Check if sill enabled.
if (!Peripheral::Setup::Enable::is_set()) {
break;
};
// Read data.
const auto incoming_data = Peripheral::RX::Data::read();
@ -175,11 +176,6 @@ We can even add more expressive methods for our peripheral:
// Peripheral register.
struct PeripheralInterface : Peripheral {
// To enable the peripheral:
// write 1 to EN field; if the peripheral fails to start this will be reset to zero.
// To disable the peripheral:
// clear EN field; no effect if not enabled.
// Configuration with mode and frequency.
template <Mode::type mode, Frequency::type f>
inline static void configure() {
@ -193,6 +189,11 @@ struct PeripheralInterface : Peripheral {
Enable::set();
};
// Is enabled method.
inline static bool is_enabled() {
return Enable::is_set();
};
// Disable method.
inline static void disable() {
Enable::clear();
@ -265,10 +266,10 @@ namespace leds {
// Define the relevant fields.
// Some of these fields are write only (e.g., PSOR) but we define them
// as read write (it will always read zero but we will not read them).
using pin_direction = Field<gpio::pddr, 1u, Pin, AccessPolicy::rw>;
using pin_set = Field<gpio::psor, 1u, Pin, AccessPolicy::rw>;
using pin_clear = Field<gpio::pcor, 1u, Pin, AccessPolicy::rw>;
using pin_toggle = Field<gpio::ptor, 1u, Pin, AccessPolicy::rw>;
using pin_direction = Field<gpio::pddr, 1u, Pin, read_write>;
using pin_set = Field<gpio::psor, 1u, Pin, read_write>;
using pin_clear = Field<gpio::pcor, 1u, Pin, read_write>;
using pin_toggle = Field<gpio::ptor, 1u, Pin, read_write>;
// We also define some constants.
constexpr static const gpio::pddr::type pin_output_dir = 1u;
@ -298,7 +299,7 @@ namespace leds {
}
```
At this point we have defined an interface to initialize and control the LEDs attached to two GPIO pins. Note that, at no moment we had to deal with masking or shifting operations. Furthermore, we only needed to deal with the register addresses when defining the mapping. At compile time, `cppreg` also makes sure that the field actually fits within the register specifications (a `Field` type cannot overflow its `Register` type). Similarly, `cppreg` also checks that packed registers are properly aligned and fit within the pack.
At this point we have defined an interface to initialize and control the LEDs attached to two GPIO pins. Note that, at no moment we had to deal with masking or shifting operations. Furthermore, we only needed to deal with the register addresses when defining the mapping. At compile time `cppreg` also makes sure that the field actually fits within the register specifications (a `Field`-based type cannot overflow the register in which it is defined). Similarly, `cppreg` also checks that packed registers are properly aligned and fit within the pack.
Using this interface it becomes easy to write very expressive code such as:
@ -318,4 +319,4 @@ for (std::size_t i = 0; i < 500000; ++i)
leds::blue::off();
```
A quick note: in this example some of the registers are write-only (set, clear and toggle); in genereal extra care has to be taken when dealing with write-only fields or registers but for this example the implementation still work fine due to the nature of the GPIO registers. Check the [API documentation](API.md) for more details.
A quick note: in this example some of the fields are write-only (set, clear and toggle); in general extra care has to be taken when dealing with write-only fields but for this example the implementation still work fine due to the nature of the GPIO registers. Check the [API documentation](API.md) for more details.