Created
October 22, 2011 20:20
-
-
Save brennon/1306450 to your computer and use it in GitHub Desktop.
ADC -> PWM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdint.h> | |
#include <avr/interrupt.h> | |
#include <avr/io.h> | |
#include <avr/pgmspace.h> | |
// Uncomment to enable debugging messages and values | |
#define DEBUG | |
uint16_t adc_readings[8]; | |
uint8_t digital_output = 11; // (PCINT22/OC0A/AIN0)PD6, Arduino Digital Pin 11 | |
uint16_t sample_0; | |
uint16_t sample_1; | |
uint16_t sinewave_length = 501; | |
uint32_t sinewave_data[] PROGMEM = { 0x80, 0x81, 0x83, 0x84, 0x86, 0x88, 0x89, 0x8B, 0x8C, 0x8E, 0x8F, 0x91, 0x93, 0x94, 0x96, 0x97, 0x99, 0x9B, 0x9C, 0x9E, 0x9F, 0xA1, 0xA2, 0xA4, 0xA5, 0xA7, 0xA8, 0xAA, 0xAB, 0xAD, 0xAE, 0xB0, 0xB1, 0xB3, 0xB4, 0xB6, 0xB7, 0xB9, 0xBA, 0xBC, 0xBD, 0xBE, 0xC0, 0xC1, 0xC2, 0xC4, 0xC5, 0xC7, 0xC8, 0xC9, 0xCA, 0xCC, 0xCD, 0xCE, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEE, 0xEF, 0xF0, 0xF1, 0xF1, 0xF2, 0xF3, 0xF4, 0xF4, 0xF5, 0xF5, 0xF6, 0xF7, 0xF7, 0xF8, 0xF8, 0xF9, 0xF9, 0xFA, 0xFA, 0xFB, 0xFB, 0xFB, 0xFC, 0xFC, 0xFC, 0xFD, 0xFD, 0xFD, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xFD, 0xFD, 0xFC, 0xFC, 0xFC, 0xFB, 0xFB, 0xFB, 0xFA, 0xFA, 0xF9, 0xF9, 0xF8, 0xF8, 0xF7, 0xF7, 0xF6, 0xF5, 0xF5, 0xF4, 0xF4, 0xF3, 0xF2, 0xF1, 0xF1, 0xF0, 0xEF, 0xEE, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE9, 0xE8, 0xE7, 0xE6, 0xE5, 0xE4, 0xE3, 0xE2, 0xE1, 0xE0, 0xDF, 0xDE, 0xDC, 0xDB, 0xDA, 0xD9, 0xD8, 0xD7, 0xD6, 0xD4, 0xD3, 0xD2, 0xD1, 0xD0, 0xCE, 0xCD, 0xCC, 0xCA, 0xC9, 0xC8, 0xC7, 0xC5, 0xC4, 0xC2, 0xC1, 0xC0, 0xBE, 0xBD, 0xBC, 0xBA, 0xB9, 0xB7, 0xB6, 0xB4, 0xB3, 0xB1, 0xB0, 0xAE, 0xAD, 0xAB, 0xAA, 0xA8, 0xA7, 0xA5, 0xA4, 0xA2, 0xA1, 0x9F, 0x9E, 0x9C, 0x9B, 0x99, 0x97, 0x96, 0x94, 0x93, 0x91, 0x8F, 0x8E, 0x8C, 0x8B, 0x89, 0x88, 0x86, 0x84, 0x83, 0x81, 0x80, 0x7E, 0x7C, 0x7B, 0x79, 0x77, 0x76, 0x74, 0x73, 0x71, 0x70, 0x6E, 0x6C, 0x6B, 0x69, 0x68, 0x66, 0x64, 0x63, 0x61, 0x60, 0x5E, 0x5D, 0x5B, 0x5A, 0x58, 0x57, 0x55, 0x54, 0x52, 0x51, 0x4F, 0x4E, 0x4C, 0x4B, 0x49, 0x48, 0x46, 0x45, 0x43, 0x42, 0x41, 0x3F, 0x3E, 0x3D, 0x3B, 0x3A, 0x38, 0x37, 0x36, 0x35, 0x33, 0x32, 0x31, 0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x21, 0x20, 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, 0x17, 0x16, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x11, 0x10, 0x0F, 0x0E, 0x0E, 0x0D, 0x0C, 0x0B, 0x0B, 0x0A, 0x0A, 0x09, 0x08, 0x08, 0x07, 0x07, 0x06, 0x06, 0x05, 0x05, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, 0x07, 0x08, 0x08, 0x09, 0x0A, 0x0A, 0x0B, 0x0B, 0x0C, 0x0D, 0x0E, 0x0E, 0x0F, 0x10, 0x11, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x31, 0x32, 0x33, 0x35, 0x36, 0x37, 0x38, 0x3A, 0x3B, 0x3D, 0x3E, 0x3F, 0x41, 0x42, 0x43, 0x45, 0x46, 0x48, 0x49, 0x4B, 0x4C, 0x4E, 0x4F, 0x51, 0x52, 0x54, 0x55, 0x57, 0x58, 0x5A, 0x5B, 0x5D, 0x5E, 0x60, 0x61, 0x63, 0x64, 0x66, 0x68, 0x69, 0x6B, 0x6C, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x79, 0x7B, 0x7C, 0x7E, 0x7F }; | |
// Interrupt service routine called to grab ADC readings | |
ISR(TIMER0_COMPA_vect) | |
{ | |
// Start ADC conversion | |
ADCSRA |= (1 << ADSC); | |
} | |
// Interrupt service routine called to generate PWM compare values | |
ISR(TIMER1_COMPA_vect) | |
{ | |
uint32_t sensor_value_0 = adc_readings[0]; | |
uint32_t sample_value_0 = pgm_read_byte(&sinewave_data[sample_0]); | |
uint32_t sine_0 = (sample_value_0 * sensor_value_0) >> 10; | |
uint32_t sensor_value_1 = 1024; | |
uint32_t sample_value_1 = pgm_read_byte(&sinewave_data[sample_1]); | |
uint32_t sine_1 = (sample_value_1 * sensor_value_1) >> 10; | |
OCR2A = (sine_0 + sine_1) >> 1; | |
sample_0 += 2; | |
sample_1 += 3; | |
if (sample_0 >= sinewave_length) { | |
sample_0 = 0; | |
} | |
if (sample_1 >= sinewave_length) { | |
sample_1 = 0; | |
} | |
} // END - Interrupt service routine called to generate PWM compare values | |
// ADC Conversion Complete interrupt service routine | |
ISR(ADC_vect) | |
{ | |
static uint8_t firstTime = 1; | |
static uint8_t low_val, high_val; | |
uint8_t current_pin; | |
low_val = ADCL; | |
high_val = ADCH; | |
if (firstTime == 1) { | |
firstTime = 0; | |
} else { | |
// Get current reading from MUX | |
current_pin = (ADMUX & 0xF); | |
// Store reading in readings array | |
adc_readings[current_pin] = (high_val << 8) | low_val; | |
// Clear MUX bits | |
ADMUX &= 0xF0; | |
// Unless we're at the top pin, increment the MUX unit, otherwise leave it cleared at 0 | |
if (current_pin < 7) { | |
ADMUX |= (current_pin + 1); | |
} | |
#ifdef DEBUG | |
// Toggle LED pin | |
PORTD ^= (1 << 7); | |
#endif | |
} | |
} // END - ADC Conversion Complete interrupt service routine | |
void setup() | |
{ | |
// Setup digital_output for PWM output | |
DDRD |= _BV(6); | |
// Clear ADC reading array | |
memset(adc_readings, 0, sizeof(adc_readings)); | |
// Disable global interrupts | |
cli(); | |
#ifdef DEBUG | |
// Setup serial port for printing debug messages | |
Serial.begin(9600); | |
// Setup LED pin (PD7, Arduino Digital Pin 7) for testing | |
DDRD |= _BV(7); | |
// Set LED pin high | |
PORTD |= _BV(7); | |
#endif | |
/*************************************************************************** | |
** Timer/Counter0 Configuration (used for triggering ADC) ** | |
***************************************************************************/ | |
// Setup Timer/Counter0 Control Registers (TCCR0) with the following options: | |
// Timer/Counter Mode of Operation | CTC | |
// TOP | OCRA | |
// Update of OCR1x at | Immediate | |
// TOV1 Flag Set on | MAX | |
TCCR0A = (TCCR0A & ~_BV(WGM00)) | _BV(WGM01); | |
TCCR0B &= ~_BV(WGM02); | |
// Setup clock prescaler for TCCR0 as clk_IO/1024 | |
TCCR0B = (TCCR0B & ~_BV(CS01)) | _BV(CS02) | _BV(CS00); | |
// Set compare value (OCR0A) to value equivalent to: | |
// C = Compare value | |
// F_CPU = Core clock rate | |
// F_S = Desired ADC sampling rate (in Hertz) | |
// PS = Prescaler value | |
// N = Number of ADC channels | |
// C = F_CPU / (F_S * N * PS) | |
OCR0A = F_CPU / (50 * 8 * 1024); // 50Hz x 8 channels x 1024 prescaler | |
// Enable Timer/Counter0 Output Compare A Match Interrupt | |
TIMSK0 |= _BV(OCIE0A); | |
/*************************************************************************** | |
** END - Timer/Counter0 Configuration ** | |
***************************************************************************/ | |
/*************************************************************************** | |
** Timer/Counter1 Configuration (used for setting PWM compare values) ** | |
***************************************************************************/ | |
// Setup Timer/Counter1 Control Registers (TCCR1) with the following options: | |
// Timer/Counter Mode of Operation | CTC | |
// TOP | OCR1A | |
// Update of OCR1x at | Immediate | |
// TOV1 Flag Set on | MAX | |
TCCR1A &= ~(_BV(WGM11) | _BV(WGM10)); // Clear bits WGM11 and WGM10 in TCCR1A | |
TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12); // Clear bit WGM13 and set bit WGM12 in TCCR1B | |
// Setup clock prescaler for TCCR1 as clk_IO/8 | |
TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS10))) | _BV(CS11); | |
// Set compare value (OCR1A) to value equivalent to: | |
// C = Compare value | |
// PS = Prescaler value | |
// N = Number of samples in wavetable read by interrupt routine | |
// B = Base frequency gained by reading one sample from wavetable per call of interrupt routine | |
/ F_CPU = Core clock frequency | |
// C = F_CPU / (PS * B * N) | |
uint8_t base_frequency = 100; | |
uint16_t tccr1_prescaler = 8; | |
// Clock Speed / Target Interrupt Frequency (4kHz) | |
OCR1A = F_CPU / ((uint32_t) tccr1_prescaler * (uint32_t) base_frequency * (uint32_t) sinewave_length); | |
// Enable Timer/Counter1 Output Compare A Match Interrupt | |
TIMSK1 |= _BV(OCIE1A); | |
sample_0 = 0; | |
/*************************************************************************** | |
** END - Timer/Counter1 Configuration ** | |
***************************************************************************/ | |
/*************************************************************************** | |
** Timer/Counter2 Configuration (used for generating PWM) ** | |
***************************************************************************/ | |
// Setup Timer/Counter2 Control Registers (TCCR2) with the following options: | |
// Mode | Fast PWM | |
// TOP | 0xFF | |
// Update of compare register | BOTTOM | |
// TOV Flag Set on | MAX | |
TCCR2A |= _BV(WGM21) | _BV(WGM20); // Set WGM21 and WGM20 bits in TCCR2A | |
TCCR2B &= ~_BV(WGM22); // Clear bit WGM22 in TCCR2B | |
// Setup Timer/Counter2 Control Registers (TCCR2) in Compare Output Mode with the following options: | |
// Clear OC2A on Compare Match, set OC2A at BOTTOM, (non-inverting mode). | |
TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0); // Set COM2A1 and clear COM2A0 bit in TCCR2A | |
// Setup Timer/Counter2 Control Registers (TCCR2) in Compare Output Mode with the following options: | |
// Normal port operation, OC2B disconnected. | |
TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0)); // Clear COM2B0 and COM2B1 bits in TCCR2A | |
// Setup clock prescaler for Timer 2 (TCCR2) as clk_IO/8 | |
// The highest frequency generated by our wavetable (in the future) is planned to be 500Hz. | |
// Thus, PWM base frequency / PWM resolution (255) must be at least 1kHz. | |
TCCR2B = (TCCR2B & ~(_BV(CS22) | _BV(CS20))) | _BV(CS21); | |
OCR2A = pgm_read_byte(&sinewave_data[0]); | |
/*************************************************************************** | |
** END - Timer/Counter2 Configuration ** | |
***************************************************************************/ | |
/*************************************************************************** | |
** ADC Configuration ** | |
***************************************************************************/ | |
// Set ADC reference to AVCC | |
ADMUX |= _BV(REFS0); | |
// Initialise ADC pointing to channel 2 | |
ADMUX |= _BV(MUX1); | |
// Enable ADC conversion complete interrupt (ADC_vect) | |
ADCSRA |= _BV(ADIE); | |
// Set ADC prescaler to 128 - 125kHz sample rate @ 16MHz, 62.5kHz sample rate @ 8MHz | |
ADCSRA |= _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); | |
// Enable ADC | |
ADCSRA |= _BV(ADEN); | |
// Start an initial ADC conversion | |
ADCSRA |= _BV(ADSC); | |
// Wait for conversion to complete, then clear interrupt flag | |
while(ADCSRA & _BV(ADSC)); | |
ADCSRA |= _BV(ADIF); | |
/*************************************************************************** | |
** END - ADC Configuration ** | |
***************************************************************************/ | |
// Enable global interrupts | |
sei(); | |
} | |
void loop() {} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment