Mostrando entradas con la etiqueta atmega168. Mostrar todas las entradas
Mostrando entradas con la etiqueta atmega168. Mostrar todas las entradas

Timer 2: Introducción

.

 


El microcontrolador de las placas Arduino dispone de 3 módulos Contador/Timers (dos de 8 bits y uno de 16 bits). Se trata de hardware independiente al microprocesador que puede ejecutar tareas paralelas sin detener el flujo del programa. Algunas de éstas tareas son contador, medida precisa de tiempos, generador de ondas, .... Estos Timers son usados internamente por el core de Arduino para sus funciones internas, como pueden ser millis, delay, analogWrite,...

Aquí vamos a ver cómo trabajar con el Timer 2, el cual es de 8 Bits. El Timer 2 es usado internamente en el core Arduino para generar el PWM por los pines 3 y 11, por lo que no se podrá usar dicha función en conjunto con el código propuesto más adelante.

Para empezar vamos a ver el funcionamiento de dicho contador en modo Normal (Normal mode).
Se trata del modo más sencillo, en el cual el el contador se incrementa a cada pulso de reloj  hasta llegar a su máximo (8 bits=255) y se reinicia de nuevo desde 0. Es decir 0,1,2,...255 -> 0,1,...,255 -> 0,1,...
En el momento que el contador vuelve a 0 (registro TCNT2), se activa el  flag de  "overflow" (TOV2).  Se puede configurar una interrupción (overflow del timer) cuando ésto ocurre. Dicha interrupcion se encarga de resetear dicho el flag.

Notas:
El flag es un bit que cambia su estado, como si fuera una "alarma" para avisar al programador que un evento ha ocurrido. Dicho bit (TOV2) de overflow se encuentra en el registro TIFR2.
Una interrupción es una señal recibida por el microcontrolador, que interrumpe la ejecución del código actual y cambia a la rutina que atiende dicha interrupción.

Normalmente el cristal usado en la mayoría de las placas Arduino es de 16 MHz (cuidado que algunas como LilyPad van a 8Mhz), lo que significa que la resolución máxima es de 1/16.000.000= 62.5 ns. Es decir, que el contador alimentado por la frecuencia reloj del micro, incrementa en uno su valor cada 62.5 ns, y tendría su overflow cada 256*1/16000000=0.016ms.

Se puede observar que es un tiempo muy pequeño. Para poder tener más rangos, el timer dispone de PRESCALER. Ésto es simplemente un divisor de la frecuencia del reloj que le esta alimentando al timer. Los PRESCALER disponibles son 1,8,32,64,128,256 y 1024.


El lenguaje Arduino no dispone de funciones propias para configurar los timer. Los registros internos del microcontrolador para configurar el Timer2 son TCCR2A, TCCR2B, TNT2, OCR2A,OCR2B, TIMSK2,TIFR2,ASSR y GTCCR. Lo mejor para ampliar conocimientos acerca de todas las posibilidades que existen es el datasheet del propio microcontrolador.

En el registro TCCT2B se disponen los bits CS22,CS21 y CS20 (bit 2,1 y 0). Dichos bits son los encargados de configurar el PRESCALER del timer.
Veamos las combinaciones de CS22 - CS21 - CS20 respectivamente.
  • 0 - 0 - 0 => No Clock
  • 0 - 0 - 1 => No Prescaling  [0.016 ms]
  • 0 - 1 - 0 => Prescaler=8  [0.128 ms]
  • 0 - 1 - 1 => Prescaler=32  [0.512 ms]
  • 1 - 0 - 0  => Prescaler=64  [1.024 ms]
  • 1 - 0 - 1  => Prescaler=128  [2.048 ms]
  • 1 - 1 - 0  => Prescaler=256  [4.096 ms]
  • 1 - 1 - 1  => Prescaler=1024  [16.384 ms]

He preparado un programa, el cual conmuta el Pin 13 de Arduino cada vez que se produce la interrupción de Overflow. He recogido la salida de dicho pin con un Analizador Lógico. Lo que se observa, es una señal cuadrada, es decir el pin esta conmutando de estado ALTO="1" a estado BAJO="0" según la configuración de los bits CS22-CS21-CS20.


(click para agrandar)

Para lograr el máximo rendimiento del código, he usado control directo de los registros del microcontrolador para conmutar el pin 13 y así no meter "retrasos" debido a la ejecución de código. Para entendernos, la instrucción digitalWrite es "lenta".
Los tres registros internos usados por el microcontrolador para el control de los puertos son:
  • DDRx   => Configura los pines del puerto como entrada ("0") o salida ("1").
  • PORTx => Controla si el estado ALTO y BAJO de los pines de dicho puerto.
  • PINx    => Estado de los pines.
siendo x alguno de los tres puertos disponibles en el micro: B,C ó D.
Para saber la equivalencia entre los pines de Arduino con los del micro, visitar: http://www.arduino.cc/en/Reference/PortManipulation
Cómo se puede ver, el pin 13 de Arduino equivale al PB5 (puerto B, bit 5).

Truco => Si escribes sobre el registro PINx cuando esta declarado cómo salida, el pin conmutará.


_BV() usado en el código es un macro definido en avr/sfr_defs.h, el cual se añade indirectamente al incluir avr/io.h.  Para hacerlo fácil, lo puedes usar sin añadir dichos includes, ya que esta incluido en el core de Arduino.
Dicho macro esta definido como: #define _BV( bit ) ( 1<<(bit) )
Lo que hace es poner a "1" el bit definido dentro del paréntesis de la función.

No he usado dicho macro para la configuración de los registros del Timer 2 dentro del setup() para intentar ser más claro y entenderlo mejor mientras se esta leyendo el datasheet del micro.

Queda de ver una cosa.... ¿Cómo configuro las interrupción de overflow del timer 2?
Para "recoger" las interrupciones generadas por el microcontrolador, nuestro código debe disponer de la siguiente estructura:


#include <avr/interrupt.h>

ISR(TIMER2_OVF_vect)
{
// código a ejecutar cuando se activa la interrupción
}

ISR(int) define la función que se ejecutará cuando se active la interrupción int, en este caso concreto, la interrupción de overflow del Timer 2 (TIMER2_OV_vect). Existen más interrupciones cómo por ejemplo: INT0_vect, USART_RX_vect, ...
Para ampliar información, visitar http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html .




Código del sketch:

#include <avr/interrupt.h>

//-----------------------------------------------------
// Rutina de Interrupcion [TIMER2_OVF_vect es la rutina que se ejecuta cuando el Timer2 ->Overflow]
// Arduino tiene un cristal de 16 Mhz
// Por lo que tenemos una interrupcion cada => 1/ ((16000000 / PRESCALER) / 256)
ISR(TIMER2_OVF_vect) 
{
  PINB |=_BV(PB5);
}



void setup() 
{
  DDRB=_BV(PB5);   // Pin 13 Arduino equivale a PB5

  // NORMAL MODE
  TCCR2A = (1<<COM2A1) | (0<<COM2A0) | (0<<COM2B1)| (0<<COM2B0) | (0<<3) | (0<<2) | (0<<WGM21) | (0<<WGM20);  

  TCCR2B = (0<<FOC2A) | (0<<FOC2B) | (0<<5) | (0<<4) | (0<<WGM22) | (1<<CS22) | (0<<CS21) | (0<<CS20); 
  
  // Activo Timer2 Overflow Interrupt
  TIMSK2 =(0<<7) | (0<<6) | (0<<5) | (0<<4) | (0<<3) | (0<<OCIE2B) | (0<<OCIE2A) | (1<<TOIE2);  
  
  // Oscilador interno
  ASSR = (0<<7) | (0<<EXCLK) | (0<<AS2) | (0<<TCN2UB) | (0<<OCR2AUB) | (0<<OCR2BUB) | (0<<TCR2AUB) | (0<<TCR2BUB) ;
  
  //Activo interrupciones globales.
  sei();

}


void loop() 
{
  

}







Veámos ahora un ejemplo de cómo usar el Timer2 para controlar diferentes tareas. En este ejemplo, conmutaremos el estado de dos pines (pin 13 y pin 1 de Arduino) a dos frecuencias diferentes. Es decir, haremos que el led conectado al pin 13 parpadee cada 10 ms y que el pin 1 cada 25 ms.
Para ello configuramos nuestro Timer2 para que active la interrupción cada 1ms. Dentro de la interrupción aumentará una variable llamada ticks. Dicha variable será la base para controlar la ejecución del resto de mi programa.
Por lo que el flujo de mi programa vendrá controlado basado en unidades de 1ms (variable ticks). Esto es la base de cómo hacer "tareas multiples" con un microcontrolador.



(click para agrandar)



Código del sketch 2:

#include <avr/interrupt.h>

//DECLARACION DE VARIABLES GLOBALES
volatile unsigned long ticks;
unsigned long Tarea1;
unsigned long Tarea2;

//-----------------------------------------------------
// Rutina de Interrupcion [TIMER2_OVF_vect es la rutina que se ejecuta cuando el Timer2 ->Overflow]
// Arduino tiene un cristal de 16 Mhz
// Por lo que tenemos una interrupcion cada => 1/ ((16000000 / PRESCALER) / 256)
ISR(TIMER2_OVF_vect) 
{
  ticks++;
}



void setup() 
{
  DDRB=_BV(PB5);   // Pin 13 Arduino equivale a PB5
  DDRD=_BV(PD1);   // Pin 2 Arduino equivale a PD1

  // NORMAL MODE
  TCCR2A = (1<<COM2A1) | (0<<COM2A0) | (0<<COM2B1)| (0<<COM2B0) | (0<<3) | (0<<2) | (0<<WGM21) | (0<<WGM20);  
 
  TCCR2B = (0<<FOC2A) | (0<<FOC2B) | (0<<5) | (0<<4) | (0<<WGM22) | (1<<CS22) | (0<<CS21) | (0<<CS20); 
  //Configurado a 1.024 ms
  
  // Activo Timer2 Overflow Interrupt
  TIMSK2 =(0<<7) | (0<<6) | (0<<5) | (0<<4) | (0<<3) | (0<<OCIE2B) | (0<<OCIE2A) | (1<<TOIE2);  
  
  
  // Oscilador interno
  ASSR = (0<<7) | (0<<EXCLK) | (0<<AS2) | (0<<TCN2UB) | (0<<OCR2AUB) | (0<<OCR2BUB) | (0<<TCR2AUB) | (0<<TCR2BUB) ;

  // Inicializo variables
  Tarea1=10;
  Tarea2=25;  
  
  //Activo interrupciones globales.
  sei();


}


void loop() 
{
   
  
  if (ticks>=Tarea1)
  {
    PINB |=_BV(PB5);   //Conmuto pin 13
    Tarea1+=10;        //Configuro para que se ejecute de nuevo la Tarea1 dentro de 10 ticks
  }
  
  if (ticks>=Tarea2)
  {
    PIND |=_BV(PD1);  //Conmuto pin 2
    Tarea2+=25;       //Configuro para que se ejecute de nuevo la Tarea2 dentro de 25 ticks
  }
  
  
}



Espero que ésto ayude y no dudes en contactar conmigo si encuentras algun error.




Links recomendados:
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=50106   [Newbie's Guide to AVR Timers]
http://arduino.cc/en/Tutorial/SecretsOfArduinoPWM ó http://arduino.cc/es/Tutorial/SecretsOfArduinoPWM [castellano]
http://www.nongnu.org/avr-libc       [Libreria de C para AVR usada por el core de Arduino]
http://www.tte-systems.com/books/pttes [ Libro Patterns for Time-Triggered Embedded Systems ]







.

Cálculo de número de marcha con RPM y Velocidad


 



Vamos a explicar como crear un display de marchas para una moto ó coche mediante un canal matemático, el cual usa la velocidad del vehículo y las rpm motor. Normalmente resulta dificil intrumentar la caja de cambios, pero hoy en día es casi inmediato disponer de la señal de velocidad ruedas y velocidad motor.
Muy útil para motos.

Primero se estudiará el sistema mecánico el cual une la salida motor con las ruedas, para ello nos ayudamos del siguiente diagrama:



siendo RPM=revoluciones por minuto, Speed=velocidad del vehículo, Rwheel= el radio de la rueda.
Por lo cual, resulta sencillo observa que si disponemos la velocidad en rueda y la de motor, nuestra única incognita será el ratio entre ambas (caja cambios + grupo final).

En youtube se puede ver un video subido por Herbold de cómo obtener el dato de grupo final experimentalmente http://www.youtube.com/watch?v=i_Enyf67KTs:





Veamos algunos ejemplos reales (fuente wikipedia), Corvette C5 Z06:
  1st  => 2'97
  2nd => 2'07
  3rd => 1.43
  4th => 1
  5th => 0'84
  6th => 0'56
  Final drive (diferencial)=> 3.42

Esto significa, por ejemplo, cada 2.97x3.42=10.16 vueltas motor en primera, gira una de rueda (1:10.16)

Veamos el tipo de señal que obtenemos usando dicho canal matemático. Podemos observar la señal azul, la cual corresponde a nuestro canal calculado. Se observa, que la señal no es limpia, y sobretodo si tenemos patinaje de ruedas (wheelspin) ó en el momento de pisar embrague. Veáse un ejemplo de wheelspin en la parte rodeada de la figura. También vemos la señal rosa, la cual proviene de un sensor rotativo en el selector de marchas (barrel), la cual es perfecta.

 
Estas señales son reales grabadas con un sistema adquisición de datos.


Una mejora de la señal, se consigue aplicando un filtro de tipo Moving Average. En la figura, se ha aplicado dicho filtro tomando 5 muestras.

Por ejemplo, se podría calcular dicho canal cada 50 Hz (20 ms) gracias a un timer, y el pseudocódigo sería algo como:

función cada 20ms{
   si (Velocidad >0)
   {
       n=(3.6*Radio_rueda*3.14159*RPM)/(30*Speed);
   }

   filtro1=filtro2;
   filtro2=filtro3;
   filtro3=filtro4;
   filtro4=filtro5;
   filtro5=n;

   Ratio= (filtro1+filtro2+filtro3+filtro4+filtro5)/5;
}


RPM =revoluciones por minuto del motor
Velocidad=Km/h
Radio_rueda= en metros



Una vez obtenido el ratio (caja cambios + grupo final), ya sabemos en que marcha nos encontramos, por lo cual sólo quedará mostrarla al usuario. Ya que con el ratio +/- un cierto margen histeresis, tendremos directamente la marcha actual.

Para conseguir los dos canales necesarios (velocidad y rpm), normalmente nos encontramos dos tipos de tecnologías en los sensores utilizados en automoción:
- Sensores inductivos (pasivos)
- Sensores Hall (activos)



Ejemplo señal sensor inductivo


Los primeros generan un voltaje cuya frecuencia y amplitud son proporcionales a la velocidad.
Para poder introducir este tipo de señal a un microcontrolador, deberemos usar una etapa de adaptación de señales, como puede ser un lm1815 ó etapa con AO para convertir a señal de pulsos tipo TTL. Este tipo de etapas suelen tener el zero-crossing (detectan el paso por cero) y la histeresis ajustables. Gracias a ésto último, se puede reducir el efecto del ruido que suelen tener dichas entradas.

En los sensores hall, nos podemos encontrar algunos que su salida tiene un offset DC sobre la señal de pulsos. Para eliminarla, podemos usar un condensador en serie con la señal. Si la señal no tiene la amplitud suficiente (mayor de 2,5 V), tendremos que adaptar para hacer ésto posible, por ejemplo con la ayuda de transistores.

Las RPM, a parte de poder recogerlas del sensor cigueñal motor, podríamos sacar de una de las fases del alternador. Hay que tener en cuenta, que la velocidad motor y la velocidad del alternador no es la misma, ya que van unidas por una polea con ratios típicos de 1:2.2; 1:3 hasta 1:5.
La frecuencia medida en una de las fases será => f=p*n/60 , siendo p el número de polos (típicamente 6 ó 8) y n la velocidad del alternador.
Algunos alternadores vienen ya preparados con dicha señal (Terminal W).

La mayoría de los coches, disponen señal de tacómetro en los conectores de la radio, en el cuadro,... por lo que es una posible fuente de la señal de la velocidad del vehiculo.

También se puede disponer de las rpm de las señales de control de los inyectores.

Para mayor información de donde sacar dichas señales, puedes visitar el proyecto MPGuino:

Cabe destacar, que la señal cuadrada debe superar aproximadamente Vcc/2  para que nuestro micro ATMega lo reconozca.

Para aprender un poco más de los sensores utilizados en automoción, podemos visitar el siguiente link:

La ventaja principal entre sensores hall e inductivos, es que los primeros son capaces de detectar bajas velocidades e integran la electrónica de adaptación de la señal obteniendo directamente pulsos. También son mejores ante ruido eléctrico, ya normalmente existe distancia entre los sensores inductivos y sus etapas de adaptación de señales. Cabe recordar el uso de una resistencia de pull-up con los sensores hall. Si se va a puentear dicha señal del cableado del coche o moto, ya dispondrá de ella la ecu motor.
Dichas señales deben entrar a las entradas de interrupción de nuestro Arduino. Conviene proteger dichas entradas con una resistencia en serie de gran valor ,ya que las entradas de nuestro micro disponen de "clamping diodes", los cuales convierten señales elevadas de voltaje en señal cuadrada de valor igual al de alimentación +/- 0.5V. Atmel recomienda que por dichos diodos no circulen más de 1mA.
Si es posible, añadir un diodo tipo TVS ó zener entre señal y masa para proteger de transitorios a nuestra entrada.


Aunque no tenga nada que ver,este video es de regalo.... cómo funciona un diferencial.






Arduino and RS485 (ENGLISH VERSION)

.

NEWS => Are you interested in CAN Bus with Arduino?  Visit http://secuduino.blogspot.com/ 


RS485 is a standard serial protocol, with bus topology where you can connect several devices in a very cheap way. Here will be explained how to create a microcontrollers network just with 2 wires.


It is based in a differential couple of wires with which we can get a half-duplex comunication channel. The driver has a configurable input that provides us the possibility to set our transceiver as sender or receiver.

What do we need apart of our Arduino?
  • MAX485
  • SN75176 (cheaper)

What will be it used for? It is a voltage level converter in order to use our UART as RS485 interface.




Ok, now we are ready to start. RS485 resides in the physical layer based on the OSI Model, so it just tell us about voltages, physic parameters,....

Recommended links:

http://www.i-micro.com/pdf/articulos/rs-485.pdfdf
http://www.maxim-ic.com/appnotes.cfm?appnote_number=763&CMP=WP-1
http://www.neoteo.com/rs485-domotica-al-alcance-de-tu-mano-15810.neo-alcance-de-tu-mano-15810.neo
RS485 Oscilloscope Screenshots
http://www.sbc-support.ch/faq/files/files.get.php?ZSESSIONID=n38bphov4ls4cdsvtbd8tl6jb4&fi_index=100179

Then, we need an structural way to send our information. We will use as frames a configuration already created by Fuji in their industrial variators.

Look at: http://www.cdautomation.com/download/ENG_L_M_FUJI_RS485_COMM_for_FRENIC-Mini.PDFL_M_FUJI_RS485_COMM_for_FRENIC-Mini.PDF

Let's take a look at the frame configuration:



  • Byte 1: Start Byte ( 0 hexadecimal ).
  • Byte 2-3: ASCII Arduino's address.
  • Byte 4: Byte ENQ, ACK or NAK (0x05h, 0x06h y 0x15h) .
  • Byte 5: ASCII Requested command.
  • Byte 6 y 7: ASCII Function number.
  • Byte 8: Sign byte (Positive 0x20h y Negative 2D)
  • Byte 9-12: ASCII of data bytes (0x00h-0xFFFFh)
  • Byte 13: Byte EOT (End of Text) (0x03h)
  • Byte 14-15: Checksum (addition from 2nd byte to 13th byte)


With this configuration we have
  • 1 Byte indicating the Function Number
  • 2 Bytes indicating the sub-function Number


So imagine the possibles combinations!!! For instance:
A01 Requet of value on Analog input 1
P01 Request Configuration of PWM 1 with setup data in DATA bytes
And so on!!!! Is not wonderful?

Notice:
- Commands are ASCII Coded
- Control bytes are not ASCII Coded
It is a nice feature due to, in case we receive a 0x00 (thinking it is the start byte), the program will understand it is the starting frame instead of as 0 value, because in this case, it should be sent as ASCII (0x30).

Please take a look at the ASCII Table, even better print it! :P Probably most of us we have a copy pasted in front of us all the time, wrong?
http://www.cs.utk.edu/~pham/ascii_table.jpg




Let's see an example:
Imagine we need an arduino(master) which will decide either switch on or switch off a remote lights. Then, it have to send a command on our RS485 network and then other arduino (slave) will interpretate the command and excute it.

Then, the master will follow the switchces state, a and as needed, the master will send the frame requesting switch on/off to the slave called 01. The function will be called D and number 0.(you can set it up as needed). So,
Master sends: Slave(01)-Request execution function(D)-SubFunction(0)
0x00 0x30 0x31 0x05 0x44 0x30 0x30 0x20 0x30 0x30 0x30 0x31 0x03 0x01 0xEE
Slave answers: AKNOWLEDGE (ACK).
0x00 0x30 0x31 0x06 0x44 0x30 0x30 0x20 0x30 0x30 0x30 0x31 0x03 0x01 0xEF


The salve's ACK frame has the following configuration(take a look at Byte 4 due to here is specified ACK)

Even better, watch the following video: http://www.youtube.com/watch?v=ABcjU0Ua-d4



Urgh!!!!Sorry but the switcher is just a couple of wires going from VCC to GND! ;)

In order to see the frames flowing on the net, you need just a USB <----> RS232 like FTDI232 or MAX232+MAX485,... If you have an Arduino PCB, notice you can remove the uC from its socket and you'll have an USB <---->Rs232 converter!


If you want to monitorize what is going on , you can use Hyperterminal, or any one that include some useful features as format output, saving options,....for instance we like:
RealTerm
FREE SERIAL TERMINAL MONITOR
Really nice if you want to sniff RS232 frames

In this example we have 3 arduinos connected. The master have 2 switchers used to switch on/off leds connected to each of the slaves.
.




Let's see how it works: http://www.youtube.com/watch?v=S9FSQaToVZ4


Now...THE CODE!

IMPORTANT:
Checksum are done directly in hex instead of convert it to ASCII. Change it could be necessary , to don't have problems. This entry is a howto paper to show you how RS485 works and explain how to implemnt a protocol, but is not 100% debbuged.



MASTER:


//----------------------------------
//RS 485
//By Igor Real
//24-06-09
//----------------------------------


byte            data[12];
unsigned long   previous_time;
unsigned long   previous_time2;
byte            times_repeat=5;
byte            times_repeat2=5;

byte            state=0;
byte            state2=0;

#define  pinCONTROL    02
#define  myaddress     01
  

void setup() {

   pinMode(13,OUTPUT);
   pinMode(pinCONTROL,OUTPUT);
   digitalWrite(13,HIGH);
   digitalWrite(12,LOW);
   pinMode(8,INPUT);
   pinMode(9,INPUT);
   digitalWrite(pinCONTROL,LOW);
   Serial.begin(9600);
   Serial.println("Empezamos");
   state=0;
   state2=0;
}

void loop()
{

  if (digitalRead(8)==state){
    state=!state;
    times_repeat=0;  
  }
  if (digitalRead(9)==state2){
    state2=!state2;
    times_repeat2=0;  
  }

  
  
  
  if (times_repeat<4){
    Serial.flush();  
    //(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4)
    if (digitalRead(8)==HIGH){  
      sendMSG(48,49,68,48,48,32,48,48,48,49);
    }else {
      sendMSG(48,49,68,48,48,32,48,48,48,48);
    }
    times_repeat=times_repeat+1;
  
    previous_time=millis();
    while (((millis()-previous_time)<500) && (Serial.available()!=15)){
      ;;
    }
  
    if (Serial.available()>=15){
      if (receiveMSG()==1){
        Serial.println("Trama correcta");
        if (data[0]==48 && data[1]==49 && data[2]==6){
          //ACK  
          times_repeat=5;
          Serial.println("ACK recibido");
        }
      }
     }
   }  
  
  

  if (times_repeat2<4){
    Serial.flush();  
    //(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4)
    if (digitalRead(9)==HIGH){  
      sendMSG(48,50,68,48,48,32,48,48,48,49);
    }else {
      sendMSG(48,50,68,48,48,32,48,48,48,48);
    }
    times_repeat2=times_repeat2+1;
  
    previous_time2=millis();
    while (((millis()-previous_time2)<500) && (Serial.available()!=15)){
    ;;
    }
  
    if (Serial.available()>=15){
      if (receiveMSG()==1){
        //Serial.println("Trama correcta");
        if (data[0]==48 && data[1]==50 && data[2]==6){
          //ACK  
          times_repeat2=5;
          //Serial.println("ACK recibido");
        }
      }
    }
  }
  
  
  
}


//------------------------
//FUNCIONES
//------------------------

byte receiveMSG(){

  byte  byte_receive;
  byte  state=0;
  byte  cont1=1;
  byte  trace_OK=0;

  unsigned int checksum;
  unsigned int checksum_trace;
  
  
  
  while (Serial.available() > 0){
    
     byte_receive=Serial.read();
     if (byte_receive==00){
       state=1;
       checksum_trace=0;
       checksum=0;
       trace_OK=0;
       cont1=1;
     }else if (state==1 && cont1<=12){
       data[cont1-1]=byte_receive;
       checksum=checksum+byte_receive;
       cont1=cont1+1;
     }else if (state==1 && cont1==13){
       checksum_trace=byte_receive<<8;
       cont1=cont1+1;
     }else if (state==1 && cont1==14){
       checksum_trace=checksum_trace+byte_receive;
       cont1=cont1+1;
       state=0;
       if (checksum_trace==checksum){
           trace_OK=1;
       }else{
         trace_OK=0;
       }
       break;
     }
  }
  return trace_OK;

}



void sendMSG(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+5+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(5,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)& 255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);

  
  
}



void sendACK(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+6+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(6,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)&255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);
  
  
}

void sendNAK(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+6+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(15,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)&255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);
  
  
}



byte hex2num(byte x){

  byte result;
  
  if (x>=48 && x<=57){
    result=x-48;  
  }else if (x>=65 && x<=70){
    switch(x){
      case 65:
        result=10;
        break;
      case 66:
        result=11;
        break;
      case 67:
        result=12;
        break;
      case 68:
        result=13;
        break;
      case 69:
        result=14;
        break;
      case 70:
        result=15;
        break;    
    }  
  }
  return result;  
}



 

SLAVES:
(you have to change myaddress to 01 or 02)


//----------------------------------
//RS 485
//By Igor Real
//24-06-09
//----------------------------------


byte  data[12];
byte  address;
byte  function;
byte  function_code;
unsigned int data_received;
byte  byte_receive;
byte  state=0;
byte  cont1=1;
byte  trace_OK=0;
unsigned int checksum;
unsigned int checksum_trace;

#define  pinCONTROL    02
#define  myaddress     02
  

void setup() {

   pinMode(13,OUTPUT);
   pinMode(pinCONTROL,OUTPUT);
   digitalWrite(2,LOW);
   Serial.begin(9600);
   Serial.println("Empezamos");

}

void loop()
{

   while (Serial.available() > 0){
    
     byte_receive=Serial.read();
     if (byte_receive==00){
       //Serial.println("Se ha recibido byte Start");
       state=1;
       checksum_trace=0;
       checksum=0;
       trace_OK=0;
       address=0;
       data_received=0;
       cont1=1;
     }else if (state==1 && cont1<=12){
       data[cont1-1]=byte_receive;
       checksum=checksum+byte_receive;
       cont1=cont1+1;
     }else if (state==1 && cont1==13){
       checksum_trace=byte_receive<<8;
       cont1=cont1+1;
       //Serial.print("Primer Byte Checksum");      
       //Serial.print(checksum_trace,HEX);
     }else if (state==1 && cont1==14){
       checksum_trace=checksum_trace+byte_receive;
       cont1=cont1+1;
       state=0;
       //Serial.println(byte_receive,HEX);
       //Serial.println("Recibida trama");
       //Serial.print("Checksum Trace= ");
       //Serial.println(checksum_trace,HEX);
       //Serial.print("Checksum= ");
       //Serial.println(checksum,HEX);
       //Serial.println(checksum,DEC);
       //Serial.println("Trama= ");
       //Serial.print(data[0]);
       //Serial.print(data[1]);
       //Serial.print(data[2]);
       //Serial.print(data[3]);
       //Serial.print(data[4]);
       //Serial.print(data[5]);
       //Serial.print(data[6]);
       //Serial.print(data[7]);
       //Serial.print(data[8]);
       //Serial.print(data[9]);
       //Serial.print(data[10]);
       //Serial.println(data[11]);      

       if (checksum_trace==checksum){
         trace_OK=1;
        
         address=(hex2num(data[0])<<4)+(hex2num(data[1]));
         function=data[3];
         function_code=(hex2num(data[4])<<4)+(hex2num(data[5]));
         data_received=(hex2num(data[7])<<12)+(hex2num(data[8])<<8)+(hex2num(data[9])<<4)+(hex2num(data[10]));
        
         //Serial.println("TRAZA CORRECTA");
         //Serial.println(address,DEC);
         //Serial.println(data_received);
         if (address==myaddress){
           if ((function=='D') && (function_code==0) && data[2]==5){
             if (data_received==1){
               digitalWrite(13,HIGH);
               //Serial.println(data_received,DEC);
               sendACK(data[0],data[1],data[3],data[4],data[5],data[6],data[7],data[8],data[9],data[10]);
             }else if (data_received==0){
               digitalWrite(13,LOW);
               sendACK(data[0],data[1],data[3],data[4],data[5],data[6],data[7],data[8],data[9],data[10]);
             }
           }
         }
       }else{
         //Serial.println("TRAZA INCORRECTA");  
         sendNAK(data[0],data[1],data[3],data[4],data[5],data[6],data[7],data[8],data[9],data[10]);
       }
     }

  }
  
}


//------------------------
//FUNCIONES
//------------------------

byte receiveMSG(){

  byte  byte_receive;
  byte  state=0;
  byte  cont1=1;
  byte  trace_OK=0;

  unsigned int checksum;
  unsigned int checksum_trace;
  

  
  
  while (Serial.available() > 0){
    
     byte_receive=Serial.read();
     if (byte_receive==00){
       state=1;
       checksum_trace=0;
       checksum=0;
       trace_OK=0;
       cont1=1;
     }else if (state==1 && cont1<=12){
       data[cont1-1]=byte_receive;
       checksum=checksum+byte_receive;
       cont1=cont1+1;
     }else if (state==1 && cont1==13){
       checksum_trace=byte_receive<<8;
       cont1=cont1+1;
     }else if (state==1 && cont1==14){
       checksum_trace=checksum_trace+byte_receive;
       cont1=cont1+1;
       state=0;
       if (checksum_trace==checksum){
           trace_OK=1;
       }else{
         trace_OK=0;
       }
       break;
     }
  }
  return trace_OK;

}



void sendMSG(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+5+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(5,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)& 255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);

  
  
}



void sendACK(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+6+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(6,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)&255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);
  
  
}

void sendNAK(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+6+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(15,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)&255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);
  
  
}



byte hex2num(byte x){

  byte result;
  
  if (x>=48 && x<=57){
    result=x-48;  
  }else if (x>=65 && x<=70){
    switch(x){
      case 65:
        result=10;
        break;
      case 66:
        result=11;
        break;
      case 67:
        result=12;
        break;
      case 68:
        result=13;
        break;
      case 69:
        result=14;
        break;
      case 70:
        result=15;
        break;    
    }  
  }
  return result;  
}



Now, we will learn how to wire a bus topology:
.
 
.
Each Arduino or gadget conneted to the net, is wired with an Y welded which comes out 2 connectors from same family different kind(Male/female). The shorter the better. Each connector will have VCC,Sginal A, Singal B and GND. This way, if we want to connect another device onto the net, you need just insert each device or put it at the end. Now is easy understand why we are putting a Y , it's extremely easy connect and disconnect a device from the bus, due to each device has male an female. Is worthy have always a couple of connectors with a end-resistor between Singal A and Signal B RS485 pins in order to "close" the wires' end. If we need add another device is as easy as disconnet the connector with the end-resistor, insert the new device and close again with that connector. Wire VCC and GND is extremely easy, and in future, will make things really easier in possible expansions..... Take care, and twist Signal A and Signal B wires. Please take a loot at next link if needed : http://www.rs-485.com/download/485%20network%20topology.pdf
.
This paper was based on spanish Igor R's one. Thanks Igor R!! Cheers!!!
.
.
Aritz R.
.
.

Añadiendo logger a tu Arduino....Vinculum

.
He estado experimentando un poco con el chip Vinculum de FTDI. En concreto con el módulo de prototipos VDIP1. http://www.vinculum.com/prd_vdip1.html

Te permite manejar un dispositivo USB y maneja archivos FAT de forma transparente, por lo que es ideal para aplicaciones donde se desea guardar datos sin muchas complicaciones. El precio es de 22 euros desde la web del fabricante.
He realizado un pequeño ejemplo basandome en la información que hay en el Playground de Arduino y a un ejemplo de la web del fabricante con un PIC.
http://www.arduino.cc/playground/Main/UsbMemory
http://www.vinculum.com/downloads.html#vutilities

He usado el Mega con el Serial 3, y sin "flow control". Lo suyo sería utilizar las lineas CTS y RTS para saber si el dispositivo esta preparado para recibir información.
La conexión del VDIP1:
Pin 1.- 5 voltios
Pin 6.- TXD
Pin 7.- GND
Pin 8.-RXD
Pin 10.- GND


En el ejemplo, cada vez que se pulsa un pulsador conectado al pin 9, se abre un archivo como escritura llamado igor.txt y se guarda una linea de texto en la que pone IGOR y el tiempo millis() en una linea. Es un poco tonto, pero para empezar esta bien... Voy sacando la info del programa por el Serial (el USB de programar).


 
 






//---------------------------------------
//By Igor Real
//VDIP1 without flow control
//11-07-09
//---------------------------------------
byte dato_recibido;
byte VNC1_State=0;
int noOfChars=1;
long int x;
long int val;

void setup() {

  pinMode(9,INPUT);
  
  Serial.begin(9600);
  Serial3.begin(9600);
  Serial3.flush();
  Serial3.print("IPA");
  Serial3.print(13,BYTE);  //RETURN
  //CONTESTA UN D:\> + CR
  if (VNC1_Confirmation()){
    Serial.println("CONFIGURADO LONG COMMAND");
  }else{
    Serial.println("ALGO FALLO");
  }


}

void loop()
{

  if (digitalRead(9)==1){
  delay(300);
  //VNC1_State=0   => Sync
  //VNC1_State=1   => FindDisk
  switch(VNC1_State){
    case 0:
      if (VNC1_Sync()==1){
        if (VNC1_FindDisk()==1){  
          VNC1_State=1;  
        }else{
          Serial.println("No hay disco");
        }  
      }else{
        Serial.println("Imposible Sync");
      }
      break;
    case 1:
      Serial.println("Hay disco");
      
      Serial3.flush();
      //Abro fichero para escritura
      Serial3.print("OPW igor.txt");
      Serial3.print(13,BYTE);  //RETURN
      //CONTESTA UN D:\> + CR
      while (VNC1_Confirmation()==0);
      //Meto datos
      val=millis();  
      x=val;
      noOfChars=1;      
      while (x>= 10){                
        noOfChars++;                
        x/=10;    
      }
      noOfChars +=7;
      
      Serial3.print("WRF ");
      Serial3.print(noOfChars);
      Serial3.print(13,BYTE);  //RETURN
      Serial3.print(73,BYTE);  //I
      Serial3.print(71,BYTE);  //G
      Serial3.print(79,BYTE);  //O
      Serial3.print(82,BYTE);  //R
      Serial3.print(32,BYTE);  //
      Serial3.print(val);  
      Serial3.print(13,BYTE);  //RETURN
      Serial3.print(10,BYTE);  //NEW LINE
      Serial3.print(13,BYTE);  //RETURN  
      //CONTESTA UN D:\> + CR
      while (VNC1_Confirmation()==0);
      Serial3.print("CLF igor.txt");
      Serial3.print(13,BYTE);
      //CONTESTA UN D:\> + CR
      while (VNC1_Confirmation()==0);      
      VNC1_Sync();
      Serial.println("Sacar el disco");
      
      VNC1_State=0;
      break;
    default:
      VNC1_State=0;
    
  }
  
  }

}

//----------------------------------------------------------------
// FUNCION: MIRAR SI HAY PENDRIVE
//----------------------------------------------------------------
byte VNC1_FindDisk(){

  byte FindDisk=0;

  Serial3.flush();
  Serial3.print(13,BYTE);  //RETURN
  //CONTESTA UN D:\> + CR
  if (VNC1_Confirmation()){
    FindDisk=1;
  }else{
    FindDisk=0;
  }
    
  //Serial.print("FindDisk= ");
  //Serial.println(FindDisk,DEC);
  Serial3.flush();
  return FindDisk;  
}

//----------------------------------------------------------------
// FUNCION: MIRAR SI HE RECIBIDO D:\>
//----------------------------------------------------------------      
byte VNC1_Confirmation(){  

  byte state=0;
  byte byte_received;
  byte confirm=0;  
  
  while (Serial3.available()<5) ;
  
  //Serial.print("Buffer= ");
  //Serial.println(Serial3.available(),DEC);
  
  while (Serial3.available()){
    byte_received=Serial3.read();
    //Serial.println(byte_received,DEC);
    
    if (byte_received==68 && state==0){
      //Received "D"
      state=1;
      //Serial.println ("Estado 1");
    }else if (byte_received==58 && state==1){
      //Received ":"  
      state=2;
      //Serial.println ("Estado 2");
    }else if (byte_received==92 && state==2){
      //Received "\"  
      state=3;
      //Serial.println ("Estado 3");
    }else if (byte_received==62 && state==3){
      //Received ">"
      state=4;
      //Serial.println ("Estado 4");
    }else if (byte_received==13 && state==4){
      //Received "CR"
      state=0;
      confirm=1;
      //Serial.println ("Estado 5");
      break;
    }else{
      confirm=0;
      break;
    }
  }  
  return confirm;
}  
//----------------------------------------------------------------
// FUNCION: SYNC
//----------------------------------------------------------------
byte VNC1_Sync(){
  
  byte cont_E=0;
  byte confirm_E=0;
  byte confirm_e=0;
  byte state=0;
  byte byte_received=0;
  
  
  Serial3.flush();
  while (confirm_E==0 && cont_E<255){
    Serial3.print(69,BYTE);  //E
    Serial3.print(13,BYTE);  //RETURN
    cont_E++;
    //Serial.print("Contador= ");
    //Serial.println(cont_E,DEC);
    
    while (Serial3.available()<2) ;
    
    while (Serial3.available()){
      byte_received=Serial3.read();
      //Serial.println(byte_received,DEC);
    
      if (byte_received==69 && state==0){
        //Received "E"
        state=1;
        //Serial.println ("Estado 1");
      }else if (byte_received==13 && state==1){
        //Received "CR"  
        state=2;
        confirm_E=1;
        //Serial.println ("Estado 2");
        break;
      }else{
        confirm_E=0;  
        delay(10);
      }
    }
    
  }
  if (confirm_E==1){
    //Serial.println("Vamos por e");
    Serial3.print(101,BYTE);  //e
    Serial3.print(13,BYTE);  //RETURN
    while (Serial3.available()<2) ;
    if (Serial3.read()==101){
      if (Serial3.read()==13){
        confirm_e=1;
      }
    }
  }
  return confirm_e;
  
}
//----------------------------------------------------------------      

 


.
.
Saludos

.
.Igor R.
.
.

Buses de campo para Arduino.... RS485

.
.
Originalmente, toda la información la publiqué en el foro de Arduino en español. Puedes seguirlo aquí.

El RS485 es un estandar de comunicación serie en el cual se pueden tener varios dispositivos en topología de bus y de una manera muy económica. Aquí explicaremos brevemente como realizar una pequeña red basada en 2 hilos.
Dado que sólo se usan dos hilos balanceados (diferencial), las comunicaciones son semiduplex. Para configurar el driver como transmisor o receptor, el driver dispone de una entrada digital para su configuración.

Para darle conectividad RS485 a nuestro arduino, sólo necesitamos de un convertidor de niveles para usar la UART del micro, y por menos de 1 euro tendremos todo lo necesario.
Los chips más usados son:
- Max485
- SN75176 (siendo éste el más económico)




La norma RS485 sólo establece las características físicas, no definiendo ningún protocolo ó forma de conexionado.

Señalamos unos links muy recomendables:
http://www.i-micro.com/pdf/articulos/rs-485.pdf
http://www.maxim-ic.com/appnotes.cfm?appnote_number=763&CMP=WP-1
http://www.neoteo.com/rs485-domotica-al-alcance-de-tu-mano-15810.neo
Documento con tramas de ejemplo para solucionar problemas
http://www.sbc-support.ch/faq/files/files.get.php?ZSESSIONID=n38bphov4ls4cdsvtbd...



La manera más sencilla de aprender como realizar un protocolo, es basarte en algo que ya existe. Para ello, me he basado en el protocolo profesional usado por Fuji para sus variadores industriales.
Véase: http://www.cdautomation.com/download/ENG_L_M_FUJI_RS485_COMM_for_FRENIC-Mini.PDF

Veamos un diagrama explicativo de la trama:



Las tramas son de 15 bytes:
  • Byte 1: Byte de start ( 0 hexadecimal ).
  • Byte 2-3: SCII de la dirección del arduino.
  • Byte 4: Byte ENQ, ACK ó NAK (0x05h, 0x06h y 0x15h) .
  • Byte 5: ASCII del comando petición.
  • Byte 6 y 7: ASCII del número de función.
  • Byte 8: Byte signo (Positivo 0x20h y Negativo 2D)
  • Byte 9-12: ASCII con el dato (0x00h-0xFFFFh)
  • Byte 13: Byte fin de texto (0x03h)
  • Byte 14-15: Checksum (suma de byte 2 al byte13)

Con esta trama se dispone de un byte que indica función + 2 bytes con el número de la función, por lo cual se pueden hacer multitud de combinaciones… Algunos ejemplos pueden ser: A01 realiza una petición de datos de la entrada analógica 1, P01 realiza una configuración de PWM 1 con el dato enviado en los 4 bytes de datos,… Elección del diseñador!!!
Se puede observar que los comandos están en ASCII y los bytes de control no. Esto ayuda enormente en la programación, ya que si recibimos 0x00 (suponiendo que es el byte de start), nuestro programa sabe que es un inicio de trama y no lo entiende como un "0" (número cero), ya que éste se enviaría en ASCII (0x30).
A continuación, adjunto una tabla ASCII sacada de http://www.cs.utk.edu/~pham/ascii_table.jpg





Veamos un ejemplo:
Queremos que un arduino (maestro) envíe la orden de encendido/apagado de un led conectado a otro arduino arduino (esclavo). El interruptor está en el maestro y la actuación (encendido/apagado) en el esclavo.

Según el estado del interruptor, el maestro envía la trama de petición de encendio/apagado, al esclavo ,cuya dirección es 01. Se ha decidido que la función se llamará D y el número 00 (completamente configurable).
Ej: 0x00 0x30 0x31 0x05 0x44 0x30 0x30 0x20 0x30 0x30 0x30 0x31 0x03 0x01 0xEE
El esclavo, contesta con un ACK (byte 4) al maestro confirmando el dato recibido.
Ej: 0x00 0x30 0x31 0x06 0x44 0x30 0x30 0x20 0x30 0x30 0x30 0x31 0x03 0x01 0xEF

Mejor,lo vemos con un video: http://www.youtube.com/watch?v=ABcjU0Ua-d4


(el interruptor es un triste cable que lo llevo a masa o VCC.... sorry!!!)

Para poder ver las tramas, bastará un conversor USB <--> RS232 basado en chip ftdi, ó un MAX232+MAX485,... Si tienes una placa Arduino de sobra, quitale el micro y tendrás un conversor USB --> RS232.



 
El software para monitorizar lo que está pasado, puede ser el hyperterminal de windows ó alguno gratuito que añaden mejoras como poder ver los datos en hexadecimal, grabar,...:
RealTerm
FREE SERIAL TERMINAL MONITOR


Ahora veamos un ejemplo con 3 arduinos interconectados. El mestro dispone de 2 switch, los cuales comandan el encendido/apagado de un led conectado a cada uno de los esclavos.




Veamos el video de funcionamiento: http://www.youtube.com/watch?v=S9FSQaToVZ4




Ahora toca el código:

IMPORTANTE: El checksum lo hago directamente en hexadecimal en vez de convertirlo a ASCII. Se debería cambiar el código, para no tener problemas. Esta publicación se trata de un acercamiento de como manejar el bus rs485 y explicar como implementar un protocolo, por lo que no está 100%.




MAESTRO:


//----------------------------------
//RS 485
//By Igor Real
//24-06-09
//----------------------------------


byte            data[12];
unsigned long   previous_time;
unsigned long   previous_time2;
byte            times_repeat=5;
byte            times_repeat2=5;

byte            state=0;
byte            state2=0;

#define  pinCONTROL    02
#define  myaddress     01
  

void setup() {

   pinMode(13,OUTPUT);
   pinMode(pinCONTROL,OUTPUT);
   digitalWrite(13,HIGH);
   digitalWrite(12,LOW);
   pinMode(8,INPUT);
   pinMode(9,INPUT);
   digitalWrite(pinCONTROL,LOW);
   Serial.begin(9600);
   Serial.println("Empezamos");
   state=0;
   state2=0;
}

void loop()
{

  if (digitalRead(8)==state){
    state=!state;
    times_repeat=0;  
  }
  if (digitalRead(9)==state2){
    state2=!state2;
    times_repeat2=0;  
  }

  
  
  
  if (times_repeat<4){
    Serial.flush();  
    //(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4)
    if (digitalRead(8)==HIGH){  
      sendMSG(48,49,68,48,48,32,48,48,48,49);
    }else {
      sendMSG(48,49,68,48,48,32,48,48,48,48);
    }
    times_repeat=times_repeat+1;
  
    previous_time=millis();
    while (((millis()-previous_time)<500) && (Serial.available()!=15)){
      ;;
    }
  
    if (Serial.available()>=15){
      if (receiveMSG()==1){
        Serial.println("Trama correcta");
        if (data[0]==48 && data[1]==49 && data[2]==6){
          //ACK  
          times_repeat=5;
          Serial.println("ACK recibido");
        }
      }
     }
   }  
  
  

  if (times_repeat2<4){
    Serial.flush();  
    //(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4)
    if (digitalRead(9)==HIGH){  
      sendMSG(48,50,68,48,48,32,48,48,48,49);
    }else {
      sendMSG(48,50,68,48,48,32,48,48,48,48);
    }
    times_repeat2=times_repeat2+1;
  
    previous_time2=millis();
    while (((millis()-previous_time2)<500) && (Serial.available()!=15)){
    ;;
    }
  
    if (Serial.available()>=15){
      if (receiveMSG()==1){
        //Serial.println("Trama correcta");
        if (data[0]==48 && data[1]==50 && data[2]==6){
          //ACK  
          times_repeat2=5;
          //Serial.println("ACK recibido");
        }
      }
    }
  }
  
  
  
}


//------------------------
//FUNCIONES
//------------------------

byte receiveMSG(){

  byte  byte_receive;
  byte  state=0;
  byte  cont1=1;
  byte  trace_OK=0;

  unsigned int checksum;
  unsigned int checksum_trace;
  
  
  
  while (Serial.available() > 0){
    
     byte_receive=Serial.read();
     if (byte_receive==00){
       state=1;
       checksum_trace=0;
       checksum=0;
       trace_OK=0;
       cont1=1;
     }else if (state==1 && cont1<=12){
       data[cont1-1]=byte_receive;
       checksum=checksum+byte_receive;
       cont1=cont1+1;
     }else if (state==1 && cont1==13){
       checksum_trace=byte_receive<<8;
       cont1=cont1+1;
     }else if (state==1 && cont1==14){
       checksum_trace=checksum_trace+byte_receive;
       cont1=cont1+1;
       state=0;
       if (checksum_trace==checksum){
           trace_OK=1;
       }else{
         trace_OK=0;
       }
       break;
     }
  }
  return trace_OK;

}



void sendMSG(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+5+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(5,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)& 255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);

  
  
}



void sendACK(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+6+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(6,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)&255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);
  
  
}

void sendNAK(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+6+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(15,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)&255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);
  
  
}



byte hex2num(byte x){

  byte result;
  
  if (x>=48 && x<=57){
    result=x-48;  
  }else if (x>=65 && x<=70){
    switch(x){
      case 65:
        result=10;
        break;
      case 66:
        result=11;
        break;
      case 67:
        result=12;
        break;
      case 68:
        result=13;
        break;
      case 69:
        result=14;
        break;
      case 70:
        result=15;
        break;    
    }  
  }
  return result;  
}



 

ESCLAVOS:
(hay que cambiar la dirección en myaddress a 01 y 02,dependiendo del esclavo)


//----------------------------------
//RS 485
//By Igor Real
//24-06-09
//----------------------------------


byte  data[12];
byte  address;
byte  function;
byte  function_code;
unsigned int data_received;
byte  byte_receive;
byte  state=0;
byte  cont1=1;
byte  trace_OK=0;
unsigned int checksum;
unsigned int checksum_trace;

#define  pinCONTROL    02
#define  myaddress     02
  

void setup() {

   pinMode(13,OUTPUT);
   pinMode(pinCONTROL,OUTPUT);
   digitalWrite(2,LOW);
   Serial.begin(9600);
   Serial.println("Empezamos");

}

void loop()
{

   while (Serial.available() > 0){
    
     byte_receive=Serial.read();
     if (byte_receive==00){
       //Serial.println("Se ha recibido byte Start");
       state=1;
       checksum_trace=0;
       checksum=0;
       trace_OK=0;
       address=0;
       data_received=0;
       cont1=1;
     }else if (state==1 && cont1<=12){
       data[cont1-1]=byte_receive;
       checksum=checksum+byte_receive;
       cont1=cont1+1;
     }else if (state==1 && cont1==13){
       checksum_trace=byte_receive<<8;
       cont1=cont1+1;
       //Serial.print("Primer Byte Checksum");      
       //Serial.print(checksum_trace,HEX);
     }else if (state==1 && cont1==14){
       checksum_trace=checksum_trace+byte_receive;
       cont1=cont1+1;
       state=0;
       //Serial.println(byte_receive,HEX);
       //Serial.println("Recibida trama");
       //Serial.print("Checksum Trace= ");
       //Serial.println(checksum_trace,HEX);
       //Serial.print("Checksum= ");
       //Serial.println(checksum,HEX);
       //Serial.println(checksum,DEC);
       //Serial.println("Trama= ");
       //Serial.print(data[0]);
       //Serial.print(data[1]);
       //Serial.print(data[2]);
       //Serial.print(data[3]);
       //Serial.print(data[4]);
       //Serial.print(data[5]);
       //Serial.print(data[6]);
       //Serial.print(data[7]);
       //Serial.print(data[8]);
       //Serial.print(data[9]);
       //Serial.print(data[10]);
       //Serial.println(data[11]);      

       if (checksum_trace==checksum){
         trace_OK=1;
        
         address=(hex2num(data[0])<<4)+(hex2num(data[1]));
         function=data[3];
         function_code=(hex2num(data[4])<<4)+(hex2num(data[5]));
         data_received=(hex2num(data[7])<<12)+(hex2num(data[8])<<8)+(hex2num(data[9])<<4)+(hex2num(data[10]));
        
         //Serial.println("TRAZA CORRECTA");
         //Serial.println(address,DEC);
         //Serial.println(data_received);
         if (address==myaddress){
           if ((function=='D') && (function_code==0) && data[2]==5){
             if (data_received==1){
               digitalWrite(13,HIGH);
               //Serial.println(data_received,DEC);
               sendACK(data[0],data[1],data[3],data[4],data[5],data[6],data[7],data[8],data[9],data[10]);
             }else if (data_received==0){
               digitalWrite(13,LOW);
               sendACK(data[0],data[1],data[3],data[4],data[5],data[6],data[7],data[8],data[9],data[10]);
             }
           }
         }
       }else{
         //Serial.println("TRAZA INCORRECTA");  
         sendNAK(data[0],data[1],data[3],data[4],data[5],data[6],data[7],data[8],data[9],data[10]);
       }
     }

  }
  
}


//------------------------
//FUNCIONES
//------------------------

byte receiveMSG(){

  byte  byte_receive;
  byte  state=0;
  byte  cont1=1;
  byte  trace_OK=0;

  unsigned int checksum;
  unsigned int checksum_trace;
  

  
  
  while (Serial.available() > 0){
    
     byte_receive=Serial.read();
     if (byte_receive==00){
       state=1;
       checksum_trace=0;
       checksum=0;
       trace_OK=0;
       cont1=1;
     }else if (state==1 && cont1<=12){
       data[cont1-1]=byte_receive;
       checksum=checksum+byte_receive;
       cont1=cont1+1;
     }else if (state==1 && cont1==13){
       checksum_trace=byte_receive<<8;
       cont1=cont1+1;
     }else if (state==1 && cont1==14){
       checksum_trace=checksum_trace+byte_receive;
       cont1=cont1+1;
       state=0;
       if (checksum_trace==checksum){
           trace_OK=1;
       }else{
         trace_OK=0;
       }
       break;
     }
  }
  return trace_OK;

}



void sendMSG(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+5+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(5,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)& 255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);

  
  
}



void sendACK(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+6+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(6,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)&255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);
  
  
}

void sendNAK(byte address1,byte address2,byte data_type,byte code1,byte code2,byte Sign,byte data1,byte data2,byte data3,byte data4){
  
  unsigned int checksum_ACK;
  checksum_ACK=address1+address2+6+data_type+code1+code2+Sign+data1+data2+data3+data4+3;
  
  UCSR0A=UCSR0A |(1 << TXC0);
  
  digitalWrite(pinCONTROL,HIGH);
  delay(1);

  Serial.print(0,BYTE);
  Serial.print(address1,BYTE);
  Serial.print(address2,BYTE);
  Serial.print(15,BYTE);
  Serial.print(data_type,BYTE);
  Serial.print(code1,BYTE);
  Serial.print(code2,BYTE);
  Serial.print(Sign,BYTE);
  Serial.print(data1,BYTE);
  Serial.print(data2,BYTE);
  Serial.print(data3,BYTE);
  Serial.print(data4,BYTE);  
  Serial.print(3,BYTE);
  Serial.print(((checksum_ACK>>8)&255),BYTE);
  Serial.print(((checksum_ACK)&255),BYTE);
  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite(pinCONTROL,LOW);
  
  
}



byte hex2num(byte x){

  byte result;
  
  if (x>=48 && x<=57){
    result=x-48;  
  }else if (x>=65 && x<=70){
    switch(x){
      case 65:
        result=10;
        break;
      case 66:
        result=11;
        break;
      case 67:
        result=12;
        break;
      case 68:
        result=13;
        break;
      case 69:
        result=14;
        break;
      case 70:
        result=15;
        break;    
    }  
  }
  return result;  
}

Ahora veamos como cablear una red de topología bus:

 
En cada Arduino o dispositivo de la red, se cablea con una Y soldada en la cual salen dos conectores de la misma familia. Debe tener la longitud más corta posible.
Cada conector tiene Alimentación, Señal A, Señal B y Masa.

De esta forma, si se quiere ir ampliando la red con dispositivos, bastará con ir intercalandolos ó poniendolos a final. Ya que cada dispositivo tiene conectores macho y hembra, por lo que encaja perfecto.
Se puede preparar un par de conectores, con una resistencia terminadora puesta en los terminales correspondientes a la Señal A y Señal B del bus RS485 para cerrar los extremos (siempre quedará un par de conectores libres). Si se necesita añadir más dispositivos, se desconecta dicho conector con la resistencia terminadora, se intercala el nuevo dispostivo y se vuelve a cerrar la red.

Llevar alimentación en el cableado, no cuesta nada y todo será más fácil para futuras ampliaciones...
El cable de Señal A y B debe ser trenzado.
Recomiendo este link acerca del conexionado: http://www.rs-485.com/download/485%20network%20topology.pdf 
.
.
.
.
Saludos!!
.
Igor R.
.
.