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: