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/
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`.
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
@ -45,7 +45,7 @@ There are several options for flashing Thermostat. DFU requires only a micro-USB
### dfu-util on Linux
* 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.
* Add jumper to Thermostat v2.0 across 2-pin jumper adjacent to JTAG connector.
* Cycle board power to put it in DFU update mode

View File

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

View File

@ -1,7 +1,7 @@
{
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 = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
@ -47,26 +47,24 @@
'';
dontFixup = true;
auditable = false;
};
in {
packages.x86_64-linux = {
inherit thermostat;
default = thermostat;
};
hydraJobs = {
inherit thermostat;
};
devShells.x86_64-linux.default = pkgs.mkShellNoCC {
devShell.x86_64-linux = pkgs.mkShell {
name = "thermostat-dev-shell";
packages = with pkgs; [
rust llvm
openocd dfu-util rlwrap
buildInputs = with pkgs; [
rust openocd dfu-util
] ++ (with python3Packages; [
numpy matplotlib
]);
};
defaultPackage.x86_64-linux = thermostat;
};
}

View File

@ -24,7 +24,7 @@ pub struct Channel<C: ChannelPins> {
pub vref_meas: ElectricPotential,
pub shdn: C::Shdn,
pub vref_pin: C::VRefPin,
pub itec_pin: C::ITecPin,
pub itec_pin: C::ItecPin,
/// feedback from `dac` output
pub dac_feedback_pin: C::DacFeedbackPin,
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 num_traits::Zero;
use serde::{Serialize, Serializer};
@ -22,7 +22,6 @@ use crate::{
pins::{self, Channel0VRef, Channel1VRef},
steinhart_hart,
};
use crate::timer::sleep;
pub enum PinsAdcReadTarget {
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.
///
/// 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 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;
for value in (start_value..=ad5680::MAX_VALUE).step_by(1 << step) {
match channel {
@ -308,9 +318,8 @@ impl Channels {
}
_ => 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;
if error < ElectricPotential::new::<volt>(0.0) {
break;
@ -552,10 +561,9 @@ impl Channels {
}
pub fn current_abs_max_tec_i(&mut self) -> ElectricCurrent {
(0..CHANNELS)
.map(|channel| self.get_tec_i(channel).abs())
.max_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal))
.unwrap()
max_by(self.get_tec_i(0).abs(),
self.get_tec_i(1).abs(),
|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal))
}
}

View File

@ -345,7 +345,7 @@ impl Handler {
fn set_fan(socket: &mut TcpSocket, fan_pwm: u32, fan_ctrl: &mut FanCtrl) -> Result<Handler, Error> {
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);
}
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> {
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);
}
fan_ctrl.set_auto_mode(true);

View File

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

View File

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