NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » help with interrupts

February 01, 2012
by tchilzer2
tchilzer2's Avatar

Hello,

I am having a problem with setting up an interrupt for a digital distance sensor. It will switch just fine in a software manipulation but trying to set it up on an interrupt has been excrutiating. I have read the atmega data sheet several times to try and understand. I have also used the ps/2 keyboard tutorial to help.

My question is this: the data sheet says Any change on any enabled PCINT14..8 pin will cause an interrupt. (when PCIE1 is enabled) The corresponding interrupt of Pin Change Interrupt Request is executed from the PCI1 Interrupt Vector. Q. does this mean that if I have two pins masked for interrupt say (1 and 5) both pc1 and pc5 will be able to trip the same interrupt and both must be handled within the same ISR(PCINT1_vect){} becase both pins fall within the PCI1 interrupt vector right? Also what if pc1 is configured to PCINT ( SAY PC5(13)) and pc1 is configured to portc1 does this same rule apply (if this is the rule). Simply asked, do I have to write a new PCI interrupt vector for "corresponding" to each pin? or is both pins handled within the same vector because they both fall within PCIE1?

February 01, 2012
by tchilzer2
tchilzer2's Avatar

HERE IS MY CODE FOR THE PROJECT. I AM HAVING TROUBLE GETTING PC1 (DISTNCE SENSOR) TO TRIP. AM I ASKING THE RIGHT QUESTIONS?

// CAMI.c
// for NerdKits with ATmega168 & ATmega328

#define F_CPU 14745600
#include <stdio.h>
#include <math.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include <string.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
#include "keymap.h"

//Keyboard pin out
//Green - pin 5 - clock
//Black - pin 3 - GND
//White - pin 1 - data
//Red - pin 4 - VCC (+5V)

// PIN DEFINITIONS:

// PC5 (PCINT13) -  CLK
// PC4 -- LED anode 2 (blue)
// pc3 -- LED anode 1 (red)
// PC2           -  DATA
// pc1 -- distance sensor digital input
// PC0 -- temperature sensor analog input

//Manual keyboard varialbles

volatile uint8_t kbd_data;
volatile uint8_t char_waiting;
uint8_t started;
uint8_t bit_count;
uint8_t shift;
uint8_t caps_lock;
uint8_t extended;
uint8_t release;
uint8_t d;

ISR(PCINT1_vect){

  //make sure clock line is low, if not ignore this transition
  if(PINC & (1<<PC5)){
    return;
  }

   if (PINC&(1<<PC1)) { d = (PINC & (1<<PC1)) >> PC1;} 
  //if we have not started, check for start bit on DATA line
  if(!started){
    if ( (PINC & (1<<PC2)) == 0 ) {
      started = 1;
      bit_count = 0;
      kbd_data = 0;
      //printf_P(PSTR("%d"),started);
      return;
    }
  } else if(bit_count < 8) { //we started, read in the new bit
    //put a 1 in the right place of kdb_data if PC2 is high, leave
    //a 0 otherwise
    if(PINC & (1<<PC2)){
      kbd_data |= (1<<bit_count);
    }
    bit_count++;
    return;
  } else if(bit_count == 8){ //pairty bit 
    //not implemented
    bit_count++;
    return;
  } else {  //stop bit
    //should check to make sure DATA line is high, what to do if not?
    started = 0;
    bit_count = 0;
  }

  if(kbd_data == 0xF0){ //release code
    release = 1;
    kbd_data = 0;
    return;
  } else if (kbd_data == 0x12) { //hanlde shift key
    if(release == 0){
      shift = 1;
    } else {
      shift = 0;
      release = 0;
    }
    return;
  } else { //not a special character
    if(release){ //we were in release mode - exit release mode
      release = 0;
      //ignore that character
    } else {
      char_waiting = 1;
    }
  }

}

char render_scan_code(uint8_t data){
  char to_ret = pgm_read_byte(&(keymap[data])); //grab character from array
  if(shift){
    to_ret -= 0x20;
  }
  return to_ret;
}

uint8_t read_char(){
  while(!char_waiting){
     //wait for a character
  }
  char_waiting = 0;
  return kbd_data;
}

void init_keyboard(){
 PCICR |= (1<<PCIE1);    PCMSK1 |= (1<<PCINT9);
  started = 0;
  kbd_data = 0;
  bit_count = 0;

  //make PC5 input pin
  //DDRC &= ~(1<<PC5);
  //turn on pullup resistor
  PORTC |= (1<<PC5);

  //Enable PIN Change Interrupt 1 - This enables interrupts on pins
  //PCINT14...8 see p70 of datasheet
  PCICR |= (1<<PCIE1);

  //Set the mask on Pin change interrupt 1 so that only PCINT13 (PC5) triggers
  //the interrupt. see p71 of datasheet
  PCMSK1 |= (1<<PCINT13);
}

uint8_t key_code = 0;

void adc_init() { 
// set analog to digital converter
// for external reference (5v), single ended input ADC0
ADMUX = 0;

// set analog to digital converter
// to be enabled, with a clock prescale of 1/128
// so that the ADC clock runs at 115.2kHz.
ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

// fire a conversion just to get the ADC warmed up
ADCSRA |= (1<<ADSC);

}

uint16_t adc_read() {
// read from ADC, waiting for conversion to finish
// (assumes someone else asked for a conversion.)
// wait for it to be cleared
while(ADCSRA & (1<<ADSC)) {
  // do nothing... just hold your breath.
  }
  // bit is cleared, so we have a result.

  // read from the ADCL/ADCH registers, and combine the result
  // Note: ADCL must be read first (datasheet pp. 259)
uint16_t result = ADCL;
uint16_t temp = ADCH;
result = result + (temp<<8);

// set Adsc bit to get the +next+ conversion started
ADCSRA |= (1<<ADSC);

return result;

}

double sampleToFahrenheit(uint16_t sample) {
// conversion ration in DEGREES/STEP:
// (5000 mV / 1024 steps * (1 degree / 10mV)
//    ^^^^^^^^^^^                ^^^^^^^^^
//
//     from ADC           from LM34 (tem. snsr.)
return sample * (5000.0 / 1024.0 /10.0);

}

 // holder variables for temperature data
 uint16_t last_sample = 0;
 double this_temp;
 double temp_avg;
 uint8_t i;

// holder variable for distance sensor data

void temperature(){
  // take 100 samples and average them!
  temp_avg = 0.0;
  for(i=0; i<100; i++) {
  last_sample = adc_read();
  this_temp = sampleToFahrenheit(last_sample);

  //add this contribution to the average
  temp_avg = temp_avg + this_temp/100.0;}
  }

void flash() {
    PORTC |= (1<<PC4);
    PORTC &= ~(1<<PC3);
    delay_ms(100);

    PORTC &= ~(1<<PC4);
    delay_ms(50);

     PORTC |= (1<<PC4);
    delay_ms(100);

    PORTC &= ~(1<<PC4);
    delay_ms(50);

    PORTC |= (1<<PC4);
    delay_ms(100);

    PORTC &= ~(1<<PC4);
    PORTC |= (1<<PC3);
    delay_ms(100);

    PORTC &= ~(1<<PC3);
    delay_ms(50);

    PORTC |= (1<<PC3);
    delay_ms(100);

    PORTC &= ~(1<<PC3);
    delay_ms(50);

   PORTC |= (1<<PC3);
   delay_ms(100);
   PORTC &= ~(1<<PC3);
   // Check for "s" stop command
   if(char_waiting){
      key_code = read_char();
      if(key_code != 0x1B && d !=0){key_code = 0x24;}
       }
   }

int main() {

 // init serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

   // start up the LCD
 lcd_init();
 FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

 DDRC &= ~(1<<PC1); // set PC1 as input (distance sensor)
 PORTC |= (1<<PC1); // turn on internal pull up resistor for PC1

  // LED as output
  DDRC |= (1<<PC4);
  DDRC |= (1<<PC3);

  // H BRIDGE CONTROLL PIN DEFINITIONS
  //    PB1 = H BRIDGE ENABLE/DISABLE
  //    PB2 = LINE 1                 
  //    PB3 = LINE 2                      
  //    PB4 = LINE 3
  //    PB5 = LINE 4

//PORT B 1-5 AS OUTPUT     
  DDRB |= (1<<PB1); 
  DDRB |= (1<<PB2); 
  DDRB |= (1<<PB3);
  DDRB |= (1<<PB4);
  DDRB |= (1<<PB5);

  PORTB |= (1<<PB1);//H BRIDGE IC ON

 void forward(){ // DRIVE MOTOR FORWARD                
  PORTB &= ~(1<<PB2);               
  PORTB |= (1<<PB3);}

 void backward(){ // DRIVE MOTOR BACKWARD
  PORTB |= (1<<PB2);
  PORTB &= ~(1<<PB3);}

 void stop(){ // DRIVE MOTOR STOP
  PORTB &= ~(1<<PB2);
  PORTB &= ~(1<<PB3);}

 void left(){ // TURN LEFT
  PORTB &= ~(1<<PB4);
  PORTB |= (1<<PB5);}

 void right(){ // TURN RIGHT
  PORTB |= (1<<PB4);
  PORTB &= ~(1<<PB5);}

 void straight(){ // CENTER
  PORTB &= ~(1<<PB4);
  PORTB &= ~(1<<PB5);}

  // initial temperature trigger callibration

  double temp_adjv;  //initialize temperature adjustment value
  double temp_adjh;  //initialize max temperature value
  double temp_adjl;  //initialize min temperature value
void cal_tag(){  
  printf_P(PSTR("                                        C.A.M.I.   Temperature trigger Callibration  \r\n"));
  printf_P(PSTR(" \r\n"));

  //sample temperature

  // start up the Analog to Digital Converter
 adc_init();

  temperature();

  //show trigger callibration results

  printf_P(PSTR("C.A.M.I.    average temperature = %.2f deg. F\r\n"), temp_avg);
  printf_P(PSTR("C.A.M.I.    Temperature trigger successfully callibrated to 05.00 degrees above/below initial average temperature. \r\n"));

  //set trigger value
  temp_adjv = 5.0;

  //set min/max temperature triggers

  temp_adjh = temp_avg + temp_adjv;
  temp_adjl = temp_avg - temp_adjv;

  //scroll the screen
  int a;
  for (a=0; a<4 ; a++){
  printf_P(PSTR("\r\n"));}
  key_code = 0x00;
  }

  init_keyboard();

  //enable interrupts
  sei();

  char str_buf[16];
  uint8_t buf_pos = 0;
  str_buf[0] = str_buf[1] = 0x00;

  // loop keeps looking forever

 while(1) {

   // start up the Analog to Digital Converter
 adc_init();

cal_tag();

    do {

 // listen for command

if(char_waiting){
      key_code = read_char();
      if(key_code == 0x5A){ // enter key, Check for command, clear the line
       buf_pos = 0;
        str_buf[0] = str_buf[1] = 0x00;
      } else if (key_code == 0x66){ //backspace

        buf_pos--;
        str_buf[buf_pos] = 0x00;
      }

       //store manual keyboard characters pressed
        else {
        str_buf[buf_pos] = render_scan_code(key_code);
        buf_pos++;
        str_buf[buf_pos] = 0x00;
      }
    }

    //set up command line and display manual keyboard characters pressed

    lcd_line_three();
    lcd_write_string(PSTR("CMD: "));
    lcd_goto_position(2,5);
    fprintf_P(&lcd_stream,PSTR("%-20s"),str_buf);

    // check for cami manual commands
    if (key_code == 0x24){
    while (key_code == 0x24){//emergency flash command "e"
      flash();}
     }
    else if (key_code == 0x21){cal_tag();}//temperature trigger callibration command "c" 
    else if (key_code == 0x2B){forward();}//drive motor forward "f"
    else if (key_code == 0x32){backward();}//drive motor backward "b"
    else if (key_code == 0x1B){stop();}//stop drive motor "s"
    else if (key_code == 0x4B){left();}//turn left "l"
    else if (key_code == 0x2D){right();}//turn right "r"
    else if (key_code == 0x29){straight();}//go straight "space"
  temperature();

    // read distance sensor
    do {
    // distance sensor trip handler
    if (d==0){printf_P(PSTR("C.A.M.I.   distance interupt %d        Object detected!  \r"), d);
    stop();
    straight();

    lcd_home();
    lcd_write_string(PSTR("      C.A.M.I.      "));

    lcd_line_two();
    lcd_write_string(PSTR("!!!    ALERT!    !!!"));

    lcd_line_three();
    lcd_write_string(PSTR(" Object    Detected "));

    lcd_line_four();
    lcd_write_string(PSTR(" dist. sensor trip  "));

    flash();}
    }
    while (d == 0);

    //turn of led's
   PORTC &= ~(1<<PC3);
   PORTC &= ~(1<<PC4);

    // write normal operation messages to LCD
    lcd_home();
    lcd_write_string(PSTR("    Hello world!    "));
    lcd_line_two();
    lcd_write_string(PSTR("   I am C.A.M.I.!   "));

    lcd_line_three();
    lcd_write_string(PSTR("CMD:                "));

    lcd_line_four();
    fprintf_P(&lcd_stream, PSTR("Temperature: %.2f"), temp_avg);
    lcd_write_string(PSTR("F     "));

    // write normal operation messages to serial port

    printf_P(PSTR("C.A.M.I.  "));

    printf_P(PSTR("  Operating Temperature = %.2f degrees F        "), temp_avg);
    printf_P(PSTR("\r"));

  } while (temp_avg < temp_adjh && temp_avg > temp_adjl);

  // start temperature triggar sequence

  // write temp. triggar message to port

  printf_P(PSTR("C.A.M.I.   !!!ALERT!!! TEMPERATURE IS OUTSIDE OF SPECIFIED PERAMETERS!!! \r\n"));
  printf_P(PSTR("\r\n"));

 // write temp. trip data messages to LCD

    lcd_home();
    lcd_write_string(PSTR("      C.A.M.I.      "));

    lcd_line_two();
    lcd_write_string(PSTR("!!!    ALERT!    !!!"));

    lcd_line_three();
    lcd_write_string(PSTR("                    "));

 // set flash count variable
  int flash_count = 0;

    //flash LED's and count the flashes

    do{

   printf_P(PSTR("C.A.M.I.   Temperature triggered at %.2f F   EMERGENCY LIGHTS FLASH X %d \r"),   temp_avg, flash_count);

   //update LCD
   lcd_line_four();
   fprintf_P(&lcd_stream, PSTR("Temp. trip @ %.2f"), temp_avg);

    flash();

   flash_count ++;

   //re sample the temperature
   temperature();

    // read distance sensor
    do {d = (PINC & (1<<PC1)) >> PC1;
    // distance sensor trip handler
    if (d==0){
    stop();
    straight();

    lcd_home();
    lcd_write_string(PSTR("      C.A.M.I.      "));

    lcd_line_two();
    lcd_write_string(PSTR("!!!    ALERT!    !!!"));

    lcd_line_three();
    lcd_write_string(PSTR(" Object    Detected "));

    lcd_line_four();
    lcd_write_string(PSTR(" dist. sensor trip  "));

    printf_P(PSTR("C.A.M.I.   distance interupt %d      I SEE YOU!                           \r"), d);
    flash();}
    } while (d == 0);
   }
    while (temp_avg > temp_adjh || temp_avg < temp_adjl);

   PORTC |= (1<<PC4); 
   PORTC |= (1<<PC3);
   delay_ms(500);
   printf_P(PSTR("\r\n"));
   printf_P(PSTR("C.A.M.I.   Emergency lights off \r\n"));
   printf_P(PSTR(" \r\n"));
   PORTC &= ~(1<<PC3);
   PORTC &= ~(1<<PC4);

  }
  return 0;
}
February 01, 2012
by tchilzer2
tchilzer2's Avatar

NOTE: Please ignore the code within the temperature trigger sequence as It has no effect unless the temperature triggar is tripped. Meaning it has nothing to do with the distance sensor during the main opperation. I will change that code to match once I get it working in the main part. Thanks again in advance!

February 03, 2012
by pcbolt
pcbolt's Avatar

@ tchilzer2

Seems like your throwing a whole bunch of code into the stew. It's hard to follow all the way through. To answer your question about the interrupt mask, if you enable interrupts on different pins, they will all trigger the same interrupt. Of course you can use your code to change the mask at any time, or poll the interrupt pins to see which one fired it off. Problem there is what if they both trigger it or have reset themselves by the time you check. I think the easiest way to proceed is to simplify your code as much as possible just to test things out. Try doing something like this:

#define F_CPU 14745600

#include <avr/io.h>
#include <inttypes.h>
#include <avr/interrupt.h>

// PIN DEFINITIONS:
// PC2 -- Button
// PC3 -- LED anode

// Global variables
volatile uint8_t but_pressed, count;

ISR(PCINT1_vect){

// This fires 30-40 times each press
    count++;
    if (count > 25){
        // toggle the value of but_pressed from 1 to 0 or 0 to 1 using XOR
        but_pressed ^= 1;   
        count = 0;
    }
    return;
}

void init_button(){

  but_pressed = 0;
  count = 0;
  DDRC &= ~(1<<PC2); // set PC2 as input - Button

  //turn on pullup resistor
  PORTC |= (1<<PC2);

  //Enable PIN Change Interrupt 1 - This enables interrupts on pins
  //PCINT14...8 see p70 of datasheet
  PCICR |= (1<<PCIE1);

  //Set the mask on Pin change interrupt 1 so that only PCINT10 (PC2) triggers
  //the interrupt. see p71 of datasheet
  PCMSK1 |= (1<<PCINT10);
  return;
}

int main() {
// LED as output
DDRC |= (1<<PC3);
PORTC |= (1<<PC3);

init_button();
sei();

// loop forever
while(1) {
    // if button is pressed, toggle the LED
    if (but_pressed) PORTC ^= (1<<PC3);
}

return 0;
}
February 04, 2012
by tchilzer2
tchilzer2's Avatar

Hi Bolt,

Thanks for the reply and the test code. I thought I was on the right track with two pins in the same vector triggering the same vect. In my program, the hardware settup makes it very clear what has triggered the interrupt (when they both work). What does the ^ opperater do? Im not familiar with it. I will try your test code and put in my second interrupt. I will let you know what I find. Please get back to me about that opperater though ok? Oh... Just a note PC2 pin is on mask 9 not 10 ;)

Thanks again!

February 04, 2012
by dvdsnyd
dvdsnyd's Avatar

@ tchilzer2

The ^ operator is called XOR. In a nutshell it is used to toggle a bit high or low. So if it is high at a certain time and you XOR that bit it will put it to low. Then if you go through and do it again, it will flip it back high. Maybe looking at it with an example would be best...

0101 ^ 0011 = 0110

Here is the Wikipedia reference. It goes into a bit more detail.

XOR-Wikipedia

Hope that helps more than confuses :-)

David

Post a Reply

Please log in to post a reply.

Did you know that the Timer/Counter modules on the microcontroller can be configured to output a PWM (Pulse Width Modulation) signal? Learn more...