Forum: Mikrocontroller und Digitale Elektronik mit µC Sinus erzeugen + Amplitudenmodulation


von Mathias K. (matzeknu)


Lesenswert?

Hallo,

ich möchte ein veriables Sinus-Signal (40-500 Hz Nutzsignal) erzeugen 
und dieses mit ungefähr 3kHz (Trägersignal) Modulieren 
(Amplitudenmodulation).
Das möchte ich mit einem µC machen (z.B. Atmega64).

Zum Ausgeben der Spannung habe ich mir ein R2R-Widerstandsnetzwerk 
aufgebaut, dessen Ausgang mit einem OP verstärkt wird.

Theoretisch kann ich meinen Sinus (Nutzsignal) ja in einer lookup table 
anlegen. Die Frequenz stell ich durch unteschiedlich schnelles 
rausschreiben an die Ports ein. Sehe ich das richtig? (oder ist eine 
lookup table nur für ein Signal mit fester Frequenz geeigne?)

nun habe ich mir überlegt eine zweite lookup table für das Trägersignal 
anzulegen (feste 3kHz) und diese beiden miteinander zu multiplizieren 
(für die Amplitudenmodulation) und dann erst an die Ports zu schreiben.

Hat jemand Erfahrungen damit? Ist es generell möglich mit dem µC ein 
Moduliertes Signal zu erzeugen?


danke schonmal
Grüße Matze

von Pandur S. (jetztnicht)


Lesenswert?

Ja, man kann einen Sinus modulieren und ausgeben. Der Geforderte ist 
allerdings etwas seltsam..

von Pandur S. (jetztnicht)


Lesenswert?

Kannst du dir den mal als simulation im PC rechnen lasen ?

von Reinhard #. (gruebler)


Lesenswert?

Mathias K. schrieb:
> ....zu multiplizieren> (für die Amplitudenmodulation)
> und dann erst an die Ports zu schreiben.

Bei der AM wir der Träger erst mit dem Nutzsignal
multipliziert und das Produkt dann mit dem
Träger addiert.

von Mathias K. (matzeknu)


Angehängte Dateien:

Lesenswert?

Jetzt Nicht schrieb:
> Kannst du dir den mal als simulation im PC rechnen lasen ?


Bisher mache ich es mit einem Tektronix Funktionsgenerator, das sieht 
dann so aus.. Berechnen am PC für die tabelle? Wie macht man das am 
einfachsten?

von Reinhard #. (gruebler)


Lesenswert?

Mathias K. schrieb:
> das sieht
> dann so aus..

Das linke Bild ist nur das Produkt
aus Träger und Nutzsignal. Man sieht
es deutlich am Phasensprung in der Mitte.
Da ist noch kein AM-Signal.

> Berechnen am PC...Wie macht man das am einfachsten?
Mit Excel

: Bearbeitet durch User
von Mathias K. (matzeknu)


Angehängte Dateien:

Lesenswert?

Reinhard ## schrieb:
> Mathias K. schrieb:
>> das sieht
>> dann so aus..
>
> Das linke Bild ist nur das Produkt
> aus Träger und Nutzsignal. Man sieht
> es deutlich am Phasensprung in der Mitte.
> Da ist noch kein AM-Signal.
>
>> Berechnen am PC...Wie macht man das am einfachsten?
> Mit Excel


Sprung in der Mitte? ich hab das Modulierte Signal verschoben, so sieht 
es aus wenn ichs nicht verschieb.

Ich probier das in excel gleich mal..

von Mathias K. (matzeknu)


Angehängte Dateien:

Lesenswert?

das kann doch nicht stimmen

von Georg (Gast)


Lesenswert?

Mathias K. schrieb:
> ich möchte ein veriables Sinus-Signal (40-500 Hz Nutzsignal) erzeugen
> und dieses mit ungefähr 3kHz (Trägersignal) Modulieren

Dann sagt man üblicherweise, man moduliert das 3kHz-Signal mit dem 
variablen Sinus. Nicht umgekehrt. Auch wenn das rein mathematisch nicht 
so wichtig ist.

Georg

von Hans (Gast)


Lesenswert?

Mega64 und in software modulieren dürfte sportlich sein...

Was gehen dürfte wäre 2 solcher DACs zu koppeln ... sprich an einem port 
den träger ausgeben und das signal das dort rauskommt dann quasi als 
Vref für den 2. verwenden.

Damit hättest du keine Multiplikation in software mehr.

Wenn du nur auf einen sinusförmigen Träger einen sinus aufmodulieren 
willst, dann würdest du mit einer einzigen sinus-lookup-table (und davon 
auch nur die 1. 45°) auskommen.

73

von Christian L. (cyan)


Lesenswert?

Mathias K. schrieb:
> das kann doch nicht stimmen

In deinen Screenshots benutzt du Sinussignale, welche symmetrisch zum 
Nullpunkt sind. In deiner Exceltabelle sind sie das aber nicht. Folglich 
sieht das alles völlig anders aus.

von Tim S. (tim_seidel) Benutzerseite


Lesenswert?

Mathias K. schrieb:
> das kann doch nicht stimmen

Ein Sinus pendelt um 0, nicht um 125.
In der Multiplikation ist dieser Versatz (Bias / DC Offset) relevant.

Wenn du das Nutzsignal und Trägersignal zwischen 0 und 250 definiesrt, 
dann ist die Bias-kompensierte Multiplikation dieser Träger:

ModuliertesSignal(t) = (Nutz(t) - Amplitude_Nutz / 2) * (Träger(t) - 
Amplitude_Träger / 2)

Da du es per ADC ausgibst und wahrscheinlich wieder einen Bias brauchst:

AusgabeADC(t) = ModuliertesSignal(t) + AmplutudeADC / 2

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Application Note AVR314 beschreibt, wie man mit einem kleinen AVR aus 
einer Sinustabelle gleichzeitig zwei sinusähnliche Töne generiert und 
diese gemeinsam ausgibt.
Das ganze dient eigentlich zur Erzeugung von DTMF Tönen, kann aber auch 
für sowas hier benutzt werden.

von Alexander S. (esko) Benutzerseite


Angehängte Dateien:

Lesenswert?

Hier noch eine Tabelle zum ausprobieren.

von Georg (Gast)


Lesenswert?

Matthias Sch. schrieb:
> Das ganze dient eigentlich zur Erzeugung von DTMF Tönen

Das ist aber eine Addition, keine Multiplikation.

Georg

von c-hater (Gast)


Lesenswert?

Mathias K. schrieb:

> ich möchte ein veriables Sinus-Signal (40-500 Hz Nutzsignal) erzeugen
> und dieses mit ungefähr 3kHz (Trägersignal) Modulieren
> (Amplitudenmodulation).

Das ist völlig unmöglich. Genau anders rum kann es hingegen 
funktionieren. Moduliert wird der Träger, nicht das Basisband...

> Das möchte ich mit einem µC machen (z.B. Atmega64).

Kein Problem.

> Theoretisch kann ich meinen Sinus (Nutzsignal) ja in einer lookup table
> anlegen.

Das geht sogar praktisch sehr gut...

> Die Frequenz stell ich durch unteschiedlich schnelles
> rausschreiben an die Ports ein. Sehe ich das richtig?

Kann man so machen, muß man aber nicht. Man kann sie auch mit konstenter 
Geschwindigkeit ausgeben, aber unterschiedlich "schnell" aus der Tabelle 
einlesen. Das nennt man dann DDS...

> nun habe ich mir überlegt eine zweite lookup table für das Trägersignal
> anzulegen

Das ist nun kompletter Unsinn. Die Sinustabelle benötigt man immer nur 
einmal. Denn ein Sinus sieht unabhängig von der Frequenz immer wie ein 
Sinus aus und der einzige Sinn der Tabelle ist, die Form des Signals zu 
beschreiben.

> diese beiden miteinander zu multiplizieren

Darum wirst du keinesfalls herumkommen...

Du brauchst also:

Eine Tabelle
Zwei Instanzen einer DDS mit unterschiedlichen Schrittrate (eine davon 
zur Laufzeit änderbar: dein Nutzsignal)
Eine Multiplikation
Eine Ausgabe

von Reinhard #. (gruebler)


Angehängte Dateien:

Lesenswert?

Hier ein Beispiel, zu dem was ich oben
bereits gesagt habe.

Im Bild:
Im oberen Diagramm sind nur Träger mit
dem Signal multipliziert. Man erkennt
deutlich den Phasensprung.
Im unteren Diagramm wird das Produkt
durch einen Modulationsfaktor geschwächt
zum Träger addiert. Es entsteht die AM.

Die Excel-Tabelle liegt bei.

von Mathias K. (matzeknu)


Lesenswert?

woow vielen Dank für die ganzen Antworten, ist echt sehr hilfreich!
Danke Leute!!

Ich schau mir das mal in Ruhe an und versuche es dann in code 
umzusetzen.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Georg schrieb:
> Das ist aber eine Addition, keine Multiplikation.

Na, wie wäre ein wenig Denken 'out of the Box'? Natürlich kann man mit 
ein wenig Eigeninitiative aus der Addition eine Multiplikation machen...
Die AN demonstriert aber sehr gut, wie man simultan zwei verschiedene 
Sinustöne aus einer Tabelle mit einem lächerlich kleinen MC produzieren 
kann - nix

Hans schrieb:
> Mega64 und in software modulieren dürfte sportlich sein...

Von wegen...

von komisch (Gast)


Lesenswert?

Nehmen wir mal 8,5 KHz Abtastfrequenz für die Look-Up-Tables, bleiben 
uns bei einem CPU-Takt von 16 MHz ca. 1882 Takte um ein paar Werte aus 
dem Speicher in Register zu laden, ein(e) paar Addition und 
Multiplikationen durchzuführen und das ganze auf einen port auszugeben. 
Dass geht sogar mit c noch locker...

Gruß J

von Mathias K. (matzeknu)


Angehängte Dateien:

Lesenswert?

so nun habe ich es mal Programmiert, leider bekomme ich nur eine sehr 
geringe Frequenz hin (maximal 315Hz ohne Compiler-Optimierung, mit 
höchster Optimierung 1,1kHz)..
glaub mein Porgramm ist nicht das effektivste (siehe unten)..

Jetz schlagt ihr sicher die Hände überm Kopf zusammen ^^
Ich frage mit einer if-Anweisung jedes meiner 8-Bits einzeln ab.

Eigentlich habe ich ein 16-Bit R2R aufgebaut allerdings benutz ich jetz 
nur die oberen 8 bit, da es sonst noch langsamer ist.

Bit 0..7 sind auf PortA0 .. PortA7
Bit 8..15 sind auf PortC7 .. PortC0

mit
PORTC = byte_1;

geht es ja nicht, da die 8bit verdreht sind...

Hier mal mein Code:


uint8_t sinus_array[128] = {128,134,140,146,152,158,165,170,
  176,182,188,193,198,203,208,213,
  218,222,226,230,234,237,240,243,
  245,248,250,251,253,254,254,255,
  255,255,254,254,253,251,250,248,
  245,243,240,237,234,230,226,222,
  218,213,208,203,198,193,188,182,
  176,170,165,158,152,146,140,134,
  128,121,115,109,103,97,90,85,
  79,73,67,62,57,52,47,42,
  37,33,29,25,21,18,15,12,
  10,7,5,4,2,1,1,0,
  0,0,1,1,2,4,5,7,
  10,12,15,18,21,25,29,33,
  37,42,47,52,57,62,67,73,
  79,85,90,97,103,109,115,121};

 for (int i = 0; i<127;i++)
 {
   byte_1 = sinus_array[i];

// ***************************  PortC7  ************
         if (byte_1 & (0x01 << 0))
         {
           // Pin auf High
           PORTC |= (1<<PC7);
         }
         else
         {
           // pin auf Low
           PORTC &=~ (1<<PC7);
         }

         // ***************************  PortC6  ************
         if (byte_1 & (0x01 << 1))
         {
           // Pin auf High
           PORTC |= (1<<PC6);
         }
         else
         {
           // pin auf Low
           PORTC &=~ (1<<PC6);
         }

         // ***************************  PortC5  ************
         if (byte_1 & (0x01 << 2))
         {
           // Pin auf High
           PORTC |= (1<<PC5);
         }
         else
         {
           // pin auf Low
           PORTC &=~ (1<<PC5);
         }

         // ***************************  PortC4 ************
         if (byte_1 & (0x01 << 3))
         {
           // Pin auf High
           PORTC |= (1<<PC4);
         }
         else
         {
           // pin auf Low
           PORTC &=~ (1<<PC4);
         }

         // ***************************  PortC3 ************
         if (byte_1 & (0x01 << 4))
         {
           // Pin auf High
           PORTC|= (1<<PC3);
         }
         else
         {
           // pin auf Low
           PORTC &=~ (1<<PC3);
         }
         // ***************************  PortC2 ************
         if (byte_1 & (0x01 << 5))
         {
           // Pin auf High
           PORTC |= (1<<PC2);
         }
         else
         {
           // pin auf Low
           PORTC &=~ (1<<PC2);
         }
         // ***************************  PortC1  ************
         if (byte_1 & (0x01 << 6))
         {
           // Pin auf High
           PORTC |= (1<<PC1);
         }
         else
         {
           // pin auf Low
           PORTC &=~ (1<<PC1);
         }

         // ***************************  PortC0  ************
         if (byte_1 & (0x01 << 7))
         {
           // Pin auf High
           PORTC |= (1<<PC0);
         }
         else
         {
           // pin auf Low
           PORTC &=~ (1<<PC0);
         }

}

: Bearbeitet durch User
von Uwe (de0508)


Lesenswert?

Hallo Mathias,

der Trick besteht ja darin die Bytes direkt auf den Port schreiben zu 
können.

So etwas zu machen, ist nicht effizient:
1
Bit 0..7 sind auf PortA0 .. PortA7
2
Bit 8..15 sind auf PortC7 .. PortC0

PORTA 7-0 MSB-LSB LOW_BYTE
PORTC 7-0 MSB-LSB HIGH_BYTE

Dann kann man einfach schreiben:
1
uint16_t sinus_value = sinus_array[idx];
2
PORTC = (uint8_t)(sinus_value >> 8);
3
PORTA = (uint8_t)sinus_value;

von Uwe (de0508)


Angehängte Dateien:

Lesenswert?

Hallo Mathias,

Ich habe z.B. mit einem Attiny45 über eine PWM-Sinustabelle mit 135 
Stützstellen ein 1750Hz Sinussignal generiert.
Das 3 PWM Schwingungen pro Stützstelle enthält und am PWM-Ausgang folgt 
noch ein 3-Stufiges TP-Filter und ein Emitterfolger.

: Bearbeitet durch User
von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Uwe S. schrieb:
> Ich habe z.B. mit einem Attiny45 über eine PWM-Sinustabelle mit 135
> Stützstellen ein 1750Hz Sinussignal generiert.

Würde man den kleinen Kerl jetzt noch mit einem 16Mhz Oszillator vor 
sich hertreiben, sind also schon 3500 Hz drin und wäre damit innerhalb 
der Anforderungen des TE. Bei DTMF ist ja die höchste Frequenz 1633 Hz.
https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling
Generell gilt, je kürzer und gröber die Tabelle, desto höher die 
erreichbare Frequenz.

von Uwe S. (Gast)


Lesenswert?

Hallo  Matthias,

die 16MHz sind auch jetzt schon die Taktfrequenz des attiny45.
Dieser läuft ja mit dem R/C Oszillator und mit dem PLL Mode bei64MHz I/O 
Frequenz und mit dem entsprechendem CPU Teiler auf 16MHz.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Uwe S. schrieb:
> die 16MHz sind auch jetzt schon die Taktfrequenz des attiny45.

Ah, cool. Siehste, ich habe mit der PLL noch gar nicht viel gemacht. Es 
gab allerdings ein Projekt, wo ein Tiny direkt auf 96 Mhz einen UKW 
Sender gemimt hat, da scheint also noch Luft nach oben zu sein.

von klausr (Gast)


Lesenswert?

Matthias Sch. schrieb:
> Generell gilt, je kürzer und gröber die Tabelle, desto höher die
> erreichbare Frequenz.

Oder: je kürzer und gröber die Tabelle, desto mehr Oberwellen hat der 
Sinus.

von X4U (Gast)


Angehängte Dateien:

Lesenswert?

Mein Vorschlag:

2 PWMs nehmen
2 Übertrager erzeugen 2 x Sinus (wie lautet eigentlich der Plural?)
Dann durch einen Mischer jagen.


- null Prozessorlast für den Sinus
- gute THD
- galvanisch getrennt
- Spannungsbereich über Wicklungsverhältnis einstellbar

von X4U (Gast)


Angehängte Dateien:

Lesenswert?

X4U schrieb:
> Mein Vorschlag:

Ist nicht so dolle, sieht zwar gut aus taugt aber nichts.

THD von 0,5% schafft man auch mit einem 8 Bit Wandler

von chris_ (Gast)


Lesenswert?

Es gab hier schon einige Versuche, die Reinheit eines DDS-erzeugten 
Sinus zu verbessern:
Beitrag "DDS Sinus erzeugung"

von Mathias K. (matzeknu)


Lesenswert?

Uwe S. schrieb:
> Hallo Mathias,
>
> der Trick besteht ja darin die Bytes direkt auf den Port schreiben zu
> können.
...
...


ja das hab ich mir auch gedacht, habs jetz nochmal neu aufgebaut mit nem 
Amega32 und korrekter Byte-Portbelegung :) danke

von Mathias K. (matzeknu)


Angehängte Dateien:

Lesenswert?

Tim Seidel schrieb:
> Mathias K. schrieb:
>> das kann doch nicht stimmen
>
> Ein Sinus pendelt um 0, nicht um 125.
> In der Multiplikation ist dieser Versatz (Bias / DC Offset) relevant.
>
> Wenn du das Nutzsignal und Trägersignal zwischen 0 und 250 definiesrt,
> dann ist die Bias-kompensierte Multiplikation dieser Träger:
>
> ModuliertesSignal(t) = (Nutz(t) - Amplitude_Nutz / 2) * (Träger(t) -
> Amplitude_Träger / 2)
>
> Da du es per ADC ausgibst und wahrscheinlich wieder einen Bias brauchst:
>
> AusgabeADC(t) = ModuliertesSignal(t) + AmplutudeADC / 2




Hallo ich bins nochmal, ich habe die Platine wie gesagt optimiert, 
sodass

Bit 0..7  -> PortC 0..7
Bit 8..15 -> PortA 0..7

sind.

Die ersten 8bit, also PortC fällt kaum ins Gewicht, deshalb hab ich mich 
jetz mal auf PortA also nur 8bit beschränkt:


uint8_t sinus_array_8bit[256] = {128,131,134,137,140,143,146,149,
   152,155,158,162,165,167,170,173,
         ...
         ...


while(1)
{
  sinus_trager = sinus_array_8bit[i];
  PORTA = sinus_trager;
  i++;
}

dabei kommt beigefügter Sinus raus. Ich komme natürlich auch mit weniger 
als 256 Werten aus, dementsprechend erhöht sich dann meine Frequenz.

So nun weiß ich leider gar nicht wie ich jetz weiter machen soll.

Ich muss jetz irgendwie zwei Signale aus der Tabelle entnehmen, den 
offset raus rechnen und diese dann miteanander Multiplizieren?

Mit zwei verschachtelten for schleifen hab ich es nicht hinbekommen.
Ich weiß erhlich gesagt auch nicht wie ich das in C programmieren soll.

Grüße Matze

von Uwe (de0508)


Lesenswert?

Hallo Matze,

ich denke, fange mal bei deiner Grundfrequenz an :
f_grund = 1000Hz => t_grund = 1/f_grund = 1ms

Die Anzahl der Stützstellen des Sinus sei N_grund = 100,
damit muss man alle t1_dac = t_grund /N_grund = 10µs eine neuen DAC-Wert 
berechnen und ausgeben.

Diese 100kHz seinen mal dein Grundtakt im AVR µC System, auch wenn die 
100kHz nur sehr schwer zu erreichen sein werden.

Willst Du nun dieses Trägersignal mit einem Nutzsignal AM Modulieren,
so folgt für die Nutzfrequenz 100Hz mit 100 Stützstellen:

f_trag = 100Hz ==> t_trag = 10ms
N_trag = 100
t2_dac = t_trag /N_trag = 100µs

Das Verhältnis D = t2_dac /t1_dac = 10 bestimmt den abgeleiteten 
Zeittakt (Tick).
Somit wird alle 10 Ticks ein neuer Wert für das Trägersignal ausgelesen 
und für insg. 10 Ticks verwendet.

von Mathias K. (matzeknu)


Angehängte Dateien:

Lesenswert?

Uwe S. schrieb:
> Hallo Matze,
>
> ich denke, fange mal bei deiner Grundfrequenz an :
> f_grund = 1000Hz => t_grund = 1/f_grund = 1ms
> ...
> ...




Hallo danke schon mal für den Tipp.

Ich habs jetz so gelößt:
Damit bekomm ich schonmal eine Trägerfrequenz von 1800Hz hin.
Allerdings wird diese mit jeder Zeile Code immer kleiner.

Könnte das ganze auch in einem Interrupt über z.B. Timer0 laufen lassen, 
dann komme ich aber maximal auf 1000 Hz Trägerfrequenz.

Ist mein code völliger mist oder kann man was damit anfangen?
Nun weiß ich nicht, wie ich die AM lösen kann.. sorry ich brauch mal 
wieder ein Gedankenanstoß, da einfach Multiplizieren nicht geklappt hat.

Danke schonmal!!


while(1)
{
// Träger N
if (N_trag>99)
{
  N_trag=0;
}
// Trägerfrequenz
sinus_trager = sinus_array_8bit[N_trag];


// alle 17 Zyklen Nutzfrequenz (1900Hz / 19 Hz = 100 Hz)
if (N_tick>18)
{
  // Nutz N
  if (N_nutz>99)
  {
    N_nutz=0;
  }
  sinus_nutz = sinus_array_8bit[N_nutz];
  N_nutz++;
  N_tick=0;
}

PORTA = sinus_trager;

N_trag++;
N_tick++;
}

von Mathias K. (matzeknu)


Angehängte Dateien:

Lesenswert?

bzw einfach multipliziert kommt eben wieder murks raus, weil ich ja nur 
positive werte hab..

von Mathias K. (matzeknu)


Angehängte Dateien:

Lesenswert?

ok ich habs glaub ich hinbekommen:

moduliert = ((sinus_nutz-128) * (sinus_trager-128)) / 128;
PORTA = moduliert+128;

von Uwe (de0508)


Lesenswert?

Hallo Mathias,

fein, den AM Modulationsgrad hat die wie festgelegt ?

Und Mathematisch kannst Du dir so etwas erspare !
1
sinus_nutz-128
lege halt je eine Tabelle an vom Typ int8_t an.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

Eine Lösung der ursprünglichen Aufgabestellung wäre: Einen DDS für den 
Träger in einen AVR bauen und die Sample-Werte mit dem Nutzsignal zu 
modulieren.

Wär nur interessant, wo das Nutzsignal herkommt ...

von TelTower (Gast)


Lesenswert?

ElmChan hat in seiner DDS-Spieldose 6Töne gleichzeitig erzeugt.
Gilt das Abklingen lassen des Tons am Ende auch als 
Amplitudenmodulation? Dann könnte man dort evtl. ansetzen?
Er hatte ja für jeden Ton einen Tonanschlag, einen Mittelteil und ein 
"Auskling"teil.
der Tonanschlag wurde aus einer Tabelle genommen, der Tonmittelteil aus 
einer anderen, einer Sinustabelle. Dieser Teil wurde dann immer 
wiederholt und mit 8Bit "Pegel" multipliziert. Das höherwertige Byte der 
Multiplikation war dann der, in der Lautstärke abklingende, Ton.

http://elm-chan.org/works/mxb/report.html
https://www.youtube.com/watch?v=rJHp8H-gobs

von Mathias K. (matzeknu)


Lesenswert?

Uwe S. schrieb:
> Hallo Mathias,
>
> fein, den AM Modulationsgrad hat die wie festgelegt ?
>
> Und Mathematisch kannst Du dir so etwas erspare !
>
1
sinus_nutz-128
> lege halt je eine Tabelle an vom Typ int8_t an.



Modulationsgrad ist 100% wenn Träger und Nutz die gleiche Amplitude 
haben? Oder?
Bin gerade auf dem Heimweg, werde das dann recherchieren.

von c-hater (Gast)


Lesenswert?

Uwe S. schrieb:

> Diese 100kHz seinen mal dein Grundtakt im AVR µC System, auch wenn die
> 100kHz nur sehr schwer zu erreichen sein werden.

Warum um alles in der Welt sollten die schwer zu erreichen sein? Bei 
20MHz Systemtakt hat man pro Periode des "Grundtaktes" (gemeint ist wohl 
der Ausgabetakt) satte 200 MCU-Takte zur Verfügung. Damit kann man eine 
Menge auf die Beine stellen.

Und, na klar, es ist überhaupt kein Problem, innert 200 Takten zwei 
Werte aus einer Tabelle im Flash zu lesen, diese miteinander zu 
multiplizieren und das Ergebnis auszugeben sowie die zwei Phasenzähler 
für den Zugriff auf die Tabelle zu inkrementieren. Tatsächlich geht das 
sogar mit wesentlich weniger Takten.

Phasenzähler incrementieren mit konstantem Increment (für den Träger), 
Tabellenwert holen und in Register speichern:

subi CP0,Byte1(-CarrierIncrement)
sbci CP1,Byte2(-CarrierIncrement)
sbci CP2,Byte3(-CarrierIncrement)
mov ZL,CP2
lpm R0,Z

macht 7 Takte für diesen Teil.

Phasenzähler mit variablem Increment (für die Modulation), Tabellenwert 
holen und in Register speichern:

add MP0,Mod0
adc MP1,Mod1
adc MP2,Mod2
mov ZL,MP2
lpm R1,Z

macht nochmal 7 Takte für diesen Teil.

Beide Werte multiplizieren, und das Ergebnis ausgeben:

mul R0,R1
out DACPort,R1

macht drei Takte für den Kram.

Und damit ist man auch schon komplett durch und hat nur 17 von den 200 
Takten verbraucht, also nicht mal 10% der verfügbaren Rechenleistung...

Na gut, wenn man mal annimmt, daß in der restlichen Zeit irgendeine 
aufwendige Modulation erst berechnet werden muß, braucht man R0 und R1 
für die Multiplikationen dort. Und wenn in dem Teil auch noch auf 
konstante Daten aus dem Flash zugegriffen werden muß, braucht man auch Z 
dort. Und wenn dann noch die Struktur der Berechnung einfach gehalten 
werden soll, muß man die Ausgabe als ISR laufen lassen. Das führt dann 
zu dem Overhead von:

(konstante Interuptlatenz)
ISR:
push R0
push R1
push ZL
push ZH

ldi ZH,High(Tableaddress)

(Operationen von oben)

pop ZH
pop ZL
pop R1
pop R0
reti

Dadurch kommen also nochmal 25 Takte dazu (wenn man die ISR "in place" 
ab Interruptvektor verortet), wir sind also dann bei 42 Takten. Es 
bleiben dann immer noch 79% der Rechenleistung für andere Aufgaben (also 
insbesondere die Modulation) über.

Sprich effektiv hast du etwa die Rechenleistung eines AVR mit 16MHz in 
main() zur Verfügung und mußt dort nur auf drei Allzweck-Register und 
sechs von den weniger wichtigen verzichten, hast also immer noch satte 
23 Register zur Verfügung, insbesondere auch alle, die für irgendwelche 
Sonderzwecke nötig sind.

Was genau scheint dir angesichts dieser Fakten denn nun so schwer 
erreichbar an dieser einfachen Sache?

Deine Krücke ist C. Wirf die einfach weg, dann merkst du, wie elegant 
und schnell man auf eigenen Beinen laufen kann!

von Uwe (de0508)


Lesenswert?

Ach Herr c-hater,
der TE will nun mal in C programmieren.
Dass ich das Problem auch in Assembler lösen kann, weis ich.
Aber das nützt Mathias kein bisschen sich die Grundlagen zu erarbeiten 
und ein immer bessere/ effizientere Lösung (Programm) zu finden.
Wir waren alle mal ein Kleinkind und sind nun schon 8 Jahre alt, wenn 
wir mit 20 in Rente gehen, haben wir auch alles gelernt.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

c-hater schrieb:
> Deine Krücke ist C. Wirf die einfach weg, dann merkst du, wie elegant
> und schnell man auf eigenen Beinen laufen kann!

Auch der C-Compiler in -O3 braucht keine 200 Takte für sowas ...

Schön, dass es Leute gibt, die immer noch in Assembler programmieren. 
Meiner Meinung nach aber völlig unnötig ...

von m.n. (Gast)


Lesenswert?

Uwe S. schrieb:
> Ach Herr c-hater,
> der TE will nun mal in C programmieren.

Da wäre ich mir nicht mehr sicher, wenn Mathias den 
Geschwindigkeitsvorteil von Assembler sieht. Je nach Compiler werden in 
C nämlich zu viele Register gesichert, was nur unnötig bremst.

Mampf F. schrieb:
> Schön, dass es Leute gibt, die immer noch in Assembler programmieren.
> Meiner Meinung nach aber völlig unnötig ...

Genau hier ist Assembler optimal. Eine Mischung aus C, wo die 
zeitunkritischen Aufgaben ablaufen, und Assembler, der schnelles und 
exaktes Timing erzeugt, ist hier sinnvoll.

c-hater schrieb:
> (konstante Interuptlatenz)
> ISR:
> push R0
> push R1
> push ZL
> push ZH

Am besten sichert man diese Register im IO-Bereich des AVR. Die neueren 
µCs haben GPIOx-Register, auf die in nur einem Takt zugegriffen werden 
kann. Alternativ nimmt man andere Register von ungenutzter Peripherie.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

m.n. schrieb:
> Uwe S. schrieb:
>> Ach Herr c-hater,
>> der TE will nun mal in C programmieren.
>
> Da wäre ich mir nicht mehr sicher, wenn Mathias den
> Geschwindigkeitsvorteil von Assembler sieht. Je nach Compiler werden in
> C nämlich zu viele Register gesichert, was nur unnötig bremst.
>
> Mampf F. schrieb:
>> Schön, dass es Leute gibt, die immer noch in Assembler programmieren.
>> Meiner Meinung nach aber völlig unnötig ...
>
> Genau hier ist Assembler optimal. Eine Mischung aus C, wo die
> zeitunkritischen Aufgaben ablaufen, und Assembler, der schnelles und
> exaktes Timing erzeugt, ist hier sinnvoll.

Ich kann schnelles und exaktes Timing auch in C machen ... Man kann auch 
ISRs für Timer in C schreiben ... Es könnte sein, dass die ISR mehr 
Register speichert als verwendet, aber in 200 Takte bringt man sehr viel 
unter


> µCs haben GPIOx-Register, auf die in nur einem Takt zugegriffen werden
> kann. Alternativ nimmt man andere Register von ungenutzter Peripherie.

Den Tipp würde ich mit Vorsicht genießen ... Ist ja schon ein ziemlicher 
Hack.

Ich bin 100% davon überzeugt, dass man weder Assembler noch andere fiese 
Tricks wie zB Register in Register ungenutzter Peripherie speichern 
benötigt.

Ich sag keinesfalls, dass Assembler schlecht ist oder so ... Ich hab 
selber zwei Jahrzehnte mit Assembler von 8032, 8080, Z80, PIC8, AVR, 
ARM7, x86 von 8086 bis SSE4, DSP usw gearbeitet. Es gibt 
Einsatzbereiche, wo Assembler sinnvoll und oft notwendig ist. Aber bei 
dieser Popelaufgabe seh ich das jetzt ehrlich gesagt nicht und nur des 
Idealismus wegen die Aufgabe in Assembler zu lösen ist wie mit Kanonen 
auf Spatzen schießen - imho ;)

Die letzten Controller-Projekte hab ich nur noch mit dem GCC gemacht und 
fand eigentlich ganz gut, was da an kompilierten Code herausgekommen ist 
...

von Mampf F. (mampf) Benutzerseite


Lesenswert?

Achja und wenn man die ursprüngliche Fragestellung berücksichtigt, hat 
mat man für einen Samplewert sogar >2000 Takte Zeit ...

Obwohl das Nyquist-Theorem sagt, dass man die doppelte Frequenz zum 
Abtasten braucht, geht man bei DDS von der dreifachen aus (Rauschen wird 
geringer), um ein brauchbares Signal zu erhalten.

Bei 20MHz und 1Takt/Befehl wären das bei 9kHz Timer-Frequenz ~2222 
Takte, die man Zeit hat einen Sample-Wert zu erzeugen.

Die obigen 100kHz oder 200 Takte entspringen einfach falschen 
Überlegungen  ...

: Bearbeitet durch User
von m.n. (Gast)


Lesenswert?

Mampf F. schrieb:
> Ich bin 100% davon überzeugt, dass man weder Assembler noch andere fiese
> Tricks wie zB Register in Register ungenutzter Peripherie speichern
> benötigt.

Ich gratuliere Dir zu Deiner 100% Sicherheit, obwohl Du vieleicht 10% 
der Aufgabe kennst ;-)
Verrate mir aber bitte noch, woher Du weißt, daß es 200 Takte/Zyklus 
sind und 30 Stützstellen ausreichen.

Ganz oben steht ja noch etwas von variabler Frequenz. Wie und wo die 
Variation stattfinden soll, ist noch garnicht klar. Vermutlich sollen 
die Signale auch ohne Jitter erzeugt werden. Da muß das Timing immer 
stimmen.

Mampf F. schrieb:
> Aber bei
> dieser Popelaufgabe seh ich das jetzt ehrlich gesagt nicht

Ich nehme solche Sachen schon ernster.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

m.n. schrieb:
> Mampf F. schrieb:
>> Ich bin 100% davon überzeugt, dass man weder Assembler noch andere fiese
>> Tricks wie zB Register in Register ungenutzter Peripherie speichern
>> benötigt.
>
> Ich gratuliere Dir zu Deiner 100% Sicherheit, obwohl Du vieleicht 10%
> der Aufgabe kennst ;-)

Die Stand doch oben ... der OP hat einen 3kHz Träger und möchte ihn mit 
40-500Hz amplitudenmodulieren ... Was benötigt man da noch für Infos?

> Verrate mir aber bitte noch, woher Du weißt, daß es 200 Takte/Zyklus
> sind und 30 Stützstellen ausreichen.

Für mich geben die 200 Takte/Zyklus oder 30 Stützstellen keinen Sinn ... 
Ich geh von der 3fachen Sample-Frequenz des Trägers aus, weil das 
reicht. Das wären 3kHz * 3 = 9kHz. Schneller muss der Timer garnicht 
laufen ...


m.n. schrieb:
> Ganz oben steht ja noch etwas von variabler Frequenz. Wie und wo die
> Variation stattfinden soll, ist noch garnicht klar. Vermutlich sollen
> die Signale auch ohne Jitter erzeugt werden. Da muß das Timing immer
> stimmen.

Ja, das ist wirklich einfach, wenn man weiß, wie ein DDS funktioniert 
... Man hat zwei DDS-Generatoren. Der eine macht den Träger, der andere 
macht das Nutzsignal. Der Phaseninkrementer des DDS1 bleibt konstant 
(wird vorher ausgerechnet), der Phaseninkrementer des DDS2 wird je nach 
Nutzsignal variiert. Dadurch erzeugt DDS2 einen schönen stetigen Sinus 
ohne Sprungstellen mit variabler Frequenz.

Mit jedem Timer-Aufruf wird ein Sample des DDS1 und DDS2 generiert. Das 
von DDS2 normiert man auf den Wertebereich [0:1] und multipliziert ihn 
auf das Sample von DDS1 (Amplitudenmodulation). Das Ergebnis gibt man 
auf die I/Os aus.

voila :)

*edit*: ach und Jitter gibts da keinen ... die ISR kommt völlig ohne 
Fallunterscheidungen aus und macht immer das gleiche ...

: Bearbeitet durch User
von Stefan (Gast)


Lesenswert?

Hallo Matthias,

benutzt Du einen 16-Bit-DA-Wandler?

> Bit 0..7  -> PortC 0..7
> Bit 8..15 -> PortA 0..7
> ...
> Die ersten 8bit, also PortC fällt kaum ins Gewicht, deshalb hab ich mich
> jetz mal auf PortA also nur 8bit beschränkt:

Falls Du irgendwann wieder auf 16 Bit umstellen willst, kannst Du ein 
Problem bekommen, Beispiel:
1
// DA-Wandler-Wert ist angenommen 0080h
2
// neuerWert = 0100h
3
4
                             // DA-Wandler gibt 0080h aus
5
PortA = High(neuerWert);
6
                             // DA-Wandler gibt 0180h aus
7
PortC = Low (neuerWert);
8
                             // DA-Wandler gibt 0100h aus
Du erhälst also einen Spike. Den kannst Du verhindern, wenn Du einen 
Latch-Input o.ä. an Deinem DA-Wandler hast.

@Mampf:
Sehe ich genauso. Der Zeitvorteil, den Assembler bringt, geht oft im 
Laufe eines Projekts wieder verloren. Optimierungen müssen in Assembler 
bei jeder Codeänderung neu angepasst werden. In C macht das bei jedem 
Compilieren ganz automatisch der Compiler. Einziger Vorteil: den 
AssemblerCode nimmt Dir in der Firma niemand freiwillig weg ...

Viele Grüße, Stefan

von Uwe (de0508)


Lesenswert?

Hallo Mampf F.,

Mampf F. schrieb:
> Achja und wenn man die ursprüngliche Fragestellung berücksichtigt, hat
> mat man für einen Samplewert sogar >2000 Takte Zeit ...
>
> Obwohl das Nyquist-Theorem sagt, dass man die doppelte Frequenz zum
> Abtasten braucht, geht man bei DDS von der dreifachen aus (Rauschen wird
> geringer), um ein brauchbares Signal zu erhalten.
>
> Bei 20MHz und 1Takt/Befehl wären das bei 9kHz Timer-Frequenz ~2222
> Takte, die man Zeit hat einen Sample-Wert zu erzeugen.
>
> Die obigen 100kHz oder 200 Takte entspringen einfach falschen
> Überlegungen  ...

Die 100kHz sind bezogen auf einen 1kHz Sinus mit 100 Stützstellen, d.h. 
der Wertebereich 2* Pi wird in 100 Teile aufgeteilt und dafür der Sinus 
in einer Tabelle abgelegt.

Wenn Du nun behauptest es würden 1kHz * 3 ausreichen, dann hast du das 
noch nie Programmiert und die Reinheit des erzeugten Sinus beurteilt.
3 Stützstellen ist eine Dreieck-Form des Signals.
p0=(0,-1), p1=(1,0) und p2=(2,+1)

: Bearbeitet durch User
von Mampf F. (mampf) Benutzerseite


Lesenswert?

Uwe S. schrieb:
> Wenn Du nun behauptest es würden 1kHz * 3 ausreichen, dann hast du das
> noch nie Programmiert und die Reinheit des erzeugten Sinus beurteilt.
> 3 Stützstellen ist eine Dreieck-Form des Signals.
> p0=(0,-1), p1=(1,0) und p2=(2,+1)

Wenn du das ernst meinst, hast du noch nie was von DDS und 
Nyquist-Abtasttheorem gehört ...

Bitte, wenn du keine Ahnung hast, verbreite doch keinen Unsinn ...

Aber probiers doch mal aus ... Schnapp dir einen 3kHz Sinus, nimm es mit 
9kHz über deine Soundkarte auf und schau, ob du ihn wieder abspielen 
kannst ... Nichts anderes macht DDS ... Nur, dass es die Samples 
errechnet, die du abspielen würdest, hättest du sie aufgenommen.

Man nimmt nur die 3fache Frequenz deshalb, weil das Signal-Rauschen 
deutlich besser wird. Das Rauschen kommt daher, weil der Fraktionale 
Anteil des Phasenakkumulators nicht als Index auf die Lookup-Table 
verwendet wird und daher das Ergebnis "ungenau" ist.

: Bearbeitet durch User
von Uwe (de0508)


Lesenswert?

Ok,

dann verstehe ich das Problem,
wir reden die ganze Zeit von: "mit µC Sinus erzeugen + 
Amplitudenmodulation"
Du aber von ADC-Wandlung eines Sinus, damit hast Du natürlich recht.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

Uwe S. schrieb:
> Ok,
>
> dann verstehe ich das Problem,
> wir reden die ganze Zeit von: "mit µC Sinus erzeugen +
> Amplitudenmodulation"
> Du aber von ADC-Wandlung eines Sinus, damit hast Du natürlich recht.

Nein, der DDS macht das Gegenteil der AD-Wandlung eines Sinus ... Du 
darfst nicht in Stützstellen ála Pi, Pi/2, Pi/4 usw denken ...

Man benötigt keine festen Stützstellen ... Man kann mit einem festen 
Sample-Takt von zB 44.1kHz jede beliebige Frequenz von 0 bis 22.05kHz 
erzeugen ... Da hat man auch keine festen Stützstellen ...

Les dich doch mal bitte in DDS ein, sonst hat die Diskussion überhaupt 
keinen Sinn ...

von Mathias K. (matzeknu)


Lesenswert?

Vielen Dank für die super Unterstützung, hat mich echt viel 
weitergeholfen!!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.