CAN BUS + ARDUINO

Hola,

Siguiendo jugando con los buses de campo, despues del RS-485, tiene que venir bus CAN!!! http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1245014666

Es una mejora del bus RS485,y fue pensado para automoción,aunque hoy en día ha sido implementado en el entorno industrial.

Ninguno de los micros usados en Arduino dispone de controlador CAN, por lo que es necesario un controlador externo, en concreto MCP2515. Yo he usado la placa de http://www.avrcard.com/products/can_adapter.htm

Se puede ver, que es más costoso montar una red CAN que RS485 a nivel de hardware.

Para la conexión en topología de bus, se puede tomar lo mismo que escribi que para RS485.

Me he basado en un tutorial que encontre en el foro de TODOPIC traducido al español de la página de kreatives-chaos.

Existe un proyecto de CAN para Arduino llamado Arcan. Puedes seguir los avances de Raúl en =>http://www.arcan.es/ Muchas de las funciones son parecidas, aunque he ampliado un poco el poder seleccionar 3 velocidades del bus: 1 Mbit/s, 500 Kbits/s y 125 Kbits/s (Es lo que más se encuentra en automoción). He dejado transmisión fija de los 8 bytes de datos y trama standard (CAN 2.0 A). También esta cálculado el Bit Timing para un cristal de 16 Mhz, que es el que viene con la placa de avrcard.

De momento sólo he implementado enviar dato. En el ejemplo, se envian 4 tramas con ID 100h,200h,300h y 400h con bytes constantes. Es un ejemplo tonto, pero bueno, era para probar que todo vaya bien, es fácil extrapolarlo al envio de un dato analógico adquirido.(0-1023) por lo que en 2 Bytes se envia sin problemas. La velocidad que se consigue de envio, mirandola con un sniffer CAN de la casa PEAK es de 1ms=1000 Hz!!! A 1Mbit/s ó 500 Kbits/s.

Para el Bit Timing, que reconozco que no es fácil de entender, existe un programa llamado Microchip CAN Bit Timing Calculator. En el código, he puesto un ejemplo a mano para calcular la configuración con 500 Kbits/s.

A ver si alguien se anima a trastear con el bus CAN!!! ;)

Saludos

Igor R.

Ahora toca el código del ejemplo. Lo único que no tengo claro del todo es la gestión de prioridades de los 3 buffers que tiene el controlador.

//Ejemplo transmisión trama CAN 2.0A
//Basado en el trabajo del Tutorial MCP2515 http://www.kreatives-chaos.com/
//Igor Real



#define P_CS       10
#define P_SCK      13
#define P_MOSI     11
#define P_MISO     12
#define P_INT      9
//-------------------------
//MCP2515
#define CNF3        0x28
#define CNF2        0x29
#define CNF1        0x2A
#define CANINTE     0x2B
#define RXB0CTRL    0x60
#define RXB1CTRL    0x70
#define RXM0SIDH    0x20
#define RXM0SIDL    0x21
#define RXM0EID8    0x22
#define RXM0EID0    0x23
#define RXM1SIDH    0x24
#define RXM1SIDL    0x25
#define RXM1EID8    0x26
#define RXM1EID0    0x27
#define CANCTRL     0x0F
#define TXB0SIDH    0x31
#define TXB0SIDL    0x32
#define TXB0DLC     0x35
#define TXB0D0      0x36
#define TXB0CTRL    0x30
//-------------------------

byte cond_inicial=0;



void setup()
{
  Serial.begin(9600);
        
  pinMode(P_CS,OUTPUT);
      
  pinMode(P_SCK,OUTPUT);
  pinMode(P_MOSI,OUTPUT);
  pinMode(P_MISO,INPUT);
  pinMode(P_INT,INPUT);

  // activamos el SPI de Arduino como Master y Fosc/2=8 MHz
  SPCR = (1<<SPE)|(1<<MSTR) | (0<<SPR1)|(0<<SPR0);
  SPSR = (1<<SPI2X);
  

  
}

void loop()
{
  if (cond_inicial==0)
  {      
    Serial.println("Empezamos...");
    mcp1515_init(1);  //Indicamos la velocidad del bus: 1Mbit/s, 500 ó 125 Kbits/s
    cond_inicial=1;
  }
  byte bool_send;
  //Envio de tramas con la funcion can_send_message
  //Los parametros que se le pasan son ID (Can 2.0A) y los 8 bytes de datos)
  //Esta configurado para enviar siempre 8 bytes
  //Devuelve 0 si no se ha podido enviar el msje.
  bool_send=can_send_message(0x100,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08);
  bool_send=can_send_message(0x200,0xFF,0xEE,0xDD,0xCC,0xBB,0xAA,0x99,0x88);
  bool_send=can_send_message(0x300,0x00,0x03,0x06,0x09,0x0B,0x0D,0x0F,0x00);
  bool_send=can_send_message(0x400,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88);
  //Se estan enviando las tramas con un periodo de 1 ms = 1000 Hz (con Speed de 500 y 1 Mbit/s)
  
}

//-----------------------------------
//ESCRIBIR-RECIBIR BYTE VIA SPI
//-----------------------------------
byte SPI_ReadWrite( byte data )
{
  SPDR = data;
      
  // Esperar hasta final de transmisión
  while( !( SPSR & (1<<SPIF) ) );
      
  return SPDR;
}
//-----------------------------------
//ESCRIBIR REGISTRO EN MCP2515
//-----------------------------------
void mcp2515_write_register(uint8_t address,uint8_t data)
{
  digitalWrite(P_CS,LOW);
  SPI_ReadWrite(0x02);    //Instrucción de WRITE
  SPI_ReadWrite(address);
  SPI_ReadWrite(data);
  digitalWrite(P_CS,HIGH);  
}
//-----------------------------------
//LEER REGISTRO EN MCP2515
//-----------------------------------
uint8_t mcp2515_read_register(uint8_t address)
{
  uint8_t data;
  
  digitalWrite(P_CS,LOW);
  SPI_ReadWrite(0x03);     //Instrucción de READ
  SPI_ReadWrite(address);
  data=SPI_ReadWrite(0xff);
  digitalWrite(P_CS,HIGH); 
 
  return data; 
}
//-----------------------------------
//INIT DEL MCP2515
//-----------------------------------
void mcp1515_init(uint16_t speed)
{
  digitalWrite(P_CS,LOW);
  SPI_ReadWrite(0xC0);     //Instrucción de RESET  
  digitalWrite(P_CS,HIGH); 
  delay(10);  
  //-------------------------------------------------
  //CONFIGURAR BIT TIME
  //Fosc=16 MHz significa Tosc=62.5 nsec
  //Eligiendo BRP=1
  //BRP=1     PRESCALER
  //Tq=2*(BPR+1)/Fosc=250 nsec
  //Si velocidad=500 Khz, Veces de Tq=1/(500000*250e-9)=> El bit time tiene que ser 8*Tq
  //-------------------------------------------------
  //Usar el software Microchip CAN Bit Timing Calculator
  //-------------------------------------------------
  //Requerimientos:
  //Prop Seg + Phase Seg1 >=Phase Seg2  => 1+3 >= 3
  //Prop Seg + Phase Seg1 >=Tdelay      => 1+3 >= 2
  //Phase Seg2 > Sync Jump Width        => 3   > SJW  
  //Por lo que teniendo en cuenta los requerimientos
  //Sync Seg                  =1 Tq
  //Prop Seg=(PRSEG+1)*Tq     =1 Tq
  //Phase Seg1=(PHSEG1+1)*Tq  =3 Tq significa PHSEG1=2 (010b)
  //Phase Seg2=(PHSEG2+1)*Tq  =3 Tq significa PHSEG2=2 (010b) 
  //t bit=1+1+3+3=8 <-OK
  //SJW=1
  //Por lo que para 500Kbits/s@16Mhz:
  //CNF1=0x01h(00000001b),CNF2=0x90h(10010000b),CNF3=0x02h(00000010b)
  //-------------------------------------------------
  //Para 1Mbit/s@16Mhz:
  //CNF1=0x00h(00000000b),CNF2=0x90h(10010000b),CNF3=0x02h(00000010b)
  //Para 125Kbit/s@16Mhz:
  //CNF1=0x07h(00000111b),CNF2=0x90h(10010000b),CNF3=0x02h(00000010b)  
  
  switch(speed){
    case 1:
      mcp2515_write_register(CNF1,0x00);
      mcp2515_write_register(CNF2,0x90);
      mcp2515_write_register(CNF3,0x02);
      Serial.println("Caso 1");
      break;
    case 500:
      mcp2515_write_register(CNF1,0x01);
      mcp2515_write_register(CNF2,0x90);
      mcp2515_write_register(CNF3,0x02);
      Serial.println("Caso 2");
      break;
    case 125:
      mcp2515_write_register(CNF1,0x07);
      mcp2515_write_register(CNF2,0x90);
      mcp2515_write_register(CNF3,0x02);
      Serial.println("Caso 3");
      break;
  }

  //Activamos Interrupcion de RX
  mcp2515_write_register(CANINTE,(1<<1)|(1<<0)); //RX1IE y RX0IE (los dos buffers)
  //Filtros
  //Bufer 0: Todos los msjes
  mcp2515_write_register(RXB0CTRL,(1<<6)|(1<<5)); //RXM1 y RXM0 para filter/mask off
  //Bufer 1: Todos los msjes
  mcp2515_write_register(RXB1CTRL,(1<<6)|(1<<5)); //RXM1 y RXM0 para filter/mask off
  //Borrar bits de máscara de recepción
  mcp2515_write_register( RXM0SIDH, 0 );
  mcp2515_write_register( RXM0SIDL, 0 );
  mcp2515_write_register( RXM0EID8, 0 );
  mcp2515_write_register( RXM0EID0, 0 );
  mcp2515_write_register( RXM1SIDH, 0 );
  mcp2515_write_register( RXM1SIDL, 0 );
  mcp2515_write_register( RXM1EID8, 0 );
  mcp2515_write_register( RXM1EID0, 0 );
  //Pasar el MCP2515 a modo normal
  mcp2515_write_register(CANCTRL, 0);
}

//-----------------------------------
//ENVIO MSJE CAN
//-----------------------------------
byte can_send_message(uint16_t id,byte D0,byte D1,byte D2,byte D3,byte D4,byte D5,byte D6,byte D7)
{

  //Miramos que buffer esta libre
  byte status;
  digitalWrite(P_CS,LOW);
  SPI_ReadWrite(0xA0);    //Read Status
  status=SPI_ReadWrite(0xFF);
  digitalWrite(P_CS,HIGH);
  //Read Status devuelve
  //Bit0.- CANINTF.RX0IF
  //BIT1.- CANINTF.RX1IF
  //BIT2.- TXB0CNTRL.TXREQ
  //BIT3.- CANINTF.TX0IF
  //BIT4.- TXB1CNTRL.TXREQ
  //BIT5.- CANINTF.TX1IF
  //BIT6.- TXB2CNTRL.TXREQ
  //BIT7.- CANINTF.TX2IF

  //Busco un buffer libre 
  byte buffer_free;
  byte address;
  if (bit_is_clear(status,2)){
    buffer_free=1;
    address=0x31;    //Registro Standard ID buffer 0
  }else if (bit_is_clear(status,4)){
    buffer_free=2;
    address=0x41;    //Registro Standard ID buffer 1
  }else if (bit_is_clear(status,6)){
    buffer_free=4;
    address=0x51;    //Registro Standard ID buffer 2
  }else{
    return 0;    //No hay buffer libre, no se ha transmitido msje  
  }

  //Configuración de las prioridades de envio de buffers
  //Es independiente de las prioridades definidas del bus CAN =>(Mayor prioridad=Menor ID)
  //Se debe poner siempre el buffer actual(último msje)con la prioridad menor
  switch (address){
    case 0x31:
      mcp2515_write_register(0x30,(0<<1)|(0<<0));  //Menor prioridad
      mcp2515_write_register(0x40,(0<<1)|(1<<0));
      mcp2515_write_register(0x50,(1<<1)|(0<<0));  //Mayor prioridad
      //Serial.println("Buffer 0");
      break;
    case 0x41:
      mcp2515_write_register(0x30,(0<<1)|(1<<0));  
      mcp2515_write_register(0x40,(0<<1)|(0<<0));  //Menor prioridad
      mcp2515_write_register(0x50,(1<<1)|(0<<0));  //Mayor prioridad
      //Serial.println("Buffer 1");
      break;
    case 0x51:
      mcp2515_write_register(0x30,(1<<1)|(0<<0));  //Mayor prioridad  
      mcp2515_write_register(0x40,(0<<1)|(1<<0));  
      mcp2515_write_register(0x50,(0<<1)|(0<<0));  //Menor prioridad
      //Serial.println("Buffer 2");
      break;    
  }
  // Configurar ID
  mcp2515_write_register(address, (uint8_t) (id>>3));
  mcp2515_write_register(address+1, (uint8_t) (id<<5));
  // Configurar el largo del dato(8bytes) + RTR=0. 
  mcp2515_write_register(address+4, 8);     //Registro TXBnDLC
  // Bytes de Datos -> Registro TXBnDm
  mcp2515_write_register(address+5,D0);   //Byte0 
  mcp2515_write_register(address+6,D1);   //Byte1
  mcp2515_write_register(address+7,D2);   //Byte2
  mcp2515_write_register(address+8,D3);   //Byte3
  mcp2515_write_register(address+9,D4);   //Byte4  
  mcp2515_write_register(address+10,D5);  //Byte5
  mcp2515_write_register(address+11,D6);  //Byte6
  mcp2515_write_register(address+12,D7);  //Byte7  

  // Enviar mensaje CAN
  digitalWrite(P_CS,LOW);
  SPI_ReadWrite(0x80 | buffer_free);  //RTS(Message Request To Send)
  digitalWrite(P_CS,HIGH); 
  return 1;
}

Links interesantes: http://www.siwawi.arubi.uni-kl.de/avr_projects/can/index.html

http://www.kreatives-chaos.com/artikel/ansteuerung-eines-mcp2515

;)

Muchas gracias Igor. ;D

Hola!

El montaje de http://www.avrcard.com/products/can_adapter.htm también está disponible en http://www.mikroe.com/ en otro formato.

Yo también estoy trabajando en algo parecido. La cosa está en estado embrionario, pero he recogido la idea en esta web:

http://sites.google.com/site/freeduinoprojekt/can-shield/hardware-board

Se trata de tener un CAN-Shield bastante parecido al ArCAN, pero mucho más dinámico con un DIPswitch que permita programar el número de nodo o los baudios.

Tengo el montaje en una placa de puntos y estoy trabajando en las librerías, que son bastante parecidas a las del ArCAN.

Mi idea es tener un driver de CAN y añadirle librerías para extenderlo a CANopen, que es la opción a nivel industrial. A partir de ahí, se le pueden colgar sensores y demás dispositivos...

Saludos,

Hola,

Yo esta semana me pondré con la parte de recepción, ya que la de enviar funciona bastante bien. Y la he probado a las 3 velocidades que me interesaban(1Mbit/s,500 y 125 Kb/s) y enviando a una frecuencia de 1000 Hz y no ha habido problemas....

Tiene pinta, a priori, de ser algo más complicado. Sobretodo porque hay que implementar un buffer software donde ir colocando los mensajes que entren. (algo como lo que hay implementado para el serial).

También me tengo que mirar de hacerme una rutina de manejo de los filtros.

¿Tienes hecho algo de esto?

Salu2

Igor R.

Hola!

Vas mucho más avanzado que yo.

Lo del CANopen va para largo, pero ya llegará. Tengo varias referencias de código y alguna cosa saldrá.

En cuanto a los filtros,

/**************************************************************************
DOES: This function implements the initialization of a CAN ID hardware
filter as supported by many CAN controllers.
RETURNS: 1 if filter was set
2 if this HW does not support filters
(in this case HW will receive EVERY CAN message)
0 if no more filter is available
**************************************************************************/
boolean mcp2515::SetCanFilter(char number,int CANID)
{
if (number<0) || (number>5) return false;

// copy current operation mode
char mode = RdReg(CANSTAT) >> 5;

// set MPC2515 in operation mode
SetMode(CONFIGURATION);

byte2 SID = SetSID(CANID); // adapt CANID to SID structure
byte SIDH_add; // get pointer to filter

switch (number) {
case 0: SIDH_add = RXF0SIDH;
break;
case 1: SIDH_add = RXF1SIDH;
break;
case 2: SIDH_add = RXF2SIDH;
break;
case 3: SIDH_add = RXF3SIDH;
break;
case 4: SIDH_add = RXF4SIDH;
break;
default: SIDH_add = RXF5SIDH;
break;
}

digitalWrite(_cs, LOW); // call MCP2515 for SPI link
Spi.transfer(d2515Wrt); // send operation code to MCP2515
Spi.transfer(SIDH_add); // point to filter address
Spi.transfer(SID.byteHi);
Spi.transfer(SID.byteLo);
digitalWrite(_cs, HIGH); // close link

// return to previous operation mode
SetMode(mode);

return true;
}

O sea,

  1. Poner el MCP2515 en modo “configuración” y guardar el anterior modo para poder volver;
  2. Adaptar el ID del telegrama al formato del MCP2515 (míratelo en el manual y verás que se divide en dos…);
  3. Escribir el ID sobre el filtro que deseemos;
  4. Salir.

No lo tengo probado aún. Ya me dirás qué tal!

Saludos,

Se trata de tener un CAN-Shield bastante parecido al ArCAN, pero mucho más dinámico con un DIPswitch que permita programar el número de nodo o los baudios.

Tal como está diseñado ArCan prevé la expansión de nuevas ideas, como la del pdip-swicht para la configuración del número de nodo y/o el baudrates, tan solo sería colocar un shield más encima con la necesidades que demande la aplicación. Ten en cuenta que si hubiese puesto el pdip-swicht tendría muchas menos entradas y salidas disponible para otras aplicaciones, como por ejemplo el control domótico.

Al igual, que como hablé con Igor, la interrupción del mcp2515 no está rutada a la interrupción externa de Arduino, esto lo hice por aumentar la compatibilidad con otros shield del mercado, Su corrección es aún más fácil que la anterior, tan solo con puentear dos pines del conector de expansión es suficiente para usar la int externa.

Cuando diseñe ArCan todos estos detalles los plantee y supuse que esta era la mejor forma de afrontarlos con el objetivo de crear una plataforma de propósito general lo más abierta posible.

Un saludo y bravo Igor por animarte con CAN

Otro link, interesantísimo!!

http://projekt.auml.se/start

:)

Hola Igor,
Estoy tratando de hacer un proyecto similar. Tengo el convertidor CAN-SPI y estoy tratando de enviar el ID = 000, datos Longitud = 2, Byte 1 Byte = 01 y 2 = 00. Quiero enviar esta información a mi Arduino a los controladores CAN. el convertidor CAN-SPI está usando el MCP2515 mismo chip. ¿Puedes por favor me ayude. Su ayuda será muy apreciada.

Los datos que quiero enviar es ID=000, Data Length =2, Byte1 = 01 Byte 2 = 00.

Hola,

Recopile toda la informacion en:

http://real2electronics.blogspot.com/2010/06/can-bus-mcp2515.html

Saludos

Igor R.

Una nueva libreria con Arduino https://github.com/franksmicro/Arduino/tree/master/libraries/MCP2515

Sparkfun vende una shield para CAN... http://www.sparkfun.com/products/10039

;)