Werden Zeiger in AVR anders behandelt als beispielsweise in x86?

Mikrocontroller: ATtiny13

IDE: Atmel Studio

Ich versuche, eine Hello World-Anwendung zu schreiben, indem ich High auf Port PB4 schreibe.

Das funktioniert gut:

int main(void)
{
    DDRB = 0x10;
    PORTB = 0x10;

    while(1)
    {
    }
}

Betrachtet man die Definition von DDRB und PORTB, zeigen sie wie erwartet auf 0x17 und 0x18.

Das funktioniert aber nicht:

int main(void)
{
    char *dir = (void *)0x17;
    char *port = (void *)0x18;

    *dir = (unsigned int)0x10;
    *port = (unsigned int)0x10;

    while(1)
    {
    }
}

Ist mein Code falsch oder muss ich etwas anderes tun, um Zeiger zu verwenden?

Warum sollten Sie Zeiger für etwas so Einfaches verwenden?
Irgendwo muss ich anfangen - zum ersten Mal eine AVR-MCU programmieren.
Der Vergleich der Demontage von beiden wäre eine gute Übung.
Die unsigned intBesetzungen ergeben keinen Sinn; Die Datenrichtungs- und Portregister sind nur 8 Bit breit. Es ist ziemlich unwahrscheinlich, dass das Ihr Problem ist, aber versuchen Sie es mit dem Casting uint8_t.
@sherrellbc Ich habe verschiedene Arten von Casting und kein Casting ausprobiert, aber es hat keinen Unterschied gemacht. 0x10ist 8 Bit breit, oder?
Aus diesem Grund habe ich gesagt, dass dies wahrscheinlich nicht das Problem ist, da 0x10 in die letzten 8 Bits der größeren Bitlänge des unsigned int passen würde. Probieren Sie etwas expliziteres aus und machen Sie uint16_t* dir = DDRBund dann *dir = (uint8_t) 0x10.

Antworten (1)

Zeiger sind Zeiger. Das sind sie. Sie werden überhaupt nicht anders behandelt (wie könnte man sie anders behandeln?)

Die Hauptunterschiede zwischen X86 und AVR sind:

  • AVR ist 8 Bit, X86 ist 32 Bit (oder 64 Bit für x86_64), also haben Zeiger eine andere Größe.
  • Der AVR ist eine modifizierte Harvard-Architektur, daher gibt es mehr als einen Adressraum, sodass Sie sicherstellen müssen, dass Sie auf den richtigen Adressraum verweisen.

Außerdem macht Ihr Code keinen Sinn:

char *dir = (void *)0x17;

Einem Zeichen * eine Leere * zuweisen?

error: invalid conversion from ‘void*’ to ‘char*’

Ich bin im Moment nicht zum Kompilieren des ATTiny13 eingerichtet, also sind diese Nummern alle für den ATMega328p:

Der Zugriff auf DDRB und PORTB führt zu dieser Assembly:

12c:    80 e1           ldi r24, 0x10   ; 16  
12e:    84 b9           out 0x04, r24   ; 4
130:    85 b9           out 0x05, r24   ; 5

Der Zugriff auf einen Zeiger auf einen Speicherplatz führt zu dieser Assembly:

12c:    80 e1           ldi r24, 0x10   ; 16
12e:    80 93 17 00     sts 0x0017, r24
132:    80 93 18 00     sts 0x0018, r24

Wie Sie sehen können, sind DDRB und PORTB keine normalen Variablen. DDRB ist definiert als:

#define DDRB _SFR_IO8(0x04)

und PORTB als:

#define PORTB _SFR_IO8(0x05)

_SFR_IO8() ist ein Makro:

#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)

und _MMIO_BYTE ist:

#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))

__SFR_OFFSET kann je nach Chip entweder 0 oder 0x20 sein (normalerweise 0x20).

Das muss also bedeuten, dass die Adressen von DDRB und PORTB größer als 0x20 sein müssen.

Beim 328P-Chip ist DDRB 0x04 und PORTB 0x05. Also, Zugriff als 0x24 und 0x25, mit den richtigen Datentypen also:

volatile uint8_t *dir = (volatile uint8_t *)0x24;
volatile uint8_t *port = (volatile uint8_t *)0x25;

*dir = 0x10;
*port = 0x10;

ergibt diese Baugruppe:

12c:    80 e1           ldi r24, 0x10   ; 16
12e:    84 b9           out 0x04, r24   ; 4
130:    85 b9           out 0x05, r24   ; 5

Ähnlich aussehend? Der Compiler hat den 0x20-Offset erkannt, erkannt, dass es sich um SFRs handelt, und die richtigen outAnweisungen ohne den 0x20-Offset kompiliert.

Der Zugriff auf Ihre Portadressen + 0x20 funktioniert also möglicherweise für ATTiny13.

Wenn man sich nur den ATTiny25 ansieht, DDRB = 0x10ergibt sich Folgendes:

out 0x17, r24

und der Zugriff auf einen Zeiger an der Adresse 0x37 führt zu:

out 0x17, r24

Das sieht also so aus, als wäre es wahrscheinlich (fügen Sie 0x20 zu Ihrer Zeigeradresse hinzu).

Was meinst du damit, dass es mehr als einen Adressraum gibt? Ich habe mir die Dokumentation für die MCU angesehen und sie zeigt eine Speicherzuordnung, von der aus 0x0auf 0x9Fdie Register, E / A und SRAM zugegriffen wird. Was sind die anderen Adressräume?
Die Harvard-Architektur definiert mindestens 2 Adressräume – Programm (Flash) und Daten (SRAM). Für den Zugriff auf den Programmadressraum gibt es spezielle Funktionen.
@ tgun926, ich bin mir ziemlich sicher, dass die E / A-Register Teil des SRAM sind, aber dann haben Sie auch EEPROM- und FLASH-Speicherplätze (Programmspeicher - PGM). Beispielsweise müssen Sie explizit eine EEPROM-Variable deklarieren, um in diesem Bereich darauf zugreifen zu können, ansonsten sucht der Compiler im "normalen?" auf dem Laufzeitstapel zugewiesener Speicherplatz.
Ich bin mir immer noch nicht sicher, warum die Zeiger volatilein diesem Fall Qualifizierer erfordern. Warum sollten sie sonst optimiert werden, wenn Sie eindeutig einen expliziten Schreibvorgang durchführen und dies nicht einer extern ausgelösten Funktionalität überlassen haben (die daher dem Compiler so erscheint, als würde sie nie verwendet), wie eine ISR?
Sie können optimiert werden, da der Compiler nicht weiß, dass sie gelesen werden. Wenn eine Variable gesetzt, aber nie gelesen wird, kann sie den Schreibvorgang verwerfen, da sie nirgendwo verwendet wird. Indem es flüchtig ist, teilt es dem Compiler mit, dass es an anderer Stelle gelesen wird (in diesem Fall in der Hardware), um das Schreiben an Ort und Stelle zu belassen.
0x17und 0x18stammten aus dem Datenblatt und der Eingabe für das Makro in VS. Ich hätte mir genauer ansehen sollen, was das Makro macht. Was ich nicht verstehe, ist, wie der Compiler erkennen kann, dass es sich um SFRs handelt (was ist ein SFR?) Und den Offset loswerden kann?
SFR = Spezialfunktionsregister. Es erkennt sie an der Adresse. Keine Ahnung wie, aber es geht. Sie sollten die Datei ioxxxx.h für den von Ihnen verwendeten Chip überprüfen. Es enthält alle Definitionen für diese Register.
Sogar auf dem im PC verwendeten 8x86 (und dem 8080 davor) gab es separate Anweisungen für den Zugriff auf E/A-Raum und Speicher. Einige E/A-Geräte wie Grafikkarten verwenden zusätzlich zu einigen E/A-Adressräumen einen Teil des Speicheradressraums, aber auf dem 8x86 gibt es eine Möglichkeit, einen Zeiger auf zB das LPT1-Steuerregister zu definieren. Es hat eine Adresse (0x378), aber das Schreiben von 0x34 in einen typischen C-Dialekt würde erfordern outportb(0x378, 0x34);.
Dasselbe beim Z80 - Spezial- inund outOpcodes für den Zugriff auf einen separaten IO-Bus (es gibt 2 Signale, /MREQ und /IORQ, um die verschiedenen Adressräume auszuwählen).
"Einem Zeichen * eine Leere * zuweisen?" Warum nicht? " error: invalid conversion from ‘void’ to ‘char*’" Hier geht es um ein void, nicht um ein void *.
Entschuldigung, Tippfehler da. Immer noch ein Fehler: error: invalid conversion from 'void*' to 'char*'
Eigentlich nein, es ist kein Tippfehler - es ist ein Markdown, der den *