Forum: Mikrocontroller und Digitale Elektronik C und Register


von Matt (Gast)


Lesenswert?

Hallo Forengemeinde,

ich suche nach einer Lösung wie ich das am besten mache. Ich wollte mir 
eine Registertabelle anlegen für einen IC. Das ganze mache ich mit 
#define um dann in den Registern Bits setzen oder Löschen können.
Das #define im Code nachhinen nicht mehr verändert werden kann, habe ich 
auch schon rausgefunden, aber wie machen das den die Profis?

Wenn ich in der Lib vom Atmel AVR reinschaue steht dort auch
#define DDRB
#define DRB1

Trotzdem ist sowas:
DDRB |= (1<<DRB1)
problemlos machbar.

Mein Code siet wie folgt aus:
1
//Mode Register
2
#define W5500_MR   (uint16_t)0x0000
3
#define W5500_MR_RST  (uint8_t)0x07
4
5
W5500_MR |= (1 << W5500_MR_RST);

Wie geht das von statten?

MfG
Matt

von Gerald K. (geku)


Lesenswert?

Eine Möglichkeit ist die Register in einer Struktur zu definieren. Wo in 
der Struktur jedes Register als Bitfield definiert werden kann.

von Cyblord -. (cyblord)


Lesenswert?

Matt schrieb:

> Trotzdem ist sowas:
> DDRB |= (1<<DRB1)
> problemlos machbar.

Das ist machbar weil da nicht

> #define DDRB

steht sondern z.B.
1
#define DDRB  (uint_8*)(0x1234)

Defines sind TEXTERSETZUNGEN. Sonst nichts. Bitte merken.
Und wenn der Text durch eine Variable oder einen Pointer ersetzt wird, 
kann ich damit tun was ich sonst auch ohne das define damit hätte tun 
können.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Du bist längst nicht der erste mit dieser Idee.

Leider hat Programmiersprache C keine Befehle für einzelne Bits. 
Deswegen lässt es sich damit nicht ordentlich umsetzen, ganz egal was 
für ausgefuchste Strukturen du dir ausdenkst. Irgendwo ist immer ein 
Haken.

Deswegen nutze die Zeit lieber für andere Sachen die Spaß machen oder 
Geld einbringen.

von S. R. (svenska)


Lesenswert?

Matt schrieb:
> Mein Code siet wie folgt aus://Mode Register
> #define W5500_MR   (uint16_t)0x0000
> #define W5500_MR_RST  (uint8_t)0x07
> W5500_MR |= (1 << W5500_MR_RST);

Der Präprozessor ist ein Textersetzer.

Der macht dadraus folgendes:
> 0x0000 |= (1 << 0x07)

Und das geht nicht, du kannst in einer Null nicht einfach ein paar Bits 
setzen. Probiere mal folgendes:

> #define W5500_MR *(uint16_t*)0x0000

Dann geht das, weil deine Null dann eine Adresse ist und keine 
einfache Zahl. Aber in dem Fall ist das ein Nullpointer (weil Adresse 
0x0000), darauf schreiben tut ziemlich sicher nicht das, was du willst.

Das ganze System funktioniert sowieso nur, wenn deine Register 
memory-mapped sind, also direkt per Adresse ansprechbar. Wenn da ein 
Bussystem (I2C, SPI, CAN, ...) dazwischen liegt, geht das alles nicht.

von Bauteiltöter (Gast)


Lesenswert?

In den Headers von Atmel steht sicher mehr als nur
#define DDRB
#define DRB1

Der Präprozessor mit seinem #define macht nur Textersetzung: mach das 
doch mal im Kopf und schau was aus deinem Beispiel wird:
1
//Mode Register
2
3
#define W5500_MR   (uint16_t)0x0000
4
5
#define W5500_MR_RST  (uint8_t)0x07
6
7
W5500_MR |= (1 << W5500_MR_RST);

wird zu
1
(uint16_t)0x0000 |= (1 << (uint8_t)0x07);

Was soll das also sein? Du weißt einer Zahl einen Wert zu, das kann 
natürlich nicht gehen.

Was du in den Atmel-Headern gesehen hast ist ähnlich zu dem hier:
1
#define W5500_MR (*(uint16_t*)0x0000)

das wird dann bei einem Schreibzugriff zu:
1
(*(uint16_t*)0x0000) = 42;

Du hast also Links (LValue) eine Adresse und keine Zahl. Der Code würde 
die Zahl 42 an die Adresse 0x0000 schreiben. So kann man auf 
AVR-Register zugreifen weil die direkt im Adressraum des AVRs liegen 
(memory mapped). Das geht also bei Chips die über SPI, I2C oder sowas 
angebunden sind natürlich nicht. Einzige Ausnahme wären z.B. parallele 
LCDs die man z.B. beim Mega128 memory mapped anschließen könnte.

Ich würde das so machen:
1
#define MYCHIP_REG_A 0x00
2
#define MYCHIP_REG_B 0x01
3
4
#define MYCHIP_CFG_BIT0 0
5
#define MYCHIP_CFG_BIT1 1
6
7
mychip_write_reg(MYCHIP_REG_A, 1<<MYCHIP_CFG_BIT0);

in der Funktion mychip_write_reg ist dann das Schreiben auf den Chip.

Lg
Bauteiltöter

von PittyJ (Gast)


Lesenswert?

Gerade bei Anfängern führen #define immer wieder zu Problemen. Deshalb 
sollten Anfänger niemals #define verwenden. (Ausnahme Include-Guards)

Das meiste lässt sich wunderbar über richtige Funktionen, enums und 
static const int  etc erschlagen.
Der Compiler optimiert sowieso noch.

Aber der Anfänger bekommt weniger Probleme mit Code, den er nicht 
versteht.

von MaWin (Gast)


Lesenswert?

Matt schrieb:
> Trotzdem ist sowas:
> DDRB |= (1<<DRB1)
> problemlos machbar.

Natürlich.

Da wird ja nicht geändert, WO sich das Datenrichtungsregister für Port B 
im Adressraum des Chips befindet, sondern sein Inhalt bekommt ein Bit 
mehr gesetzt.

Matt schrieb:
> #define W5500_MR   (uint16_t)0x0000
> #define W5500_MR_RST  (uint8_t)0x07
> W5500_MR |= (1 << W5500_MR_RST);

Hier wird ja nicht W5500_MR statt als 0x0000 nun zu 0x0080 umdefiniert, 
sondern IN Speicherplatz 0000 das Bit 0x0080 hinzugesetzt.

von S. R. (svenska)


Lesenswert?

PittyJ schrieb:
> (Ausnahme Include-Guards)

Ich habe angefangen, durchgängig "#pragma once" in Headern zu benutzen. 
Allerdings habe ich nur mit einem C++-Compiler zu tun, auch wenn ich 
"eher C" programmiere.

von PittyJ (Gast)


Lesenswert?

Bauteiltöter schrieb:
> Ich würde das so machen:
> [c]
> #define MYCHIP_REG_A 0x00
> #define MYCHIP_REG_B 0x01
>

Bei mir wäre das
enum
{
   MYCHIP_REG_A  = 0x00,
   MYCHIP_REG_B  = 0x01,
}

von Matt (Gast)


Lesenswert?

Vielen Dank svenska,

Jetzt geht es. Ich muss es nur noch verstehten, also

Ich habe jetzt einen Zeiger mit dem Namen W5500_MR und der Zeigt auf die 
Adresse 0x0000, aber was macht (uint16_t*)?

MfG
Matt

von Matt (Gast)


Lesenswert?

Ok wärend ich den Text geschireben habe sind schon soviele neu 
Nachrichten gekommen das der Text auch wieder als veraltet angesehen 
werden kann.
MfG
Matt

von Stefan F. (Gast)


Lesenswert?

Matt schrieb:
> aber was macht (uint16_t*)?

Das sagt dem Compiler, dass der Zeicher auf ein unsigned 16 bit integer 
zeigt. So weiß der Compiler wie groß die Speicherzelle bzw. das Register 
ist.

Aber wie mehrfach gesagt wurde geht das so nicht. Du kannst auf dem 
Mikrocontroller die Register eines anderen externen Chips nicht einfach 
so ansprechen, wie internes RAM/Register. Jeder Zugriff muss eine SPI 
Kommunikation auslösen. Das macht der Compiler nicht von alleine.

von S. R. (svenska)


Lesenswert?

Matt schrieb:
> Ich habe jetzt einen Zeiger mit dem Namen W5500_MR und der Zeigt auf die
> Adresse 0x0000, aber was macht (uint16_t*)?

(uint16_t*)0x0000 ist ein Cast und bedeutet, dass 0x0000 ein Zeiger auf 
die Adresse 0x0000 ist (und nicht der Wert 0x0000).

*(uint16_t*)0x0000 sagt, dass dieser Zeiger dereferenziert werden soll, 
also der Wert an der Adresse 0x0000 (und nicht die Adresse 0x0000).

Und das uint16_t in der Mitte besagt, dass der Wert an der Adresse 
0x0000 ein 16 Bit Unsigned Integer ist, gibt also sowohl den Datentypen 
des Wertes und damit auch den Adresstypen (Typ des Zeigers) an.

: Bearbeitet durch User
von Cyblord -. (cyblord)


Lesenswert?

Stefan ⛄ F. schrieb:
> Du bist längst nicht der erste mit dieser Idee.

Und du hast die Frage des TE gar nicht verstanden. Weil du so damit 
beschäftigt bist, einfach schnell irgendwas loszublubbern.

: Bearbeitet durch User
von lexi (Gast)


Lesenswert?

Einen ganz guten Überblick, wie man mit Structs auf memory-mapped 
Register zugreifen kann, bietet das hier:

https://blog.feabhas.com/2019/01/peripheral-register-access-using-c-structs-part-1/

von Stefan F. (Gast)


Lesenswert?

Cyblord -. schrieb:
> Und du hast die Frage des TE gar nicht verstanden. Weil du so damit
> beschäftigt bist, einfach schnell irgendwas loszublubbern.

Danke, dass du meine Augen geöffnet hast. Die Welt braucht mehr solcher 
Kommentare von dir.

von Matt (Gast)


Lesenswert?

Super also das mit dem Register habe ich verstanden und auch warum das 
über SPI nicht funktionieren kann. In der Adresse von dem 
Mikrocontroller halt irgendwas drinstehen kann und ich dann mit dem 
Inhalt von der Adresse über SPI in irgendein Register schreiben würde.

Wie wäre den eure Lösung um das am besten zu Ordnen?
Ist der Vorschlag von Bauteiletöter eine gute Variante oder ist das mal 
wieder so eine glaubensfrage?

MfG
Matt

Beitrag #6847651 wurde von einem Moderator gelöscht.
von Erich (Gast)


Lesenswert?

Stefan ⛄ F. schrieb im Beitrag #6847651:
> Linux Distributionen


Oje, schon wieder.
immer dieser Drang
einfach schnell irgendwas loszublubbern.

von Stefan F. (Gast)


Lesenswert?

Matt schrieb:
> Wie wäre den eure Lösung um das am besten zu Ordnen?

Schreibe dir Funktionen für die SPI Kommunikation mit sprechenden Namen. 
Meinetwegen eine Funktion für jedes Register und dann nochmal eine 
generische, die davon aufgerufen wird (mit der Adresse des Registers).

w5500_write_common_mode(uint16_t value);
ruft auf: w5500_write(0x0000, uint16_t value);

uint16_t w5500_read_common_mode();
ruft auf: w5500_read(0x0000);

Oder besser: Nutze die Bibliotheken der Chip-Herstellers. Ich kann mir 
nicht vorstellen, dass man dieses Rad neu erfinden muss.

von Erich (Gast)


Lesenswert?

Erich schrieb:
> Stefan ⛄ F. schrieb im Beitrag #6847651:
>> Linux Distributionen

Beitrag #6847651 wurde vom Autor gelöscht.

Ok, er hat's gemerkt.

>einfach schnell irgendwas loszublubbern.

ist nicht immer sinnvoll.

Etwas Zurückhaltung tut Not.

Erst denken.
Dann schreiben.

Gruss

von Stefan F. (Gast)


Lesenswert?

Erich schrieb:
> Ok, er hat's gemerkt.

Ist im falschen Thread gelandet. Das passiert öfters, ich vermute einen 
Bug in der Foren-Software.

von Matt (Gast)


Lesenswert?

Jup also das Rad muss ich auch nicht neu erfinden aber um die vorgänge 
wirklich zu verstehen ist es ganz praktisch mal eins selbstzubauen. Rad 
ist dafür nicht das beste Beispiel.
1
void W5500_Daten_Senden(W5500 *_w5500, uint16_t _adresse, uint8_t _controll, uint8_t *_p_daten, uint8_t _size_daten)
2
{
3
  W5500_Adresse_Umwandeln(_w5500, _adresse);
4
5
  uint8_t buffer[_size_daten + 3];
6
7
  buffer[0] = _w5500->sW5500_adresse.Adresse_High;
8
  buffer[1] = _w5500->sW5500_adresse.Adresse_Low;
9
  buffer[2] = _controll;
10
11
  for(uint16_t i = 3; i < _size_daten; i++)
12
  {
13
    buffer[i] = *_p_daten++;
14
  }
15
16
  HAL_SPI_Transmit(&hspi1, buffer, _size_daten + 3, SPI_Time_Out);
17
}

Eine funktion dafür, habe ich mir auch schon gecshrieben.

MfG
Matt

von Gerald K. (geku)


Lesenswert?

Vorsicht bei Register mit Spezialfunktionen.

Z.B.
- UART Sende- und Empfangsregister sind meist auf der gleichen  Adresse
- manche Registerbits dienen zum Quittieren bzw. Rückstellen und können 
nur geschrieben werden. Eine '1' löst diesen Vorgang aus. Wird das 
komplette Register, zwecks Manipulation einzelner Bits gelesen und 
zurückgeschrieben, dann kann es sein, dass diese Spezialfunktionen 
unbeabsichtigt mit ausgelöst werden.

von Klaus W. (mfgkw)


Lesenswert?

Namen, die mit _ beginnen, sollte man übrigens nicht unnötig für eigenen 
Kram verwenden.
Die werden nämlich für viele interne Geschichten in Libs und Headern 
genutzt, um Kollisionen mit den Namen des Anwendungsprogramms zu 
vermeiden.

von Gerald K. (geku)


Lesenswert?

Gerald K. schrieb:
> Eine Möglichkeit ist die Register in einer Struktur zu definieren. Wo in
> der Struktur jedes Register als Bitfield definiert werden kann.

Beispiel: 
http://www.raviyp.com/bitfields-in-c-for-accessing-microcontroller-registers/

von A. S. (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Deswegen lässt es sich damit nicht ordentlich umsetzen, ganz egal was
> für ausgefuchste Strukturen du dir ausdenkst. Irgendwo ist immer ein
> Haken.

Der Haken ist Plattformabhängigkeit. Ein Makel, der bei einer 
Plattform akzeptabel ist.

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.