avratmel

AVR I/O: DIgital vs Analog Input Programming


I'm trying to use some previously developed sw from Github and ran across an interesting bit of sw coding.

He's using an Atmel ATtiny45 with digital I/O's on most of the PortB pins but PB3 is used as an analog (AtoD) input from an external potentiometer. In his code, he's got the following snippet:

  if(PINB & MANCLK){
       PORTB |= CLKOUT;
       } else {
       PORTB &= ~CLKOUT;
       }
    }

(Note: 'CLKOUT' is a digital output pin.)

"MANCLK" is the analog input pin at PB3 so, what does the line: "if(PINB & MANCLK)..." do? Does it actually consider all analog values coming in on that pin so, anything less than 2.5V is considered a logic low and anything over 2.5V is a high or, is this even a valid statement? Is 'MANCLK' simply to be considered as a "0" or a "1"? Not sure where to find good information on this particular situation.

Thanks for any help provided.

Regards, Grant

[update] Here's the code....

`

 #include <stdio.h>
 #include <stdlib.h>
 #include <avr/io.h>
 #include <avr/interrupt.h>
 #include <avr/sleep.h>
 #if !defined(TIMSK)
 #define TIMSK               TIMSK0
 #endif
 #if !defined(TIMER0_COMPA_vect)
 #define TIMER0_COMPA_vect   TIM0_COMPA_vect
 #endif

// 100 us per tick
#define COUNT   199
#define TPSC    (1<<CS01)

#define AUTO    (1<<PB0)    //Pin5, MOSI and EN (via RN33 Pins 4&5).
 High = Enable
#define RUN     (1<<PB1)    //Pin6, MISO and ~HLT (via RN33 Pins 3&6).
 Low = Halt
#define MANCLK  (1<<PB3)    //Pin2, Analog input, Manual Clock Speed
 Setting (via potentiometer RV1)
#define CLKOUT  (1<<PB4)    //Pin3, CLK output
#define AUTOINT (1<<PCINT0)
#define RUNINT  (1<<PCINT1)
#define CLKINT  (1<<PCINT2)

#define MANDLY  500

volatile uint16_t count;
volatile uint8_t trigger;
volatile uint8_t manual;

uint16_t maxcnt;

void stopTimer(void) {
    PORTB &= ~CLKOUT;       // Clock output low
    TCCR0B &= ~TPSC;
    TCNT0;
    count = 0;
    trigger = 0;
}

void startTimer(void) {
    PCMSK = 0;
    TCCR0B |= TPSC;
}

void halt(void) {
    cli();
    stopTimer();
    PCMSK = RUNINT|AUTOINT;
    sei();

    sleep_enable();
    while(!(PINB & RUN)) sleep_cpu();
    sleep_disable();

    cli();
    startTimer();
    sei();
}

void manclk(void) {
    cli();
    stopTimer();
    PCMSK = CLKINT|RUNINT|AUTOINT;
    sei();

    while(!(PINB & AUTO)) {
        if(PINB & RUN) {
            if(PINB & MANCLK) {
                PORTB |= CLKOUT;
                } else {
                PORTB &= ~CLKOUT;
                }
        }
        sleep_enable();
        sleep_cpu();
        sleep_disable();
    }

    cli();
    startTimer();
    sei();
}

int main(void)
{
   DDRB = CLKOUT;
   PORTB &= ~CLKOUT;

   cli();
   TCCR0A = 0;
   TCCR0B = 0;
   TCNT0 = 0;
   TCCR0A |= (1 << WGM01);
   OCR0A = COUNT;          // Interrupts every 100us when Timer running
   TCCR0B |= (1 << CS01);  // Timer Start /8 pre-scaler
   TIMSK |= (1 << OCIE0A);
   GIMSK |= (1 << PCIE);
   sei();

   // Setup the ADC
   //ADMUX |= (1 << MUX1); // Only using ADC2 (original)
   ADMUX |= (1 << MUX0) | (1 << MUX1);   //new code - MUX Select ADC3 @
    PB3
   ADCSRA |= (1 << ADEN);
   ADCSRA |= (1 << ADPS2) | (1 << ADPS1); // 125 kHz ADC clock

   //Enable Sleeping
   set_sleep_mode(SLEEP_MODE_IDLE);

   while (1) {

//if RUN = HLT = Low ==> HALT
       if (!(PINB & RUN)) {
           halt();
           continue;
       }

//if AUTO = ENABLE = Low ==> manclk...if AUTO (ENABLE) == LOW ==>
 MANCLK
       if (!(PINB & AUTO)) {
           manclk();
           continue;
       }

       ADCSRA |= (1 << ADSC);
       while (ADCSRA & (1 << ADSC));
       maxcnt = 5000 / (64 - ADC/16);

       if (trigger) {#
           PORTB ^= CLKOUT;
           trigger = 0;
       }

       sleep_enable();
       sleep_cpu();
       sleep_disable();
   }

   return 0; // never reached
   }

   ISR(TIMER0_COMPA_vect) {
       count++;
       if (count >= maxcnt) {
           count = 0;
           trigger = 1;
       }

}

EMPTY_INTERRUPT(PCINT0_vect);`

Solution

  • If we expand some of the preprocessor macros, your code becomes easier to explain:

    if (PINB & (1 << 3))
    {
      PORTB |= (1 << 4);
    }
    else
    {
      PORTB &= ~(1 << 4);
    }
    

    Now just looking at the code above we can tell that it does the following: it reads bit 3 of the PINB register and if it is 1, it sets bit 4 of the PORTB register to 1. Otherwise, it clears (sets to 0) bit 4 of the PORTB register.

    If you don't understand why this is true, I recommend that you look up the definitions of the following C bitwise logic operators in a C book or C tutorial: & |, <<, |=, &=.

    A simpler way of describing the code is that it copies the value from bit 3 of PINB to bit 4 of PORTB.

    Now to get a better understanding of what the code does, you need to find the ATtiny45 datasheet and read the definitions of the PINB and PORTB registers. Then you will discover the following:

    1. Reading bit 3 of PINB is how you do a digital reading of PB3. It does not use the ADC and it does not have a super accurate threshold like you were thinking. The thresholds are defined somewhere in the "Electrical Specifications" section of the datasheet or similar.

    2. Since bit 4 of DDRB is 1 in your program, PB4 is configured as a digital output. Therefore, writing a value to bit 4 of PORTB is how we set the output value of PB4. Writing a 0 to it makes it drive low (0 V), and writing a 1 to it makes it drive high.

    In summary, this is some pretty basic AVR code that does a digital reading and writes that value to a digital output.