use core::iter;
use core::slice;
use arrayvec::ArrayVec;
use embedded_hal::blocking::i2c;
#[cfg_attr(feature = "std", allow(unused_imports))]
use num_traits::Float;
use crate::common::*;
use crate::error::{Error, LibraryError};
use crate::expose_member;
use crate::register::{AccessPattern, Subpage};
use crate::util::{i16_from_bits, Buffer};
use super::address::EepromAddress;
use super::hamming::validate_checksum;
use super::Mlx90641;
const NUM_CORNER_TEMPERATURES: usize = 8;
const WORD_SIZE: usize = 16 / 8;
#[derive(Clone, Debug, PartialEq)]
pub struct Mlx90641Calibration {
k_v_dd: i16,
v_dd_25: i16,
resolution: u8,
k_v_ptat: f32,
k_t_ptat: f32,
v_ptat_25: f32,
alpha_ptat: f32,
gain: f32,
k_s_ta: f32,
corner_temperatures: [i16; NUM_CORNER_TEMPERATURES],
k_s_to: [f32; NUM_CORNER_TEMPERATURES],
alpha_correction: [f32; NUM_CORNER_TEMPERATURES],
emissivity: Option<f32>,
alpha_pixels: [f32; <Self as CalibrationData>::Camera::NUM_PIXELS],
alpha_cp: f32,
offset_reference_pixels: [[i16; <Self as CalibrationData>::Camera::NUM_PIXELS]; 2],
offset_reference_cp: i16,
k_v_pixels: [f32; <Self as CalibrationData>::Camera::NUM_PIXELS],
k_v_cp: f32,
k_ta_pixels: [f32; <Self as CalibrationData>::Camera::NUM_PIXELS],
k_ta_cp: f32,
temperature_gradient_coefficient: Option<f32>,
}
impl Mlx90641Calibration {
pub fn from_data(mut buf: &[u8]) -> Result<Self, LibraryError> {
buf.advance(WORD_SIZE * 16);
let (offset_scale, _) = get_6_5_split(&mut buf)?;
let offset_average_bytes = get_combined_word(&mut buf)?;
let offset_average = i16_from_bits(&offset_average_bytes[..], 11);
buf.advance(WORD_SIZE * 2);
let k_ta_average = get_hamming_i16(&mut buf)?;
let k_ta_scales = get_6_5_split(&mut buf)?;
let k_v_average = get_hamming_i16(&mut buf)?;
let k_v_scales = get_6_5_split(&mut buf)?;
let alpha_reference = Self::get_sensitivity_reference(&mut buf)?;
let k_s_ta = f32::from(get_hamming_i16(&mut buf)?) / 15f32.exp2();
let emissivity = f32::from(get_hamming_i16(&mut buf)?) / 9f32.exp2();
let gain = u16::from_be_bytes(get_combined_word(&mut buf)?);
let v_dd_25 = get_hamming_i16(&mut buf)? << 5;
let k_v_dd = get_hamming_i16(&mut buf)? << 5;
let v_ptat_25 = u16::from_be_bytes(get_combined_word(&mut buf)?);
let k_t_ptat = f32::from(get_hamming_i16(&mut buf)?) / 8f32;
let k_v_ptat = f32::from(get_hamming_i16(&mut buf)?) / 4096f32;
let alpha_ptat = f32::from(get_hamming_u16(&mut buf)?) / 128f32;
let alpha_cp = f32::from(get_hamming_u16(&mut buf)?);
let alpha_cp_scale = f32::from(get_hamming_u16(&mut buf)?);
let alpha_cp = alpha_cp / alpha_cp_scale.exp2();
let offset_reference_cp = i16::from_be_bytes(get_combined_word(&mut buf)?);
let k_ta_cp = Self::get_scaled_cp_constant(&mut buf)?;
let k_v_cp = Self::get_scaled_cp_constant(&mut buf)?;
let (resolution, tgc) = Self::get_resolution_with_tgc(&mut buf)?;
let (corner_temperatures, k_s_to) = Self::get_temperature_range_data(&mut buf)?;
let basic_range = <Self as CalibrationData>::Camera::BASIC_TEMPERATURE_RANGE;
let alpha_correction =
alpha_correction_coefficients(basic_range, &corner_temperatures, &k_s_to);
let pixel_offsets_0 = Self::get_pixel_offsets(&mut buf, offset_scale, offset_average)?;
let alpha_pixels = Self::get_pixel_sensitivities(&mut buf, alpha_reference)?;
let (k_ta_pixels, k_v_pixels) = Self::get_pixel_temperature_constants(
&mut buf,
k_ta_average,
k_ta_scales,
k_v_average,
k_v_scales,
)?;
let pixel_offsets_1 = Self::get_pixel_offsets(&mut buf, offset_scale, offset_average)?;
Ok(Self {
k_v_dd,
v_dd_25,
resolution,
k_v_ptat,
k_t_ptat,
v_ptat_25: v_ptat_25.into(),
alpha_ptat,
gain: gain.into(),
k_s_ta,
corner_temperatures,
k_s_to,
alpha_correction,
emissivity: Some(emissivity),
alpha_pixels,
alpha_cp,
offset_reference_pixels: [pixel_offsets_0, pixel_offsets_1],
offset_reference_cp,
k_v_pixels,
k_v_cp,
k_ta_pixels,
k_ta_cp,
temperature_gradient_coefficient: Some(tgc),
})
}
#[doc = include_str!("../katex.html")]
fn get_sensitivity_reference(buf: &mut &[u8]) -> Result<[f32; 6], LibraryError> {
let mut scales: ArrayVec<u8, 6> = ArrayVec::new();
for _ in 0..3 {
let (first_scale, second_scale) = get_6_5_split(buf)?;
scales.push(first_scale + 20);
scales.push(second_scale + 20);
}
let mut a_reference = [0f32; 6];
for (dest, scale) in a_reference.iter_mut().zip(scales) {
let row_max = get_hamming_u16(buf)?;
*dest = f32::from(row_max) / f32::from(scale).exp2();
}
Ok(a_reference)
}
fn get_scaled_cp_constant(buf: &mut &[u8]) -> Result<f32, LibraryError> {
let word = get_hamming_u16(buf)?;
let raw_scale = (word & 0x07C0) >> 6;
let value_bytes = (word & 0x003F).to_be_bytes();
let value_unscaled = i16_from_bits(&value_bytes[..], 6);
let scale = f32::from(raw_scale).exp2();
Ok(f32::from(value_unscaled) / scale)
}
fn get_resolution_with_tgc(buf: &mut &[u8]) -> Result<(u8, f32), LibraryError> {
let word = get_hamming_u16(buf)?;
let resolution = (word & 0x0600) >> 9;
let tgc_bytes = (word & 0x01FF).to_be_bytes();
let tgc_unscaled = i16_from_bits(&tgc_bytes[..], 9);
let tgc = f32::from(tgc_unscaled) / 64f32;
Ok((resolution as u8, tgc))
}
fn get_temperature_range_data(
buf: &mut &[u8],
) -> Result<
(
[i16; NUM_CORNER_TEMPERATURES],
[f32; NUM_CORNER_TEMPERATURES],
),
LibraryError,
> {
let scale = f32::from(get_hamming_u16(buf)?).exp2();
let mut corner_temperatures: [i16; NUM_CORNER_TEMPERATURES] =
[-40, -20, 0, 80, 120, 0, 0, 0];
let mut k_s_to = [0f32; NUM_CORNER_TEMPERATURES];
for dest in k_s_to[..5].iter_mut() {
let unscaled = get_hamming_i16(buf)?;
*dest = f32::from(unscaled) / scale;
}
let paired_iter = corner_temperatures[5..]
.iter_mut()
.zip(k_s_to[5..].iter_mut());
for (ct, k_s_to) in paired_iter {
*ct = get_hamming_u16(buf)? as i16;
let unscaled = get_hamming_i16(buf)?;
*k_s_to = f32::from(unscaled) / scale;
}
Ok((corner_temperatures, k_s_to))
}
fn get_pixel_offsets(
buf: &mut &[u8],
offset_scale: u8,
offset_average: i16,
) -> Result<[i16; <Self as CalibrationData>::Camera::NUM_PIXELS], LibraryError> {
let mut pixel_offsets = [0i16; <Self as CalibrationData>::Camera::NUM_PIXELS];
let scale = 2i16.pow(offset_scale as u32);
for pixel_offset in pixel_offsets.iter_mut() {
let offset_raw = get_hamming_i16(buf)?;
let scaled_offset = offset_raw * scale;
*pixel_offset = offset_average + scaled_offset;
}
Ok(pixel_offsets)
}
fn get_pixel_sensitivities(
buf: &mut &[u8],
alpha_reference: [f32; 6],
) -> Result<[f32; <Self as CalibrationData>::Camera::NUM_PIXELS], LibraryError> {
let mut pixel_sensitivites = [0f32; <Self as CalibrationData>::Camera::NUM_PIXELS];
let referenced_rows = alpha_reference
.iter()
.zip(pixel_sensitivites.chunks_exact_mut(32));
for (reference, row) in referenced_rows {
for pixel_sensitivity in row {
let raw_alpha = f32::from(get_hamming_u16(buf)?);
*pixel_sensitivity = (raw_alpha / 2047f32) * reference;
}
}
Ok(pixel_sensitivites)
}
fn get_pixel_temperature_constants(
buf: &mut &[u8],
k_ta_average: i16,
k_ta_scales: (u8, u8),
k_v_average: i16,
k_v_scales: (u8, u8),
) -> Result<
(
[f32; <Self as CalibrationData>::Camera::NUM_PIXELS],
[f32; <Self as CalibrationData>::Camera::NUM_PIXELS],
),
LibraryError,
> {
let mut k_ta_pixels = [0f32; <Self as CalibrationData>::Camera::NUM_PIXELS];
let mut k_v_pixels = [0f32; <Self as CalibrationData>::Camera::NUM_PIXELS];
let k_ta_scale1 = f32::from(k_ta_scales.0).exp2();
let k_ta_scale2 = f32::from(k_ta_scales.1).exp2();
let k_v_scale1 = f32::from(k_v_scales.0).exp2();
let k_v_scale2 = f32::from(k_v_scales.1).exp2();
let scale_fn = |raw_value: i8, avg: i16, scale1: f32, scale2: f32| {
let numerator = f32::from(raw_value) * scale2 + f32::from(avg);
numerator / scale1
};
for (k_ta, k_v) in k_ta_pixels.iter_mut().zip(k_v_pixels.iter_mut()) {
let (k_ta_raw, k_v_raw) = get_6_5_split(buf)?;
let k_ta_raw = i16_from_bits(&[k_ta_raw], 6) as i8;
let k_v_raw = i16_from_bits(&[k_v_raw], 5) as i8;
*k_ta = scale_fn(k_ta_raw, k_ta_average, k_ta_scale1, k_ta_scale2);
*k_v = scale_fn(k_v_raw, k_v_average, k_v_scale1, k_v_scale2);
}
Ok((k_ta_pixels, k_v_pixels))
}
}
impl<I2C> FromI2C<I2C> for Mlx90641Calibration
where
I2C: i2c::WriteRead + i2c::Write,
{
type Error = Error<I2C>;
type Ok = Self;
fn from_i2c(bus: &mut I2C, i2c_address: u8) -> Result<Self, Error<I2C>> {
const EEPROM_LENGTH: usize =
(EepromAddress::End as usize - EepromAddress::Base as usize + 1) * 2;
let mut eeprom_buf = [0u8; EEPROM_LENGTH];
let eeprom_base: Address = EepromAddress::Base.into();
bus.write_read(i2c_address, &eeprom_base.as_bytes(), &mut eeprom_buf)
.map_err(Error::I2cWriteReadError)?;
Ok(Self::from_data(&eeprom_buf)?)
}
}
impl<'a> CalibrationData<'a> for Mlx90641Calibration {
type Camera = Mlx90641;
expose_member!(k_v_dd, i16);
expose_member!(v_dd_25, i16);
expose_member!(resolution, u8);
expose_member!(k_v_ptat, f32);
expose_member!(k_t_ptat, f32);
expose_member!(v_ptat_25, f32);
expose_member!(alpha_ptat, f32);
expose_member!(gain, f32);
expose_member!(k_s_ta, f32);
expose_member!(&corner_temperatures, [i16]);
expose_member!(&k_s_to, [f32]);
expose_member!(&alpha_correction, [f32]);
expose_member!(emissivity, Option<f32>);
type OffsetReferenceIterator = slice::Iter<'a, i16>;
fn offset_reference_pixels(&'a self, subpage: Subpage) -> Self::OffsetReferenceIterator {
match subpage {
Subpage::Zero => self.offset_reference_pixels[0].iter(),
Subpage::One => self.offset_reference_pixels[1].iter(),
}
}
fn offset_reference_cp(&self, _subpage: Subpage) -> i16 {
self.offset_reference_cp
}
type AlphaIterator = slice::Iter<'a, f32>;
fn alpha_pixels(&'a self, _subpage: Subpage) -> Self::AlphaIterator {
self.alpha_pixels.iter()
}
fn alpha_cp(&self, _subpage: Subpage) -> f32 {
self.alpha_cp
}
type KvIterator = slice::Iter<'a, f32>;
fn k_v_pixels(&'a self, _subpage: Subpage) -> Self::KvIterator {
self.k_v_pixels.iter()
}
fn k_v_cp(&self, _subpage: Subpage) -> f32 {
self.k_v_cp
}
type KtaIterator = slice::Iter<'a, f32>;
fn k_ta_pixels(&'a self, _subpage: Subpage) -> Self::KtaIterator {
self.k_ta_pixels.iter()
}
fn k_ta_cp(&self, _subpage: Subpage) -> f32 {
self.k_ta_cp
}
expose_member!(temperature_gradient_coefficient, Option<f32>);
type AccessPatternCompensation = iter::Take<iter::Repeat<Option<&'a f32>>>;
fn access_pattern_compensation_pixels(
&'a self,
_access_pattern: AccessPattern,
) -> Self::AccessPatternCompensation {
iter::repeat(None).take(Self::Camera::NUM_PIXELS)
}
fn access_pattern_compensation_cp(
&self,
_subpage: Subpage,
_access_pattern: AccessPattern,
) -> Option<f32> {
None
}
}
fn get_hamming_u16(buf: &mut &[u8]) -> Result<u16, LibraryError> {
let codeword = buf.get_u16();
validate_checksum(codeword)
}
fn get_hamming_i16(buf: &mut &[u8]) -> Result<i16, LibraryError> {
let bytes = get_hamming_u16(buf)?.to_be_bytes();
Ok(i16_from_bits(&bytes[..], 11))
}
fn get_combined_word(buf: &mut &[u8]) -> Result<[u8; 2], LibraryError> {
let upper = get_hamming_u16(buf)?;
let lower = get_hamming_u16(buf)?;
let combined = (upper << 5) | lower;
Ok(combined.to_be_bytes())
}
fn get_6_5_split(buf: &mut &[u8]) -> Result<(u8, u8), LibraryError> {
let word = get_hamming_u16(buf)?;
let upper = (word & 0x07E0) >> 5;
let lower = word & 0x001F;
Ok((upper as u8, lower as u8))
}
#[cfg(test)]
#[allow(clippy::excessive_precision)]
pub(crate) mod test {
use arrayvec::ArrayVec;
use crate::common::{CalibrationData, MelexisCamera};
use crate::mlx90641::eeprom::NUM_CORNER_TEMPERATURES;
use crate::mlx90641::Mlx90641;
use crate::register::{AccessPattern, Subpage};
use crate::test::mlx90641_datasheet_eeprom;
use super::Mlx90641Calibration;
const TEST_PIXEL_INDEX: usize = 5 * Mlx90641::WIDTH + 8;
pub(crate) fn datasheet_eeprom() -> Mlx90641Calibration {
let eeprom_bytes = mlx90641_datasheet_eeprom();
Mlx90641Calibration::from_data(&eeprom_bytes).expect("The EEPROM data to be parsed.")
}
#[test]
fn get_hamming_u16() {
let data = b"\x26\x58\xff\xff\x19\xe6";
let mut buf = &data[..];
assert_eq!(super::get_hamming_u16(&mut buf), Ok(0x0658));
assert_eq!(super::get_hamming_u16(&mut buf), Ok(0x07FF));
assert_eq!(super::get_hamming_u16(&mut buf), Ok(0x01e6));
assert_eq!(buf.len(), 0);
}
#[test]
fn get_hamming_i16() {
let data = b"\x26\x58\x19\xe6";
let mut buf = &data[..];
assert_eq!(super::get_hamming_i16(&mut buf), Ok(-424));
assert_eq!(super::get_hamming_i16(&mut buf), Ok(486));
assert_eq!(buf.len(), 0);
}
#[test]
fn get_combined_word() {
let data = b"\xb7\xe8\xd0\x16\xf1\x37\x78\x14\x91\x7f\xf0\x18\xcf\xfc\xa0\x09";
let mut buf = &data[..];
assert_eq!(
super::get_combined_word(&mut buf),
Ok(64790u16.to_be_bytes())
);
assert_eq!(
super::get_combined_word(&mut buf),
Ok(9972u16.to_be_bytes())
);
assert_eq!(
super::get_combined_word(&mut buf),
Ok(12280u16.to_be_bytes())
);
assert_eq!(
super::get_combined_word(&mut buf),
Ok(65417u16.to_be_bytes())
);
assert_eq!(buf.len(), 0);
}
#[test]
fn smoke() {
datasheet_eeprom();
}
#[test]
fn resolution() {
assert_eq!(datasheet_eeprom().resolution(), 2);
}
#[test]
fn k_v_dd() {
assert_eq!(datasheet_eeprom().k_v_dd(), -3136);
}
#[test]
fn v_dd_25() {
assert_eq!(datasheet_eeprom().v_dd_25(), -13568);
}
#[test]
fn v_dd_0() {
assert_eq!(datasheet_eeprom().v_dd_0(), 3.3);
}
#[test]
fn k_v_ptat() {
assert_eq!(datasheet_eeprom().k_v_ptat(), 0.0056152344);
}
#[test]
fn k_t_ptat() {
assert_eq!(datasheet_eeprom().k_t_ptat(), 42.75);
}
#[test]
fn v_ptat_25() {
assert_eq!(datasheet_eeprom().v_ptat_25(), 12280f32);
}
#[test]
fn alpha_ptat() {
assert_eq!(datasheet_eeprom().alpha_ptat(), 9f32);
}
#[test]
fn gain() {
assert_eq!(datasheet_eeprom().gain(), 9972f32);
}
#[test]
fn pixel_offset() {
let e = datasheet_eeprom();
let offsets0: ArrayVec<i16, { Mlx90641::NUM_PIXELS }> =
e.offset_reference_pixels(Subpage::Zero).copied().collect();
let offsets1: ArrayVec<i16, { Mlx90641::NUM_PIXELS }> =
e.offset_reference_pixels(Subpage::One).copied().collect();
assert_eq!(offsets0[TEST_PIXEL_INDEX], -673);
assert_eq!(offsets1[TEST_PIXEL_INDEX], -675);
}
#[test]
fn k_ta_pixels() {
let e = datasheet_eeprom();
let k_ta_pixels0: ArrayVec<f32, { Mlx90641::NUM_PIXELS }> =
e.k_ta_pixels(Subpage::Zero).copied().collect();
let k_ta_pixels1: ArrayVec<f32, { Mlx90641::NUM_PIXELS }> =
e.k_ta_pixels(Subpage::One).copied().collect();
assert_eq!(k_ta_pixels0, k_ta_pixels1);
assert_eq!(k_ta_pixels0[TEST_PIXEL_INDEX], 0.0031013489);
}
#[test]
fn k_v_pixels() {
let e = datasheet_eeprom();
let k_v_pixels0: ArrayVec<f32, { Mlx90641::NUM_PIXELS }> =
e.k_v_pixels(Subpage::Zero).copied().collect();
let k_v_pixels1: ArrayVec<f32, { Mlx90641::NUM_PIXELS }> =
e.k_v_pixels(Subpage::One).copied().collect();
assert_eq!(k_v_pixels0, k_v_pixels1);
assert_eq!(k_v_pixels0[TEST_PIXEL_INDEX], 0.3251953);
}
#[test]
fn emissivity() {
assert_eq!(datasheet_eeprom().emissivity(), Some(0.94921875));
}
#[test]
fn offset_reference_cp() {
let e = datasheet_eeprom();
assert_eq!(
e.offset_reference_cp(Subpage::Zero),
e.offset_reference_cp(Subpage::One)
);
assert_eq!(e.offset_reference_cp(Subpage::One), -119);
}
#[test]
fn k_ta_cp() {
let e = datasheet_eeprom();
assert_eq!(e.k_ta_cp(Subpage::Zero), e.k_ta_cp(Subpage::One));
assert_eq!(e.k_ta_cp(Subpage::Zero), 0.0023193359);
}
#[test]
fn k_v_cp() {
let e = datasheet_eeprom();
assert_eq!(e.k_v_cp(Subpage::Zero), e.k_v_cp(Subpage::One));
assert_eq!(e.k_v_cp(Subpage::Zero), 0.3125);
}
#[test]
fn temperature_gradient_coefficient() {
assert_eq!(
datasheet_eeprom().temperature_gradient_coefficient(),
Some(0f32)
);
}
#[test]
fn alpha_cp() {
let e = datasheet_eeprom();
let expected = 3.01952240988612E-9;
assert_eq!(e.alpha_cp(Subpage::Zero), e.alpha_cp(Subpage::One));
assert_eq!(e.alpha_cp(Subpage::One), expected);
}
#[test]
fn k_s_ta() {
assert_eq!(datasheet_eeprom().k_s_ta(), -0.002197265625);
}
#[test]
fn pixel_alpha() {
let e = datasheet_eeprom();
let alpha0: ArrayVec<f32, { Mlx90641::NUM_PIXELS }> =
e.alpha_pixels(Subpage::One).copied().collect();
let alpha1: ArrayVec<f32, { Mlx90641::NUM_PIXELS }> =
e.alpha_pixels(Subpage::Zero).copied().collect();
assert_eq!(alpha0, alpha1);
let pixel = alpha0[TEST_PIXEL_INDEX];
assert_eq!(pixel, 3.45520675182343E-7);
}
#[test]
fn k_s_to() {
let e = datasheet_eeprom();
let expected: [f32; NUM_CORNER_TEMPERATURES] = [-0.00069999695; NUM_CORNER_TEMPERATURES];
for (actual, expected) in e.k_s_to().iter().zip(expected.iter()) {
assert_eq!(actual, expected);
}
}
#[test]
fn corner_temperatures() {
let e = datasheet_eeprom();
let ct = e.corner_temperatures();
assert_eq!(ct.len(), super::NUM_CORNER_TEMPERATURES);
assert_eq!(ct[0], -40);
assert_eq!(ct[1], -20);
assert_eq!(ct[2], 0);
assert_eq!(ct[3], 80);
assert_eq!(ct[4], 120);
assert_eq!(ct[5], 200);
assert_eq!(ct[6], 400);
assert_eq!(ct[7], 600);
}
#[test]
fn access_pattern_compensation() {
let e = datasheet_eeprom();
for compensation in e.access_pattern_compensation_pixels(AccessPattern::Interleave) {
assert!(
compensation.is_none(),
"There is no access pattern compensation for the 90641"
);
}
assert_eq!(
e.access_pattern_compensation_pixels(AccessPattern::Interleave)
.count(),
Mlx90641::NUM_PIXELS
);
for compensation in e.access_pattern_compensation_pixels(AccessPattern::Chess) {
assert!(
compensation.is_none(),
"There is no access pattern compensation for the 90641"
);
}
assert_eq!(
e.access_pattern_compensation_pixels(AccessPattern::Chess)
.count(),
Mlx90641::NUM_PIXELS
);
}
}