Arduino with iPod and Steeringwheel controls

Discussion in 'Programming & Software Development' started by teno45, Jun 29, 2010.

  1. teno45

    teno45 Member

    Joined:
    Dec 27, 2009
    Messages:
    219
    Location:
    Wagga Wagga
    Hey guys, I think I'm in the right area here...

    Im having a bit of trouble with a project im working on. Im trying to use the Steeringwheel controls in my car to control my iPod, using an Arduino.

    Now I already have one sketch working that controls the iPod just using momentary buttons connected directly to the arduino. Thanks goes to here for the sketch.

    Code:
    //code that controls basic (play/pause, next, previous, volume up, and volume down) functions of ipod
    //rosie daniel
    
    int hits = 0;
    
    int buttonStates[]={0,0,LOW,LOW,LOW,LOW,LOW};
    int buttonPrevious[]={0,0,LOW,LOW,LOW,LOW,LOW};
    int buttonRelease[] = {0xFF, 0x55, 0x03, 0x02, 0x00, 0x00,0xFB};
    
    
    
    int commands[]={0,0,0x01,0x08,0x10,0x02,0x04,};
    
    int checkSum(int len, int mode, int command1, int command2, int parameter) {
    int checksum = 0x100 - ((len + mode + command1 + command2+ parameter) & 0xFF);
    return checksum;
    }
    
    
    void setup() {
    Serial.begin(19200);
    pinMode(2, INPUT);
    pinMode(3, INPUT);
    pinMode(4, INPUT);
    pinMode(5, INPUT);
    pinMode(6, INPUT);
    pinMode(7, INPUT);
    
    }
    
    void loop() {
    
    for (int c=2; c<7; c++)
    {
    buttonStates[c] = digitalRead(c);
    
    
    buttonStates[c] = digitalRead(c);
    
    if (buttonStates[c] != buttonPrevious[c] ) {
    delay(5); //helps avoid a 'double' press - check a second time to see if the button is still pressed after a delay
    buttonStates[c] = digitalRead(c);
    if (buttonStates[c] == HIGH) {
    sendCommand(commands[c]);
    
    //Serial.print(hits);
    hits++;
    }
    buttonPrevious[c] = buttonStates[c];
    }
    
    }
    
    }
    
    void sendCommand(int cmd) {
    int cs = checkSum(0x03, 0x02, 0x00, cmd, 0);
    
    Serial.println(cs,HEX);
    
    
    
    int bytes[] = {0xFF, 0x55, 0x03, 0x02, 0x00, cmd, cs};
    for (int i = 0; i < 8; i++) {
    Serial.print(bytes[i], BYTE);
    
    }
    
    for (int i = 0; i < 8; i++) {
    Serial.print(buttonRelease[i],BYTE);
    }
    
    } 
    I also have a code that I wrote mostly myself, with tad borrowed from the Arduino tutorials. This sketch can identify the different steering wheel switches when pushed. They are all connected in parallel via one wire to the cd player. Each switch has a different resistor value.

    Code:
    const int numReadings = 10;
    #include <LiquidCrystal.h>
    //LiquidCrystal lcd(2, 3, 4, 5, 6, 7, 8);
    LiquidCrystal lcd(13, 12, 11, 10, 9, 8, 7);
    int readings[numReadings];      // the readings from the analog input
    int index = 0;                  // the index of the current reading
    int total = 0;                  // the running total
    int average = 0;                // the average
    
    int inputPin = 0;
    
    void setup()
    {
      // initialize serial communication with computer:
      Serial.begin(9600);                   
      // initialize all the readings to 0: 
      for (int thisReading = 0; thisReading < numReadings; thisReading++)
        readings[thisReading] = 0;     
      lcd.begin(16, 2);
      // Print a message to the LCD.
    //  lcd.print("hello, world!");    
    
    }
    
    void loop() {
    
    
          // subtract the last reading:
      total = total - readings[index];         
      // read from the sensor:  
      readings[index] = analogRead(inputPin); 
      // add the reading to the total:
      total= total + readings[index];       
      // advance to the next position in the array:  
      index = index + 1;                    
    
      // if we're at the end of the array...
      if (index >= numReadings)              
        // ...wrap around to the beginning: 
        index = 0;                           
    
      // calculate the average:
      average = total / numReadings;         
      // send it to the computer (as ASCII digits) 
    
                   
    
          lcd.setCursor(7,0);
      if((average <= 450) && (average >= 400)) {
          lcd.print("MODE");
        }
       else if((average <= 380) && (average >= 320)) {
          lcd.print("NEXT");
        }
       else if((average <= 310) && (average >= 250)) {
          lcd.print("PREV");
        }
       else if((average <= 240) && (average >= 190)) {
          lcd.print("MUTE");
        }
       else if((average <= 130) && (average >= 90)) {
          lcd.print("UP  ");
        }
       else if(average <= 50) {
          lcd.print("DOWN");
        }
        else {
          lcd.print("NONE ");
         
        }
          lcd.setCursor(0,1);
      lcd.print(average, DEC);
    }
    
    
    Now for the question/problem! I cant seem to merge the two scripts succesfully. Sometimes the iPod just freezes. other times it doesnt stop the function (ie: just keeps going next next next next song...). and other times nothing happens at all.

    If on script two, i replace the lines "lcd.print..." with writing a pin digital high, connecting that pin to the digital switch pins in script one, everything works perfectly. But that uses twice as many pins, and i wish to use them for something else!

    Can someone please help me out by pointing me in the right direction with where to go!

    Thanks, Teno.
     
    Last edited: Jun 30, 2010
  2. mikeyyy

    mikeyyy Member

    Joined:
    Apr 7, 2005
    Messages:
    590
    Location:
    Sydney
    The two pieces of code you posted are the same. :p
     
  3. OP
    OP
    teno45

    teno45 Member

    Joined:
    Dec 27, 2009
    Messages:
    219
    Location:
    Wagga Wagga
    Damnit. That'll teach me not to post a new thread at 2am...

    original post edited.
     
  4. mikeyyy

    mikeyyy Member

    Joined:
    Apr 7, 2005
    Messages:
    590
    Location:
    Sydney
    Can you give a bit more detail or do you have a schematic of how your system is setup? I'm guessing you have a serial connection to the ipod and the Serial.print() is used to send commands to it to that generates keypresses?

    So let me get this right. The first piece of code controls the ipod via switches connected directly to the arduino, and the second piece of code writes messages to an lcd screen on the arduino in response to keypresses on your steering wheel. What you want to do is then instead of printing to the lcd screen, send keypress commands over the serial (in response to buttons on your steering wheel) as is done in the first script?

    Can you post up the 'failed' merged code, and the 'successful but using twice as many pins' merged code? I haven't worked with these boards before, but I'm thinking about getting one to mess with or start playing with FPGAs again. :)

    edit: Just had a thought, since you mentioned that it keeps going next next next. Could be a couple of things...

    1) It looks like you aren't debouncing the keypresses and are looping at full speed, so a single press of 'next' might generate a few. That's why the first piece of code has the delay(5) and checks the state again, and probably why you're taking an average over 10 readings. However, 10 readings can probably be taken very quickly. You could possibly try taking 10 readings, delay for ~10ms (or some other arbitrary amount, play with it I guess) then take 10 more and if it's still within the same range, send the keypress.

    2) Otherwise you might not be handling the key-up/button-release state properly and so it's as if it is stuck down.

    I think I understand what you're doing now and what you need to do, so post up the broken code and I'll try to help debug it. :)
     
    Last edited: Jul 1, 2010
  5. OP
    OP
    teno45

    teno45 Member

    Joined:
    Dec 27, 2009
    Messages:
    219
    Location:
    Wagga Wagga
    You are correct, serial.print is used to send commands to the iPod (to simulate button presses).

    This is the circuit that has simple buttons/digital inputs that works controlling the ipod. It matches the first code from my original post.


    Click to view full size!


    Exactly!
    This is the circuit (minus the LCD) that the arduino can work out which button is pushed. It matches the second lot of code from my original post.

    Click to view full size!
    steering buttons

    Here is the dodgy one where I have outputs linked to input.

    Click to view full size!
    dodge

    The code that runs with this one is:
    Code:
    //code that controls basic (play/pause, next, previous, volume up, and volume down) functions of ipod
    //rosie daniel
    
    int hits = 0;
    
    int buttonStates[]={0,0,LOW,LOW,LOW,LOW,LOW};
    int buttonPrevious[]={0,0,LOW,LOW,LOW,LOW,LOW};
    int buttonRelease[] = {0xFF, 0x55, 0x03, 0x02, 0x00, 0x00,0xFB};
    
    //int wheelStates[]={0,0,LOW,LOW,LOW,LOW,LOW};
    
    int commands[]={0,0,0x01,0x08,0x10,0x02,0x04,};
    
    int checkSum(int len, int mode, int command1, int command2, int parameter) {
    int checksum = 0x100 - ((len + mode + command1 + command2+ parameter) & 0xFF);
    return checksum;
    }
    const int numReadings = 10;
    #include <LiquidCrystal.h>
    
    LiquidCrystal lcd(13, 12, 11, 10, 9, 8, 7);
    int readings[numReadings];      // the readings from the analog input
    int index = 0;                  // the index of the current reading
    int total = 0;                  // the running total
    int average = 0;                // the average
    
    int inputPin = 0;
    
    const int play = 8;
    const int next = 9;
    const int prev = 10;
    const int up = 11;
    const int down = 12;
    
    void setup() {
    Serial.begin(19200);
    pinMode(2, INPUT);
    pinMode(3, INPUT);
    pinMode(4, INPUT);
    pinMode(5, INPUT);
    pinMode(6, INPUT);
    
    
      // initialize all the readings to 0: 
      for (int thisReading = 0; thisReading < numReadings; thisReading++)
        readings[thisReading] = 0;     
      lcd.begin(16, 2);
      // Print a message to the LCD.
    //  lcd.print("hello, world!");    
    
    pinMode(play, OUTPUT);
    pinMode(prev, OUTPUT);
    pinMode(next, OUTPUT);
    pinMode(up, OUTPUT);
    pinMode(down, OUTPUT);
    
    }
    
    void loop() {
    
          // subtract the last reading:
      total = total - readings[index];         
      // read from the sensor:  
      readings[index] = analogRead(inputPin); 
      // add the reading to the total:
      total= total + readings[index];       
      // advance to the next position in the array:  
      index = index + 1;                    
    
      // if we're at the end of the array...
      if (index >= numReadings)              
        // ...wrap around to the beginning: 
        index = 0;                           
    
      // calculate the average:
      average = total / numReadings;         
      // send it to the computer (as ASCII digits) 
    
                   
    
          lcd.setCursor(7,0);
      if((average <= 450) && (average >= 400)) {
          lcd.print("MODE");
    //not going to be used for iPod (will be used for next part of project      
        }
       else if((average <= 380) && (average >= 320)) {
          lcd.print("NEXT");
          digitalWrite(next, HIGH); 
        }
       else if((average <= 310) && (average >= 250)) {
          lcd.print("PREV");
          digitalWrite(prev, HIGH);
        }
       else if((average <= 240) && (average >= 190)) {
          lcd.print("MUTE");
          digitalWrite(play, HIGH);
        }
       else if((average <= 130) && (average >= 90)) {
          lcd.print("UP  ");
          digitalWrite(up, HIGH); 
        }
       else if(average <= 50) {
          lcd.print("DOWN");
          digitalWrite(down, HIGH); 
        }
        else {
          lcd.print("NONE");
    
         
        }
    
      lcd.setCursor(0,1);
      average = average + 10000;
      lcd.print(average, DEC);  
      
      
    for (int c=2; c<7; c++)
    {
    //buttonStates[c] = digitalRead(c);
    
    if (buttonStates[c] != buttonPrevious[c] ) {
    delay(5); //helps avoid a 'double' press - check a second time to see if the button is still pressed after a delay
    //buttonStates[c] = digitalRead(c);
    if (buttonStates[c] == HIGH) {
      
    
    sendCommand(commands[c]);
    
    //Serial.print(hits);
    hits++;
    }
    buttonPrevious[c] = buttonStates[c];
    }
    
    }
    
    }
    
    void sendCommand(int cmd) {
    int cs = checkSum(0x03, 0x02, 0x00, cmd, 0);
    
    //Serial.println(cs,HEX);
    
    
    
    int bytes[] = {0xFF, 0x55, 0x03, 0x02, 0x00, cmd, cs};
    for (int i = 0; i < 8; i++) {
    Serial.print(bytes[i], BYTE);
    
    }
    
    for (int i = 0; i < 8; i++) {
    Serial.print(buttonRelease[i],BYTE);
    }
    
    }
     
  6. mikeyyy

    mikeyyy Member

    Joined:
    Apr 7, 2005
    Messages:
    590
    Location:
    Sydney
    Thanks for the schematics, I think i got it now. That code you just put up was the one that worked right? Do you have the merged one using only one pin that is bugged?

    edit: Also in the above code, shouldn't you be resetting the button pin states to LOW when that key is no longer detected? Otherwise the for loop that checks the pins will always be HIGH once it is set high.
     
    Last edited: Jul 2, 2010
  7. millers

    millers Member

    Joined:
    Dec 15, 2002
    Messages:
    85
    Location:
    Melbourne
    Code:
    //buttonStates[c] = digitalRead(c);
    that line will set the button state to HIGH if the button is pressed and LOW if it isnt.
    any reason its commented out in the latest code posted?
     
  8. OP
    OP
    teno45

    teno45 Member

    Joined:
    Dec 27, 2009
    Messages:
    219
    Location:
    Wagga Wagga
    the reason for it being commented out is me tweaking with the code. sorry should have taken it out before posting.

    as for the failed merge:

    Code:
    //code that controls basic play/pause, next, previous, volume up, and volume down) functions of ipod
    //rosie daniel
    
    int hits = 0;
    
    int buttonStates[]={0,0,LOW,LOW,LOW,LOW,LOW};
    int buttonPrevious[]={0,0,LOW,LOW,LOW,LOW,LOW};
    int buttonRelease[] = {0xFF, 0x55, 0x03, 0x02, 0x00, 0x00,0xFB};
    
    //int wheelStates[]={0,0,LOW,LOW,LOW,LOW,LOW};
    
    int commands[]={0,0,0x01,0x08,0x10,0x02,0x04,};
    
    int checkSum(int len, int mode, int command1, int command2, int parameter) {
    int checksum = 0x100 - ((len + mode + command1 + command2+ parameter) & 0xFF);
    return checksum;
    }
    const int numReadings = 10;
    #include <LiquidCrystal.h>
    
    LiquidCrystal lcd(13, 12, 11, 10, 9, 8, 7);
    int readings[numReadings];      // the readings from the analog input
    int index = 0;                  // the index of the current reading
    int total = 0;                  // the running total
    int average = 0;                // the average
    
    int inputPin = 0;
    
    
    void setup() {
    Serial.begin(19200);
    pinMode(2, INPUT);
    pinMode(3, INPUT);
    pinMode(4, INPUT);
    pinMode(5, INPUT);
    pinMode(6, INPUT);
    
    
      // initialize all the readings to 0: 
      for (int thisReading = 0; thisReading < numReadings; thisReading++)
        readings[thisReading] = 0;     
      lcd.begin(16, 2);
    
    }
    
    void loop() {
    
          // subtract the last reading:
      total = total - readings[index];         
      // read from the sensor:  
      readings[index] = analogRead(inputPin); 
      // add the reading to the total:
      total= total + readings[index];       
      // advance to the next position in the array:  
      index = index + 1;                    
    
      // if we're at the end of the array...
      if (index >= numReadings)              
        // ...wrap around to the beginning: 
        index = 0;                           
    
      // calculate the average:
      average = total / numReadings;         
      // send it to the computer (as ASCII digits) 
    
                   
    
          lcd.setCursor(7,0);
      if((average <= 450) && (average >= 400)) {
          lcd.print("MODE");
        }
       else if((average <= 380) && (average >= 320)) {
          lcd.print("NEXT");
          buttonStates[3] = HIGH;
        }
       else if((average <= 310) && (average >= 250)) {
          lcd.print("PREV");
          buttonStates[4] = HIGH;
        }
       else if((average <= 240) && (average >= 190)) {
          lcd.print("MUTE");
          buttonStates[2] = HIGH;
        }
       else if((average <= 130) && (average >= 90)) {
          lcd.print("UP  ");
          buttonStates[5] = HIGH;
        }
       else if(average <= 50) {
          lcd.print("DOWN");
          buttonStates[3] = HIGH;
        }
        else {
          lcd.print("NONE ");
         
        }
    
      lcd.setCursor(0,1);
      average = average + 10000;
      lcd.print(average, DEC);  
      
      
    for (int c=2; c<7; c++)
    {
    //buttonStates[c] = digitalRead(c);
    
    if (buttonStates[c] != buttonPrevious[c] ) {
    delay(5); //helps avoid a 'double' press - check a second time to see if the button is still pressed after a delay
    //buttonStates[c] = digitalRead(c);
    if (buttonStates[c] == HIGH) {
      
    
    sendCommand(commands[c]);
    
    //Serial.print(hits);
    hits++;
    }
    buttonPrevious[c] = buttonStates[c];
    }
    
    }
    
    }
    
    void sendCommand(int cmd) {
    int cs = checkSum(0x03, 0x02, 0x00, cmd, 0);
    
    //Serial.println(cs,HEX);
    
    
    
    int bytes[] = {0xFF, 0x55, 0x03, 0x02, 0x00, cmd, cs};
    for (int i = 0; i < 8; i++) {
    Serial.print(bytes[i], BYTE);
    
    }
    
    for (int i = 0; i < 8; i++) {
    Serial.print(buttonRelease[i],BYTE);
    }
    
    }
    with line:
    Code:
    //buttonStates[c] = digitalRead(c);
    commented out it keeps repeating, like mikeyy said, nothing is setting it to low so it just loops (eg: next, next, next....). without it commented out, it doesnt do anything.

    just a though, if I change the if else statements for detecting the averaged analog input, to a while loop. could that work? ie:
    Code:
    while((average <= 380) && (average >= 320)) {
          lcd.print("NEXT");
          buttonStates[3] = HIGH;
    }
    
    can you use an else statement with a while?
     
  9. mikeyyy

    mikeyyy Member

    Joined:
    Apr 7, 2005
    Messages:
    590
    Location:
    Sydney
    digitalRead(c) will aways return HIGH once it is HIGH because he never resets the PINs to LOW.

    Don't change it to while. You need to reset the buttonStates to LOW at some point. So if you receive the NEXT key press, the next loop if you get NONE you need to set buttonStates[NEXT] to LOW.

    If you haven't fixed it by tomorrow, I might try and whip something up that's less frankenstein. :p

    edit:

    You want something like:

    Code:
      else {
          lcd.print("NONE ");
          for (int c=2; c<7; c++) {
              buttonStates[c] = LOW;
          }
        }
    
    For the rest of the cases you need to set the one key HIGH and the rest LOW, or something to that effect.
     
    Last edited: Jul 3, 2010
  10. OP
    OP
    teno45

    teno45 Member

    Joined:
    Dec 27, 2009
    Messages:
    219
    Location:
    Wagga Wagga
    my interpretation is that line will set buttonStates[c] to what ever digitalRead is. ie: if that digital pin is currently high, then buttonStates[c] is high. if that digital pin is low, then buttonStates[c] is low. buttonStates[c] is a variable held in an array. and digitalRead[c] is a command to read the digital pin, this will return whatever the (digital) value of that pin is.

    I've never used a while statement before, but my impression is it only does something while the conditions are true. I was kinda hoping that once the conditions are no longer true that it would return to is previous state, or you could define a default (ie: the "else" statement.) I probably wouldnt be so lucky for that to work....

    what if before the if else statment sequence, I set all values to low? then run the if statements for the applicable high?

    I should have some time off work over the next few days ( on a side note, I love having two jobs!), so Ill take this new code on board, and see if I can rewrite this thing (almost) from scratch. Try to tidy everything up.

    Thanks for all your help so far!
     
  11. mikeyyy

    mikeyyy Member

    Joined:
    Apr 7, 2005
    Messages:
    590
    Location:
    Sydney
    I think I meant more along the lines of you are setting the output PINs HIGH but never LOW.

    Code:
    digitalWrite(next, HIGH); 
    
    When are you doing digitalWrite(next, LOW); ? It seems never, and so that output PIN is now permanently high. With regards to the comment I was making about the digitalRead(), I was under the impression you were then reading back from the pin to decide when to send to the serial (as you would in a basic merge of the first two pieces of code).

    The function loop() seems to already be looped, so you don't really need your own loop. Sure you can loop while a condition is true, but given that the value of the analog pin can vary so greatly, you would need several loops for each range and it'd just get messy real quick, loops aren't the answer here.

    Also you probably don't need to be setting digital output pins HIGH and LOW for this anyway. Try this...

    1) Take 8 readings (not sure how stable the analogRead is so take a few... and 8 because it's cheaper to divide by a power of 2)
    2) If it detects a key-press, delay(5).
    3) Take 8 more readings, if it's still the same keypress, send the serial command for that key.
    4) goto step 1 (or in your case, since the function is called loop(), maybe just let the function return)

    This way it acts like the original many-pins-and-buttons code. I'll see if I can write something up like this later today.

    Psuedo-code would be like:

    Code:
    
    int getReading() {
       int i;
       int val = 0;
       for (i = 0; i < 8; i++) {
          val += analogRead(inputPin);
       }
       return val / 8;
    }
    
    int getButton() {
    
       int button;
       int average = getReading();
    
       if ((average <= 450) && (average >= 400)) {
          button = MODE;
       } else if ((average <= 380) && (average >= 320)) {
          button = NEXT;
       } else if ((average <= 310) && (average >= 250)) {
          button = PREV;
       } else if ((average <= 240) && (average >= 190)) {
          button = MUTE;
       } else if ((average <= 130) && (average >= 90)) {
          button = UP;
       } else if (average <= 50) {
          button = DOWN;
       } else {
          button = NO_BUTTON;
       }
       return button;
    }
    
    void loop() {
       int button;
    
       button = getButton();
    
       if (button != NO_BUTTON) {
          delay(5);
          if (button == getButton()) {
             // button is still the same after the delay, so send the serial cmd
             sendCommand(?);
          }
       }
    }
    
    
    Yeah it needs a rewrite. :) You just need 1 analog IN pin to read the voltage of the steering buttons, and send serial commands to the ipod if a key has been detected for a certain amount of time, which can be anywhere from 5 to 100ms, best to experiment once it works to see if pressing a key causes 2 keypresses or sometimes doesn't detect at all.
     
  12. mikeyyy

    mikeyyy Member

    Joined:
    Apr 7, 2005
    Messages:
    590
    Location:
    Sydney
    It just occurred to me that what I posted won't work since it doesn't remember that the button was already pressed down, and so will still spam. :p

    Code:
    int prev_button = NO_BUTTON;
    
    void loop() {
    
       int button = getButton();
    
       if (button != NO_BUTTON && button != prev_button) {
          delay(5);
          if (button == getButton()) {
             // button is still the same after the delay, so send the serial cmd
             sendCommand(?);
          }
       }
       prev_button = button;
    }
    
    Added a state to remember which was the last button detected and to not send another command until it changes to NONE or another button. This code can't detect multiple keypresses at the same time, since you'd have to measure the analog reading for every combination and account for it. So because of this I just remember which is the last detected keypress instead of remembering the state for each button.

    Alternative you could actually use a while loop here and spin until the button is released before detecting the next button, I think this is what you meant earlier. I might've misunderstood you there, sorry. :)

    Code:
    void loop() {
    
       int button = getButton();
    
       if (button != NO_BUTTON) {
          delay(5);
          if (button == getButton()) {
             // button is still the same after the delay, so send the serial cmd
             sendCommand(?);
    
             // spin until button is released
             while (getButton() != NO_BUTTON);
          }
       }
    }
    
     
    Last edited: Jul 5, 2010

Share This Page

Advertisement: