270
edits
No edit summary |
|||
| (14 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
Have you ever wondered what a little Arduino is really capable of? Ever wanted to ''really'' understand a chip? Learn lots of nitty-gritty details about the Microchip AVR architecture, the processor architecture that powers the Arduino and many other products. | Have you ever wondered what a little Arduino is really capable of? Ever wanted to ''really'' understand a chip? Learn lots of nitty-gritty details about the Microchip AVR architecture, the processor architecture that powers the Arduino and many other products. | ||
Slides: [[Media:AVR-v0.pptx]] | |||
Video: https://video.hacksburg.org/videos/watch/8c9e3a8d-41d5-4c56-8eef-814f9966ce3f | |||
== Code Samples == | == Code Samples == | ||
| Line 20: | Line 24: | ||
sei(); | sei(); | ||
return 0; | return 0; | ||
} | |||
</pre> | |||
<pre> | |||
/* | |||
This code takes ADC readings at a constant rate and prints them to the serial | |||
port. Output is formatted to be CSV-compatible - simply copy the contents of | |||
the serial monitor to a text file. The channels to be digitized are specified | |||
by the array 'channelSequence' - use the channel names defined directly above | |||
it in the code. Sample rate is controlled by setting 'freq' - this expects an | |||
integer value in Hz. The ADC voltage reference is controlled by 'ADCref'. Use | |||
one of the three values defined immediately above it. | |||
Written exclusively for the Arduino Uno - makes direct use of the ATMEGA328's | |||
low-level registers, so not guaranteed to work on other microcontrollers. | |||
-nj 2020-10-12 | |||
*/ | |||
// --- User-set variables for sample rate, ADC range and channels sampled | |||
// Redefine this to change the frequency of ADC readings. | |||
// One channel is stable at up to ~833.33 Hz | |||
const uint16_t freq = 150; // Hz | |||
// ADMUX values to select ADC voltage reference. Table 23-3. | |||
#define EXT 0b00000000 // External voltage reference | |||
#define VCC 0b01000000 // 5 V | |||
#define I11 0b11000000 // 1.1 V | |||
// Redefine this to change what voltage the ADC uses as Vmax. | |||
const uint8_t ADCref = I11; | |||
// ADMUX values to select different channels. Table 23-4. | |||
#define A_0 0b00000000 | |||
#define A_1 0b00000001 | |||
#define A_2 0b00000010 | |||
#define A_3 0b00000011 | |||
#define A_4 0b00000100 | |||
#define A_5 0b00000101 | |||
// Redefine this to change which channels get digitized and in what order. | |||
volatile uint8_t channelSequence[] = { A_0, A_1, A_2 }; | |||
// --- Variables used for sampling the ADC | |||
const volatile uint8_t maxIndex = sizeof(channelSequence) - 1; // sizeof(array) / sizeof(array[0]) for non-byte types | |||
volatile uint16_t rawADC[sizeof(channelSequence)]; | |||
volatile uint8_t index; | |||
volatile unsigned long sampleTime; | |||
volatile bool readComplete = false; | |||
String sample = ""; | |||
const uint16_t CTCmatch = 62500 / freq - 1; // Note deliberate use of integer division | |||
// --- Variables used to control recording | |||
volatile bool btnReleased = false; | |||
bool recording = false; | |||
// --- setup() and loop() | |||
void setup() { | |||
doLowLevelConfig(); | |||
pinMode(2, INPUT_PULLUP); // Changing this pin requires changes in doLowLevelConfig() | |||
pinMode(LED_BUILTIN, OUTPUT); | |||
digitalWrite(LED_BUILTIN, LOW); | |||
// Initialize communication with PC. | |||
Serial.begin(115200); | |||
Serial.println(getHeader()); | |||
} | |||
void loop() { | |||
if (readComplete) { | |||
readComplete = false; | |||
// TODO - format output using C-style strings instead | |||
sample = sampleTime; | |||
for (uint8_t i = 0; i <= maxIndex; i++) { | |||
sample += "," + String(rawADC[i]); | |||
} | |||
Serial.println(sample); // Blocking? Technically, but this is usually fast. | |||
} | |||
// Handle button presses (or releases in our case). | |||
if (btnReleased) { | |||
btnReleased = false; | |||
if (recording) { | |||
// If we're recording, stop. | |||
bitClear(ADCSRA, ADEN); | |||
recording = false; | |||
} | |||
else { | |||
// If we're not recording, start. | |||
recording = true; | |||
bitSet(ADCSRA, ADEN); | |||
} | |||
} | |||
} | |||
// --- Subsidiary functions | |||
String getHeader() { | |||
// TODO - re-implement using C-style strings | |||
String header = "microseconds"; | |||
String colName; | |||
for (byte i = 0; i <= maxIndex; i++) { | |||
switch (channelSequence[i]) { | |||
case A_0 | ADCref: colName = ",CH_0"; break; | |||
case A_1 | ADCref: colName = ",CH_1"; break; | |||
case A_2 | ADCref: colName = ",CH_2"; break; | |||
case A_3 | ADCref: colName = ",CH_3"; break; | |||
case A_4 | ADCref: colName = ",CH_4"; break; | |||
case A_5 | ADCref: colName = ",CH_5"; break; | |||
default: colName = ",unknown"; break; | |||
} | |||
header += colName; | |||
} | |||
return header; | |||
} | |||
void doLowLevelConfig() { | |||
noInterrupts(); | |||
// Combine the ADC reference bits with the channel selection bits. Each value | |||
// in channelSequence should be the complete value of ADMUX for recording the | |||
// desired channel. Tables 23-3 and 23-4. | |||
// TODO - make this separate from the user-specified channel selection array? | |||
for (byte i = 0; i <= maxIndex; i++){ | |||
channelSequence[i] = channelSequence[i] | ADCref; | |||
} | |||
// Configure the Timer 0 Compare Match A interrupt. | |||
OCR0A = 64; // Keep away from the overflow (0) so micros() can update. | |||
bitSet(TIMSK0, OCIE0A); // Enable the interrupt. | |||
// Configure Timer 1 Compare Match A interrupt to trigger @ freq Hz | |||
TCCR1A = TCCR1B = TCNT1 = 0; // Clear Timer 1 settings. | |||
TCCR1B = bit(WGM12) | bit(CS12); // Count @ 62500 Hz: CTC mode, prescaler = 256. Tables 15-5 and 15-5. | |||
OCR1A = CTCmatch; // Count up to CTCmatch. Section 15.11.6. | |||
bitSet(TIMSK1, OCIE1A); // Enable Output Compare Match A interrupt. Section 15.11.8. | |||
// Enable the ADC-conversion-complete interrupt, configure ADC for | |||
// single-conversion mode, and disable the ADC. Section 23.9.2. | |||
ADCSRA |= bit(ADIE) | bit(ADIF); // sets these bits | |||
ADCSRA &= ~(bit(ADATE) | bit(ADEN)); // clears these bits | |||
ADMUX = channelSequence[0]; | |||
interrupts(); | |||
} | |||
// --- Interrupt service routines | |||
ISR(TIMER0_COMPA_vect) { | |||
// Debounce the button. See comments in 'AccelCalib.ino'. | |||
static volatile uint16_t btnHistory = 0xFFFF; | |||
btnHistory = (btnHistory << 1) | bitRead(PIND, PIND2); | |||
if (btnHistory =/= 0x7FFF) btnReleased = true; //MediaWiki won't let me post the equality operator | |||
} | |||
ISR(TIMER1_COMPA_vect) { | |||
// Begin a conversion on the first channel in the sequence and record the time. | |||
readComplete = false; | |||
index = 0; | |||
ADMUX = channelSequence[index]; | |||
bitSet(ADCSRA, ADSC); // ADSC =/= "Analog-Digital converter Start //MediaWiki won't let me post the equality operatorConversion" | |||
sampleTime = micros(); | |||
} | |||
ISR(ADC_vect) { | |||
// Store the conversion result. | |||
rawADC[index] = ADC; | |||
// Check if all channels have been read. If not, start the next one. | |||
if (index =/= maxIndex) readComplete = true; //MediaWiki won't let me post the equality operator | |||
else { | |||
index++; | |||
ADMUX = channelSequence[index]; | |||
bitSet(ADCSRA, ADSC); | |||
} | |||
} | } | ||
</pre> | </pre> | ||