DSP volume controlled from lookup table via i2c

Hello first of all, I'm new to this forum

Ok, my project is, I have DSP (ti tas3308) which is used to all audio related operations. I have mini pro, which I use to read encoder(volume). And based on the step at which the encoder is, I send string of data via I2C to DSP.

Now I would need some (a lot of) help.
Since volume can change from +24dB to below -100dB in 0.5dB steps, and I am very bad at writing code, I would need to make A LOT ofif statements and I am sure there is better way to do it.

I am sure I have missed something and will add if needed...

So here is my code:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Encoder.h>
LiquidCrystal_I2C lcd(0x20, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address
Encoder myEnc(8, 9); // Encoder pins


void setup()
{
  Wire.begin(); // join i2c bus (address optional for master)
  lcd.begin(16,2);  // initialize the lcd for 16 chars 2 lines and turn on backlight
  lcd.backlight(); // backlight on
  Serial.begin(9600);
  Serial.println("Basic Encoder Test:");
}
String volumeChar[10];
long oldPosition  = -999;
int out = 0;
int data1 =0;
int data2 =0;
int data3 =0;
int data4 =0;

void loop()
{
  long newPosition = myEnc.read();
  if (newPosition != oldPosition) {
    oldPosition = newPosition;
    if (newPosition <= 0){
    myEnc.write(0);
    }
    Serial.println(newPosition);
    lcd.setCursor(0,0); //Start at character 0 on line 0
    lcd.write(newPosition);
    lcd.setCursor(0,1); //Start at character 0 on line 1
    lcd.println(volumeChar[10]);
 }
 if ((newPosition == 0)& (out==0)){
    out=1;
    data1=0x00;
    data2=0x10;
    data3=0x00;
    data4=0x00;
    String volumeChar[10] = "-18.0 dB";
 } 
  if ((newPosition == 2)& (out==0)){
    out=1;
    data1=0x00;
    data2=0x14;
    data3=0x49;
    data4=0x60;
    String volumeChar[10] = "-16.0 dB";
 } 
 if ((newPosition == 4)& (out==0)){
    out=1;
    data1=0x00;
    data2=0x19;
    data3=0x8A;
    data4=0x13;
    String volumeChar[10] = "-14.0 dB";
 } 
 if ((newPosition == 6)& (out==0)){
    out=1;
    data1=0x00;
    data2=0x20;
    data3=0x00;
    data4=0x00;
    String volumeChar[10] = "-12.0 dB";
 } 
 if ((newPosition == 8)& (out==0)){
    out=1;
    data1=0x00;
    data2=0x28;
    data3=0x7A;
    data4=0x26;
    String volumeChar[10] = "-10.0 dB";
 } 
 if ((newPosition == 10)& (out==0)){
    out=1;
    data1=0x00;
    data2=0x32;
    data3=0xF5;
    data4=0x2C;
    String volumeChar[10] = "-8.0 dB";
 }
 if ((newPosition == 12)& (out==0)){
    out=1;
    data1=0x00;
    data2=0x40;
    data3=0x00;
    data4=0x00;
    String volumeChar[10] = "-6.0 dB";
 }
 if ((newPosition == 14)& (out==0)){
    out=1;
    data1=0x00;
    data2=0x50;
    data3=0xC3;
    data4=0x35;
    String volumeChar[10] = "-4.0 dB";
 }
 if ((newPosition == 16)& (out==0)){
    out=1;
    data1=0x00;
    data2=0x65;
    data3=0xAC;
    data4=0x8C;
    String volumeChar[10] = "-2.0 dB";
 }
 if ((newPosition == 18)& (out==0)){
    out=1;
    data1=0x00;
    data2=0x80;
    data3=0x00;
    data4=0x00;
    String volumeChar[10] = " 0.0 dB";
 }
 while (out == 1) {
     sendData();
    }
}
void sendData() {
      Wire.beginTransmission(0x34); // transmit to device 0x25
      Wire.write(0x25);         // sends data
      Wire.write(data1);
      Wire.write(data2);
      Wire.write(data3);
      Wire.write(data4);
      Wire.endTransmission();    // stop transmitting
      out=0;
     }

I send 4 sets of data (4 bytes?). One problem I have is, data keeps being send even if I don't change position of encoder. But code doesnt' work if I put i2c part at the bottom in IF statement. So that is one of the things I would like to change. Other one is, to have all this data in lookup table.

Volume table is in attached file

don't be too hard on me, I hate writting code (but only because it is hard for me, getting better by the day), and this is the result of only 2-3 days of playing around with arduinos and logic analyzers.

Note: i2c LCD is not used yet.

Best regards,
Luka

Volume_data_table.txt (7.97 KB)

This block of code only executes when the encoder position changes:

  if (newPosition != oldPosition) {
    oldPosition = newPosition;
    if (newPosition <= 0){
    myEnc.write(0);
    }
    Serial.println(newPosition);
    lcd.setCursor(0,0); //Start at character 0 on line 0
    lcd.write(newPosition);
    lcd.setCursor(0,1); //Start at character 0 on line 1
    lcd.println(volumeChar[10]);
 }

If you only want to send your data when the position changes, I suggest you put the code to do it inside that block.

This and similar statements are probably not doing what you think:

String volumeChar[10] = "-18.0 dB";

(It declares a local array of String variables which immediately goes out of scope.)

I suggest you stop using the String class completely. It's a recipe for trouble and gives you nothing that can't be done just as easily using plain old c-strings (null-terminated char arrays).

I suggest you take all the logic to calculate the value associated with each position into a separate function which takes the position as an argument and returns the corresponding value. This will make the main logic (such as controlling how updates get sent) more obvious and make it easier to try different methods for making the conversion without ripping up your code (you can define multiple functions which use different approaches and just call the one you want to try).

This piece of code seems unnecessarily complex:

 while (out == 1) {
     sendData();
    }

Since sendData() sets out=0, this is equivalent to a simple call to sendData() and the out variable is redundant.

Since the data consists of an unsigned 32-bit value I suggest you store it internally as an unsigned long, and have sendData() take the value as an unsigned long argument. Inside sendData() you can pick the four bytes out of the unsigned long value and send them individually.

You could implement that lookup table as an array of 272 unsigned long values held in PROGMEM and look up the appropriate value at runtime, but it would also be worth seeing whether you can fit a curve to that data - if so, you would be able to calculate the value instead of looking it up. (At first glance it does look as if it might fit an exponential curve, but I haven't checked that.)

Thank you Peter, I'm looking at what you said.

Curve is exponential, I have plotted it with excel and if I use log for one of the axis, out comes straight line.

How would I use that info to make function?
function would need input of that number, translate that into hex and then output that. And that I would send. What would be an easy example of that, how would the code look like?

Calc agrees that there is an exponential relationship between readout and value and suggests the formula is:

value = 8391918.40642948 * EXP(0.1151873458 * readout)

I must admit I'm not sure which column in that sample data represents your input value but supposing you know Readout and want to calculate the corresponding Value, you could do that like this:

#include <math.h>

...

unsigned long getValue(float readout)
{
  float scale = 8391918.40642948;
  float exponent = 0.1151873458 * readout;
  float result = scale * exp(exponent);
  return (unsigned long) result;
}

If that's not the conversion you're trying to perform you'd have a different formula but hopefully that snippet gives you some idea how you'd implement that formula in code.

that data is:

on the left you have what dB gain will the DSP apply to input audio signal. On the right you have hex value you need to send to DSP over i2c to set that gain.
I would like to display the number on the left to LCD, and send the number on the right to DSP

I hope that clears things up

better formula is:

result = 8388608 * exp(0.1151292521 * readout);

Now if I could only print that out... would be easier to print to LCD or serial?

Serial.print("result = ");
Serial.println(result);

LukaQ:
on the left you have what dB gain will the DSP apply to input audio signal. On the right you have hex value you need to send to DSP over i2c to set that gain.
I would like to display the number on the left to LCD, and send the number on the right to DSP

Could you write this again, but this time using the column names ("Trackbar", "Readout", "Value") from your spreadsheet and explain which value you are starting from and which value(s) you want to calculate from it?

Sure can, here goes

//Trackbar Readout Value
|0        24.0   0x07ECA9CD
|1        23.5   0x077B2E7F
|2        23.0   0x07100C4D
|3        22.5   0x06AAE84D

Trackbar is nothing else but line numbering, for easy reference... let's say, line 3 corresponds to 22.5dB gain and for that you need to send 0x06AAE84D

Readout is number in dB that you print to serial or LCD, which is normal indication of how much gain amp is having, 0dB would be full power with full input signal amplitude. -db would be, amp would play at lower level then it's max, +db would be over (if you have low input signal)

And value is hex code that you need to send to DSP, to get that gain, for 23db, that would be 0x07100C4D (line number 2)

Here is the deal, I don't know which value would be better starting point, but two things are clear, YOU must have "Value" and it is good to have "Readout". You could live without "Readout" and wouldn't know at what volume you are at, but you can not be wihtout "Value", since that changes the volume itself

In that case I think you already have the solution you need. You will need to come up with some scheme to decide what the current Readout value is, as a float value. For example you might store it internally as a number of clicks entered using your encoder, and then scale that up to get the corresponding Readout value. Or you might choose to store the Readout directly.

The code I suggested demonstrates how to convert from a Readout to a Value (i.e. the 32-bit number you use to configure the DSP). You will need to make a small change to the code that sends the Value, to convert the 32-bit Value into four 8-bit bytes. You already know how to send the four bytes. A FOR loop would be a good way to shift and mask the 32-bit Value to separate out each byte.

I can't seem to find, how would I convert DEC to HEX
I guess using mask and then bit shift would work

how about like so, would it be possible to do the same from char*?

#include <avr/pgmspace.h>
prog_char string_0[] PROGMEM ="24.0 dB";
prog_char string_1[] PROGMEM ="23.5 dB";
.
.
.
prog_char string_270[] PROGMEM ="-111.0 dB";
prog_char string_271[] PROGMEM ="-111.5 dB";
prog_char string_272[] PROGMEM ="0x07ECA9CD";
prog_char string_273[] PROGMEM ="0x077B2E7F";
.
.
.
prog_char string_542[] PROGMEM ="0x00000017";
prog_char string_543[] PROGMEM ="0x00000017";




// Then set up a table to refer to your strings.

PROGMEM const char *string_table[] = 	   // change "string_table" name to suit
{   
  string_0,
  string_1,
 .
.
.
  string_542,
  string_543,
 };

char buffer[11];    // make sure this is large enough for the largest string it must hold
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
void setup()			  
{
  Serial.begin(9600);
  Wire.begin(); // join i2c bus (address optional for master)
  lcd.begin(16,2);  // initialize the lcd for 16 chars 2 lines and turn on backlight
  lcd.backlight(); // backlight on
}
int j=0;
char* data1=0;
char* data2=0;
void loop()			  
{
  /* Using the string table in program memory requires the use of special functions to retrieve the data.
     The strcpy_P function copies a string from program space to a string in RAM ("buffer"). 
     Make sure your receiving string in RAM  is large enough to hold whatever
     you are retrieving from program space. */


  for (int i = 0; i < 272; i++)
  {
    strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy.
    data1=buffer;
    Serial.print(data1);
    j=i+272;
    delay(10);
    strcpy_P(buffer, (char*)pgm_read_word(&(string_table[j]))); // Necessary casts and dereferencing, just copy. 
    data2=buffer;
    Serial.print("\t");
    Serial.print(buffer);
    Serial.println("");
    lcd.print(data2);
    lcd.setCursor(0,0);
    delay(1000);
    lcd.clear();
  }
}

could you do the same from data2?

Peter, anyone else ?

LukaQ:
I can't seem to find, how would I convert DEC to HEX

Decimal and hexadecimal formats are ways to represent numbers textually. I don't understand why you would need to convert to/from textual formats here.

UPDATE:

I have had some help, and with that help, I have working program the way I would like it.

If there is someone else that will need the code or find it useful, I add it here in arduino file.
Thank you Peter, for the help.

Best regards,

Luka

tas3308Volume_new.ino (20.4 KB)