2
\$\begingroup\$

I'm faced with a weird problem. I'm developing a program for AVR.

  • Microcontroller ATtiny85.
  • Frequency 1 MHz.
  • Programmer pickit2.
  • Software for programming avrdude on Linux.
  • Compiler avr-g++.
  • Optimization -Os (I tested with -O0, it doesn't work too).

This program must enable output pin PB3. But for some reason, it doesn't.

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

#define NOINLINE_ENABLED 1

#if NOINLINE_ENABLED == 1
#define NOINLINE __attribute__ ((noinline))
#else
#define NOINLINE
#endif

// Error occurs only if SetLedState method has noinline attribute or function implemented in other .cpp file (also noinline).
// NOINLINE_ENABLED macros enables noinline for this method
void NOINLINE SetLedState(bool state) {
    if (state)
        PORTB |= (1 << PB3);
    else
        PORTB &= ~(1 << PB3);
}

class Test {
public:
    Test(unsigned int a) : a_(a) {}

    void Update() {
        // a_ for some reasons doesn't equal 5, but it's impossible.
        if (this->a_ == 5)
            SetLedState(true);
    }

private:
    // I don't know why, but error doesn't occur if I add first variable, which can never be used
    // unsigned int padding = 0;

    const unsigned int a_;
};

static Test *test;

ISR(TIMER1_OVF_vect) {
    // error occurs when method called from timer interrupt method
    test->Update();
}

[[noreturn]] int main() {
    test = new Test(5);

    DDRB = (1 << PB3);
    PORTB = 0;

    TIMSK = (1 << TOIE1);
    TCCR1 = (1 << CS10);

    sei();

    // no error occurs when method called from this place
    // test->Update();

    while (true);
}

I wrote some comments to explain why it doesn't work. I think the problem is in the optimizer. Output PB3 doesn't enable if (and):

  • Test::Update method calls from timer overflow interrupt.
  • a_ class member has offset 2 and more.
  • SetLedState is no inlined or implemented in other *.cpp file.

Why? Thanks in advance!

Edit: Also, I tried to change the implementation of new C++ operator. Earlier it called return alloca(size), but now I tried to call return malloc(size). And it worked. Also, this initialization works too:

static Test test_static;
test = &test_static;

Why it doesn't work with alloca? What is the difference with malloc and alloca AVR functions?

\$\endgroup\$
9
  • 1
    \$\begingroup\$ @user448: Can you post the code generated by the compiler? \$\endgroup\$
    – doynax
    Jan 1, 2021 at 14:55
  • 1
    \$\begingroup\$ @user448 alloca allocates memory on the stack, rather then the heap, in the stack frame of the caller, so it's automatically freed when the caller returns. There's little use in that for allocating objects of known size like in this case, you could make it a plain automatic variable. \$\endgroup\$ Jan 1, 2021 at 15:01
  • 3
    \$\begingroup\$ @user448: Another sanity is to verify that the allocation actually succeeds. Given the 512 bytes of RAM of the target chip I wouldn't be surprised if no heap space had been set up, in which case we are touching whatever happened to be at address zero. \$\endgroup\$
    – doynax
    Jan 1, 2021 at 15:11
  • 2
    \$\begingroup\$ The original question is a good one, but as an aside, wouldn't this kind of thing be better in C rather than C++? \$\endgroup\$
    – Pete W
    Jan 1, 2021 at 15:11
  • 2
    \$\begingroup\$ Thank you for reminding me why I write code for small machines in assembler and avoid second-guessing compilers. \$\endgroup\$
    – glen_geek
    Jan 1, 2021 at 15:40

1 Answer 1

2
\$\begingroup\$

The reason(s) why this doesn't work for you could be

  • heap memory allocation doesn't work at all on your setup
  • the interrupt doesn't see the updated pointer because the pointer isn't volatile.

That said, I'd advise not to use the heap at all on such a small target. And why would you? You will know at build time which pins you want to trigger, so you can create the object(s) globally or local to main().

Also, why use an interrupt? If it is just to try it, OK, go ahead, but for beginners I'd stick to other solutions. Check the FSM (Finite State Machine) style.

Coding for such a small system in C++ is IMO the good way, but be aware that it is not like coding for larger systems. You will have to know very well what you are doing, and you will find lots of C++ examples and advice that might be pefect for largere systems, but not for an AVR8.

\$\endgroup\$
4
  • \$\begingroup\$ Agree massively re: no heap. The big 4 rules for small real time firmware, IMHO -- no malloc, no recursion, no unbounded loops, no exceptions \$\endgroup\$
    – Pete W
    Jan 1, 2021 at 17:51
  • 1
    \$\begingroup\$ In my freferred C++ style I add: no indirections (no vurtuals, no function pointers). That makes it possible to calculate the stack size - one design nightmare less. \$\endgroup\$ Jan 1, 2021 at 17:54
  • \$\begingroup\$ I would agree re: dynamic dispatch resulting from polymorphism -- I think that's what you mean? As a C rather than C++ programmer, I must allow one use of function pointers, for the dispatch table, if implementing a command interface. The table is static and gets generated at compile-time with some macro madness. It makes it convenient to combine different groups of commonly used "command sets". \$\endgroup\$
    – Pete W
    Jan 1, 2021 at 18:05
  • \$\begingroup\$ I am a C++ programmer, so I would use templates to make those functions sufficiently "known" at compile time to avoid indirection. But I must use some 'magic' to amke the tooling find the mains of all threads. \$\endgroup\$ Jan 1, 2021 at 18:08

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.