Help Needed! Transmitting Rotary Encoder signal to IR Emitter on Car Stereo

Hello All!

I'm new to Arduino but I have been doing a great deal of research and have a project mostly thought out (and partially programmed) but was needing some expert advice on the best way to finalize the programming.

Project
Project is based on adding a "volume knob" to my Clarion Car Stereo; which only has "push buttons" currently. I have a sketch and breadboard wired up that reads the rotary encoder (new volume knob) and understands the clockwise/counterclockwise turns and displays each click as a positive increment or negative increment on an LCD screen.
Got this information from : FarrellF.com

Goal/Help Needed
My goal (and what I need help with) is adding code to the sketch that will allow an IR emitter to transmit the appropriate "remote code" to the IR receiver on the car stereo. I have the Clarion remote codes, so basically, I need help with how to tell the IR to send a "Volume Up" command if the encoder is turned clockwise. Likewise, I would need the IR to transmit a "volume down" command if the encoder is turned counterclockwise.

I plan on keeping the small LCD to have a graphical display of the encoder reading, but my car stereo also has a 7: LCD that will show the volume increasing/decreasing if everything is transmitted correctly.

Options?
Originally, I bounced a few ideas around on how to send the commands to the stereo. I could tap into the existing volume buttons (which is still an option), tap into the Steering Wheel Control inputs (don't know the protocol Clarion uses for this input), or use the IR receiver that was originally used for the wireless remote ( which died about 6 months after purchasing the unit). Since I know the remote codes and I can easily add additional functions with IR commands, this seemed the easiest and most expansive option for the short and long term.

So, can anyone assist me in what I need to add to the sketch to allow for the IR to transmit the appropriate remote codes based on the encoder's output? Again, I used the diagram and sketch from FarrellF.com This is where I am at this point.

Thank you very much in advance!!!!!

I'm new to Arduino...

You and me both, brother! :slight_smile:

BTW, very nicely written description of your problem.

To make it easy for you to find my changes, I added " // <--Add this" to the end of each line that I added. Just do a search for it.

#include <LiquidCrystal.h>

LiquidCrystal lcd(10, 9, 5, 4, 3, 2); // RS, Enable, D4, D5, D6, D7
int count = 0;
int precount = 0; // <--Add this
int diffcount = 0; // <--Add this
byte currentA, currentB, previousA, previousB = 0;

void setup() {
  lcd.begin(16, 2);
  previousA = currentA = digitalRead(12);
  previousB = currentB = digitalRead(11);
}

void loop() {
  previousA = currentA;
  previousB = currentB;
  currentA = digitalRead(12);
  currentB = digitalRead(11);
  
  if(previousA == LOW && previousB == LOW && currentA == HIGH && currentB == LOW) // clockwise
    count++;
  else if(previousA == HIGH && previousB == HIGH && currentA == LOW && currentB == HIGH) // clockwise
    count++;
  else if(previousA == LOW && previousB == LOW && currentA == LOW && currentB == HIGH) // counter-clockwise
    count--;
  else if(previousA == HIGH && previousB == HIGH && currentA == HIGH && currentB == LOW) // counter-clockwise
    count--;
  
  lcd.setCursor(0, 0);
  lcd.print(count);
  lcd.print("   ");

  if (count != precount) {   // <--Add this
    setting = setting + count - precount;   // <--Add this
    if (setting > 11) setting = 11; // Everyone knows you can only crank it to 11... (Spinal Tap)   <-- Add this
    if (setting < 0) setting = 0; //  // <--Add this
    precount = count;  // <--Add this
    // Now add your control code that you will be sending to your radio  // <--Add this
}   // <--Add this
}

Clever idea...I suspect the designers would be upset to see that someone wanted to retro their sleek design.

Anyway, just get the IRremote (or IRLib) library set up on your Arduino and go thrugh the examples first.

You can record the signals from any existing remote & replay them based on the movement of your rotary dial.

You could get even more functionality by adding an on and off signal based on the speed of turning the rotary.

(If you don't have the remote, but have the codes, you can build up the equivalent signals using RAW mode)

"You could get even more functionality by adding an on and off signal based on the speed of turning the rotary."

And you could change how much you add/subtract from the volume by looking at the rate of change of the encoder. If you see that the pulses are coming in faster than what would be normal, you could multiply the difference by two for instance.

rootboy - thanks for posting the revised code. I will look at add to my sketch and try to add the Clarion remote codes to it and see how it functions.

In your latest post - how would you recommend adding the speed factor to the volume control. do you think it would work well considering I'm just sending a vol + command via IR verses connecting directly to the volume buttons?

OK - so I added some commands/code to reflect the Clarion IR codes. Does this look right to everyone? I got some of it from the IRremote Lib site and some from a similar sketch that has other functions unrelated to this project

#include <LiquidCrystal.h>
#include <IRremote.h>

IRsend irsend;

LiquidCrystal lcd(10, 9, 5, 4, 3, 2); // RS, Enable, D4, D5, D6, D7
int count = 0;
int precount = 0; // <--Add this
int diffcount = 0; // <--Add this
byte currentA, currentB, previousA, previousB = 0;
int IRledPin=6; //IR Led pin PWM 6

void setup() {
  lcd.begin(16, 2);
  previousA = currentA = digitalRead(12);
  previousB = currentB = digitalRead(11);
  Serial.begin(9600);
}

void loop() {
  previousA = currentA;
  previousB = currentB;
  currentA = digitalRead(12);
  currentB = digitalRead(11);
  
  if(previousA == LOW && previousB == LOW && currentA == HIGH && currentB == LOW) // clockwise
    count++;
  else if(previousA == HIGH && previousB == HIGH && currentA == LOW && currentB == HIGH) // clockwise
    count++;
  else if(previousA == LOW && previousB == LOW && currentA == LOW && currentB == HIGH) // counter-clockwise
    count--;
  else if(previousA == HIGH && previousB == HIGH && currentA == HIGH && currentB == LOW) // counter-clockwise
    count--;
  
  lcd.setCursor(0, 0);
  lcd.print(count);
  lcd.print("   ");

  if (count != precount) {   // <--Add this
    setting = setting + count - precount;   // <--Add this
    if (setting > 11) setting = 11; // Everyone knows you can only crank it to 11... (Spinal Tap)   <-- Add this
    if (setting < 0) setting = 0; //  // <--Add this
    precount = count;  // <--Add this
       
    if ( setting.read() != 11
  {
      for (int i = 0; i < 3; i++) {       
      irsend.sendNEC (0x6106D827, 32); // Clarion Volume + Code
      delay(100); 
      }
      
    if ( setting.read() != 0
  {    
      for (int i = 0; i < 3; i++) {       
      irsend.sendNEC (0x6106B847, 32); // Clarion Volume - Code
      delay(100);      
      }

}   // <--Add this
}
  }

As an update - I tried to compile the code above and it was a massive fail.

rootboy - first error was "settings" was not defined. Should it be difined in the setup somewhere?

I'm trying a few different things to see if I can get this running, but any input would be greatly appreciated b/c I'm just stabbing in the dark....

anyone got any suggestions?

arshull:
rootboy - thanks for posting the revised code. I will look at add to my sketch and try to add the Clarion remote codes to it and see how it functions.

In your latest post - how would you recommend adding the speed factor to the volume control. do you think it would work well considering I'm just sending a vol + command via IR verses connecting directly to the volume buttons?

First off, sorry about the late reply, we have been having equipment problems here at the plant and I have been busy.

As for your rate detection, what I would try is to have a variable count up each time the encoder sends out a pulse. Create yourself a 100 mS interval timer which fires off an interrupt which reads the current value of the variable, and then resets it. Looking at the magnitude of the variable you will be able to tell if you are moving it fast, slow, or not at all.

Another way would be to create three signed variables, one called "currentCount", another called "previousCount", and the third called "changeRate".

  1. Load previousCount with the encoder's position.

  2. Using the same timer interrupt, when it fires subtract previousCount from currentCount and store the result in changeRate.

  3. Then load the value of currentCount into previousCount.

Be sure to do a limit test so that you don't overflow any of your variables. The simplest way would be to let your previousCount variable get to around the midpoint of its range (about 16k). Do steps 1 & 2, but for 3 zero out previousCount and load changeRate into currentCount.

This way changeRate will not only give you the same information as the first example, but it will indicate direction as well.

I've got some encoders in my laptop bag, when (if) I get some time I will work a circuit up and see if I can make a routine for you.

arshull:
As an update - I tried to compile the code above and it was a massive fail.

rootboy - first error was "settings" was not defined. Should it be difined in the setup somewhere?

I'm trying a few different things to see if I can get this running, but any input would be greatly appreciated b/c I'm just stabbing in the dark....

The first thing I saw was that you are missing a parenthesis

if ( setting.read() != 11

should be

if ( setting.read() != 11)

As for the LCD and IR remote code, I'll have to dig up some parts at home to try it out (I've got them, somewhere...)

As for your IRDA problems, I think that this will fix it.

http://forum.arduino.cc/index.php/topic,96719.0.html

And I forgot to declare "setting" in my example. Oops...

In your modified example, you are missing some parentheses (more than the one that I previously pointed out).

Thanks for the response. I have not worked on this in a while, but now picking it up again. I need to get this project done so I can move on to some other things.

This is the code as I have it now. It compiles and functions (to an extent). When I turn the encoder, the volume goes up, no matter which direction I turn it. At least it's transmitting the command, but obviously my code is still not quite right.

#include <IRremote.h>
#include <IRremoteInt.h>

#include <LiquidCrystal.h>

LiquidCrystal lcd(10, 9, 5, 4, 6, 7); // RS, Enable, D4, D5, D6, D7
int count = 0;
int precount = 0; // <--Add this
int diffcount = 0; // <--Add this
byte currentA, currentB, previousA, previousB = 0;

IRsend irsend;

void setup() {
  lcd.begin(16, 2);
  previousA = currentA = digitalRead(12);
  previousB = currentB = digitalRead(11);
}

void loop() {
  previousA = currentA;
  previousB = currentB;
  currentA = digitalRead(12);
  currentB = digitalRead(11);

  if(previousA == LOW && previousB == LOW && currentA == HIGH && currentB == LOW) // clockwise
    count++;

    irsend.sendNEC(0x6106D827, 32); // Clarion Volume + Code
    delay(100);

  if(previousA == HIGH && previousB == HIGH && currentA == LOW && currentB == HIGH) // clockwise
    count++;

    irsend.sendNEC(0x6106D827, 32); // Clarion Volume + Code
    delay(100);

  if(previousA == LOW && previousB == LOW && currentA == LOW && currentB == HIGH) // counter-clockwise
    count--;

    irsend.sendNEC(0x6106B847, 32); // Clarion Volume - Code
    delay(100); 

  if(previousA == HIGH && previousB == HIGH && currentA == HIGH && currentB == LOW) // counter-clockwise
    count--;

    irsend.sendNEC(0x6106B847, 32); // Clarion Volume - Code
    delay(100);


  lcd.setCursor(0, 0);
  lcd.print(count);
  lcd.print("   ");

}

In reading through the suggestions above, most of it is still greek to me. I just don't know enough about the specific language and/or the various libraries/function they provide. I did update my IR library per the link you provided.....so hopefully that helps in the end.

Regardless, please review my latest code and let me know what I need to change in order to get this working properly. Any specific code is greatly appreciated b/c I just don't know how to transfer "suggestions" into real code yet.

Thanks again!!!!!!

Why are you sending data on every pass through loop()? I'd expect you to send data only when count changed.

I'd also expect you to be using interrupts to detect the encoder changes. Though polling may be fast enough.

I also don't see any point in any of the delay()s.

PaulS:
Why are you sending data on every pass through loop()? I'd expect you to send data only when count changed.

I'd also expect you to be using interrupts to detect the encoder changes. Though polling may be fast enough.

I also don't see any point in any of the delay()s.

Haha - I have no idea. Like I mentioned before, I am completely new to this and have no experience developing sketches or development of any kind. This is why I have asked for assistance.... hopefully I will learn from correcting the code and grasp a better understanding of the various dependencies that are required in the sketch's.

How would you suggest that I resolve the problems you mentioned before? Again, specifics please b/c I am not able to translate suggestions into revised code yet.

Thanks again!!!!!

You need curly braces after you if statements, and ditch the delays!

Example:

if(previousA == HIGH && previousB == HIGH && currentA == HIGH && currentB == LOW) // counter-clockwise
{
    count--;

    irsend.sendNEC(0x6106B847, 32); // Clarion Volume - Code
}

Jiggy-Ninja:
You need curly braces after you if statements, and ditch the delays!

Example:

if(previousA == HIGH && previousB == HIGH && currentA == HIGH && currentB == LOW) // counter-clockwise

{
    count--;

irsend.sendNEC(0x6106B847, 32); // Clarion Volume - Code
}

Thanks! I'll try these revisions and test ASAP.

Greatly appreciate your help!

As an update.... the suggestion by Jiggy-Ninja worked! Now my encoder controls the vol + and Vol - of my Clarion head unit via IR transmissions. Thanks again to Jiggy-Ninja and rootboy for their help on this project!

For future reference, here is the full sketch in case anyone is working on a similar project down the road:

#include <IRremote.h>


#include <LiquidCrystal.h>

LiquidCrystal lcd(10, 9, 5, 4, 6, 7); // RS, Enable, D4, D5, D6, D7
int count = 0;
int precount = 0; // <--Add this
int diffcount = 0; // <--Add this
byte currentA, currentB, previousA, previousB = 0;

IRsend irsend;

void setup() {
  lcd.begin(16, 2);
  previousA = currentA = digitalRead(12);
  previousB = currentB = digitalRead(11);
}

void loop() {
  previousA = currentA;
  previousB = currentB;
  currentA = digitalRead(12);
  currentB = digitalRead(11);

  if(previousA == LOW && previousB == LOW && currentA == HIGH && currentB == LOW) // clockwise
  {
      count++;

    irsend.sendNEC(0x6106D827, 32); // Clarion Volume + Code
  }

  if(previousA == HIGH && previousB == HIGH && currentA == LOW && currentB == HIGH) // clockwise
  {
      count++;

    irsend.sendNEC(0x6106D827, 32); // Clarion Volume + Code
  }

  if(previousA == LOW && previousB == LOW && currentA == LOW && currentB == HIGH) // counter-clockwise
  {
      count--;

    irsend.sendNEC(0x6106B847, 32); // Clarion Volume - Code
  }

  if(previousA == HIGH && previousB == HIGH && currentA == HIGH && currentB == LOW) // counter-clockwise
  {
    count--;

    irsend.sendNEC(0x6106B847, 32); // Clarion Volume - Code
  }


  lcd.setCursor(0, 0);
  lcd.print(count);
  lcd.print("   ");

}

The only improvement I would like to make to this is for the volume to increase based on the speed the encoder is turned. currently, it increases/decreases as fast as the ir can transmit the turn pulses but it would be nice for a speed augmentation to be implemented when the encoder is turned quickly to suddenly drop or raise the volume quickly.

Thanks again!

Hi! Awesome project, dude!

It would be possible to use this code without the LCD screen? only the encoder sending signal to the IR emitter ?
To increase / decrease the volume and when pressed trigger the mute ...

Could someone help me with the code?

Thanks!

There is!! I got it!! :slight_smile:
I made some adjustments in the code to later add 3 more switches (Source; Mute; NextStation). When I press the Encoder the Mute button is activated.
P.S. Replace the code for the desired device.

I will replace the UNO by a Nano Arduino to reduce the circuit size...

Next step, create a rotary volume control; play / pause; stop; next; foward; to my pc with an Arduino micro pro ...

Thank you so much, for the post !!


these are the changes that I made...

#include <IRremote.h>

//========Buttons ins config

const int mute = 10;//the Encoder SW
const int source = 9;
const int next = 8;

int buttonState = 0;

//==========Rotary Encoder
int count = 0;
int precount = 0; 
int diffcount = 0;
byte currentA, currentB, previousA, previousB = 0;

IRsend irsend; //=====conect the IR_Led to port ~3 pwm

void setup() {

  Serial.begin(9600);//====start serial (optional)
  previousA = currentA = digitalRead(11);
  previousB = currentB = digitalRead(12);
 
  //=====Buttons Config
  pinMode(mute, INPUT);
  pinMode(source, INPUT);
  pinMode(next, INPUT);
}

void loop() {
  
//==========================ENCODER================================
  previousA = currentA;
  previousB = currentB;
  currentA = digitalRead(11);
  currentB = digitalRead(12);

  if(previousA == LOW && previousB == LOW && currentA == HIGH && currentB == LOW) // clockwise
  {
      count++;

    irsend.sendNEC(0x6106D827, 32); // Volume UP Code
    Serial.println(count);
  }

  if(previousA == HIGH && previousB == HIGH && currentA == LOW && currentB == HIGH) // clockwise
  {
      count++;

    irsend.sendNEC(0x6106D827, 32); // Volume UP Code
    Serial.println(count);
  }

  if(previousA == LOW && previousB == LOW && currentA == LOW && currentB == HIGH) // counter-clockwise
  {
      count--;

    irsend.sendNEC(0x6106B847, 32); // Volume DOWN Code
    Serial.println(count);
  }

  if(previousA == HIGH && previousB == HIGH && currentA == HIGH && currentB == LOW) // counter-clockwise
  {
    count--;

    irsend.sendNEC(0x6106B847, 32); // Volume DOWN Code
    Serial.println(count);
  }
  
//===================================BUTTONS========================

//MUTE
  if (digitalRead(mute) == HIGH) {
    irsend.sendNEC(0x39C600FF, 32);// MUTE Code
  }

//SOURCE
  if (digitalRead(source) == HIGH) {
    irsend.sendNEC(0x39C6807F, 32);// SOURCE Code
  }

//NEXT
  if (digitalRead(next) == HIGH) {
    irsend.sendNEC(0x39C6E817, 32); // NEXTStation Code
  }

}