Compare commits

..

1 Commits

Author SHA1 Message Date
atse 39736dc773 README: Update instruction on finding Rust version 2024-08-06 10:50:10 +08:00
9 changed files with 36 additions and 33 deletions

3
.gitignore vendored
View File

@ -1,5 +1,2 @@
target/ target/
result result
*.bin
__pycache__/

View File

@ -23,7 +23,7 @@ cargo build --release
The resulting ELF file will be located under `target/thumbv7em-none-eabihf/release/thermostat`. The resulting ELF file will be located under `target/thumbv7em-none-eabihf/release/thermostat`.
Alternatively, you can install the Rust toolchain without Nix using rustup; see the Rust manifest file pulled in `flake.nix` to determine which Rust version to use. Alternatively, you can install the Rust toolchain without Nix using rustup; see the `rust` variable in `flake.nix` to determine which Rust version to use.
## Debugging ## Debugging
@ -45,7 +45,7 @@ There are several options for flashing Thermostat. DFU requires only a micro-USB
### dfu-util on Linux ### dfu-util on Linux
* Install the DFU USB tool (dfu-util). * Install the DFU USB tool (dfu-util).
* Convert firmware from ELF to BIN: `llvm-objcopy -O binary target/thumbv7em-none-eabihf/release/thermostat thermostat.bin` (you can skip this step if using the BIN from Hydra) * Convert firmware from ELF to BIN: `arm-none-eabi-objcopy -O binary thermostat thermostat.bin` (you can skip this step if using the BIN from Hydra)
* Connect to the Micro USB connector to Thermostat below the RJ45. * Connect to the Micro USB connector to Thermostat below the RJ45.
* Add jumper to Thermostat v2.0 across 2-pin jumper adjacent to JTAG connector. * Add jumper to Thermostat v2.0 across 2-pin jumper adjacent to JTAG connector.
* Cycle board power to put it in DFU update mode * Cycle board power to put it in DFU update mode

View File

@ -2,16 +2,16 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1722791413, "lastModified": 1691421349,
"narHash": "sha256-rCTrlCWvHzMCNcKxPE3Z/mMK2gDZ+BvvpEVyRM4tKmU=", "narHash": "sha256-RRJyX0CUrs4uW4gMhd/X4rcDG8PTgaaCQM5rXEJOx6g=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "8b5b6723aca5a51edf075936439d9cd3947b7b2c", "rev": "011567f35433879aae5024fc6ec53f2a0568a6c4",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-24.05", "ref": "nixos-23.05",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }

View File

@ -1,7 +1,7 @@
{ {
description = "Firmware for the Sinara 8451 Thermostat"; description = "Firmware for the Sinara 8451 Thermostat";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-23.05;
inputs.rust-overlay = { inputs.rust-overlay = {
url = "github:oxalica/rust-overlay"; url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@ -47,26 +47,24 @@
''; '';
dontFixup = true; dontFixup = true;
auditable = false;
}; };
in { in {
packages.x86_64-linux = { packages.x86_64-linux = {
inherit thermostat; inherit thermostat;
default = thermostat;
}; };
hydraJobs = { hydraJobs = {
inherit thermostat; inherit thermostat;
}; };
devShells.x86_64-linux.default = pkgs.mkShellNoCC { devShell.x86_64-linux = pkgs.mkShell {
name = "thermostat-dev-shell"; name = "thermostat-dev-shell";
packages = with pkgs; [ buildInputs = with pkgs; [
rust llvm rust openocd dfu-util
openocd dfu-util rlwrap
] ++ (with python3Packages; [ ] ++ (with python3Packages; [
numpy matplotlib numpy matplotlib
]); ]);
}; };
defaultPackage.x86_64-linux = thermostat;
}; };
} }

View File

@ -24,7 +24,7 @@ pub struct Channel<C: ChannelPins> {
pub vref_meas: ElectricPotential, pub vref_meas: ElectricPotential,
pub shdn: C::Shdn, pub shdn: C::Shdn,
pub vref_pin: C::VRefPin, pub vref_pin: C::VRefPin,
pub itec_pin: C::ITecPin, pub itec_pin: C::ItecPin,
/// feedback from `dac` output /// feedback from `dac` output
pub dac_feedback_pin: C::DacFeedbackPin, pub dac_feedback_pin: C::DacFeedbackPin,
pub tec_u_meas_pin: C::TecUMeasPin, pub tec_u_meas_pin: C::TecUMeasPin,

View File

@ -1,4 +1,4 @@
use core::marker::PhantomData; use core::{cmp::max_by, marker::PhantomData};
use heapless::{consts::U2, Vec}; use heapless::{consts::U2, Vec};
use num_traits::Zero; use num_traits::Zero;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
@ -22,7 +22,6 @@ use crate::{
pins::{self, Channel0VRef, Channel1VRef}, pins::{self, Channel0VRef, Channel1VRef},
steinhart_hart, steinhart_hart,
}; };
use crate::timer::sleep;
pub enum PinsAdcReadTarget { pub enum PinsAdcReadTarget {
VREF, VREF,
@ -270,6 +269,17 @@ impl Channels {
} }
} }
pub fn read_dac_feedback_until_stable(&mut self, channel: usize, tolerance: ElectricPotential) -> ElectricPotential {
let mut prev = self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1);
loop {
let current = self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1);
if (current - prev).abs() < tolerance {
return current;
}
prev = current;
}
}
/// Calibrates the DAC output to match vref of the MAX driver to reduce zero-current offset of the MAX driver output. /// Calibrates the DAC output to match vref of the MAX driver to reduce zero-current offset of the MAX driver output.
/// ///
/// The thermostat DAC applies a control voltage signal to the CTLI pin of MAX driver chip to control its output current. /// The thermostat DAC applies a control voltage signal to the CTLI pin of MAX driver chip to control its output current.
@ -296,7 +306,7 @@ impl Channels {
let mut start_value = 1; let mut start_value = 1;
let mut best_error = ElectricPotential::new::<volt>(100.0); let mut best_error = ElectricPotential::new::<volt>(100.0);
for step in (5..18).rev() { for step in (0..18).rev() {
let mut prev_value = start_value; let mut prev_value = start_value;
for value in (start_value..=ad5680::MAX_VALUE).step_by(1 << step) { for value in (start_value..=ad5680::MAX_VALUE).step_by(1 << step) {
match channel { match channel {
@ -308,9 +318,8 @@ impl Channels {
} }
_ => unreachable!(), _ => unreachable!(),
} }
sleep(10);
let dac_feedback = self.adc_read(channel, PinsAdcReadTarget::DacVfb, 64); let dac_feedback = self.read_dac_feedback_until_stable(channel, ElectricPotential::new::<volt>(0.001));
let error = target_voltage - dac_feedback; let error = target_voltage - dac_feedback;
if error < ElectricPotential::new::<volt>(0.0) { if error < ElectricPotential::new::<volt>(0.0) {
break; break;
@ -552,10 +561,9 @@ impl Channels {
} }
pub fn current_abs_max_tec_i(&mut self) -> ElectricCurrent { pub fn current_abs_max_tec_i(&mut self) -> ElectricCurrent {
(0..CHANNELS) max_by(self.get_tec_i(0).abs(),
.map(|channel| self.get_tec_i(channel).abs()) self.get_tec_i(1).abs(),
.max_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)) |a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal))
.unwrap()
} }
} }

View File

@ -345,7 +345,7 @@ impl Handler {
fn set_fan(socket: &mut TcpSocket, fan_pwm: u32, fan_ctrl: &mut FanCtrl) -> Result<Handler, Error> { fn set_fan(socket: &mut TcpSocket, fan_pwm: u32, fan_ctrl: &mut FanCtrl) -> Result<Handler, Error> {
if !fan_ctrl.fan_available() { if !fan_ctrl.fan_available() {
send_line(socket, b"{ \"warning\": \"this thermostat doesn't have a fan!\" }"); send_line(socket, b"{ \"warning\": \"this thermostat doesn't have fan!\" }");
return Ok(Handler::Handled); return Ok(Handler::Handled);
} }
fan_ctrl.set_auto_mode(false); fan_ctrl.set_auto_mode(false);
@ -374,7 +374,7 @@ impl Handler {
fn fan_auto(socket: &mut TcpSocket, fan_ctrl: &mut FanCtrl) -> Result<Handler, Error> { fn fan_auto(socket: &mut TcpSocket, fan_ctrl: &mut FanCtrl) -> Result<Handler, Error> {
if !fan_ctrl.fan_available() { if !fan_ctrl.fan_available() {
send_line(socket, b"{ \"warning\": \"this thermostat doesn't have a fan!\" }"); send_line(socket, b"{ \"warning\": \"this thermostat doesn't have fan!\" }");
return Ok(Handler::Handled); return Ok(Handler::Handled);
} }
fan_ctrl.set_auto_mode(true); fan_ctrl.set_auto_mode(true);

View File

@ -58,7 +58,7 @@ mod hw_rev;
const HSE: MegaHertz = MegaHertz(8); const HSE: MegaHertz = MegaHertz(8);
#[cfg(not(feature = "semihosting"))] #[cfg(not(feature = "semihosting"))]
const WATCHDOG_INTERVAL: u32 = 2_000; const WATCHDOG_INTERVAL: u32 = 1_000;
#[cfg(feature = "semihosting")] #[cfg(feature = "semihosting")]
const WATCHDOG_INTERVAL: u32 = 30_000; const WATCHDOG_INTERVAL: u32 = 30_000;

View File

@ -61,7 +61,7 @@ pub trait ChannelPins {
type DacSync: OutputPin; type DacSync: OutputPin;
type Shdn: OutputPin; type Shdn: OutputPin;
type VRefPin; type VRefPin;
type ITecPin; type ItecPin;
type DacFeedbackPin; type DacFeedbackPin;
type TecUMeasPin; type TecUMeasPin;
} }
@ -76,7 +76,7 @@ impl ChannelPins for Channel0 {
type DacSync = PE4<Output<PushPull>>; type DacSync = PE4<Output<PushPull>>;
type Shdn = PE10<Output<PushPull>>; type Shdn = PE10<Output<PushPull>>;
type VRefPin = Channel0VRef; type VRefPin = Channel0VRef;
type ITecPin = PA6<Analog>; type ItecPin = PA6<Analog>;
type DacFeedbackPin = PA4<Analog>; type DacFeedbackPin = PA4<Analog>;
type TecUMeasPin = PC2<Analog>; type TecUMeasPin = PC2<Analog>;
} }
@ -91,7 +91,7 @@ impl ChannelPins for Channel1 {
type DacSync = PF6<Output<PushPull>>; type DacSync = PF6<Output<PushPull>>;
type Shdn = PE15<Output<PushPull>>; type Shdn = PE15<Output<PushPull>>;
type VRefPin = Channel1VRef; type VRefPin = Channel1VRef;
type ITecPin = PB0<Analog>; type ItecPin = PB0<Analog>;
type DacFeedbackPin = PA5<Analog>; type DacFeedbackPin = PA5<Analog>;
type TecUMeasPin = PC3<Analog>; type TecUMeasPin = PC3<Analog>;
} }
@ -108,7 +108,7 @@ pub struct ChannelPinSet<C: ChannelPins> {
pub dac_sync: C::DacSync, pub dac_sync: C::DacSync,
pub shdn: C::Shdn, pub shdn: C::Shdn,
pub vref_pin: C::VRefPin, pub vref_pin: C::VRefPin,
pub itec_pin: C::ITecPin, pub itec_pin: C::ItecPin,
pub dac_feedback_pin: C::DacFeedbackPin, pub dac_feedback_pin: C::DacFeedbackPin,
pub tec_u_meas_pin: C::TecUMeasPin, pub tec_u_meas_pin: C::TecUMeasPin,
} }