initial commit

main
q3k 2023-10-11 17:18:08 +02:00
commit 789a437590
15 changed files with 1052 additions and 0 deletions

58
README.md Normal file
View File

@ -0,0 +1,58 @@
q3k's Pullstruder
===
PET bottle recycling machine. Based on the [Recreator3d](http://recreator3d.com/) by Joshua R. Taylor, but with a few twists.
Work in progress.
Electronics
===
Mainboard is a Melzi 'v5.0' with an ATMega1284P. Reused from a TRONXY X1.
Schematic of 2.0: https://reprap.org/mediawiki/images/7/7d/Melzi-circuit.png
EXP connector:
----
PC1 | 1 2 | PA1
PC0 | 3 4 | PA2
PD3 | 5 6 | PA3
PD2 | 7 8 | PA4
VCC | 9 10 | GND
LCD board
===
| EXP Pin | AVR Pin | AVR Function | Screen board function |
|---------|---------|--------------|-----------------------|
| 1 | PC1 | I2C SDA | LCD D7 |
| 2 | PA1 | ADC1 | Button Matrix |
| 3 | PC0 | I2C SCL | LCD D6 |
| 4 | PA2 | ADC2 | LCD EN |
| 5 | PD3 | INT1 | LCD D5 |
| 6 | PA3 | ADC3 | LCD RS |
| 7 | PD2 | INT0 | LCD D4 |
| 8 | PA4 | ADC4 | Unused? |
Button Matrix
---
Based on a voltage divider between button(s) pressed and a constant pull-up to VCC.
Resistor to VCC: 4.7k
| Button | Resistor to GND | Voltage when pressed (VCC=5V) | ADC Value |
|--------|-----------------|-------------------------------|-----------|
| Left | 470 | 0.45V | 93 |
| Down | 1k | 0.87V | 180 |
| Menu | 2.2k | 1.6V | 326 |
| Right | 4.7k | 2.5V | 512 |
| Up | 10k | 3.4V | 697 |
Firmware
===
In progress, written in Rust. Aims to be purpose-specific and not just a repurposed 3D printer control firmware.

View File

@ -0,0 +1,5 @@
[build]
target = "avr-specs/avr-atmega1284p.json"
[unstable]
build-std = ["core"]

2
firmware/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target
old

318
firmware/Cargo.lock generated Normal file
View File

@ -0,0 +1,318 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "atmega-hal"
version = "0.1.0"
source = "git+https://github.com/rahix/avr-hal?rev=7b3e82a15e97e657559ec82cf934ba36c38312ec#7b3e82a15e97e657559ec82cf934ba36c38312ec"
dependencies = [
"avr-device",
"avr-hal-generic",
]
[[package]]
name = "atomic-polyfill"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28"
dependencies = [
"critical-section",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "avr-device"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84b1e02e846628f283a1c103ef5946224a07092761e4fb91fb93ab794d0b0ada"
dependencies = [
"avr-device-macros",
"bare-metal",
"cfg-if 1.0.0",
"vcell",
]
[[package]]
name = "avr-device-macros"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "817bb402f7da3b9e586d8f2db8a8f3f7d8fdc1562aaa4238a5ed495ebd19fc8f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "avr-hal-generic"
version = "0.1.0"
source = "git+https://github.com/rahix/avr-hal?rev=7b3e82a15e97e657559ec82cf934ba36c38312ec#7b3e82a15e97e657559ec82cf934ba36c38312ec"
dependencies = [
"avr-device",
"cfg-if 0.1.10",
"embedded-hal",
"embedded-storage",
"nb 0.1.3",
"paste",
"rustversion",
"ufmt",
"void",
]
[[package]]
name = "bare-metal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "critical-section"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216"
[[package]]
name = "embedded-hal"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
dependencies = [
"nb 0.1.3",
"void",
]
[[package]]
name = "embedded-storage"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "723dce4e9f25b6e6c5f35628e144794e5b459216ed7da97b7c4b66cdb3fa82ca"
[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[package]]
name = "hd44780-driver"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aab2b13fdeaed7dde9133a57c28b2cbde4a8fc8c3196b5631428aad114857d3a"
dependencies = [
"embedded-hal",
]
[[package]]
name = "heapless"
version = "0.7.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743"
dependencies = [
"atomic-polyfill",
"hash32",
"rustc_version",
"spin",
"stable_deref_trait",
]
[[package]]
name = "lock_api"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "nb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
dependencies = [
"nb 1.1.0",
]
[[package]]
name = "nb"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
[[package]]
name = "panic-halt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812"
[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "proc-macro-hack"
version = "0.5.20+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "recreator3d-q3k-fw"
version = "1.0.0"
dependencies = [
"atmega-hal",
"avr-device",
"embedded-hal",
"hd44780-driver",
"heapless",
"nb 0.1.3",
"panic-halt",
"ufmt",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "rustversion"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "ufmt"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31d3c0c63312dfc9d8e5c71114d617018a19f6058674003c0da29ee8d8036cdd"
dependencies = [
"proc-macro-hack",
"ufmt-macros",
"ufmt-write",
]
[[package]]
name = "ufmt-macros"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4ab6c92f30c996394a8bd525aef9f03ce01d0d7ac82d81902968057e37dd7d9"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "ufmt-write"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "vcell"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"

27
firmware/Cargo.toml Normal file
View File

@ -0,0 +1,27 @@
[package]
name = "recreator3d-q3k-fw"
version = "1.0.0"
authors = ["q3k"]
edition = "2021"
[dependencies]
panic-halt = "0.2.0"
ufmt = "0.1.0"
nb = "0.1.2"
embedded-hal = "0.2.3"
hd44780-driver = "0"
heapless = "0"
[dependencies.avr-device]
version = "0"
features = ["rt"]
[dependencies.atmega-hal]
git = "https://github.com/rahix/avr-hal"
rev = "7b3e82a15e97e657559ec82cf934ba36c38312ec"
features = ["atmega1284p"]
[profile.dev]
lto = true
opt-level = "z"
panic = "abort"

View File

@ -0,0 +1,25 @@
{
"arch": "avr",
"atomic-cas": false,
"cpu": "atmega1284p",
"data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
"eh-frame-header": false,
"exe-suffix": ".elf",
"executables": true,
"late-link-args": {
"gcc": [
"-lgcc"
]
},
"linker": "avr-gcc",
"llvm-target": "avr-unknown-unknown",
"max-atomic-width": 8,
"no-default-libraries": false,
"pre-link-args": {
"gcc": [
"-mmcu=atmega1284p"
]
},
"target-c-int-width": "16",
"target-pointer-width": "16"
}

1
firmware/result Symbolic link
View File

@ -0,0 +1 @@
/nix/store/4dg5g26sw712f6a9di2zslby8pz9klbd-nixos-system-mimeomia-23.11pre532072.81e8f48ebdec

7
firmware/shell.nix Normal file
View File

@ -0,0 +1,7 @@
with import <nixpkgs> {};
pkgs.mkShell {
nativeBuildInputs = [
pkgs.pkgsCross.avr.buildPackages.gcc
];
}

186
firmware/src/display.rs Normal file
View File

@ -0,0 +1,186 @@
use hd44780_driver::HD44780;
use embedded_hal::digital::v2::OutputPin;
use core::fmt::Write;
use crate::hw::Delay;
use crate::logic::State;
use crate::view;
pub struct Display<B: hd44780_driver::bus::DataBus> {
lcd: HD44780<B>,
delay: Delay,
layout: Layout,
}
impl <RS, EN, D4, D5, D6, D7> Display<hd44780_driver::bus::FourBitBus<RS, EN, D4, D5, D6, D7>>
where
RS: OutputPin, EN: OutputPin, D4: OutputPin,
D5: OutputPin, D6: OutputPin, D7: OutputPin,
{
pub fn new(rs: RS, en: EN, d4: D4, d5: D5, d6: D6, d7: D7) -> Self
{
let mut delay = Delay::new();
let mut lcd = HD44780::new_4bit(rs, en, d4, d5, d6, d7, &mut delay).unwrap();
lcd.reset(&mut delay).ok();
lcd.clear(&mut delay).ok();
lcd.set_display_mode(hd44780_driver::display_mode::DisplayMode {
cursor_visibility: hd44780_driver::Cursor::Invisible,
cursor_blink: hd44780_driver::CursorBlink::Off,
display: hd44780_driver::Display::On,
}, &mut delay).ok();
Display {
lcd,
delay,
layout: Layout::Empty,
}
}
}
impl <B: hd44780_driver::bus::DataBus> Display<B> {
fn pos(&self, x: u8, y: u8) -> u8 {
match y {
0 => x,
1 => x + 64,
2 => x + 20,
3 => x + 84,
_ => 0,
}
}
fn write_str_at(&mut self, x: usize, y: usize, s: &str) {
if x > 19 || y > 3 {
return
}
let mut len = 20 - x;
if len as usize > s.len() {
len = s.len();
}
let x = x as u8;
let y = y as u8;
self.lcd.set_cursor_pos(self.pos(x, y), &mut self.delay).ok();
let len = len as usize;
self.lcd.write_str(&s[..len], &mut self.delay).ok();
}
pub fn write(&mut self, l: Layout) {
match l {
Layout::Empty => {
if let Layout::Empty = self.layout {
return;
}
self.lcd.clear(&mut self.delay).ok();
},
Layout::Splash => {
self.lcd.clear(&mut self.delay).ok();
self.write_str_at((20 - SPLASH_1.len()) / 2, 1, SPLASH_1);
self.write_str_at((20 - SPLASH_2.len()) / 2, 2, SPLASH_2);
},
Layout::Status(ref cur) => {
let mut temperature_changed = true;
let mut time_left_changed = true;
let mut state_changed = true;
let mut speed_changed = true;
if let Layout::Status(ref prev) = &self.layout {
temperature_changed = prev.temperature != cur.temperature;
time_left_changed = prev.time_left != cur.time_left;
state_changed = prev.state != cur.state;
speed_changed = prev.speed != cur.speed;
} else {
self.lcd.clear(&mut self.delay).ok();
self.write_str_at(0, 0, " State");
self.write_str_at(0, 1, "Temperature");
self.write_str_at(0, 2, " Speed");
self.write_str_at(0, 3, " Time Left");
}
let mut s: heapless::String<16> = heapless::String::new();
if state_changed {
let s = match cur.state {
State::Idle => "Idle ",
State::Preheating => "Heating",
State::Running => "Running",
};
self.write_str_at(12, 0, s);
}
if temperature_changed {
s.clear();
write!(s, "{}.{}C", cur.temperature / 10, cur.temperature % 10).ok();
while s.len() < 8 {
s.push(' ').ok();
}
self.write_str_at(12, 1, &s);
}
if speed_changed {
s.clear();
write!(s, "{}mm/s", cur.speed).ok();
while s.len() < 8 {
s.push(' ').ok();
}
self.write_str_at(12, 2, &s);
}
if time_left_changed {
s.clear();
let h = cur.time_left / 3600;
let m = (cur.time_left / 60) % 60;
let ss = cur.time_left % 60;
write!(s, "{:02}:{:02}:{:02}", h, m, ss).ok();
while s.len() < 8 {
s.push(' ').ok();
}
self.write_str_at(12, 3, &s);
}
},
Layout::Menu(ref cur) => {
let redraw_options = match &self.layout {
Layout::Menu(LayoutMenu { name, .. }) => *name != cur.name,
_ => true,
};
let redraw_cursor = match &self.layout {
Layout::Menu(LayoutMenu { pos, .. }) => *pos != cur.pos,
_ => true,
};
if redraw_options {
self.lcd.clear(&mut self.delay).ok();
for (i, option) in cur.options.iter().enumerate() {
self.write_str_at(1, i, option.label);
}
}
if redraw_cursor {
for (i, _) in cur.options.iter().enumerate() {
if i == cur.pos {
self.write_str_at(0, i, ">");
} else {
self.write_str_at(0, i, " ");
}
}
}
},
}
self.layout = l;
}
}
pub struct LayoutStatus {
pub temperature: u16,
pub time_left: u32,
pub state: State,
pub speed: u16,
}
pub struct LayoutMenu {
pub name: view::ViewID,
pub pos: usize,
pub options: &'static [view::MenuOption],
}
pub enum Layout {
Empty,
Splash,
Status(LayoutStatus),
Menu(LayoutMenu),
}
const SPLASH_1: &'static str = "q3k's pullstruder";
const SPLASH_2: &'static str = "2023/10/07";

9
firmware/src/hw.rs Normal file
View File

@ -0,0 +1,9 @@
use embedded_hal::blocking::delay::DelayMs;
pub type Clock = atmega_hal::clock::MHz16;
pub type Delay = atmega_hal::delay::Delay<Clock>;
pub fn delay_ms(ms: u16) {
Delay::new().delay_ms(ms)
}

20
firmware/src/key.rs Normal file
View File

@ -0,0 +1,20 @@
#[derive(Eq, PartialEq, Clone, Copy)]
pub enum Key {
Up,
Down,
Left,
Right,
Select,
}
pub fn parse(button_adc_val: u16) -> Option<Key> {
match button_adc_val {
80..=100 => Some(Key::Left),
170..=190 => Some(Key::Down),
320..=330 => Some(Key::Select),
500..=520 => Some(Key::Right),
690..=710 => Some(Key::Up),
_ => None,
}
}

122
firmware/src/logic.rs Normal file
View File

@ -0,0 +1,122 @@
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum State {
Idle,
Preheating,
Running,
}
#[derive(Clone)]
pub enum UserRequest {
Start,
Adjust {
speed: Option<u16>,
temperature: Option<u16>,
time: Option<u32>,
},
Stop,
}
pub struct Input {
pub timestamp: u32,
pub temperature: u16,
pub request: Option<UserRequest>,
}
pub struct Controller {
pub state: State,
deadline: u32,
configured_seconds: u32,
last_timestamp: u32,
pub speed: u16,
pub temperature: u16,
}
impl Controller {
pub fn new() -> Self {
Self {
state: State::Idle,
deadline: 0,
configured_seconds: 3600,
last_timestamp: 0,
speed: 300,
temperature: 2137,
}
}
pub fn time_left(&self) -> u32 {
match self.state {
State::Running => {
if self.last_timestamp > self.deadline {
return 0;
}
self.deadline - self.last_timestamp
},
_ => self.configured_seconds,
}
}
pub fn process(&mut self, i: &Input) {
self.last_timestamp = i.timestamp;
match self.state {
State::Idle => self.process_idle(i),
State::Preheating => self.process_preheating(i),
State::Running => self.process_running(i),
}
}
fn apply_adjust(&mut self, speed: Option<u16>, temperature: Option<u16>, time: Option<u32>) {
if let Some(speed) = speed {
self.speed = speed;
}
if let Some(temperature) = temperature {
self.temperature = temperature;
}
if let Some(time) = time {
self.configured_seconds = time;
}
}
fn process_idle(&mut self, i: &Input) {
if let Some(req) = &i.request {
match req {
UserRequest::Start => {
self.deadline = i.timestamp + self.configured_seconds;
self.state = State::Preheating;
},
UserRequest::Adjust { speed, temperature, time } => {
self.apply_adjust(speed.clone(), temperature.clone(), time.clone());
},
UserRequest::Stop => {},
}
}
}
fn process_preheating(&mut self, i: &Input) {
if let Some(req) = &i.request {
match req {
UserRequest::Start {..} => {},
UserRequest::Adjust { speed, temperature, time } => {
self.apply_adjust(speed.clone(), temperature.clone(), time.clone());
},
UserRequest::Stop => {
self.state = State::Idle;
},
}
}
}
fn process_running(&mut self, i: &Input) {
if let Some(req) = &i.request {
match req {
UserRequest::Start {..} => {},
UserRequest::Adjust { speed, temperature, time } => {
self.apply_adjust(speed.clone(), temperature.clone(), time.clone());
},
UserRequest::Stop => {
self.state = State::Idle;
},
}
}
}
}

96
firmware/src/main.rs Normal file
View File

@ -0,0 +1,96 @@
#![no_std]
#![no_main]
#![feature(abi_avr_interrupt)]
use panic_halt as _;
mod key;
mod view;
mod display;
mod logic;
mod hw;
/// Seconds elapsed since startup. Overflows after 136 years of uptime.
static mut TICKS_SEC: u32 = 0;
#[avr_device::interrupt(atmega1284p)]
fn TIMER1_COMPA() {
unsafe {
TICKS_SEC += 1;
if TICKS_SEC == 0 {
panic!("have you been running this non-stop for 136 years????");
}
}
}
fn ticks_sec() -> u32 {
let v = unsafe {
TICKS_SEC
};
return v;
}
#[avr_device::entry]
fn main() -> ! {
let dp = atmega_hal::Peripherals::take().unwrap();
let pins = atmega_hal::pins!(dp);
let mut led = pins.pa4.into_output().downgrade();
let mut display = display::Display::new(
pins.pa3.into_output(), // RS
pins.pa2.into_output(), // EN
pins.pd2.into_output(), // D4
pins.pd3.into_output(), // D5
pins.pc0.into_output(), // D6
pins.pc1.into_output(), // D7
);
let mut adc = atmega_hal::Adc::<hw::Clock>::new(dp.ADC, Default::default());
let menu_pin = pins.pa1.into_analog_input(&mut adc);
// Configure TMR1 at 1Hz.
let tmr1 = dp.TC1;
tmr1.tccr1a.write(|w| w.wgm1().bits(0b00));
tmr1.tccr1b.write(|w| w.cs1().prescale_1024().wgm1().bits(0b01));
tmr1.ocr1a.write(|w| w.bits(15624));
tmr1.timsk1.write(|w| w.ocie1a().set_bit());
unsafe {
avr_device::interrupt::enable();
}
display.write(display::Layout::Splash);
hw::delay_ms(1000);
let mut ctrl = logic::Controller::new();
let mut menu = view::ViewManager::new();
let mut prev_key: Option<key::Key> = None;
loop {
let ticks = ticks_sec();
// UI
let key = key::parse(adc.read_blocking(&menu_pin));
let mut menu_input = view::ViewInput {
key: None,
};
if key != prev_key {
menu_input.key = key;
};
prev_key = key;
let req = menu.process(&menu_input);
display.write(menu.layout(&ctrl));
// Logic
let input = logic::Input {
timestamp: ticks,
temperature: 2137,
request: req,
};
ctrl.process(&input);
led.toggle();
}
}

View File

@ -0,0 +1,17 @@
/// Convert from ADC value to decidegrees.
fn convert(adc: u16) -> u16 {
// The thermistor is connected via a resistor divider:
//
// VCC <---/\/\/\---+---/\/\/\----> GND
// 4.7k | NTC
// V ADC
//
// If we express V_SUP in arbitrary ADC units (0-1023), then we can
// calculate the resistance as a function of ADC value:
//
// R = 4700 / ((1024 / ADC) - 1)
// Clamp to safe value. This prevents division by zero.
let adc = if adc >= 1024 { 1024 } else { adc};
let adc = if adc < 1 { 1 } else { adc };
let r = 4700 / ((1024 / adc) - 1);

159
firmware/src/view.rs Normal file
View File

@ -0,0 +1,159 @@
use crate::key::Key;
use crate::logic::{Controller, UserRequest};
use crate::display::{Layout, LayoutStatus, LayoutMenu};
pub struct ViewInput {
pub key: Option<Key>,
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum ViewID {
Status,
Main,
}
#[derive(Clone)]
pub struct MenuAction {
goto_view: Option<ViewID>,
logic_request: Option<UserRequest>,
}
impl MenuAction {
fn nothing() -> Self {
Self {
goto_view: None,
logic_request: None,
}
}
fn just_goto_view(vid: ViewID) -> Self {
Self {
goto_view: Some(vid),
logic_request: None,
}
}
fn just_logic_request(req: UserRequest) -> Self {
Self {
goto_view: None,
logic_request: Some(req),
}
}
}
pub trait View {
fn layout(&self, controller: &Controller) -> Layout;
fn process(&mut self, input: &ViewInput) -> MenuAction;
fn enter(&mut self) { }
}
pub struct StatusView;
impl View for StatusView {
fn layout(&self, controller: &Controller) -> Layout {
Layout::Status(LayoutStatus {
temperature: controller.temperature,
time_left: controller.time_left(),
state: controller.state,
speed: controller.speed,
})
}
fn process(&mut self, input: &ViewInput) -> MenuAction {
if input.key.is_some() {
return MenuAction::just_goto_view(ViewID::Main);
}
return MenuAction::nothing();
}
}
struct Menu {
id: ViewID,
pos: usize,
options: &'static [MenuOption],
}
pub struct MenuOption {
pub label: &'static str,
action: MenuAction,
}
impl View for Menu {
fn layout(&self, _controller: &Controller) -> Layout {
Layout::Menu(LayoutMenu {
name: self.id,
pos: self.pos,
options: self.options,
})
}
fn process(&mut self, input: &ViewInput) -> MenuAction{
match input.key {
Some(Key::Up) => { if self.pos > 0 { self.pos -= 1; } },
Some(Key::Down) => { if self.pos < self.options.len()-1 { self.pos += 1; } },
Some(Key::Select) => { return self.options[self.pos].action.clone() },
_ => (),
}
return MenuAction::nothing();
}
fn enter(&mut self) {
self.pos = 0;
}
}
pub struct ViewManager {
status: StatusView,
main: Menu,
cur: ViewID,
}
impl ViewManager {
pub fn new() -> Self {
Self {
status: StatusView,
main: Menu {
id: ViewID::Main,
pos: 0,
options: &[
MenuOption {
label: "Back",
action: MenuAction {
goto_view: Some(ViewID::Status),
logic_request: None,
},
},
MenuOption {
label: "Start",
action: MenuAction {
goto_view: Some(ViewID::Status),
logic_request: Some(UserRequest::Start),
},
},
],
},
cur: ViewID::Status,
}
}
pub fn layout(&self, controller: &Controller) -> Layout {
match self.cur {
ViewID::Status => self.status.layout(controller),
ViewID::Main => self.main.layout(controller),
}
}
pub fn process(&mut self, input: &ViewInput) -> Option<UserRequest> {
let new = match self.cur {
ViewID::Status => self.status.process(input),
ViewID::Main => self.main.process(input),
};
if let Some(id) = new.goto_view {
self.cur = id;
match self.cur {
ViewID::Status => self.status.enter(),
ViewID::Main => self.main.enter(),
}
}
return new.logic_request;
}
}