Author Topic: NEC IR Remote using PIC  (Read 21060 times)

0 Members and 1 Guest are viewing this topic.

Offline xtoffer

  • Contributor
  • Posts: 33
  • Country: se
  • A CS turned electronic hobbyist.
Re: NEC IR Remote using PIC
« Reply #25 on: March 06, 2016, 08:52:49 am »
Quote
fixed the inverted bytes, changed "byte_to_send = byte_to_send>>1;" to "byte_to_send = 1<<byte_to_send;"

If you want to send the data in the reverse direction, a correct way of reversing would be just to replace the operator. In your case you are now shifting a 1, byte_to_send number of times left.

So to be clear that would be
Code: [Select]
byte_to_send = byte_to_send << 1;
And as I tried to explain above, if you shift the other way because you want to reverse the direction you need to read from the other end of the byte as well. You can do this by (byte_to_send & 0x80).
 

Offline dannyf

  • Super Contributor
  • ***
  • Posts: 8221
  • Country: 00
Re: NEC IR Remote using PIC
« Reply #26 on: March 06, 2016, 02:15:09 pm »
an alternate approach to what you are doing is to generate a 38Khz carrier signal, and then short it periodically to ground to generate the signal to be transmitted. This can be done with a separate pin controlled in a timer interrupt.

The benefit of this approach is so that all transmission can be done in the interrupt and your main loop can be freed to do something else, like processing user buttons.
================================
https://dannyelectronics.wordpress.com/
 

Offline luviniTopic starter

  • Contributor
  • Posts: 33
  • Country: br
  • PIC C programming!
Re: NEC IR Remote using PIC
« Reply #27 on: March 06, 2016, 08:03:43 pm »
Quote
fixed the inverted bytes, changed "byte_to_send = byte_to_send>>1;" to "byte_to_send = 1<<byte_to_send;"

If you want to send the data in the reverse direction, a correct way of reversing would be just to replace the operator. In your case you are now shifting a 1, byte_to_send number of times left.

So to be clear that would be
Code: [Select]
byte_to_send = byte_to_send << 1;
And as I tried to explain above, if you shift the other way because you want to reverse the direction you need to read from the other end of the byte as well. You can do this by (byte_to_send & 0x80).

that worked! but why 0x80? isnt that 128?

current code:
Code: [Select]
#include <16F628A.h>

#fuses NOWDT //Disable Watchdog Timer
#fuses NOPROTECT //Disable Read/Write Protect
#FUSES NOCPD //Disable EE protection
#fuses NOLVP //Disable Low Voltage Programming (ICSP) ==keep low during operation if enabled to prevent entering ICSP mode==
#fuses INTRC_IO //Set CLK pins as I/O
#fuses NOMCLR //Disable Master Clear
#FUSES NOBROWNOUT //Disable Reseting On Power Loss
//#FUSES HS //Crystal With More than 4Mhz

#use delay(clock=4000000)
//=================
// Examples
//DeviceID of TV
//00100000=32
//
//Code of button OK
//00100010=34
//=================

//DeviceID
const char device_address = 32; //DeviceID of TV
//Buttons
const char OK = 34; //button OK


//=================
char i;
char byte_to_send;
//================================
//      NEC PROTOCOL

void startSending() // START
{
set_pwm1_duty(12);;
delay_ms(9);
set_pwm1_duty(0);
delay_us(4500);
}

void logical1() // 1
{
set_pwm1_duty(12);
delay_us(560);
set_pwm1_duty(0);
delay_us(1690);
}

void logical0() // 0
{
set_pwm1_duty(12);
delay_us(560);
set_pwm1_duty(0);
delay_us(560);
}

void repeatCode() // REPEAT
{
set_pwm1_duty(12);
delay_ms(9);
set_pwm1_duty(0);
delay_us(2250);
set_pwm1_duty(12);
delay_us(560);
set_pwm1_duty(0);
delay_us(560);
}

void end_sending()
{
set_pwm1_duty(12);
delay_us(560);
set_pwm1_duty(0);
}
//================================

void send_byte(byte_to_send)
{
for (i=0; i<8; i++)
{
if (byte_to_send & 0x80)
{
output_high(pin_b1); //probe pin B1 for 1 signals
logical1();
output_low(pin_b1);
}
else
{
output_high(pin_b0); //probe pin B0 for 0 signals
logical0();
output_low(pin_b0);
}
byte_to_send = byte_to_send << 1;
}
}

//================================

void send_key_OK() //Send button OK
{++++++++++++++++++++++++++
startSending(); //Send 9ms START pulse
send_byte(device_address); //Send DeviceID
send_byte(~device_address); //Send Inverted DeviceID
send_byte(OK); //Send ButtonCode
send_byte(~OK); //Send inverted ButtonCode
end_sending();
}

void main()
{
setup_ccp1(ccp_pwm);            //CCPM1 = PIN_B3 on PIC16F628A
setup_timer_2(T2_DIV_BY_1,25, 1);   //38Khz PWM using 4Mhz Osc.
delay_us(20);

while(1)
{
//===============================
if(!input(pin_a0))
{
send_key_OK();
while(!input(pin_a0))
{
delay_ms(110);
repeatCode();
}
}
//===============================
}
}
 

Offline xtoffer

  • Contributor
  • Posts: 33
  • Country: se
  • A CS turned electronic hobbyist.
Re: NEC IR Remote using PIC
« Reply #28 on: March 06, 2016, 09:00:39 pm »
that worked! but why 0x80? isnt that 128?

It is! Some people find it easier to think in hex because it is easy to convert between hex and binary (each digit in hex represents four digits in binary). To be even more obvious in the code you could write something like (1 << 7) to select the eighth bit.
 

Offline dannyf

  • Super Contributor
  • ***
  • Posts: 8221
  • Country: 00
Re: NEC IR Remote using PIC
« Reply #29 on: March 06, 2016, 11:32:49 pm »
I took my old soft_uart module (on a LM4F120) and got it working on a pic16f684 to send a string under the nec protocol.

The basic set-up is to generate a 38Khz carrier signal via the CCP1 module on RC5/CCP1 pin. That signal is periodically grounded by RC0 controlled by tmr0 interrupt, to produce the desired '1' and '0'.

Code: [Select]
/tmr0 isr
void interrupt isr(void) {
T0IF = 0; //clear the flag
if (_nec_carrier) { //carrier was sent. now, short the output
if (_nec_mask & *_nec_ptr) TMR0=-NEC_TICK; //this is a 1 bit. followed by 560us
else TMR0=-NEC_TICKx3; //this is a 0 bit. followed by 560us x 3
NEC_OFF(); //short the output
_nec_carrier=0; //output has been shorted
_nec_mask = _nec_mask >> 1; //shift to the next bit
if (_nec_mask == 0) { //advance to the next char
_nec_mask = 0x80; //msb first
_nec_ptr+=1; //advance tot he next char
IO_FLP(OUT_PORT, OUT_PIN); //flip out_pin for debugging
};
if (*_nec_ptr==0) { //if no more char to be sent, stop the transmission
T0IE = 0; //stop the interrupt
_nec_ready=1; //nec ready
};
} else { //output has been shorted. now see if we can transmitt new data
//send the carrier signal
TMR0=-NEC_TICK; //load the offset for the carrier seignal
NEC_ON(); //send the carrier signal
_nec_carrier=1; //carrier has been sent
}
}

The set-up is a fire-and-forget type: the main loop looks to see if the nec module is ready. Once it is ready, it loads up the string to be sent and go about doing its other businesses.

Code: [Select]
while (1) {
if (nec_isready()) nec_send(nRAM); //if nec unit is ready, send data
}

The buffer, nRAM, contains the chars to be sent. And in this case, it is defined as:

Code: [Select]
uint8_t nRAM[11]={0x55, 0x22, 0xf8, 0x8f, 0}; //nec buffer, 10 digits max

So it is a typical C string.

In the ISR, I flipped RC2 to indicate the chars being sent.

The timing parameters are defined and calculated by the compiler so the code can be easily adopted to other mcus, including non-PIC ones. The same concept can be used to produce soft-peripherals, like spi, i2c, uart, etc.

Comparing to the polling approach, it has very low cpu load: about 3% in this case.


================================
https://dannyelectronics.wordpress.com/
 

Offline luviniTopic starter

  • Contributor
  • Posts: 33
  • Country: br
  • PIC C programming!
Re: NEC IR Remote using PIC
« Reply #30 on: March 07, 2016, 03:21:02 am »
UPDATED CODE, now supports multiple devices, easier to add devices and buttons

Code: [Select]
#include <16F628A.h>

#fuses NOWDT //Disable Watchdog Timer
#fuses NOPROTECT //Disable Read/Write Protect
#FUSES NOCPD //Disable EE protection
#fuses NOLVP //Disable Low Voltage Programming (ICSP) ==keep low during operation if enabled to prevent entering ICSP mode==
#fuses INTRC_IO //Set CLK pins as I/O
#fuses NOMCLR //Disable Master Clear
#FUSES NOBROWNOUT //Disable Reseting On Power Loss
//#FUSES HS //Crystal With More than 4Mhz

#use delay(clock=4000000)

//==============CODES==============
//DeviceID
const char TV = 32 ;
const char RGBlamp =   0 ;

//Buttons
//=====TV======
const char power = 16 ;//TV Power
const char input = 208 ;//TV input
const char addVol = 64 ;//TV Vol+
const char subVol = 192 ;//TV Vol-
const char OK = 34 ;//TV OK
const char Mute = 144 ;//TV Mute
const char Right = 96 ;//TV Right
const char Left = 224 ;//TV Left
const char Up =   2 ;//TV Up
const char Down = 130 ;//TV Down
const char Back = 20 ;//TV Back
const char Settings = 194 ;//TV Settings

//===RGBlamp===
const char rgbOn = 224 ;//RGBlamp ON
const char rgbOff = 96 ;//RGBlamp OFF
const char addBright= 160 ;//RGBlamp +Bright
const char subBright= 32 ;//RGBlamp -Bright
const char white = 208 ;//RGBlamp white
const char red = 144 ;//RGBlamp red
const char green = 16 ;//RGBlamp green
const char blue = 80 ;//RGBlamp blue


//================================
char i;
char byte_to_send;
int button;
int device;
//================================
int RGBpowerToggle;
//================================
//      NEC PROTOCOL

void startSending() // START
{
set_pwm1_duty(12);
delay_ms(9);
set_pwm1_duty(0);
delay_us(4500);
}

void logical1() // 1
{
set_pwm1_duty(12);
delay_us(560);
set_pwm1_duty(0);
delay_us(1690);
}

void logical0() // 0
{
set_pwm1_duty(12);
delay_us(560);
set_pwm1_duty(0);
delay_us(560);
}

void repeatCode() // REPEAT
{
set_pwm1_duty(12);
delay_ms(9);
set_pwm1_duty(0);
delay_us(2250);
set_pwm1_duty(12);
delay_us(560);
set_pwm1_duty(0);
delay_us(560);
}

void end_sending()
{
set_pwm1_duty(12);
delay_us(560);
set_pwm1_duty(0);
}
//================================
void send_byte(byte_to_send)
{
for (i=0; i<8; i++)
{
if (byte_to_send & 0x80)
{
output_high(pin_b1); //probe pin B1 for 1 signals
logical1();
output_low(pin_b1);
}
else
{
output_high(pin_b0); //probe pin B0 for 0 signals
logical0();
output_low(pin_b0);
}
byte_to_send = byte_to_send << 1;
}
}
//================================
void send_key() //Send the code
{
startSending(); //Send 9ms START pulse
send_byte(device); //Send DeviceID
send_byte(~device); //Send Inverted DeviceID
send_byte(button); //Send ButtonCode
send_byte(~button); //Send inverted ButtonCode
end_sending(); //Send final pulse (to complete the last bit)
}
//================================
void main()
{
setup_ccp1(ccp_pwm); //CCPM1 = PIN_B3 on PIC16F628A
setup_timer_2(T2_DIV_BY_1,25, 1); //38Khz PWM using 4Mhz Osc.
delay_us(20);
//================================

RGBpowerToggle = 0;

while(1)
{
//===============================
if(!input(pin_a0))
{
device=TV;
button=power;

send_key();
while(!input(pin_a0))
{
delay_ms(110);
repeatCode();
}
}
//===============================
if(!input(pin_a1))
{
device=TV;
button=input;

send_key();
while(!input(pin_a1))
{
delay_ms(110);
repeatCode();
}
}
//===============================
if(!input(pin_a2))
{
device=TV;
button=addVol;

send_key();
while(!input(pin_a2))
{
delay_ms(110);
repeatCode();
}
}
//===============================
if(!input(pin_a3))
{
device=TV;
button=subVol;

send_key();
while(!input(pin_a3))
{
delay_ms(110);
repeatCode();
}
}
//===============================
if(!input(pin_b0))
{
device=RGBlamp;

if(RGBpowerToggle==0)
{
button=rgbOn;
}

if(RGBpowerToggle==1)
{
button=rgbOff;
}

RGBpowerToggle++;

if(RGBpowerToggle==2)
{
RGBpowerToggle=0;
}

send_key();
while(!input(pin_b0))
{
delay_ms(110);
repeatCode();
}
}
//===============================
if(!input(pin_b1))
{
device=RGBlamp;
button=addBright;

send_key();
while(!input(pin_b1))
{
delay_ms(110);
repeatCode();
}
}
//===============================
if(!input(pin_b5))
{
device=RGBlamp;
button=subBright;

send_key();
while(!input(pin_b5))
{
delay_ms(110);
repeatCode();
}
}
//===============================
if(!input(pin_b4))
{
device=RGBlamp;
button=white;

send_key();
while(!input(pin_b4))
{
delay_ms(110);
repeatCode();
}
}
//===============================
if(!input(pin_a6))
{
device=RGBlamp;
button=red;

send_key();
while(!input(pin_a6))
{
delay_ms(110);
repeatCode();
}
}
//===============================
if(!input(pin_a7))
{
device=RGBlamp;
button=green;

send_key();
while(!input(pin_a7))
{
delay_ms(110);
repeatCode();
}
}
//===============================
if(!input(pin_a4))
{
device=RGBlamp;
button=blue;

send_key();
while(!input(pin_a4))
{
delay_ms(110);
repeatCode();
}
}
//===============================

}
}

Code: [Select]
PIC16F628A
Memory usage:   ROM=21%      RAM=5% - 7%

https://youtu.be/-yqfaucz32k
« Last Edit: March 13, 2016, 12:57:20 am by luvini »
 

Offline luviniTopic starter

  • Contributor
  • Posts: 33
  • Country: br
  • PIC C programming!
Re: NEC IR Remote using PIC
« Reply #31 on: March 13, 2016, 12:57:30 am »
current while idle=1227uA

how do i decrease power consumption while idle now?
i think there was a sleep function, how does that work?

i tried to add "Sleep();" in the loop, just to see what happens, and indeed, the current stays at 375uA, much lower, but i looked on google, and it seems it should get as low as 43uA http://embedded-lab.com/blog/lab-17-sleep-and-wake-pic-microcontrollers/

But then after going into Sleep() it does nothing, how would i wakeup the device from any input(button)?

theres this Example file "EX_WAKUP.C"
Code: [Select]
/////////////////////////////////////////////////////////////////////////
////                          EX_WAKUP.C                             ////
////                                                                 ////
////  This example shows how to use the sleep function.  When the    ////
////  button is pushed, the processor goes into sleep mode.  When    ////
////  the button is released, the processor wakes up and continues   ////
////  counting.                                                      ////
////                                                                 ////
////  Configure the CCS prototype card as follows:                   ////
////     Jumper from pin B0 to a switch.                             ////
////                                                                 ////
////  Jumpers:                                                       ////
////     PCM,PCH    pin C7 to RS232 RX, pin C6 to RS232 TX           ////
////                                                                 ////
////  This example will work with the PCM and PCH compilers.  The    ////
////  following conditional compilation lines are used to include a  ////
////  valid device for each compiler.  Change the device, clock and  ////
////  RS232 pins for your hardware if needed.                        ////
/////////////////////////////////////////////////////////////////////////
////        (C) Copyright 1996,2003 Custom Computer Services         ////
//// This source code may only be used by licensed users of the CCS  ////
//// C compiler.  This source code may only be distributed to other  ////
//// licensed users of the CCS C compiler.  No other use,            ////
//// reproduction or distribution is permitted without written       ////
//// permission.  Derivative programs created using this software    ////
//// in object code form are not restricted in any way.              ////
/////////////////////////////////////////////////////////////////////////


#if defined(__PCM__)
#include <16F877.h>
#fuses HS,NOWDT,NOPROTECT,NOLVP
#use delay(clock=20000000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7)

#elif defined(__PCH__)
#include <18F452.h>
#fuses HS,NOWDT,NOPROTECT,NOLVP
#use delay(clock=20000000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7)
#endif

// global flag to send processor into sleep mode
short sleep_mode;

// external interrupt when button pushed and released
#INT_EXT
void ext_isr() {
static short button_pressed=FALSE;

   if(!button_pressed)        // if button action and was not pressed
   {
      button_pressed=TRUE;    // the button is now down
      sleep_mode=TRUE;        // activate sleep
      printf("The processor is now sleeping.\r\n");
      ext_int_edge(L_TO_H);   // change so interrupts on release
   }
   else                       // if button action and was pressed
   {
      button_pressed=FALSE;   // the button is now up
      sleep_mode=FALSE;       // reset sleep flag
      ext_int_edge(H_TO_L);   // change so interrupts on press
   }
   if(!input(PIN_B0))         // keep button action sychronized wth button flag
      button_pressed=TRUE;
   delay_ms(100);             // debounce button
}

// main program that increments counter every second unless sleeping
void main()   {
   long counter;

   sleep_mode=FALSE;          // init sleep flag

   ext_int_edge(H_TO_L);      // init interrupt triggering for button press
   enable_interrupts(INT_EXT);// turn on interrupts
   enable_interrupts(GLOBAL);

   printf("\n\n");

   counter=0;                 // reset the counter
   while(TRUE)
   {
      if(sleep_mode)          // if sleep flag set
         sleep();             // make processor sleep
      printf("The count value is:  %5ld     \r\n",counter);
      counter++;              // display count value and increment
      delay_ms(1000);         // every second
   }
}

=========================

also, would the "void end_sending()" on my code be called a "stop bit"?

=========================

and i also updated the PWM Calculator: https://docs.google.com/spreadsheets/d/1VXUw5Sm8fl1sn6Jrq0pawhZ0cFhNQzehLE0Sc8wFf2Q/edit?usp=sharing
now it has a graphical preview and is able to be edited without having to download locally

try it and see if its working properly...
« Last Edit: March 14, 2016, 01:02:07 am by luvini »
 

Offline luviniTopic starter

  • Contributor
  • Posts: 33
  • Country: br
  • PIC C programming!
Re: NEC IR Remote using PIC
« Reply #32 on: April 06, 2016, 04:36:03 am »
i still need help with decreasing power consumption while idle...
 

Offline macboy

  • Super Contributor
  • ***
  • Posts: 2254
  • Country: ca
Re: NEC IR Remote using PIC
« Reply #33 on: April 06, 2016, 02:19:54 pm »
i still need help with decreasing power consumption while idle...
You need to put the PIC to sleep, and more importantly, you need to wake it up again!

To wake up, generally you will need a reset, an interrupt (either internal or external) or a watchdog timer timeout. The WDT can be used to periodically wake up the PIC to check for things to be done (like scanning for a button press). You can also use any interrupt source, such as a timer reaching zero*, comparator interrupt, or an interrupt-on-change pin (for 16F628A, these are RB4..RB7). (note that internally timed timers stop during sleep since the oscillator is turned off during sleep).

For your case, you will likely want a WDT wake-up. You will setup up the WDT for a suitable timeout period, then go to sleep. After that timeout, you wake up and execute the next instruction (no reset occurs as with some lower end PICs). You can then disable the WDT, scan for a key press, and send a code if necessary. When no keys are pressed, enable WDT and sleep.

There are notes in the datasheet about lowering power consumption while sleeping... oscillator options, disabling voltage reference, etc.
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 9890
  • Country: us
Re: NEC IR Remote using PIC
« Reply #34 on: April 07, 2016, 12:20:48 am »

why atmel is better than microchip?
i am using pic for a couple of years and it's working fine.
i be glad if give me a good reason to try them

Sent from my SM-N920C using Tapatalk

For 16F PICs and the assembly level programmer (and C is pretty grim on an 8 bit PIC as well), the banking and paging nonsense of the PIC will drive you crazy!  The AVR has a larger instruction set and the interrupts are easier to handle.  The architecture of the PIC is something from the deep lagoon back in the '60s.  But there are a lot of them around.  Clearly, they outsell Atmel so what do I know?

 

Offline mathsquid

  • Regular Contributor
  • *
  • Posts: 247
  • Country: us
  • I like math.
Re: NEC IR Remote using PIC
« Reply #35 on: April 07, 2016, 02:13:17 am »
You need to put the PIC to sleep, and more importantly, you need to wake it up again!

To wake up, generally you will need a reset, an interrupt (either internal or external) or a watchdog timer timeout. The WDT can be used to periodically wake up the PIC to check for things to be done (like scanning for a button press). You can also use any interrupt source, such as a timer reaching zero*, comparator interrupt, or an interrupt-on-change pin (for 16F628A, these are RB4..RB7). (note that internally timed timers stop during sleep since the oscillator is turned off during sleep).

I've done a few IR remote circuits, but they've all been either temporary circuits I'm just playing with or a web-controlled  remote powered by a USB cable. I've wondered about powering a remote long-term using a battery.  Is it standard to have the microcontroller cycle between sleep and wake frequently enough to catch up a normal-length button press?  Or would it be more common to use a pin change interrupt so that a button press would wake it up?  Or is there some better method I don't know about?
 

Offline macboy

  • Super Contributor
  • ***
  • Posts: 2254
  • Country: ca
Re: NEC IR Remote using PIC
« Reply #36 on: April 07, 2016, 02:23:24 pm »
You need to put the PIC to sleep, and more importantly, you need to wake it up again!

To wake up, generally you will need a reset, an interrupt (either internal or external) or a watchdog timer timeout. The WDT can be used to periodically wake up the PIC to check for things to be done (like scanning for a button press). You can also use any interrupt source, such as a timer reaching zero*, comparator interrupt, or an interrupt-on-change pin (for 16F628A, these are RB4..RB7). (note that internally timed timers stop during sleep since the oscillator is turned off during sleep).
I've done a few IR remote circuits, but they've all been either temporary circuits I'm just playing with or a web-controlled  remote powered by a USB cable. I've wondered about powering a remote long-term using a battery.  Is it standard to have the microcontroller cycle between sleep and wake frequently enough to catch up a normal-length button press?  Or would it be more common to use a pin change interrupt so that a button press would wake it up?  Or is there some better method I don't know about?
The best way for power consumption would be interrupt on change. With a 16 key (4x4) key matrix, you could use 4 IOC capable pins to achieve this. Before sleeping, drive all 4 columns and enable IOC interrupt on all 4 row inputs. After the interrupt fires, do the normal key scanning (driving one column at a time) to determine which key is pressed. The PIC can sleep for hours or days on end this way. The WDT wake-up works if you don't have IOC pins or not enough of them (only four on the '628A) or if you use a resistor ladder and ADC to do key scanning rather than a matrix. You can also forgo sleeping and switch to the low speed internal oscillator and just continue polling the inputs as usual (but at ~1/100 the normal execution speed) then switch to high speed before sending the IR signals. This method also gives the lowest possible latency since it does not incur the OST (Oscillator Startup Timer) delay upon wake-up. Using the internal 4 MHz RC oscillator also incurs less wake up delay, only 1 us vs. 1024 cycles for an external crystal. That delay before the code runs means wasted power.
About the best you can do is run at ~ 2.0 V for lowest current consumption, use the internal RC oscillator for lowest wake up delay, and use IOC pins to wake from sleep.
 
The following users thanked this post: mathsquid


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf