C-Datentypen
Datentyp | Alternativ | Alternativ | Bit | Dezimalbereich | Bemerkung |
---|---|---|---|---|---|
unsigned char | uint8_t | Byte | 8 | 255 | vorzeichenlose kleine Zahlen |
char | int8_t | 8 | -128..+127 | kleine Zahlen mit Vorzeichen | |
unsigned int | uint16_t | Word | 16 | 65535 | |
int | int16_t | 16 | -32768..+32767 | ||
unsigned long | uint32_t | Double Word | 32 | 4294967295 | *Hinweis: 9600UL |
long | int32_t | 32 | -2147483648..+2147483647 | *Hinweis: 9600L | |
float | 32 | ±10-37 .. ±10+38 | |||
void | leer oder unbestimmt |
Die Bitbreite der Datentypen kann auf unterschiedlichen Systemen variieren, deshalb gibt es alternative Bezeichnungen, aus denen die Bitbreite hervorgeht.
sizeof() gibt die Byte-Grösse einer Variablen zurück.
Variabeln werden entweder in Registern oder im SRAM gespeichert. Durch die vielen GPR (General Purpose Register) werden bei wenigen einfachen Variablen zuerst diese verwendet, bevor auf das SRAM ausgewichen wird.
Definieren von Variablen
Hinweis: Hier wird davon ausgegangen, dass die Variabeln im SRAM gespeichert werden.
Beim Definieren von Variablen wird vom Compiler Speicherplatz entsprechend des Datentyps reserviert.
Unter dem Namen (Identifier) merkt sich der Compiler die Startadresse der Variable. Der Speicher hat dabei zunächst zufällige, alte, undefinierte Werte. Durch Initalisierung können Startwerte vorgegeben werden.
char zeichen = 3;
int zahl = 0x1234;
Bei zahl wird Little Endian Speicherung verwendet:
Das niederwertigste Byte zuerst speichern.
char feld[] = {1,2,3};
// feld hat den Wert 0x0063
// feld[0] hat den Wert 1
Compilersicht | Adresse | Inhalt |
---|---|---|
Name: zeichen Typ: char sizeof(zeichen): 1 |
0x0060 | 3 |
Name: zahl Typ: int sizeof(zahl): 2 |
0x0061 | 0x34 |
0x0062 | 0x12 | |
Name: feld |
0x0063 | 1 |
0x0064 | 2 | |
0x0065 | 3 |
Felder, Arrays unter der Lupe
char text[5]="Bla";
Für das Textfeld werden 5 Byte reserviert. Initialisiert wird es mit einem String, der nur 4 Byte lang ist: Drei Zeichen und eine 0 als Terminalsymbol um das Ende zu markieren. Byte 5 bleibt somit undefiniert.
char feld2[2][3] = {{1,2,3},{4,5,6}};
// feld2 hat den Wert 0x006b
// feld2[1][0] hat den Wert 4
Indexberechnungen
Der Compiler ermittelt den Inhalt von Feld-Elementen mit Rechnungen wie diesen:
text[n] = inhalt_von(text + n*sizeof(text[0]))
feld2[a][b] = inhalt_von(feld2+ a*sizeof(feld2[0])+b*sizeof(feld2[0][0]))
Die inhalt_von-Funktion ist der *-Operator siehe Zeiger
Compilersicht | Adresse | Inhalt |
---|---|---|
Name: text Typ: char[5] sizeof(text): 5 sizeof(text[0]): 1 |
0x0066 | 'B' |
0x0067 | 'l' | |
0x0068 | 'a' | |
0x0069 | '\0' = 0 | |
0x006A | ? = undefiniert | |
Name: feld2 Typ: char[2][3] sizeof(feld2): 6 sizeof(feld2[0]): 3 sizeof(feld2[0][0]): 1 |
0x006B | 1 |
0x006C | 2 | |
0x006D | 3 | |
0x006E | 4 | |
0x006F | 5 | |
0x0070 | 6 |
Zeiger, Pointer
Unter einem Zeiger (engl. Pointer) versteht man den Verweis, die Referenz auf eine Speicheradresse.
Ein Zeiger ist eine Variable, die sich eine Speicheradresse merken kann.
char *zeichen_ptr; // Zeiger auf char definieren
zeichen_ptr = &zeichen; // Adresse von zeichen zuweisen
*-Operator dereferenziert den Zeiger, greift auf Wert der referenzierten Speicheradresse zu.
&-Operator gibt die Adresse der Variablen zurück
// &zeichen ergibt 0x0060
// zeichen_ptr ergibt 0x0060
// *zeichen_ptr ergibt 3
int zahlen[] = {1,2};
int *zahlen_ptr = &zahlen[1];
Compilersicht | Adresse | Inhalt |
---|---|---|
Name: zeichen_ptr Typ: char* sizeof(zeichen_ptr): 2 |
0x0071 | 0x60 |
0x0072 | 0x00 | |
Name: zahlen Typ: int[2] sizeof(zahlen): 4 sizeof(zahlen[0]): 2 |
0x0073 | 1 |
0x0074 | 0 | |
0x0075 | 2 | |
0x0076 | 0 | |
Name: zahlen_ptr Typ: int* sizeof(zahlen_ptr): 2 |
0x0077 | 0x75 |
0x0078 | 0 |
Aufgaben
Der Speicher beginnt bei 0x0060. Ermitteln Sie den Speicherinhalt (Tabelle) und die Ausgaben (mit cout) bei folgendem Code.
unsigned int muriel = 0x0815;
int *sister_ptr = &muriel;
cout << *sister_ptr; // welchen Wert hat *sister_ptr
*sister_ptr = 0x6666;
cout << muriel;
unsigned char tom = 128;
char *tom_prt = &tom;
cout << *tom_ptr;
char biene[3][2] = {{1,2},{3,4}};
cout << sizeof(biene);
char *bee_ptr = &biene[2][0];
cout << bee_ptr;
*bee_ptr = 7;
*(++bee_ptr) = 8;
cout << bee_ptr;
Lösung..
Compilersicht | Adresse | Inhalt |
Wert neu |
---|---|---|---|
Name: muriel Typ: uint sizeof(muriel): 2 |
0x0060 | 0x15 | 0x66 |
0x0061 | 0x08 | 0x66 | |
Name: sister_ptr Typ: int* sizeof(sister_ptr): 2 sizeof(int*): 2 |
0x0062 | 0x60 | |
0x0063 | 0x00 | ||
Name: tom Typ: uchar |
0x0064 | 128 | |
Name: tom_ptr Typ: char* |
0x0065 | 0x64 | |
0x0066 | 0x00 | ||
Name: biene Typ: char[3][2] sizeof(biene): 6 sizeof(biene[0]): 2 sizeof(biene[0][0]): 1 |
0x0067 | 1 | |
0x0068 | 2 | ||
0x0069 | 3 | ||
0x006a | 4 | ||
0x006b | ? | 7 | |
0x006c | ? | 8 | |
Name: bee_ptr Typ: char* |
0x006d | 0x6b | 0x6c |
0x006e | 0x00 | 0x00 |
Kurztest
Welche Funktion hat sizeof(..)?
Beschreiben Sie, was bei der Definition einer Variablen geschieht?
Welche Funktion haben der *- und &- Operator bei Variablen?
Erklären Sie die Begriffe Zeiger, Pointer.
Erklären Sie diese Abkürzungen und ihre Funktion ROM, RAM, SRAM, DRAM, EEPROM.
Der freie RAM-Speicher beginnt bei Adresse 0x80. Erstellen Sie eine Tabelle [Compilersicht | Adresse | Inhalt] nachdem dieser Code durchlaufen wurde:
char a = 127+1;
int b = 1024;
char c[4] = {2,3,4};
char *d = c[1];
Mit PROGMEM Daten im Flash-Programmspeicher speichern und lesen
Bei AVR-µC sind Daten- und Programmspeicher getrennt (Havard-Architektur) dies bereitet mit dem GCC einige Probleme, da dieser für die Von-Neumann-Architektur konstuiert wurde, bei dem von einem durchgehenden Speicherbereich ausgegangen wird. Bei zwei getrennten Speichern kommen Adressen doppelt vor, auch der Zugriff auf Daten im Flash funktioniert anders als beim SRAM. Mit PROGMEM können konstante Daten im Flash-Speicher untergebracht werden.
[mikrocontroller.net/articles/AVR-GCC-Tutorial] [<avr/pgmspace.h>: Program Space Utilities].
#include <avr/pgmspace.h>
const int zahl PROGMEM = 0x1234;
const char text[] PROGMEM = "Bo";
Compilersicht | Adresse | Inhalt |
---|---|---|
Name: zahl Typ: int sizeof(zahl): 2 |
0x0200 | 0x34 |
0x0201 | 0x12 | |
Name: text Typ: char[3] sizeof(text): 3 |
0x0202 | 'B' |
0x0203 | 'o' | |
0x0204 | '\0' |
Gelesen werden die Daten mit besonderen Funktionen, denen die Speicheradresse der Daten mittels des &-Operators übergeben wird.
// pgm_read_word(&zahl) ergibt 0x1234
// pgm_read_byte(&text[1]) ergibt 'o'
Ergebnis | Aufruf | Beschreibung |
---|---|---|
8 Bit | pgm_read_byte(&name) | Byte aus Programm-Flash |
16 Bit | pgm_read_word(&name) | Wort aus Programm-Flash |
32 Bit | pgm_read_dword(&name) | DWort aus Programm-Flash |
Texte im Flash speichern und lesen
const char text1[] PROGMEM = "Danke OM";
const char text2[] PROGMEM = "Logoroe";
const char text3[] PROGMEM = "Wunderbar";
const char *const texte[] PROGMEM = { // Zeiger auf Texte in Zeigerfeld speichern
text1,text2,text3
};
char anzahlTexte = sizeof(texte)/sizeof(texte[0]);
void ausgebenLCD(char i){ // den i-ten Text auf LCD ausgeben
char buffer[20]; // Puffer in SRAM
const char *texte_ptr = (const char*) pgm_read_word (&texte[i]); // Zeiger auf text
strcpy_P(buffer, texte_ptr ); // Text in SRAM kopieren
lcd_string(buffer); // ausgeben
}
Bei verschieden langen Strings wäre ein einfaches 2D-Array verschwendeter Platz. Deswegen ist ein Zeiger-Array auf Texte die sparsamere Lösung. Weil der GCC nicht mit dem getrennten Flash-Speicherbereich klar kommt müssen einige Klimmzüge gemacht werden. Die Daten werden dabei in den SRAM kopiert.