1
\$\begingroup\$

I noticed that the Delay.h wasn't working well in a project when used with an ATtiny45, so I've made a small piece of code to test where I went wrong with my coding. I've just made a blinky-LED program on PB3 which goes on for 200 ms and off for 500 ms.

#define F_CPU 1000000
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    DDRB |= (1 << PB3);
    while (1) 
    {
        PORTB |= (1 << PB3);
        _delay_ms(200);
        PORTB &= ~(1 << PB3);
        _delay_ms(500);
    }
}

What I've noticed is that the LED goes on for 200 ms and then the LED goes off for a very, very short time. After a lot of testing, searching, and failing I've figured out that the problem could be that the linking to delay.h isn't working well.

The problem is you are creating an object file, but not linking it. Without linking, branch instructions in the machine code are not filled with the required destination addresses, so the code simply executes linearly until it 'falls off the end' into unallocated memory. Eventually it reaches the end of memory and wraps around to zero to execute your code again. The end result is the LED flashes with a very low duty cycle.

(Source: Delay doesn't work (avr attiny 26, delay.h))
(Another post with same issues: Using avr-gcc _delay_ms causes chip to freeze)

Now I've been using (Atmel &) Microchip Studio for 5 or 6 years to make all kind of small ATtiny projects. There are some features I don't know a lot about, but everything always worked without any problems.

I've started a few new projects to be sure I didn't change any settings or stuff like that, but it always failed with an ATtiny45.

Now the strange part is, when I started a new project with an ATtiny461 (and used an ATtiny461 ofcourse), and used this code to try it on, it worked without any problems. After that, I tried to change the device to an ATtiny45 and it failed again, and reverting back to a 461 made it work again.

  • Can anyone explain why it's not working on one device and does work on the other?
  • Is my assumption correct that this Delay.h doesn't work on an ATtiny45??
  • The links above are talking about a linker-file, but as said before, these are the features I'm not familiar with. If this is the problem, where or how should I change this? And how come there's a difference between the two devices?

Also, in the project-properties>ToolChain>AVR/GNU C Compiler: The compile-string is the same (with the exception of Attiny45<->Attiny461)

Extra info:
I'm using Microchip Studio to write (in C) and compile the whole thing and I'm using AVRDUDESS to write the .hex via USBasp to the Attiny.

Edit:
As @Martin said/asked, the MCU restarts (I didn't put that clear in the question). I had done some tests with different times and an input to trigger different scenarios, but it clearly restarted every time.

Below you can find the assembler code (well the .lss, but I hope it's what you asked for). I've put them also in a compare-file and the differences don't seem too big (as far as I understand everything).
The only thing I see what could be something is the fact that with the ATtiny45 the 5th last line says:

60: ea cf           rjmp    .-44        ; 0x36 <main+0x6>

which could mean to restart the MCU? (assumptions, assumptions.)

Attiny45:

JustANotherTestProject.elf:     file format elf32-avr

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000066  00000000  00000000  00000054  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  00800060  00800060  000000ba  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .comment      00000030  00000000  00000000  000000ba  2**0
                  CONTENTS, READONLY
  3 .note.gnu.avr.deviceinfo 0000003c  00000000  00000000  000000ec  2**2
                  CONTENTS, READONLY
  4 .debug_aranges 00000020  00000000  00000000  00000128  2**0
                  CONTENTS, READONLY, DEBUGGING
  5 .debug_info   0000056c  00000000  00000000  00000148  2**0
                  CONTENTS, READONLY, DEBUGGING
  6 .debug_abbrev 000004dc  00000000  00000000  000006b4  2**0
                  CONTENTS, READONLY, DEBUGGING
  7 .debug_line   0000022c  00000000  00000000  00000b90  2**0
                  CONTENTS, READONLY, DEBUGGING
  8 .debug_frame  00000024  00000000  00000000  00000dbc  2**2
                  CONTENTS, READONLY, DEBUGGING
  9 .debug_str    00000336  00000000  00000000  00000de0  2**0
                  CONTENTS, READONLY, DEBUGGING
 10 .debug_loc    00000048  00000000  00000000  00001116  2**0
                  CONTENTS, READONLY, DEBUGGING
 11 .debug_ranges 00000010  00000000  00000000  0000115e  2**0
                  CONTENTS, READONLY, DEBUGGING

Disassembly of section .text:

00000000 <__vectors>:
   0:   0e c0           rjmp    .+28        ; 0x1e <__ctors_end>
   2:   15 c0           rjmp    .+42        ; 0x2e <__bad_interrupt>
   4:   14 c0           rjmp    .+40        ; 0x2e <__bad_interrupt>
   6:   13 c0           rjmp    .+38        ; 0x2e <__bad_interrupt>
   8:   12 c0           rjmp    .+36        ; 0x2e <__bad_interrupt>
   a:   11 c0           rjmp    .+34        ; 0x2e <__bad_interrupt>
   c:   10 c0           rjmp    .+32        ; 0x2e <__bad_interrupt>
   e:   0f c0           rjmp    .+30        ; 0x2e <__bad_interrupt>
  10:   0e c0           rjmp    .+28        ; 0x2e <__bad_interrupt>
  12:   0d c0           rjmp    .+26        ; 0x2e <__bad_interrupt>
  14:   0c c0           rjmp    .+24        ; 0x2e <__bad_interrupt>
  16:   0b c0           rjmp    .+22        ; 0x2e <__bad_interrupt>
  18:   0a c0           rjmp    .+20        ; 0x2e <__bad_interrupt>
  1a:   09 c0           rjmp    .+18        ; 0x2e <__bad_interrupt>
  1c:   08 c0           rjmp    .+16        ; 0x2e <__bad_interrupt>

0000001e <__ctors_end>:
  1e:   11 24           eor r1, r1
  20:   1f be           out 0x3f, r1    ; 63
  22:   cf e5           ldi r28, 0x5F   ; 95
  24:   d1 e0           ldi r29, 0x01   ; 1
  26:   de bf           out 0x3e, r29   ; 62
  28:   cd bf           out 0x3d, r28   ; 61
  2a:   02 d0           rcall   .+4         ; 0x30 <main>
  2c:   1a c0           rjmp    .+52        ; 0x62 <_exit>

0000002e <__bad_interrupt>:
  2e:   e8 cf           rjmp    .-48        ; 0x0 <__vectors>

00000030 <main>:
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    DDRB |= (1 << PB3);
  30:   87 b3           in  r24, 0x17   ; 23
  32:   88 60           ori r24, 0x08   ; 8
  34:   87 bb           out 0x17, r24   ; 23
    while (1) 
    {
        PORTB |= (1 << PB3);
  36:   88 b3           in  r24, 0x18   ; 24
  38:   88 60           ori r24, 0x08   ; 8
  3a:   88 bb           out 0x18, r24   ; 24
    #else
        //round up by default
        __ticks_dc = (uint32_t)(ceil(fabs(__tmp)));
    #endif

    __builtin_avr_delay_cycles(__ticks_dc);
  3c:   8f e4           ldi r24, 0x4F   ; 79
  3e:   93 ec           ldi r25, 0xC3   ; 195
  40:   01 97           sbiw    r24, 0x01   ; 1
  42:   f1 f7           brne    .-4         ; 0x40 <__SREG__+0x1>
  44:   00 c0           rjmp    .+0         ; 0x46 <__SREG__+0x7>
  46:   00 00           nop
        _delay_ms(200);
        PORTB &= ~(1 << PB3);
  48:   88 b3           in  r24, 0x18   ; 24
  4a:   87 7f           andi    r24, 0xF7   ; 247
  4c:   88 bb           out 0x18, r24   ; 24
  4e:   9f e9           ldi r25, 0x9F   ; 159
  50:   26 e8           ldi r18, 0x86   ; 134
  52:   81 e0           ldi r24, 0x01   ; 1
  54:   91 50           subi    r25, 0x01   ; 1
  56:   20 40           sbci    r18, 0x00   ; 0
  58:   80 40           sbci    r24, 0x00   ; 0
  5a:   e1 f7           brne    .-8         ; 0x54 <__SREG__+0x15>
  5c:   00 c0           rjmp    .+0         ; 0x5e <__SREG__+0x1f>
  5e:   00 00           nop
  60:   ea cf           rjmp    .-44        ; 0x36 <main+0x6>

00000062 <_exit>:
  62:   f8 94           cli

00000064 <__stop_program>:
  64:   ff cf           rjmp    .-2         ; 0x64 <__stop_program>

Attiny461:


YetANotherTest.elf:     file format elf32-avr

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000006e  00000000  00000000  00000054  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  00800060  00800060  000000c2  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .comment      00000030  00000000  00000000  000000c2  2**0
                  CONTENTS, READONLY
  3 .note.gnu.avr.deviceinfo 0000003c  00000000  00000000  000000f4  2**2
                  CONTENTS, READONLY
  4 .debug_aranges 00000020  00000000  00000000  00000130  2**0
                  CONTENTS, READONLY, DEBUGGING
  5 .debug_info   000005fc  00000000  00000000  00000150  2**0
                  CONTENTS, READONLY, DEBUGGING
  6 .debug_abbrev 00000564  00000000  00000000  0000074c  2**0
                  CONTENTS, READONLY, DEBUGGING
  7 .debug_line   00000244  00000000  00000000  00000cb0  2**0
                  CONTENTS, READONLY, DEBUGGING
  8 .debug_frame  00000024  00000000  00000000  00000ef4  2**2
                  CONTENTS, READONLY, DEBUGGING
  9 .debug_str    0000035e  00000000  00000000  00000f18  2**0
                  CONTENTS, READONLY, DEBUGGING
 10 .debug_loc    00000048  00000000  00000000  00001276  2**0
                  CONTENTS, READONLY, DEBUGGING
 11 .debug_ranges 00000010  00000000  00000000  000012be  2**0
                  CONTENTS, READONLY, DEBUGGING

Disassembly of section .text:

00000000 <__vectors>:
   0:   12 c0           rjmp    .+36        ; 0x26 <__ctors_end>
   2:   19 c0           rjmp    .+50        ; 0x36 <__bad_interrupt>
   4:   18 c0           rjmp    .+48        ; 0x36 <__bad_interrupt>
   6:   17 c0           rjmp    .+46        ; 0x36 <__bad_interrupt>
   8:   16 c0           rjmp    .+44        ; 0x36 <__bad_interrupt>
   a:   15 c0           rjmp    .+42        ; 0x36 <__bad_interrupt>
   c:   14 c0           rjmp    .+40        ; 0x36 <__bad_interrupt>
   e:   13 c0           rjmp    .+38        ; 0x36 <__bad_interrupt>
  10:   12 c0           rjmp    .+36        ; 0x36 <__bad_interrupt>
  12:   11 c0           rjmp    .+34        ; 0x36 <__bad_interrupt>
  14:   10 c0           rjmp    .+32        ; 0x36 <__bad_interrupt>
  16:   0f c0           rjmp    .+30        ; 0x36 <__bad_interrupt>
  18:   0e c0           rjmp    .+28        ; 0x36 <__bad_interrupt>
  1a:   0d c0           rjmp    .+26        ; 0x36 <__bad_interrupt>
  1c:   0c c0           rjmp    .+24        ; 0x36 <__bad_interrupt>
  1e:   0b c0           rjmp    .+22        ; 0x36 <__bad_interrupt>
  20:   0a c0           rjmp    .+20        ; 0x36 <__bad_interrupt>
  22:   09 c0           rjmp    .+18        ; 0x36 <__bad_interrupt>
  24:   08 c0           rjmp    .+16        ; 0x36 <__bad_interrupt>

00000026 <__ctors_end>:
  26:   11 24           eor r1, r1
  28:   1f be           out 0x3f, r1    ; 63
  2a:   cf e5           ldi r28, 0x5F   ; 95
  2c:   d1 e0           ldi r29, 0x01   ; 1
  2e:   de bf           out 0x3e, r29   ; 62
  30:   cd bf           out 0x3d, r28   ; 61
  32:   02 d0           rcall   .+4         ; 0x38 <main>
  34:   1a c0           rjmp    .+52        ; 0x6a <_exit>

00000036 <__bad_interrupt>:
  36:   e4 cf           rjmp    .-56        ; 0x0 <__vectors>

00000038 <main>:
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    DDRB |= (1 << PB3);
  38:   87 b3           in  r24, 0x17   ; 23
  3a:   88 60           ori r24, 0x08   ; 8
  3c:   87 bb           out 0x17, r24   ; 23
    while (1) 
    {
        PORTB |= (1 << PB3);
  3e:   88 b3           in  r24, 0x18   ; 24
  40:   88 60           ori r24, 0x08   ; 8
  42:   88 bb           out 0x18, r24   ; 24
    #else
        //round up by default
        __ticks_dc = (uint32_t)(ceil(fabs(__tmp)));
    #endif

    __builtin_avr_delay_cycles(__ticks_dc);
  44:   8f e4           ldi r24, 0x4F   ; 79
  46:   93 ec           ldi r25, 0xC3   ; 195
  48:   01 97           sbiw    r24, 0x01   ; 1
  4a:   f1 f7           brne    .-4         ; 0x48 <__SREG__+0x9>
  4c:   00 c0           rjmp    .+0         ; 0x4e <__SREG__+0xf>
  4e:   00 00           nop
        _delay_ms(200);
        PORTB &= ~(1 << PB3);
  50:   88 b3           in  r24, 0x18   ; 24
  52:   87 7f           andi    r24, 0xF7   ; 247
  54:   88 bb           out 0x18, r24   ; 24
  56:   9f e9           ldi r25, 0x9F   ; 159
  58:   26 e8           ldi r18, 0x86   ; 134
  5a:   81 e0           ldi r24, 0x01   ; 1
  5c:   91 50           subi    r25, 0x01   ; 1
  5e:   20 40           sbci    r18, 0x00   ; 0
  60:   80 40           sbci    r24, 0x00   ; 0
  62:   e1 f7           brne    .-8         ; 0x5c <__SREG__+0x1d>
  64:   00 c0           rjmp    .+0         ; 0x66 <__SREG__+0x27>
  66:   00 00           nop
  68:   ea cf           rjmp    .-44        ; 0x3e <__SP_H__>

0000006a <_exit>:
  6a:   f8 94           cli

0000006c <__stop_program>:
  6c:   ff cf           rjmp    .-2         ; 0x6c <__stop_program>
\$\endgroup\$
9
  • \$\begingroup\$ The clock arrangement is a little different between the ATtiny45 and ATtiny461, the compiler might be configuring them slightly differently. Or maybe the delay.h you have is meant for one type over the other. Be sure you are selecting the correct P/N when compiling, (or allow for auto selecting). Here is a wiki comparison chart of most of the ATtiny series: en.wikipedia.org/wiki/ATtiny_microcontroller_comparison_chart \$\endgroup\$
    – Nedd
    Commented Mar 26, 2023 at 20:07
  • 1
    \$\begingroup\$ It would be useful to show assembler code produced by the compiler in both cases too (not easy/possible to reliably reproduce without exact build environment). Can you confirm if your code continues normally after second (shortened) delay and it is not unintended MCU restart (because of wdg for example), it would look the same way for blink test. \$\endgroup\$
    – Martin
    Commented Mar 26, 2023 at 20:09
  • 2
    \$\begingroup\$ The RJMP -44 is just your end of the while loop jumping to start of while loop. And watchdogs, when enabled, are by definition meant to restart the MCU, if software does not do anything to periodically reset the watchdog. \$\endgroup\$
    – Justme
    Commented Mar 26, 2023 at 23:42
  • 2
    \$\begingroup\$ I agree with Justme - if smaller delays produce the same device-reset behavior, then the problem is almost certainly due to the watchdog. \$\endgroup\$
    – brhans
    Commented Mar 27, 2023 at 6:24
  • 2
    \$\begingroup\$ "linking to delay.h isn't working well" makes no sense. Linking joins together compilation units. #include header files don't create separate compilation units. \$\endgroup\$
    – Ben Voigt
    Commented Mar 27, 2023 at 21:15

3 Answers 3

0
\$\begingroup\$

That is a documented feature of avr-libc. Please read manual how to use built-in avr-libc functions.

Maximal possible delay is 262.14 milliseconds if you are running with 1 MHz clock.

As many say, it should switch to less accurate version if larger delays are called, but that is according to modern documentation, which may not apply if you run an older compiler suite. Also many compiler defines and optimization settings affect which version of delay functions are actually used.

In reality, the solution for your problem is easy and has been in use for more than 15 years; for example, write a function yourself that calls 1ms delay in a loop so you can have any amount of 1ms delays you want. The functions given in the library were originally meant for short delays of up to 255 loops of 3 machine cycles or 65535 loops of 4 machine cycles and these were just extension wrappers around them.

Since you say the MCU simply restarts, then you have the watchdog left on, and you need to disable it, or at least keep resetting the watchdog before it resets the MCU.

EDIT:

It's NOT a software or watchdog issue. It's a purely hardware issue caused simply by not using a resistor for the LED.

With the new info given that MCU resets only if a red LED is driven from a GPIO pin without a resistor is that depending on capabilities of the power supply and amount of bypass capacitace, turning on a red LED with less than 1.8V forward voltage will eventually discharge the supply voltage below the level of MCU working properly and the brown-out detector resetting the MCU because it does not have enough voltage to run.

\$\endgroup\$
8
  • \$\begingroup\$ The Microchip site (onlinedocs.microchip.com/pr/…) and other online documentation says otherwise: "The maximal possible delay is 262.14ms / F_CPU in MHz with the highest resolution. When the user request delay which exceed the maximum possible one, delay_ms() provides a decreased resolution functionality." (... is limit for maximum resolution only, not functional limit) Please provide the link to documentation stating 262 ms as hard functional limit. \$\endgroup\$
    – Martin
    Commented Mar 26, 2023 at 20:51
  • \$\begingroup\$ @Martin avr-libc includes some conditional defines to include that feature or not. Time to check if the defines are correct then. But no way of knowing which version of avr-libc is present in the system. Please note that you link to current documentation, and the compiler used is 5-6 years old now. \$\endgroup\$
    – Justme
    Commented Mar 26, 2023 at 21:13
  • 1
    \$\begingroup\$ @Ksack If you have delays of 200ms on and 200ms off, if should just work. If not, you may have watchdog enabled and it will reset the MCU after the WDT timeout period. \$\endgroup\$
    – Justme
    Commented Mar 26, 2023 at 22:18
  • 2
    \$\begingroup\$ The watchdog is not enabled by code. There are fuse bits for that. If you go and reprogram the fuse bits, be extremely careful or you will lock yourself out from the chip and it is a brick that you can't reprogram any more. \$\endgroup\$
    – Justme
    Commented Mar 26, 2023 at 22:37
  • 3
    \$\begingroup\$ When watchdog is enabled, it restarts MCU when there is no code to prevent watchdog from restarting MCU. The code has just delays blinking a LED, it can't fail in any way. If it does not work with smaller delays either, then it's not a problem with delays at all. The assembly code looks fine. Please double check your watchdog fuse, so then you know if that is it or not. Or reset the watchdog in your code always before a delay, so you know of that is it or not. \$\endgroup\$
    – Justme
    Commented Mar 26, 2023 at 23:35
-1
\$\begingroup\$

So I found out where I went wrong.
I was working on a small breadboard for a quick test and didn't use a resistor in serie with my (red) LED. (I did this before in other small tests without any problem, so I didn't think it was such a big deal)
Anyways it looks like the Attiny45 doesn't like this behaviour in combination with a timerfunction...
It looks like the Tiny461 is more robust then the Tiny45...

So curious me did some other tests and noticed some more strange behavior. This is a list of strange things:

  • Initial problem: When using an Attiny45 with a LED (1) without a resistor in serie and a Delay.h function makes the mcu to restart (watchdog or something else?)
  • When adding a resistor in serie, the delay-functionality works as it should be
  • When adding a second LED (2) without a resistor in serie, still the same delay on LED (1) and LED (2) constantly on, the delay also works as it should be
  • When adding the second LED but, while running, you remove the wire from the second LED to the ground (which should be constantly on) the mcu restarts again. When connecting the wire again, the delay works again.

I'm still eager to learn why this happened in the first place (and why the other stuff happens), so if anyone can give me a good explanation, be my guest!
Lessons Learned: use your resistors!

\$\endgroup\$
3
  • 3
    \$\begingroup\$ Nobody expected that your circuit had no LED for a resistor. That is important to know that there is something weird already going on which should not be. The second important thing is to know the LED colours, as different LEDs have different voltage needs - if I had to guess, a red one caused failures and a blue/white did not. The third important thing is to know how the weird system is powered - is it a power supply that can push amps or weak little battery that limits current by itself. And how much, if any, bypass capacitance is in the system. The point is, supply drops when LED turned on. \$\endgroup\$
    – Justme
    Commented Mar 27, 2023 at 19:58
  • 5
    \$\begingroup\$ Without a resistor, you're overloading the IO pin, probably causing the internal VCC voltage to drop due to bond resistance, causing a brownout. \$\endgroup\$ Commented Mar 27, 2023 at 19:58
  • \$\begingroup\$ @Justme, the fact that I didn't expect it as such a big deal is also the reason why I didn't mention it. As I said, for a small quick breadboard set-up i'd left the resistors a lot of times... Your guess about red LED and blue is correct, just tested it... For the complete information, the set-up was supplied by USB. As said before, lessons learned. Thanks for the total background info, I really thought I lost my mind :-) \$\endgroup\$
    – Ksack
    Commented Mar 28, 2023 at 7:51
-5
\$\begingroup\$

I personally never trust these "Manufacturer Libs....".

ARM CMSIS is another story, but problems like this can cost you plenty of time.

I can not give a detailed anweser to the question, but i can provide a solution.

#define Lib_BusyWait_10ms   (uint16_t) (..)
#define Lib_BusyWait_20ms   (uint16_t) (..)
#define Lib_BusyWait_50ms   (uint16_t) (..)
#define Lib_BusyWait_100ms  (uint16_t) (..)
#define Lib_BusyWait_200ms  (uint16_t) (..)
#define Lib_BusyWait_500ms  (uint16_t) (..)
//#define Lib_BusyWait_BuildWithSafeGuard

#pragma GCC push_options
#pragma GCC optimize("O2")
void Lib_BusyWait(uint16_t cycles){
    #ifdef Lib_BusyWait_BuildWithSafeGuard
    //Limit bounds
    cycles = ( cycles == 0xFFFFU ) ? 0xFFFEU : ( ( cycles == 0 ) ? 1U : cycles );
    #endif
    
    //Do busy wait
    #pragma GCC unroll 4
    for( volatile uint16_t cnt = 0U; cnt < cycles; cnt++ ){
        asm volatile("nop");
        asm volatile("nop");
        asm volatile("nop");
        asm volatile("nop");
        asm volatile("nop");
    }
}
#pragma GCC pop_options
\$\endgroup\$
4
  • 1
    \$\begingroup\$ It's not a manufacturer lib. It's standard avr-libc with avr-gcc. And this is a documented limitation of the built-in functions listed in the manual. \$\endgroup\$
    – Justme
    Commented Mar 26, 2023 at 20:47
  • \$\begingroup\$ @Justme Great point! But why - in your opinion - is this not be seen as a Manuf. Lib? Its a product/product-family/core specific library provided by/or designed in close cooperation with the manufacturer of these parts. \$\endgroup\$ Commented Mar 26, 2023 at 21:09
  • 2
    \$\begingroup\$ No it isn't. The Microchip Studio is just a fancy GUI for calling the open source avr-gcc compiler which uses the open-source avr-libc under the hood. These open soure projects existed well before Microchip even bought Atmel. \$\endgroup\$
    – Justme
    Commented Mar 26, 2023 at 21:18
  • \$\begingroup\$ @Justme Okay, i did not know this. Thank you! \$\endgroup\$ Commented Mar 26, 2023 at 21:22

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.