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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
// SPDX-License-Identifier: Apache-2.0
// Copyright © 2021 Will Ross
//! Common functionality between MLX90640 and MLX90641 cameras.
use core::fmt;
use arrayvec::ArrayVec;
use crate::register::{AccessPattern, Subpage};
use crate::util::Sealed;
/// A trait for types that can be created by reading data from an I²C device.
pub trait FromI2C<I2C> {
type Error;
type Ok;
/// Create an instance of a type using data retrieved over I²C.
fn from_i2c(bus: &mut I2C, i2c_address: u8) -> Result<Self::Ok, Self::Error>;
}
/// This trait provides access to the module-specific calibration data.
///
/// Each MLX9064\* camera has calibration data from the factory stored on its EEPROM. The
/// factory-provided data is then used as the input to generate the constants needed to convert the
/// raw output of the camera into concrete temperatures (or even just raw infrared radition
/// measurements). The naming scheme for the methods in this trait is taken from the names of the
/// variables used in the formulas in the datasheet. Most users of this library can make use fo the
/// provided implementations, but if you're trying to minimize memory usage or tweak performance
/// for a specific use case, this might be a way to do it.
#[doc = include_str!("katex.html")]
pub trait CalibrationData<'a> {
/// The camera model this caliberation data is for.
type Camera: MelexisCamera;
/// Pixel supply voltage constant ($K_{V_{DD}}$).
fn k_v_dd(&self) -> i16;
/// Constant for pixel supply voltage at 25℃ ($K_{V_{DD_{25}}}$).
fn v_dd_25(&self) -> i16;
/// ADC resolution this camera was calibrated at.
// TODO: Should this return `Resolution`?
fn resolution(&self) -> u8;
/// Pixel supply voltage ($K_{DD_0}$).
///
/// This is the voltage supplied to the device, and should be 3.3V for the MLX90640 and
/// MLX90641. The default implementation is hardcoded to return `3.3f32`, but if there's a
/// reason it needs to be overridden, it's possible.
fn v_dd_0(&self) -> f32 {
3.3f32
}
/// Voltage proportional to ambient temperature constant ($K_{V_{PTAT}}$).
fn k_v_ptat(&self) -> f32;
/// Temperature proportional to ambient temperature constant ($K_{T_{PTAT}}$).
fn k_t_ptat(&self) -> f32;
/// Voltage proportional to ambient temperature at 25℃ ($V_{PTAT_{25}}$).
fn v_ptat_25(&self) -> f32;
/// Sensitivity proportional to ambient temperature ($\alpha_{PTAT}$).
fn alpha_ptat(&self) -> f32;
/// The gain constant. Usually written as <var>GAIN</var> in the datasheets.
fn gain(&self) -> f32;
/// Sensitivity constant for ambient temperature ($K_{S_{T_{a}}}$).
fn k_s_ta(&self) -> f32;
/// A slice of the "corner temperatures".
///
/// These define temperature ranges with different sensitivity characteristics. They are
/// indexed in the datasheet starting from 1 but everything in this library is 0-indexed, so be
/// aware of the difference.
fn corner_temperatures(&self) -> &[i16];
/// Constant for the object temperature sensitivity ($K_{s_{T_{o}N}}$)
/// depending on the temperature range.
///
/// This is a slight variance from the datasheet's nomenclature. In the symbol above,
/// <var>N</var> is the index of the temperature range, which the datasheet normally just
/// writes out (ex: $K_{S_{T_{o}1}}$ through how every many temperature
/// ranges the camera has).
///
/// This method returns a slice of values equal in length to
/// [`corner_temperatures`](CalibrationData::corner_temperatures).
fn k_s_to(&self) -> &[f32];
/// Temperature range sensitivity correction ($\alpha_{\text{correction}_{N}}$)
///
/// Like [`k_s_to`], the name of this method is slightly different that the naming in the
/// datasheet. Also like `k_s_to`, this method returns a slice of values with a length equal to
/// the length of the slice returned by
/// [`corner_temperatures`](CalibrationData::corner_temperatures),
///
/// [`k_s_to`]: CalibrationData::k_s_to
fn alpha_correction(&self) -> &[f32];
/// The emissivity stored on the device.
///
/// Not all devices support storing the emissivity, in which case they should return [None]
/// (which is what the provided implementation does).
fn emissivity(&self) -> Option<f32> {
None
}
type OffsetReferenceIterator: Iterator<Item = &'a i16>;
/// An iterator over the per-pixel offset reference values for the given subpage
/// ($\text{Offset}_\text{reference}(i, j)$).
///
/// The iterator must yield pixels by row, then by columns, with the rows increasing from left
/// to right and the columns increasing from top to bottom. The iterator must yield *all*
/// pixels, even if they would not normally be present in the given subpage.
fn offset_reference_pixels(&'a self, subpage: Subpage) -> Self::OffsetReferenceIterator;
/// The offset reference value for the compensation pixel corresponding to the given subpage
/// ($\text{Offset}\_{\text{reference}\_{CP}}$).
fn offset_reference_cp(&self, subpage: Subpage) -> i16;
type AlphaIterator: Iterator<Item = &'a f32>;
/// An iterator over the per-pixel sensitivity calibration values ($\alpha_{pixel}(i, j)$).
///
/// The iterator must yield pixels by row, then by columns, with the rows increasing from left
/// to right and the columns increasing from top to bottom. The iterator must yield *all*
/// pixels, even if they would not normally be present in the given subpage.
fn alpha_pixels(&'a self, subpage: Subpage) -> Self::AlphaIterator;
/// The sensitivity calibration value for the compensation pixel for the given subpage
/// ($\alpha_{CP}$).
fn alpha_cp(&self, subpage: Subpage) -> f32;
type KvIterator: Iterator<Item = &'a f32>;
/// An iterator over the per-pixel voltage calibration constants ($K_{V_{pixel}}$).
///
/// The iterator must yield pixels by row, then by columns, with the rows increasing from left
/// to right and the columns increasing from top to bottom. The iterator must yield *all*
/// pixels, even if they would not normally be present in the given subpage.
fn k_v_pixels(&'a self, subpage: Subpage) -> Self::KvIterator;
/// The voltage calibration constant for the compensation pixel for the given subpage
/// ($K_{V_{CP}}$).
fn k_v_cp(&self, subpage: Subpage) -> f32;
type KtaIterator: Iterator<Item = &'a f32>;
/// The per pixel ambient temperature calibration constants ($K_{T_{a}pixel}$).
///
/// The iterator must yield pixels by row, then by columns, with the rows increasing from left
/// to right and the columns increasing from top to bottom. The iterator must yield *all*
/// pixels, even if they would not normally be present in the given subpage.
fn k_ta_pixels(&'a self, subpage: Subpage) -> Self::KtaIterator;
/// The ambient temperature calibration constant for the compensation pixel for the given
/// subpage ($K_{T_{a}CP}$).
fn k_ta_cp(&self, subpage: Subpage) -> f32;
/// Temperature gradient coefficient (<var>TGC</var>).
///
/// Some devices do not support a TGC (it can also be disabled manually on other devices).
fn temperature_gradient_coefficient(&self) -> Option<f32>;
type AccessPatternCompensation: Iterator<Item = Option<&'a f32>>;
/// A sequence of per-pixel correction values that are added to the pixel gain value.
///
/// The MLX90640 can be used in interleaved mode, but for optimal performance a correction
/// needs to be applied. This value is summed with the pixel gain value and reference offset
/// (the reference offset being scaled relative to the temperature difference).
fn access_pattern_compensation_pixels(
&'a self,
access_pattern: AccessPattern,
) -> Self::AccessPatternCompensation;
/// Equivalent to [`Self::access_pattern_compensation_pixels`] for compensation pixels.
fn access_pattern_compensation_cp(
&self,
subpage: Subpage,
access_pattern: AccessPattern,
) -> Option<f32>;
}
/// Marker newtype for addresses accessible over I<sup>2</sup>C.
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
pub struct Address(u16);
impl Address {
/// Wrap the given address in an `Address`.
///
/// This function is intended to be used in const contexts, in other cases the
/// [`From`][core::convert::From]/[`TryFrom`][core::convert::TryFrom] implementations are
/// probably easier to use.
pub const fn new(address: u16) -> Self {
Self(address)
}
pub(crate) fn as_bytes(&self) -> [u8; 2] {
self.0.to_be_bytes()
}
}
impl fmt::Debug for Address {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Address({:#X})", self.0)
}
}
impl From<u16> for Address {
fn from(raw_address: u16) -> Self {
Self::new(raw_address)
}
}
impl From<Address> for u16 {
fn from(address: Address) -> Self {
address.0
}
}
impl From<Address> for usize {
fn from(address: Address) -> Self {
address.0 as usize
}
}
/// Define common constants specific to a camera model.
///
/// The values from this trait are common between all cameras of a single model, and do not depend
/// on the calibration values from a specific camera.
///
/// This is a sealed trait, and can only be implemented by types defined within this crate.
#[doc = include_str!("katex.html")]
pub trait MelexisCamera: Sealed {
type PixelRangeIterator: IntoIterator<Item = PixelAddressRange>;
type PixelsInSubpageIterator: IntoIterator<Item = bool>;
/// Ranges of memory that should be read to load a subpage's data from RAM.
///
/// Different cameras with different [access patterns][crate::AccessPattern] have different optimal
/// ways of loading data from RAM. In some cases loading by row and then ignoring half the data
/// may be appropriate, in other loading individual pixels may be more efficient.
///
/// The returned iterator will yield at most [`Self::HEIGHT`] items.
fn pixel_ranges(subpage: Subpage, access_pattern: AccessPattern) -> Self::PixelRangeIterator;
/// Returns an iterator of booleans for whether or not a pixel should be considered for a
/// subpage.
///
/// This is a complement to [`pixel_ranges`][MelexisCamera::pixel_ranges], in that it lets an
/// implementation load extra memory when it's more efficient but then ignore the pixels for
/// later computations.
///
/// The iterator should return true when the pixel is part of this subpage, and false when it is
/// not. The ordering is rows, then columns. The iterator must not be infinite; it should only
/// yield as many values as there are pixels.
fn pixels_in_subpage(
subpage: Subpage,
access_pattern: AccessPattern,
) -> Self::PixelsInSubpageIterator;
/// The address for $T_{a_{V_{BE}}}$.
const T_A_V_BE: Address;
/// The address for $T_{a_{PTAT}}$.
const T_A_PTAT: Address;
/// The address of the compensation pixel for the given subpage.
fn compensation_pixel(subpage: Subpage) -> Address;
/// The address of the current gain.
const GAIN: Address;
/// The address for $V_{DD_{pixel}}$.
const V_DD_PIXEL: Address;
/// Calculate the ADC resolution correction factor
fn resolution_correction(calibrated_resolution: u8, current_resolution: u8) -> f32;
/// The index of the basic temperature range.
///
/// Temperature ranges (delimited by the control temperatures) outside of the basic range
/// are "extended temperature ranges" and require extra processing for accuracy. The datasheets
/// don't give a generic definition of the basic range, but for this library it is defined as
/// the temperature range with α<sub>correction</sub>(r) = 1. Also note that this library uses
/// 0-indexing as opposed to the datasheets that use 1-indexing.
const BASIC_TEMPERATURE_RANGE: usize;
/// The expected amount of self-heating for this camera.
///
/// In normal operation the camera generates some heat. If $T_r$ is not available, it
/// can be calculated by subtracting this value from $T_a$.
const SELF_HEATING: f32;
/// The height of the thermal image in pixels.
const HEIGHT: usize;
/// The width of the thermal image in pixels.
const WIDTH: usize;
/// The total number of pixels in the thermal image.
const NUM_PIXELS: usize;
}
/// A range of camera memory.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PixelAddressRange {
/// The address of memory to start reading from.
pub(crate) start_address: Address,
/// The offset of this range of pixels in the larger image.
pub(crate) buffer_offset: usize,
/// The number of bytes in this range of pixels.
///
/// Remember that each pixel is *two* bytes.
pub(crate) length: usize,
}
/// A helper function for calculating the sensitivity correction coefficients
/// (Alpha<sub>corr<sub>range<sub>n</sub></sub></sub>) for the different temperature ranges.
///
/// This function will `panic` if the passed in slices both do not have exactly `N` elements.
pub(crate) fn alpha_correction_coefficients<const NUM_RANGES: usize>(
basic_range: usize,
corner_temperatures: &[i16],
k_s_to: &[f32],
) -> [f32; NUM_RANGES] {
// This is the actual calculation. The values are built up recursively from the base case of
// the basic range (which doesn't need correcting, so it's 1).
// Memoizing would be nice here, but these calculations are done only once, at start up, so the
// impact isn't that big.
let results: ArrayVec<f32, NUM_RANGES> = (0..NUM_RANGES)
.map(|n| alpha_corr_n(n, basic_range, corner_temperatures, k_s_to))
.collect();
results
.into_inner()
.expect("The Rust-range 0..NUM_RANGES should fill an array of NUM_RANGES elements")
}
/// The actual calculations for [alpha_correction_coefficients] as a recursive function. Memoizing
/// would be nice, but these calculations are only performed once, at start up.
fn alpha_corr_n(n: usize, basic_range: usize, ct: &[i16], k_s_to: &[f32]) -> f32 {
match n.cmp(&basic_range) {
core::cmp::Ordering::Equal => 1f32,
core::cmp::Ordering::Less => {
(1f32 + k_s_to[n] * f32::from(ct[n + 1] - ct[n])).recip()
* alpha_corr_n(n + 1, basic_range, ct, k_s_to)
}
core::cmp::Ordering::Greater => {
(1f32 + k_s_to[n - 1] * f32::from(ct[n] - ct[n - 1]))
* alpha_corr_n(n - 1, basic_range, ct, k_s_to)
}
}
}