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::Sealed;
pub use address::RamAddress;
pub use eeprom::Mlx90640Calibration;
#[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(u16),
}
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 => {
let starting_address = match subpage {
Subpage::Zero => 0,
Subpage::One => Mlx90640::WIDTH as u16,
};
Self::Interleave(starting_address)
}
}
}
}
impl iter::Iterator for Mlx90640Pixels {
type Item = PixelAddressRange;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Chess(inner) => inner.next(),
Mlx90640Pixels::Interleave(current_offset) => {
if *current_offset < Mlx90640::NUM_PIXELS as u16 {
let next_value = PixelAddressRange {
start_address: (RamAddress::Base as u16 + *current_offset).into(),
buffer_offset: (*current_offset * 2) as usize,
length: Mlx90640::WIDTH * 2,
};
*current_offset += Mlx90640::WIDTH as u16 * 2;
Some(next_value)
} else {
None
}
}
}
}
}
#[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 crate::common::PixelAddressRange;
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(
mut ranges: <Mlx90640 as MelexisCamera>::PixelRangeIterator,
start_address: u16,
) {
const RANGE_LENGTH: usize = Mlx90640::WIDTH * 2;
let buffer_start_offset = (start_address - RamAddress::Base as u16) as usize * 2;
let first = ranges.next().unwrap();
assert_eq!(
first,
PixelAddressRange {
start_address: start_address.into(),
buffer_offset: buffer_start_offset,
length: RANGE_LENGTH
}
);
let second = ranges.next().unwrap();
assert_eq!(
second,
PixelAddressRange {
start_address: (start_address + Mlx90640::WIDTH as u16 * 2).into(),
buffer_offset: buffer_start_offset + RANGE_LENGTH * 2,
length: RANGE_LENGTH
}
);
let third = ranges.next().unwrap();
assert_eq!(
third,
PixelAddressRange {
start_address: (start_address + Mlx90640::WIDTH as u16 * 4).into(),
buffer_offset: buffer_start_offset + RANGE_LENGTH * 4,
length: RANGE_LENGTH
}
);
let fourth = ranges.next().unwrap();
assert_eq!(
fourth,
PixelAddressRange {
start_address: (start_address + Mlx90640::WIDTH as u16 * 6).into(),
buffer_offset: buffer_start_offset + RANGE_LENGTH * 6,
length: RANGE_LENGTH
}
);
assert_eq!(ranges.count(), 8);
}
#[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,
);
}
#[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);
}
}
}