mod address;
mod eeprom;
use core::iter;
#[cfg_attr(feature = "std", allow(unused_imports))]
use num_traits::Float;
use crate::common::{Address, MelexisCamera, PixelAddressRange};
use crate::register::{AccessPattern, Subpage};
use crate::util::{self, Sealed};
pub use address::RamAddress;
pub use eeprom::Mlx90640Calibration;
type SubpageInterleave = util::SubpageInterleave<
{ <Mlx90640 as MelexisCamera>::WIDTH as u16 },
{ (<Mlx90640 as MelexisCamera>::HEIGHT / 2) as u16 },
{ RamAddress::Base as u16 },
>;
#[derive(Clone, Debug, PartialEq)]
pub struct Mlx90640();
impl Sealed for Mlx90640 {}
impl MelexisCamera for Mlx90640 {
type PixelRangeIterator = Mlx90640Pixels;
type PixelsInSubpageIterator = Mlx90640PixelSubpage;
fn pixel_ranges(subpage: Subpage, access_pattern: AccessPattern) -> Self::PixelRangeIterator {
Mlx90640Pixels::new(subpage, access_pattern)
}
fn pixels_in_subpage(
subpage: Subpage,
access_pattern: AccessPattern,
) -> Self::PixelsInSubpageIterator {
Mlx90640PixelSubpage::new(access_pattern, subpage)
}
const T_A_V_BE: Address = Address::new(RamAddress::AmbientTemperatureVoltageBe as u16);
const T_A_PTAT: Address = Address::new(RamAddress::AmbientTemperatureVoltage as u16);
fn compensation_pixel(subpage: Subpage) -> Address {
match subpage {
Subpage::Zero => RamAddress::CompensationPixelZero.into(),
Subpage::One => RamAddress::CompensationPixelOne.into(),
}
}
const GAIN: Address = Address::new(RamAddress::Gain as u16);
const V_DD_PIXEL: Address = Address::new(RamAddress::PixelSupplyVoltage as u16);
fn resolution_correction(calibrated_resolution: u8, current_resolution: u8) -> f32 {
let resolution_exp: i8 = calibrated_resolution as i8 - current_resolution as i8;
f32::from(resolution_exp).exp2()
}
const BASIC_TEMPERATURE_RANGE: usize = 1;
const SELF_HEATING: f32 = 8.0;
const HEIGHT: usize = 24;
const WIDTH: usize = 32;
const NUM_PIXELS: usize = Self::HEIGHT * Self::WIDTH;
}
pub enum Mlx90640Pixels {
#[doc(hidden)]
Chess(iter::Once<PixelAddressRange>),
#[doc(hidden)]
Interleave(SubpageInterleave),
}
impl Mlx90640Pixels {
fn new(subpage: Subpage, access_pattern: AccessPattern) -> Self {
match access_pattern {
AccessPattern::Chess => {
let once = iter::once(PixelAddressRange {
start_address: RamAddress::Base.into(),
buffer_offset: 0,
length: Mlx90640::NUM_PIXELS * 2,
});
Self::Chess(once)
}
AccessPattern::Interleave => Self::Interleave(SubpageInterleave::new(subpage)),
}
}
}
impl iter::Iterator for Mlx90640Pixels {
type Item = PixelAddressRange;
fn next(&mut self) -> Option<Self::Item> {
let inner: &mut dyn iter::Iterator<Item = Self::Item> = match self {
Self::Chess(inner) => inner,
Self::Interleave(inner) => inner,
};
inner.next()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Mlx90640PixelSubpage {
index: usize,
access_pattern: AccessPattern,
subpage_num: usize,
}
impl Mlx90640PixelSubpage {
fn new(access_pattern: AccessPattern, subpage: Subpage) -> Self {
Self {
index: 0,
access_pattern,
subpage_num: subpage as usize,
}
}
}
impl Iterator for Mlx90640PixelSubpage {
type Item = bool;
fn next(&mut self) -> Option<Self::Item> {
if self.index < Mlx90640::NUM_PIXELS {
let row = self.index / Mlx90640::WIDTH;
let column = self.index % Mlx90640::WIDTH;
self.index += 1;
Some(match self.access_pattern {
AccessPattern::Chess => {
if self.subpage_num == 0 {
row % 2 == column % 2
} else {
row % 2 != column % 2
}
}
AccessPattern::Interleave => row % 2 == self.subpage_num,
})
} else {
None
}
}
}
#[cfg(test)]
mod test {
use core::iter::repeat;
use crate::{AccessPattern, Address, MelexisCamera, Resolution, Subpage};
use super::{Mlx90640, Mlx90640PixelSubpage, RamAddress};
#[test]
fn resolution_correction() {
let resolutions = [
(Resolution::Sixteen, 4.0),
(Resolution::Seventeen, 2.0),
(Resolution::Eighteen, 1.0),
(Resolution::Nineteen, 0.5),
];
for (register_resolution, expected) in resolutions {
assert_eq!(
Mlx90640::resolution_correction(
Resolution::Eighteen as u8,
register_resolution as u8
),
expected,
)
}
}
#[test]
fn pixel_access_chess() {
let mut pixel_iter0 = Mlx90640::pixel_ranges(Subpage::Zero, AccessPattern::Chess);
let range0 = pixel_iter0.next();
assert!(range0.is_some());
assert!(
pixel_iter0.next().is_none(),
"Mlx90640::pixel_ranges() should only yield one range for chess mode"
);
let mut pixel_iter1 = Mlx90640::pixel_ranges(Subpage::One, AccessPattern::Chess);
let range1 = pixel_iter1.next();
assert!(range1.is_some());
assert!(
pixel_iter1.next().is_none(),
"Mlx90640::pixel_ranges() should only yield one range for chess mode"
);
assert_eq!(
range0, range1,
"MLX90640 pixel ranges don't vary on subpage in chess mode"
);
let range0 = range0.unwrap();
assert_eq!(range0.start_address, RamAddress::Base.into());
assert_eq!(range0.buffer_offset, 0);
assert_eq!(range0.length, Mlx90640::NUM_PIXELS * 2);
}
fn access_interleave_test(
pixel_iter: <Mlx90640 as MelexisCamera>::PixelRangeIterator,
first_address: Address,
) {
let mut count = 0;
let base_offset: u16 = first_address.into();
for (index, range) in pixel_iter.enumerate() {
let ram_offset = index * Mlx90640::WIDTH;
assert_eq!(
range.buffer_offset,
ram_offset * 2,
"Buffer offset is incorrect"
);
let start = base_offset + ram_offset as u16;
assert_eq!(
range.start_address,
start.into(),
"Range starting address incorrect"
);
assert_eq!(range.length, Mlx90640::WIDTH * 2, "Range length incorrect");
count += 1;
}
assert_eq!(count, Mlx90640::HEIGHT / 2);
}
#[test]
fn pixel_access_interleave_subpage0() {
access_interleave_test(
Mlx90640::pixel_ranges(Subpage::Zero, AccessPattern::Interleave),
RamAddress::Base.into(),
);
}
#[test]
fn pixel_access_interleave_subpage1() {
let second_row = RamAddress::Base as u16 + Mlx90640::WIDTH as u16;
access_interleave_test(
Mlx90640::pixel_ranges(Subpage::One, AccessPattern::Interleave),
second_row.into(),
);
}
#[test]
fn pixel_subpage_interleaved() {
let seq0 = Mlx90640PixelSubpage::new(AccessPattern::Interleave, Subpage::Zero);
let pattern0 = repeat(true)
.take(Mlx90640::WIDTH)
.chain(repeat(false).take(Mlx90640::WIDTH))
.cycle();
seq0.zip(pattern0)
.enumerate()
.for_each(|(index, (seq, expected))| {
assert_eq!(seq, expected, "{} is incorrect (pixel {})", seq, index)
});
let seq1 = Mlx90640PixelSubpage::new(AccessPattern::Interleave, Subpage::One);
let pattern1 = repeat(false)
.take(Mlx90640::WIDTH)
.chain(repeat(true).take(Mlx90640::WIDTH))
.cycle();
seq1.zip(pattern1)
.enumerate()
.for_each(|(index, (seq, expected))| {
assert_eq!(seq, expected, "{} is incorrect (pixel {})", seq, index)
});
}
#[test]
fn pixel_subpage_chess() {
let subpages = [Subpage::Zero, Subpage::One];
let zero_first = subpages.iter().copied().cycle().take(Mlx90640::WIDTH);
let one_first = subpages
.iter()
.copied()
.cycle()
.skip(1)
.take(Mlx90640::WIDTH);
let chessboard = zero_first
.chain(one_first)
.cycle()
.take(Mlx90640::NUM_PIXELS);
let seq0 = Mlx90640PixelSubpage::new(AccessPattern::Chess, Subpage::Zero);
let seq1 = Mlx90640PixelSubpage::new(AccessPattern::Chess, Subpage::One);
let all = chessboard.zip(seq0.zip(seq1));
for (subpage, (seq0, seq1)) in all {
assert_eq!(subpage == Subpage::Zero, seq0);
assert_eq!(subpage == Subpage::One, seq1);
}
}
}