Nighthawk.dk

Konfigurering af sensorer på CAN bus

Skrevet d. 4. April 2024 af Claus Nielsen.

I løbet af min tid hos Unicontrol har jeg konfigureret utallige sensorer, som kommunikerer via CAN-bus. Kommunikationen er foregået via protokollerne CANOpen og J1939, som jeg beskriver her.

Oversigt

Beskeder på CAN-bussen
CANOpen
J1939

Introduktion

En CAN-bus (Controller Area Network) består idéelt set af en lang række serieforbundne enheder, ECU'er (Electronic Control Unit), med en modstand på 120 Ohm i hver ende af kæden (kaldet en terminering).

Det er muligt at bruge "CAN-splitters" som deler kæden op, således at der opstår "sidegrene" på kæden. Ligeledes har jeg erfaret at man ofte kan nøjes med en enkelt 120 Ohms modstand på kæden. Begge disse afvigelser fra den idéelle CAN-bus vil dog have negativ indvirkning på signal-kvaliteten på bussen.

Når en ECU (f.eks. en sensor) er tilsluttet CAN-bussen vil den typisk kommunikere via protokollerne CANOpen eller J1939, og det er muligt at tilslutte sin computer via en CAN adapter fra f.eks. PEAK (PCAN-USB).

Kører du Linux kan kommunikere med CAN-bussen via SocketCAN, dvs. via sockets ligesom ved almindelig netværksprogrammering (f.eks. TCP/UDP). Du kan også installere can-utils, som giver dig en række fantastiske værktøjer såsom candump, cansend og cansniffer.

Du kan se et eksempel på brugen af SocketCAN fra C++ her.

På Linux kan der oprettes forbindelse til CAN-bussen således:

sudo ip link set can0 type can bitrate 250000

Der er nu sat et CAN-interface op med navnet "can0" og baudrate 250000 (250 kbit/s). Nogle andre gode kommandoer er:

sudo ip link set can0 up
sudo ip link set can0 down
sudo ip link set can0 type can restart
sudo ip -details -statistics link show can0

Beskeder på CAN-bussen

En CAN-besked består primært af et ID og op til 8 bytes data. Dertil kommer nogle flag (RTR og ERR), en CRC-værdi, m.m. som vi ikke ser på her. Der er mulighed for at bruge et udvidet format som bl.a. har et længere ID.

Forskellige enheder er synkroniseret på CAN-bussen og kan i princippet begynde at sende beskeder samtidigt, men hvis to enheder begynder at sende samtidigt vil beskeden med lavest ID blive sendt (groft sagt - der ses også på tidligere bits i CAN frame), mens de andre enheder stopper med at sende, og forsøger igen senere.

Det foregår simpelthen ved, at enhederne læser mens de skriver på CAN-bussen, og bit-værdien "0" er dominant ift. "1". Dvs. at hvis enhed 1 skriver en bit med værdi "0" mens enhed 2 skriver "1", så vil der kun blive skrevet "0" til CAN-bussen, og enhed 2 stopper med at skrive.

Bit-timing er derfor vigtig, og alle enheder skal derfor konfigureres til at køre med samme baudrate på f.eks. 250 kbit/s. Jeg har også oplevet en enkelt gang at det var nødvendigt at ændre sampling point for en enhed, hvilket er en mere advanceret bit-timing-parameter.

Selve ID'et på en besked, også kaldet CAN ID efterfølgende, består generelt set af flere komponenter: både adresse på afsender-ECU og en besked-type. Den præcise sammensætning af CAN ID'et afhænger dog af den anvendte protokol, og CANOpen bruger CAN ID'er på 11 bits (3 hex-cifre, f.eks. 3B1) mens J1939 bruger et længere CAN ID.

Alle CAN ID'er angives her i hex (altså som hexadecimale tal, med præfiks "0x"), og det samme gælder data bytes i beskeder.

Med can-utils på Linux kan du se beskeder på CAN-bussen således (med "-t z" for at få tidsstempler siden start):

candump -t z can0

CANOpen

CANOpen-protokollen er karaketeriseret ved et CAN ID på 11 bits (3 hex-cifre), som består af et Node ID på 7 bits og en besked-type på 4 bits.

Hver ECU tildeles et specifikt Node ID som identificerer enheden, og den beholder sit Node ID så længe man ikke manuelt omkonfigurerer enheden. Node ID har en værdi mellem 0 og 127 (0x7F i hex-notation), hvor Node ID 0 er et specielt broadcast-ID.

Der findes en række forskellige besked-typer hvoraf Service Data Object (SDO) og Layer Setting Services (LSS) kan bruges til konfigurering af enheder. Jeg har stort set udenlukkende brugt SDO, som også umiddelbart er nemmest at bruge, og LSS vil derfor ikke blive beskrevet yderligere her.

Indstillinger i en ECU ligger gemt i objekter, kaldet Communication Objects (COB), som har et 16-bit ID (kaldet COB-ID). Derudover har hvert objekt et 8-bit sub-index, således at et 16-bit COB-ID samt et sub-index angiver adressen på en indstilling.

Hver indstilling kan have forskellige typer, men fylder altid 1, 2 eller 4 bytes - jeg har i hvert fald ikke set andet.

Konfigurering med SDO

SDO bruger to forskellige besked-typer: En anmodning om at læse eller skrive til et objekt, samt et svar på anmodningen.

En SDO-anmodning har et CAN ID på 0x600 + Node ID, mens et svar har CAN ID 0x580 + Node ID. Så hvis vi f.eks. har en sensor med Node ID 0x2D vil SDO-anmodning og svar til denne sensor have CAN ID hhv. 0x62D og 0x5AD.

Begge SDO-besked-typer har 8 bytes data med, som har følgende struktur:

  • Byte 1: Function code.
  • Byte 2-3: COB-ID (little-endian).
  • Byte 4: Sub-index.
  • Byte 5-8: Data.

Funktionskoden for en læse-anmodning er 0x40, mens den for en skrive-anmodning er 0x2F, 0x2B og 0x23 for at skrive hhv. 1, 2 og 4 bytes.

Som et eksempel kan vi spørge en sensor med Node ID 0x2D om hvilken værdi den har på COB-ID 0x1801 med subindex 2. Med cansend fra can-utils kan vi spørge således:

cansend can0 62D#40.01.18.02.00.00.00.00

De sidste 4 data bytes er bare sat til 0 idet de ikke skal bruges her. Svaret fra sensoren vil så kunne ses i et candump (can-utils), og kan f.eks. se således ud:

5AD  [8]  43 01 18 02 10 02 07 40

Svaret her er altså 0x40070210 (sidste 4 bytes) skrevet i little-endian. Hvis du får et svar hvor første byte (her 0x43) er 0x80, har du fået en fejlkode - dette sker f.eks. hvis det COB-ID og sub-index du efterspurgte ikke findes i den pågældende ECU.

De specifikke indstillinger, som er tilgængelige for en ECU kan findes i manualen fra producenten, og står typisk i en tabel med COB-ID og sub-index samt beskrivelse og objekt-type (f.eks. "UINT32" eller "unsigned 32-bit integer"). Lad her forestille os, at vi vil skrive værdien 200 til COB-ID 0x3240 med subindex 0, som en unsigned 16-bit integer, til Node ID 0x3E.

Værdien 200 er 0xC8 i hex, og i 16-bit form bliver dette 0x00C8. Function code for at skrive 2 bytes (16 bit) er 0x2B, så derfor kan vi bruge:

cansend can0 63E#2B.40.32.00.C8.00.00.00

Det er vist stort set hvad der er at vide om SDO - herfra kræver det bare dokumentation for den konkrete ECU/sensor for at kunne konfigurere den. Vær dog opmærksom på, at nogle objekter er read-only, mens andre kun kan skrives til hvis bestemte andre indstillinger først er sat (f.eks. ved mapping af TPDO'er, som vi kommer til senere).

Bemærk dog at du normalt skal huske at gemme indstillingerne før du tager strømmen fra ECU'en, og nogle indstillinger træder først i kraft efter save og reboot af ECU. En SDO save-kommando ser typisk således ud:

cansend can0 63E#23.10.10.01.73.61.76.65

Dvs. skriv 0x65766173 (4 bytes) til COB-ID 0x1010 med sub-index 01. Bemærk at 0x65766173 i little-endian bliver til ASCII-koderne for "save".

Network Management (NMT)

En sensor/ECU på CAN-bussen kan være i flere forskellige tilstande, men vil typisk være i operational mode under normal brug. En anden tilstand er pre-operational mode, hvor enheden ikke streamer data med stadig kan konfigureres via SDO.

Nogle enheder er konfigureret til at starte op i pre-operational mode, og kræver at man sætter dem operational mode før de kan streame data. Omvendt kan det være en fordel at sætte enheder i pre-operational mode hvis man f.eks. skal konfigurere en enhed - så kan man bedre overskue et candump.

Ved at bruge NMT kan man bl.a. skifte mellem tilstande på enheder. NMT-beskeder har CAN ID 000 (uden noget Node ID), og har kun 2 data bytes når du vil skifte tilstand:

  • Byte 1: Ny tilstand.
  • Byte 2: Node ID.

Her angives Node ID altså i data byte 2 i stedet for i selve CAN ID'et, og bemærk at du kan bruge 0 for broadcast. Den første byte bestemmer den tilstand, som du vil flytte enheden over i, og kan bl.a. have værdierne:

  • 0x01: Operational
  • 0x80: Pre-operational
  • 0x81: Reset node

For flere forskellige sensor-type har jeg brugt 0x81 til at skifte til pre-operational, men jeg har også oplevet at andre sensor-typer har lavet factory reset med 0x81. Derfor er det er nok mest sikkert at bruge 0x80 til at skifte til pre-operational.

Her er to eksempler, der først sætter alle enheder i pre-operational mode, og bagefter sætter en enhed med Node ID 0x42 i operational mode:

cansend can0 000#80.00
cansend can0 000#01.42

Data streaming med TPDOs

Hvis man har sensorer på sin CAN-bus, vil man normalt gerne have masser data fra dem. Selvom det ofte er muligt at spørge efter sensor-data med SDO, er dette ikke praktisk.

I stedet kan du benytte Transmit Process Data Object (TPDO), som bl.a. giver mulighed for kontinuerlig streaming af data.

Der er 4 forskellige TPDO'er, dvs. 4 "kanaler" som hver enhed kan streame data på - nogle enheder tillader dog færre, og jeg har i et enkelt tilfælde set en sensor tilbyde flere (hvilket dog ikke passer helt ind i CANOpen-standarden så vidt jeg ved!).

De fire TPDO'er har CANOpen besked-typerne 0x180, 0x280, 0x380 og 0x480, og sammen med et Node ID udgør disse CAN ID'et for en TPDO. F.eks. vil en enhed med Node ID 0x1C sende beskeder på TPDO2 ved at bruge CAN ID 0x29C, eller TPDO4 med 0x49C. Og CAN ID 0x1B7 er en TPDO1-besked fra en enhed med Node ID 0x37.

Udover CAN ID'et kan hver TPDO-besked have op til 8 bytes data, og hvad disse data indeholder afhænger af den enkelte ECU. Typisk bruges COB-ID 1A00 til 1A03 til konfigurering af de fire TPDO'er, hvor sub-index 0 er antallet af parametre, som er indeholdt i en TPDO-besked (indeholdt i de op til 8 data bytes).

De efterfølgende sub-indices bruges til at angive COB-ID, sub-index og antal bits for hver parameter, som skal sendes i en TPDO-besked.

Lad os som eksempel antage, at vi har en sensor, node 0x2E, som måler to værdier, X og Y, som vi gerne vil streame regelmæssigt på CAN-bussen. Lad os desuden sige at X fylder 16 (0x10) bits og ligger på COB-ID 60A1, sub-index 1, mens Y fylder 32 (0x20) bits og ligger på COB-ID 60B1 og sub-index 4.

Vi kan nu konfigurere TPDO1 til at sende disse værdier: Før vi kan ændre hvilke parametre, der skal sendes, skal vi først sætte antallet af parametre til 0. Dernæst kan vi skrive tallet 0x60A10110 til 0x1A00 sub-index 1, og 0x60B10420 til 0x1A00 sub-index 2. Til sidst sættes antallet af parametre til 2.

Dette kan gøres med følgende SDO-beskeder:

cansend can0 62E#2f.00.1A.00.00.00.00.00
cansend can0 62E#23.00.1A.01.10.01.A1.60
cansend can0 62E#23.00.1A.02.20.04.B1.60
cansend can0 62E#2f.00.1A.00.02.00.00.00

Hvis vi nu antager, at X har værdien 0x1234 mens Y har værdien 0xDEADBEEF vil en TPDO1-besked fra denne sensor nu se således ud:

1AE  [6]  34 12 EF BE AD DE

Men hvornår sendes denne TPDO-besked så? Her er der typisk to muligheder: Enten som svar på at du sender en SYNC-besked (CAN ID 0x80), eller regelmæssigt med et interval. Hvilken mekanisme som bruges indstilles typisk i COB-ID 0x1800 sub-index 2, transmission type.

Ved at sætte transmission type til 0xFF kan der sættes en event timer i COB-ID 1800 sub-index 5, som angiver tiden i millisekunder mellem to TPDO1-besker når der streames data.

Det skal bemærkes, at indstillingerne for en TPDO (COB-ID 1800 samt mapping i COB-ID 1A00) normalt ikke kan ændres når en TPDO er aktiv. En TPDO er aktiv så længe COB-ID 1800 sub-index 1 har bit 32 sat til 0.

Hvis du vil omkonfigurere en TPDO (via SDO) bør du altså først læse 1800 sub-index 1 og så skrive en tilsvarende værdi hvor bit 32 er sat. Når du er færdig med konfigureringen kan du aktivere TPDO'en igen ved at skrive værdien hvor bit 32 er sat til 0.

Hvis vi antager at COB-ID 1800 sub-index 1 har værdien 0x40001AE (TPDO1 CAN ID er 0x1AE = 0x180 + 0x2E), som vi kan læse med SDO, så skal vi ændre dette til 0xC0001AE for at få sat bit 32 til 1. Dernæst kan transmission type og event timer ændres:

... Evt. først læs 1800/01:
cansend can0 62E#40.00.18.01.00.00.00.00

... Så konfigurér:
cansend can0 62E#23.00.18.01.AE.01.00.C0
cansend can0 62E#2f.00.18.02.FF.00.00.00
cansend can0 62E#2f.00.18.05.C8.00.00.00

... Evt. sæt mapping i COB-ID 1A00 som vist ovenfor

... Afslut med at aktivere TPDO igen:
cansend can0 62E#23.00.18.01.AE.01.00.40

... Husk at gemme ændringer:
cansend can0 62E#23.10.10.01.73.61.76.65

Her sættes event timer til 0xC8 (200 ms) som svarer til en frekvens på 5 Hz. Husk at sætte sensoren i operational mode (NMT) for at den begynder at streame data.

COB-ID 1800 angiver indstillinger for TPDO1, og COB-ID 1801 til 1803 angiver tilsvarende indstillinger for TPDO2 til 4.

Emergency (EMCY)

Nogle enheder bruger EMCY-beskeder til at kommunikere om fejl. Disse beskeder har besked-type 0x80 således at CAN ID bliver 0x80 + Node ID. Pas på med ikke at forveksle dette med SYNC (CAN ID 0x80) - EMCY-beskeder kan ikke bruge Node ID 0.

Receive Process Data Object (RPDO)

Udover at streame data fra en enhed på CAN-bussen kan man også streame data til en enhed. Dette kan f.eks. bruges til en sensor som kan integrere sine målinger med data fra andre sensorer, og som derfor har brug for at kende opdaterede værdier af disse andre sensormålinger.

Opsætningen af RPDO'er er meget lig opsætningen af TPDO'er, og der er ligeledes 4 af dem. Data som skal læses via en RPDO sendes med besked-type 0x200, 0x300, 0x400 og 0x500 for de fire RPDO'er. Der kan desuden lægges et "Node ID" til for at få det endelige CAN ID.

Det er dog ikke nødvendigt at bruge et egentligt Node ID til en RPDO: Du kan bare sende data ud på CAN ID f.eks. 0x200 eller 0x201 for RPDO1, for hver enhed kan konfigureres til at bruge ét bestemt CAN ID til en RPDO.

Det er COB-ID 0x1400 sub-index 1 hvor du angiver hvilket CAN ID, som skal bruges til RPDO1. Desuden kan du aktivere/deaktivere en RPDO her ved at bruge bit 32, så dette COB-ID fungerer altså ligesom 0x1800 for TPDO1.

Du kan sætte en mapping for RPDO1 via COB-ID 0x1600, og igen fungerer det præcis som når du sætter en TPDO-mapping. Du sætter blot en mapping for hvilke COB-ID'er og sub-indices som data fra en RPDO-besked skal læses ind i, i enheden.

COB-ID 1400 og 1600 angiver indstillinger for RPDO1, og COB-ID 1801-1803 og 1601-1603 angiver tilsvarende indstillinger for RPDO2 til 4.

Heartbeats

CANOpen giver også mulighed for, at en enhed kan sende "heartbeats" med et bestemt interval - dette er den del af NMT, som jeg dog ikke selv har haft brug for.

Til gengæld er det utroligt nyttigt, at alle CANOpen-enheder sender et heartbeat under initialisering/boot, dvs. så snart de forbinder til CAN-bussen. Dette er praktisk hvis man f.eks. ikke kender Node ID på en enhed, og ikke kan se den i et candump (hvis den f.eks. ikke streamer data). Hvis man er i tvivl om man kører med den rette baudrate i forhold til en sensor, kan man også hurtigt se, om den sender et heartbeat når den forbindes til bussen.

Et CANOpen Heartbeat har CAN ID 0x700 + Node ID, og har kun en enkelt data byte, som angiver NMT-tilstanden (0 for boot up, 1 for operational, 80 for pre-operational, osv.).

J1939

En anden meget anvendt protokol er J1939, som på to punkter adskiller sig væsentligt fra CANOpen. Først og fremmest benytter J1939 et meget længere CAN ID, og desuden har hver ECU ikke en fast Source Address (J1939-terminologi for Node ID).

Et J1939 CAN ID består af 29 bits udover tre flag (EFF, RTR, ERR), som består af en række forskellige parametre. Det vigtigste her source address, SA, som tæller de først 8 bits og parameter group number, PGN, som er de næste 18 bits.

SA har samme betydning som Node ID i CANOpen, og PGN kan igen underinddeles i forskellige parametre. Her skal det bare bemærkes, at der findes utroligt mange besked-typer - altså PGN'er - i J1939-protokollen, og en lang række af disse er standardiserede således at f.eks. PGN 61460 (0xF014) giver rotationen af en tiltrotator, mens PGN 61485 (0xF02D) er en besked med accelerometer-data.

Udover de standardiserede PGN-værdier er en lang række PGN'er også reserveret til ECU-specifikke beskeder, som producenten af ECU'en kan bruge til hvad end de har brug for.

J1939-protokollen har hverken TPDO'er, SDO, LSS eller NMT - i stedet kan forskellige PGN'er typisk konfigureres for enheden, så de f.eks. sendes regelmæssigt med en bestemt frekvens. Her vil jeg dog henvise til manualen for den enkelte ECU for mere information.

Address claiming

I CANOpen konfigureres en enhed til altid at have ét bestemt Node ID, men i J1939 bruges dynamisk adressering, hvilket vil sige, at man ikke på forhånd vil vide hvilken source address en enhed vil få når den tilsluttes en CAN-bus.

En enhed vil typisk have en foretrukken source address, men hvis flere enheder foretrækker samme adresse, vil kun den ene ende med at få den. Hvem der ender med at få en bestemt source address bliver afgjort via address claims.

Et address claim er en besked med PGN 60928 (0xEE00) + en 8-bit destination address, som ofte er 0xFF (broadcast) dvs. 0xEEFF. Source address for et address claim er den foretrukne adresse, dvs. den adresse som enheden, der sender beskeden, gerne vil have.

Når en ECU har sendt et address claim med en bestemt source address, så har denne ECU den valgte source address. Dette kan give en konflikt hvis en anden ECU tidligere har claimed samme source address, og i så fald skal den "gamle" ECU, som tidligere har claimed samme source address gøre ét af følgende to muligheder:

  1. Send et address claim på en ny source address. Nu har den "gamle" ECU fået en ny source address.
  2. Send et address claim på samme source address. Den "gamle" ECU har ret til at beholde sin source address.

I tilfælde 2 ovenfor har den "gamle" ECU ret til at beholde sin source address, og den "nye" ECU på CAN-bussen må derfor vælge sig en ny source address, og prøve at sende et nyt address claim.

Hvem der har mest ret til en bestemt source address bliver afgjort af de 8 data bytes, som sendes med et address claim. Disse 8 bytes svarer til en 64-bit unsigned integer, og ECU'en med lavest værdi af denne 64-bit integer har retten til den efterspurgte source address.

Et address claim for source address 0x42 kan se således ud:

18EEFF42  [8]  12 34 56 78 DE AD BE EF

De højeste bits ("18") i CAN ID'et er en 3-bit "priority" som ikke har nogen praktisk betydning for address claims. De 8 data bytes kaldes "Navnet" og indeholder en række informationer omkring den ECU, som har sendt address claim-beskeden, som f.eks. serienummer og en manufacturer code. Dette "navn" kan bruges til at identificere ECU'en, således at man ved hvem der er hvem, når nu der ikke er garanteret en fast source address.

Address claim requests

Med J1939 er man (ofte) nødt til at holde styr på hvilken source address en ECU har fået, og forbinder man først til CAN-bussen efter en ECU har udsendt et address claim har man misset denne information.

Derfor kan man sende en address claim request, som får enheder til at udsende address claims igen. Dette skulle ikke ændre på anvendte source addresses, men giver mulighed for at se dels hvem som et tilsluttet CAN-bussen, samt hvem der anvender hvilken source address.

J1939 Get Requests sendes med PGN 59904 (0xEA00) plus en destination address (0xFF for broadcast, dvs. 0xEAFF). En sådan Get Request kan bruges til at efterspørge forskellige PGN'er (lidt ligesom med SDO i CANOpen), hvor det efterspurgte PGN står i data bytes 2 og 3 i CAN-beskeden, som har 3 data bytes.

En address claim request er derfor en broadcasted get request på PGN 0xEE00, og kan sendes således:

cansend can0 18EAFF00#00.EE.00

Her er source address bare sat til 0 - det gør ikke så meget hvad der bruges der.

Til slut skal det dog lige bemærkes, at ikke alle ECU'er følger J1939-standarden lige godt, og særligt address claiming er ikke altid implementeret korrekt. Nogle enheder vil kun forsøge at få én bestemt source address, og jeg har mødt enkelte enheder som slet ikke sender address claims.

Emner
×