Frequenz von 20Hz bis 20kHz mit dem Pico erzeugen

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Achim Klein
User
Beiträge: 41
Registriert: Dienstag 21. Februar 2023, 13:57

Mit diesem Programm möchte ich eine durchstimmbare Frequenz von 20Hz bis 20kHz erzeugen. Die einstellung soll mit einem Poti am ADC(26) erfolgen. Wenn ich nur einzelne Frequenzen erzeufe funktioniert es super. Habe zur Kontrolle des ADC Wertes eine print drin. Diese zeigt mir "Frequenz: 20000" an. Am MCP4921 erhalte ixch keine Frequenzausgabe. Es werden keine Fehler angegeben.

Code: Alles auswählen

import machine
from machine import Pin, SPI, ADC, Timer
import math
import time

spi_sck = Pin(2)              # SCK pin at GP2
spi_tx = Pin(3)               # TX pin at GP3
spi_rx = Pin(0)               # RX pin at GP4 (not used)

spi = SPI(0, baudrate=100000, sck=spi_sck, mosi=spi_tx, miso=spi_rx)

CS = Pin(16, Pin.OUT)         # CS

R = 2 * math.pi / 50
T = 100
Conv = 4095.0 / 3.3
PeaktoPeak = 1.4 * Conv       # 1.4V
ReqDCoffset = 1.0 * Conv      # 1.6V
k = 0

potentiometer = ADC(26)        # Potentiometer connected to ADC pin 26

def DAC(timer):
    global k, CS, sins
    buff = bytearray(2)
    k = k + 1
    if k == 50:
        k = 0
    data = int(sins[k])
    buff[0] = (data >> 8) & 0xFF      # HIGH Byte
    buff[1] = data & 0xFF             # LOW Byte
    CS.value(0)                       # Enable MCP4921
    spi.write(buff)                   # Send to SPI bus
    CS.value(1)                       # Disable MCP4921

# Main program
sins = [0] * 101

def update_frequency(pot):
    global sins
    frequency = map_range(pot.read_u16(), 0, 65535, 20, 20000)  # Map ADC value to desired frequency range
    print("Frequency:", frequency)  # Debugging output 
    for i in range(101):
        sins[i] = ReqDCoffset + PeaktoPeak/2 + PeaktoPeak/2 * math.sin(R*i * frequency)

def map_range(value, in_min, in_max, out_min, out_max):
    return int((value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)

tim = machine.Timer()

start_frequency = 20  # Start frequency (20 Hz)
end_frequency = 20000  # End frequency (20 kHz)
wobble_duration = 10  # Duration of the wobble in seconds

step_count = 100
step_delay = wobble_duration / step_count

current_frequency = start_frequency
frequency_step = (end_frequency - start_frequency) / step_count

for _ in range(step_count):
    update_frequency(potentiometer)  # Update frequency based on potentiometer
    tim.init(freq=current_frequency, mode=machine.Timer.PERIODIC, callback=DAC)
    time.sleep(step_delay)
    current_frequency += frequency_step

tim.deinit()  # Stop the timer


Hat jemand eine Idee was ich falsch mache?
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Achim Klein: Das ist ja alles ein wenig unübersichtlich mit dem was da alles global auf Modulebene herum schwirrt. Und man erkennt an den Namen auch nicht was Konstante und was Variable ist. Das sollte so nicht sein. Bei ``global sins`` musste ich schmunzeln.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

`Timer` wird importiert aber nicht verwendet. Stattdessen wird da über `machine` zugegriffen. Das ist dann aber auch der einzige Zugriff auf etwas aus dem Modul. Alles andere wird explizit importiert.

Kommentare sollten keine Information wiederholen die im Code schon mal steht. Also nicht noch mal den Variablen-/Konstantennamen und schon gar nicht den Wert der zugewiesen wird. Wenn man den mal ändert, muss man den ja auch in den Kommentaren noch mal ändern. Wenn man das vergisst, oder da einen Fehler macht, dann widersprechen sich Code und Kommentar und man erreicht genau das Gegenteil von dem wozu Kommentare gedacht sind. Der Kommentar ``# 1.6V`` ist so ein Beispiel. Entweder der Kommentar ist falsch, oder der Code, oder der Kommentar sollte da noch erklären wie man von ``1.0 * Conv`` auf 1,6V kommt. Oder der Code/Kommentar der Zeile darüber ist falsch. Bei `spi_rx` scheint der Code oder der Kommentar auch falsch zu sein.

`T` wird definiert aber nirgends verwendet.

Warum werden 101 Werte generiert, von denen aber immer nur die ersten 50 verwendet? Und sollte die 50 da tatsächlich hart im Code kodiert werden. Die muss man ja an zwei Stellen im Programm anpassen, wo sie doch anscheinend von der Länge der Sünden — äh Sinuswerte abhängt. Und auch diese 101 sollte nur einmal im Programm stehen, damit man die Pufferlänge für die Samples einfach ändern kann in dem man die Länge der Liste ändert.

Das ``current_frequency += frequency_step`` ist keine so gute Idee, weil man da wiederholt Ungenauigkeiten von der Gleitkommarepräsentation aufaddiert. Besser wäre es die Schrittnummer mit der Schrittweite multiplizieren.

Zwischenstand (ungetestet):

Code: Alles auswählen

import math
import time

from machine import ADC, SPI, Pin, Timer

SPI_SCK_PIN = 2
SPI_TX_PIN = 3
SPI_RX_PIN = 0  # Not used by hardware.

CURRENT_FACTOR = 4095 / 3.3  # Volt to DAC value.
PEAK_TO_PEAK = 1.4 * CURRENT_FACTOR
DC_OFFSET = 1.0 * CURRENT_FACTOR


def map_range(value, in_min, in_max, out_min, out_max):
    return int(
        (value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
    )


class SignalGenerator:
    def __init__(self, spi, enable_pin, timer):
        self.samples = [0] * 100
        self.sample_index = 0
        self.spi = spi
        self.enable_pin = enable_pin
        self.timer = timer

    def output_sample(self, _timer=None):
        value = int(self.samples[self.sample_index])
        self.sample_index = (self.sample_index + 1) % (len(self.samples) // 2)
        self.enable_pin.value(0)  # Enable MCP4921
        self.spi.write(bytes([(value >> 8) & 0xFF, value & 0xFF]))
        self.enable_pin.value(1)  # Disable MCP4921

    def set_frequencies(self, signal_frequency, output_frequency):
        r = 2 * math.pi / (len(self.samples) // 2)
        for i in range(len(self.samples)):
            self.samples[i] = (
                DC_OFFSET
                + PEAK_TO_PEAK / 2
                + PEAK_TO_PEAK / 2 * math.sin(r * i * signal_frequency)
            )
        self.timer.init(
            freq=output_frequency,
            mode=Timer.PERIODIC,
            callback=self.output_sample,
        )


def main():
    timer = Timer()
    try:
        generator = SignalGenerator(
            SPI(
                0,
                baudrate=100_000,
                sck=Pin(SPI_SCK_PIN),
                mosi=Pin(SPI_TX_PIN),
                miso=Pin(SPI_RX_PIN),
            ),
            Pin(16, Pin.OUT),
            timer,
        )
        potentiometer = ADC(26)

        start_frequency = 20  # in Hz.
        end_frequency = 20_000  # in Hz.
        wobble_duration = 10  # in seconds.

        step_count = 100
        step_delay = wobble_duration / step_count
        frequency_step = (end_frequency - start_frequency) / step_count

        for i in range(step_count):
            generator.set_frequencies(
                map_range(potentiometer.read_u16(), 0, 0xFFFF, 20, 20000),
                start_frequency + i * frequency_step,
            )
            time.sleep(step_delay)

    finally:
        timer.deinit()


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Achim Klein
User
Beiträge: 41
Registriert: Dienstag 21. Februar 2023, 13:57

Habe es auf den Pico geladen. Es kommt keine Fehlermeldung, es wird aber keine Frquenz ausgegeben.
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Es ist ja auch ”nichts” geändert gegenüber dem ursprünglichen Programm. Wie sehen denn die Samplewerte aus? Und warum werden davon nur die Hälfte verwendet?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Achim Klein
User
Beiträge: 41
Registriert: Dienstag 21. Februar 2023, 13:57

Eigentlich sieht das Programm anders aus als bei mir. Habe angefangen print einzufügen und mir Werte anzeigen zu lassen. Bin mir aber nicht sicher ob ich immer die richtige Stelle gefunden habe. Beispiel:

Code: Alles auswählen

def update_frequency(timer):
    global sins
    frequency = map_range(potentiometer.read_u16(), 0, 65535, 20, 20000)  # Map ADC value to desired frequency range
    print("Frequency:", frequency)  # Debugging output
    for i in range(101):
        sins[i] = ReqDCoffset + PeaktoPeak/2 + PeaktoPeak/2 * math.sin(R*i * frequency)
    print("Sine wave values:", sins)  # Debugging output
Da wird mir bei 3,3V eingang am ADC 20000 angezeigt. Sine wave gar nicht.
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Achim Klein: Es sieht anders aus als bei Dir, weil so wie es bei Dir aussieht sollte es nicht aussehen. Aber es macht das gleiche. Falls ich keinen Fehler gemacht habe.

Was heisst „Sine wave gar nicht“? Das `print()` wird nicht ausgeführt?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Achim Klein
User
Beiträge: 41
Registriert: Dienstag 21. Februar 2023, 13:57

Es wird auf dem Monitor angezeigt" Frequenz: 20000". Das "Sine.. " wird nicht angezeigt.
Antworten