ATtiny3217: RTC won't wake CPU from sleep if sleep is induced from pin interrupt vector.

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

I hope the title makes sense, I'm a newbie at this...

 

If I put the MCU (ATtiny3217) to sleep in the main() function it will sleep until the RTC period times out, wake up and continue executing the code placed after sleep_mode(). If I do that from the ISR(PORTA_PORT_vect) it won't wake up at all and I can't figure out why. I have this working fine on the ATtiny1634 using WDT interrupt (not reset) to wake up.

 

Background: This is flashlight firmware with a mechanical switch for on/off. I have the flashlight do different things depending on how long the power was out. When power is out the MCU is running from a 100uF cap. With the 1634 I have a pin interrupt that triggers when the pin goes low (the pin is connected to V+ and has a pull down resistor). The interrupt code turns off the pin interrupt, shuts down peripherals and does the following loop until power is restored (pin is high) or BOD induces reset:

 

ISR(PCINT2_vect) {
    PINT_Stop();
    PORTA = 0;
    PORTB = 0;
    PORTC = 0;
    PRR = 0b01101111;       // Everything off in Power Reduction Register except Timer/Counter0.

    cli();
    MCUSR = 0;              // Clear MCU status register.
    CCP = 0xD8;             // Enable Watchdog configuration change.
    WDTCSR = 0;             // Disable and clear all Watchdog settings.

    OffTime = 0;

    do {
        OffTime++;
        set_sleep_mode(SLEEP_MODE_PWR_DOWN);
        CCP = 0xD8;                         // Enable Watchdog configuration change.
        WDTCSR = (1 << WDIE) | 0b011;       // Setup Watchdog for interrupt (not reset) every 125ms.
        sleep_mode();
        cli();
    }
    while (!POWER);         // Repeat until power back or BOD resets.
}

 

If power comes back before BOD reset I just check OffTime integer to find out how long the light was off. With 100uF cap I can get about 10 seconds before BOD resets, and that's more than enough (I do software reset if over 3 seconds). It's working very well on my 1634 based driver boards, but with the 3217 I can't get this sleep loop working in the pin interrupt vector. It will not wake up once put to sleep. If i call that same code in the main() loop it works fine, it goes to sleep, wakes up and continues running the next instructions. There is obviously something I'm doing wrong and I haven't been able to figure it out. I've been reading the datasheet but to be honest a lot of it is above me.

 

I have attached my stripped down code I'm using for testing with the 3217. I'm using a momentary switch to ground the "power detection" pin, and my simple sleep test routine does a short LED flash, sleep, long LED flash, sleep. I'm using SLEEP_MODE_IDLE for this test. If I call my sleep test routine from the main() loop it works fine, but if I call it from the pin interrupt vector it will not wake up from sleep after that first blink. Does anyone have any suggestions on how to solve this?

 

Attachment(s): 

This topic has a solution.
Last Edited: Tue. Jun 11, 2019 - 09:26 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Akrylamid wrote:
If I do that from the ISR(PORTA_PORT_vect) it won't wake up at all and I can't figure out why
Really? But you can't sleep in an ISR(). When the CPU enters an ISR it disables the I bit in SREG. So no more interrupts can now occur. If you then go to sleep the only way to awaken is by an interrupt happening. But it cannot because I is disabled. Do not be tempted to just sei() in the ISR() - that leads to all kinds of other problems.

 

Suggest you rethink your design - do all your sleeping from non-interrupt code. Interrupts MUST be enabled before you enter SLEEP.

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

clawson wrote:
But you can't sleep in an ISR().

So this is a limitation with the 1-series? It is working perfectly with the 1634 and the 85.

Last Edited: Wed. Jun 5, 2019 - 12:46 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ah, do these new AVRs have the four level interrupt thing like Xmega or are they like proper tiny/AVR? I was (possibly in error) assuming these were like the real AVRs but, yeah, if they do the Xmega-like PMIC thing then they probably don't clear SGEG-I on the way in - sorry for the noise.

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

Well, you have given me some clues that I can look into, thanks.

 

About redesigning, is there an option I could look into that doesn't put the MCU to sleep so I can do off time measuring within ISR vector code? I'd like to measure ~10 sec off time with 100uF cap. Putting my off time measuring in the main loop would be too painful, I use off time measurement much like momentary switch press time measurement (as discussed in the "Detecting INT1 switch short vs long press" thread).

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

Maybe it's an interrupt priority problem?

On classic AVRs, ISRs can interrupt each other at will (so long as interrupts are enabled), but on AVR-0/1 this may be different. Maybe the RTC just can't interrupt the PORT ISR because it doesn't have priority to do that.

 

You can give high priority to one interrupt using the CPUINT.LVL1VEC register on the interrupt controller peripheral.

 

edit: Yup, as clawson suspected in post #4, these MCUs _do not_ clear the global interrupt flag on entering the ISR. Interrupts are temporarily suspended by the interrupt controller until a RETI instruction is executed (see the CPUINT.STATUS register for details).

So you can either give high priority to the RTC interrupt, so that it can interrupt normal priority ISRs, or do dirty assembly tricks with PUSH and RETI (highly unrecommended).

Last Edited: Wed. Jun 5, 2019 - 04:21 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Akrylamid wrote:
It is working perfectly with the 1634 and the 85.

Please show the short test program for e.g. Tiny85 that awakens from sleep initiated in an ISR without allowing global interrupts.

 

If the answer is "well, yeah, I am doing SEI in the ISR before sleeping" then tell how you will avoid windup in the general case.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

El Tangas wrote:
So you can either give high priority to the RTC interrupt, so that it can interrupt normal priority ISRs

Thanks for the tip but I have been unable to get it to work. Within the pin ISR on the 1634 I could start the WDT, wait for it to complete a cycle, stop it and repeat the process until power is detected, all within pin ISR code. With the 3217 it halts on the waiting for RTC to complete a cycle. The datasheet does say "An interrupt request from a high priority source will interrupt any ongoing interrupt handler from a normal priority source. When returning from the high priority interrupt handler, the execution of the normal priority interrupt handler will resume." I can enable RTC interrupt from within pin ISR, but I can't enable RTC, wait for it to complete a cycle and then continue within the pin ISR.

 

theusch wrote:
Please show the short test program for e.g. Tiny85 that awakens from sleep initiated in an ISR without allowing global interrupts.

I checked my old 85 code and have to apologize. I was not using pin interrupt to start WDT on the 85, I was only doing that with the 1634. Sorry about that.

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

Akrylamid wrote:
An interrupt request from a high priority source will interrupt any ongoing interrupt handler from a normal priority source.
So whatever interrupt is going to be used to wake it from a sleeping interrupt needs to be set at a higher priority than the interrupt where it does SLEEP.

 

EDIT: I'm looking at sleeptest.c from #1 and I don't see anything about priorities.

 

I am trying to follow the logic. So you seem to have a PORT interrupt and then within that you start the RTC then sleep. So presumably the idea is that the RTC interrupt is going to interrupt the PORT interrupt? I can't see anything there about priorities?

 

EDIT2: You need to read chapter 13 in the datasheet about CPUINT. By default all interrupts are the same priority so one will not interrupt another.

 

EDIT3: OK so if I have got this right it seems you can nominate ONE of the vectors as high priority over the normal ones and you write its vector number in the CPUINT.LVL1VEC register. - this thread  https://www.avrfreaks.net/forum/attiny817-and-interrupt-priority-level-1-cpuintlvl1vec seems to confirm that is how you do it (it's the vector NUMBER you put into the LVL1VEC register)

 

What I don't yet understand is how all this works for you on the 1634? Surely it's the same architecture (in the interrupt department)

 

EDIT4: OK so now I am confused - the 1634 (it turns out) is one of the "old" AVRs with single level interrupts so the only way sleeping in an ISR could work for those is with a preceding sei() but that brings in all the usual dangers about stack nesting. So how on earth did you do sleep in an ISR() for the 1634 ??

Last Edited: Mon. Jun 10, 2019 - 02:15 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
(it's the vector NUMBER you put into the LVL1VEC register)

 

Man, the datasheet says address, not number. Those datasheets are a minefield!

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

Yup, that was basically what that linked thread was all about.

 

Funny thing is that I "skim read" chapter 13 before posting and I'd already determined it was likely to be the vector number not address. Not sure how I concluded that? Perhaps just intuition on the basis that "it must be this" perhaps?

 

EDIT: no actually, it's because it says different things in different places. For the actual register description (which is what I read first) it says:

This bit field contains the number of the single vector with increased priority level 1 (LVL1).

(which I take to be "number of vector") but then the confusing text elsewhere is:

Select the priority level 1 vector by writing its address to the Interrupt Vector (LVL1VEC) in the Level 1 Priority register (CPUINT.LVL1VEC)

Last Edited: Mon. Jun 10, 2019 - 03:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Its actually wanting the word address (it wouldn't be right if they didn't mix in a word address problem somewhere)-

 

my c++ 'driver' which sets/gets the level1 priority-

https://github.com/cv007/ATTiny416XplainedNano/blob/master/Cpuint.hpp

(edit, changed git link- I'm in a git fight at the moment)

 

I'm using MPLABX, c++ templates, and a nano board, so debugging is a bit of a problem (not pleasant). I could not get level1 priority to work until I took 'address' to mean 'address'. I used the vector number <<1 to put into the register, and the register value >>1 to get the vector number, and it started to work.

 

I'm using only a single universal isr handler that calls function pointers (can get current irq from the level0 priority reg with round robin enabled). I currently have a level1 priority set for the tca0 cmp irq, and its working ok, so I either have it right, or I have it working by mistake.

 

You can wakeup from a rtc pit irq from deep sleep when inside another isr when the rtc pit irq priority is level 1, it seems (I briefly tested it).

 

Last Edited: Tue. Jun 11, 2019 - 02:12 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:
Its actually wanting the word address (it wouldn't be right if they didn't mix in a word address problem somewhere)-

 

Are you sure? Did you try in chips other than the t416? I'm asking, because the t416 has 1 word vectors, since this is enough for 8kB memory or less, but the chips with more memory need a 2 word vector, right?

 

If the vector is 1 word, then word address = vector number, but if it's 2 words, the values will not coincide. This needs to be tested.

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

>Are you sure?

 

Not anymore. I have to test more- both my 416nano and 4809nano boards. I'm starting to think I'm all mixed up now. Back to square one.

 

edit-

 

OK, I think I'm half right. I' using both boards at the same time- most code is duplicated, much of the datasheets are the same, and I get confused as to which one I'm currently using. The tiny416 uses the vector number priority as the level1 priority value, only because it is the same as the vector word address as there is one word per vector (rjmp). The mega4809 also needs the 'address', but of course in this case the vectors are 2 words so the vector number needs to be doubled when storing its value in the level1 priority register.  I have tested both boards, and have verified I'm getting the level1 irq's to fire (LVL1EX bit set), and am getting to the isr's correctly. What I mistook for success on the tiny416 was simply the fact that the wrong register value was being set and the irq was simply using level0 and somehow overlooked I was never in a level1 isr.

 

I think I'm 50% certain I'm right now.

 

Last Edited: Tue. Jun 11, 2019 - 03:57 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
it's the vector NUMBER you put into the LVL1VEC register

Now it's working. I set number 6 instead of address 0x0C and now after RTC cycle is completed the remaining code in pin ISR is executed. Thanks to all for helping me out with this!

clawson wrote:
So how on earth did you do sleep in an ISR() for the 1634 ??

I've updated the code patch in the OP. It's a pin interrupt that's triggered when the power detection pin goes low. In main() I'm checking that OffTime integer and can see if and for how long power was off.

Last Edited: Tue. Jun 11, 2019 - 09:29 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:
The mega4809 also needs the 'address', but of course in this case the vectors are 2 words so the vector number needs to be doubled when storing its value in the level1 priority register.

 

Well, then you are right, it's the vector word address that needs to be stored, this coincides with the vector number for low memory chips, but it's 2x vector number for chips with >8kB flash.

 

So if you want to give high priority to, for example, vector 2, you put 2 in the priority register for lets say, a tiny414, and 4 for a mega4809. Is that right?

 

edit: No it can't be right because the OP just got it working using the vector number on a tiny3217 which has 32kB flash. Probably I'm confused with your wording, so I will have to test for myself.

Last Edited: Tue. Jun 11, 2019 - 11:42 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:
No it can't be right because the OP just got it working using the vector number on a tiny3217 which has 32kB flash. Probably I'm confused with your wording, so I will have to test for myself.

I don't know if this helps but with the 3217/1617 the base address is the vector number doubled.

 

Last Edited: Tue. Jun 11, 2019 - 12:37 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Akrylamid wrote:
I don't know if this helps but with the 3217/1617 the base address is the vector number doubled.

 

Yeah, but we never know if they mean byte or word address, do we? I think even the datasheet writers sometimes don't know themselves...

I just ignore it and test. So, this is an excerpt of the vector table for a t3217. The addresses are byte, the compiler generates rjmp if it can (I'm using -mrelax optimize option) but there is room for a full jmp if needed.

There are 4 bytes per vector, so the vector byte addresses are 0, 4, 8 and so on:

Disassembly of section .text:

00000000 <__vectors>:
   0:	3d c0       	rjmp	.+122    	; 0x7c <__ctors_end>
   2:	00 00       	nop
   4:	43 c0       	rjmp	.+134    	; 0x8c <__bad_interrupt>
   6:	00 00       	nop
   8:	41 c0       	rjmp	.+130    	; 0x8c <__bad_interrupt>
   a:	00 00       	nop
   c:	3f c0       	rjmp	.+126    	; 0x8c <__bad_interrupt>
   e:	00 00       	nop

 

And this is an excerpt for t817. You can see that now the vectors are just 2 bytes in size, and their byte addresses are 0, 2, 4 and so on:

Disassembly of section .text:

00000000 <__vectors>:
   0:	19 c0       	rjmp	.+50     	; 0x34 <__ctors_end>
   2:	20 c0       	rjmp	.+64     	; 0x44 <__bad_interrupt>
   4:	1f c0       	rjmp	.+62     	; 0x44 <__bad_interrupt>
   6:	1e c0       	rjmp	.+60     	; 0x44 <__bad_interrupt>
   8:	1d c0       	rjmp	.+58     	; 0x44 <__bad_interrupt>

 

It so happens that word addresses are half the byte addresses, so they happen to coincide with vector numbers for the t817, but not for the t3217.

Last Edited: Tue. Jun 11, 2019 - 01:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As noted elsewhere, if it's an<=8K micro with RJMP sized vectors then number=address. I think the text was probably originally written for such a micro so the author used the terms interchangeably. Later the same (copy/paste) text was used for >= 16K where the assertion no longer holds true.

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

>Well, then you are right, 

 

Please ignore me, I am not helping.

 

I have MPLABX showing me register values that do not refresh properly, and so on. I am not getting good debug info, and what I think is working, only works because the failure mode for not setting the level1 correctly is a functioning level0 interrupt.

 

So I just used the uart to print register values inside the isr's (this is a mega4809)-

https://github.com/cv007/ATmega4809CuriosityNano/blob/master/test-sleep.cpp

simple test- press button, go to isr, print info, done

 

If I just put the vector number as-is into LVL1VEC, it works properly. Contrary to what I said earlier. I'm not sure what I was doing wrong before.

I'm not sure why they need to confuse us in the datasheet by misusing words. This is not the only example.

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

 

Yeah, and they did a copy paste of the entire vector table from the <8kB chips too (compare with the one on #17, the addresses are the same, which is not correct):

 

 

edit: But well, what matters is that it is established, now, that what goes into the priority register is the vector number no matter the memory size, and not the vector word address.

This could not be concluded for sure from the previous thread (https://www.avrfreaks.net/forum/...) because an 8kB chip was used (meaning vector number = word address in that case).

Last Edited: Tue. Jun 11, 2019 - 01:43 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:

 

my c++ 'driver' which sets/gets the level1 priority-

https://github.com/cv007/ATTiny416XplainedNano/blob/master/Cpuint.hpp

(edit, changed git link- I'm in a git fight at the moment)

 

 

Sorry everyone for a bit of OT.

 

I took a look at your code, but was having some trouble compiling. The problem is that on your "Nvmctrl.hpp" header, you have lines like:

 

static constexpr vu8ptr m_flashbase{ (vu8ptr)FLASHBASE  };

As you know, this is equivalent to

static constexpr vu8ptr m_flashbase{ reinterpret_cast<vu8ptr>(FLASHBASE)  };

Unfortunately, casting integral types to pointers like this in constexpr statements is not allowed by the C++ standard, but older gcc versions do it anyway. And it does seem pretty harmless if you ask me, but the standards people beg to differ.

The problem is when you use a newer version of avr-gcc, like I do, this will give errors (as it should to follow the standard). This thread discusses the fixing of this rather useful bug: https://gcc.gnu.org/bugzilla/sho...

There are probably some workarounds, for example: https://arne-mertz.de/2017/06/st...

 

But me, with my low-level language background, just opted for horribly manglingcheeky your elegant code by replacing the offending lines like this:

 

        //static constexpr vu8ptr m_flashbase{ (vu8ptr)FLASHBASE  };
#       define m_flashbase ((vu8ptr)(FLASHBASE))

 

IMO, you should look into this so that your program complies with the C++ standard.

 

edit: example error using avr-gcc 9.1.0

error: 'reinterpret_cast<Nvmctrl::vu8ptr {aka volatile unsigned char*}>(Flash::FLASHBASE)' is not a constant expression

 

Last Edited: Tue. Jun 11, 2019 - 04:26 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>The problem is when you use a newer version of avr-gcc

 

That will be a while, I'm sure. The versions I use always seem to be old- avr-gcc from xc8, xc32 for pic32mm, etc. I think the latest of anything I have in for arm (samD) which is 7.something I think.

 

I removed the 'problem' in Nvmctrl.hpp, but may have simply moved it somewhere else (using my Reg class templates).

The options I add to MPLABX are simply-> -std=c++14, change gcc/g++ to -Os, add a build step to get an objdump listing.

 

If I can compile without errors (and it works correctly), I am happy. When I get to the next bridge, I'll cross it :)

 

edit-

Now I have any problems sitting in just one file- Reg.hpp

I think I need to only change usage in that file from-

   *(vu8ptr)r

to

  *(reinterpret_cast<vu8ptr>(r))

 

at least that works when I use godbolt.org online compiler with gcc 9.1 (x64)

https://godbolt.org/z/-VgPJ7

 

I'm not a language lawyer, so I search for solutions to problems on an as needed-basis. I can use a fraction of c++, and certainly do not know all the rules. I find it amazing anyone can know the c++ (or c to a lesser extent) language thoroughly AND also keep track of the rules. Me- compiler complains, me fix. Next. Me- I want to do X to make easier/better, search for answer, implement, compiler complains, repeat until it works or until I give up.

Last Edited: Tue. Jun 11, 2019 - 05:56 PM