In this exercise, let's build a microphone preamp on the protoboard, acquire the audio signals on the ATmega328P using the ADC, process the auditory signal to measure the RMS power (sound volume), and then use the average sound volume over the past few hundred ms of time to set the position of a servo motor. In lab_4 you will turn your prototype into a PCB schematic.
To achieve this, we need to interface to and control a servo motor and build a microphone preamplifier. Let's use the protoboard for both of these.
If you finally have to give up for lack of time or success, a solution will be revealed in two weeks. Follow the following general steps to complete this exercise:
The easiest way to start is to open from the File menu a basic example and use it as a template, like the 01-Basic⇒Blink.
Let's modify this sketch so that we produce a suitable servo output signal in response to the sampled sounds from the ADC.
Here you have a quick example of how to use Servo library:
#include<Servo.h> int led = 13; char i=0; //You can instantiate a new variable of Servo type, that correspond to an object with several useful methods. Servo myservo; // the setup routine runs once when you press reset: void setup() { // initialize the digital pin as an output. pinMode(led, INPUT); // initialize servo to be on pin D9, min high time of 1ms and max 2ms. myservo.attach(9,1000,2000); // start with a PWM signal of 20ms period (default value) and 1ms high time (0 degrees) myservo.write(0); } // the loop routine runs over and over again forever: void loop() { delay(10); // wait for a 10 milliseconds myservo.write(i%180); // update PWM for servo controller with a new duty cycle (high time) }
To see what this all means, look at the following block diagram, from the datasheet. There are three timers that can be used for generating up to 6 PWM signals, but for Servo control only Timer1 can be used because of the requirements of the PWM period signal of a servo. Each timer has two waveform generators (A and B). Depending on timer value and OCR1A or OCR1B register values the PWM signtal is generated. The Timer1 overflows the waveform generator reset signal output, and when Timer1 reach the value stored in OCR1x register, the waveform generator invert the state of the output signal.
Timer1 clock source can come from outside (External pin T1) or it can be a prescaled clock from system clock circuitry of the microcontroller. Timer1 has several operating modes that allow different frequency resolutions on PWM signals. The counter can be used in one direction (once the TOP count is reached it changes to BOTTOM and starts again), or in two directions (from BOTTOM to TOP counting up and then from TOP to BOTTOM counting down, obtaining double resolution).
You can try it out with a servo motor, using the USB VBUS +5V to power the servo and a long pin header to connect to the protoboard and the servo, following the guide above according to your servo type. Use a 10000uF electrolytic capacitor and a blocking 1N4007 diode to bypass the VBUS to ground on the protoboard or you will get a huge amount of motor noise appearing in VBUS! You will end up with about 4V to power the servo which is on the low side but it should still work.
We've already built the microphone preamp circuit on the protoboard, using a JEFT microphone (datasheet), resistors, capacitors, and an LM324N quad opamp.
Now let's write a main loop that samples the microphone input, computes the average mean square sound level, low pass filters it, and sets the servo according to this sound volume. Ideally you would sample at a regular rate set by a timer interrupt. Remember that Timer1 is already in use by PWM generator and that Timer0 is used by some internal Arduino functions (like delay one), so let's use remaining timer to manage a correct sample rate for the ADC, Timer2. In order to manage easily Timer2, you can download MsTimer2 library and install it on the Arduino/libraries folder unziping this zip file there. Every time you install a new library in the libraries folder you must restart Arduino software. Then you can use it in your code like in this example:
#include <MsTimer2.h> // the setup routine runs once when you press reset: void setup() { // initialize timer2 overflow and a function to be call during ISR. MsTimer2::set(1000,t2_ovf); MsTimer2::start(); } // the loop routine runs over and over again forever: void loop() { //wait for ADC value and process it using DSP chain. } void t2_ovf() { //do something like set a FLAG for the loop for reading the ADC }
The pseudo code for the signal processing should go as follows:
Remember that you only have fixed point hardware. The ADC samples are 10 bits expressed as the 10 lsbs of a int value. You can add several of 10 bit values in a 16 bit value, or you can move to a long variable
You use a line of C-code to invert the state of a pin for debuggin purpose:
digitalWrite(11,!digitalRead(11));
This call toggles D11. You can monitor D11 on an oscilloscope to see that the program gets to this line of code and how often/fast it does this.
We recommend two possible procedures for using the ADC:
1. analogRead(n): this function will return 10-bit value took from the ADC once it finished the conversion. Nothing can be done by the CPU during conversion time.
adcval = analogRead(0); dsp_process (adcval);
2. using ISR: you can start the ADC and execute other code while the ADC works. You need to program an ISR in order to continue the work with the ADC converted value. This ISR will be executed everytime the ADC finish.
void adc_start() { ADCSRA = B11001111; } ISR(ADC_vect) { unsigned char adc_l = ADCL; unsigned char adc_h = ADCH; adcval = adc_h*256+adc_l; dsp_process(adcval); }
You can put your signal processing code in the function dsp_process(). You can then use the Serial interface to send back your computed sound volume, to check whether your computations make sense.
You should probably do your signal processing using signed and unsigned 32 bit ints (int and unsigned int, or S32 and U32). In complier.h you can find these handy definitions:
typedef signed char S8 ; //!< 8-bit signed integer. typedef unsigned char U8 ; //!< 8-bit unsigned integer. typedef signed short int S16; //!< 16-bit signed integer. typedef unsigned short int U16; //!< 16-bit unsigned integer. typedef signed long int S32; //!< 32-bit signed integer. typedef unsigned long int U32; //!< 32-bit unsigned integer. typedef signed long long int S64; //!< 64-bit signed integer. typedef unsigned long long int U64; //!< 64-bit unsigned integer. typedef float F32; //!< 32-bit floating-point number. typedef double F64; //!< 64-bit floating-point number.
Some hints for signal processing using fixed point:
You demo should end up responding to transient sound volume increases by turning the servo. You can mount an arm and hand on the servo using one of the servo arms and some hot glue to build a “BE QUIET” hand, for instance.
Congratulations if you get all this to work! It seems as though there is a large amount to learn to use a rather simple processor like the ATmega328P. But if you work with a device like this for a while you will get familiar with it and it will become quite easy to write new functionality.
If you finally have to give up for lack of time or success, see how to make the mic-servo application work.