¶
L’échantillonnage consiste de la lecture d’un signal à intervalles réguliers. Dans un cas idéal on serait capable de mesurer une valeur pour chaque instant du domaine temporel. Cela n’est pas réalisable en pratique.
Effet du chevauchement et Spectre d’Échantillonnage¶
Cette section illustre l’effet du chevauchement lors de l’échantillonnage d’un signal triangulaire. Vous pouvez ajuster la fréquence du signal et la fréquence d’échantillonnage pour observer :
Dans le domaine temporel :
- Le signal continu (en bleu).
- Les échantillons obtenus (points rouges).
Dans le domaine fréquentiel :
- Le spectre du signal continu.
- Les répliques du spectre causées par l’échantillonnage.
Explorez les paramètres pour observer comment le chevauchement se manifeste lorsque est insuffisante pour capturer correctement le signal.
Source
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, FloatSlider
def triangular_wave(frequency, t):
return 2 * (np.abs(2 * ((t * frequency) % 1) - 1)) - 1
def plot_chevauchement_spectral(signal_frequency=5, sampling_frequency=20):
T = 1.0 # Durée (1s)
N = 2000 # Points pour la FFT
dt = T / N
t = np.linspace(0, T, N, endpoint=False)
# Signal continu + échantillonnage
x_continu = triangular_wave(signal_frequency, t)
t_sample = np.arange(0, T, 1/sampling_frequency)
x_sample = triangular_wave(signal_frequency, t_sample)
# FFT
X = np.fft.fftshift(np.fft.fft(x_continu))
freq_axis = np.fft.fftshift(np.fft.fftfreq(N, d=dt))
X_mag = np.abs(X) / N
# Création de la figure
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
# 1) Domaine temporel
axes[0].plot(t, x_continu, label='Signal continu', lw=2)
axes[0].stem(t_sample, x_sample, linefmt='r', markerfmt='ro', basefmt=' ', label='Échantillons')
axes[0].set_title(f"Signal triangulaire : f={signal_frequency} Hz, fs={sampling_frequency} Hz")
axes[0].set_xlabel("Temps (s)")
axes[0].set_ylabel("Amplitude")
axes[0].set_ylim(-1.2, 1.2)
axes[0].grid(True)
axes[0].legend()
# 2) Domaine fréquentiel
Fs = sampling_frequency
axes[1].plot(freq_axis, X_mag, label='Spectre principal', lw=2, color='blue')
# # Bande de Nyquist
# axes[1].axvspan(-Fs/2, Fs/2, color='y', alpha=0.2, label='Bande de Nyquist')
# axes[1].axvline(x=-Fs/2, color='g', linestyle='--', alpha=0.5, label='-Fs/2')
# axes[1].axvline(x=Fs/2, color='g', linestyle='--', alpha=0.5, label='Fs/2')
# Répliques du spectre autour de k*Fs
nb_replicas = 3
for k in range(-nb_replicas, nb_replicas + 1):
if k != 0:
axes[1].plot(freq_axis + k*Fs, X_mag, 'r--', alpha=0.4,
label='Échantillons' if k == 1 else None)
axes[1].set_title("Spectre échantillonné et répliques")
axes[1].set_xlabel("Fréquence (Hz)")
axes[1].set_ylabel("Amplitude")
axes[1].set_xlim(-nb_replicas * Fs, nb_replicas * Fs)
axes[1].set_ylim(0, 1.2 * X_mag.max())
axes[1].grid(True)
axes[1].legend(loc='upper right')
plt.tight_layout()
plt.show()
interact(
plot_chevauchement_spectral,
signal_frequency=FloatSlider(value=5, min=1, max=30, step=1, description="Signal (Hz)"),
sampling_frequency=FloatSlider(value=20, min=5, max=60, step=1, description="Échantillonnage (Hz)")
)1. Domaine temporel (graphique de gauche)¶
- La courbe bleue représente le signal triangulaire continu.
- Les points rouges (et leurs traits verticaux) représentent les échantillons du signal, obtenus en prélevant régulièrement le signal continu à la fréquence d’échantillonnage (fs).
- Lorsque la fréquence d’échantillonnage est trop faible, on observe un chevauchement temporel : les échantillons ne capturent plus correctement la forme réelle du signal.
2. Domaine fréquentiel (graphique de droite)¶
- La courbe bleue au centre illustre le spectre du signal triangulaire (FFT de la version continue, centrée via
fftshift). - Les courbes rouges en pointillé représentent les répliques de ce spectre autour des multiples de la fréquence d’échantillonnage .
- Quand (la fréquence d’échantillonnage) est suffisamment élevée, les répliques ne chevauchent pas le spectre principal : il n’y a pas de chevauchement.
- Si est trop faible, alors ces répliques viennent se superposer (ou se rapprocher) du spectre principal : il se produit un chevauchement fréquentiel.
#Modulations Binaires de Porteuse de Base
¶
La modulation PAM (Pulse Amplitude Modulation) consiste à représenter un signal message sous forme d’impulsions en multipliant le signal par un train d’impulsions périodiques.
Cette section illustre :
Signal message et onde carrée :
- Une sinusoïde représentant le message.
- Un train d’impulsions périodiques contrôlé par le rapport cyclique.
Signal PAM :
- Résultat de la multiplication du signal message par les impulsions.
Source
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, FloatSlider
# Paramètres globaux pour la simulation
fs = 2000 # Fréquence d'échantillonnage "interne" (haute) pour la simulation
t_max = 1.0 # Durée d'observation (1 seconde)
t = np.linspace(0, t_max, int(fs*t_max), endpoint=False)
def pam_square_wave(
message_freq=5, # Fréquence du signal message
sampling_freq=10, # Fréquence de la porteuse/échantillonnage
duty_cycle=0.2 # Proportion de la période où l'onde carrée est à 1
):
"""
PAM avec impulsions de type onde carrée.
- message_freq : fréq. du signal message (sinus)
- sampling_freq : "fréquence" du train d'impulsion
- duty_cycle : rapport cyclique de l'onde carrée (entre 0 et 1)
"""
# ---------- 1) Génération du signal message (sinusoïde) ----------
message = np.sin(2 * np.pi * message_freq * t)
# ---------- 2) Génération de l'onde carrée pour l'échantillonnage ----------
# Période d'échantillonnage
Ts = 1.0 / sampling_freq
# Création d'un vecteur "onde carrée" (même dimension que t)
# Méthode : pour chaque intervalle [k*Ts, (k+1)*Ts),
# on met 1 pendant 'duty_cycle'% du temps, sinon 0.
square_wave = np.zeros_like(t)
for k in range(int(t_max * sampling_freq)):
start_idx = int(k * Ts * fs) # indice début de période
end_idx = int((k+1) * Ts * fs) # indice fin de période
if end_idx > len(t):
end_idx = len(t)
pulse_width = duty_cycle * (end_idx - start_idx)
pulse_end = start_idx + int(pulse_width)
# Remplit de 1 dans [start_idx, pulse_end)
square_wave[start_idx:pulse_end] = 1
# ---------- 3) Réalisation de la modulation PAM ----------
# Multiplication point à point du message par l'onde carrée
pam_signal = message * square_wave
# ---------- 4) Affichage ----------
plt.figure(figsize=(10, 5))
# a) Affichage du signal message + onde carrée
plt.subplot(2, 1, 1)
plt.plot(t, message, label='Signal message', lw=1.5)
plt.plot(t, square_wave, label='Onde carrée (Train d\'impulsions)', alpha=0.7)
plt.ylim(-1.2, 1.2)
plt.title(f"PAM - Signal message & onde carrée\nf_msg={message_freq}Hz, f_sampling={sampling_freq}Hz, duty={duty_cycle}")
plt.xlabel("Temps (s)")
plt.ylabel("Amplitude")
plt.grid(True)
plt.legend()
# b) Affichage du signal PAM
plt.subplot(2, 1, 2)
plt.plot(t, pam_signal, color='red', label='Signal PAM', lw=1.5)
plt.ylim(-1.2, 1.2)
plt.xlabel("Temps (s)")
plt.ylabel("Amplitude")
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
# ----- Interface interactive -----
interact(
pam_square_wave,
message_freq=FloatSlider(value=5, min=1, max=30, step=1, description="f_msg"),
sampling_freq=FloatSlider(value=10, min=2, max=60, step=1, description="f_samp"),
duty_cycle=FloatSlider(value=0.2, min=0.01, max=0.9, step=0.01, description="duty")
);¶
La modulation PWM (Pulse Width Modulation) consiste à moduler la largeur des impulsions en fonction de l’amplitude d’un signal message. Cela est réalisé en comparant le signal message avec une onde triangulaire .
Résultats visualisés¶
Signal message et onde triangulaire :
- Le signal message varie entre 0 et 1.
- L’onde triangulaire est utilisée comme référence de comparaison.
Signal PWM :
- Un signal binaire obtenu en mettant 1 lorsque , sinon 0.
Instructions interactives¶
- : Fréquence du signal message (en Hz).
- : Fréquence de l’onde triangulaire (en Hz).
Source
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, FloatSlider
# Paramètres globaux pour la simulation
fs = 2000 # Fréquence d'échantillonnage interne (pour la simulation)
t_max = 1.0 # Durée d'observation
t = np.linspace(0, t_max, int(fs*t_max), endpoint=False)
def pwm_modulation(
message_freq=5, # Fréquence du message (en Hz)
carrier_freq=50 # Fréquence de l'onde triangulaire (en Hz)
):
"""
PWM : On compare le signal message (entre 0 et 1) avec une onde triangulaire
pour générer un signal à largeur d'impulsion variable.
"""
# 1) Générer le signal message (amplitude entre 0 et 1)
# - Variation sinus : centrée en 0.5, amplitude 0.5 => [0,1]
message = 0.5 + 0.5 * np.sin(2 * np.pi * message_freq * t)
# 2) Générer l'onde triangulaire (0 -> 1 sur chaque période)
# Période de l'onde triangulaire
Tc = 1.0 / carrier_freq
tri_wave = (t % Tc) / Tc # Remise à zéro toutes les Tc
# 3) Comparaison -> PWM
# - On met 1 si message > tri, sinon 0
pwm_signal = (message > tri_wave).astype(float)
# 4) Tracé
plt.figure(figsize=(10, 5))
# a) Signal message + onde triangulaire
plt.subplot(2,1,1)
plt.plot(t, message, label='Message (0 à 1)', lw=1.5)
plt.plot(t, tri_wave, 'r', label='Onde triangulaire', alpha=0.7)
plt.ylim(-0.1, 1.1)
plt.title(f"Signal Message vs. Onde Triangulaire\nf_msg={message_freq}Hz, f_car={carrier_freq}Hz")
plt.xlabel("Temps (s)")
plt.legend()
plt.grid(True)
# b) Signal PWM
plt.subplot(2,1,2)
plt.plot(t, pwm_signal, 'g', label='Signal PWM', lw=1.5)
plt.ylim(-0.1, 1.1)
plt.title("Signal PWM (largeur d'impulsion modulée)")
plt.xlabel("Temps (s)")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# ----- Interface interactive -----
interact(
pwm_modulation,
message_freq=FloatSlider(value=5, min=1, max=30, step=1, description="f_msg"),
carrier_freq=FloatSlider(value=50, min=10, max=200, step=10, description="f_car")
)¶
La modulation PPM (Pulse Position Modulation) consiste à placer une impulsion unique dans chaque période d’un signal de référence. La position de l’impulsion dépend de l’amplitude du signal message .
Résultats visualisés¶
Signal message :
- Le signal message varie entre 0 et 1.
Signal PPM :
- Une impulsion est placée dans chaque période .
- La position de l’impulsion est proportionnelle à l’amplitude de .
Instructions interactives¶
- : Fréquence du signal message (en Hz).
- : Fréquence de la porteuse ou référence temporelle (en Hz).
Source
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, FloatSlider
# Paramètres pour la simulation
fs = 2000 # "Haute" fréquence pour la simulation
t_max = 1.0 # Durée d'observation en secondes
t = np.linspace(0, t_max, int(fs*t_max), endpoint=False)
def ppm_example(message_freq=5, carrier_freq=10):
"""
PPM :
- On divise le temps en périodes T = 1/carrier_freq.
- Dans chaque période, on place UNE impulsion.
- Sa position dépend de l’amplitude du message (entre 0 et 1).
"""
# 1) Générer le signal message entre 0 et 1
message = 0.5 + 0.5 * np.sin(2 * np.pi * message_freq * t)
# 2) Nombre de périodes pendant la durée t_max
Ts = 1.0 / carrier_freq
nb_periods = int(t_max * carrier_freq)
# 3) Génération du signal PPM initialisé à 0
ppm_signal = np.zeros_like(t)
# 4) Placer une impulsion par période
# La position de l’impulsion est proportionnelle à la valeur du message
max_delay_fraction = 0.8 # évite que l'impulsion ne dépasse la fin de la période
for k in range(nb_periods):
start_idx = int(k * Ts * fs)
end_idx = int((k+1) * Ts * fs)
if end_idx >= len(t):
break
# Amplitude du message au début de la période
amp_val = message[start_idx]
# Décalage en échantillons
delay_samples = int(amp_val * max_delay_fraction * (end_idx - start_idx))
pulse_idx = start_idx + delay_samples
if pulse_idx < end_idx:
ppm_signal[pulse_idx] = 1
# 5) Affichage
plt.figure(figsize=(10, 5))
# a) Signal message
plt.subplot(2, 1, 1)
plt.plot(t, message, label='Message (0 -> 1)', lw=1.5)
plt.ylim(-0.1, 1.1)
plt.title(f"Signal Message : f_msg = {message_freq} Hz")
plt.xlabel("Temps (s)")
plt.legend()
plt.grid(True)
# b) Signal PPM
plt.subplot(2, 1, 2)
plt.plot(t, ppm_signal, 'r', label='Signal PPM', lw=1.5)
plt.ylim(-0.2, 1.2)
plt.title(f"Signal PPM : f_car = {carrier_freq} Hz")
plt.xlabel("Temps (s)")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# Interface interactive
interact(
ppm_example,
message_freq=FloatSlider(value=5, min=1, max=30, step=1, description="f_msg"),
carrier_freq=FloatSlider(value=10, min=5, max=50, step=5, description="f_car")
)Explication du résultat ci-dessus (Modulation PPM)¶
- Le temps total est découpé en périodes de durée :
- Ces périodes sont numérotées :
- Au début de chaque période, on lit l’amplitude du message.
- Cette amplitude est convertie en un décalage (en échantillons), qui détermine où la seule impulsion est placée à l’intérieur de la période.
- Signal message fort (proche de 1) : l’impulsion est placée vers la fin de la période.
- Signal message faible (proche de 0) : l’impulsion est placée au début de la période.
Caractéristiques principales :¶
- La largeur de l’impulsion est constante.
- La position de l’impulsion varie proportionnellement à l’amplitude du signal.
¶
La modulation PCM (Pulse Code Modulation) est un processus de conversion d’un signal analogique en un signal numérique par trois étapes principales : l’échantillonnage, la quantification, et la reconstruction.
- : Nombre de bits pour la quantification (contrôle la précision de la quantification).
- : Nombre d’échantillons utilisés pour la reconstruction (contrôle la fréquence d’échantillonnage).
Ajustez les paramètres pour observer l’impact de la fréquence d’échantillonnage et du nombre de bits sur la qualité du signal reconstruit.
Source
# Importation des bibliothèques nécessaires
import numpy as np
import matplotlib.pyplot as plt
import ipywidgets as widgets
from IPython.display import display, clear_output
import math # Importation du module math pour la fonction ceil
# Activation des graphiques inline
%matplotlib inline
# Définition de la fonction principale pour le PCM interactif
def pcm_interactif(n_bits=3, num_samples=50):
"""
Fonction interactive pour démontrer le processus de Modulation par Code de Pulsations (PCM).
Paramètres :
- n_bits : nombre de bits pour la quantification
- num_samples : nombre d'échantillons
"""
# Effacer les sorties précédentes
clear_output(wait=True)
# Paramètres fixes
A = 1 # Amplitude du signal
f = 5 # Fréquence du signal en Hz
t_max = 1 # Durée du signal en secondes
fs_analog = 1000 # Fréquence d'échantillonnage analogique en Hz
# Calcul de la fréquence d'échantillonnage
fs = num_samples / t_max # Fréquence d'échantillonnage en Hz
# Génération du signal analogique
t = np.arange(0, t_max, 1/fs_analog)
signal_analog = A * np.sin(2 * np.pi * f * t)
# Échantillonnage du signal
Ts = 1/fs
t_ech = np.arange(0, t_max, Ts)
signal_echantillonne = A * np.sin(2 * np.pi * f * t_ech)
# Quantification des échantillons
q_levels = 2 ** n_bits # Nombre de niveaux de quantification
A_max = A
A_min = -A
delta = (A_max - A_min) / q_levels # Taille d'un pas de quantification
# Quantification des échantillons
signal_quantifie = np.floor((signal_echantillonne - A_min) / delta) * delta + delta / 2 + A_min
signal_quantifie = np.clip(signal_quantifie, A_min, A_max - delta)
# Reconstruction du signal
if fs == 0:
repeat_factor = 1
else:
repeat_factor = math.ceil(fs_analog / fs) # Utilisation de ceil pour éviter les longueurs insuffisantes
signal_reconstruit = np.repeat(signal_quantifie, repeat_factor)
signal_reconstruit = signal_reconstruit[:len(t)] # Ajustement de la longueur
# Affichage des graphiques
plt.figure(figsize=(14, 6))
plt.plot(t, signal_analog, label='Signal analogique', color='blue')
plt.stem(t_ech, signal_echantillonne, linefmt='r--', markerfmt='ro', basefmt='k-', label='Échantillons') # Suppression de 'use_line_collection'
plt.step(t, signal_reconstruit, where='mid', label='Signal reconstruit (PCM)', color='green', linestyle='-')
plt.title(f'PCM avec {n_bits} bits de quantification et {num_samples} échantillons')
plt.xlabel('Temps (s)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.show()
# Affichage des informations sur la quantification
print(f"**Paramètres actuels :**")
print(f"- Fréquence du signal (f) : {f} Hz")
print(f"- Fréquence d'échantillonnage (fs) : {fs:.2f} Hz")
print(f"- Nombre de bits (n_bits) : {n_bits}")
print(f"- Nombre d'échantillons : {num_samples}")
print(f"- Durée du signal : {t_max} s")
print()
print(f"**Quantification :**")
print(f"- Nombre de niveaux de quantification (q_levels) : {q_levels}")
print(f"- Taille d'un pas de quantification (delta) : {delta:.5f}")
print()
# print(f"**Échantillons quantifiés :**")
# for i, val in enumerate(signal_quantifie):
# if n_bits <= 32:
# code = format(int((val - A_min) / delta), f'0{n_bits}b')
# else:
# # Pour n_bits > 32, l'encodage binaire peut être trop long, donc on l'indique comme N/A
# code = 'N/A'
# print(f"Échantillon {i+1}: Valeur = {val:.5f}, Code = `{code}`")
# Création des widgets interactifs
bits_widget = widgets.IntSlider(value=3, min=1, max=8, step=1, description='Bits:')
samples_widget = widgets.IntSlider(value=50, min=10, max=125, step=1, description='Échantillons:')
# Liaison des widgets à la fonction interactive
interactive_pcm = widgets.interactive(pcm_interactif, n_bits=bits_widget, num_samples=samples_widget)
# Affichage des widgets et de la fonction interactive
display(interactive_pcm)