Forum: Mikrocontroller und Digitale Elektronik Ansteuerung des MCP2515 (SPI)


von uCStarter (Gast)


Lesenswert?

Hallo zusammen,

ich habe zwar schon etwas in C programmiert, aber das Thema hardwarenahe 
bzw. uC-Programmierung ist ziemlich neu für mich.

Ich habe den folgenden Aufbau (Arduino Uno + CANdiy-Shield): 
https://github.com/watterott/CANdiy-Shield/blob/master/pcb/CANdiy-Shield_v13.pdf?raw=true

Das CANdiy-Shield beinhaltet hauptsächlich einen MCP2515 CAN-Controller 
und MCP2551 CAN-Transceiver. Die Verbindung zum ATmega328 ist im oberen 
Link ersichtlicht.
MCP2515-Datenblatt: 
http://ww1.microchip.com/downloads/en/DeviceDoc/21801G.pdf
MCP2551-Datenblatt: 
http://ww1.microchip.com/downloads/en/DeviceDoc/21667f.pdf
ATmega328-Datenblatt: 
http://www.atmel.com/Images/Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Summary.pdf

Ich konnte über das Atmel Studio 6.1 ein simples Programm auf den 
Atmega328 übertragen, womit LEDs angesteuert wurden. Als nächstes möchte 
ich eine CAN-Botschaft in einem CAN-Bus absetzen und habe dazu folgendes 
Tutorial gefunden: 
http://www.kreatives-chaos.com/artikel/ansteuerung-eines-mcp2515#receive

Jetzt kann ich aber schon die Initialisierung des SPI-Interface nicht 
ganz nachvollziehen. Hier der C-Code:

void spi_init(void)
{
    // Aktivieren der Pins für das SPI Interface
    DDR_SPI  |= (1<<P_SCK)|(1<<P_MOSI);
    PORT_SPI &= ~((1<<P_SCK)|(1<<P_MOSI)|(1<<P_MISO));

    DDR_CS   |= (1<<P_CS);
    PORT_CS  |= (1<<P_CS);

    // Aktivieren des SPI Master Interfaces, fosc = fclk / 2
    SPCR = (1<<SPE)|(1<<MSTR);
    SPSR = (1<<SPI2X);
}

Ich könnte natürlich das Ganze Tutorial kopieren, aber ich würde diesen 
Code auch gerne nachvollziehen. DDR und PORT dient dazu die 
Datenrichtung bzw. den Ausgang zu definieren, auch Bitweise UND/ODER 
kann ich nachvollziehen, aber wozu das BIT KOMPLEMENT und wozu wird z.B. 
P_SCK/P_MOSI/P_CS um 1 digit nach links (<<) verschoben?

Wenn im Laufe des Tutorials weitere Unklarheiten ergeben, würde ich 
gerne weiterhin diesen Thread nutzen. Danke.

LG

von H.Joachim S. (crazyhorse)


Lesenswert?

irgendwo vorher steht noch was in der Art:

#define DDR_SPI DDRB
#define P_SCK 4  (keine Ahnung, welcher Wert da tatsächlich steht!)
#define P_MOSI 3

Und das hier heisst dann:
DDR_SPI  |= (1<<P_SCK)|(1<<P_MOSI);
Am gewählten Port (hier B) werden die Ausgänge 3 und 4 als Ausgang 
gesetzt.

Man hätte auch schreiben können:
DDRB |= 0x18;

Damit hätte aber niemand was anfangen können. Und die Sache auf einen 
anderen Port/andere Pins umzuschreiben wäre auch nicht so einfach bzw. 
fehlerträchtig.

von uCStarter (Gast)


Lesenswert?

#define DDR_CS      DDRB
#define PORT_CS     PORTB
#define P_CS        2

#define DDR_SPI     DDRB
#define PORT_SPI    PORTB
#define P_MISO      4
#define P_MOSI      3
#define P_SCK       5

Ich verstehe es aber immer noch konkret. Nehmen wir mal folgende Zeile:

DDR_SPI  |= (1<<P_SCK)|(1<<P_MOSI);

Was ist der Vorteil von "1<<" in diesem Fall? Lässt sich der Code 
dadurch einfacher (mit weniger Änderungen) auf verschiedenen AVR-uC 
integrieren? Lesbarer finde ich es zumindest für Anfänger nämlich nicht.

von H.Joachim S. (crazyhorse)


Lesenswert?

Ja, es ist einfacher auf andere übertragbarer. Und ja, es ist einfacher 
lesbar. Ich seh sofort, dass du die Pins SCK und MOSI auf Ausgang 
schalten willst, unabhängig davon, auf welchen Ports und Pins die 
tatsächlich liegen (was für die Logik des Programms auch egal ist)

Und wenn nun SCK auf Port5 liegt, ergibt 1<<5 0b00100000.
D.h. egal wo SCK tatsächlich liegt, das Programm arbeitet korrekt, wenn 
du bei define den tatsächlichen physikalischen Port angibst, je nach 
Prozessor.
Es wird unabhängig vom Chip und jeder versteht es.

von uCStarter (Gast)


Lesenswert?

H.Joachim Seifert schrieb:0
> Und wenn nun SCK auf Port5 liegt, ergibt 1<<5 0b00100000.

Es wird also um eine Stelle verschoben, da die Kanäle des jeweiligen 
Ports mit 0 beginnend gezählt werden und Port 5 damit tatsächlich auf 
Position 6 ist.
Welche Bedeutung hat die Bitwise-OR-Verknüpfung zwischen den beiden 
Ports in der Zeile "DDR_SPI  |= (1<<P_SCK)|(1<<P_MOSI);" ? Dadurch 
erkennt man dann letztlich doch gar nicht mehr, dass es Port 5 und 3 
sein sollen.

Und dann kommt noch |= dazu, oder heben die sich auf?

von H.Joachim S. (crazyhorse)


Lesenswert?

DDR_SPI  |= ()
heisst ausgeschrieben:
DDR_SPI=DDR_SPI | ()

Programmierer sind eben schreibfaul, Quelle und Ziel sind identisch, 
also brauch man sie nicht zweimal schreiben.

Sinn dieses ORs ist, die anderen Bits des Ports nicht anzufassen, 
sondern nur die gewünschten (SCK und MOSI) auf 1 zu setzen. Also egal, 
ob die anderen auf 1 oder 0 standen - die bleiben so, wie sie waren.
Es gab schon ganz viele Fragen hier in der Art:

DDRB=0x20;   //SCK als Output
DDRB=0x08;   //Mosi als Output

Mit der zweiten Anweisung wird die erste hinfällig, ohne dass man das 
gleich sieht. Das arme Register bzw. der Compiler weiss natürlich nicht, 
dass das anders gedacht war :-)

DDRB=DDRB | 0x20; //SCK als Output
DDRB=DDRB | 0x08; //Mosi als Output

so funktioniert das wie gewollt.
Und jetzt noch die magic numbers weg (niemand, auch du selbst weisst 
nach kurzer Zeit nicht mehr, was gemeint war, falls kein Kommentar da.

Richtig witzig wirds, wenn du das Bit mehrfach im Programm brauchst und 
es auf eine andere Position wandert (anderer Prozessor z.B), dann musst 
du alle Stellen im Programm suchen, wo das gebraucht wird. Bei grösseren 
Programmen eine Fehlerquelle 1.Grades.

Also per #define, und schon wird es überall richtig ersetzt.


#define P_MOSI      3
#define P_SCK       5

(1<<P_MOSI) ergibt 1<<3 = 0b00001000
(1<<SCK)  ergibt 1<<5 = 0b00100000

Und (1<<P_MOSI) | (1<<SCK) ergibt folgerichtig 0b00101000. D.h. genau 
diese 2 Bits werden auf 1 gesetzt. Und das ist genau das, was der ganze 
Kram bewirken soll und es auch tut.

von Kostjan K. (Gast)


Lesenswert?

Danke. Jetzt hat es auch bei mir Klick gemacht.

Ich habe nun die relevanten Teile des Tutorials in ein neues Projekt in 
Atmel Studio 6 transferiert. Mir geht es zunächst nur um das Senden von 
Dummy-Botschaften. Jedoch erhalte ich nach Auswahl von Build Solution 
eine Fehlermeldung.

Tutorial: 
http://www.kreatives-chaos.com/artikel/ansteuerung-eines-mcp2515
Fehlermeldung: Error   unknown type name 'CANMessage'  CANdiy.c  169  23
in der Funktion:
1
void can_send_message(CANMessage *p_message)

CANMessage ist eine struct aus der main, dass die einzelnen teile der 
CAN-Botschaft enthält:
1
int main(void)
2
{
3
  mpc2515_init();
4
  typedef struct{
5
    uint16_t  id;
6
    uint8_t   rtr;
7
    uint8_t   length;
8
    uint8_t   data[8];
9
} CANMessage;
10
11
// Neue Nachricht erzeugen
12
CANMessage message;
13
14
// Daten eintragen
15
message.id = 0x0123;
16
message.rtr = 0;
17
message.length = 2;
18
message.data[0] = 0x04;
19
message.data[1] = 0xf3;
20
21
// Nachricht verschicken
22
    while(1)
23
    {
24
        can_send_message(&message);
25
    _delay_ms(5);
26
    }
27
}

Muss ich die einzelnen Variablen in struct VOR der function 
can_send_message() deklarieren? Oder muss in den () der Funktion 
can_send_message jede einzelne Variable von struct nacheinander 
deklariert werden? Über einen Tipp würde ich mich freuen.

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.