Angular determinism - Direction (using AS5600) [Timer Interrupts]

Hello,

I'm in a bit of a pickle on how to correctly detect the direction of really any type of motor, but, in my case a stepper motor with a sprocket with a pulling wire mounted on its drive shaft.

My goal:
determine wether the motor has turned clockwise or counterclockwise soley reliant on the angle measured by the AS5600 sensor

the problem:
when for example: we yank the pulling wire on the sprocket of the motor realy, realy fast we will lose the absolute position (not the angle) because we no longer know if we moved clockwise or counterclockwise because we moved to fast for the mcu to register the entirety of the event.

Can it be done foolproof purely from the software without any extra sensory equiped/installed?

/* situations: 
previous angle: 	360°			0°		
current angle:		1°			   359°		

diffrence	: 1-360 = -359°			359°		
moved CW: 		  1°			    359°		
moved CCW: 	   	  359°			    1°		


We either moved 1° in a CW/CCW direction or 359° in the other direction super duper fast.
How to detect and register the correct event? */

So far I just came up with this code but this is far from finished or being foolproof:

uint16_t iCurrentRawAngle = 0;    
uint16_t iPreviousRawAngle = 0; 
float fCurrentPos = 0.000;   
int32_t iCompleteRevs = 0;    // negative values can occur when turning Counter clockwise from homeposition
uint8_t AS5600AnalogOutPin = 10;

void setup() {
  
}

void loop() {
  // alot of other stuf...
  UpdatePosition();
  // alot of other stuf...
}

void UpdatePosition()
{
  // (Analog position recall is the fastest method to get the angle from the as5600)
  iCurrentRawAngle = analogRead(AS5600AnalogOutPin);

  // if the angle has changed we moved, proceed.. 
  if( iCurrentRawAngle != iPreviousRawAngle)
  {
    float currentangle = iCurrentRawAngle * (float) (360/1023);   // using 10bit ADC mcu
    float previousangle = iPreviousRawAngle * (float) (360/1023);

    // (not good enough) - check to see if we made a turn CW/CCW
    if( previousangle > 180 && currentangle < 180)
    {
      // Clockwise (theoreticly, not foolproof)
      // or we turned a couple degrees CCW
    } 
    else if( previousangle < 180 && currentangle > 180)
    {
      // Counter Clockwise (theoreticly, not foolproof)
      // or we turned a couple degrees clockwise 
    }

  
  }
  fCurrentPos = (iCompleteRevs*360) + iCurrentRawAngle * (float) (360/1023);
  iPreviousRawAngle = iCurrentRawAngle;
}

That is exceedingly unlikely. By default, analogRead() takes a few microseconds (110 us on the Arduino Uno R3) and the AS5600 sensor is comparably fast.

The motor shaft can't turn far in that short time, so it should be trivial to determine the direction of rotation using two successive read operations.

No math other than a single subtraction is required.

It is a whole lot more likely then you would initially think, the problem only increases the chance of occurance the bigger the total program is, the longer it takes for the loop to be back at the position where we are updating the position.
I haven't done any math on it but i reckon it can easily reach over 100's of ms even without the use of blocking code. With a pulley small enough its very easily to overcome that distance multiple times in several 100s ms.

It is no major problem to write code so that time critical components have priority, eliminating the issue. For example, have a timer interrupt (or use the micros() function) to execute an analog read operation every millisecond.

Incidentally, stepper motors generate quadrature voltage pulses if the shaft is mechanically rotated. The uP can simply count those to determine the total angle change and direction of rotation at the same time, just like a shaft encoder.

Even with a microcontroller moving at the slow-as-molasses speed of 16MHz, you have to go out of your way to take this long. You have to be using very poorly written libraries to have this problem (and those libraries themselves would have to be blocking).

I've descided that the only reliable answer to prevent missing rotation and cause the error i explained is to use timer interrupt as @jremington hinted at, so that the position is updated at a steady and fast enough interval, this is just a little tutorial so I will need to speed up the timing still.
I've read on the forum here that it is usually not recommended using timer interrupts or interrupts in general but this is my only option as far as i know.

I had some trouble making sense of it all with online tutorials that just don't give any information of what is really going on so i digged into the datasheet of the arduino processor, started from a small tutorial i found online and added pretty much all relevant info to it so that its noobfriendly.

I do have some questions about the matter still so any help is greatly appreciated.
Original tut: https://electronoobs.com/eng_arduino_tut140.php
ATmega datasheet: https://ww1.microchip.com/downloads/aemDocuments/documents/MCU08/ProductDocuments/DataSheets/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061B.pdf

Next up i'm gonna do the same thing for the ESP32 cpu and add it to this code/tutorial

/* (elaborated ) Example code with timer intyerrutp that will create an interruption each (original source: https://electronoobs.com/eng_arduino_tut140.php)
 *  500ms using timer1 and prescalar of 256.
Calculations (for 500ms): 
  System clock 16 Mhz and Prescalar 256;
  Timer 1 speed = 16Mhz/256 = 62.5 Khz ==> 1 pulse every (1 000 000us/62 500):  16us
  Count up to = 500 000us / 16us = 31250 (so this is the value the Output Compare Register should have)
  
  ATmega docs: https://ww1.microchip.com/downloads/aemDocuments/documents/MCU08/ProductDocuments/DataSheets/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061B.pdf
  
  */  
volatile /*static?*/ uint16_t CritVal = 0;
uint8_t CritValPin = A1;

/* Goal: Let counter count, when enough time has passed call the ISR event/interrupt vector where we will perform a simple analogread on a single input pin, 
         reset the counter and repeat.
*/

/* Questions:
- Should i declare the critical val as static aswell?
- initially the status register is completely blank (all 0's), so by default on the arduino mcu interrupts are disabled? and become disabled again when powered down and a new program is uploaded to the arduino?
 & Does the I-Bit in the status register need to be manually set to 1 in order for interrupts to work or is this bit automaticly set by the mcu whenever an interrupt in the Interrupt Mask Register is activated or set high? 

- What happens when the mcu goes to sleep mode to preserve power, will interrupts & Timers/counters still work?
- Do continous non stop usage(24/7) of high frequency(triggering ISR every couple milisecond/microseconds) timer interrupts increase power consumtion, decrease life expectancy of the mcu/increase breakchance?
- In Clear Timer on Compare Match (CTC) Mode, do we need to set OCIE1A or OCIE1B to 1 aswell in the iterrupt mask register? or is it enough to change the WGM bits so mode 4(CT output compare) is active?
- In the datasheet there is nothing to be found about TIMER1_COMPA_vect, only TIMER1_COMPA why or where does the _vect part come from and will the interrupt work when using "ISR(TIMER1_COMPA){}" instead 
- Can I set a single bit in a byte to 1 or 0 without changing the entire bit at once?
*/

void setup() {
  cli();                      //stop interrupts while we edit the settings
/*  1. First we reset the control register to make sure we start with everything disabled.
        TCCR1 register is a 24 bit(3 byte) register for timer 1 that has the possibility to configure 2 seperate interrupts with the one timer/counter
        By default registers TCCR1A, TCCR1B and TCCR1C are initially all 0's */
  TCCR1A = 0;                 // Reset entire TCCR1A byte to 0; is the same as &= B00000000 or = B00000000
  TCCR1B = 0;                 // Reset entire TCCR1B byte to 0; is the same as &= B00000000 or 0x00
  TCCR1C = 0;
  /* TCCR1A Register (adress = 0x80):
      -  Bit 7:6 – COM1A1:0: Compare Output Mode for Channel A
      -  Bit 5:4 – COM1B1:0: Compare Output Mode for Channel B
          The COM1A1:0 and COM1B1:0 control the Output Compare pins (OC1A and OC1B respectively) behavior. 
          If one or both of the COM1A1:0 bits are written to one, the OC1A output overrides the normal port functionality of the I/O pin it is connected to. 
          If one or both of the COM1B1:0 bit are written to one, the OC1B output overrides the normal port functionality of the I/O pin it is connected to. 
          However, note that the Data Direction Register(DDR) bit corresponding to the OC1A or OC1B pin must be set in order to enable the output driver.
          When the OC1A or OC1B is connected to the pin, the function of the COM1x1:0 bits is dependent of the 
          WGM13:0 bits setting. Table 16-1 shows the COM1x1:0 bit functionality when the WGM13:0 bits are set to a 
          Normal or a CTC mode (non-PWM).
          COM1A1/COM1B1     COM1A0/COM1B0      Description
                0                 0            Normal port operation, OC1A/OC1B disconnected.
                0                 1            Toggle OC1A/OC1B on Compare Match.
                1                 0            Clear OC1A/OC1B on Compare Match (Set output to low level).
                1                 1            Set OC1A/OC1B on Compare Match (Set output to high level).
      - Bit 2:3 - Unused
      - Bit 1 – WGM11 Waveform Generation Mode (PWM11)
      - Bit 0 - WGM10 Waveform Generation Mode (PWM10)
            Combined with the WGM13:2 bits found in the TCCR1B Register, these bits control the counting sequence of 
            the counter, the source for maximum (TOP) counter value, and what type of waveform generation to be used, 
            see Table 16-4. Modes of operation supported by the Timer/Counter unit are: 
            Normal mode (counter), Clear Timer on Compare match (CTC) mode, and three types of Pulse Width Modulation (PWM) modes. (See ”Modes 
            of Operation” on page 131).
          Table 16-4: Waveform Generation Mode Bit Description :
      TCCR1-          B(Bit4) B(bit3)   A(bit1)      A(bit0)
              Mode    WGM13    WGM12     WGM11       WGM10     Timer/Counter Mode of          TOP        Update of         TOV1 Flag
                              (CTC1)    (PWM11)     (PWM10)    Operation                                 OCR1x at          Set on 
                0       0       0         0           0         Normal                        0xFFFF      Immediate         MAX
                1       0       0         0           1         PWM, Phase Correct, 8-bit     0x00FF      TOP               BOTTOM
                2       0       0         1           0         PWM, Phase Correct, 9-bit     0x01FF      TOP               BOTTOM
                3       0       0         1           1         PWM, Phase Correct, 10-bit    0x03FF      TOP               BOTTOM
  We need -->   4       0       1         0           0         CTC                           OCR1A       Immediate         MAX     <-- ( WGM13:0 = 4; ? )
                5       0       1         0           1         Fast PWM, 8-bit               0x00FF      BOTTOM            TOP
                6       0       1         1           0         Fast PWM, 9-bit               0x01FF      BOTTOM            TOP
                7       0       1         1           1         Fast PWM, 10-bit              0x03FF      BOTTOM            TOP
                8       1       0         0           0    PWM, Phase and Frequency Correct   ICR1        BOTTOM            BOTTOM
                9       1       0         0           1    PWM, Phase and Frequency Correct   OCR1A       BOTTOM            BOTTOM
                10      1       0         1           0         PWM, Phase Correct            ICR1        TOP               BOTTOM
                11      1       0         1           1         PWM, Phase Correct            OCR1A       TOP               BOTTOM
                12      1       1         0           0         CTC                           ICR1        Immediate         MAX
                13      1       1         0           1         (Reserved)                    –           –                 –
                14      1       1         1           0         Fast PWM                      ICR1        BOTTOM            TOP
                15      1       1         1           1         Fast PWM                      OCR1A       BOTTOM            TOP
  */
  /*  TCCR1B Register (0x81):
      - Bit 7 – ICNC1: Input Capture Noise Canceler
            Setting this bit (to one) activates the Input Capture Noise Canceler. When the noise canceler is activated, the
            input from the Input Capture pin (ICP1) is filtered. The filter function requires four successive equal valued
            samples of the ICP1 pin for changing its output. The Input Capture is therefore delayed by four Oscillator cycles
            when the noise canceler is enabled.
      - Bit 6 – ICES1: Input Capture Edge Select (for use with Input Capture Interrupt(ICIE1))
            This bit selects which edge on the Input Capture pin (ICP1) that is used to trigger a capture event. When the
            ICES1 bit is written to zero, a falling (negative) edge is used as trigger, and when the ICES1 bit is written to one,
            a rising (positive) edge will trigger the capture.
            When a capture is triggered according to the ICES1 setting, the counter value is copied into the Input Capture
            Register (ICR1). The event will also set the Input Capture Flag (ICF1), and this can be used to cause an Input
            Capture Interrupt, if this interrupt is enabled.
            When the ICR1 is used as TOP value (see description of the WGM13:0 bits located in the TCCR1A and the
            TCCR1B Register), the ICP1 is disconnected and consequently the Input Capture function is disabled.
      - Bit 5 – unused
      - Bit 4 – WGM13: Waveform Generation Mode (See TCCR1A (1:0) Register description)
      - Bit 3 – WGM12(CTC1): Waveform Generation Mode (See TCCR1A (1:0) Register description)
      - bit 2:0 - CS12:0: Clock Select 
            (CS12-CS11-CS10)  prescalar config
              0   0   0       (Timer Stopped) 
              0   0   1       PCK (System Clock)(prescalar 1)
              0   1   0       PCK/8 
              0   1   1       PCK/64
              1   0   0       PCK/256 
              1   0   1       PCK/1024 
  */
  /*  TCCR1C Register (0x82):
      - Bit 7 – FOC1A: Force Output Compare for Channel A
      - Bit 6 – FOC1B: Force Output Compare for Channel B
              The FOC1A/FOC1B bits are only active when the WGM13:0 bits specifies a non-PWM mode. When writing a 
              logical one to the FOC1A/FOC1B bit, an immediate compare match is forced on the Waveform Generation unit. 
              The OC1A/OC1B output is changed according to its COM1x1:0 bits setting. Note that the FOC1A/FOC1B bits 
              are implemented as strobes. Therefore it is the value present in the COM1x1:0 bits that determine the effect of 
              the forced compare.
              A FOC1A/FOC1B strobe will not generate any interrupt nor will it clear the timer in Clear Timer on Compare 
              match (CTC) mode using OCR1A as TOP. The FOC1A/FOC1B bits are always read as zero.
      - Bits 5:0 - Unused, only readable, always 0.
  */

  /*2. in the TCCR1B Register we set the prescalar to the desired value by changing the CS10 CS12 and CS12 bits.
                                     and the WGM to mode 4(CTC mode) (bit3 to 1) */
  TCCR1B |= B00001100;        //alter bits in OR comparison CS12 to 1 so we get prescalar 256  
  // how can i assign the value of each bit independantly from the entire byte?
  //WGM12 |= (1 << 0);  Doesn't work..
  //CS12 = 1;          Doesn't work..
  //WGM13 &= ~(1<<0);     Doesn't work.. (setting to 0)

  /*3. We enable compare match mode on timer1A in register TIMSK1 – Timer/Counter1 Interrupt Mask Register:
    - Bit 0 – TOIE1: Timer/Counter1, Overflow Interrupt Enable
            When this bit is written to one, and the I-flag in the Status Register(SREG – AVR Status Register, bit 7) is set (interrupts globally enabled), the
            Timer/Counter1 Overflow interrupt is enabled. The corresponding Interrupt Vector (TIMER1_OVF (Timer/Counter1 Overflow))
            is executed when the TOV1 Flag, located in TIFR1, is set.
    - Bit 1 – OCIE1A: Timer/Counter1, Output Compare A Match Interrupt Enable ( includes Clear Timer on Compare Match (CTC) Mode? )
    - Bit 2 – OCIE1B: Timer/Counter1, Output Compare B Match Interrupt Enable ( includes Clear Timer on Compare Match (CTC) Mode? )
            When this bit is written to one, and the I-flag in the Status Register(SREG – AVR Status Register, bit 7) is set (interrupts globally enabled), the
            Timer/Counter1 Output Compare B Match interrupt is enabled. The corresponding Interrupt Vector (TIMER1_COMPB (Timer/Counter1 Compare Match B)) 
            is executed when the OCF1B Flag, located in TIFR1, is set. 
          * The 16-bit comparator continuously compares TCNT1 with the Output Compare Register (OCR1x). If TCNT 
            equals OCR1x the comparator signals a match. A match will set the Output Compare Flag (OCF1x) at the next 
            timer clock cycle. If enabled (OCIE1x = 1), the Output Compare Flag generates an Output Compare interrupt. 
            The OCF1x Flag is automatically cleared when the interrupt is executed. Alternatively the OCF1x Flag can be 
            cleared by software by writing a logical one to its I/O bit location. The Waveform Generator uses the match 
            signal to generate an output according to operating mode set by the Waveform Generation mode (WGM13:0) 
            bits and Compare Output mode (COM1x1:0) bits.
          * In Clear Timer on Compare or CTC mode (WGM13:0 = 4 or 12), the OCR1A or ICR1 Register are used to 
            manipulate the counter resolution. In CTC mode the counter is cleared to zero when the counter value (TCNT1) 
            matches either the OCR1A (WGM13:0 = 4) or the ICR1 (WGM13:0 = 12). The OCR1A or ICR1 define the top 
            value for the counter, hence also its resolution. This mode allows greater control of the compare match output 
            frequency. It also simplifies the operation of counting external events.
    - Bit 5 – ICIE1: Timer/Counter1, Input Capture Interrupt Enable
            When this bit is written to one, and the I-flag in the Status Register(SREG – AVR Status Register, bit 7) is set (interrupts globally enabled), the
            Timer/Counter1 Input Capture interrupt is enabled. The corresponding Interrupt Vector (TIMER1_CAPT (Timer/Counter1 Capture Event)) 
            is executed when the ICF1 Flag, located in TIFR1, is set.
            Input capture events can be used to measure the width of a pulse. This is done by using an input capture event that triggers on the rising edge of a pulse. 
            The ISR servicing this interrupt saves the time when the interrupt was received and then resets the interrupt so it triggers on the falling edge.
  - Bit 3,4,6,7 - Reserved/unrelated, always checks 0
  */
  TIMSK1 |= B00000010;        //Set OCIE1A to 1 so the system triggers the TIMER1_COMPA interrupt vector? (should be the same as TIMSK1 = 2 (only incase all other bits are 0)?)
  
  /*4. Set the value of the Output Compare Register Timer1A to 31250
      * The Output Compare Registers contain a 16-bit value that is continuously compared with the counter value 
        (TCNT1). A match can be used to generate an Output Compare interrupt, or to generate a waveform output on 
        the OC1x pin. The Output Compare Registers are 16-bit in size.
      * There is also an Input Compare Register (ICR1x): 
      The Input Capture is updated with the counter (TCNT1) value each time an event occurs on the ICP1 pin (or 
      optionally on the Analog Comparator output for Timer/Counter1). The Input Capture can be used for defining the 
      counter TOP value.
      The Input Capture Register is 16-bit in size. */
  OCR1A = 31250;             //Finally we set compare register A to this value  
  sei();                    //Enable back the interrupts
}

void loop() {
  // put your main code here, to run repeatedly:
}

//With the settings above, this Interrupt Service Routine will trigger each 500ms.
ISR(TIMER1_COMPA_vect){
  //TCNT1  = 0;                  //First, set the timer back to 0 so it resets for next interrupt (Redundant as the system already does this for us in CTC mode)
  CritVal = analogRead(CritValPin);
}

Nick Gammon has outstanding tutorials on these topics, and you will probably benefit greatly from them (and learn much quicker than from short questions and replies on the Arduino forum).

We generally recommend that beginners avoid using interrupts, because they often create more problems than they solve, due to lots of subtle issues that must be taken into account.

This tutorial shows how to use the ADC, including using a timer with the ADC to collect data at timed intervals:

1 Like

This example code (Gammon's heading is Automatic Mode) does what I mentioned: uses a timer to trigger interrupts and collect ADC data. It is currently set for 50 kHz, but for your purposes, somewhere around 50 Hz would probably be enough. Change the prescaler from 8 to 1024 and OCR1B to about 300. (Untested)

Test the example by itself and make sure you understand how it works before trying to incorporate the method into another program.


const byte adcPin = 0;  // A0

const int MAX_RESULTS = 256;

volatile int results [MAX_RESULTS];
volatile int resultNumber;

// ADC complete ISR
ISR (ADC_vect)
  {
  if (resultNumber >= MAX_RESULTS)
    ADCSRA = 0;  // turn off ADC
  else
    results [resultNumber++] = ADC;
  }  // end of ADC_vect
  
EMPTY_INTERRUPT (TIMER1_COMPB_vect);
 
void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  TCCR1B = bit (CS11) | bit (WGM12);  // CTC, prescaler of 8
  TIMSK1 = bit (OCIE1B);  // WTF?
  OCR1A = 39;    
  OCR1B = 39;   // 20 uS - sampling frequency 50 kHz

  ADCSRA =  bit (ADEN) | bit (ADIE) | bit (ADIF);   // turn ADC on, want interrupt on completion
  ADCSRA |= bit (ADPS2);  // Prescaler of 16
  ADMUX = bit (REFS0) | (adcPin & 7);
  ADCSRB = bit (ADTS0) | bit (ADTS2);  // Timer/Counter1 Compare Match B
  ADCSRA |= bit (ADATE);   // turn on automatic triggering

  // wait for buffer to fill
  while (resultNumber < MAX_RESULTS)
    { }
    
  for (int i = 0; i < MAX_RESULTS; i++)
    Serial.println (results [i]);

  }  // end of setup

void loop () { }

I've reviewed the code and added the background info aswell, but i'm struggling with the ADC principle: What is the advantage of using auto triggered ADC conversion as opposed to just calling the counter/timer vector and just calling analogRead in there? i feel like fiddling with these ADC settings is just asking for problems, there's so much that can go wrong to be honost.

For example isnt the adc on by default? and doesnt analogread work on all analog inputs always? with the multiplexer bits it gets even more confusing, its like we can only use the adc for one specifik port, does that mean that by using this method we need to reassign the port to read from every time we want to read from a diffrent analog input? its much more convienent to let the system deal with all that.

I've also read that only one conversion can happen at a time and that with the auto triggered adc conversion the conversion can run in the background without halting the code but that also means that should the auto triggered conversion takes place for value A and in the loop we are at that moment calling analogread for value B, the latter will be lost.

There are just to many principle tied to the adc module that i do not understand sufficiently to be fiddling around with that when what i want to achieve has an alot more straightforward and more reliable means of approach to the same end.

below is the altered version, feel free to fill in some questions as i do feel like that is the number one way to learn. To bad i haven't got any answers yet on my previous questions, i appreciate the help but i feel like i'm being send all sorts of directions without getting any tangible answers.
Could you please explain to me what's happening in following line?

ADMUX = bit (REFS0) | (adcPin & 7); // AVCC with external capacitor at AREF pin; not sure what adcPin & 7 means

const byte adcPin = 0;  // A0

const int MAX_RESULTS = 256;

volatile int results [MAX_RESULTS];
volatile int resultNumber;

// ADC complete ISR
ISR(ADC_vect)
{
  if (resultNumber >= MAX_RESULTS)
    ADCSRA = 0;  // turn off ADC. We will only be able to do MAX_RESULTS-1 timer triggered conversion runs this way, with adc disabled, in loop analogreads wont work anymore?
  else
    results [resultNumber++] = ADC;
}  // end of ADC_vect
  
// Why and What is this doing here: is this because we have set up the interrupt vector but are not using this vector directly in our program(loose ends) 
// but instead the adc triggered vector checks the OC1B Flag to initiate a new adc read.
EMPTY_INTERRUPT(TIMER1_COMPB_vect);
/* we could replace this with the below but it would probably be less efficient
ISR(TIMER1_COMPB_vect) {}
*/

void setup ()
{
  Serial.begin (115200);
  Serial.println ();

  // this was not called first, and in all examples from gammon, it is never used, not even when changing register values in a function call so how important is it really?
  cli();
  // reset Timer 1
  TCCR1A = 0;   // Clearing possible previously set interrupt settings for timer/counter1
  TCCR1B = 0;
  TCNT1 = 0;    // Resetting the count of counter1 to 0 when initialising the mcu (optional/redundant as this will only affect the first call of what we want to do, was also changed while in operation(not calling cli();))
  TCCR1B = bit (CS11) | bit (WGM12);  // setting up CTC mode(clear timer on compare), prescaler of 8
  TIMSK1 = bit (OCIE1B);  // enable calling the timer1B interrupt vector on compare match, yet we are not using the vector itself directly? adc conversion start will look at OC1B flag to initiate a conversion
 
  /* From the datasheet:
      The OCR1x Register is double buffered when using any of the twelve Pulse Width Modulation (PWM) modes.
      For the Normal and Clear Timer on Compare (CTC) modes of operation, the double buffering is disabled. The
      double buffering synchronizes the update of the OCR1x Compare Register to either TOP or BOTTOM of the
      counting sequence. The synchronization prevents the occurrence of odd-length, non-symmetrical PWM pulses,
      thereby making the output glitch-free */
  //OCR1A = 39;    // not using so not needed? can't harm neighter to set though
  OCR1B = 39;   // 19.5 uS - sampling frequency 50 kHz ((1/16.000.000 * prescale(8) * OCR1B)), 40 makes 20µs??
  
  /* bit(b) (1UL << (b)) (this is basicly the same as bitSet so whats diffrent from it? bit can only be used inside a byte("TCCR1B =")?
    macro from ..?; 
    shifts the value 1 left in the form of datatype Unsigned Long to b(bitname/nr)?
    how can you shift a bit in a bit? a bit is either 0 or 1 and comprises of a single bitvalue, shifting bits in a byte makes some sort of sense but in a bit?
    whats the diffrence with 1UL >> (b)?
    why can't we use TCCR1B = 0UL << (CS10) to set a bit(in this case CS10) to 0?

    Arduino.h version of bit(n):
    Computes the value of the specified bit (bit 0 is 1, bit 1 is 2, bit 2 is 4, etc.). returns the value of the bit
    
    so are we setting the bit to one or are we computing(calculating) the value of the bit? 
    alternatives: bitWrite(TCCR1B, CS11, 1); bitWrite(TCCR1B, WGM12, 1); 
                  TCCR1B |= B0001010 (setting the entire byte at once (prone to errors))
  */
  /* Bit description:
    ADCSRA:
      Bit 7 – ADEN: ADC Enable
        Writing this bit to one enables the ADC. By writing it to zero, the ADC is turned off. Turning the ADC off while a
        conversion is in progress, will terminate this conversion.
      Bit 5 – ADATE: ADC Auto Trigger Enable
        When this bit is written to one, Auto Triggering of the ADC is enabled. The ADC will start a conversion on a
        positive edge of the selected trigger signal. The trigger source is selected by setting the ADC Trigger Select bits,
        ADTS in ADCSRB.
      Bit 4 – ADIF: ADC Interrupt Flag
        This bit is set when an ADC conversion completes and the Data Registers are updated. The ADC Conversion
        Complete Interrupt is executed if the ADIE bit and the I-bit in SREG are set. ADIF is cleared by hardware when
        executing the corresponding interrupt handling vector. Alternatively, ADIF is cleared by writing a logical one to
        the flag. Beware that if doing a Read-Modify-Write on ADCSRA, a pending interrupt can be disabled. This also
        applies if the SBI and CBI instructions are used.
      Bit 3 – ADIE: ADC Interrupt Enable
        When this bit is written to one and the I-bit in SREG is set, the ADC Conversion Complete Interrupt vector is activated.
      Bits 2:0 – ADPS[2:0]: ADC Prescaler Select Bits
        These bits determine the division factor between the system clock frequency and the input clock to the ADC.
        see Table 24-5. ADC Prescaler Selections

    ADCSRB:
      Bit 6 – ACME: Analog Comparator Multiplexer Enable
        When this bit is written logic one and the ADC is switched off (ADEN in ADCSRA is zero), the ADC multiplexer
        selects the negative input to the Analog Comparator. When this bit is written logic zero, AIN1 is applied to the
        negative input of the Analog Comparator. For a detailed description of this bit, see ”Analog Comparator
        Multiplexed Input” on page 243.
       Bit 2:0 – ADTS[2:0]: ADC Auto Trigger Source
        If ADATE in ADCSRA is written to one, the value of these bits selects which source will trigger an ADC
        conversion. If ADATE is cleared, the ADTS[2:0] settings will have no effect. A conversion will be triggered by the
        rising edge of the selected Interrupt Flag. Note that switching from a trigger source that is cleared to a trigger
        source that is set, will generate a positive edge on the trigger signal. If ADEN in ADCSRA is set, this will start a
        conversion. Switching to Free Running mode (ADTS[2:0]=0) will not cause a trigger event, even if the ADC
        Interrupt Flag is set. See Table 24-6. ADC Auto Trigger Source Selections
    ADMUX: (I don't quite understand the multiplexer what it does or how it works, more specificly the MUX3:0 Bits)
      Bit 7:6 – REFS[1:0]: Reference Selection Bits
        These bits select the voltage reference for the ADC, as shown in Table 24-3. If these bits are changed during a
        conversion, the change will not go in effect until this conversion is complete (ADIF in ADCSRA is set). The
        internal voltage reference options may not be used if an external reference voltage is being applied to the AREF
        pin.
              Table 24-3. Voltage Reference Selections for ADC
              REFS1   REFS0     Voltage Reference Selection
                0       0       AREF, Internal Vref turned off
                0       1       AVCC with external capacitor at AREF pin (0.1µF should suffice)
                1       0       Reserved
                1       1       Internal 1.1V Voltage Reference with external capacitor at AREF pin
      Bit 5 – ADLAR: ADC Left Adjust Result
        The ADLAR bit affects the presentation of the ADC conversion result in the ADC Data Register. Write one to
        ADLAR to left adjust the result. Otherwise, the result is right adjusted. Changing the ADLAR bit will affect the
        ADC Data Register immediately, regardless of any ongoing conversions.
          When ADCL is read, the ADC Data Register is not updated until ADCH is read. Consequently, if the result is left
          adjusted and no more than 8-bit precision is required, it is sufficient to read ADCH. Otherwise, ADCL must be
          read first, then ADCH.
          The ADLAR bit in ADMUX, and the MUXn bits in ADMUX affect the way the result is read from the registers. If
          ADLAR is set, the result is left adjusted. If ADLAR is cleared (default), the result is right adjusted(10bit precision required).
      Bits 3:0 – MUX[3:0]: Analog Channel Selection Bits
          The value of these bits selects which analog inputs are connected to the ADC. See Table 24-4 for details. If
          these bits are changed during a conversion, the change will not go in effect until this conversion is complete
          (ADIF in ADCSRA is set). 
        WHAT IS THIS?? are not all analog pins tied to the adc, why can i select only one?      
  */
  ADCSRA =  bit(ADEN) | bit(ADIE) | bit(ADIF);   // turn ADC on, want interrupt on completion. ADIF is not needed here to set to 1??
  ADCSRA |= bit (ADPS2);                         // Prescaler of 16
  ADMUX = bit (REFS0) | (adcPin & 7);  // AVCC with external capacitor at AREF pin; not sure what adcPin & 7 means
  ADCSRB = bit (ADTS0) | bit (ADTS2);  // Timer/Counter1 Compare Match B
  ADCSRA |= bit (ADATE);   // turn on automatic triggering 
  sei();

  // wait for buffer to fill
  while (resultNumber < MAX_RESULTS)
    { }
    
  for (int i = 0; i < MAX_RESULTS; i++)
    Serial.println (results [i]);

  }  // end of setup

void loop () { }

The auto-triggered ADC option runs independently of whatever the rest of your code is doing. The direction of motor rotation can be very rapidly determined (say, 50 times per second) and made available to the rest of the code, via the ADC ISR in post #8:

// ADC complete ISR  (TBD: determine direction of rotation)
ISR (ADC_vect)

To learn how to set up timers, study the Timers & Counters tutorial in linked in post #7.

I have no idea what you mean by the second option.

Yes but isn't there just one adc module? we are configuring that one module to scan a single analog input, but how is this not going to conflict when we are using more then 1 analog input? that doesnt seem good practice at all, defenitly not when one does not know enough about whats going on in the background

Simply switch inputs.

I agree that this project seems too ambitious for someone at such an early stage of the learning process.

seems like alot of unnescesary extra hassle for no extra value and extra chance of error.
espescially when the system does all these things already for you in the background.

I fail to see the extra benefits from setting up an auto triggered adc conversion using timer1 and fiddling around with an already good working adc module instead of using a simple set up timer1 with output compare interrupt and in that interrupt simply call analogRead().
Why would the first be better then using a simple timer interrupt

I haven't used any of the "special" modes of the ADC on AVR but I know that many STM32 ADCs have features such as the ability to automatically scan a set of input channels in the background and send the data to a buffer continuously using DMA. Does AVR have anything like that?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.