Posts with «simple projects.» label

TRUE ANALOG AUDIO VOLUME CONTROL (T.A.A.V.C.).

 

More:   Video1   and   Video2

Now my Arduino can precisely measure audio input (VU meter),   and obviously, next thing that comes to mind right after measurements, is regulation or control. There are many different ways how to electronically adjust audio volume or level of AC signal.  I’ll name a few:

  • Specifically design IC, Digital potentiometers.
  • Mechanical potentiometers, driven by servo / motors.
  • Vacuum tubes amplifiers in “variable-mu” configuration.
  • Resistive opto-isolators.

First category (#1) fits nicely for arduino project. But it’s not interesting to me. My apologies to someone, who was searching an example of interface between arduino and digital pot. Other people don’t tolerate semiconductors in audio path ( tube sound lovers ). Third option would make them happy, only (#3) requires high voltage and difficult to accomplish on low hobbyist budget, so I left it out of the scope. Mechanical pot (#2) would be good solution to satisfy Hi-Fi perfectionists and arduino fans. The same time response time of mechanical parts is too slow, verdict – discarded.  (#4) have been in use since 1960s, but would you like your lovely music to be adjusted by highly toxic CdSe / CdS ?  I don’t think so. Wiki says opto-isolators have low THD distortion level, less than 0.1%. Probably true, but apart from technical aspect, there is always psychological, poisonous CdSe affects my perception.

How about variable resistor in it’s simplest form – tungsten wire? Where you can get one? In the electrical bulb. Perfect material for audiophiles – where distortion could get into ? – It’s pure metal ! And here is my design of the “basic cell” – True Analog Audio Volume Control (T.A.A.V.C.)

As you can see, cell consists of 5 bulbs plus 1 resistor. All elements form 2 attenuation stages, basically – voltage dividers with variable resistors. Resistive values of bulbs proportional to temperature, which is easy to control passing DC current through. To make it work with the biggest possible dynamic range, middle bulb is also heated up by current flow from two differential control lines / phases.

 Hardware prototype / Proof of Concept.

Differential Power Amplifier (PA) IC LM4863  is used as DC current source for control lines. Circuitry powered from 5V regulated switching power supply (4A).  Bulbs – clear mini lights, 2.5V, approximately 200 mA. Cold state resistance about 1.2 – 1.5 Ohm, hot state resistance is rising up to 15 Ohm.  Volume regulator could be connected to any audio source with output impedance no more than 32 Ohm, for example, headphones output. For test I used second channel of the PA, that shown in “optional” box on the left side. Second channel is a “nice to have” feature  in stereo version of the project, when both channels would drive two separate TAAVC cells, so using it as a “buffer” amplifier may be not an option.

Results.

  Measured attenuation  range of the “basic cell” is  20 dB, or 10 x times.

 to be continue…

 Chart, PWM (Voltage) to Attenuation:

 Quite interesting, isn’t it? I was not expecting it’s to be linear, but changing direction surprised me. There is one things, which could explain such abnormality, is that when voltage on the control lines 1 and 2 ( LM4863 outputs ) is approaching power rails, output impedance of the power amplifier is increasing, and consequently, attenuation characteristics of the basic cell deteriorate. It means, that in order to keep attenuation curve gradually declining, more powerful PA necessary. For now, I limited PWM to 0 – 200 range.

I’m thinking, STA540  powered from +12V, and 5 V bulbs would make sense to try next time.  Probably, replacing middle bulb for less current hungry, will increase max attenuation per cell up to 30-35 dB.

 O’K, after I get this data, how could I “straighten it up” for practical use ?  Volume control device, could be linear or logarithmic, but chart doesn’t resemble nether of this. And this is exactly what I need Arduino for.

Linearization.

 If you, by chance, have read this page, than you know how to do this part. Polynomial approximation. Unfortunately,  2-nd degree polynomial I used last time is not enough for VERY non-linear curve like I have. So, I “upgraded” my calibration subroutine (method: LEAST SQUARES) up to 3-rd degree:

void calibrate()
{
 //Least squares 3-rd degree polynomial coefficient calculation
 float arr[10][5] ={{0},{0}}, err[10] = {0}, coeff[10] = {0};

 err[0] = 10;
 for(uint8_t i = 1; i < 7; i++)
 {
  err[i] = 0;
  for(uint8_t j = 0; j < 10; j++)
  {
   err[i] += pow(level_table[j], i);
  }
 }

 for(uint8_t i = 0; i < 4; i++)
 {
  for(uint8_t j = 0; j < 4; j++)
  {
   arr[i][j] = err[i+j];
  }
 }

 for(uint8_t i = 0; i < 4; i++)
 {
  arr[i][4] = 0;
  for(uint8_t j = 0; j < 10; j++)
  {
   if (i==0) arr[i][4] += calibration[j];
   else arr[i][4] += calibration[j] * pow(level_table[j], i);
  }
 }

 for(uint8_t k = 0; k < 4; k++)
 {
  for(uint8_t i = 0; i < 4; i++)
  {
   if ( i != k )
   {
    for(uint8_t j = (k + 1); j < 5; j++)
    {
     arr[i][j] -= ((arr[i][k] * arr[k][j]) / arr[k][k]);
    }
   }
  }
 }

 union split_float {
 uint16_t tegri[2];
 float loatf;
 } sf; 

 for(uint8_t i = 0; i < 4; i++)
 {
  coeff[i] = ( arr[i][4] / arr[i][i]);
  sf.loatf = coeff[i];
  store_entry(( 2 * i ), sf.tegri[0] );
  store_entry(( 1 + (2 * i)), sf.tegri[1] );
 }
}

Procedure takes 10 data samples as input, calculates 4 coefficients and stores them in EEPROM memory.

VU meter based on Arduino UNO ( in minimum configuration Arduino and DC offsetting circuit ) should be connected right to T.A.A.V.C. output. Everything works in automatic mode, with results printed on serial monitor for review. Stable AC input is necessary, which easy to get from any PC sound card based signal generator, recorded media file or lab sine-wave generator. Arduino also provides PWM for T.A.A.V.C       via pin D3 (TIMER2 OCR2B).

Link to download Arduino UNO sketch: T.A.A.V.C.

to be continue…audio compressor, .

Last part,

Dynamic Range Compressor.

There are two important parameters defined in the beginning of the sketch:

#define          CMP_THRE                     -10             // Compression Threshold 
#define          CMP_RATE                        4             // Compression Ratio 4 : 1

Threshold and Ratio. I’m not into explaining all bunch of the details about compressors or what for do you need one, rather forward you to this link.  I only want to say, that I didn’t find any evidence that someone ever used electrical bulbs as compressors “engine”. So, this is my idea, and my implementation.

Technical specification of this design is quite modest, having close to 20 dB maximum attenuation and setting ratio to 2:1, threshold couldn’t be set lower than -40 dB. Good news, that for practical use in home environment it’s  really unlikely, that you ‘d need more. It’s also should be enough to solve a problem with annoying TV or Radio commercial / advertisement.

Compare to VU Meter project, I’ve made a few “relaxing” changes to the code, as it appears there are no strict industry standard on Dynamic Range Regulation. I reduce sampling rate down to 10 kHz,  and  split Low Pass Filtering in two sections. One is the same, as in VU Meter, inside sampling interruption subroutine, only with lower time constants. First LPF section is responsible for “shaping” attack and decay timing. Using quite inertial electrical bulbs in the project, reduce importance of this timing. Here attack and release mostly defined  by thermal responsiveness of the bulbs, which isn’t very high. Decreasing software LPF time constants helps to improve sensitivity. Other LPF section included inside LCD drawing function, works to overcome display slowness, suppressing LCD flickering. Other changes from simple VU Meter, is that finally I “scaled” everything correctly,  and “0″ db corresponds exactly to 1.228 V RMS at the input. Threshold level -10 expressed in dB as well. You may see threshold “mark” above the log scale. Indicator “needle” just below it, small 5×2 pixels only, but you can make it bigger if you wish.

I already described calibration procedure, to do it right, you need to connect arduino to output of the TAAVC cell.  Polynomial coefficients and minimum / maximum constants stored in EEPROM, so you don’t have to do this procedure each time after power cycling.  In normal mode arduino getting input measurements from the cell input:

Finished.  I’ll do a video “on demand” -);  If I had time…

Arduino UNO sketch:  Audio Dynamic Range Compressor. (TAAVC part 3).


Audio VU Meter.

Simple project, some kind of “Arduino-Wiki” for beginners.

How to do measurements right:

  1. Biasing AC input using resistor divider and scaling up / down if necessary;
  2. Sampling fast and accurately using direct access to Control Registers of ADC and Timer1;
  3. Subtract whatever was added at step #1 from result of Analog / Digital conversion and Square;
  4. Average / accumulate, filtering following VU specification;
  5. Sqrt – take square root, Log10 – transform to dB scale;
  6. Display.

 1.   Hope, you follow my advice and hack your cheap USB speakers, to get nice ( pre-assembled ! ) analog “front-end” for this project. If not, than get your soldering iron to work, minimum two resistors and 1 cap would required, assuming you already have display wired up and running.

 First things with AC measurements ( audio in this category ) on Atmel microcontroler is to get rid of negative half-wave of the input signal, and this what front-end circuitry should do. There are at least two option: rectifying AC to DC before voltage could reach arduino analog input, or biasing signal with external DC offset. Rectification, could nicely be done with help of specifically design IC, LM3914 / 15 / 16 for example. But in this article, I’d describe second alternative, as it’d be not fare to ask you to hack your speakers and than tell you to solder another board…. Here is my set-up, slightly modified version from last blog:

When AC input signal is mixed with DC offset, so it stays always in positive area, ( think about sine, which defined betseen -1 and +1, if I add +1 it always would be positive ), I only save arduino life, preventing it from destruction by negative voltage. When arduino ADC completes conversion from analog to digital form, I don’t need DC offset anymore, and it should be subtracted.

  NOTE: DC voltage was added just to pass audio through arduino ADC. 

2. Sampling subroutine is running at 40 kHz, that is more than enough for ANY application. You may decrease sampling rate to lower CPU load, with current settings VU metering consumes more than 50%. Higher sampling rate gives better linearity / precision over wide band, the same time with regular audio content even 10 kHz sampling would provide better than 1 dB accuracy. All input capture process goes in Interruption subroutine, which is configured in setup. Two channels of Timer 1 Configured to run in parallel, “A” is responsible to keep clock at 40 kHz sharp, and “B” fires start conversion event to ADC with the same speed. Restarting new conversion via hardware provides lowest phase noise compare to any other way of doing this.

     ADCSRB = ((1<<ADTS2)| // Sets Auto Trigger source – Timer/Counter1 Compare Match B
                       (0<<ADTS1)|
                       (1<<ADTS0));

/* Set up TIMER 1 – ADC sampler */
       TCCR1A = ((1<<WGM11) | (1<<WGM10)); // Mode 15, Fast PWM
       TCCR1B = ((1<<WGM13) | (1<<WGM12)); // Mode 15, Fast PWM

       TCCR1B |= (1<<CS10); // clk/1 prescaling.
       OCR1A = SMP_TMR1;
       OCR1B = SMP_TMR1;

      TIFR1 |= (1<<OCF1B); 
      TIMSK1 |= (1<<OCIE1B);

3 .  As you can see in a code snipped below, adc_Offst is subtracted from new ADC result. Quite simple, value of DC offset ( adc_Offst ) was obtained in setup() during start up, using Arduino IDE “analogRead”. The only problem with this simple solution, is that no audio should be present at this moment ( start up ) at input, otherwise all measurements would be erroneous.

 ISR(TIMER1_COMPB_vect)
   {  
      int32_t temp = ADC – adc_Offst; 
                 temp = temp * temp;
      if ( temp > ppm_Level ) ppm_Level = ((ppm_Level * 255) + temp) >> 8;
      else ppm_Level = (ppm_Level * 16383) >> 14; 
   }  

4. The same piece of code includes VU filtering algorithm. I was trying to get as close to standard as possible, but tough timing requirements ( 25 usec ! ) doesn’t allow get full satisfaction. Attack time is very close to specification, 3 milliseconds or so. Decay, I’d estimate in 200 milliseconds, which is less than recommended 650 milliseconds for Peak Program Meter (PPM), and also less than 300 milliseconds for regular VU.  The limits come from 32-bit integer math, from one side, and high sampling rate from another.

updated:

This comparison operator     if ( temp > ppm_Level )  separates attack – when new value is bigger than stored, and decay – when new value is smaller. Now, lets me explain what this line of code does:

     ppm_Level = ((ppm_Level * 255) + temp) >> 8;
Rewriting it to:  ppm_Level = ((ppm_Level * 255) + temp) / 256;

and than:          ppm_Level = ppm_Level *  ( 255 / 256 ) + temp * ( 1 / 256 );

reveals:            ppm_Level = ppm_Level *  0.99609375 + temp * 0.00390625;

Which is simple, single pole Low Pass Filter. For more details on recursive filtering I’d refer to this book. Equation 19.2.   Trick with right shift operator (>>8) is just to improve speed of calculation. Remind you, that Arduino doesn’t have floating point CPU, and any mathematics work with floating point coefficient very slow.

Same with filtering decay process, the difference is only in coefficient value.

( >> 14) is the same as 1 / 16384,   and so   16383 / 16384 = 0.999938965.

5.   Read comments, please:

    int32_t temp;

     temp = ppm_Level;                        // Take a copy, so Real Value not affected by calculation.
     temp = sqrt(temp);

    rms_Level = 20.0 * log10(temp +1); // Calculated, available over Serial

6. Last part, drawing VU indicator on graphical display. Ones again, referring you to short hardware description of using model.  There is not much to say, all display interface based on GLCD library. The only “tweak” from my side, is that I added “if” condition in front of drawing needle subfunctions:

      if ( abs(st1 – st2) > 3 )                          //  ~1/3 dB

I discovered, that DrawLine is quite slow, 4 calls ( 2 – erase, 2 – draw, all double – to make needle thicker ) take 125 milliseconds, so it make sense not to draw anything if there is not big difference between old and new needle position. At least, new position has to be off by width of the needle itself.

Link to Arduino (UNO) sketch: download.


DIY Arduino FM Radio (Part 2).

 Updates on 24 Oct. 2012.

If you have read my first blog on the topic, than you already know what I’m experimenting with. Low price FM Radio, build with TDA7088 / YD 9088. It was obvious, that technology from the early 90-x is outdated. I mean, simple “search and hold” function of the radio.

  • Scan function works only one way, up in the frequency.
  • After switching power “on/off” you have to tune it again on your preferred radio wave.
  • You have no means to know, what is the frequency it’s on.
  •  Occasionally you can’t “push” a tuner to the next station on the air, and have to send multiple commends.

The problem is that radio luck intellectuality, you can’t do much with  RS trigger.  Arduino, even it’s small and 8-bits only, just a monster on this matter, with CPU, ADC and EEPROM. Now it’s time to use them!  Radio-2 able to automatically scan all radio station in the area, store them in non-volatile memory, and plus do a manual “tunning” – mostly for debugging purposes.

This version of the shield requires 4 wires (plus a cap, 4 resistors, and radio, of course). Two power lines, the same like in the first project, and other two connected to pin 1 of the IC and to variable capacitance diode. You also have to cut a trace, which connects Varicap and pin 16. Look at the schematic above.

Digital pin 9 of the arduino, internally driven by TIMER 1, is PWM-ed frequency control voltage to tune a heterodyne.  FAST PWM mode, 10-bit is a compromise between PWM frequency and voltage resolution. We can’t get both, for example 16-bit PWM would drop a frequency down to 16 MHz / 65536 = 244 Hz, which is too low to be filtered by primitive  RC filter, and plus not adequate to do a fast “sweep” or changes in tunning. The same time 8-bit PWM has low voltage resolution, and consequently creates too big “steps” in bandwidth. Measurements show, that radio has about 1 V / 20 MHz sensitivity of the VCO, which would imply to have a resolution at least:  1 V / 20 MHz / 200 kHz = 10 mV, where 200 kHz is a “spacing” in FM broadcasting schedule.  8 bit analogWrite has a resolution 5 V / 256 = .19.53 mV. Even resistors divider improves this value on 0.6 times (3 v / 256 = 11.72 mV), it’s still bigger than necessary minimum. With 10-bits I have 3V / 1024 =   2.93 mV.   Using of external DAC isn’t an option for a few backs radio, but may be worse to try. PWM frequency with 10-bit equals to 16 kHz, which could be easily filtered out with 1-st order RC network.

Analog pin 3 (AN3 ADC), reading the “signal strength” output line from the YD9088.

uint16_t read_tuner()
  {
   uint16_t temp = 0;
      for( uint8_t i = 0; i < 16; i++ ) {
        ADCSRA |= (1<<ADSC);
        while(!(ADCSRA & 0x10));
        temp += ADC;
       }
    temp = 5 * temp / 16;
    return temp;
  }

As you can see, readings averaged over 16 values, which increases a resolution on two additional bits.

Search algorithm has a “window” of 5 samples, to be able to recognize a peaks in the incoming data.

for( uint16_t i = 1023, prn_dot = 0; i > 500; i-- ) {
     OCR1A = i;
     delay(200); // T = R x C = 1 M x 0.1 uF = 0.1 seconds.
     lpf[i%5] = read_tuner();

  uint16_t accum = 0;
       for( uint8_t jp = 0; jp < 5; jp++ ) {
         accum += lpf[jp];
         }
        accum /= 5;
        if ((prn_dot++) % 32 == 0) Serial.print("\n");
          Serial.print(" *");
        if ( accum > maxim )
         {
          maxim = accum;
          trigg = 1;
         }
        if ( accum < maxim )
         { 
          if ( trigg )
           { 
            if ( accum > BARRIER )
             {
              store_entry( posit, (i + 1));
                Serial.print("\n");
                print_transl( posit );
                prn_dot = 0;
              posit++;
              if ( posit >=20 ) return;
              }
             }
          maxim = accum;
          trigg = 0;
     }
   }
 }

All I have to do, is to set a threshold for radio station, in order to be distinguished from the background noise and interference. Tunning data are stored in 40 EEPROM bytes, list includes 20 radio stations overall.

Sketch, Arduino UNO:  FM_Radio_YD9088

 24 Oct. 2012

 In first part of this project, I already write about voltage – frequency non-linearity and how I get the coefficients to improve accuracy of the voltage-to-frequency (VTF) conversion formula, based on power regression. Formula in second part was slightly different, I used second degree polynomial (parabola) approximation, which provides better match to measurements data. Even LibreOffice helps a  lot with calculation, but in overall I spend a lot of time to figure out the values of the coefficients. Plus filling the data table in spreadsheet also quite time consuming. Can Arduino do this work, finding a coefficients to second degree polynomial that “fits” to experimental data? From mathematical / science point of view, the task is interesting itself, and could be a great part of any science project, not necessarily a radio tuner. The method I choose – Least Squares.

 To my great surprise, arduino solved a problem in a splits of second!  I upgrade a sketch, which includes a calibration procedure now, calling via “c” from the serial monitor console input-output interface (Hi, DOS era).

void calibrate()
 {
 //Least squares parabola (2-nd degree polynomial) coefficient calculation
 float arr[5][5] ={{0},{0}}, err[5] = {0}, coeff[5] = {0};

 err[0] = 5;
 for(uint8_t i = 1; i < 5; i++)
  {
   err[i] = 0;
   for(uint8_t j = 0; j < 5; j++)
    {
     err[i] += pow(calibration[j], i);
    }
  }
 for(uint8_t i = 0; i < 3; i++)
  {
   for(uint8_t j = 0; j < 3; j++)
    {
     arr[i][j] = err[i+j];
    }
  }
 for(uint8_t i = 0; i < 3; i++)
  {
   arr[i][3] = 0;
   for(uint8_t j = 0; j < 5; j++)
    {
     if (i==0) arr[i][3] += radio_table[j];
     else arr[i][3] += radio_table[j] * pow(calibration[j], i);
    }
  }
 for(uint8_t k = 0; k < 3; k++)
  {
   for(uint8_t i = 0; i < 3; i++)
    {
     if ( i != k )
      {
      for(uint8_t j = (k + 1); j < 4; j++)
       {
        arr[i][j] -= ((arr[i][k] * arr[k][j]) / arr[k][k]);
       }
      }
    }
  }

union split_float {
 uint16_t tegri[2];
 float loatf;
 } sf;

for(uint8_t i = 0; i < 3; i++)
 {
  coeff[i] = ( arr[i][3] / arr[i][i]);
  sf.loatf = coeff[i];
  store_entry(( 20 + 2 * i), sf.tegri[0] );
  store_entry(( 21 + 2 * i), sf.tegri[1] );
 }
}

In order to perform a calibration procedure, you would need 5 Radio Stations (RSts) available in your area, and their frequencies. (Calibration doesn’t have much sense, if there are less than 5).  Edit this line in the code:

const float radio_table[5] = { 92.5, 97.7, 100.7, 105.7, 107.3 };  

For better results, choose two RSts from both side of the band ( 88 and 108 for North America ) and others in the middle. The signal (RSSI) has to be strong, you may need an external antenna properly oriented for maximum RF field. Run a “scan” first, and see what you get in the list. After this part completed, run “c”.

Arduino starts / a scan : optional /, than would ask you to tune a radio  on the first RSt in the radio_table.  When calibration was done at least ones, print a list with “l” command (interface isn’t blocking, you can send any commands, except digits). If Rst in the list, than use “u” or “d” to tune a radio. Than use “v”  -  ”n” for fine tunning, plus “x”, pay attention to RSSI, find the best position. If you start calibration first time, you can’t use a radio_list, because MHz data would be far off or completely wrong. Use another radio with digital scale or you have to listen content and compare it to RSts broadcasting schedule.

Enter “y” and send. Follow the instructions on the screen. At the end, arduino would store coefficients in the EEPROM, print them out for your review and say “Finished”. Print a list again, and check if the data in front of MHz is correct.

CLI:

  1. “x” – print current settings, fine tunning / calibration / debug;
  2. “Dr” – read settings from the previously stored radio list. (D is digits 0 <-> 19).
  3. “Dw” – write settings to the radio list. (D is digits 0 <-> 19).
  4. “u” – change radio to next “UP” in the list;
  5. “d” – change radio to next “DOWN” in the list;
  6. “s” – full “SCAN” of the air, and save to EEPROM;
  7. “l” – print out a stations list from the memory;
  8. “v” – push tuner “up” on 1 step, debug (“ux”);
  9. “n” – push tuner “down” on 1 step, debug (“nx”);
  10. “c” and “y” – calibration of the VCO, voltage settings – frequency conversion.

Listing of commands isn’t final, and  may have some variation.

Sketch:  FM_Radio_calibration.


DIY Arduino FM Radio Shield.

I’ve been visiting local convenience store (Dollarama, here in Montreal, Canada) and notice nice looking FM Radio, just for only $3. Why not to try to interface it to my lovely Arduino? Idea looks quite challenging, the same time what is the point in interfacing a DSP radio shield to arduino? I don’t need a radio, I want to have fun experimenting with it, so  lets go to the bare metal!

You, probably, could find the same or very similar radio all around the globe, with two buttons user interface, powered by two AAA or one CR2032 coin battery (like in my case), and low price. Hardware design based on IC TDA7088 (depending on the manufacturer, may be “clones” SC1088, SA1088, CD9088, D7088, or YD9088). My radio has YD9088  inside. Quick search on a Google, brings a  data sheet. I’d say, It’s not very informative, but at least it shows basic application circuit.

 HARDWARE.

 The most difficult part of this project, is soldering to surface mount radio components. In minimum configuration just two wires, “interfacing” two front-panel buttons. (Other two, for powering up the radio from arduino +3.3 V on-board voltage regulator instead of battery, should be much easier to attach). I solder wires to the caps, on the side, which connected  to the pins 15 and 16 of the IC. In this case, there is minimum impact on usability of the radio, as buttons were not touch. May be important for debugging. If your soldering skills are not as good as mine, you could solder to the traces, removing buttons. On the pictures below you would find two more wires, attached to pin 1 and to earphone’s jack-connector, but they are not in use in this project, and you could left them out.

If you look at the electrical drawings of the shield, you would notice  1 k pot. I build a first version using just two resistors divider, as it shown in “Reset” signal line. But it turns out, that IC is quite capricious for the voltage level it senses on the “Scan” input. On some occasions, it refused  to change a station, and in some it flipped to “reset”. Trim the pot, to get voltage at pin 15 about 3.1 – 3.2 V.  It would be easy to measure voltage with DMM, temporary changing “delay” in this section of the code:

 if (incomingByte == 's') { // SCAN - UP 
  digitalWrite( scan_pin, HIGH ); 
  delay(50); 
  digitalWrite( scan_pin, LOW ); 
  } 

to 10000 or even 20000.  You may need something to be plugged in the earphones jack, as radio is using wires like an antenna. Headphones, or USB speakers cable, works quite well. BTW, the default value 50 may not be enough to “push” a radio up with strong RF signal. Try to send a few “s” simultaneously, “ss” or “ssss”. Setting delay higher than 50 is not recommended, as “jump” may be to wide, so you likely to miss something interesting in broadcasting.

SOFTWARE.

int tunning = 0;

const uint8_t scan_pin = 14;
const uint8_t reset_pin = 15;

void setup() {
Serial.begin(115200); 
 ADCSRA = 0x87; // turn on adc, freq = 1/128 , 125 kHz. 
 ADMUX = 0x44;

pinMode ( scan_pin, OUTPUT );
pinMode ( reset_pin, OUTPUT );
digitalWrite( scan_pin, LOW );
digitalWrite(reset_pin, LOW );
}

void loop() {
char incomingByte;

 ADCSRA |= (1<<ADSC);
 while(!(ADCSRA & 0x10));

 uint16_t temp = 0;

 for( uint8_t i = 0; i < 16; i++ ) {
 ADCSRA |= (1<<ADSC);
 while(!(ADCSRA & 0x10));
 temp += ADC;
 } 
 tunning = (5 * temp) / 16;

 if (Serial.available() > 0) {
 incomingByte = Serial.read();
 if (incomingByte == 'x') { // 
 Serial.print(F("\t Tunning = "));
 Serial.print( tunning, DEC);
 Serial.print(F("\t Frequency = "));
 Serial.println(( 92.5 + pow((3340 - tunning), 0.85)/ 24.5), 1);
}

 if (incomingByte == 's') { // SCAN - UP
 digitalWrite( scan_pin, HIGH );
 delay(50);
 digitalWrite( scan_pin, LOW );
 }
 if (incomingByte == 'r') { // RESET - RESTART
 digitalWrite(reset_pin, HIGH );
 delay(50);
 digitalWrite(reset_pin, LOW );
 }
 } 
}

As you can see, arduino is not doing much in the scetch, only constantly monitoring “tunning” line and checking serial if there is any command waiting to be proceed. CLI includes 3 commands, so far, “x”, “s” and “r”. Sending last two, is equivalent to pressing “switch to next” and “reset” buttons. First one, would print out current “tunning” settings of the radio in millivolts and in MHz. Very likely, you will have to change a coefficients in frequency calculation formula (power regression equation):

92.5 + pow((3340 – tunning), 0.85)/ 24.5

In order to find right digits, you would need to run a full “scan” of all available radio stations in your area. Write down a name of radio station, and corresponding voltage you are getting constantly sending “x”. Than, replace the names with appropriate frequency numbers to any radio station in your list. Look up for such data over Internet at  the station’s web page.  If you do have an another radio with digital scale, than tune this radio on the same radio station as your shield currently preset, and write down a frequency – voltage table.  Filling this data in LibreOffice spreadsheet and plotting a chart would reveal some-kind of non-linear curve:  On the picture you can see:  blue line – real data for my DIY FM Radio shield, orange line – linear approximation, and yellow line is a “power – polynom” approximation. Linear gives big error in the middle, around 1 MHz “off”. Polynomial is much better, and stay close to real data with accuracy +- 0.2 MHz, which is quite good. All you have to do, is “tweak” a coefficients to find the best approximation. Start with lowest frequency you get in your list ( 92.5 and 3340 in an example), than vary 24.5 to get close for highest radio station data. Very likely, 0.85 will be the same, I just have no clue why is it so. To do calculation right, a lot of information is missing, I even don’t know the varicap diode they use to tune a radio!


Quasi real-time oscilloscope: REMIX

 Updated on 21 Sept. 2012, version 4.

 Updated on 15 Oct. 2012, version 5.

Recently I was reviewing one of my oldest project, and decided to “refresh” previous design by taking full advantage of the new arduino Leonardo board.  Based on AtMega32U4, which include PGA (programmable gain amplifier), oscilloscope’s  analog front end doesn’t require external OPA this time, end could be build in 1-2 hours on prototype board, using 5 resistors, 5 capacitors and one IC. Short specification:

  • Four channels.
  • Switchable gain settings 1x, 10x, 40x, 200x.

Hardware.

 As you can see on drawings above, inputs are AC coupled with caps, and biased with 1.25V generated by LM317. Connector for other two inputs not installed yet.

Software.

Project keeps almost same  structure of commands (CLI – command line interface) as its ancestor, with only two new for channel and gain selection. Read comments, it explains how to use them. One more things, I removed “r” – re-print option from the list of available commands.

Have fun!.

Link to arduino Leonardo sketch:  Oscilloscope_Leonardo.

********************************************* Version 4*********************************************

 Well, even posted above sketch has low complexity, and its good for beginning, nevertheless it’s quite limited as measuring device. The most important feature for oscilloscope, except V/div,  is T/div, or timing, that has to be as much precise as possible.   This is why “standard” timing options based on TIMER1 were add to next version of software. There are 9 time settings Time/div (10 samples):

50ms, 20ms, 10ms, 5ms, 2ms, 1ms, 500us, 200us, 100usec

corresponding to

200Hz, 500Hz, 1kHz, 2kHz, 5kHz, 10kHz, 20kHz, 50kHz, 100kHz

sampling rate. You can choose any of of this in the same manner, entering a digit 1-9 and letter d (display). Zero would skip capture, and just do whatever next letter request. Basically, 0 should be used to print channels data from memory. Combination: 9d0i – capture at 100 kHz rate, print chart and info-table,
4c2g7d – select 4-th channel, set gain to 10, select 20 kHz sampling rate and display.

I also add multichannel sampling capability. Commands 2m, 3m or 4m would configure oscilloscope for 2, 3 or 4 channels simultaneously capturing input waveform.   As arduino has only 1 ADC, switching in multichannel mode would reduce sampling rate proportionally to number of channels, and this changes would be reflected in right top corner of the display. What more, arduino would automatically change vertical resolution per channel, to fit all 2 – 4 charts on one screen!

Known caveats:

  • Sampling rate 9, or 100 usec per division (10 usec per sample) could not be selected in multichannel mode, should be in use for single channel only (1m, 1c – 2c – 3c – 4c).
  • There is a “shift” in channel number, signal  presented at input 1 would show up on screen 2, and so on. This happens on time settings 7 (occasionally), 8 and 9 in 4x multichannel mode due delay in MUX registers switching. Shouldn’t be an issue for 2x channel mode, or when you have an “overview” of the signals shape in single channel mode (1m) before switching to 4x, so you would know what to expect at each input port.

Link to arduino Leonardo sketch:  Oscilloscope_LeonardoV4

********************************************* Version 5*********************************************

 New updated version. I was thinking how to improve simplest ever oscilloscope, and have made some structural changes in the code, mostly related to sampling in multichannel mode.

First of all, instead of “delay” 2 microseconds, that was used to give a multiplexer (and PGA amplifier) time to “settle” on new channel, I decided not to waste a time (that may be priceless in real-time application), rather start new conversion, than track samples based on the “history” of MUX settings, and store new sample in corresponding two dimensional array box. Now MUX and PGA would have more time,  and consequently I could reduce ADC pre-selector’s clock to get better readings. It solved all the problems with wrong association port number and picture on the screen.

Secondly,  as you, probably, already notice I’ve been working on another project recently, where phase is a PRIME factor of the whole idea of the design. Phase noise is a jitter, and it degrades  spatial resolution and the sensitivity of the sound localization.  Jitter always would be presented in the incoming signal – sampled waveform due “not synchronous” way of sampling, as “start new conversion” events were generated in “manual” mode. As microprocessor spend different amount of time to get inside of the ISR (interrupt subroutine) depends on where it was interrupted, time frame of the events, basically, was not defined. In order to get rid off the phase noise, I changed ADC settings to be triggered via TIMER 1. There is a code:

ADCSRA = ((1<< ADEN)| // 1 = ADC Enable
(0<< ADSC)| // 1 = ADC Start Conversion
(1 <<ADATE) | / / 1 = ADC Auto Trigger Enable
*****
ADCSRB = ((1<<ADHSM)| // High Speed mode select
(0<< MUX5)| // 0 (10100) ADC1<->ADC4 Gain = 1x.
(0<<ADTS3)|
(1 <<ADTS2) | / / Sets Auto Trigger source Timer/Counter1 Compare Match B
(0 <<ADTS1) |
(1 <<ADTS0) );

For some unknown for me reason, Atmel designed TIMER 1 channel B to be an auto trigger source of the ADC, the same time to run TIMER 1 itself in CTC mode, channel A must be set. I simply “bind” two channels A and B in “parallel”, so both of them rise interrupt flag at the same moment, only A re-starts a TIMER 1, and B generates “start new conversion” event and calling ISR for “maintenance” – take a new sample waiting in the line and switch a MUX to another channel of the oscilloscope.

uint8_t take_it( int fast )
{
   ADCSRA &= 0xF8;
   if ( multChan -1 )
   {
    switch( fast ) { 
       case 7: // 20 kHz / 
         ADCSRA |= 0×03; 
         break; 
       case 8: // 50 kHz / 
         ADCSRA |= 0×02; 
         break; 
       case 9: // 100 kHz / 
         Serial.print(F(“\n\t *** NOT SUPPORTED ***”));
         return 0;
        default:
        ADCSRA |= 0×04;
       }
     }
    else
    { 
     switch( fast ) { 
        case 6: // 10 kHz / 
          ADCSRA |= 0×06; 
          break; 
        case 7: // 20 kHz / 
          ADCSRA |= 0×05; 
          break; 
        case 8: // 50 kHz / 
          ADCSRA |= 0×04; 
          break; 
        case 9: // 100 kHz / 
          ADCSRA |= 0×03; 
          break; 
        default:
          ADCSRA |= 0×07;
        }
      }
OCR1A   = smplTime[fast -1];
OCR1B   = smplTime[fast -1];
TCNT1   = 0;
TIFR1    |= (1<<OCF1B); 
TIMSK1 |= (1<<OCIE1B);
   flagSamp = 0; 
   while ( !flagSamp );
   for ( uint8_t indx, y = 0; y < multChan; y++){
      if ( multChan -1) indx = y;
      else indx = chanNumb;
   for ( int i = 0; i < INBUF; i++){
      if ( x[indx][i] & 0×0200) x[indx][i] += 0xFE00; // Convert to negative 16-bit word (2′s comp)
      else x[indx][i] += 0×200;
      }
     }
  return 1;
}
ISR(TIMER1_COMPB_vect)
{
   static uint8_t n_sampl = 0;
   static uint8_t history = 0;
   x[history][n_sampl] = ADC;
    history = chanNumb; 
   if ( multChan -1 )
   { 
    chanNumb++;
    if ( chanNumb >= multChan )
      {
       chanNumb = 0;
       n_sampl++;
      }   
     ADMUX &= 0xFC;
     ADMUX |= chanNumb; 
    }
   else
   {
     n_sampl++;
    }
   if ( n_sampl >= INBUF )
    {
      flagSamp = 1;
      n_sampl = 0;
TIMSK1 &= ~(1<<OCIE1B);
   }
}

Oscilloscope_Leonardo_V5.

The only issue that not solved yet, is a sampling in multichannel mode in “9″ T/div. Probably, Atmel just was not design to do such things…