CAN + OpenWrt

Sunnary:

Building a cheap CAN<->Ethernet Gateway for a RailRoad Interface:

Used parts

OpenWrt Router BR-6104K
CAN Controller MCP2515 + Tranceiver MCP2551
7805 and some caps and resitors

Why using a BR-6104K and OpenWrt ?

I'm still waiting getting my RasperryPi, but I don't expect getting one the next few month :-(
So I was looking for an alternative Linux board. In the past I was experimenting with OpenWrt and BR-6104K. The router has only 2MByte Flash and 16Mbyte RAM - quite challenging. But 13 GPIOS are easily reachable and the documentation is excellent. There are many projects using this board, have a look at: Omnima Forum.

Connecting the CAN Controllers to the BR-6104K - hardware part

The CAN-Controller MCP2515 must be connected thru SPI:


The schematic is trivial. You can find a lot examples using google. There is only one sticking point: the MCP2551 tranceiver needs 5V, but the SoC is using 3V3.So you need a voltage divider (R1/R2).

The ADM5120 SoC doesn't have a SPI interface but there is still a way by using SPI bitbanging thru GPIO. I was skeptic if the speed is high enough (roughly 340kHz SPI - the designated CAN bandwidth 250kHz). The MCP2515 provides a small hardware receiving FIFO (2 entries) and the socket CAN layer a 10 entries wide sending FIFO. This should be enough for small bursts.
Sure this is not sufficient for industrial applications, but the main purpose here is a RailRoad interface.

Connecting the CAN Controllers to the BR-6104K - software part

OpenWrt environment

OpenWrt provides all needed parts: an actual Kernel with SPI GPIO bitbanging and the MCP251x modul.

The software part is a little bit tricky.

SPI Interface

First you have to patch br-6104k.c. If you like to use the leds signalling network traffic with the led-gpio module, you have to comment the SPI ports out.
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),
};
The CAN controller is connected to following ports:
ADM5120_GPIO_P3L1 GPIO17 -> MISO
ADM5120_GPIO_P3L0 GPIO18 -> MOSI
ADM5120_GPIO_P4L1 GPIO20 -> SCK
ADM5120_GPIO_P4L0 GPIO21 -> CS
RESET Button GPIO2 -> INT
Another part is the board defintion: br-61xx.c
#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

The MCP2515 kernel module mcp251x.c needs an EDGE trigger (high->low), but the SoC only provides LEVEL interrupts on GPIO2. I didn't find a proper way to implement so a made a dirty hack of the mcp251x module:
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);

After compiling an installing can,can-dev,can-raw and mcp251x the ports should look like this:
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
dmesg should show something like this:
[   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
After starting the controller:
/usr/sbin/ip link set can0 type can bitrate 250000
/sbin/ifconfig can0 up
dmesg should show:
[   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
You also should see the corresponding interrupts:
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
BTW: The number of interrupts is always even for the mcp251x because of the workaround.

A look at the 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     


Now we are ready to use the CAN, e.g. controlling a Railroad can2udp (googenglish translation).
Reduced to the max

Impressum: