///|
/// UART parity modes supported by the waveform decoder.
pub enum UartParity {
NoParity
EvenParity
OddParity
} derive(Debug, Eq)
///|
/// Sampling configuration for an asynchronous serial line.
pub struct UartConfig {
signal_id : String
bit_period : Int64
data_bits : Int
stop_bits : Int
parity : UartParity
idle_high : Bool
sample_offset : Int64
lsb_first : Bool
} derive(Debug, Eq)
///|
/// A decoded UART frame and its validation status.
pub struct UartFrame {
index : Int
start : Int64
end : Int64
value : Int64
data_bits : String
parity_bit : Char?
stop_ok : Bool
parity_ok : Bool
framing_ok : Bool
error : String?
} derive(Debug, Eq)
///|
/// Aggregate UART decode metrics.
pub struct UartReport {
signal_id : String
frames : Int
valid_frames : Int
invalid_frames : Int
parity_errors : Int
framing_errors : Int
minimum_gap : Int64?
maximum_gap : Int64?
mean_gap : Double?
bytes_per_time : Double
} derive(Debug, Eq)
///|
/// Return all supported parity modes.
pub fn uart_parity_catalog() -> Array[UartParity] {
[NoParity, EvenParity, OddParity]
}
///|
/// Create a conventional 8-N-1 UART configuration.
pub fn uart_8n1(signal_id : String, bit_period : Int64) -> UartConfig {
{
signal_id,
bit_period: Int64::max(1L, bit_period),
data_bits: 8,
stop_bits: 1,
parity: NoParity,
idle_high: true,
sample_offset: bit_period / 2L,
lsb_first: true,
}
}
///|
/// Update the data width while keeping timing parameters intact.
pub fn UartConfig::with_data_bits(
self : UartConfig,
data_bits : Int,
) -> UartConfig {
{ ..self, data_bits: Int::max(5, Int::min(9, data_bits)) }
}
///|
/// Update parity handling.
pub fn UartConfig::with_parity(
self : UartConfig,
parity : UartParity,
) -> UartConfig {
{ ..self, parity, }
}
///|
/// Update stop-bit count.
pub fn UartConfig::with_stop_bits(
self : UartConfig,
stop_bits : Int,
) -> UartConfig {
{ ..self, stop_bits: Int::max(1, Int::min(2, stop_bits)) }
}
///|
/// Update the sample offset within the first data bit.
pub fn UartConfig::with_sample_offset(
self : UartConfig,
offset : Int64,
) -> UartConfig {
{
..self,
sample_offset: Int64::max(0L, Int64::min(self.bit_period - 1L, offset)),
}
}
///|
/// Decode all falling start edges that have enough samples for a frame.
pub fn uart_decode(trace : Trace, config : UartConfig) -> Array[UartFrame] {
let starts = uart_start_edges(trace, config)
let frames : Array[UartFrame] = []
for index, start in starts {
frames.push(decode_uart_frame(trace, config, index, start))
}
frames
}
///|
/// Calculate aggregate decode metrics.
pub fn uart_report(trace : Trace, config : UartConfig) -> UartReport {
let frames = uart_decode(trace, config)
let valid = frames.count_if(fn(frame) { frame.framing_ok && frame.parity_ok })
let parity_errors = frames.count_if(fn(frame) { !frame.parity_ok })
let framing_errors = frames.count_if(fn(frame) { !frame.framing_ok })
let gaps : Array[Int64] = []
for index in 1.. String {
let rows : Array[String] = [
"index,start,end,value,data_bits,parity,stop_ok,parity_ok,framing_ok,error",
]
for frame in frames {
rows.push(
frame.index.to_string() +
"," +
frame.start.to_string() +
"," +
frame.end.to_string() +
"," +
frame.value.to_string() +
"," +
frame.data_bits +
"," +
optional_char_text(frame.parity_bit) +
"," +
frame.stop_ok.to_string() +
"," +
frame.parity_ok.to_string() +
"," +
frame.framing_ok.to_string() +
"," +
optional_string_text(frame.error),
)
}
rows.join("\n")
}
///|
/// Render a UART report as text.
pub fn UartReport::to_text(self : UartReport) -> String {
"uart=" +
self.signal_id +
" frames=" +
self.frames.to_string() +
" valid=" +
self.valid_frames.to_string() +
" invalid=" +
self.invalid_frames.to_string() +
" parity_errors=" +
self.parity_errors.to_string() +
" framing_errors=" +
self.framing_errors.to_string() +
" mean_gap=" +
optional_double_text(self.mean_gap) +
" bytes_per_time=" +
self.bytes_per_time.to_string()
}
///|
/// Render a UART report as JSON.
pub fn UartReport::to_json(self : UartReport) -> String {
"{\"signal\":\"" +
csv_cell(self.signal_id) +
"\",\"frames\":" +
self.frames.to_string() +
",\"valid_frames\":" +
self.valid_frames.to_string() +
",\"invalid_frames\":" +
self.invalid_frames.to_string() +
",\"parity_errors\":" +
self.parity_errors.to_string() +
",\"framing_errors\":" +
self.framing_errors.to_string() +
",\"minimum_gap\":" +
optional_int64_json(self.minimum_gap) +
",\"maximum_gap\":" +
optional_int64_json(self.maximum_gap) +
",\"mean_gap\":" +
optional_double_json(self.mean_gap) +
",\"bytes_per_time\":" +
self.bytes_per_time.to_string() +
"}"
}
///|
/// Find falling transitions from idle to the start bit.
fn uart_start_edges(trace : Trace, config : UartConfig) -> Array[Int64] {
let starts : Array[Int64] = []
let changes = trace.changes.filter(fn(change) {
change.id == config.signal_id
})
for index in 1..= config.bit_period {
starts.push(changes[index].time)
}
}
}
starts
}
///|
/// Decode one frame by sampling the center of each serial bit.
fn decode_uart_frame(
trace : Trace,
config : UartConfig,
index : Int,
start : Int64,
) -> UartFrame {
let data : Array[Char] = []
let mut numeric : Int64 = 0L
let mut ones = 0
for bit in 0.. None
_ => Some(uart_sample(trace, config.signal_id, parity_time))
}
let stop_start = if config.parity == NoParity {
config.data_bits + 1
} else {
config.data_bits + 2
}
let stop_ok = uart_stop_ok(trace, config, start, stop_start)
let parity_ok = uart_parity_ok(config.parity, ones, parity_bit)
let start_level = if config.idle_high { '0' } else { '1' }
let framing_ok = uart_sample(
trace,
config.signal_id,
start + config.sample_offset,
) ==
start_level &&
stop_ok
let data_bits = data.map(fn(bit) { bit.to_string() }).join("")
let error = if !framing_ok {
Some("framing")
} else if !parity_ok {
Some("parity")
} else {
None
}
{
index,
start,
end: start + (stop_start + config.stop_bits).to_int64() * config.bit_period,
value: numeric,
data_bits,
parity_bit,
stop_ok,
parity_ok,
framing_ok,
error,
}
}
///|
/// Return a binary sample or unknown marker.
fn uart_sample(trace : Trace, signal_id : String, time : Int64) -> Char {
match value_at(trace, signal_id, time) {
Some(Scalar(value)) =>
if value == '0' || value == '1' {
value
} else {
'x'
}
Some(Vector(bits)) =>
if bits.length() > 0 {
bits.get_char(0).unwrap_or('x')
} else {
'x'
}
_ => 'x'
}
}
///|
/// Return whether a value represents the configured logical level.
fn uart_level(value : Value, active_high : Bool) -> Bool {
match value {
Scalar('1') => active_high
Scalar('0') => !active_high
_ => false
}
}
///|
/// Check all stop-bit samples.
fn uart_stop_ok(
trace : Trace,
config : UartConfig,
start : Int64,
stop_start : Int,
) -> Bool {
let expected = if config.idle_high { '1' } else { '0' }
for bit in 0.. Bool {
match mode {
NoParity => true
EvenParity =>
match parity {
Some(bit) => {
let parity_one = if bit == '1' { 1 } else { 0 }
(ones + parity_one) % 2 == 0
}
None => false
}
OddParity =>
match parity {
Some(bit) => {
let parity_one = if bit == '1' { 1 } else { 0 }
(ones + parity_one) % 2 == 1
}
None => false
}
}
}
///|
/// Return a minimum element for a timestamp array.
fn optional_minimum_i64(values : Array[Int64]) -> Int64? {
if values.length() == 0 {
None
} else {
Some(
values.fold(init=values[0], fn(value, item) { Int64::min(value, item) }),
)
}
}
///|
/// Return a maximum element for a timestamp array.
fn optional_maximum_i64(values : Array[Int64]) -> Int64? {
if values.length() == 0 {
None
} else {
Some(
values.fold(init=values[0], fn(value, item) { Int64::max(value, item) }),
)
}
}
///|
/// Render an optional character.
fn optional_char_text(value : Char?) -> String {
match value {
Some(item) => item.to_string()
None => ""
}
}
///|
/// Render an optional string.
fn optional_string_text(value : String?) -> String {
match value {
Some(item) => item
None => ""
}
}