Mcp2515 Avr
Mcp2515 Avr
Mcp2515 Avr
DDR_CS |= (1<<P_CS);
PORT_CS |= (1<<P_CS);
1 de 17
C:
#define DDR_CS DDRB
#define PORT_CS PORTB
#define P_CS 2
return SPDR;
}
Como el bus SPI transmite y recibe desde pins distintos, se puede operar en full-duplex. Para recibir
un byte, se debe enviar un byte de contenido arbitrario y evaluar el dato del lado del receptor.
C:
// Enviar un dato por SPI
spi_putc(0xaa);
Escribir en registro
C:
void mcp2515_write_register( uint8_t adress, uint8_t data )
{
// Activar el /CS del MCP2515 (activo bajo)
PORT_CS &= ~(1<<P_CS);
2 de 17
spi_putc(SPI_WRITE);
spi_putc(adress);
spi_putc(data);
// Liberar el /CS
PORT_CS |= (1<<P_CS);
}
Leer registro
C:
uint8_t mcp2515_read_register(uint8_t adress)
{
uint8_t data;
spi_putc(SPI_READ);
spi_putc(adress);
data = spi_putc(0xff);
// Liberar el /CS
PORT_CS |= (1<<P_CS);
return data;
}
Otra funcionalidad práctica del MCP2515 es la modificación de bit. Eso permite setear o resetear bits
individuales. Se presenta un ejemplo de eso en el proceso de inicialización, que permite cambiar el
modo de operación:
spi_putc(SPI_BIT_MODIFY);
spi_putc(adress);
spi_putc(mask);
spi_putc(data);
// Liberar el /CS
PORT_CS |= (1<<P_CS);
}
Se debe notar que esta función opera solamente sobre ciertos registros (ver especificación p. 59). Se
envía primero una máscara que define los bits a afectar. Después vienen los datos significativos. Si
un bit está en 1 en la máscara y en 0 en el dato significativo, se resetea el bit que corresponde en el
registro. Si está en 1 en el dato significativo, se setea el bit en el registro. Si el bit de la máscara está
en 0, el registro no cambia.
3 de 17
Ahora podemos inicializar el MCP2515 para enviar y recibir mensajes. La única complejidad es la
configuración de la velocidad del bus CAN.
Para poder escribir en los registros de configuración del MCP2515, se debe poner primero en modo
de configuración. Sólo en este modo se pueden escribir los registros CNF1, CNF2, CNF3,
TXRTSCTRL y los filtros y máscaras.
Hay todavía algunos otros modos que voy a omitir acá ya que la especificación los describe muy
claramente. :-)
C:
// CAN Bitrate 125 kbps
#define R_CNF1 (1<<BRP2)|(1<<BRP1)|(1<<BRP0)
#define R_CNF2 (1<<BTLMODE)|(1<<PHSEG11)
#define R_CNF3 (1<<PHSEG21)
Interrupciones
Hay 8 fuentes distintas de interrupciones:
1. Message Error Interrupt
2. Wakeup Interrupt
3. Error Interrupt Enable (multiple sources in EFLG register)
4. Transmit Buffer 2 Empty Interrupt
5. Transmit Buffer 1 Empty Interrupt
6. Transmit Buffer 0 Empty Interrupt
7. Receive Buffer 1 Full Interrupt
4 de 17
8. Receive Buffer 0 Full Interrupt
Cuando se activan estas interrupciones, la línea de interrupción se activa y queda activa hasta que
la última fuente sea procesada.
En mi programa, usé solamente las interrupciones de “buffer lleno” para indicar que un nuevo
mensaje llegó. Obviamente, hay muchas otras posibilidades de uso de las interrupciones – más
detalles p.49 de la especificación técnica.
// Setear el pin
mcp2515_bit_modify( BFPCTRL, (1<<B0BFE), (1<<B0BFE));
El pin RX1BF se controla de la misma manera pero, obviamente, se puede usar como salida de
interrupción:
C:
// Usar RX0BF y RX1BF como Interrupciones
mcp2515_write_register( BFPCTRL, (1<<B1BFE)|(1<<B0BFE)|(1<<B1BFM)|(1<<B0BFM));
De la misma manera, se pueden usar los pins TxnRTS como GPIO o para gatillar el envío del
mensaje siguiente. La selección se hace con TXRTSXTRL (ver p.19 de la especificación técnica). El
control se hace de la misma manera que los RxnBF.
CLKOUT Pin
El MCP2515 ofrece la posibilidad de proporcionar una señal de clock a otros componentes. El pin
CLKOUT del MCP2515 genera la señal, de tal forma que se puede conectar a otro microcontrolador.
Además, contiene un divisor por 2, 4 o 8, que permite generar frecuencias adicionales. Con un clock
de 16MHz, se puede contar con una frecuencia de 16, 8, 4 o 2MHz.
El pin CLKOUT se controla con los 3 últimos bits del registro CANCTRL. Como el pin está activo por
defecto al alimentar el MCP2515, se puede contar con esta señal para arrancar el AVR. (Nota del
traductor: verificar que entendí bien...)
C:
// Resetear CLKOUT
// => fuerza la frecuencia de clock del MCP2515 en CLKOUT
mcp2515_bit_modify( CANCTRL, 0x07, (1<<CLKEN));
No obstante, se debe tener cuidado con el manejo del RESET: se debe evitar a toda costa de
5 de 17
conectar directamente el RESET del AVR con él del MCP2515, ya que eso puede impedir la
programación del AVR. Durante la fase de programación del AVR, se debe activar el RESET del
microcontrolador, lo que resetearía también el MCP2515 y deactivaría CLKOUT. En este caso, el AVR
no recibiría su clock. La única solución es separar las líneas de reset y poner un pull-up sobre el
MCP2515. Eso requiere que el microcontrolador efectúe un reset en software durante la fase de
inicialización para asegurar que el MCP2515 parte en un estado conocido.
Los bits del identificador se toman en cuenta solamente si el bit de máscara está en 1. En caso de
que todos los mensajes se deban interpretar (por ejemplo en fase de prueba), basta con forzar el bit
de máscara en 0. Recuerde que los bits de filtro y de máscara se pueden modificar sólo en modo de
configuración.
Ahora, un ejemplo de inicialización:
C:
void mcp2515_init(void)
{
// Inicializar la interfaz SPI del AVR
spi_init();
/*
* Configurar los Timings de bits (velocidad)
*
* Fosc = 16MHz
* BRP = 7 (dividir por 8)
* TQ = 2 * (BRP + 1) / Fosc (=> 1 uS)
*
* Sync Seg = 1TQ
* Prop Seg = (PRSEG + 1) * TQ = 1 TQ
* Phase Seg1 = (PHSEG1 + 1) * TQ = 3 TQ
* Phase Seg2 = (PHSEG2 + 1) * TQ = 3 TQ
*
6 de 17
* Bus speed = 1 / (Total # of TQ) * TQ
* = 1 / 8 * TQ = 125 kHz
*/
// BRP = 7
mcp2515_write_register( CNF1, (1<<BRP0)|(1<<BRP1)|(1<<BRP2) );
/*
* Configuración del filtro
*/
mcp2515_write_register( RXM1SIDH, 0 );
mcp2515_write_register( RXM1SIDL, 0 );
mcp2515_write_register( RXM1EID8, 0 );
mcp2515_write_register( RXM1EID0, 0 );
/*
* Configurar los pines de funciones
*/
7 de 17
fácilmente extender el código para soportar Ios de 29 bits.
El MCP2515 tiene 3 buffers de emisión independientes con prioridad configurable. Para enviar un
mensaje, se deben cargar los registros de ID, el campo de datos y gatillar el envío.
Para gatillar el envío, hay 3 posibilidades:
• Escribir los bits correspondientes en los registros TXBnCTRL
• Enviar el comando RTS por SPI
• Forzar a 0 el pin TXnRTS del buffer que corresponde.
Si el microcontrolador tiene pocos pines libres, se recomienda manejar todo por SPI. Eso ahorra
entre 1 y 3 I/Os según la cantidad de buffer en uso en la aplicación sin afectar demasiado la
latencia.
El envío se puede hacer como sigue:
C:
/* Enviar un mensaje sobre buffer 0
* 2 bytes de datos (0x04, 0xf3)
* Standard ID: 0x0123
*/
uint16_t id = 0x0123;
// Configurar ID
mcp2515_write_register(TXB0SIDH, (uint8_t) (id>>3));
mcp2515_write_register(TXB0SIDL, (uint8_t) (id<<5));
// Datos
mcp2515_write_register(TXB0D0, 0x04);
mcp2515_write_register(TXB0D1, 0xf3);
8 de 17
uint8_t length;
uint8_t data[8];
} CANMessage;
// Cargar datos
message.id = 0x0123;
message.rtr = 0;
message.length = 2;
message.data[0] = 0x04;
message.data[1] = 0xf3;
// Enviar mensaje
can_send_message(&message);
La función de emisión que corresponde se puede estructurar como sigue:
C:
void can_send_message(CANMessage *p_message)
{
uint8_t length = p_message->length;
// Configurar ID
mcp2515_write_register(TXB0SIDH, (uint8_t) (p_message->id>>3));
mcp2515_write_register(TXB0SIDL, (uint8_t) (p_message->id<<5));
else
{
// Configurar largo de mensaje
mcp2515_write_register(TXB0DLC, length);
// Datos
for (uint8_t i=0;i<length;i++) {
mcp2515_write_register(TXB0D0 + i, p_message->data[i]);
}
}
9 de 17
SPI particular (Read Status Instruction):
/* Byte de estado:
*
* Bit Función
* 2 TXB0CNTRL.TXREQ
* 4 TXB1CNTRL.TXREQ
* 6 TXB2CNTRL.TXREQ
*/
if (bit_is_clear(status, 2)) {
address = 0x00;
}
else if (bit_is_clear(status, 4)) {
address = 0x02;
}
else if (bit_is_clear(status, 6)) {
address = 0x04;
}
else {
/*Todos los buffers estan ocupados, no se puede transmitir el mensaje*/
return 0;
}
// Configurar Standard ID
spi_putc((uint8_t) (p_message->id>>3));
spi_putc((uint8_t) (p_message->id<<5));
// Extended ID
spi_putc(0x00);
spi_putc(0x00);
10 de 17
if (length > 8) {
length = 8;
}
// Datos
for (uint8_t i=0;i<length;i++) {
spi_putc(p_message->data[i]);
}
}
PORT_CS |= (1<<P_CS); // CS = High
/* Enviar el mensaje CAN los ultimos 3 bits del comando RTS indican
* a que buffer se deben enviar los datos.
*/
PORT_CS &= ~(1<<P_CS); // CS = Low
if (address == 0x00)
{
spi_putc(SPI_RTS | 0x01);
}
else {
spi_putc(SPI_RTS | address);
}
PORT_CS |= (1<<P_CS); // CS = High
return 1;
}
En teoría, puede suceder que un mensaje se quede trancado si se envían muchos mensajes uno trás
el otro, ya que el primer buffer tiene una prioridad más alta y que se transmite primero. Para evitar
eso, se debería configurar la prioridad del buffer actual con la menor prioridad e incrementar la
prioridad de los otros buffers. Eso aseguraría que los mensajes serían transmitidos en la secuencia
correcta.
Para verificar que algo está sucediendo, se necesita una capa de recepción, lo que vamos a ver
ahora.
11 de 17
Recibir mensajes
Para saber si llegó un mensaje, hay varias posibilidades, según la configuración:
• a través de los pines RXnBF
• por el pin de interrupción
• por comando SPI
Obviamente, estas posibilidades no son exclusivas y se pueden combinar. Una posibilidad
recomendable es, por ejemplo, conectar la señal de interrupción al AVR para detectar la llegada de
un mensaje y preguntar por SPI en qué buffer está. Como la placa de prueba de CAN tiene esta
configuración, le voy a presentar esta solución en más detalle:
Para poder usar el pin de interrupción como modo de detección de llegada de mensaje, se debe
configurar como corresponde. Como ya lo hemos hecho en la inicialización (ver sección
Interrupciones), podemos sencillamente esperar hasta que el pin de interrupción baje y que nos
muestre así, que tenemos un mensaje a disposición.
Para detectar en qué buffer está el mensaje o de qué tipo es (ID estándar, ID extendido, Remote
Transfer Request, etc.), el MCP2515 soporta un comando dedicado (sino, se puede también, leer
CANINTF):
C:
uint8_t mcp2515_read_rx_status(void)
{
uint8_t data;
spi_putc(SPI_RX_STATUS);
data = spi_putc(0xff);
// Los datos se van a repetir, así que se requiere leer solo uno de los 2
spi_putc(0xff);
// Liberar /CS
PORT_CS |= (1<<P_CS);
return data;
}
Verificamos primero si el pin de interrupción está bajo. Si es el caso, un mensaje está disponible.
C:
uint8_t can_get_message(CANMessage *p_message)
{
// Leer estado
uint8_t status = mcp2515_read_rx_status();
if (bit_is_set(status,6))
{
// Mensaje en buffer 0
PORT_CS &= ~(1<<P_CS); // CS Low
spi_putc(SPI_READ_RX);
12 de 17
}
else if (bit_is_set(status,7))
{
// Mensaje en buffer 1
PORT_CS &= ~(1<<P_CS); // CS Low
spi_putc(SPI_READ_RX | 0x04);
}
else {
/* Error: no hay mensaje disponible */
return 0xff;
}
// Leer Standard ID
p_message->id = (uint16_t) spi_putc(0xff) << 3;
p_message->id |= (uint16_t) spi_putc(0xff) >> 5;
spi_putc(0xff);
spi_putc(0xff);
// Leer el largo
uint8_t length = spi_putc(0xff) & 0x0f;
p_message->length = length;
// Leer datos
for (uint8_t i=0;i<length;i++) {
p_message->data[i] = spi_putc(0xff);
}
PORT_CS |= (1<<P_CS);
if (bit_is_set(status,3))
{
p_message->rtr = 1;
}
else {
p_message->rtr = 0;
}
La función de recepción recupera, con los comandos SPI previamente descritos, la información sobre
13 de 17
el tipo de mensaje y el buffer en él que está el mensaje. Después, se deben leer los datos en el
registro que corresponde. Aquí, las tramas con ID extendido son descartadas. (Nota del traductor:
verificar si es el caso...).
La función retorna el número del filtro que permitió en ingreso del mensaje. Si no hay nuevo
mensaje, la función retorna el error 0xff.
Con eso, podemos finalmente enviar y recibir mensajes y empezar a escribir el software relevante
para solucionar el problema :-)
UPDATE: En la sección Downloads, hay un pequeño programa para la placa de prueba de CAN. Si el
diseño usa una frecuencia distinta para el quartz, se debe ajustar en el Makefile. Si /CS y /INT se
conectan a pines distintos, se debe configurar en defaults.h . Los valores para P_MOSI, P_MISO y
P_SCK se deben ajustar a la implementación hardware del SPI. Este programa se debe compilar sin
problema con otros AVR.
Inicializa el MCP2515, lo pasa al modo Loopback, envía un mensaje (internamente), recibe el
mensaje, pasa a modo normal, reenvía el mensaje al bus CAN y espera hasta que llegue una
respuesta.
Biblioteca
Se acaba de crear una biblioteca CAN que se basa en la aplicación descrita acá.
Downloads:
mcp2515_demo.zip [17.49 kB]
Comentarios
# Manfred Feitzinger - 15. November 2006, 12:46:
Hola, lindo y bueno. No conozco bien GCC. Lo tienes también para Bascom, o me podrías decir como
puedo escribir en los registros?
Gracias Manfred
# Fabian Greif - 15. November 2006, 21:52 :
En principio, no debería ser difícil adaptar el código para Bascom. Tienes que reemplazar
spi_init() y spi_putc() por su equivalente, adaptar el manejo de CS y, sino, deberías poder
reusar el código sin muchos cambios. Como no uso Bascom, desgraciadamente, no puedo probar.
Aquí encontrarás unos ejemplos que otro escribió.
# Frank - 2. Dezember 2006, 14:42:
Hola. Hay algún programa completo basado en este tutorial que muestre el manejo del MCP2515? O
será que no vi el enlace? A propósito, buen trabajo! Saludos, Franck
# Ralf Handrich - 11. Dezember 2006, 16:37:
Hola,
yo también te felicito. El circuito y el software son bárbaros!
14 de 17
Armé 2 módulos más. El bus CAN funciona sin problema. Sin embargo, tengo un pequeño problema
con el reset del módulo. Cada vez que activo el reset (o que enciendo la placa), se envían 2 o 3
mensajes sin que yo envíe algo explícitamente. Me fijé que es una repetición del último mensaje
enviado antes del reset. Los IDs parecen ser arbitrarios. Dónde está el problema? Qué puedo hacer
para evitarlo?
Saludos Ralf
# Maxx - 30. Dezember 2006, 01:21:
Hola todos! Primero, muchas gracias por este súper tutorial.
Mientras estaba compilando, me encontré con unas cositas. Dónde se definen SPI_RESET o SPI_CS?
Me gustaría compilar mi proyecto con GCC. Tiene alguien un ejemplo funcional con Makefile que me
podría proporcionar?
Gracias!
Saludos Maxx
# Volker Bosch am 4. Januar 2007, 13:06 :
Hola Fabian,
muchas gracias por el tutorial sobre el control del MCP2515 – me facilitó mucho la vida para
empezar con mi proyecto.
Tengo 3 comentarios sobre tu texto:
1. Escribes que el MCP2515 enviaría básicamente el byte de estado 2 veces. Como interpreto yo
la especificación, envía el byte de estado una cantidad arbitraria de veces, es decir que
podrías omitir la segunda lectura del byte de estado.
2. No entiendo el último ejemplo, es la misma función de transmisión de mensaje por el primero
de los 3 buffers libre?
3. Según la especificación, el AVR Mega16 es muy sensible a cambios de frecuencia de clock.
Atmel recomienda activar el RESET durante el cambio. No tuve problema pero, ahora, activo
1 reset por el watchdog después de cambiar la frecuencia.
Saludos, Volker.
# Björn - 5. Januar 2007, 20:52:
Hola,
tu enlace hacia el #defines no funciona con la nueva página!
Saludos
Björn
# Fabian Greif - 9. Januar 2007, 00:08 :
Lo corregí.
# Manfred Feitzinger - 23. Februar 2007, 09:57:
Hola Fabian. Traté de portar tu programa a Bascom. Creo que la comunicación con el MCP2515
funciona pero si, por ejemplo, leo los registros después del reset, recibo valores distintos a los que
escribí. Por ejemplo, escribo 0x02 en el registro DCF1 y leo 0x06. De dónde puede venir?
# Fabian Greif - 23. Februar 2007, 17:50 :
Acerca de un problema de valores erróneos, verificaría primero si no viene de un exceso de
velocidad en el bus SPI, ya que puede venir de un dato incorrectamente transmitido. Cómo se ve la
transmisión SPI?
15 de 17
A propósito, para qué sirve el registro DCF1?
# Manfred Feitzinger - 26. Februar 2007, 08:53:
Hola Fabian. Me equivoqué. Quería decir CNF1. Probé con una frecuencia de 50kbytes/s. El MCP2515
tiene un quartz de 16MHz, el ATMega16 corre con una frecuencia interna de 1MHz.
# Stephan - 6. März 2007, 14:05 :
Hola, me gustaría manejar el MCP2515 mediante un AVR butterfly. Como no tengo mucho
conocimiento en programación, quería saber si tu programa funciona también para el Butterfly.
Saludos Stephan
# Fabian Greif - 6. März 2007, 14:24 :
No conozco el Butterfly pero mientras usa un núcleo AVR normal (idealmente con SPI en hardware) y
tiene un mínimo de 1 a 2KB de Flash, no debería haber ningún problema para portar el código.
# Frank Lorenzen - 24. Mai 2007, 22:34 :
Hola, tengo una pregunta acerca de la función mcp2515_init(). Al final, reinicializas el valor de
CANCTRL con: mcp2515_bit_modify( CANCTRL, 0xE0, 0);
Los valores no me suenan. Según la especificación, el valor por defecto de CANCTRL es 11100000.
Los parámetros no deberían ser “0xE0, 0xE0” o “0xE0, 0xFF”? Creo que sabes lo que quiero decir.
Me podrías explicar?
Saludos Frank
# Fabian Greif - 25. Mai 2007, 11:42:
Quiero dejar el MCP2515 en modo normal. Para eso, se deben borrar todos los bits REQOPn. Es
precisamente lo que alcanzo con este comando ;-)
Es verdad que el valor por defecto de REQOP2..0 es 111. Sin embargo, la especificación dice que
este estado es inválido y no se debe usar en una aplicación (ver especificación p.56)
# Maximilian - 2. Juli 2007, 13:09 :
Quizás podrías dar una pequeña descripción de cómo se conectan, por ejemplo, 3 nodos y cómo se
filtra el tráfico?
# Fabian Greif - 6. Juli 2007, 17:39 :
...
Cada placa representa un nodo. Lo único que tienes que hacer es conectar respectivamente CANH,
CANL y GND de cada nodo y terminar el bus en cada punta con una resistencia de 120 ohms.
Un ejemplo de control multinodo se encuentra aquí.
# Fabian Greif - 10. Juli 2007, 15:41 :
Entonces, por lo que veo, sólo se puede emitir o recibir.
16 de 17
Quisiera manejar el controlador con 100kbit/s para conectarlo al “komfortbus” de
VW/Audi/Skoda/Seat. Sin embargo, tengo problemas para configurar el registro de timings. Me
podrías enviar los valores a programar?
# Fabian Greif - 11. August 2007, 17:49 :
El calculador en línea de www.kvaser.com indica que para 100 kBit/s con un punto de muestra
(“sample“???) de 75%, necesitas lo siguiente:
CNF1 = 0x09
CNF2 = 0x91
CNF3 = 0x01
17 de 17