Forum: Mikrocontroller und Digitale Elektronik MCP 4921 & ATTINY 2313 Sprünge


von Stefan S. (chiefeinherjar)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

ich bin momentan ein wenig ratlos.
Ich habe folgendes Problem: Für eine kleine Spielerei wollte ich eine 
Soft-DDS mit einem ATTINY2313 und dem MCP4921 D/A-Wandler 
implementieren. Im Rahmen des ursprünglichen Projekts habe ich 
festgestellt, dass die Ausgabe des MCP4921 sehr viele periodische 
Störungen aufgewiesen hat.
Nach einigem Messen hatte ich schließlich die Software im Verdacht und 
eine reine Soft-DDS geschrieben - das Ergebnis war zwar (weitestgehend) 
vernünftig, es traten aber weiterhin einzelne sehr starke periodische 
Störungen auf.

Geprüft habe ich:
- +5 Volt Spannungsversorgung (sauber)
- Taktsignale (Flanken waren tadellos und das Timing war auch richtig)
- Spannungsreferenz (sauber)
- Lötverbindungen der Chips
- Datensignale (Flanken waren auch tadellos, die Daten an sich aber 
stellenweise fehlerhaft)

Disclaimer Ja, ich weiß, im Schaltplan ist PB5 mit dem Serial-In des 
MCP 4921 verbunden - das ist mir bereits aufgefallen und musste daher 
auf dem Board die bestehende Verbindung auftrennen (die ist auch 
WIRKLICH getrennt) und habe dann die Verbindung mit ein paar Centimetern 
Lackdraht gemacht. Foto vom Aufbau kann ich bei Bedarf nachreichen.

Ich habe dann den Logic-Analyzer (ein 10 Euro Saleae-Klon) an die 
Datenleitungen gehängt und festgestellt, dass die 
übertragenenen/mitgeschnittenen Daten passen exakt zu den Störungen auf 
den Bildern.

Ich habe dazu eine CSV-Datei mit den mitgeschnittenen Daten angehängt 
und in der Spalte "Sinus" die gesendeten Daten von den 
Konfigurationsbits bereinigt, sodass man die eigentlichen Daten sieht.

Offensichtlich werden also schon die falschen Daten zum DA-Wandler 
übertragen...
Ich habe bereits testweise mit der Optimierung herumgespielt, aus dem 
Array ein Const gemacht.

Ich habe offensichtlich ein großes Brett vor'm Kopf, aber ich brauche 
einfach mal einen Anstoß, nachdem ich bereits mehrere Abende in dieses 
Problem versenkt habe.

Hier ist mein Quick-And-Dirty-Code für das reine Testen der Soft-DDS:
1
#define F_CPU 8000000
2
#include <avr/io.h>
3
4
5
uint8_t sine[128] = {
6
128,134,140,146,152,158,165,170,
7
176,182,188,193,198,203,208,213,
8
218,222,226,230,234,237,240,243,
9
245,248,250,251,253,254,254,255,
10
255,255,254,254,253,251,250,248,
11
245,243,240,237,234,230,226,222,
12
218,213,208,203,198,193,188,182,
13
176,170,165,158,152,146,140,134,
14
128,121,115,109,103,97,90,85,
15
79,73,67,62,57,52,47,42,
16
37,33,29,25,21,18,15,12,
17
10,7,5,4,2,1,1,0,
18
0,0,1,1,2,4,5,7,
19
10,12,15,18,21,25,29,33,
20
37,42,47,52,57,62,67,73,
21
79,85,90,97,103,109,115,121
22
};
23
24
#define GAIN_SELECT_x1  13
25
#define NOT_SHUTDOWN  12
26
27
28
#define SPI_CS_SET_OUTPUT(){    \
29
  DDRB |= 1<<DDB4;        \
30
}
31
32
33
#define SPI_SCK_SET_OUTPUT(){    \
34
  DDRB  |= 1<<DDB7;        \
35
  PORTB  &= ~(1<<PB7);    \
36
}
37
38
#define SPI_MISO_INPUT(){      \
39
  DDRB  &= ~(1<<DDB6);      \
40
  PORTB  &= ~(1<<PB6);    \
41
}
42
43
#define SPI_SetSS(){        \
44
  DDRB |= 1<<DDB4;        \
45
  PORTB |= 1<<PB4;        \
46
}
47
48
#define SPI_ClrSS(){        \
49
  DDRB |= 1<<DDB4;        \
50
  PORTB &= ~( 1<<PB4);      \
51
}
52
53
#define SPI_DO_SET_OUTPUT(){    \
54
  DDRB  |= 1<<DDB6;        \
55
  PORTB  |= 1<<PB6;      \
56
}
57
58
void usi_spi_init(){
59
  SPI_DO_SET_OUTPUT();
60
  SPI_CS_SET_OUTPUT();
61
  SPI_SCK_SET_OUTPUT();
62
  USICR = 0x00;
63
  USISR |= 1<<USIOIF;
64
  USICR |= 1<< USIWM0; //Three-Wire-Mode
65
  //  USICR |= 1<< USICLK;
66
  return;
67
}
68
69
70
void usi_spi_send_data(uint16_t spi_data_to_send){
71
  spi_data_to_send &= 0xfff;
72
  spi_data_to_send |= 1<<GAIN_SELECT_x1 | 1<<NOT_SHUTDOWN;
73
74
  SPI_ClrSS();
75
  PORTD &= ~(1<<PORTD0);
76
  USIDR=spi_data_to_send>>8;
77
78
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
79
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
80
81
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
82
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
83
84
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
85
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
86
  
87
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
88
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
89
  
90
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
91
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
92
93
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
94
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
95
96
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
97
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
98
  
99
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
100
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
101
  
102
  USIDR=spi_data_to_send;
103
  
104
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
105
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
106
107
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
108
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
109
110
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
111
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
112
  
113
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
114
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
115
  
116
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
117
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
118
119
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
120
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
121
122
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
123
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
124
  
125
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
126
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
127
  PORTD |= (1<<PORTD0);
128
  SPI_SetSS();
129
  return;
130
}
131
132
133
int main(void)
134
{
135
  usi_spi_init();
136
  uint8_t sine_cnt = 0;
137
  uint8_t phase_acc = 1;
138
  DDRD |= 1<<DDD0;
139
  PORTD &= ~(1<<PORTD0);
140
    while(1)
141
    {
142
        usi_spi_send_data(sine[sine_cnt]<<4);
143
  sine_cnt += phase_acc;
144
  if(sine_cnt == 128){
145
    sine_cnt = 0;
146
  }
147
    }
148
}

von Stefan S. (chiefeinherjar)



Lesenswert?

Mist - ganz vergessen: Hier sind die Gerber-Dateien, damit es auch 
Nicht-KiCAD-User anschauen können.

von Bernd B. (bbrand)


Lesenswert?

Tja, ein ATTINY2313 hat aber nur 128 Byte SRAM. Das ist durch das 128 
Byte große Array schon komplett belegt. Für die lokalen Variablen und 
Funktionsaufrufe werden aber noch ein paar Bytes für den Stack 
gebraucht, und damit werden dann die letzten 12 Byte des Array 
überschrieben.

von Stefan S. (chiefeinherjar)


Lesenswert?

Bernd B. schrieb:
> Für die lokalen Variablen und
> Funktionsaufrufe werden aber noch ein paar Bytes für den Stack
> gebraucht, und damit werden dann die letzten 12 Byte des Array
> überschrieben.

Ich Hornochse! Natürlich!

Ich habe mich so darin verrannt, dass mich der Compiler nicht von sich 
aus gewarnt hat, dass mein RAM überbelegt ist.
Normalerweise meckert Atmel Studio schon von sich aus, wenn der RAM mehr 
als 100 % belegt ist.
Ich habe das Array mal in den Flash verschoben - und siehe da: Alles 
funktioniert tadellos!
Jetzt interessiert mich aber doch irgendwie eines: Ich programmiere mit 
Atmel Studio 6.2 und dem GCC Compiler - wird bei der Angabe
1
        Program Memory Usage   :  970 bytes   47,4 % Full
2
        Data Memory Usage     :  0 bytes   0,0 % Full
der Stack nicht mit berücksichtigt?!

von Bernd B. (bbrand)


Lesenswert?

Stefan S. schrieb:
> wird bei der Angabe        Program Memory Usage   :  970 bytes   47,4 %
> Full
>         Data Memory Usage     :  0 bytes   0,0 % Full
> der Stack nicht mit berücksichtigt?!

Nein. Weder der Compiler noch der Linker wissen, wieviel Stack benötigt 
wird; dazu ist eine tiefere Analyse der Programmstruktur nötig, als ein 
Compiler normalerweise macht. Programme zur statischen Codeanalyse, wie 
z.B. Lint, können die benötigte Stackgröße bestimmen.

von c-hater (Gast)


Lesenswert?

Stefan S. schrieb:

> Jetzt interessiert mich aber doch irgendwie eines: Ich programmiere mit
> Atmel Studio 6.2 und dem GCC Compiler - wird bei der Angabe
>
1
>         Program Memory Usage   :  970 bytes   47,4 % Full
2
>         Data Memory Usage     :  0 bytes   0,0 % Full
3
>
> der Stack nicht mit berücksichtigt?!

Weil zur Compilezeit unmöglich für den allgemeinen Fall die maximale 
Stacknutzung zur Laufzeit berechnet werden kann.

Das geht nur für sehr einfache Programme, die obendrein von vorherein 
besonderen Bedingungen genügen müssen. Solche trivialen Programme 
stellen aber so eine große Ausnahme dar, dass es den Compilerbauern 
nicht lohnend erschien, eine entsprechende Analyse zu integrieren. Zumal 
eine solche Analyse wiederum die Gefahr der Rekursion in sich birgt, 
d.h.: schon der Compiler schmiert dann ab, es kommt also garnicht mehr 
zu einem lauffähigen Code. Und das selbst dann, wenn der Code zur 
Laufzeit niemals tatsächlich ein Problem hätte. Allein das Potential, 
eines verursachen zu können, würde dem Compiler bereits zum Verhängnis 
werden...

von Stefan S. (chiefeinherjar)


Lesenswert?

Vielen Dank euch beiden für die konstruktiven Antworten!

Ich habe mich bereits wieder mit meinen alten Unterlagen und den 
Artikeln hier beschäftigt und da ist es mir wie Schuppen von den Augen 
gefallen.

In diesem Sinne: Das Brett vor meinem Kopf ist in den Ofen gewandert und 
ich habe meine zugegebenermaßen etwas eingerosteten Kenntnisse wieder 
etwas schmieren können.

von Christian S. (roehrenvorheizer)


Lesenswert?

Hallo,

Wird mit der Deklaration das Array grundsätzlich im RAM vorgehalten?

"uint8_t sine[128] ="

Wäre es mit const davor anders gewesen? Oder ist nur die Deklaration mit 
Progmem hier die richtige, damit das Array im Flash residiert?

z.B. const uint8_t PROGMEM [128]= .....

Mit freundlichem Gruß

von Stefan S. (chiefeinherjar)


Lesenswert?

Christian S. schrieb:
> Oder ist nur die Deklaration mit Progmem hier die richtige, damit das
> Array im Flash residiert?

Dies ist meines Wissens nach die einzig richtige Lösung, um das ganze in 
den Flash zu schieben.
Ein "const" alleine macht nicht viel, außer dass es den Compiler dazu 
veranlasst, dich zu warnen, wenn du die Variable/Array doch nachträglich 
verändern willst.

Du bist übrigens nicht der erste mit dieser Frage - in diesem Thread 
wird das ganze gut erklärt:

Beitrag "der gcc, das const-Schlüsselwort und der Speicher..."

von Stefan S. (chiefeinherjar)


Lesenswert?

Christian S. schrieb:
> Oder ist nur die Deklaration mit Progmem hier die richtige, damit das
> Array im Flash residiert?

Dies ist meines Wissens nach die einzig richtige Lösung, um das ganze in 
den Flash zu schieben.
Ein "const uint8_t" alleine macht nicht viel, außer dass es den Compiler 
dazu veranlasst, dich zu warnen, wenn du die Variable/Array doch 
nachträglich verändern willst.

Du bist übrigens nicht der erste mit dieser Frage - in diesem Thread 
wird das ganze gut erklärt:

Beitrag "der gcc, das const-Schlüsselwort und der Speicher..."

@Mods: Kann bitte jemand meinen Doppelpost bitte löschen. Hatte Probleme 
mit dem Netz...

: Bearbeitet durch User
von Christian S. (roehrenvorheizer)


Lesenswert?

Danke, zu Zeiten des Links waren wir alle noch jung und ungestüm....

von Stefan S. (chiefeinherjar)



Lesenswert?

Ich muss mich doch nochmals an euch wenden, ich habe mittlerweile die 
Ursache dafür gefunden, dass in meinem ursprünglichen Code der Sinus nur 
mit schlechter Qualität ausgegeben wurde:
Wenn ich die Optimierung DEAKTIVIERE - wie ich es bei dem DDS-Beispiel 
gemacht hatte - dann sieht der Sinus sauber aus, ich kriege aber nur ca. 
670kHz Taktrate.
Wenn ich die Optimierung AKTIVIERE (-O1 und aufwärts) bekomme ich zwar 
meine gewünschte Taktrate von 4MHz, die Datensignale sind aber leicht 
versetzt und der ausgegebene Sinus .

In den Bildern sieht man es ganz gut, bei deaktiviere, sieht der Sinus 
"sauber" aus (von den obligatorischen Stufen abgesehen) und die 
Datensignale sind zur steigenden Taktflanke stabil.
Bei aktivierter Optimierung sind liegt die Änderung der Datensignale mit 
der steigenden Flanke des Taktes zusammen - was wohl die miese Qualität 
des Sinus erklärt.
Mein Oszi scheint wohl damit klarzukommen, aber der DAC wohl - 
verständlicherweise - nicht, was mich zu der ursprünglichen und irrigen 
Annahme verleitet hatte, dass die Datensignale in Ordnung seien.

Liegt es wirklich an der Optimierung?! - Habe ich mal wieder ein völlig 
offensichtliches Brett vor meinem Kopf?!

von Stefan S. (chiefeinherjar)


Lesenswert?

Hm.

Jetzt bin ich endgültig verwirrt:
Ich habe mit verschiedenen Codes aus diversen Quellen herumgespielt.
Das Ergebnis ist wie folgt:

Wenn ich das Assembler-Beispiel aus dem Datenblatt für High-Speed-SPI in 
C übernehme, dann funktioniert alles wie es soll (auch mit Optimierung):
1
  USICR=  (1<<USIWM0)|(1<<USITC);
2
  USICR=  (1<<USIWM0)|(1<<USITC)|(1<<USICLK);
3
4
  USICR=  (1<<USIWM0)|(1<<USITC);
5
  USICR=  (1<<USIWM0)|(1<<USITC)|(1<<USICLK);
6
7
  USICR=  (1<<USIWM0)|(1<<USITC);
8
  USICR=  (1<<USIWM0)|(1<<USITC)|(1<<USICLK);
9
10
  USICR=  (1<<USIWM0)|(1<<USITC);
11
  USICR=  (1<<USIWM0)|(1<<USITC)|(1<<USICLK);
12
  
13
  USICR=  (1<<USIWM0)|(1<<USITC);
14
  USICR=  (1<<USIWM0)|(1<<USITC)|(1<<USICLK);
15
16
  USICR=  (1<<USIWM0)|(1<<USITC);
17
  USICR=  (1<<USIWM0)|(1<<USITC)|(1<<USICLK);
18
19
  USICR=  (1<<USIWM0)|(1<<USITC);
20
  USICR=  (1<<USIWM0)|(1<<USITC)|(1<<USICLK);
21
22
  USICR=  (1<<USIWM0)|(1<<USITC);
23
  USICR=  (1<<USIWM0)|(1<<USITC)|(1<<USICLK);

Weiterhin habe ich aus diesem Thread 
(Beitrag "SPI am attiny2313 initialisieren") diesen Ansatz entnommen, 
welcher aus bestens (auch mit Optimierungen) funktioniert:
1
  USISR=(1<<USIOIF);
2
  do
3
    {
4
    USICR=  (1<<USIWM0)|
5
        (1<<USICS1)|
6
        (1<<USICLK)|
7
        (1<<USITC);
8
    }
9
  while (!(USISR&(1<<USIOIF)));


Auf Basis dieses Codes habe ich mir gedacht, dass ich einfach die obige 
Schleife "entrolle" und habe einfach pro Byte den Teil des USICR 16 Mal 
kopiert.
Nur dies funktioniert nur, wenn die Optimierungen deaktiviert sind:
1
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
2
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
3
4
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
5
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
6
7
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
8
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
9
  
10
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
11
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
12
  
13
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
14
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
15
16
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
17
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
18
19
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
20
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
21
  
22
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);
23
  USICR=  (1<<USIWM0)|(1<<USICLK)|(1<<USICS1)|(1<<USITC);

Eigentlich sollte es doch bei den letzten beiden Varianten keinen 
nennenswerten Unterschied geben?!

von holger (Gast)


Lesenswert?

>Eigentlich sollte es doch bei den letzten beiden Varianten keinen
>nennenswerten Unterschied geben?!

Doch, am Ende der Schleife hast du eine Abfrage und einen Sprung
zum Beginn der Schleife. Das ergibt ein kleines Delay.

von Stefan S. (chiefeinherjar)


Lesenswert?

holger schrieb:
>> Eigentlich sollte es doch bei den letzten beiden Varianten keinen
>>>nennenswerten Unterschied geben?!
>
> Doch, am Ende der Schleife hast du eine Abfrage und einen Sprung
> zum Beginn der Schleife. Das ergibt ein kleines Delay.

Das war auch bereits mein Gedanke... Daran muss es offensichtlich 
liegen.
Im Datenblatt gibt es die gleiche Schleife in Assembler, da wird auch 
zyklisch eine Abfrage gemacht.
Interessant...

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.