CAN + OpenWrt
This adapter is outdated. Please use the CAN-CAN Interface
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: