mlx9064x/mlx90641/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright © 2021 Will Ross
3//! MLX90641 specific details.
4mod address;
5mod eeprom;
6pub mod hamming;
7
8// Various floating point operations are not implemented in core, so we use libm to provide them as
9// needed.
10#[cfg_attr(feature = "std", allow(unused_imports))]
11use num_traits::Float;
12
13use core::iter;
14use core::cmp::Ordering;
15
16use crate::common::{Address, MelexisCamera, PixelAddressRange};
17use crate::register::{AccessPattern, Subpage};
18use crate::util::Sealed;
19
20pub use address::RamAddress;
21pub use eeprom::Mlx90641Calibration;
22
23/// MLX90641-specific constants and supporting functions.
24///
25/// The functionality of this type covers any MLX90641 camera module. The individual
26/// camera-specific processing is performed by [`Mlx90641Calibration`].
27#[derive(Clone, Debug, PartialEq)]
28pub struct Mlx90641();
29
30impl Sealed for Mlx90641 {}
31
32impl MelexisCamera for Mlx90641 {
33    type PixelRangeIterator = SubpageInterleave;
34    type PixelsInSubpageIterator = iter::Take<iter::Repeat<bool>>;
35
36    fn pixel_ranges(subpage: Subpage, _access_pattern: AccessPattern) -> Self::PixelRangeIterator {
37        // The 90641 updates an entire frame at a time and only vary the data location on subpage
38        SubpageInterleave::new(subpage)
39    }
40
41    fn pixels_in_subpage(
42        _subpage: Subpage,
43        _access_pattern: AccessPattern,
44    ) -> Self::PixelsInSubpageIterator {
45        // All pixels in the image are valid, each subpage covers all of the pixels.
46        iter::repeat(true).take(Self::NUM_PIXELS)
47    }
48
49    const T_A_V_BE: Address = Address::new(RamAddress::AmbientTemperatureVoltageBe as u16);
50
51    const T_A_PTAT: Address = Address::new(RamAddress::AmbientTemperatureVoltage as u16);
52
53    fn compensation_pixel(subpage: Subpage) -> Address {
54        match subpage {
55            Subpage::Zero => RamAddress::CompensationPixelZero,
56            Subpage::One => RamAddress::CompensationPixelOne,
57        }
58        .into()
59    }
60
61    const GAIN: Address = Address::new(RamAddress::Gain as u16);
62
63    const V_DD_PIXEL: Address = Address::new(RamAddress::PixelSupplyVoltage as u16);
64
65    fn resolution_correction(calibrated_resolution: u8, current_resolution: u8) -> f32 {
66        // These values are safe to convert to i8, as they were originally 4-bit unsigned ints.
67        let resolution_exp: i8 = calibrated_resolution as i8 - current_resolution as i8;
68        // Have to use an f32 here as resolution_exp may be negative.
69        f32::from(resolution_exp).exp2()
70    }
71
72    // It's defined as 2 in the datasheet(well, 3, but 1-indexed, so 2 when 0-indexed).
73    const BASIC_TEMPERATURE_RANGE: usize = 2;
74
75    // Implicitly documented in section 11.2.2.9 of the datasheet.
76    const SELF_HEATING: f32 = 5.0;
77
78    const HEIGHT: usize = 12;
79
80    const WIDTH: usize = 16;
81
82    const NUM_PIXELS: usize = Self::HEIGHT * Self::WIDTH;
83}
84
85#[derive(Clone, Copy, Debug)]
86pub struct SubpageInterleave {
87    stride_count: u16,
88    base_address: u16,
89}
90
91impl SubpageInterleave {
92    /// Length of each section of pixels with a common subpage
93    ///
94    /// The MLX90641 updates the entire frame at a time, but interleaves the data in memory. This
95    /// means the access pattern doesn't really matter, the subpage only changes where to read
96    /// from, but every pixel is written to.
97    /// The datasheet documents the interleaved access mode, and in that mode pixels alternate
98    /// subpages every 32 pixels, but each pixel is present in both subpages. In other words,
99    /// starting at 0x0400, there are pixels 0 through 31 for subpage 0. Then there are pixels 0
100    /// through 31 for subpage 1. Then pixels 32-63 for subpage 0, and so on.
101    // Multiply by two to get the number of bytes.
102    const STRIDE_LENGTH: u16 = 32 * 2;
103
104    /// The beginning of the range of valid pixels.
105    const PIXEL_START_ADDRESS: u16 = RamAddress::Base as u16;
106
107    /// The number of strides in each frame.
108    const NUM_STRIDES: u16 = (Mlx90641::HEIGHT / 2) as u16;
109
110    fn new(subpage: Subpage) -> Self {
111        let starting_address: u16 = match subpage {
112            Subpage::Zero => Self::PIXEL_START_ADDRESS,
113            // We need to divide by two to get the *address* offset
114            Subpage::One => Self::PIXEL_START_ADDRESS + (Self::STRIDE_LENGTH / 2),
115        };
116        Self {
117            stride_count: 0,
118            base_address: starting_address,
119        }
120    }
121}
122
123impl iter::Iterator for SubpageInterleave {
124    type Item = PixelAddressRange;
125
126    fn next(&mut self) -> Option<Self::Item> {
127        // There are two frame rows per stride
128        match self.stride_count.cmp(&Self::NUM_STRIDES) {
129            Ordering::Less => {
130                let next_value = PixelAddressRange {
131                    start_address: (self.base_address + self.stride_count * Self::STRIDE_LENGTH)
132                        .into(),
133                    buffer_offset: (self.stride_count * Self::STRIDE_LENGTH) as usize,
134                    length: Self::STRIDE_LENGTH as usize,
135                };
136                self.stride_count += 1;
137                Some(next_value)
138            }
139            _ => None,
140        }
141    }
142}
143
144#[cfg(test)]
145mod test {
146    use crate::{AccessPattern, MelexisCamera, Resolution, Subpage};
147
148    use super::Mlx90641;
149
150    #[test]
151    fn pixels_in_subpage() {
152        let mut count = 0;
153        // Only testing interleave as chess mode isn't used with the MLX90641
154        let sub0 = Mlx90641::pixels_in_subpage(Subpage::Zero, AccessPattern::Interleave);
155        let sub1 = Mlx90641::pixels_in_subpage(Subpage::One, AccessPattern::Interleave);
156        for (zero, one) in sub0.zip(sub1) {
157            assert_eq!(zero, one, "MLX90641 doesn't vary pixels on subpages");
158            assert!(zero, "Every pixel is valid for MLX90641");
159            count += 1;
160        }
161        assert_eq!(
162            count,
163            Mlx90641::NUM_PIXELS,
164            "Ever pixels needs a value for pixels_in_subpage()"
165        );
166    }
167
168    #[test]
169    fn resolution_correction() {
170        let resolutions = [
171            (Resolution::Sixteen, 4.0),
172            (Resolution::Seventeen, 2.0),
173            (Resolution::Eighteen, 1.0),
174            (Resolution::Nineteen, 0.5),
175        ];
176        for (register_resolution, expected) in resolutions {
177            assert_eq!(
178                Mlx90641::resolution_correction(
179                    // Using 18 as the calibration value as that's the default calibration value.
180                    Resolution::Eighteen as u8,
181                    register_resolution as u8
182                ),
183                expected,
184            )
185        }
186    }
187}