1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
//! Non-volatile storage in the MSP430FR5969's FRAM.
//!
//! FRAM (ferroelectric RAM) is the defining feature of this part: non-volatile
//! like flash, but **written like RAM**. A write is a single `MOV` to memory —
//! there is no erase step, no unlock/password sequence, no program-then-verify,
//! and no busy flag to poll. Write time equals read time, and endurance is
//! ~10^15 cycles (datasheet SLAS704G Table 5-34). ECC and the small read cache
//! are handled transparently by the FRAM controller. That makes FRAM a near
//! perfect fit for the byte-granular [`embedded_storage`] traits, which this
//! module implements.
//!
//! # Two regions, two backends
//!
//! The FR5969's 63 KB of main FRAM is one contiguous block, `0x4400..=0x13FFF`,
//! that **straddles the 16-bit address boundary** (datasheet Table 6-6). That
//! split is the whole story of this module:
//!
//! - [`InfoFram`] — the **Information FRAM** at `0x1800..=0x19FF` (512 B). This
//! is ordinary low memory, reached with normal 16-bit pointers and volatile
//! loads/stores. It is TI's intended home for persistent configuration and
//! calibration, and (like the upper region) sits outside every linker
//! `MEMORY` region, so using it can never clobber code or data.
//!
//! - [`HighFram`] — the **upper 16 KB** at `0x10000..=0x13FFF`: the storage
//! "beyond the first 48 K." This address is **above the 16-bit address
//! space**, so no Rust pointer (`usize`/`*mut T` are 16-bit on
//! `msp430-none-elf`) can name it. See below.
//!
//! Both backends implement [`embedded_storage::ReadStorage`] and
//! [`embedded_storage::Storage`]; `offset` is relative to the start of each
//! region.
//!
//! # Reaching the upper 16 KB (the 20-bit problem)
//!
//! The MSP430**X** CPU in this chip has a 20-bit address space, but two things
//! conspire to hide the top of FRAM from Rust:
//!
//! 1. rustc's `msp430-none-elf` target emits **baseline MSP430** code, where
//! pointers and `usize` are 16 bits. `0x10000` does not fit in a pointer.
//! 2. LLVM's MSP430 backend defines **no MSP430X instructions**, so even inline
//! `asm!` cannot use the `MOVX`/`MOVA` mnemonics that perform 20-bit access —
//! the integrated assembler rejects them.
//!
//! TI's C toolchain solves the identical problem with the `__data20_read_char` /
//! `__data20_write_char` runtime intrinsics (the "large data model"), which are
//! just hand-written MSP430X instruction sequences. We do the same: the byte
//! primitives below emit the 20-bit instructions as raw `.word` opcodes inside
//! an `asm!` block, pinned to explicit registers. The opcodes were verified
//! against the TI assembler (`msp430-elf-as -mcpu=msp430x`) — see the comments
//! on [`read_high_byte`].
//!
//! Wait states (`FRCTL0.NWAITS`) stay at their reset default of 0 because this
//! project runs MCLK at 1 MHz (they are only needed above 8 MHz), and the MPU is
//! disabled out of reset, so all of FRAM is writable. This module therefore
//! never touches the FRAM controller — which is just as well, since the PAC's
//! generated `frctl0().write()` would zero the mandatory `0xA5` password byte
//! and trigger a PUC.
//!
//! # Example: a reset-surviving boot counter in Info FRAM
//!
//! ```ignore
//! use hal::fram::InfoFram;
//! use hal::embedded_storage::{ReadStorage, Storage};
//!
//! let mut info = InfoFram::new();
//! let mut buf = [0u8; 4];
//! info.read(0, &mut buf).unwrap();
//! let mut count = u32::from_le_bytes(buf);
//! count = count.wrapping_add(1);
//! info.write(0, &count.to_le_bytes()).unwrap(); // persists across power-cycles
//! ```
use crate;
use ;
pub use crateFramError as Error;
/// Byte-addressable storage backed by the 512 B of Information FRAM
/// (`0x1800..=0x19FF`), reached with ordinary 16-bit pointers.
///
/// A zero-sized handle. Construct one with [`InfoFram::new`]. Holding more than
/// one at a time is not unsafe (accesses are volatile byte loads/stores) but is
/// logically a shared-mutable-resource hazard — treat it as a singleton.
/// Byte-addressable storage backed by the upper 16 KB of FRAM
/// (`0x10000..=0x13FFF`) — the region "beyond the first 48 K," reached with
/// hand-emitted MSP430X 20-bit instructions.
///
/// A zero-sized handle; see [`HighFram::new`]. Access is one byte per `asm!`
/// block, so this is slower than [`InfoFram`]; it is meant for bulk persistent
/// data rather than hot loops.
// The 20-bit base baked into the `MOVA #0x10000` immediate below must match the
// region constant; assert it at compile time so the two cannot drift apart.
const _: = assert!;
/// Read one byte from upper FRAM at `offset` (0-based within the 16 KB region).
///
/// The address arithmetic and access are done entirely in MSP430X 20-bit
/// instructions, emitted as raw opcodes because LLVM's assembler has no
/// `MOVX`/`MOVA` mnemonics. Register allocation is pinned: R12 is the 20-bit
/// address scratch, R13 carries `offset` in and the result byte out.
///
/// Opcodes (confirmed with `msp430-elf-as -mcpu=msp430x`):
/// ```text
/// 018C 0000 MOVA #0x10000, R12 ; R12 = 0x10000 (20-bit base)
/// 0DEC ADDA R13, R12 ; R12 += offset -> absolute address
/// 1840 4C6D MOVX.B @R12, R13 ; R13 = [R12] (byte; upper bits cleared)
/// ```
///
/// # Safety
/// `offset` must be `< 16384` so the absolute address stays within upper FRAM.
unsafe
/// Write one byte `val` to upper FRAM at `offset` (0-based within the region).
///
/// Same scheme as [`read_high_byte`]: R12 is the address scratch, R13 carries
/// `offset` in, R14 carries the value. A FRAM store is a plain (extended) `MOV`
/// — no erase or unlock.
///
/// Opcodes (confirmed with `msp430-elf-as -mcpu=msp430x`):
/// ```text
/// 018C 0000 MOVA #0x10000, R12 ; R12 = 0x10000
/// 0DEC ADDA R13, R12 ; R12 += offset
/// 1840 4ECC 0000 MOVX.B R14, 0(R12) ; [R12] = R14 (low byte), index 0
/// ```
///
/// # Safety
/// `offset` must be `< 16384` so the absolute address stays within upper FRAM.
unsafe