Forum: Mikrocontroller und Digitale Elektronik Wie Konstanten tatsächlich im flash ablegen?


von H.Joachim S. (crazyhorse)


Lesenswert?

Das Problem ist an und für sich etwas kurios:
H-Brückencontroller mit ein paar Zusatzfunktionen. Kunde hat mehr oder 
weniger keine Ahnung von MC-Programmierung und will sich auch gar nicht 
damit beschäftigen. Nun kommt es immer wieder vor, dass ein paar Sachen 
erst bei ihm endgültig eingestellt/sich rangetastet werden sollen. 
Ablegen der Daten im EEPROM ist ausdrücklich (aufgrund früherer 
schlechter Erfahrungen) unerwünscht.

Und das sieht nun ursprünglich so aus (es gibt noch 9 andere Parameter):
1
#include <tiny25.h>
2
#define DEAD_TIME  200    //µs, da stell ich das vor dem Compilerlauf ein
3
.
4
.
5
.
6
#define DEAD_TIME_VALUE DEAD_TIME/8  //das macht der Präprozessor
7
.
8
.
9
OCR0A=DEAD_TIME_VALUE;     //und das schliesslich der eigentliche Code
Und da wird dann folgerichtig folgendes draus
1
                 ; 0000 008F OCR0A=DEAD_TIME_VALUE;
2
000084 e1e9        LDI  R30,LOW(25)
3
000085 bde9        OUT  0x29,R30
Die 0x19 im AVR-Studio-Memory-Window zu ändern bekommt er hin, die 
Adresse ändert sich aber mehr oder weniger nach jeder Programmänderung. 
Und es gibt ja noch ein paar andere Parameter (die z.T. auch mehrfach 
benutzt werden) - kurz, das ist nicht praktikabel.
Idee: die Parameter als Konstanten im flash abzulegen und diese alle am 
Stück und nicht verstreut im Programmcode, noch dazu verhackstückelt im 
Befehl:
1
flash unsigned int  DEAD_TIME_VALUE=DEAD_TIME/8;
2
flash unsigned int  START_UP_DELAY_VALUE=START_UP_DELAY/16;
Macht der Compiler (CodeVision) aber nicht, unabhängig von irgendwelchen 
Optimierungsstufen. Assemblercode bleibt der gleiche, ist je auch 
effektiver (schneller und kürzer) - nur möchte ich dies ausnahmsweise 
nicht.
Liegt das an meinem Compiler (bzw. machen andere das anders)? 
Lösungsmöglichkeiten?

von Jim M. (turboj)


Lesenswert?

H.Joachim Seifert schrieb:
> Ablegen der Daten im EEPROM ist ausdrücklich (aufgrund früherer
> schlechter Erfahrungen) unerwünscht.

Der ist aber genau für diesen Anwendungsfall gedacht. Mann muss sich 
natürlich vorher über einen strukturierten Zugriff Gedanken machen.

Alles andere ist entweder Murks oder deutlich aufwändiger.

von dummschwaetzer (Gast)


Lesenswert?

1
static const int meine_eeprom_variablen[Anzahl]@wunschadresse={meine_werte};

von Karl H. (kbuchegg)


Lesenswert?

H.Joachim Seifert schrieb:

> Idee: die Parameter als Konstanten im flash abzulegen und diese alle am
> Stück und nicht verstreut im Programmcode, noch dazu verhackstückelt im
> Befehl:
>
1
> flash unsigned int  DEAD_TIME_VALUE=DEAD_TIME/8;
2
> flash unsigned int  START_UP_DELAY_VALUE=START_UP_DELAY/16;
3
>
> Macht der Compiler (CodeVision) aber nicht, unabhängig von irgendwelchen
> Optimierungsstufen. Assemblercode bleibt der gleiche, ist je auch
> effektiver (schneller und kürzer) - nur möchte ich dies ausnahmsweise
> nicht.
> Liegt das an meinem Compiler (bzw. machen andere das anders)?
> Lösungsmöglichkeiten?

Vorab:
Wenn ich dich richtig verstehe, dann möchtest du ihm die Möglichkeit 
geben, direkt im Flash die Werte zu ändern. Dieses Vorgehen finde ich 
eher zweifelhaft, denn eigentlich ist das ganz genau der Grund, warum 
man ein EEPROM mit hat. Aber seis drum, es geh ums Prinzip:

Was passiert den in deinen Ursprungsfällen?
Was da passiert, nennt man Constant Folding. D.h. Der Compiler kennt ja 
die Werte der konstanten Variablen, und weil er weiß, dass diese 
konstant sind, kann er selbstverständlich deren Werte überall dort 
benutzen, wo er sonst einen Variablenzugriff machen müsste.
Daraus folgt sofort eine Strategie, wie du den Compiler da aushebeln 
kannst. Verstecke die tatsächlichen Werte vor ihm, und er kann kein 
Constant Folding mehr betreiben.
Wie kannst du das machen?
Ganz einfach: Wenn der Compiler nur Deklarationen und keine Definitionen 
der Konstanten sieht, dann weiß er zwar, dass es diese Konstanten gibt, 
nur bringt ihm das nix, weil er deren Werte nicht kennt

constants.c
1
#define DEAD_TIME  200
2
#define DEAD_TIME_VALUE DEAD_TIME/8
3
4
flash unsigned int  DEAD_TIME_VALUE = DEAD_TIME/8;
5
flash unsigned int  START_UP_DELAY_VALUE = START_UP_DELAY/16;

constants.h
1
extern flash unsigned int  DEAD_TIME_VALUE;
2
extern flash unsigned int  START_UP_DELAY_VALUE;


main.c
1
#include "constants.h"
2
3
int main()
4
{
5
  ....
6
  OCR0A = DEAD_TIME_VALUE;
7
8
  ....
9
}

Jetzt hat der Compiler keine andere Wahl. Er muss für DEAD_TIME_VALUE 
und START_UP_DELAY_VALUE Platz im Flash reservieren und für eine 
Intialisierung mit den entsprechenden Werten sorgen.

Sagte ich schon, dass ich von derartigen Vorgehensweisen "Wir patchen 
die Werte direkt im Speicher" nicht viel halte? Setz ihm wenigstens eine 
Entwicklungsumgebung so auf, dass er die Werte direkt im Header-File 
ändert und dann auf 'make' clickt (oder was auch immer benutzt wird) und 
dann kriegt er ein neu zu flashendes Programm. Denn das wäre nicht das 
erste mal, dass das tatsächlich laufende Programm mit dem was in Source 
Form vorliegt nicht übereinstimmt, was dann regelmässig zu Katzenjammer 
führt, wenn er sich den µC ruiniert und die Patchung nicht dokumentiert 
wurde (was sie mit Sicherheit nicht wird, dass kann ich dir jetzt schon 
sagen)


Warum ignorierst du eigentlich die weltweit übliche Konvention (und das 
dürfte so ziemlich die einzige Konvention sein, an die sich tatsächlich 
99% aller C-Programmierer halten), nach der Namen in Grossbuchstaben 
ausschliesslich für Makros reserviert sind, und umgekehrt Makronamen 
ausschliesslich in Grossbuchstaben geschrieben werden, sodass man beim 
Code lesen, wenn man auf einen Namen in Grossbuchstaben trifft, sofort 
mit Sicherheit weiß, dass es sich dabei um ein Makro handelt?

Diese Konvention hat einen triftigen Grund, weil gerade Makros, die als 
Funktionsersatz dienen, gefährlich sind. Es ist daher im eigentlich 
Source Code u.U lebenswichtig zu wissen, ob etwas eine echte Funktion 
oder ein Makro ist. Ein
1
#define MIN(x,y)  ((x)<(y) ? (x) : (y))

darf ich nun mal nicht so ...
1
int main()
2
{
3
  int i = 5; j = 8, k;
4
  
5
  k = MIN( i++, j++ );
... benutzen. D.h. ich 'darf' natürlich schon, nur sind die Ergebnisse 
nicht die, die man naiv erwarten würde. k kriegt den Wert 7 und nicht 6, 
wie man beim unbedarften Drüberlesen erwarten würde.
Aber da MIN in Grossbuchstaben geschrieben ist und ich mich an die 
Konvention halte, weiß ich, das das ein Makro ist und dass ich 
vorsichtig sein muss.
Während ich bei ...
1
int main()
2
{
3
  int i = 5; j = 8, k;
4
  
5
  k = min( i++, j++ );
... keinerlei Hemmungen zu haben brauche. min ist nicht groß 
geschrieben, daher kein Makro sondern eine Funktion. Ich darf es als 
nach Herzenslust benutzen, wie ich will.

Das funktioniert aber nur, wenn du dich an die Konvention hältst: 
Makronamen in Grossbuchstaben und Namen in Grossbuchstaben 
aussschliesslich nur für Makros.

von Wolfgang H. (frickelkram)


Lesenswert?

Hi,
der Linker legt fest was wo liegen soll.
Beitrag "Feste Speicheradressen mit WinAVR und AVRStudio"
http://www.nongnu.org/avr-libc/user-manual/pgmspace.html

Ich habe das eben mal ausprobiert, bei einem alten Projekt von mir.

radio.h
1
#ifndef __RADIO_H__
2
#define __RADIO_H__
3
4
...
5
6
// bass and treble adjustment lookup table
7
const uint8_t klang_code[] __attribute__ ((section (".radio"))) = {
8
  0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
9
  0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8
10
};
11
12
13
#endif

Makefile
1
...
2
$(TARGET).hex : $(TARGET).elf
3
        $(OBJCOPY) -R .eeprom -j .data -j .text -j .radio --change-section-address=.radio=0x2000 -O ihex $< $@
4
...
5
$(TARGET).const: $(TARGET).elf
6
        $(OBJCOPY) -j .radio --change-section-address=.radio=0x2000 -O ihex $< $@
7
...

Ergebnis:

radio.const
1
:1020000000010203040506070F0E0D0C0B0A090858
2
:00000001FF

radio.hex
1
:1000000093C0A2C0A1C0F4C39FC09EC09DC09CC0AD
2
:100010009BC09BC099C098C097C096C095C094C023
3
:1000200093C092C091C06E616E00696E6600004020
4
...
5
:1017B000ECCFDC01CB01FC01E199FECF06C0FFBB01
6
:1017C000EEBBE09A31960DB20D9241505040B8F701
7
:0617D0000895F894FFCF1C
8
:1020000000010203040506070F0E0D0C0B0A090858
9
:00000001FF

Ist es das, was Du möchtest?
Die Konstante steht jetzt an Speicheradresse $2000 im Flash. Du kannst 
natürlich sinnvolle Mechanismen finden mit denen es möglich ist die 
Speicheradresse zu berechnen und den Speicherblock an eine sinnvolle 
Stelle zulegen. Du könntest z.B. einen Bereich hinter den Vektoren 
reservieren und das Programm dann hinter Deinen Speicherblock für die 
Konstanten legen. Du kannst auch den maximalen Flash-Speicher Deines 
Chips her nehmen und dann rückwärts berechnen, wie viele Platz du 
brauchst und dann den Block genau ans Ende des Flash-Speichers legen.

Getestet unter Linux mit avr-gcc-4.3.5.

von Wolfgang H. (frickelkram)


Lesenswert?

Ahhh, zu schnell gepostet ... jetzt bin ich auf die Harward-Architektur 
herein gefallen. Beim AVR sind ja die Speicherbereiche getrennt (darum 
benötigt man ja auch spezielle Makros um auf das EEPROM zugreifen zu 
können). Da muss ich wohl nochmal tiefer rein schauen ;-)

von H.Joachim S. (crazyhorse)


Lesenswert?

@Karl-Heinz:
so funktionierts :-), Danke.

Ich sagte ja, es ist etwas kurios....
Das Problem, dass Source und Hex nicht konsistent sein könnten, besteht 
nicht. Das Ganze dient zur Ermittlung der optimalen Parameter in der 
Entwicklungsphase auf der Werkbank, die bekomme ich dann und dann gibts 
für jeden Motortyp/Läufertyp ein Source/Hex-file. D.h. in der Produktion 
werden nur die eingesetzt, die ich liefere.
EEPROM - tja, klar wäre es einfacher. Aber es gab früher ja ab und zu 
Probleme damit, irgendwann musste mal ne ganze Serie zurückgeholt werden 
und ist seitdem ein rotes Tuch :-).

Thema Gross/Kleinschreibung: da hast du natürlich auch recht, aber ich 
benutze Grossschreibung für Konstanten, eigentlich schon immer.

Und ohne copy hier im Editor hätte es natürlich so aussehen müssen:
flash unsigned int  dead_time_value=DEAD_TIME/8;

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.