[Aporte] Librería RS-485 para PIC C Compiler (CCS)

D@rkbytes

Moderador general
Saludos.
Les comparto una librería para comunicación RS-485
Es una librería bastante sencilla, pero cumple con muy buen desempeño.
La escribí usando PIC C Compiler de CCS, y aunque este entorno cuenta con una librería para este propósito, (rs485.c) no logré hacer que funcionara usando el módulo USART (RS-232 por hardware)
Dicha librería funciona correctamente pero usando la interrupción externa por RB0.
Tal vez no implementé bien su uso, pero hice bastantes comprobaciones y no pude hacer que funcionara por USART.

La que he escrito cuenta con los dos modos también, pero esta sí funciona con ambos. :)

El ejemplo adjunto es para un maestro y dos esclavos, pero obviamente se pueden agregar más.
El maestro captura los datos por petición y los envía a la PC.
Esto puede ser opcional, ya que yo usé este modo únicamente por motivos de depuración.

Para comunicarme desde la PC hacia los dispositivos, usé este Conversor/Adaptador de USB a RS-485
Conversor USB a RS485-422.jpg
Para recibir los datos recolectados por el maestro, se necesita otra conexión serial.
Yo usé un convertidor de USB a RS-232 con el chip FTD-232RL
También usé otro similar pero usa el chip PL2303.
Cualquier otra forma de conexión RS-232 funcionará correctamente.
Mi PC cuenta con puerto serie, pero usar este tipo de adaptadores me resulta más conveniente.
(Por el uso de otro convertidor a TTL como el MAX-232)

También escribí un programa para poder comunicarme con los dispositivos.

Este programa emula al maestro, enviando los mismos datos que el microcontrolador.

RS-485 PC.jpg

En este programa se puede ver la cadena que envía hacia los dispositivos y también se puede ver la cadena de bytes enviada por el esclavo solicitado.
En este caso se le hizo una petición de datos al esclavo número 11, que retornó el valor 1023.
Si la conexión fuera con el maestro, retornaría el valor 512 que se ve marcado como ADC.

El primer byte es el ID del maestro, que es al que el esclavo va a mandar los datos.
El segundo byte es el ID del esclavo, es para que el maestro sepa de quien vienen los datos.
El tercer byte es el comando solicitado. (1 = Leer el conversor AD y 2 = Encender/Apagar un LED)
El cuarto y quinto byte, equivalen al MSB y LSB de la lectura del conversor AD.
MSB = 3 y LSB = 255, que corresponden al valor máximo de lectura a 5 V. y 10 bits. (1023)
El sexto byte corresponde al checksum de los datos enviados.
En el ejemplo no se hace nada con el checksum, únicamente se calcula y se muestra.
Sin embargo, este dato nos sirve para comprobar si los datos llegaron correctamente.
Las pruebas en físico las realicé a más de 30 metros cada dispositivo, y raramente se perdían datos. :aplauso:
(Muy poca distancia para un sistema que puede alcanzar 1.2 Km de cableado.)
El séptimo byte es el retorno de carro usado para comprobar el final de la cadena a enviar. (Chr 13 o "r" en C)

De esta forma es como se capturan los datos en la PC, aparte de la aplicación anterior.

SPCTRL.jpg


Como pueden ver, aquí los datos ya son mostrados en lenguaje humano. :D
Porque lo que realmente podríamos ver en un terminal común, serían los caracteres imprimibles del ASCII.
Así como se puede observar en la línea (Datos en el búfer)

Como se usaron dos aplicaciones, también se necesitaron dos conexiones serie con la PC.
Esto es totalmente opcional, ya que no es necesario si los microcontroladores van a trabajar sin conexión al exterior.

En este ejemplo usé un PIC16F88 como Maestro y dos PIC12F683 como esclavos.
La librería se puede usar en cualquier PIC que tenga módulo USART o que cuente con interrupción externa. (En este caso se usará RS-232 por software para la comunicación.

Como siempre, espero que esta información sea de utilidad.
Cualquier duda que tengan, con gusto la atenderé.
 

Adjuntos

  • 16F88 Maestro y Esclavos RS-485.rar
    212.3 KB · Visitas: 124
hola pregunta de novato, si o si tengo que utilizar usb-rs485 yo para probarlo ahora con la notebook queria ver como funcionaba, o a caso que tengas otro realizado asi que sea todo emulado.
segunda consulta si o si hay que utilizar Watch-dog Timer o se puede obiar eso en estos tipos de programas de comunicacion. pregunto porque como es por hardware, pense que cuando llega la señal hace una interrupcion y listo. O algo que no estoy entendiendo. gracias y disculpas por las preguntas
 
¿Sí o sí tengo que utilizar usb-rs485 yo para probarlo?
No es un requisito indispensable, pero las laptops o PC's actuales ya no cuentan con puerto serial RS-232
Así que usar ese tipo de conversores es lo único que se puede hacer.
¿Sí o sí hay que utilizar Watch-dog Timer o se puede obviar eso en estos tipos de programas de comunicación?
Tampoco es necesario. El Watch-dog se usa principalmente en programas que van a manejar muchos procesos y que pueden provocar que el programa se cuelgue.
Por ejemplo: Un programa se puede quedar en un bucle esperando la respuesta de un dispositivo I2C desconectado o con falla.
Hasta una mala programación puede provocar cuelgues, así que el uso del Watch-dog es opcional.
 
Hola D@rkbytes estoy usando tu libreria hice un pequeño programa y en proteus dos circuitos como si fueran dos plaquetas el programa es uno solo para las dos plaquetas , pero cada plaqueta tiene unos dipswitch que podes programar ID local e ID Remoto , y cada uno con un led, pulsas en un circuito el boton enciende el led en el otro y viceversa. Pero no me esta funcionando y la veces que lo logre tenia que pulsar un moton de veces el boton, es raro siendo que esta trabajand con interrupcion. ahi te adjunte el zip con el programa y el proteus a ver que opinas? no entiendo lo que pasa, y lo estoy probando con un boton, eso es ahora porque mas adelante seria que envie solo datos in eso, es a modo de prueba pero no me funciona o estare usando algo mal de la libreria nose.
Código:
         IDL=buffer_rs485[0];       //DATO DIRECCION RECEPTOR
         IDR=buffer_rs485[1];       //DATO DIRECCION TRANSMISOR
         dato=buffer_rs485[2];      //DATO ACCION
         chks=buffer_rs485[3];      //DATO CHECKSUM
        
         if((IDL==RS485_ID)&&(IDR==RS485_REPETIDOR))//&&(chks==(IDR+dato)))

En el codigo se ve el IF que utilizo para ver si ese dato es para ese circuito, capaz se puede utilizar buffer_rs485[0]==RS485_ID por ejemplo en vez de almacenarlo en IDL, pero bueno en conclusion no me enciende el led.

Muchas Gracias.
 

Adjuntos

  • Modulo v2r0.zip
    109.4 KB · Visitas: 42
Última edición:
Yo veo un caos completo en el programa.
Cargas la variable ID con un valor que luego cambias, así que ni al caso que tenga un valor inicial.
Eso de multiplicar el estado de un pin tampoco le veo sentido.
Sería mejor usar una máscara para obtener digamos la parte LSB y MSB del puerto B.
Algo así: ID = input_b() & 0x0F; // Se obtiene el valor de la parte baja del puerto B (Bits RB0 <> RB3)
Y para la parte alta sería igual pero con la máscara 0xF0
Con eso te ahorras la variable "a" y dos subrutinas.

A final de cuentas sigues con el mismo problema del otro programa que tienes en otro post.
Te sería más sencillo si usaras un programa para el maestro y otro para los esclavos. (Fue lo que hice yo en el ejemplo expuesto)
Y no es que no se pueda con un solo programa, pero estás perdiendo la lógica mientras avanzas.
 
Voy a probar con la mascara, uso una variable ID, porque si utilizo RS485_ID para cargar un valor no deja,yo comprendo de dos programas, pero si ambos hacen lo mismo es necesario hacer dos programas? pregunto porque capaz estoy haciendo mal. Si por mediante dipswitch puedo programar La direccion local y de destino, ya no me tendria que regir con un valor estatico , agarro una plaqueta pongo la direccion que quiero y listo. por eso mi intencion es de hacer un programa y ver que funcione en ambas plaquetas, con solo cambiar esos dipswitch. Lo que me llama la atencion es que por ejemplo en el
sprintf(buffer_rs485, "%c%c%c%c!",
RS485_REPETIDOR, // Enviar el ID remoto.
RS485_ID, // Enviar el ID local.
comando,
Checksum
);

cambie los %c%c%c%c por %x%x%x%x asi veia en la terminal virtual que enviaba, y vi por ejemplo 212052........ osea que vi la direccion de destino 21 y vi la local 20 y el 52 seria el 'R' pero nunca encendio el led de destino. :cry::cry::cry:
SI es verdad habia escrito en un posto anterior pero usaba RS485 recibiendo por RB0 que la realidad nunca me gusto , ahora tube la suerte que vi eso que posteaste y me gusto porque utilizo la UART del PIC pero me sucede esto que escribo.
 
Última edición:
Justo no me dio timpo a editar el mensaje anterior asi lo agregaba ahi y no escribir otro ahi subi el nuevo zip con la modificacion, hice algunas alteraciones que en verdad es mejor asi como me dijiste deje de usar el RS485_ID utilice dos variables ID_LOCAL y otra ID_RECEPTOR asi no ando variando a cada rato. cuando recibe el dato osea Bandera en 1, hice un simple IF donde chequea si es la direccion del receptor y otro que chequea la suma del dato e ID del receptor. SI se cumple esa Y osea los dos datos correctos (puede ser asi o chequear otras dos cosas es a nivel de ejemplo), luego se fija con otro IF que el dato recibido sea lo mismo del comando osea una letra R y enciende el led.
Bueno sso no lo cumple, como que si no funcionaran los IF o que los datos no esten llegando o que este leyendo mal los datos del Array que capaz que el dato recibido en buffer_rs485[0] en vez de ser la direccion del receptor haya otra cosa, pero no creo fui variando y nada. pase otro zip asi ves el simulado. gracias @D@rkbytes
 

Adjuntos

  • Modulo v2r0.zip
    107.4 KB · Visitas: 35
Usa dos programas y verás que te será más sencillo.
Para que puedas ir viendo lo que está pasando con los datos, usa un sencillo método de depuración local.
Esto se hace enviando datos específicos por RS-232.
De esa forma puedes visualizar lo que tienes en el búfer e ir viendo por qué no te funcionan las comparaciones.

Por ejemplo:
C:
    // Debug local: (RS-232)
    /*****************************************************************/
    printf("ID Local: %u\r", id_local);
    printf("ID Esclavo: %u\r", id_esclavo);
    printf("Comando: %u\r", comando);
    printf("Checksum: %u\r", checksum);

    for(int8 i =0;i<=RS485_MAX_BUFFER;i++)
        printf("Buffer RS-485[%u] = %u\r", i, buffer_rs485[i]);
    /*****************************************************************/
 
Gracias por el aporte.


Este programa emula al maestro, enviando los mismos datos que el microcontrolador.

Ver el archivo adjunto 163419


De esta forma es como se capturan los datos en la PC, aparte de la aplicación anterior.

Ver el archivo adjunto 163420

Que software utilizas para programar este tipo de aplicaciones?.
Tengo interés en la programación y estoy empezando a "tocar" un poco el Visual Studio pero me gustaría saber que programa utilizais los profesionales.

Saludos
 
Hola D@rkbytes, probe lo que me dijiste, pero es cosa increible, si hago dos programas y coloco numero fijo para local y remoto, transmito y el otro recive y viceversa todo perfecto uno manda el otro recibe chequea enciende, a un parentesis
// Debug local: (RS-232)
/*****************************************************************/
printf("ID Local: %u\r", id_local);
printf("ID Esclavo: %u\r", id_esclavo);
printf("Comando: %u\r", comando);
printf("Checksum: %u\r", checksum);

for(int8 i =0;i<=RS485_MAX_BUFFER;i++)
printf("Buffer RS-485[%u] = %u\r", i, buffer_rs485);
/*****************************************************************/

eso que me pasaste buenisimo no se me ocurrio ahi pude ver, con el programa que es uno solo para los dos , no el que es receptor y transmisor.
Igual lo probe en todo.


El que es transmisor y receptor lo pegue eso, y vi perfecto como van los datos y lo bueno que de una ya envia y enciende el led.
Ahora vamos al otro al que seria un programa universal para los dos, en ese no solo tengo que precionar varias veces el boton para que envie, que en realidad nose porque hace eso, (siemrpe hablo en proteus), siendo que con un programa transmisor y otro receptor funciona de una, porque seria eso sera problema de proteus? y el dao veo que no lo envia porque gracias a eso veo en la terminal virtual que esta negra.
Y pregunta dos que seguro sabes eso, porque cuando envia los datos una vez que comienza a enviarlos, no los pone cada uno en su posicion sino que los manda por cualquier lado.
Osea los datos tendrian que estar en buffer_rs485[0],buffer_rs485[1],buffer_rs485[2],buffer_rs485[3] y gracias a esedato que pasaste de depuracion veo que los datos no estan asi a veces el buffer_rs485[0] no tiene valos o a veces si, y veo que hay un dato en buffer_rs485[4], siendo que tendria que estar en el buffer_rs485[0] o buffer_rs485[1],como que no me lo pone en el orden que tendria que salir. Siendo que con los programas Transmisor y Receptor si.


Código:
           sprintf(buffer_rs485, "%c%c%c%c!",
            RS485_REPETIDOR,   // Enviar el ID remoto.
            RS485_ID,         // Enviar el ID local.
            comando,
            Checksum
            );   // Comando para encender/apagar el LED

Ese comando sprint almacena los datos en el array, quiero creer que lo esta haciendo del 0 al x, y la funcion que hiciste enviar_datos_rs485(buffer_rs485); perfecto envia los datos, ahora el porque desordenado, o en cualquier posicion, nose. Y el asunto es que queria hacerlo con un programa configurable para mover los dipswitch y poner las direcciones y no ir cambiando con la computadora y programarle a cada uno eso. Capaz que quiero hacer algo que es dificil nose, pero bueno la verdad sos un guru me salvaste muchas veces, pero esto me quema la cabeza de querer hacer uno generico y capaz no se puede.
 
Ahora vamos al otro al que sería un programa universal para los dos, en ese no solo tengo que presionar varias veces el botón para que envíe, que en realidad no sé por qué hace eso, (siempre hablo en proteus) siendo que con un programa transmisor y otro receptor funciona de una.
¿Por qué sería eso? ¿Será problema de proteus?
Porque tal vez tengas retardos en algunas rutinas y eso hará que se pierda el momento en que llegan datos.
Y pregunta dos, que seguro sabes eso.
¿Por qué cuando envía los datos una vez que comienza a enviarlos, no los pone cada uno en su posición, sino que los manda por cualquier lado?
O sea, los datos tendrían que estar en buffer_rs485[0],buffer_rs485[1],buffer_rs485[2],buffer_rs485[3] y gracias a ese dato que pasaste de depuración veo que los datos no están así.
A veces el buffer_rs485[0] no tiene datos o a veces si, y veo que hay un dato en buffer_rs485[4], siendo que tendría que estar en el buffer_rs485[0] o buffer_rs485[1], como que no me lo pone en el orden que tendría que salir.
Siendo que con los programas Transmisor y Receptor, si.
Eso puede ser por varias causas.
- Una mala sincronización entre los microcontroladores debido a el uso de retardos, en uno o en ambos.
- Mala adaptación o construcción de las líneas RS-485
- Frecuencia alterada de TX y RX
- Que el búfer no se encuentre limpio al momento de recibir nuevos datos y los confundas con datos anteriores.

Y es que los datos no deben cambiar de lugar, a menos que algo los revuelva en el camino o durante su proceso.
Nuevamente, la depuración es el mejor método para saber qué está pasando.
Esto se hace dentro de cada rutina que pienses que puede tener problemas.

El ejemplo que está en post #1 es funcional, solo tienes que hacer que el RS485_ID sea leído por Dipswitch en los esclavos.
 
El ejemplo que está en post #1 es funcional, solo tienes que hacer que el RS485_ID sea leído por Dipswitch en los esclavos.

Justamente lo que hice si te fijas es tu programa con la diferencia que coloque el dipswitch, (Aclaro que siempre con proteus, por lo tanto no puede estar mal la linea RS485), entonces por eso digo esta muy sencillo el programa con un par de lineas modificadas, por lo tanto no entiendo el porque se cambian los datos de lugar y menos presionar varias veces.

Lo que si te comento que estoy haciendo es agregar esto en dos programas diferentes uno transmisor y otro receptor , no un solo programa para los dos y tampoco funciona:
Código:
INT ID_LOCAL=0x20;
INT ID_RECEPTOR=0x20;
INT Checksum;

//port1
#define RS485_ID ID_LOCAL
#define RS485_REPETIDOR ID_RECEPTOR
#define RX_ENABLE_PIN EN
#include "RS485_Lib.h"
char comando='R';

//////////////////////////////////////////////////////////////////////////////
// CONFIGURA LA DIRECCION DEL MODULO LOCAL Y RECEPTOR DE 20 A 2F
//////////////////////////////////////////////////////////////////////////////
void address()
{
   ID_LOCAL+=input_b() & 0x0F;               //20 a 2F
   ID_RECEPTOR+=((input_b()>>4) & 0x0F);     //20 a 2F  en un futuro 30 a 3F
}

Al agregar las lineas de INT ID_LOCAL=0x20; e INT ID_RECEPTOR=0x20; y la funcion address(); que la llamo dentro del void main(), no dentro del bucle infinito , note, que con eso ya el programa funciona mal. Ahi es cuando preciono varias veces, y los datos del buffer_rs485[1] me los pasa al buffer_rs485[4] una locura eso.
Ahora saco es IN ID_LCAL y el otro y coloco RS485_ID y el RS485_REPETIDOR con los valores fijos, ya funciona todo bien. Esas cosas me las hace funcionar mal. Pero sin eso no puedo utilizar los dipswitch.
tambien probe haciendo comentario // //#define RS485_ID ID_LOCAL y al repetidor tambien como se observa abajo y manejarme con las variables, me pasa lo mismo, sin funcionar.
Solo funciona con dos programas uno transmisor y otro receptor , se comunican entre ellos lo mas bien y con ID fijas, agrego esas lineas sea un programa o en dos como mencione y no funciona.

Código:
INT ID_LOCAL=0x20;
INT ID_RECEPTOR=0x20;
INT Checksum;

//port1
//#define RS485_ID ID_LOCAL
//#define RS485_REPETIDOR ID_RECEPTOR

//////////////////////////////////////////////////////////////////////////////
// CONFIGURA LA DIRECCION DEL MODULO LOCAL Y RECEPTOR DE 20 A 2F
//////////////////////////////////////////////////////////////////////////////
void address()
{
   ID_LOCAL+=input_b() & 0x0F;               //20 a 2F
   ID_RECEPTOR+=((input_b()>>4) & 0x0F);     //20 a 2F  en un futuro 30 a 3F
}

void main()
{
   port_b_pullups(0xFF);

   address();
   iniciar_rs485();

   for(int8 i=0; i < RS485_MAX_BUFFER; i++)   // Limpiar el búfer "buffer_rs485"
      buffer_rs485[i] = 0;
     
   while(TRUE)
   {
      if(flag_rs485==1)
      {
         flag_rs485=0;

          printf("ID Local: %u\r", buffer_rs485[0]);
          printf("ID Esclavo: %u\r", buffer_rs485[1]);
          printf("Comando: %u\r", buffer_rs485[2]);
          printf("Checksum: %u\r", buffer_rs485[3]);
     
          for(int8 i =0;i<=RS485_MAX_BUFFER;i++)
              printf("Buffer RS-485[%u] = %u\r", i, buffer_rs485[i]);
             
         if((buffer_rs485[0]==RS485_ID)&&(buffer_rs485[3]==(RS485_ID+comando)))
         {
            if((buffer_rs485[2]=='R'))
               output_high(PIN_C0);

         }  
            delay_ms(500);
            output_low(PIN_C0);
      }
     
     
      if(!input(RA7))
      {
         Checksum=ID_RECEPTOR+comando;
            // Ingresar los datos al arreglo "buffer_rs485" (! = Fin de datos.)
            sprintf(buffer_rs485, "%c%c%c%c!",
            ID_RECEPTOR,   // Enviar el ID remoto.
            ID_LOCAL,         // Enviar el ID local.
            comando,
            Checksum
            );   // Comando para encender/apagar el LED
            // Enviar los datos por RS-485
            enviar_datos_rs485(buffer_rs485);
      }//FIN INPUT RA7
      while(!input(RA7)) delay_ms(10);   // Esperar hasta que se suelte el pulsador.
   }//WHILE

}

La verdad triste el asunto este pero bueno, nose. Gracias D@rkbytes.
 
Última edición:
Supongo que eso sucede porque los esclavos al tener el mismo programa, se crea una colisión y los datos se alteran.
Esto debe pasar porque los esclavos transmiten al mismo tiempo generando un conflicto.

Obviamente eso se evita de la forma en que lo hice yo en el programa de ejemplo.
El maestro va solicitando los datos de cada esclavo por su ID y por ende, cada esclavo responde cuando se le solicita.

El problema entonces radica en que si cada esclavo tendrá un ID definido por un dipswitch, el maestro deberá conocerlo.
Pero como los esclavos realizan el envío de datos al mismo tiempo, se creará la colisión y los datos se alterarán.
Entonces el maestro recibirá datos erróneos y no habrá comunicación.
En sistemas más avanzados se usa una comprobación de bus ocupado y los dispositivos quedan en espera hasta que se libera el bus.
Me parece que la librería de PICC tiene esa rutina y la podrías implementar. (No sé que tan compleja sea)
Ahora que si te quieres evitar ese embrollo, coloca los ID's de los esclavos en el maestro y podrás usar bastantes sin colisión.
Y bueno, el ejemplo del post #1 cumple con eso y funciona sin problemas con varios esclavos.
 
Atrás
Arriba