Hallo zusammen,
ich stehe vor einer kleinen Herausforderung, die mir so neu ist.
Ich habe ein kleines Projekt für einen Attiny 2313A aufgesetzt. Das
Projekt besteht - mehr oder weniger - aus zwei unabhängigen Teilen:
1) Kommunikation mit einem Master via MODBUS-ASCII
2) Auslesen von Sensoren via I2C
Ich habe für die Entwicklung die beiden Teile unabhängig programmiert
und erfolgreich getestet.
Beide Teile zusammen erzeugen leider unsinnige Ergebnisse. Meine
Vermutung ist, dass es etwas mit doppelter Belegung des Speichers zu tun
hat.
Leider bemerkte ich dann auch, dass schon Optimierung "-Os" aktiviert
war. AVR-GCC meldet auch ca. 98% ROM-Auslastung.
Ich habe versucht mit "-O1" zu kompilieren, was jedoch mit Fehler
abbricht ("Program memory 114% Full").
Ich habe schon alle konstanten als "const" angelegt und alle Variablen
als "static".
Habt ihr schon mal ähnliches Erlebt?
Habt ihr einen Tipp, wie man die Sache angehen kann?
Zur Not würde ich auf den Attiny 4313 umsteigen.
Viele Grüße,
Alex
Alex B. schrieb:> AVR-GCC meldet auch ca. 98% ROM-Auslastung.
ist doch ok. Flash wird zur Laufzeit nicht gebraucht.
> Beide Teile zusammen erzeugen leider unsinnige Ergebnisse. Meine> Vermutung ist, dass es etwas mit doppelter Belegung des Speichers zu tun> hat.
kann nur passieren, wenn du selber den Speicher vergibst, wenn es der
GCC macht (was üblich bei C ist) dann passiert das nicht.
Hallo Alex,
zum Glück gibt es ja noch einen größern attiny mit 4kB Flash.
Hast Du modular Programmiert und saubere Schnittstellen zwischen den
Modulen festgelegt ?
Zeig uns doch einfach mal den gesamten Code und Schaltplan.
Peter II schrieb:> Uwe S. schrieb:>> zum Glück gibt es ja noch einen größern attiny mit 4kB Flash.>> damit wird das Problem aber nicht automatisch verschwinden.
Das habe ich auch nicht behauptet.
Uwe S. schrieb:> Peter II schrieb:>> Uwe S. schrieb:>>> zum Glück gibt es ja noch einen größern attiny mit 4kB Flash.>>>> damit wird das Problem aber nicht automatisch verschwinden.>> Das habe ich auch nicht behauptet.
Vielen Dank für die ganzen Antworten bisher!
Uwe S. schrieb:> Hast Du modular Programmiert und saubere Schnittstellen zwischen den> Modulen festgelegt ?
Hatte ich. Ich schmeiße aber gerade "alles in eine main()". Vorher hatte
ich ein paar globale Variablen und die waren zum Teil "extern"
deklariert.
Uwe S. schrieb:> zum Glück gibt es ja noch einen größern attiny mit 4kB Flash.
Das ist auch meine Nofall-Lösung.
Uwe S. schrieb:> Zeig uns doch einfach mal den gesamten Code und Schaltplan.
Damit warte ich mal noch ein bisschen... Ich will eure wertvolle Zeit
nicht damit vergeuden in meinem Code einen Fehler zu finden...
Viele Grüße,
Alex
Ausreichend neuen Compiler benutzen: "avr-GCC -v" sollte 4.8.2 oder
größer melden.
Compiler und Linker mit "-flto -Os" füttern, dann läuft der Optimizer
über das ganze Programm.
Compiler probeweise mit "-mcall-prologues" füttern, das kann bei vielen
Funktionen mit hoher Registerlast den Flash-Bedarf (bei minimaler
Laufzeiterhöhung) verringern.
Static-Variablen müssen nicht gut sein, wenn sie nicht wirklich
weiterleben müssen und jedesmal neu initialisiert werden, dann kann das
auch nach hinten losgehen. Da hilft eine
Stefan U. schrieb:> Na dann sehen wir auch davon ab, unsere Zeit mit wildem herumraten zu> vergeuden.
Was sagt man dazu... angespornt durch diese Aussage habe ich mich daran
gemacht den Code besser lesbar zu machen und habe ihn weiter in kleinere
Funktionen unterteilt. Außerdem habe ich alle Variablen als
"nicht-static" erstellt. Das Ergebnis:
Program Memory Usage : 1858 bytes 90,7 % Full
Data Memory Usage : 94 bytes 73,4 % Full
EEPROM Memory Usage : 1 bytes 0,8 % Full
Das Programm ist ein paar Prozent kleiner.
Aber viel wichtiger: Es läuft!
Erklären kann ich es mir nicht.
Optimierung steht nun auf "-Os".
Für den Fall, dass sich dennoch jemand das Projekt anschauen möchte und
ggf. ein paar Tipps hat, wie man die Performance noch verbessern kann,
habe ich mal die Quelldateien angehängt.
Ich werde es jetzt mal ein paar Stunden laufen lassen um zu sehen, ob es
auch dauerhaft stabil läuft...
Danke euch allen und viele Grüße,
Alex
> Erklären kann ich es mir nicht.
Manchmal liegt es an Stack überlauf. Hier im Forum wurden schon merhamls
Tips gegeben, wie man Stack Überlauf mit ein paar Zeilen Code zur
Laufzeit erkennen kann. Such mal danach. Du brauchst es zwar jetzt
gerade nicht mehr, aber das kommt irgendwann wieder.
Alex B. schrieb:> Aber viel wichtiger: Es läuft!>> Erklären kann ich es mir nicht.
Kann gut sein, daß durch die Strukturierung weniger RAM verbraucht wird.
34 Byte Stack ist nicht gerade viel.
Peter D. schrieb:> Alex B. schrieb:>> Aber viel wichtiger: Es läuft!>>>> Erklären kann ich es mir nicht.>> Kann gut sein, daß durch die Strukturierung weniger RAM verbraucht wird.> 34 Byte Stack ist nicht gerade viel.
Insgesamt sollen später ca. 20 Bus-Knoten am Modbus hängen. Der hier
beschriebene war der erste Prototyp. Für die "Serie" werde ich dann doch
lieber den Attiny4313 bestücken. Dann kann ich auch noch mal ohne
Optimierung kompilieren lassen und vergleichen...
Vielen Dank und viele Grüße,
Alex
Ist der gepostete Code der letzte Stand? Da sind nämlich immer noch
diverse static Variablen in Funktionen, die wohl eher nicht statisch
sind, da sie, bevor sie erstmals gelesen werden, überschrieben werden.
Wenn man eh zuwenig RAM hat, dann reservieren die dauerhaft Platz (und
der Compiler benutzt sie eventuell (fast) gar nicht). Gefährlich wird's
dann, wenn man meint, man hätte sie initialisiert. Das passiert in der
Form
1
voidfoo(){
2
Staticinti=0;
3
...
4
}
nämlich nur einmal. Beim Programmstart. Danach macht man mit den
Hinterlassenschaften des letzte foo()-Aufrufs weiter. Wer das nicht
vorher wußte, der tut sich auch mit dem debuggen schwer.
modbus_calcLRC() benutzt z.B. diesen "Trick" um die Prüfsumme über ALLE
bisher geprüften Nachrichten zu berechnen. Das geht wohl nur einmal.
Hallo Alex,
ich habe schon die Uart Interruput Routinen für TX und RX gesucht und
sie nicht direkt gefunden.
Und im File Modbus.c sind sie.
Aua:
a) cli / sei in einer Interruputroutine - warum ?
b) was bezweckst Du mit dem Return, das ist überflüssig !
1
//USART (RS485) received character interrupt
2
ISR(USART_RX_vect){
3
cli();
4
modbusProccessRxChar(UDR);//interrupt is cleared automatically upon read of UDR
5
sei();
6
return;
7
}
8
9
//USART (RS485) transmit data completed interrupt
10
ISR(USART_TX_vect){
11
cli();
12
;//interrupt is cleared automatically upon execution of this interrupt
13
14
//check if last Byte was transmitted. If yes, disable Line-Driver to free RS485 lines
Das sieht mehr nach zu wenig RAM aus, was dann zum Stack überlauf führen
kann.
Ein Problem sind da z.B. Aufrufe von funktionen in ISRs, insbesondere
solchen die nicht als Static deklariert sind. Das braucht unnötig viel
Platz auf dem Stack. Wenn es wegen der Übersicht mit funktionen sein
soll, dann bevorzugt als static inline ... . Flash Speicher scheint ja
jetzt vorhanden zu sein.
Das SEI() in der ISR ist gefährlich. Es erlaubt verschaltelte Interrupts
und damit zusätzlichen Bedarf an Platz auf dem Stack. Nötig ist es so
wie gezeigt nicht (die Freigabe kurz vor ende bringt fast keinen
Vorteil), und erzeugt die Gefahr selten auftretender und damit schwer zu
findender Fehler.
Stefan U. schrieb:> Das dachte ich auch bis vor kurzem. Ist aber falsch.
falsch, da ging es nicht um static.
static wird wirklich nur einmal initialisiert und liegt auch nicht auf
dem Stack sondern auf dem Heap.
> static wird wirklich nur einmal initialisiert
Und wird in "modbus.c" in der Prüfsummenberechnung als temporäre
Variable für das Ergebnis benutz. Und wird nur einmal mit 0
initialisiert.
Es stellen sich bei dem Programm also noch keine Optimierungfragen, denn
noch funktioniert es nicht.
Sollte es inzwischen doch funktionieren, weil der TO die "static"-Orgien
entfernt hat, so sollte er verstehen wollen, warum das jetzt tut. Sonst
setzt sich neben der bekannten "Regel", "viel volatile hilft viel", auch
noch die neue Regel, "mach dad static weg", zur Fehlerbehandlung durch.
Das ist Woodoo-Programmierung.
Carl D. schrieb:> Es stellen sich bei dem Programm also noch keine Optimierungfragen, denn> noch funktioniert es nicht.> Sollte es inzwischen doch funktionieren, weil der TO die "static"-Orgien> entfernt hat, so sollte er verstehen wollen, warum das jetzt tut. Sonst> setzt sich neben der bekannten "Regel", "viel volatile hilft viel", auch> noch die neue Regel, "mach dad static weg", zur Fehlerbehandlung durch.> Das ist Woodoo-Programmierung.
Erstaunlicherweise funktionierte es oft aber nicht immer. Von ca.
100 Messungen kamen ca. 90% beim Master an. Es kann durchaus sein,
dass es immer dann klappte, wenn die Prüfsumme zufällig korrekt war.
Manchmal kamen leider keine Messwerte an und manchmal betrug die
gesendete Temperatur 250 °C ... :-/
Wie dem auch sei. Ich werde es mir, sobald es meine Zeit zulässt, noch
mal im Detail ansehen.
Und Ja ich möchte sinnvoll programmieren und halte nichts von Vodoo
- weder in der Software, noch in der Hardware.
Bis dahin vielen Dank an alle, die sich an der Diskussion beteiligt
haben!
Viele Grüße,
Alex
P.S.:
Uwe S. schrieb:> ich habe schon die Uart Interruput Routinen für TX und RX gesucht und> sie nicht direkt gefunden.> Und im File Modbus.c sind sie.
Hältst du es für sinnvoller sie in uart.c unterzubringen? Dann müssen in
uart.c aber die modbus-Variablen bekannt sein. Oder ich müsste mir noch
mal Gedanken zur Schnittstelle machen...
Carl D. schrieb:> auch> noch die neue Regel, "mach dad static weg", zur Fehlerbehandlung durch.> Das ist Woodoo-Programmierung.
Ja und nein.
Klar, das static will man hier erst mal nicht im Sinne des Erfinders
haben. Auf der anderen Seite kann es sinnvoll sein, auch lokale
Variablen static zu machen, weil sie dann in der SRAM Statistik
auftauchen, was gerade bei kleinen Prozessoren nicht ganz uninteressant
ist. Das man dann sich natürlich nicht auf die Intialisierung verlassen
kann, ist selbstredend und folgt aus der eigentlichen Bedeutung von
'static'. Man verschwendet zwar ein paar Bytes, weiss aber exakt
wieviele.
Dem gegenüber steht, dass der Compiler in solchen Funktionen
Beim gcc bringt dir das 'const' hier genau gar nichts. OK. Du kriegst
hier einen Schönheitspreis.
Dein Problem ist das SRAM und das wird ohne und mit const genau gleich
benutzt.
Wenn du dir diese 16, auf deinem Tiny kostbaren Bytes aus dem SRAM
freischaufeln willst, dann musst das Array als __flash markieren oder
bei einem älteren gcc den Weg über PROGMEM und die pgm_readxxx
Funktionen gehen. Du zahlst dafür einen kleinen Preis in Form von ein
paar Takzyklen aber ansonsten hat das keine Nachteile. Ausser natürlich,
dass du 16 Bytes mehr SRAM zur Verfügung hast.
https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Flash_mit_PROGMEM_und_pgm_read
@khb
Wenn ich ein Problem mit der RAM-Größe habe, dann wird das nicht besser,
wenn ich für den lokalen Speicher aller Funktionen vorab RAM reserviere.
Den kann der Compiler nämlich nicht so einfach wegoptimieren, der könnte
ja tatsächlich bis zum nächsten Aufruf weiterleben müssen. Wegoptimieren
kann er die nur, wenn sie vor der ersten Verwendung immer initialisiert
wird. Aber genau das kann man dabei ja leicht falsch machen. Es kann
sogar sein, das der Code dank "LD Rn,Y+idx" kürzer wird, wenn lokale
Variablen, falls keine Register frei sind, im Stackframe liegen.
Viel einfacher spart man RAM tatsächlich, wenn man die
AVR/const/Flash-Problematik angeht. Und neuere GCC-Versionen machen es
(in C) ja leicht Dank "__flash". Wenn man die Sourcen anschaut, dann
sieht man, daß der Schreiber nicht orginär aus der AVR-Welt kommt:
//Clock System Initialization
klingt nach MSP430, da hat man einen linearen Addressraum und der
Compiler muß nicht mit "named address space" belästigt werden.
Carl D. schrieb:> @khb> Wenn ich ein Problem mit der RAM-Größe habe, dann wird das nicht besser,> wenn ich für den lokalen Speicher aller Funktionen vorab RAM reserviere.
Natürlich nicht.
Aber ich kann wenigstens in der SRAM Statistik sehen, dass ich ein
Problem haben werde. Das kann hilfreich sein. Darauf wollte ich hinaus.
Ein größeres Problem von dem ich weiß kann (muss nicht) mir lieber
sein, als ein kleineres von dem ich nichts weiß.
Ein Compiler für einen AVR erzeugt den besten Code, wenn man möglichst
wenig Variablen als "static" oder global nutzt. Am effizientesten kann
der AVR nämlich mit lokalen non-static Variablen umgehen, sofern es
keine Arrays sondern Basistypen sind (char, int, long, ...).
Lokale Variablen sollten nur dann "static" sein, wenn ihr Inhalt über
die Aufrufe hinaus erhalten bleiben muss. Andernfalls kosten sie nur
Platz und Zeit. Es spart auch keinen Platz, mehrfach verwendete
Schleifenzähler "i" ausserhalb der Funktion zu definieren, ganz im
Gegenteil.
Wenn du nicht wirklich weisst was du tust, dann versuch nicht, die
Arbeit des Compilers zu übernehmen und im Quellcode unnötig zu
optimieren. Ganz besonders nicht, wenn du dabei Regeln folgst, die
zumindest beim verwendeten Prozessor ein Schuss in den Ofen sind.
Dazu bräuchte es etwas, an das ich mich dunkel zu erinnern glaube, es
schon mal gesehen zu haben: eine Statistik der Stackbekegung, das geht
natürlich nur für nicht-rekursive Aufrufe, aber die Meldung "Achtung
Rekursion" wäre ja als Warnung auch ganz gut. Jede Funktion hat Bedarf
an Stack für ihren Aufruf, das Register-Sichern und eine Stackframe mit
lokalen Daten. Der Compiler kennt den. Und auch die Aufrufreihenfolge.
Wie geschrieben: ich hab schon Listings gesehen, wo so was mit
draufstand. Ich meine es wäre PLMxyz gewesen.
Peter II schrieb:> static wird wirklich nur einmal initialisiert und liegt auch nicht auf> dem Stack sondern auf dem Heap.
Als Heap bezeichnet man nur den Speicher für explizit dynamisch
allozierte Daten, via malloc/calloc.
Globale Variablen wie auch alle "static" Variablen liegen nicht auf dem
Heap, sondern einträchtig nebeneinander in zwei festen Daten-Sektionen.
Eine für explizit initialisierte Daten und eine für implizit beim Start
genullte Daten. Der Unterschied ist nur, ob diese Variablen im gesamten
Programm, nur im File oder nur in der Funktion bekannt sind.
Globals/statics explizit mit 0 zu initialisieren kann je nach Compiler
unnötig Platz im ROM verbrauchen, nämlich für jene 0, mit der die
Variable ohnehin initialisiert würde.
Hallo zusammen!
Es freut mich, dass ich eine so angeregte Diskussion angestoßen habe.
Ich habe versucht die hier gegebenen Vorschläge umzusetzen. Insbesondere
habe ich ein Update des AVR-GCC gemacht (war 4.3.3, ist jetzt 4.8.1).
Das Ergebnis:
Program Memory Usage : 1940 bytes 94,7 % Full
Data Memory Usage : 56 bytes 43,8 % Full
EEPROM Memory Usage : 1 bytes 0,8 % Full
Insbesondere der RAM ist nun also leerer.
Ich habe jetzt auch Konstanten mit "__flash" angelegt.
Ich werde über Nacht den Sensor (Modbus-Slave) wieder messen lassen, um
die "Stabilität" zu beobachten.
Carl D. schrieb:> //Clock System Initialization> klingt nach MSP430
Ich arbeite zumeist mit Freescale HCS08- und HCS12X- MCUs.
Uwe S. schrieb:> ich habe schon die Uart Interruput Routinen für TX und RX gesucht und> sie nicht direkt gefunden.> Und im File Modbus.c sind sie.
Wie könnte man das geschickter anordnen?
Zum TX-Interrupt: Ich möchte den Driver möglichst schnell nach Senden
des letzten Zeichens deaktivieren (da halb-duplex-Betrieb).
Zum RX-Interrupt: Da werden die Zeichen zu einem String zusammengepackt
und wenn der String fertig ist ("\r\n") wird ein Flag gesetzt.
Das Problem der UART weiß ja nichts davon, dass er das
"modbus-Protokoll" bedienen soll...
Für Vorschläge bin ich jederzeit offen.
Viele Grüße,
Alex
Alex B. schrieb:> Ich arbeite zumeist mit Freescale HCS08- und HCS12X- MCUs.
Die haben keine für lokale Variablen geeigneten Register, weshalb alle
nicht sowieso vom Compiler wegoptimierten Variablen unweigerlich im RAM
landen, egal ob global oder lokal, ob static oder auto. Bei den AVRs mit
32 Registern ist das anders. Regeln, die man sich für die Freescales
angewöhnt haben mag, sind auf AVRs oft nicht anwendbar.
> Die haben keine für lokale Variablen geeigneten Register, weshalb alle
nicht sowieso vom Compiler wegoptimierten Variablen unweigerlich im RAM
landen, egal ob global oder lokal, ob static oder auto.
Aber auch für den Fall gibt es den fiesen Unterschied ob ich static in
der Deklaration oder erst später initialisiere. Also hinschreiben, was
man meint. Legt der Compiler für die Freescale Dinger auto Variablen
dann auf den Stack, oder haben die wirklich fixe Adressen? Kann der dann
ohne Spezial-Keyword Reentrante Funktionen? Ich denke da an 51er
Compiler.
BTW, "auto" (ja, ich hab's selbst benutzt) hat in neusten C++-Versionen
eine geänderte Bedeutung. Nicht die Speicherklasse, "nicht static/nicht
register" sonder der Typ wird aus dem Umfeld ermittelt. Man hat diese
Inkompatible Änderung wohl deshalb gemacht, weil auto das "nie benutzte"
Keyword ist.
Carl D. schrieb:> Legt der Compiler für die Freescale Dinger auto Variablen> dann auf den Stack, oder haben die wirklich fixe Adressen?
Da diese Prozessoren relativ zu Stackpointer adressieren können werden
"auto" Vars wohl auf dem Stack landen. Neben Zirkus mit Reentrancy und
Int-Handlern erspart das dem Compiler die Optimierung nie gleichzeitig
genutzter Variablen auf die gleichen Adressen. Etwas, was für 8051 und
PIC (12/14bit) Compiler wichtig ist. Ein Stack macht das automatisch -
ist aber, wie oben schon erwähnt, schlechter kalkulierbar.
Letztlich bringt es auch bei diesen Prozessoren sicherlich nicht viel,
nur lokal genutzte Variablen global/statisch zu definieren. Zumal man
dem Compiler die Optimierung erschwert. Der Compiler ist (hoffentlich)
für den üblichen C Stil gebaut.
Nur macht es im Code der Freescales keinen grossen Unterschied aus, ob
die Daten mit 8 Bit Offset relativ zu SP adressiert werden, oder mit
fester 8 Bit Adresse (wenn wenig Daten). Hingegen ist der Unterschied
zwischen Register und RAM beim AVR sehr gross.
Ich wäre aber nicht erstaunt, im Kontext der Freescales Tipps zu finden,
dass häufig genutzte Daten mit direkter 8-Bit Adresse besser sind als
lokale. Denn 8-Bit relativ zu SP ist einen Takt langsamer ('08).
Für die 12er gibts übrigens eine GCC Implementierung. Wobei GCC für
Akku-Architekturen alles andere als ideal ist. Darin finden sich einige
Pseudo-Register im RAM. GCC wird vorgegaukelt, als hätte der einige
Register. Auch in einer Version für die an Register etwas knappen
Renesas R8C/M16C fand ich diese Technik.
Wenn so ein Compiler das Overlay-en von Daten beherscht, dann hat er
auch wesentliche Informationen, die zur Berechnung der notwendigen
Stack-Tiefe gebraucht werden. Auch daß er das Ende einer Rekursion nicht
kennt, könnte er sagen. Dann müßte Informationsbedarf nicht zu falschen
Programmierweises führen. "static" weils dann in .data Segment
ausgewiesen wird.
(das ist so wie der BWLer, dem Kosten egal sind, solange sie nicht an
der falschen Stelle in der Bilanz auftauchen. "Extern sind keine Bösen
Personalkosten".)
Carl D. schrieb:> Wenn so ein Compiler das Overlay-en von Daten beherscht, dann hat er> auch wesentliche Informationen, die zur Berechnung der notwendigen> Stack-Tiefe gebraucht werden.
So herum schon. Aber nicht unbedingt anders herum. Ein Compiler, der
Autos auf den Stack legt und somit keine RAM-Overlays braucht, der macht
sich evtl. nicht diese Mühe. Zumal GCC nicht primär für Embedded
entwickelt wird, und bei PCs interessiert das kein Schwein.