Skip to content

Commit 3976ba2

Browse files
committed
Add string utilities to make working with string literals easier.
See #3
1 parent 1ebc117 commit 3976ba2

File tree

3 files changed

+243
-0
lines changed

3 files changed

+243
-0
lines changed

examples/uno-string.rs

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//
2+
// This file provides a example on how to use strings on an Arduino Uno.
3+
//
4+
5+
6+
// Define no_std only for AVR
7+
#![cfg_attr(target_arch = "avr", no_std)]
8+
#![no_main]
9+
//
10+
// To unwrap the Option in const context
11+
#![feature(const_option)]
12+
13+
14+
use avr_progmem::progmem; // The macro
15+
use avr_progmem::string::ByteString; // Helper for storing strings
16+
#[cfg(target_arch = "avr")]
17+
use panic_halt as _; // halting panic implementation for AVR
18+
19+
20+
progmem! {
21+
/// The static data to be stored in program code section
22+
/// Notice the usage of `ByteString`, to store a string as `[u8;N]`,
23+
/// because you can't store a `str` and storing a `&str` wouldn't have
24+
/// much of an effect.
25+
static progmem SOME_TEXT: ByteString<189> = ByteString::new("
26+
A long test string literal, that is stored in progmem instead of DRAM.
27+
However, to use it, it needs to be temporarily load into DRAM,
28+
so an individual `ByteString` shouldn't be too long.
29+
").unwrap();
30+
31+
/// More data to be stored in program code section
32+
static progmem MORE_TEXT: ByteString<102> = ByteString::new("
33+
However, you easily store your strings individual, limiting the amount of
34+
temporary DRAM necessary.
35+
").unwrap();
36+
37+
/// Unicode works of course as expected
38+
///
39+
static progmem UNICODE_TEXT: ByteString<137> = ByteString::new(
40+
"dai 大賢者 kenja, Völlerei lässt grüßen, le garçon de théâtre, Ελληνική Δημοκρατία, Слава Україні"
41+
).unwrap();
42+
}
43+
44+
// Include a fancy printer supporting Arduino Uno's USB-Serial output as well
45+
// as stdout on non-AVR targets.
46+
mod printer;
47+
use printer::Printer;
48+
49+
#[no_mangle]
50+
fn main() -> ! {
51+
let mut printer = {
52+
#[cfg(target_arch = "avr")]
53+
{
54+
// Initialize the USB-Serial output on the Arduino Uno
55+
56+
let dp = arduino_uno::Peripherals::take().unwrap();
57+
58+
let mut pins = arduino_uno::Pins::new(dp.PORTB, dp.PORTC, dp.PORTD);
59+
60+
let serial = arduino_uno::Serial::new(
61+
dp.USART0,
62+
pins.d0,
63+
pins.d1.into_output(&mut pins.ddr),
64+
9600,
65+
);
66+
Printer(serial)
67+
}
68+
#[cfg(not(target_arch = "avr"))]
69+
{
70+
// Just use stdout for non-AVR targets
71+
Printer
72+
}
73+
};
74+
75+
// Print some introduction text
76+
printer.println("Hello from Arduino!");
77+
printer.println("");
78+
printer.println("--------------------------");
79+
printer.println("");
80+
81+
// Scope to limit the lifetime of `text_buffer`
82+
{
83+
// The temporary DRAM buffer for the string
84+
let text_buffer = SOME_TEXT.load();
85+
let text: &str = &text_buffer; // Just derefs to `str`
86+
printer.println(text);
87+
}
88+
89+
// Or just using temporaries
90+
printer.println(&MORE_TEXT.load());
91+
printer.println(&UNICODE_TEXT.load());
92+
93+
// Even more convenient: use a one-off in-place progmem static via `progmem_str`
94+
printer.println(avr_progmem::progmem_str!("Just a lone literal progmem str"));
95+
printer.println(avr_progmem::progmem_str!("And another one"));
96+
97+
// Print some final lines
98+
printer.println("");
99+
printer.println("--------------------------");
100+
printer.println("");
101+
printer.println("DONE");
102+
103+
// It is very convenient to just exit on non-AVR platforms, otherwise users
104+
// might get the impression that the program hangs, whereas it already
105+
// succeeded.
106+
#[cfg(not(target_arch = "avr"))]
107+
std::process::exit(0);
108+
109+
// Otherwise, that is on AVR, just go into an infinite loop, because on AVR
110+
// we just can't exit!
111+
loop {
112+
// Done, just do nothing
113+
}
114+
}

src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
// However, it seems in more recent Rust version there is no more `llvm_asm`.
66
// And docs.rs uses the latest Rust version.
77
#![cfg_attr(not(doc), feature(llvm_asm))]
8+
//
9+
// For string support, we need to convert from slice to array in const context.
10+
#![cfg_attr(not(doc), feature(const_raw_ptr_deref))]
811

912
//!
1013
//! Progmem utilities for the AVR architectures.
@@ -187,6 +190,7 @@
187190

188191

189192
pub mod raw;
193+
pub mod string;
190194
mod wrapper;
191195

192196
pub use wrapper::ProgMem;

src/string.rs

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use core::ops::Deref;
2+
3+
4+
5+
/// Our own `core::array::TryFromSliceError`
6+
///
7+
/// Used in [`array_ref_try_from_slice`].
8+
// We need a local copy of this type, because we need to instantiate it, but it
9+
// has a private field.
10+
struct TryFromSliceError(());
11+
12+
/// Const version of `<&[T; N]>::try_from(&[T])`
13+
///
14+
/// Original Source:
15+
/// https://github.com/rust-lang/rust/blob/eb82facb1626166188d49599a3313fc95201f556/library/core/src/array/mod.rs#L203-L215
16+
const fn array_ref_try_from_slice<'a, T, const N: usize>(
17+
slice: &[T],
18+
) -> Result<&[T; N], TryFromSliceError> {
19+
if slice.len() == N {
20+
let ptr = slice.as_ptr() as *const [T; N];
21+
// SAFETY: ok because we just checked that the length fits
22+
unsafe { Ok(&*ptr) }
23+
} else {
24+
Err(TryFromSliceError(()))
25+
}
26+
}
27+
28+
/// A string stored as byte array.
29+
///
30+
/// This type is a simple wrapper around a byte array `[u8;N]` and therefore,
31+
/// is stored as such.
32+
/// However, this type primarily is created from `&str` and derefs to `&str`,
33+
/// thus it can be used similar to `String` except that it is not mutable.
34+
///
35+
/// This type is particularly useful to store string literals in progmem.
36+
///
37+
/// # Example
38+
///
39+
/// ```rust
40+
/// #![feature(const_option)]
41+
///
42+
/// use avr_progmem::progmem;
43+
/// use avr_progmem::string::ByteString;
44+
///
45+
/// progmem! {
46+
/// // Stores a string as a byte array, i.e. `[u8;19]`, but makes it usable
47+
/// // as `&str` (via `Deref`)
48+
/// static progmem TEXT: ByteString<19> = ByteString::new(
49+
/// "dai 大賢者 kenja"
50+
/// ).unwrap();
51+
/// }
52+
///
53+
/// // usage:
54+
/// let text_buffer = TEXT.load(); // The temporary DRAM buffer for `TEXT`
55+
/// let text: &str = &text_buffer; // Just derefs to `str`
56+
/// assert_eq!(text, "dai 大賢者 kenja")
57+
/// ```
58+
59+
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
60+
pub struct ByteString<const N: usize>(pub [u8; N]);
61+
62+
impl<const N: usize> ByteString<N> {
63+
/// Creates a new byte array from the given string
64+
pub const fn new(s: &str) -> Option<Self> {
65+
Self::from_bytes(s.as_bytes())
66+
}
67+
68+
/// Wraps the given byte slice
69+
pub const fn from_bytes(bytes: &[u8]) -> Option<Self> {
70+
let res = array_ref_try_from_slice(bytes);
71+
72+
match res {
73+
Ok(array) => Some(Self(*array)),
74+
Err(_e) => None,
75+
}
76+
}
77+
}
78+
79+
impl<const N: usize> Deref for ByteString<N> {
80+
type Target = str;
81+
82+
fn deref(&self) -> &str {
83+
core::str::from_utf8(&self.0).unwrap()
84+
}
85+
}
86+
87+
/// Define a string in progmem
88+
///
89+
/// This is a short-cut macro to create an ad-hoc static storing the given
90+
/// string literal as by [`ByteString`] and load it here from progmem into a
91+
/// temporary and return it as `&str`.
92+
///
93+
/// This macro allows to conveniently put literal string into progmem exactly,
94+
/// where they are used. However, since they are directly loaded into a
95+
/// temporary you don't get a `&'static str` back, and must use the `&str`
96+
/// immediately (i.e. pass it as a function parameter).
97+
/// You can't even store the returned `&str` in a local `let` assignment.
98+
///
99+
/// # Example
100+
///
101+
/// ```rust
102+
/// #![feature(const_option)]
103+
///
104+
/// use avr_progmem::progmem_str as S;
105+
///
106+
/// fn print(s: &str) {
107+
/// // -- snip --
108+
/// # assert_eq!(s, "dai 大賢者 kenja")
109+
/// }
110+
///
111+
/// // Put the literal as byte array into progmem and load it here as `&str`
112+
/// print(S!("dai 大賢者 kenja"));
113+
/// ```
114+
#[macro_export]
115+
macro_rules! progmem_str {
116+
($text:literal) => {{
117+
const TEXT_LEN: usize = <str>::as_bytes($text).len();
118+
$crate::progmem! {
119+
static progmem TEXT: $crate::string::ByteString<TEXT_LEN> = $crate::string::ByteString::new(
120+
$text
121+
).unwrap();
122+
}
123+
&*TEXT.load()
124+
}};
125+
}

0 commit comments

Comments
 (0)