Project : Steering wheel audio controls – Programming II

Let’s hit this nail on the head. Last time I talked about some general considerations regarding the code. Now I want to go through the code in blocks and, hopefully, give you an understanding of what I did and why I did it for you to know what works or, at least, what to steer clear off.

Now for the actual code I came up with: This is version 0.2 that I will be testing and probably improving upon if needed. I am sure this can be optimized but for now I want to seal the control module and get it tested in a real environment (yes, I have already built the module, I will cover that later). So here are the main code blocks:

Include directives

The only thing I need is to include IRLib2 by cyborg5. I’ll do a separate posting about IRLib2. All you need to know for now is that:

  1. It’s a library that permits reading and transmitting of IR signals from remotes etc.
  2. It does all the magic at the end of the program in one call
  3. It’s a great library that I fully encourage you to experiment with

First the library itself is included then the protocols and finally combo (IRLibCombo.h)  that ties all of it together. Basically, this part is just using what I need from cyborg5’s great documentation here: https://github.com/cyborg5/IRLib2/blob/master/IRLib2/examples/send/send.ino .

 

//IR LED
#include "IRLibAll.h" //https://github.com/cyborg5/IRLib2
//Include the send base
#include <IRLibSendBase.h>    
//The lowest numbered protocol should be first but remainder can be any order.
#include <IRLib_P02_Sony.h>   
#include <IRLibCombo.h>
//Create a sender Object (always pin 3 )
IRsend mySender;

Global variables

The global variables I am using are grouped by usage:

  • ohmmeter: the pin I am using to detect the voltage, a variable to store the value red by the DAC (raw), Vin to hold the computed voltage and Vref to hold the reference voltage which I measured on my Arduino being ran by the USB input (4.95V).
  • light controls: just the pin to control the lights on the factory buttons. I chose a PWM capable pin because I wanted to be able to flash and pulse the lights to show feedback but testing showed that they are not bright enough to see in the daytime so relying on any feedback from them would be useless. Come to think of it I could still have implemented it just not rely on it … mental note: further improvement opportunity.
  • Sony controls: these are just two Booleans to store the state of the buttons. Shift means that a long press was detected and super shift means that I pressed the button that activates the door controls.
  • door controls: just declarations for the pins used for locking and unlocking and a delay of 100 that represents the time I want the lock/unlock signal held high
  • debounce: 4 values used for preventing multiple accidental activations on one button push.
  • debug: I like switching off the debug text to free up processing power once the module is installed. Sending by serial takes time even with the high baud rates Arduino can handle

 

//ohmmeter
int ohmeterPin= A0;//pin 0 analog
int raw= 0;//initialize DAC output
float Vin= 5.0;//initialize sensed voltage
float Vref= 4.95;//ref voltage

//light control
byte lightPower=11;//the pin that constols the steering weel leds - pwm in case of brighter leds

//sony controls
bool shift=false;//modifier for long press
bool superShift=false;//modifier for door control safety

//door controls 
byte doorLock=13;//pin for door lock signal
byte doorUnlock=12;//pin for door unlock safety
int doorDelay=100;//door control signal time

//debounce
long int duration=0;//duration of button press
int inputThreshold=700;//threshold for false input signals
int debounceThreshold=25;//threshold for debounce time
int shiftThreshold=500;//threshold for activating shift modifier

//debug
bool debug=false;//everything works faster when tehre's not alot of waiting to comunicate going on

Setup

In the setup I just initialized serial communications with the PC for debugging set the needed output and input pins and printed a message to confirm in the Serial Monitor that everything is ready

 

void setup() {
  Serial.begin(115200);
  pinMode(doorLock, OUTPUT);
  pinMode(doorUnlock, OUTPUT);
  pinMode(lightPower, OUTPUT);
  pinMode(ohmeterPin, INPUT);
  Serial.println("INITIALISED");
}

Loop

I turned the button backlighting on to full power and call SENSE. Remember I am controlling the backlighting through a PWM pin and 255 is the maximum value. Although I could have set this in the setup I just wanted to make sure I never forget the backlight off.

 

void loop(){
  analogWrite(lightPower, 255); // power on the leds
  sense();//start infinetly sensing
}

Sense

The things I want done in the sense function are:

  • Make sure the voltage read from the resistive divider is within limits
  • Once an accurate reading is received readings are taken periodically to determine the length of time a button was pressed and the average value of the received input values from the ADC is calculated. This value is used as a way of eliminating errors caused by imperfect contacts etc.
  • If the above time measurement exceeds the threshold for a so called long press the measuring routine is exited
  • Once it is determined I have a valid button press with a corresponding duration the average input voltage is calculated based on the raw value provided by the ADC and the DETECT function is called. In the case of a long press it is presumed the DETECT function will be called while an input is still pressed so after Detect the necessary action the code waits for the button to be depressed.

 

void sense(){
    bool check = false;
    raw = analogRead(ohmeterPin);///read the value
    if(debug){Serial.println(raw);}
    if(raw<inputThreshold)//if the value is less than threshold discard 
    { 
      long start=millis();//note when the fisrt buton activation occured
      long sum=raw;//first raw value is already loaded when we enter the while loop
      int i=1;//first raw value is already loaded when we enter the while loop
      analogWrite(lightPower, 0);//give the user some feedback that the button is pressed
      while(raw < 700 )//as long as the ADC value is smaller than the threshold keep reeding and checking
      {
        raw=analogRead(ohmeterPin);
        duration=millis()-start;
        sum=sum+raw;
        i++;
        if(debug){
          Serial.print("raw_inproc = ");
          Serial.println(raw);
          Serial.print("i_inproc = ");
          Serial.println(i);
          Serial.print("sum_inproc = ");
          Serial.println(sum);
        }
        if(duration>shiftThreshold)break;
      }
      raw=sum/i; //raw becomes avrage
      if(debug)
      {
        Serial.print("i_final = ");
        Serial.println(i);
        Serial.print("sum_final = ");
        Serial.println(sum);
        Serial.print("raw_final : ");
        Serial.println(raw);
      }
      analogWrite(lightPower, 255);//once the buton is depressed turn on the lights
      check=true;
    }
    if(check)
    {
      Vin= raw * Vref /1024.0;//calculate Vin
      if(debug)
      {
        Serial.print("time pressed : ");
        Serial.println(duration);
      }
      if(shiftThreshold>(duration)&&(duration)>debounceThreshold)
      { 
        //if the buton was pressed less that the shift threshold and more than the debounce time make shure shift is false and start executing
        shift=false; 
        Serial.println("Short press detection :");
        detect(Vin);
      }
      if((duration)>shiftThreshold)
      { 
        //if the button has been pressed long enaugh trigger the modifier and start executing
        shift=true;
        Serial.println("Long press detection :");
        detect(Vin);
        raw=analogRead(ohmeterPin);
        if(debug)
        {
          Serial.print("release check: ");
          Serial.println(raw);
        }
        while(raw<inputThreshold)//wait for button to be released as to not trigger 1 long and 1 short press
        {
          if(debug)
          {
            Serial.print("waiting for release : ");
            Serial.println(raw);
          }
          raw=analogRead(ohmeterPin);
        }
        if(debug){Serial.println("released");}
      }
      duration=0;//reset the start moment just to be safe
    }
}

Detect

This function simply calls the execute function with a parameter representing the necessary code to be sent. This parameter is determined by a series of cascading IF statements that take into account the length of a button press (the shift Boolean is set to true for long presses) and also if the “SuperShift” was activated which is also stored in a Boolean.

 

void detect(float Voltage){
  //checks the voltages and executes the propper comands to the leds of to variabels
  //resistor values :   82         82        120        180        330        680      2200
  //totals          :   82        164        284        464        794       1474      3674
  //voltages        :0.379      0.704      1.106      1.585      2.213      2.979      3.93
  //thresholds      :     0.5415     0.905      1.335      1.899      2.596      3.184
  Serial.print("Vin: ");
  Serial.println(Voltage);
  if(Voltage>3.184)
  {
    Serial.println("Out of range");//should hardly happen
  }else
  if(Voltage>2.596)
  {
    //|
    //SuperShift/5
    Serial.println("|");
    if(!shift){
      superShift=!superShift;
      Serial.print("superShift :");
      Serial.println(superShift);
      }// short command enable door comands
    else 
    {
      execute(0x1021);//long command 5key
      superShift=false;
    }
    shift=false;
  }else
  if(Voltage>1.899 )
  {
    //<
    //seek- 2(alb+)
    Serial.println("<");
    if(!shift)execute(0x5621);//short command seek-
    else execute(0x4021);//long command 2key
    shift=false;
    superShift=false;
  }else
  if(Voltage>1.335)
  {
    //>
    //seek+ 1(alb-)
    Serial.println(">");
    if(!shift)execute(0x1621);//short command seek+
    else execute(0x21);//long command 1key
    shift=false;
    superShift=false;
  }else
  if(Voltage>0.905)
  {
    //O
    //source 6(pause)
    Serial.println("O");
    if(!shift)execute(0x3121);//short command source
    else execute(0x5021);//long command 6key
    shift=false;
    superShift=false;
  }else
  if(Voltage>0.5415 )
  {
    //+
    //vol+ 3(rep) SS:LOCK
    Serial.println("+");
    if(superShift && shift){lock();}//if it's a long press and the door controls where enabeld just before lock
    else
    {
      if(!shift)execute(0x2421);//short command vol+
      else execute(0x2021);//long command 3key
    }
    shift=false;
    superShift=false;
  }else
  {
    //-
    //vol- 4(shuf) SS:UNLOCK
    Serial.println("-");
    if(superShift && shift){unlock();}//if it's a long press and the door controls where enabeld just before unlock
    else
    { 
      if(!shift)execute(0x6421);//short command vol-
      else execute(0x6021); //long command 4key
    }
    shift=false;
    superShift=false;
  }
}

Execute

Here is where the IR library comes into the picture. This function uses the IR sender I created at the beginning of the program to send the code it receives. The protocol name and number of bits was determined experimentally by first using the IR library to detect the output of all my buttons. This detection process retrieves not only the code that needs to be sent for a specific action but also the protocol name and number of bits which is expected to stay consistent for all communications from the respective remote.

 

void execute(uint32_t code){
  Serial.println (code);
  mySender.send(SONY,code, 15);//use mySender to send the code with SONY encoding 15 bits
}

Lock and Unlock

Booth functions serve to keep the corresponding signals high for a predetermined period of time. This will probably need to be refined by testing when it is mounted on the car but for the moment I set this duration to 100ms.

 

void lock(){
  Serial.println("Locking doors");
  digitalWrite(doorLock, HIGH);
  delay(doorDelay);
  digitalWrite(doorLock, LOW);
  Serial.println("Doors locked");
}
void unlock(){
  Serial.println("Unlocking doors");
  digitalWrite(doorUnlock, HIGH);
  delay(doorDelay);
  digitalWrite(doorUnlock, LOW);
  Serial.println("Doors unlocked");
}

 
And that is all. All together it adds up to what I hope is a functional interface between the controls and Sony unit.