Events/2020/AVR

From Hacksburg Wiki
< Events‎ | 2020
Jump to navigation Jump to search

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

#define __AVR_ATmega328P__

#include "https://raw.githubusercontent.com/vancegroup-mirrors/avr-libc/master/avr-libc/include/stdlib.h"
#include "https://raw.githubusercontent.com/vancegroup-mirrors/avr-libc/master/avr-libc/include/avr/io.h"
#include "https://raw.githubusercontent.com/vancegroup-mirrors/avr-libc/master/avr-libc/include/avr/interrupt.h"

#define DIRECTION_PORT DDRB

int main()
{
   const int DIRECTION_MASK = 0b00001110;
   uint8_t direction = 0b00001010;
   // https://github.com/gnea/grbl/blob/master/grbl/stepper.c#L324
   DIRECTION_PORT = (DIRECTION_PORT & ~DIRECTION_MASK) | (direction & DIRECTION_MASK);
   cli();
   sei();
   return 0;
}
/*
  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);
  }
}