The "Holy Bible" for embedded engineers
Logic analyzers are essential tools for digital signal analysis in embedded systems, providing high-speed capture and analysis of multiple digital signals simultaneously. They excel at protocol analysis, timing verification, and debugging complex digital interactions that oscilloscopes cannot easily capture.
Key Benefits:
Logic analyzers capture digital signals as binary states (high/low) rather than analog voltage levels. This digital approach enables efficient storage, analysis, and protocol decoding of complex signal interactions.
Signal Characteristics:
Logic analyzers use sampling to capture digital signals at specific time intervals. The sampling rate must be sufficient to accurately represent the fastest signal transitions.
Nyquist Principle:
Sampling Rate > 2 × Highest Frequency Component
Practical Considerations:
Modern logic analyzers can decode common digital protocols automatically, converting raw signal data into meaningful information.
Supported Protocols:
Logic analyzers use sophisticated triggering to capture specific events of interest, reducing memory usage and focusing analysis on relevant data.
Trigger Types:
Trigger Position:
Pre-trigger: [Memory] ← [Trigger Event] ← [Post-trigger]
↑ ↑ ↑
Old data Trigger New data
Logic analyzer memory is finite and must be managed efficiently to capture the most relevant information.
Memory Allocation:
Memory Depth Calculation:
Capture Time = Memory Depth / Sampling Rate
Proper channel setup is crucial for accurate signal capture and analysis.
Channel Settings:
// Logic analyzer configuration structure
typedef struct {
uint32_t sample_rate;
uint32_t memory_depth;
uint32_t channel_count;
uint32_t threshold_voltage;
uint32_t trigger_position;
bool compression_enabled;
} logic_analyzer_config_t;
// Channel configuration
typedef struct {
uint32_t channel_id;
uint32_t threshold;
bool enabled;
bool inverted;
char label[32];
} channel_config_t;
// Initialize logic analyzer
bool logic_analyzer_init(logic_analyzer_config_t *config) {
// Set sampling rate
if (!set_sample_rate(config->sample_rate)) {
return false;
}
// Configure memory depth
if (!set_memory_depth(config->memory_depth)) {
return false;
}
// Set threshold voltage
if (!set_threshold_voltage(config->threshold_voltage)) {
return false;
}
// Enable compression if requested
if (config->compression_enabled) {
enable_compression();
}
return true;
}
// Trigger configuration structure
typedef struct {
uint32_t trigger_type;
uint32_t trigger_channel;
uint32_t trigger_level;
uint32_t trigger_pattern;
uint32_t trigger_timeout;
bool trigger_enabled;
} trigger_config_t;
// Trigger types
typedef enum {
TRIGGER_EDGE_RISING,
TRIGGER_EDGE_FALLING,
TRIGGER_PATTERN,
TRIGGER_PROTOCOL,
TRIGGER_TIMING,
TRIGGER_STATE
} trigger_type_t;
// Configure trigger
bool configure_trigger(trigger_config_t *trigger) {
if (!trigger->trigger_enabled) {
return true; // No trigger needed
}
switch (trigger->trigger_type) {
case TRIGGER_EDGE_RISING:
return set_edge_trigger(trigger->trigger_channel,
TRIGGER_EDGE_RISING,
trigger->trigger_level);
case TRIGGER_EDGE_FALLING:
return set_edge_trigger(trigger->trigger_channel,
TRIGGER_EDGE_FALLING,
trigger->trigger_level);
case TRIGGER_PATTERN:
return set_pattern_trigger(trigger->trigger_pattern);
case TRIGGER_PROTOCOL:
return set_protocol_trigger(trigger->trigger_channel);
case TRIGGER_TIMING:
return set_timing_trigger(trigger->trigger_timeout);
case TRIGGER_STATE:
return set_state_trigger(trigger->trigger_pattern);
default:
return false;
}
}
// Capture buffer structure
typedef struct {
uint8_t *data;
uint32_t size;
uint32_t sample_count;
uint64_t timestamp_start;
uint64_t timestamp_end;
bool trigger_found;
} capture_buffer_t;
// Start capture
bool start_capture(logic_analyzer_config_t *config) {
// Arm trigger
if (!arm_trigger()) {
return false;
}
// Start sampling
if (!start_sampling(config->sample_rate)) {
return false;
}
// Wait for trigger or timeout
uint32_t timeout_ms = 5000; // 5 second timeout
uint32_t elapsed_ms = 0;
while (!is_triggered() && elapsed_ms < timeout_ms) {
delay_ms(10);
elapsed_ms += 10;
}
if (elapsed_ms >= timeout_ms) {
stop_sampling();
return false; // Timeout
}
// Continue sampling for post-trigger data
uint32_t post_trigger_samples = config->memory_depth *
(100 - config->trigger_position) / 100;
delay_ms(post_trigger_samples * 1000 / config->sample_rate);
stop_sampling();
return true;
}
// Read captured data
bool read_captured_data(capture_buffer_t *buffer) {
// Allocate buffer
buffer->data = malloc(buffer->size);
if (!buffer->data) {
return false;
}
// Read data from logic analyzer
if (!read_capture_memory(buffer->data, buffer->size)) {
free(buffer->data);
return false;
}
// Get timing information
buffer->timestamp_start = get_capture_start_time();
buffer->timestamp_end = get_capture_end_time();
buffer->trigger_found = is_trigger_found();
return true;
}
// Protocol decoder structure
typedef struct {
uint32_t protocol_type;
uint32_t start_channel;
uint32_t clock_channel;
uint32_t data_channels[8];
uint32_t channel_count;
uint32_t bit_rate;
} protocol_decoder_t;
// Protocol types
typedef enum {
PROTOCOL_UART,
PROTOCOL_SPI,
PROTOCOL_I2C,
PROTOCOL_CAN,
PROTOCOL_USB
} protocol_type_t;
// Decode UART protocol
bool decode_uart_protocol(capture_buffer_t *buffer,
protocol_decoder_t *decoder) {
if (decoder->protocol_type != PROTOCOL_UART) {
return false;
}
// Find start bits (falling edges)
uint32_t start_bits[1000];
uint32_t start_bit_count = 0;
for (uint32_t i = 1; i < buffer->sample_count; i++) {
uint8_t current_sample = buffer->data[i];
uint8_t previous_sample = buffer->data[i-1];
// Check for falling edge on start channel
if ((previous_sample & (1 << decoder->start_channel)) &&
!(current_sample & (1 << decoder->start_channel))) {
if (start_bit_count < 1000) {
start_bits[start_bit_count++] = i;
}
}
}
// Decode each byte
for (uint32_t i = 0; i < start_bit_count; i++) {
uint32_t start_sample = start_bits[i];
uint8_t byte_value = 0;
// Sample data bits at middle of each bit period
uint32_t bit_period = buffer->sample_count /
(decoder->bit_rate * buffer->sample_count / 1000000);
for (uint32_t bit = 0; bit < 8; bit++) {
uint32_t sample_index = start_sample +
(bit + 0.5) * bit_period;
if (sample_index < buffer->sample_count) {
uint8_t bit_value = (buffer->data[sample_index] >>
decoder->data_channels[0]) & 1;
byte_value |= (bit_value << bit);
}
}
// Process decoded byte
process_decoded_byte(byte_value, start_sample);
}
return true;
}
// Complex trigger configuration
typedef struct {
uint32_t trigger_count;
trigger_config_t triggers[4];
uint32_t trigger_logic; // AND, OR, XOR combinations
uint32_t trigger_delay;
} complex_trigger_t;
// Configure complex trigger
bool configure_complex_trigger(complex_trigger_t *complex_trigger) {
// Configure individual triggers
for (uint32_t i = 0; i < complex_trigger->trigger_count; i++) {
if (!configure_trigger(&complex_trigger->triggers[i])) {
return false;
}
}
// Set trigger logic
if (!set_trigger_logic(complex_trigger->trigger_logic)) {
return false;
}
// Set trigger delay
if (complex_trigger->trigger_delay > 0) {
if (!set_trigger_delay(complex_trigger->trigger_delay)) {
return false;
}
}
return true;
}
// Signal integrity metrics
typedef struct {
float rise_time;
float fall_time;
float overshoot;
float undershoot;
float jitter;
float noise_margin;
} signal_integrity_t;
// Analyze signal integrity
signal_integrity_t analyze_signal_integrity(capture_buffer_t *buffer,
uint32_t channel) {
signal_integrity_t integrity = {0};
// Find transitions
uint32_t transitions[1000];
uint32_t transition_count = 0;
for (uint32_t i = 1; i < buffer->sample_count; i++) {
uint8_t current = buffer->data[i];
uint8_t previous = buffer->data[i-1];
if ((current >> channel) & 1 != (previous >> channel) & 1) {
if (transition_count < 1000) {
transitions[transition_count++] = i;
}
}
}
// Calculate rise/fall times
for (uint32_t i = 0; i < transition_count - 1; i++) {
uint32_t start = transitions[i];
uint32_t end = transitions[i + 1];
if (end - start > 1) {
float time_ns = (end - start) * 1000000000.0f /
buffer->sample_count;
if ((buffer->data[end] >> channel) & 1) {
// Rising edge
if (integrity.rise_time == 0 || time_ns < integrity.rise_time) {
integrity.rise_time = time_ns;
}
} else {
// Falling edge
if (integrity.fall_time == 0 || time_ns < integrity.fall_time) {
integrity.fall_time = time_ns;
}
}
}
}
return integrity;
}
// Protocol validation result
typedef struct {
bool is_valid;
uint32_t error_count;
uint32_t warning_count;
char errors[100][256];
char warnings[100][256];
} protocol_validation_t;
// Validate SPI protocol
protocol_validation_t validate_spi_protocol(capture_buffer_t *buffer,
protocol_decoder_t *decoder) {
protocol_validation_t validation = {0};
// Check clock frequency consistency
uint32_t clock_periods[1000];
uint32_t period_count = 0;
// Find clock edges
for (uint32_t i = 1; i < buffer->sample_count; i++) {
uint8_t current = buffer->data[i];
uint8_t previous = buffer->data[i-1];
if ((current >> decoder->clock_channel) & 1 !=
(previous >> decoder->clock_channel) & 1) {
if (period_count < 1000) {
clock_periods[period_count++] = i;
}
}
}
// Calculate period variations
float avg_period = 0;
for (uint32_t i = 1; i < period_count; i++) {
avg_period += (clock_periods[i] - clock_periods[i-1]);
}
avg_period /= (period_count - 1);
// Check for excessive jitter
for (uint32_t i = 1; i < period_count; i++) {
float period = clock_periods[i] - clock_periods[i-1];
float jitter = fabs(period - avg_period) / avg_period;
if (jitter > 0.1f) { // 10% jitter threshold
snprintf(validation.warnings[validation.warning_count], 256,
"Clock jitter at sample %d: %.1f%%",
clock_periods[i], jitter * 100);
validation.warning_count++;
}
}
validation.is_valid = (validation.error_count == 0);
return validation;
}