Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/self tests #40

Merged
merged 7 commits into from
Aug 19, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions max6639/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub enum AddressPin {

#[doc(hidden)]
#[allow(dead_code)]
#[derive(Copy, Clone, Debug)]
/// Represents various registers that can be read or written on the controller.
enum Register {
Status = 0x02,
Expand All @@ -45,6 +46,9 @@ enum Register {
Fan2Config2b = 0x16,
Fan2Config3 = 0x17,

Fan1TachCount = 0x20,
Fan2TachCount = 0x21,

// Note: The duty cycle register can be written to the target duty cycle, but will read out the
// current duty cycle.
Fan1DutyCycle = 0x26,
Expand All @@ -56,13 +60,15 @@ enum Register {
}

/// An error that the fan controller driver may encounter.
#[derive(Copy, Clone, Debug)]
pub enum Error {
Interface,
FanFail,
Bounds,
}

/// An indication of which fan to operate on.
#[derive(Copy, Clone, Debug)]
pub enum Fan {
Fan1,
Fan2,
Expand All @@ -89,8 +95,10 @@ where
// Configure both fans for PWM control mode with the fan off.
device.write(Register::Fan1DutyCycle, 0)?;
device.write(Register::Fan2DutyCycle, 0)?;
device.write(Register::Fan1Config1, 1 << 7)?;
device.write(Register::Fan2Config1, 1 << 7)?;

// Run the tachometer clocks at 4KHz and select PWM control mode.
device.write(Register::Fan1Config1, 1 << 7 | 0b10)?;
device.write(Register::Fan2Config1, 1 << 7 | 0b10)?;

Ok(device)
}
Expand Down Expand Up @@ -166,4 +174,29 @@ where
Fan::Fan2 => Ok(status_register.get_bit(0)),
}
}

/// Get the current RPMs of the fan.
///
/// # Args
/// * `fan` - The fan to get the RPM count of.
///
/// # Returns
/// The current fan speed in RPMs (revolutions per minute).
pub fn current_rpms(&mut self, fan: Fan) -> Result<u16, Error> {
let tach_reg = match fan {
Fan::Fan1 => Register::Fan1TachCount,
Fan::Fan2 => Register::Fan2TachCount,
};

let tach_count = self.read(tach_reg)?;

// If the tachometer registers 0xFF, we can't measure the RPMs. Assume the fan is stopped.
if tach_count == 0xFF {
Ok(0)
} else {
// The tachometer clock is configured for 4KHz. The RPMs are calculated as follows:
// RPM = (Tach_Clk * 60) / tach_count
Ok(((60 * 4000) as f32 / tach_count as f32) as u16)
}
}
}
107 changes: 107 additions & 0 deletions src/chassis_fans.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//! Booster NGFW Application
//!
//! # Copyright
//! Copyright (C) 2020 QUARTIQ GmbH - All Rights Reserved
//! Unauthorized usage, editing, or copying is strictly prohibited.
//! Proprietary and confidential.
use super::I2C;
use max6639::Max6639;
use shared_bus_rtic::BusProxy;

/// Provides control of the chassis-mounted cooling fans.
pub struct ChassisFans {
fans: [Max6639<BusProxy<I2C>>; 3],
}

impl ChassisFans {
/// Create a new fan controller.
///
/// # Args
/// * `fans` - The fan controllers to use.
///
/// # Returns
/// A new fan controller.
pub fn new(fans: [Max6639<BusProxy<I2C>>; 3]) -> Self {
ChassisFans { fans }
}

fn set_duty_cycles(&mut self, duty_cycle: f32) {
for fan in self.fans.iter_mut() {
fan.set_duty_cycle(max6639::Fan::Fan1, duty_cycle).unwrap();
fan.set_duty_cycle(max6639::Fan::Fan2, duty_cycle).unwrap();
}
}

fn read_rpms(&mut self) -> [u16; 6] {
let mut rpms: [u16; 6] = [0; 6];
rpms[0] = self.fans[0].current_rpms(max6639::Fan::Fan1).unwrap();
rpms[1] = self.fans[0].current_rpms(max6639::Fan::Fan2).unwrap();
rpms[2] = self.fans[1].current_rpms(max6639::Fan::Fan1).unwrap();
rpms[3] = self.fans[1].current_rpms(max6639::Fan::Fan2).unwrap();
rpms[4] = self.fans[2].current_rpms(max6639::Fan::Fan1).unwrap();
rpms[5] = self.fans[2].current_rpms(max6639::Fan::Fan2).unwrap();
rpms
}

/// Perform a self-test of the fan operation.
///
/// # Args
/// * `delay` - An object to implement delays during the test.
///
/// # Returns
/// True if 5 of the six fans properly spun up to a high-speed RPM, all 6 were below a specific
/// RPM at 10% duty cycle, and all fans had no speed when disabled.
pub fn self_test(
&mut self,
delay: &mut impl embedded_hal::blocking::delay::DelayMs<u16>,
) -> bool {
delay.delay_ms(7000);
let dead_rpms = self.read_rpms();

self.set_duty_cycles(0.1);
delay.delay_ms(7000);
let low_rpms = self.read_rpms();

self.set_duty_cycles(1.0);
delay.delay_ms(2000);
let high_rpms = self.read_rpms();

self.set_duty_cycles(0.0);

// Check that all dead RPMS are zero.
let fans_powered_down =
dead_rpms
.iter()
.fold(0, |count, rpms| if *rpms == 0 { count + 1 } else { count });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that just:

Suggested change
.fold(0, |count, rpms| if *rpms == 0 { count + 1 } else { count });
.filter(|rpms| *rpms == 0).count();

For the two others below as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I knew there had to be a better way. Thanks for pointing to filter(). I'm still getting used to rust iterators.


// Check all the low RPMs are lower than 3200 RPMs.
let fans_spun_low = low_rpms.iter().fold(
0,
|count, rpms| {
if *rpms <= 3200 {
count + 1
} else {
count
}
},
);

// Check all the high RPMs are higher than 4800 RPMs.
let fans_spun_high =
high_rpms.iter().fold(
0,
|count, rpms| {
if *rpms >= 4800 {
count + 1
} else {
count
}
},
);

// If 5 fans (the count mounted on the chassis) spun up to a nominal high speed RPM, 5-6
// fans were at a nominal low RPM, and 6 fans were not spinning when powered down, fans are
// operating nominally.
(fans_spun_high == 5) && (fans_spun_low >= 6) && (fans_powered_down == 6)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the comment:

Suggested change
(fans_spun_high == 5) && (fans_spun_low >= 6) && (fans_powered_down == 6)
(fans_spun_high == 5) && (fans_spun_low >= 5) && (fans_powered_down == 6)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated it such that low spinup requires a non-zero RPM and have slightly reworked this logic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO verifying that the 1.0 duty cycle having high RPM and then going back to 0.1 duty (and later with feedback) would be enough.

}
}
28 changes: 27 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ use stm32f4xx_hal as hal;
use hal::prelude::*;

mod booster_channels;
mod chassis_fans;
mod error;
mod linear_transformation;
mod rf_channel;
use booster_channels::BoosterChannels;
use chassis_fans::ChassisFans;
use rf_channel::{AdcPin, AnalogPins as AdcPins, ChannelPins as RfChannelPins};

// Convenience type definition for the I2C bus used for booster RF channels.
Expand Down Expand Up @@ -174,15 +176,39 @@ const APP: () = {
]
};

let mux = {
let mut mux = {
let mut i2c_mux_reset = gpiob.pb14.into_push_pull_output();
tca9548::Tca9548::default(i2c_bus_manager.acquire(), &mut i2c_mux_reset, &mut delay)
.unwrap()
};

// Test scanning and reading back MUX channels.
assert!(mux.self_test().unwrap() == true);

BoosterChannels::new(mux, &i2c_bus_manager, channel_pins, &mut delay)
};

let i2c2 = {
let scl = gpiob.pb10.into_alternate_af4_open_drain();
let sda = gpiob.pb11.into_alternate_af4_open_drain();
hal::i2c::I2c::i2c2(c.device.I2C2, (scl, sda), 100.khz(), clocks)
};

// Selftest: Read the EUI48 identifier.
let mut eui = microchip_24aa02e48::Microchip24AA02E48::new(i2c2).unwrap();
let mut eui48: [u8; 6] = [0; 6];
eui.read_eui48(&mut eui48).unwrap();

let fan1 = max6639::Max6639::new(i2c_bus_manager.acquire(), max6639::AddressPin::Pulldown)
.unwrap();
let fan2 =
max6639::Max6639::new(i2c_bus_manager.acquire(), max6639::AddressPin::Float).unwrap();
let fan3 =
max6639::Max6639::new(i2c_bus_manager.acquire(), max6639::AddressPin::Pullup).unwrap();

let mut fans = ChassisFans::new([fan1, fan2, fan3]);
assert!(fans.self_test(&mut delay));

info!("Startup complete");

init::LateResources { channels: channels }
Expand Down
8 changes: 8 additions & 0 deletions src/rf_channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,14 @@ impl Devices {
.get_voltage(ads7924::Channel::Three)
.expect("Power monitor did not respond");

// Configure alarm thresholds for the P5V0_MP signal.
ads7924
.set_thresholds(ads7924::Channel::Three, 0.0, 5.5 / 2.5)
.expect("Power monitor failed to set thresholds");

// Verify that there is no active alarm condition.
assert!(ads7924.clear_alarm().expect("Failed to clear alarm") == 0);

if let Ok(ad5627) = Ad5627::default(manager.acquire()) {
if let Ok(eui48) = Microchip24AA02E48::new(manager.acquire()) {
// Query devices on the RF module to verify they are present.
Expand Down
61 changes: 49 additions & 12 deletions tca9548/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,27 @@
#![deny(warnings)]

use embedded_hal::{
blocking::{delay::DelayUs, i2c::Write},
blocking::{
delay::DelayUs,
i2c::{Read, Write},
},
digital::v2::OutputPin,
};

/// The driver for the TCA9548 I2C bus multiplexer.
pub struct Tca9548<I2C>
where
I2C: Write,
I2C: Write + Read,
{
i2c: I2C,
address: u8,
}

#[derive(Debug, Copy, Clone)]
pub enum Error {
Interface,
}

/// Represents a bus connection on the I2C fanout.
pub enum Bus {
Zero = 0x01,
Expand All @@ -39,7 +47,7 @@ pub enum Bus {

impl<I2C> Tca9548<I2C>
where
I2C: Write,
I2C: Write + Read,
{
/// Construct a new I2C bus mux.
///
Expand All @@ -53,7 +61,7 @@ where
address: u8,
reset: &mut RST,
delay: &mut DELAY,
) -> Result<Self, I2C::Error>
) -> Result<Self, Error>
where
RST: OutputPin,
DELAY: DelayUs<u8>,
Expand All @@ -78,11 +86,7 @@ where
/// * `i2c` - The I2C bus to use for communication with the MUX.
/// * `reset` - A pin connected to the RST input of the device.
/// * `delay` - A means of delaying for a specific amount of time.
pub fn default<RST, DELAY>(
i2c: I2C,
reset: &mut RST,
delay: &mut DELAY,
) -> Result<Self, I2C::Error>
pub fn default<RST, DELAY>(i2c: I2C, reset: &mut RST, delay: &mut DELAY) -> Result<Self, Error>
where
RST: OutputPin,
DELAY: DelayUs<u8>,
Expand All @@ -95,8 +99,10 @@ where
///
/// # Args
/// * `bus` - A bitmap indicating which buses to connect.
pub fn enable(&mut self, bus: u8) -> Result<(), I2C::Error> {
self.i2c.write(self.address, &[bus])?;
pub fn enable(&mut self, bus: u8) -> Result<(), Error> {
self.i2c
.write(self.address, &[bus])
.map_err(|_| Error::Interface)?;

Ok(())
}
Expand All @@ -105,11 +111,42 @@ where
///
/// # Args
/// * `bus` - An optional bus to connect. If None, all buses will be disconnected.
pub fn select_bus(&mut self, bus: Option<Bus>) -> Result<(), I2C::Error> {
pub fn select_bus(&mut self, bus: Option<Bus>) -> Result<(), Error> {
if let Some(bus) = bus {
self.enable(bus as u8)
} else {
self.enable(0u8)
}
}

/// Get a bit-field of all the selected buses.
///
/// # Returns
/// A bitfield where the bit index corresponds with the bus index. A 1 in the field indicates
/// the bus is selected.
pub fn get_selected_buses(&mut self) -> Result<u8, Error> {
let mut bus: [u8; 1] = [0];
self.i2c
.read(self.address, &mut bus)
.map_err(|_| Error::Interface)?;

Ok(bus[0])
}

/// Run a self-test of the device.
///
/// # Returns
/// True if the self test was successful.
pub fn self_test(&mut self) -> Result<bool, Error> {
let mut passed = true;
for i in 0..8 {
self.enable(1u8 << i)?;
let selected_bus = self.get_selected_buses()?;
if selected_bus != 1u8 << i {
passed = false;
}
}
ryan-summers marked this conversation as resolved.
Show resolved Hide resolved

Ok(passed)
}
}