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
use core::convert::From;

/// 16 or 24 bit program memory address
///
/// Used internally to provide correct page allignment and efficient storage.
/// Use u16.into() or u32.into() to suit your target MCU's address space size.
///
/// Although this struct is always 3 bytes in size, on an MCU with <65kB of flash memory,
/// the highest byte is optimised away completely, taking it's effective size down to only 2 bytes.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Address {
    base: u16,
    ramp: u8,
}

impl Address {
    const PCWORD_MASK: u16 = (crate::SPM_PAGESIZE_BYTES - 1) as u16;
    const PCPAGE_MASK: u16 = !Self::PCWORD_MASK;

    fn new(base: u32) -> Self {
        Self {
            base: base as u16,
            ramp: (base >> 16) as u8,
        }
    }

    /// Mask off the PCWORD part of the address, leaving only PCPAGE.  
    ///
    /// The resulting address is aligned to the start of the page.
    pub fn into_page_aligned(self) -> Self {
        Self {
            base: self.base & Self::PCPAGE_MASK,
            ramp: self.ramp,
        }
    }

    /// The word byte index within the page: technically PCWORD << 1
    pub fn word(&self) -> u16 {
        self.base & Self::PCWORD_MASK
    }

    /// The extended byte of the address, usually written to RAMPZ on MCUs with extended addressing
    pub fn ramp(&self) -> u8 {
        self.ramp
    }
}

impl From<u16> for Address {
    fn from(i: u16) -> Self {
        Self::new(i as u32)
    }
}

impl From<u8> for Address {
    fn from(i: u8) -> Self {
        Self::new(i as u32)
    }
}

impl From<u32> for Address {
    fn from(i: u32) -> Self {
        Self::new(i)
    }
}

impl From<Address> for u32 {
    fn from(address: Address) -> u32 {
        address.base as u32 + ((address.ramp as u32) << 16)
    }
}

impl From<Address> for u16 {
    fn from(address: Address) -> u16 {
        address.base
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_aligns_addess_to_page() {
        let start_address = crate::SPM_PAGESIZE_BYTES as u32 * 8 + 17;
        let address: Address = start_address.into();

        assert_eq!(
            address.into_page_aligned(),
            Address::new(crate::SPM_PAGESIZE_BYTES as u32 * 8)
        );
    }

    #[test]
    fn it_masks_pcword_part() {
        let start_address = crate::SPM_PAGESIZE_BYTES as u32 * 8 + 17;
        let address: Address = start_address.into();

        assert_eq!(address.word(), 17);
    }
}