Compare commits
286 Commits
Author | SHA1 | Date |
---|---|---|
Astro | 2773ba47fe | |
Astro | 13771bf770 | |
Astro | 02d2403547 | |
Astro | ecdebe76bc | |
Astro | 6041e41716 | |
Astro | 9b1a0696ab | |
Astro | d360ec6dce | |
Astro | 7efc95941b | |
Astro | 7efd42715e | |
Astro | aafb733209 | |
Astro | bb35f91a57 | |
Astro | 225be7b911 | |
Astro | c2aa0e2989 | |
Astro | cc5c21e088 | |
Astro | 420be00407 | |
Astro | 2010b4fe10 | |
Astro | c8d31c7b0d | |
Astro | 4ac0f7b171 | |
Astro | 21615819f6 | |
Astro | 59d3fde32e | |
Astro | 86b5841119 | |
Astro | e1af02f77a | |
Astro | 53ff7e70ca | |
Astro | 83117db8c5 | |
Astro | 12c2be0a03 | |
Astro | 8611cc1c79 | |
Astro | 2992688184 | |
Astro | 7f32591441 | |
Astro | 9486946d06 | |
Astro | ef17e1c4ff | |
Astro | b508129c37 | |
Astro | fca427cb5a | |
Astro | 18e3e95615 | |
Astro | a82ffadb35 | |
Astro | 8d01ca8d20 | |
Astro | f3664f01be | |
Astro | 6f36c682cd | |
Astro | 35dfba99e1 | |
Astro | 63aa2347b7 | |
Astro | 87de8b7859 | |
Astro | e126bc0fe1 | |
Astro | 1514131fa3 | |
Astro | 6d4676a72a | |
Astro | f64e4fe2f3 | |
Astro | 2d2e7e80e0 | |
Astro | 2ca06e023b | |
Astro | 4e5de7831b | |
Astro | db1788bafb | |
Astro | ba485cab16 | |
Astro | ee8f8e87c3 | |
Astro | f048026d21 | |
Astro | 42a9b89db1 | |
Astro | 393c276bda | |
Astro | 4c00548646 | |
Astro | a7ee2107ea | |
Astro | 4587406d44 | |
Astro | 7e51585aa9 | |
Astro | d4428b7fdc | |
Astro | ee4d24de6a | |
Astro | b969f5c057 | |
Astro | 700ab47f0e | |
Astro | b6af43feda | |
Astro | 426be0d5f1 | |
Astro | 5c58c4370d | |
Astro | 328f6921fa | |
Astro | 8163d083b9 | |
Astro | 1395e8b410 | |
Astro | 5e0f55647a | |
Astro | 5ef8d6a747 | |
Astro | ff3a793c19 | |
Astro | 07dcc608bc | |
Astro | 957f92d177 | |
Astro | f8dd7d1912 | |
Astro | c50e1c7766 | |
Astro | 76e30c0f7c | |
Astro | f2dcb8b08d | |
Astro | 3fd1b2265d | |
Astro | edb0401838 | |
Astro | 44f5a8338c | |
Astro | 0dcd35c9f2 | |
Astro | 7f95f01711 | |
Astro | 3ef317f00d | |
Astro | 8b2cc15d7d | |
Astro | 98a5788770 | |
Astro | 4e518e88ee | |
Astro | 25dc3fb70c | |
Astro | 4249addba2 | |
Astro | 225f3754a1 | |
Astro | e5529a8b94 | |
Astro | 152bc7b98b | |
Astro | 3bf1010969 | |
Astro | baab9b2d3f | |
Astro | f7af12adf5 | |
Astro | 0697914182 | |
Astro | c5dfaf0ee2 | |
Astro | 3f7da6e328 | |
Astro | f94d915328 | |
Astro | ea395460c8 | |
Astro | 01a9decfa1 | |
Astro | e947415242 | |
Astro | 5ba09db517 | |
Astro | fad1050556 | |
Astro | bf8c7fda88 | |
Astro | f94173788e | |
Astro | d5b7855c3b | |
Astro | 1df35ef15f | |
Astro | 12a1c1ac07 | |
Astro | 2b855c8ad9 | |
Astro | 574b96187a | |
Astro | 6e02b2c4f6 | |
Astro | f3831aba18 | |
Astro | 0eefebf3ed | |
Astro | 9c0f560367 | |
Astro | 8b20118198 | |
Sebastien Bourdeauducq | 1329c1567c | |
Sebastien Bourdeauducq | 68e2b4634f | |
Sebastien Bourdeauducq | e23e13fced | |
Astro | 82b6e8e179 | |
Astro | 057ddbdbf6 | |
Astro | 4437a4195e | |
Astro | 44f48f6e0f | |
Astro | 9c7ca0df87 | |
Astro | f04bbd8726 | |
Astro | 4bf52b093e | |
Sebastien Bourdeauducq | 2fcae2cdee | |
sb10q | 3fe5aae04e | |
sb10q | 9a9a9e0107 | |
Astro | 5f3d674e24 | |
Astro | e4478c3efd | |
Astro | 2ac3485b30 | |
whitequark | 1a00f5cf1a | |
whitequark | 517e531589 | |
whitequark | 89104a551c | |
Sébastien Bourdeauducq | 8164b75dfa | |
a-shafir | 9e1aa8b698 | |
whitequark | 941f602a20 | |
whitequark | 6ffb157cb8 | |
whitequark | 2bdc483e15 | |
whitequark | 2d29078ba1 | |
whitequark | 8de311a34f | |
whitequark | b13ef96bbe | |
whitequark | 308ad97586 | |
whitequark | 5a5596f7a2 | |
whitequark | dce70f352a | |
whitequark | f067d0fee9 | |
whitequark | 83629cac49 | |
whitequark | 4cac825c41 | |
Sebastien Bourdeauducq | e1d7924969 | |
Sebastien Bourdeauducq | cbdd2c409c | |
Sebastien Bourdeauducq | 13aa02a318 | |
Sebastien Bourdeauducq | 762ababe7f | |
Sebastien Bourdeauducq | 8737557752 | |
Sebastien Bourdeauducq | 4641af7dcb | |
Sebastien Bourdeauducq | 7a2cf0a5e5 | |
Sebastien Bourdeauducq | 9a1d839e19 | |
Sebastien Bourdeauducq | cf2e3c251e | |
Sebastien Bourdeauducq | 7b9594f81a | |
whitequark | dcb5321e82 | |
whitequark | 724221c643 | |
whitequark | c6887e3813 | |
whitequark | 8491394a50 | |
Sebastien Bourdeauducq | fe088d7bba | |
Sebastien Bourdeauducq | 4347ddc537 | |
Sebastien Bourdeauducq | 416ac30496 | |
Sebastien Bourdeauducq | 7b10386907 | |
Sebastien Bourdeauducq | 34ae0901ae | |
Sebastien Bourdeauducq | 30746d0565 | |
Sebastien Bourdeauducq | 916e940780 | |
Sebastien Bourdeauducq | 46d7d8bf99 | |
Sebastien Bourdeauducq | e6f3a65642 | |
Sebastien Bourdeauducq | 9c64304cf2 | |
Sebastien Bourdeauducq | 5c1cacbd38 | |
Sebastien Bourdeauducq | 1a06b524d2 | |
Sebastien Bourdeauducq | e8174f0773 | |
Sebastien Bourdeauducq | e5ea9a3918 | |
Sebastien Bourdeauducq | 5955c0f97d | |
Sebastien Bourdeauducq | bd9082561c | |
Sebastien Bourdeauducq | 49cac15621 | |
Sebastien Bourdeauducq | 98256a0239 | |
Sebastien Bourdeauducq | 3059720430 | |
Sebastien Bourdeauducq | d812932732 | |
Alexander Shafir | f94b50e9ab | |
Sebastien Bourdeauducq | dcd2a57aa4 | |
Sebastien Bourdeauducq | 5ef86b4516 | |
Alexander Shafir | 115211c143 | |
Sebastien Bourdeauducq | e7bca6d0c7 | |
Sebastien Bourdeauducq | 93d0401b71 | |
Sebastien Bourdeauducq | 5d4a223800 | |
Sebastien Bourdeauducq | c815d4d37f | |
Sebastien Bourdeauducq | 9a4adb267d | |
Sebastien Bourdeauducq | cccd6e52f6 | |
Sebastien Bourdeauducq | e676cb59bf | |
Sebastien Bourdeauducq | f156c7c6d1 | |
Sebastien Bourdeauducq | d18712d1be | |
Sebastien Bourdeauducq | 68eb1cdbba | |
Sebastien Bourdeauducq | 98b17fc574 | |
Sebastien Bourdeauducq | 1da96a2a4d | |
Sebastien Bourdeauducq | 8247c8f5a5 | |
Sebastien Bourdeauducq | e8d6d84ac5 | |
Sebastien Bourdeauducq | 648b4da9da | |
Sebastien Bourdeauducq | 98f116e226 | |
Alexander Shafir | 0ab3b6116e | |
Sebastien Bourdeauducq | c03b6a6fb7 | |
Sebastien Bourdeauducq | b3fd5568e5 | |
Sebastien Bourdeauducq | 25f62e361c | |
Sebastien Bourdeauducq | 96489e2b92 | |
Sebastien Bourdeauducq | 3503a9c6d3 | |
Sebastien Bourdeauducq | 13f614f033 | |
Sebastien Bourdeauducq | 23ba68bca9 | |
Sebastien Bourdeauducq | d0889ed1f6 | |
Sebastien Bourdeauducq | 2ac07d78d4 | |
Sebastien Bourdeauducq | 1444882679 | |
Sebastien Bourdeauducq | 914dc7f6c8 | |
Sebastien Bourdeauducq | 52c123f215 | |
Sebastien Bourdeauducq | 5f19c2fdf5 | |
Sebastien Bourdeauducq | ef52ca7e2c | |
Sebastien Bourdeauducq | 0094a73336 | |
Sebastien Bourdeauducq | 2127da51a3 | |
Sebastien Bourdeauducq | fd513e553d | |
Sebastien Bourdeauducq | 6175c80f79 | |
Sebastien Bourdeauducq | 536b46031e | |
Sebastien Bourdeauducq | 46b806d66f | |
Sebastien Bourdeauducq | f8cdbd6592 | |
Sebastien Bourdeauducq | 3bbdce1d43 | |
Sebastien Bourdeauducq | 7d7ea4228c | |
Sebastien Bourdeauducq | 680a00779e | |
Sebastien Bourdeauducq | ebc341f28c | |
Sebastien Bourdeauducq | 5f90960ce1 | |
Sebastien Bourdeauducq | 5e13d30cc1 | |
Sebastien Bourdeauducq | 5ab0885a1f | |
Sebastien Bourdeauducq | 246839f153 | |
Sebastien Bourdeauducq | dc4534eb00 | |
Sebastien Bourdeauducq | ba1482db43 | |
Sebastien Bourdeauducq | 9803388f49 | |
Sebastien Bourdeauducq | ad447cd5f9 | |
Sebastien Bourdeauducq | df335865b7 | |
Sebastien Bourdeauducq | 0ff950128c | |
Sebastien Bourdeauducq | 44d95973ca | |
Sebastien Bourdeauducq | c676102b33 | |
Sebastien Bourdeauducq | 1c516ca357 | |
Sébastien Bourdeauducq | 3cbbd124b3 | |
Sebastien Bourdeauducq | fd507cb6fb | |
Sebastien Bourdeauducq | 8975f8c240 | |
Sebastien Bourdeauducq | 9662570999 | |
Sebastien Bourdeauducq | 0d0c09b074 | |
Sebastien Bourdeauducq | f993e65fca | |
Sebastien Bourdeauducq | caec6f1a3b | |
Sebastien Bourdeauducq | c7f4dba53a | |
Sebastien Bourdeauducq | e4f513d444 | |
whitequark | 8a49dfc980 | |
Sebastien Bourdeauducq | 9fdce3ac4c | |
Sebastien Bourdeauducq | 252b8eeb28 | |
Sebastien Bourdeauducq | ecadb7c996 | |
whitequark | b07cd31572 | |
Sebastien Bourdeauducq | 592aee4735 | |
whitequark | ba37d13dae | |
whitequark | 3b7eb8bd9a | |
whitequark | 0ba8971aff | |
whitequark | 8353ffbe98 | |
whitequark | d6bfba821e | |
Sebastien Bourdeauducq | 15c9bafb51 | |
Sebastien Bourdeauducq | 0cb8d0c53d | |
Sebastien Bourdeauducq | a39c95e276 | |
Sebastien Bourdeauducq | 8abeff05a1 | |
Sebastien Bourdeauducq | 1069944ea2 | |
Sebastien Bourdeauducq | 4da283648b | |
Sebastien Bourdeauducq | 63c65448b2 | |
Sebastien Bourdeauducq | 3393d9e3be | |
whitequark | 5431780700 | |
whitequark | afda70c2a0 | |
whitequark | 5a011ea410 | |
whitequark | 87e97c4894 | |
whitequark | e17658c8db | |
whitequark | 8fa7ba9a8d | |
Sebastien Bourdeauducq | 3bbccdce58 | |
Sebastien Bourdeauducq | 14b4d0a496 | |
Sebastien Bourdeauducq | 67bee0ec06 | |
Sebastien Bourdeauducq | 64b20aa335 | |
whitequark | 4274d5e69b | |
Sebastien Bourdeauducq | 070152c82f | |
Sebastien Bourdeauducq | 2bb6be415d | |
Sebastien Bourdeauducq | d458a337df | |
Sebastien Bourdeauducq | 492b1e3668 | |
Sebastien Bourdeauducq | a214a1c6e0 | |
Sebastien Bourdeauducq | 6af043a7ba | |
Sebastien Bourdeauducq | 2f2c53b28e |
|
@ -0,0 +1,2 @@
|
|||
firmware/target/
|
||||
**/*.rs.bk
|
37
README.md
37
README.md
|
@ -1,37 +0,0 @@
|
|||
Ionpak
|
||||
======
|
||||
|
||||
A modern, low-cost universal controller for ionization vacuum gauges.
|
||||
|
||||
![Prototype picture](https://raw.githubusercontent.com/m-labs/ionpak/master/proto_rev1_resized.jpg)
|
||||
|
||||
Flyback transformer construction
|
||||
--------------------------------
|
||||
|
||||
TR300
|
||||
*****
|
||||
|
||||
Use EPCOS coilformer B66208X1010T1. Wind 5 turns on the primary and spread them across the length of the coilformer - it is important that the air gap between the cores is covered by windings. Wind 70 turns on the secondary in multiple layers. As with all flyback transformers, the polarity of the windings is critical. Assemble with EPCOS cores B66317G500X127 and B66317GX127 (one half gapped core, one half ungapped core), and corresponding clips.
|
||||
|
||||
TR350
|
||||
*****
|
||||
|
||||
Use EPCOS coilformer B66206W1110T1 and cores B66311G250X127 and B66311GX127. Both the primary and the secondary have 5 turns and must be wound together, interleaving the windings. The same remarks as for TR300 apply.
|
||||
|
||||
Errata
|
||||
------
|
||||
|
||||
PCB rev 1
|
||||
*********
|
||||
|
||||
* R307 needs more clearance from D400
|
||||
* Pins 1 and 12 of U502 need pull-downs
|
||||
* Pin 1 of U501 needs pull-up
|
||||
* D203 reversed polarity
|
||||
* R236 and R234 are swapped
|
||||
* Q301 needs to be NPN, change to BC817
|
||||
* increase R307 -> 3.3Kohm and increase R300 -> 33Kohm
|
||||
* C201: oscillates at 0 and 1nF, stable at 100nF
|
||||
* add clamp diodes to GND on op-amp outputs to ADC when op-amp has negative supply
|
||||
* R214 -> 4.7k
|
||||
* LM339PT is in TSSOP package. Change for SOIC P/N
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,62 @@
|
|||
{ # Use master branch of the overlay by default
|
||||
mozillaOverlay ? import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz),
|
||||
rustManifest ? ./channel-rust-nightly.toml,
|
||||
}:
|
||||
|
||||
let
|
||||
pkgs = import <nixpkgs> { overlays = [ mozillaOverlay ]; };
|
||||
in
|
||||
with pkgs;
|
||||
let
|
||||
rustcSrc = pkgs.fetchgit {
|
||||
url = https://github.com/rust-lang/rust.git;
|
||||
# master of 2019-11-09
|
||||
rev = "ac162c6abe34cdf965afc0389f6cefa79653c63b";
|
||||
sha256 = "06c5gws1mrpr69z1gzs358zf7hcsg6ky8n4ha0vv2s9d9w93x1kj";
|
||||
fetchSubmodules = true;
|
||||
};
|
||||
target = "thumbv7em-none-eabihf";
|
||||
targets = [ target ];
|
||||
rustChannelOfTargets = _channel: _date: targets:
|
||||
(pkgs.lib.rustLib.fromManifestFile rustManifest {
|
||||
inherit (pkgs) stdenv fetchurl patchelf;
|
||||
}).rust.override { inherit targets; };
|
||||
rust =
|
||||
rustChannelOfTargets "nightly" null targets;
|
||||
rustPlatform = recurseIntoAttrs (makeRustPlatform {
|
||||
rustc = rust // { src = rustcSrc; };
|
||||
cargo = rust;
|
||||
});
|
||||
gcc = pkgsCross.armv7l-hf-multiplatform.buildPackages.gcc;
|
||||
xbuildRustPackage = attrs:
|
||||
let
|
||||
buildPkg = rustPlatform.buildRustPackage attrs;
|
||||
in
|
||||
buildPkg.overrideAttrs ({ name, nativeBuildInputs, ... }: {
|
||||
nativeBuildInputs =
|
||||
nativeBuildInputs ++ [ cargo-xbuild ];
|
||||
buildPhase = ''
|
||||
cargo xbuild --release --frozen
|
||||
'';
|
||||
XARGO_RUST_SRC = "${rustcSrc}/src";
|
||||
installPhase = ''
|
||||
mkdir $out
|
||||
cp target/${target}/release/${name} $out/${name}.elf
|
||||
'';
|
||||
});
|
||||
firmware = xbuildRustPackage {
|
||||
name = "firmware";
|
||||
src = ./firmware;
|
||||
cargoSha256 = "0lf8h5g8sas36cxzqy0p65qqivnihh4gn4mkc1k210xp7niaymc5";
|
||||
nativeBuildInputs = [
|
||||
gcc
|
||||
];
|
||||
"CC_${target}" = "${gcc}/bin/armv7l-unknown-linux-gnueabihf-gcc";
|
||||
RUST_COMPILER_RT_ROOT = "${rustcSrc}/src/llvm-project/compiler-rt";
|
||||
checkPhase = ''
|
||||
cargo test --target=${hostPlatform.config}
|
||||
'';
|
||||
};
|
||||
in {
|
||||
inherit pkgs rustPlatform rustcSrc gcc firmware;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
[target.thumbv7em-none-eabihf]
|
||||
runner = "arm-none-eabi-gdb"
|
||||
rustflags = [
|
||||
"-C", "link-arg=-Tlink.x",
|
||||
]
|
||||
|
||||
[build]
|
||||
target = "thumbv7em-none-eabihf"
|
|
@ -0,0 +1,421 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "aligned"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"as-slice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "as-slice"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bare-metal"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "build_const"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "compiler_builtins"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cortex-m"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aligned 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cortex-m-rt"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cortex-m-rt-macros 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cortex-m-rt-macros"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cortex-m-semihosting"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cortex-m 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-hal"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"nb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "firmware"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"compiler_builtins 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cortex-m 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cortex-m-rt 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cortex-m-semihosting 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"embedded-hal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lexical-core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smoltcp 0.5.0 (git+https://github.com/m-labs/smoltcp.git?rev=8eb01aca364aefe5f823d68d552d62c76c9be4a3)",
|
||||
"tm4c129x 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "managed"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "nb"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r0"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "smoltcp"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/m-labs/smoltcp.git?rev=8eb01aca364aefe5f823d68d552d62c76c9be4a3#8eb01aca364aefe5f823d68d552d62c76c9be4a3"
|
||||
dependencies = [
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tm4c129x"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cortex-m 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cortex-m-rt 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "vcell"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "volatile-register"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum aligned 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a316c7ea8e1e9ece54862c992def5a7ac14de9f5832b69d71760680efeeefa"
|
||||
"checksum as-slice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "293dac66b274fab06f95e7efb05ec439a6b70136081ea522d270bc351ae5bb27"
|
||||
"checksum bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
|
||||
"checksum bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a165d606cf084741d4ac3a28fb6e9b1eb0bd31f6cd999098cfddb0b2ab381dc0"
|
||||
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
"checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39"
|
||||
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
|
||||
"checksum cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)" = "aa87058dce70a3ff5621797f1506cb837edd02ac4c0ae642b4542dce802908b8"
|
||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
"checksum compiler_builtins 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d3c520d376a5b582ce93a7f881ba3fae1b72d9404aa9539af09d11e68b27e123"
|
||||
"checksum cortex-m 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "145da2fc379bbd378ed425e75e1748214add9bbd800d4d5b77abb54ca423dbca"
|
||||
"checksum cortex-m-rt 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "17805910e3ecf029bdbfcc42b7384d9e3d9e5626153fa810002c1ef9839338ac"
|
||||
"checksum cortex-m-rt-macros 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2a6dc359ebb215c4924bffacfe46a8f02ef80fe2071bba1635a2ded42b40f936"
|
||||
"checksum cortex-m-semihosting 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "113ef0ecffee2b62b58f9380f4469099b30e9f9cbee2804771b4203ba1762cfa"
|
||||
"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb"
|
||||
"checksum embedded-hal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ee4908a155094da7723c2d60d617b820061e3b4efcc3d9e293d206a5a76c170b"
|
||||
"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
|
||||
"checksum lexical-core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d7043aa5c05dd34fb73b47acb8c3708eac428de4545ea3682ed2f11293ebd890"
|
||||
"checksum libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
|
||||
"checksum managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6"
|
||||
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
|
||||
"checksum nb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b1411551beb3c11dedfb0a90a0fa256b47d28b9ec2cdff34c25a2fa59e45dbdc"
|
||||
"checksum nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c618b63422da4401283884e6668d39f819a106ef51f5f59b81add00075da35ca"
|
||||
"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
|
||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||
"checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f"
|
||||
"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"
|
||||
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
"checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421"
|
||||
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
"checksum smoltcp 0.5.0 (git+https://github.com/m-labs/smoltcp.git?rev=8eb01aca364aefe5f823d68d552d62c76c9be4a3)" = "<none>"
|
||||
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
|
||||
"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
|
||||
"checksum syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92"
|
||||
"checksum tm4c129x 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41ef3b3b339fa51167fcbe5fca426906bf9e7ac83333c3c69e94311a8586231d"
|
||||
"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
|
||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
"checksum vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c"
|
||||
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286"
|
||||
"checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e"
|
||||
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
@ -0,0 +1,38 @@
|
|||
[package]
|
||||
name = "firmware"
|
||||
version = "1.0.0"
|
||||
authors = ["whitequark <whitequark@whitequark.org>"]
|
||||
edition = "2018"
|
||||
|
||||
[build-dependencies]
|
||||
walkdir = "~2"
|
||||
|
||||
[dependencies]
|
||||
libm = "0.1.4"
|
||||
cortex-m = { version = "0.6", features = ["const-fn"] }
|
||||
cortex-m-rt = "0.6"
|
||||
crc = { version = "~1", default-features = false }
|
||||
tm4c129x = { version = "0.9", features = ["rt"] }
|
||||
embedded-hal = { version = "0.2", features = ["unproven"] }
|
||||
nb = "0.1"
|
||||
cortex-m-semihosting = "0.3"
|
||||
byteorder = { version = "1.3", default-features = false }
|
||||
bit_field = "0.10"
|
||||
bare-metal = "0.2"
|
||||
lexical-core = { version = "~0.6", default-features = false }
|
||||
nom = { version = "~5", default-features = false }
|
||||
|
||||
[dependencies.smoltcp]
|
||||
git = "https://github.com/m-labs/smoltcp.git"
|
||||
rev = "8eb01aca364aefe5f823d68d552d62c76c9be4a3"
|
||||
features = ["ethernet", "proto-ipv4", "socket-tcp"]
|
||||
default-features = false
|
||||
|
||||
[dependencies.compiler_builtins]
|
||||
version = "0.1"
|
||||
default-features = false
|
||||
features = ["mem", "no-lang-items", "c"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
debug = true
|
|
@ -0,0 +1,75 @@
|
|||
# Thermostat v1 prototype firmware
|
||||
|
||||
## Building
|
||||
|
||||
### On Debian-based systems
|
||||
|
||||
- install [rustup](https://rustup.rs/)
|
||||
|
||||
```shell
|
||||
apt install gcc gcc-arm-none-eabi git-core
|
||||
rustup toolchain install nightly
|
||||
rustup update
|
||||
rustup target add thumbv7em-none-eabihf --toolchain nightly
|
||||
rustup default nightly
|
||||
rustup component add rust-src
|
||||
cargo install cargo-xbuild
|
||||
git clone https://github.com/llvm/llvm-project.git
|
||||
export RUST_COMPILER_RT_ROOT=`pwd`/llvm-project/compiler-rt
|
||||
|
||||
cd firmware && cargo xbuild --release
|
||||
```
|
||||
|
||||
The built ELF file will be at `target/thumbv7em-none-eabihf/release/ionpak-firmware`
|
||||
|
||||
### Development build on NixOS
|
||||
|
||||
Requires NixOS 19.09 or later for cargo-xbuild.
|
||||
|
||||
```shell
|
||||
nix-shell --run "cd firmware && cargo xbuild --release"
|
||||
```
|
||||
|
||||
## Network
|
||||
|
||||
### Setup
|
||||
|
||||
Ethernet, IP: 192.168.1.26/24
|
||||
|
||||
Use telnet or netcat to connect to port 23/tcp (telnet)
|
||||
|
||||
### Reading ADC input
|
||||
|
||||
Set report mode to `once` to obtain the single next value. Report mode
|
||||
will turn itself off after the next reading.
|
||||
|
||||
Set report mode to `continuous` for a continuous stream of input data.
|
||||
|
||||
The scope of this setting is per TCP session.
|
||||
|
||||
|
||||
### Commands
|
||||
|
||||
| Syntax | Function |
|
||||
| --- | --- |
|
||||
| `report` | Show current input |
|
||||
| `report mode` | Show current report mode |
|
||||
| `report mode <off/on>` | Set report mode |
|
||||
| `pwm <0/1> max_i_pos <width> <total>` | Set PWM duty cycle for **max_i_pos** to *width / total* |
|
||||
| `pwm <0/1> max_i_neg <width> <total>` | Set PWM duty cycle for **max_i_neg** to *width / total* |
|
||||
| `pwm <0/1> max_v <width> <total>` | Set PWM duty cycle for **max_v** to *width / total* |
|
||||
| `pwm <0/1> <width> <total>` | Set PWM duty cycle for **i_set** to manual *width / total* |
|
||||
| `pwm <0/1> pid` | Set PWM to be controlled by PID |
|
||||
| `pid` | Show PID configuration |
|
||||
| `pid <0/1> target <value>` | Set the PID controller target |
|
||||
| `pid <0/1> kp <value>` | Set proportional gain |
|
||||
| `pid <0/1> ki <value>` | Set integral gain |
|
||||
| `pid <0/1> kd <value>` | Set differential gain |
|
||||
| `pid <0/1> output_min <value>` | Set mininum output |
|
||||
| `pid <0/1> output_max <value>` | Set maximum output |
|
||||
| `pid <0/1> integral_min <value>` | Set integral lower bound |
|
||||
| `pid <0/1> integral_max <value>` | Set integral upper bound |
|
||||
| `s-h` | Show Steinhart-Hart equation parameters |
|
||||
| `s-h <0/1> <a/b/c> <value>` | Set Steinhart-Hart parameter for a channel |
|
||||
| `s-h <0/1> parallel_r <value>` | Set parallel resistance of the ADC |
|
||||
| `postfilter <0/1> rate <rate>` | Set postfilter output data rate |
|
|
@ -0,0 +1,22 @@
|
|||
extern crate walkdir;
|
||||
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn linker_script() {
|
||||
// Put the linker script somewhere the linker can find it
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
linker_script();
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
MEMORY
|
||||
{
|
||||
FLASH : ORIGIN = 0x00000000, LENGTH = 512K
|
||||
RAM : ORIGIN = 0x20000000, LENGTH = 256K
|
||||
}
|
||||
|
||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|
@ -0,0 +1,9 @@
|
|||
source [find interface/stlink-v2.cfg]
|
||||
transport select hla_swd
|
||||
set CHIPNAME tm4c1294kcpd
|
||||
set CPUTAPID 0x2ba01477
|
||||
source [find target/stellaris.cfg]
|
||||
|
||||
program target/thumbv7em-none-eabihf/release/ionpak-firmware verify
|
||||
reset
|
||||
exit
|
|
@ -0,0 +1,201 @@
|
|||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal::blocking::spi::Transfer;
|
||||
use super::checksum::{ChecksumMode, Checksum};
|
||||
use super::AdcError;
|
||||
use super::{
|
||||
regs, regs::RegisterData,
|
||||
Input, RefSource, PostFilter, DigitalFilterOrder,
|
||||
};
|
||||
|
||||
/// AD7172-2 implementation
|
||||
///
|
||||
/// [Manual](https://www.analog.com/media/en/technical-documentation/data-sheets/AD7172-2.pdf)
|
||||
pub struct Adc<SPI: Transfer<u8>, NSS: OutputPin> {
|
||||
spi: SPI,
|
||||
nss: NSS,
|
||||
checksum_mode: ChecksumMode,
|
||||
}
|
||||
|
||||
impl<SPI: Transfer<u8>, NSS: OutputPin> Adc<SPI, NSS> {
|
||||
pub fn new(spi: SPI, mut nss: NSS) -> Result<Self, SPI::Error> {
|
||||
let _ = nss.set_high();
|
||||
let mut adc = Adc {
|
||||
spi, nss,
|
||||
checksum_mode: ChecksumMode::Off,
|
||||
};
|
||||
adc.reset()?;
|
||||
|
||||
Ok(adc)
|
||||
}
|
||||
|
||||
/// `0x00DX` for AD7172-2
|
||||
pub fn identify(&mut self) -> Result<u16, AdcError<SPI::Error>> {
|
||||
self.read_reg(®s::Id)
|
||||
.map(|id| id.id())
|
||||
}
|
||||
|
||||
pub fn set_checksum_mode(&mut self, mode: ChecksumMode) -> Result<(), AdcError<SPI::Error>> {
|
||||
// Cannot use update_reg() here because checksum_mode is
|
||||
// updated between read_reg() and write_reg().
|
||||
let mut ifmode = self.read_reg(®s::IfMode)?;
|
||||
ifmode.set_crc(mode);
|
||||
self.checksum_mode = mode;
|
||||
self.write_reg(®s::IfMode, &mut ifmode)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_sync_enable(&mut self, enable: bool) -> Result<(), AdcError<SPI::Error>> {
|
||||
self.update_reg(®s::GpioCon, |data| {
|
||||
data.set_sync_en(enable);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn setup_channel(
|
||||
&mut self, index: u8, in_pos: Input, in_neg: Input
|
||||
) -> Result<(), AdcError<SPI::Error>> {
|
||||
self.update_reg(®s::SetupCon { index }, |data| {
|
||||
data.set_bipolar(false);
|
||||
data.set_refbuf_pos(true);
|
||||
data.set_refbuf_neg(true);
|
||||
data.set_ainbuf_pos(true);
|
||||
data.set_ainbuf_neg(true);
|
||||
data.set_ref_sel(RefSource::External);
|
||||
})?;
|
||||
self.update_reg(®s::FiltCon { index }, |data| {
|
||||
data.set_enh_filt_en(true);
|
||||
data.set_enh_filt(PostFilter::F16SPS);
|
||||
data.set_order(DigitalFilterOrder::Sinc5Sinc1);
|
||||
})?;
|
||||
// let mut offset = <regs::Offset as regs::Register>::Data::empty();
|
||||
// offset.set_offset(0);
|
||||
// self.write_reg(®s::Offset { index }, &mut offset);
|
||||
self.update_reg(®s::Channel { index }, |data| {
|
||||
data.set_setup(index);
|
||||
data.set_enabled(true);
|
||||
data.set_a_in_pos(in_pos);
|
||||
data.set_a_in_neg(in_neg);
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_postfilter(&mut self, index: u8) -> Result<Option<PostFilter>, AdcError<SPI::Error>> {
|
||||
self.read_reg(®s::FiltCon { index })
|
||||
.map(|data| {
|
||||
if data.enh_filt_en() {
|
||||
Some(data.enh_filt())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_postfilter(&mut self, index: u8, filter: Option<PostFilter>) -> Result<(), AdcError<SPI::Error>> {
|
||||
self.update_reg(®s::FiltCon { index }, |data| {
|
||||
match filter {
|
||||
None => data.set_enh_filt_en(false),
|
||||
Some(filter) => {
|
||||
data.set_enh_filt_en(true);
|
||||
data.set_enh_filt(filter);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the channel the data is from
|
||||
pub fn data_ready(&mut self) -> Result<Option<u8>, AdcError<SPI::Error>> {
|
||||
self.read_reg(®s::Status)
|
||||
.map(|status| {
|
||||
if status.ready() {
|
||||
Some(status.channel())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get data
|
||||
pub fn read_data(&mut self) -> Result<i32, AdcError<SPI::Error>> {
|
||||
self.read_reg(®s::Data)
|
||||
.map(|data| data.data())
|
||||
}
|
||||
|
||||
fn read_reg<R: regs::Register>(&mut self, reg: &R) -> Result<R::Data, AdcError<SPI::Error>> {
|
||||
let mut reg_data = R::Data::empty();
|
||||
let address = 0x40 | reg.address();
|
||||
let mut checksum = Checksum::new(self.checksum_mode);
|
||||
checksum.feed(address);
|
||||
let checksum_out = checksum.result();
|
||||
let checksum_in = self.transfer(address, reg_data.as_mut(), checksum_out)?;
|
||||
for &mut b in reg_data.as_mut() {
|
||||
checksum.feed(b);
|
||||
}
|
||||
let checksum_expected = checksum.result();
|
||||
if checksum_expected != checksum_in {
|
||||
return Err(AdcError::ChecksumMismatch(checksum_expected, checksum_in));
|
||||
}
|
||||
Ok(reg_data)
|
||||
}
|
||||
|
||||
fn write_reg<R: regs::Register>(&mut self, reg: &R, reg_data: &mut R::Data) -> Result<(), AdcError<SPI::Error>> {
|
||||
let address = reg.address();
|
||||
let mut checksum = Checksum::new(match self.checksum_mode {
|
||||
ChecksumMode::Off => ChecksumMode::Off,
|
||||
// write checksums are always crc
|
||||
ChecksumMode::Xor => ChecksumMode::Crc,
|
||||
ChecksumMode::Crc => ChecksumMode::Crc,
|
||||
});
|
||||
checksum.feed(address);
|
||||
for &mut b in reg_data.as_mut() {
|
||||
checksum.feed(b);
|
||||
}
|
||||
let checksum_out = checksum.result();
|
||||
self.transfer(address, reg_data.as_mut(), checksum_out)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_reg<R, F, A>(&mut self, reg: &R, f: F) -> Result<A, AdcError<SPI::Error>>
|
||||
where
|
||||
R: regs::Register,
|
||||
F: FnOnce(&mut R::Data) -> A,
|
||||
{
|
||||
let mut reg_data = self.read_reg(reg)?;
|
||||
let result = f(&mut reg_data);
|
||||
self.write_reg(reg, &mut reg_data)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) -> Result<(), SPI::Error> {
|
||||
let mut buf = [0xFFu8; 8];
|
||||
let _ = self.nss.set_low();
|
||||
let result = self.spi.transfer(&mut buf);
|
||||
let _ = self.nss.set_high();
|
||||
result?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer<'w>(&mut self, addr: u8, reg_data: &'w mut [u8], checksum: Option<u8>) -> Result<Option<u8>, SPI::Error> {
|
||||
let mut addr_buf = [addr];
|
||||
|
||||
let _ = self.nss.set_low();
|
||||
let result = match self.spi.transfer(&mut addr_buf) {
|
||||
Ok(_) => self.spi.transfer(reg_data),
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
let result = match (result, checksum) {
|
||||
(Ok(_),None) =>
|
||||
Ok(None),
|
||||
(Ok(_), Some(checksum_out)) => {
|
||||
let mut checksum_buf = [checksum_out; 1];
|
||||
match self.spi.transfer(&mut checksum_buf) {
|
||||
Ok(_) => Ok(Some(checksum_buf[0])),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
(Err(e), _) =>
|
||||
Err(e),
|
||||
};
|
||||
let _ = self.nss.set_high();
|
||||
|
||||
result
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum ChecksumMode {
|
||||
Off = 0b00,
|
||||
/// Seems much less reliable than `Crc`
|
||||
Xor = 0b01,
|
||||
Crc = 0b10,
|
||||
}
|
||||
|
||||
impl From<u8> for ChecksumMode {
|
||||
fn from(x: u8) -> Self {
|
||||
match x {
|
||||
0 => ChecksumMode::Off,
|
||||
1 => ChecksumMode::Xor,
|
||||
_ => ChecksumMode::Crc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Checksum {
|
||||
mode: ChecksumMode,
|
||||
state: u8,
|
||||
}
|
||||
|
||||
impl Checksum {
|
||||
pub fn new(mode: ChecksumMode) -> Self {
|
||||
Checksum { mode, state: 0 }
|
||||
}
|
||||
|
||||
pub fn feed(&mut self, input: u8) {
|
||||
match self.mode {
|
||||
ChecksumMode::Off => {},
|
||||
ChecksumMode::Xor => self.state ^= input,
|
||||
ChecksumMode::Crc => {
|
||||
for i in 0..8 {
|
||||
let input_mask = 0x80 >> i;
|
||||
self.state = (self.state << 1) ^
|
||||
if ((self.state & 0x80) != 0) != ((input & input_mask) != 0) {
|
||||
0x07 /* x8 + x2 + x + 1 */
|
||||
} else {
|
||||
0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn result(&self) -> Option<u8> {
|
||||
match self.mode {
|
||||
ChecksumMode::Off => None,
|
||||
_ => Some(self.state)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
use core::fmt;
|
||||
|
||||
pub mod regs;
|
||||
mod checksum;
|
||||
pub use checksum::ChecksumMode;
|
||||
mod adc;
|
||||
pub use adc::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum AdcError<SPI> {
|
||||
SPI(SPI),
|
||||
ChecksumMismatch(Option<u8>, Option<u8>),
|
||||
}
|
||||
|
||||
impl<SPI> From<SPI> for AdcError<SPI> {
|
||||
fn from(e: SPI) -> Self {
|
||||
AdcError::SPI(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum Input {
|
||||
Ain0 = 0,
|
||||
Ain1 = 1,
|
||||
Ain2 = 2,
|
||||
Ain3 = 3,
|
||||
Ain4 = 4,
|
||||
TemperaturePos = 17,
|
||||
TemperatureNeg = 18,
|
||||
AnalogSupplyPos = 19,
|
||||
AnalogSupplyNeg = 20,
|
||||
RefPos = 21,
|
||||
RefNeg = 22,
|
||||
Invalid = 0b11111,
|
||||
}
|
||||
|
||||
impl From<u8> for Input {
|
||||
fn from(x: u8) -> Self {
|
||||
match x {
|
||||
0 => Input::Ain0,
|
||||
1 => Input::Ain1,
|
||||
2 => Input::Ain2,
|
||||
3 => Input::Ain3,
|
||||
4 => Input::Ain4,
|
||||
17 => Input::TemperaturePos,
|
||||
18 => Input::TemperatureNeg,
|
||||
19 => Input::AnalogSupplyPos,
|
||||
20 => Input::AnalogSupplyNeg,
|
||||
21 => Input::RefPos,
|
||||
22 => Input::RefNeg,
|
||||
_ => Input::Invalid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Input {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
use Input::*;
|
||||
|
||||
match self {
|
||||
Ain0 => "ain0",
|
||||
Ain1 => "ain1",
|
||||
Ain2 => "ain2",
|
||||
Ain3 => "ain3",
|
||||
Ain4 => "ain4",
|
||||
TemperaturePos => "temperature+",
|
||||
TemperatureNeg => "temperature-",
|
||||
AnalogSupplyPos => "analogsupply+",
|
||||
AnalogSupplyNeg => "analogsupply-",
|
||||
RefPos => "ref+",
|
||||
RefNeg => "ref-",
|
||||
_ => "<INVALID>",
|
||||
}.fmt(fmt)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference source for ADC conversion
|
||||
#[repr(u8)]
|
||||
pub enum RefSource {
|
||||
/// External reference
|
||||
External = 0b00,
|
||||
/// Internal 2.5V reference
|
||||
Internal = 0b10,
|
||||
/// AVDD1 − AVSS
|
||||
Avdd1MinusAvss = 0b11,
|
||||
Invalid = 0b01,
|
||||
}
|
||||
|
||||
impl From<u8> for RefSource {
|
||||
fn from(x: u8) -> Self {
|
||||
match x {
|
||||
0 => RefSource::External,
|
||||
1 => RefSource::Internal,
|
||||
2 => RefSource::Avdd1MinusAvss,
|
||||
_ => RefSource::Invalid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RefSource {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
use RefSource::*;
|
||||
|
||||
match self {
|
||||
External => "external",
|
||||
Internal => "internal",
|
||||
Avdd1MinusAvss => "avdd1-avss",
|
||||
_ => "<INVALID>",
|
||||
}.fmt(fmt)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum PostFilter {
|
||||
/// 27 SPS, 47 dB rejection, 36.7 ms settling
|
||||
F27SPS = 0b010,
|
||||
/// 21.25 SPS, 62 dB rejection, 40 ms settling
|
||||
F21SPS = 0b011,
|
||||
/// 20 SPS, 86 dB rejection, 50 ms settling
|
||||
F20SPS = 0b101,
|
||||
/// 16.67 SPS, 92 dB rejection, 60 ms settling
|
||||
F16SPS = 0b110,
|
||||
Invalid = 0b111,
|
||||
}
|
||||
|
||||
impl PostFilter {
|
||||
pub const VALID_VALUES: &'static [Self] = &[
|
||||
PostFilter::F27SPS,
|
||||
PostFilter::F21SPS,
|
||||
PostFilter::F20SPS,
|
||||
PostFilter::F16SPS,
|
||||
];
|
||||
|
||||
pub fn closest(rate: f32) -> Option<Self> {
|
||||
/// (x - y).abs()
|
||||
fn d(x: f32, y: f32) -> f32 {
|
||||
if x >= y {
|
||||
x - y
|
||||
} else {
|
||||
y - x
|
||||
}
|
||||
}
|
||||
|
||||
let mut best: Option<(f32, Self)> = None;
|
||||
for value in Self::VALID_VALUES {
|
||||
let error = d(rate, value.output_rate().unwrap());
|
||||
let better = best
|
||||
.map(|(best_error, _)| error < best_error)
|
||||
.unwrap_or(true);
|
||||
if better {
|
||||
best = Some((error, *value));
|
||||
}
|
||||
}
|
||||
best.map(|(_, best)| best)
|
||||
}
|
||||
|
||||
/// Samples per Second
|
||||
pub fn output_rate(&self) -> Option<f32> {
|
||||
match self {
|
||||
PostFilter::F27SPS => Some(27.0),
|
||||
PostFilter::F21SPS => Some(21.25),
|
||||
PostFilter::F20SPS => Some(20.0),
|
||||
PostFilter::F16SPS => Some(16.67),
|
||||
PostFilter::Invalid => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for PostFilter {
|
||||
fn from(x: u8) -> Self {
|
||||
match x {
|
||||
0b010 => PostFilter::F27SPS,
|
||||
0b011 => PostFilter::F21SPS,
|
||||
0b101 => PostFilter::F20SPS,
|
||||
0b110 => PostFilter::F16SPS,
|
||||
_ => PostFilter::Invalid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum DigitalFilterOrder {
|
||||
Sinc5Sinc1 = 0b00,
|
||||
Sinc3 = 0b11,
|
||||
Invalid = 0b10,
|
||||
}
|
||||
|
||||
impl From<u8> for DigitalFilterOrder {
|
||||
fn from(x: u8) -> Self {
|
||||
match x {
|
||||
0b00 => DigitalFilterOrder::Sinc5Sinc1,
|
||||
0b11 => DigitalFilterOrder::Sinc3,
|
||||
_ => DigitalFilterOrder::Invalid,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
use byteorder::{BigEndian, ByteOrder};
|
||||
use bit_field::BitField;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub trait Register {
|
||||
type Data: RegisterData;
|
||||
fn address(&self) -> u8;
|
||||
}
|
||||
pub trait RegisterData {
|
||||
fn empty() -> Self;
|
||||
fn as_mut(&mut self) -> &mut [u8];
|
||||
}
|
||||
|
||||
macro_rules! def_reg {
|
||||
($Reg: ident, $reg: ident, $addr: expr, $size: expr) => {
|
||||
/// AD7172 register
|
||||
pub struct $Reg;
|
||||
impl Register for $Reg {
|
||||
/// Register contents
|
||||
type Data = $reg::Data;
|
||||
/// Register address
|
||||
fn address(&self) -> u8 {
|
||||
$addr
|
||||
}
|
||||
}
|
||||
mod $reg {
|
||||
/// Register contents
|
||||
pub struct Data(pub [u8; $size]);
|
||||
impl super::RegisterData for Data {
|
||||
/// Generate zeroed register contents
|
||||
fn empty() -> Self {
|
||||
Data([0; $size])
|
||||
}
|
||||
/// Borrow for SPI transfer
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
($Reg: ident, u8, $reg: ident, $addr: expr, $size: expr) => {
|
||||
pub struct $Reg { pub index: u8, }
|
||||
impl Register for $Reg {
|
||||
type Data = $reg::Data;
|
||||
fn address(&self) -> u8 {
|
||||
$addr + self.index
|
||||
}
|
||||
}
|
||||
mod $reg {
|
||||
pub struct Data(pub [u8; $size]);
|
||||
impl super::RegisterData for Data {
|
||||
fn empty() -> Self {
|
||||
Data([0; $size])
|
||||
}
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! reg_bit {
|
||||
($getter: ident, $byte: expr, $bit: expr, $doc: expr) => {
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $getter(&self) -> bool {
|
||||
self.0[$byte].get_bit($bit)
|
||||
}
|
||||
};
|
||||
($getter: ident, $setter: ident, $byte: expr, $bit: expr, $doc: expr) => {
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $getter(&self) -> bool {
|
||||
self.0[$byte].get_bit($bit)
|
||||
}
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $setter(&mut self, value: bool) {
|
||||
self.0[$byte].set_bit($bit, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! reg_bits {
|
||||
($getter: ident, $byte: expr, $bits: expr, $doc: expr) => {
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $getter(&self) -> u8 {
|
||||
self.0[$byte].get_bits($bits)
|
||||
}
|
||||
};
|
||||
($getter: ident, $setter: ident, $byte: expr, $bits: expr, $doc: expr) => {
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $getter(&self) -> u8 {
|
||||
self.0[$byte].get_bits($bits)
|
||||
}
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $setter(&mut self, value: u8) {
|
||||
self.0[$byte].set_bits($bits, value);
|
||||
}
|
||||
};
|
||||
($getter: ident, $byte: expr, $bits: expr, $ty: ty, $doc: expr) => {
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $getter(&self) -> $ty {
|
||||
self.0[$byte].get_bits($bits) as $ty
|
||||
}
|
||||
};
|
||||
($getter: ident, $setter: ident, $byte: expr, $bits: expr, $ty: ty, $doc: expr) => {
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $getter(&self) -> $ty {
|
||||
self.0[$byte].get_bits($bits).into()
|
||||
}
|
||||
#[allow(unused)]
|
||||
#[doc = $doc]
|
||||
pub fn $setter(&mut self, value: $ty) {
|
||||
self.0[$byte].set_bits($bits, value as u8);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
def_reg!(Status, status, 0x00, 1);
|
||||
impl status::Data {
|
||||
/// Is there new data to read?
|
||||
pub fn ready(&self) -> bool {
|
||||
! self.not_ready()
|
||||
}
|
||||
|
||||
reg_bit!(not_ready, 0, 7, "No data ready indicator");
|
||||
reg_bits!(channel, 0, 0..=1, "Channel for which data is ready");
|
||||
reg_bit!(adc_error, 0, 6, "ADC error");
|
||||
reg_bit!(crc_error, 0, 5, "SPI CRC error");
|
||||
reg_bit!(reg_error, 0,4, "Register error");
|
||||
}
|
||||
|
||||
def_reg!(IfMode, if_mode, 0x02, 2);
|
||||
impl if_mode::Data {
|
||||
reg_bits!(crc, set_crc, 1, 2..=3, ChecksumMode, "SPI checksum mode");
|
||||
}
|
||||
|
||||
def_reg!(Data, data, 0x04, 3);
|
||||
impl data::Data {
|
||||
pub fn data(&self) -> i32 {
|
||||
let raw =
|
||||
(u32::from(self.0[0]) << 16) |
|
||||
(u32::from(self.0[1]) << 8) |
|
||||
u32::from(self.0[2]);
|
||||
if raw & 0x80_0000 != 0 {
|
||||
((raw & 0x7F_FFFF) | 0x8000_0000) as i32
|
||||
} else {
|
||||
raw as i32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def_reg!(GpioCon, gpio_con, 0x06, 2);
|
||||
impl gpio_con::Data {
|
||||
reg_bit!(sync_en, set_sync_en, 0, 3, "Enables the SYNC/ERROR pin as a sync input");
|
||||
}
|
||||
|
||||
def_reg!(Id, id, 0x07, 2);
|
||||
impl id::Data {
|
||||
pub fn id(&self) -> u16 {
|
||||
BigEndian::read_u16(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
def_reg!(Channel, u8, channel, 0x10, 2);
|
||||
impl channel::Data {
|
||||
reg_bit!(enabled, set_enabled, 0, 7, "Channel enabled");
|
||||
reg_bits!(setup, set_setup, 0, 4..=5, "Setup number");
|
||||
|
||||
/// Which input is connected to positive input of this channel
|
||||
#[allow(unused)]
|
||||
pub fn a_in_pos(&self) -> Input {
|
||||
((self.0[0].get_bits(0..=1) << 3) |
|
||||
self.0[1].get_bits(5..=7)).into()
|
||||
}
|
||||
/// Set which input is connected to positive input of this channel
|
||||
#[allow(unused)]
|
||||
pub fn set_a_in_pos(&mut self, value: Input) {
|
||||
let value = value as u8;
|
||||
self.0[0].set_bits(0..=1, value >> 3);
|
||||
self.0[1].set_bits(5..=7, value & 0x7);
|
||||
}
|
||||
reg_bits!(a_in_neg, set_a_in_neg, 1, 0..=4, Input,
|
||||
"Which input is connected to negative input of this channel");
|
||||
|
||||
// const PROPS: &'static [Property<Self>] = &[
|
||||
// Property::named("enable")
|
||||
// .readable(&|self_: &Self| self_.enabled().into())
|
||||
// .writebale(&|self_: &mut Self, value| self_.set_enabled(value != 0)),
|
||||
// Property::named("setup")
|
||||
// .readable(&|self_: &Self| self_.0[0].get_bits(4..=5).into())
|
||||
// .writeable(&|self_: &mut Self, value| {
|
||||
// self_.0[0].set_bits(4..=5, value as u8);
|
||||
// }),
|
||||
// ];
|
||||
|
||||
// pub fn props() -> &'static [Property<Self>] {
|
||||
// Self::PROPS
|
||||
// }
|
||||
}
|
||||
|
||||
def_reg!(SetupCon, u8, setup_con, 0x20, 2);
|
||||
impl setup_con::Data {
|
||||
reg_bit!(bipolar, set_bipolar, 0, 4, "Unipolar (`false`) or bipolar (`true`) coded output");
|
||||
reg_bit!(refbuf_pos, set_refbuf_pos, 0, 3, "Enable REF+ input buffer");
|
||||
reg_bit!(refbuf_neg, set_refbuf_neg, 0, 2, "Enable REF- input buffer");
|
||||
reg_bit!(ainbuf_pos, set_ainbuf_pos, 0, 1, "Enable AIN+ input buffer");
|
||||
reg_bit!(ainbuf_neg, set_ainbuf_neg, 0, 0, "Enable AIN- input buffer");
|
||||
reg_bit!(burnout_en, 1, 7, "enables a 10 µA current source on the positive analog input selected and a 10 µA current sink on the negative analog input selected");
|
||||
reg_bits!(ref_sel, set_ref_sel, 1, 4..=5, RefSource, "Select reference source for conversion");
|
||||
}
|
||||
|
||||
def_reg!(FiltCon, u8, filt_con, 0x28, 2);
|
||||
impl filt_con::Data {
|
||||
reg_bit!(sinc3_map, 0, 7, "If set, mapping of filter register changes to directly program the decimation rate of the sinc3 filter");
|
||||
reg_bit!(enh_filt_en, set_enh_filt_en, 0, 3, "Enable postfilters for enhanced 50Hz and 60Hz rejection");
|
||||
reg_bits!(enh_filt, set_enh_filt, 0, 0..=2, PostFilter, "Select postfilters for enhanced 50Hz and 60Hz rejection");
|
||||
reg_bits!(order, set_order, 1, 5..=6, DigitalFilterOrder, "order of the digital filter that processes the modulator data");
|
||||
reg_bits!(odr, set_odr, 1, 0..=4, "Output data rate");
|
||||
}
|
||||
|
||||
def_reg!(Offset, u8, offset, 0x30, 3);
|
||||
impl offset::Data {
|
||||
#[allow(unused)]
|
||||
pub fn offset(&self) -> u32 {
|
||||
(u32::from(self.0[0]) << 16) |
|
||||
(u32::from(self.0[1]) << 8) |
|
||||
u32::from(self.0[2])
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn set_offset(&mut self, value: u32) {
|
||||
self.0[0] = (value >> 16) as u8;
|
||||
self.0[1] = (value >> 8) as u8;
|
||||
self.0[2] = value as u8;
|
||||
}
|
||||
}
|
||||
|
||||
def_reg!(Gain, u8, gain, 0x38, 3);
|
||||
impl gain::Data {
|
||||
#[allow(unused)]
|
||||
pub fn gain(&self) -> u32 {
|
||||
(u32::from(self.0[0]) << 16) |
|
||||
(u32::from(self.0[1]) << 8) |
|
||||
u32::from(self.0[2])
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn set_gain(&mut self, value: u32) {
|
||||
self.0[0] = (value >> 16) as u8;
|
||||
self.0[1] = (value >> 8) as u8;
|
||||
self.0[2] = value as u8;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
use core::slice::{from_raw_parts, from_raw_parts_mut};
|
||||
use embedded_hal::digital::v2::{InputPin, OutputPin};
|
||||
|
||||
pub trait Gpio where Self: Sized {
|
||||
fn into_output(self) -> GpioOutput<Self>;
|
||||
fn into_input(self) -> GpioInput<Self>;
|
||||
}
|
||||
|
||||
pub struct GpioInput<PIN>(PIN);
|
||||
pub struct GpioOutput<PIN>(PIN);
|
||||
|
||||
macro_rules! def_gpio {
|
||||
($PORT: tt, $PIN: tt, $idx: expr) => (
|
||||
impl $PIN {
|
||||
fn data(&self) -> &u32 {
|
||||
let gpio = tm4c129x::$PORT::ptr();
|
||||
let data = unsafe { from_raw_parts(gpio as *const _ as *mut u32, 0x100) };
|
||||
&data[(1 << $idx) as usize]
|
||||
}
|
||||
|
||||
fn data_mut(&mut self) -> &mut u32 {
|
||||
let gpio = tm4c129x::$PORT::ptr();
|
||||
let data = unsafe { from_raw_parts_mut(gpio as *const _ as *mut u32, 0x100) };
|
||||
&mut data[(1 << $idx) as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl Gpio for $PIN {
|
||||
fn into_output(self) -> GpioOutput<Self> {
|
||||
let gpio = unsafe { &*tm4c129x::$PORT::ptr() };
|
||||
gpio.dir.modify(|r, w| w.dir().bits(r.dir().bits() | (1 << $idx)));
|
||||
gpio.den.modify(|r, w| w.den().bits(r.den().bits() | (1 << $idx)));
|
||||
GpioOutput(self)
|
||||
}
|
||||
fn into_input(self) -> GpioInput<Self> {
|
||||
let gpio = unsafe { &*tm4c129x::$PORT::ptr() };
|
||||
gpio.dir.modify(|r, w| w.dir().bits(r.dir().bits() & !(1 << $idx)));
|
||||
gpio.den.modify(|r, w| w.den().bits(r.den().bits() | (1 << $idx)));
|
||||
GpioInput(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl InputPin for GpioInput<$PIN> {
|
||||
type Error = ();
|
||||
fn is_high(&self) -> Result<bool, Self::Error> {
|
||||
Ok(*self.0.data() != 0)
|
||||
}
|
||||
fn is_low(&self) -> Result<bool, Self::Error> {
|
||||
Ok(*self.0.data() == 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputPin for GpioOutput<$PIN> {
|
||||
type Error = ();
|
||||
fn set_low(&mut self) -> Result<(), Self::Error> {
|
||||
*self.0.data_mut() = 0;
|
||||
Ok(())
|
||||
}
|
||||
fn set_high(&mut self) -> Result<(), Self::Error> {
|
||||
*self.0.data_mut() = 0xFF;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub struct PB4;
|
||||
def_gpio!(GPIO_PORTB_AHB, PB4, 4);
|
||||
pub struct PB5;
|
||||
def_gpio!(GPIO_PORTB_AHB, PB5, 5);
|
||||
pub struct PE4;
|
||||
def_gpio!(GPIO_PORTE_AHB, PE4, 4);
|
||||
pub struct PE5;
|
||||
def_gpio!(GPIO_PORTE_AHB, PE5, 5);
|
||||
pub struct PK1;
|
||||
def_gpio!(GPIO_PORTK, PK1, 1);
|
||||
pub struct PP2;
|
||||
def_gpio!(GPIO_PORTP, PP2, 2);
|
||||
pub struct PP3;
|
||||
def_gpio!(GPIO_PORTP, PP3, 3);
|
||||
pub struct PQ4;
|
||||
def_gpio!(GPIO_PORTQ, PQ4, 4);
|
|
@ -0,0 +1,146 @@
|
|||
use cortex_m;
|
||||
use tm4c129x;
|
||||
|
||||
pub mod gpio;
|
||||
pub mod softspi;
|
||||
pub mod systick;
|
||||
pub mod pwm;
|
||||
|
||||
|
||||
const UART_DIV: u32 = (((/*sysclk*/120_000_000 * 8) / /*baud*/115200) + 1) / 2;
|
||||
|
||||
pub fn init() {
|
||||
cortex_m::interrupt::free(|cs| {
|
||||
let sysctl = unsafe { &*tm4c129x::SYSCTL::ptr() };
|
||||
|
||||
// Set up main oscillator
|
||||
sysctl.moscctl.write(|w| w.noxtal().bit(false));
|
||||
sysctl.moscctl.modify(|_, w| w.pwrdn().bit(false).oscrng().bit(true));
|
||||
|
||||
// Prepare flash for the high-freq clk
|
||||
sysctl.memtim0.write(|w| unsafe { w.bits(0x01950195u32) });
|
||||
sysctl.rsclkcfg.write(|w| unsafe { w.bits(0x80000000u32) });
|
||||
|
||||
// Set up PLL with fVCO=480 MHz
|
||||
sysctl.pllfreq1.write(|w| w.q().bits(0).n().bits(4));
|
||||
sysctl.pllfreq0.write(|w| w.mint().bits(96).pllpwr().bit(true));
|
||||
sysctl.rsclkcfg.modify(|_, w| w.pllsrc().mosc().newfreq().bit(true));
|
||||
while !sysctl.pllstat.read().lock().bit() {}
|
||||
|
||||
// Switch to PLL (sysclk=120MHz)
|
||||
sysctl.rsclkcfg.write(|w| unsafe { w.bits(0b1_0_0_1_0011_0000_0000000000_0000000011) });
|
||||
|
||||
// Bring up GPIO ports A, B, D, E, F, G, K, L, M, P, Q
|
||||
sysctl.rcgcgpio.modify(|_, w| {
|
||||
w.r0().bit(true)
|
||||
.r1().bit(true)
|
||||
.r3().bit(true)
|
||||
.r4().bit(true)
|
||||
.r5().bit(true)
|
||||
.r6().bit(true)
|
||||
.r9().bit(true)
|
||||
.r10().bit(true)
|
||||
.r11().bit(true)
|
||||
.r13().bit(true)
|
||||
.r14().bit(true)
|
||||
});
|
||||
while !sysctl.prgpio.read().r0().bit() {}
|
||||
while !sysctl.prgpio.read().r1().bit() {}
|
||||
while !sysctl.prgpio.read().r3().bit() {}
|
||||
while !sysctl.prgpio.read().r4().bit() {}
|
||||
while !sysctl.prgpio.read().r5().bit() {}
|
||||
while !sysctl.prgpio.read().r6().bit() {}
|
||||
while !sysctl.prgpio.read().r9().bit() {}
|
||||
while !sysctl.prgpio.read().r10().bit() {}
|
||||
while !sysctl.prgpio.read().r11().bit() {}
|
||||
while !sysctl.prgpio.read().r13().bit() {}
|
||||
while !sysctl.prgpio.read().r14().bit() {}
|
||||
|
||||
// Set up UART0
|
||||
let gpio_a = unsafe { &*tm4c129x::GPIO_PORTA_AHB::ptr() };
|
||||
gpio_a.dir.write(|w| w.dir().bits(0b11));
|
||||
gpio_a.den.write(|w| w.den().bits(0b11));
|
||||
gpio_a.afsel.write(|w| w.afsel().bits(0b11));
|
||||
gpio_a.pctl.write(|w| unsafe { w.pmc0().bits(1).pmc1().bits(1) });
|
||||
|
||||
sysctl.rcgcuart.modify(|_, w| w.r0().bit(true));
|
||||
while !sysctl.pruart.read().r0().bit() {}
|
||||
|
||||
let uart_0 = unsafe { &*tm4c129x::UART0::ptr() };
|
||||
uart_0.cc.write(|w| w.cs().sysclk());
|
||||
uart_0.ibrd.write(|w| w.divint().bits((UART_DIV / 64) as u16));
|
||||
uart_0.fbrd.write(|w| w.divfrac().bits((UART_DIV % 64) as u8));
|
||||
uart_0.lcrh.write(|w| w.wlen()._8().fen().bit(true));
|
||||
uart_0.ctl.write(|w| w.rxe().bit(true).txe().bit(true).uarten().bit(true));
|
||||
|
||||
// Set up PWMs
|
||||
let gpio_m = unsafe { &*tm4c129x::GPIO_PORTM::ptr() };
|
||||
// Output
|
||||
gpio_m.dir.write(|w| w.dir().bits(0xff));
|
||||
// Enable
|
||||
gpio_m.den.write(|w| w.den().bits(0xff));
|
||||
// Alternate function
|
||||
gpio_m.afsel.write(|w| w.afsel().bits(0xff));
|
||||
// Function: Timer PWM
|
||||
gpio_m.pctl.write(|w| unsafe {
|
||||
w
|
||||
// t2ccp0
|
||||
.pmc0().bits(3)
|
||||
// t2ccp1
|
||||
.pmc1().bits(3)
|
||||
// t3ccp0
|
||||
.pmc2().bits(3)
|
||||
// t3ccp1
|
||||
.pmc3().bits(3)
|
||||
// t4ccp0
|
||||
.pmc4().bits(3)
|
||||
// t4ccp1
|
||||
.pmc5().bits(3)
|
||||
// t5ccp0
|
||||
.pmc6().bits(3)
|
||||
// t5ccp1
|
||||
.pmc7().bits(3)
|
||||
});
|
||||
|
||||
// Enable timers
|
||||
sysctl.rcgctimer.write(|w| w
|
||||
.r2().set_bit()
|
||||
.r3().set_bit()
|
||||
.r4().set_bit()
|
||||
.r5().set_bit()
|
||||
);
|
||||
// Reset timers
|
||||
sysctl.srtimer.write(|w| w
|
||||
.r2().set_bit()
|
||||
.r3().set_bit()
|
||||
.r4().set_bit()
|
||||
.r5().set_bit()
|
||||
);
|
||||
sysctl.srtimer.write(|w| w
|
||||
.r2().clear_bit()
|
||||
.r3().clear_bit()
|
||||
.r4().clear_bit()
|
||||
.r5().clear_bit()
|
||||
);
|
||||
fn timers_ready(sysctl: &tm4c129x::sysctl::RegisterBlock) -> bool {
|
||||
let prtimer = sysctl.prtimer.read();
|
||||
prtimer.r2().bit() &&
|
||||
prtimer.r3().bit() &&
|
||||
prtimer.r4().bit() &&
|
||||
prtimer.r5().bit()
|
||||
}
|
||||
while !timers_ready(sysctl) {}
|
||||
|
||||
systick::init(cs);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_mac_address() -> [u8; 6] {
|
||||
let (userreg0, userreg1) = cortex_m::interrupt::free(|_cs| {
|
||||
let flashctl = unsafe { &*tm4c129x::FLASH_CTRL::ptr() };
|
||||
(flashctl.userreg0.read().bits(),
|
||||
flashctl.userreg1.read().bits())
|
||||
});
|
||||
[userreg0 as u8, (userreg0 >> 8) as u8, (userreg0 >> 16) as u8,
|
||||
userreg1 as u8, (userreg1 >> 8) as u8, (userreg1 >> 16) as u8]
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
use tm4c129x::{
|
||||
TIMER2, TIMER3, TIMER4, TIMER5,
|
||||
};
|
||||
|
||||
pub struct T2CCP0;
|
||||
pub struct T2CCP1;
|
||||
pub struct T3CCP0;
|
||||
pub struct T3CCP1;
|
||||
pub struct T4CCP0;
|
||||
pub struct T4CCP1;
|
||||
pub struct T5CCP0;
|
||||
pub struct T5CCP1;
|
||||
|
||||
pub trait PwmPeripheral {
|
||||
type ChannelA: PwmChannel;
|
||||
type ChannelB: PwmChannel;
|
||||
fn split() -> (Self::ChannelA, Self::ChannelB);
|
||||
}
|
||||
|
||||
macro_rules! pwm_peripheral {
|
||||
($TIMER: ty, $A: tt, $B: tt) => {
|
||||
impl PwmPeripheral for $TIMER {
|
||||
type ChannelA = $A;
|
||||
type ChannelB = $B;
|
||||
fn split() -> (Self::ChannelA, Self::ChannelB) {
|
||||
let regs = unsafe { &*Self::ptr() };
|
||||
regs.cfg.write(|w| unsafe { w.bits(4) });
|
||||
|
||||
let mut a = $A;
|
||||
a.configure();
|
||||
let mut b = $B;
|
||||
b.configure();
|
||||
(a, b)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pwm_peripheral!(TIMER2, T2CCP0, T2CCP1);
|
||||
pwm_peripheral!(TIMER3, T3CCP0, T3CCP1);
|
||||
pwm_peripheral!(TIMER4, T4CCP0, T4CCP1);
|
||||
pwm_peripheral!(TIMER5, T5CCP0, T5CCP1);
|
||||
|
||||
|
||||
pub trait PwmChannel {
|
||||
fn configure(&mut self);
|
||||
fn get(&mut self) -> (u16, u16);
|
||||
fn set(&mut self, width: u16, total: u16);
|
||||
}
|
||||
|
||||
macro_rules! pwm_channel_a {
|
||||
($CHANNEL: ty, $TIMER: tt) => {
|
||||
impl PwmChannel for $CHANNEL {
|
||||
fn configure(&mut self) {
|
||||
let timer = unsafe { &*tm4c129x::$TIMER::ptr() };
|
||||
timer.tamr.modify(|_, w| unsafe {
|
||||
w
|
||||
.taams().bit(true)
|
||||
.tacmr().bit(false)
|
||||
.tamr().bits(2)
|
||||
});
|
||||
timer.ctl.modify(|_, w| {
|
||||
w
|
||||
.tapwml().bit(false)
|
||||
});
|
||||
// no prescaler
|
||||
// no interrupts
|
||||
timer.tailr.write(|w| unsafe { w.bits(0xFFFF) });
|
||||
timer.tamatchr.write(|w| unsafe { w.bits(0x0) });
|
||||
timer.ctl.modify(|_, w| {
|
||||
w
|
||||
.taen().bit(true)
|
||||
});
|
||||
}
|
||||
|
||||
fn get(&mut self) -> (u16, u16) {
|
||||
let timer = unsafe { &*tm4c129x::$TIMER::ptr() };
|
||||
(timer.tamatchr.read().bits() as u16,
|
||||
timer.tailr.read().bits() as u16)
|
||||
}
|
||||
|
||||
fn set(&mut self, width: u16, total: u16) {
|
||||
let timer = unsafe { &*tm4c129x::$TIMER::ptr() };
|
||||
timer.tamatchr.write(|w| unsafe { w.bits(width.into()) });
|
||||
timer.tailr.write(|w| unsafe { w.bits(total.into()) });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! pwm_channel_b {
|
||||
($CHANNEL: ty, $TIMER: tt) => {
|
||||
impl PwmChannel for $CHANNEL {
|
||||
fn configure(&mut self) {
|
||||
let timer = unsafe { &*tm4c129x::$TIMER::ptr() };
|
||||
timer.tbmr.modify(|_, w| unsafe {
|
||||
w
|
||||
.tbams().bit(true)
|
||||
.tbcmr().bit(false)
|
||||
.tbmr().bits(2)
|
||||
});
|
||||
timer.ctl.modify(|_, w| {
|
||||
w
|
||||
.tbpwml().bit(false)
|
||||
});
|
||||
// no prescaler
|
||||
// no interrupts
|
||||
timer.tbilr.write(|w| unsafe { w.bits(0xFFFF) });
|
||||
timer.tbmatchr.write(|w| unsafe { w.bits(0x0) });
|
||||
timer.ctl.modify(|_, w| {
|
||||
w
|
||||
.tben().bit(true)
|
||||
});
|
||||
}
|
||||
|
||||
fn get(&mut self) -> (u16, u16) {
|
||||
let timer = unsafe { &*tm4c129x::$TIMER::ptr() };
|
||||
(timer.tbmatchr.read().bits() as u16,
|
||||
timer.tbilr.read().bits() as u16)
|
||||
}
|
||||
|
||||
fn set(&mut self, width: u16, total: u16) {
|
||||
let timer = unsafe { &*tm4c129x::$TIMER::ptr() };
|
||||
timer.tbmatchr.write(|w| unsafe { w.bits(width.into()) });
|
||||
timer.tbilr.write(|w| unsafe { w.bits(total.into()) });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pwm_channel_a!(T2CCP0, TIMER2);
|
||||
pwm_channel_b!(T2CCP1, TIMER2);
|
||||
pwm_channel_a!(T3CCP0, TIMER3);
|
||||
pwm_channel_b!(T3CCP1, TIMER3);
|
||||
pwm_channel_a!(T4CCP0, TIMER4);
|
||||
pwm_channel_b!(T4CCP1, TIMER4);
|
||||
pwm_channel_a!(T5CCP0, TIMER5);
|
||||
pwm_channel_b!(T5CCP1, TIMER5);
|
|
@ -0,0 +1,148 @@
|
|||
use embedded_hal::spi::FullDuplex;
|
||||
use embedded_hal::digital::v2::{InputPin, OutputPin};
|
||||
use embedded_hal::blocking::spi::Transfer;
|
||||
use nb::Error::WouldBlock;
|
||||
|
||||
/// Bit-banged Mode3 SPI
|
||||
pub struct SoftSpi<SCK, MOSI, MISO> {
|
||||
sck: SCK,
|
||||
mosi: MOSI,
|
||||
miso: MISO,
|
||||
state: State,
|
||||
input: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum State {
|
||||
Idle,
|
||||
Transfer {
|
||||
clock_phase: bool,
|
||||
mask: u8,
|
||||
output: u8,
|
||||
input: u8,
|
||||
},
|
||||
}
|
||||
|
||||
impl<SCK: OutputPin, MOSI: OutputPin, MISO: InputPin> SoftSpi<SCK, MOSI, MISO> {
|
||||
pub fn new(mut sck: SCK, mut mosi: MOSI, miso: MISO) -> Self {
|
||||
let _ = sck.set_high();
|
||||
let _ = mosi.set_low();
|
||||
SoftSpi {
|
||||
sck, mosi, miso,
|
||||
state: State::Idle,
|
||||
input: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Call this at twice the data rate
|
||||
pub fn tick(&mut self) {
|
||||
match self.state {
|
||||
State::Idle => {}
|
||||
State::Transfer { clock_phase: false,
|
||||
mask, output, input } => {
|
||||
if output & mask != 0 {
|
||||
let _ = self.mosi.set_high();
|
||||
} else {
|
||||
let _ = self.mosi.set_low();
|
||||
}
|
||||
let _ = self.sck.set_low();
|
||||
|
||||
self.state = State::Transfer {
|
||||
clock_phase: true,
|
||||
mask, output, input,
|
||||
};
|
||||
}
|
||||
State::Transfer { clock_phase: true,
|
||||
mask, output, mut input } => {
|
||||
if self.miso.is_high().unwrap_or(false) {
|
||||
input |= mask;
|
||||
}
|
||||
let _ = self.sck.set_high();
|
||||
|
||||
if mask != 1 {
|
||||
self.state = State::Transfer {
|
||||
clock_phase: false,
|
||||
mask: mask >> 1,
|
||||
output, input,
|
||||
};
|
||||
} else {
|
||||
self.input = Some(input);
|
||||
self.state = State::Idle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run<F: FnMut()>(&mut self, delay: &'_ mut F) {
|
||||
while self.state != State::Idle {
|
||||
self.tick();
|
||||
delay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SCK: OutputPin, MOSI: OutputPin, MISO: InputPin> FullDuplex<u8> for SoftSpi<SCK, MOSI, MISO> {
|
||||
type Error = ();
|
||||
|
||||
fn read(&mut self) -> Result<u8, nb::Error<Self::Error>> {
|
||||
match self.input.take() {
|
||||
Some(input) =>
|
||||
Ok(input),
|
||||
None if self.state == State::Idle =>
|
||||
Err(nb::Error::Other(())),
|
||||
None =>
|
||||
Err(WouldBlock),
|
||||
}
|
||||
}
|
||||
|
||||
fn send(&mut self, output: u8) -> Result<(), nb::Error<Self::Error>> {
|
||||
match self.state {
|
||||
State::Idle => {
|
||||
self.state = State::Transfer {
|
||||
clock_phase: false,
|
||||
mask: 0x80,
|
||||
output,
|
||||
input: 0,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(WouldBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SyncSoftSpi<'d, SCK: OutputPin, MOSI: OutputPin, MISO: InputPin, D: FnMut()> {
|
||||
spi: SoftSpi<SCK, MOSI, MISO>,
|
||||
delay: &'d mut D,
|
||||
}
|
||||
|
||||
impl<'d, SCK: OutputPin, MOSI: OutputPin, MISO: InputPin, D: FnMut()> SyncSoftSpi<'d, SCK, MOSI, MISO, D> {
|
||||
pub fn new(spi: SoftSpi<SCK, MOSI, MISO>, delay: &'d mut D) -> Self {
|
||||
SyncSoftSpi { spi, delay }
|
||||
}
|
||||
|
||||
fn retry<R, E, F>(&mut self, f: &F) -> Result<R, E>
|
||||
where
|
||||
F: Fn(&'_ mut SoftSpi<SCK, MOSI, MISO>) -> Result<R, nb::Error<E>>
|
||||
{
|
||||
loop {
|
||||
match f(&mut self.spi) {
|
||||
Ok(r) => return Ok(r),
|
||||
Err(nb::Error::Other(e)) => return Err(e),
|
||||
Err(WouldBlock) => self.spi.run(self.delay),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, SCK: OutputPin, MOSI: OutputPin, MISO: InputPin, D: FnMut()> Transfer<u8> for SyncSoftSpi<'d, SCK, MOSI, MISO, D> {
|
||||
// TODO: proper type
|
||||
type Error = ();
|
||||
fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> {
|
||||
for b in words.iter_mut() {
|
||||
self.retry(&|spi| spi.send(*b))?;
|
||||
*b = self.retry(&|spi| spi.read())?;
|
||||
}
|
||||
Ok(words)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
use core::cell::RefCell;
|
||||
use cortex_m::interrupt::Mutex;
|
||||
use cortex_m::peripheral::{SYST, syst::SystClkSource};
|
||||
use cortex_m_rt::exception;
|
||||
use bare_metal::CriticalSection;
|
||||
|
||||
static mut TIME: Mutex<RefCell<u64>> = Mutex::new(RefCell::new(0));
|
||||
/// In HZ
|
||||
const RATE: u32 = 10;
|
||||
/// Period between two interrupts in ns
|
||||
const INTERVAL: u64 = 1_000_000 / RATE as u64;
|
||||
|
||||
fn syst() -> &'static mut SYST {
|
||||
#[allow(mutable_transmutes)]
|
||||
unsafe { core::mem::transmute(&*SYST::ptr()) }
|
||||
}
|
||||
|
||||
pub fn init(_cs: &CriticalSection) {
|
||||
let syst = syst();
|
||||
// syst.set_clock_source(SystClkSource::Core);
|
||||
syst.set_clock_source(SystClkSource::External);
|
||||
syst.set_reload(100 * SYST::get_ticks_per_10ms() / RATE);
|
||||
syst.clear_current();
|
||||
syst.enable_interrupt();
|
||||
syst.enable_counter();
|
||||
}
|
||||
|
||||
#[exception]
|
||||
unsafe fn SysTick() {
|
||||
cortex_m::interrupt::free(|cs| {
|
||||
TIME.borrow(cs).replace_with(|time| *time + INTERVAL);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_time() -> u64 {
|
||||
let base = cortex_m::interrupt::free(|cs| {
|
||||
*unsafe { &mut TIME }.borrow(cs).borrow()
|
||||
});
|
||||
let syst_current = u64::from(SYST::get_current());
|
||||
let syst_reload = u64::from(SYST::get_reload());
|
||||
let precise = INTERVAL - (INTERVAL * syst_current / syst_reload);
|
||||
base + u64::from(precise)
|
||||
}
|
|
@ -0,0 +1,533 @@
|
|||
use core::fmt;
|
||||
use nom::{
|
||||
IResult,
|
||||
branch::alt,
|
||||
bytes::complete::{is_a, tag, take_while1},
|
||||
character::{is_digit, complete::{char, one_of}},
|
||||
combinator::{complete, map, opt, value},
|
||||
sequence::{preceded, separated_pair},
|
||||
multi::{fold_many0, fold_many1},
|
||||
error::ErrorKind,
|
||||
};
|
||||
use lexical_core as lexical;
|
||||
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
Parser(ErrorKind),
|
||||
Incomplete,
|
||||
UnexpectedInput(u8),
|
||||
ParseNumber(lexical::Error)
|
||||
}
|
||||
|
||||
impl<'t> From<nom::Err<(&'t [u8], ErrorKind)>> for Error {
|
||||
fn from(e: nom::Err<(&'t [u8], ErrorKind)>) -> Self {
|
||||
match e {
|
||||
nom::Err::Incomplete(_) =>
|
||||
Error::Incomplete,
|
||||
nom::Err::Error((_, e)) =>
|
||||
Error::Parser(e),
|
||||
nom::Err::Failure((_, e)) =>
|
||||
Error::Parser(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<lexical::Error> for Error {
|
||||
fn from(e: lexical::Error) -> Self {
|
||||
Error::ParseNumber(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
Error::Incomplete =>
|
||||
"incomplete input".fmt(fmt),
|
||||
Error::UnexpectedInput(c) => {
|
||||
"unexpected input: ".fmt(fmt)?;
|
||||
c.fmt(fmt)
|
||||
}
|
||||
Error::Parser(e) => {
|
||||
"parser: ".fmt(fmt)?;
|
||||
(e as &dyn core::fmt::Debug).fmt(fmt)
|
||||
}
|
||||
Error::ParseNumber(e) => {
|
||||
"parsing number: ".fmt(fmt)?;
|
||||
(e as &dyn core::fmt::Debug).fmt(fmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ShowCommand {
|
||||
Input,
|
||||
Reporting,
|
||||
Pwm,
|
||||
Pid,
|
||||
SteinhartHart,
|
||||
PostFilter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PidParameter {
|
||||
Target,
|
||||
KP,
|
||||
KI,
|
||||
KD,
|
||||
OutputMin,
|
||||
OutputMax,
|
||||
IntegralMin,
|
||||
IntegralMax,
|
||||
}
|
||||
|
||||
/// Steinhart-Hart equation parameter
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ShParameter {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
ParallelR,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PwmConfig {
|
||||
pub width: u16,
|
||||
pub total: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PwmMode {
|
||||
Manual(PwmConfig),
|
||||
Pid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PwmSetup {
|
||||
ISet(PwmMode),
|
||||
MaxIPos(PwmConfig),
|
||||
MaxINeg(PwmConfig),
|
||||
MaxV(PwmConfig),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Command {
|
||||
Quit,
|
||||
Show(ShowCommand),
|
||||
Reporting(bool),
|
||||
Pwm {
|
||||
channel: usize,
|
||||
setup: PwmSetup,
|
||||
},
|
||||
Pid {
|
||||
channel: usize,
|
||||
parameter: PidParameter,
|
||||
value: f32,
|
||||
},
|
||||
SteinhartHart {
|
||||
channel: usize,
|
||||
parameter: ShParameter,
|
||||
value: f32,
|
||||
},
|
||||
PostFilter {
|
||||
channel: usize,
|
||||
rate: f32,
|
||||
},
|
||||
}
|
||||
|
||||
fn end(input: &[u8]) -> IResult<&[u8], ()> {
|
||||
complete(
|
||||
fold_many0(
|
||||
one_of("\r\n\t "),
|
||||
(), |(), _| ()
|
||||
)
|
||||
)(input)
|
||||
}
|
||||
|
||||
fn whitespace(input: &[u8]) -> IResult<&[u8], ()> {
|
||||
fold_many1(char(' '), (), |(), _| ())(input)
|
||||
}
|
||||
|
||||
fn unsigned(input: &[u8]) -> IResult<&[u8], Result<u16, Error>> {
|
||||
take_while1(is_digit)(input)
|
||||
.map(|(input, digits)| {
|
||||
let result = lexical::parse(digits)
|
||||
.map_err(|e| e.into());
|
||||
(input, result)
|
||||
})
|
||||
}
|
||||
|
||||
fn float(input: &[u8]) -> IResult<&[u8], Result<f32, Error>> {
|
||||
let (input, sign) = opt(is_a("-"))(input)?;
|
||||
let negative = sign.is_some();
|
||||
let (input, digits) = take_while1(|c| is_digit(c) || c == '.' as u8)(input)?;
|
||||
let result = lexical::parse(digits)
|
||||
.map(|result: f32| if negative { -result } else { result })
|
||||
.map_err(|e| e.into());
|
||||
Ok((input, result))
|
||||
}
|
||||
|
||||
fn off_on(input: &[u8]) -> IResult<&[u8], bool> {
|
||||
alt((value(false, tag("off")),
|
||||
value(true, tag("on"))
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn channel(input: &[u8]) -> IResult<&[u8], usize> {
|
||||
map(one_of("01"), |c| (c as usize) - ('0' as usize))(input)
|
||||
}
|
||||
|
||||
fn report(input: &[u8]) -> IResult<&[u8], Command> {
|
||||
preceded(
|
||||
tag("report"),
|
||||
alt((
|
||||
preceded(
|
||||
whitespace,
|
||||
preceded(
|
||||
tag("mode"),
|
||||
alt((
|
||||
preceded(
|
||||
whitespace,
|
||||
// `report mode <on | off>` - Switch repoting mode
|
||||
map(off_on, Command::Reporting)
|
||||
),
|
||||
// `report mode` - Show current reporting state
|
||||
value(Command::Show(ShowCommand::Reporting), end)
|
||||
))
|
||||
)),
|
||||
// `report` - Report once
|
||||
value(Command::Show(ShowCommand::Input), end)
|
||||
))
|
||||
)(input)
|
||||
}
|
||||
|
||||
/// `pwm ... <width> <total>` - Set pwm duty cycle
|
||||
fn pwm_config(input: &[u8]) -> IResult<&[u8], Result<PwmConfig, Error>> {
|
||||
let (input, width) = unsigned(input)?;
|
||||
let width = match width {
|
||||
Ok(width) => width,
|
||||
Err(e) => return Ok((input, Err(e.into()))),
|
||||
};
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, total) = unsigned(input)?;
|
||||
let total = match total {
|
||||
Ok(total) => total,
|
||||
Err(e) => return Ok((input, Err(e.into()))),
|
||||
};
|
||||
Ok((input, Ok(PwmConfig { width, total })))
|
||||
}
|
||||
|
||||
fn pwm_setup(input: &[u8]) -> IResult<&[u8], Result<PwmSetup, Error>> {
|
||||
alt((
|
||||
map(
|
||||
preceded(
|
||||
tag("max_i_pos"),
|
||||
preceded(
|
||||
whitespace,
|
||||
pwm_config
|
||||
)
|
||||
),
|
||||
|result| result.map(PwmSetup::MaxIPos)
|
||||
),
|
||||
map(
|
||||
preceded(
|
||||
tag("max_i_neg"),
|
||||
preceded(
|
||||
whitespace,
|
||||
pwm_config
|
||||
)
|
||||
),
|
||||
|result| result.map(PwmSetup::MaxINeg)
|
||||
),
|
||||
map(
|
||||
preceded(
|
||||
tag("max_v"),
|
||||
preceded(
|
||||
whitespace,
|
||||
pwm_config
|
||||
)
|
||||
),
|
||||
|result| result.map(PwmSetup::MaxV)
|
||||
),
|
||||
map(pwm_config, |result| result.map(|config| {
|
||||
PwmSetup::ISet(PwmMode::Manual(config))
|
||||
}))
|
||||
))(input)
|
||||
}
|
||||
|
||||
/// `pwm <0-1> pid` - Set PWM to be controlled by PID
|
||||
fn pwm_pid(input: &[u8]) -> IResult<&[u8], Result<PwmSetup, Error>> {
|
||||
value(Ok(PwmSetup::ISet(PwmMode::Pid)), tag("pid"))(input)
|
||||
}
|
||||
|
||||
fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||
let (input, _) = tag("pwm")(input)?;
|
||||
alt((
|
||||
preceded(
|
||||
whitespace,
|
||||
map(
|
||||
separated_pair(
|
||||
channel,
|
||||
whitespace,
|
||||
alt((
|
||||
pwm_pid,
|
||||
pwm_setup
|
||||
))
|
||||
),
|
||||
|(channel, setup)| setup.map(|setup| Command::Pwm { channel, setup })
|
||||
)
|
||||
),
|
||||
value(Ok(Command::Show(ShowCommand::Pwm)), end)
|
||||
))(input)
|
||||
}
|
||||
|
||||
/// `pid <0-1> <parameter> <value>`
|
||||
fn pid_parameter(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||
let (input, channel) = channel(input)?;
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, parameter) =
|
||||
alt((value(PidParameter::Target, tag("target")),
|
||||
value(PidParameter::KP, tag("kp")),
|
||||
value(PidParameter::KI, tag("ki")),
|
||||
value(PidParameter::KD, tag("kd")),
|
||||
value(PidParameter::OutputMin, tag("output_min")),
|
||||
value(PidParameter::OutputMax, tag("output_max")),
|
||||
value(PidParameter::IntegralMin, tag("integral_min")),
|
||||
value(PidParameter::IntegralMax, tag("integral_max"))
|
||||
))(input)?;
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, value) = float(input)?;
|
||||
let result = value
|
||||
.map(|value| Command::Pid { channel, parameter, value });
|
||||
Ok((input, result))
|
||||
}
|
||||
|
||||
/// `pid` | `pid <pid_parameter>`
|
||||
fn pid(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||
let (input, _) = tag("pid")(input)?;
|
||||
alt((
|
||||
preceded(
|
||||
whitespace,
|
||||
pid_parameter
|
||||
),
|
||||
value(Ok(Command::Show(ShowCommand::Pid)), end)
|
||||
))(input)
|
||||
}
|
||||
|
||||
/// `s-h <0-1> <parameter> <value>`
|
||||
fn steinhart_hart_parameter(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||
let (input, channel) = channel(input)?;
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, parameter) =
|
||||
alt((value(ShParameter::A, tag("a")),
|
||||
value(ShParameter::B, tag("b")),
|
||||
value(ShParameter::C, tag("c")),
|
||||
value(ShParameter::ParallelR, tag("parallel_r"))
|
||||
))(input)?;
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, value) = float(input)?;
|
||||
let result = value
|
||||
.map(|value| Command::SteinhartHart { channel, parameter, value });
|
||||
Ok((input, result))
|
||||
}
|
||||
|
||||
/// `s-h` | `s-h <steinhart_hart_parameter>`
|
||||
fn steinhart_hart(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||
let (input, _) = tag("s-h")(input)?;
|
||||
alt((
|
||||
preceded(
|
||||
whitespace,
|
||||
steinhart_hart_parameter
|
||||
),
|
||||
value(Ok(Command::Show(ShowCommand::SteinhartHart)), end)
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn postfilter(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||
let (input, _) = tag("postfilter")(input)?;
|
||||
alt((
|
||||
preceded(
|
||||
whitespace,
|
||||
|input| {
|
||||
let (input, channel) = channel(input)?;
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, _) = tag("rate")(input)?;
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, rate) = float(input)?;
|
||||
let result = rate
|
||||
.map(|rate| Command::PostFilter {
|
||||
channel, rate,
|
||||
});
|
||||
Ok((input, result))
|
||||
}
|
||||
),
|
||||
value(Ok(Command::Show(ShowCommand::PostFilter)), end)
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn command(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||
alt((value(Ok(Command::Quit), tag("quit")),
|
||||
map(report, Ok),
|
||||
pwm,
|
||||
pid,
|
||||
steinhart_hart,
|
||||
postfilter,
|
||||
))(input)
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn parse(input: &[u8]) -> Result<Self, Error> {
|
||||
match command(input) {
|
||||
Ok((b"", result)) =>
|
||||
result,
|
||||
Ok((input_remain, _)) =>
|
||||
Err(Error::UnexpectedInput(input_remain[0])),
|
||||
Err(e) =>
|
||||
Err(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_quit() {
|
||||
let command = Command::parse(b"quit");
|
||||
assert_eq!(command, Ok(Command::Quit));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_report() {
|
||||
let command = Command::parse(b"report");
|
||||
assert_eq!(command, Ok(Command::Show(ShowCommand::Input)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_report_mode() {
|
||||
let command = Command::parse(b"report mode");
|
||||
assert_eq!(command, Ok(Command::Show(ShowCommand::Reporting)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_report_mode_on() {
|
||||
let command = Command::parse(b"report mode on");
|
||||
assert_eq!(command, Ok(Command::Reporting(true)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_report_mode_off() {
|
||||
let command = Command::parse(b"report mode off");
|
||||
assert_eq!(command, Ok(Command::Reporting(false)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pwm_manual() {
|
||||
let command = Command::parse(b"pwm 1 16383 65535");
|
||||
assert_eq!(command, Ok(Command::Pwm {
|
||||
channel: 1,
|
||||
setup: PwmSetup::ISet(PwmMode::Manual(PwmConfig {
|
||||
width: 16383,
|
||||
total: 65535,
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pwm_pid() {
|
||||
let command = Command::parse(b"pwm 0 pid");
|
||||
assert_eq!(command, Ok(Command::Pwm {
|
||||
channel: 0,
|
||||
setup: PwmSetup::ISet(PwmMode::Pid),
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pwm_max_i_pos() {
|
||||
let command = Command::parse(b"pwm 0 max_i_pos 7 13");
|
||||
assert_eq!(command, Ok(Command::Pwm {
|
||||
channel: 0,
|
||||
setup: PwmSetup::MaxIPos(PwmConfig {
|
||||
width: 7,
|
||||
total: 13,
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pwm_max_i_neg() {
|
||||
let command = Command::parse(b"pwm 0 max_i_neg 128 65535");
|
||||
assert_eq!(command, Ok(Command::Pwm {
|
||||
channel: 0,
|
||||
setup: PwmSetup::MaxINeg(PwmConfig {
|
||||
width: 128,
|
||||
total: 65535,
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pwm_max_v() {
|
||||
let command = Command::parse(b"pwm 0 max_v 32768 65535");
|
||||
assert_eq!(command, Ok(Command::Pwm {
|
||||
channel: 0,
|
||||
setup: PwmSetup::MaxV(PwmConfig {
|
||||
width: 32768,
|
||||
total: 65535,
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pid() {
|
||||
let command = Command::parse(b"pid");
|
||||
assert_eq!(command, Ok(Command::Show(ShowCommand::Pid)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pid_target() {
|
||||
let command = Command::parse(b"pid 0 target 36.5");
|
||||
assert_eq!(command, Ok(Command::Pid {
|
||||
channel: 0,
|
||||
parameter: PidParameter::Target,
|
||||
value: 36.5,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pid_integral_max() {
|
||||
let command = Command::parse(b"pid 1 integral_max 2000");
|
||||
assert_eq!(command, Ok(Command::Pid {
|
||||
channel: 1,
|
||||
parameter: PidParameter::IntegralMax,
|
||||
value: 2000.0,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_steinhart_hart() {
|
||||
let command = Command::parse(b"s-h");
|
||||
assert_eq!(command, Ok(Command::Show(ShowCommand::SteinhartHart)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_steinhart_hart_parallel_r() {
|
||||
let command = Command::parse(b"s-h 1 parallel_r 23.05");
|
||||
assert_eq!(command, Ok(Command::SteinhartHart {
|
||||
channel: 1,
|
||||
parameter: ShParameter::ParallelR,
|
||||
value: 23.05,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_postfilter_rate() {
|
||||
let command = Command::parse(b"postfilter 0 rate 21");
|
||||
assert_eq!(command, Ok(Command::PostFilter {
|
||||
channel: 0,
|
||||
rate: 21.0,
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,447 @@
|
|||
use core::{slice, cmp};
|
||||
use cortex_m::{self, asm::delay};
|
||||
use tm4c129x;
|
||||
use smoltcp::Result;
|
||||
use smoltcp::time::Instant;
|
||||
use smoltcp::wire::EthernetAddress;
|
||||
use smoltcp::phy;
|
||||
|
||||
const EPHY_BMCR: u8 = 0x00; // Ethernet PHY Basic Mode Control
|
||||
#[allow(dead_code)]
|
||||
const EPHY_BMSR: u8 = 0x01; // Ethernet PHY Basic Mode Status
|
||||
const EPHY_ID1: u8 = 0x02; // Ethernet PHY Identifier Register 1
|
||||
const EPHY_ID2: u8 = 0x03; // Ethernet PHY Identifier Register 2
|
||||
|
||||
const EPHY_REGCTL: u8 = 0x0D; // Ethernet PHY Register Control
|
||||
const EPHY_ADDAR: u8 = 0x0E; // Ethernet PHY Address or Data
|
||||
|
||||
const EPHY_LEDCFG: u8 = 0x25; // Ethernet PHY LED Configuration
|
||||
|
||||
// Transmit DMA descriptor flags
|
||||
const EMAC_TDES0_OWN: u32 = 0x80000000; // Indicates that the descriptor is owned by the DMA
|
||||
const EMAC_TDES0_LS: u32 = 0x20000000; // Last Segment
|
||||
const EMAC_TDES0_FS: u32 = 0x10000000; // First Segment
|
||||
const EMAC_TDES0_TCH: u32 = 0x00100000; // Second Address Chained
|
||||
#[allow(dead_code)]
|
||||
const EMAC_TDES1_TBS1: u32 = 0x00001FFF; // Transmit Buffer 1 Size
|
||||
|
||||
// Receive DMA descriptor flags
|
||||
const EMAC_RDES0_OWN: u32 = 0x80000000; // indicates that the descriptor is owned by the DMA
|
||||
const EMAC_RDES0_FL: u32 = 0x3FFF0000; // Frame Length
|
||||
const EMAC_RDES0_ES: u32 = 0x00008000; // Error Summary
|
||||
const EMAC_RDES0_FS: u32 = 0x00000200; // First Descriptor
|
||||
const EMAC_RDES0_LS: u32 = 0x00000100; // Last Descriptor
|
||||
const EMAC_RDES1_RCH: u32 = 0x00004000; // Second Address Chained
|
||||
const EMAC_RDES1_RBS1: u32 = 0x00001FFF; // Receive Buffer 1 Size
|
||||
|
||||
const ETH_DESC_U32_SIZE: usize = 8;
|
||||
const ETH_TX_BUFFER_COUNT: usize = 2;
|
||||
const ETH_TX_BUFFER_SIZE: usize = 1536;
|
||||
const ETH_RX_BUFFER_COUNT: usize = 3;
|
||||
const ETH_RX_BUFFER_SIZE: usize = 1536;
|
||||
|
||||
fn phy_read(reg_addr: u8) -> u16 {
|
||||
cortex_m::interrupt::free(|_cs| {
|
||||
let emac0 = unsafe { &*tm4c129x::EMAC0::ptr() };
|
||||
|
||||
// Make sure the MII is idle
|
||||
while emac0.miiaddr.read().miib().bit() {};
|
||||
|
||||
// Tell the MAC to read the given PHY register
|
||||
unsafe {
|
||||
emac0.miiaddr.write(|w| {
|
||||
w.cr()._100_150()
|
||||
.mii().bits(reg_addr & 0x1F)
|
||||
.miib().bit(true)
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for the read to complete
|
||||
while emac0.miiaddr.read().miib().bit() {};
|
||||
|
||||
emac0.miidata.read().data().bits()
|
||||
})
|
||||
}
|
||||
|
||||
fn phy_write(reg_addr: u8, reg_data: u16) {
|
||||
cortex_m::interrupt::free(|_cs| {
|
||||
let emac0 = unsafe { &*tm4c129x::EMAC0::ptr() };
|
||||
|
||||
// Make sure the MII is idle
|
||||
while emac0.miiaddr.read().miib().bit() {};
|
||||
|
||||
unsafe {
|
||||
emac0.miidata.write(|w| {
|
||||
w.data().bits(reg_data)
|
||||
});
|
||||
|
||||
// Tell the MAC to write the given PHY register
|
||||
emac0.miiaddr.write(|w| {
|
||||
w.cr()._100_150()
|
||||
.mii().bits(reg_addr & 0x1F)
|
||||
.miiw().bit(true)
|
||||
.miib().bit(true)
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for the read to complete
|
||||
while emac0.miiaddr.read().miib().bit() {};
|
||||
})
|
||||
}
|
||||
|
||||
// Writes a value to an extended PHY register in MMD address space
|
||||
fn phy_write_ext(reg_addr: u8, reg_data: u16) {
|
||||
phy_write(EPHY_REGCTL, 0x001F); // set address (datasheet page 1612)
|
||||
phy_write(EPHY_ADDAR, reg_addr as u16);
|
||||
phy_write(EPHY_REGCTL, 0x401F); // set write mode
|
||||
phy_write(EPHY_ADDAR, reg_data);
|
||||
}
|
||||
|
||||
struct RxRing {
|
||||
desc_buf: [u32; ETH_RX_BUFFER_COUNT * ETH_DESC_U32_SIZE],
|
||||
cur_desc: usize,
|
||||
counter: u32,
|
||||
pkt_buf: [u8; ETH_RX_BUFFER_COUNT * ETH_RX_BUFFER_SIZE],
|
||||
}
|
||||
|
||||
impl RxRing {
|
||||
fn new() -> RxRing {
|
||||
RxRing {
|
||||
desc_buf: [0; ETH_RX_BUFFER_COUNT * ETH_DESC_U32_SIZE],
|
||||
cur_desc: 0,
|
||||
counter: 0,
|
||||
pkt_buf: [0; ETH_RX_BUFFER_COUNT * ETH_RX_BUFFER_SIZE],
|
||||
}
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
// Initialize RX DMA descriptors
|
||||
for x in 0..ETH_RX_BUFFER_COUNT {
|
||||
let p = x * ETH_DESC_U32_SIZE;
|
||||
let r = x * ETH_RX_BUFFER_SIZE;
|
||||
|
||||
// The descriptor is initially owned by the DMA
|
||||
self.desc_buf[p + 0] = EMAC_RDES0_OWN;
|
||||
// Use chain structure rather than ring structure
|
||||
self.desc_buf[p + 1] =
|
||||
EMAC_RDES1_RCH | ((ETH_RX_BUFFER_SIZE as u32) & EMAC_RDES1_RBS1);
|
||||
// Receive buffer address
|
||||
self.desc_buf[p + 2] = (&self.pkt_buf[r] as *const u8) as u32;
|
||||
// Next descriptor address
|
||||
if x != ETH_RX_BUFFER_COUNT - 1 {
|
||||
self.desc_buf[p + 3] =
|
||||
(&self.desc_buf[p + ETH_DESC_U32_SIZE] as *const u32) as u32;
|
||||
} else {
|
||||
self.desc_buf[p + 3] =
|
||||
(&self.desc_buf[0] as *const u32) as u32;
|
||||
}
|
||||
// Extended status
|
||||
self.desc_buf[p + 4] = 0;
|
||||
// Reserved field
|
||||
self.desc_buf[p + 5] = 0;
|
||||
// Transmit frame time stamp
|
||||
self.desc_buf[p + 6] = 0;
|
||||
self.desc_buf[p + 7] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn buf_owned(&self) -> bool {
|
||||
self.desc_buf[self.cur_desc + 0] & EMAC_RDES0_OWN == 0
|
||||
}
|
||||
|
||||
fn buf_valid(&self) -> bool {
|
||||
self.desc_buf[self.cur_desc + 0] &
|
||||
(EMAC_RDES0_FS | EMAC_RDES0_LS | EMAC_RDES0_ES) ==
|
||||
(EMAC_RDES0_FS | EMAC_RDES0_LS)
|
||||
}
|
||||
|
||||
unsafe fn buf_as_slice<'a>(&self) -> &'a mut [u8] {
|
||||
let len = (self.desc_buf[self.cur_desc + 0] & EMAC_RDES0_FL) >> 16;
|
||||
let len = cmp::min(len as usize, ETH_RX_BUFFER_SIZE);
|
||||
let addr = self.desc_buf[self.cur_desc + 2] as *mut u8;
|
||||
slice::from_raw_parts_mut(addr, len)
|
||||
}
|
||||
|
||||
fn buf_release(&mut self) {
|
||||
self.cur_desc += ETH_DESC_U32_SIZE;
|
||||
if self.cur_desc == self.desc_buf.len() {
|
||||
self.cur_desc = 0;
|
||||
}
|
||||
self.counter += 1;
|
||||
|
||||
self.desc_buf[self.cur_desc + 0] = EMAC_RDES0_OWN;
|
||||
}
|
||||
}
|
||||
|
||||
struct TxRing {
|
||||
desc_buf: [u32; ETH_TX_BUFFER_COUNT * ETH_DESC_U32_SIZE],
|
||||
cur_desc: usize,
|
||||
counter: u32,
|
||||
pkt_buf: [u8; ETH_TX_BUFFER_COUNT * ETH_TX_BUFFER_SIZE],
|
||||
}
|
||||
|
||||
impl TxRing {
|
||||
fn new() -> TxRing {
|
||||
TxRing {
|
||||
desc_buf: [0; ETH_TX_BUFFER_COUNT * ETH_DESC_U32_SIZE],
|
||||
cur_desc: 0,
|
||||
counter: 0,
|
||||
pkt_buf: [0; ETH_TX_BUFFER_COUNT * ETH_TX_BUFFER_SIZE],
|
||||
}
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
// Initialize TX DMA descriptors
|
||||
for x in 0..ETH_TX_BUFFER_COUNT {
|
||||
let p = x * ETH_DESC_U32_SIZE;
|
||||
let r = x * ETH_TX_BUFFER_SIZE;
|
||||
|
||||
// Initialize transmit flags
|
||||
self.desc_buf[p + 0] = 0;
|
||||
// Initialize transmit buffer size
|
||||
self.desc_buf[p + 1] = 0;
|
||||
// Transmit buffer address
|
||||
self.desc_buf[p + 2] = (&self.pkt_buf[r] as *const u8) as u32;
|
||||
// Next descriptor address
|
||||
if x != ETH_TX_BUFFER_COUNT - 1 {
|
||||
self.desc_buf[p + 3] =
|
||||
(&self.desc_buf[p + ETH_DESC_U32_SIZE] as *const u32) as u32;
|
||||
} else {
|
||||
self.desc_buf[p + 3] =
|
||||
(&self.desc_buf[0] as *const u32) as u32;
|
||||
}
|
||||
// Reserved fields
|
||||
self.desc_buf[p + 4] = 0;
|
||||
self.desc_buf[p + 5] = 0;
|
||||
// Transmit frame time stamp
|
||||
self.desc_buf[p + 6] = 0;
|
||||
self.desc_buf[p + 7] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn buf_owned(&self) -> bool {
|
||||
self.desc_buf[self.cur_desc + 0] & EMAC_TDES0_OWN == 0
|
||||
}
|
||||
|
||||
unsafe fn buf_as_slice<'a>(&mut self, len: usize) -> &'a mut [u8] {
|
||||
let len = cmp::min(len, ETH_TX_BUFFER_SIZE);
|
||||
self.desc_buf[self.cur_desc + 1] = len as u32;
|
||||
let addr = self.desc_buf[self.cur_desc + 2] as *mut u8;
|
||||
slice::from_raw_parts_mut(addr, len)
|
||||
}
|
||||
|
||||
fn buf_release(&mut self) {
|
||||
self.desc_buf[self.cur_desc + 0] =
|
||||
EMAC_TDES0_OWN | EMAC_TDES0_LS | EMAC_TDES0_FS | EMAC_TDES0_TCH;
|
||||
|
||||
cortex_m::interrupt::free(|_cs| {
|
||||
let emac0 = unsafe { &*tm4c129x::EMAC0::ptr() };
|
||||
// Clear TU flag to resume processing
|
||||
emac0.dmaris.write(|w| w.tu().bit(true));
|
||||
// Instruct the DMA to poll the transmit descriptor list
|
||||
unsafe { emac0.txpolld.write(|w| w.tpd().bits(0)); }
|
||||
});
|
||||
|
||||
self.cur_desc += ETH_DESC_U32_SIZE;
|
||||
if self.cur_desc == self.desc_buf.len() {
|
||||
self.cur_desc = 0;
|
||||
}
|
||||
self.counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Device {
|
||||
rx: RxRing,
|
||||
tx: TxRing,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
pub fn new() -> Device {
|
||||
Device {
|
||||
rx: RxRing::new(),
|
||||
tx: TxRing::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// After `init` is called, `Device` shall not be moved.
|
||||
pub unsafe fn init(&mut self, mac: EthernetAddress) {
|
||||
self.rx.init();
|
||||
self.tx.init();
|
||||
|
||||
cortex_m::interrupt::free(|_cs| {
|
||||
let sysctl = &*tm4c129x::SYSCTL::ptr();
|
||||
let emac0 = &*tm4c129x::EMAC0::ptr();
|
||||
|
||||
sysctl.rcgcemac.modify(|_, w| w.r0().bit(true)); // Bring up MAC
|
||||
sysctl.sremac.modify(|_, w| w.r0().bit(true)); // Activate MAC reset
|
||||
delay(16);
|
||||
sysctl.sremac.modify(|_, w| w.r0().bit(false)); // Dectivate MAC reset
|
||||
|
||||
sysctl.rcgcephy.modify(|_, w| w.r0().bit(true)); // Bring up PHY
|
||||
sysctl.srephy.modify(|_, w| w.r0().bit(true)); // Activate PHY reset
|
||||
delay(16);
|
||||
sysctl.srephy.modify(|_, w| w.r0().bit(false)); // Dectivate PHY reset
|
||||
|
||||
while !sysctl.premac.read().r0().bit() {} // Wait for the MAC to come out of reset
|
||||
while !sysctl.prephy.read().r0().bit() {} // Wait for the PHY to come out of reset
|
||||
delay(10000);
|
||||
|
||||
emac0.dmabusmod.modify(|_, w| w.swr().bit(true)); // Reset MAC DMA
|
||||
while emac0.dmabusmod.read().swr().bit() {} // Wait for the MAC DMA to come out of reset
|
||||
delay(1000);
|
||||
|
||||
emac0.miiaddr.write(|w| w.cr()._100_150()); // Set the MII CSR clock speed.
|
||||
|
||||
// Checking PHY
|
||||
if (phy_read(EPHY_ID1) != 0x2000) | (phy_read(EPHY_ID2) != 0xA221) {
|
||||
panic!("PHY ID error!");
|
||||
}
|
||||
|
||||
// Reset PHY transceiver
|
||||
phy_write(EPHY_BMCR, 1); // Initiate MII reset
|
||||
while (phy_read(EPHY_BMCR) & 1) == 1 {}; // Wait for the reset to be completed
|
||||
|
||||
// Configure PHY LEDs
|
||||
phy_write_ext(EPHY_LEDCFG, 0x0008); // LED0 Link OK/Blink on TX/RX Activity
|
||||
|
||||
// Tell the PHY to start an auto-negotiation cycle
|
||||
phy_write(EPHY_BMCR, 0b00010010_00000000); // ANEN and RESTARTAN
|
||||
|
||||
// Set the DMA operation mode
|
||||
emac0.dmaopmode.write(|w|
|
||||
w.rsf().bit(true) // Receive Store and Forward
|
||||
.tsf().bit(true) // Transmit Store and Forward
|
||||
.ttc()._64() // Transmit Threshold Control
|
||||
.rtc()._64() // Receive Threshold Control
|
||||
);
|
||||
|
||||
// Set the bus mode register.
|
||||
emac0.dmabusmod.write(|w|
|
||||
w.atds().bit(true)
|
||||
.aal().bit(true) // Address Aligned Beats
|
||||
.usp().bit(true) // Use Separate Programmable Burst Length ???
|
||||
.rpbl().bits(1) // RX DMA Programmable Burst Length
|
||||
.pbl().bits(1) // Programmable Burst Length
|
||||
.pr().bits(0) // Priority Ratio 1:1
|
||||
);
|
||||
|
||||
// Disable all the MMC interrupts as these are enabled by default at reset.
|
||||
emac0.mmcrxim.write(|w| w.bits(0xFFFFFFFF));
|
||||
emac0.mmctxim.write(|w| w.bits(0xFFFFFFFF));
|
||||
|
||||
// Set MAC configuration options
|
||||
emac0.cfg.write(|w|
|
||||
w.dupm().bit(true) // MAC operates in full-duplex mode
|
||||
.ipc().bit(true) // Checksum Offload Enable
|
||||
.prelen()._7() // 7 bytes of preamble
|
||||
.ifg()._96() // 96 bit times
|
||||
.bl()._1024() // Back-Off Limit 1024
|
||||
.ps().bit(true) // ?
|
||||
);
|
||||
|
||||
// Set the maximum receive frame size
|
||||
emac0.wdogto.write(|w|
|
||||
w.bits(0) // ??? no use watchdog
|
||||
);
|
||||
|
||||
// Set the MAC address
|
||||
emac0.addr0l.write(|w|
|
||||
w.addrlo().bits( mac.0[0] as u32 |
|
||||
((mac.0[1] as u32) << 8) |
|
||||
((mac.0[2] as u32) << 16) |
|
||||
((mac.0[3] as u32) << 24))
|
||||
);
|
||||
emac0.addr0h.write(|w|
|
||||
w.addrhi().bits( mac.0[4] as u16 |
|
||||
((mac.0[5] as u16) << 8))
|
||||
);
|
||||
|
||||
// Set MAC filtering options (?)
|
||||
emac0.framefltr.write(|w|
|
||||
w.hpf().bit(true) // Hash or Perfect Filter
|
||||
//.hmc().bit(true) // Hash Multicast ???
|
||||
.pm().bit(true) // Pass All Multicast
|
||||
);
|
||||
|
||||
// Initialize hash table
|
||||
emac0.hashtbll.write(|w| w.htl().bits(0));
|
||||
emac0.hashtblh.write(|w| w.hth().bits(0));
|
||||
|
||||
emac0.flowctl.write(|w| w.bits(0)); // Disable flow control ???
|
||||
|
||||
emac0.txdladdr.write(|w| /*unsafe*/ {
|
||||
w.bits((&mut self.tx.desc_buf[0] as *mut u32) as u32)
|
||||
});
|
||||
emac0.rxdladdr.write(|w| /*unsafe*/ {
|
||||
w.bits((&mut self.rx.desc_buf[0] as *mut u32) as u32)
|
||||
});
|
||||
|
||||
// Manage MAC transmission and reception
|
||||
emac0.cfg.modify(|_, w|
|
||||
w.re().bit(true) // Receiver Enable
|
||||
.te().bit(true) // Transmiter Enable
|
||||
);
|
||||
|
||||
// Manage DMA transmission and reception
|
||||
emac0.dmaopmode.modify(|_, w|
|
||||
w.sr().bit(true) // Start Receive
|
||||
.st().bit(true) // Start Transmit
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> phy::Device<'a> for &'b mut Device {
|
||||
type RxToken = RxToken<'a>;
|
||||
type TxToken = TxToken<'a>;
|
||||
|
||||
fn capabilities(&self) -> phy::DeviceCapabilities {
|
||||
let mut capabilities = phy::DeviceCapabilities::default();
|
||||
capabilities.max_transmission_unit = 1500;
|
||||
capabilities.max_burst_size = Some(ETH_RX_BUFFER_COUNT);
|
||||
capabilities
|
||||
}
|
||||
|
||||
fn receive(&mut self) -> Option<(RxToken, TxToken)> {
|
||||
// Skip all queued packets with errors.
|
||||
while self.rx.buf_owned() && !self.rx.buf_valid() {
|
||||
self.rx.buf_release()
|
||||
}
|
||||
|
||||
if !(self.rx.buf_owned() && self.tx.buf_owned()) {
|
||||
return None
|
||||
}
|
||||
|
||||
Some((RxToken(&mut self.rx), TxToken(&mut self.tx)))
|
||||
}
|
||||
|
||||
fn transmit(&mut self) -> Option<TxToken> {
|
||||
if !self.tx.buf_owned() {
|
||||
return None
|
||||
}
|
||||
|
||||
Some(TxToken(&mut self.tx))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RxToken<'a>(&'a mut RxRing);
|
||||
|
||||
impl<'a> phy::RxToken for RxToken<'a> {
|
||||
fn consume<R, F>(self, _timestamp: Instant, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R> {
|
||||
let result = f(unsafe { self.0.buf_as_slice() });
|
||||
self.0.buf_release();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TxToken<'a>(&'a mut TxRing);
|
||||
|
||||
impl<'a> phy::TxToken for TxToken<'a> {
|
||||
fn consume<R, F>(self, _timestamp: Instant, len: usize, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R> {
|
||||
let result = f(unsafe { self.0.buf_as_slice(len) });
|
||||
self.0.buf_release();
|
||||
result
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,516 @@
|
|||
#![feature(const_fn, proc_macro_hygiene)]
|
||||
#![cfg_attr(not(test), no_std)]
|
||||
#![cfg_attr(not(test), no_main)]
|
||||
|
||||
use cortex_m_rt::entry;
|
||||
use core::fmt::{self, Write};
|
||||
use smoltcp::time::Instant;
|
||||
use smoltcp::wire::{IpCidr, IpAddress, EthernetAddress};
|
||||
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
|
||||
use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer};
|
||||
use cortex_m_semihosting::hio;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! print {
|
||||
($($arg:tt)*) => ({
|
||||
use core::fmt::Write;
|
||||
write!($crate::UART0, $($arg)*).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! println {
|
||||
($fmt:expr) => (print!(concat!($fmt, "\n")));
|
||||
($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[no_mangle] // https://github.com/rust-lang/rust/issues/{38281,51647}
|
||||
#[panic_handler]
|
||||
pub fn panic_fmt(info: &core::panic::PanicInfo) -> ! {
|
||||
println!("{}", info);
|
||||
let mut stdout = hio::hstdout().unwrap();
|
||||
let _ = writeln!(stdout, "{}", info);
|
||||
loop {}
|
||||
}
|
||||
|
||||
mod board;
|
||||
use self::board::{
|
||||
gpio::Gpio,
|
||||
systick::get_time,
|
||||
};
|
||||
mod ethmac;
|
||||
mod command_parser;
|
||||
use command_parser::{Command, ShowCommand, PwmSetup, PwmMode, PwmConfig};
|
||||
mod session;
|
||||
use self::session::{Session, SessionOutput};
|
||||
mod ad7172;
|
||||
mod pid;
|
||||
mod tec;
|
||||
use tec::{Tec, TecPin};
|
||||
mod steinhart_hart;
|
||||
use steinhart_hart as sh;
|
||||
|
||||
pub struct UART0;
|
||||
|
||||
impl fmt::Write for UART0 {
|
||||
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
|
||||
let uart_0 = unsafe { &*tm4c129x::UART0::ptr() };
|
||||
for c in s.bytes() {
|
||||
while uart_0.fr.read().txff().bit() {}
|
||||
uart_0.dr.write(|w| w.data().bits(c))
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const TCP_RX_BUFFER_SIZE: usize = 256;
|
||||
const TCP_TX_BUFFER_SIZE: usize = 8192;
|
||||
|
||||
|
||||
macro_rules! create_socket_storage {
|
||||
($rx_storage:ident, $tx_storage:ident) => (
|
||||
let mut $rx_storage = [0; TCP_RX_BUFFER_SIZE];
|
||||
let mut $tx_storage = [0; TCP_TX_BUFFER_SIZE];
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! create_socket {
|
||||
($set:ident, $rx_storage:ident, $tx_storage:ident, $target:ident) => (
|
||||
let tcp_rx_buffer = TcpSocketBuffer::new(&mut $rx_storage[..]);
|
||||
let tcp_tx_buffer = TcpSocketBuffer::new(&mut $tx_storage[..]);
|
||||
let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
|
||||
let $target = $set.add(tcp_socket);
|
||||
)
|
||||
}
|
||||
|
||||
const VCC: f32 = 3.3;
|
||||
|
||||
const PWM_PID_WIDTH: u16 = 0xffff;
|
||||
const PWM_MAX: f32 = PWM_PID_WIDTH as f32;
|
||||
|
||||
const DEFAULT_PID_PARAMETERS: pid::Parameters = pid::Parameters {
|
||||
kp: 0.5 * PWM_MAX,
|
||||
ki: 0.05 * PWM_MAX,
|
||||
kd: 0.45 * PWM_MAX,
|
||||
output_min: 0.0,
|
||||
output_max: PWM_MAX,
|
||||
integral_min: 0.0,
|
||||
integral_max: PWM_MAX,
|
||||
};
|
||||
|
||||
const DEFAULT_SH_PARAMETERS: sh::Parameters = sh::Parameters {
|
||||
a: 0.001_4,
|
||||
b: 0.000_237,
|
||||
c: 0.000_000_099,
|
||||
parallel_r: 5_110.0, // Ohm (TODO: verify)
|
||||
};
|
||||
|
||||
// TODO: maybe rename to `TECS`?
|
||||
/// Number of TEC channels with four PWM channels each
|
||||
pub const CHANNELS: usize = 2;
|
||||
|
||||
// TODO: maybe rename to `TecState`?
|
||||
/// State per TEC channel
|
||||
#[derive(Clone)]
|
||||
struct ControlState {
|
||||
/// Report data (time, data, temperature)
|
||||
report: Option<(u64, i32, f32, Option<u16>)>,
|
||||
pid_enabled: bool,
|
||||
pid: pid::Controller,
|
||||
sh: sh::Parameters,
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let mut stdout = hio::hstdout().unwrap();
|
||||
writeln!(stdout, "tecpak boot").unwrap();
|
||||
board::init();
|
||||
writeln!(stdout, "board initialized").unwrap();
|
||||
let mut tec0 = Tec::tec0().setup(PWM_PID_WIDTH / 2, PWM_PID_WIDTH);
|
||||
let mut tec1 = Tec::tec1().setup(PWM_PID_WIDTH / 2, PWM_PID_WIDTH);
|
||||
|
||||
println!(r#"
|
||||
_ _
|
||||
| | | |
|
||||
/ _/___ _ __ _ __ __ _| |
|
||||
| |/ _ \ /'__\| '_ \ / _` | |/ /
|
||||
| | (/_/| |___| |_) | (_| | <
|
||||
|_|\___\ \___/| .__/ \__,_|_|\_\
|
||||
| |
|
||||
|_| v1
|
||||
"#);
|
||||
// CSn
|
||||
let pb4 = board::gpio::PB4.into_output();
|
||||
// SCLK
|
||||
let pb5 = board::gpio::PB5.into_output();
|
||||
// MOSI
|
||||
let pe4 = board::gpio::PE4.into_output();
|
||||
// MISO
|
||||
let pe5 = board::gpio::PE5.into_input();
|
||||
// max 2 MHz = 0.5 us
|
||||
let mut delay_fn = || for _ in 0..10 { cortex_m::asm::nop(); };
|
||||
let spi = board::softspi::SyncSoftSpi::new(
|
||||
board::softspi::SoftSpi::new(pb5, pe4, pe5),
|
||||
&mut delay_fn
|
||||
);
|
||||
let mut adc = ad7172::Adc::new(spi, pb4).unwrap();
|
||||
loop {
|
||||
let r = adc.identify();
|
||||
match r {
|
||||
Err(e) =>
|
||||
writeln!(stdout, "Cannot identify ADC: {:?}", e).unwrap(),
|
||||
Ok(id) if id & 0xFFF0 == 0x00D0 => {
|
||||
writeln!(stdout, "ADC id: {:04X}", id).unwrap();
|
||||
break;
|
||||
}
|
||||
Ok(_id) => {
|
||||
// This always happens on the first attempt. So retry silently
|
||||
}
|
||||
};
|
||||
}
|
||||
writeln!(stdout, "AD7172: setting checksum mode").unwrap();
|
||||
adc.set_checksum_mode(ad7172::ChecksumMode::Crc).unwrap();
|
||||
loop {
|
||||
let r = adc.identify();
|
||||
match r {
|
||||
Err(e) =>
|
||||
writeln!(stdout, "Cannot identify ADC: {:?}", e).unwrap(),
|
||||
Ok(id) if id & 0xFFF0 == 0x00D0 => {
|
||||
writeln!(stdout, "ADC id: {:04X}", id).unwrap();
|
||||
break;
|
||||
}
|
||||
Ok(id) =>
|
||||
writeln!(stdout, "Corrupt ADC id: {:04X}", id).unwrap(),
|
||||
};
|
||||
}
|
||||
adc.set_sync_enable(false).unwrap();
|
||||
// SENS0_{P,N}
|
||||
adc.setup_channel(0, ad7172::Input::Ain0, ad7172::Input::Ain1).unwrap();
|
||||
// SENS1_{P,N}
|
||||
adc.setup_channel(1, ad7172::Input::Ain2, ad7172::Input::Ain3).unwrap();
|
||||
|
||||
let init_state = ControlState {
|
||||
report: None,
|
||||
// Start with disengaged PID to let user setup parameters first
|
||||
pid_enabled: false,
|
||||
pid: pid::Controller::new(DEFAULT_PID_PARAMETERS.clone()),
|
||||
sh: DEFAULT_SH_PARAMETERS.clone(),
|
||||
};
|
||||
let mut states = [init_state.clone(), init_state.clone()];
|
||||
|
||||
let mut hardware_addr = EthernetAddress(board::get_mac_address());
|
||||
writeln!(stdout, "MAC address: {}", hardware_addr).unwrap();
|
||||
if hardware_addr.is_multicast() {
|
||||
writeln!(stdout, "programmed MAC address is invalid, using default").unwrap();
|
||||
hardware_addr = EthernetAddress([0x10, 0xE2, 0xD5, 0x00, 0x03, 0x00]);
|
||||
}
|
||||
let mut ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 1, 26), 24)];
|
||||
println!("MAC {} IP {}", hardware_addr, ip_addrs[0]);
|
||||
let mut neighbor_cache_storage = [None; 8];
|
||||
let neighbor_cache = NeighborCache::new(&mut neighbor_cache_storage[..]);
|
||||
let mut device = ethmac::Device::new();
|
||||
unsafe { device.init(hardware_addr) };
|
||||
let mut iface = EthernetInterfaceBuilder::new(&mut device)
|
||||
.ethernet_addr(hardware_addr)
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.ip_addrs(&mut ip_addrs[..])
|
||||
.finalize();
|
||||
|
||||
create_socket_storage!(tcp_rx_storage0, tcp_tx_storage0);
|
||||
create_socket_storage!(tcp_rx_storage1, tcp_tx_storage1);
|
||||
create_socket_storage!(tcp_rx_storage2, tcp_tx_storage2);
|
||||
create_socket_storage!(tcp_rx_storage3, tcp_tx_storage3);
|
||||
create_socket_storage!(tcp_rx_storage4, tcp_tx_storage4);
|
||||
create_socket_storage!(tcp_rx_storage5, tcp_tx_storage5);
|
||||
create_socket_storage!(tcp_rx_storage6, tcp_tx_storage6);
|
||||
create_socket_storage!(tcp_rx_storage7, tcp_tx_storage7);
|
||||
|
||||
let mut socket_set_entries: [_; 8] = Default::default();
|
||||
let mut sockets = SocketSet::new(&mut socket_set_entries[..]);
|
||||
|
||||
create_socket!(sockets, tcp_rx_storage0, tcp_tx_storage0, tcp_handle0);
|
||||
create_socket!(sockets, tcp_rx_storage1, tcp_tx_storage1, tcp_handle1);
|
||||
create_socket!(sockets, tcp_rx_storage2, tcp_tx_storage2, tcp_handle2);
|
||||
create_socket!(sockets, tcp_rx_storage3, tcp_tx_storage3, tcp_handle3);
|
||||
create_socket!(sockets, tcp_rx_storage4, tcp_tx_storage4, tcp_handle4);
|
||||
create_socket!(sockets, tcp_rx_storage5, tcp_tx_storage5, tcp_handle5);
|
||||
create_socket!(sockets, tcp_rx_storage6, tcp_tx_storage6, tcp_handle6);
|
||||
create_socket!(sockets, tcp_rx_storage7, tcp_tx_storage7, tcp_handle7);
|
||||
let mut sessions_handles = [
|
||||
(Session::new(), tcp_handle0),
|
||||
(Session::new(), tcp_handle1),
|
||||
(Session::new(), tcp_handle2),
|
||||
(Session::new(), tcp_handle3),
|
||||
(Session::new(), tcp_handle4),
|
||||
(Session::new(), tcp_handle5),
|
||||
(Session::new(), tcp_handle6),
|
||||
(Session::new(), tcp_handle7),
|
||||
];
|
||||
|
||||
loop {
|
||||
// ADC input
|
||||
adc.data_ready()
|
||||
.unwrap_or_else(|e| {
|
||||
writeln!(stdout, "ADC error: {:?}", e).unwrap();
|
||||
None
|
||||
}).map(|channel| {
|
||||
let now = get_time();
|
||||
let data = adc.read_data().unwrap();
|
||||
let state = &mut states[usize::from(channel)];
|
||||
let voltage = VCC * (data as f32) / (0x7FFFFF as f32);
|
||||
let temperature = state.sh.get_temperature(voltage);
|
||||
|
||||
let pwm_width = if state.pid_enabled {
|
||||
let width = state.pid.update(temperature) as u16;
|
||||
match channel {
|
||||
0 => tec0.set(TecPin::ISet, width, PWM_PID_WIDTH),
|
||||
1 => tec1.set(TecPin::ISet, width, PWM_PID_WIDTH),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
Some(width)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
state.report = Some((now, data, temperature, pwm_width));
|
||||
for (session, _) in sessions_handles.iter_mut() {
|
||||
session.set_report_pending(channel.into());
|
||||
}
|
||||
});
|
||||
|
||||
for (session, tcp_handle) in sessions_handles.iter_mut() {
|
||||
let socket = &mut *sockets.get::<TcpSocket>(*tcp_handle);
|
||||
if !socket.is_open() {
|
||||
if session.is_dirty() {
|
||||
// Reset a previously uses session/socket
|
||||
*session = Session::new();
|
||||
}
|
||||
socket.listen(23).unwrap()
|
||||
}
|
||||
|
||||
if socket.may_recv() && socket.may_send() {
|
||||
let output = socket.recv(|buf| session.feed(buf));
|
||||
|
||||
// TODO: use "{}" to display pretty errors
|
||||
match output {
|
||||
Ok(SessionOutput::Nothing) => {}
|
||||
Ok(SessionOutput::Command(command)) => match command {
|
||||
Command::Quit =>
|
||||
socket.close(),
|
||||
Command::Reporting(reporting) => {
|
||||
let _ = writeln!(socket, "report={}", if reporting { "on" } else { "off" });
|
||||
}
|
||||
Command::Show(ShowCommand::Reporting) => {
|
||||
let _ = writeln!(socket, "report={}", if session.reporting() { "on" } else { "off" });
|
||||
}
|
||||
Command::Show(ShowCommand::Input) => {
|
||||
for (channel, state) in states.iter().enumerate() {
|
||||
state.report.map(|(time, data, temp, pwm_width)| {
|
||||
let _ = write!(
|
||||
socket, "t={} temp{}={} raw{}=0x{:06X}",
|
||||
time, channel, temp, channel, data
|
||||
);
|
||||
pwm_width.map(|width| {
|
||||
let _ = write!(
|
||||
socket, " pwm{}=0x{:04X}",
|
||||
channel, width
|
||||
);
|
||||
});
|
||||
let _ = writeln!(socket, "");
|
||||
});
|
||||
}
|
||||
}
|
||||
Command::Show(ShowCommand::Pid) => {
|
||||
for (channel, state) in states.iter().enumerate() {
|
||||
let _ = writeln!(socket, "PID settings for channel {}", channel);
|
||||
let pid = &state.pid;
|
||||
let _ = writeln!(socket, "- target={:.4}", pid.get_target());
|
||||
let p = pid.get_parameters();
|
||||
macro_rules! out {
|
||||
($p: tt) => {
|
||||
let _ = writeln!(socket, "- {}={:.4}", stringify!($p), p.$p);
|
||||
};
|
||||
}
|
||||
out!(kp);
|
||||
out!(ki);
|
||||
out!(kd);
|
||||
out!(output_min);
|
||||
out!(output_max);
|
||||
out!(integral_min);
|
||||
out!(integral_max);
|
||||
let _ = writeln!(socket, "");
|
||||
}
|
||||
}
|
||||
Command::Show(ShowCommand::Pwm) => {
|
||||
for (channel, state) in states.iter().enumerate() {
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: PID={}",
|
||||
channel,
|
||||
if state.pid_enabled { "engaged" } else { "disengaged" }
|
||||
);
|
||||
for pin in TecPin::VALID_VALUES {
|
||||
let (width, total) = match channel {
|
||||
0 => tec0.get(*pin),
|
||||
1 => tec1.get(*pin),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let _ = writeln!(socket, "- {}={}/{}", pin, width, total);
|
||||
}
|
||||
let _ = writeln!(socket, "");
|
||||
}
|
||||
}
|
||||
Command::Show(ShowCommand::SteinhartHart) => {
|
||||
for (channel, state) in states.iter().enumerate() {
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: Steinhart-Hart equation parameters",
|
||||
channel,
|
||||
);
|
||||
let _ = writeln!(socket, "- a={}", state.sh.a);
|
||||
let _ = writeln!(socket, "- b={}", state.sh.b);
|
||||
let _ = writeln!(socket, "- c={}", state.sh.c);
|
||||
let _ = writeln!(socket, "- parallel_r={}", state.sh.parallel_r);
|
||||
let _ = writeln!(socket, "");
|
||||
}
|
||||
}
|
||||
Command::Show(ShowCommand::PostFilter) => {
|
||||
for (channel, _) in states.iter().enumerate() {
|
||||
match adc.get_postfilter(channel as u8).unwrap() {
|
||||
Some(filter) => {
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: postfilter={:.2} SPS",
|
||||
channel, filter.output_rate().unwrap()
|
||||
);
|
||||
}
|
||||
None => {
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: no postfilter",
|
||||
channel
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Pwm { channel, setup: PwmSetup::ISet(PwmMode::Pid) } => {
|
||||
states[channel].pid_enabled = true;
|
||||
let _ = writeln!(socket, "channel {}: PID enabled to control PWM", channel);
|
||||
}
|
||||
Command::Pwm { channel, setup: PwmSetup::ISet(PwmMode::Manual(config))} => {
|
||||
states[channel].pid_enabled = false;
|
||||
let PwmConfig { width, total } = config;
|
||||
match channel {
|
||||
0 => tec0.set(TecPin::ISet, width, total),
|
||||
1 => tec1.set(TecPin::ISet, width, total),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: PWM duty cycle manually set to {}/{}",
|
||||
channel, config.width, config.total
|
||||
);
|
||||
}
|
||||
Command::Pwm { channel, setup } => {
|
||||
let (pin, config) = match setup {
|
||||
PwmSetup::ISet(_) =>
|
||||
// Handled above
|
||||
unreachable!(),
|
||||
PwmSetup::MaxIPos(config) =>
|
||||
(TecPin::MaxIPos, config),
|
||||
PwmSetup::MaxINeg(config) =>
|
||||
(TecPin::MaxINeg, config),
|
||||
PwmSetup::MaxV(config) =>
|
||||
(TecPin::MaxV, config),
|
||||
};
|
||||
let PwmConfig { width, total } = config;
|
||||
match channel {
|
||||
0 => tec0.set(pin, width, total),
|
||||
1 => tec1.set(pin, width, total),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: PWM {} reconfigured to {}/{}",
|
||||
channel, pin, width, total
|
||||
);
|
||||
}
|
||||
Command::Pid { channel, parameter, value } => {
|
||||
let pid = &mut states[channel].pid;
|
||||
use command_parser::PidParameter::*;
|
||||
match parameter {
|
||||
Target =>
|
||||
pid.set_target(value),
|
||||
KP =>
|
||||
pid.update_parameters(|parameters| parameters.kp = value),
|
||||
KI =>
|
||||
pid.update_parameters(|parameters| parameters.ki = value),
|
||||
KD =>
|
||||
pid.update_parameters(|parameters| parameters.kd = value),
|
||||
OutputMin =>
|
||||
pid.update_parameters(|parameters| parameters.output_min = value),
|
||||
OutputMax =>
|
||||
pid.update_parameters(|parameters| parameters.output_max = value),
|
||||
IntegralMin =>
|
||||
pid.update_parameters(|parameters| parameters.integral_min = value),
|
||||
IntegralMax =>
|
||||
pid.update_parameters(|parameters| parameters.integral_max = value),
|
||||
}
|
||||
pid.reset();
|
||||
let _ = writeln!(socket, "PID parameter updated");
|
||||
}
|
||||
Command::SteinhartHart { channel, parameter, value } => {
|
||||
let sh = &mut states[channel].sh;
|
||||
use command_parser::ShParameter::*;
|
||||
match parameter {
|
||||
A => sh.a = value,
|
||||
B => sh.b = value,
|
||||
C => sh.c = value,
|
||||
ParallelR => sh.parallel_r = value,
|
||||
}
|
||||
let _ = writeln!(socket, "Steinhart-Hart equation parameter updated");
|
||||
}
|
||||
Command::PostFilter { channel, rate } => {
|
||||
let filter = ad7172::PostFilter::closest(rate);
|
||||
match filter {
|
||||
Some(filter) => {
|
||||
adc.set_postfilter(channel as u8, Some(filter)).unwrap();
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: postfilter set to {:.2} SPS",
|
||||
channel, filter.output_rate().unwrap()
|
||||
);
|
||||
}
|
||||
None => {
|
||||
let _ = writeln!(socket, "Unable to choose postfilter");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(SessionOutput::Error(e)) => {
|
||||
let _ = writeln!(socket, "Command error: {:?}", e);
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
if socket.may_send() {
|
||||
if let Some(channel) = session.is_report_pending() {
|
||||
states[channel].report.map(|(time, data, temp, pwm_width)| {
|
||||
let _ = write!(
|
||||
socket, "t={} temp{}={} raw{}=0x{:06X}",
|
||||
time, channel, temp, channel, data
|
||||
);
|
||||
pwm_width.map(|width| {
|
||||
let _ = write!(
|
||||
socket, " pwm{}=0x{:04X}",
|
||||
channel, width
|
||||
);
|
||||
});
|
||||
let _ = writeln!(socket, "");
|
||||
});
|
||||
session.mark_report_sent(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
match iface.poll(&mut sockets, Instant::from_millis((get_time() / 1000) as i64)) {
|
||||
Ok(_) => (),
|
||||
Err(e) => println!("poll error: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
#[derive(Clone, Copy)]
|
||||
pub struct Parameters {
|
||||
pub kp: f32,
|
||||
pub ki: f32,
|
||||
pub kd: f32,
|
||||
pub output_min: f32,
|
||||
pub output_max: f32,
|
||||
pub integral_min: f32,
|
||||
pub integral_max: f32
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Controller {
|
||||
parameters: Parameters,
|
||||
target: f32,
|
||||
integral: f32,
|
||||
last_input: Option<f32>
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
pub const fn new(parameters: Parameters) -> Controller {
|
||||
Controller {
|
||||
parameters: parameters,
|
||||
target: 0.0,
|
||||
last_input: None,
|
||||
integral: 0.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, input: f32) -> f32 {
|
||||
let error = self.target - input;
|
||||
|
||||
let p = self.parameters.kp * error;
|
||||
|
||||
self.integral += error;
|
||||
if self.integral < self.parameters.integral_min {
|
||||
self.integral = self.parameters.integral_min;
|
||||
}
|
||||
if self.integral > self.parameters.integral_max {
|
||||
self.integral = self.parameters.integral_max;
|
||||
}
|
||||
let i = self.parameters.ki * self.integral;
|
||||
|
||||
let d = match self.last_input {
|
||||
None => 0.0,
|
||||
Some(last_input) => self.parameters.kd * (last_input - input)
|
||||
};
|
||||
self.last_input = Some(input);
|
||||
|
||||
let mut output = p + i + d;
|
||||
if output < self.parameters.output_min {
|
||||
output = self.parameters.output_min;
|
||||
}
|
||||
if output > self.parameters.output_max {
|
||||
output = self.parameters.output_max;
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
pub fn get_target(&self) -> f32 {
|
||||
self.target
|
||||
}
|
||||
|
||||
pub fn set_target(&mut self, target: f32) {
|
||||
self.target = target;
|
||||
}
|
||||
|
||||
pub fn get_parameters(&self) -> &Parameters {
|
||||
&self.parameters
|
||||
}
|
||||
|
||||
pub fn update_parameters<F: FnOnce(&mut Parameters)>(&mut self, f: F) {
|
||||
f(&mut self.parameters);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn reset(&mut self) {
|
||||
self.integral = 0.0;
|
||||
self.last_input = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
const PARAMETERS: Parameters = Parameters {
|
||||
kp: 0.055,
|
||||
ki: 0.005,
|
||||
kd: 0.04,
|
||||
output_min: -10.0,
|
||||
output_max: 10.0,
|
||||
integral_min: -100.0,
|
||||
integral_max: 100.0,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_controller() {
|
||||
const DEFAULT: f32 = 0.0;
|
||||
const TARGET: f32 = 1234.56;
|
||||
const ERROR: f32 = 0.01;
|
||||
const DELAY: usize = 10;
|
||||
|
||||
let mut pid = Controller::new(PARAMETERS.clone());
|
||||
pid.set_target(TARGET);
|
||||
|
||||
let mut values = [DEFAULT; DELAY];
|
||||
let mut t = 0;
|
||||
let mut total_t = 0;
|
||||
let target = (TARGET - ERROR)..=(TARGET + ERROR);
|
||||
while !values.iter().all(|value| target.contains(value)) {
|
||||
let next_t = (t + 1) % DELAY;
|
||||
// Feed the oldest temperature
|
||||
let output = pid.update(values[next_t]);
|
||||
// Overwrite oldest with previous temperature + output
|
||||
values[next_t] = values[t] + output;
|
||||
t = next_t;
|
||||
total_t += 1;
|
||||
}
|
||||
dbg!(values[t], total_t);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
use core::ops::Deref;
|
||||
use super::command_parser::{Command, Error as ParserError};
|
||||
use super::CHANNELS;
|
||||
|
||||
const MAX_LINE_LEN: usize = 64;
|
||||
|
||||
struct LineReader {
|
||||
buf: [u8; MAX_LINE_LEN],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl LineReader {
|
||||
pub fn new() -> Self {
|
||||
LineReader {
|
||||
buf: [0; MAX_LINE_LEN],
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn feed(&mut self, c: u8) -> Option<LineResult> {
|
||||
if c == 13 || c == 10 {
|
||||
// Enter
|
||||
if self.pos > 0 {
|
||||
let len = self.pos;
|
||||
self.pos = 0;
|
||||
Some(LineResult {
|
||||
buf: self.buf.clone(),
|
||||
len,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if self.pos < self.buf.len() {
|
||||
// Add input
|
||||
self.buf[self.pos] = c;
|
||||
self.pos += 1;
|
||||
None
|
||||
} else {
|
||||
// Buffer is full, ignore
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LineResult {
|
||||
buf: [u8; MAX_LINE_LEN],
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl Deref for LineResult {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.buf[..self.len]
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SessionOutput {
|
||||
Nothing,
|
||||
Command(Command),
|
||||
Error(ParserError),
|
||||
}
|
||||
|
||||
impl From<Result<Command, ParserError>> for SessionOutput {
|
||||
fn from(input: Result<Command, ParserError>) -> Self {
|
||||
input.map(SessionOutput::Command)
|
||||
.unwrap_or_else(SessionOutput::Error)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Session {
|
||||
reader: LineReader,
|
||||
reporting: bool,
|
||||
report_pending: [bool; CHANNELS],
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn new() -> Self {
|
||||
Session {
|
||||
reader: LineReader::new(),
|
||||
reporting: false,
|
||||
report_pending: [false; CHANNELS],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_dirty(&self) -> bool {
|
||||
self.reader.pos > 0
|
||||
}
|
||||
|
||||
pub fn reporting(&self) -> bool {
|
||||
self.reporting
|
||||
}
|
||||
|
||||
pub fn set_report_pending(&mut self, channel: usize) {
|
||||
if self.reporting {
|
||||
self.report_pending[channel] = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_report_pending(&self) -> Option<usize> {
|
||||
if ! self.reporting {
|
||||
None
|
||||
} else {
|
||||
self.report_pending.iter()
|
||||
.enumerate()
|
||||
.fold(None, |result, (channel, report_pending)| {
|
||||
result.or_else(|| {
|
||||
if *report_pending { Some(channel) } else { None }
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_report_sent(&mut self, channel: usize) {
|
||||
self.report_pending[channel] = false;
|
||||
}
|
||||
|
||||
pub fn feed(&mut self, buf: &[u8]) -> (usize, SessionOutput) {
|
||||
let mut buf_bytes = 0;
|
||||
for (i, b) in buf.iter().enumerate() {
|
||||
buf_bytes = i + 1;
|
||||
let line = self.reader.feed(*b);
|
||||
match line {
|
||||
Some(line) => {
|
||||
let command = Command::parse(&line);
|
||||
match command {
|
||||
Ok(Command::Reporting(reporting)) => {
|
||||
self.reporting = reporting;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return (buf_bytes, command.into());
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
(buf_bytes, SessionOutput::Nothing)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
use libm::F32Ext;
|
||||
|
||||
/// Steinhart-Hart equation parameters
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Parameters {
|
||||
pub a: f32,
|
||||
pub b: f32,
|
||||
pub c: f32,
|
||||
/// Parallel resistance
|
||||
///
|
||||
/// Not truly part of the equation but required to calculate
|
||||
/// resistance from voltage.
|
||||
pub parallel_r: f32,
|
||||
}
|
||||
|
||||
impl Parameters {
|
||||
/// Perform the voltage to temperature conversion.
|
||||
///
|
||||
/// Result unit: Kelvin
|
||||
///
|
||||
/// TODO: verify
|
||||
pub fn get_temperature(&self, voltage: f32) -> f32 {
|
||||
let r = self.parallel_r * voltage;
|
||||
let ln_r = r.abs().ln();
|
||||
let inv_temp = self.a +
|
||||
self.b * ln_r +
|
||||
self.c * ln_r * ln_r * ln_r;
|
||||
1.0 / inv_temp
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,128 @@
|
|||
use core::fmt;
|
||||
use crate::board::pwm::{self, PwmChannel, PwmPeripheral};
|
||||
use crate::board::gpio::{Gpio, GpioOutput, PP2, PP3, PK1, PQ4};
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum TecPin {
|
||||
ISet,
|
||||
MaxIPos,
|
||||
MaxINeg,
|
||||
MaxV,
|
||||
}
|
||||
|
||||
impl TecPin {
|
||||
pub const VALID_VALUES: &'static [TecPin] = &[
|
||||
TecPin::ISet,
|
||||
TecPin::MaxIPos,
|
||||
TecPin::MaxINeg,
|
||||
TecPin::MaxV,
|
||||
];
|
||||
}
|
||||
|
||||
impl fmt::Display for TecPin {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
TecPin::ISet =>
|
||||
"i_set".fmt(fmt),
|
||||
TecPin::MaxIPos =>
|
||||
"max_i_pos".fmt(fmt),
|
||||
TecPin::MaxINeg =>
|
||||
"max_i_neg".fmt(fmt),
|
||||
TecPin::MaxV =>
|
||||
"max_v".fmt(fmt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_shdn<G>(gpio: G) -> GpioOutput<G>
|
||||
where
|
||||
G: Gpio,
|
||||
GpioOutput<G>: OutputPin,
|
||||
{
|
||||
let mut pin = gpio.into_output();
|
||||
// keep off until first use
|
||||
let _ = pin.set_low();
|
||||
pin
|
||||
}
|
||||
|
||||
fn setup_freq<G>(gpio: G)
|
||||
where
|
||||
G: Gpio,
|
||||
GpioOutput<G>: OutputPin,
|
||||
{
|
||||
let mut pin = gpio.into_output();
|
||||
// Switching Frequency Select
|
||||
// high: 1 MHz, low: 500 kHz
|
||||
let _ = pin.set_high();
|
||||
}
|
||||
|
||||
/// Thermo-Electric Cooling device controlled through four PWM
|
||||
/// channels
|
||||
pub struct Tec<MaxIPos: PwmChannel, MaxINeg: PwmChannel, ISet: PwmChannel, MaxV: PwmChannel, SHDN: OutputPin> {
|
||||
max_i_pos: MaxIPos,
|
||||
max_i_neg: MaxINeg,
|
||||
i_set: ISet,
|
||||
max_v: MaxV,
|
||||
shdn: SHDN,
|
||||
}
|
||||
|
||||
impl Tec<pwm::T2CCP0, pwm::T2CCP1, pwm::T3CCP0, pwm::T3CCP1, GpioOutput<PP2>> {
|
||||
pub fn tec0() -> Self {
|
||||
let (max_i_pos, max_i_neg) = tm4c129x::TIMER2::split();
|
||||
let (i_set, max_v) = tm4c129x::TIMER3::split();
|
||||
let shdn = setup_shdn(PP2);
|
||||
setup_freq(PK1);
|
||||
Tec { max_i_pos, max_i_neg, i_set, max_v, shdn }
|
||||
}
|
||||
}
|
||||
|
||||
impl Tec<pwm::T4CCP0, pwm::T4CCP1, pwm::T5CCP0, pwm::T5CCP1, GpioOutput<PP3>> {
|
||||
pub fn tec1() -> Self {
|
||||
let (max_i_pos, max_i_neg) = tm4c129x::TIMER4::split();
|
||||
let (i_set, max_v) = tm4c129x::TIMER5::split();
|
||||
let shdn = setup_shdn(PP3);
|
||||
setup_freq(PQ4);
|
||||
Tec { max_i_pos, max_i_neg, i_set, max_v, shdn }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<MaxIPos: PwmChannel, MaxINeg: PwmChannel, ISet: PwmChannel, MaxV: PwmChannel, SHDN: OutputPin> Tec<MaxIPos, MaxINeg, ISet, MaxV, SHDN> {
|
||||
pub fn setup(mut self, iset_width: u16, max: u16) -> Self {
|
||||
self.max_i_pos.set(max, max);
|
||||
self.max_i_neg.set(max, max);
|
||||
self.max_v.set(max, max);
|
||||
self.i_set.set(iset_width, max);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get(&mut self, pin: TecPin) -> (u16, u16) {
|
||||
match pin {
|
||||
TecPin::MaxIPos =>
|
||||
self.max_i_pos.get(),
|
||||
TecPin::MaxINeg =>
|
||||
self.max_i_neg.get(),
|
||||
TecPin::ISet =>
|
||||
self.i_set.get(),
|
||||
TecPin::MaxV =>
|
||||
self.max_v.get(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, pin: TecPin, width: u16, total: u16) {
|
||||
match pin {
|
||||
TecPin::MaxIPos =>
|
||||
self.max_i_pos.set(width, total),
|
||||
TecPin::MaxINeg =>
|
||||
self.max_i_neg.set(width, total),
|
||||
TecPin::ISet => {
|
||||
self.i_set.set(width, total);
|
||||
// enable on first use
|
||||
let _ = self.shdn.set_high();
|
||||
}
|
||||
TecPin::MaxV =>
|
||||
self.max_v.set(width, total),
|
||||
}
|
||||
}
|
||||
}
|
BIN
proto_rev1.jpg
BIN
proto_rev1.jpg
Binary file not shown.
Before Width: | Height: | Size: 143 KiB |
Binary file not shown.
Before Width: | Height: | Size: 64 KiB |
|
@ -0,0 +1,28 @@
|
|||
let
|
||||
mozillaOverlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz);
|
||||
pkgs = import <nixpkgs> { overlays = [ mozillaOverlay ]; };
|
||||
in
|
||||
with pkgs;
|
||||
let
|
||||
project = callPackage ./default.nix {};
|
||||
in
|
||||
with project;
|
||||
stdenv.mkDerivation {
|
||||
name = "armdev-env";
|
||||
buildInputs = with rustPlatform.rust; [
|
||||
rustc cargo cargo-xbuild
|
||||
rustcSrc
|
||||
pkgsCross.arm-embedded.stdenv.cc
|
||||
openocd
|
||||
];
|
||||
|
||||
# Set Environment Variables
|
||||
RUST_BACKTRACE = 1;
|
||||
XARGO_RUST_SRC = "${rustcSrc}/src";
|
||||
RUST_COMPILER_RT_ROOT = "${rustcSrc}/src/llvm-project/compiler-rt";
|
||||
|
||||
shellHook = ''
|
||||
cd firmware
|
||||
echo "Run 'cargo xbuild --release' to build."
|
||||
'';
|
||||
}
|
Loading…
Reference in New Issue