first tnetplug version
This commit is contained in:
parent
91c27d2499
commit
8b35c4260a
36
Cargo.lock
generated
36
Cargo.lock
generated
@ -1,23 +1,5 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "adc2tcp"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bare-metal 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cortex-m 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cortex-m-log 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cortex-m-rt 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"embedded-hal 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hash2hwaddr 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"panic-abort 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"panic-semihosting 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smoltcp 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"stm32-eth 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"stm32f4xx-hal 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aligned"
|
||||
version = "0.3.1"
|
||||
@ -300,6 +282,24 @@ dependencies = [
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tnetplug"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bare-metal 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cortex-m 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cortex-m-log 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cortex-m-rt 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"embedded-hal 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hash2hwaddr 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"panic-abort 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"panic-semihosting 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smoltcp 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"stm32-eth 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"stm32f4xx-hal 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.10.0"
|
||||
|
15
Cargo.toml
15
Cargo.toml
@ -1,18 +1,13 @@
|
||||
[package]
|
||||
categories = ["embedded", "no-std"]
|
||||
name = "adc2tcp"
|
||||
description = "Poll ADC pin, report over TCP"
|
||||
name = "tnetplug"
|
||||
description = "Trivial network-controlled plugs"
|
||||
license = "GPL-3.0-only"
|
||||
authors = ["Astro <astro@spaceboyz.net>"]
|
||||
version = "0.0.0"
|
||||
keywords = ["ethernet", "eth", "stm32", "adc", "tcp"]
|
||||
repository = "https://github.com/m-labs/adc2tcp"
|
||||
authors = ["Sebastien Bourdeauducq <sb@m-labs.hk>"]
|
||||
version = "0.1.0"
|
||||
keywords = ["ethernet", "eth", "stm32", "smartplug", "internetofshit"]
|
||||
edition = "2018"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "astro/adc2tcp", branch = "master" }
|
||||
maintenance = { status = "experimental" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = []
|
||||
default-target = "thumbv7em-none-eabihf"
|
||||
|
63
README.md
63
README.md
@ -1,66 +1,21 @@
|
||||
# Synopsis
|
||||
|
||||
Exposes readings from an ADC pin (currently: *PA3*) of the board via a
|
||||
TCP service on the Ethernet port.
|
||||
Trivial network-controlled plugs
|
||||
================================
|
||||
|
||||
|
||||
# Network Protocol
|
||||
|
||||
Sensor readings produce lines of `key=value` pairs, joined by `,`,
|
||||
terminated by `"\r\n"`.
|
||||
|
||||
```
|
||||
t=21000,pa3=685
|
||||
t=22000,pa3=684
|
||||
t=23000,pa3=681
|
||||
t=24000,pa3=696
|
||||
t=25000,pa3=673
|
||||
t=26000,pa3=689
|
||||
t=27000,pa3=657
|
||||
t=28000,pa3=654
|
||||
t=29000,pa3=652
|
||||
t=30000,pa3=662
|
||||
t=31000,pa3=663
|
||||
```
|
||||
|
||||
| Key | Value | Unit |
|
||||
|:---:|-------------|------|
|
||||
| t | Time | ms |
|
||||
| pa3 | ADC reading | mV |
|
||||
|
||||
|
||||
# LEDs
|
||||
|
||||
Colors indicate what the MCU is occupied with.
|
||||
|
||||
| Color | Indication |
|
||||
|:-------:|-------------------|
|
||||
| Green | WFI (idle) |
|
||||
| Blue | Network poll |
|
||||
| Red | Message broadcast |
|
||||
|
||||
|
||||
# Crate features
|
||||
|
||||
* `semihosting` enables log output via the **cortex-m-semihosting**
|
||||
crate. Use only in development! MCU will hang when no OpenOCD is
|
||||
running.
|
||||
|
||||
* `generate-hwaddr` generates an Ethernet MAC address by hashing the
|
||||
unique device ID from flash memory.
|
||||
|
||||
|
||||
# Instructions
|
||||
Instructions
|
||||
------------
|
||||
|
||||
![Made for NixOS](https://nixos.org/logo/nixos-lores.png)
|
||||
|
||||
## Build the firmware with `default.nix`
|
||||
Build the firmware with `default.nix`
|
||||
+++++++++++++++++++++++++++++++++++++
|
||||
|
||||
* `nix-build`
|
||||
* This uses **cargo-vendor** to bundle dependencies, so that unstable versions from git can be used.
|
||||
* Run `result/bin/flash-adc2tcp` to flash a devboard with OpenOCD and quit.
|
||||
* Run `result/bin/flash-tnetplug` to flash a devboard with OpenOCD and quit.
|
||||
|
||||
## Development environment with `shell.nix`
|
||||
Development environment with `shell.nix`
|
||||
++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
* `nix-shell`
|
||||
* Spawning `openocd`, the devboard should be connected already.
|
||||
|
12
default.nix
12
default.nix
@ -11,13 +11,13 @@ let
|
||||
rustPlatform = recurseIntoAttrs (callPackage ./nix/rustPlatform.nix {
|
||||
inherit rustManifest;
|
||||
});
|
||||
adc2tcp = callPackage ./nix/adc2tcp.nix { inherit rustPlatform; };
|
||||
tnetplug = callPackage ./nix/tnetplug.nix { inherit rustPlatform; };
|
||||
openocd = callPackage ./nix/openocd.nix {};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
name = "adc2tcp-dist";
|
||||
name = "tnetplug-dist";
|
||||
buildInputs = [
|
||||
adc2tcp
|
||||
tnetplug
|
||||
openocd
|
||||
makeWrapper
|
||||
];
|
||||
@ -26,7 +26,7 @@ stdenv.mkDerivation {
|
||||
|
||||
installPhase =
|
||||
let
|
||||
firmwareBinary = "$out/lib/adc2tcp.elf";
|
||||
firmwareBinary = "$out/lib/tnetplug.elf";
|
||||
openOcdFlags = [
|
||||
"-c" "reset halt"
|
||||
"-c" "flash write_image erase ${firmwareBinary}"
|
||||
@ -37,9 +37,9 @@ stdenv.mkDerivation {
|
||||
in ''
|
||||
mkdir -p $out/bin $out/lib $out/nix-support
|
||||
|
||||
ln -s ${adc2tcp}/lib/adc2tcp ${firmwareBinary}
|
||||
ln -s ${tnetplug}/lib/tnetplug ${firmwareBinary}
|
||||
|
||||
makeWrapper ${openocd}/bin/openocd-nucleo-f429zi $out/bin/flash-adc2tcp \
|
||||
makeWrapper ${openocd}/bin/openocd-nucleo-f429zi $out/bin/flash-tnetplug \
|
||||
--add-flags "${lib.escapeShellArgs openOcdFlags}"
|
||||
|
||||
echo file binary-dist ${firmwareBinary} >> $out/nix-support/hydra-build-products
|
||||
|
@ -2,33 +2,33 @@
|
||||
|
||||
with rustPlatform;
|
||||
let
|
||||
sha256 = "1i9p5d5n01ajbp8lmavyway6vr1mmy107qnccff9glvr91rqx352";
|
||||
sha256 = "19cdc0lkm9247n6kf23ki66gysz530j1x2lfnzq7n0cpcs53q3h3";
|
||||
fetchcargo = import ./fetchcargo.nix {
|
||||
inherit stdenv cacert git cargo-vendor;
|
||||
inherit (rust) cargo;
|
||||
};
|
||||
adc2tcpDeps = fetchcargo {
|
||||
name = "adc2tcp";
|
||||
tnetplugDeps = fetchcargo {
|
||||
name = "tnetplug";
|
||||
src = ../.;
|
||||
inherit sha256;
|
||||
};
|
||||
in
|
||||
|
||||
buildRustPackage rec {
|
||||
name = "adc2tcp";
|
||||
name = "tnetplug";
|
||||
version = "0.0.0";
|
||||
|
||||
src = ../.;
|
||||
cargoSha256 = sha256;
|
||||
|
||||
buildInputs = [ adc2tcpDeps ];
|
||||
buildInputs = [ tnetplugDeps ];
|
||||
patchPhase = ''
|
||||
cat >> .cargo/config <<EOF
|
||||
[source.crates-io]
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source.vendored-sources]
|
||||
directory = "${adc2tcpDeps}"
|
||||
directory = "${tnetplugDeps}"
|
||||
EOF
|
||||
'';
|
||||
|
||||
@ -40,6 +40,6 @@ buildRustPackage rec {
|
||||
doCheck = false;
|
||||
installPhase = ''
|
||||
mkdir -p $out/lib
|
||||
cp target/thumbv7em-none-eabihf/release/adc2tcp $out/lib/
|
||||
cp target/thumbv7em-none-eabihf/release/tnetplug $out/lib/
|
||||
'';
|
||||
}
|
@ -5,11 +5,11 @@
|
||||
|
||||
with pkgs;
|
||||
let
|
||||
adc2tcp = callPackage ./default.nix {
|
||||
tnetplug = callPackage ./default.nix {
|
||||
inherit rustManifest;
|
||||
mozillaOverlay = import <mozillaOverlay>;
|
||||
};
|
||||
in
|
||||
{
|
||||
build = lib.hydraJob adc2tcp;
|
||||
build = lib.hydraJob tnetplug;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ let
|
||||
openocd = callPackage ./nix/openocd.nix {};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
name = "adc2tcp-env";
|
||||
name = "tnetplug-env";
|
||||
buildInputs = with rustPlatform.rust; [
|
||||
rustc cargo pkgs.gdb
|
||||
];
|
||||
|
@ -1,46 +0,0 @@
|
||||
use stm32f4xx_hal::{
|
||||
adc::{
|
||||
Adc,
|
||||
config::*,
|
||||
},
|
||||
gpio::{Analog, gpioa::PA3 as Pin},
|
||||
stm32::ADC1 as ADC,
|
||||
};
|
||||
|
||||
/// ADC Input
|
||||
pub struct AdcInput {
|
||||
/// unused but consumed
|
||||
_pin: Pin<Analog>,
|
||||
adc: Adc<ADC>,
|
||||
}
|
||||
|
||||
impl AdcInput {
|
||||
/// Configure pin into analog mode
|
||||
pub fn new<MODE>(adc: ADC, pin: Pin<MODE>) -> Self {
|
||||
let pin = pin.into_analog();
|
||||
let adc_config = AdcConfig::default()
|
||||
.scan(Scan::Enabled)
|
||||
.continuous(Continuous::Single)
|
||||
.clock(Clock::Pclk2_div_2);
|
||||
let mut adc = Adc::adc1(adc, true, adc_config);
|
||||
|
||||
adc.configure_channel(&pin, Sequence::One, SampleTime::Cycles_480);
|
||||
|
||||
AdcInput { _pin: pin, adc }
|
||||
}
|
||||
|
||||
/// Enable the ADC,
|
||||
/// run a conversion
|
||||
/// disable the ADC
|
||||
pub fn read(&mut self) -> u16 {
|
||||
let adc = &mut self.adc;
|
||||
adc.enable();
|
||||
adc.clear_end_of_conversion_flag();
|
||||
adc.start_conversion();
|
||||
let sample = adc.current_sample();
|
||||
let result = adc.sample_to_millivolts(sample);
|
||||
adc.wait_for_conversion_sequence();
|
||||
adc.disable();
|
||||
result
|
||||
}
|
||||
}
|
46
src/led.rs
46
src/led.rs
@ -1,46 +0,0 @@
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use stm32f4xx_hal::gpio::{
|
||||
Output, PushPull,
|
||||
gpiob::{PB0, PB7, PB14},
|
||||
};
|
||||
|
||||
type GreenPin = PB0<Output<PushPull>>;
|
||||
type BluePin = PB7<Output<PushPull>>;
|
||||
type RedPin = PB14<Output<PushPull>>;
|
||||
|
||||
pub struct Led<PIN> {
|
||||
pin: PIN,
|
||||
}
|
||||
|
||||
impl<PIN: OutputPin> Led<PIN> {
|
||||
fn new(pin: PIN) -> Self {
|
||||
Led { pin }
|
||||
}
|
||||
|
||||
pub fn on(&mut self) {
|
||||
self.pin.set_high();
|
||||
}
|
||||
|
||||
pub fn off(&mut self) {
|
||||
self.pin.set_low();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Led<GreenPin> {
|
||||
pub fn green(pin: GreenPin) -> Self {
|
||||
Self::new(pin)
|
||||
}
|
||||
}
|
||||
|
||||
impl Led<BluePin> {
|
||||
pub fn blue(pin: BluePin) -> Self {
|
||||
Self::new(pin)
|
||||
}
|
||||
}
|
||||
|
||||
impl Led<RedPin> {
|
||||
pub fn red(pin: RedPin) -> Self {
|
||||
Self::new(pin)
|
||||
}
|
||||
}
|
42
src/main.rs
42
src/main.rs
@ -13,6 +13,7 @@ use core::fmt::Write;
|
||||
use cortex_m::asm::wfi;
|
||||
use cortex_m_rt::entry;
|
||||
use embedded_hal::watchdog::{WatchdogEnable, Watchdog};
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use stm32f4xx_hal::{
|
||||
rcc::RccExt,
|
||||
gpio::GpioExt,
|
||||
@ -25,20 +26,11 @@ use smoltcp::{
|
||||
wire::EthernetAddress,
|
||||
};
|
||||
|
||||
mod adc_input;
|
||||
use adc_input::AdcInput;
|
||||
mod net;
|
||||
mod server;
|
||||
use server::Server;
|
||||
mod timer;
|
||||
mod led;
|
||||
use led::Led;
|
||||
|
||||
/// Interval at which to sample the ADC input and broadcast to all
|
||||
/// clients.
|
||||
///
|
||||
/// This should be a multiple of the `TIMER_RATE`.
|
||||
const OUTPUT_INTERVAL: u32 = 1000;
|
||||
|
||||
#[cfg(not(feature = "generate-hwaddr"))]
|
||||
const NET_HWADDR: [u8; 6] = [0x02, 0x00, 0xDE, 0xAD, 0xBE, 0xEF];
|
||||
@ -67,7 +59,7 @@ fn init_log() {
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
init_log();
|
||||
info!("adc2tcp");
|
||||
info!("tnetplug");
|
||||
|
||||
let mut cp = CorePeripherals::take().unwrap();
|
||||
cp.SCB.enable_icache();
|
||||
@ -90,14 +82,11 @@ fn main() -> ! {
|
||||
let gpioa = dp.GPIOA.split();
|
||||
let gpiob = dp.GPIOB.split();
|
||||
let gpioc = dp.GPIOC.split();
|
||||
let gpioe = dp.GPIOE.split();
|
||||
let gpiog = dp.GPIOG.split();
|
||||
|
||||
let mut led_green = Led::green(gpiob.pb0.into_push_pull_output());
|
||||
let mut led_blue = Led::blue(gpiob.pb7.into_push_pull_output());
|
||||
let mut led_red = Led::red(gpiob.pb14.into_push_pull_output());
|
||||
|
||||
info!("ADC init");
|
||||
let mut adc_input = AdcInput::new(dp.ADC1, gpioa.pa3);
|
||||
let mut relay1 = gpioe.pe11.into_push_pull_output();
|
||||
let mut relay2 = gpioe.pe9.into_push_pull_output();
|
||||
|
||||
info!("Eth setup");
|
||||
stm32_eth::setup_pins(
|
||||
@ -120,25 +109,22 @@ fn main() -> ! {
|
||||
info!("Net startup");
|
||||
net::run(&mut cp.NVIC, dp.ETHERNET_MAC, dp.ETHERNET_DMA, hwaddr, |iface| {
|
||||
Server::run(iface, |server| {
|
||||
let mut last_output = 0_u32;
|
||||
loop {
|
||||
let now = timer::now().0;
|
||||
let instant = Instant::from_millis(i64::from(now));
|
||||
led_blue.on();
|
||||
cortex_m::interrupt::free(net::clear_pending);
|
||||
server.poll(instant)
|
||||
let cmd = server.poll(instant)
|
||||
.unwrap_or_else(|e| {
|
||||
warn!("poll: {:?}", e);
|
||||
None
|
||||
});
|
||||
led_blue.off();
|
||||
|
||||
let now = timer::now().0;
|
||||
if now - last_output >= OUTPUT_INTERVAL {
|
||||
led_red.on();
|
||||
let adc_value = adc_input.read();
|
||||
writeln!(server, "t={},pa3={}\r", now, adc_value).unwrap();
|
||||
last_output = now;
|
||||
led_red.off();
|
||||
match cmd {
|
||||
Some(b'a') => { relay1.set_low(); writeln!(server, "A=OFF").unwrap(); },
|
||||
Some(b'A') => { relay1.set_high(); writeln!(server, "A=ON").unwrap(); },
|
||||
Some(b'b') => { relay2.set_low(); writeln!(server, "B=OFF").unwrap(); },
|
||||
Some(b'B') => { relay2.set_high(); writeln!(server, "B=ON").unwrap(); },
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Update watchdog
|
||||
@ -146,10 +132,8 @@ fn main() -> ! {
|
||||
|
||||
cortex_m::interrupt::free(|cs| {
|
||||
if !net::is_pending(cs) {
|
||||
led_green.on();
|
||||
// Wait for interrupts
|
||||
wfi();
|
||||
led_green.off();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ pub fn run<F>(
|
||||
eth_dev.enable_interrupt(nvic);
|
||||
|
||||
// IP stack
|
||||
let local_addr = IpAddress::v4(192, 168, 69, 3);
|
||||
let local_addr = IpAddress::v4(192, 168, 1, 31);
|
||||
let mut ip_addrs = [IpCidr::new(local_addr, 24)];
|
||||
let mut neighbor_storage = [None; 16];
|
||||
let neighbor_cache = NeighborCache::new(&mut neighbor_storage[..]);
|
||||
|
@ -7,7 +7,7 @@ use smoltcp::{
|
||||
};
|
||||
|
||||
|
||||
const TCP_PORT: u16 = 23;
|
||||
const TCP_PORT: u16 = 3131;
|
||||
/// Number of server sockets and therefore concurrent client
|
||||
/// sessions. Many data structures in `Server::run()` correspond to
|
||||
/// this const.
|
||||
@ -32,8 +32,6 @@ macro_rules! create_socket {
|
||||
)
|
||||
}
|
||||
|
||||
/// Contains a number of server sockets that get all sent the same
|
||||
/// data (through `fmt::Write`).
|
||||
pub struct Server<'a, 'b> {
|
||||
net: EthernetInterface<'a, 'a, 'a, &'a mut stm32_eth::Eth<'static, 'static>>,
|
||||
sockets: SocketSet<'b, 'b, 'static>,
|
||||
@ -79,7 +77,7 @@ impl<'a, 'b> Server<'a, 'b> {
|
||||
}
|
||||
|
||||
/// Poll the interface and the sockets
|
||||
pub fn poll(&mut self, now: Instant) -> Result<(), smoltcp::Error> {
|
||||
pub fn poll(&mut self, now: Instant) -> Result<Option<u8>, smoltcp::Error> {
|
||||
// Poll smoltcp EthernetInterface
|
||||
let mut poll_error = None;
|
||||
let activity = self.net.poll(&mut self.sockets, now)
|
||||
@ -88,23 +86,33 @@ impl<'a, 'b> Server<'a, 'b> {
|
||||
true
|
||||
});
|
||||
|
||||
// Pass some smoltcp errors to the caller
|
||||
match poll_error {
|
||||
None => (),
|
||||
Some(smoltcp::Error::Malformed) => (),
|
||||
Some(smoltcp::Error::Unrecognized) => (),
|
||||
Some(e) => return Err(e),
|
||||
}
|
||||
|
||||
let mut ret = None;
|
||||
if activity {
|
||||
// Listen on all sockets
|
||||
for handle in &self.handles {
|
||||
let mut socket = self.sockets.get::<TcpSocket>(*handle);
|
||||
if ! socket.is_open() {
|
||||
let _ = socket.listen(TCP_PORT);
|
||||
if !socket.is_open() {
|
||||
socket.listen(TCP_PORT).unwrap();
|
||||
}
|
||||
|
||||
if socket.may_recv() {
|
||||
if ret.is_none() {
|
||||
ret = socket.recv(|data| { if data.len() > 0 { (1, Some(data[0])) } else { (0, None) } }).unwrap();
|
||||
}
|
||||
} else if socket.may_send() {
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass some smoltcp errors to the caller
|
||||
match poll_error {
|
||||
None => Ok(()),
|
||||
Some(smoltcp::Error::Malformed) => Ok(()),
|
||||
Some(smoltcp::Error::Unrecognized) => Ok(()),
|
||||
Some(e) => Err(e),
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user