dig:lab_3
no way to compare when less than two revisions
Differences
This shows you the differences between two versions of the page.
Previous revisionNext revision | |||
— | dig:lab_3 [2013/03/05 15:00] – [AVR32 board pin headers] tobi | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ===== Lab 3 ====== | ||
+ | |||
+ | In this exercise, let's build a microphone preamp on the protoboard, acquire the audio signals on the AVR32 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. | ||
+ | |||
+ | To achieve this, we need to interface to and control a servo motor and build a microphone preamplifier. | ||
+ | |||
+ | ==== Steps for this exercise ==== | ||
+ | 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: | ||
+ | |||
+ | - Build the [[: | ||
+ | - Sketch an outline (flow diagram) of your application, | ||
+ | - Start your AVR32 project (see below) by cloning the usb-rgb-ldr project. | ||
+ | - Adapt the PWM code to control the [[: | ||
+ | - Adapt the ADC code to read the microphone preamp output. | ||
+ | - Write the DSP (digital signal processing) code to process the ADC samples to control the servo position. | ||
+ | - Adapt the USB code on the device and host (rainbow.py) to debug your code. | ||
+ | - Document your code with comments. | ||
+ | - Demonstrate your working system to Tobi and send your project by zip (or .7z) archive attachment email to [[tobi@ini.phys.ethz.ch? | ||
+ | |||
+ | |||
+ | ===== Background information ===== | ||
+ | * [[:dig:uc]] for microcontroller software tools and example firmware setup | ||
+ | * [[: | ||
+ | * [[: | ||
+ | * [[: | ||
+ | * [[: | ||
+ | |||
+ | |||
+ | ===== Starting AVR32 Project ===== | ||
+ | |||
+ | Let's copy the // | ||
+ | |||
+ | Let's modify this project so that we produce a suitable servo output signal in response to the sampled sounds from the ADC. | ||
+ | |||
+ | ==== AVR32 board pin headers ==== | ||
+ | The header pins we plug into the solderless breadboard are here: | ||
+ | |||
+ | {{dig: | ||
+ | ===== PWM - Pulse Width Modulation ===== | ||
+ | - Decide on a new PWM output pin for your servo output pulses. Looking at the {{dig: | ||
+ | |||
+ | {{dig: | ||
+ | |||
+ | Let's use PWM[6]=PA19=pin 31 for the servo PWM output. | ||
+ | |||
+ | We enable this " | ||
+ | <code c> | ||
+ | #define SERVO_PIN AVR32_PWM_6_0_PIN | ||
+ | #define SERVO_PWM_FUNCTION AVR32_PWM_6_0_FUNCTION | ||
+ | gpio_enable_module_pin(SERVO_PIN, | ||
+ | </ | ||
+ | |||
+ | - To //main.c//, add code to use PWM channel 6 cloned from one of the RGB LED outputs: | ||
+ | <code c> | ||
+ | #define B_PWM_PIN AVR32_PWM_4_0_PIN | ||
+ | #define B_PWM_FUNCTION AVR32_PWM_4_0_FUNCTION | ||
+ | #define B_PWM_CHANNEL_ID 4 | ||
+ | </ | ||
+ | - Increase the number of channels to 4 from what is there now: | ||
+ | < | ||
+ | static avr32_pwm_channel_t pwm_channel[3]; | ||
+ | static unsigned int channel_id[3]; | ||
+ | </ | ||
+ | - Modify the // | ||
+ | <code c> | ||
+ | gpio_enable_module_pin(B_PWM_PIN, | ||
+ | </ | ||
+ | In pwm_init(), the PWM controller is configured for certain choices of prescaler and divider. These choices determine the clocks available to individual PWM channels: | ||
+ | <code c> | ||
+ | // PWM controller configuration. | ||
+ | pwm_opt.diva = AVR32_PWM_DIVA_CLK_OFF; | ||
+ | pwm_opt.divb = AVR32_PWM_DIVB_CLK_OFF; | ||
+ | pwm_opt.prea = AVR32_PWM_PREA_MCK; | ||
+ | pwm_opt.preb = AVR32_PWM_PREB_MCK; | ||
+ | </ | ||
+ | You also need to initialize the servo PWM channel to use one of these available clock sources, to end up with a period of about 100Hz. An existing LED channel looks like this: | ||
+ | <code c> | ||
+ | pwm_channel[c].CMR.calg = PWM_MODE_LEFT_ALIGNED; | ||
+ | pwm_channel[c].CMR.cpol = PWM_POLARITY_HIGH; | ||
+ | pwm_channel[c].CMR.cpd = PWM_UPDATE_DUTY; | ||
+ | pwm_channel[c].CMR.cpre = AVR32_PWM_CPRE_MCK_DIV_2; | ||
+ | pwm_channel[c].cdty = 0; // Channel duty cycle, should be < CPRD. | ||
+ | pwm_channel[c].cprd = (256 << TTT); // Channel period. | ||
+ | pwm_channel[c].cupd = 0; // Channel update is not used here. | ||
+ | </ | ||
+ | The prescaler is what you need to change; it is set to 2 above and you need to increase it to reduce the servo pulse frequency. The 20 bit //cdty// field sets the duty cycle and the 20 bit //cprd// field sets the period, in units of the PWM channel clock period, which is set by the clock you choose with the //cpre// field. | ||
+ | - Write a function // | ||
+ | <code c> | ||
+ | /** Argument, pulse width in us | ||
+ | * | ||
+ | */ | ||
+ | void setServoPWUs(U16 us) { | ||
+ | pwm_channel[cServo].cdty = .... // you need to fill this in | ||
+ | pwm_channel_init(channel_id[cServo], | ||
+ | } | ||
+ | </ | ||
+ | To see what this all means, look at the following block diagram, from the datasheet. The PWM channels all share a common // | ||
+ | |||
+ | {{: | ||
+ | |||
+ | {{: | ||
+ | |||
+ | 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. | ||
+ | |||
+ | To test the servo code, modify firmware so that the LED PWM value sent from the host now sets the servo position as well. See the function // | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ===== Sampling audio and signal processing ===== | ||
+ | |||
+ | We've already built the microphone preamp circuit on the protoboard, using a JEFT microphone ([[http:// | ||
+ | |||
+ | 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. | ||
+ | |||
+ | The pseudo code for the signal processing should go as follows: | ||
+ | |||
+ | - Get an ADC sample | ||
+ | - Update the fading average sample value. We need to subtract this from the sample to get the deviation from the mean. | ||
+ | - Compute the square of the difference of the sample from the fading average. | ||
+ | - Update the fading average mean square value. | ||
+ | - Use this value to set the servo position. | ||
+ | |||
+ | Remember that you only have fixed point hardware. The ADC samples are 10 bits expressed as the 10 lsbs of a U16 value. You can add up a lot of 10 bit values in a 32 bit value. | ||
+ | |||
+ | ==== Debugging by GPIO toggling ==== | ||
+ | |||
+ | You may notice certain calls such as the bit banger: | ||
+ | <code c> | ||
+ | gpio_local_tgl_gpio_pin(AVR32_PIN_PA11); | ||
+ | </ | ||
+ | This call toggles PA11. You can monitor PA11 on an oscilloscope to see that the program gets to this line of code and how often/fast it does this. | ||
+ | |||
+ | ====Converting the microphone input ==== | ||
+ | Referring again to the {{dig: | ||
+ | |||
+ | Let's modify the usb-rgb-ldr program to use this other ADC channel instead of the one connected to the LDR. (We won't worry about putting the ADC sampling in a timer interrupt routine for now.) | ||
+ | Just before // | ||
+ | |||
+ | <code c> | ||
+ | #define ADC_CHANNEL | ||
+ | #define ADC_PIN | ||
+ | #define ADC_FUNCTION | ||
+ | </ | ||
+ | |||
+ | ===== Signal processing ===== | ||
+ | You can put your signal processing at the start of // | ||
+ | |||
+ | <code c> | ||
+ | // Load the IN endpoint with the contents of the RAM buffer | ||
+ | if (Is_usb_in_ready(EP_TEMP_IN)) { | ||
+ | gpio_local_tgl_gpio_pin(AVR32_PIN_PA11); | ||
+ | |||
+ | // read ADC and store to buffer...: | ||
+ | U16 adcval = get_adc_value(); | ||
+ | in_buf[0] = 0xDE; | ||
+ | in_buf[1] = 0xAD; | ||
+ | in_buf[2] = 0xFF & (adcval >> 8); | ||
+ | in_buf[3] = 0xFF & (adcval >> 0); | ||
+ | in_data_length = 4; | ||
+ | |||
+ | Usb_reset_endpoint_fifo_access(EP_TEMP_IN); | ||
+ | usb_write_ep_txpacket(EP_TEMP_IN, | ||
+ | in_data_length = 0; | ||
+ | Usb_ack_in_ready_send(EP_TEMP_IN); | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | |||
+ | You should probably do your signal processing using signed and unsigned 32 bit ints (//int// and //unsigned int//, or //S32// and //U32//). In // | ||
+ | <code c> | ||
+ | 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 | ||
+ | typedef unsigned long int | ||
+ | typedef signed long long int S64; //!< 64-bit signed integer. | ||
+ | typedef unsigned long long int U64; //!< 64-bit unsigned integer. | ||
+ | typedef float | ||
+ | typedef double | ||
+ | </ | ||
+ | |||
+ | Some hints for signal processing using fixed point: | ||
+ | * To multiply by a fraction, multiply by the numerator and then divide by the denominator. | ||
+ | * Watch out for overflow, since these will wrap around. | ||
+ | * Divide by zero will not throw any kind of exception. | ||
+ | * Fast divides or multiplies by powers of 2 are done with shift operators << or >>. | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ==== Advanced Signal Processing with the DSP library ==== | ||
+ | |||
+ | If you are ambitious, you can try using the {{dig: | ||
+ | |||
+ | {{dig: | ||
+ | |||
+ | And don't forget to add the #include statement at the start of your main.c: | ||
+ | <code c> | ||
+ | #include " | ||
+ | </ | ||
+ | |||
+ | ==== Using Timer interrupts to Trigger the ADC ==== | ||
+ | |||
+ | You could also try to run the ADC in a timer interrupt to sample at a regular rate. See [[http:// | ||
+ | |||
+ | ===== Host side debugging with USB ===== | ||
+ | |||
+ | Let's use the host side to debug the device. | ||
+ | |||
+ | **To better work in python,** you may want to install in Eclipse the plugin to handle python projects. See this link: http:// | ||
+ | |||
+ | On the host side, the python method **// | ||
+ | |||
+ | <code python> | ||
+ | usbiocnt = 0L | ||
+ | def usbio(dh, r, g, b): | ||
+ | global usbiocnt | ||
+ | usbiocnt += 1 | ||
+ | | ||
+ | dout = array.array(' | ||
+ | |||
+ | dout[0] = 0xFF & 0x00 | ||
+ | dout[1] = 0xFF & (r) | ||
+ | dout[2] = 0xFF & (g) | ||
+ | dout[3] = 0xFF & (b) | ||
+ | |||
+ | dh.bulkWrite(EP_OUT, | ||
+ | |||
+ | #if usbiocnt % PWMperADC == 0: | ||
+ | if 1: | ||
+ | |||
+ | din = dh.bulkRead(EP_IN, | ||
+ | l = len(din) | ||
+ | if l != 4: | ||
+ | print " | ||
+ | else: | ||
+ | if usbiocnt % PWMperADC == 0: | ||
+ | adc( (din[2] << 8) + din[3] ) | ||
+ | |||
+ | </ | ||
+ | You can modify this code to pass to the // | ||
+ | <code python> | ||
+ | vmax = 1 | ||
+ | vmin = 2**16 | ||
+ | def adc(v): | ||
+ | global vmax, vmin | ||
+ | if v > vmax: vmax = v | ||
+ | if v < vmin: vmin = v | ||
+ | if vmax <= vmin: vmin -= 1 | ||
+ | |||
+ | t = time.time() - tstart | ||
+ | hz = usbiocnt / t | ||
+ | |||
+ | o = '% 4d Hz ' % hz | ||
+ | o += '#' | ||
+ | print o | ||
+ | </ | ||
+ | The print methods may be unfamiliar to you. The line "o = '% 4d Hz ' % hz" prints the value of variable hz using 4d format, i.e. 4 place decimal. You can add a field after this on the next line to print your sound volume value. | ||
+ | |||
+ | Remember that [[http:// | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ===== Desired Behavior ===== | ||
+ | |||
+ | You demo should end up responding to transient sound volume increases by turning the servo. | ||
+ | |||
+ | ---- | ||
+ | |||
+ | 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 AVR32. 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, [[micro-servo|see how to make the mic-servo application work]]. | ||
+ | |||
+ | |||
+ | |||
+ | ===== Documentation ===== | ||
+ | |||
+ | * See [[: | ||
+ | * {{: | ||
+ | * {{: | ||
+ | |||
+ | ~~DISCUSSION~~ | ||
dig/lab_3.txt · Last modified: 2024/02/29 07:28 by 127.0.0.1