RC Bode plot

Introduction

According to Wikipedia,

A Bode plot /ˈboʊdi/ is a graph of the frequency response of a system. It is usually a combination of a Bode magnitude plot, expressing the magnitude (usually in decibels) of the frequency response, and a Bode phase plot, expressing the phase shift.

Note: The code is not specific to RC, you can actually use it for other type of circuits & systems.

Connection

Breadboard The above is a RC circuit

Real

In the circuit, Capacitor = $0.1\mu F$ and Resistance = $550\Omega$.
Using the formula of Cut-off frequency, $f = \frac 1 {2\pi \dot RC}$.
That gives us, $f = \frac 1 {0.000345} = 2.89KHz$

In [3]:
#~ %matplotlib notebook
import box0
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
import math

# allocated resources
dev = box0.usb.open_supported()
ain0 = dev.ain(0)
aout0 = dev.aout(0)

# prepare resources

ain0.snapshot_prepare()
ain0.chan_seq_set([0, 1])

aout0.snapshot_prepare()
aout0.chan_seq_set([0])

FREQ = range(100, 5000, 100)

REF_LOW = -3.3
REF_HIGH = 3.3
INPUT_AMPLITUDE = (REF_HIGH - REF_LOW) / 2

data_freq = []
data_phase = []
data_magnitude = []

def set_freq(freq):
    """
    Set frequency to AOUT0.CH0
    :param float freq: Requested frequency
    :return: Actual frequency
    """
    global aout0

    # try to stop aout0 from last run
    try: aout0.snapshot_stop()
    except: pass

    # calculate the actual frequency that can be generated
    count, speed = aout0.snapshot_calc(freq, 12)
    freq = float(speed) / float(count)

    # generate a sine wave
    x = np.linspace(-np.pi, +np.pi, count)
    y = np.sin(x) * INPUT_AMPLITUDE

    # set the configuration to module
    aout0.bitsize_speed_set(12, speed)
    aout0.snapshot_start(y)

    return freq

def get_best_speed_and_count(freq):
    """
    Find the best sampling frequency at which signal with `freq` can be captured.
    Care is taken to not exceed more than 100ms acquisition duration.
    :param float freq: Frequency of expected signal that need to be captured
    :return: sampling_freq, count
    :rtype: int, int
    """
    sampling_freq = freq * 40 # atleast sample 40 time more than the signal frequency
    max_sample_count = 1000
    max_duration = 0.1 # 100ms

    sample_count = min(max_duration * sampling_freq, max_sample_count)

    # make sample_count multiple of 2, because two channel are being captured
    sample_count = int(sample_count / 2) * 2

    return int(sampling_freq), int(sample_count)

# ref: http://stackoverflow.com/a/26512495/1500988
def curve_fit_sine(t, y, freq):
    """Perform curve fitting of sine data"""
    p0 = (INPUT_AMPLITUDE, 0.0, 0.0)
    p_lo = (0.0, -np.pi, 0.0)
    p_up = (INPUT_AMPLITUDE, np.pi, REF_HIGH)

    def cb(t, amplitude, phase, offset):
        return np.sin(2 * np.pi * t * freq + phase) * amplitude + offset

    fit = curve_fit(cb, t, y, p0=p0, bounds=(p_lo, p_up))
    fit = fit[0]

    return fit[0], math.degrees(fit[1])

def get_data(freq):
    """
    Get channel data of ain0.
    :param float freq: expected Frequency of the signal that need to be captured
    :return: magnitude_in_db, phase_in_degree
    """
    global ain0

    # retrive data
    sampling_freq, sample_count = get_best_speed_and_count(freq)
    ain0.bitsize_speed_set(12, sampling_freq)
    data = np.empty(sample_count)
    ain0.snapshot_start(data)

    # process the data
    t = np.linspace(0.0, float(sample_count) / sampling_freq, sample_count, endpoint=False)
    input_amplitude, input_phase = curve_fit_sine(t[0::2], data[0::2], freq)
    output_amplitude, output_phase = curve_fit_sine(t[1::2], data[1::2], freq)

    # extract results
    magnitude = 20 * math.log10(output_amplitude / input_amplitude)
    phase = (output_phase - input_phase) % 360

    # convert phase into a value that is around 0
    if phase < -180:
        phase += 360
    elif phase > 180:
        phase -= 360

    return magnitude, phase

for freq in FREQ:
    #print("capturing ", freq)

    # do the main part!
    freq = set_freq(freq)
    magnitude, phase = get_data(freq)

    # store the results
    data_freq.append(freq)
    data_magnitude.append(magnitude)
    data_phase.append(phase)

# free up resources
ain0.close()
aout0.close()
dev.close()

fig, axs = plt.subplots(nrows=1, ncols=2, sharex=True)
fig.suptitle("Bode Plot")

magnitude = axs[0]
magnitude.grid(True)
magnitude.set_xscale('log')
#magnitude.set_yscale('log')
magnitude.set_xlabel("Frequency (Hz)")
magnitude.set_ylabel("Magnitude (dB)")
magnitude.plot(data_freq, data_magnitude, 'r.-')

phase = axs[1]
phase.grid(True)
phase.set_xscale('log')
phase.set_xlabel("Frequency (Hz)")
phase.set_ylabel("Phase (Degree)")
phase.plot(data_freq, data_phase, 'r.-')

# Show the data
plt.show()