Forum: Projekte & Code CAN Datentransfer auf Basis von kreatives-chaos.com


von Hugo P. (portisch)


Angehängte Dateien:

Lesenswert?

Hallo,

ich arbeite nun schon etwas länger daran wie am besten mehr als 8 Byte 
Nutzdaten per CAN Bus überträgt.

Da ich nichts gefunden habe, habe ich mich an kreatives-chaos.com 
orientiert. Besser gesagt an deren CAN Bootloader:
http://www.kreatives-chaos.com/artikel/can-bootloader

Dazu vielen Dank an die Macher des Bootloaders und der can-lib!!

Ich habe mich für dieses Protokoll entschieden, da ich Slaves im 
Netzwerk über den CAN Bus updaten will. Somit kann das Protokoll für den 
Normalbetrieb und den Flashvorgang verwendet werden.

Im Normalbetrieb wird das Protokoll "abgespeckt" verwendet. D.h. z.B. 
der Message_Counter wird nicht verwendet. Auch fehlen dann AKN/NAKN da 
es sich bei mir im System um nicht so wichtige Daten handelt.

Der Source ist noch nicht 100%'ig und soll als Basis für andere dienen 
die mehr als 8 Bytes übertragen wollen.
Ich selber übertrage im Normalfall ca. 10-15 Bytes. Es sind Daten wie 
z.b. von einem DS2438 (Spannung & Temp) oder DS18S20 (Temp).
D.h. es werden die großen Distanzen mit CAN Bus überbrückt. Die OneWire 
Kabel sind dann eher kurz gehalten.

Flashen mache ich über V-USB. Ich sende 32 Byte Pakete an den 
CAN-Master. Dieser zerlegt sie in der SendCANMessage in die einzelnen 
CAN Nachrichten und leitet sie an die angegebene Slave NodeID.

Um einen Atmega168 mit MCP2515 ein ~12kB Flash zu übertragen brauche ich 
dann bei 10kbps ca. 72 Sekunden.

Wichtig ist noch bei can-lin:
In der "mcp2515_send_message" sollten am besten 2 TX Buffer deakiviert 
werden. Somit steht nur ein TX Buffer zur Verfügung und es entsteht 
Automatisch ein "Gänsemarsch" der CAN Nachrichten. Ansonsten dreht der 
MCP2515 die Nachrichten durcheinander und der Message_Counter kommt aus 
dem Takt.

Viel Spass damit!

Beispiel "WHO_IS_ONLINE" Request vom Master ans Netzwerk:
1
        can_transfer.Board_Identifier = CAN_NODE_NR;
2
        can_transfer.CAN_MessageID = CAN_WHO_IS_ONLINE;
3
        can_transfer.T_OR_C = WHO_IS_ONLINE | REQUEST;        
4
        SendCANMessage(&can_transfer, NULL, NULL, 0);

Einen bestimmten Slave reseten:
1
        can_transfer.Board_Identifier = slave_nodeid;
2
        can_transfer.CAN_MessageID = CAN_RESET_SLAVE;
3
        can_transfer.T_OR_C = RESET_SLAVE | REQUEST;        
4
        SendCANMessage(&can_transfer, NULL, NULL, 0);    
5
      
6
        ret = WaitforCANResponse(&can_transfer, NULL, NULL, NULL);

von canni (Gast)


Lesenswert?

Was ist denn ein >> CAN-MAster << ?

von Hugo P. (portisch)


Lesenswert?

Der CAN Master ist der mit der NodeID 0x00.
Slaves sind mit der NodeID 0x01-0xFF.

Ich habe nur einen Master der die Schnittstelle vom CAN Bus System nach 
"Aussen" ist. Die Slaves schicken ihre Daten an den Master, dieser gibt 
sie nach Aussen (z.B. über RS232, V-USB,...) ab.

von Leo (Gast)


Lesenswert?

Hallo Hugo,

ich habe mir mal deinen Code angeschaut. Wo verwendest du diese Funktion 
"WaitforCANResponse" ? Könntest du eine Beispielapplikation für den 
Master hier reinposten ? Das wäre super. Danke.

von Hugo P. (portisch)


Angehängte Dateien:

Lesenswert?

Die "WaitforCANResponse" ist um auf ein AKN/NAKN zu warten.
Maximal 500ms. Er wartet auf eine Nachricht von der angegeben Node ID.
Andere Nachrichten die sich dazwischen schumeln werden in den Buffer 
geladen.

Auch habe ich noch eine neue Funktion hinzugefügt um zu überprüfen ob 
gerade ein Transfer aktiv ist: "CANTranserActive"

Ich habe im Master im Timer0 z.B. eine 1-Wire Devices Abfrage. Diese 
sollte aber nur durchgeführt werden wenn gerade kein CAN Transfer aktiv 
ist um Bufferüberlappungen zu verhindern.

Auch noch kurz zur Erklärung:
In der SendCANMessage wird die Message ID mit der Node Nummer "ver-ort".

D.h.
Master -> Slave:
CAN_NODE_NR = 0x00
aus 0x300 | CAN_NODE_NR = 0x300

Slave -> Master:
CAN_NODE_NR = 0x11
aus 0x300 | CAN_NODE_NR = 0x311

Das bedeutet, dass man durch die CAN_NODE_NR die Priorität der Slaves 
steuern kann. Der mit der CAN_NODE_NR = 0x01 hat auf dem CAN Bus 
gegenüber den Slave mit der CAN_NODE_NR = 0x02 Vorrang.

Der Filter ist bei den Slaves so eingestellt, dass nur Messages mit 
0xX00 und der WHO_IS_ONLINE empfangen werden. Also nur Nachrichten vom 
Master.
Die Slaves können sich nichts untereinander schicken. Das kann man aber 
durch andere Filtereinstellungen ändern.
Der Master filtert keine Nachrichten und bekommt alle Message IDs.

Kurze Beispiele:
Slave:
1
CAN_HEADER can_transfer;
2
uint8_t can_message_count;
3
4
// filter for:
5
// receive only messages with matching node id 0xX00 (Messages from CAN Master)
6
// receive onyl message CAN_WHO_IS_ONLINE for response
7
prog_uint8_t can_filter[] = {
8
  // Group 0
9
  MCP2515_FILTER(0x000),        // Filter 0
10
  MCP2515_FILTER(0x000),        // Filter 1
11
    
12
  // Group 1
13
  MCP2515_FILTER(CAN_WHO_IS_ONLINE),  // Filter 2
14
  MCP2515_FILTER(CAN_WHO_IS_ONLINE),  // Filter 3
15
  MCP2515_FILTER(CAN_WHO_IS_ONLINE),  // Filter 4
16
  MCP2515_FILTER(CAN_WHO_IS_ONLINE),  // Filter 5
17
    
18
  MCP2515_FILTER(0x0FF),        // Mask 0 (for group 0)
19
  MCP2515_FILTER(0x7FF),        // Mask 1 (for group 1)
20
};
21
22
int main(void)
23
{
24
  uint8_t buffer[64];
25
  uint8_t len;
26
  
27
  cli();
28
  
29
  /* Even if you don't use the watchdog, turn it off here. On newer devices,
30
   * the status of the watchdog (on/off, period) is PRESERVED OVER RESET!
31
   */
32
  wdt_disable();                            // first disable watchdog
33
    
34
  wdt_enable(WDTO_4S);                        // enable again watchdog
35
  sei();                                // global interrupt enable
36
  
37
  // init can data:
38
  InitCANBus(BITRATE_10_KBPS, can_filter);    
39
    
40
  // send dummy message to signal to be online:
41
  can_transfer.Board_Identifier = CAN_NODE_NR;
42
  can_transfer.CAN_MessageID = CAN_WHO_IS_ONLINE;
43
  can_transfer.T_OR_C = WHO_IS_ONLINE | REQUEST;        
44
  SendCANMessage(&can_transfer, NULL, NULL, 0);
45
    
46
    while(1)
47
    {
48
    wdt_reset();
49
    
50
    // Check if a new message was received
51
    if (ReceiveCANMessage(&can_transfer, NULL, &buffer[0], &len))
52
    {  
53
              
54
      switch (can_transfer.T_OR_C & COMMAND_MASK)
55
      {
56
        // Online Request was received, send response
57
        case WHO_IS_ONLINE:  
58
          can_transfer.Board_Identifier = CAN_NODE_NR;
59
          can_transfer.CAN_MessageID = CAN_WHO_IS_ONLINE;
60
          can_transfer.T_OR_C = WHO_IS_ONLINE | REQUEST;        
61
          SendCANMessage(&can_transfer, NULL, NULL, 0);      
62
        break;            
63
        
64
        // Reset Slave was received, make watchdog reset    
65
        case RESET_SLAVE:          
66
          if (can_transfer.Board_Identifier == CAN_NODE_NR)
67
          {
68
            can_transfer.Board_Identifier = CAN_NODE_NR;
69
            can_transfer.CAN_MessageID = CAN_RESET_SLAVE;
70
            can_transfer.T_OR_C = RESET_SLAVE | SUCCESSFULL_RESPONSE;        
71
            SendCANMessage(&can_transfer, NULL, NULL, 0);            
72
73
            _delay_ms(50);
74
            for (;;);
75
          }            
76
        break;
77
      }      
78
            
79
    }
80
    }
81
}

Master:
1
CAN_HEADER can_transfer;
2
uint8_t can_message_count;
3
4
// filter for:
5
// receive all messages
6
prog_uint8_t can_filter[] = {
7
  // Group 0
8
  MCP2515_FILTER(CAN_NODE_NR),    // Filter 0
9
  MCP2515_FILTER(CAN_NODE_NR),    // Filter 1
10
    
11
  // Group 1
12
  MCP2515_FILTER(CAN_UPDATE_SLAVE_RESPONSE),  // Filter 2
13
  MCP2515_FILTER(CAN_UPDATE_SLAVE_RESPONSE),  // Filter 3
14
  MCP2515_FILTER(CAN_UPDATE_SLAVE_RESPONSE),  // Filter 4
15
  MCP2515_FILTER(CAN_UPDATE_SLAVE_RESPONSE),  // Filter 5
16
    
17
  MCP2515_FILTER(0),        // Mask 0 (for group 0)
18
  MCP2515_FILTER(0),        // Mask 1 (for group 1)
19
};
20
21
int main(void)
22
{
23
  uint8_t buffer[64];
24
  uint8_t len;
25
    
26
  cli();
27
  
28
  /* Even if you don't use the watchdog, turn it off here. On newer devices,
29
   * the status of the watchdog (on/off, period) is PRESERVED OVER RESET!
30
   */
31
  wdt_disable();                            // first disable watchdog
32
  
33
  wdt_enable(WDTO_4S);                        // enable again watchdog
34
  sei();                                // global interrupt enable  
35
  
36
  InitCANBus(BITRATE_10_KBPS, can_filter);    
37
  
38
  uart_put(WHO_IS_ONLINE, CAN_NODE_NR, NULL, 0);
39
40
    while(1)
41
    {
42
    wdt_reset();
43
    
44
      if (ReceiveCANMessage(&can_transfer, NULL, &buffer[0], &len))
45
      {      
46
        // CAN Message received, send it to rs232    
47
        uart_put(can_transfer.T_OR_C & COMMAND_MASK, can_transfer.Board_Identifier, &buffer[0], len);
48
        
49
        // also send it to USB host
50
        SendUSBInterrupt(NewDataAvailable, can_transfer.Board_Identifier,
51
          can_transfer.T_OR_C & COMMAND_MASK, &buffer[0], len);            
52
      }
53
    }
54
}

Die Steuerung der Slaves mache ich über V-USB. Kann aber per RS232 
(uart_getc) auch gemacht werden. Die Befehle IDENTIFY, START_APP, 
SET_ADDRESS und DATA werden nur für den echten CAN Bootloader für das 
Flashupdate benötigt.

Im "receive_buffer" stehen die USB Daten vom Host:
receive_buffer[0]: Report ID
receive_buffer[1]: Command
receive_buffer[2]: Command Parameter

Master -> Slave Handling:
1
/* usbFunctionWrite() is called when the host sends a chunk of data to the
2
 * device. For more information see the documentation in usbdrv/usbdrv.h.
3
 */
4
uchar usbFunctionWrite(uchar *data, uchar len)
5
{
6
  uint8_t ret;
7
  uint8_t rlen;
8
  uint8_t buffer[32];
9
  
10
    if(len > bytesRemaining)
11
        len = bytesRemaining;    
12
        
13
    bytesRemaining -= len;
14
  
15
  // store received data
16
  if (len > 0)
17
    memcpy(&receive_buffer[receive_data_len], &data[0], len);
18
      
19
  receive_data_len += len;  
20
  
21
    if(bytesRemaining == 0)
22
  {
23
    if ( DoWriteReport == USB_Command )
24
    {
25
      switch (receive_buffer[1])
26
      {
27
      // who is online in the network
28
      case WHO_IS_ONLINE:
29
        can_transfer.Board_Identifier = CAN_NODE_NR;
30
        can_transfer.CAN_MessageID = CAN_WHO_IS_ONLINE;
31
        can_transfer.T_OR_C = WHO_IS_ONLINE | REQUEST;        
32
        SendCANMessage(&can_transfer, NULL, NULL, 0);    
33
        break;
34
      // reset a specified slave node
35
      case RESET_SLAVE:
36
        can_transfer.Board_Identifier = receive_buffer[2];
37
        can_transfer.CAN_MessageID = CAN_RESET_SLAVE;
38
        can_transfer.T_OR_C = RESET_SLAVE | REQUEST;        
39
        SendCANMessage(&can_transfer, NULL, NULL, 0);    
40
      
41
        ret = WaitforCANResponse(&can_transfer, NULL, NULL, NULL);
42
        // send back response to the host
43
        SendUSBInterrupt(NewDataAvailable, receive_buffer[2], RESET_SLAVE | ret, NULL, 0);
44
        break;    
45
      // send identify to a specified slave node
46
      case IDENTIFY:
47
        // first disable onewire timer before start flashing
48
        // disable timer0:
49
        #ifdef ONEWIRE                    
50
        TCCR0B = 0;
51
        #endif
52
        
53
        can_transfer.Board_Identifier = receive_buffer[2];
54
        can_transfer.CAN_MessageID = CAN_UPDATE_SLAVE_FLASH;
55
        can_transfer.T_OR_C = IDENTIFY | REQUEST;  
56
      
57
        can_message_count = -1;      
58
        SendCANMessage(&can_transfer, &can_message_count, NULL, 0);      
59
  
60
        ret = WaitforCANResponse(&can_transfer, NULL, &buffer[0], &rlen);
61
        // send back response to the host
62
        SendUSBInterrupt(NewDataAvailable, receive_buffer[2], IDENTIFY | ret, &buffer[0], rlen);              
63
        break;    
64
      // send START_APP to a specified slave node
65
      case START_APP:
66
        can_transfer.Board_Identifier = receive_buffer[2];
67
        can_transfer.CAN_MessageID = CAN_UPDATE_SLAVE_FLASH;
68
        can_transfer.T_OR_C = START_APP | REQUEST;  
69
          
70
        SendCANMessage(&can_transfer, &can_message_count, NULL, 0);      
71
  
72
        ret = WaitforCANResponse(&can_transfer, NULL, &buffer[0], &rlen);
73
        // send back response to the host
74
        SendUSBInterrupt(NewDataAvailable, receive_buffer[2], START_APP | ret, &buffer[0], rlen);
75
        
76
        // flashing of slave is finished
77
        // restart timer if onewire is used
78
        #ifdef ONEWIRE
79
        TCCR0B = (1<<CS01);
80
        #endif                      
81
        break;              
82
      }    
83
    }
84
    else
85
    if ( DoWriteReport == USB_Bootloader_Data )
86
    {
87
      switch (receive_buffer[1])
88
      {
89
      // set address for slave
90
      case SET_ADDRESS:
91
        can_transfer.Board_Identifier = receive_buffer[2];
92
        can_transfer.CAN_MessageID = CAN_UPDATE_SLAVE_FLASH;
93
        can_transfer.T_OR_C = SET_ADDRESS | REQUEST;        
94
        SendCANMessage(&can_transfer, &can_message_count, &receive_buffer[3], 4);  
95
              
96
        ret = WaitforCANResponse(&can_transfer, NULL, &buffer[0], &rlen);
97
        // send back response to the host
98
        SendUSBInterrupt(NewDataAvailable, receive_buffer[2], SET_ADDRESS | ret, &buffer[0], rlen);    
99
      break;
100
    
101
      // send data to slave
102
      case DATA:
103
        can_transfer.Board_Identifier = receive_buffer[2];
104
        can_transfer.CAN_MessageID = CAN_UPDATE_SLAVE_FLASH;
105
        can_transfer.T_OR_C = DATA | REQUEST;        
106
        SendCANMessage(&can_transfer, &can_message_count, &receive_buffer[3], 32);  
107
              
108
        ret = WaitforCANResponse(&can_transfer, NULL, &buffer[0], &rlen);
109
        // send back response to the host
110
        SendUSBInterrupt(NewDataAvailable, receive_buffer[2], DATA | ret, &buffer[0], rlen);    
111
      break;    
112
      }            
113
    }          
114
115
    receive_data_len = 0;
116
        return 1;               /* end of transfer */
117
  }
118
  
119
    return bytesRemaining == 0;                               /* return 1 if this was the last chunk */
120
}

Man beachte auch, dass maximal 4 * 0x0F an Datalength möglich ist.
Also maximal 64 Bytes die mit einer SendCANMessage möglich sind.

Hier noch ein Beispiel wie ich im Slave im Timer0 überprüfe ob ein neuer 
IR-Code empfangen wurde. Die CAN Message ID IRMP_DECODED = 0x300.
1
void check_for_new_irmp_data(void)
2
{
3
  if (irmp_get_data (&irmp_data))    
4
  {
5
    if (!(irmp_data.flags & IRMP_FLAG_REPETITION))        // first check if the IR command is repeated
6
    {
7
      RepeatCounter = 0;                    // new command received, reset RepeatCounter
8
    }                                  
9
    else
10
    {  
11
      RepeatCounter++;                    // still the same command, inc RepeatCounter
12
    }    
13
      
14
    if ((RepeatCounter == 0) | ( RepeatCounter >= MinRepeats))  // only send interrupt if first command, or Repeatcounter >= MinRepeats
15
    {
16
      if (RepeatCounter >= MinRepeats)  
17
        RepeatCounter = MinRepeats;              // fix overflow for long key push
18
19
      // Send the new message
20
      // slave handling      
21
      can_transfer.Board_Identifier = CAN_NODE_NR;
22
      can_transfer.CAN_MessageID = CAN_IRMP_DECODED;
23
      can_transfer.T_OR_C = IRMP_DECODED | REQUEST;        
24
      SendCANMessage(&can_transfer, NULL, (uint8_t *)&irmp_data, sizeof(IRMP_DATA));              
25
    }      
26
  }
27
}

Anbei auch ein Bild wie ich dann die Daten per V-USB auf dem Windows PC 
auswerte.

Beispiel:
Device Data: USB AVR Device,          (USB Device Name)
ReportID: 0x01,                       (USB Report ID)
NodeID: 0x11,                         (CAN Node ID von wo die Daten 
kommen)
Command: 0x09,                        (CAN Command (Temperatur CMD))
Length: 0x0E,                         (Daten länge)
Data: 0x26F610550100002FE900F401A900  (eigentliche Daten)

{26}F61055010000[2F]                  ({DS2438 Family} Serial [CRC-8]])
E900                                  (Temperatur: 23,3°C)
F401                                  (Versorgungsspannung: 5,00V)
A900                                  (Sensorspannung: 1,69V)

von MarcusW (Gast)


Lesenswert?

Kennst du das hier:

http://de.wikipedia.org/wiki/ISO_15765-2

Ansonsten natürlich immer gut, wenn man sich selber Gedanken macht und 
eine Lösung findet.

von Hugo P. (portisch)


Lesenswert?

MarcusW schrieb:
> Kennst du das hier:
>
> http://de.wikipedia.org/wiki/ISO_15765-2
>
> Ansonsten natürlich immer gut, wenn man sich selber Gedanken macht und
> eine Lösung findet.

Danke für den Tipp!
Aber mein Ziel war es, dass ich ja von einem CAN Teilnehmer einen 
anderen per CAN (CAN Bootloader) updaten kann. Darum habe ich mich an 
das "Protokoll" von kreatives-chaos.com gehalten.

von Leo (Gast)


Lesenswert?

Hallo Huga,

erstamal danke für den Code. Was mir noch aufgefallen ist, das die 
FUnktion WaitforCANResponse nicht in der Master Applikation verwendet 
wird. Eigentlich müsste ein Timeout im Master implementiert werden. Beim 
Slave macht es keinen Sinn! Wenn der Master nach einer gewissen Zeit 
kein Response vom Slave bekommt dann sollte der Timeout zuschlagen.

von Hugo P. (portisch)


Lesenswert?

Leo schrieb:
> Hallo Huga,
>
> erstamal danke für den Code. Was mir noch aufgefallen ist, das die
> FUnktion WaitforCANResponse nicht in der Master Applikation verwendet
> wird. Eigentlich müsste ein Timeout im Master implementiert werden. Beim
> Slave macht es keinen Sinn! Wenn der Master nach einer gewissen Zeit
> kein Response vom Slave bekommt dann sollte der Timeout zuschlagen.

Die WaitforCANResponse verwende ich nur in der usbFunctionWrite.
Also im Master. Hauptsächlich nur wegen den originalen CAN Bootloader.
Die WaitforCANResponse überprüft ca. 500ms lang ob eine passende 
Nachricht empfangen wird.

z.b. im switch case IDENTIFY schicke ich eine CAN Message vom Master an 
den Slave. Danach wird auf einen Response von dem Slave gewartet.

Im normalen Datentransfer zwischen den CAN Nodes verzichte ich auf eine 
Bestätigung.

von Frank Bockwurst (Gast)


Lesenswert?

Sucht mal im Netz unter BOSCH CAN_FD bzw. CAN Flexible Datarate.

Hier geht mehr Datenrate und mehr Payload. Es wird aber noch dauern, bis 
die Chips verfügbar sein werden...

von Entwickler (Gast)


Lesenswert?

Hallo Huga,

mich würde mal ibnteressieren, ob dein CAN Transportprotokoll auch dann 
noch funktioniert, wenn niederpriore CAN Nachrichten auf dem Bus 
vorhanden sind.

von Hugo P. (portisch)


Lesenswert?

Durchgetestet habe ich es noch nicht zu 100%.

Aber als Beispiel:
Der "Master" empfängt einen Datentransfer von 5 Nachrichten mit der 
MSGID 0x302.
Jetzt kommt aber dazwischen ein anderer auf die Idee einen Transfer mit 
der MSGID 0x301 auf den Weg zu schicken.
Somit schummelt sich die 0x301 zwischen den 0x302 Nachrichten.

Der "Master" weis aber, dass der angefangen Transfer von der 0x302 noch 
nicht fertig ist und buffert somit automatisch die 0x301 Nachrichten. 
Erst wenn der Transfer mit der 0x302 vorbei ist wird der Buffer mit den 
0x301 Nachrichten abgearbeitet.

Das Priority/Kollision Handling wird von MCP2515 selber erledigt.

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.