En la primera parte de este texto, hablamos sobre el control de luminosidad de una lámpara incandescente con un microcontrolador usando un triac y detección de cruce por cero. En esta segunda parte trataremos  la recepción de las teclas de control remoto.

En el trabajo original que acompaña el proyecto del dimer se trataba muy brevemente la recepción de las señales del protocolo SIRC de Sony. Si ya revisaste el reporte original que se encuentra en la zona de descargas, encontraras que este tipo de control remoto envía los “unos” y “ceros” codificados como pulsos de distinta duración (pulse width encoding).

En esta entrada tratamos la recepción del protocolo SIRC con un microcontrolador PIC.

 

Características del protocolo de SONY.

Recordemos algunas de las características del protocolo SIRC de SONY:

  • Versiones con 12, 15 y 20 bits de longitud en la trama.
  • 5 bits de dirección y 7 de comando (trama de 12 bits).
  • Codificación por anchura de pulsos.
  • Frecuencia portadora de haz infrarrojo de 40 Khz.
  • Tiempo de bit de 1.2 o 0.6 mili-segundos.

 

Implementación del software.

Para recibir las tramas enviadas por el control remoto, el firmware del dimer mide la longitud de los pulsos que esta recibiendo en el pin al que se encuentra conectado el receptor infrarrojo (el cual deberá tener una frecuencia central de 40 Khz).

La manera más practica de medir los tiempos de bit aprovechando el hardware del microcontrolador PIC12F683 sería utilizar el módulo input capture, el cual puede realizar la medición del periodo de los pulsos recibidos desde el  control remoto. Sin embargo, no podemos hacer esto debido  a que el pin de entrada del módulo CCP (Capture, Compare, PWM) comparte el mismo pin con el de interrupción externa, que como explicamos anteriormente, estaremos usando para detectar los cruces por cero de la linea de C.A.

La solución que se me ocurrió entonces fue utilizar un timer auxiliado con la interrupción por cambio de estado, disponible en todos  los pines de I/O del microcontrolador PIC12F683.

Con la solución solución mencionada anteriormente, logramos medir el tiempo entre dos interrupciones de cambio de estado producidas por un flanco descendente en el pin que designemos, y nos da la flexibilidad de usar cualquier pin de GPIO del microcontrolador. Este método también se describe en una nota de aplicación de microchip technology, citada al final de este artículo.

La interrupción que atiende los cambios de estado en el pin realiza las siguientes operaciones:

  • Determinar los pines que cambiaron en el puerto (XOR) y determinar si el pin que nos interesa esta en estado bajo tras el cambio
  • Determinar si ya estaba recibiendo bits, caso de que sea falso, comenzar la recepción
  • Cada vez que se produce una interrupción de cambio de estado por flanco descendente, se coloca el valor actual del timer en lTimerValue y se señaliza con la bandera iBitReadyFlag
void vInterruptRxEdge()
{
	static int iPortChanges, iPortLastVal;		// Static vars
	iPortChanges = iPortLastVal ^ input_a();	// Apply Xor opertarion to detect which bits changed since last interrupt
	iPortLastVal = input_a();					// Keep current port values for the next interrupt

	if(bit_test(iPortChanges,1) && !bit_test( iPortLastVal,1))	// Check if high to low transition
	{
		if( iRxStatusFlag )		// This code measures the time between falling edges of the remote signal
		{
			lTimerValue = get_timer1();
			iBitReadyFlag = TRUE;
		}
		else	// First high to low edge will execute this
			iRxStatusFlag = TRUE;	// A high to low transition triggers reception state
		set_timer1( 0xFB1E );		// Always prepare timmer to overflow every 5ms
	}
	return;
}

La interrupción del timer 1 realiza las siguientes operaciones:

  • Cargar un valor al timer para que se vuelva a desbordar tras 5 ms.
  • Poner en falso la bandera iRxStatusFlag.
#INT_TIMER1
void vDisableRx()
{
	set_timer1( 0xFB1E );
  	iRxStatusFlag = FALSE;
	return;
}

El primer paso es determinar si se trata de un flanco ascendente, o descendente ya que el objetivo del algoritmo que pretendemos realizar debe medir los tiempos entre los flancos descendentes únicamente.

En el primer flanco descendente el programa entra en “Estado de recepción” y prepara el Timer1 para desbordarse cada 5 ms (en SIRC el tiempo de bit es de menos de 3 ms), lo que garantiza que el timer no se desbordará mientras reciba una secuencia continua de bits.

Luego, cada flanco descendente provoca que el valor del timer en ese instante sea leído y colocado en una variable para su posterior procesamiento. En el programa principal tenemos la función que se encarga del procesamiento para cada uno de los bits recibidos.

long lRemoteReception()
{
	long lReturnValue = 0;
	int iRxBitCount = 0;

	while( iRxStatusFlag )		// If there has been a high to low transition on the IR receiver pin
	{							// main program will enter the RX loop and will wait here for every bit to arrive
		if( iBitReadyFlag )		// Check if the ISR has set the flag, otherwise wait
		{
			if( lTimerValue > 0xFC95 && lTimerValue <= 0xFD44 )		// Check the period of the received bit  			{														// lTimerValue > 1.5ms & <= 2.2ms 				bit_set( lReturnValue, _SIRC_FRAME_LENGTH-1 ); 				lReturnValue >>= 1;
			}
			else if( lTimerValue > 0xFBE6 && lTimerValue <= 0xFC95 )// lTimerValue > 0.8ms & <= 1.5ms 			{ 				bit_clear( lReturnValue, _SIRC_FRAME_LENGTH-1 ); 				lReturnValue >>= 1;									//Keep the bit as zero and shift right
			}
			iBitReadyFlag = FALSE; 		//Clear the flag since we've already processed the time measured by the timer
			iRxBitCount++;				//Increase the received bit counter
		}
	}
	//Timer 1 Interrupt will clear the iRxStatus Flag and the program will be ready to return, this means that the SIRC frame has ended
	//return ( iRxBitCount >= 12 ) ? lReturnValue : 0;	//since more than 5ms has passed since the last edge detection.
	if( iRxBitCount >= 12 )
		return lReturnValue;
	return 0;
}

En esta función el programa entra en un ciclo para esperar  a que se produzcan las interrupciones por cambio de estado y nos arrojen nuevos valores medidos mediante el timer del PIC, el programa no dejará el ciclo while, hasta que no pasen más de 5 ms sin que se produzca un flanco descendente en el pin del microcontrolador conectado al receptor IR, en este momento se producirá una interrupción por desbordamiento del timer1.

Cada vez que se produce una interrupción por un flanco descendente, el valor es comparado con constantes predefinidas que se han calculado previamente y que corresponden a los tiempos estándar para los bits definidos en el protocolo SIRC. Luego, cada bit se almacena en la variable de tipo long lReturnValue.

Este software se creó para recibir tramas SIRC de 12 bits, por lo cual, solo retornará un valor válido si se reciben 12 o más bits.

Finalmente solo nos queda usar el valor recibido para hacer lo que queramos, en mi caso escribí una pequeña función que decide que hacer con la tecla que oprimimos:

void iRemoteKeyProcess( long lRemoteData )
{
	switch( lRemoteData )
	{
		case 0x0095:		// On/Off Key
			vLampToggle();
		break;
		case 0x0092:		// Increase Brightness Key (Volume Up)
			vLampBright();
		break;
		case 0x0093:		// Decrease Brightness Key (Volume Down)
			VLampDimm();
		break;
		default:			// Unrecognized Key
		vHwFlashLed ( 5, _SHORT_DELAY );
	}
}

void main()
{
	long lSircFrame = 0;

	vHwInitialize();
	vHwFlashLed ( 10, _SHORT_DELAY );
	enable_interrupts( GLOBAL );
	for( ; ; )									// Main Program Loop
	{
		lSircFrame = lRemoteReception();		// Check if we need to process any incoming frame
		if( lSircFrame != 0 )					// If something has been received, take care of it
		iRemoteKeyProcess( lSircFrame );
		vButtonProcess();						// Check the button
	}
}

Conclusiones

En esta entrada tratamos la recepción de un protocolo para control remoto utilizado en aparatos electrónicos domésticos, mediante un microcontrolador PIC y el compilador CCS. Como podemos ver, resulta sencillo realizar la recepción de las tramas del protocolo SIRC usando un timer y la interrupción por cambio de estado de los pines de I/O.

El software podría mejorarse, adaptarse y simplificarse: Por ejemplo si usamos el módulo CCP del PIC podría quedar más sencillo. También puede adaptarse a otras situaciones en las que se requiere usar el CPU para otras tareas mientras se recibe una trama, implementando una función que no bloquee la ejecución del programa mientras se recibe una trama SIRC.

Enlaces de interés