Forum: FPGA, VHDL & Co. Sinustabelle 256x8


von Gustl B. (-gb-)


Lesenswert?

Hallo, falls Jemand eine Sinustabelle für einen 1024 Punkte Sinus sucht, 
hier ist ein Quadrant mit 256 Punkten:
1
type Rom256x8 is array (0 to 255) of unsigned(7 downto 0); 
2
constant Sinus_Rom_256x8 : Rom256x8 := (
3
x"00", x"01", x"03", x"04", x"06", x"07", x"09", x"0a", x"0c", x"0e", x"0f", x"11", x"12", x"14", x"15", x"17",
4
x"19", x"1a", x"1c", x"1d", x"1f", x"20", x"22", x"24", x"25", x"27", x"28", x"2a", x"2b", x"2d", x"2e", x"30",
5
x"31", x"33", x"35", x"36", x"38", x"39", x"3b", x"3c", x"3e", x"3f", x"41", x"42", x"44", x"45", x"47", x"48",
6
x"4a", x"4b", x"4d", x"4e", x"50", x"51", x"53", x"54", x"56", x"57", x"59", x"5a", x"5c", x"5d", x"5f", x"60",
7
x"61", x"63", x"64", x"66", x"67", x"69", x"6a", x"6c", x"6d", x"6e", x"70", x"71", x"73", x"74", x"75", x"77",
8
x"78", x"7a", x"7b", x"7c", x"7e", x"7f", x"80", x"82", x"83", x"84", x"86", x"87", x"88", x"8a", x"8b", x"8c",
9
x"8e", x"8f", x"90", x"92", x"93", x"94", x"95", x"97", x"98", x"99", x"9a", x"9c", x"9d", x"9e", x"9f", x"a1",
10
x"a2", x"a3", x"a4", x"a5", x"a7", x"a8", x"a9", x"aa", x"ab", x"ac", x"ae", x"af", x"b0", x"b1", x"b2", x"b3",
11
x"b4", x"b5", x"b7", x"b8", x"b9", x"ba", x"bb", x"bc", x"bd", x"be", x"bf", x"c0", x"c1", x"c2", x"c3", x"c4",
12
x"c5", x"c6", x"c7", x"c8", x"c9", x"ca", x"cb", x"cc", x"cd", x"ce", x"cf", x"d0", x"d1", x"d1", x"d2", x"d3",
13
x"d4", x"d5", x"d6", x"d7", x"d7", x"d8", x"d9", x"da", x"db", x"dc", x"dc", x"dd", x"de", x"df", x"df", x"e0",
14
x"e1", x"e2", x"e2", x"e3", x"e4", x"e4", x"e5", x"e6", x"e6", x"e7", x"e8", x"e8", x"e9", x"ea", x"ea", x"eb",
15
x"ec", x"ec", x"ed", x"ed", x"ee", x"ee", x"ef", x"ef", x"f0", x"f1", x"f1", x"f2", x"f2", x"f3", x"f3", x"f3",
16
x"f4", x"f4", x"f5", x"f5", x"f6", x"f6", x"f6", x"f7", x"f7", x"f8", x"f8", x"f8", x"f9", x"f9", x"f9", x"fa",
17
x"fa", x"fa", x"fa", x"fb", x"fb", x"fb", x"fb", x"fc", x"fc", x"fc", x"fc", x"fd", x"fd", x"fd", x"fd", x"fd",
18
x"fd", x"fe", x"fe", x"fe", x"fe", x"fe", x"fe", x"fe", x"fe", x"fe", x"fe", x"fe", x"fe", x"fe", x"fe", x"ff");

von Sinusspezialist (Gast)


Lesenswert?

An dem 2. und dem letzten Wert kann man sehen, dass da was faul ist. Die 
Werte müssten gesättigt verlaufen.

Sieh Dir mal den Wiki-Artikel hier an.

von -gb- (Gast)


Lesenswert?

Also ich hab das mit Python gemacht, also sin((90/128)*x) mit x von 1 
bis 128. Und das dann mal 255 damit das Maximum statt 1 dann 255 ist. 
Und das dann als Integer.

Ist die Herangehensweise falsch oder wie lässt man das berechnen?

von Max M. (jens2001)


Lesenswert?

-gb- schrieb:

1. Ist es nicht schön wenn sich Gäste in einem Tread mit immer neuen 
Namen anmelden.

> Also ich hab das mit Python gemacht, also sin((90/128)*x) mit x von 1
> bis 128. Und das dann mal 255 damit das Maximum statt 1 dann 255 ist.
> Und das dann als Integer.

2. ist die Formel seltsam.
3. und ergibt nicht die oben angegebenen Werte
4. die irgentwie nicht wirklich stimmen.

> Ist die Herangehensweise falsch oder wie lässt man das berechnen?

Ich würd's so rechnen:

A(N) = SIN(((Pi/2)/256)*N)*256 [N=1->255]  /* Die vielen Klammern sind
                                           /* nur zur Verdeutlichung
A(0) = 0                                   /* Die Werte für "0"
A(256) = 256                               /* und "256" muss man nicht
                                           /* berechnen

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Ich verweise da mal auf den recht übersichtlichen Artikel [[Digitale 
Sinusfunktion]]

von MWS (Gast)


Lesenswert?

Max Mustermann schrieb:
> A(0) = 0                                   /* Die Werte für "0"
> A(256) = 256                               /* und "256" muss man nicht

Das wären dann 257 Stützstellen und ein Wert von 256 in ein Byte?
Nicht wirklich...

von Tom Thomsen (Gast)


Lesenswert?

Max Mustermann schrieb:
> A(N) = SIN(((Pi/2)/256)*N)*256 [N=1->255]  /* Die vielen Klammern sind

Besser, dir Formel gilt auch für N=0, 256 ;-)

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Gustl Buheitel schrieb:
> hier ist ein Quadrant mit 256 Punkten:
Man kann die Tabelle auch vom Synthesizer rechnen lassen:
http://www.lothar-miller.de/s9y/archives/37-DDFS-mit-BROM.html
Für die 256 Punkte des ersten Quadranten eines 9-Bit-Sinus 
(8+Vorzeichen) wäre das dann:
1
use ieee.math_real.all;  
2
:
3
  type Rom256x8 is array (0 to 255) of unsigned (7 downto 0); 
4
  -- Sinus von 0° bis 90° (0 bis PI/2)
5
  signal Sinus_Rom : Rom256x8;
6
:
7
   table: for i in 0 to 255 generate
8
       Sinus_Rom(i) <= to_unsigned(integer( sin(2.0*MATH_PI*(real(i)+0.5)/1024.0) *255.5),8);
9
   end generate;

: Bearbeitet durch Moderator
von Max M. (jens2001)


Lesenswert?

MWS schrieb:
> Max Mustermann schrieb:
>> A(0) = 0                                   /* Die Werte für "0"
>> A(256) = 256                               /* und "256" muss man nicht
>
> Das wären dann 257 Stützstellen und ein Wert von 256 in ein Byte?
> Nicht wirklich...

OK. Das ist natürlich QUATSCH!
Ich schieb's mal auf die Uhrzeit;-)

Habs geändert:

A(N) = SIN(((Pi/2)/255)*(N+0,5))*255 [N=0->255]  /* Die vielen Klammern 
sind
                                                 /* nur zur 
Verdeutlichung

Ich würd's allerdings nicht zur Laufzeit berechnen sondern als Tabelle 
mit reincompilieren damit es nicht im RAM oder ROM liegt sondern im 
Flash.
Lieg ich damit falsch?

von Max M. (jens2001)


Lesenswert?

Lothar Miller schrieb:
> Für die 256 Punkte des ersten Quadranten eines 9-Bit-Sinus
> (8+Vorzeichen) wäre das dann

Wozu Vorzeichen?
Alle Werte eines Quadranten haben das gleiche Vorzeichen.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Max Mustermann schrieb:
> Wozu Vorzeichen?
> Alle Werte eines Quadranten haben das gleiche Vorzeichen.
Weil aber ein einzelner Quadrant in freier Wildbahn kaum vorkommt, wird 
für den kompletten Sinus ein Bit mehr benötigt. Das ist das erwähnte 
Vorzeichen.

Max Mustermann schrieb:
> damit es nicht im RAM oder ROM liegt sondern im Flash.
Wir sind hier im Forum: FPGA, VHDL...

von -gb- (Gast)


Lesenswert?

Das Vorzeichen kann man aber auch nachträglich ranmachen und muss es 
nicht in der Tabelle speichern die ja Platz kostet. Ich werde nachher 
das Python Posten.

von Max M. (jens2001)


Lesenswert?

Lothar Miller schrieb:
> Max Mustermann schrieb:
>> Wozu Vorzeichen?
>> Alle Werte eines Quadranten haben das gleiche Vorzeichen.
> Weil aber ein einzelner Quadrant in freier Wildbahn kaum vorkommt, wird
> für den kompletten Sinus ein Bit mehr benötigt. Das ist das erwähnte
> Vorzeichen.
Du wills also für einen kompletten Sinus 1024*9Bit abspeichern?


>
> Max Mustermann schrieb:
>> damit es nicht im RAM oder ROM liegt sondern im Flash.
> Wir sind hier im Forum: FPGA, VHDL...
Sorry, nicht gesehen. Was über "alle Foren" auf diesen Tread geraten.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Max Mustermann schrieb:
>> Weil aber ein einzelner Quadrant in freier Wildbahn kaum vorkommt, wird
>> für den kompletten Sinus ein Bit mehr benötigt. Das ist das erwähnte
>> Vorzeichen.
> Du wills also für einen kompletten Sinus 1024*9Bit abspeichern?

Nein, natürlich nicht.

Die Tabelle hat 256 Einträge im Bereich von 0..255 (8 Bit). Siehe
hier:

Lothar Miller schrieb:
> type Rom256x8 is array (0 to 255) of unsigned (7 downto 0);
>   -- Sinus von 0° bis 90° (0 bis PI/2)
>   signal Sinus_Rom : Rom256x8;

Das Argument der Sinusfunktion bewegt sich im Bereich 0..1023 (10
Bit), damit ein kompletter Vollkreis abgedeckt wird.

Die Funktionswerte bewegen sich im Bereich -255..+255 (9 Bit).

Was in den bisherigen Beiträgen fehlt und vielleicht zum besseren
Verständnis beitragen würde, ist die Realisierung der Sinus-Funktion
selber, d.h. einer Logik, die 10 Eingangs- und 9 Ausgangsleitungen hat
und die aus dem Eingangswert unter Zuhilfenahme der Tabelle den
Funktionswert bestimmt.

Das geht ganz leicht: Ist das Argument n Bit breit, legen Bit n-1 und
Bit n-2 den Quadranten fest:

1
Bits n-1   Bit n   Quadrant
2
———————————————————————————
3
   0         0        1
4
   0         1        2
5
   1         0        3
6
   1         1        4
7
———————————————————————————

Die restlichen n-2 Bits (Bit n-3 bis Bit 0) liefern den Index auf die
Tabelle, wobei dieser Index für die Quadranten 2 und 4 (also wenn des
zweithöchste Bit gesetzt ist) negiert werden muss, und zwar so, dass das
Ergebnis wieder n-2 Bits breit ist. Der aus der Tabelle ausgelesene Wert
muss für die Quadranten 3 und 4 (also wenn Bit n-1 des Arguments gesetzt
ist) ebenfalls negiert werden, und zwar so, dass das Ergebnis 1 Bit
breiter als die Tabelleneinträge ist.

Wie das für einen Mikrocontroller mit einem 8-Bit-Argument geht, habe
ich hier gezeigt:

  Beitrag "Re: SINUS - COSINUS / KOSINUS - BEISPIEL (Assembler) ATmega8"

Das Ganze in Verilog oder VHDL umzusetzen ist eine nette Übungsaufgabe
für Anfänger auf diesem Gebiet :)

: Bearbeitet durch Moderator
von Max M. (jens2001)


Lesenswert?

-gb- schrieb:
> Das Vorzeichen kann man aber auch nachträglich ranmachen und muss es
> nicht in der Tabelle speichern die ja Platz kostet.


So hatte ich das auch gedacht.
Aber Lothar kam irgentwie mit 8+1 Bit auf.
Und ich hatte das so verstanden das er das Vorzeichenbit mit in der 
Tabelle speichern wollte.

Von FPGA, VHDL und Python hab ich NULL Ahnung und werd mich deshalb 
jetzt aus diesem Forum raushalten.

von -gb- (Gast)


Lesenswert?

Genau, so hab ich das auch gemacht:
1
  if   counter(9 downto 8) = "00" then
2
    Sin <= 255 + Sinus_Rom_256x8(to_integer(counter(7 downto 0)));
3
  elsif counter(9 downto 8) = "01" then
4
    Sin <= 255 + Sinus_Rom_256x8(255 - to_integer(counter(7 downto 0)));
5
  elsif counter(9 downto 8) = "10" then
6
    Sin <= 255 - Sinus_Rom_256x8(to_integer(counter(7 downto 0)));
7
  elsif counter(9 downto 8) = "11" then
8
    Sin <= 255 - Sinus_Rom_256x8(255 - to_integer(counter(7 downto 0)));
9
  end if;

von -gb- (Gast)


Lesenswert?

Und so sah mein Python aus:
1
import math
2
i = 0
3
while i < 256:
4
  x = hex(int((math.sin(((math.pi/2)/255)*i))*255))
5
  print (x)
6
  i += 1

von Max M. (jens2001)


Lesenswert?

-gb- schrieb:
> Und so sah mein Python aus:
>
>
1
> import math
2
> i = 0
3
> while i < 256:
4
>   x = hex(int((math.sin(((math.pi/2)/255)*i))*255))
5
>   print (x)
6
>   i += 1
7
>

Das ergibt aber Werte die imho nicht richtig sein können.

Wenn ich das richtig rechne erhalte ich für i=0 den Wert "Null".
Das kann aber nicht richtig sein.
Wenn du dir damit aus der Tabell durch Negieren u. Spiegeln einen 
kompletten Sinus zusammenbaust hast du am Nulldurchgang 2mal 
hintereinander den Ausgabewert "Null". Das würde aber bedeuten das die 
Kurve dort die Steigung "Null"hat. Das kann aber für einen Sinus 
hinkommen.

Wenn man den Quadranten in 256 Intervalle aufteilt dann liegt die Mitte 
des ersten Intervalls bei (Pi/2)/512 .(plenk;-)
Ich würde daher die Formel benutzen:

x = hex(int((math.sin(((math.pi/2)/(255+0,5))*i))*255))

von Gustl B. (-gb-)


Lesenswert?

Vielen Dank! Lothar hat das weiter oben auch schon beschrieben. Wusste 
nicht, dass man sich die ROM Inhalte generieren lassen kann, eine sehr 
coole Möglichkeit!

Jetzt habe ich eine weitere Frage:

Also ich habe den 1024 Punkte Sinus und einen Zähler von 0 ... 1023. 
Dann habe ich einen Takt mit sagen wir weil es schön ist 128MHz.

Dann ist die maximale Frequenz des Sinus 128M/1024=125kHz wenn ich in 
jedem Takt um 1 weiterzähle.

Jetzt will ich die Frequenz veränderbar machen, also hab ich mir noch 
einen Zähler dazugenommen mit einstellbarer Obergrenze. Wenn die 
Obergrenze erreicht ist, dann wird der Zähler auf 0 gesetzt und der 
Zähler für das SinusROM um 1 erhöht. Jetzt kann ich den Zähler für das 
SinusROM variabel alle 1 ... 4096 Takte um 1 erhöhen und so schön die 
Frequenz verändern.

Wenn ich statt jeden Takt nur jeden 2. Takt den Zähler für das SinusROM 
um 1 erhöhe, dann halbiert sich die Frequenz, wenn ich das nur jeden 3. 
Takt mache, dann ist es nurnoch 1/3 der Maximalfrequenz.

Schneller machen kann man das auch indem man den Zähler für das SinusROM 
nicht nur um 1 erhöht, sondern größere Werte.
Erhöht man diesen Zähler in jedem Takt um 2 bekommt man einen 512 Punkte 
Sinus der doppelten Frequenz, erhöht man immer um 3 bekommt man einen 
1024/3 Punkte Sinus der dreifachen(?) Frequenz.

Ich hab das schön einstellbar gemacht, der Zähler für das SinusROM kann 
also alle 1 ... 4096 Takte um 1 erhöht werden, aber auch jeweils um 1 
... 16 für dann einen 1024/64=16 Punkte Sinus deutlich höherer Frequenz.

Guckt man sich die Frequenzänderung an, dann ist das aber nicht linear:

Ändert man die Anzahl der Takte bir zur Erhöhung des SinusROM Zählers um 
1 von 4096 bis 1, dann geht das schon nicht linear, aber wenn man danach 
die Erhöhung des SinusROM Zählers je Takt noch von 1 ... 16 verändert 
dann auch nicht.

Das ist irgendwie ein seltsames Verhalten, dass es dann bei der 
Einstellung einen Punkt gibt bei dem eine Veränderung um 1 gleich eine 
Verdopplung/Halbierung der Frequenz bewirkt, davor und danach aber nur 
eine kleinere Veränderung der Frequenz bewirkt.

Kann man das irgendwie linear bekommen? Oder wenn nicht dann irgendwie 
so dass zumindest die Änderung der Frequenz linear bleibt?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Sieh dir einfach mal den von mir hier schon des Öfteren geposteten Link 
auf meine HP an... :-/

Du kannst auch einfach da mal schauen: 
http://www.lothar-miller.de/s9y/categories/31-DDFS

von Gustl B. (-gb-)


Lesenswert?

Danke! Ja das Konzept ist mir neu und auch nicht soooo leicht zu 
verstehen.

Ich habe derzeit einen Zähler für das SinusROM und einen Zähler mit 
einstellbarer Obergrenze mit dem man bestimmen kann wie schnell der 
SinusROM-Zähler hochgezählt wird.

Mit diesem "Akku" macht man das jetzt irgendwie beides in einem? Sehe 
ich das richtig? Dann müsste ich eigentlich nur die Schrittgröße 
variabel einstellen mit der der Akku zählt.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Gustl Buheitel schrieb:
> Mit diesem "Akku" macht man das jetzt irgendwie beides in einem? Sehe
> ich das richtig?
Ja. Der Akku zählt jedesmal um den Incrementwert hoch. Und wenn du z.B. 
deine Zählfrequenz richtig wählst, dann kannst du mit dem Wert 10000 
weine Frequenz von 10000 Hz erhalten, und mit dem Wert 10001 eine 
Frequenz von 10001 Hz. Das ist unmittelbar logisch, wenn man sich vor 
Augen hält, dass mit einer "etwas" größeren Zahl der Zähler 
(=Akkumulator) "etwas" schneller überläuft. Und mit einer "sehr viel" 
größeren Zahl läuft er "sehr viel" schneller über...

von Gustl B. (-gb-)


Lesenswert?

Sehr cooles Prinzip!

Jetzt hab ich also meinen Frequenzgenerator und kann die Frequenz in 
sagen wir 0,1Hz Schritten einstellen. Dann klappt das doch auch nur für 
niedrige Frequenzen. Oder?

Ich meine diese 0,1Hz sind bei niedrigen Frequenzen sehr viele Takte, 
aber bei schnellen Frequenzen, wie ist das da? Also wenn das 
niederwertigste Index-Bit schon jeden Takt kippt?

: Bearbeitet durch User
von Georg A. (georga)


Lesenswert?

Das macht trotzdem nichts, es werden halt Werte in der Tabelle 
übersprungen. Damit geht einem natürlich die Genauigkeit der Tabelle 
verloren und man bekommt immer mehr deutliches Rauschen ausserhalb der 
eigentlichen Frequenz. Wenn die Werte rein digital bleiben, könnte man 
noch interpolieren. D.h. aus den "Nachkommastellen" des Phasenakkus und 
den beiden Tabellenstützpunkten einen Zwischenwert ausrechnen. Das wäre 
dann lineare Interpolation. Ginge auch komplizierter (kubisch, sinc, 
...), kostet aber auch alles Logik und evtl. Geschwindigkeit. 
Möglicherweise ist es dann einfacher, gleich mit der doppelten Frequenz 
zu laufen, die Tabellengrösse ist ja inzwischen eher selten ein Problem.

Wenn der Sinus über einen Wandler analog ausgegeben wird, braucht man 
eigentlich auch einen Ausgangsfilter, der alles über der halben 
Samplefrequenz dämpft. Der sorgt auch dafür, dass bei schnellen 
Durchläufen die wenigen Stützpunkte "analog" interpoliert werden. 
Theoretisch müssen es ja auch deutlich mehr als zwei Werte pro Periode 
werden, damit der Sinus auch danach aussieht (Abtasttheorem)...

von Gustl B. (-gb-)


Lesenswert?

Ok, verstanden.

Jetzt soll das ein Funktionsgenerator für mich werden. Bei niedrigen 
Frequenzen würde ich die gerne mit einer Genauigkeit von 0,1Hz 
einstellen können. Aber ich will auch auf mehrere MHz hochdrehen können. 
Das wären sehr viele Werte um die ich den Wert ändern müsste mit einem 
Drehencoder oder so. Wie macht man das am besten als Bedieninterface?

Sowas wie < 100Hz in 0,1HZ Schritten, bis 1000Hz in 1Hz Schritten, bis 
10kHz in 10Hz Schritten, ...

von Lattice User (Gast)


Lesenswert?

In einem FPGA ist es nicht immer sinnvoll nur einen Quadranten 
abzulegen. Wenn man im vorliegenden Fall für die Tabelle einen BRAM 
verwendet, ist dieser bei weiten nicht ausgenutzt. BRAMs sind 
typischerweise 4 bis 8 mal so gross. Man kann also die ganze Periode 
ablegen und sich die Quadrantenlogik sparen. Das muss man zwar 
zusätzöoch das Vorzeichen mit speichern. aber die BRAMs lassen sich 
meistens auch als 2k * 9 konfigurieren, man hat also keinen 
Genauigkeitsverlust.

Für einen Funktionsgenerator ist das erst recht sinnvoll, da man dann 
auch asymetrische periodische Signale erzeugen kann.

von Michael W. (Gast)


Lesenswert?

Das Vorzeichen braucht man nicht wirklich, ist ja nur eine Inversion, 
wenn die Punktenummerierung stimmt. Und die Grösse der BRAMS würde ich 
nutzen, um nicht nur 8 Bit oder 256 Werte zu nehmen. Das gibt ja keinen 
Sinus mehr, wenn mit krummen Werten angesteuert wird.

von Lattice User (Gast)


Lesenswert?

M. W. schrieb:
> Das Vorzeichen braucht man nicht wirklich, ist ja nur eine Inversion,

Falsch, im 2er Komplement muss man nach der Inversion eine eins 
dazuaddieren. Und die Ausgabe sollte im 2er Komplement sein wenn man es 
weiterverarbeiten möchte, es sei den man gibt es auf einen DAC der auch 
1er Komplement oder Betrag mit Vorzeichen kann.

> wenn die Punktenummerierung stimmt. Und die Grösse der BRAMS würde ich
> nutzen, um nicht nur 8 Bit oder 256 Werte zu nehmen. Das gibt ja keinen
> Sinus mehr, wenn mit krummen Werten angesteuert wird.

Das ist nur eine Frage der erforderlichen Genauigkeit. Man muss nicht 
immer alles im Audiophilen 32bit Format machen (ok, das war jetzt etwas 
pöse).

von Mark S. (voltwide)


Lesenswert?

Gustl Buheitel schrieb:
> Ok, verstanden.
>
> Jetzt soll das ein Funktionsgenerator für mich werden. Bei niedrigen
> Frequenzen würde ich die gerne mit einer Genauigkeit von 0,1Hz
> einstellen können. Aber ich will auch auf mehrere MHz hochdrehen können.
> Das wären sehr viele Werte um die ich den Wert ändern müsste mit einem
> Drehencoder oder so. Wie macht man das am besten als Bedieninterface?
>
> Sowas wie < 100Hz in 0,1HZ Schritten, bis 1000Hz in 1Hz Schritten, bis
> 10kHz in 10Hz Schritten, ...

Ich erklärs mal so: Du hast einen festen Takt einerseits, und eine 
Sinus-Tabelle andererseits. Die 1024 Stützstellen Deiner Tabelle 
adressierst Du nun mit einem Zeiger, der jedesmal um einen beliebig fein 
abgestuften PHASENSPRUNG inkrementiert wird. Dieser Phasensprung ist 
direkt proportional zu der ausgegebenen Frequenz.
Und das heißt, dass Du den gesamten Frequenzbereich bis zur 
Nyquist-Grenze (=halbe Taktfrequenz) in Schritten von z.B 0,1Hz linear 
einstellen kannst.

von Gustl B. (-gb-)


Lesenswert?

Ich meinte etwas Anderes:

Wenn ich bei einer 1024Punkte Tabelle und einen Takt von 128MHz eine 
Sinusfrequenz von 0,1Hz einstellen will, und der Akku je Takt um 1 
erhöht werden soll, dann muss der Akku bis 1280M zählen. Ist das 
richtig? Davon gingnen dann die oberen 10Bits an den Tabellenindex.

Edit1:
Die oberen 10 Bits gehen aber nicht von 0 ... 1023. Wie macht man das am 
geschicktesten mit einem Akku, dass die oberen Bits tatsächlich den 
gesamten Index durchwandern?

Meine Idee wäre wie schon vorher den Akku in zwei Zähler zu teilen, 
einen der von 1 ... 1250k zählt und einen der dann von 0 ... 1023 zählt. 
Das mit einem Akku wäre aber trotzdem hybscher irgendwie.
/Edit

Edit2:
Also jetzt habe ich die 1280M mal auf einen vollen Binärwert aufgefüllt 
und komme auf 2147,4...*10^6 also auf einen Takt von 214,7MHz. Wenn ich 
eine Stelle weniger nehme, dann ist es die Hälfte, also 107,3MHz. Und 
dann laufe ich da in Genauigkeitsprobleme weil ich es nie schaffe im 
FPGA einen Takt zu erzeugen der auf 9 Stellen genau ist. Oder kann das 
der DCM im Spartan3?
Als Alternative kann ich natürlich weiterhin bis 1280M zählen mit dem 
Akku. Die oberen 10 Bits laufen aber nur von 0 bis 610, ich könnte also 
eine kleinere Tabelle machen für den Sinus, also 611/4 Werte. Was würdet 
ihr machen?
/Edit

Jetzt ist 1280M sehr viel, aber der Aku wird da bei 0,1Hz je Takt nur um 
1 erhöht. Bei einer maximalen (erwünschten) Sinusfrequenz von 8MHz muss 
dann der Akku je Takt um 1280/16=80M oder 8MHz*10=80M erhöht werden. Das 
wäre dann der 8MHz Sinus mit 16 Punkten.

Will man das variabel einstellbar, muss der Benutzer Werte von 0 ... bis 
80M einstellen können. Wie macht man das mit einem Dreh-Encoder? Also um 
gleichzeitig fein aber auch sehr grob einstellen zu können?

: Bearbeitet durch User
von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Gustl Buheitel schrieb:
> Wie macht man das mit einem Dreh-Encoder? Also um gleichzeitig fein aber
> auch sehr grob einstellen zu können?
Man macht es so: wenn schnell und ausdauernd gedreht wird, dann wird 
schnell inkrementiert. Sonst eben nur langsam.

In C sieht das so aus:
http://www.lothar-miller.de/s9y/archives/71-Drehgeberauswertung-mit-Beschleunigung.html
Das kann man sicher einfach nach VHDL portieren.

: Bearbeitet durch Moderator
von Gustl B. (-gb-)


Lesenswert?

Ok, wunderbar, ich zähle also z. B. die Zahl der Impulse je 0,1s die vom 
Encoder kommen und nehme die dann zum Quadrat oder multipliziere die mit 
einem Wert. Mit dem Ergebnis wird dann der Akku gezählt.

Wie sollte ich das Problem mit dem Akku lösen, das ich oben in den 
Edit-Sektionen beschrieben habe? Also dass die oberen 10Bits des Akkus 
nicht alle Indizes erreichen?

von Lattice User (Gast)


Lesenswert?

Gustl Buheitel schrieb:

>
> Wie sollte ich das Problem mit dem Akku lösen, das ich oben in den
> Edit-Sektionen beschrieben habe? Also dass die oberen 10Bits des Akkus
> nicht alle Indizes erreichen?

Wenn du exakte 0.1 Hz Auflösung haben:

1. Ein Refezenzoscillator der eine passende Frequenz hat: z.B. wenn 20 
ppm absolute Genauigkeit reichen: 13,422 MHz.

oder

2. Mit einem Hilfsakku den Phasenincrement des Hauptakkus zwischen 2 
Werten springen lassen.

3. die Sinustabelle in der Länge anpassen und den Akkuüberlauf 
enstprechend behandeln.
Pseudocode:
Temp = Akku + Schrittweite
if (Temp < Akkulänge) Akku = Temp
else Akku = Temp - Akkulänge.

von Gustl B. (-gb-)


Lesenswert?

Also ich habe mich jetzt entschieden ein SinusROM mit 611 Einträgen zu 
verwenden und zwar für eine komplette Periode. Das Ergebnis ist nicht 
ganz wie ich mir das vorstelle. Eigentlich ist es nicht viel:
1
type Rom611x9 is array (0 to 610) of signed(8 downto 0); 
2
signal Sinus_Rom : Rom611x9;
3
4
signal Sin_A_speed: integer range 0 to 79999999:;
5
signal Sin_A_Akku: integer range 0 to 1279999999;
6
signal Sin_A: signed(8 downto 0):="000000000";
7
8
begin
9
10
tabel : for I in 0 to 610 generate
11
   Sinus_Rom(I) <= to_signed(integer( sin(2.0*MATH_PI*(real(I)+0.5)/611.0) *255.5),9);
12
end generate;
13
14
process begin
15
   wait until rising_edge(CLK128);
16
   
17
   Sin_A_Akku <= Sin_A_Akku + Sin_A_speed;
18
19
   Sin_A <= Sinus_Rom(to_integer(to_unsigned(Sin_A_Akku,31)(30 downto 21)));
20
21
   VGAg <= std_logic_vector(Sin_A(8 downto 2));
22
23
end process;

Das Python liefert ordentliche Werte:
1
import math
2
i = 0
3
while i < 611
4
   x = math.sin(2*math.pi*(i+0.5)/611)*255.5
5
   print(x)
6
   i += 1

von Lattice User (Gast)


Lesenswert?

4. Den Akku lang genaug machen, dass der Fehler kleiner als die 
Genauigkeit des Referenztaktes ist.

von Lattice User (Gast)


Lesenswert?

Noch ein Nachtrag:

> 4. Den Akku lang genaug machen, dass der Fehler kleiner als die
> Genauigkeit des Referenztaktes ist.

wenn ich mir es genau überlege ist 2) und 4) identisch.

von Gustl B. (-gb-)


Angehängte Dateien:

Lesenswert?

Einen Fehler habe ich schon gefunden in meinem Code oben. Und zwar 
laufen Integer, wenn sie nicht garade auf eine Zweierpotenz limitiert 
sind, nicht bei der Limitierung über. Das musste ich noch einbauen.

von Michael W. (Gast)


Lesenswert?

Lattice User schrieb:
> Falsch, im 2er Komplement muss man nach der Inversion eine eins
> dazuaddieren.

Warum, es führt nur zu einem Offset:

Hier ist ein Beispiel für das Dreieick:

Q1
0  0  0  0  (  0  )
0  0  0  1  (  1  )
0  0  1  0  (  2  )
0  0  1  1  (  3  )
0  1  0  0  (  4  )
0  1  0  1  (  5  )
0  1  1  0  (  6  )
0  1  1  1  (  7  )

Q2
0  1  1  1  (  7  )
0  1  1  0  (  6  )
0  1  0  1  (  5  )
0  1  0  0  (  4  )
0  0  1  1  (  3  )
0  0  1  0  (  2  )
0  0  0  1  (  1  )
0  0  0  0  (  0  )

Q3
1  1  1  1  (  -1  )
1  1  1  0  (  -2  )
1  1  0  1  (  -3  )
1  1  0  0  (  -4  )
1  0  1  1  (  -5  )
1  0  1  0  (  -6  )
1  0  0  1  (  -7  )
1  0  0  0  (  -8  )

Q4
1  0  0  0  (  -8  )
1  0  0  1  (  -7  )
1  0  1  0  (  -6  )
1  0  1  1  (  -5  )
1  1  0  0  (  -4  )
1  1  0  1  (  -3  )
1  1  1  0  (  -2  )
1  1  1  1  (  -1  )

Die Werte in den Quadranten Q3 und Q4 sind einfache Bitinversionen



>> wenn die Punktenummerierung stimmt.
> Das ist nur eine Frage der erforderlichen Genauigkeit.
?

Ich meinte, die richtigen X-Werte anzuwählen

von Lattice User (Gast)


Lesenswert?

M. W. schrieb:
> Lattice User schrieb:
>> Falsch, im 2er Komplement muss man nach der Inversion eine eins
>> dazuaddieren.
>
> Warum, es führt nur zu einem Offset:
>

Der Offset ist aber nur in der negativen Halbwelle da. Du addierst damit 
effektiv ein Rechtecksignal zur Ausgabe.


>
>
>>> wenn die Punktenummerierung stimmt.
>> Das ist nur eine Frage der erforderlichen Genauigkeit.
> ?
>
> Ich meinte, die richtigen X-Werte anzuwählen

Und ich meinte die Anforderung an das Ergebnis. Die richtigen X Werte 
auswählen braucht in der Tat nur eine Inversion wenn man die Tabelle wie 
Max vorgeschlagen hat erzeugt.

Wenn die Genaugigkeit für die Amplitude von 8 bit reicht, wozu dann 
Resourcen verschwenden? 256 Punkte für den Quadranten reichen in diesem 
Falle.

von Lattice User (Gast)


Lesenswert?

Gustl Buheitel schrieb:
> Einen Fehler habe ich schon gefunden in meinem Code oben. Und zwar
> laufen Integer, wenn sie nicht garade auf eine Zweierpotenz limitiert
> sind, nicht bei der Limitierung über. Das musste ich noch einbauen.

Hat sich überschnitten, aber das entspricht meinem Vorschlag 3.

Nur macht das keiner so, Analog Devices z.B. verwendet in ihren DDS 
Generator bis zu 48bit Akkus (z.B. AD9912).

Wenn man sich auf 2er Potenzen beschränkt benötigt man weniger Logik und 
erreicht damit höhere Taktraten für die DDS.

von Gustl B. (-gb-)


Lesenswert?

Ok, aber dann braucht man auch einen recht genauen Takt oder?

Und wo ich noch drüber gestolpert bin war, dass signed natürlich das 
Vorzeichen bei negativen Zahlen gestezt hat. Man kann es also nicht 
einfach ausgeben und bekommt den Sinus sondern muss noch das Vorzeichen 
invertieren.

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


Lesenswert?

Hier ein paar Online Generatoren:
http://meraman.com/htmls/en/sinTable.html
http://www.daycounter.com/Calculators/Sine-Generator-Calculator.phtml

Das muss man heute meistens nicht mehr selber errechnen.

: Bearbeitet durch User
von Lattice User (Gast)


Lesenswert?

Gustl Buheitel schrieb:
> Ok, aber dann braucht man auch einen recht genauen Takt oder?

Auch nicht genauer als deine Anforderung an die absolute Genauigket.

Deine CLK128 ist 128 MHz?

Bei einem 48bit Akku entspricht 0.1 Hz dann einen Wert von 219902. D.h. 
du musst vielfache davon in das Increment (Speed) Register schreiben.

Diese Multiplikation muss man aber nicht in einem Takt der 128 MHz 
rechnen, selbst die serielle Implememtation mit Schieberegister und 
Addierer bremst dich nicht aus.

von Gustl B. (-gb-)


Lesenswert?

Verlockend ... erstmal reicht mir aber meine aktuelle Lösung. Jetzt hab 
ich schon AM mit zwei unabhängigen Sinussignalen und gleich kommt noch 
FM was leicht anspruchsvoller wird.

Unzufrieden bin ich noch mit der Einstllungemöglichkeit für den 
Benutzer. Klar kann man mit dem Encoder verschiedene Geschwindigkeiten 
erkennen und das mache ich auch, aber ... das muss noch schneller gehen. 
weiterbastel

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Gustl Buheitel schrieb:
> das muss noch schneller gehen.
Du könntest mit dem Drucktaster im Encoder die Zehnerstellen bzw. 
Einstellgeschwindigkeit umschalten...

von Gustl B. (-gb-)


Lesenswert?

Jetzt zähle ich die Encoderimpulse für immer eine Viertelsekunde ca. und 
rechne dann 2 hoch diese Zahl. Damit ändere ich den Wert 
(Geschwindigkeit) um den der Akku je Takt erhöht wird.

Also ich habe zweimal einen variablen Sinus, ich habe AM, und ich habe 
FM angefangen. Dazu mache ich folgendes:
1
-- ein normaler Sinus
2
Sin_A_Akku <= Sin_A_Akku + Sin_A_Geschwindigkeit;
3
Sin_A <= Sinus_Rom(to_integer(Sin_A_Akku(30 downto 21)));
4
5
-- das Gleiche nochmal für Sin_B
6
7
-- jetzt die FM
8
Sin_C_Akku <= Sin_C_Akku + to_unsigned(to_integer(Sin_A_Geschwindigkeit) + to_integer(Sin_B),31);

Nur so grab als Skizze, das ist in Wirklichkeit signed und ich beachte 
auch das Vorzeichen.

Mein Problem:
Bei schnellen Sinusfrequenzen der Trägerfrequenz Sin_A macht die 
Amplitude von Sin_B (0 ... 511) viel aus, bei langsamer Trägerfrequenz 
macht das aber kaum noch was.

Irgendwie hätte ich das gerne dass sich das aufhebt und die FM immer 
gleich sichtbar ist. Also der Wert um den der Sin_A_Akku erhöht wird der 
wird mit steigender Frequenz eher exponentiell kleiner, jetzt müsste bei 
mir die Amplitude von Sin_B das auch machen damit es sich aufhebt.
Morgen guck ich mir das mal genauer an ...

von Michael W. (Gast)


Lesenswert?

Lattice User schrieb:
> Der Offset ist aber nur in der negativen Halbwelle da. Du addierst damit
> effektiv ein Rechtecksignal zur Ausgabe.

Ok, von der Theorie her stimmt das wohl, aber mein Dreieck von der 
Tabelle oben ist kontinuierlich. So wäre es auch mit einem Sinus, meine 
ich.

von berndl (Gast)


Lesenswert?

ist es nicht so, dass ein Sinus, der als 'signed' intern in der Logik 
laeuft, einfach einen Offset braucht wenn man ihn ueber einen DAC 
ausgibt? Der Screenshot deutet darauf hin... Also einfach 1/2 
DAC-Ausgangsspannung auf den 'signed' Sinuswert draufaddieren, dann 
kommt da ja ein richtiger Sinus am DAC raus...

von Gustl B. (-gb-)


Lesenswert?

Ja, genau ist signed, und "leider" ist das Vorzeichenbit genau dann '1' 
wenn die Zahl negativ ist. Ich musste also das Vorzeichenbit invertieren 
was quasi der Offset um das MSB ist. Dann passt das.

Zu meinem Problem mit der FM:

Statt
1
Sin_C_Akku <= Sin_C_Akku + to_unsigned(to_integer(Sin_A_Geschwindigkeit) + to_integer(Sin_B),31);

sollte ich wohl
1
Sin_C_Akku <= Sin_C_Akku + to_unsigned(to_integer(Sin_A_Geschwindigkeit)*to_integer(Sin_B),31);

schreiben, also eine Multiplikation, dann ist das Verhältnis der FM 
immer konstant und unabhängig von der Trägerfrequenz. Muss ich mal 
ausprobieren :-)

Und dann kommen noch variable Amplituden der beiden Sinusschwingungen 
dazu.

: Bearbeitet durch User
von Gustl B. (-gb-)


Angehängte Dateien:

Lesenswert?

So, FM ist fertig und zwar so, dass die Trägerfrequenz maximal 
verdoppelt und minimal halbiert wird.

Im Prinzip habe ich einen Sinus A der der Träger ist mit Sin_A_Inkrement 
um das der Akku dieses Sinus je Takt erhöht wird. Und ich habe ein 
Signal Sinus B das darauf moduliert wird. Daraus wird Sinus C mit 
Sin_C_Inkrement.

Sin_C_Inkrement = (Sin_A_Inkrement/1024)*((Sin_B*3)+512)

Die Werte von Sin_B gehen unsigned von 0 ... 511.

Ist der Wert 511, dann erhält man:
Sin_C_Inkrement = (Sin_A_Inkrement/1024)*((511*3)+512) =
= (Sin_A_Inkrement/1024)*(2045) = Sin_A_Inkrement*1.997

Ist der Wert 0, dann erhält man:
Sin_C_Inkrement = (Sin_A_Inkrement/1024)*((0*3)+512) =
= (Sin_A_Inkrement/1024)*(512) = Sin_A_Inkrement*0.5

So sähe es für den Akku von Sin_C aus:

Sin_C_Akku = Sin_C_Akku + (Sin_A_Inkrement/1024)*((Sin_B*3)+512)

Aber weil das erste /1024 vermutlich die Genauigkeit sehr stört, habe 
ich das nach hinten gezogen, also:

Sin_C_Akku = Sin_C_Akku + ((Sin_A_Inkrement)*((Sin_B*3)+512))/1024

Fertig sieht es so aus aufgeteilt auf 3 Schritte:
1
Sin_C_Inkrement0 <= to_unsigned((to_integer(Sin_B)*3)+512,31);
2
Sin_C_Inkrement1 <= to_unsigned((to_integer(Sin_A_Inkrement)*to_integer(Sin_C_Inkrement0))/1024,31);
3
Sin_C_Akku <= Sin_C_Akku + Sin_C_Inkrement1;

: Bearbeitet durch User
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.