The "Holy Bible" for embedded engineers
Oscilloscopes are fundamental tools for embedded system debugging, providing real-time visualization of analog and digital signals. They excel at measuring voltage levels, timing relationships, and signal quality, making them essential for hardware validation, power analysis, and signal integrity verification.
Key Benefits:
Oscilloscopes capture continuous voltage variations over time, providing detailed insight into signal characteristics that digital tools cannot provide.
Signal Parameters:
Modern oscilloscopes convert analog signals to digital form for storage, analysis, and display. The quality of this conversion directly affects measurement accuracy.
Sampling Considerations:
Oscilloscope bandwidth determines the fastest signals that can be accurately measured, while rise time affects timing measurement precision.
Bandwidth Relationship:
Bandwidth = 0.35 / Rise Time
Practical Bandwidth Requirements:
Oscilloscope triggers synchronize capture to specific signal events, enabling stable display and focused analysis of complex signals.
Trigger Types:
Trigger Modes:
Modern oscilloscopes provide automated measurements for common signal parameters, improving accuracy and reducing measurement time.
Basic Measurements:
Advanced Measurements:
Oscilloscope displays provide multiple views of captured data, enabling comprehensive signal analysis and comparison.
Display Modes:
// Oscilloscope configuration structure
typedef struct {
uint32_t sample_rate;
uint32_t memory_depth;
uint32_t channel_count;
float time_base;
float voltage_scale;
uint32_t trigger_source;
float trigger_level;
bool trigger_enabled;
} oscilloscope_config_t;
// Channel configuration
typedef struct {
uint32_t channel_id;
float voltage_scale;
float offset;
bool enabled;
bool inverted;
uint32_t coupling; // AC, DC, GND
char label[32];
} channel_config_t;
// Initialize oscilloscope
bool oscilloscope_init(oscilloscope_config_t *config) {
// Set time base
if (!set_time_base(config->time_base)) {
return false;
}
// Configure channels
for (uint32_t i = 0; i < config->channel_count; i++) {
if (!configure_channel(i, &config->channels[i])) {
return false;
}
}
// Set up trigger
if (config->trigger_enabled) {
if (!configure_trigger(config->trigger_source, config->trigger_level)) {
return false;
}
}
return true;
}
// Trigger configuration structure
typedef struct {
uint32_t trigger_type;
uint32_t trigger_source;
float trigger_level;
uint32_t trigger_mode;
float trigger_delay;
bool trigger_enabled;
} trigger_config_t;
// Trigger types
typedef enum {
TRIGGER_EDGE_RISING,
TRIGGER_EDGE_FALLING,
TRIGGER_PULSE_WIDTH,
TRIGGER_PATTERN,
TRIGGER_VIDEO,
TRIGGER_SERIAL
} trigger_type_t;
// Configure edge trigger
bool configure_edge_trigger(uint32_t source, float level, bool rising) {
// Set trigger source
if (!set_trigger_source(source)) {
return false;
}
// Set trigger level
if (!set_trigger_level(level)) {
return false;
}
// Set edge direction
if (!set_trigger_edge(rising)) {
return false;
}
// Enable trigger
return enable_trigger();
}
// Configure pulse width trigger
bool configure_pulse_trigger(uint32_t source, float level,
float min_width, float max_width) {
if (!configure_edge_trigger(source, level, true)) {
return false;
}
// Set pulse width conditions
if (!set_pulse_width_trigger(min_width, max_width)) {
return false;
}
return true;
}
// Acquisition buffer structure
typedef struct {
float *voltage_data;
uint64_t *timestamp_data;
uint32_t sample_count;
uint32_t channel_count;
uint64_t trigger_time;
bool trigger_found;
} acquisition_buffer_t;
// Start acquisition
bool start_acquisition(oscilloscope_config_t *config) {
// Arm trigger
if (config->trigger_enabled) {
if (!arm_trigger()) {
return false;
}
}
// Start sampling
if (!start_sampling(config->sample_rate)) {
return false;
}
// Wait for trigger or timeout
uint32_t timeout_ms = 10000; // 10 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 * 0.8f; // 80% post-trigger
delay_ms(post_trigger_samples * 1000 / config->sample_rate);
stop_sampling();
return true;
}
// Read acquired data
bool read_acquired_data(acquisition_buffer_t *buffer) {
// Allocate buffers
size_t data_size = buffer->sample_count * buffer->channel_count * sizeof(float);
buffer->voltage_data = malloc(data_size);
if (!buffer->voltage_data) {
return false;
}
size_t timestamp_size = buffer->sample_count * sizeof(uint64_t);
buffer->timestamp_data = malloc(timestamp_size);
if (!buffer->timestamp_data) {
free(buffer->voltage_data);
return false;
}
// Read voltage data
if (!read_voltage_data(buffer->voltage_data, data_size)) {
free(buffer->voltage_data);
free(buffer->timestamp_data);
return false;
}
// Read timestamp data
if (!read_timestamp_data(buffer->timestamp_data, timestamp_size)) {
free(buffer->voltage_data);
free(buffer->timestamp_data);
return false;
}
// Get trigger information
buffer->trigger_time = get_trigger_time();
buffer->trigger_found = is_trigger_found();
return true;
}
// Measurement result structure
typedef struct {
float peak_to_peak;
float rms;
float average;
float frequency;
float period;
float rise_time;
float fall_time;
float duty_cycle;
} measurement_result_t;
// Calculate voltage measurements
measurement_result_t calculate_voltage_measurements(float *voltage_data,
uint32_t sample_count) {
measurement_result_t result = {0};
if (sample_count == 0) {
return result;
}
float min_val = voltage_data[0];
float max_val = voltage_data[0];
float sum = 0;
float sum_squares = 0;
// Find min, max, and calculate sum
for (uint32_t i = 0; i < sample_count; i++) {
float voltage = voltage_data[i];
if (voltage < min_val) min_val = voltage;
if (voltage > max_val) max_val = voltage;
sum += voltage;
sum_squares += voltage * voltage;
}
// Calculate measurements
result.peak_to_peak = max_val - min_val;
result.average = sum / sample_count;
result.rms = sqrt(sum_squares / sample_count);
return result;
}
// Calculate timing measurements
measurement_result_t calculate_timing_measurements(float *voltage_data,
uint64_t *timestamp_data,
uint32_t sample_count,
float threshold) {
measurement_result_t result = {0};
if (sample_count < 2) {
return result;
}
// Find zero crossings
uint32_t crossings[1000];
uint32_t crossing_count = 0;
for (uint32_t i = 1; i < sample_count; i++) {
float current = voltage_data[i];
float previous = voltage_data[i-1];
// Check for crossing threshold
if ((previous < threshold && current >= threshold) ||
(previous >= threshold && current < threshold)) {
if (crossing_count < 1000) {
crossings[crossing_count++] = i;
}
}
}
// Calculate period and frequency
if (crossing_count >= 2) {
uint64_t time_diff = timestamp_data[crossings[1]] - timestamp_data[crossings[0]];
result.period = (float)time_diff / 1000000.0f; // Convert to ms
result.frequency = 1000.0f / result.period; // Convert to Hz
}
// Calculate rise and fall times
if (crossing_count >= 4) {
// Rise time: 10% to 90% of peak-to-peak
float v_pp = 0;
for (uint32_t i = 0; i < sample_count; i++) {
if (voltage_data[i] > v_pp) v_pp = voltage_data[i];
}
float v_10 = v_pp * 0.1f;
float v_90 = v_pp * 0.9f;
// Find 10% and 90% crossing points
uint32_t t_10 = 0, t_90 = 0;
for (uint32_t i = 0; i < sample_count; i++) {
if (voltage_data[i] >= v_10 && t_10 == 0) t_10 = i;
if (voltage_data[i] >= v_90 && t_90 == 0) t_90 = i;
}
if (t_10 > 0 && t_90 > 0 && t_90 > t_10) {
result.rise_time = (float)(timestamp_data[t_90] - timestamp_data[t_10]) / 1000000.0f;
}
}
return result;
}
// FFT configuration
typedef struct {
uint32_t fft_size;
uint32_t window_type;
float frequency_resolution;
bool db_scale;
} fft_config_t;
// Window types
typedef enum {
WINDOW_RECTANGULAR,
WINDOW_HANNING,
WINDOW_HAMMING,
WINDOW_BLACKMAN,
WINDOW_FLAT_TOP
} window_type_t;
// Perform FFT analysis
bool perform_fft_analysis(float *voltage_data,
uint32_t sample_count,
fft_config_t *config,
float *magnitude_data,
float *phase_data) {
// Apply window function
float *windowed_data = malloc(sample_count * sizeof(float));
if (!windowed_data) {
return false;
}
apply_window_function(voltage_data, windowed_data, sample_count, config->window_type);
// Perform FFT
if (!fft_compute(windowed_data, magnitude_data, phase_data, config->fft_size)) {
free(windowed_data);
return false;
}
// Convert to dB scale if requested
if (config->db_scale) {
for (uint32_t i = 0; i < config->fft_size / 2; i++) {
if (magnitude_data[i] > 0) {
magnitude_data[i] = 20 * log10(magnitude_data[i]);
} else {
magnitude_data[i] = -100; // -100 dB floor
}
}
}
free(windowed_data);
return true;
}
// Statistical measurement structure
typedef struct {
float mean;
float standard_deviation;
float min_value;
float max_value;
uint32_t sample_count;
float confidence_interval;
} statistical_measurement_t;
// Calculate statistical measurements
statistical_measurement_t calculate_statistics(float *voltage_data,
uint32_t sample_count) {
statistical_measurement_t stats = {0};
if (sample_count == 0) {
return stats;
}
// Calculate mean
float sum = 0;
for (uint32_t i = 0; i < sample_count; i++) {
sum += voltage_data[i];
}
stats.mean = sum / sample_count;
// Calculate standard deviation
float sum_squares = 0;
for (uint32_t i = 0; i < sample_count; i++) {
float diff = voltage_data[i] - stats.mean;
sum_squares += diff * diff;
}
stats.standard_deviation = sqrt(sum_squares / (sample_count - 1));
// Find min and max
stats.min_value = voltage_data[0];
stats.max_value = voltage_data[0];
for (uint32_t i = 1; i < sample_count; i++) {
if (voltage_data[i] < stats.min_value) stats.min_value = voltage_data[i];
if (voltage_data[i] > stats.max_value) stats.max_value = voltage_data[i];
}
stats.sample_count = sample_count;
// Calculate 95% confidence interval
stats.confidence_interval = 1.96f * stats.standard_deviation / sqrt(sample_count);
return stats;
}
// Power measurement structure
typedef struct {
float rms_voltage;
float rms_current;
float apparent_power;
float real_power;
float power_factor;
float efficiency;
} power_measurement_t;
// Calculate power measurements
power_measurement_t calculate_power_measurements(float *voltage_data,
float *current_data,
uint32_t sample_count) {
power_measurement_t power = {0};
if (sample_count == 0) {
return power;
}
// Calculate RMS voltage and current
float v_sum_squares = 0;
float i_sum_squares = 0;
float power_sum = 0;
for (uint32_t i = 0; i < sample_count; i++) {
v_sum_squares += voltage_data[i] * voltage_data[i];
i_sum_squares += current_data[i] * current_data[i];
power_sum += voltage_data[i] * current_data[i];
}
power.rms_voltage = sqrt(v_sum_squares / sample_count);
power.rms_current = sqrt(i_sum_squares / sample_count);
// Calculate power values
power.apparent_power = power.rms_voltage * power.rms_current;
power.real_power = power_sum / sample_count;
if (power.apparent_power > 0) {
power.power_factor = power.real_power / power.apparent_power;
}
return power;
}