Modulation · Digital

Amplitude Shift Keying (ASK)

Digitale Information über Amplitudenstufen eines Trägersignals — einfach zu implementieren, aber störanfällig.

Amplitude Shift Keying ist eine Modulationsmethode, bei der die Amplitude eines Trägersignals verändert wird, um digitale Informationen zu übertragen. Sie ist einfach zu implementieren, aber anfällig für Rauschen und Störungen — geeignet für Anwendungen über kurze Distanzen mit geringem Rauschpegel.

Typische Anwendungen

  • Fernbedienungen
  • Nahfeldkommunikation (NFC)
  • RFID (Radio Frequency Identification)
  • Übertragung von Messdaten
  • Hausautomation

Wichtige Konzepte

On-Off-Keying (OOK) — eine Sonderform, bei der die Amplitude für den Low-Pegel auf 0 gesetzt wird, wie z.B. bei der Morsetelegrafie.

Mehrstufiges ASK — es können mehrere Amplitudenstufen genutzt werden. ASK-4 überträgt 2 Bit pro Symbol mittels vier unterschiedlicher Amplitudenwerte.

Samples per Symbol (SPS) — Anzahl der Samples pro Symbol bei der Signalerzeugung; im Beispiel werden 100 Samples pro Symbol verwendet.

Symbolrate — gibt an, wie viele Symbole pro Sekunde übertragen werden. Bei 3 MHz Abtastrate und SPS = 100 ergeben sich ca. 30.000 Symbole pro Sekunde.

Vierstufiges ASK-Signal im Zeitbereich

Beispiel in Python

Die folgenden Funktionen erzeugen ein 4-stufiges ASK-Signal aus einer Symbolfolge und demodulieren es anschließend wieder — einmal per Korrelation, einmal per Schwellwertentscheidung.

import numpy as np

# Parameter
sampling_rate = 3_000_000    # 3 MHz
sps = 100                    # Samples pro Symbol
carrier_frequency = 300_000  # Hz
levels = np.array([0.0, 1/3, 2/3, 1.0])  # 4 Amplitudenstufen für 2 Bit/Symbol

def generate_ask_signal(symbols, levels, carrier_frequency, sampling_rate, sps):
    """Erzeugt ein mehrstufiges ASK-Signal aus einer Symbolfolge."""
    t_symbol = np.arange(sps) / sampling_rate
    carrier = np.cos(2 * np.pi * carrier_frequency * t_symbol)
    return np.concatenate([levels[symbol] * carrier for symbol in symbols])
import numpy as np

# Parameter
sampling_rate = 3_000_000    # 3 MHz
sps = 100                    # Samples pro Symbol
carrier_frequency = 300_000  # Hz
levels = np.array([0.0, 1/3, 2/3, 1.0])  # 4 Amplitudenstufen für 2 Bit/Symbol

def generate_ask_signal(symbols, levels, carrier_frequency, sampling_rate, sps):
    """Erzeugt ein mehrstufiges ASK-Signal aus einer Symbolfolge."""
    t_symbol = np.arange(sps) / sampling_rate
    carrier = np.cos(2 * np.pi * carrier_frequency * t_symbol)
    return np.concatenate([levels[symbol] * carrier for symbol in symbols])

Ausschnitt eines erzeugten ASK-Signals

Demodulationsverfahren

Es gibt zwei grundlegende Ansätze:

1. Korrelationsverfahren

Das Empfangssegment wird mit dem bekannten Trägersignal korreliert (Skalarprodukt) und auf die Energie des Trägers normiert. Das Ergebnis entspricht direkt der gesendeten Amplitude; die nächstgelegene bekannte Stufe liefert das demodulierte Symbol.

def demodulate_ask_correlation(signal, levels, carrier_frequency, sampling_rate, sps):
    """Demoduliert ein ASK-Signal per Korrelation mit dem Träger."""
    t_symbol = np.arange(sps) / sampling_rate
    carrier = np.cos(2 * np.pi * carrier_frequency * t_symbol)
    carrier_energy = np.dot(carrier, carrier)

    symbols = []
    for i in range(0, len(signal) - sps + 1, sps):
        segment = signal[i:i + sps]
        amplitude = np.dot(segment, carrier) / carrier_energy
        symbol = np.argmin(np.abs(levels - amplitude))
        symbols.append(symbol)
    return np.array(symbols)
def demodulate_ask_correlation(signal, levels, carrier_frequency, sampling_rate, sps):
    """Demoduliert ein ASK-Signal per Korrelation mit dem Träger."""
    t_symbol = np.arange(sps) / sampling_rate
    carrier = np.cos(2 * np.pi * carrier_frequency * t_symbol)
    carrier_energy = np.dot(carrier, carrier)

    symbols = []
    for i in range(0, len(signal) - sps + 1, sps):
        segment = signal[i:i + sps]
        amplitude = np.dot(segment, carrier) / carrier_energy
        symbol = np.argmin(np.abs(levels - amplitude))
        symbols.append(symbol)
    return np.array(symbols)

2. Schwellwertverfahren

Statt der Korrelation wird hier nur die Energie des Empfangssegments relativ zur Energie des Trägers betrachtet. Aus dem Energieverhältnis lässt sich die Amplitude zurückrechnen; feste Schwellen zwischen den bekannten Stufen entscheiden dann über das Symbol.

def demodulate_ask_threshold(signal, levels, carrier_frequency, sampling_rate, sps):
    """Demoduliert ein ASK-Signal über die Energie je Symbolabschnitt und feste Schwellen."""
    t_symbol = np.arange(sps) / sampling_rate
    carrier = np.cos(2 * np.pi * carrier_frequency * t_symbol)
    carrier_energy = np.dot(carrier, carrier)
    thresholds = (levels[:-1] + levels[1:]) / 2  # Mitte zwischen benachbarten Stufen

    symbols = []
    for i in range(0, len(signal) - sps + 1, sps):
        segment = signal[i:i + sps]
        amplitude_estimate = np.sqrt(np.dot(segment, segment) / carrier_energy)
        symbol = np.searchsorted(thresholds, amplitude_estimate)
        symbols.append(symbol)
    return np.array(symbols)
def demodulate_ask_threshold(signal, levels, carrier_frequency, sampling_rate, sps):
    """Demoduliert ein ASK-Signal über die Energie je Symbolabschnitt und feste Schwellen."""
    t_symbol = np.arange(sps) / sampling_rate
    carrier = np.cos(2 * np.pi * carrier_frequency * t_symbol)
    carrier_energy = np.dot(carrier, carrier)
    thresholds = (levels[:-1] + levels[1:]) / 2  # Mitte zwischen benachbarten Stufen

    symbols = []
    for i in range(0, len(signal) - sps + 1, sps):
        segment = signal[i:i + sps]
        amplitude_estimate = np.sqrt(np.dot(segment, segment) / carrier_energy)
        symbol = np.searchsorted(thresholds, amplitude_estimate)
        symbols.append(symbol)
    return np.array(symbols)

Screenshot der Demodulation

Das Korrelationsverfahren erweist sich unter verrauschten Bedingungen als widerstandsfähiger, da es die Gesamtenergie über das komplette Symbolintervall gegen den Träger auswertet statt nur einzelne Momentanamplituden — Rauschanteile mitteln sich über die Symboldauer teilweise heraus.

Zum Test lässt sich Rauschen künstlich hinzufügen und beide Verfahren vergleichen:

symbols_to_send = [0, 1, 2, 3, 2, 1, 0, 3]
tx_signal = generate_ask_signal(symbols_to_send, levels, carrier_frequency, sampling_rate, sps)

# Rauschen hinzufügen
noise = np.random.normal(0, 0.05, size=tx_signal.shape)
rx_signal = tx_signal + noise

decoded_correlation = demodulate_ask_correlation(rx_signal, levels, carrier_frequency, sampling_rate, sps)
decoded_threshold = demodulate_ask_threshold(rx_signal, levels, carrier_frequency, sampling_rate, sps)
symbols_to_send = [0, 1, 2, 3, 2, 1, 0, 3]
tx_signal = generate_ask_signal(symbols_to_send, levels, carrier_frequency, sampling_rate, sps)

# Rauschen hinzufügen
noise = np.random.normal(0, 0.05, size=tx_signal.shape)
rx_signal = tx_signal + noise

decoded_correlation = demodulate_ask_correlation(rx_signal, levels, carrier_frequency, sampling_rate, sps)
decoded_threshold = demodulate_ask_threshold(rx_signal, levels, carrier_frequency, sampling_rate, sps)

ASK-Signal mit Rauschen

Video

Understanding Amplitude Shift Keying