Merge branch 'master' into new-pac

* master:
  refactor server
  cleanup, robustify
  iir: remove old setting
  frontend: cleanup
  cargo: use published serde-json-core
  cargo: drop lexical
This commit is contained in:
Robert Jördens 2019-05-28 11:02:26 +00:00
commit 9dff71a0c4
6 changed files with 100 additions and 91 deletions

26
Cargo.lock generated
View File

@ -122,15 +122,6 @@ dependencies = [
"hash32 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "hash32 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "lexical-core"
version = "0.4.0"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.6" version = "0.4.6"
@ -208,11 +199,6 @@ dependencies = [
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "ryu"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "semver" name = "semver"
version = "0.9.0" version = "0.9.0"
@ -237,9 +223,9 @@ dependencies = [
[[package]] [[package]]
name = "serde-json-core" name = "serde-json-core"
version = "0.0.1" version = "0.0.1"
source = "git+https://github.com/quartiq/serde-json-core.git?rev=6143ac2#6143ac229ec3f5382555f37fe673413c8b46b0cf"
dependencies = [ dependencies = [
"heapless 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "heapless 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"lexical-core 0.4.0",
"serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -275,7 +261,7 @@ dependencies = [
"panic-abort 0.3.1 (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)", "panic-semihosting 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)",
"serde-json-core 0.0.1", "serde-json-core 0.0.1 (git+https://github.com/quartiq/serde-json-core.git?rev=6143ac2)",
"smoltcp 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "smoltcp 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"stm32h7 0.7.0", "stm32h7 0.7.0",
] ]
@ -285,11 +271,6 @@ name = "stable_deref_trait"
version = "1.1.1" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "static_assertions"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "stm32h7" name = "stm32h7"
version = "0.7.0" version = "0.7.0"
@ -360,14 +341,13 @@ dependencies = [
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" "checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "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 semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "a72e9b96fa45ce22a4bc23da3858dfccfd60acd28a25bcd328a98fdd6bea43fd" "checksum serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "a72e9b96fa45ce22a4bc23da3858dfccfd60acd28a25bcd328a98fdd6bea43fd"
"checksum serde-json-core 0.0.1 (git+https://github.com/quartiq/serde-json-core.git?rev=6143ac2)" = "<none>"
"checksum serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "101b495b109a3e3ca8c4cbe44cf62391527cdfb6ba15821c5ce80bcd5ea23f9f" "checksum serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "101b495b109a3e3ca8c4cbe44cf62391527cdfb6ba15821c5ce80bcd5ea23f9f"
"checksum smoltcp 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fef582369edb298c6c41319a544ca9c4e83622f226055ccfcb35974fbb55ed34" "checksum smoltcp 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fef582369edb298c6c41319a544ca9c4e83622f226055ccfcb35974fbb55ed34"
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
"checksum static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c19be23126415861cb3a23e501d34a708f7f9b2183c5252d690941c2e69199d5"
"checksum syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)" = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe" "checksum syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)" = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe"
"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"

View File

@ -33,13 +33,13 @@ cortex-m-log = { version = "0.5", features = ["log-integration"] }
log = "0.4" log = "0.4"
panic-abort = "0.3" panic-abort = "0.3"
panic-semihosting = { version = "0.5.2", optional = true } panic-semihosting = { version = "0.5.2", optional = true }
serde-json-core = { version = "0.0" }
serde = { version = "1.0", features = ["derive"], default-features = false } serde = { version = "1.0", features = ["derive"], default-features = false }
heapless = { version = "0.4" } heapless = { version = "0.4" }
[patch.crates-io] [dependencies.serde-json-core]
serde-json-core = { path = "../serde-json-core" } # version = "0.0"
lexical-core = { path = "../rust-lexical/lexical-core" } git = "https://github.com/quartiq/serde-json-core.git"
rev = "6143ac2"
[dependencies.stm32h7] [dependencies.stm32h7]
path = "../stm32-rs/stm32h7" path = "../stm32-rs/stm32h7"
@ -47,7 +47,8 @@ path = "../stm32-rs/stm32h7"
features = ["stm32h7x3", "rt"] features = ["stm32h7x3", "rt"]
[dependencies.smoltcp] [dependencies.smoltcp]
#git = "https://github.com/m-labs/smoltcp" #git = "https://github.com/m-labs/smoltcp.git"
#rev = "cd893e6"
version = "0.5" version = "0.5"
features = ["proto-ipv4", "socket-tcp"] features = ["proto-ipv4", "socket-tcp"]
default-features = false default-features = false

View File

@ -9,14 +9,20 @@
* dual channel * dual channel
* SPI ADC * SPI ADC
* SPI DAC * SPI DAC
* fixed AFE gains * 500 kHz rate, timed sampling
* 500 kHz rate, timed * 2 µs latency, unmatched between channels
* < 2 µs latency, unmatched
* f32 IIR math * f32 IIR math
* generic biquad (second order) IIR filter * generic biquad (second order) IIR filter
* anti-windup * anti-windup
* derivative kick avoidance * derivative kick avoidance
* configurable output limits
## Limitations/TODOs
* Fixed AFE gains
* The IP and MAC address are [hardcoded](src/main.rs)
* Expose configurable limits
* 100Base-T only
* Digital IO, GPIO header, AFE header, EEM header are not handled
## Hardware ## Hardware
@ -33,3 +39,10 @@ See https://github.com/sinara-hw/Stabilizer
* `rustup target add thumbv7em-none-eabihf` * `rustup target add thumbv7em-none-eabihf`
* `openocd -f stabilizer.cfg` and leave it running * `openocd -f stabilizer.cfg` and leave it running
* `cargo run --release` * `cargo run --release`
## Protocol
Stabilizer can be configured via newline-delimited JSON over TCP.
It listens on port 1235. [stabilizer.py](stabilizer.py) contains a reference
implementation of the protocol.

View File

@ -39,6 +39,7 @@ fn macc<T>(y0: T, x: &[T], a: &[T]) -> T
x.iter().zip(a.iter()).map(|(&i, &j)| i * j).fold(y0, |y, xa| y + xa) x.iter().zip(a.iter()).map(|(&i, &j)| i * j).fold(y0, |y, xa| y + xa)
} }
#[allow(unused)]
impl IIR { impl IIR {
pub fn set_pi(&mut self, kp: f32, ki: f32, g: f32) -> Result<(), &str> { pub fn set_pi(&mut self, kp: f32, ki: f32, g: f32) -> Result<(), &str> {
let ki = copysign(ki, kp); let ki = copysign(ki, kp);

View File

@ -601,16 +601,6 @@ fn main() -> ! {
let dbgmcu = dp.DBGMCU; let dbgmcu = dp.DBGMCU;
dbgmcu.apb1lfz1.modify(|_, w| w.tim2().set_bit()); dbgmcu.apb1lfz1.modify(|_, w| w.tim2().set_bit());
unsafe {
let t = 2e-6*2.;
IIR_CH[0].set_pi(1., 0., 0.).unwrap();
IIR_CH[0].set_x_offset(0.*SCALE);
IIR_CH[1].set_pi(-0.1, -10.*t, 0.).unwrap();
IIR_CH[1].set_x_offset(0.1*SCALE);
IIR_CH[1].get_x_offset().unwrap();
}
eth::setup(&rcc, &dp.SYSCFG); eth::setup(&rcc, &dp.SYSCFG);
eth::setup_pins(&dp.GPIOA, &dp.GPIOB, &dp.GPIOC, &dp.GPIOG); eth::setup_pins(&dp.GPIOA, &dp.GPIOB, &dp.GPIOC, &dp.GPIOG);
@ -644,6 +634,7 @@ fn main() -> ! {
}); });
let mut last = 0; let mut last = 0;
let mut server = Server::new();
loop { loop {
// if ETHERNET_PENDING.swap(false, Ordering::Relaxed) { } // if ETHERNET_PENDING.swap(false, Ordering::Relaxed) { }
let time = TIME.load(Ordering::Relaxed); let time = TIME.load(Ordering::Relaxed);
@ -653,14 +644,7 @@ fn main() -> ! {
socket.listen(1234).unwrap_or_else(|e| warn!("TCP listen error: {:?}", e)); socket.listen(1234).unwrap_or_else(|e| warn!("TCP listen error: {:?}", e));
} else if last != time && socket.can_send() { } else if last != time && socket.can_send() {
last = time; last = time;
let s = unsafe { Status{ handle_status(socket, time);
t: time,
x0: IIR_STATE[0][0],
y0: IIR_STATE[0][2],
x1: IIR_STATE[1][0],
y1: IIR_STATE[1][2],
}};
send_response(socket, &s);
} }
} }
{ {
@ -668,7 +652,7 @@ fn main() -> ! {
if !(socket.is_open() || socket.is_listening()) { if !(socket.is_open() || socket.is_listening()) {
socket.listen(1235).unwrap_or_else(|e| warn!("TCP listen error: {:?}", e)); socket.listen(1235).unwrap_or_else(|e| warn!("TCP listen error: {:?}", e));
} else { } else {
handle_command(socket); server.handle_command(socket);
} }
} }
@ -684,7 +668,7 @@ fn main() -> ! {
#[derive(Deserialize,Serialize)] #[derive(Deserialize,Serialize)]
struct Request { struct Request {
channel: i8, channel: u8,
iir: IIR, iir: IIR,
} }
@ -694,52 +678,79 @@ struct Response<'a> {
message: &'a str, message: &'a str,
} }
fn send_response<T: Serialize>(socket: &mut net::socket::TcpSocket, msg: &T) { fn reply<T: Serialize>(socket: &mut net::socket::TcpSocket, msg: &T) {
let mut u: String<U128> = to_string(msg).unwrap(); let mut u: String<U128> = to_string(msg).unwrap();
u.push('\n').unwrap(); u.push('\n').unwrap();
socket.write_str(&u).unwrap(); socket.write_str(&u).unwrap();
} }
fn handle_command(socket: &mut net::socket::TcpSocket) { struct Server {
let mut data: Vec<u8, U256> = Vec::new(); data: Vec<u8, U256>,
let mut discard: bool = false; discard: bool,
}
impl Server {
fn new() -> Self {
Self { data: Vec::new(), discard: false }
}
fn handle_command(&mut self, socket: &mut net::socket::TcpSocket) {
while socket.can_recv() { while socket.can_recv() {
if socket.recv(|buf| { let found = socket.recv(|buf| {
let (len, found) = match buf.iter().position(|&c| c as char == '\n') { let (len, found) = match buf.iter().position(|&c| c as char == '\n') {
Some(end) => (end + 1, true), Some(end) => (end + 1, true),
None => (buf.len(), false), None => (buf.len(), false),
}; };
if data.len() + len >= data.capacity() { if self.data.len() + len >= self.data.capacity() {
discard = true; self.discard = true;
data.clear(); self.data.clear();
} else if !discard && len > 0 { } else if !self.discard && len > 0 {
data.extend_from_slice(&buf[..len - 1]).unwrap(); self.data.extend_from_slice(&buf[..len - 1]).unwrap();
} }
(len, found) (len, found)
}).unwrap() { }).unwrap();
let resp = if discard { if !found {
discard = false; continue;
Response{ code: 500, message: "command buffer overflow" } }
let resp = if self.discard {
self.discard = false;
Response{ code: 520, message: "command buffer overflow" }
} else { } else {
match from_slice::<Request>(&data) { match from_slice::<Request>(&self.data) {
Ok(request) => { Ok(request) => {
if request.channel > 1 {
Response{ code: 530, message: "invalid channel" }
} else {
cortex_m::interrupt::free(|_| { cortex_m::interrupt::free(|_| {
unsafe { IIR_CH[request.channel as usize] = request.iir; }; unsafe { IIR_CH[request.channel as usize] = request.iir; };
}); });
Response{ code: 200, message: "ok" } Response{ code: 200, message: "ok" }
}
}, },
Err(err) => { Err(err) => {
warn!("parse error {}", err); warn!("parse error {:?}", err);
Response{ code: 550, message: "parse error" } Response{ code: 550, message: "parse error" }
}, },
} }
}; };
send_response(socket, &resp); self.data.clear();
reply(socket, &resp);
socket.close(); socket.close();
} }
} }
} }
fn handle_status(socket: &mut net::socket::TcpSocket, time: u32) {
let s = unsafe { Status{
t: time,
x0: IIR_STATE[0][0],
y0: IIR_STATE[0][2],
x1: IIR_STATE[1][0],
y1: IIR_STATE[1][2],
}};
reply(socket, &s);
}
#[derive(Serialize)] #[derive(Serialize)]
struct Status { struct Status {
t: u32, t: u32,
@ -753,7 +764,7 @@ const SCALE: f32 = ((1 << 15) - 1) as f32;
static mut IIR_STATE: [IIRState; 2] = [[0.; 5]; 2]; static mut IIR_STATE: [IIRState; 2] = [[0.; 5]; 2];
static mut IIR_CH: [IIR; 2] = [ static mut IIR_CH: [IIR; 2] = [
IIR{ ba: [0., 0., 0., 0., 0.], y_offset: 0., IIR{ ba: [0., 0., 0., 0., 0.], y_offset: 0.,
y_min: -SCALE, y_max: SCALE }; 2]; y_min: -SCALE - 1., y_max: SCALE }; 2];
// seems to slow it down // seems to slow it down
// #[link_section = ".data.spi1"] // #[link_section = ".data.spi1"]

View File

@ -1,6 +1,6 @@
import json import json
import asyncio import asyncio
from collections import OrderedDict from collections import OrderedDict as OD
import logging import logging
import numpy as np import numpy as np
@ -17,14 +17,14 @@ class StabilizerConfig:
self.reader, self.writer = await asyncio.open_connection(host, port) self.reader, self.writer = await asyncio.open_connection(host, port)
async def set(self, channel, iir): async def set(self, channel, iir):
up = OrderedDict([("channel", channel), ("iir", iir.as_dict())]) up = OD([("channel", channel), ("iir", iir.as_dict())])
s = json.dumps(up, separators=(",", ":")) s = json.dumps(up, separators=(",", ":"))
assert "\n" not in s assert "\n" not in s
logger.debug("send %s", s) logger.debug("send %s", s)
self.writer.write(s.encode() + b"\n") self.writer.write(s.encode() + b"\n")
r = (await self.reader.readline()).decode() r = (await self.reader.readline()).decode()
logger.debug("recv %s", r) logger.debug("recv %s", r)
ret = json.loads(r, object_pairs_hook=OrderedDict) ret = json.loads(r, object_pairs_hook=OD)
if ret["code"] != 200: if ret["code"] != 200:
raise StabilizerError(ret) raise StabilizerError(ret)
return ret return ret
@ -32,15 +32,16 @@ class StabilizerConfig:
class IIR: class IIR:
t_update = 2e-6 t_update = 2e-6
full_scale = float((1 << 15) - 1)
def __init__(self): def __init__(self):
self.ba = np.zeros(5, np.float32) self.ba = np.zeros(5, np.float32)
self.y_offset = 0. self.y_offset = 0.
self.y_min = -float(1 << 15) self.y_min = -self.full_scale - 1
self.y_max = float(1 << 15) - 1 self.y_max = self.full_scale
def as_dict(self): def as_dict(self):
iir = OrderedDict() iir = OD()
iir["ba"] = [float(_) for _ in self.ba] iir["ba"] = [float(_) for _ in self.ba]
iir["y_offset"] = self.y_offset iir["y_offset"] = self.y_offset
iir["y_min"] = self.y_min iir["y_min"] = self.y_min
@ -70,7 +71,7 @@ class IIR:
self.ba[4] = 0. self.ba[4] = 0.
def set_x_offset(self, o): def set_x_offset(self, o):
b = self.ba[:3].sum() b = self.ba[:3].sum()*self.full_scale
self.y_offset = b*o self.y_offset = b*o
@ -81,11 +82,12 @@ if __name__ == "__main__":
p.add_argument("-c", "--channel", default=0, type=int, p.add_argument("-c", "--channel", default=0, type=int,
help="Stabilizer channel to configure") help="Stabilizer channel to configure")
p.add_argument("-o", "--offset", default=0., type=float, p.add_argument("-o", "--offset", default=0., type=float,
help="X offset, in ADC LSB") help="input offset, in units of full scale")
p.add_argument("-p", "--proportional-gain", default=1., type=float, p.add_argument("-p", "--proportional-gain", default=1., type=float,
help="Proportional gain, in DAC LSB/ADC LSB") help="Proportional gain, in units of 1")
p.add_argument("-i", "--integral-gain", default=0., type=float, p.add_argument("-i", "--integral-gain", default=0., type=float,
help="Integral gain, in DAC LSB/(ADC LSB*s)") help="Integral gain, in units of Hz, "
"sign taken from proportional-gain")
args = p.parse_args() args = p.parse_args()
@ -99,6 +101,7 @@ if __name__ == "__main__":
i.set_x_offset(args.offset) i.set_x_offset(args.offset)
s = StabilizerConfig() s = StabilizerConfig()
await s.connect(args.stabilizer) await s.connect(args.stabilizer)
r = await s.set(0, i) assert args.channel in range(2)
r = await s.set(args.channel, i)
loop.run_until_complete(main()) loop.run_until_complete(main())