Skip to content

Commit

Permalink
Merge #529
Browse files Browse the repository at this point in the history
529: Refactor user-guide of `book` r=Urhengulas a=Urhengulas

This PR aims to update the user guide of the book to the dirbaio PRs and is doing quite some refactoring on the side.

Preview: https://deploy-preview-529--admiring-dubinsky-56dff5.netlify.app

Co-authored-by: Johann Hemmann <johann.hemmann@code.berlin>
  • Loading branch information
bors[bot] and Urhengulas authored Jul 15, 2021
2 parents 5089314 + c38b98d commit 09278af
Show file tree
Hide file tree
Showing 17 changed files with 294 additions and 194 deletions.
1 change: 0 additions & 1 deletion book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
[Introduction](./introduction.md)
- [User guide](./user-guide.md)
- [Setup](./setup.md)
- [Libraries](./setup-library.md)
- [Applications](./setup-app.md)
- [Logging macros](./macros.md)
- [Primitives](./primitives.md)
Expand Down
30 changes: 15 additions & 15 deletions book/src/bitfields.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Bitfields

`:m..n` is the bitfield formatting parameter.
When paired with a positional parameter it can be used to display the bitfields of a register.
> `:M..N` is the bitfield formatting parameter.
The bitfield argument is expected to be a *unsigned* integer that's large enough to contain the bitfields.
For example, if bitfield ranges only cover up to bit `11` (e.g. `=8..12`) then the argument must be at least `u16`.

When paired with a positional parameter it can be used to display the bitfields of a register.
``` rust
# extern crate defmt;
# let pcnf1 = 0u32;
Expand All @@ -14,20 +17,17 @@ defmt::trace!(
);
```

The bitfield argument is expected to be a *fully typed, unsigned* integer that's large enough to contain the bitfields.
For example, if bitfield ranges only cover up to bit `11` (e.g. `=8..12`) then the argument must be `u16`.

Bit indices are little-endian: the 0th bit is the rightmost bit.

Bitfields are not range inclusive, e.g.
Bitfields are not range inclusive, e.g. following statement will evaluate to `5` (`0b110`):
``` rust
# extern crate defmt;
defmt::trace!("first two bits: {0=0..3}", 254u32);
// -> TRACE: first three bits: 110
defmt::trace!("first three bits: {0=0..3}", 254u32);
```
will evaluate to `2` (`0b10`).

⚠️ You can not reuse the same argument in a bitfield- and a non bitfield parameter. This will not compile:
``` rust,compile_fail
# extern crate defmt;
defmt::trace!("{0=5..13} {0=u16}", 256u16);
```
> ⚠️ You can not reuse the same argument in a bitfield- and a non bitfield parameter.
>
> This will not compile:
> ``` rust,compile_fail
> # extern crate defmt;
> defmt::trace!("{0=5..13} {0=u16}", 256u16);
> ```
24 changes: 13 additions & 11 deletions book/src/filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@

`defmt` supports 5 different logging levels listed below from lowest severity to highest severity:

- TRACE
- DEBUG
- INFO
- WARN
- ERROR
- `TRACE`
- `DEBUG`
- `INFO`
- `WARN`
- `ERROR`

By default all logging is *disabled*.
The amount of logging to perform can be controlled at the *crate* level using the following Cargo features:

- `defmt-default`, log at INFO, or TRACE, level and up
- `defmt-trace`, log at TRACE level and up
- `defmt-debug`, log at DEBUG level and up
- `defmt-info`, log at INFO level and up
- `defmt-warn`, log at WARN level and up
- `defmt-error`, log at ERROR level
| Feature | Log at ... |
| :-------------- | :----------------------------------- |
| `defmt-trace` | ... `TRACE` level and up |
| `defmt-debug` | ... `DEBUG` level and up |
| `defmt-info` | ... `INFO` level and up |
| `defmt-warn` | ... `WARN` level and up |
| `defmt-error` | ... `ERROR` level |
| `defmt-default` | ... `INFO`, or `TRACE`, level and up |

These features must only be enabled by the top level *application* crate as shown below.

Expand Down
31 changes: 20 additions & 11 deletions book/src/format-slices.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,47 @@
# Format slices / arrays

The `{=[?]}` parameter can be used to log a slices of values that implement the `Format` trait.
> The `{=[?]}` parameter can be used to log a slice of values that implement the `Format` trait.
The expected argument is a slice.

``` rust
# extern crate defmt;
# use defmt::{Format, info};
# use defmt::Format;
#
#[derive(Format)]
struct X {
y: u16,
z: u8,
}
let xs: &[X] = &[/* .. */];
info!("xs={=[?]}", xs);

let xs: &[X] = &[ /* .. */ ];
defmt::info!("xs={=[?]}", xs);
```

Note that for slices of bytes `{=[u8]}` should be preferred as it's better compressed.
`[T] where T: Format` also implements the `Format` trait so it's possible to format `[T]` with `{=?}` but `{=[?]}` uses slightly less bandwidth.

If you have an array of types that implement the `Format` trait, instead of a slice. You should use
the `{=[?; N]}` parameter (where `N` is a number); this saves bandwidth compared to `{=[?]}`.
> 💡 Note that for slices of bytes, `{=[u8]}` should be preferred as it's better compressed.

## Arrays

If you have an array of types that implement the `Format` trait, you should use
the `{=[?; N]}` parameter (where `N` is the number of elements); this saves bandwidth compared to `{=[?]}`.

``` rust
# extern crate defmt;
# use defmt::{Format, info};
# use defmt::Format;
#
#[derive(Format)]
struct X {
y: u16,
z: u8,
}

let xs: [X; 2] = [
# X { y: 1, z: 2 },
# X { y: 3, z: 4 },
# X { y: 1, z: 2 },
# X { y: 3, z: 4 },
// ..
];
info!("xs={=[?; 2]}", xs);
defmt::info!("xs={=[?; 2]}", xs);
```
13 changes: 6 additions & 7 deletions book/src/format.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## `#[derive(Format)]`

The easiest way to implement the `Format` trait for a struct or enum is to use the `derive` attribute.
The easiest way to implement the `Format` trait for a `struct` or `enum` is to use the `derive` attribute.

``` rust
# extern crate defmt;
Expand All @@ -17,20 +17,19 @@ struct Header {

# #[derive(Format)]
# struct Descriptor;
#
#[derive(Format)]
enum Request {
GetDescriptor { descriptor: Descriptor, length: u16 },
SetAddress { address: u8 },
}
```

NOTE: Like built-in derives like `#[derive(Debug)]`, `#[derive(Format)]` will add `Format` bounds to the generic type parameters of the struct.
Like built-in derives (e.g. `#[derive(Debug)]`), `#[derive(Format)]` will add `Format` bounds to the generic type parameters of the struct.

NOTE: Do *not* use the API used by the expansion of the `derive(Format)` macro; it is *unstable*.
> ⚠️ Do *not* use the API used by the expansion of the `derive(Format)` macro; it is *unstable*.
## `write!`

> NOTE `write!` is available in `defmt` v0.1.**2**+
## Manual implementation with `write!`

It is also possible to implement the `Format` trait manually.
This trait has a single required method: `format`.
Expand Down Expand Up @@ -76,7 +75,7 @@ impl defmt::Format for MyU8 {

If you quickly want to get some code running and do not care about it being efficient you can use the two adapter types [`Display2Format`] and [`Debug2Format`].

These adapters disable compression and use the `core::fmt` code on-device! You should always prefer `defmt::Format` over `Debug` whenever possible.
> ⚠️ These adapters disable compression and use the `core::fmt` code on-device! You should always prefer `defmt::Format` over `Debug` whenever possible!
Note that this always uses `{:?}` to format the contained value, meaning that any provided defmt display hints will be ignored.

Expand Down
58 changes: 49 additions & 9 deletions book/src/global-logger.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,66 @@
# #[global_logger]

*Applications* that, directly or transitively, use any of `defmt` logging macros need to define a `#[global_logger]` or include one in their dependency graph.
> *Applications* that, directly or transitively, use any of `defmt` logging macros need to define a `#[global_logger]` or include one in their dependency graph.
This is similar to how the `alloc` crate depends on a `#[global_allocator]`.

The `global_logger` defines how data is moved from the *device*, where the application runs, to the host, where logs will be formatted and displayed.
`global_logger` is transport agnostic: you can use a serial interface, serial over USB, RTT, semihosting, Ethernet, 6LoWPAN, etc. to transfer the data.

The `global_logger` interface comprises two traits, `Write` and `Logger`, and one attribute, `#[global_logger]`.
The `global_logger` interface comprises the trait `Logger` and the `#[global_logger]` attribute.

## The `Logger` trait

`Logger` specifies how to acquire and release a handle to a global logger, as well as how the data is put on the wire.

```rust
# extern crate defmt;
# struct Logger;
#
unsafe impl defmt::Logger for Logger {
fn acquire() {
// ...
}
unsafe fn release() {
// ...
}
unsafe fn write(bytes: &[u8]) {
// ...
}
}
```

The `Write` trait specifies how the data is put on the wire.
The `write` method is not allowed to fail.
Buffering, rather than waiting on I/O, is recommended.
If using buffering `write` should not overwrite old data as this can corrupt log frames and most printers cannot deal with incomplete log frames.

The `Logger` specifies how to acquire and release a handle to a global logger.
See the API documentation for more details about the safety requirements of the acquire-release mechanism.

Finally, `#[global_logger]` specifies which `Logger` implementation will be used by the application.
`#[global_logger]` must be used on a *unit* struct, a struct with no fields.
This struct must implement the `Logger` trait.

## The `#[global_logger]` attribute

`#[global_logger]` specifies which `Logger` implementation will be used by the application.

`#[global_logger]` must be used on a *unit* struct, a struct with no fields, which must implement the `Logger` trait.
It's recommended that this struct is kept private.
Only a single `#[global_logger]` struct can appear in the dependency graph of an application.
The `global_logger` should be selected *at the top* of the dependency graph, that is in the application crate.

```rust
# extern crate defmt;
#
#[defmt::global_logger]
struct Logger;

unsafe impl defmt::Logger for Logger {
// ...
# fn acquire() {}
# unsafe fn release() {}
# unsafe fn write(bytes: &[u8]) {}
}
```

> ⚠️ Only a single `#[global_logger]` struct can appear in the dependency graph of an application.
>
> Therefore the `global_logger` should be selected *at the top* of the dependency graph, that is in the application crate.
There are two general ways to implement a `global_logger`.

Expand All @@ -40,6 +79,7 @@ The other approach uses multiple logging channels: e.g. one for each priority le
With this approach logging can be made lock-free: interrupts are not disabled while logging data.
This approach requires channel multiplexing in the transport layer.
RTT, for example, natively supports multiple channels so this is not an issue, but other transports, like ITM, will require that each log frame to be tagged with the channel it belongs to (e.g. one logging channel = ITM channel).

The trade-offs of using more channels are:
- Lock-freedom
- higher memory usage on the target, for buffering
Expand Down
83 changes: 49 additions & 34 deletions book/src/hints.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,75 @@
# Display hints

A hint can be applied to each formatting parameter to change how it's printed on the host side.
The hint follows the syntax `:x` and must come after the type within the braces.
Examples: typed `{=u8:x}`, untyped `{:b}`
> A hint can be applied to each formatting parameter to change how it's printed on the host side.
The hint follows the syntax `:Display` and must come after the type within the braces, for example:
* typed `{=u8:x}`
* untyped `{:b}`

The following display hints are currently supported:

- `x`, lowercase hexadecimal
- `X`, uppercase hexadecimal
- `?`, `core::fmt::Debug`-like
- `b`, binary
- `a`, ASCII
- `us`, microseconds (formats integers as time stamps)
| hint | name |
| :---- | :--------------------------------------------- |
| `:x` | lowercase hexadecimal |
| `:X` | uppercase hexadecimal |
| `:?` | `core::fmt::Debug`-like |
| `:b` | binary |
| `:a` | ASCII |
| `:us` | microseconds (formats integers as time stamps) |

The first 4 display hints resemble what's supported in `core::fmt`. Examples below:
The first 4 display hints resemble what's supported in `core::fmt`, for example:

``` rust
# extern crate defmt;
defmt::info!("{=u8:x}", 42); // -> INFO 2a
defmt::info!("{=u8:X}", 42); // -> INFO 2A
defmt::info!("{=u8:x}", 42); // -> INFO 2a
defmt::info!("{=u8:X}", 42); // -> INFO 2A
defmt::info!("{=u8:#x}", 42); // -> INFO 0x2a
defmt::info!("{=u8:b}", 42); // -> INFO 101010
defmt::info!("{=u8:b}", 42); // -> INFO 101010

defmt::info!("{=str}", "hello\tworld"); // -> INFO hello world
defmt::info!("{=str:?}", "hello\tworld"); // -> INFO "hello\tworld"
```

Leading zeros are supported, for example
The ASCII display hint formats byte slices (and arrays) using Rust's byte string syntax.

``` rust
# extern crate defmt;
defmt::info!("{=u8:03}", 42); // -> INFO 042
defmt::info!("{=u8:08X}", 42); // -> INFO 0000002A
defmt::info!("{=u8:#010X}", 42); // -> INFO 0x0000002A
let bytes = [104, 101, 255, 108, 108, 111];

defmt::info!("{=[u8]:a}", bytes); // -> INFO b"he\xffllo"
```

When the alternate form is used for hex and binary, the `0x`/`0b` length is subtracted from the
leading zeros. This matches [`core::fmt` behavior](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b11809759f975e266251f7968e542756). No further
customization is supported (at the moment).
## Alternate printing

The ASCII display hint formats byte slices (and arrays) using Rust's byte string syntax.
Adding `#` in front of a binary and hexadecimal display hints, precedes these numbers with a base indicator.

``` rust
# extern crate defmt;
let bytes = [104, 101, 255, 108, 108, 111];
defmt::info!("{=u8:b}", 42); // -> INFO 101010
defmt::info!("{=u8:#b}", 42); // -> INFO 0b101010

defmt::info!("{=u8:x}", 42); // -> INFO 2a
defmt::info!("{=u8:#x}", 42); // -> INFO 0x2a

defmt::info!("{=u8:X}", 42); // -> INFO 2A
defmt::info!("{=u8:#X}", 42); // -> INFO 0x2A
```

## Zero padding

Padding numbers with leading zeros is supported, for example:

defmt::info!("{=[u8]:a}", bytes);
// -> INFO b"he\xffllo"
``` rust
# extern crate defmt;
defmt::info!("{=u8}", 42); // -> INFO 42
defmt::info!("{=u8:04}", 42); // -> INFO 0042

defmt::info!("{=u8:08X}", 42); // -> INFO 0000002A
defmt::info!("{=u8:#08X}", 42); // -> INFO 0x00002A
```

When the alternate form is used for hex and binary, the `0x`/`0b` length is subtracted from the leading zeros. This matches [`core::fmt` behavior](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b11809759f975e266251f7968e542756).

## Propagation

Display hints "propagate downwards" and apply to formatting parameters that specify no display hint.
Expand All @@ -59,11 +80,8 @@ Display hints "propagate downwards" and apply to formatting parameters that spec
struct S { x: u8 }

let x = S { x: 42 };
defmt::info!("{}", x);
// -> INFO S { x: 42 }

defmt::info!("{:#x}", x);
// -> INFO S { x: 0x2a }
defmt::info!("{}", x); // -> INFO S { x: 42 }
defmt::info!("{:#x}", x); // -> INFO S { x: 0x2a }
```

``` rust
Expand All @@ -78,9 +96,6 @@ impl defmt::Format for S {
}

let x = S { x: 42, y: 42 };
defmt::info!("{}", x);
// -> INFO S { x: 42, y: 2a }

defmt::info!("{:b}", x);
// -> INFO S { x: 101010, y: 2a }
defmt::info!("{}", x); // -> INFO S { x: 42, y: 2a }
defmt::info!("{:b}", x); // -> INFO S { x: 101010, y: 2a }
```
Loading

0 comments on commit 09278af

Please sign in to comment.