8ビットADCのFFT解析と性能評価のダミーシミュレーション(SNDR・ENOB・SFDRの計算)

# 8ビットADCのFFT解析と性能評価

## 1. 概要
本記事では、**8ビットADCのFFT解析**を行い、SNDR（信号対ノイズ歪み比）、ENOB（有効ビット数）、SFDR（スプリアスフリー・ダイナミックレンジ）を計算します。Pythonを用いたシミュレーションを通じて、**ADCの性能評価**を実施します。

---

## 2. シミュレーション環境

- **サンプリング周波数**: 1 MSPS（1MHz）
- **入力信号周波数**: 124.511 kHz
- **振幅**: 8ビットADCのフルスケール範囲
- **FFTポイント数**: 4096
- **時間領域のシミュレーション**: 4.096 ms
補足として、入力信号はコヒーレントサンプリングに基づいて計算します

https://qiita.com/qwer123123/items/530e440d93577b8b33ec

---

## 3. 実装コード
以下のPythonコードでは、**信号の生成、量子化、FFT解析**を行い、**SNDR・ENOB・SFDRの計算**を実施します。

```python
import numpy as np
import matplotlib.pyplot as plt

# -------------------- 関数定義 / Function Definitions --------------------

def freq_search(N: int, fs: float, finRate=8):
    """Calculate input frequency for coherent sampling"""
    freq = []
    FFT_points = 2**N
    for n in range(2, N):
        prime = np.exp(n * np.log(2)) - 1
        fin_per_fs = prime / FFT_points
        freq.append(fin_per_fs)
    array = np.array(freq)
    index = (np.abs(array - (1 / finRate))).argmin()
    fin = freq[index] * fs
    return fin

def wave_sort(data, fs: float, f_signal: float):
    """Sort data for one cycle"""
    data_num = len(data)
    t_signal = 1 / f_signal
    Ts = 1 / fs
    dx_list = [(Ts * n) % t_signal for n in range(data_num)]
    sorted_indices = sorted(range(len(dx_list)), key=lambda k: dx_list[k])
    sort = [data[i] for i in sorted_indices]
    return sort

def ideal_adc(input_voltage, n_bits: int, v_min: float, v_max: float):
    """Ideal ADC conversion"""
    LSB = (v_max - v_min) / (2**n_bits)
    digital_level = ((input_voltage - v_min) / LSB).astype(int)
    digital_level = np.clip(digital_level, 0, 2**n_bits - 1)
    return digital_level

def sine_wave_generator(N: int, f_signal: float, fs: float, amplitude: float, offset: float):
    """Generate sine wave"""
    t_sampling = 1 / fs
    duration = N * t_sampling
    t = np.arange(0, duration, t_sampling)
    signal = amplitude * np.sin(2 * np.pi * f_signal * t) + offset
    return signal

def AC_evaluation(bit: int, data, N: int, fs: float, f_in: float):
    """Evaluate ADC's AC performance"""
    f = np.divide(data, 2 ** (bit - 1))
    dt = 1 / fs
    t = np.arange(0, N * dt, dt)
    freq = np.fft.fftfreq(N, dt)
    freq_norm = freq / fs
    F = np.fft.fft(f) * 2 / N
    Power = np.abs(F) ** 2
    Pow_dB = 10 * np.log10(Power / 1.0)

    signal_index = np.argmax(Power[1 : int(N / 2)]) + 1
    Signal = Power[signal_index]
    Noise = np.sum(Power[1 : int(N / 2)]) - Signal
    SNDR = 10 * np.log10(Signal / Noise)
    ENOB = (SNDR - 1.76) / 6.02
    spurious = [np.max(Pow_dB[(signal_index * i) - 2 : signal_index * i + 2]) for i in range(2, 5)]
    SFDR = Pow_dB[signal_index] - np.max(spurious)

    # --- 結果表示 / Print results ---
    print("===== AC Evaluation Results =====")
    print("FFT Peak Frequency =", round(freq[signal_index], 2), "Hz")
    print("FFT Peak Power =", round(Pow_dB[signal_index], 2), "dB")
    print("SNDR =", round(SNDR, 2), "dB")
    print("ENOB =", round(ENOB, 2), "bit")
    print("SFDR =", round(SFDR, 2), "dB")
    print("=================================")

    # --- グラフ表示 / Plotting ---
    sorted_data = wave_sort(data, fs, f_in)
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))

    ax1.set_title("Sorted Wave")
    ax1.set_xlabel("Time [sec]")
    ax1.set_ylabel("Code")
    ax1.grid()
    ax1.set_ylim(0, 2**bit)
    ax1.plot(t, sorted_data, marker=".", linestyle="", markersize=4)

    ax2.set_title("Power Spectrum")
    ax2.set_xlabel("f/fs")
    ax2.set_ylabel("Power [dB]")
    ax2.grid()
    ax2.plot(freq_norm[1:int(N/2)], Pow_dB[1:int(N/2)])
    ax2.text(0.3, -5, f"SNDR = {round(SNDR, 2)} dB")
    ax2.text(0.3, -10, f"ENOB = {round(ENOB, 2)} bit")
    ax2.text(0.3, -15, f"SFDR = {round(SFDR, 2)} dB")

    plt.tight_layout()
    plt.show()

# -------------------- メイン処理 / Main Execution --------------------

N = 12                # FFTポイントの指数（2^Nポイント）
fs = 1000000          # サンプリング周波数
bit = 8               # ADCビット数
fin_rate = 8          # 入力周波数に対するfsの比（整合サンプリングのため）
v_amplitude = 2.5     # 振幅
v_offset = 2.5        # オフセット
v_min = 0             # ADCの最小電圧
v_max = 5             # ADCの最大電圧
noise = 0         # ノイズ振幅

# 入力周波数と信号生成
f_in = freq_search(N, fs, fin_rate)
print("Input signal frequency:", round(f_in, 2), "Hz")

# 入力信号の周期を計算 / Calculate signal period
T_signal = 1 / f_in
print("Input signal period T =", T_signal, "s")


# 入力信号の周期を計算 / Calculate signal period
T_signal = 1 / f_in

# 入力信号の周期を表示 / Print the input signal period
print("Input signal period T =", T_signal, "s")


# FFTポイントとシミュレーション時間
FFT_points = 2 ** N
Ts = 1 / fs
simulation_time = FFT_points * Ts

print("FFT Points =", FFT_points)
print("Sampling Period Ts =", Ts, "s")
print("Simulation Time =", simulation_time, "s ( = FFT Points × Ts )")

# パラメータ確認
print("===== Experiment Parameters =====")
print("N =", N, "->", 2**N, "points")
print("fs =", fs, "Hz")
print("bit =", bit)
print("fin_rate =", fin_rate)
print("v_amplitude =", v_amplitude, "V")
print("v_offset =", v_offset, "V")
print("v_min =", v_min, "V")
print("v_max =", v_max, "V")
print("noise amplitude =", noise, "V")
print("=================================")

# 入力周波数と信号生成
f_in = freq_search(N, fs, fin_rate)
print("Input signal frequency:", round(f_in, 2), "Hz")

v_signal = sine_wave_generator(2**N, f_in, fs, v_amplitude, v_offset)
v_signal += noise * np.random.rand(2**N)  # ノイズ加算



# ADC変換と評価
data = ideal_adc(v_signal, bit, v_min, v_max)
AC_evaluation(bit, data, 2**N, fs, f_in)

# 時間軸生成
t = np.arange(0, 2**N) / fs

# 入力信号全体プロット（アナログ信号）
plt.figure(figsize=(10, 4))
plt.plot(t, v_signal, label="Input Signal")
plt.title("Full Input Signal")
plt.xlabel("Time [s]")
plt.ylabel("Voltage [V]")
plt.grid()
plt.tight_layout()
plt.show()

# 入力信号全体プロットでADCコードをプロット
# ADCコードによる全体波形をプロット / Plot full ADC output (Code)
plt.figure(figsize=(10, 4))
plt.plot(t, data, label="ADC Output Code")  # ← data に変更
plt.title("Full ADC Output")
plt.xlabel("Time [s]")
plt.ylabel("Code")  # ← Voltage → Code に変更
plt.grid()
plt.tight_layout()
plt.show()


# 1周期のサンプル数でADCコードをプロット
cycle_length = int(fs / f_in)  # 1周期のサンプル数
data_cycle = data[:cycle_length]
t_cycle = t[:cycle_length]

plt.figure(figsize=(10, 4))
plt.plot(t_cycle, data_cycle, marker='o', linestyle='-', label="1 Cycle Code")
plt.title("One Cycle of ADC Output")
plt.xlabel("Time [s]")
plt.ylabel("Code")  # Codeとして縦軸表示
plt.grid()
plt.tight_layout()
plt.show()

# 5周期のサンプル数でADCコードをプロット

# -------------------- ADCコードの5周期分をプロット / Plot 5 cycles of ADC code --------------------

# 1周期あたりのサンプル数を計算 / Calculate samples per cycle
samples_per_cycle = int(fs / f_in)

# 5周期分のインデックス範囲 / Total number of samples for 5 cycles
num_samples_5cycles = samples_per_cycle * 5

# 時間軸とデータを抽出 / Extract time and ADC data for 5 cycles
t_5cycle = t[:num_samples_5cycles]
data_5cycle = data[:num_samples_5cycles]

# プロット / Plot
plt.figure(figsize=(10, 4))
plt.plot(t_5cycle, data_5cycle, marker='o', linestyle='-', label="5 Cycles of ADC Code")
plt.title("Five Cycles of ADC Output")
plt.xlabel("Time [s]")
plt.ylabel("Code")
plt.grid()
plt.tight_layout()
plt.show()

# -------------------- 入力信号とADCコードを先頭と末尾10点表示 / Display first and last 10 points of signal and ADC code --------------------

print("===== First 10 Points =====")
print(" Index |     Time [s]     |  Voltage [V]  |  ADC Code")
print("-----------------------------------------------")
for i in range(10):
    print(f"{i:6d} | {t[i]:.6f} s | {v_signal[i]:.6f} V | {data[i]:9d}")

print("\n===== Last 10 Points =====")
print(" Index |     Time [s]     |  Voltage [V]  |  ADC Code")
print("-----------------------------------------------")
for i in range(10):
    idx = -10 + i
    print(f"{len(t)+idx:6d} | {t[idx]:.6f} s | {v_signal[idx]:.6f} V | {data[idx]:9d}")




# --- 並び替え前後の波形を個別にプロット / Plot before and after sorting (separately) ---
sorted_data = wave_sort(data, fs, f_in)

# 並び替え前 / Original Wave
plt.figure(figsize=(10, 4))
plt.title("Original Wave")
plt.xlabel("Time [sec]")
plt.ylabel("Code")
plt.grid()
plt.ylim(0, 2**bit)
plt.plot(t, data, marker=".", linestyle="", markersize=4)
plt.tight_layout()
plt.show()

# 並び替え後 / Sorted Wave
plt.figure(figsize=(10, 4))
plt.title("Sorted Wave")
plt.xlabel("Time [sec]")
plt.ylabel("Code")
plt.grid()
plt.ylim(0, 2**bit)
plt.plot(t, sorted_data, marker=".", linestyle="", markersize=4)
plt.tight_layout()
plt.show()



```

## 結果



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759109/5d87fe12-c991-472c-ab21-0e62ad33d2b1.png)







追加CSVファイルで読み込んだ波形をFFT
```
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from google.colab import files
from scipy.signal import find_peaks
from scipy.fft import fft

# ========================================
# 📥 ① CSVファイルのアップロード / Upload CSV
# ========================================
uploaded = files.upload()
filename = next(iter(uploaded))
df = pd.read_csv(filename)

# ========================================
# ⏱ ② 信号データの抽出と前処理 / Extract & preprocess signal
# ========================================
time = df["time_data"]
signal = df["signal_data"]
adjusted_signal = signal - 127  # DCオフセット除去 / Remove DC offset

# ========================================
# 📊 ③ 波形の表示 / Plot original & adjusted signals
# ========================================
plt.figure(figsize=(12, 4))
plt.plot(time, signal, label="Original Signal", color='blue', alpha=0.6)
plt.plot(time, adjusted_signal, label="Adjusted Signal", color='green', alpha=0.6)
plt.title("Original vs Adjusted Signal")
plt.xlabel("Time [s]")
plt.ylabel("Amplitude")
plt.grid(True)
plt.legend()
plt.show()

# ========================================
# 🧾 ④ 先頭と末尾の値を確認 / Print head & tail of signals
# ========================================
print("=== Original Signal: First 10 values ===")
print(signal.head(10))
print("=== Original Signal: Last 10 values ===")
print(signal.tail(10))

print("=== Adjusted Signal: First 10 values ===")
print(adjusted_signal.head(10))
print("=== Adjusted Signal: Last 10 values ===")
print(adjusted_signal.tail(10))

# ========================================
# 🎯 ⑤ サンプリング処理 / Sampling
# ========================================
sampling_freq = 10_000_000  # 10 MHz
sampling_period = 1 / sampling_freq
sampled_times = np.arange(time.iloc[0], time.iloc[-1], sampling_period)

# インデックスを検索してサンプリング / Find nearest indices
time_np = time.to_numpy()
idx = np.searchsorted(time_np, sampled_times, side="left")
idx = np.clip(idx, 0, len(time_np) - 1)

sampled_original = signal.to_numpy()[idx]
sampled_adjusted = adjusted_signal.to_numpy()[idx]

# プロット / Plot sampled signals
plt.figure(figsize=(12, 4))
plt.plot(time, signal, alpha=0.3, label="Original")
plt.scatter(sampled_times, sampled_original, s=10, c='blue', label="Sampled Original")
plt.plot(time, adjusted_signal, alpha=0.3, label="Adjusted")
plt.scatter(sampled_times, sampled_adjusted, s=10, c='green', label="Sampled Adjusted")
plt.title("Sampled Signals")
plt.xlabel("Time [s]")
plt.ylabel("Amplitude")
plt.legend()
plt.grid(True)
plt.show()

# ========================================
# ⏱ ⑥ 周波数推定 / Frequency estimation
# ========================================
def estimate_frequency(signal, label):
    peaks, _ = find_peaks(signal, height=10)
    if len(peaks) < 2:
        print(f"{label} → Not enough peaks.")
        return None
    peak_times = time.iloc[peaks].to_numpy()
    periods = np.diff(peak_times)
    avg_period = np.mean(periods)
    freq = 1 / avg_period
    print(f"{label} Frequency: {freq:.2f} Hz, Period: {avg_period:.2e} s")
    return avg_period

period_orig = estimate_frequency(signal, "Original")
period_adj = estimate_frequency(adjusted_signal, "Adjusted")

# ========================================
# 🔄 ⑦ 1周期で並び替え / Sort samples by phase
# ========================================
def sort_by_phase(t, y, period):
    phase = t % period
    sorted_idx = np.argsort(phase)
    return phase[sorted_idx], y[sorted_idx]

if period_orig and period_adj:
    phase_orig, sorted_orig = sort_by_phase(sampled_times, sampled_original, period_orig)
    phase_adj, sorted_adj = sort_by_phase(sampled_times, sampled_adjusted, period_adj)

    plt.figure(figsize=(12, 4))
    plt.plot(phase_orig, sorted_orig, '.', markersize=3, label="Original")
    plt.plot(phase_adj, sorted_adj, '.', markersize=3, label="Adjusted")
    plt.title("Sorted by Phase")
    plt.xlabel("Phase [s]")
    plt.ylabel("Amplitude")
    plt.legend()
    plt.grid(True)
    plt.show()

# ========================================
# 🔍 ⑧ FFTによるAC性能評価 / AC performance via FFT
# ========================================
def fft_ac_analysis(signal, fs, label):
    N = len(signal)
    bit_resolution = 8
    v_min = np.min(signal)
    v_max = np.max(signal)

    # 正規化（8ビットADC相当）/ Normalize to 8-bit range
    normalized = (signal - v_min) / (v_max - v_min) * (2**bit_resolution - 1)

    # FFT & パワースペクトル / FFT and power spectrum
    F = fft(normalized) * 2 / N
    Power = np.abs(F)**2
    freq = np.fft.fftfreq(N, d=1/fs)        # 実周波数 / Actual frequency
    freq_norm = freq / fs                   # 正規化周波数 / Normalized frequency
    Pow_dB = 10 * np.log10(Power + 1e-12)   # Avoid log(0)

    # 基本波成分 / Fundamental component
    signal_idx = np.argmax(Power[1:N//2]) + 1
    Signal = Power[signal_idx]
    Noise = np.sum(Power[1:N//2]) - Signal

    # SNDR, ENOB計算 / Compute SNDR and ENOB
    SNDR = 10 * np.log10(Signal / Noise)
    ENOB = (SNDR - 1.76) / 6.02

    # SFDR（スプリアス除去比）/ Spurious-Free Dynamic Range
    spurious = [np.max(Pow_dB[signal_idx * i - 2: signal_idx * i + 2]) for i in range(2, 5)]
    SFDR = Pow_dB[signal_idx] - np.max(spurious)

    # 正規化：最大成分を0dBに調整 / Normalize peak to 0 dB
    Pow_dB -= Pow_dB[signal_idx]

    # プロット / Plot
    plt.figure(figsize=(10, 4))
    plt.plot(freq_norm[1:N//2], Pow_dB[1:N//2])
    plt.title(f"{label} - Power Spectrum (Normalized to 0 dB)")
    plt.xlabel("f/fs")
    plt.ylabel("Power [dB]")
    plt.grid(True)
    plt.text(0.3, -5, f"SNDR = {SNDR:.2f} dB")
    plt.text(0.3, -10, f"ENOB = {ENOB:.2f} bit")
    plt.text(0.3, -15, f"SFDR = {SFDR:.2f} dB")
    plt.tight_layout()
    plt.show()

    # 結果の表示 / Print metrics
    print(f"===== {label} FFT Results =====")
    print(f"Peak Frequency Index: {signal_idx}")
    print(f"→ Normalized Freq: {freq_norm[signal_idx]:.6f} f/fs")
    print(f"→ Actual Freq:     {freq[signal_idx]:.2f} Hz")
    print(f"SNDR: {SNDR:.2f} dB")
    print(f"ENOB: {ENOB:.2f} bit")
    print(f"SFDR: {SFDR:.2f} dB")
    print("================================")

# FFT解析の実行 / Run FFT analysis
fft_ac_analysis(sampled_original, sampling_freq, "Original")
fft_ac_analysis(sampled_adjusted, sampling_freq, "Adjusted")
```



```

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from google.colab import files
from scipy.signal import find_peaks
from scipy.fft import fft

# ========================================
# 📥 ① CSVファイルのアップロード / Upload CSV
# ========================================
# Google Colab で CSV ファイルをアップロードする
# Upload a CSV file from your local machine into Google Colab
uploaded = files.upload()
filename = next(iter(uploaded))
df = pd.read_csv(filename)

# ========================================
# ⏱ ② 信号データの抽出と前処理 / Extract & preprocess signal
# ========================================
# time_data列を取得し、全体を0.0001秒だけ前にシフト
# Get time_data and subtract 0.0001s from all values
time = df["time_data"] - 0.0001

# 信号データを取得
# Get the raw signal values
signal = df["signal_data"]

# DCオフセット（127）を除去して中央を0に揃える
# Remove DC offset assuming center at 127 (e.g., 8-bit ADC)
adjusted_signal = signal - 127

# 時間が0以上のデータだけを残す（負の時間は無効とする）
# Filter out negative time values (keep only time >= 0)
mask = time >= 0
time = time[mask].reset_index(drop=True)
signal = signal[mask].reset_index(drop=True)
adjusted_signal = adjusted_signal[mask].reset_index(drop=True)

# ========================================
# 📊 ③ 波形の表示 / Plot original & adjusted signals
# ========================================
# 元の信号とDCオフセット除去後の信号を同時にプロット
# Plot both original and adjusted signal on the same graph
plt.figure(figsize=(12, 4))
plt.plot(time, signal, label="Original Signal", color='blue', alpha=0.6)
plt.plot(time, adjusted_signal, label="Adjusted Signal", color='green', alpha=0.6)
plt.title("Original vs Adjusted Signal")
plt.xlabel("Time [s]")
plt.ylabel("Amplitude")
plt.grid(True)
plt.legend()
plt.show()

# ========================================
# 🧾 ④ 先頭と末尾の値を確認 / Print head & tail of signals
# ========================================
# 信号と調整後信号の最初と最後の10点を表示
# Print first and last 10 values of both original and adjusted signals
print("=== Original Signal: First 10 values ===")
print(signal.head(10))
print("=== Original Signal: Last 10 values ===")
print(signal.tail(10))

print("=== Adjusted Signal: First 10 values ===")
print(adjusted_signal.head(10))
print("=== Adjusted Signal: Last 10 values ===")
print(adjusted_signal.tail(10))

# ========================================
# 🎯 ⑤ サンプリング処理 / Sampling
# ========================================
# サンプリング周波数を10MHz（=0.1μs間隔）に設定
# Set sampling frequency to 10 MHz (0.1 µs interval)
sampling_freq = 10_000_000
sampling_period = 1 / sampling_freq

# サンプリング時間軸を作成（等間隔）
# Create evenly spaced sampling timestamps
sampled_times = np.arange(time.iloc[0], time.iloc[-1], sampling_period)

# 最も近い実データ点を検索し、インデックスとして取得
# Find indices of actual data points nearest to the sampling times
time_np = time.to_numpy()
idx = np.searchsorted(time_np, sampled_times, side="left")
idx = np.clip(idx, 0, len(time_np) - 1)  # 範囲外を防ぐ / Prevent out-of-bounds

# サンプリングした信号の配列を作成
# Extract sampled signals using the indices
sampled_original = signal.to_numpy()[idx]
sampled_adjusted = adjusted_signal.to_numpy()[idx]

# サンプリング結果のプロット
# Plot sampled points over the original and adjusted signals
plt.figure(figsize=(12, 4))
plt.plot(time, signal, alpha=0.3, label="Original")
plt.scatter(sampled_times, sampled_original, s=10, c='blue', label="Sampled Original")
plt.plot(time, adjusted_signal, alpha=0.3, label="Adjusted")
plt.scatter(sampled_times, sampled_adjusted, s=10, c='green', label="Sampled Adjusted")
plt.title("Sampled Signals")
plt.xlabel("Time [s]")
plt.ylabel("Amplitude")
plt.legend()
plt.grid(True)
plt.show()

# ========================================
# ⏱ ⑥ 周波数推定 / Frequency estimation
# ========================================
# ピーク検出から信号周期・周波数を推定する関数
# Function to estimate frequency from signal peaks
def estimate_frequency(signal, label):
    peaks, _ = find_peaks(signal, height=10)
    if len(peaks) < 2:
        print(f"{label} → Not enough peaks.")
        return None
    peak_times = time.iloc[peaks].to_numpy()
    periods = np.diff(peak_times)
    avg_period = np.mean(periods)
    freq = 1 / avg_period
    print(f"{label} Frequency: {freq:.2f} Hz, Period: {avg_period:.2e} s")
    return avg_period

# オリジナルと調整後の周期を取得
# Estimate the period for original and adjusted signals
period_orig = estimate_frequency(signal, "Original")
period_adj = estimate_frequency(adjusted_signal, "Adjusted")

# ========================================
# 🔄 ⑦ 1周期で並び替え / Sort samples by phase
# ========================================
# 位相順にデータを並べ替える関数
# Sort signal data by phase modulo period
def sort_by_phase(t, y, period):
    phase = t % period
    sorted_idx = np.argsort(phase)
    return phase[sorted_idx], y[sorted_idx]

# 周期が得られていれば、位相順で並べてプロット
# If period is valid, sort and plot by phase
if period_orig and period_adj:
    phase_orig, sorted_orig = sort_by_phase(sampled_times, sampled_original, period_orig)
    phase_adj, sorted_adj = sort_by_phase(sampled_times, sampled_adjusted, period_adj)

    plt.figure(figsize=(12, 4))
    plt.plot(phase_orig, sorted_orig, '.', markersize=3, label="Original")
    plt.plot(phase_adj, sorted_adj, '.', markersize=3, label="Adjusted")
    plt.title("Sorted by Phase")
    plt.xlabel("Phase [s]")
    plt.ylabel("Amplitude")
    plt.legend()
    plt.grid(True)
    plt.show()

# ========================================
# 🔍 ⑧ FFTによるAC性能評価 / AC performance via FFT
# ========================================
# FFTを使ってSNDR, ENOB, SFDRを計算しプロットする関数
# Analyze AC performance (SNDR, ENOB, SFDR) using FFT
def fft_ac_analysis(signal, fs, label):
    N = len(signal)
    bit_resolution = 8
    v_min = np.min(signal)
    v_max = np.max(signal)

    # 0〜255に正規化（8ビットADC相当）
    # Normalize signal to 8-bit digital levels
    normalized = (signal - v_min) / (v_max - v_min) * (2**bit_resolution - 1)

    # FFTの実行とパワースペクトルの計算
    # Perform FFT and compute power spectrum
    F = fft(normalized) * 2 / N
    Power = np.abs(F)**2
    freq = np.fft.fftfreq(N, d=1/fs)
    freq_norm = freq / fs
    Pow_dB = 10 * np.log10(Power + 1e-12)  # log(0)回避 / Avoid log(0)

    # 基本波のインデックスを探す
    # Identify fundamental frequency component
    signal_idx = np.argmax(Power[1:N//2]) + 1
    Signal = Power[signal_idx]
    Noise = np.sum(Power[1:N//2]) - Signal

    # SNDRとENOBの計算
    SNDR = 10 * np.log10(Signal / Noise)
    ENOB = (SNDR - 1.76) / 6.02

    # SFDR（スプリアスの最大値との差）
    spurious = [np.max(Pow_dB[signal_idx * i - 2: signal_idx * i + 2]) for i in range(2, 5)]
    SFDR = Pow_dB[signal_idx] - np.max(spurious)

    # 最大成分を0dBに正規化
    Pow_dB -= Pow_dB[signal_idx]

    # パワースペクトルのプロット
    plt.figure(figsize=(10, 4))
    plt.plot(freq_norm[1:N//2], Pow_dB[1:N//2])
    plt.title(f"{label} - Power Spectrum (Normalized to 0 dB)")
    plt.xlabel("f/fs")
    plt.ylabel("Power [dB]")
    plt.grid(True)
    plt.text(0.3, -5, f"SNDR = {SNDR:.2f} dB")
    plt.text(0.3, -10, f"ENOB = {ENOB:.2f} bit")
    plt.text(0.3, -15, f"SFDR = {SFDR:.2f} dB")
    plt.tight_layout()
    plt.show()

    # 結果出力
    print(f"===== {label} FFT Results =====")
    print(f"Peak Frequency Index: {signal_idx}")
    print(f"→ Normalized Freq: {freq_norm[signal_idx]:.6f} f/fs")
    print(f"→ Actual Freq:     {freq[signal_idx]:.2f} Hz")
    print(f"SNDR: {SNDR:.2f} dB")
    print(f"ENOB: {ENOB:.2f} bit")
    print(f"SFDR: {SFDR:.2f} dB")
    print("================================")

# FFTによる解析を実行 / Run FFT analysis
fft_ac_analysis(sampled_original, sampling_freq, "Original")
fft_ac_analysis(sampled_adjusted, sampling_freq, "Adjusted")
```