CAN + OpenWrt
Schaltung und Software sind veraltet. Hier eine aktuelle Adaption:CAN-CAN Interface
Zusammenfassung:
Aufbau eines preiswerten CAN<->Ethernet Gateways:
Zutaten
OpenWrt Router BR-6104K
CAN Controller MCP2515 + Tranceiver MCP2551
7805 und etwas Hühnerfutter
Warum BR-6104K und OpenWrt ?
Eigentlich hatte ich geplant RasperryPi zu verwenden.
Da ich aber davon ausgehe, das man erst in einem halben Jahr damit rechnen kann eins zu bekommen, habe ich eine Alternative gesucht.
Die Wahl fiel auf ein bei mir zu verstauben drohenden Router: BR-6104K
Der Router hat nur 2MByte Flash und 16Mbyte RAM. Ziemlich knapp für OpenWrt. Aber es hat einen
entscheidenen Vorteil: Der SoC ADM5120 ist gut dokumentiert und es existieren viele Beispiele über
hardwarenahe Experimente. Zudem habe ich noch ein paar rumliegen ;-)
Eine interessante Alternative scheint das Carambola Board zu sein:
- 320 MHz SoC RT3050F (Dokumentation zugänglich - sehr wichtig !)
- 8MByte Flash + 32 MByte RAM
- LAN + WLAN
- Hardware SPI zur Anbindung des CAN Controllers
- GPIOs über PINS erreichbar
- Preis des Moduls: 22 Euro
Hier die Umsetzung:Carambola
Zurück zum BR-6104k.
Anbindung des CAN Controllers an BR-6104K - Hardware
Der CAN-Controller MCP2515 wird über SPI angebunden:
Eagle Layout
Die Schaltung ist unspektakulär. Sie findet sich so oder so ähnlich zuhauf im Internet. Der CAN-Controller wird mit 3V3 betrieben, wobei
der Tranceiver 5V benötigt. Dadurch war ein Spannungsteiler (R1&R2 besser mit 10k/18k und nicht wie in der Zeichnung 10k/15k) für RXCAN notwendig.
Der Adm5120 SoC hat leider keine SPI Schnittstelle. Es gibt aber ein Linux Kernel Modul, das SPI in
Software emuliert (spi_bitbang). Ich war am Anfang skeptisch, ob die Geschwindigkeit reicht (ca. 340kHz SPI - CAN Bus 250kHz). Aber die Anzahl der empfangenen
und gesendeten CAN Frames ist gering. Zudem gibt es mehrere Buffer: Hardware Receive FIFO 2 und Socket CAN 10, das kurzfristige Engpässe abfängt.
Im kritischen Industrie Umfeld wäre die Bandbreite nicht ausreichend. Aber für eine Spielzeugeisenbahn reicht das vollkommen ;-)
Anbindung des CAN Controllers an BR-6104K - Software
OpenWrt Umgebung
Ich habe OpenWrt Trunk (damals Revision 30857) verwendet. Auf einer Mailingliste habe ich Patches für Kernel 3.1.10 gefunden.
Mittlerweile fand ein Update des von OpenWrt verwendeten Kernels auf 3.3.x statt: Das Patchen entfällt damit.
Der etwas schwierigere Teil ist die Softwareanbindung des CAN Controllers.
SPI
Als erstes muss die Datei br-6104k.c angepasst werden. Da ich weiterhin das Modul
led-gpio verwenden möchte (ich mag im Netzwerk-Takt blinkende Leds), müssen die entsprechenden Ports
ausgeklammert werden:
static struct gpio_led br6104k_gpio_leds[] __initdata = {
GPIO_LED_STD(ADM5120_GPIO_PIN0, "power", NULL),
GPIO_LED_INV(ADM5120_GPIO_P0L1, "wan_speed", NULL),
GPIO_LED_INV(ADM5120_GPIO_P0L0, "wan_lnkact", NULL),
GPIO_LED_INV(ADM5120_GPIO_P1L1, "lan1_speed", NULL),
GPIO_LED_INV(ADM5120_GPIO_P1L0, "lan1_lnkact", NULL),
GPIO_LED_INV(ADM5120_GPIO_P2L1, "lan2_speed", NULL),
GPIO_LED_INV(ADM5120_GPIO_P2L0, "lan2_lnkact", NULL),
// GPIO_LED_INV(ADM5120_GPIO_P3L1, "lan3_speed", NULL),
// GPIO_LED_INV(ADM5120_GPIO_P3L0, "lan3_lnkact", NULL),
// GPIO_LED_INV(ADM5120_GPIO_P4L1, "lan4_speed", NULL),
// GPIO_LED_INV(ADM5120_GPIO_P4L0, "lan4_lnkact", NULL),
};
Das ist notwendig, damit die Ports für SPI zur Verfügung stehen. Der CAN-Controller ist
über GPIO 17,18,20 und 21 angebunden. Das entspricht den Ports:
ADM5120_GPIO_P3L1 GPIO17
ADM5120_GPIO_P3L0 GPIO18
ADM5120_GPIO_P4L1 GPIO20
ADM5120_GPIO_P4L0 GPIO21
Auf dem Board sieht das dann (incl. SPI) so aus:
root@OpenWrt:~# cat /sys/kernel/debug/gpio
GPIOs 0-3, adm5120 gpio0:
gpio-0 (power ) out hi
gpio-2 (MCP251x /INT ) in hi
GPIOs 8-22, adm5120 gpio1:
gpio-8 (wan_lnkact ) out hi
gpio-9 (wan_speed ) out hi
gpio-11 (lan1_lnkact ) out hi
gpio-12 (lan1_speed ) out hi
gpio-14 (lan2_lnkact ) out hi
gpio-15 (lan2_speed ) out hi
gpio-17 (spi_gpio.1 ) in lo
gpio-18 (spi_gpio.1 ) out lo
gpio-20 (spi_gpio.1 ) out lo
gpio-21 (spi1.0 ) out hi
Die SPI Anpassungen müssen in der Boarddefintionsdatei br-61xx.c hinterlegt werden.
Zudem werden die notwendigen Einstiegspunkte für das CAN-Controller Kernel-Modul definiert:
#include <linux/spi/spi_gpio.h>
#include <inux/can/platform/mcp251x.h>
...
static struct mcp251x_platform_data mcp251x_info = {
.oscillator_frequency = 16E6,
.board_specific_setup = NULL,
.power_enable = NULL,
.transceiver_enable = NULL,
};
static struct spi_gpio_platform_data br61xx_gpio_spi = {
.sck = 20,
.mosi = 18,
.miso = 17,
.num_chipselect = 1, /* number of chip selects for spi gpio master */
};
static struct platform_device spi_gpio_device = {
.name = "spi_gpio",
.id = 1, /* Bus number */
.dev.platform_data = &br61xx_gpio_spi,
};
static struct spi_board_info mcp2515_spi_gpio_board_info [] = {
{
.modalias = "mcp2515",
.max_speed_hz = 10000000,
.bus_num = 1,
.chip_select = 0,
.platform_data = &mcp251x_info,
.mode = SPI_MODE_0,
.controller_data = (void *) 21,
},
};
static struct platform_device *br61xx_devices[] __initdata = {
&spi_gpio_device,
};
void __init br61xx_generic_setup(void)
{
...
spi_register_board_info(mcp2515_spi_gpio_board_info,ARRAY_SIZE(mcp2515_spi_gpio_board_info));
adm5120_add_device_gpio_buttons(ARRAY_SIZE(br61xx_gpio_buttons),
br61xx_gpio_buttons);
Interrupt
Das MCP2515 Kernel Modul mcp251x.c benötigt einen EDGE Trigger (High->Low) ; der SoC liefert aber nur LEVEL Interrupts auf GPIO2.
Ich habe keine andere Möglichkleit gefunden, dies (ausser im Kernel-Modul selbst) einzubauen.
static irqreturn_t mcp251x_can_hardirq(int irq, void *dev_id)
{
struct mcp251x_priv *priv = dev_id;
struct spi_device *spi = priv->spi;
unsigned long flags;
unsigned int intc_reg_int_level;
// ADM5120 IRQs can only be level, not edge so we are going
// switch level and only use high to low trigger
spin_lock_irqsave(&level_register_lock, flags);
intc_reg_int_level = intc_read_reg(INTC_REG_INT_LEVEL) ^ BIT(4);
intc_write_reg(INTC_REG_INT_LEVEL, intc_reg_int_level);
// dev_info(&spi->dev, " mcp251x INT level 0x%X \n",intc_reg_int_level);
// we ignore raising edge - we have already xored
// if (intc_reg_int_level & BIT(4))
if (intc_reg_int_level &= 0x10)
{
// dev_info(&spi->dev, " mcp251x INT high -> IRQ_HANDLED\n");
spin_unlock_irqrestore(&level_register_lock, flags);
return IRQ_HANDLED;
} else {
// dev_info(&spi->dev, " mcp251x INT low -> IRQ_WAKE_THREAD\n");
spin_unlock_irqrestore(&level_register_lock, flags);
return IRQ_WAKE_THREAD;
}
}
...
static int mcp251x_open(struct net_device *net)
{
...
ret = request_threaded_irq(spi->irq, mcp251x_can_hardirq, mcp251x_can_ist,
// pdata->irq_flags ? pdata->irq_flags : IRQF_TRIGGER_FALLING,
pdata->irq_flags ? pdata->irq_flags : IRQF_TRIGGER_NONE,
DEVICE_NAME, priv);
Ist alles erfolgreich wird der Controller nach Laden der Module can,can-dev,can-raw und mcp251x initialisiert. Unter dmesg
sieht das dann so aus:
[ 17.648000] can: controller area network core (rev 20090105 abi 8)
[ 17.656000] NET: Registered protocol family 29
[ 17.704000] CAN device driver interface
[ 17.748000] can: raw protocol (rev 20090105)
[ 17.956000] mcp251x spi1.0: CANSTAT 0x80 CANCTRL 0x07
[ 17.956000] mcp251x spi1.0: probed
Nach Starten des Controllers:
/usr/sbin/ip link set can0 type can bitrate 250000
/sbin/ifconfig can0 up
sieht man unter dmesg:
[ 39.108000] mcp251x spi1.0: INTC_REG_IRQ_ENABLE was = 0x212
[ 39.108000] mcp251x spi1.0: INTC_REG_IRQ_ENABLE now = 0x212
[ 39.120000] mcp251x spi1.0: GPIO2 IRQ initialized: IRQ 12
[ 39.160000] mcp251x spi1.0: CNF: 0x01 0xb5 0x01
Auch sollte der reservierte Interrupt sichtbar sein:
root@OpenWrt:~# cat /proc/interrupts
CPU0
2: 0 MIPS cascade [INTC]
7: 113752 MIPS timer
9: 35 INTC uart-pl010
12: 0 INTC mcp251x
17: 18955 INTC eth0, eth1
ERR: 0
Übrigens ist die Anzahl der Interrupts durch das MCP251x Modul immer gerade - ein Interrupt durch den CAN Controller hat immer
zwei LEVEL Interrupts zur Folge.
Ein Blick auf das Interface:
root@OpenWrt:/# ip -s -d link show can0
8: can0: mtu 16 qdisc pfifo_fast state UNKNOWN qlen 10
link/can
can state ERROR-ACTIVE restart-ms 0
bitrate 250000 sample-point 0.875
tq 250 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1
mcp251x: tseg1 3..16 tseg2 2..8 sjw 1..4 brp 1..64 brp-inc 1
clock 8000000
re-started bus-errors arbit-lost error-warn error-pass bus-off
0 0 0 0 0 0
RX: bytes packets errors dropped overrun mcast
0 0 0 0 0 0
TX: bytes packets errors dropped carrier collsns
0 0 0 0 0 0
Jetzt steht der CAN-Controller zur Verfügung und kann z.B. Theo's Eisenbahn mittels can2udp steuern.
Reduced to the max
Impressum: