Skip to content

Instantly share code, notes, and snippets.

@brennon
Created October 22, 2011 20:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brennon/1306450 to your computer and use it in GitHub Desktop.
Save brennon/1306450 to your computer and use it in GitHub Desktop.
ADC -> PWM
#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