6
\$\begingroup\$

I'm a programmer and not jet experienced with arduino or any microcontrollers. Especially the technical side.

I soldered a 6x8 RGB Led Matrix and use binary coded modulation http://www.batsocks.co.uk/readme/art_bcm_3.htm to mix the colors.

The Leds are controlled by 4 74hc595 shiftregisters. Basically I shift out the bits to control the really fast during a timer interrupt.

Now I want to influence the color of the leds by measuring sound frequencies with an Electret microphone breakout board. To achieve fast frequency detection I use a technique described here http://www.instructables.com/id/Arduino-Frequency-Detection/#step1.

It is based on ADC interrupts.

Both impementations work alone but when I try to bring the code together it breaks.

The timer and the ADC are initialzed like this:

cli(); //stop all interrupts

/////////////////////
//initialize TIMER//
//////////////////// 
TCCR1A = 0;
TCCR1B = 0;
TCNT1  = 0;

OCR1A = 1;            // compare match register
TCCR1B |= (1 << WGM12);   // CTC mode
TCCR1B |= ((1<<CS21)|(1<<CS20)) ;
TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt

//////////////////
//initialize ADC//
//////////////////
ADCSRA = 0;
ADCSRB = 0;

ADMUX |= (1 << REFS1); //set reference voltage
ADMUX |= (1 << ADLAR); //left align the ADC value- so we can read highest 8 bits from          ADCH register only

//ADCSRA |= (1 << ADPS2) | (1 << ADPS0); //set ADC clock with 32 prescaler- 16mHz/32=500kHz
ADCSRA |= (1 << ADATE); //enabble auto trigger
ADCSRA |= (1 << ADIE); //enable interrupts when measurement complete
ADCSRA |= (1 << ADEN); //enable ADC
ADCSRA |= (1 << ADSC); //start ADC measurements

sei(); //start all interrupts

So my question is - Is it even possible to use those interrupts together? And how do I have to configure them to make it work. Or is this a dead end?

EDIT The Timer interrupt:

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{
zaehlbit <<=1; 
//reset timercompare and zaehlbit
if( zaehlbit == 0 ) {
  OCR1A= 1;
  zaehlbit  = 1;
}
//latch low
bitClear(PORTB, latchPinPORTB);

//red
for (int i=0; i<8; i++){
  // clock low
  bitClear(PORTB, clockPinPORTB);
  //check led position and brightness
  //and set Bit on Datapin if zaehlbit in Brightness
  if (ledCounter&led && zaehlbit&red){
    bitClear(PORTB, dataPinPORTB);
  }
  else {
  bitSet(PORTB, dataPinPORTB);
  }
  // clock low (register bit)
  bitSet(PORTB, clockPinPORTB);
  ledCounter >>= 1;
  if( ledCounter == 0 ) {
  ledCounter  = 128;
 }  
}

//shift timer compare and set timer back to generate delay (Bit Angle Modulation)
OCR1A <<= 1;
TCNT1 = 0;
}

I do the same for-loop for the rows and the other colors. I just left it out because it looked confusing. The compare match register is shifted left during the interrupt and the timer is set back to generate a "growing" delay. There is one interrupt per cycle. It runs at a clock/32 tickrate

http://www.batsocks.co.uk/img/art_bcm/bcm_1.png

The ADC interrupt looks like this:

ISR(ADC_vect) {//when new ADC value ready

  prevData = newData;//store previous value
  newData = ADCH;//get value from A0
  if (newData > prevData){//if positive slope
    bitSet(PORTB, ledPORTB);//set pin 12 high
  }
  else if (newData < prevData){
    bitClear(PORTB, ledPORTB); //set pin 12 low
  }

}

The ADC interrupt is triggered everytime a value from A0 is ready. In the mainloop I just try to set some leds but it doesn't work.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ It should - of course - be possible. Could you elaborate a bit: What exactely doesn't work as expected? Also, could you post the full code (or at least the interrupt handlers and your main loop). Also: How often do you expect an ADC interrupt and how often do you expect the timer interrupt to happen? \$\endgroup\$
    – Tom L.
    Commented Jun 30, 2013 at 13:53
  • \$\begingroup\$ I edited the question I hope it's more clear now \$\endgroup\$ Commented Jun 30, 2013 at 14:13
  • 1
    \$\begingroup\$ Check the datasheet for your exact microcontroller (ATmega[something]). Every interrupt has its own vector and comes with its own priority, this defines which interrupt will be serviced when two arrive at the same moment. While one interrupt is being serviced, the other interrupt will be put on hold until the first ISR finished. \$\endgroup\$
    – jippie
    Commented Jun 30, 2013 at 17:57
  • 2
    \$\begingroup\$ Whats your clock frequency? Are you triggering the timer interrupt every clock cycle (due to OCR1A = 1)? Maybe the timer ISR execution time is longer that your interrupt interval? What exactly does "when I try to bring the code together it breaks" mean? \$\endgroup\$
    – Rev
    Commented Jun 30, 2013 at 17:57

2 Answers 2

3
\$\begingroup\$

One thing that I notice from your code is that you are acquiring the samples in free running mode with ADC clock at F_CPU/2. According to the Atmega 328P datasheet

In Free Running mode, a new conversion will be started immediately after the conversion com- pletes, while ADSC remains high.

and the conversion time for free running mode (on table 23-1, page 255) is specified as 13 ADC clock cycles. This means that your ADC ISR is firing every 26 clock cycles with your current prescaler settings. On top of that, your TIMER1_COMPA ISR is also fired so frequently (every 64 cycles) that your ISR might actually take longer to execute than the desired time. You basically use all your time acquiring data and/or in your timer ISR. An easy solution is to increase the count in OCR1A to, say, 5 and increase the prescaler of the ADC

ADCSRA |= (1 << ADPS1); // ADC prescaler 8, conversion time 4*26 MCU cycles
OCR1A = 5; // 5*64;

If you want to squeeze every last drop of performance out of the old 328 you should inspect the generated assembly for your interrupt routines, and figure out if you can do something to optimize them. Common performance hogs are, for example, frequent data fetches from SRAM, but if you're using gcc you might find all sorts if groovy stuff happening upon ISR entry.

Keep in mind that the human eye cannot detect insanely fast flickering (hence pwm driven leds work as they do, with no visible flicker). If the slower running rate looks visually unpleasant you can of course try to reduce the prescaler and OCR1A, but you should also consider dispensing the ISR's altogether and just run your led flashing code in the main loop and have just the ADC ISR acquire data to be processed. This might work better since every function call takes at minimum 8 cycles (might have been 6, memory is a bit fuzzy on this particular detail) PLUS all the stack PUSH'n an' POP'n, which depends on your variable usage. So you could go for something like:

while(1) {
   checkData();
   handleLeds();
}

and have both functions be inlined.

\$\endgroup\$
0
\$\begingroup\$

Yes you can, jus three things:

  • Try to use the main loop to do all you need, trying to programm a cyclic executive and use the interrupts only to activate flags or read ADC values. You can achive this by setting times for each task, for example, read the ADC every 5ms, Set the LEDs every 10ms, etc.

  • In some Atmel devices like Atmega16, you need to reset (setting it again) the ADC every time the ADC finish a measurement.

  • Deactivate the general interrupt flag when entering to an interrupt function and activate it again when leaving. cli() and sei().

Any question, let me know, I didn't use the comment because I have no reputation to do so, but I have lot of exeperience working on Atmel microcontrollers and Digital systems.

\$\endgroup\$
5
  • \$\begingroup\$ 1) The ADC has been properly configured for autotrigger, and exiting the ISR will retrigger it. 2) Interrupts are disabled when an ISR is executed; they need to be explicitly enabled if nested interrupts are to be used. \$\endgroup\$ Commented Nov 27, 2013 at 18:50
  • \$\begingroup\$ That would be a reason why it's not working with both interrupt vector, sei() have to be called when finishing any interrupt function listener. \$\endgroup\$
    – Chirry
    Commented Nov 27, 2013 at 19:19
  • \$\begingroup\$ The other thing I see, is that the timer interrupt is being called every 64 clock cycles (CS20 and CS21 = 64 prescaling and OCR1A = 1), and maybe is being triggered when the micro is "busy" doing the ADC interrupt function. At least, the Timer vector function takes more than 64 clock cycles itself. \$\endgroup\$
    – Chirry
    Commented Nov 27, 2013 at 19:22
  • \$\begingroup\$ Interrupts are restored upon completion of the ISR. And pending interrupts are handled when the flag is restored. \$\endgroup\$ Commented Nov 27, 2013 at 21:24
  • \$\begingroup\$ I had no idea, I have always set by hand the global interrupt at the end of every ISR. Other thing that I realised is that: as far as I know, there are no interrupts queue but there are different interruption priorities. So if the timer interrupt has a higher priority than the ADC, and the timer interrupt keeps calling, the micro will always handle the timer interruption. \$\endgroup\$
    – Chirry
    Commented Nov 27, 2013 at 23:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.