| |
Introducción:
Venimos hablando desde hace tiempo del RS485, de cómo montarlo con los
PIC's y de cómo implementar algún algoritmo fácil para que varios
dispositivos contesten cuando se les pregunte, y solo entonces. Rescato
aquí un pequeño proyecto de hace unos meses en el que hicimos exactamente
eso: Un PIC 16F628A que habla con el PC mediante RS485 y es capaz de
comandar un sencillo Relé. Poca cosa para algunos pero todo un logro para
muchos.
Con este proyecto podemos aprender todas las técnicas básicas para
implementar una red RS485 con dispositivos diferenciados hablando con un
PC que va a actuar como MASTER. |
|
|
1ª Parte: El
Hardware.
El esquema:
Este pequeño proyecto tiene pocas cosas, un PIC 16F628A sin cristal
externo, vamos a usar el oscilador interno a 4Mhz, un Relé de 5V que vamos
a activar con un simple transistor y una pequeña fuente alrededor de un
7805 para poder utilizar 12V, y por supuesto, un SN75176 que es uno de los
muchos chips idénticos al MAX485 pero mas baratos.

El PCB:
Como veis el ruteado es razonablemente simple dada la escasez de
componentes que calza este dispositivo.

Los componentes:
Y esta es la misma imagen pero con la capa de componentes visible.

El producto montado:
Aquí tenéis el cacharro ya montado y con la corriente dada.

|
|
2ª Parte: El
Software.
El dispositivo que vamos a montar es un SLAVE, esclavo, de una Red
RS485.
Este firmware tiene la característica de tener que conmutar de Low
un Pin del PIC para recibir desde la USART, que es el estado por
defecto, a High para poder transmitir, son las funciones
USART_activa_rx() y USART_activa_tx().
La recepción se realiza sobre un buffer, carácter a carácter, de forma que
sólo al recibir un carácter especial de finalización podemos pasar a
procesar lo recibido. Para ello usamos la interrupción de la
USART y la funcione USART_add_to_buffer(). El carácter de
finalización de una trama lo tenemos definido con un define const
char end_of_transmit = '!' con vistas a facilitar su cambio según las
necesidades. Yo he utilizado el carácter '!' para indicar el fin de la
trama, podéis coger el que queráis.
Como la transmisión de un mensaje del MASTER es broadcast,
todos lo oyen, es imprescindible que cada PIC lleve un identificador
único en la red RS485, para que se sienta aludido, responda, sólo
cuando dicho identificador único aparezca en la trama enviada por el
MASTER. Para ello he implementado un define const char WhoAmI =
'1'; que ha de ser único para cada PIC, así a uno le pondremos
'1', a otro '2' ... etc.
Como además podemos tener en la misma red varios tipos de dispositivos,
que no sean éste Relé en la Lejanía, le he colocado otro define que
me dice a qué tipo de dispositivo vamos a responder. En nuestro
caso vamos a usar const char WhatAmI = 'R';
Y por último necesitamos al menos una orden directa para ejecutar
una función específica. La única orden que he implementado es const
char Command_Execute = 'X'; que va a producir una conmutación del Relé
de un segundo de tiempo.
Todo esto significa que el PC MASTER va a transmitir un frame del
estilo de "R1X!" cuyo significado sería: Mensaje a los Dispositivos
de Tipo 'R', al concreto dispositivo '1' para que ejecute el
Comando 'X', fin de transmisión.
Todos los PIC escucharán este mensaje pero sólo el PIC que sea de Tipo
'R', que tenga el identificador concreto '1' ejecutará el
comando 'X'.
Y por último, para comprobar que la orden ha sido recibida
correctamente este mismo PIC que sí va a ejecutar la orden recibida
responderá con una trama del estilo de "R1A!" significando que el
Relé 1 ha actuado correctamente.
Fijaos que todos los demás oirán ese "R1A!" pero no harán nada
porque no son el '1' ni 'A' es un comando válido.
El código completo es:
|
| |
Titulo |
|
| |
/** \file firmware_iRELE485-628.c
* \brief Este fichero contiene el cuerpo principal del Firmware del\n
* dispositivo auxiliar satélite de la iACD V1.0.4. denominado iRELE485-628
*
* Integra todos los subsistemas y funciones implementados.\n
* Source para el compilador CCS C v.3.242
*
* Microprocesador : <b>16F628A</b> (18 pines PDIP) \n\n
* Fuses utilizados: \n
* \li <b>INTRC</b> Internal RC Osc
* \li <b>MCLR</b> Master Clear pin enabled
* \li <b>NOWDT</b> No Watch Dog Timer
* \li <b>NOPROTECT</b> Code not protected from reading
* \li <b>NOPUT</b> No Power Up Timer
* \li <b>NOBROWNOUT</b> No brownout reset
* \li <b>NOLVP</b> No low voltage prgming, B3(PIC16)
* \li <b>NOCPD</b> No EE protection
*
* \author Diego Márquez García-Cuervo \n
* <http://picmania.garcia-cuervo.net>
*
* \version 1.0.0.A
*/
///////////////////////////////////////////////////////////
//
// Definiciones estándar del 16F628A
//
///////////////////////////////////////////////////////////
#include <16F628A.h>
///////////////////////////////////////////////////////////
//
// Fuses y ajuste de Clock
//
///////////////////////////////////////////////////////////
#fuses INTRC,MCLR,NOWDT,NOPROTECT,NOPUT,NOBROWNOUT,NOLVP,NOCPD
#use delay(clock=4000000)
///////////////////////////////////////////////////////////
//
// Defines y Constantes
//
///////////////////////////////////////////////////////////
/*! \def DRIVER_RELE
* Pin accionador del Relé número 1 \li HIGH : Relé ON.
*/
#define DRIVER_RELE PIN_B4
/*! \def TX_485_ENABLE
* Pin que habilita la transmisión por el canal RS485
* \li LOW Habilita recepción RS485
* \li HIGH Habilita transmisión RS485.
*/
#define TX_485_ENABLE PIN_B5
/*! \def bytes_for_USART_buffer
* \brief Establece el número de bytes de longitud del buffer de la USART.
*/
#define bytes_for_USART_buffer 12
/*! \var const char VERSION
* \brief Versión del Firmware.
*/
const char VERSION[]="1.0.0\0";
/*! \var const char WhatAmI
* \brief Identificador de tipo de dispositivo: 'R' Relé.
*/
const char WhatAmI = 'R';
/*! \var const char WhoAmI
* \brief Identificador de dispositivo.
*/
const char WhoAmI = '1';
/*! \var const char Command_Execute
* \brief Comando que indica ejecución de acción : Activar Relé
*/
const char Command_Execute = 'X';
/*! \var const char end_of_transmit
* \brief Carácter que indica fin de transmisión (no buffereable)
*/
const char end_of_transmit = '!';
///////////////////////////////////////////////////////////
//
// Canal de Comunicación
//
///////////////////////////////////////////////////////////
#use rs232(baud=9600,xmit=PIN_B2,rcv=PIN_B1)
///////////////////////////////////////////////////////////
//
// Prototipos de Funciones
//
///////////////////////////////////////////////////////////
void USART_activa_tx(void);
void USART_activa_rx(void);
void USART_add_to_buffer(char c);
///////////////////////////////////////////////////////////
//
// R A M : Variables Globales Estáticas
//
///////////////////////////////////////////////////////////
/*! \var USART_buffer
* \brief Buffer de la USART
*/
static char USART_buffer[bytes_for_USART_buffer];
/*! \var USART_nextRec
* \brief Indice del Buffer de la USART
*/
static int8 USART_nextRec;
/** \var Command
* \brief Comando válido para ejecutar.
*/
int8 Command;
///////////////////////////////////////////////////////////
//
// Funciones
//
///////////////////////////////////////////////////////////
/** \brief Abre el canal RS485 para transmitir.
* \return void
*/
void USART_activa_tx(void){
output_high(TX_485_ENABLE);
delay_ms(5);
}
/** \brief Abre el canal RS485 para recibir.
* \return void
*/
void USART_activa_rx(void){
delay_ms(5);
output_low(TX_485_ENABLE);
delay_ms(1);
}
/** \brief Inicializa el Buffer de la USART
*
* Recoge el último carácter recibido desde la USART sobre USART_buffer
* e incrementa en 1 el índice USART_nextRec.\n\n
* Si el carácter recibido es Retorno de carro '\\r' ó 0x0D comprueba
identidad de dispositivo y comando recibido
*
* \return void
*/
void USART_add_to_buffer(char c){
USART_buffer[USART_nextRec++]=c;
if(USART_nextRec==bytes_for_USART_buffer){
USART_nextRec=0;
}
if(c==end_of_transmit){
--USART_nextRec;
if(USART_buffer[USART_nextRec-3]==WhatAmI){
if(USART_buffer[USART_nextRec-2]==WhoAmI){
if(USART_buffer[USART_nextRec-1]==Command_Execute){
Command=0x01;
}
}
}
USART_nextRec = 0;
}
}
///////////////////////////////////////////////////////////
//
// Rutinas de Servicio de Interrupciones
//
///////////////////////////////////////////////////////////
#int_rda
/** \brief Interrupción por : Recepción del Canal Serie.
*
*/
void interrupt_service_rutine_rda(void) {
char USART_nextChar;
USART_nextChar='\0';
if(kbhit()){
USART_nextChar=getc();
USART_add_to_buffer(USART_nextChar);
}
}
///////////////////////////////////////////////////////////
//
// M A I N
//
///////////////////////////////////////////////////////////
/** \brief Constituye el núcleo principal del sistema
*
* \return void.
*/
void main(void) {
setup_oscillator(OSC_4MHZ);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
setup_timer_1(T1_INTERNAL|T1_DIV_BY_4);
setup_timer_2(T2_DISABLED,0,1);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
port_b_pullups(False);
set_tris_b(0b00000010);
output_low(DRIVER_RELE);
delay_ms(100);
USART_nextRec = 0;
Command = 0x00;
USART_activa_rx();
delay_ms(333);
enable_interrupts(global);
enable_interrupts(int_rda);
USART_activa_tx();
printf("[iRELE485-628] v.%s\r\n",version);
USART_activa_rx();
do{
if(Command==0x01){
Command =0x00;
USART_activa_tx();
printf("R%cA%c\r\n",WhoAmI,end_of_transmit);
USART_activa_rx();
output_high(DRIVER_RELE);
delay_ms(2000);
output_low(DRIVER_RELE);
}
} while(true);
}
///////////////////////////////////////////////////////////
//
// End of firmware
//
///////////////////////////////////////////////////////////
nte
|
|
| |
|
|
|