Transmisor protocolo NEC con PIC

En una entrada anterior propusimos un código para programar un microcontrolador PIC y que fuera capaz de recibir y decodificar la señal que emite un mando a distancia que use el protocolo NEC (los más frecuentes hoy día según mi experiencia). Para complementar esa entrada, hoy os voy a proponer una rutina que envía un código NEC simulando ser un mando a distancia.

Las aplicaciones de estos circuitos transmisores son variadas, desde hacernos nuestro propio mando a distancia para algún proyecto hasta apagar discretamente el televisor en un bar justo antes del último penalti. También se puede usar para transmisión de datos de manera inalámbrica desde uno o varios sensores, ya que tenemos 32 bits disponibles usaríamos los primeros para indicar un código y el resto para el valor. La velocidad de transferencia sería de unos 15 códigos por segundo. No hay ni que mencionar los las utilidades en domótica que cada uno pueda imaginar, como encender y apagar la TV a determinadas horas, incluso cambiar de canal para aparentar que la casa está habitada mientras estamos de vacaciones; o controlar algún dispositivo por ordenador.


Portadora

La rutina es muy sencilla. Aunque hay un punto que merece especial atención: la modulación en 38kHz de la portadora. Lo más cómodo es usar el módulo PWM y configurarlo para esa frecuencia. Cuando queramos transmitir ponemos el duty-cycle al 50% y cuando no queramos lo fijamos al 0%. Con el siguiente código preparamos el módulo CCP:
setup_ccp1(CCP_PWM);
set_pwm1_duty(0);
setup_timer_2(T2_DIV_BY_1,26,1);
Si nuestro PIC funciona a 4MHz significa que Timer2 se incrementa a un ritmo de 1 cada microsegundo. Como no aplicamos prescaler ni postscaler en Timer2, este tardará en llegar a 26 y desbordarse un periodo de 26us. Esto nos da una frecuencia de 38.462kHz, que está un 1.2% por encima de los 38.000kHz que queríamos. Pero si pusiéramos 27 en lugar de 26 tendríamos una frecuencia de 37.037kHz que está un 2.5% por debajo. Podéis probar con ambos, para ver cual de los dos tiene mayor alcance pues depende del receptor usado que tenga más sensibilidad en una frecuencia portadora u otra.

Modulación

Como habíamos dicho, la modulación la haremos conmutando el duty-cycle con la instrucción set_pwm1_duty(13). Nota: 13 es la mitad de 26 que habíamos puesto al fijar el Timer2, por tanto tenemos el DC al 50%.

void ir_send(unsigned int32 code)
{
 unsigned char i = 0;
 disable_interrupts(GLOBAL); 
 
 // Envío el START
 set_pwm1_duty(13);
 delay_us(9000);
 
 set_pwm1_duty(0);
 delay_us(4500);

 // Voy desgranando el código
 while (i < 32) {
  #bit first = code.31
  
  // Transmitimos un 1
  if (first) {
   set_pwm1_duty(13);
   delay_us(560);
   set_pwm1_duty(0);
   delay_us(1690); 
  }
  
  // Transmitimos un 0
  else {
   set_pwm1_duty(13);
   delay_us(560); 
   set_pwm1_duty(0);
   delay_us(560); 
  }
  
  code <<= 1;
  i++;
 }
 
 // Bit de parada
 set_pwm1_duty(13);
 delay_us(560); 
 set_pwm1_duty(0);
 
 enable_interrupts(GLOBAL);
}


  • Comenzamos emitiendo una ráfaga de 9ms que sirve para que el receptor se prepare y ajuste su control de ganancia.
  • Tras esto una pausa de 4.5ms.
  • Ahora vamos desgranando el código binario. Para el 1 se transmite un pulso de 560us seguido de una pausa de 1690us.
  • Para el 0 se transmite un pulso de 560us de duración igual que el anterior, pero a continuación se hace una pausa más corta, que dura otros 560us.
  • Finalmente se transmite un pulso de parada que cierra la transmisión. Este último pulso es necesario para que el receptor sepa si la pausa tras el último bit es de 560 o de 1690us, y pueda deducir si se trataba de un 0 o de un 1 respectivamente.

Uso de la función

Para llamar a la función es suficiente con pasarle un argumento de 32bits que será lo que se transmita. Por ejemplo si hemos capturado con el receptor el código de encendido/apagado del televisor y es 20DF10EF lo podemos transmitir de esta manera:

#define IR_CODE_TV_ON   0x20DF10EFL

ir_send(IR_CODE_TV_ON);

el código 20DF10EF se escribe en binario como 0010 0000 1101 1111 0001 0000 1110 1111 (32 bits, 32 unos y ceros). Si monitorizamos GP2 en el analizador lógico de MPLAB veremos la siguiente ráfaga:


se aprecia el pulso de start, la duración de los 1 y los 0 y el bit de parada. Pero recordemos que lo que modulamos es una portadora de 38kHz, así que si hacemos zoom apreciamos esta frecuencia:


Conexionado

Una de las preguntas más frecuentes es si necesitamos un transistor para encender y apagar el LED IR o si por el contrario será suficiente con la capacidad de salida del PIC. ¿Hay que poner alguna resistencia en serie? Para aclararlo es preciso calcular el tiempo promedio que permanece encendido el LED durante el envío de un código.


Vamos a tomar el supuesto de que el código sea todo ceros, puesto que la pausa para los 1 es más larga, un pulso que sea todo ceros tiene más energía por unidad de tiempo. Digamos que no deja descansar el LED. En este pulso durará 9000us + 4500us + 32*1120 + 560 = 49900us, de todo ese tiempo, sólo estará activo el pulso inicial de 9000us y los 32 pulsos de 560 más el de parada, en total 27480us. Pero recordemos que la portadora tiene un 50% de duty-cycle, por lo que el tiempo que realmente está encendido es la mitad: 13740us. Esto es un 28% del tiempo total. Y para el código que más potencia transmite.

Hagamos los cálculos para un código que esté compuesto sólo de unos. En es caso, el tiempo invertido en la transmisión es: 9000us + 4500us + 32*2250us + 560us = 86060us. Y el tiempo que permanece encendido es el mismo que antes, 13740us, porque sólo varían las pausas. Esto es un 16%.

Como no hay razones para suponer que los códigos van a estar formados por todo unos o por todo ceros, haremos la media de los dos casos. En promedio el LED recibe un 22% de la energía durante el tiempo que está encendido. Y eso suponiendo que emita continuamente, cosa que no se suele hacer, pues se transmite el código, una pausa y a continuación sólo el comando de repetición, que es mucho más breve. En cualquier caso si alimentamos el PIC con 5 voltios, el 22% supone 1.1V, un valor aceptable para el LED.

Como conclusión no es preciso usar un transistor ni una resistencia cuando se conecta el LED al PIC. Conseguimos el máximo alcance a costa de reducir muy ligeramente el tiempo de vida. Si acaso quisiera usarse una resistencia limitadora para proteger tanto al PIC como al LED debería usarse un valor bajo, alrededor de 100ohm, para no rebajar demasiado el alcance del transmisor.


Os dejo el código fuente y el fichero .hex en este enlace.

5 comentarios:

  1. seria posible ver el ejemplo que poneis para un pic 16f84a

    ResponderEliminar
  2. El 16F84A no tiene módulo de PWM así que tendríamos que generar la frecuencia portadora usando algún timer.

    Si utilizáramos un cristal de 4MHz como es lo habitual, tendríamos un millón de instrucciones por segundo; si dividimos ese millón entre 36000 que es la frecuencia de la portadora nos da aproximadamente 28.

    Eso significa que tendríamos sólo 28 instrucciones de margen entre cambios de estado. Está muy ajustado, supongo que se podría hacer pero quizá programando en ASM, desde luego que no en C.

    ResponderEliminar
  3. Hola, tengo unos mandos para leds rgb que usan este protocolo, a traves de una web alemana he conseguido el valor de cada boton, pero no me cuadra con el tipo de valor en bits que se transmiten:

    /* RC Commands */

    // Functions on Remote Control
    #define RC_BRIGHTER 16187647
    #define RC_DARKER 16220287
    #define RC_OFF 16203967
    #define RC_ON 16236607
    #define RC_FLASH 16240687
    #define RC_STROBE 16248847
    #define RC_FADE 16238647
    #define RC_SMOOTH 16246807

    // Colors on Remote Control
    #define RC_RED 16195807
    #define RC_GREEN 16228447
    #define RC_BLUE 16212127
    #define RC_WHITE 16244767

    #define RC_RED1 16191727
    #define RC_RED2 16199887
    #define RC_RED3 16189687
    #define RC_RED4 16197847

    #define RC_GREEN1 16224367
    #define RC_GREEN2 16232527
    #define RC_GREEN3 16222327
    #define RC_GREEN4 16230487

    #define RC_BLUE1 16208047
    #define RC_BLUE2 16216207
    #define RC_BLUE3 16206007
    #define RC_BLUE4 16214167

    estos son los valores decimales, no se pero no me cuadran, como lo ves?

    ResponderEliminar
  4. Hola espero que me puedas ayudar, estoy haciendo un emisor para encender tres focos por IR pero tengo problemas en la forma de generar el código, (tome tu idea de cómo generar el código), tengo una opción para escoger el código que se va a mandar pero no cambia la opción, lo he revisado pero no entiendo porque no hace este cambio. Dejo el código que hice, espero que me puedas ayudar. Gracias

    #include <12F683.H>
    #fuses INTRC, NOWDT, MCLR, NOIESO, PROTECT, PUT, NOBROWNOUT, NOFCMEN
    #use delay( clock = 8000000 )
    #use standard_io( A )

    #define CO1 0xA1
    #define CO2 0xA2
    #define CO3 0xA3

    void Rutime( int limt ) //Rutina de tiempo.
    {
    int Cont = 0;
    while( Cont < limt )
    {
    set_timer0( 100 ); //Carga el timer para 5ms.
    while( get_timer0() ); //Se espera aqui hasta que el timer0 valga 0 osea que se ha desbordado y ha pasado 10ms.
    Cont++; //Cada 5ms se incrementa contador.
    }
    Cont = 0; //Pasando los 5ms en alto pasamos el contador a 0.
    }

    void espera( void ) //Esta funcion retarda 1/2 segundo.
    {
    int lime = 100;
    set_pwm1_duty( 0 );
    Rutime( lime );
    }

    void mancode( int cod, int lim1, int lim2 )
    {
    int s = 0;
    espera();
    while( s < 8 ) //genera el codigo
    {
    #bit uno = cod.7
    if( uno )
    {
    set_pwm1_duty( 25 ); //Modula a 38KHz
    Rutime( lim1 );
    }
    else
    {
    set_pwm1_duty( 0 ); //PWM a cero
    Rutime( lim2 );
    }
    cod <<= 1;
    s++;
    }
    espera();
    }

    void main( void )
    {
    int menu = 0; //Variable del menu.
    int li1, li2; //Intervalos de tiempo

    //setup_oscillator( OSC_4MHZ );
    setup_wdt( WDT_OFF );
    setup_adc( ADC_OFF ); //Desactiva el ADC.
    setup_adc_ports( NO_ANALOGS ); //No puertos analogos
    setup_timer_2( T2_DIV_BY_1, 51, 1 ); //Prescaler de 1, PR2 = 51.
    setup_ccp1( CCP_PWM ); //Activa el PWM
    set_pwm1_duty( 0 ); //Desactiva el modulo PWM. si se lo pone 0 al ciclo util es como si se desactivara.
    set_timer0( 100 ); //Carga del Timer0.
    setup_timer_0( RTCC_INTERNAL | RTCC_DIV_64 ); //Prescaler de 64.

    while( 1 )
    {
    if( !INPUT( PIN_A0 ) )
    {
    menu++; //Incrementa menu si A0 = 0.
    if( menu == 4 )
    {
    menu = 0;
    }
    delay_ms( 500 ); //Espera 500mseg para evitar rebotes.
    }
    if( menu == 0 )
    {
    set_pwm1_duty( 0 ); //CCP a cero
    }
    if( menu == 1 )
    {
    li1 = 2;
    li2 = 1;
    mancode( CO1, li1, li2 ); //Manda a generar el primer codigo
    }
    if( menu == 2 )
    {
    li1 = 4;
    li2 = 1;
    mancode( CO2, li1, li2 ); //Manda a generar el segundo codigo
    }
    if( menu == 3 )
    {
    li1 = 6;
    li2 = 1;
    mancode( CO3, li1, li2 ); //Manda a generar el tercer codigo
    }
    }
    }

    ResponderEliminar

Por favor, usa mayúsculas, minúsculas, puntos, comas, interrogaciones y todo eso. Muchas gracias.