///|
/// A semantic event observed on an I²C bus.
pub enum I2cEventKind {
Start
RepeatedStart
Stop
Byte
Acknowledge
NotAcknowledge
Unknown
} derive(Debug, Eq)
///|
/// I²C bus configuration.
pub struct I2cConfig {
clock : String
data : String
address_bits : Int
include_ack : Bool
address_is_seven_bit : Bool
} derive(Debug)
///|
/// One decoded byte, including its address/data role and ACK result.
pub struct I2cByte {
index : Int
time : Int64
value : Int
text : String
address : Bool
read : Bool
acknowledged : Bool?
bit_count : Int
} derive(Debug)
///|
/// A complete I²C transaction between START and STOP.
pub struct I2cTransaction {
index : Int
start_time : Int64
end_time : Int64
repeated_start_count : Int
bytes : Array[I2cByte]
address : Int?
read : Bool?
completed : Bool
} derive(Debug)
///|
/// Aggregate I²C decode output.
pub struct I2cReport {
config : I2cConfig
transactions : Array[I2cTransaction]
events : Int
bytes : Int
acknowledged : Int
not_acknowledged : Int
first_time : Int64?
last_time : Int64?
} derive(Debug)
///|
/// List the event kinds used by the decoder.
pub fn i2c_event_catalog() -> Array[I2cEventKind] {
[Start, RepeatedStart, Stop, Byte, Acknowledge, NotAcknowledge, Unknown]
}
///|
/// Create the usual seven-bit address I²C configuration.
pub fn i2c_config(clock : String, data : String) -> I2cConfig {
{
clock,
data,
address_bits: 7,
include_ack: true,
address_is_seven_bit: true,
}
}
///|
/// Select seven- or ten-bit address interpretation.
pub fn I2cConfig::with_address_width(self : I2cConfig, bits : Int) -> I2cConfig {
{
..self,
address_bits: if bits >= 10 {
10
} else {
7
},
address_is_seven_bit: bits < 10,
}
}
///|
/// Include or omit ACK slots from byte grouping.
pub fn I2cConfig::with_ack_slots(
self : I2cConfig,
include_ack : Bool,
) -> I2cConfig {
{ ..self, include_ack, }
}
///|
/// Decode all transactions in a trace.
pub fn i2c_decode(trace : Trace, config : I2cConfig) -> Array[I2cTransaction] {
let times = i2c_bus_times(trace, config)
let result : Array[I2cTransaction] = []
let mut active : I2cTransaction? = None
let mut bits : Array[String] = []
let mut byte_index = 0
let mut repeated = 0
let mut previous_sda = true
let mut previous_scl = true
for time in times {
let scl = i2c_level(trace, config.clock, time, true)
let sda = i2c_level(trace, config.data, time, true)
let start = previous_sda && !sda && scl
let stop = !previous_sda && sda && scl
let rising = !previous_scl && scl
if start {
match active {
Some(current) => {
active = Some({ ..current, repeated_start_count: repeated + 1 })
repeated += 1
}
None =>
active = Some({
index: result.length(),
start_time: time,
end_time: time,
repeated_start_count: 0,
bytes: [],
address: None,
read: None,
completed: false,
})
}
bits = []
byte_index = 0
} else if stop {
match active {
Some(current) => {
result.push({ ..current, end_time: time, completed: true })
active = None
}
None => ()
}
bits = []
} else if rising && active is Some(_) {
bits.push(if sda { "1" } else { "0" })
if bits.length() == 8 {
let value = i2c_bits_value(bits)
let address_byte = byte_index == 0
let read = if address_byte { (value & 1) == 1 } else { false }
let byte = {
index: byte_index,
time,
value,
text: i2c_hex_byte(value),
address: address_byte,
read,
acknowledged: None,
bit_count: bits.length(),
}
match active {
Some(current) => {
let next_address = if address_byte {
Some(value / 2)
} else {
current.address
}
let next_read = if address_byte { Some(read) } else { current.read }
let next_bytes = current.bytes.copy()
next_bytes.push(byte)
active = Some({
..current,
end_time: time,
bytes: next_bytes,
address: next_address,
read: next_read,
})
}
None => ()
}
byte_index += 1
bits = []
}
}
previous_sda = sda
previous_scl = scl
}
match active {
Some(current) =>
result.push({ ..current, end_time: trace.end_time, completed: false })
None => ()
}
result
}
///|
/// Decode and summarize an I²C capture.
pub fn i2c_report(trace : Trace, config : I2cConfig) -> I2cReport {
let transactions = i2c_decode(trace, config)
let bytes = transactions.fold(init=0, fn(total, item) {
total + item.bytes.length()
})
let mut ack = 0
let mut nack = 0
for transaction in transactions {
for byte in transaction.bytes {
match byte.acknowledged {
Some(true) => ack += 1
Some(false) => nack += 1
None => ()
}
}
}
let first = transactions.fold(init=None, fn(current, item) {
match current {
Some(value) => Some(Int64::min(value, item.start_time))
None => Some(item.start_time)
}
})
let last = transactions.fold(init=None, fn(current, item) {
match current {
Some(value) => Some(Int64::max(value, item.end_time))
None => Some(item.end_time)
}
})
{
config,
transactions,
events: bytes + transactions.length(),
bytes,
acknowledged: ack,
not_acknowledged: nack,
first_time: first,
last_time: last,
}
}
///|
/// Return the number of decoded bytes.
pub fn I2cReport::byte_count(self : I2cReport) -> Int {
self.bytes
}
///|
/// Return every byte from every transaction in wire order.
pub fn I2cReport::all_bytes(self : I2cReport) -> Array[I2cByte] {
let result : Array[I2cByte] = []
for transaction in self.transactions {
for byte in transaction.bytes {
result.push(byte)
}
}
result
}
///|
/// Export transactions as CSV.
pub fn i2c_transactions_csv(transactions : Array[I2cTransaction]) -> String {
let out = StringBuilder()
out.write_string("index,start,end,address,read,bytes,completed\n")
for item in transactions {
out.write_string(item.index.to_string())
out.write_char(',')
out.write_string(item.start_time.to_string())
out.write_char(',')
out.write_string(item.end_time.to_string())
out.write_char(',')
out.write_string(i2c_optional_int(item.address))
out.write_char(',')
out.write_string(i2c_optional_bool(item.read))
out.write_char(',')
out.write_string(item.bytes.map(fn(byte) { byte.text }).join(" "))
out.write_char(',')
out.write_string(item.completed.to_string())
out.write_char('\n')
}
out.to_string()
}
///|
/// Render an I²C report for a terminal.
pub fn I2cReport::to_text(self : I2cReport) -> String {
"I2C transactions=" +
self.transactions.length().to_string() +
" bytes=" +
self.bytes.to_string() +
" ack=" +
self.acknowledged.to_string() +
" nack=" +
self.not_acknowledged.to_string()
}
///|
/// Render an I²C report as JSON.
pub fn I2cReport::to_json(self : I2cReport) -> String {
let transactions = self.transactions.map(i2c_transaction_json).join(",")
"{\"clock\":\"" +
escape_text(self.config.clock) +
"\",\"data\":\"" +
escape_text(self.config.data) +
"\",\"bytes\":" +
self.bytes.to_string() +
",\"acknowledged\":" +
self.acknowledged.to_string() +
",\"not_acknowledged\":" +
self.not_acknowledged.to_string() +
",\"transactions\":[" +
transactions +
"]}"
}
///|
/// Collect timestamps where either bus line changes.
fn i2c_bus_times(trace : Trace, config : I2cConfig) -> Array[Int64] {
let result : Array[Int64] = []
for change in trace.changes {
if change.id == config.clock || change.id == config.data {
if !result.contains(change.time) {
result.push(change.time)
}
}
}
result.sort()
result
}
///|
/// Read a digital line at or before a timestamp.
fn i2c_level(trace : Trace, id : String, time : Int64, default : Bool) -> Bool {
match value_at(trace, id, time) {
Some(Scalar(ch)) => ch == '1'
Some(Vector(bits)) => bits == "1"
_ => default
}
}
///|
/// Convert eight wire bits to an integer.
fn i2c_bits_value(bits : Array[String]) -> Int {
let mut result = 0
for bit in bits {
result *= 2
if bit == "1" {
result += 1
}
}
result
}
///|
/// Format one data byte as hexadecimal.
fn i2c_hex_byte(value : Int) -> String {
let high = value / 16
let low = value % 16
i2c_hex_digit(high) + i2c_hex_digit(low)
}
///|
/// Format a hexadecimal digit.
fn i2c_hex_digit(value : Int) -> String {
let digits = [
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F",
]
if value >= 0 && value < digits.length() {
digits[value]
} else {
"?"
}
}
///|
/// Encode an optional integer.
fn i2c_optional_int(value : Int?) -> String {
match value {
Some(item) => item.to_string()
None => ""
}
}
///|
/// Encode an optional Boolean.
fn i2c_optional_bool(value : Bool?) -> String {
match value {
Some(item) => item.to_string()
None => ""
}
}
///|
/// Encode one transaction as JSON.
fn i2c_transaction_json(item : I2cTransaction) -> String {
let bytes = item.bytes
.map(fn(byte) {
"{\"index\":" +
byte.index.to_string() +
",\"time\":" +
byte.time.to_string() +
",\"value\":" +
byte.value.to_string() +
",\"text\":\"" +
byte.text +
"\",\"address\":" +
byte.address.to_string() +
",\"read\":" +
byte.read.to_string() +
"}"
})
.join(",")
"{\"index\":" +
item.index.to_string() +
",\"start\":" +
item.start_time.to_string() +
",\"end\":" +
item.end_time.to_string() +
",\"address\":" +
i2c_optional_int(item.address) +
",\"read\":" +
i2c_optional_bool(item.read) +
",\"completed\":" +
item.completed.to_string() +
",\"bytes\":[" +
bytes +
"]}"
}