Compare commits
No commits in common. "5b62dd300b96affd2f0813da0b51c3c1f567bc53" and "0e3328aac1143ab1ca057cdb742c31217c69dbb9" have entirely different histories.
5b62dd300b
...
0e3328aac1
33
README.md
33
README.md
@ -5,39 +5,6 @@ improved passive sonar data acquisition system. The goal of this system is
|
|||||||
simply to be able to capture pinger data, filtered by the preprocessor board,
|
simply to be able to capture pinger data, filtered by the preprocessor board,
|
||||||
via ethernet, at significantly faster speed than the previous system.
|
via ethernet, at significantly faster speed than the previous system.
|
||||||
|
|
||||||
## Pysonar
|
|
||||||
|
|
||||||
This provides a library to interface with the FPGA, both pulling data and configuring it. To install, simply `cd`
|
|
||||||
into this repo and:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pip install ./
|
|
||||||
```
|
|
||||||
|
|
||||||
A CLI/console to test configuration is provided as `sonar_config`. Usage can be found with
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sonar_config --help
|
|
||||||
```
|
|
||||||
|
|
||||||
*Note: This is a pure python package. In an ideal world, I would have implemented all protocol
|
|
||||||
implementation in rust, and exported via PyO3, to keep potential changes in sync, but it's not
|
|
||||||
worth the implementation effort and potential installation difficulty here.*
|
|
||||||
|
|
||||||
## Hacky hacks to patch into migen
|
|
||||||
|
|
||||||
migen.fhdl.simplify.py (line 81): add the attributeerror exception
|
|
||||||
|
|
||||||
```
|
|
||||||
for port in mem.ports:
|
|
||||||
try:
|
|
||||||
sync = f.sync[port.clock.cd]
|
|
||||||
except KeyError:
|
|
||||||
sync = f.sync[port.clock.cd] = []
|
|
||||||
except AttributeError:
|
|
||||||
sync = f.sync["sys"]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Repo Layout
|
## Repo Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
|
7
firmware/Cargo.lock
generated
7
firmware/Cargo.lock
generated
@ -50,12 +50,6 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crc-any"
|
|
||||||
version = "2.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "774646b687f63643eb0f4bf13dc263cb581c8c9e57973b6ddf78bda3994d88df"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "critical-section"
|
name = "critical-section"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@ -108,7 +102,6 @@ dependencies = [
|
|||||||
name = "fw"
|
name = "fw"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc-any",
|
|
||||||
"defmt",
|
"defmt",
|
||||||
"embedded-hal",
|
"embedded-hal",
|
||||||
"panic-halt",
|
"panic-halt",
|
||||||
|
@ -10,13 +10,12 @@ riscv-rt = "0.11.0"
|
|||||||
riscv = "0.10.1"
|
riscv = "0.10.1"
|
||||||
panic-halt = "0.2.0"
|
panic-halt = "0.2.0"
|
||||||
embedded-hal = "0.2.7"
|
embedded-hal = "0.2.7"
|
||||||
defmt = { version = "0.3.4", features = ["encoding-raw"] }
|
defmt = {version = "0.3.4", features = ["encoding-raw"] }
|
||||||
crc-any = { version = "2.4", default-features = false }
|
|
||||||
|
|
||||||
[dependencies.smoltcp]
|
[dependencies.smoltcp]
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["medium-ethernet", "proto-ipv4", "socket-tcp", "socket-icmp", "defmt"]
|
features = ["medium-ethernet", "proto-ipv4", "socket-icmp", "defmt"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
use smoltcp::socket::tcp::{Socket, State};
|
|
||||||
|
|
||||||
use crate::proto::{
|
|
||||||
serialize_response_error, serialize_response_value, PacketParser, ResponsePacket, Settings, ErrorCodes
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CommandInterface {
|
|
||||||
parser: PacketParser,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CommandInterface {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
parser: PacketParser::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run the command interface
|
|
||||||
pub fn run(&mut self, sock: &mut Socket) {
|
|
||||||
// Note that we don't attempt to check status of the connection. All of
|
|
||||||
// the smoltcp functions handle the error cases gracefully. Recv
|
|
||||||
// provides a buffer of length 0 when there is nothing to receive, so we
|
|
||||||
// are good there. We just reset on any smoltcp errors.
|
|
||||||
|
|
||||||
// TODO should put packet parsing behind a fn with the smoltcp result, then on smoltcp error
|
|
||||||
// we reconfigure here instead of calling it 4 times
|
|
||||||
|
|
||||||
// Try and parse a packet
|
|
||||||
let res = sock.recv(|rx_buf| {
|
|
||||||
let (res, processed_bytes) = self.parser.try_parse(rx_buf);
|
|
||||||
|
|
||||||
(processed_bytes, res.ok())
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for socket errors
|
|
||||||
let packet = match res {
|
|
||||||
Ok(Some(packet)) => packet,
|
|
||||||
// No packet to process
|
|
||||||
Ok(None) => return,
|
|
||||||
// Any socket error means we just want to drop the connection and re-connect
|
|
||||||
Err(_) => {
|
|
||||||
Self::reestablish_socket(sock);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check that setting is valid
|
|
||||||
let setting = match Settings::try_from(packet.setting) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(()) => {
|
|
||||||
// Write out an error packet
|
|
||||||
let result = sock.send(|tx_buf| {
|
|
||||||
if tx_buf.len() < 8 {
|
|
||||||
return (0, ());
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = serialize_response_error(packet.setting, ErrorCodes::InvalidSetting);
|
|
||||||
&tx_buf[0..8].copy_from_slice(&response);
|
|
||||||
return (8, ());
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Err(_) = result {
|
|
||||||
// TX error, close socket and reconnect
|
|
||||||
Self::reestablish_socket(sock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO validate setting values
|
|
||||||
|
|
||||||
// TODO handle actually changing/getting settings in all the ways
|
|
||||||
// For temp testing, return values sent
|
|
||||||
match sock.send(|tx_buf| {
|
|
||||||
if tx_buf.len() < 8 {
|
|
||||||
// Since this is a low-BW configuration socket, we assume we have space, and drop
|
|
||||||
// the response otherwise. If this is an issue, may re-think this and queue commands but
|
|
||||||
// for now this is fine.
|
|
||||||
return (0, ());
|
|
||||||
}
|
|
||||||
let response = serialize_response_value(setting, packet.value);
|
|
||||||
&tx_buf[0..8].copy_from_slice(&response);
|
|
||||||
return (8, ());
|
|
||||||
}) {
|
|
||||||
Ok(()) => (),
|
|
||||||
Err(_) => {
|
|
||||||
Self::reestablish_socket(sock);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reestablish_socket(sock: &mut Socket) {
|
|
||||||
sock.abort();
|
|
||||||
sock.listen(2000);
|
|
||||||
}
|
|
||||||
}
|
|
@ -28,8 +28,6 @@ mod mcp4726;
|
|||||||
mod uart;
|
mod uart;
|
||||||
mod litex_uart;
|
mod litex_uart;
|
||||||
mod logging;
|
mod logging;
|
||||||
mod proto;
|
|
||||||
mod command_interface;
|
|
||||||
|
|
||||||
const MAC: [u8; 6] = [0xA0, 0xBB, 0xCC, 0xDD, 0xEE, 0xF0];
|
const MAC: [u8; 6] = [0xA0, 0xBB, 0xCC, 0xDD, 0xEE, 0xF0];
|
||||||
|
|
||||||
|
@ -1,229 +0,0 @@
|
|||||||
//! # Configuration protocol
|
|
||||||
//!
|
|
||||||
//! Configuration and runtime management is done over a separate TCP port, to
|
|
||||||
//! simplify management. It follows a simple binary protocol, described here.
|
|
||||||
//!
|
|
||||||
//! The protocol is packet-based, 2 bytes start sequence, 1 byte command, 4 byte
|
|
||||||
//! data, 1 byte CRC. CRC includes the start sequence, and is MAXIM-DOW. Chosen
|
|
||||||
//! because the crc python package had it as well.
|
|
||||||
//!
|
|
||||||
//! The 4 bytes of data are little-endian.
|
|
||||||
//!
|
|
||||||
//! For the settings that can be set, see [Settings].
|
|
||||||
//!
|
|
||||||
//! ## Sending a command
|
|
||||||
//!
|
|
||||||
//! The command is simply the index of the setting to read or write, with the
|
|
||||||
//! top bit set to indicate a write. Set the value to 0 during a read.
|
|
||||||
//!
|
|
||||||
//! ## Receiving a response
|
|
||||||
//!
|
|
||||||
//! The command is the index of the setting given, with the top bit set if it's
|
|
||||||
//! an error. The value is either the value saved in the setting, or an error
|
|
||||||
//! code. Error codes are defined in [ErrorCodes]
|
|
||||||
|
|
||||||
/// Start sequence to indicate the beginning of a packet.
|
|
||||||
const START_SEQUENCE: [u8; 2] = [0xDE, 0xAD];
|
|
||||||
|
|
||||||
/// Possible return values from a command
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum ErrorCodes {
|
|
||||||
/// Command accepted
|
|
||||||
Success = 0,
|
|
||||||
/// Value was not valid for the provided setting
|
|
||||||
ValueOutOfRange = 1,
|
|
||||||
/// Setting index provided does not exist
|
|
||||||
InvalidSetting = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Setting indices
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum Settings {
|
|
||||||
/// Trigger threshold (in ADC counts, peak to peak is 1024)
|
|
||||||
TriggerThreshold = 0,
|
|
||||||
/// Number of samples that diff must be higher than threshold to trigger
|
|
||||||
TriggerPeriod = 1,
|
|
||||||
/// Value to decay difference every DecayPeriod cycles
|
|
||||||
DecayValue = 2,
|
|
||||||
/// The number of cycles between every decay
|
|
||||||
DecayPeriod = 3,
|
|
||||||
/// Gain setting of preprocessor (value in dB)
|
|
||||||
Gain = 4,
|
|
||||||
/// Center frequency to set preprocessor filter to (value in Hz)
|
|
||||||
CenterFreq = 5,
|
|
||||||
/// Sampling enabled, 1 to enable, 0 to disable
|
|
||||||
SamplingEnabled = 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Start sequence incorrect, no packet
|
|
||||||
BadStartSequence,
|
|
||||||
/// CRC was bad
|
|
||||||
BadCrc,
|
|
||||||
/// State machine error
|
|
||||||
StateMachineError,
|
|
||||||
/// No packet found in data
|
|
||||||
NoPacket,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<u8> for Settings {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
0 => Ok(Self::TriggerThreshold),
|
|
||||||
1 => Ok(Self::TriggerPeriod),
|
|
||||||
2 => Ok(Self::DecayValue),
|
|
||||||
3 => Ok(Self::DecayPeriod),
|
|
||||||
4 => Ok(Self::Gain),
|
|
||||||
5 => Ok(Self::CenterFreq),
|
|
||||||
6 => Ok(Self::SamplingEnabled),
|
|
||||||
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CommandPacket {
|
|
||||||
pub is_write: bool,
|
|
||||||
pub setting: u8,
|
|
||||||
pub value: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ResponsePacket {
|
|
||||||
pub is_error: bool,
|
|
||||||
pub setting: u8,
|
|
||||||
pub value: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serialize a valid response
|
|
||||||
pub fn serialize_response_value(setting: Settings, value: u32) -> [u8; 8] {
|
|
||||||
serialize_response(setting as u8, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serialize an error response
|
|
||||||
pub fn serialize_response_error(setting: u8, error: ErrorCodes) -> [u8; 8] {
|
|
||||||
serialize_response(0x80 | setting, error as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serialize_response(command: u8, value: u32) -> [u8; 8] {
|
|
||||||
let mut crc = crc_any::CRCu8::crc8maxim();
|
|
||||||
let mut buf = [0u8; 8];
|
|
||||||
|
|
||||||
// Construct packet data
|
|
||||||
&buf[0..2].copy_from_slice(&START_SEQUENCE);
|
|
||||||
buf[2] = command;
|
|
||||||
for i in 0..4 {
|
|
||||||
buf[3 + i] = (value >> (8 * i)) as u8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run CRC and append value
|
|
||||||
crc.digest(&buf[0..7]);
|
|
||||||
buf[7] = crc.get_crc();
|
|
||||||
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PacketParser {
|
|
||||||
packet_index: u8,
|
|
||||||
crc: crc_any::CRCu8,
|
|
||||||
|
|
||||||
is_write: bool,
|
|
||||||
setting: u8,
|
|
||||||
value: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PacketParser {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
packet_index: 0,
|
|
||||||
crc: crc_any::CRCu8::crc8maxim(),
|
|
||||||
|
|
||||||
is_write: false,
|
|
||||||
setting: 0,
|
|
||||||
value: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try and parse a packet from a received buffer. Returns Some(packet) if a
|
|
||||||
/// packet was found, and the number of bytes processed from the buffer. Bytes
|
|
||||||
/// processed may be smaller than the buffer length if a packet was found.
|
|
||||||
pub fn try_parse(&mut self, buf: &[u8]) -> (Result<CommandPacket, Error>, usize) {
|
|
||||||
let mut bytes_processed = 0;
|
|
||||||
for byte in buf {
|
|
||||||
bytes_processed += 1;
|
|
||||||
match self.process_byte(*byte) {
|
|
||||||
Ok(packet) => return (Ok(packet), bytes_processed),
|
|
||||||
|
|
||||||
// We don't care to pass errors to the user
|
|
||||||
// TODO log
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(Err(Error::NoPacket), bytes_processed)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Process incoming byte using a simple state machine.
|
|
||||||
pub fn process_byte(&mut self, byte: u8) -> Result<CommandPacket, Error> {
|
|
||||||
match self.packet_index {
|
|
||||||
// Check the start sequence
|
|
||||||
0 => {
|
|
||||||
if byte != START_SEQUENCE[0] {
|
|
||||||
self.reset();
|
|
||||||
return Err(Error::BadStartSequence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1 => {
|
|
||||||
if byte != START_SEQUENCE[1] {
|
|
||||||
self.reset();
|
|
||||||
return Err(Error::BadStartSequence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save data
|
|
||||||
2 => {
|
|
||||||
self.is_write = byte & 0x80 != 0;
|
|
||||||
self.setting = byte & 0x7F;
|
|
||||||
}
|
|
||||||
3..=6 => {
|
|
||||||
self.value |= (byte << (8 * (self.packet_index - 3))) as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check CRC
|
|
||||||
7 => {
|
|
||||||
if self.crc.get_crc() == byte {
|
|
||||||
return Ok(CommandPacket {
|
|
||||||
is_write: self.is_write,
|
|
||||||
setting: self.setting,
|
|
||||||
value: self.value,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
self.reset();
|
|
||||||
return Err(Error::BadCrc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should be unreachable, just reset
|
|
||||||
_ => {
|
|
||||||
self.reset();
|
|
||||||
return Err(Error::StateMachineError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment index
|
|
||||||
self.packet_index += 1;
|
|
||||||
// Process CRC (sucks I can't just give it one byte)
|
|
||||||
self.crc.digest(&[byte]);
|
|
||||||
|
|
||||||
Err(Error::NoPacket)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset values to start parsing a packet again
|
|
||||||
fn reset(&mut self) {
|
|
||||||
self.packet_index = 0;
|
|
||||||
self.crc = crc_any::CRCu8::crc8maxim();
|
|
||||||
self.value = 0;
|
|
||||||
}
|
|
||||||
}
|
|
@ -27,7 +27,7 @@ from litedram.phy import GENSDRPHY, HalfRateGENSDRPHY
|
|||||||
|
|
||||||
from liteeth.phy.ecp5rgmii import LiteEthPHYRGMII
|
from liteeth.phy.ecp5rgmii import LiteEthPHYRGMII
|
||||||
|
|
||||||
from sampler import SamplerController, Sampler
|
from sampler import Sampler
|
||||||
from litex.soc.integration.soc import SoCRegion
|
from litex.soc.integration.soc import SoCRegion
|
||||||
|
|
||||||
from test import run_test, TestResult, skip_suite
|
from test import run_test, TestResult, skip_suite
|
||||||
@ -143,11 +143,11 @@ class BaseSoC(SoCCore):
|
|||||||
if with_video_framebuffer:
|
if with_video_framebuffer:
|
||||||
self.add_video_framebuffer(phy=self.videophy, timings="800x600@60Hz", clock_domain="hdmi")
|
self.add_video_framebuffer(phy=self.videophy, timings="800x600@60Hz", clock_domain="hdmi")
|
||||||
|
|
||||||
#samplers = [Sampler(platform.request("adc", i)) for i in range(3)]
|
self.submodules.sampler = Sampler(platform.request("adc"), self.crg.cd_sample_clock.clk)
|
||||||
#self.submodules.sampler_controller = SamplerController(samplers, buffer_len=2048 * 10)
|
sampler_region = SoCRegion(origin=None, size=0x1000, cached=False)
|
||||||
### TODO better way to do this?
|
#self.add_wb_slave(0x9000_0000, self.sampler.bus, 0x1000)
|
||||||
#sampler_region = SoCRegion(origin=None, size=0x4000, cached=False)
|
## TODO better way to do this?
|
||||||
#self.bus.add_slave(name="sampler", slave=self.sampler_controller.bus, region=sampler_region)
|
#self.bus.add_slave(name="sampler", slave=self.sampler.bus, region=sampler_region)
|
||||||
|
|
||||||
# Build --------------------------------------------------------------------------------------------
|
# Build --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -185,7 +185,6 @@ def main():
|
|||||||
results.append(run_test("SamplerController", controller.test_bus_access))
|
results.append(run_test("SamplerController", controller.test_bus_access))
|
||||||
results.append(run_test("SamplerController", controller.test_simple_waveform))
|
results.append(run_test("SamplerController", controller.test_simple_waveform))
|
||||||
results.append(run_test("SamplerController", controller.test_simple_waveform_capture_offset))
|
results.append(run_test("SamplerController", controller.test_simple_waveform_capture_offset))
|
||||||
results.append(run_test("SamplerController", controller.test_multiple_reads))
|
|
||||||
results.append(run_test("PeakDetector", peak_detector.test_simple_waveform))
|
results.append(run_test("PeakDetector", peak_detector.test_simple_waveform))
|
||||||
results.append(run_test("PeakDetector", peak_detector.test_scrunched_simple_waveform))
|
results.append(run_test("PeakDetector", peak_detector.test_scrunched_simple_waveform))
|
||||||
results.append(run_test("PeakDetector", peak_detector.test_decay_simple_waveform))
|
results.append(run_test("PeakDetector", peak_detector.test_decay_simple_waveform))
|
||||||
@ -200,9 +199,8 @@ def main():
|
|||||||
|
|
||||||
print(f"{passed}/{passed + failed} passed ({skipped} skipped)")
|
print(f"{passed}/{passed + failed} passed ({skipped} skipped)")
|
||||||
|
|
||||||
if failed > 0 or not args.build:
|
# TODO maybe don't do this?
|
||||||
# Don't also build after this
|
return
|
||||||
return
|
|
||||||
|
|
||||||
# Build firmware
|
# Build firmware
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
|
@ -19,7 +19,7 @@ from litex.build.lattice.programmer import EcpDapProgrammer
|
|||||||
|
|
||||||
# IOs ----------------------------------------------------------------------------------------------
|
# IOs ----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
_io_v7_0 = [ # Colorlight i9 documented by @smunaut
|
_io_v7_0 = [ # Documented by @smunaut
|
||||||
# Clk
|
# Clk
|
||||||
("clk25", 0, Pins("P3"), IOStandard("LVCMOS33")),
|
("clk25", 0, Pins("P3"), IOStandard("LVCMOS33")),
|
||||||
|
|
||||||
@ -117,42 +117,13 @@ _io_v7_0 = [ # Colorlight i9 documented by @smunaut
|
|||||||
|
|
||||||
# High speed parallel ADCs
|
# High speed parallel ADCs
|
||||||
("adc", 0,
|
("adc", 0,
|
||||||
# Rev A pins
|
Subsignal("data", Pins("M18 N18 N17 P18 U17 U18 T17 M17 P17 R17")),
|
||||||
#Subsignal("data", Pins("M18 N18 N17 P18 U17 U18 T17 M17 P17 R17")),
|
# TODO ???? what other pins are changed in 7.2
|
||||||
# Rev B pins
|
Subsignal("refclk", Pins("L2")),
|
||||||
Subsignal("data", Pins("L20 M18 N18 N17 P18 U17 U18 T17 M17 P17")),
|
Subsignal("oen_b", Pins("K18")),
|
||||||
Subsignal("refclk", Pins("K18")),
|
Subsignal("standby", Pins("C18")),
|
||||||
Subsignal("oen_b", Pins("C18")),
|
Subsignal("dfs", Pins("T18")),
|
||||||
Subsignal("standby", Pins("T18")),
|
Subsignal("otr", Pins("R18")),
|
||||||
Subsignal("dfs", Pins("R18")),
|
|
||||||
Subsignal("otr", Pins("R17")),
|
|
||||||
IOStandard("LVCMOS33")
|
|
||||||
),
|
|
||||||
("adc", 1,
|
|
||||||
Subsignal("data", Pins("R1 T1 U1 Y2 W1 V1 M1 N2 N3 T2")),
|
|
||||||
Subsignal("refclk", Pins("M4")),
|
|
||||||
Subsignal("oen_b", Pins("N4")),
|
|
||||||
Subsignal("standby", Pins("R3")),
|
|
||||||
Subsignal("dfs", Pins("T3")),
|
|
||||||
Subsignal("otr", Pins("M3")),
|
|
||||||
IOStandard("LVCMOS33")
|
|
||||||
),
|
|
||||||
("adc", 2,
|
|
||||||
Subsignal("data", Pins("A18 C17 A19 B18 B19 B20 C20 D19 D20 E19")),
|
|
||||||
Subsignal("refclk", Pins("J19")),
|
|
||||||
Subsignal("oen_b", Pins("H20")),
|
|
||||||
Subsignal("standby", Pins("G20")),
|
|
||||||
Subsignal("dfs", Pins("G19")),
|
|
||||||
Subsignal("otr", Pins("F20")),
|
|
||||||
IOStandard("LVCMOS33")
|
|
||||||
),
|
|
||||||
("adc", 3,
|
|
||||||
Subsignal("data", Pins("E4 F1 F3 G3 H3 H4 H5 J4 J5 K3")),
|
|
||||||
Subsignal("refclk", Pins("M4")),
|
|
||||||
Subsignal("oen_b", Pins("N4")),
|
|
||||||
Subsignal("standby", Pins("B3")),
|
|
||||||
Subsignal("dfs", Pins("K5")),
|
|
||||||
Subsignal("otr", Pins("K4")),
|
|
||||||
IOStandard("LVCMOS33")
|
IOStandard("LVCMOS33")
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -175,7 +146,9 @@ _io_v7_2 = copy.deepcopy(_io_v7_0)
|
|||||||
|
|
||||||
for i, x in enumerate(_io_v7_2):
|
for i, x in enumerate(_io_v7_2):
|
||||||
if x[:2] == ("user_led_n", 0):
|
if x[:2] == ("user_led_n", 0):
|
||||||
_io_v7_2[i] = ("user_led_n", 0, Pins("L2"), IOStandard("LVCMOS33"))
|
# TODO fix in HW
|
||||||
|
#_io_v7_2[i] = ("user_led_n", 0, Pins("L2"), IOStandard("LVCMOS33"))
|
||||||
|
_io_v7_2[i] = ("user_led_n", 0, Pins("J19"), IOStandard("LVCMOS33"))
|
||||||
break
|
break
|
||||||
|
|
||||||
# optional, alternative uart location
|
# optional, alternative uart location
|
||||||
|
@ -34,23 +34,14 @@ class CircularBuffer(Module):
|
|||||||
rd_ptr = Signal(ptr_width)
|
rd_ptr = Signal(ptr_width)
|
||||||
empty = Signal(reset=1) # Extra signal to distinguish between full and empty condition
|
empty = Signal(reset=1) # Extra signal to distinguish between full and empty condition
|
||||||
|
|
||||||
# TODO this shouldn't be needed. Bug in migen IMO, I don't use this signal
|
|
||||||
dat_r = Signal(width)
|
|
||||||
|
|
||||||
# Hook write input signals to memory
|
# Hook write input signals to memory
|
||||||
wr_port = storage.get_port(write_capable=True)
|
wr_port = storage.get_port(write_capable=True)
|
||||||
# TODO hacky bullshit because migen is broken or I'm using it wrong
|
|
||||||
if not hasattr(wr_port.clock, "cd"):
|
|
||||||
wr_port.clock.cd = "sys"
|
|
||||||
|
|
||||||
# Always ready to write data into memory, so hook these signals straight in
|
# Always ready to write data into memory, so hook these signals straight in
|
||||||
self.comb += [
|
self.comb += [
|
||||||
wr_port.adr.eq(wr_ptr),
|
wr_port.adr.eq(wr_ptr),
|
||||||
wr_port.dat_w.eq(self.wr_data),
|
wr_port.dat_w.eq(self.wr_data),
|
||||||
wr_port.we.eq(self.wr_valid),
|
wr_port.we.eq(self.wr_valid),
|
||||||
self.wr_ready.eq(1), # We are always ready to write data in
|
self.wr_ready.eq(1), # We are always ready to write data in
|
||||||
|
|
||||||
dat_r.eq(wr_port.dat_r)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Advance write (and potentially read)
|
# Advance write (and potentially read)
|
||||||
@ -75,10 +66,6 @@ class CircularBuffer(Module):
|
|||||||
|
|
||||||
# TODO should I actually set async_read?
|
# TODO should I actually set async_read?
|
||||||
rd_port = storage.get_port(async_read=True)
|
rd_port = storage.get_port(async_read=True)
|
||||||
# TODO hacky bullshit because migen is broken or I'm using it wrong
|
|
||||||
if not hasattr(rd_port.clock, "cd"):
|
|
||||||
rd_port.clock.cd = "sys"
|
|
||||||
|
|
||||||
# Set read addr so 0 starts at rd_ptr and wraps around, and connect read data up
|
# Set read addr so 0 starts at rd_ptr and wraps around, and connect read data up
|
||||||
self.comb += [
|
self.comb += [
|
||||||
If(self.rd_addr + rd_ptr < depth,
|
If(self.rd_addr + rd_ptr < depth,
|
||||||
|
@ -27,14 +27,11 @@ class SamplerController(Module):
|
|||||||
|
|
||||||
Registers
|
Registers
|
||||||
--------
|
--------
|
||||||
0x00: Control Register (WO)
|
0x00: Control Register (RW)
|
||||||
Bit 0 - Start capture
|
Bit 0 - Begin capture. Resets all FIFOs and starts the peak detector
|
||||||
Bit 1 - Stop capture. Does nothing if capture is not ongoing
|
|
||||||
Bit 2 - Clear sample buffers
|
|
||||||
|
|
||||||
0x01: Status Register (RO)
|
0x01: Status Register (RO)
|
||||||
Bit 0 - Capture complete. Set by peak detection block and cleared when capture is began
|
Bit 0 - Capture complete. Set by peak detection block and cleared when capture is began
|
||||||
Bit 1 - Sampling running
|
|
||||||
|
|
||||||
0x02: trigger_run_len (RW)
|
0x02: trigger_run_len (RW)
|
||||||
Number of samples to acquire after triggering sample.
|
Number of samples to acquire after triggering sample.
|
||||||
@ -141,7 +138,7 @@ class SamplerController(Module):
|
|||||||
|
|
||||||
# Handle explicit config registers
|
# Handle explicit config registers
|
||||||
cases = {
|
cases = {
|
||||||
0: rw_register(control_register, read=False),
|
0: rw_register(control_register),
|
||||||
1: rw_register(status_register, write=False),
|
1: rw_register(status_register, write=False),
|
||||||
2: rw_register(trigger_run_len),
|
2: rw_register(trigger_run_len),
|
||||||
3: rw_register(self.peak_detector.thresh_value),
|
3: rw_register(self.peak_detector.thresh_value),
|
||||||
@ -159,8 +156,6 @@ class SamplerController(Module):
|
|||||||
# Connect up control registers bus
|
# Connect up control registers bus
|
||||||
self.sync += [
|
self.sync += [
|
||||||
self.control_regs_bus.ack.eq(0),
|
self.control_regs_bus.ack.eq(0),
|
||||||
# Hold control register low to use as strobe functionality
|
|
||||||
control_register.eq(0),
|
|
||||||
If(self.control_regs_bus.cyc & self.control_regs_bus.stb,
|
If(self.control_regs_bus.cyc & self.control_regs_bus.stb,
|
||||||
self.control_regs_bus.ack.eq(1),
|
self.control_regs_bus.ack.eq(1),
|
||||||
Case(self.control_regs_bus.adr, cases)),
|
Case(self.control_regs_bus.adr, cases)),
|
||||||
@ -185,20 +180,13 @@ class SamplerController(Module):
|
|||||||
# We have sampled enough, update status and stop sampling
|
# We have sampled enough, update status and stop sampling
|
||||||
If(post_trigger_count + 1 >= trigger_run_len,
|
If(post_trigger_count + 1 >= trigger_run_len,
|
||||||
status_register[0].eq(1),
|
status_register[0].eq(1),
|
||||||
sample_enable.eq(0))),
|
control_register[0].eq(0))),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Update register storage
|
# Update register storage
|
||||||
self.sync += [
|
self.comb += [
|
||||||
status_register[1].eq(sample_enable),
|
sample_enable.eq(control_register[0]),
|
||||||
If(control_register[0], sample_enable.eq(1)),
|
|
||||||
If(control_register[1], sample_enable.eq(0)),
|
|
||||||
]
|
]
|
||||||
for buffer in self.buffers:
|
|
||||||
self.sync += [
|
|
||||||
buffer.clear.eq(0),
|
|
||||||
If(control_register[2], buffer.clear.eq(1)),
|
|
||||||
]
|
|
||||||
|
|
||||||
def write_wishbone(bus, address, value):
|
def write_wishbone(bus, address, value):
|
||||||
# Set up bus
|
# Set up bus
|
||||||
@ -353,11 +341,6 @@ def test_simple_waveform():
|
|||||||
sample = (yield dut.bus.dat_r)
|
sample = (yield dut.bus.dat_r)
|
||||||
data.append(sample)
|
data.append(sample)
|
||||||
|
|
||||||
# Manually validated, this is what we should read on a correct
|
|
||||||
# run
|
|
||||||
assert data[15] == 138
|
|
||||||
assert data[16] == 132
|
|
||||||
|
|
||||||
# Test pass
|
# Test pass
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -435,172 +418,3 @@ def test_simple_waveform_capture_offset():
|
|||||||
assert False, "We should have triggered"
|
assert False, "We should have triggered"
|
||||||
|
|
||||||
run_simulation(dut, test_fn())
|
run_simulation(dut, test_fn())
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_reads():
|
|
||||||
"""
|
|
||||||
Testing multiple triggers/captures in succession to ensure typical (i.e. repeated) operation
|
|
||||||
works correctly.
|
|
||||||
"""
|
|
||||||
# Enable, trigger works correctly
|
|
||||||
# Enable, tick a bit of data in, should not trigger, and trigger should have reset immediately
|
|
||||||
# Enable again, and tick in lots of data, should trigger again now
|
|
||||||
"""Test a simple waveform captured at an offset"""
|
|
||||||
from .peak_detector import create_waveform
|
|
||||||
_, data = create_waveform()
|
|
||||||
data = [int(d) for d in data]
|
|
||||||
dut = TestSoC(data, buffer_len=32)
|
|
||||||
|
|
||||||
def test_fn():
|
|
||||||
# Set settings
|
|
||||||
yield from write_wishbone(dut.bus, 2, 0) # trigger_run_len = 0
|
|
||||||
yield from write_wishbone(dut.bus, 3, 800) # thresh_value = 800
|
|
||||||
yield from write_wishbone(dut.bus, 4, 10) # thresh_time = 10
|
|
||||||
yield from write_wishbone(dut.bus, 5, 1) # decay_value = 1
|
|
||||||
yield from write_wishbone(dut.bus, 5, 0) # decay_period = 0
|
|
||||||
|
|
||||||
# Start controller
|
|
||||||
yield from write_wishbone(dut.bus, 0, 1)
|
|
||||||
|
|
||||||
triggered_yet = False
|
|
||||||
triggered_num = 0
|
|
||||||
for i in range(1000):
|
|
||||||
(yield dut.samplers[0].index.eq(i))
|
|
||||||
(yield dut.samplers[0].valid.eq(1))
|
|
||||||
yield
|
|
||||||
|
|
||||||
(yield dut.samplers[0].valid.eq(0))
|
|
||||||
yield
|
|
||||||
|
|
||||||
# Total of 6 clocks per sample clock
|
|
||||||
yield
|
|
||||||
yield
|
|
||||||
yield
|
|
||||||
yield
|
|
||||||
|
|
||||||
if not triggered_yet and (yield dut.controller.peak_detector.triggered) == 1:
|
|
||||||
# Triggered, now we need to run some number of cycles
|
|
||||||
triggered_yet = True
|
|
||||||
|
|
||||||
if triggered_yet:
|
|
||||||
triggered_num += 1
|
|
||||||
if triggered_num > 16:
|
|
||||||
# We should now have collected all our samples
|
|
||||||
yield from read_wishbone(dut.bus, 1)
|
|
||||||
assert (yield dut.bus.dat_r) == 1, "Trigger did not propogate to WB!"
|
|
||||||
|
|
||||||
# Check that length is correct
|
|
||||||
yield from read_wishbone(dut.bus, 0x100)
|
|
||||||
len = (yield dut.bus.dat_r)
|
|
||||||
assert len == 32, f"Len ({len}) not correct!"
|
|
||||||
|
|
||||||
# Read data in
|
|
||||||
data = []
|
|
||||||
for i in range(32):
|
|
||||||
yield from read_wishbone(dut.bus, 0x800 + i)
|
|
||||||
sample = (yield dut.bus.dat_r)
|
|
||||||
data.append(sample)
|
|
||||||
|
|
||||||
|
|
||||||
# Manually validated from test above to be offset into the
|
|
||||||
# data
|
|
||||||
assert data[15] == 138
|
|
||||||
assert data[16] == 132
|
|
||||||
break
|
|
||||||
|
|
||||||
assert triggered_yet, "We should have triggered"
|
|
||||||
|
|
||||||
# Clear out sampler and re-enable
|
|
||||||
yield from write_wishbone(dut.bus, 0, 0b101)
|
|
||||||
yield
|
|
||||||
|
|
||||||
assert (yield dut.controller.peak_detector.triggered) == 0, "Trigger should have been cleared"
|
|
||||||
assert (yield dut.controller.buffers[0].len) == 0, "Buffers should have been cleared"
|
|
||||||
|
|
||||||
# Tick a few clocks through, and we shouldn't have triggered
|
|
||||||
for i in range(10):
|
|
||||||
(yield dut.samplers[0].index.eq(i))
|
|
||||||
(yield dut.samplers[0].valid.eq(1))
|
|
||||||
yield
|
|
||||||
|
|
||||||
(yield dut.samplers[0].valid.eq(0))
|
|
||||||
yield
|
|
||||||
|
|
||||||
# Total of 6 clocks per sample clock
|
|
||||||
yield
|
|
||||||
yield
|
|
||||||
yield
|
|
||||||
yield
|
|
||||||
|
|
||||||
assert (yield dut.controller.peak_detector.triggered) == 0, "We didn't push enough data through to trigger"
|
|
||||||
|
|
||||||
# Disable sampler, run lots of data through, we should not trigger
|
|
||||||
yield from write_wishbone(dut.bus, 0, 0b010)
|
|
||||||
for i in range(1000):
|
|
||||||
(yield dut.samplers[0].index.eq(i))
|
|
||||||
(yield dut.samplers[0].valid.eq(1))
|
|
||||||
yield
|
|
||||||
|
|
||||||
(yield dut.samplers[0].valid.eq(0))
|
|
||||||
yield
|
|
||||||
|
|
||||||
# Total of 6 clocks per sample clock
|
|
||||||
yield
|
|
||||||
yield
|
|
||||||
yield
|
|
||||||
yield
|
|
||||||
|
|
||||||
# Enable sampler and run again, we should get another trigger
|
|
||||||
yield from write_wishbone(dut.bus, 2, 16) # trigger_run_len = 16
|
|
||||||
yield from write_wishbone(dut.bus, 0, 1)
|
|
||||||
triggered_yet = False
|
|
||||||
triggered_num = 0
|
|
||||||
for i in range(1000):
|
|
||||||
(yield dut.samplers[0].index.eq(i))
|
|
||||||
(yield dut.samplers[0].valid.eq(1))
|
|
||||||
yield
|
|
||||||
|
|
||||||
(yield dut.samplers[0].valid.eq(0))
|
|
||||||
yield
|
|
||||||
|
|
||||||
# Total of 6 clocks per sample clock
|
|
||||||
yield
|
|
||||||
yield
|
|
||||||
yield
|
|
||||||
yield
|
|
||||||
|
|
||||||
if not triggered_yet and (yield dut.controller.peak_detector.triggered) == 1:
|
|
||||||
# Triggered, now we need to run some number of cycles
|
|
||||||
triggered_yet = True
|
|
||||||
|
|
||||||
if triggered_yet:
|
|
||||||
triggered_num += 1
|
|
||||||
if triggered_num > 16:
|
|
||||||
# We should now have collected all our samples
|
|
||||||
yield from read_wishbone(dut.bus, 1)
|
|
||||||
assert (yield dut.bus.dat_r) == 1, "Trigger did not propogate to WB!"
|
|
||||||
|
|
||||||
# Check that length is correct
|
|
||||||
yield from read_wishbone(dut.bus, 0x100)
|
|
||||||
len = (yield dut.bus.dat_r)
|
|
||||||
assert len == 32, f"Len ({len}) not correct!"
|
|
||||||
|
|
||||||
# Read data in
|
|
||||||
data = []
|
|
||||||
for i in range(32):
|
|
||||||
yield from read_wishbone(dut.bus, 0x800 + i)
|
|
||||||
sample = (yield dut.bus.dat_r)
|
|
||||||
data.append(sample)
|
|
||||||
|
|
||||||
|
|
||||||
# Manually validated from test above to be offset into the
|
|
||||||
# data
|
|
||||||
assert data[0] == 138
|
|
||||||
assert data[1] == 132
|
|
||||||
|
|
||||||
# Test pass
|
|
||||||
return
|
|
||||||
|
|
||||||
assert triggered_yet, "We should have triggered"
|
|
||||||
|
|
||||||
run_simulation(dut, test_fn(), vcd_name="controller.vcd")
|
|
||||||
|
@ -3,9 +3,14 @@ from migen.genlib.cdc import PulseSynchronizer
|
|||||||
|
|
||||||
|
|
||||||
class Sampler(Module):
|
class Sampler(Module):
|
||||||
def __init__(self, adc_pins: Record):
|
def __init__(self, adc_pins: Record, sampler_clock: Signal):
|
||||||
|
# self.clock_domains.foo = ClockDomain() is how to add a new clock domain, accessible at self.foo
|
||||||
|
# Connect sampler clock domain
|
||||||
|
self.clock_domains.sample_clock = ClockDomain("sample_clock")
|
||||||
|
self.comb += self.sample_clock.clk.eq(sampler_clock)
|
||||||
|
|
||||||
# Hook up ADC REFCLK to sample_clock
|
# Hook up ADC REFCLK to sample_clock
|
||||||
self.comb += adc_pins.refclk.eq(ClockDomain("sample_clock").clk)
|
self.comb += adc_pins.refclk.eq(sampler_clock)
|
||||||
|
|
||||||
# We can synchronize to the sampler clock, whenever it goes high we can
|
# We can synchronize to the sampler clock, whenever it goes high we can
|
||||||
# strobe a single valid signal
|
# strobe a single valid signal
|
||||||
@ -16,10 +21,10 @@ class Sampler(Module):
|
|||||||
self.data = Signal(10)
|
self.data = Signal(10)
|
||||||
|
|
||||||
self.comb += [
|
self.comb += [
|
||||||
synchronizer.i.eq(ClockDomain("sample_clock").clk),
|
synchronizer.i.eq(self.sample_clock.clk),
|
||||||
self.valid.eq(synchronizer.o),
|
self.valid.eq(synchronizer.o),
|
||||||
|
self.data.eq(adc_pins.data),
|
||||||
]
|
]
|
||||||
self.sync += self.data.eq(adc_pins.data)
|
|
||||||
|
|
||||||
# Set config pins to constant values
|
# Set config pins to constant values
|
||||||
self.comb += adc_pins.oen_b.eq(0) # Data pins enable
|
self.comb += adc_pins.oen_b.eq(0) # Data pins enable
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
# Built from this documentation: https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html
|
|
||||||
|
|
||||||
[project]
|
|
||||||
name = "pysonar"
|
|
||||||
description = "Library to communicate with ARVP's sonar FPGA"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = [
|
|
||||||
{ name = "David Lenfesty", email = "lenfesty@ualberta.ca" }
|
|
||||||
]
|
|
||||||
requires-python = ">=3.9"
|
|
||||||
dependencies = ["crc>=4.2"]
|
|
||||||
|
|
||||||
[project.scripts]
|
|
||||||
# Export CLI to configure FPGA
|
|
||||||
sonar_config = "pysonar:main"
|
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["setuptools", "setuptools-scm"]
|
|
||||||
build-backend = "setuptools.build_meta"
|
|
||||||
|
|
||||||
[tool.setuptools]
|
|
||||||
# Just include the python directory
|
|
||||||
packages = ["pysonar"]
|
|
@ -1,2 +0,0 @@
|
|||||||
def main():
|
|
||||||
print("Hello world!")
|
|
@ -1,108 +0,0 @@
|
|||||||
from crc import Calculator, Crc8
|
|
||||||
from typing import Optional, Tuple
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from enum import IntEnum
|
|
||||||
from struct import pack
|
|
||||||
|
|
||||||
|
|
||||||
PKT_START_SEQUENCE = [0xDE, 0xAD]
|
|
||||||
|
|
||||||
|
|
||||||
class Settings(IntEnum):
|
|
||||||
TriggerThreshold = 0
|
|
||||||
TriggerPeriod = 1
|
|
||||||
DecayValue = 2
|
|
||||||
DecayPeriod = 3
|
|
||||||
Gain = 4
|
|
||||||
CenterFreq = 5
|
|
||||||
SamplingEnabled = 6
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CommandPacket:
|
|
||||||
is_write: bool
|
|
||||||
setting: Settings
|
|
||||||
value: int
|
|
||||||
|
|
||||||
def serialize(self) -> bytearray:
|
|
||||||
buf = bytearray()
|
|
||||||
|
|
||||||
# TODO raise exception if setting is invalid here
|
|
||||||
buf.extend(PKT_START_SEQUENCE)
|
|
||||||
command = int(self.setting)
|
|
||||||
if self.is_write:
|
|
||||||
command |= 1 << 7
|
|
||||||
buf.append(command)
|
|
||||||
buf.extend(pack("<I", self.value))
|
|
||||||
|
|
||||||
# Append CRC
|
|
||||||
crc = Calculator(Crc8.MAXIM_DOW).checksum(buf)
|
|
||||||
buf.append(crc)
|
|
||||||
|
|
||||||
return buf
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ResponsePacket:
|
|
||||||
# Deserialization
|
|
||||||
is_error: bool
|
|
||||||
setting: Settings
|
|
||||||
value: int
|
|
||||||
|
|
||||||
|
|
||||||
class PacketParser:
|
|
||||||
def __init__(self):
|
|
||||||
self.crc = Calculator(Crc8.MAXIM_DOW)
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
|
|
||||||
def parse_byte(self, b: int) -> Optional[ResponsePacket]:
|
|
||||||
byte_index = len(self.packet_data)
|
|
||||||
|
|
||||||
if byte_index == 0:
|
|
||||||
if b != PKT_START_SEQUENCE[0]:
|
|
||||||
self.reset()
|
|
||||||
return
|
|
||||||
|
|
||||||
elif byte_index == 1:
|
|
||||||
if b != PKT_START_SEQUENCE[1]:
|
|
||||||
self.reset()
|
|
||||||
return
|
|
||||||
|
|
||||||
elif byte_index == 7:
|
|
||||||
# Final CRC byte, check and generate data
|
|
||||||
packet = None
|
|
||||||
if self.crc.verify(self.packet_data, b):
|
|
||||||
packet = self.build_response_packet()
|
|
||||||
|
|
||||||
self.reset()
|
|
||||||
return packet
|
|
||||||
|
|
||||||
# Pull packet data in otherwise
|
|
||||||
self.packet_data.append(b)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_bytearray(self, bytes: bytearray) -> Tuple[bytearray, Optional[ResponsePacket]]:
|
|
||||||
"""
|
|
||||||
Parse a bytearray for a packet. Returns a bytearray of bytes that haven't been processed.
|
|
||||||
"""
|
|
||||||
for offset, byte in enumerate(bytes):
|
|
||||||
packet = self.parse_byte(byte)
|
|
||||||
|
|
||||||
if packet is not None:
|
|
||||||
# Return packet and consumed bytearray
|
|
||||||
return (bytes[offset:], packet)
|
|
||||||
|
|
||||||
# No packets found, entire buffer has been read
|
|
||||||
return (bytearray(), None)
|
|
||||||
|
|
||||||
|
|
||||||
def build_response_packet(self) -> ResponsePacket:
|
|
||||||
"""Builds a response packet out of the data we have received"""
|
|
||||||
if len(self.packet_data) < 7:
|
|
||||||
raise Exception("Invalid amount of packet data received!")
|
|
||||||
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
self.packet_data = bytearray()
|
|
Loading…
Reference in New Issue
Block a user