ATTINY13 timer and interrupts for reading RC receiver signal

Go To Last Post
18 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello Forum,

 

I am new to AVR and ATTINY, but had some learning curve recently.

The attached code has comprehensive documentation (maybe too much for being able to read the code well).

But as I am new to the topic, I found myself reading the datasheet over and over and searching for the respective sections.

 

I need help on ATTINY13 timer and interrupts for reading and interpreting RC receiver signals.

I use a MULTIPLEX 2,4GHz M-Link RC receiver and connect it to an ATTINY13 pin 5 (PB0).

I try to measure the signal from the receiver (PWM, duration 21ms, pulse between 1ms and 2ms, level 3,4V)

I measure the time via pin change interrupts and time overflows.

Programming is done using an ARDUINO UNO/R3 configured as ISP programmer via SPI.

The sketch is written using the ARDUINO IDE in C and bit manipulation of the registers.

 

The clock is set to 9.6MHz (checked via reading low and high fuses using avrdude).

The timer uses a prescaler of 8.

 

The code almost works. Almost means here, that I can control a led on pin 5 via the radio control.

This means in principle timer and interrupts are setup correctly. Hopefully :)

 

THE PROBLEM:

If I just wait, the led is switched off after a while, which means that I miss something. E.g. resetting a variable in an ISR or inside the main loop.

 

THE CODE:

// *********************************************************************
// Read and interpret signals from MULTIPLEX M-Link 2.4 GHz RC Receivers
// *********************************************************************
//
// Author: A. Ennemoser
// Date: December 2017
//
// MULTIPLEX M-Link RC Receivers send following PWM signal:
//   - stick or switch at minimum position:  950micros pulse width (duty cycle approx. 4.5%)
//   - stick or switch at middle position:  1500micros pulse width (duty cycle approx. 7.1%)
//   - stick or switch at maximum position: 2050micros pulse width (duty cycle approx. 9.7%)
//   - period: 21.11millis (47,37 Hz)
//   - pwm level on the signal pin approx. 3.4V (LVTTL like)
//   - measured with VOLTCRAFT DSO-1062D
//
// ATTINY13 DC Characteristics (page 115)
// Minimum voltage so that Input High is guaranteed with 0.6VCC for VCC in the range 2.4V - 5.5V
// Maximum voltage so that Input Low is guaranteed with 0.3VCC for VCC in the range 2.4V - 5.5V
// Example: VCC=2.4V --> input HIGH above 1.44V, input LOW below 0.72V
// Example: VCC=4.8V --> input HIGH above 2.88V, input LOW below 1.44V
// Example: VCC=5.5V --> input HIGH above 3.30V, input LOW below 1.65V
//
// Reset pin used as input pin
// Minimum voltage so that Input High is guaranteed with 0.9VCC for VCC in the range 1.8V - 5.5V

// MicroCore - lightweight Arduino hardware package for ATtiny13
// https://github.com/MCUdude/MicroCore
// The file core_Settings.h can be modified for configurations
// For this sketch at least "#define ENABLE_MICROS" has to be commented out (as it uses also --> ISR(TIM0_OVF_vect))

// ATTINY13 pinout (partly)
//                   *--------
//     RESET PB5 ===|1   P   8|=== VCC
//           PB3 ===|2   I   7|=== PB2
//           PB4 ===|3   N   6|=== PB1
//           GND ===|4   S   5|=== PB0
//                   ---------

// *****************************
// ***** global variables ******
// *****************************

volatile uint8_t timer_ovf = 0;
volatile uint8_t count = 0;
volatile uint16_t pulses = 0;

#define position_lights PB1
#define landing_lights PB2
#define strobe_lights PB3
#define taxi_light PB4
#define receiver PB0

// *****************************
// ****** initialization *******
// *****************************
void setup() {

  // ****************************
  // ***** setup interrupts *****
  // ****************************

  cli();   // disable global interrupts

  // General Interrupt Mask Register (page 46)
  GIMSK |= (1 << PCIE);   // generally enable pin change interrupt
  // Pin Change Mask Register (page 47)
  PCMSK |= (1 << PCINT0);   // enable pin change interrupt on PB0 (pin 5)
  // Timer/Counter Interrupt Mask Register (page 74)
  TIMSK0 |= (1 << TOIE0);   // Timer/Counter0 Overflow Interrupt Enable

  // ****************************
  // ******* setup timer ********
  // ****************************
  // The I/O clock (clkI/O) has always the same frequency as the CPU clock.
  // The "I/O clock" may get further divided in individual modules such as timer modules.

  // GTCCR – General Timer/Counter Control Register (page 77)
  GTCCR |= (1 << TSM) | (1 << PSR10);   // halt timer
  // set prescaler for timer (page 73)
  TCCR0B |= (1 << CS01);   // select internal system clock (clkI/O) and set prescaler to 8
  GTCCR &= ~(1 << TSM);   // start timer

  // ****************************
  // ******* setup PINS *********
  // ****************************

  // PCMSK – Pin Change Mask Register
  PCMSK |= (1 << PCINT0); // pin change interrupt enabled for pin 5
  // DDRB – Port B Data Direction Register
  // set position_lights as output (PB1, pin 6)
  DDRB |= (1 << position_lights);
  // PORTB – Port B Data Register
  // switch on position_lights (set PB1 to HIGH=5V)
  PORTB |= (1 << position_lights);

  // set receiver pin as input (PB0, pin 5)
  DDRB &= ~(1 << receiver);   // set logical 0

  // set Global Interrupt Enable
  // should be last statement wrt interrupts because:
  // When using the SEI instruction to enable interrupts,
  // the instruction following SEI will be executed before any pending interrupts
  sei();

} // end setup

// **********************************************
// ***** main program (loops over and over) *****
// **********************************************

void loop() {

  // with clock set to 9,6 MHz and prescaler of 8
  // 0.950ms = 1140 pulses, 1.5ms = 1800 pulses, 2.05ms = 2460 pulses

  // RC signal is closer to maximum
  if (pulses > 2100) {
    PORTB &= ~(1 << position_lights);
  }
  // RC signal is closer to minimum
  if (pulses < 1400) {
    PORTB |= (1 << position_lights);
  }

} // end loop

// **********************************************
// ***** ISR - interrupt service routine(s) *****
// **********************************************

// called when an interrupt enabled pin changes its state
ISR(PCINT0_vect){
  // here we check the level of our pin of interest
  // if it is HIGH, a rising edge interrupt has happened
  // if it is LOW, a falling edge interrupt has happened

  // PINB – Port B Input Pins Address (page 57)
  // check if PB0 (receiver) bit is 1 (HIGH)
  if (PINB & (1<<receiver)) {
    timer_ovf = 0;   // reset timer overflow counter at rising edge pin change interrupt
    // TCNT0 – Timer/Counter Register (page 73)
    TCNT0 = 0;   // initialize counter register at rising edge pin change interrupt
  }
  // otherwise PB0 (receiver) bit is 0 (LOW)
  // can also be checked like: if ( !(PINB & (1<<PB0)) ) {...}
  else if ( !(PINB & (1<<receiver)) ) {
    // TCNT0 – Timer/Counter Register (page 73)
    count = TCNT0;   // use counter register at falling edge pin change interrupt
    // time between rising and falling edge (pulse width)
    // count number of timer overflows
    // multiply them by 256 (if we have an 8-bit counter)
    // add the timer counts from beginning of last overflow
    pulses = timer_ovf * 256 + count;
  }
} // end ISR pin change interrupt

// called when a timer overflow occurs
ISR(TIM0_OVF_vect){
  timer_ovf++;   // count timer overflows since reset in rising edge pin change interrupt
} // end ISR timer overflow

The code section 

if (pulses > 2100)

should not be reached when I do nothing on the remote control, as the pulses then always should be less than this value.

Obviously my logic is wrong somewhere.

 

Best regards,

Andreas

 

This topic has a solution.
Last Edited: Wed. Jan 24, 2018 - 08:12 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

When I change the threshold from:

if (pulses > 2100) {

to

if (pulses > 2200) {

it seems to work. Still it does not fit to my timer calculations ...

With clock at 9.6MHZ and prescaler of 8 I get 1.2MHZ timer frequency. This is 8.33e-7 seconds or 0.833 micro-seconds.

The neutral position of the RC stick sends pulses with 1500 micro-seconds width, which makes 1800 counts with that timer.

There fit 7 overflows with each 256 counts into that pulse which makes 1792. A rest of 8 counts gives than ideally 1800. 

So I set thresholds at 1400 and 2100 to stay away safe from the neutral position.

 

I miss here something, that I do not understand.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Please post a schematic of your circuit, or at least a clear photo.

 

Jim

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I see no definition of F_CPU in your software.

I do not know what kind of assumptions "arduino" makes about your clock frequency.

 

9.6MHz is an uncommon value.

9.621 Crystals are very common. Could your calculations be off?

 

If you do not have an oscilloscope / frequency meter you can check the actual clock frequency with a stopwatch and a led.

Make it blink at 1Hz (in software) or so and then verify after timing a minute.

 

The pulse withs for (especially the cheap) RC servo's als tend to have quite a big tolerance.

Paul van der Hoeven.
Bunch of old projects with AVR's:
http://www.hoevendesign.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The Attiny13 datasheet says that the CPU runs at 9.6MHz. With a certain accuracy guaranteed (10%) from factory, but I could be fine tuned (which I did not).

The frequency is set via the ARDUINO IDE. I installed as an addon the MicroCore hardware extension. This allows to select the frequency from the IDE and respective fuses can be set.

 

I tried to measure the timer frequency now with the oscilloscope.

For this I used the timer overflow ISR and there toggled a pin.

 

If I am not mistaken, then:

frequency of timer = 2.47kHz (frequency measured at pin) * 256 (overflows occur each 256th step) * 2 (to consider toggling)

 

With this I get 1,264 MHz which would be around 5% error wrt the nominal 1,2MHz (9.6MHz and prescaler of 8).

 

This would somehow fit to my expectations. It would not explain (at least to me), that the threshold is not working.

 

Attached is also a photo of my setup.

 

Last Edited: Tue. Jan 23, 2018 - 09:17 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

No decoupling capacitors AT the chip.

"This forum helps those that help themselves."

"If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Nice layout, with led resistors!   But all AVR's need 0.1uf (100nf) cap between VCC and GND pins with as short of leads as possible (difficult to do with DIP8 packages!)

 

Jim

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I added the capacitor. What you see is my second try. In the first try I made it smoking :)

I change the chip, reprogrammed the fuses, checked the frequency again.

No change at all.

My capacitor is something like 400nF since I have no 100nF at the moment. Does this matter a lot?

 

 

Andreas

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, before I looked at the photo, I wondered how a capcitor could smoke... then I noticed it was an expensive chemical one (when polarity is reversed, it burnt in the 1980s; thought they improved). Some Al electrolytical capacitors can have a high impedance at high frequencies (being unfit for decoupling) ... but I am not sure with your Ta capacitor (on modern cards, electrolytic tantalum capacitors  are paralleled with ceramic 100 nf, much cheaper).

Last Edited: Tue. Jan 23, 2018 - 07:51 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think you need to clear the TIM0_OVF interrupt flag at the same time you clear TCNT0 and timer_ovf. Otherwise, you'll occasionally register an extra 256 pulses.
 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Is that capacitor a tantalum? There is a lot of smoke in those...

You'll need ceramic for better High frequency decoupling.

 

And if you happen to have a hot solering iron nearby: I tend to solder the decoupling caps directly on top op the AVR's I use on breadboards.

You only have to do that once for an avr and it saves room on your breadboard.

 

Because you have 2 interrupts and an AVR can only handle 1 they have to wait for each other (occasionally).

This introduces jitter in the timing.

 

You can avoid that by using the timer capture / compare registers to save the internal timer value when an external event happens.

Combine that with your interrupt.

chiefenne wrote:
I try to measure the signal from the receiver (PWM, duration 21ms, pulse between 1ms and 2ms, level 3,4V)
  This sounds very much like an RC servo. Very old protocol in which they "waste" 80 percent of the time resolution. To avoid making it worse you want high resolution on your timer capture. Use the capture registers.

 

For debugging it is also very handy to add a serial monitor to your AVR.

You can convert captured pulse widhts to ascii and send them to a terminal emulator.

You can even copy and paste them in a spreadsheet, draw graphs.

This gives you a lot more info of what is actually going on than a single led.

 

If your small 8 pin AVR does not have a uart, then get a bigger one for software development.

Go back to the mini version after the code is debugged.

Paul van der Hoeven.
Bunch of old projects with AVR's:
http://www.hoevendesign.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Is that capacitor a tantalum? There is a lot of smoke in those...

Not that much in the 1980's...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I followed now the advice of balisong42. I cleared the timer overflow flag by writing a logic one to it (as per the datasheet). I did that in the pin change ISR where the other parameters are being reset.

TIFR0 |= (1<<TOV0);

This did the job! Thank you for the answer.

 

Thanks also to all your comments and elaborate answers. I'll consider also Paulvdh's suggestions to have a variant of the now working setup and to better understand the AVR capabilities. I'll also check the serial monitor (I have plenty of different AVRs).

 

Thank you.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
TIFR0 |= (1<<TOV0);

This did the job! Thank you for the answer.

By the way, the correct way to do this is:

 

TIFR0 = (1<<TOV0);

Otherwise you can clear every other pending interrupt flag in that register.  While in your case you're only using one timer interrupt, it's a bad habit to get into.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Otherwise you can clear every other pending interrupt flag in that register

The first statement writes a one to the desired bit, but also includes any adjacent "ones" it read from the register.   Normally this would be what is desired.

The second statement only writes one "1" to a specific, desired, bit &"attempts" to set the others to zero.  Normally this would be bad news ("why do my bits get reset when I set a bit") 

 

That's because the irq flag register is "weird"  ...writing a one clears a flag bit, writing zero does nothing.

if it had 1010 1011 and you wrote 0000 0111 to such a register, it will not read back as 0000 0111 like you'd normally expect....instead it would read  1010 1000

 

 

When in the dark remember-the future looks brighter than ever.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That's because the irq flag register is "weird"

Not weird, no.  But yes, that was my point ;-)  It works for the OP now, but in some future app when he tries this again, if he's using more than one timer interrupt with flags in the same register, it won't work correctly and he'll scratch his head s to why.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Not weird, no.  But yes, that was my point

Well it is a bit strange compared with standard writes.....the flag bits you write as zero may end up being one afterwards.  The flag bits you write as one will be zero afterwards...so the newbie might be scratching their head a bit.

When in the dark remember-the future looks brighter than ever.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

the flag bits you write as zero may end up being one afterwards

Not by the action of writing them.

 

the newbie might be scratching their head a bit.

That is certain.  Plenty of evidence.

 

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]