import sample program & help page
parent
fe748b1c2c
commit
198c5801ec
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
OUTPUT = j1939.html testj1939
|
||||
default: $(OUTPUT)
|
||||
|
||||
%.html: %.page page.theme
|
||||
theme -f -o $@ $< -p "$*"
|
||||
|
||||
CFLAGS = -Wall -g3 -O0
|
||||
|
||||
clean:
|
||||
rm -f $(wildcard *.o) $(OUTPUT)
|
||||
|
|
@ -0,0 +1,327 @@
|
|||
# CAN-J1939 on linux
|
||||
|
||||
## CAN on linux
|
||||
|
||||
See [Wikipedia:socketcan](http://en.wikipedia.org/wiki/Socketcan)
|
||||
|
||||
## J1939 networking in short
|
||||
|
||||
* Add addressing on top of CAN (destination address & broadcast)
|
||||
|
||||
* Any (max 1780) length packets.
|
||||
Packets of 9 or more use **Transport Protocol** (fragmentation)
|
||||
Such packets use different CANid for the same PGN.
|
||||
|
||||
* only **29**bit, non-**RTR** CAN frames
|
||||
|
||||
* CAN id is composed of
|
||||
* 0..8: SA (source address)
|
||||
* 9..26:
|
||||
* PDU1: PGN+DA (destionation address)
|
||||
* PDU2: PGN
|
||||
* 27..29: PRIO
|
||||
|
||||
* SA / DA may be dynamically assigned via j1939-81
|
||||
Fixed rules of precedence in Specification, no master necessary
|
||||
|
||||
## J1939 on SocketCAN
|
||||
|
||||
J1939 is *just another protocol* that fits
|
||||
in the Berkely sockets.
|
||||
|
||||
socket(AF_CAN, SOCK_DGRAM, CAN_J1939)
|
||||
|
||||
## differences from CAN_RAW
|
||||
### addressing
|
||||
|
||||
SA, DA & PGN are used, not CAN id.
|
||||
|
||||
Berkeley socket API is used to communicate these to userspace:
|
||||
|
||||
* SA+PGN is put in sockname ([getsockname](http://man7.org/linux/man-pages/man2/getsockname.2.html))
|
||||
* DA+PGN is put in peername ([getpeername](http://man7.org/linux/man-pages/man2/getpeername.2.html))
|
||||
PGN is put in both structs
|
||||
|
||||
PRIO is a datalink property, and irrelevant for interpretation
|
||||
Therefore, PRIO is not in *sockname* or *peername*.
|
||||
|
||||
The *data* that is [recv][recvfrom] or [send][sendto] is the real payload.
|
||||
Unlike CAN_RAW, where addressing info is data.
|
||||
|
||||
### Packet size
|
||||
|
||||
J1939 handles packets of 8+ bytes with **Transport Protocol** fragmentation transparently.
|
||||
No fixed data size is necessary.
|
||||
|
||||
send(sock, data, 8, 0);
|
||||
|
||||
will emit a single CAN frame.
|
||||
|
||||
send(sock, data, 9, 0);
|
||||
|
||||
will use fragementation, emitting 1+ CAN frames.
|
||||
|
||||
# Enable j1939
|
||||
|
||||
CAN has no protocol id field.
|
||||
Enabling protocols must be done manually
|
||||
|
||||
### netlink
|
||||
|
||||
ip link set can0 j1939 on
|
||||
|
||||
### procfs for legacy kernel (2.6.25)
|
||||
|
||||
This API is dropped for kernels with netlink support!
|
||||
|
||||
echo can0 > /proc/net/can-j1939/net
|
||||
|
||||
# Using J1939
|
||||
|
||||
## BSD socket implementation
|
||||
* socket
|
||||
* bind / connect
|
||||
* recvfrom / sendto
|
||||
* getsockname / getpeername
|
||||
|
||||
## Modified *struct sockaddr_can*
|
||||
|
||||
struct sockaddr_can {
|
||||
sa_family_t can_family;
|
||||
int can_ifindex;
|
||||
union {
|
||||
struct {
|
||||
__u64 name;
|
||||
__u32 pgn;
|
||||
__u8 addr;
|
||||
} j1939;
|
||||
} can_addr;
|
||||
}
|
||||
|
||||
* *can_addr.j1939.pgn* is PGN
|
||||
|
||||
* *can_addr.j1939.addr* & *can_addr.j1939.name*
|
||||
determine the ECU
|
||||
|
||||
* receiving address information,
|
||||
*addr* is always set,
|
||||
*name* is set when available.
|
||||
|
||||
* When providing address information,
|
||||
*name* != 0 indicates dynamic addressing
|
||||
|
||||
## iproute2
|
||||
|
||||
### Static addressing
|
||||
|
||||
ip addr add j1939 0x80 dev can0
|
||||
|
||||
### Dynamic addressing
|
||||
|
||||
ip addr add j1939 name 0x012345678abcdef dev can0
|
||||
|
||||
# First steps with j1939
|
||||
|
||||
Use [testj1939](testj1939.c)
|
||||
|
||||
Load modules, when vcan & can-j1939 are not in-kernel
|
||||
|
||||
modprobe vcan
|
||||
modprobe can-j1939
|
||||
|
||||
### create virtual CAN bus
|
||||
|
||||
Make sure *can0* is available, or replace *can0* with *vcan0*
|
||||
|
||||
ip link add can0 type vcan
|
||||
|
||||
### enable CAN
|
||||
|
||||
ip link set can0 up
|
||||
|
||||
### enable j1939
|
||||
|
||||
modprobe can-j1939
|
||||
|
||||
### receive without source address
|
||||
|
||||
Do in terminal 1
|
||||
|
||||
./testj1939 -r can0:
|
||||
|
||||
Send raw CAN in terminal 2
|
||||
|
||||
cansend can0 1823ff40#0123
|
||||
|
||||
You should have this output in terminal 1
|
||||
|
||||
40 02300: 01 23
|
||||
|
||||
This means, from NAME 0, SA 40, PGN 02300 was received,
|
||||
with 2 databytes, *01* & *02*.
|
||||
|
||||
now emit this CAN message:
|
||||
|
||||
cansend can0 18234140#0123
|
||||
|
||||
In J1939, this means that ECU 0x40 sends directly to ECU 0x41
|
||||
Since we did not bind to address 0x41, this traffic
|
||||
is not meant for us.
|
||||
|
||||
### Use source address
|
||||
|
||||
./testj1939 can0:0x80
|
||||
|
||||
will say
|
||||
|
||||
./testj1939: bind(): Cannot assign requested address
|
||||
|
||||
Since J1939 maintains addressing, **0x80** has not yet been assigned
|
||||
as an address on **can0** . This behaviour is very similar to IP
|
||||
addressing: you cannot bind to an address that is not your own.
|
||||
|
||||
Now tell the kernel that we *own* address 0x80.
|
||||
It will be available from now on.
|
||||
|
||||
ip addr add j1939 0x80 dev can0
|
||||
./testj1939 can0:0x80
|
||||
|
||||
now succeeds.
|
||||
|
||||
### receive with source address
|
||||
|
||||
Terminal 1:
|
||||
|
||||
./testj1939 -r can0:0x80
|
||||
|
||||
Terminal 2:
|
||||
|
||||
cansend can0 18238040#0123
|
||||
|
||||
Will emit this output
|
||||
|
||||
40 02300: 01 23
|
||||
|
||||
This is because the traffic had destination address __0x80__ .
|
||||
|
||||
### send
|
||||
|
||||
Open in terminal 1:
|
||||
|
||||
candump -L can0
|
||||
|
||||
And to these test in another terminal
|
||||
|
||||
./testj1939 -s can0:0x80
|
||||
|
||||
This produces **1BFFFF80#0123456789ABCDEF** on CAN.
|
||||
|
||||
### Multiple source addresses on 1 CAN device
|
||||
|
||||
ip addr add j1939 0x90 dev can0
|
||||
|
||||
./testj1939 -s can0:0x90
|
||||
|
||||
produces **1BFFFF90#0123456789ABCDEF** ,
|
||||
|
||||
./testj1939 -s can0:
|
||||
|
||||
still produces **1BFFFF80#0123456789ABCDEF** , since **0x80**
|
||||
is the default _source address_.
|
||||
Check
|
||||
|
||||
ip addr show can0
|
||||
|
||||
emits
|
||||
|
||||
X: can0: <NOARP,UP,LOWER_UP> mtu 16 qdisc noqueue state UNKNOWN
|
||||
link/can
|
||||
can-j1939 0x80 scope link
|
||||
can-j1939 0x90 scope link
|
||||
|
||||
0x80 is the first address on can0.
|
||||
|
||||
### Use specific PGN
|
||||
|
||||
./testj1939 -s can0:,0x12345
|
||||
|
||||
emits **1923FF80#0123456789ABCDEF** .
|
||||
|
||||
Note that the real PGN is **0x12300**, and destination address is **0xff**.
|
||||
|
||||
### Emit destination specific packets
|
||||
|
||||
The destination field may be set during sendto().
|
||||
*testj1939* implements that like this
|
||||
|
||||
./testj1939 -s can0:,0x12345 can0:0x40
|
||||
|
||||
emits **19234080#0123456789ABCDEF** .
|
||||
|
||||
The destination CAN iface __must__ always match the source CAN iface.
|
||||
Specifing one during bind is therefore sufficient.
|
||||
|
||||
./testj1939 -s can0:,0x12300 :0x40
|
||||
|
||||
emits the very same.
|
||||
|
||||
### Emit different PGNs using the same socket
|
||||
|
||||
The PGN is provided in both __bind( *sockname* )__ and
|
||||
__sendto( *peername* )__ , and only one is used.
|
||||
*peername* PGN has highest precedence.
|
||||
|
||||
./testj1939 -s can0:,0x12300 :0x40,0x32100
|
||||
|
||||
emits **1B214080#0123456789ABCDEF** .
|
||||
It makes sometimes sense to omit the PGN in __bind( *sockname* )__ .
|
||||
|
||||
### Larger packets
|
||||
|
||||
J1939 transparently switches to *Transport Protocol* when packets
|
||||
do not fit into single CAN packets.
|
||||
|
||||
./testj1939 -s20 can0:0x80 :,0x12300
|
||||
|
||||
emits:
|
||||
|
||||
18ECFF80#20140003FF002301
|
||||
18EBFF80#010123456789ABCD
|
||||
18EBFF80#02EF0123456789AB
|
||||
18EBFF80#03CDEF01234567
|
||||
|
||||
The fragments for broadcasted *Transport Protocol* are seperated
|
||||
__50ms__ from each other.
|
||||
Destination specific *Transport Protocol* applies flow control
|
||||
and may emit CAN packets much faster.
|
||||
|
||||
./testj1939 -s20 can0:0x80 :0x90,0x12300
|
||||
|
||||
emits:
|
||||
|
||||
18EC9080#1014000303002301
|
||||
18EC8090#110301FFFF002301
|
||||
18EB9080#010123456789ABCD
|
||||
18EB9080#02EF0123456789AB
|
||||
18EB9080#03CDEF01234567
|
||||
18EC8090#13140003FF002301
|
||||
|
||||
The flow control causes a bit overhead.
|
||||
This overhead scales very good for larger J1939 packets.
|
||||
|
||||
# Advanced topics with j1939
|
||||
|
||||
### Change priority of J1939 packets
|
||||
|
||||
./testj1939 -s can0:0x80,0x0100
|
||||
./testj1939 -s -p3 can0:0x80,0x0200
|
||||
|
||||
emits
|
||||
|
||||
1801FF80#0123456789ABCDEF
|
||||
0C02FF80#0123456789ABCDEF
|
||||
|
||||
### using connect
|
||||
|
||||
## dynamic addressing
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>page: <?theme title?></title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<style>
|
||||
|
||||
div#toc li {
|
||||
list-style : none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='toc'>
|
||||
<?theme toc?>
|
||||
</div>
|
||||
<?theme body?>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
* {
|
||||
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
body {
|
||||
max-width: 60em;
|
||||
margin: 0 auto;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
font-family: Monaco, Courier New, monospace;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: rgb(43,105,145);
|
||||
font-weight: bold;
|
||||
font-size: 40px;
|
||||
letter-spacing: -1px;
|
||||
margin-bottom: -5px;
|
||||
margin: 0;
|
||||
}
|
||||
h1 code {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: rgb(43,105,145);
|
||||
font-weight: bold;
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
h2 code {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
h3 code {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: blue;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:visited {
|
||||
color: navy;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
pre {
|
||||
border-width: 1px;
|
||||
border-color: #777;
|
||||
border-style: solid;
|
||||
padding: 0.5em;
|
||||
background-color: #ccc;
|
||||
overflow: auto;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
p, li {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* Copyright (c) 2013 EIA Electronics
|
||||
*
|
||||
* Authors:
|
||||
* Kurt Van Dijck <kurt.van.dijck@eia.be>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <inttypes.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <error.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <net/if.h>
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/j1939.h>
|
||||
|
||||
static const char help_msg[] =
|
||||
"testj1939: demonstrate j1939 use\n"
|
||||
"Usage: testj1939 FROM TO\n"
|
||||
" FROM / TO - or [IFACE][:[SA][,[PGN][,NAME]]]\n"
|
||||
"Options:\n"
|
||||
" -s[=LEN] Initial send of LEN bytes dummy data\n"
|
||||
" -r Receive (and print) data\n"
|
||||
" -e Echo incoming packets back\n"
|
||||
" This atually receives packets\n"
|
||||
" -c Issue connect()\n"
|
||||
" -p=PRIO Set priority to PRIO\n"
|
||||
" -n Emit 64bit NAMEs in output\n"
|
||||
"\n"
|
||||
"Example:\n"
|
||||
"testj1939 can1 20\n"
|
||||
"\n"
|
||||
;
|
||||
|
||||
static const char optstring[] = "?vs::rep:cn";
|
||||
|
||||
static void parse_canaddr(char *spec, struct sockaddr_can *paddr)
|
||||
{
|
||||
char *str;
|
||||
|
||||
str = strsep(&spec, ":");
|
||||
if (strlen(str))
|
||||
paddr->can_ifindex = if_nametoindex(str);
|
||||
|
||||
str = strsep(&spec, ",");
|
||||
if (str && strlen(str))
|
||||
paddr->can_addr.j1939.addr = strtoul(str, NULL, 0);
|
||||
|
||||
str = strsep(&spec, ",");
|
||||
if (str && strlen(str))
|
||||
paddr->can_addr.j1939.pgn = strtoul(str, NULL, 0);
|
||||
|
||||
str = strsep(&spec, ",");
|
||||
if (str && strlen(str))
|
||||
paddr->can_addr.j1939.name = strtoul(str, NULL, 0);
|
||||
}
|
||||
|
||||
/* main */
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ret, sock, opt, j;
|
||||
socklen_t peernamelen;
|
||||
struct sockaddr_can sockname = {
|
||||
.can_family = AF_CAN,
|
||||
.can_addr.j1939 = {
|
||||
.addr = J1939_NO_ADDR,
|
||||
.name = J1939_NO_NAME,
|
||||
.pgn = J1939_NO_PGN,
|
||||
},
|
||||
}, peername = {
|
||||
.can_family = AF_CAN,
|
||||
.can_addr.j1939 = {
|
||||
.addr = J1939_NO_ADDR,
|
||||
.name = J1939_NO_NAME,
|
||||
.pgn = J1939_NO_PGN,
|
||||
},
|
||||
};
|
||||
uint8_t dat[128];
|
||||
int todo_send = 0, todo_recv = 0, todo_echo = 0, todo_prio = -1;
|
||||
int todo_connect = 0, todo_names = 0;
|
||||
|
||||
/* argument parsing */
|
||||
while ((opt = getopt(argc, argv, optstring)) != -1)
|
||||
switch (opt) {
|
||||
case 's':
|
||||
todo_send = strtoul(optarg ?: "8", NULL, 0);
|
||||
break;
|
||||
case 'r':
|
||||
todo_recv = 1;
|
||||
break;
|
||||
case 'e':
|
||||
todo_echo = 1;
|
||||
break;
|
||||
case 'p':
|
||||
todo_prio = strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
case 'c':
|
||||
todo_connect = 1;
|
||||
break;
|
||||
case 'n':
|
||||
todo_names = 1;
|
||||
break;
|
||||
default:
|
||||
fputs(help_msg, stderr);
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (argv[optind]) {
|
||||
if (strcmp("-", argv[optind]))
|
||||
parse_canaddr(argv[optind], &sockname);
|
||||
++optind;
|
||||
}
|
||||
|
||||
if (argv[optind]) {
|
||||
if (strcmp("-", argv[optind]))
|
||||
parse_canaddr(argv[optind], &peername);
|
||||
++optind;
|
||||
}
|
||||
|
||||
/* open socket */
|
||||
sock = ret = socket(PF_CAN, SOCK_DGRAM, CAN_J1939);
|
||||
if (ret < 0)
|
||||
error(1, errno, "socket(j1939)");
|
||||
|
||||
if (todo_prio >= 0) {
|
||||
ret = setsockopt(sock, SOL_CAN_J1939, SO_J1939_SEND_PRIO,
|
||||
&todo_prio, sizeof(todo_prio));
|
||||
if (ret < 0)
|
||||
error(1, errno, "set priority %i", todo_prio);
|
||||
}
|
||||
|
||||
ret = bind(sock, (void *)&sockname, sizeof(sockname));
|
||||
if (ret < 0)
|
||||
error(1, errno, "bind()");
|
||||
|
||||
if (todo_connect) {
|
||||
ret = connect(sock, (void *)&peername, sizeof(peername));
|
||||
if (ret < 0)
|
||||
error(1, errno, "connect()");
|
||||
}
|
||||
|
||||
if (todo_send) {
|
||||
/* initialize test vector */
|
||||
for (j = 0; j < sizeof(dat); ++j)
|
||||
dat[j] = ((2*j) << 4) + ((2*j+1) & 0xf);
|
||||
|
||||
/* send data */
|
||||
ret = sendto(sock, dat, todo_send, 0,
|
||||
(void *)&peername, sizeof(peername));
|
||||
if (ret < 0)
|
||||
error(1, errno, "sendto");
|
||||
}
|
||||
|
||||
/* main loop */
|
||||
while (todo_echo || todo_recv) {
|
||||
/*
|
||||
* re-use peername for storing the sender's peername of
|
||||
* received packets
|
||||
*/
|
||||
peernamelen = sizeof(peername);
|
||||
ret = recvfrom(sock, dat, sizeof(dat), 0,
|
||||
(void *)&peername, &peernamelen);
|
||||
if (ret < 0) {
|
||||
if (EINTR == errno)
|
||||
continue;
|
||||
error(1, errno, "recvfrom()");
|
||||
}
|
||||
|
||||
if (todo_echo) {
|
||||
ret = sendto(sock, dat, ret, 0,
|
||||
(void *)&peername, peernamelen);
|
||||
if (ret < 0)
|
||||
error(1, errno, "sendto");
|
||||
}
|
||||
if (todo_recv) {
|
||||
if (todo_names && peername.can_addr.j1939.name)
|
||||
printf("%016llx ", peername.can_addr.j1939.name);
|
||||
printf("%02x %05x:", peername.can_addr.j1939.addr,
|
||||
peername.can_addr.j1939.pgn);
|
||||
for (j = 0; j < ret; ++j)
|
||||
printf(" %02x", dat[j]);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue