Progmem utilities for the AVR architectures.
This crate provides unsafe utilities for working with data stored in
the program memory of an AVR micro-controller. Additionally, it defines a
'best-effort' safe wrapper struct ProgMem
to simplify working with it,
as well as a PmString
wrapper for string
handling.
This crate is implemented only in Rust and some short assembly, it does NOT
depend on the avr-libc
or any other C-library. However, due to the use
of inline assembly, this crate may only be compiled using a nightly Rust
compiler (as of mid 2022, inline assembly for AVR is still 'experimental').
This crate works with a Rust nightly-2023-08-08
compiler.
All versions 0.4.x
will adhere to work with nightly-2023-08-08
.
Other Rust compiler version (particularly newer ones) might also work,
but due to the use of experimental compiler features it is possible that
some future Rust compiler version will fail to work.
Future versions such as 0.5.x
might required a newer Rust compiler
version.
This crate is specifically for AVR-base micro-controllers such as the Arduino Uno (and some other Arduino boards, but not all), which have a modified Harvard architecture which implies the strict separation of program code and data while having special instructions to read and write to program memory.
While, of course, all ordinary data is stored in the data domain where it is
perfectly usable, the harsh constraints of most AVR processors make it very
appealing to use the program memory (also referred to as progmem) for
storing constant values. However, due to the Harvard design, those values
are not usable with normal instructions (i.e. those emitted from normal
Rust code). Instead, special instructions are required to load data from
the program code domain, such as the lpm
(load [from] program memory)
instruction. And because there is no way to emit it from Rust code, this
crate uses inline assembly to emit that instruction.
However, since a pointer into program code cannot be differentiated from a
normal data pointer, it is entirely up to the programmer to ensure that
these different 'pointer-types' are not accidentally mixed. In other words,
this is unsafe
in the context of Rust.
The first part of this crate simply provides a few functions (e.g.
read_byte
) to load constant data (i.e. a Rust static
that is
immutable) from the program memory into the data domain, so that
sub-sequentially it is normal usable data, i.e. as owned data on the stack.
Because, as aforementioned, a simple *const u8
in Rust does not specify
whether is lives in the program code domain or the data domain, all
functions which simply load a given pointer from the program memory are
inherently unsafe
.
Notice that using references (e.g. &u8
) to the program code domain should
generally be avoided because references in Rust should be dereferencable,
which the program code domain is not.
Additionally references can be easily dereferenced by safe code,
which would be undefined behavior if that reference points into the
program memory.
Therefore, a Rust reference to a static
that is stored in program memory
must be considered hazardous (if not UB),
and it is recommended to only use raw pointers to those static
s,
e.g. obtained via the addr_of!
macro,
which directly creates raw pointers without needing a reference.
use avr_progmem::raw::read_byte;
use core::ptr::addr_of;
// This `static` must never be directly dereferenced/accessed!
// So a `let data: u8 = P_BYTE;` ⚠️ is **undefined behavior**!!!
/// Static byte stored in progmem!
#[link_section = ".progmem.data"]
static P_BYTE: u8 = b'A';
// Load the byte from progmem
// Here, it is sound, because due to the link_section it is indeed in the
// program code memory.
let data: u8 = unsafe { read_byte(addr_of!(P_BYTE)) };
assert_eq!(b'A', data);
Since working with progmem data is inherently unsafe and rather
difficult to do correctly, this crate introduces the best-effort 'safe'
wrapper ProgMem
,
that is supposed to only wrap data in progmem, thus
offering only functions to load its content using the progmem loading
function introduced above.
Using these functions is sound, if that the wrapper data is really stored
in the program memory. Therefore, to enforce this invariant,
the constructor of ProgMem
is unsafe
.
Additionally, since proper Rust references (unlike pointers) come with a lot
special requirements, it should be considered hazardous to have a reference
to data stored in program memory.
Instead, only raw pointers to this kind of data should be kept,
created e.g. via the addr_of!
macro.
Consequently, the ProgMem
just wrap a pointer to data in progmem,
which in turn must be stored in a static
marked with
#[link_section = ".progmem.data"]
.
However, since, safe Rust can always create a "normal" Rust reference to any
(accessible) static
, it must be considered hazardous if not just unsound,
to expose such a static
to safe Rust code.
To also make this easier (and less hazardous), this crate provides the
progmem!
macro, which will create a hidden static
in program memory
initialized with the data you give it,
wrap it's pointer in the ProgMem
struct,
and put that wrapper into yet another (normal RAM) static, so you can
access it.
This will ensure that the static
that is stored in program memory can not
be referenced by safe Rust code (because it is not accessible),
while the accessible ProgMem
wrapper allows access to the underling data
by loading it correctly from program memory.
use avr_progmem::progmem;
// It will be wrapped in the ProgMem struct and expand to:
// ```
// static P_BYTE: ProgMem<u8> = {
// #[link_section = ".progmem.data"]
// static INNER_HIDDEN: u8 = 42;
// unsafe { ProgMem::new(addr_of!(INNER_HIDDEN)) }
// };
// ```
// Thus it is impossible for safe Rust to directly access the progmem data!
progmem! {
/// Static byte stored in progmem!
static progmem P_BYTE: u8 = 42;
}
// Load the byte from progmem
// This is sound, because the `ProgMem` always uses the special operation to
// load the data from program memory.
let data: u8 = P_BYTE.load();
assert_eq!(42, data);
Using strings such as &str
with ProgMem
is rather difficult, and
surprisingly hard if Unicode support is needed
(see issue #3).
Thus, to make working with string convenient the
PmString
struct is provided on top of
ProgMem
.
PmString
stores any given &str
as statically sized
UTF-8 byte array (with full Unicode support).
To make its content usable, it provides a Display
& uDisplay
implementation, a lazy chars
iterator,
and load
function similar to
ProgMem
's
load
,
that yields a LoadedString
,
which in turn defers to &str
.
For more details see the string module.
use avr_progmem::progmem;
progmem! {
// A simple Unicode string in progmem.
static progmem string TEXT = "Hello 大賢者";
}
// You can load it and use that as `&str`
let buffer = TEXT.load();
assert_eq!("Hello 大賢者", &*buffer);
// Or you use directly the `Display` impl
assert_eq!("Hello 大賢者", format!("{}", TEXT));
Additionally, two special macros are provided similar to the F
macro
of the Arduino IDE, that allows to "mark" a string as to be stored in
progmem while being returned at this place as a loaded &str
.
// Or you skip the static and use in-line progmem strings:
use avr_progmem::progmem_str as F;
use avr_progmem::progmem_display as D;
// Either as `&str`
assert_eq!("Foo 大賢者", F!("Foo 大賢者"));
// Or as some `impl Display + uDisplay`
assert_eq!("Bar 大賢者", format!("{}", D!("Bar 大賢者")));
If you enabled the ufmt
crate feature (its a default feature),
you can also use uDisplay
in addition to Display
.
use avr_progmem::progmem;
use avr_progmem::progmem_str as F;
use avr_progmem::progmem_display as D;
fn foo<W: ufmt::uWrite>(writer: &mut W) {
progmem! {
// A simple Unicode string in progmem.
static progmem string TEXT = "Hello 大賢者";
}
// You can use the `uDisplay` impl
ufmt::uwriteln!(writer, "{}", TEXT);
// Or use the in-line `&str`
writer.write_str(F!("Foo 大賢者\n"));
// Or the in-line `impl uDisplay`
ufmt::uwriteln!(writer, "{}", D!("Bar 大賢者"));
}
//
As mentioned before, this crate is specifically designed to be use with
AVR-base micro-controllers. But since most of us don't write their programs
on an AVR system but e.g. on x86 systems, and might want to test them
there (well as far as it is possible), this crate also has a fallback
implementation for all other architectures that are not AVR, falling back
to a simple Rust static
in the default data segment. And all the data
loading functions will just dereference the pointed-to data, assuming that
they just live in the default location.
This fallback is perfectly safe on x86 and friend, and should also be fine
on all further architectures, otherwise normal Rust static
s would be
broken. However, it is an important point to know when for instance writing
a library that is not limited to AVR.
Aside from what has been already been covered, the current implementation has two further limitations.
First, since this crate uses an inline assembly loop on a 8-bit
architecture, the loop counter only allows values up to 255. This means
that not more that 255 bytes can be loaded at once with any of the methods
of this crate. However, this only applies to a single continuous load
operation, so for instance ProgMem<[u8;1024]>::load()
will panic, but
accessing such a big type in smaller chunks e.g.
ProgMem<[u8;1024]>::load_sub_array::<[u8;128]>(512)
is perfectly fine
because the to be loaded type [u8;128]
is only 128 bytes in size.
Notice that the same limitation holds for PmString<N>::load()
(i.e. you can only use it if N <= 255
holds.
On the other hand, there is no such limitation on PmString<N>::chars()
and PmString
's Display
/uDisplay
implementation,
because those, just load each char
individually
(i.e. no more that 4 bytes at a time).
Second, since this crate only uses the lpm
instruction, which is limited
by a 16-bit pointer, this crate may only be used with data stored in the
lower 64 kiB of program memory. Since this property has not be tested it is
unclear whether it will cause a panic or right-up undefined behavior, so be
very wary when working with AVR chips that have more then 64 kiB of program
memory.
Licensed under Apache License, Version 2.0 (LICENSE or https://www.apache.org/licenses/LICENSE-2.0).
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.