IR transmitter & recorder

it says that the functions are private.

I got the code running but seems like my fan didn’t responded to the code.

Maybe the commands are slightly different from my model.

sadly…sadly…

Wow some awesome progress here guys! Sadly I got stuck on arduino.h… It seemed to go on for ever and I didn’t get it working. Looking forward to trying out your code above though :slight_smile: … Clearing diary for next weekend.

Hi All

I have implemented IR sending using the PWM, and made this into some fairly light weight code. It just needs to know the microsecond timings between the pulses, and the carrier frequency.

unsigned int ir[] = {8576,4224,640,1664,640,640,640,640,640,640,640,1664,640,640,640,640,640,640,640,640,640,640,
640,640,640,640,640,640,640,1664,640,640,640,640,640,1664,640,640,640,640,640,640,640,640,
640,640,640,640,640,640,640,1664,640,1664,640,640,640,640,640};

send (ir, 59, 36000);

I have assumed the use of A5 to send the IR code, but this was mostly arbitrary. The trick into turning the PWM on and off was a bit counterintuitive.

pinMode(A5, OUTPUT); // Turn PWM Off
pinMode(A5, AF_OUTPUT_PUSHPULL) // Turn PWM On;

From my perspective, the code is fairly straight forward. It could be integrated into any of the other libraries. However, for my purposes, it works, and seems to work well.

As for the hardware side, I used a 120 Ohm resistor from A5 to the base of a BC547. The emitter was connected to ground, and the collector was connected to the IR diode via a 39 Ohm resistor to 5V.

// From https://github.com/zoellner/IRLib/blob/master/IRLib.cpp and also 
// based on https://gist.github.com/technobly/8313449
uint16_t TIM_ARR = (uint16_t)(24000000 / 38000) - 1; // 38 KHz Init

// User defined analogWrite() to gain control of PWM initialization
void analogWrite2(uint16_t pin, uint8_t value) {
  TIM_OCInitTypeDef TIM_OCInitStructure;
 
  if (pin >= TOTAL_PINS || PIN_MAP[pin].timer_peripheral == NULL) {
    return;
  }
  // SPI safety check
  if (SPI.isEnabled() == true && (pin == SCK || pin == MOSI || pin == MISO)) {
    return;
  }
  // I2C safety check
  if (Wire.isEnabled() == true && (pin == SCL || pin == SDA)) {
    return;
  }
  // Serial1 safety check
  if (Serial1.isEnabled() == true && (pin == RX || pin == TX)) {
    return;
  }
  if (PIN_MAP[pin].pin_mode != OUTPUT && PIN_MAP[pin].pin_mode != AF_OUTPUT_PUSHPULL) {
    return;
  }
  // Don't re-init PWM and cause a glitch if already setup, just update duty cycle and return.
  if (PIN_MAP[pin].pin_mode == AF_OUTPUT_PUSHPULL) {
    TIM_OCInitStructure.TIM_Pulse = (uint16_t)(value * (TIM_ARR + 1) / 255);
    if (PIN_MAP[pin].timer_ch == TIM_Channel_1) {
      PIN_MAP[pin].timer_peripheral-> CCR1 = TIM_OCInitStructure.TIM_Pulse;
    } else if (PIN_MAP[pin].timer_ch == TIM_Channel_2) {
      PIN_MAP[pin].timer_peripheral-> CCR2 = TIM_OCInitStructure.TIM_Pulse;
    } else if (PIN_MAP[pin].timer_ch == TIM_Channel_3) {
      PIN_MAP[pin].timer_peripheral-> CCR3 = TIM_OCInitStructure.TIM_Pulse;
    } else if (PIN_MAP[pin].timer_ch == TIM_Channel_4) {
      PIN_MAP[pin].timer_peripheral-> CCR4 = TIM_OCInitStructure.TIM_Pulse;
    }
    return;
  }
 
  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
 
  //PWM Frequency : PWM_FREQ (Hz)
  uint16_t TIM_Prescaler = (uint16_t)(SystemCoreClock / 24000000) - 1; //TIM Counter clock = 24MHz
 
  // TIM Channel Duty Cycle(%) = (TIM_CCR / TIM_ARR + 1) * 100
  uint16_t TIM_CCR = (uint16_t)(value * (TIM_ARR + 1) / 255);
 
  // AFIO clock enable
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
 
  //pinMode(pin, AF_OUTPUT_PUSHPULL); // we need to do this manually else we get a glitch
 
  // TIM clock enable
  if (PIN_MAP[pin].timer_peripheral == TIM2)
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
  else if (PIN_MAP[pin].timer_peripheral == TIM3)
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
  else if (PIN_MAP[pin].timer_peripheral == TIM4)
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
 
  // Time base configuration
  TIM_TimeBaseStructure.TIM_Period = TIM_ARR;
  TIM_TimeBaseStructure.TIM_Prescaler = TIM_Prescaler;
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
 
  TIM_TimeBaseInit(PIN_MAP[pin].timer_peripheral, & TIM_TimeBaseStructure);
 
  // PWM1 Mode configuration
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
  TIM_OCInitStructure.TIM_Pulse = TIM_CCR;
 
  if (PIN_MAP[pin].timer_ch == TIM_Channel_1) {
    // PWM1 Mode configuration: Channel1
    TIM_OC1Init(PIN_MAP[pin].timer_peripheral, & TIM_OCInitStructure);
    TIM_OC1PreloadConfig(PIN_MAP[pin].timer_peripheral, TIM_OCPreload_Enable);
  } else if (PIN_MAP[pin].timer_ch == TIM_Channel_2) {
    // PWM1 Mode configuration: Channel2
    TIM_OC2Init(PIN_MAP[pin].timer_peripheral, & TIM_OCInitStructure);
    TIM_OC2PreloadConfig(PIN_MAP[pin].timer_peripheral, TIM_OCPreload_Enable);
  } else if (PIN_MAP[pin].timer_ch == TIM_Channel_3) {
    // PWM1 Mode configuration: Channel3
    TIM_OC3Init(PIN_MAP[pin].timer_peripheral, & TIM_OCInitStructure);
    TIM_OC3PreloadConfig(PIN_MAP[pin].timer_peripheral, TIM_OCPreload_Enable);
  } else if (PIN_MAP[pin].timer_ch == TIM_Channel_4) {
    // PWM1 Mode configuration: Channel4
    TIM_OC4Init(PIN_MAP[pin].timer_peripheral, & TIM_OCInitStructure);
    TIM_OC4PreloadConfig(PIN_MAP[pin].timer_peripheral, TIM_OCPreload_Enable);
  }
 
  TIM_ARRPreloadConfig(PIN_MAP[pin].timer_peripheral, ENABLE);
 
  // TIM enable counter
  TIM_Cmd(PIN_MAP[pin].timer_peripheral, ENABLE);
}

void enableIROut (unsigned int hz){
    pinMode (A5, OUTPUT);
    TIM_ARR = (uint16_t)(24000000 / hz) - 1; 
    analogWrite2 (A5,128); // 50% Duty Cycle
    space(0); // And turn off the PWM output until we need it
    delay(100);
}

void  My_delay_uSecs(unsigned int T) {
    if(T){if(T>16000) {delayMicroseconds(T % 1000); delay(T/1000); } else delayMicroseconds(T);};
}

void mark(unsigned int time) {
    pinMode(A5, AF_OUTPUT_PUSHPULL);
    My_delay_uSecs(time);
}

void space(unsigned int time) {
    pinMode(A5, OUTPUT);
    My_delay_uSecs(time);
}

void send(unsigned int buf[], unsigned char len, unsigned int hz)
{
  enableIROut(hz);
  for (unsigned char i = 0; i < len; i++) {
    if (i & 1) {
      space(buf[i]);
    } 
    else {
      mark(buf[i]);
    }
  }
  space(0); // Just to be sure
}

Comments appreciated

Darryl

2 Likes

I would suggest to paste the code in Gist and share the link but great work there! :slight_smile:

I have uploaded this, creating my first Gist... SparkCore_IRsend.ino

1 Like

Instead of

pinMode(A5, AF_OUTPUT_PUSHPULL); // PWM ON
pinMode(A5, OUTPUT); // PWM OFF

Could you try this

analogWrite2 (A5, 128); // PWM ON, 50% Duty Cycle
analogWrite2 (A5, 0); // PWM OFF, 0% Duty Cycle

At least that’s more intuitive, which is what you were after I think. It should also not glitch the output and be very cleanly set between 0 and 50%. If you need the output to be HIGH instead of LOW, then set it to 100% (255) instead of 0%

2 Likes

I was a bit skeptical since the code for analogWrite2, even with a pre-initialized PWM, was a bit longer than using pinMode, but it turned out that this gives better results, at least in terms of timing. These changes cause the PWM to start with a full pulse width high, which is what I was after.

And I really should put in another plug for the Saleae16 Logic Analyser. It saved me so much time debugging this code, and then debugging the code that was driving this. This is to a storage CRO (for digital) what a storage CRO is to a normal one, and what a normal CRO is to a MultiMeter. I cannot remember how much it cost, but it has been worth it!

I have updated the GIST with these changes.

Darryl

2 Likes

Hi @vk2tds,

Purely FYI, and for the information of others reading this…

Here’s a similar library (based on the Arduino IR library) : https://github.com/pkourany/Ir_Remote

And the relevant discussion : https://community.spark.io/t/working-code-on-original-spark-core-doesnt-work-with-my-new-core/4454/23

As a n00b : are there any noticeable advantages/disadvantages of either?

Cheers,
R

1 Like

Hi Rehaan,

The main issue that I see with the library from pkourany is that it does the modulation of the IR carrier in software. That is, it needs to modulate the 38 KHz or whatever by doing delays. The issue here is that it not only takes up a heap of CPU time, but there is a lot more possibility for the frequency to shift. This means that there is an on to off or off to on transition every 13 microseconds (0.013 milliseconds) or so.

During a typical 640 microsecond mark (one), the code needs 25 high to low transitions and 25 low to high transitions.

In order to maintain accuracy, the library disables interrupts, and reenables them at the end. Maybe I should do this too, but it is less critical.

What I do is set the PWM to the base carrier, and then modulate that with data. This allows me to control the signal at a minimum of every 400 microseconds (0.4 milliseconds) , but often more than that (it is physical layer protocol dependent). A microsecond at this level is a lot less significant than at the lower level.

I guess the 'ideal' solution is to take my Gist (This is a snippet of code that allows IR to be sent using PWM on the Spark Core. · GitHub) and combine that with the code from pkourany. To be honest, I did use the same interface as him in creating marks and spaces so combining the code should be fairly straightforward.

In terms of failure modes, if the software crashes the results will be different. Worst case is that I will be modulating the LED at 50%, whereas the other code is modulating at 100%. Depending on how the LED is being driven, this might make the difference between blowing the LED and simply having the watchdog recover the system without damage.

The main thing that his code has, which mine does not, is the higher level protocols for IR. I made the decision not to include those since I was not interested in them. My application uses raw recordings of IR without any attempt to move them into protocols.

Hope this helps without going into too much detail.

Darryl

2 Likes

@Rehaan, @vk2tds, that code was just a fix of some Arduino I did for @arduima since he was having RAM problems.

He tells me it works but it is by no means robust and I agree with @vk2tds that a combination of the two would be ideal. :smile:

1 Like

Hey all, some notes I wanted to share:

  • There is a good arduino based IR camera control library. It has almost all of the primitives necessary to run this sort of application. It can be found here.
  • Almost all of these devices use pwm with a fixed frequency and a protocol something like the following to encode 1001:

(Initiator pulse train) (freq F for N us) (off for N us) (off for N us) (freq F for N us)

Where initiator pulse train is usually one of:
(Level high for N seconds)(Level Low for N microseconds)
(Freq G for M us)

These sorts of protocols let the decoding/encoding circuitry be really dumb/reliable. I am about 90% done porting that camera control library to the spark-core, and I will drop another ping to this thread when I finish it up, there are a couple of functions and structures that make this all more concise and readable which I would love to share.

Lastly, if you are worried about CPU, a potential ($2-3) work around is with a 555 Timer in a monostable configuration. This way you can just schedule timer interrupts at the edge of each next pulse.

Hi... I have just quickly hacked the code together for the two, and uploaded it into GitHub and put in a Pull Request. I did it all in the Web UI pasting everything into the one source file, and then editing the individual files, so I am hoping I grabbed everything.

One thing I did note was that frequency is in KHz, whereas my code was in Hz. Not sure it really matters given the resolution of the PWM, but I kept the existing libraries KHz resolution.

Darryl

1 Like

@vk2tds, did you test the new code? I’ll merge the code so others can test it. Can you also update the README file to reflect your changes? Then I can package it and put it up as a library on the IDE. :smile:

1 Like

I have been using this library and it works fine with sending raw signals:

There was an issue with the core freezing up after sending raw codes - turned out it was because of a mistake I made between the integer of codes to send versus the actual number of codes sent in the array. Once fixed, everything was fine.

Hi... I tested it but not as a library. Tomorrow I will verify that it will work locally just to make sure. The README has been updated, but I dont know if I need to do another PULL to get that linked.

Darryl

1 Like

@zoellner Have you made any progress with the forked IRLib? I am looking to do a spark-based project with IR Receive capabilities and your fork is the closest thing I’ve found.

sorry, haven’t worked on this any further since I only ended up needing transmit capabilities on the spark (did all the recording with an Arduino)

Okay, thanks for the reply. We are talking more about this issue over here in case someone else stumbles upon this thread looking to receive IR (although we aren’t there yet).

@Qwertzguy, would you be so kind to post your code to control the roomba with your library?

Hi all

Does anyone have some simple code to share to:
a) read IR codes from various remotes
b) record new IR codes from Spark Core?